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 Map<String, dynamic> toJson() { throw UnimplementedError(); } @override ConnectFourGame cloneAndApplyMove(Move column, Node<Move, Player>? root) { 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] = 10; } }); if (getMoves().length == 0 && winner == null) { newScores = { Player.FIRST: 5, Player.SECOND: 5, }; } 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; } }