Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Checkers #40

Merged
merged 68 commits into from
Jun 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
b1c3aeb
Initial cut
Wycott Jun 16, 2022
edab10f
Overhaul the way moves are made, remove some cruft from the engine, a…
Wycott Jun 17, 2022
e171168
prompt user on invalid input rather than default
ZacharyPatten Jun 18, 2022
757488c
implicit usings -> global usings
ZacharyPatten Jun 18, 2022
3ab4529
minor clean up
ZacharyPatten Jun 18, 2022
42b44a1
mor appropriate exception
ZacharyPatten Jun 18, 2022
33c05b9
game state loop clean up: handle each state in a seperate method
ZacharyPatten Jun 18, 2022
0b2168c
NewGuid for random selection is unnecessary
ZacharyPatten Jun 18, 2022
3a73f32
user confirmation > Thread.Sleep
ZacharyPatten Jun 18, 2022
b098a15
root readme +Checkers
ZacharyPatten Jun 19, 2022
2573823
slnf +Checkers
ZacharyPatten Jun 19, 2022
4ace7b9
Checkers Build Action
ZacharyPatten Jun 19, 2022
62002c7
Visual Studio Code Checkers launch
ZacharyPatten Jun 19, 2022
f82f271
Checkers readme
ZacharyPatten Jun 19, 2022
e3a82dd
always list the default enum value first with value 0
ZacharyPatten Jun 19, 2022
07db906
file names should match class names
ZacharyPatten Jun 19, 2022
57e559b
remove unnecessary using (the Thread.Sleep was previously removed)
ZacharyPatten Jun 19, 2022
a94140e
refactor coordinates (1..8->0..7) and position helper and move BoardH…
ZacharyPatten Jun 19, 2022
37436f5
moved DisplayHelper into Display
ZacharyPatten Jun 19, 2022
15b9abc
press key to continue clean up
ZacharyPatten Jun 19, 2022
fe4f45e
log file based on command line args rather than a const variable
ZacharyPatten Jun 19, 2022
9ea5e69
white space
ZacharyPatten Jun 19, 2022
1133813
moved input/output into Program.cs
ZacharyPatten Jun 21, 2022
9897b5c
moved position logic into Board.cs
ZacharyPatten Jun 21, 2022
2e30535
moved initial board layout into Board.cs
ZacharyPatten Jun 21, 2022
fbc72ab
removed InPlay from Piece
ZacharyPatten Jun 21, 2022
7b05b54
rename Color types and properties
ZacharyPatten Jun 21, 2022
60f53a2
rename GameState to ProgramState
ZacharyPatten Jun 21, 2022
d235f36
move Player initialization logic into Game.cs
ZacharyPatten Jun 21, 2022
65548ef
various clean up
ZacharyPatten Jun 21, 2022
03c294d
fix inverted y-axis
ZacharyPatten Jun 21, 2022
7cf9a87
removed "NotSet" PieceColor in favor of nullability
ZacharyPatten Jun 21, 2022
d044761
added human player count exception check on game constructor
ZacharyPatten Jun 21, 2022
d55cf1a
various clean up
ZacharyPatten Jun 21, 2022
5a63595
renamed Types -> Enums
ZacharyPatten Jun 21, 2022
7219478
removed logging (might add back in future)
ZacharyPatten Jun 21, 2022
7944add
various clean up
ZacharyPatten Jun 21, 2022
b29482e
remove DeriveToPosition (you can just multiply by 2)
ZacharyPatten Jun 21, 2022
d02499f
removed MoveCount because it wasn't calculating correctly
ZacharyPatten Jun 21, 2022
47af07b
clean up
ZacharyPatten Jun 21, 2022
857b2df
remove PlayerAction (missed when removed logging)
ZacharyPatten Jun 21, 2022
f0bf9e0
removed MoveType
ZacharyPatten Jun 21, 2022
7c9e2ff
removed MoveOutcome.Unkown
ZacharyPatten Jun 21, 2022
83f5c7f
rename NextRound to NextTurn
ZacharyPatten Jun 21, 2022
99590bb
moved Aggressor from Piece to Board (there can only be one aggressor …
ZacharyPatten Jun 21, 2022
b4675fc
fix aggressor reset bug
ZacharyPatten Jun 21, 2022
8eb2db8
removed ProgramState
ZacharyPatten Jun 21, 2022
567db32
moved VectorHelper into Engine
ZacharyPatten Jun 21, 2022
c08b7db
renamed Piece XPosition & YPosition to X & Y
ZacharyPatten Jun 21, 2022
9ff992f
adjustments to output
ZacharyPatten Jun 21, 2022
cf4b4bd
removed Direction; enums are great, but if it is only used inside one…
ZacharyPatten Jun 21, 2022
28c4979
removed PlayerControl; enums are great, but if there are only 2 value…
ZacharyPatten Jun 21, 2022
aa1ddf6
moved enums out of Enums folder (if there are only two types in there…
ZacharyPatten Jun 21, 2022
c883903
global using static Black/White
ZacharyPatten Jun 21, 2022
6757b2a
removed GetPointDistance; the distance between points is only used fo…
ZacharyPatten Jun 22, 2022
1e70415
"side" -> "color"
ZacharyPatten Jun 22, 2022
6515f2c
'.' -> '·' (period to middle dot for vacant tiles)
ZacharyPatten Jun 22, 2022
e54af78
clean up Program.cs
ZacharyPatten Jun 22, 2022
36175e4
added Visualized for Aggressor
ZacharyPatten Jun 22, 2022
b79c2ed
reimplementations
ZacharyPatten Jun 22, 2022
b5c477a
menu input adjustment
ZacharyPatten Jun 22, 2022
ace71b7
intro screen and readme update
ZacharyPatten Jun 23, 2022
04e7266
moved Checkers to Weight of 4 in root README.md
ZacharyPatten Jun 23, 2022
6fa4570
Checkers blazor port
ZacharyPatten Jun 23, 2022
9c14b10
added exception text that matches condition check
ZacharyPatten Jun 23, 2022
029f097
unnecessary qualification
ZacharyPatten Jun 23, 2022
880198f
clean up from feedback on pull request
ZacharyPatten Jun 23, 2022
a7f2c78
ValidateMove -> ValidateDiagonalMove
ZacharyPatten Jun 23, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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},
ZacharyPatten marked this conversation as resolved.
Show resolved Hide resolved
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)
ZacharyPatten marked this conversation as resolved.
Show resolved Hide resolved
{
if (notation is null) throw new ArgumentNullException(nameof(notation));
ZacharyPatten marked this conversation as resolved.
Show resolved Hide resolved
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)
ZacharyPatten marked this conversation as resolved.
Show resolved Hide resolved
{
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;
ZacharyPatten marked this conversation as resolved.
Show resolved Hide resolved
(int X, int Y) target = (piece.X + dx, piece.Y + dy);
ZacharyPatten marked this conversation as resolved.
Show resolved Hide resolved
if (!IsValidPosition(target.X, target.Y)) return;
PieceColor? targetColor = this[target.X, target.Y]?.Color;
ZacharyPatten marked this conversation as resolved.
Show resolved Hide resolved
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;
ZacharyPatten marked this conversation as resolved.
Show resolved Hide resolved
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">
ZacharyPatten marked this conversation as resolved.
Show resolved Hide resolved
<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