Skip to content

Commit

Permalink
Merge pull request dotnet#40 from Wycott/main
Browse files Browse the repository at this point in the history
Checkers
  • Loading branch information
ZacharyPatten authored Jun 23, 2022
2 parents a50d1b2 + 99c09b1 commit 64281b8
Show file tree
Hide file tree
Showing 28 changed files with 1,272 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ indent_style = tab

# IDE0160: Convert to file-scoped namespace
csharp_style_namespace_declarations = file_scoped:warning

# IDE0042: Deconstruct variable declaration
csharp_style_deconstructed_variable_declaration = false
24 changes: 24 additions & 0 deletions .github/workflows/Checkers Build.yml
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
10 changes: 10 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,16 @@
"console": "externalTerminal",
"stopAtEntry": false,
},
{
"name": "Checkers",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "Build Checkers",
"program": "${workspaceFolder}/Projects/Checkers/bin/Debug/Checkers.dll",
"cwd": "${workspaceFolder}/Projects/Checkers/bin/Debug",
"console": "externalTerminal",
"stopAtEntry": false,
},
{
"name": "Blackjack",
"type": "coreclr",
Expand Down
13 changes: 13 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@
"version": "2.0.0",
"tasks":
[
{
"label": "Build Checkers",
"command": "dotnet",
"type": "process",
"args":
[
"build",
"${workspaceFolder}/Projects/Checkers/Checkers.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary",
],
"problemMatcher": "$msCompile",
},
{
"label": "Build Duck Hunt",
"command": "dotnet",
Expand Down
171 changes: 171 additions & 0 deletions Projects/Checkers/Board.cs
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"/>-&gt;<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;
}
}
8 changes: 8 additions & 0 deletions Projects/Checkers/Checkers.csproj
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>
68 changes: 68 additions & 0 deletions Projects/Checkers/Game.cs
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);
}
17 changes: 17 additions & 0 deletions Projects/Checkers/Move.cs
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;
}
}
18 changes: 18 additions & 0 deletions Projects/Checkers/Piece.cs
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; }
}
7 changes: 7 additions & 0 deletions Projects/Checkers/PieceColor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Checkers;

public enum PieceColor
{
Black = 1,
White = 2,
}
13 changes: 13 additions & 0 deletions Projects/Checkers/Player.cs
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;
}
}
Loading

0 comments on commit 64281b8

Please sign in to comment.