5AAIYEWRNV2H5226AFCA3SNM44P6ENVRDXGOYENPSCJV6L2N62AAC
import 'package:flutter_test/flutter_test.dart';
import 'package:dartmcts/fourinarow.dart';
import 'package:dartmcts/dartmcts.dart';
void main() {
test('new board is properly generated', () {
var board = emptyBoard();
expect(board.length, equals(6));
expect(board[0].length, equals(7));
});
test('checkTopRow returns all open plays', () {
var board = emptyBoard();
expect(checkTopRow(board), equals([0, 1, 2, 3, 4, 5, 6]));
board[0][3] = Player.FIRST;
expect(checkTopRow(board), equals([0, 1, 2, 4, 5, 6]));
});
test('findRowForColumn works as expected', () {
var o = Player.FIRST;
var x = Player.SECOND;
var _;
Board board = [
[o, o, _, _, _, x, x],
[x, o, _, _, _, o, o],
[o, x, o, _, _, x, x],
[x, o, x, _, _, o, o],
[o, x, o, _, _, x, x],
[x, o, x, _, x, o, o]
];
expect(findRowForColumn(board, 2), equals(1));
expect(() => findRowForColumn(board, 0), throwsException);
expect(findRowForColumn(board, 3), equals(5));
expect(findRowForColumn(board, 4), equals(4));
expect(() => findRowForColumn(board, 5), throwsException);
});
test('cloneAndApplyMove works as expected', () {
var o = Player.FIRST;
var x = Player.SECOND;
var _;
Board board = [
[_, _, _, _, _, _, _],
[_, _, _, _, _, _, _],
[_, _, _, _, _, _, _],
[_, _, _, _, _, _, _],
[_, _, _, _, _, _, _],
[_, _, _, _, o, o, o]
];
var game = ConnectFourGame(
board: board, bitboards: getBitBoards(board), scores: {});
var game2 = game.cloneAndApplyMove(6);
expect(game2.winner, isNull);
expect(
game2.board,
equals([
[_, _, _, _, _, _, _],
[_, _, _, _, _, _, _],
[_, _, _, _, _, _, _],
[_, _, _, _, _, _, _],
[_, _, _, _, _, _, o],
[_, _, _, _, o, o, o]
]));
game = ConnectFourGame(
board: board, bitboards: getBitBoards(board), scores: {});
game = game.cloneAndApplyMove(3);
expect(
game.board,
equals([
[_, _, _, _, _, _, _],
[_, _, _, _, _, _, _],
[_, _, _, _, _, _, _],
[_, _, _, _, _, _, _],
[_, _, _, _, _, _, _],
[_, _, _, o, o, o, o]
]));
expect(game.winner, equals(o));
board = [
[_, _, _, _, _, _, _],
[_, _, _, _, _, _, _],
[_, _, _, _, _, _, _],
[_, _, _, _, _, x, o],
[_, _, _, _, x, o, x],
[_, _, _, x, o, o, o]
];
game = ConnectFourGame(
board: board,
bitboards: getBitBoards(board),
currentPlayer: Player.SECOND,
scores: {});
game = game.cloneAndApplyMove(6);
expect(game.winner, equals(Player.SECOND));
});
test('plays out a game from start to finish', () {
int smartWins = 0;
for (var _ = 0; _ < 100; _++) {
ConnectFourGame game = ConnectFourGame.newGame();
while (game.getMoves().length > 0) {
MCTSResult<Move, Player> result;
int iterations;
if (game.currentPlayer == Player.FIRST) {
iterations = 5;
} else {
iterations = 10;
}
result =
MCTS(gameState: game).getSimulationResult(iterations: iterations);
game = game.cloneAndApplyMove(result.move!);
}
if (_ == 99) {
print(game.board);
print(game.winner);
}
if (game.winner == Player.SECOND) {
smartWins++;
}
}
expect(smartWins, greaterThan(70));
});
}
}
/* @Test
@Test
fun testWinningBoard() {
val e = null
val r = FourInARowPlayer.RED
val b = FourInARowPlayer.BLUE
val board: FourInARowBoard = arrayOf(
arrayOfNulls(7),
arrayOfNulls(7),
arrayOfNulls(7),
arrayOfNulls(7),
arrayOf(e, e, r, r, r, e, e),
arrayOf(e, b, b, b, b, e, e))
assertEquals(BigInteger("270549120"),
getBitBoard(
FourInARowPlayer.BLUE,
board
)
)
assertEquals(true,
winningBoard(
FourInARowPlayer.BLUE,
board
)
)
}
@Test
fun testOnlyDeterminedMovesAreFollowed() {
result = (MCTS(GameWithManyMovesOnlyOneDetermined)
.get_simulation_result(100))
self.assertEqual(result.root.children[0].move, 1)
self.assertEqual(result.root.children[0].visits, 100)
}
@Test
fun testFourInARowWithMCTS() {
val e = null
val r = FourInARowPlayer.RED
val b = FourInARowPlayer.BLUE
val oneMoveFromWinning = FourInARowGame(
board = arrayOf(
arrayOfNulls(7),
arrayOfNulls(7),
arrayOfNulls(7),
arrayOfNulls(7),
arrayOf(e, e, r, r, r, e, e),
arrayOf(e, e, b, b, b, e, e)
),
currentPlayer = FourInARowPlayer.BLUE
)
val result = MCTS(oneMoveFromWinning).getSimulationResult(maxSeconds = 10)
assertTrue(result.move in listOf(1, 5))
val board = arrayOf(
arrayOfNulls(7),
arrayOfNulls(7),
arrayOfNulls(7),
arrayOfNulls(7),
arrayOf(e, e, r, r, r, e, e),
arrayOf(e, e, b, b, b, b, e))
assertEquals(BigInteger("34630287360"),
getBitBoard(b, board)
)
assertTrue(
checkWin(
getBitBoard(
b,
board
)
)
)
}
import 'package:dartmcts/dartmcts.dart';
const List<List<int>> bitboardLookup = [
[5, 12, 19, 26, 33, 40, 47],
[4, 11, 18, 25, 32, 39, 46],
[3, 10, 17, 24, 31, 38, 45],
[2, 9, 16, 23, 30, 37, 44],
[1, 8, 15, 22, 29, 36, 43],
[0, 7, 14, 21, 28, 35, 42],
];
typedef Move = int;
typedef Board = List<List<Player?>>;
enum Player {
FIRST,
SECOND,
}
Board emptyBoard() {
return [
for (var i = 0; i < 6; i++) [for (var _ = 0; _ < 7; _++) null]
];
}
bool checkWin(int bitboard) {
var height = 6;
var h1 = height + 1;
var h2 = height + 2;
var diag1 = bitboard & (bitboard >> height);
var hori = bitboard & (bitboard >> h1);
var diag2 = bitboard & (bitboard >> h2);
var vert = bitboard & (bitboard >> 1);
return ((diag1 & (diag1 >> 2 * height)) |
(hori & (hori >> 2 * h1)) |
(diag2 & (diag2 >> 2 * h2)) |
(vert & (vert >> 2))) >
0;
}
List<int> checkTopRow(Board board) {
return [
for (var c = 0; c < 7; c++)
if (board[0][c] == null) c
];
}
Map<Player, int> getBitBoards(Board board) {
Map<Player, int> bitboards = {Player.FIRST: 0, Player.SECOND: 0};
for (var player in bitboards.keys) {
for (var row = 5; row >= 0; row--) {
for (var col = 0; col < 7; col++) {
if (board[row][col] == player) {
bitboards[player] =
bitboards[player]! ^ (1 << bitboardLookup[row][col]);
}
}
}
}
return bitboards;
}
int findRowForColumn(Board board, int column) {
for (var row = 5; row >= 0; row--) {
if (board[row][column] == null) {
return row;
}
}
throw Exception('No empty spot in that column');
}
class ConnectFourGame implements GameState<Move, Player> {
Player? currentPlayer;
Map<Player, int> bitboards;
Board board;
Player? winner;
Map<Player, int> scores;
ConnectFourGame(
{this.winner,
required this.board,
required this.bitboards,
required this.scores,
this.currentPlayer = Player.FIRST});
static ConnectFourGame newGame() {
return ConnectFourGame(
board: emptyBoard(),
bitboards: {Player.FIRST: 0, Player.SECOND: 0},
scores: {Player.FIRST: 0, Player.SECOND: 0});
}
@override
ConnectFourGame cloneAndApplyMove(Move column) {
var newBitboards = Map<Player, int>.from(bitboards);
Board newBoard =
Board.from([for (var row in board) List<Player?>.from(row)]);
Player? newWinner;
Player newPlayer;
Map<Player, int> newScores = {
Player.FIRST: 0,
Player.SECOND: 0,
};
int row = findRowForColumn(board, column);
newBoard[row][column] = currentPlayer;
newBitboards[currentPlayer!] =
newBitboards[currentPlayer]! ^ 1 << bitboardLookup[row][column];
newBitboards.forEach((player, bitboard) {
if (checkWin(bitboard)) {
newWinner = player;
newScores[player] = 1;
}
});
if (currentPlayer == Player.FIRST) {
newPlayer = Player.SECOND;
} else {
newPlayer = Player.FIRST;
}
return ConnectFourGame(
board: newBoard,
bitboards: newBitboards,
winner: newWinner,
scores: newScores,
currentPlayer: newPlayer);
}
@override
List<Move> getMoves() {
if (winner != null) {
return [];
}
return checkTopRow(board);
}
@override
GameState<Move, Player> determine(GameState<Move, Player>? initialState) {
return this;
}
}