using System;
using System.Collections.Generic;
using System.Linq;
using Games.Common.IO;
using Games.Common.Randomness;
using static Hexapawn.Pawn;
namespace Hexapawn;
///
/// Encapsulates the logic of the computer player.
///
internal class Computer
{
private readonly TextIO _io;
private readonly IRandom _random;
private readonly Dictionary> _potentialMoves;
private (List, Move) _lastMove;
public Computer(TextIO io, IRandom random)
{
_io = io;
_random = random;
// This dictionary implements the data in the original code, which encodes board positions for which the
// computer has a legal move, and the list of possible moves for each position:
// 900 DATA -1,-1,-1,1,0,0,0,1,1,-1,-1,-1,0,1,0,1,0,1
// 905 DATA -1,0,-1,-1,1,0,0,0,1,0,-1,-1,1,-1,0,0,0,1
// 910 DATA -1,0,-1,1,1,0,0,1,0,-1,-1,0,1,0,1,0,0,1
// 915 DATA 0,-1,-1,0,-1,1,1,0,0,0,-1,-1,-1,1,1,1,0,0
// 920 DATA -1,0,-1,-1,0,1,0,1,0,0,-1,-1,0,1,0,0,0,1
// 925 DATA 0,-1,-1,0,1,0,1,0,0,-1,0,-1,1,0,0,0,0,1
// 930 DATA 0,0,-1,-1,-1,1,0,0,0,-1,0,0,1,1,1,0,0,0
// 935 DATA 0,-1,0,-1,1,1,0,0,0,-1,0,0,-1,-1,1,0,0,0
// 940 DATA 0,0,-1,-1,1,0,0,0,0,0,-1,0,1,-1,0,0,0,0
// 945 DATA -1,0,0,-1,1,0,0,0,0
// 950 DATA 24,25,36,0,14,15,36,0,15,35,36,47,36,58,59,0
// 955 DATA 15,35,36,0,24,25,26,0,26,57,58,0
// 960 DATA 26,35,0,0,47,48,0,0,35,36,0,0,35,36,0,0
// 965 DATA 36,0,0,0,47,58,0,0,15,0,0,0
// 970 DATA 26,47,0,0,47,58,0,0,35,36,47,0,28,58,0,0,15,47,0,0
//
// The original code loaded this data into two arrays.
// 40 FOR I=1 TO 19: FOR J=1 TO 9: READ B(I,J): NEXT J: NEXT I
// 45 FOR I=1 TO 19: FOR J=1 TO 4: READ M(I,J): NEXT J: NEXT I
//
// When finding moves for the computer the first array was searched for the current board position, or the
// reflection of it, and the resulting index was used in the second array to get the possible moves.
// With this dictionary we can just use the current board as the index, and retrieve a list of moves for
// consideration by the computer.
_potentialMoves = new()
{
[new(Black, Black, Black, White, None, None, None, White, White)] = Moves((2, 4), (2, 5), (3, 6)),
[new(Black, Black, Black, None, White, None, White, None, White)] = Moves((1, 4), (1, 5), (3, 6)),
[new(Black, None, Black, Black, White, None, None, None, White)] = Moves((1, 5), (3, 5), (3, 6), (4, 7)),
[new(None, Black, Black, White, Black, None, None, None, White)] = Moves((3, 6), (5, 8), (5, 9)),
[new(Black, None, Black, White, White, None, None, White, None)] = Moves((1, 5), (3, 5), (3, 6)),
[new(Black, Black, None, White, None, White, None, None, White)] = Moves((2, 4), (2, 5), (2, 6)),
[new(None, Black, Black, None, Black, White, White, None, None)] = Moves((2, 6), (5, 7), (5, 8)),
[new(None, Black, Black, Black, White, White, White, None, None)] = Moves((2, 6), (3, 5)),
[new(Black, None, Black, Black, None, White, None, White, None)] = Moves((4, 7), (4, 8)),
[new(None, Black, Black, None, White, None, None, None, White)] = Moves((3, 5), (3, 6)),
[new(None, Black, Black, None, White, None, White, None, None)] = Moves((3, 5), (3, 6)),
[new(Black, None, Black, White, None, None, None, None, White)] = Moves((3, 6)),
[new(None, None, Black, Black, Black, White, None, None, None)] = Moves((4, 7), (5, 8)),
[new(Black, None, None, White, White, White, None, None, None)] = Moves((1, 5)),
[new(None, Black, None, Black, White, White, None, None, None)] = Moves((2, 6), (4, 7)),
[new(Black, None, None, Black, Black, White, None, None, None)] = Moves((4, 7), (5, 8)),
[new(None, None, Black, Black, White, None, None, None, None)] = Moves((3, 5), (3, 6), (4, 7)),
[new(None, Black, None, White, Black, None, None, None, None)] = Moves((2, 8), (5, 8)),
[new(Black, None, None, Black, White, None, None, None, None)] = Moves((1, 5), (4, 7))
};
}
// Try to make a move. We first try to find a legal move for the current board position.
public bool TryMove(Board board)
{
if (TryGetMoves(board, out var moves, out var reflected) &&
TrySelectMove(moves, out var move))
{
// We've found a move, so we record it as the last move made, and then announce and make the move.
_lastMove = (moves, move);
// If we found the move from a reflacted match of the board we need to make the reflected move.
if (reflected) { move = move.Reflected; }
_io.WriteLine($"I move {move}");
move.Execute(board);
return true;
}
// We haven't found a move for this board position, so remove the previous move that led to this board
// position from future consideration. We don't want to make that move again, because we now know it's a
// non-winning move.
ExcludeLastMoveFromFuturePlay();
return false;
}
// Looks up the given board and its reflection in the potential moves dictionary. If it's found then we have a
// list of potential moves. If the board is not found in the dictionary then the computer has no legal moves,
// and the human player wins.
private bool TryGetMoves(Board board, out List moves, out bool reflected)
{
if (_potentialMoves.TryGetValue(board, out moves))
{
reflected = false;
return true;
}
if (_potentialMoves.TryGetValue(board.Reflected, out moves))
{
reflected = true;
return true;
}
reflected = default;
return false;
}
// Get a random move from the list. If the list is empty, then we've previously eliminated all the moves for
// this board position as being non-winning moves. We therefore resign the game.
private bool TrySelectMove(List moves, out Move move)
{
if (moves.Any())
{
move = moves[_random.Next(moves.Count)];
return true;
}
_io.WriteLine("I resign.");
move = null;
return false;
}
private void ExcludeLastMoveFromFuturePlay()
{
var (moves, move) = _lastMove;
moves.Remove(move);
}
private static List Moves(params Move[] moves) => moves.ToList();
public bool IsFullyAdvanced(Board board) =>
board[9] == Black || board[8] == Black || board[7] == Black;
}