├── .gitignore
├── ChessConsole.sln
├── ChessConsole
├── App.config
├── ChessBoard.cs
├── ChessConsole.csproj
├── ChessConsole.csproj.user
├── ChessGame.cs
├── ConsoleGraphics.cs
├── Direction.cs
├── Piece.cs
├── Pieces
│ ├── Bishop.cs
│ ├── King.cs
│ ├── Knight.cs
│ ├── Pawn.cs
│ ├── Queen.cs
│ └── Rook.cs
├── Program.cs
└── Properties
│ └── AssemblyInfo.cs
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs/
2 | bin/
3 | obj/
--------------------------------------------------------------------------------
/ChessConsole.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27130.2026
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChessConsole", "ChessConsole\ChessConsole.csproj", "{66F5E978-F5C6-4A48-A689-A6F1E22BE741}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {66F5E978-F5C6-4A48-A689-A6F1E22BE741}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {66F5E978-F5C6-4A48-A689-A6F1E22BE741}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {66F5E978-F5C6-4A48-A689-A6F1E22BE741}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {66F5E978-F5C6-4A48-A689-A6F1E22BE741}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {9C90AB71-4FB3-4E52-ADEE-1C2E6C5EFDDF}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/ChessConsole/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ChessConsole/ChessBoard.cs:
--------------------------------------------------------------------------------
1 | using ChessConsole.Pieces;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 |
7 | namespace ChessConsole
8 | {
9 | ///
10 | /// Provides the data structure and algorithms for dealing with a chess board
11 | ///
12 | public class ChessBoard
13 | {
14 | ///
15 | /// Represents one chess cell in the
16 | ///
17 | public class Cell
18 | {
19 | ///
20 | /// The parent chess board
21 | ///
22 | public ChessBoard Parent
23 | {
24 | private set;
25 | get;
26 | }
27 |
28 | ///
29 | /// 0-7 -> A-H mapping on an actual chessboard
30 | ///
31 | public int X;
32 | ///
33 | /// 0-7 -> 1-8 mapping on an actual chessboard
34 | ///
35 | public int Y;
36 |
37 | ///
38 | /// The piece present at the cell (can be null). Should only be set by .
39 | ///
40 | public Piece Piece;
41 |
42 | ///
43 | /// All the pieces that can hit this cell.
44 | ///
45 | public List HitBy;
46 |
47 | public Cell(ChessBoard parent, int x, int y)
48 | {
49 | Parent = parent;
50 | HitBy = new List();
51 | X = x;
52 | Y = y;
53 | }
54 |
55 | ///
56 | /// Returns cells on the board in a relative direction to this one.
57 | ///
58 | /// The X-direction component in which to search
59 | /// The Y-direction component in which to search
60 | /// The ammount of consecutive cells to return (until outside of the board)
61 | /// Collection of cells which are in the line of sight
62 | public IEnumerable OpenLineOfSight(int dirX, int dirY, int desiredCount = 1)
63 | {
64 | for (int i = 1; i <= desiredCount; i++)
65 | {
66 | //Query the parent for a cell, if null the cell is out of the board and we should stop
67 | Cell cell = Parent.GetCell(X + dirX * i, Y + dirY * i);
68 | if (cell == null) yield break;
69 |
70 | yield return cell;
71 |
72 | //Stop anyway as line of sight is blocked
73 | if (cell.Piece != null)
74 | yield break;
75 | }
76 | }
77 |
78 | ///
79 | /// Returns a cell on the board relative to this one.
80 | ///
81 | /// Relative X-coordinate of the cell
82 | /// Relative X-coordinate of the cell
83 | /// Node at (x, y) position or null if index is out of bounds
84 | public Cell Open(int x, int y)
85 | {
86 | //Query the parent for a cell, if null the cell is out of the board and we should not return
87 | Cell cell = Parent.GetCell(X + x, Y + y);
88 | return cell ?? null;
89 | }
90 | }
91 |
92 | ///
93 | /// Contains information about the cells and the links between them
94 | ///
95 | private Cell[,] cells;
96 |
97 | ///
98 | /// The cell to hit for en passant
99 | ///
100 | public Cell EnPassant
101 | {
102 | private set;
103 | get;
104 | }
105 |
106 | ///
107 | /// The cell where the pawn will be captured after en passant is performed
108 | ///
109 | public Cell EnPassantCapture
110 |
111 | {
112 | private set;
113 | get;
114 | }
115 |
116 | ///
117 | /// List holding all the existing pieces
118 | ///
119 | private List pieces = new List();
120 |
121 | private Piece blackKing;
122 | private Piece whiteKing;
123 |
124 | ///
125 | /// Caches the method's result
126 | ///
127 | private bool inCheck;
128 |
129 | public ChessBoard()
130 | {
131 | Reset();
132 | }
133 |
134 | #region Getters
135 |
136 | ///
137 | /// Get cell by absolute coordinates
138 | ///
139 | /// Absolute X-coord
140 | /// Absolute Y-coord
141 | /// Node at (x, y) position or null if index is out of bounds
142 | public Cell GetCell(int x, int y)
143 | {
144 | if (x < 0 || cells.GetLength(0) <= x || y < 0 || cells.GetLength(1) <= y) return null;
145 |
146 | return cells[x, y];
147 | }
148 |
149 | #endregion
150 |
151 | #region HelperMethods
152 |
153 | ///
154 | /// Adds a piece in the beggining of the chess game, can also be used for promotion
155 | ///
156 | /// The cell to add to
157 | ///
158 | private void addPiece(Cell cell, Piece piece)
159 | {
160 | cell.Piece = piece;
161 | pieces.Add(piece);
162 | piece.OnPlace(cell);
163 | }
164 |
165 | #endregion
166 |
167 | #region InterfaceMethods
168 |
169 | ///
170 | /// Resets the board state
171 | ///
172 | public void Reset()
173 | {
174 | cells = new Cell[8, 8];
175 | for (int i = 0; i < 8; i++)
176 | {
177 | for (int j = 0; j < 8; j++)
178 | {
179 | cells[i, j] = new Cell(this, i, j);
180 | }
181 | }
182 |
183 | pieces.Clear();
184 |
185 | EnPassant = null;
186 | EnPassantCapture = null;
187 |
188 | addPiece(cells[0, 0], new Rook(PlayerColor.White));
189 | addPiece(cells[1, 0], new Knight(PlayerColor.White));
190 | addPiece(cells[2, 0], new Bishop(PlayerColor.White));
191 | addPiece(cells[3, 0], new Queen(PlayerColor.White));
192 | addPiece(cells[4, 0], (whiteKing = new King(PlayerColor.White)));
193 | addPiece(cells[5, 0], new Bishop(PlayerColor.White));
194 | addPiece(cells[6, 0], new Knight(PlayerColor.White));
195 | addPiece(cells[7, 0], new Rook(PlayerColor.White));
196 |
197 | addPiece(cells[0, 1], new Pawn(PlayerColor.White));
198 | addPiece(cells[1, 1], new Pawn(PlayerColor.White));
199 | addPiece(cells[2, 1], new Pawn(PlayerColor.White));
200 | addPiece(cells[3, 1], new Pawn(PlayerColor.White));
201 | addPiece(cells[4, 1], new Pawn(PlayerColor.White));
202 | addPiece(cells[5, 1], new Pawn(PlayerColor.White));
203 | addPiece(cells[6, 1], new Pawn(PlayerColor.White));
204 | addPiece(cells[7, 1], new Pawn(PlayerColor.White));
205 |
206 | addPiece(cells[0, 6], new Pawn(PlayerColor.Black));
207 | addPiece(cells[1, 6], new Pawn(PlayerColor.Black));
208 | addPiece(cells[2, 6], new Pawn(PlayerColor.Black));
209 | addPiece(cells[3, 6], new Pawn(PlayerColor.Black));
210 | addPiece(cells[4, 6], new Pawn(PlayerColor.Black));
211 | addPiece(cells[5, 6], new Pawn(PlayerColor.Black));
212 | addPiece(cells[6, 6], new Pawn(PlayerColor.Black));
213 | addPiece(cells[7, 6], new Pawn(PlayerColor.Black));
214 |
215 | addPiece(cells[0, 7], new Rook(PlayerColor.Black));
216 | addPiece(cells[1, 7], new Knight(PlayerColor.Black));
217 | addPiece(cells[2, 7], new Bishop(PlayerColor.Black));
218 | addPiece(cells[3, 7], new Queen(PlayerColor.Black));
219 | addPiece(cells[4, 7], (blackKing = new King(PlayerColor.Black)));
220 | addPiece(cells[5, 7], new Bishop(PlayerColor.Black));
221 | addPiece(cells[6, 7], new Knight(PlayerColor.Black));
222 | addPiece(cells[7, 7], new Rook(PlayerColor.Black));
223 |
224 | foreach (Piece piece in pieces)
225 | {
226 | piece.Recalculate();
227 | }
228 | }
229 |
230 | ///
231 | /// Called at the start of every turn. Recalcualtes legal moves.
232 | ///
233 | /// The player whose turn is to move
234 | /// Whether the player had any moves
235 | public bool TurnStart(PlayerColor currentPlayer)
236 | {
237 | inCheck = IsInCheck(currentPlayer, false);
238 | bool anyLegalMove = false;
239 | //Clear cell hit lists
240 | foreach (Cell cell in cells)
241 | {
242 | cell.HitBy.Clear();
243 | }
244 |
245 | //Recalculate possible moves and hit lists for cells
246 | foreach (Piece piece in pieces)
247 | {
248 | piece.Recalculate();
249 | }
250 |
251 | //Calculate legal moves
252 | foreach (Piece piece in pieces)
253 | {
254 | piece.LegalMoves.Clear();
255 | foreach (Cell move in piece.PossibleMoves)
256 | {
257 | if (piece.Color == currentPlayer && isMoveLegal(piece, move))
258 | {
259 | piece.LegalMoves.Add(move);
260 | anyLegalMove = true;
261 | }
262 | }
263 | }
264 |
265 | return anyLegalMove;
266 | }
267 |
268 | ///
269 | /// Validates if a move is legal for a given piece
270 | ///
271 | /// Piece to move
272 | /// Where the piece moves
273 | ///
274 | private bool isMoveLegal(Piece piece, Cell move)
275 | {
276 | Piece currentKing = piece.Color == PlayerColor.White ? whiteKing : blackKing;
277 | //The strategy is to try everything that can fail and return true only if nothing fails
278 |
279 | //If it's the king check if it moved into a check (or didn't move out that's really the same thing)
280 | if (piece is King)
281 | {
282 | //If some enemy hits where we move we can't move with the king
283 | foreach (Piece hitter in move.HitBy)
284 | {
285 | if (hitter.Parent != move && hitter.Color != piece.Color)
286 | return false;
287 | }
288 |
289 | //Validate castling
290 | if (Math.Abs(move.X - piece.Parent.X) == 2)
291 | {
292 | //You can't castle in check
293 | if (inCheck)
294 | return false;
295 |
296 | //Check if some enemy hits the middle castling
297 | foreach (Piece hitter in GetCell(move.X > piece.Parent.X ? move.X - 1 : move.X + 1, move.Y).HitBy)
298 | {
299 | if (hitter.Color != piece.Color)
300 | return false;
301 | }
302 | }
303 | }
304 | else //Non-king pieces
305 | {
306 | if (inCheck) //If player is in in check and if move resolves that
307 | {
308 | //Let's try capturing or blocking the attacker, keep in mind that we can't unblock another attacker
309 | foreach (Piece hitter in currentKing.Parent.HitBy)
310 | {
311 | if (hitter.Color == currentKing.Color) continue; //Same color don't care
312 | if (hitter.Parent == move) continue; //Was captured
313 | if (hitter.IsBlockedIfMove(piece.Parent, move, currentKing.Parent)) continue;
314 |
315 | return false;
316 | }
317 | }
318 |
319 | //Check if a blocker moving away results in a check
320 | //This also prevents pieces capturing an attacker and exposing the king to another
321 | foreach (Piece hitter in piece.Parent.HitBy)
322 | {
323 | if (hitter.Color == currentKing.Color) continue; //If it's the same color we don't care
324 | if (hitter.Parent == move) continue; //If we hit it it can not block
325 |
326 | if (!hitter.IsBlockedIfMove(piece.Parent, move, currentKing.Parent))
327 | return false;
328 | }
329 | }
330 |
331 |
332 | return true;
333 | }
334 |
335 | ///
336 | /// Checks if a player is currently in check (their king can be hit)
337 | ///
338 | /// The player's color to check
339 | /// Uses the cached value for the current turn
340 | ///
341 | public bool IsInCheck(PlayerColor player, bool useCache = true)
342 | {
343 | if (useCache)
344 | return inCheck;
345 |
346 | if (player == PlayerColor.White)
347 | return whiteKing.Parent.HitBy.Any(hitter => hitter.Color != player);
348 | else
349 | return blackKing.Parent.HitBy.Any(hitter => hitter.Color != player);
350 | }
351 |
352 | ///
353 | /// Move a piece from one cell the the other, after this function is called the turn MUST end
354 | ///
355 | /// Node where the moved piece is
356 | /// Node to move to
357 | /// The option chosed when promoting a pawn, will be ignored if the movement does not involve pormotion.
358 | public void Move(Cell from, Cell to, PromoteOptions promoteOption)
359 | {
360 | //Capture a piece if moved on it
361 | if (to.Piece != null)
362 | pieces.Remove(to.Piece);
363 |
364 | to.Piece = from.Piece;
365 | from.Piece = null;
366 |
367 | //Handles en passant captures
368 | if (to == EnPassant && to.Piece is Pawn)
369 | {
370 | pieces.Remove(EnPassantCapture.Piece);
371 | EnPassantCapture.Piece = null;
372 | }
373 |
374 | //Castling to the right
375 | if (to.Piece is King && to.X - from.X == 2)
376 | {
377 | Move(GetCell(7, to.Y), GetCell(to.X - 1, to.Y), promoteOption); //Move the rook as well
378 | }
379 |
380 | //Castling to the left
381 | if (to.Piece is King && to.X - from.X == -2)
382 | {
383 | Move(GetCell(0, to.Y), GetCell(to.X + 1, to.Y), promoteOption); //Move the rook as well
384 | }
385 |
386 | //Handles promotion
387 | if (to.Piece is Pawn && to.Y == (to.Piece.Color == PlayerColor.White ? 7 : 0))
388 | {
389 | Piece promoted = null; //we have to set it to null cuz C# complains
390 | switch (promoteOption)
391 | {
392 | case PromoteOptions.Queen:
393 | promoted = new Queen(to.Piece);
394 | break;
395 | case PromoteOptions.Rook:
396 | promoted = new Rook(to.Piece);
397 | break;
398 | case PromoteOptions.Bishop:
399 | promoted = new Bishop(to.Piece);
400 | break;
401 | case PromoteOptions.Knight:
402 | promoted = new Knight(to.Piece);
403 | break;
404 | }
405 |
406 | //Update the list with the new promoted piece
407 | pieces.Remove(to.Piece);
408 | to.Piece = promoted;
409 | promoted.OnPlace(to); //Place it otherwise weird bugs occur
410 | pieces.Add(promoted);
411 | }
412 |
413 | //The code has to be in this exact order to prevent from listeners firing when we move into our own listened cells.
414 | //Recalculate possible moves
415 | to.Piece.OnMove(to);
416 | to.Piece.Recalculate();
417 |
418 | //Resets en passant
419 | EnPassant = null;
420 | EnPassantCapture = null;
421 |
422 | //Handles en passant detection
423 | if (to.Piece is Pawn && Math.Abs(to.Y - from.Y) == 2)
424 | {
425 | EnPassant = GetCell(to.X, (from.Y > to.Y) ? from.Y - 1 : from.Y + 1);
426 | EnPassantCapture = to;
427 | }
428 | }
429 |
430 | ///
431 | /// Is a piece (usually a pawn) promotable if it's moving from a cell to another
432 | ///
433 | /// The cell where the piece moves from
434 | /// The cell where the piece moves to
435 | /// Whether the piece on from is promotable
436 | public bool IsPromotable(Cell from, Cell to)
437 | {
438 | return from.Piece is Pawn && to.Y == (from.Piece.Color == PlayerColor.White ? 7 : 0);
439 | }
440 |
441 | #endregion
442 | }
443 | }
444 |
--------------------------------------------------------------------------------
/ChessConsole/ChessConsole.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {66F5E978-F5C6-4A48-A689-A6F1E22BE741}
8 | Exe
9 | ChessConsole
10 | ChessConsole
11 | v4.6.1
12 | 512
13 | true
14 |
15 |
16 | AnyCPU
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | AnyCPU
27 | pdbonly
28 | true
29 | bin\Release\
30 | TRACE
31 | prompt
32 | 4
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 |
--------------------------------------------------------------------------------
/ChessConsole/ChessConsole.csproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ShowAllFiles
5 |
6 |
--------------------------------------------------------------------------------
/ChessConsole/ChessGame.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | namespace ChessConsole
5 | {
6 | public enum PlayerColor
7 | {
8 | White, Black
9 | }
10 |
11 | public enum PlayerState
12 | {
13 | Idle, Holding, AwaitPromote, GameOver
14 | }
15 |
16 | public enum PromoteOptions
17 | {
18 | Queen = 0, Rook = 1, Bishop = 2, Knight = 3
19 | }
20 |
21 | public class ChessGame
22 | {
23 | ///
24 | /// False indicates the game should exit
25 | ///
26 | public bool Running
27 | {
28 | private set;
29 | get;
30 | }
31 |
32 | private PlayerState playerState;
33 |
34 | ///
35 | /// Currently selected promote option
36 | ///
37 | private PromoteOptions promoteOption;
38 |
39 | ///
40 | /// True for white, false for black
41 | ///
42 | private PlayerColor currentPlayer;
43 |
44 | ///
45 | /// Coordinates for the virtual cursor on the board
46 | ///
47 | private int cursorX, cursorY;
48 |
49 | ///
50 | /// The actual chess board
51 | ///
52 | private ChessBoard board;
53 |
54 | ///
55 | /// Currently holded piece's parent cell
56 | ///
57 | private ChessBoard.Cell holdedNode = null;
58 |
59 | ///
60 | /// Where to move
61 | ///
62 | private ChessBoard.Cell moveTo = null;
63 |
64 | public ChessGame()
65 | {
66 | Running = true;
67 | board = new ChessBoard();
68 | currentPlayer = PlayerColor.White;
69 | turnStart();
70 | }
71 |
72 | #region PublicInterfaceCommands
73 | public void Update()
74 | {
75 | if (Console.KeyAvailable)
76 | {
77 | ConsoleKeyInfo keyInfo = Console.ReadKey(true);
78 |
79 | if (keyInfo.Key == ConsoleKey.LeftArrow && cursorX > 0 && playerState != PlayerState.AwaitPromote)
80 | cursorX--;
81 | else if (keyInfo.Key == ConsoleKey.RightArrow && cursorX < 7 && playerState != PlayerState.AwaitPromote)
82 | cursorX++;
83 | else if (keyInfo.Key == ConsoleKey.UpArrow)
84 | {
85 | if (playerState != PlayerState.AwaitPromote && cursorY < 7)
86 | cursorY++;
87 | else if ((int)promoteOption > 0)
88 | promoteOption--;
89 | }
90 | else if (keyInfo.Key == ConsoleKey.DownArrow)
91 | {
92 | if (playerState != PlayerState.AwaitPromote && cursorY > 0)
93 | cursorY--;
94 | else if ((int)promoteOption < 3)
95 | promoteOption++;
96 | }
97 | else if (keyInfo.Key == ConsoleKey.Enter)
98 | interact();
99 | else if (keyInfo.Key == ConsoleKey.D)
100 | debugInteract();
101 | else if (keyInfo.Key == ConsoleKey.Escape)
102 | cancel();
103 | }
104 | }
105 |
106 | ///
107 | /// Draws the game
108 | ///
109 | /// ConsoleGraphics object to draw with/to
110 | public void Draw(ConsoleGraphics g)
111 | {
112 | g.FillArea(new CChar(' ', ConsoleColor.Black, ConsoleColor.DarkGray), 10, 5, 8, 8);
113 |
114 | //7-j everywhere cuz it's reversed in chess
115 | for (int i = 0; i < 8; i++)
116 | {
117 | for (int j = 0; j < 8; j++)
118 | {
119 | //Draw the symbol
120 | ChessBoard.Cell cell = board.GetCell(i, j);
121 | if (cell.Piece != null)
122 | {
123 | g.DrawTransparent(cell.Piece.Char, (cell.Piece.Color == PlayerColor.White) ? ConsoleColor.White : ConsoleColor.Black, 10 + i, 5 + (7 - j));
124 | if (cell.Piece.LegalMoves.Count == 0)
125 | {
126 | g.SetBackground(ConsoleColor.DarkRed, 10 + i, 5 + (7 - j));
127 | }
128 | }
129 |
130 | if (cell.HitBy.Contains(debugPiece))
131 | g.SetBackground(ConsoleColor.DarkMagenta, 10 + i, 5 + (7 - j));
132 | }
133 | }
134 |
135 | if (holdedNode != null && playerState == PlayerState.Holding)
136 | {
137 | //Highlight legal moves
138 | foreach (ChessBoard.Cell move in holdedNode.Piece.LegalMoves)
139 | {
140 | g.SetBackground(ConsoleColor.DarkGreen, 10 + move.X, 5 + (7 - move.Y));
141 | }
142 | }
143 |
144 | //Sets the cursor color -> yellow
145 | g.SetBackground(ConsoleColor.DarkYellow, 10 + cursorX, 5 + (7 - cursorY));
146 |
147 | //TODO: Remove en passant testing
148 | /*if (board.EnPassant != null)
149 | g.SetBackground(ConsoleColor.DarkCyan, 10 + board.EnPassant.X, 5 + (7 - board.EnPassant.Y));
150 |
151 | if (board.EnPassantCapture != null)
152 | g.SetBackground(ConsoleColor.DarkMagenta, 10 + board.EnPassantCapture.X, 5 + (7 - board.EnPassantCapture.Y));*/
153 |
154 | //Lighten for checkerboard pattern
155 | for (int i = 0; i < 8; i++)
156 | {
157 | for (int j = 0; j < 8; j++)
158 | {
159 | if ((i + j) % 2 == 1) g.LightenBackground(10 + i, 5 + j);
160 | }
161 | }
162 |
163 | //Promotion option menu
164 |
165 | if (playerState == PlayerState.AwaitPromote)
166 | {
167 | g.DrawTextTrasparent("Queen", promoteOption == PromoteOptions.Queen ? ConsoleColor.Yellow : ConsoleColor.White, 22, 7);
168 | g.DrawTextTrasparent("Rook", promoteOption == PromoteOptions.Rook ? ConsoleColor.Yellow : ConsoleColor.White, 22, 9);
169 | g.DrawTextTrasparent("Bishop", promoteOption == PromoteOptions.Bishop ? ConsoleColor.Yellow : ConsoleColor.White, 22, 11);
170 | g.DrawTextTrasparent("Knight", promoteOption == PromoteOptions.Knight ? ConsoleColor.Yellow : ConsoleColor.White, 22, 13);
171 | }
172 | else
173 | {
174 | g.ClearArea(22, 7, 6, 7);
175 | }
176 | }
177 |
178 | #endregion
179 |
180 | #region EventHandlerLikeMethods
181 |
182 | ///
183 | /// Happens when the user presses the enter key
184 | ///
185 | private void interact()
186 | {
187 | switch (playerState)
188 | {
189 | case PlayerState.Idle:
190 | holdedNode = board.GetCell(cursorX, cursorY);
191 |
192 | if (holdedNode.Piece == null || holdedNode.Piece.Color != currentPlayer || holdedNode.Piece.LegalMoves.Count == 0)
193 | {
194 | holdedNode = null;
195 | return;
196 | }
197 | else playerState = PlayerState.Holding;
198 |
199 |
200 | break;
201 | case PlayerState.Holding:
202 | playerState = PlayerState.Holding;
203 |
204 | moveTo = board.GetCell(cursorX, cursorY);
205 |
206 | if (!holdedNode.Piece.LegalMoves.Contains(moveTo))
207 | {
208 | moveTo = null;
209 | return;
210 | }
211 |
212 | if (board.IsPromotable(holdedNode, moveTo))
213 | showPromote();
214 | else
215 | turnOver();
216 |
217 | break;
218 | case PlayerState.AwaitPromote:
219 | turnOver();
220 | break;
221 | case PlayerState.GameOver:
222 | Running = false;
223 | break;
224 | }
225 | }
226 |
227 |
228 | private Piece debugPiece;
229 | private void debugInteract()
230 | {
231 | debugPiece = board.GetCell(cursorX, cursorY).Piece;
232 | }
233 |
234 | ///
235 | /// Happens when the user presses the escape key
236 | ///
237 | private void cancel()
238 | {
239 | playerState = PlayerState.Idle;
240 | holdedNode = null;
241 | }
242 |
243 | #endregion
244 |
245 | #region EventLikeMethods
246 | ///
247 | /// Called on every turn start
248 | ///
249 | private void turnStart()
250 | {
251 | board.TurnStart(currentPlayer);
252 | }
253 |
254 | ///
255 | /// Shows promotion dialog (set's the state)
256 | ///
257 | private void showPromote()
258 | {
259 | playerState = PlayerState.AwaitPromote;
260 | promoteOption = PromoteOptions.Queen; //reset the menu
261 | }
262 |
263 | ///
264 | /// Called when the turn is passed to the other player
265 | ///
266 | private void turnOver()
267 | {
268 | board.Move(holdedNode, moveTo, promoteOption);
269 | holdedNode = null;
270 | moveTo = null;
271 | playerState = PlayerState.Idle;
272 | currentPlayer = currentPlayer == PlayerColor.White ? PlayerColor.Black : PlayerColor.White;
273 | turnStart();
274 | }
275 | #endregion
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/ChessConsole/ConsoleGraphics.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ChessConsole
4 | {
5 | ///
6 | /// Colored console character for the and
7 | ///
8 | public struct CChar : IEquatable
9 | {
10 | public ConsoleColor Foreground;
11 | public ConsoleColor Background;
12 | ///
13 | /// Actual character value
14 | ///
15 | public char C;
16 |
17 | public CChar(char c = ' ', ConsoleColor foreground = ConsoleColor.White, ConsoleColor background = ConsoleColor.Black)
18 | {
19 | Foreground = foreground;
20 | Background = background;
21 | C = c;
22 | }
23 |
24 | public bool Equals(CChar other)
25 | {
26 | return other.Foreground == Foreground && other.Background == Background && other.C == C;
27 | }
28 |
29 | public static bool operator==(CChar lhs, CChar rhs)
30 | {
31 | return lhs.Equals(rhs);
32 | }
33 |
34 | public static bool operator!=(CChar lhs, CChar rhs)
35 | {
36 | return !lhs.Equals(rhs);
37 | }
38 | }
39 |
40 | ///
41 | /// Handles double buffered C# console
42 | ///
43 | public class ConsoleGraphics
44 | {
45 | ///
46 | /// First everything is drawn to the back buffer for double buffering purposes
47 | /// you can "swap" buffers with
48 | ///
49 | private CChar[,] backBuffer;
50 |
51 | ///
52 | /// Current console's colored character buffer
53 | /// you can "swap" buffers with
54 | ///
55 | private CChar[,] frontBuffer;
56 |
57 | public ConsoleGraphics()
58 | {
59 | backBuffer = new CChar[Console.BufferWidth, Console.BufferHeight];
60 | frontBuffer = new CChar[Console.BufferWidth, Console.BufferHeight];
61 | }
62 |
63 |
64 |
65 | #region DrawMethods
66 |
67 | ///
68 | /// Clears the back buffer, next SwapBuffer
69 | ///
70 | public void Clear()
71 | {
72 | for (int i = 0; i < backBuffer.GetLength(0); i++)
73 | {
74 | for (int j = 0; j < backBuffer.GetLength(1); j++)
75 | {
76 | backBuffer[i, j] = new CChar();
77 | }
78 | }
79 | }
80 |
81 | ///
82 | /// Draws a colored character to the back buffer
83 | ///
84 | /// The colored character to draw
85 | /// X-coord in console buffer
86 | /// Y-coord in console buffer
87 | public void Draw(CChar cchar, int x, int y)
88 | {
89 | backBuffer[x, y] = cchar;
90 | }
91 |
92 | ///
93 | /// Draws a colored character to the back buffer, it doesn't change background color
94 | ///
95 | /// The character to draw
96 | /// Color of the character
97 | /// X-coord in console buffer
98 | /// Y-coord in console buffer
99 | public void DrawTransparent(char c, ConsoleColor foreground, int x, int y)
100 | {
101 | backBuffer[x, y].C = c;
102 | backBuffer[x, y].Foreground = foreground;
103 | }
104 |
105 | ///
106 | /// Draws an area of colored characters to the back buffer.
107 | /// The arrays length is used as area width and height.
108 | ///
109 | /// The colored character area to draw
110 | /// Starting X-coord in console buffer
111 | /// Starting Y-coord in console buffer
112 | public void DrawArea(CChar[,] cchars, int x, int y)
113 | {
114 | for (int i = 0; i < cchars.GetLength(0); i++)
115 | {
116 | for (int j = 0; j < cchars.GetLength(1); j++)
117 | {
118 | backBuffer[x + i, y + j] = cchars[i, j];
119 | }
120 | }
121 | }
122 |
123 | ///
124 | /// Draws text to the screen. Multiline is not handled.
125 | ///
126 | /// The text to draw
127 | /// Foreground color of text
128 | /// Background color of text
129 | /// Starting X-coord in console buffer
130 | /// Starting Y-coord in console buffer
131 | public void DrawText(string text, ConsoleColor foreground, ConsoleColor background, int x, int y)
132 | {
133 | CChar[,] area = new CChar[text.Length, 1];
134 | for (int i = 0; i < text.Length; i++)
135 | {
136 | area[i, 0] = new CChar(text[i], foreground, background);
137 | }
138 |
139 | DrawArea(area, x, y);
140 | }
141 |
142 | ///
143 | /// Draws text to the screen with a transparent background. Multiline is not handled.
144 | ///
145 | /// The text to draw
146 | /// Foreground color of text
147 | /// Starting X-coord in console buffer
148 | /// Starting Y-coord in console buffer
149 | public void DrawTextTrasparent(string text, ConsoleColor foreground, int x, int y)
150 | {
151 | CChar[,] area = new CChar[text.Length, 1];
152 | for (int i = 0; i < text.Length; i++)
153 | {
154 | area[i, 0] = new CChar(text[i], foreground, backBuffer[x + i, y].Background);
155 | }
156 |
157 | DrawArea(area, x, y);
158 | }
159 |
160 | ///
161 | /// Fills an area of the back buffer with one specic colored character
162 | /// The arrays length is used as area width and height.
163 | ///
164 | /// The colored character area to draw
165 | /// Starting X-coord in console buffer
166 | /// Starting Y-coord in console buffer
167 | /// Width of the area
168 | /// Height of the area
169 | public void FillArea(CChar cchar, int x, int y, int width, int height)
170 | {
171 | for (int i = 0; i < width; i++)
172 | {
173 | for (int j = 0; j < height; j++)
174 | {
175 | backBuffer[x + i, y + j] = cchar;
176 | }
177 | }
178 | }
179 |
180 | ///
181 | /// Clears area on the screen
182 | ///
183 | /// Starting X-coord in console buffer
184 | /// Starting Y-coord in console buffer
185 | /// Width of the area
186 | /// Height of the area
187 | public void ClearArea(int x, int y, int width, int height)
188 | {
189 | for (int i = 0; i < width; i++)
190 | {
191 | for (int j = 0; j < height; j++)
192 | {
193 | backBuffer[x + i, y + j] = new CChar();
194 | }
195 | }
196 | }
197 |
198 | #endregion
199 |
200 | #region Darken_Lighten
201 |
202 | ///
203 | /// Darkens the background color of a colored character in the back buffer.
204 | /// If background color is already dark or no dark version exists it leaves it unchanged.
205 | ///
206 | /// X-coord in console buffer
207 | /// Y-coord in console buffer
208 | public void DarkenBackground(int x, int y)
209 | {
210 | switch (backBuffer[x, y].Background)
211 | {
212 | case ConsoleColor.Blue:
213 | backBuffer[x, y].Background = ConsoleColor.DarkBlue;
214 | break;
215 | case ConsoleColor.Green:
216 | backBuffer[x, y].Background = ConsoleColor.DarkGreen;
217 | break;
218 | case ConsoleColor.Yellow:
219 | backBuffer[x, y].Background = ConsoleColor.DarkYellow;
220 | break;
221 | case ConsoleColor.Magenta:
222 | backBuffer[x, y].Background = ConsoleColor.DarkMagenta;
223 | break;
224 | case ConsoleColor.Gray:
225 | backBuffer[x, y].Background = ConsoleColor.DarkGray;
226 | break;
227 | case ConsoleColor.Cyan:
228 | backBuffer[x, y].Background = ConsoleColor.DarkCyan;
229 | break;
230 | case ConsoleColor.Red:
231 | backBuffer[x, y].Background = ConsoleColor.DarkRed;
232 | break;
233 | }
234 | }
235 |
236 | ///
237 | /// Darkens the foreground color of a colored character in the back buffer.
238 | /// If foreground color is already dark or no dark version exists it leaves it unchanged.
239 | ///
240 | /// X-coord in console buffer
241 | /// Y-coord in console buffer
242 | public void DarkenForeground(int x, int y)
243 | {
244 | switch (backBuffer[x, y].Foreground)
245 | {
246 | case ConsoleColor.Blue:
247 | backBuffer[x, y].Foreground = ConsoleColor.DarkBlue;
248 | break;
249 | case ConsoleColor.Green:
250 | backBuffer[x, y].Foreground = ConsoleColor.DarkGreen;
251 | break;
252 | case ConsoleColor.Yellow:
253 | backBuffer[x, y].Foreground = ConsoleColor.DarkYellow;
254 | break;
255 | case ConsoleColor.Magenta:
256 | backBuffer[x, y].Foreground = ConsoleColor.DarkMagenta;
257 | break;
258 | case ConsoleColor.Gray:
259 | backBuffer[x, y].Foreground = ConsoleColor.DarkGray;
260 | break;
261 | case ConsoleColor.Cyan:
262 | backBuffer[x, y].Foreground = ConsoleColor.DarkCyan;
263 | break;
264 | case ConsoleColor.Red:
265 | backBuffer[x, y].Foreground = ConsoleColor.DarkRed;
266 | break;
267 | }
268 | }
269 |
270 | ///
271 | /// Lightens the background color of a colored character in the back buffer.
272 | /// If background color is already light or no light version exists it leaves it unchanged.
273 | ///
274 | /// X-coord in console buffer
275 | /// Y-coord in console buffer
276 | public void LightenBackground(int x, int y)
277 | {
278 | switch (backBuffer[x, y].Background)
279 | {
280 | case ConsoleColor.DarkBlue:
281 | backBuffer[x, y].Background = ConsoleColor.Blue;
282 | break;
283 | case ConsoleColor.DarkGreen:
284 | backBuffer[x, y].Background = ConsoleColor.Green;
285 | break;
286 | case ConsoleColor.DarkYellow:
287 | backBuffer[x, y].Background = ConsoleColor.Yellow;
288 | break;
289 | case ConsoleColor.DarkMagenta:
290 | backBuffer[x, y].Background = ConsoleColor.Magenta;
291 | break;
292 | case ConsoleColor.DarkGray:
293 | backBuffer[x, y].Background = ConsoleColor.Gray;
294 | break;
295 | case ConsoleColor.DarkCyan:
296 | backBuffer[x, y].Background = ConsoleColor.Cyan;
297 | break;
298 | case ConsoleColor.DarkRed:
299 | backBuffer[x, y].Background = ConsoleColor.Red;
300 | break;
301 | }
302 | }
303 |
304 | ///
305 | /// Lightens the foreground color of a colored character in the back buffer.
306 | /// If foreground color is already light or no light version exists it leaves it unchanged.
307 | ///
308 | /// X-coord in console buffer
309 | /// Y-coord in console buffer
310 | public void LightenForeground(int x, int y)
311 | {
312 | switch (backBuffer[x, y].Foreground)
313 | {
314 | case ConsoleColor.DarkBlue:
315 | backBuffer[x, y].Foreground = ConsoleColor.Blue;
316 | break;
317 | case ConsoleColor.DarkGreen:
318 | backBuffer[x, y].Foreground = ConsoleColor.Green;
319 | break;
320 | case ConsoleColor.DarkYellow:
321 | backBuffer[x, y].Foreground = ConsoleColor.Yellow;
322 | break;
323 | case ConsoleColor.DarkMagenta:
324 | backBuffer[x, y].Foreground = ConsoleColor.Magenta;
325 | break;
326 | case ConsoleColor.DarkGray:
327 | backBuffer[x, y].Foreground = ConsoleColor.Gray;
328 | break;
329 | case ConsoleColor.DarkCyan:
330 | backBuffer[x, y].Foreground = ConsoleColor.Cyan;
331 | break;
332 | case ConsoleColor.DarkRed:
333 | backBuffer[x, y].Foreground = ConsoleColor.Red;
334 | break;
335 | }
336 | }
337 |
338 | #endregion
339 |
340 | #region Color Getters/Setters
341 | ///
342 | /// Sets the background color of the back buffer at (x, y)
343 | ///
344 | /// New background color
345 | /// X-coord in console buffer
346 | /// Y-coord in console buffer
347 | public void SetBackground(ConsoleColor color, int x, int y)
348 | {
349 | backBuffer[x, y].Background = color;
350 | }
351 |
352 | ///
353 | /// Gets the background color of the back buffer at (x, y)
354 | ///
355 | /// X-coord in console buffer
356 | /// Y-coord in console buffer
357 | /// Background color at (x, y)
358 | public ConsoleColor GetBackground(int x, int y)
359 | {
360 | return backBuffer[x, y].Background;
361 | }
362 |
363 | ///
364 | /// Sets the foreground color of the back buffer at (x, y)
365 | ///
366 | /// New foreground color
367 | /// X-coord in console buffer
368 | /// Y-coord in console buffer
369 | public void SetForeground(ConsoleColor color, int x, int y)
370 | {
371 | backBuffer[x, y].Foreground = color;
372 | }
373 |
374 | ///
375 | /// Gets the foreground color of the back buffer at (x, y)
376 | ///
377 | /// X-coord in console buffer
378 | /// Y-coord in console buffer
379 | /// Foreground color at (x, y)
380 | public ConsoleColor GetForeground(int x, int y)
381 | {
382 | return backBuffer[x, y].Foreground;
383 | }
384 | #endregion
385 |
386 | ///
387 | /// Overwrites the FrontBuffer and redraws the character if it's different from the BackBuffer
388 | ///
389 | public void SwapBuffers()
390 | {
391 | for (int i = 0; i < backBuffer.GetLength(0); i++)
392 | {
393 | for (int j = 0; j < backBuffer.GetLength(1); j++)
394 | {
395 | if (frontBuffer[i, j] != backBuffer[i, j])
396 | {
397 | Console.SetCursorPosition(i, j);
398 | Console.ForegroundColor = backBuffer[i, j].Foreground;
399 | Console.BackgroundColor = backBuffer[i, j].Background;
400 | Console.Write(backBuffer[i, j].C);
401 | frontBuffer[i, j] = backBuffer[i, j];
402 | }
403 | }
404 | }
405 | }
406 | }
407 | }
408 |
--------------------------------------------------------------------------------
/ChessConsole/Direction.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace ChessConsole
6 | {
7 | ///
8 | /// Contains possible moves and handles line of sight checks
9 | ///
10 | public class Direction
11 | {
12 | ///
13 | /// The piece whose moves are represented by this object
14 | ///
15 | public Piece Piece
16 | {
17 | private set;
18 | get;
19 | }
20 |
21 | ///
22 | /// The X-direction
23 | ///
24 | public int X
25 | {
26 | private set;
27 | get;
28 | }
29 |
30 | ///
31 | /// The Y-direction
32 | ///
33 | public int Y
34 | {
35 | private set;
36 | get;
37 | }
38 |
39 | ///
40 | /// The possible moves you can make in this direction including the line of sight blocking piece that may or may not be hittable.
41 | /// See also
42 | ///
43 | private List possibleMoves;
44 |
45 | ///
46 | /// The possible moves you can make in this direction
47 | ///
48 | /// Are the enemy pieces hittable
49 | /// An enumeration of possible moves
50 | public IEnumerable GetPossibleMoves(bool enemyHittable = true)
51 | {
52 | if (possibleMoves.Count == 0)
53 | yield break;
54 |
55 | for (int i = 0; i < possibleMoves.Count - 1; i++)
56 | {
57 | yield return possibleMoves[i];
58 | }
59 |
60 | if (possibleMoves.Last().Piece == null)
61 | yield return possibleMoves.Last();
62 | else if (enemyHittable && possibleMoves.Last().Piece.Color != Piece.Color)
63 | yield return possibleMoves.Last();
64 | }
65 |
66 | ///
67 | /// The count of possible moves
68 | ///
69 | /// Are the enemy pieces hittable
70 | /// The count of possible moves
71 | public int GetPossibleMoveCount(bool enemyHittable = true)
72 | {
73 | if (possibleMoves.Count == 0)
74 | return 0;
75 |
76 | if (possibleMoves.Last().Piece == null)
77 | return possibleMoves.Count;
78 | else if (!enemyHittable || possibleMoves.Last().Piece.Color == Piece.Color)
79 | return possibleMoves.Count - 1;
80 | else
81 | return possibleMoves.Count;
82 | }
83 |
84 | ///
85 | /// The number of moves that we could take, considering no blocking or out of board.
86 | ///
87 | public int DesiredCount
88 | {
89 | private set;
90 | get;
91 | }
92 |
93 | ///
94 | /// Tells if the direction should update the hit graph of possible move cells
95 | ///
96 | private bool updateHitGraph;
97 |
98 | public Direction(Piece piece, int x, int y, int desiredCount = 8, bool updateHitGraph = true)
99 | {
100 | Piece = piece;
101 | X = x;
102 | Y = y;
103 | DesiredCount = desiredCount;
104 | this.updateHitGraph = updateHitGraph;
105 |
106 | possibleMoves = new List();
107 | possibleMoves.AddRange(piece.Parent.OpenLineOfSight(x, y, desiredCount));
108 |
109 | foreach (ChessBoard.Cell move in possibleMoves)
110 | {
111 | if (updateHitGraph)
112 | move.HitBy.Add(Piece);
113 | }
114 | }
115 |
116 | ///
117 | /// Tells if the moved piece on the cell changed the hit state of the blocked
118 | ///
119 | /// Where the piece stands right now
120 | /// Where the piece is moved
121 | /// Hit tests this piece
122 | /// If blocked is hittable after moving the from
123 | public bool IsBlockedIfMove(ChessBoard.Cell from, ChessBoard.Cell to, ChessBoard.Cell blocked)
124 | {
125 | if (possibleMoves.Contains(blocked) && !possibleMoves.Contains(to))
126 | {
127 | //The blocked is hittable to begin with and we don't block it with a new blocker
128 | //To may still equal blocked but direction should not care about that
129 | return false;
130 | }
131 | else if (possibleMoves.Contains(from))
132 | {
133 | int toIndex = possibleMoves.IndexOf(to);
134 | if (0 <= toIndex && toIndex < possibleMoves.Count - 1)
135 | return true; //The blocker closer to the piece
136 | else
137 | {
138 | //If we moved further
139 | foreach (ChessBoard.Cell move in from.OpenLineOfSight(X, Y, DesiredCount - possibleMoves.Count))
140 | {
141 | if (move == to) //The blocker moved into the new path
142 | return true;
143 | if (move == blocked) //The blocked is hittable
144 | return false;
145 | }
146 | }
147 | }
148 |
149 | //Happens when the blocker was not cotained and the blocked was not contained a perfect combination for nothing happening
150 | return true;
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/ChessConsole/Piece.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace ChessConsole
4 | {
5 | ///
6 | /// Represents an abstract chess piece
7 | ///
8 | public abstract class Piece
9 | {
10 | ///
11 | /// Color of piece
12 | ///
13 | public PlayerColor Color
14 | {
15 | private set;
16 | get;
17 | }
18 |
19 | ///
20 | /// False by default, set to true upon first move
21 | ///
22 | public bool Moved
23 | {
24 | protected set;
25 | get;
26 | }
27 |
28 | ///
29 | /// All the moves possible to make with this piece
30 | ///
31 | public abstract IEnumerable PossibleMoves
32 | {
33 | get;
34 | }
35 |
36 | ///
37 | /// All the moves legal to make with this piece. It's a subset of .
38 | /// See also .
39 | ///
40 | public List LegalMoves
41 | {
42 | private set;
43 | get;
44 | }
45 |
46 | public ChessBoard.Cell Parent
47 | {
48 | private set;
49 | get;
50 | }
51 |
52 | public Piece(PlayerColor color)
53 | {
54 | Color = color;
55 | Moved = false;
56 | LegalMoves = new List();
57 | }
58 |
59 | ///
60 | /// Called when the piece is first placed or when the piece is replaced after promotion.
61 | /// Does not recalculate just yet, you have to call for that.
62 | ///
63 | public void OnPlace(ChessBoard.Cell cell)
64 | {
65 | Parent = cell;
66 | }
67 |
68 | ///
69 | /// Called when the piece is moved.
70 | /// Does not recalculate just yet, you have to call for that.
71 | ///
72 | public void OnMove(ChessBoard.Cell cell)
73 | {
74 | Parent = cell;
75 | Moved = true;
76 | }
77 |
78 | ///
79 | /// Recalculates the possible moves and updates the hit graph
80 | ///
81 | public abstract void Recalculate();
82 |
83 | ///
84 | /// Tells if the moved piece on the cell changed the hit state of the blocked
85 | ///
86 | /// Where the piece stands right now
87 | /// Where the piece is moved
88 | /// Hit tests this piece
89 | /// If blocked is hittable after moving the from
90 | public abstract bool IsBlockedIfMove(ChessBoard.Cell from, ChessBoard.Cell to, ChessBoard.Cell blocked);
91 |
92 | public abstract char Char { get; }
93 |
94 | protected virtual bool canHit(ChessBoard.Cell cell)
95 | {
96 | return cell != null && cell.Piece != null && cell.Piece.Color != Color;
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/ChessConsole/Pieces/Bishop.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace ChessConsole.Pieces
4 | {
5 | public class Bishop : Piece
6 | {
7 | ///
8 | /// Represents the directions of movement
9 | ///
10 | private Direction[] directions = new Direction[4];
11 |
12 | public Bishop(PlayerColor color)
13 | : base(color)
14 | {
15 | for (int i = 0; i < 4; i++)
16 | {
17 | directions[i] = null;
18 | }
19 | }
20 |
21 | public Bishop(Piece promote)
22 | : this(promote.Color)
23 | {
24 | Moved = promote.Moved;
25 | }
26 |
27 | public override IEnumerable PossibleMoves
28 | {
29 | get
30 | {
31 | foreach (Direction direction in directions)
32 | {
33 | foreach (ChessBoard.Cell cell in direction.GetPossibleMoves())
34 | {
35 | yield return cell;
36 | }
37 | }
38 | }
39 | }
40 |
41 | public override void Recalculate()
42 | {
43 | //Open up left direction and listen to it
44 | directions[0] = new Direction(this, -1, 1);
45 | //Open up right direction and listen to it
46 | directions[1] = new Direction(this, 1, 1);
47 | //Open down left direction and listen to it
48 | directions[2] = new Direction(this, -1, -1);
49 | //Open down right direction and listen to it
50 | directions[3] = new Direction(this, 1, -1);
51 | }
52 |
53 | public override bool IsBlockedIfMove(ChessBoard.Cell from, ChessBoard.Cell to, ChessBoard.Cell blocked)
54 | {
55 | foreach (Direction direction in directions)
56 | {
57 | //If any direction can hit the blocked return false
58 | if (!direction.IsBlockedIfMove(from, to, blocked)) return false;
59 | }
60 |
61 | return true;
62 | }
63 |
64 | public override char Char => 'B';
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/ChessConsole/Pieces/King.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace ChessConsole.Pieces
4 | {
5 | public class King : Piece
6 | {
7 | ///
8 | /// Represents the directions of movement
9 | ///
10 | private Direction[] directions = new Direction[8];
11 |
12 | ///
13 | /// Shows if we can castle to the left
14 | ///
15 | private bool canCastleLeft;
16 |
17 | ///
18 | /// Shows if we can castle to the right
19 | ///
20 | private bool canCastleRight;
21 |
22 | public King(PlayerColor color)
23 | : base(color)
24 | {
25 | for (int i = 0; i < 8; i++)
26 | {
27 | directions[i] = null;
28 | }
29 | }
30 |
31 | public override IEnumerable PossibleMoves
32 | {
33 | get
34 | {
35 | foreach (Direction direction in directions)
36 | {
37 | foreach (ChessBoard.Cell cell in direction.GetPossibleMoves())
38 | {
39 | yield return cell;
40 | }
41 |
42 | if (canCastleLeft)
43 | {
44 | yield return Parent.Parent.GetCell(2, (Color == PlayerColor.White) ? 0 : 7);
45 | }
46 |
47 | if (canCastleRight)
48 | {
49 | yield return Parent.Parent.GetCell(6, (Color == PlayerColor.White) ? 0 : 7);
50 | }
51 | }
52 | }
53 | }
54 |
55 | public override void Recalculate()
56 | {
57 | //If moved castling is not possible anymore an we should also remove listeners
58 | if (!Moved)
59 | {
60 | //Set it to true we'll set it to false if it wasn't true
61 | canCastleLeft = true;
62 |
63 | //Checks if the left rook is still in place and haven't moved yet
64 | ChessBoard.Cell leftRookCell = Parent.Parent.GetCell(0, (Color == PlayerColor.White) ? 0 : 7);
65 | if (leftRookCell.Piece == null || !(leftRookCell.Piece is Rook) || leftRookCell.Piece.Color != Color || leftRookCell.Piece.Moved)
66 | canCastleLeft = false;
67 | else
68 | {
69 | //Checks pieces that could block the castle
70 | for (int i = 1; i <= 3; i++)
71 | {
72 | if (Parent.Parent.GetCell(i, (Color == PlayerColor.White) ? 0 : 7).Piece != null)
73 | canCastleLeft = false;
74 | }
75 | }
76 |
77 | //Set it to true we'll set it to false if it wasn't true
78 | canCastleRight = true;
79 |
80 | //Checks if the right rook is still in place and haven't moved yet
81 | ChessBoard.Cell rightRookCell = Parent.Parent.GetCell(7, (Color == PlayerColor.White) ? 0 : 7);
82 | if (rightRookCell.Piece == null || !(rightRookCell.Piece is Rook) || rightRookCell.Piece.Color != Color || rightRookCell.Piece.Moved)
83 | canCastleRight = false;
84 | else
85 | {
86 | //Checks pieces that could block the castle
87 | for (int i = 5; i <= 6; i++)
88 | {
89 | if (Parent.Parent.GetCell(i, (Color == PlayerColor.White) ? 0 : 7).Piece != null)
90 | canCastleRight = false;
91 | }
92 | }
93 | }
94 |
95 | //Open upward direction and listen to it
96 | directions[0] = new Direction(this, 0, 1, 1);
97 | //Open downward direction and listen to it
98 | directions[1] = new Direction(this, 0, -1, 1);
99 | //Open leftward direction and listen to it
100 | directions[2] = new Direction(this, -1, 0, 1);
101 | //Open rightward direction and listen to it
102 | directions[3] = new Direction(this, 1, 0, 1);
103 | //Open up left direction and listen to it
104 | directions[4] = new Direction(this, -1, 1, 1);
105 | //Open up right direction and listen to it
106 | directions[5] = new Direction(this, 1, 1, 1);
107 | //Open down left direction and listen to it
108 | directions[6] = new Direction(this, -1, -1, 1);
109 | //Open down right direction and listen to it
110 | directions[7] = new Direction(this, 1, -1, 1);
111 | }
112 |
113 | public override bool IsBlockedIfMove(ChessBoard.Cell from, ChessBoard.Cell to, ChessBoard.Cell blocked)
114 | {
115 | foreach (Direction direction in directions)
116 | {
117 | //If any direction can hit the blocked return false
118 | if (!direction.IsBlockedIfMove(from, to, blocked))
119 | return false;
120 | }
121 |
122 | return true;
123 | }
124 |
125 | public override char Char => 'K';
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/ChessConsole/Pieces/Knight.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace ChessConsole.Pieces
4 | {
5 | public class Knight : Piece
6 | {
7 | ///
8 | /// Possible places where the knight can jump
9 | ///
10 | private ChessBoard.Cell[] possibleCells = new ChessBoard.Cell[8];
11 |
12 | public Knight(PlayerColor color)
13 | : base(color)
14 | {
15 | for (int i = 0; i < 8; i++)
16 | {
17 | possibleCells[i] = null;
18 | }
19 | }
20 |
21 | public Knight(Piece promote)
22 | : this(promote.Color)
23 | {
24 | Moved = promote.Moved;
25 | }
26 |
27 | public override IEnumerable PossibleMoves
28 | {
29 | get
30 | {
31 | foreach (ChessBoard.Cell cell in possibleCells)
32 | {
33 | if (cell != null && (cell.Piece == null || cell.Piece.Color != Color))
34 | yield return cell;
35 | }
36 | }
37 | }
38 |
39 | public override void Recalculate()
40 | {
41 | //2 up 1 left
42 | possibleCells[0] = Parent.Open(-1, 2);
43 | //2 down 1 left
44 | possibleCells[1] = Parent.Open(-1, -2);
45 | //2 up 1 right
46 | possibleCells[2] = Parent.Open(1, 2);
47 | //2 down 1 right
48 | possibleCells[3] = Parent.Open(1, -2);
49 | //1 up 2 left
50 | possibleCells[4] = Parent.Open(-2, 1);
51 | //1 down 2 left
52 | possibleCells[5] = Parent.Open(-2, -1);
53 | //1 up 2 right
54 | possibleCells[6] = Parent.Open(2, 1);
55 | //1 down 2 right
56 | possibleCells[7] = Parent.Open(2, -1);
57 |
58 | for (int i = 0; i < 8; i++)
59 | {
60 | if (possibleCells[i] != null)
61 | possibleCells[i].HitBy.Add(this);
62 | }
63 | }
64 |
65 | public override bool IsBlockedIfMove(ChessBoard.Cell from, ChessBoard.Cell to, ChessBoard.Cell blocked)
66 | {
67 | //The knight's hits cannot be blocked
68 | for (int i = 0; i < 8; i++)
69 | if (possibleCells[i] == blocked)
70 | return false;
71 |
72 | return true;
73 | }
74 |
75 | public override char Char => 'H'; //H for horse as we are using K for king
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/ChessConsole/Pieces/Pawn.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace ChessConsole.Pieces
4 | {
5 | public class Pawn : Piece
6 | {
7 | ///
8 | /// Represents the forward direction moves of the pawn
9 | ///
10 | private Direction forward = null;
11 |
12 | ///
13 | /// Represents the hittables of the pawn
14 | ///
15 | private ChessBoard.Cell[] hits = new ChessBoard.Cell[2];
16 |
17 | public Pawn(PlayerColor color)
18 | : base(color)
19 | {
20 | hits[0] = hits[1] = null;
21 | }
22 |
23 | public override IEnumerable PossibleMoves
24 | {
25 | get
26 | {
27 | foreach (ChessBoard.Cell cell in forward.GetPossibleMoves(false))
28 | {
29 | yield return cell;
30 | }
31 |
32 | if (canHit(hits[0]))
33 | yield return hits[0];
34 | if (canHit(hits[1]))
35 | yield return hits[1];
36 | }
37 | }
38 |
39 | public override void Recalculate()
40 | {
41 | //Open forward direction and listen to it
42 | forward = new Direction(this, 0, (Color == PlayerColor.White) ? 1 : -1, Moved ? 1 : 2, false);
43 |
44 | hits[0] = Parent.Open(-1, (Color == PlayerColor.White) ? 1 : -1);
45 | hits[1] = Parent.Open( 1, (Color == PlayerColor.White) ? 1 : -1);
46 |
47 | if (hits[0] != null)
48 | hits[0].HitBy.Add(this);
49 | if (hits[1] != null)
50 | hits[1].HitBy.Add(this);
51 | }
52 |
53 | public override bool IsBlockedIfMove(ChessBoard.Cell from, ChessBoard.Cell to, ChessBoard.Cell blocked)
54 | {
55 | //The pawn's hits cannot be blocked
56 | return hits[0] != blocked && hits[1] != blocked;
57 | }
58 |
59 | public override char Char => 'P';
60 |
61 | protected override bool canHit(ChessBoard.Cell cell)
62 | {
63 | //Handling en passant over here
64 | return base.canHit(cell) || (cell != null && cell == cell.Parent.EnPassant);
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/ChessConsole/Pieces/Queen.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace ChessConsole.Pieces
4 | {
5 | public class Queen : Piece
6 | {
7 | ///
8 | /// Represents the directions of movement
9 | ///
10 | private Direction[] directions = new Direction[8];
11 |
12 | public Queen(PlayerColor color)
13 | : base(color)
14 | {
15 | for (int i = 0; i < 8; i++)
16 | {
17 | directions[i] = null;
18 | }
19 | }
20 |
21 | public Queen(Piece promote)
22 | : this(promote.Color)
23 | {
24 | Moved = promote.Moved;
25 | }
26 |
27 | public override IEnumerable PossibleMoves
28 | {
29 | get
30 | {
31 | foreach (Direction direction in directions)
32 | {
33 | foreach (ChessBoard.Cell cell in direction.GetPossibleMoves())
34 | {
35 | yield return cell;
36 | }
37 | }
38 | }
39 | }
40 |
41 | public override void Recalculate()
42 | {
43 | //Open upward direction and listen to it
44 | directions[0] = new Direction(this, 0, 1);
45 | //Open downward direction and listen to it
46 | directions[1] = new Direction(this, 0, -1);
47 | //Open leftward direction and listen to it
48 | directions[2] = new Direction(this, -1, 0);
49 | //Open rightward direction and listen to it
50 | directions[3] = new Direction(this, 1, 0);
51 | //Open up left direction and listen to it
52 | directions[4] = new Direction(this, -1, 1);
53 | //Open up right direction and listen to it
54 | directions[5] = new Direction(this, 1, 1);
55 | //Open down left direction and listen to it
56 | directions[6] = new Direction(this, -1, -1);
57 | //Open down right direction and listen to it
58 | directions[7] = new Direction(this, 1, -1);
59 | }
60 |
61 | public override bool IsBlockedIfMove(ChessBoard.Cell from, ChessBoard.Cell to, ChessBoard.Cell blocked)
62 | {
63 | foreach (Direction direction in directions)
64 | {
65 | //If any direction can hit the blocked return false
66 | if (!direction.IsBlockedIfMove(from, to, blocked)) return false;
67 | }
68 |
69 | return true;
70 | }
71 |
72 | public override char Char => 'Q';
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/ChessConsole/Pieces/Rook.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace ChessConsole.Pieces
4 | {
5 | public class Rook : Piece
6 | {
7 | ///
8 | /// Represents the directions of movement
9 | ///
10 | private Direction[] directions = new Direction[4];
11 |
12 | public Rook(PlayerColor color)
13 | : base(color)
14 | {
15 | for (int i = 0; i < 4; i++)
16 | {
17 | directions[i] = null;
18 | }
19 | }
20 |
21 | public Rook(Piece promote)
22 | : this(promote.Color)
23 | {
24 | Moved = promote.Moved;
25 | }
26 |
27 | public override IEnumerable PossibleMoves
28 | {
29 | get
30 | {
31 | foreach (Direction direction in directions)
32 | {
33 | foreach (ChessBoard.Cell cell in direction.GetPossibleMoves())
34 | {
35 | yield return cell;
36 | }
37 | }
38 | }
39 | }
40 |
41 | public override void Recalculate()
42 | {
43 | //Open upward direction and listen to it
44 | directions[0] = new Direction(this, 0, 1);
45 | //Open downward direction and listen to it
46 | directions[1] = new Direction(this, 0, -1);
47 | //Open leftward direction and listen to it
48 | directions[2] = new Direction(this, -1, 0);
49 | //Open rightward direction and listen to it
50 | directions[3] = new Direction(this, 1, 0);
51 | }
52 |
53 | public override bool IsBlockedIfMove(ChessBoard.Cell from, ChessBoard.Cell to, ChessBoard.Cell blocked)
54 | {
55 | //If any direction can hit the blocked return false
56 | foreach (Direction direction in directions)
57 | {
58 | //If any direction can hit the blocked return false
59 | if (!direction.IsBlockedIfMove(from, to, blocked)) return false;
60 | }
61 |
62 | return true;
63 | }
64 |
65 | public override char Char => 'R';
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/ChessConsole/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ChessConsole
4 | {
5 | public class Program
6 | {
7 | static ChessGame game;
8 | static void Main(string[] args)
9 | {
10 | Console.CursorVisible = false;
11 | ConsoleGraphics graphics = new ConsoleGraphics();
12 | game = new ChessGame();
13 |
14 | do
15 | {
16 | game.Draw(graphics);
17 | graphics.SwapBuffers();
18 | game.Update();
19 | } while (game.Running);
20 |
21 | Console.Read();
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ChessConsole/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("ChessConsole")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("ChessConsole")]
13 | [assembly: AssemblyCopyright("Copyright © 2018")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("66f5e978-f5c6-4a48-a689-a6f1e22be741")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ConsoleChess
2 |
3 | A simple chess game in the C# console
4 |
5 | ## If you find an issue:
6 |
7 | To help development please provide the moves performed to reproduce this bug. If the bug is not always reproducable, steps can still help.
8 |
9 | Moves should be provided as:
10 |
11 | `FROM -> TO (promotion if promoted)`
12 | eg. `G2->F2`
13 |
14 | ## ChessBoard Features:
15 |
16 | - [x] All pieces with basic movement
17 | - [x] Line of sight testing for appropriate pieces
18 | - [x] En passant
19 | - [x] Castling
20 | - [x] Promotion
21 | - [x] Hit graphs for legal move resolution
22 | - [x] Legal move resolution - so that you can't put yourself in check
23 | - [x] Check and any legal move detection
24 |
25 | ## Documentation for the standalone ChessBoard:
26 |
27 | This section provides information to use the `ChessBoard` class with your own `Game` class.
28 |
29 | ### TurnStart
30 |
31 | You have to call this method everyturn and after every move.
32 |
33 | At turn start you might want to check for win and draw conditions. The two easiest (checkmate/stalemate) can be done this way:
34 |
35 | ```csharp
36 | bool hasMoves = board.TurnStart(currentPlayer);
37 | if (!hasMoves)
38 | {
39 | //Turn start sets up a cached inCheck value as well
40 | if (board.IsInCheck(currentPlayer))
41 | {
42 | //Checkmate, win for (currentPlayer == PlayerColor.White) ? Black : White
43 | }
44 | else
45 | {
46 | //Stalemate, draw
47 | }
48 | }
49 | ```
50 |
51 | ### Moving
52 |
53 | The board has no way of knowing that you picked a piece or are about to put down a piece.
54 |
55 | I'm going to provide you with typical examples of what you would want to accomplish.
56 |
57 | #### Picking a piece
58 |
59 | ```csharp
60 | Piece holding = board.GetCell(cursorX, cursorY).Piece;
61 |
62 | if (holding != null)
63 | {
64 | if (holding.LegalMoves.Count == 0)
65 | {
66 | //Do some sort of unpicking logic so that you can't get stuck with picking a piece that can't move
67 | holding = null; //As an example
68 | }
69 | else
70 | {
71 | //Set internal state variables to allow for movement etc.
72 | }
73 | }
74 | ```
75 |
76 | #### Attempting to put down a piece
77 |
78 | Before attempting to put down a piece, you should ask the board if a promotion may occur. The system was designed to be able to cancel this promotion process. And you obviously need to not move before being able to cancel it. This is why the `Move` metod takes `PromoteOptions` as parameter. It will automatically perform the promotion of a pawn if it has to. If a promotion will however not occur, this parameter will be ignored so you can safely pass anything as your default.
79 |
80 | To decide if a promotion is about to occur, then use the `IsPromotable` function.
81 |
82 | ```csharp
83 | to = board.GetCell(cursorX, cursorY);
84 |
85 | if (holding != null) //We'll check, even though you should have logic to not be able to try move if you don't hold anything
86 | {
87 | //Check if it's contained as a legal move
88 | if (!holding.LegalMoves.Contains(to))
89 | {
90 | //You might wanna cancel the current move here
91 |
92 | return; //assuming this is a function
93 | }
94 |
95 | //This function tells if your piece (usually a pawn) is about to be promoted
96 | if (board.IsPromotable(holding.Parent, to))
97 | {
98 | //Handle promotion...
99 | }
100 |
101 | //Finally tell the board to move the piece
102 | board.Move(holding.Parent, to, currentPromoteOption);
103 | }
104 | ```
105 |
106 | ### Starting a new game
107 |
108 | Constructing a chess board automatically resets the game, so that's a viable option.
109 |
110 | If you'd like the board also contains a Reset method which does exactly the same thing as the constructor.
111 |
112 | ```csharp
113 |
114 | board = new ChessBoard();
115 | //...
116 | //Worst game loop ever
117 | while (!exit)
118 | {
119 | //...
120 | while (!gameOver)
121 | {
122 | //...
123 | }
124 |
125 | //...
126 | board.Reset();
127 | }
128 | ```
--------------------------------------------------------------------------------
|