-
Notifications
You must be signed in to change notification settings - Fork 319
/
Copy pathGameManager.cs
332 lines (285 loc) · 9.25 KB
/
GameManager.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
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace Chess.Game {
public class GameManager : MonoBehaviour {
public enum Result { Playing, WhiteIsMated, BlackIsMated, Stalemate, Repetition, FiftyMoveRule, InsufficientMaterial }
public event System.Action onPositionLoaded;
public event System.Action<Move> onMoveMade;
public enum PlayerType { Human, AI }
public bool loadCustomPosition;
public string customPosition = "1rbq1r1k/2pp2pp/p1n3p1/2b1p3/R3P3/1BP2N2/1P3PPP/1NBQ1RK1 w - - 0 1";
public PlayerType whitePlayerType;
public PlayerType blackPlayerType;
public AISettings aiSettings;
public Color[] colors;
public bool useClocks;
public Clock whiteClock;
public Clock blackClock;
public TMPro.TMP_Text aiDiagnosticsUI;
public TMPro.TMP_Text resultUI;
//Scene Control
private int playerChoice;
public AIDifficulty AIDifficulty;
//UI Control
public GameObject export;
public GameObject exit;
Result gameResult;
Player whitePlayer;
Player blackPlayer;
Player playerToMove;
List<Move> gameMoves;
BoardUI boardUI;
public ulong zobristDebug;
public Board board { get; private set; }
Board searchBoard; // Duplicate version of board used for ai search
void Start () {
/*if (useClocks) {
whiteClock.isTurnToMove = false;
blackClock.isTurnToMove = false;
}*/
//UI Control
export.SetActive(false);
exit.SetActive(false);
//Board Set Up
boardUI = FindObjectOfType<BoardUI> ();
gameMoves = new List<Move> ();
board = new Board ();
searchBoard = new Board ();
aiSettings.diagnostics = new Search.SearchDiagnostics ();
NewGame (whitePlayerType, blackPlayerType);
//Start Control
playerChoice = PlayerPrefs.GetInt("StartValue", 0);
switch(playerChoice){
case 1:
//Hotseat
HotseatGame();
break;
case 2:
//White Easy
AIDifficulty.difficulty = 1;
AIDifficulty.SetDifficulty();
NewGame(true);
break;
case 3:
//Black
AIDifficulty.difficulty = 1;
AIDifficulty.SetDifficulty();
NewGame(false);
break;
case 4:
//AI Spectate
AIDifficulty.difficulty = 3;
AIDifficulty.SetDifficulty();
NewComputerVersusComputerGame();
break;
case 5:
//White Medium
AIDifficulty.difficulty = 2;
AIDifficulty.SetDifficulty();
NewGame(true);
break;
case 6:
//White Hard
AIDifficulty.difficulty = 3;
AIDifficulty.SetDifficulty();
NewGame(true);
break;
case 7:
//Black Medium
AIDifficulty.difficulty = 2;
AIDifficulty.SetDifficulty();
NewGame(false);
break;
case 8:
//Black Hard
AIDifficulty.difficulty = 3;
AIDifficulty.SetDifficulty();
NewGame(false);
break;
default:
Debug.LogError("Accessed Impossible Choice");
break;
}
}
void Update () {
zobristDebug = board.ZobristKey;
if (gameResult == Result.Playing) {
LogAIDiagnostics ();
playerToMove.Update ();
if (useClocks) {
whiteClock.isTurnToMove = board.WhiteToMove;
blackClock.isTurnToMove = !board.WhiteToMove;
}
}
if (Input.GetKeyDown (KeyCode.E)) {
ExportGame ();
}
}
void OnMoveChosen (Move move) {
bool animateMove = playerToMove is AIPlayer;
board.MakeMove (move);
searchBoard.MakeMove (move);
gameMoves.Add (move);
onMoveMade?.Invoke (move);
boardUI.OnMoveMade (board, move, animateMove);
NotifyPlayerToMove ();
}
public void NewGame (bool humanPlaysWhite) {
boardUI.SetPerspective (humanPlaysWhite);
aiDiagnosticsUI.enabled = true;
NewGame ((humanPlaysWhite) ? PlayerType.Human : PlayerType.AI, (humanPlaysWhite) ? PlayerType.AI : PlayerType.Human);
}
public void HotseatGame (){
boardUI.SetPerspective(true);
aiDiagnosticsUI.enabled = false;
NewGame(PlayerType.Human, PlayerType.Human);
}
public void NewComputerVersusComputerGame () {
boardUI.SetPerspective (true);
AIDifficulty.SetDifficulty();
aiDiagnosticsUI.enabled = true;
NewGame (PlayerType.AI, PlayerType.AI);
}
void NewGame (PlayerType whitePlayerType, PlayerType blackPlayerType) {
gameMoves.Clear ();
if (loadCustomPosition) {
board.LoadPosition (customPosition);
searchBoard.LoadPosition (customPosition);
} else {
board.LoadStartPosition ();
searchBoard.LoadStartPosition ();
}
onPositionLoaded?.Invoke ();
boardUI.UpdatePosition (board);
boardUI.ResetSquareColours ();
CreatePlayer (ref whitePlayer, whitePlayerType);
CreatePlayer (ref blackPlayer, blackPlayerType);
gameResult = Result.Playing;
PrintGameResult (gameResult);
NotifyPlayerToMove ();
}
void LogAIDiagnostics () {
string text = "";
var d = aiSettings.diagnostics;
//text += "AI Diagnostics";
text += $"<color=#{ColorUtility.ToHtmlStringRGB(colors[0])}>Depth Searched: {d.lastCompletedDepth}";
//text += $"\nPositions evaluated: {d.numPositionsEvaluated}";
string evalString = "";
if (d.isBook) {
evalString = "Book";
} else {
float displayEval = d.eval / 100f;
if (playerToMove is AIPlayer && !board.WhiteToMove) {
displayEval = -displayEval;
}
evalString = ($"{displayEval:00.00}").Replace (",", ".");
if (Search.IsMateScore (d.eval)) {
evalString = $"Mate in {Search.NumPlyToMateFromScore(d.eval)} Play";
}
}
text += $"\n<color=#{ColorUtility.ToHtmlStringRGB(colors[1])}>Eval: {evalString}";
text += $"\n<color=#{ColorUtility.ToHtmlStringRGB(colors[2])}>Move: {d.moveVal}";
aiDiagnosticsUI.text = text;
}
public void ExportGame () {
string pgn = PGNCreator.CreatePGN (gameMoves.ToArray ());
string baseUrl = "https://www.lichess.org/paste?pgn=";
string escapedPGN = UnityEngine.Networking.UnityWebRequest.EscapeURL (pgn);
string url = baseUrl + escapedPGN;
Application.OpenURL (url);
TextEditor t = new TextEditor ();
t.text = pgn;
t.SelectAll ();
t.Copy ();
}
public void QuitGame () {
Application.Quit ();
}
void NotifyPlayerToMove () {
gameResult = GetGameState ();
PrintGameResult (gameResult);
if (gameResult == Result.Playing) {
playerToMove = (board.WhiteToMove) ? whitePlayer : blackPlayer;
playerToMove.NotifyTurnToMove ();
} else {
Debug.Log ("Game Over");
}
}
void PrintGameResult (Result result) {
float subtitleSize = resultUI.fontSize * 0.75f;
string subtitleSettings = $"<color=#787878> <size={subtitleSize}>";
if (result == Result.Playing) {
resultUI.text = "";
resultUI.text += subtitleSettings + "";
} else if (result == Result.WhiteIsMated || result == Result.BlackIsMated) {
resultUI.text = "Checkmate!";
EndToggle();
} else if (result == Result.FiftyMoveRule) {
resultUI.text = "Draw";
resultUI.text += subtitleSettings + "\n(50 move rule)";
EndToggle();
} else if (result == Result.Repetition) {
resultUI.text = "Draw";
resultUI.text += subtitleSettings + "\n(3-fold repetition)";
EndToggle();
} else if (result == Result.Stalemate) {
resultUI.text = "Draw";
resultUI.text += subtitleSettings + "\n(Stalemate)";
EndToggle();
} else if (result == Result.InsufficientMaterial) {
resultUI.text = "Draw";
resultUI.text += subtitleSettings + "\n(Insufficient material)";
EndToggle();
}
}
void EndToggle(){
export.SetActive(true);
exit.SetActive(true);
}
Result GetGameState () {
MoveGenerator moveGenerator = new MoveGenerator ();
var moves = moveGenerator.GenerateMoves (board);
// Look for mate/stalemate
if (moves.Count == 0) {
if (moveGenerator.InCheck ()) {
return (board.WhiteToMove) ? Result.WhiteIsMated : Result.BlackIsMated;
}
return Result.Stalemate;
}
// Fifty move rule
if (board.fiftyMoveCounter >= 100) {
return Result.FiftyMoveRule;
}
// Threefold repetition
int repCount = board.RepetitionPositionHistory.Count ((x => x == board.ZobristKey));
if (repCount == 3) {
return Result.Repetition;
}
// Look for insufficient material (not all cases implemented yet)
int numPawns = board.pawns[Board.WhiteIndex].Count + board.pawns[Board.BlackIndex].Count;
int numRooks = board.rooks[Board.WhiteIndex].Count + board.rooks[Board.BlackIndex].Count;
int numQueens = board.queens[Board.WhiteIndex].Count + board.queens[Board.BlackIndex].Count;
int numKnights = board.knights[Board.WhiteIndex].Count + board.knights[Board.BlackIndex].Count;
int numBishops = board.bishops[Board.WhiteIndex].Count + board.bishops[Board.BlackIndex].Count;
if (numPawns + numRooks + numQueens == 0) {
if (numKnights == 1 || numBishops == 1) {
return Result.InsufficientMaterial;
}
}
return Result.Playing;
}
void CreatePlayer (ref Player player, PlayerType playerType) {
if (player != null) {
player.onMoveChosen -= OnMoveChosen;
}
if (playerType == PlayerType.Human) {
player = new HumanPlayer (board);
} else {
player = new AIPlayer (searchBoard, aiSettings);
}
player.onMoveChosen += OnMoveChosen;
}
}
}