forked from dotnet/dotnet-console-games
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request dotnet#40 from Wycott/main
Checkers
- Loading branch information
Showing
28 changed files
with
1,272 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
name: Checkers Build | ||
on: | ||
push: | ||
paths: | ||
- 'Projects/Checkers/**' | ||
branches: | ||
- main | ||
pull_request: | ||
paths: | ||
- 'Projects/Checkers/**' | ||
branches: | ||
- main | ||
workflow_dispatch: | ||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v1 | ||
- name: setup dotnet | ||
uses: actions/setup-dotnet@v1 | ||
with: | ||
dotnet-version: 6.0.x | ||
- name: dotnet build | ||
run: dotnet build "Projects\Checkers\Checkers.csproj" --configuration Release |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
namespace Checkers; | ||
|
||
public class Board | ||
{ | ||
public List<Piece> Pieces { get; } | ||
|
||
public Piece? Aggressor { get; set; } | ||
|
||
public Piece? this[int x, int y] => | ||
Pieces.FirstOrDefault(piece => piece.X == x && piece.Y == y); | ||
|
||
public Board() | ||
{ | ||
Aggressor = null; | ||
Pieces = new List<Piece> | ||
{ | ||
new() { NotationPosition ="A3", Color = Black}, | ||
new() { NotationPosition ="A1", Color = Black}, | ||
new() { NotationPosition ="B2", Color = Black}, | ||
new() { NotationPosition ="C3", Color = Black}, | ||
new() { NotationPosition ="C1", Color = Black}, | ||
new() { NotationPosition ="D2", Color = Black}, | ||
new() { NotationPosition ="E3", Color = Black}, | ||
new() { NotationPosition ="E1", Color = Black}, | ||
new() { NotationPosition ="F2", Color = Black}, | ||
new() { NotationPosition ="G3", Color = Black}, | ||
new() { NotationPosition ="G1", Color = Black}, | ||
new() { NotationPosition ="H2", Color = Black}, | ||
|
||
new() { NotationPosition ="A7", Color = White}, | ||
new() { NotationPosition ="B8", Color = White}, | ||
new() { NotationPosition ="B6", Color = White}, | ||
new() { NotationPosition ="C7", Color = White}, | ||
new() { NotationPosition ="D8", Color = White}, | ||
new() { NotationPosition ="D6", Color = White}, | ||
new() { NotationPosition ="E7", Color = White}, | ||
new() { NotationPosition ="F8", Color = White}, | ||
new() { NotationPosition ="F6", Color = White}, | ||
new() { NotationPosition ="G7", Color = White}, | ||
new() { NotationPosition ="H8", Color = White}, | ||
new() { NotationPosition ="H6", Color = White} | ||
}; | ||
} | ||
|
||
public static string ToPositionNotationString(int x, int y) | ||
{ | ||
if (!IsValidPosition(x, y)) throw new ArgumentException("Not a valid position!"); | ||
return $"{(char)('A' + x)}{y + 1}"; | ||
} | ||
|
||
public static (int X, int Y) ParsePositionNotation(string notation) | ||
{ | ||
if (notation is null) throw new ArgumentNullException(nameof(notation)); | ||
notation = notation.Trim().ToUpper(); | ||
if (notation.Length is not 2 || | ||
notation[0] < 'A' || 'H' < notation[0] || | ||
notation[1] < '1' || '8' < notation[1]) | ||
throw new FormatException($@"{nameof(notation)} ""{notation}"" is not valid"); | ||
return (notation[0] - 'A', notation[1] - '1'); | ||
} | ||
|
||
public static bool IsValidPosition(int x, int y) => | ||
0 <= x && x < 8 && | ||
0 <= y && y < 8; | ||
|
||
public (Piece A, Piece B) GetClosestRivalPieces(PieceColor priorityColor) | ||
{ | ||
double minDistanceSquared = double.MaxValue; | ||
(Piece A, Piece B) closestRivals = (null!, null!); | ||
foreach (Piece a in Pieces.Where(piece => piece.Color == priorityColor)) | ||
{ | ||
foreach (Piece b in Pieces.Where(piece => piece.Color != priorityColor)) | ||
{ | ||
(int X, int Y) vector = (a.X - b.X, a.Y - b.Y); | ||
double distanceSquared = vector.X * vector.X + vector.Y * vector.Y; | ||
if (distanceSquared < minDistanceSquared) | ||
{ | ||
minDistanceSquared = distanceSquared; | ||
closestRivals = (a, b); | ||
} | ||
} | ||
} | ||
return closestRivals; | ||
} | ||
|
||
public List<Move> GetPossibleMoves(PieceColor color) | ||
{ | ||
List<Move> moves = new(); | ||
if (Aggressor is not null) | ||
{ | ||
if (Aggressor.Color != color) | ||
{ | ||
throw new Exception($"{nameof(Aggressor)} is not null && {nameof(Aggressor)}.{nameof(Aggressor.Color)} != {nameof(color)}"); | ||
} | ||
moves.AddRange(GetPossibleMoves(Aggressor).Where(move => move.PieceToCapture is not null)); | ||
} | ||
else | ||
{ | ||
foreach (Piece piece in Pieces.Where(piece => piece.Color == color)) | ||
{ | ||
moves.AddRange(GetPossibleMoves(piece)); | ||
} | ||
} | ||
return moves.Any(move => move.PieceToCapture is not null) | ||
? moves.Where(move => move.PieceToCapture is not null).ToList() | ||
: moves; | ||
} | ||
|
||
public List<Move> GetPossibleMoves(Piece piece) | ||
{ | ||
List<Move> moves = new(); | ||
ValidateDiagonalMove(-1, -1); | ||
ValidateDiagonalMove(-1, 1); | ||
ValidateDiagonalMove( 1, -1); | ||
ValidateDiagonalMove( 1, 1); | ||
return moves.Any(move => move.PieceToCapture is not null) | ||
? moves.Where(move => move.PieceToCapture is not null).ToList() | ||
: moves; | ||
|
||
void ValidateDiagonalMove(int dx, int dy) | ||
{ | ||
if (!piece.Promoted && piece.Color is Black && dy is -1) return; | ||
if (!piece.Promoted && piece.Color is White && dy is 1) return; | ||
(int X, int Y) target = (piece.X + dx, piece.Y + dy); | ||
if (!IsValidPosition(target.X, target.Y)) return; | ||
PieceColor? targetColor = this[target.X, target.Y]?.Color; | ||
if (targetColor is null) | ||
{ | ||
if (!IsValidPosition(target.X, target.Y)) return; | ||
Move newMove = new(piece, target); | ||
moves.Add(newMove); | ||
} | ||
else if (targetColor != piece.Color) | ||
{ | ||
(int X, int Y) jump = (piece.X + 2 * dx, piece.Y + 2 * dy); | ||
if (!IsValidPosition(jump.X, jump.Y)) return; | ||
PieceColor? jumpColor = this[jump.X, jump.Y]?.Color; | ||
if (jumpColor is not null) return; | ||
Move attack = new(piece, jump, this[target.X, target.Y]); | ||
moves.Add(attack); | ||
} | ||
} | ||
} | ||
|
||
/// <summary>Returns a <see cref="Move"/> if <paramref name="from"/>-><paramref name="to"/> is valid or null if not.</summary> | ||
public Move? ValidateMove(PieceColor color, (int X, int Y) from, (int X, int Y) to) | ||
{ | ||
Piece? piece = this[from.X, from.Y]; | ||
if (piece is null) | ||
{ | ||
return null; | ||
} | ||
foreach (Move move in GetPossibleMoves(color)) | ||
{ | ||
if ((move.PieceToMove.X, move.PieceToMove.Y) == from && move.To == to) | ||
{ | ||
return move; | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
public static bool IsTowards(Move move, Piece piece) | ||
{ | ||
(int Dx, int Dy) a = (move.PieceToMove.X - piece.X, move.PieceToMove.Y - piece.Y); | ||
int a_distanceSquared = a.Dx * a.Dx + a.Dy * a.Dy; | ||
(int Dx, int Dy) b = (move.To.X - piece.X, move.To.Y - piece.Y); | ||
int b_distanceSquared = b.Dx * b.Dx + b.Dy * b.Dy; | ||
return b_distanceSquared < a_distanceSquared; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net6.0</TargetFramework> | ||
<ImplicitUsings>disable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
namespace Checkers; | ||
|
||
public class Game | ||
{ | ||
private const int PiecesPerColor = 12; | ||
|
||
public PieceColor Turn { get; private set; } | ||
public Board Board { get; } | ||
public PieceColor? Winner { get; private set; } | ||
public List<Player> Players { get; } | ||
|
||
public Game(int humanPlayerCount) | ||
{ | ||
if (humanPlayerCount < 0 || 2 < humanPlayerCount) throw new ArgumentOutOfRangeException(nameof(humanPlayerCount)); | ||
Board = new Board(); | ||
Players = new() | ||
{ | ||
new Player(humanPlayerCount >= 1, Black), | ||
new Player(humanPlayerCount >= 2, White), | ||
}; | ||
Turn = Black; | ||
Winner = null; | ||
} | ||
|
||
public void PerformMove(Move move) | ||
{ | ||
(move.PieceToMove.X, move.PieceToMove.Y) = move.To; | ||
if ((move.PieceToMove.Color is Black && move.To.Y is 7) || | ||
(move.PieceToMove.Color is White && move.To.Y is 0)) | ||
{ | ||
move.PieceToMove.Promoted = true; | ||
} | ||
if (move.PieceToCapture is not null) | ||
{ | ||
Board.Pieces.Remove(move.PieceToCapture); | ||
} | ||
if (move.PieceToCapture is not null && | ||
Board.GetPossibleMoves(move.PieceToMove).Any(m => m.PieceToCapture is not null)) | ||
{ | ||
Board.Aggressor = move.PieceToMove; | ||
} | ||
else | ||
{ | ||
Board.Aggressor = null; | ||
Turn = Turn is Black ? White : Black; | ||
} | ||
CheckForWinner(); | ||
} | ||
|
||
public void CheckForWinner() | ||
{ | ||
if (!Board.Pieces.Any(piece => piece.Color is Black)) | ||
{ | ||
Winner = White; | ||
} | ||
if (!Board.Pieces.Any(piece => piece.Color is White)) | ||
{ | ||
Winner = Black; | ||
} | ||
if (Winner is null && Board.GetPossibleMoves(Turn).Count is 0) | ||
{ | ||
Winner = Turn is Black ? White : Black; | ||
} | ||
} | ||
|
||
public int TakenCount(PieceColor colour) => | ||
PiecesPerColor - Board.Pieces.Count(piece => piece.Color == colour); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
namespace Checkers; | ||
|
||
public class Move | ||
{ | ||
public Piece PieceToMove { get; set; } | ||
|
||
public (int X, int Y) To { get; set; } | ||
|
||
public Piece? PieceToCapture { get; set; } | ||
|
||
public Move(Piece pieceToMove, (int X, int Y) to, Piece? pieceToCapture = null) | ||
{ | ||
PieceToMove = pieceToMove; | ||
To = to; | ||
PieceToCapture = pieceToCapture; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
namespace Checkers; | ||
|
||
public class Piece | ||
{ | ||
public int X { get; set; } | ||
|
||
public int Y { get; set; } | ||
|
||
public string NotationPosition | ||
{ | ||
get => Board.ToPositionNotationString(X, Y); | ||
set => (X, Y) = Board.ParsePositionNotation(value); | ||
} | ||
|
||
public PieceColor Color { get; set; } | ||
|
||
public bool Promoted { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace Checkers; | ||
|
||
public enum PieceColor | ||
{ | ||
Black = 1, | ||
White = 2, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
namespace Checkers; | ||
|
||
public class Player | ||
{ | ||
public bool IsHuman { get; } | ||
public PieceColor Color { get; } | ||
|
||
public Player(bool isHuman, PieceColor color) | ||
{ | ||
IsHuman = isHuman; | ||
Color = color; | ||
} | ||
} |
Oops, something went wrong.