Skip to content

Feature/matchmaking integration #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 9 additions & 25 deletions Substrate.Hexalem.Integration.Test/HexalemTests.cs
Original file line number Diff line number Diff line change
@@ -1,37 +1,14 @@
using Substrate.Integration;
using Substrate.Integration.Client;
using Substrate.NET.Schnorrkel.Keys;
using Substrate.NET.Wallet.Keyring;
using Substrate.NetApi;
using Substrate.NetApi.Model.Types;

namespace Substrate.Hexalem.Integration.Test
{
public class HexalemTest
public class HexalemTest : IntegrationCommonTests
{
public MiniSecret MiniSecretAlice => new MiniSecret(Utils.HexToByteArray("0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a"), ExpandMode.Ed25519);
public Account Alice => Account.Build(KeyType.Sr25519, MiniSecretAlice.ExpandToSecret().ToEd25519Bytes(), MiniSecretAlice.GetPair().Public.Key);

public MiniSecret MiniSecretBob => new MiniSecret(Utils.HexToByteArray("0x398f0c28f98885e046333d4a41c19cee4c37368a9832c6502f6cfd182e2aef89"), ExpandMode.Ed25519);
public Account Bob => Account.Build(KeyType.Sr25519, MiniSecretBob.ExpandToSecret().ToEd25519Bytes(), MiniSecretBob.GetPair().Public.Key);

private readonly string _nodeUrl = "ws://127.0.0.1:9944";

private SubstrateNetwork _client;

[SetUp]
public void Setup()
{
// create client
_client = new SubstrateNetwork(Alice, Substrate.Integration.Helper.NetworkType.Live, _nodeUrl);
}

[TearDown]
public void TearDown()
{
// dispose client
_client.SubstrateClient.Dispose();
}

[Test]
public async Task CreateGameTestAsync()
{
Expand Down Expand Up @@ -65,5 +42,12 @@ public async Task CreateGameTestAsync()
Assert.That(await _client.DisconnectAsync(), Is.True);
Assert.That(_client.IsConnected, Is.False);
}

[Test]
public async Task GetEloRatingTestAsync()
{
var elo = await _client.GetRatingStorageAsync(Alice.ToString(), CancellationToken.None);
Assert.That(elo, Is.GreaterThan(0));
}
}
}
66 changes: 66 additions & 0 deletions Substrate.Hexalem.Integration.Test/IntegrationCommonTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using Substrate.Integration;
using Substrate.NET.Wallet.Keyring;
using Substrate.NetApi.Model.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Substrate.Hexalem.Integration.Test
{
public abstract class IntegrationCommonTests
{
protected readonly string _nodeUrl = "ws://127.0.0.1:9944";
protected SubstrateNetwork _client;

protected Keyring _keyring = new Keyring();

private Account? _alice;
public Account Alice
{
get
{
if (_alice is null)
{
_alice = _keyring.AddFromUri("//Alice", new Meta() { Name = "Alice" }, KeyType.Sr25519).Account;
}

return _alice;
}
}

private Account? _bob;
public Account Bob
{
get
{
if (_bob is null)
_bob = _keyring.AddFromUri("//Bob", new Meta() { Name = "Bob" }, KeyType.Sr25519).Account;

return _bob;
}
}

[SetUp]
public async Task Setup()
{
// create client
_client = new SubstrateNetwork(Alice, Substrate.Integration.Helper.NetworkType.Live, _nodeUrl);

Assert.That(_client, Is.Not.Null);
Assert.That(_client.IsConnected, Is.False);

Assert.That(await _client.ConnectAsync(true, true, CancellationToken.None), Is.True);
Assert.That(_client.IsConnected, Is.True);
}

[TearDown]
public void TearDown()
{
// dispose client
_client.SubstrateClient.Dispose();
}

}
}
221 changes: 221 additions & 0 deletions Substrate.Hexalem.Integration.Test/MatchmakingTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
using Substrate.Hexalem.NET.NetApiExt.Generated.Model.hexalem_runtime;
using Substrate.Hexalem.NET.NetApiExt.Generated.Model.pallet_hexalem.types;
using Substrate.Hexalem.NET.NetApiExt.Generated.Model.pallet_hexalem.types.game;
using Substrate.Hexalem.NET.NetApiExt.Generated.Model.sp_runtime.multiaddress;
using Substrate.Integration;
using Substrate.Integration.Call;
using Substrate.Integration.Helper;
using Substrate.NET.Wallet.Extensions;
using Substrate.NET.Wallet.Keyring;
using Substrate.NetApi.Model.Types;
using Substrate.NetApi.Model.Types.Base;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;

