-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPlayer.cs
387 lines (339 loc) · 13.5 KB
/
Player.cs
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
using System;
using System.Data.SqlTypes;
using System.Drawing;
namespace Advance
{
/// <summary>
/// Represents a player in the game, with their own unique colour and army.
/// </summary>
public class Player
{
/// <summary>
/// Gets the colour of the player's pieces.
/// </summary>
public Colour Colour { get; }
/// <summary>
/// Gets the player's army.
/// </summary>
public Army Army { get; }
/// <summary>
/// Gets the game in which the player is participating.
/// </summary>
public Game Game { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Player"/> class.
/// </summary>
/// <param name="colour">The colour of the player's pieces.</param>
/// <param name="game">The game in which the player is participating.</param>
public Player(Colour colour, Game game)
{
Colour = colour;
Game = game;
Army = new Army(this, Game.Board);
}
/// <summary>
/// Gets the opponent of the player.
/// </summary>
public Player Opponent
{
get
{
if (Game.White.Colour == this.Colour)
return Game.Black;
else
return Game.White;
}
}
/// <summary>
/// Gets the direction of the player's movement, determined by the player's colour.
/// </summary>
public int Direction
{
get
{
return Colour == Colour.Black ? +1 : -1;
}
}
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
public override string ToString()
{
return $"Player {Colour}";
}
/// <summary>
/// Chooses a move for the player. If there are possible actions, it picks a random one. If there are no possible actions, it returns null.
/// </summary>
/// <param name="actions">A list of possible actions.</param>
/// <returns>An Action object representing the chosen move, or null if no moves are possible.</returns>
public Action ChooseMove(List<Action>? actions = null)
{
if (actions == null) actions = new List<Action>();
FindPossibleActions(actions);
// Return a random action
if (actions.Count() == 0)
return null;
else
return actions[rand.Next(actions.Count())];
}
static Random rand = new Random();
/// <summary>
/// Finds the possible actions that a player can take on their turn.
/// </summary>
/// <param name="actions">A list of Action objects representing the possible actions.</param>
public void FindPossibleActions(List<Action> actions)
{
if (IsGeneralSafe())
{
if (actions.Count >= 1) return;
FindPossibleMoves(actions);
FindPossibleAttacks(actions);
}
else
{
FindAttacksToSaveGeneral(actions);
FindMovesToSaveGeneral(actions);
}
}
/// <summary>
/// Finds actions that lead to a win. Loop through all action that opponent can counter, if after all the move
/// their General is still threatened. It results in a checkmate.
/// </summary>
/// <param name="actions">A list of Action objects representing the possible actions.</param>
public void FindWinningActions(ref List<Action> actions)
{
List<Action> winningActions = new List<Action>();
if (actions.Count == 0) return;
foreach (var move in actions)
{
move.DoAction();
// Check if this player checkmates the opponent
if (IsCheckMateOpponent())
{
winningActions.Add(move);
}
// Move the piece back to its original square
move.UndoAction();
}
if (winningActions.Count > 0)
{
actions = winningActions;
}
}
/// <summary>
/// Finds all possible moves for the player's pieces.
/// </summary>
/// <param name="actions">A list of Action objects representing the possible actions.</param>
public void FindPossibleMoves(List<Action> actions)
{
foreach (var piece in Army.Pieces)
{
if (!piece.OnBoard) continue;
foreach (var square in Game.Board.Squares)
{
if (square.IsFree && piece.CanMoveTo(square))
{
actions.Add(new Move(piece, square));
}
// Jester's swap move
else if (square.IsOccupied && piece.CanMoveTo(square) && piece is Jester)
{
actions.Add(new Move(piece, square));
}
}
}
}
/// <summary>
/// Finds all possible attacks for the player's pieces.
/// </summary>
/// <param name="actions">A list of Action objects representing the possible actions.</param>
public void FindPossibleAttacks(List<Action> actions)
{
foreach (var piece in Army.Pieces)
{
if (!piece.OnBoard) continue;
foreach (var square in Game.Board.Squares)
{
// For regular piece
if (square.IsOccupied
&& !IsProtectedByEnemySentinel(piece, square)
&& piece.RequiresEnemyToAttack
&& square.Occupant.Player != this
&& square.Occupant.Player != null
&& piece.CanAttack(square)
)
{
actions.Add(new Attack(piece, square));
}
// If piece is a Builder
else if (!piece.RequiresEnemyToAttack && piece.CanAttack(square))
{
// Builder Attacking regular piece
if (square.IsOccupied
&& !IsProtectedByEnemySentinel(piece, square)
&& !square.ContainsWall
&& square.Occupant.Player != this)
{
actions.Add(new Attack(piece, square));
}
// Builder building a wall
else if (square.IsFree)
{
actions.Add(new BuildWall(piece, square));
}
}
// Miner destroying wall
else if (square.IsOccupied
&& piece is Miner
&& square.ContainsWall
&& piece.CanAttack(square))
{
actions.Add(new DestroyWall(piece, square));
}
}
}
}
// <summary>
/// Finds moves that can save the player's General.
/// </summary>
/// <param name="actions">A list of Action objects representing the possible actions.</param>
public void FindMovesToSaveGeneral(List<Action> actions)
{
List<Action> possibleMoves = new List<Action>();
FindPossibleMoves(possibleMoves);
// No possible attacks could save the general
if (possibleMoves.Count == 0) return;
foreach (var move in possibleMoves)
{
move.DoAction();
// Check if the General would be safe after the move
if (IsGeneralSafe())
{
// If the move makes the General safe, add it to the possible actions
actions.Add(move);
}
// Move the piece back to its original square
move.UndoAction();
}
}
/// <summary>
/// Finds attacks that can save the player's General.
/// </summary>
/// <param name="actions">A list of Action objects representing the possible actions.</param>
public void FindAttacksToSaveGeneral(List<Action> actions)
{
List<Action> possibleAttacks = new List<Action>();
FindPossibleAttacks(possibleAttacks);
// No possible attacks could save the general
if (possibleAttacks.Count == 0) return;
foreach (var attack in possibleAttacks)
{
attack.DoAction();
// Check if the General would be safe after the move
if (IsGeneralSafe())
{
// If the move makes the General safe, add it to the possible actions
actions.Add(attack);
}
// Move the piece back to its original square
attack.UndoAction();
}
}
/// <summary>
/// Determines whether a square is protected by an enemy Sentinel.
/// </summary>
/// <param name="actor">The Piece attempting to move or attack.</param>
/// <param name="targetSquare">The Square being targeted.</param>
/// <returns>True if the square is protected by an enemy Sentinel, otherwise false.</returns>
public bool IsProtectedByEnemySentinel(Piece actor, Square targetSquare)
{
if (targetSquare.Occupant is Sentinel || (actor is Jester)) return false;
foreach (var piece in Opponent.Army.Pieces)
{
if (piece is Sentinel && piece.OnBoard)
{
var sentinel = piece as Sentinel;
if (sentinel.IsProtected(targetSquare))
{
return true;
}
}
}
return false;
}
/// <summary>
/// Checks if the player's General is safe.
/// </summary>
/// <returns>True if the General is safe, otherwise false.</returns>
public bool IsGeneralSafe()
{
General general = Army.Pieces.OfType<General>().FirstOrDefault();
if (general == null) throw new Exception($"{this} does not have general in their army");
var sentinels = Army.Pieces.OfType<Sentinel>().Where(piece => piece.OnBoard).ToList();
bool isProtected = sentinels.Any(sentinel => sentinel.IsProtected(general.Square));
foreach (var piece in Opponent.Army.Pieces)
{
// If an opponent's piece can attack the General and the General is not protected, it's not safe
if (piece.Square != null && piece.CanAttack(general.Square) && !isProtected)
{
return false;
}
}
return true;
}
/// <summary>
/// Determines if the opponent is in a checkmate situation.
/// </summary>
/// <returns>True if the opponent is in checkmate, otherwise false.</returns>
public bool IsCheckMateOpponent()
{
bool success = true;
if (!Opponent.IsGeneralSafe())
{
List<Action> rescueActions = new List<Action>();
Opponent.FindPossibleActions(rescueActions);
if (rescueActions.Count > 0)
{
success = false;
}
}
return success;
}
/// <summary>
/// Evaluates the game state from the player's perspective.
/// </summary>
/// <returns>An integer representing the game state. Higher values are better for the player.</returns>
public int EvaluateGame()
{
Dictionary<string, int> pieceValues = new Dictionary<string, int>
{
{ "Zombie", 1 },
{ "Builder", 2 },
{ "Jester", 3 },
{ "Miner", 4 },
{ "Sentinel", 5 },
{ "Catapult", 6 },
{ "Dragon", 7 },
{ "General", 1000 }
};
int material = 0;
foreach (var piece in Army.Pieces)
{
if (!piece.OnBoard) continue;
string pieceType = piece.GetType().Name;
if (pieceValues.ContainsKey(pieceType))
{
material += pieceValues[pieceType];
}
}
// If the player for which we're evaluating the game state is the black player, we return
// the negative of the material.
if (Colour == Colour.Black)
{
return -material;
}
return material;
}
}
}