Repository navigation
Expand file tree
/
Copy pathComputer.cs
More file actions
146 lines (129 loc) · 7.21 KB
/
Copy pathComputer.cs
File metadata and controls
146 lines (129 loc) · 7.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
using System;
using System.Collections.Generic;
using System.Linq;
using static Hexapawn.Pawn;
using static Hexapawn.Cell;
namespace Hexapawn
{
/// <summary>
/// Encapsulates the logic of the computer player.
/// </summary>
internal class Computer : IPlayer
{
private readonly Random _random = new();
private readonly Dictionary<Board, List<Move>> _potentialMoves;
private (List<Move>, Move) _lastMove;
public Computer()
{
// 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))
};
}
public int Wins { get; private set; }
public void AddWin() => Wins++;
// 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; }
Console.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<Move> 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<Move> moves, out Move move)
{
if (moves.Any())
{
move = moves[_random.Next(moves.Count)];
return true;
}
Console.Write("I resign.");
move = null;
return false;
}
private void ExcludeLastMoveFromFuturePlay()
{
var (moves, move) = _lastMove;
moves.Remove(move);
}
private static List<Move> Moves(params Move[] moves) => moves.ToList();
public bool IsFullyAdvanced(Board board) =>
board[9] == Black || board[8] == Black || board[7] == Black;
}
}