namespace Substrate.Hexalem.Integration.Test
{
public class MatchmakingTests : IntegrationCommonTests
{
private readonly string _nodeUrl = "ws://127.0.0.1:9944";

private SubstrateNetwork _client;

[SetUp]
public async Task Setup()
{
// create client
_client = new SubstrateNetwork(Alice, Substrate.Integration.Helper.NetworkType.Live, _nodeUrl);

Assert.That(_client, Is.Not.Null);
Assert.That(_client.IsConnected, Is.False);

Assert.That(await _client.ConnectAsync(true, true, CancellationToken.None), Is.True);
Assert.That(_client.IsConnected, Is.True);
}

private async Task ShouldBeInQueueAsync(Account account)
{
var isInQueue = await _client.IsPlayerInMatchmakingAsync(account, CancellationToken.None);
var bobMatchmakingState = await _client.GetMatchmakingStateAsync(account, null, CancellationToken.None);

Assert.That(isInQueue, Is.True);

Assert.That(bobMatchmakingState, Is.Not.Null);
Assert.That(bobMatchmakingState.MatchmakingState, Is.EqualTo(MatchmakingState.Matchmaking));
}

private async Task ShouldNotBeInQueueAsync(Account account)
{
var isInQueue = await _client.IsPlayerInMatchmakingAsync(account, CancellationToken.None);
var bobMatchmakingState = await _client.GetMatchmakingStateAsync(account, null, CancellationToken.None);

Assert.That(isInQueue, Is.False);

Assert.That(bobMatchmakingState, Is.Not.Null);
Assert.That(bobMatchmakingState.MatchmakingState, Is.EqualTo(MatchmakingState.None));
}

private async Task PlayersAcceptMathAsync(int concurrentTasksAllowed, List<Account> players)
{
foreach (var player in players)
{
await _client.AcceptAsync(player, concurrentTasksAllowed, CancellationToken.None);
Thread.Sleep(500);
}
}

private async Task<List<byte[]>> EnsureGamesAreCreatedButNobodyJoinedAsync(List<Account> players)
{
List<byte[]> gamesId = new List<byte[]>();
// Matchmaking is not done, game has been create but players did not accept yet
foreach (var player in players)
{
var playerMatchmakingState = await _client.GetMatchmakingStateAsync(player, null, CancellationToken.None);
Assert.That(playerMatchmakingState.MatchmakingState, Is.EqualTo(MatchmakingState.Joined));

Assert.That(playerMatchmakingState.GameId, Is.Not.Null);
if (!gamesId.Contains(playerMatchmakingState.GameId))
gamesId.Add(playerMatchmakingState.GameId);

var game = await _client.GetGameAsync(playerMatchmakingState.GameId, null, CancellationToken.None);

Assert.That(game, Is.Not.Null);
Assert.That(game.PlayerAccepted, Is.Not.Null);
Assert.Multiple(() =>
{
Assert.That(game.PlayerAccepted.All(x => x), Is.False);
Assert.That(game.State, Is.EqualTo(GameState.Accepting));
});
}

return gamesId;
}

[Test]
public async Task PlayerJoinQueueTestAsync()
{
await ShouldNotBeInQueueAsync(Bob);

_ = await _client.QueueAsync(Bob, 1, CancellationToken.None);

Thread.Sleep(6_000);

await ShouldBeInQueueAsync(Bob);
}

[Test]
public async Task Matchmaking_WhenEnoughPlayerHaveJoin_ShouldMatchAndCreateGameAsync()
{
int concurrentTasksAllowed = 20;
var players = new List<Account>();
var balanceTransfer = new List<EnumRuntimeCall>();

for (int i = 0; i < 10; i++)
{
var player = _keyring.AddFromUri($"MatchmakingTestEnoughPlayer_{i}", new Meta() { Name = $"TestPlayer{i}" }, KeyType.Sr25519);
Assert.That(player, Is.Not.Null);

balanceTransfer.Add(PalletBalances.BalancesTransferKeepAlive(
player.Account.ToAccountId32(),
new BigInteger(1000 * SubstrateNetwork.DECIMALS)));

players.Add(player.Account);
}

_ = await _client.BatchAllAsync(balanceTransfer, concurrentTasksAllowed, CancellationToken.None);

Thread.Sleep(15_000);

// Now each player queue
foreach (var player in players.Take(9))
{
await ShouldNotBeInQueueAsync(player);

_ = await _client.QueueAsync(player, concurrentTasksAllowed, CancellationToken.None);
Thread.Sleep(1_500); // To simulate asynchronous queing
}

Thread.Sleep(15_000);

foreach (var player in players.Take(9))
{
var playerMatchmakingState = await _client.GetMatchmakingStateAsync(player, null, CancellationToken.None);

Assert.That(playerMatchmakingState, Is.Not.Null);
Assert.That(playerMatchmakingState.MatchmakingState, Is.EqualTo(MatchmakingState.Matchmaking));
Assert.That(playerMatchmakingState.GameId, Is.Null);
}

// Now the last one queue
_ = await _client.QueueAsync(players.Last(), concurrentTasksAllowed, CancellationToken.None);

Thread.Sleep(15_000);

List<byte[]> gamesId = await EnsureGamesAreCreatedButNobodyJoinedAsync(players);
await PlayersAcceptMathAsync(concurrentTasksAllowed, players);

Thread.Sleep(15_000);

foreach (var gameId in gamesId)
{
var game = await _client.GetGameAsync(gameId, null, CancellationToken.None);

Assert.That(game, Is.Not.Null);
Assert.That(game.State, Is.EqualTo(GameState.Playing));
}
}

[Test]
public async Task Matchmaking_WhenNotEnoughPlayerHaveJoin_ButExceedTenBlocks_ShouldMatchAndCreateGameAsync()
{
int concurrentTasksAllowed = 20;
var players = new List<Account>();
for (int i = 0; i < 4; i++)
{
var player = _keyring.AddFromUri($"MatchmakingTestNotEnoughPlayer_{i}", new Meta() { Name = $"TestPlayer{i}" }, KeyType.Sr25519);
Assert.That(player, Is.Not.Null);

// Alice send them some token
_ = await _client.TransferKeepAliveAsync(
Alice,
player.Account.ToAccountId32(),
new BigInteger(1000 * SubstrateNetwork.DECIMALS),
concurrentTasksAllowed, CancellationToken.None);

players.Add(player.Account);
}

Thread.Sleep(15_000);

// Now each player queue
foreach (var player in players)
{
await ShouldNotBeInQueueAsync(player);

_ = await _client.QueueAsync(player, concurrentTasksAllowed, CancellationToken.None);
Thread.Sleep(1_500); // To simulate asynchronous queing
}

// Now wait > 10 blocks to trigger the matchmaking even if there is not enough player
Thread.Sleep(11 * 6_000);

List<byte[]> gamesId = await EnsureGamesAreCreatedButNobodyJoinedAsync(players);
await PlayersAcceptMathAsync(concurrentTasksAllowed, players);

Thread.Sleep(15_000);

foreach (var gameId in gamesId)
{
var game = await _client.GetGameAsync(gameId, null, CancellationToken.None);

Assert.That(game, Is.Not.Null);
Assert.That(game.State, Is.EqualTo(GameState.Playing));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" />
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
<PackageReference Include="coverlet.collector" Version="3.2.0" />
<PackageReference Include="Substrate.NET.Wallet" Version="1.0.8" />
</ItemGroup>

<ItemGroup>
Expand Down
7 changes: 7 additions & 0 deletions Substrate.Hexalem.Integration/Model/BoardSharp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,15 @@ public BoardSharp(HexBoard result)
Resources = result.Resources.Value.Select(x => (byte)x).ToArray();

HexGrid = result.HexGrid.Value.Value.Select(x => new TileSharp(x)).ToArray();

GameId = result.GameId.Value.Select(p => p.Value).ToArray();
}

/// <summary>
/// The game identifier
/// </summary>
public byte[] GameId { get; private set; }

/// <summary>
/// Resources
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions Substrate.Hexalem.Integration/Model/MatchmakingStateSharp.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Substrate.Hexalem.NET.NetApiExt.Generated.Model.pallet_hexalem.types;
using Substrate.Hexalem.NET.NetApiExt.Generated.Types.Base;
using Substrate.NetApi.Model.Types;
using System.Linq;

namespace Substrate.Hexalem.Integration.Model
Expand Down Expand Up @@ -32,5 +33,10 @@ public MatchmakingStateSharp(EnumMatchmakingState matchmakingState)
/// Game Id
/// </summary>
public byte[]? GameId { get; private set; }

/// <summary>
/// Return true if the game is created
/// </summary>
public bool IsGameFound => GameId != null;
}
}
Loading