From b1c3aeba676301f15f00f203e17faa1fc850eed5 Mon Sep 17 00:00:00 2001 From: Rob Docherty Date: Thu, 16 Jun 2022 23:33:06 +0100 Subject: [PATCH 01/68] Initial cut --- Projects/Checkers/Board.cs | 18 + Projects/Checkers/Checkers.csproj | 10 + Projects/Checkers/Data/KnowledgeBase.cs | 58 ++ Projects/Checkers/Display.cs | 99 ++++ Projects/Checkers/Engine.cs | 523 +++++++++++++++++++ Projects/Checkers/Game.cs | 115 ++++ Projects/Checkers/Helpers/BoardHelper.cs | 64 +++ Projects/Checkers/Helpers/LoggingHelper.cs | 63 +++ Projects/Checkers/Helpers/PlayerHelper.cs | 43 ++ Projects/Checkers/Helpers/PositionHelper.cs | 55 ++ Projects/Checkers/Helpers/VectorHelper.cs | 111 ++++ Projects/Checkers/Move.cs | 18 + Projects/Checkers/Piece.cs | 41 ++ Projects/Checkers/Player.cs | 9 + Projects/Checkers/Program.cs | 165 ++++++ Projects/Checkers/Types/DirectionType.cs | 9 + Projects/Checkers/Types/GameStateType.cs | 9 + Projects/Checkers/Types/MoveOutcomeType.cs | 13 + Projects/Checkers/Types/MoveType.cs | 9 + Projects/Checkers/Types/PieceColourType.cs | 8 + Projects/Checkers/Types/PlayerActionType.cs | 12 + Projects/Checkers/Types/PlayerControlType.cs | 7 + dotnet-console-games-and-website.sln | 6 + 23 files changed, 1465 insertions(+) create mode 100644 Projects/Checkers/Board.cs create mode 100644 Projects/Checkers/Checkers.csproj create mode 100644 Projects/Checkers/Data/KnowledgeBase.cs create mode 100644 Projects/Checkers/Display.cs create mode 100644 Projects/Checkers/Engine.cs create mode 100644 Projects/Checkers/Game.cs create mode 100644 Projects/Checkers/Helpers/BoardHelper.cs create mode 100644 Projects/Checkers/Helpers/LoggingHelper.cs create mode 100644 Projects/Checkers/Helpers/PlayerHelper.cs create mode 100644 Projects/Checkers/Helpers/PositionHelper.cs create mode 100644 Projects/Checkers/Helpers/VectorHelper.cs create mode 100644 Projects/Checkers/Move.cs create mode 100644 Projects/Checkers/Piece.cs create mode 100644 Projects/Checkers/Player.cs create mode 100644 Projects/Checkers/Program.cs create mode 100644 Projects/Checkers/Types/DirectionType.cs create mode 100644 Projects/Checkers/Types/GameStateType.cs create mode 100644 Projects/Checkers/Types/MoveOutcomeType.cs create mode 100644 Projects/Checkers/Types/MoveType.cs create mode 100644 Projects/Checkers/Types/PieceColourType.cs create mode 100644 Projects/Checkers/Types/PlayerActionType.cs create mode 100644 Projects/Checkers/Types/PlayerControlType.cs diff --git a/Projects/Checkers/Board.cs b/Projects/Checkers/Board.cs new file mode 100644 index 00000000..4340c6f0 --- /dev/null +++ b/Projects/Checkers/Board.cs @@ -0,0 +1,18 @@ +using Checkers.Helpers; + +namespace Checkers; + +public class Board +{ + public List Pieces { get; set; } + + public Board() + { + Pieces = BoardHelper.GetStartingPosition(); + } + + public Board(List startingPosition) + { + Pieces = startingPosition; + } +} diff --git a/Projects/Checkers/Checkers.csproj b/Projects/Checkers/Checkers.csproj new file mode 100644 index 00000000..74abf5c9 --- /dev/null +++ b/Projects/Checkers/Checkers.csproj @@ -0,0 +1,10 @@ + + + + Exe + net6.0 + enable + enable + + + diff --git a/Projects/Checkers/Data/KnowledgeBase.cs b/Projects/Checkers/Data/KnowledgeBase.cs new file mode 100644 index 00000000..0c76ccae --- /dev/null +++ b/Projects/Checkers/Data/KnowledgeBase.cs @@ -0,0 +1,58 @@ +using Checkers.Types; + +namespace Checkers.Data; + +/// +/// Stores the starting position and any custom permutation for testing +/// +public static class KnowledgeBase +{ + public static List GetStartingPosition() + { + var retVal = new List + { + new() { InitialPosition ="A3", Side = PieceColour.Black}, + new() { InitialPosition ="A1", Side = PieceColour.Black}, + new() { InitialPosition ="B2", Side = PieceColour.Black}, + new() { InitialPosition ="C3", Side = PieceColour.Black}, + new() { InitialPosition ="C1", Side = PieceColour.Black}, + new() { InitialPosition ="D2", Side = PieceColour.Black}, + new() { InitialPosition ="E3", Side = PieceColour.Black}, + new() { InitialPosition ="E1", Side = PieceColour.Black}, + new() { InitialPosition ="F2", Side = PieceColour.Black}, + new() { InitialPosition ="G3", Side = PieceColour.Black}, + new() { InitialPosition ="G1", Side = PieceColour.Black}, + new() { InitialPosition ="H2", Side = PieceColour.Black}, + new() { InitialPosition ="A7",Side = PieceColour.White}, + new() { InitialPosition ="B8",Side = PieceColour.White}, + new() { InitialPosition ="B6",Side = PieceColour.White}, + new() { InitialPosition ="C7",Side = PieceColour.White}, + new() { InitialPosition ="D8",Side = PieceColour.White}, + new() { InitialPosition ="D6",Side = PieceColour.White}, + new() { InitialPosition ="E7",Side = PieceColour.White}, + new() { InitialPosition ="F8",Side = PieceColour.White}, + new() { InitialPosition ="F6",Side = PieceColour.White}, + new() { InitialPosition ="G7",Side = PieceColour.White}, + new() { InitialPosition ="H8",Side = PieceColour.White}, + new() { InitialPosition ="H6",Side = PieceColour.White} + }; + + return retVal; + } + + public static List GetLimitedStartingPosition() + { + var retVal = new List + { + new () { InitialPosition ="H2", Side = PieceColour.Black}, + new () { InitialPosition ="A1", Side = PieceColour.Black}, + new () { InitialPosition ="G3", Side = PieceColour.White}, + new() { InitialPosition = "E5", Side = PieceColour.White} + }; + + return retVal; + } + + +} + diff --git a/Projects/Checkers/Display.cs b/Projects/Checkers/Display.cs new file mode 100644 index 00000000..9820747f --- /dev/null +++ b/Projects/Checkers/Display.cs @@ -0,0 +1,99 @@ +using Checkers.Types; + +namespace Checkers; + +public static class Display +{ + private const string BlackPiece = "○"; + private const string BlackKing = "☺"; + private const string WhitePiece = "◙"; + private const string WhiteKing = "☻"; + + public static void DisplayBoard(Board currentBoard) + { + PrintBoard(); + PrintPieces(currentBoard); + Console.CursorVisible = false; + } + + public static void DisplayStats(int whitesTaken, int blacksTaken) + { + Console.SetCursorPosition(20, 6); + Console.WriteLine(" Taken:"); + Console.SetCursorPosition(20, 7); + Console.WriteLine($"{whitesTaken.ToString(),2} x {WhitePiece}"); + Console.SetCursorPosition(20, 8); + Console.WriteLine($"{blacksTaken.ToString(),2} x {BlackPiece}"); + } + + public static void DisplayCurrentPlayer(PieceColour currentSide) + { + Console.SetCursorPosition(0, 11); + Console.WriteLine($"{currentSide} to play"); + } + + public static void DisplayWinner(PieceColour winningSide) + { + Console.SetCursorPosition(0, 11); + Console.WriteLine($"*** {winningSide} wins ***"); + } + + private static void PrintPieces(Board currentBoard) + { + foreach (var piece in currentBoard.Pieces.Where(x => x.InPlay)) + { + var actualX = piece.XPosition * 2 + 1; + var actualY = piece.YPosition + 0; + var displayPiece = GetDisplayPiece(piece); + Console.SetCursorPosition(actualX, actualY); + Console.Write(displayPiece); + } + } + + private static string GetDisplayPiece(Piece currentPiece) + { + string retVal; + + if (currentPiece.Side == PieceColour.Black) + { + retVal = currentPiece.Promoted ? BlackKing : BlackPiece; + } + else + { + retVal = currentPiece.Promoted ? WhiteKing : WhitePiece; + } + + return retVal; + } + + private static void PrintBoard() + { + Console.Clear(); + var emptyBoard = GetBlankBoard(); + + foreach (var rank in emptyBoard) + { + Console.WriteLine(rank); + } + } + + private static List GetBlankBoard() + { + var retVal = new List + { + $" ╔═════════════════╗", + $"8║ . . . . . . . . ║ {BlackPiece} = Black", + $"7║ . . . . . . . . ║ {BlackKing} = Black King", + $"6║ . . . . . . . . ║ {WhitePiece} = White", + $"5║ . . . . . . . . ║ {WhiteKing} = White King", + $"4║ . . . . . . . . ║", + $"3║ . . . . . . . . ║", + $"2║ . . . . . . . . ║", + $"1║ . . . . . . . . ║", + $" ╚═════════════════╝", + $" A B C D E F G H" + }; + + return retVal; + } +} diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs new file mode 100644 index 00000000..2dc04cbd --- /dev/null +++ b/Projects/Checkers/Engine.cs @@ -0,0 +1,523 @@ +using System.Drawing; +using Checkers.Helpers; +using Checkers.Types; + +namespace Checkers; + +public static class Engine +{ + public static MoveOutcome PlayNextMove(PieceColour currentSide, Board currentBoard, Point? playerFrom = null, Point? playerTo = null) + { + List possibleMoves; + MoveOutcome outcome; + + if (playerFrom != null && playerTo != null) + { + var playerMove = BuildPlayerMove(playerFrom, playerTo, currentBoard); + if (playerMove != null) possibleMoves = new List { playerMove }; + outcome = GetAllPossiblePlayerMoves(currentSide, currentBoard, out possibleMoves); + + if (possibleMoves.Count == 0) + { + outcome = MoveOutcome.NoMoveAvailable; + } + else + { + if (MoveIsValid(playerFrom, (Point)playerTo, possibleMoves, currentBoard, currentSide, out var selectedMove)) + { + possibleMoves.Clear(); + + if (selectedMove != null) + { + possibleMoves.Add(selectedMove); + + switch (selectedMove.TypeOfMove) + { + case MoveType.Unknown: + break; + case MoveType.StandardMove: + outcome = MoveOutcome.ValidMoves; + break; + case MoveType.Capture: + outcome = MoveOutcome.Capture; + break; + case MoveType.EndGame: + outcome = MoveOutcome.EndGame; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + } + } + else + { + outcome = AnalysePosition(currentSide, currentBoard, out possibleMoves); + } + + // If a side can't play then other side wins + if (outcome == MoveOutcome.NoMoveAvailable) + { + if (currentSide == PieceColour.Black) + { + outcome = MoveOutcome.WhiteWin; + } + else + { + outcome = MoveOutcome.BlackWin; + } + } + + if (outcome == MoveOutcome.EndGame || outcome == MoveOutcome.ValidMoves) + { + var bestMove = possibleMoves.MinBy(x => x.Weighting); + + if (bestMove == null) + { + throw new ArgumentNullException("No best move selected"); + } + + var pieceToMove = bestMove.PieceToMove; + + if (pieceToMove == null) + { + throw new ArgumentNullException("No piece selected"); + } + + var newX = bestMove.To.X; + var newY = bestMove.To.Y; + + var from = PositionHelper.GetNotationPosition(pieceToMove.XPosition, pieceToMove.YPosition); + + pieceToMove.XPosition = newX; + pieceToMove.YPosition = newY; + + var to = PositionHelper.GetNotationPosition(pieceToMove.XPosition, pieceToMove.YPosition); + + var blackPieces = BoardHelper.GetNumberOfBlackPiecesInPlay(currentBoard); + var whitePieces = BoardHelper.GetNumberOfWhitePiecesInPlay(currentBoard); + + // Promotion can only happen if not already a king and you have reached the far side + if (newY is 1 or 8 && pieceToMove.Promoted == false) + { + pieceToMove.Promoted = true; + + LoggingHelper.LogMove(from, to, PlayerAction.Promotion, currentSide, blackPieces, whitePieces); + } + else + { + LoggingHelper.LogMove(from, to, PlayerAction.Move, currentSide, blackPieces, whitePieces); + } + } + + if (outcome == MoveOutcome.Capture) + { + PerformCapture(currentSide, possibleMoves, currentBoard); + var moreAvailable = MoreCapturesAvailable(currentSide, currentBoard); + + if (moreAvailable) + { + outcome = MoveOutcome.CaptureMoreAvailable; + } + } + + if (outcome != MoveOutcome.CaptureMoreAvailable) + { + ResetCapturePiece(currentBoard); + } + + return outcome; + } + + private static void ResetCapturePiece(Board currentBoard) + { + var capturePiece = GetAggressor(currentBoard); + + if (capturePiece != null) + { + capturePiece.Aggressor = false; + } + } + + private static bool MoreCapturesAvailable(PieceColour currentSide, Board currentBoard) + { + MoveOutcome outcome; + List possibleMoves; + var aggressor = GetAggressor(currentBoard); + + if (aggressor == null) + { + return false; + } + + outcome = GetAllPossiblePlayerMoves(currentSide, currentBoard, out possibleMoves); + + foreach (var move in possibleMoves) + { + if (move.PieceToMove == aggressor && move.Capturing != null) + { + return true; + } + + } + + return false; + } + + private static Piece? GetAggressor(Board currentBoard) + { + return currentBoard.Pieces.FirstOrDefault(x => x.Aggressor); + } + + private static bool MoveIsValid(Point? from, Point to, List possibleMoves, Board currentBoard, PieceColour currentSide, out Move? selectedMove) + { + selectedMove = default; + + if (from != null) + { + var selectedPiece = BoardHelper.GetPieceAt(from.Value.X, from.Value.Y, currentBoard); + + foreach (var move in possibleMoves.Where(move => move.PieceToMove == selectedPiece && move.To == to)) + { + selectedMove = move; + + return true; + } + } + + return false; + } + + private static MoveOutcome GetAllPossiblePlayerMoves(PieceColour currentSide, Board currentBoard, + out List possibleMoves) + { + var aggressor = GetAggressor(currentBoard); + + var result = MoveOutcome.Unknown; + + possibleMoves = new List(); + + if (PlayingWithJustKings(currentSide, currentBoard, out var endGameMoves)) + { + result = MoveOutcome.EndGame; + possibleMoves.AddRange(endGameMoves); + } + + GetPossibleMovesAndAttacks(currentSide, currentBoard, out var nonEndGameMoves); + + if (nonEndGameMoves != null) + { + possibleMoves.AddRange(nonEndGameMoves); + } + + if (aggressor != null) + { + var tempMoves = possibleMoves.Where(x => x.PieceToMove == aggressor).ToList(); + possibleMoves = tempMoves; + } + + return result; + } + + private static Move? BuildPlayerMove(Point? from, Point? to, Board currentBoard) + { + if (from != null) + { + var piece = BoardHelper.GetPieceAt(from.Value.X, from.Value.Y, currentBoard); + + if (to != null) + { + var move = new Move { PieceToMove = piece, To = (Point)to }; + return move; + } + } + + return default; + } + + private static void PerformCapture(PieceColour currentSide, List possibleCaptures, Board currentBoard) + { + var captureMove = possibleCaptures.FirstOrDefault(x => x.Capturing != null); + var from = string.Empty; + var to = string.Empty; + + if (captureMove != null) + { + var squareToCapture = captureMove.Capturing; + + if (squareToCapture != null) + { + var deadMan = + BoardHelper.GetPieceAt(squareToCapture.Value.X, squareToCapture.Value.Y, currentBoard); + + if (deadMan != null) + { + deadMan.InPlay = false; + } + + if (captureMove.PieceToMove != null) + { + captureMove.PieceToMove.Aggressor = true; + from = PositionHelper.GetNotationPosition(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); + + captureMove.PieceToMove.XPosition = captureMove.To.X; + captureMove.PieceToMove.YPosition = captureMove.To.Y; + + to = PositionHelper.GetNotationPosition(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); + + } + } + } + + var anyPromoted = CheckForPiecesToPromote(currentSide, currentBoard); + var blackPieces = BoardHelper.GetNumberOfBlackPiecesInPlay(currentBoard); + var whitePieces = BoardHelper.GetNumberOfWhitePiecesInPlay(currentBoard); + + if (anyPromoted) + { + LoggingHelper.LogMove(from, to, PlayerAction.CapturePromotion, currentSide, blackPieces, whitePieces); + } + else + { + LoggingHelper.LogMove(from, to, PlayerAction.Capture, currentSide, blackPieces, whitePieces); + } + } + + private static bool CheckForPiecesToPromote(PieceColour currentSide, Board currentBoard) + { + var retVal = false; + var promotionSpot = currentSide == PieceColour.White ? 8 : 1; + + foreach (var piece in currentBoard.Pieces.Where(x => x.Side == currentSide)) + { + if (promotionSpot == piece.YPosition && !piece.Promoted) + { + piece.Promoted = retVal = true; + } + } + + return retVal; + } + + private static MoveOutcome AnalysePosition(PieceColour currentSide, Board currentBoard, + out List possibleMoves) + { + var result = MoveOutcome.Unknown; + + possibleMoves = new List(); + + // Check for endgame first + if (PlayingWithJustKings(currentSide, currentBoard, out var endGameMoves)) + { + result = MoveOutcome.EndGame; + possibleMoves.AddRange(endGameMoves); + + return result; + } + + GetPossibleMovesAndAttacks(currentSide, currentBoard, out var nonEndGameMoves); + + if (nonEndGameMoves == null) + { + result = MoveOutcome.NoMoveAvailable; + } + else + { + if (nonEndGameMoves.Any(x => x.Capturing != null)) + { + result = MoveOutcome.Capture; + } + else + { + if (nonEndGameMoves.Count > 0) + { + result = MoveOutcome.ValidMoves; + } + else + { + result = MoveOutcome.NoMoveAvailable; + } + } + } + + if (nonEndGameMoves != null) + { + possibleMoves.AddRange(nonEndGameMoves); + } + + return result; + } + + private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board currentBoard, + out List possibleMoves) + { + possibleMoves = new List(); + + foreach (var piece in currentBoard.Pieces.Where(c => c.Side == currentSide && c.InPlay)) + { + for (var x = -1; x < 2; x++) + { + for (var y = -1; y < 2; y++) + { + if (x == 0 || y == 0) + { + continue; + } + + if (!piece.Promoted) + { + if (currentSide == PieceColour.White && y == -1) + { + continue; + } + + if (currentSide == PieceColour.Black && y == 1) + { + continue; + } + } + + var currentX = piece.XPosition + x; + var currentY = piece.YPosition + y; + + if (!PositionHelper.PointValid(currentX, currentY)) + { + continue; + } + + var targetSquare = BoardHelper.GetSquareOccupancy(currentX, currentY, currentBoard); + + if (targetSquare == PieceColour.NotSet) + { + if (PositionHelper.PointValid(currentX, currentY)) + { + var newMove = new Move { PieceToMove = piece, TypeOfMove = MoveType.StandardMove, To = new Point { X = currentX, Y = currentY } }; + possibleMoves.Add(newMove); + } + } + else + { + var haveTarget = targetSquare != currentSide; + + if (!haveTarget) + { + continue; + } + + var toLocation = DeriveToPosition(piece.XPosition, piece.YPosition, currentX, currentY); + + var beyondX = toLocation.X; + var beyondY = toLocation.Y; + + if (!PositionHelper.PointValid(beyondX, beyondY)) + { + continue; + } + + var beyondSquare = BoardHelper.GetSquareOccupancy(beyondX, beyondY, currentBoard); + + if (beyondSquare != PieceColour.NotSet) + { + continue; + } + + var attack = new Move { PieceToMove = piece, TypeOfMove = MoveType.Capture, To = new Point { X = beyondX, Y = beyondY }, Capturing = new Point { X = currentX, Y = currentY } }; + possibleMoves.Add(attack); + } + } + } + } + } + + private static Point DeriveToPosition(int pieceX, int pieceY, int captureX, int captureY) + { + var newX = 0; + var newY = 0; + + if (captureX > pieceX) + { + newX = captureX + 1; + } + else + { + newX = captureX - 1; + } + + if (captureY > pieceY) + { + newY = captureY + 1; + } + else + { + newY = captureY - 1; + } + + return new Point(newX, newY); + } + + private static bool PlayingWithJustKings(PieceColour currentSide, Board currentBoard, out List possibleMoves) + { + possibleMoves = new List(); + var piecesInPlay = currentBoard.Pieces.Count(x => x.Side == currentSide && x.InPlay); + var kingsInPlay = currentBoard.Pieces.Count(x => x.Side == currentSide && x.InPlay && x.Promoted); + + var playingWithJustKings = piecesInPlay == kingsInPlay; + + if (playingWithJustKings) + { + var shortestDistance = 12.0; + + Piece? currentHero = null; + Piece? currentVillain = null; + + foreach (var king in currentBoard.Pieces.Where(x => x.Side == currentSide && x.InPlay)) + { + foreach (var target in currentBoard.Pieces.Where(x => x.Side != currentSide && x.InPlay)) + { + var kingPoint = new Point(king.XPosition, king.YPosition); + var targetPoint = new Point(target.XPosition, target.YPosition); + var distance = VectorHelper.GetPointDistance(kingPoint, targetPoint); + + if (distance < shortestDistance) + { + shortestDistance = distance; + currentHero = king; + currentVillain = target; + } + } + } + + if (currentHero != null && currentVillain != null) + { + var movementOptions = VectorHelper.WhereIsVillain(currentHero, currentVillain); + + foreach (var movementOption in movementOptions) + { + var squareStatus = BoardHelper.GetSquareOccupancy(movementOption.X, movementOption.Y, currentBoard); + + if (squareStatus == PieceColour.NotSet) + { + var theMove = new Move + { + PieceToMove = currentHero, + TypeOfMove = MoveType.EndGame, + To = new Point(movementOption.X, movementOption.Y) + }; + + if (!PositionHelper.PointValid(movementOption.X, movementOption.Y)) + { + continue; + } + + possibleMoves.Add(theMove); + + break; + } + } + } + } + + return possibleMoves.Count > 0; + } +} diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs new file mode 100644 index 00000000..aadf7f4a --- /dev/null +++ b/Projects/Checkers/Game.cs @@ -0,0 +1,115 @@ +using System.Drawing; +using Checkers.Types; + +namespace Checkers; + +public class Game +{ + private const int PiecesPerSide = 12; + + public PieceColour CurrentGo { get; set; } + public Board GameBoard { get; } + public int MovesSoFar { get; private set; } + public PieceColour GameWinner { get; set; } = PieceColour.NotSet; + + private bool NoDisplay { get; set; } + + public List Players { get; set; } = new List + { + new Player { ControlledBy = PlayerControl.Computer, Side = PieceColour.Black }, + new Player { ControlledBy = PlayerControl.Computer, Side = PieceColour.White } + }; + + public Game(List startingPosition, PieceColour toMove) + { + GameBoard = new Board(startingPosition); + CurrentGo = toMove; + NoDisplay = true; + } + + public Game() + { + GameBoard = new Board(); + CurrentGo = PieceColour.Black; + NoDisplay = false; + ShowBoard(); + } + + public MoveOutcome NextRound(Point? from = null, Point? to = null) + { + MovesSoFar++; + var res = Engine.PlayNextMove(CurrentGo, GameBoard, from, to); + + while (from == null & to == null && res == MoveOutcome.CaptureMoreAvailable) + { + res = Engine.PlayNextMove(CurrentGo, GameBoard, from, to); + } + + if (res == MoveOutcome.BlackWin) + { + GameWinner = PieceColour.Black; + } + + if (res == MoveOutcome.WhiteWin) + { + GameWinner = PieceColour.White; + } + + if (GameWinner == PieceColour.NotSet && res != MoveOutcome.CaptureMoreAvailable) + { + CheckSidesHavePiecesLeft(); + CurrentGo = CurrentGo == PieceColour.Black ? PieceColour.White : PieceColour.Black; + } + + if (res == MoveOutcome.Unknown) + { + CurrentGo = CurrentGo == PieceColour.Black + ? PieceColour.White + : PieceColour.Black; + } + + ShowBoard(); + + return res; + } + + public void CheckSidesHavePiecesLeft() + { + var retVal = GameBoard.Pieces.Where(x => x.InPlay).Select(y => y.Side).Distinct().Count() == 2; + + if (!retVal) + { + GameWinner = GameBoard.Pieces.Where(x => x.InPlay).Select(y => y.Side).Distinct().FirstOrDefault(); + } + } + + public string GetCurrentPlayer() + { + return CurrentGo.ToString(); + } + + public int GetWhitePiecesTaken() + { + return GetPiecesTakenForSide(PieceColour.White); + } + + public int GetBlackPiecesTaken() + { + return GetPiecesTakenForSide(PieceColour.Black); + } + + public int GetPiecesTakenForSide(PieceColour colour) + { + return PiecesPerSide - GameBoard.Pieces.Count(x => x.InPlay && x.Side == colour); + } + + private void ShowBoard() + { + if (!NoDisplay) + { + Display.DisplayBoard(GameBoard); + Display.DisplayStats(GetWhitePiecesTaken(), GetBlackPiecesTaken()); + Display.DisplayCurrentPlayer(CurrentGo); + } + } +} diff --git a/Projects/Checkers/Helpers/BoardHelper.cs b/Projects/Checkers/Helpers/BoardHelper.cs new file mode 100644 index 00000000..f265e2bb --- /dev/null +++ b/Projects/Checkers/Helpers/BoardHelper.cs @@ -0,0 +1,64 @@ +using Checkers.Data; +using Checkers.Types; + +namespace Checkers.Helpers; + + /// + /// Board related routines + /// + public static class BoardHelper + { + public static List GetStartingPosition() + { + // HACK: Can set to false and define a custom start position in GetLimitedStartingPosition + const bool UseDefault = true; + +#pragma warning disable CS0162 + return UseDefault ? GetDefaultStartingPosition() : GetLimitedStartingPosition(); +#pragma warning restore CS0162 + } + + private static List GetLimitedStartingPosition() + { + return KnowledgeBase.GetLimitedStartingPosition(); + } + + public static List GetDefaultStartingPosition() + { + return KnowledgeBase.GetStartingPosition(); + } + + public static PieceColour GetSquareOccupancy(int xp, int yp, Board currentBoard) + { + var piece = currentBoard.Pieces.FirstOrDefault(x => x.XPosition == xp && x.InPlay && x.YPosition == yp); + + return piece == null ? PieceColour.NotSet : piece.Side; + } + + public static Piece? GetPieceAt(int xp, int yp, Board currentBoard) + { + var retVal = currentBoard.Pieces.FirstOrDefault(x => x.XPosition == xp && x.InPlay && x.YPosition == yp); + + if (retVal == null) + { + return default; + } + + return retVal; + } + + public static int GetNumberOfWhitePiecesInPlay(Board currentBoard) + { + return GetNumberOfPiecesInPlay(currentBoard, PieceColour.White); + } + + public static int GetNumberOfBlackPiecesInPlay(Board currentBoard) + { + return GetNumberOfPiecesInPlay(currentBoard, PieceColour.Black); + } + + private static int GetNumberOfPiecesInPlay(Board currentBoard, PieceColour currentSide) + { + return currentBoard.Pieces.Count(x => x.Side == currentSide && x.InPlay); + } + } diff --git a/Projects/Checkers/Helpers/LoggingHelper.cs b/Projects/Checkers/Helpers/LoggingHelper.cs new file mode 100644 index 00000000..f719dd6e --- /dev/null +++ b/Projects/Checkers/Helpers/LoggingHelper.cs @@ -0,0 +1,63 @@ +using System.Diagnostics; +using Checkers.Types; + +namespace Checkers.Helpers; + + /// + /// Used for logging games for analysis - see flag in Program.cs + /// + public static class LoggingHelper + { + public static void LogMove(string from, string to, PlayerAction action, PieceColour sidePlaying, int blacksInPlay, int whitesInPlay) + { + var colour = sidePlaying == PieceColour.Black ? "B" : "W"; + var suffix = DecodePlayerAction(action); + + var outputLine = $"Move : {colour} {from}-{to} {suffix,2}"; + + if (action == PlayerAction.Capture || action == PlayerAction.CapturePromotion) + { + var piecesCount = $" B:{blacksInPlay,2} W:{whitesInPlay,2}"; + outputLine += piecesCount; + } + + Trace.WriteLine(outputLine); + } + + public static void LogMoves(int numberOfMoves) + { + Trace.WriteLine($"Moves : {numberOfMoves}"); + } + + public static void LogStart() + { + Trace.WriteLine($"Started: {DateTime.Now}"); + } + + public static void LogFinish() + { + Trace.WriteLine($"Stopped: {DateTime.Now}"); + } + + public static void LogOutcome(PieceColour winner) + { + Trace.WriteLine($"Winner : {winner}"); + } + + private static string DecodePlayerAction(PlayerAction action) + { + switch (action) + { + case PlayerAction.Move: + return string.Empty; + case PlayerAction.Promotion: + return "K"; + case PlayerAction.Capture: + return "X"; + case PlayerAction.CapturePromotion: + return "KX"; + default: + return String.Empty; + } + } + } diff --git a/Projects/Checkers/Helpers/PlayerHelper.cs b/Projects/Checkers/Helpers/PlayerHelper.cs new file mode 100644 index 00000000..87541113 --- /dev/null +++ b/Projects/Checkers/Helpers/PlayerHelper.cs @@ -0,0 +1,43 @@ +using Checkers.Types; + +namespace Checkers.Helpers; + + /// + /// Assigns AI/human players + /// N.B. currently can only play as black in a one player game + /// + public static class PlayerHelper + { + public static void AssignPlayersToSide(int numberOfPlayers, Game currentGame) + { + switch (numberOfPlayers) + { + case 0: + foreach (var player in currentGame.Players) + { + player.ControlledBy = PlayerControl.Computer; + } + + break; + case 1: + foreach (var player in currentGame.Players.Where(x => x.Side == PieceColour.White)) + { + player.ControlledBy = PlayerControl.Computer; + } + + foreach (var player in currentGame.Players.Where(x => x.Side == PieceColour.Black)) + { + player.ControlledBy = PlayerControl.Human; + } + + break; + case 2: + foreach (var player in currentGame.Players) + { + player.ControlledBy = PlayerControl.Human; + } + + break; + } + } + } diff --git a/Projects/Checkers/Helpers/PositionHelper.cs b/Projects/Checkers/Helpers/PositionHelper.cs new file mode 100644 index 00000000..6c732da5 --- /dev/null +++ b/Projects/Checkers/Helpers/PositionHelper.cs @@ -0,0 +1,55 @@ +using System.Drawing; + +namespace Checkers.Helpers; + + /// + /// Position routines + /// + public static class PositionHelper + { + public static string GetNotationPosition(int x, int y) + { + if (!PointValid(x, y)) + { + throw new ArgumentException("Not a valid position!"); + } + + var yPortion = 9 - y; + + var xPortion = Convert.ToChar(x + 64); + + return xPortion + yPortion.ToString(); + } + + public static Point? GetPositionByNotation(string notation) + { + const int ExpectedPositionLength = 2; + + notation = notation.Trim(); + + if (notation.Length != ExpectedPositionLength) + { + return default; + } + + try + { + var letterPart = notation.Substring(0, 1); + var numberPart = notation.Substring(1, 1); + + var x = letterPart.ToUpper().ToCharArray()[0] - 64; + var y = 9 - Convert.ToInt32(numberPart); + + return new Point(x, y); + } + catch + { + return default; + } + } + + public static bool PointValid(int x, int y) + { + return x is > 0 and < 9 && y is > 0 and < 9; + } + } diff --git a/Projects/Checkers/Helpers/VectorHelper.cs b/Projects/Checkers/Helpers/VectorHelper.cs new file mode 100644 index 00000000..2d0dd7e1 --- /dev/null +++ b/Projects/Checkers/Helpers/VectorHelper.cs @@ -0,0 +1,111 @@ +using System.Drawing; +using Checkers.Types; + +namespace Checkers.Helpers; + + /// + /// Track distance between 2 points + /// Used in endgame for kings to hunt down closest victim + /// + public static class VectorHelper + { + public static double GetPointDistance(Point first, Point second) + { + // Easiest cases are points on the same vertical or horizontal axis + if (first.X == second.X) + { + return Math.Abs(first.Y - second.Y); + } + + if (first.Y == second.Y) + { + return Math.Abs(first.X - second.X); + } + + // Pythagoras baby + var sideA = (double)Math.Abs(first.Y - second.Y); + var sideB = (double)Math.Abs(first.X - second.X); + + return Math.Sqrt(Math.Pow(sideA, 2) + Math.Pow(sideB, 2)); + } + + public static List WhereIsVillain(Piece hero, Piece villain) + { + var retVal = new List(); + + var directions = new List(); + + if (hero.XPosition > villain.XPosition) + { + directions.Add(Direction.Left); + } + + if (hero.XPosition < villain.XPosition) + { + directions.Add(Direction.Right); + } + + if (hero.YPosition > villain.YPosition) + { + directions.Add(Direction.Up); + } + + if (hero.YPosition < villain.YPosition) + { + directions.Add(Direction.Down); + } + + if (directions.Count == 1) + { + switch (directions[0]) + { + case Direction.Up: + retVal.Add(new Point(hero.XPosition - 1, hero.YPosition - 1)); + retVal.Add(new Point(hero.XPosition + 1, hero.YPosition - 1)); + + break; + case Direction.Down: + retVal.Add(new Point(hero.XPosition - 1, hero.YPosition + 1)); + retVal.Add(new Point(hero.XPosition + 1, hero.YPosition + 1)); + + break; + case Direction.Left: + retVal.Add(new Point(hero.XPosition - 1, hero.YPosition - 1)); + retVal.Add(new Point(hero.XPosition - 1, hero.YPosition + 1)); + + break; + case Direction.Right: + retVal.Add(new Point(hero.XPosition + 1, hero.YPosition - 1)); + retVal.Add(new Point(hero.XPosition + 1, hero.YPosition + 1)); + + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + else + { + if (directions.Contains(Direction.Left) && directions.Contains(Direction.Up)) + { + retVal.Add(new Point(hero.XPosition - 1, hero.YPosition - 1)); + } + + if (directions.Contains(Direction.Left) && directions.Contains(Direction.Down)) + { + retVal.Add(new Point(hero.XPosition - 1, hero.YPosition + 1)); + } + + if (directions.Contains(Direction.Right) && directions.Contains(Direction.Up)) + { + retVal.Add(new Point(hero.XPosition + 1, hero.YPosition - 1)); + } + + if (directions.Contains(Direction.Right) && directions.Contains(Direction.Down)) + { + retVal.Add(new Point(hero.XPosition + 1, hero.YPosition + 1)); + } + } + + return retVal; + } + } diff --git a/Projects/Checkers/Move.cs b/Projects/Checkers/Move.cs new file mode 100644 index 00000000..15fb8ae7 --- /dev/null +++ b/Projects/Checkers/Move.cs @@ -0,0 +1,18 @@ +using System.Drawing; +using Checkers.Types; + +namespace Checkers; + +public class Move +{ + public Piece? PieceToMove { get; set; } + + public Point To { get; set; } + + public Point? Capturing { get; set; } + + public MoveType TypeOfMove { get; set; } = MoveType.Unknown; + + // Moves are sorted by guid to give computer vs computer games a bit of variety + public string Weighting => Guid.NewGuid().ToString(); +} diff --git a/Projects/Checkers/Piece.cs b/Projects/Checkers/Piece.cs new file mode 100644 index 00000000..e83bf577 --- /dev/null +++ b/Projects/Checkers/Piece.cs @@ -0,0 +1,41 @@ +using Checkers.Helpers; +using Checkers.Types; + +namespace Checkers; + +public class Piece +{ + public Piece() + { + InPlay = true; + Promoted = false; + } + + public int XPosition { get; set; } + + public int YPosition { get; set; } + + public string InitialPosition + { + set + { + var position = PositionHelper.GetPositionByNotation(value); + + if (position == null) + { + return; + } + + XPosition = position.Value.X; + YPosition = position.Value.Y; + } + } + + public PieceColour Side { get; set; } + + public bool InPlay { get; set; } + + public bool Promoted { get; set; } + + public bool Aggressor { get; set; } +} diff --git a/Projects/Checkers/Player.cs b/Projects/Checkers/Player.cs new file mode 100644 index 00000000..d17fd6b5 --- /dev/null +++ b/Projects/Checkers/Player.cs @@ -0,0 +1,9 @@ +using Checkers.Types; + +namespace Checkers; + +public class Player +{ + public PlayerControl ControlledBy { get; set; } + public PieceColour Side { get; set; } +} diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs new file mode 100644 index 00000000..e91e414f --- /dev/null +++ b/Projects/Checkers/Program.cs @@ -0,0 +1,165 @@ +// See https://aka.ms/new-console-template for more information + +using System.Diagnostics; +using Checkers; +using Checkers.Helpers; +using Checkers.Types; + +// HACK: Set to true to create output file for game analysis +const bool createOutputFileForAnalysis = false; + +if (createOutputFileForAnalysis) +{ +#pragma warning disable CS0162 // Unreachable code detected + var prefix = Path.GetTempPath(); +#pragma warning restore CS0162 // Unreachable code detected + var traceFile = $"{prefix}checkers_game_{Guid.NewGuid()}.txt"; + + Trace.Listeners.Add((new TextWriterTraceListener(File.Create((traceFile))))); +} + +Trace.AutoFlush = true; + +Console.OutputEncoding = System.Text.Encoding.UTF8; + +var sw = new Stopwatch(); + +sw.Start(); +LoggingHelper.LogStart(); +Game? game = null; + +var numberOfPlayers = 0; + +var gameState = GameState.IntroScreen; + +while (gameState != GameState.Stopped) +{ + switch (gameState) + { + case GameState.IntroScreen: + numberOfPlayers = ShowIntroScreenAndGetOption(); + gameState = GameState.GameInProgress; + + break; + case GameState.GameInProgress: + + game = new Game(); + PlayerHelper.AssignPlayersToSide(numberOfPlayers, game); + + while (game.GameWinner == PieceColour.NotSet) + { + var currentPlayer = game.Players.FirstOrDefault(x => x.Side == game.CurrentGo); + + if (currentPlayer != null && currentPlayer.ControlledBy == PlayerControl.Human) + { + Console.SetCursorPosition(0, 12); + Console.Write($"Enter FROM square: "); + var fromSquare = Console.ReadLine(); + Console.Write($"Enter TO square : "); + var toSquare = Console.ReadLine(); + + if (fromSquare != null) + { + var fromPoint = PositionHelper.GetPositionByNotation(fromSquare); + + if (toSquare != null) + { + var toPoint = PositionHelper.GetPositionByNotation(toSquare); + + if (fromPoint != null) + { + var actualFromPiece = + BoardHelper.GetPieceAt(fromPoint.Value.X, fromPoint.Value.Y, game.GameBoard); + + if (actualFromPiece == null || actualFromPiece.Side != game.CurrentGo) + { + fromPoint = toPoint = null; + } + } + + if (fromPoint != null && toPoint != null) + { + var moveOutcome = game.NextRound(fromPoint, toPoint); + } + else + { + Console.SetCursorPosition(19, 12); + Console.Write(new string(' ', 10)); + Console.SetCursorPosition(19, 13); + Console.Write(new string(' ', 10)); + } + } + } + } + else + { + game.NextRound(); + } + + Thread.Sleep(100); + } + + LoggingHelper.LogOutcome(game.GameWinner); + + gameState = GameState.GameOver; + + break; + case GameState.GameOver: + if (game != null) + { + LoggingHelper.LogMoves(game.MovesSoFar); + } + + LoggingHelper.LogFinish(); + sw.Stop(); + + if (game != null) + { + Display.DisplayWinner(game.GameWinner); + } + + gameState = GameState.Stopped; + + break; + default: + throw new ArgumentOutOfRangeException(); + } +} + +int ShowIntroScreenAndGetOption() +{ + var validPlayers = new List() { 0, 1, 2 }; + Console.Clear(); + Console.WriteLine("CHECKERS"); + Console.WriteLine(); + Console.WriteLine("Checkers is played on an 8x8 board between two sides commonly known as black"); + Console.WriteLine("and white. The objective is simple - capture all your opponent's pieces. An"); + Console.WriteLine("alternative way to win is to trap your opponent so that they have no valid"); + Console.WriteLine("moves left."); + Console.WriteLine(); + Console.WriteLine("Black starts first and players take it in turns to move their pieces forward"); + Console.WriteLine("across the board diagonally. Should a piece reach the other side of the board"); + Console.WriteLine("the piece becomes a `king` and can then move diagonally backwards as well as"); + Console.WriteLine("forwards."); + Console.WriteLine(); + Console.WriteLine("Pieces are captured by jumping over them diagonally. More than one enemy piece"); + Console.WriteLine("can be captured in the same turn by the same piece."); + Console.WriteLine(); + Console.WriteLine("Moves are entered in `algebraic notation` e.g. A1 is the bottom left square,"); + Console.WriteLine("and H8 is the top right square. Invalid moves are ignored."); + Console.WriteLine(); + Console.WriteLine("3 modes of play are possible depending on the number of players entered:"); + Console.WriteLine(" 0 - black and white are controlled by the computer"); + Console.WriteLine(" 1 - black is controlled by the player and white by the computer"); + Console.WriteLine(" 2 - allows 2 players"); + Console.WriteLine(); + Console.Write("Enter the number of players (0-2): "); + + var entry = Console.ReadLine(); + + var conversionPassed = int.TryParse(entry, out numberOfPlayers); + + return conversionPassed && validPlayers.Contains(numberOfPlayers) ? numberOfPlayers : 0; +} + + diff --git a/Projects/Checkers/Types/DirectionType.cs b/Projects/Checkers/Types/DirectionType.cs new file mode 100644 index 00000000..aa5a2ec8 --- /dev/null +++ b/Projects/Checkers/Types/DirectionType.cs @@ -0,0 +1,9 @@ +namespace Checkers.Types; + +public enum Direction +{ + Up, + Down, + Left, + Right +} diff --git a/Projects/Checkers/Types/GameStateType.cs b/Projects/Checkers/Types/GameStateType.cs new file mode 100644 index 00000000..5f0ae720 --- /dev/null +++ b/Projects/Checkers/Types/GameStateType.cs @@ -0,0 +1,9 @@ +namespace Checkers.Types; + +public enum GameState +{ + IntroScreen, + GameInProgress, + GameOver, + Stopped +} diff --git a/Projects/Checkers/Types/MoveOutcomeType.cs b/Projects/Checkers/Types/MoveOutcomeType.cs new file mode 100644 index 00000000..f5d495d0 --- /dev/null +++ b/Projects/Checkers/Types/MoveOutcomeType.cs @@ -0,0 +1,13 @@ +namespace Checkers.Types; + +public enum MoveOutcome +{ + Unknown, + ValidMoves, + Capture, + CaptureMoreAvailable, + EndGame, // Playing with kings with prey to hunt + NoMoveAvailable, + WhiteWin, + BlackWin +} diff --git a/Projects/Checkers/Types/MoveType.cs b/Projects/Checkers/Types/MoveType.cs new file mode 100644 index 00000000..186a6ac7 --- /dev/null +++ b/Projects/Checkers/Types/MoveType.cs @@ -0,0 +1,9 @@ +namespace Checkers.Types; + +public enum MoveType +{ + Unknown, + StandardMove, + Capture, + EndGame +} diff --git a/Projects/Checkers/Types/PieceColourType.cs b/Projects/Checkers/Types/PieceColourType.cs new file mode 100644 index 00000000..cfab2492 --- /dev/null +++ b/Projects/Checkers/Types/PieceColourType.cs @@ -0,0 +1,8 @@ +namespace Checkers.Types; + +public enum PieceColour +{ + Black, + White, + NotSet +} diff --git a/Projects/Checkers/Types/PlayerActionType.cs b/Projects/Checkers/Types/PlayerActionType.cs new file mode 100644 index 00000000..60c2363e --- /dev/null +++ b/Projects/Checkers/Types/PlayerActionType.cs @@ -0,0 +1,12 @@ +namespace Checkers.Types; + +/// +/// Used for logging +/// +public enum PlayerAction +{ + Move, + Promotion, + Capture, + CapturePromotion +} diff --git a/Projects/Checkers/Types/PlayerControlType.cs b/Projects/Checkers/Types/PlayerControlType.cs new file mode 100644 index 00000000..5d3d4cf8 --- /dev/null +++ b/Projects/Checkers/Types/PlayerControlType.cs @@ -0,0 +1,7 @@ +namespace Checkers.Types; + +public enum PlayerControl +{ + Human, + Computer +} diff --git a/dotnet-console-games-and-website.sln b/dotnet-console-games-and-website.sln index 1feedfde..a97c6a46 100644 --- a/dotnet-console-games-and-website.sln +++ b/dotnet-console-games-and-website.sln @@ -87,6 +87,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tug Of War", "Projects\Tug EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duck Hunt", "Projects\Duck Hunt\Duck Hunt.csproj", "{B52224E3-87D4-441B-B5BF-32382DC54C9E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Checkers", "Projects\Checkers\Checkers.csproj", "{25D5F8B8-4E27-4F37-BF40-18C987D3644E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -261,6 +263,10 @@ Global {B52224E3-87D4-441B-B5BF-32382DC54C9E}.Debug|Any CPU.Build.0 = Debug|Any CPU {B52224E3-87D4-441B-B5BF-32382DC54C9E}.Release|Any CPU.ActiveCfg = Release|Any CPU {B52224E3-87D4-441B-B5BF-32382DC54C9E}.Release|Any CPU.Build.0 = Release|Any CPU + {25D5F8B8-4E27-4F37-BF40-18C987D3644E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25D5F8B8-4E27-4F37-BF40-18C987D3644E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25D5F8B8-4E27-4F37-BF40-18C987D3644E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25D5F8B8-4E27-4F37-BF40-18C987D3644E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From edab10f1c4e3f140f463b2822d0b37418e4137bf Mon Sep 17 00:00:00 2001 From: Rob Docherty Date: Sat, 18 Jun 2022 00:48:04 +0100 Subject: [PATCH 02/68] Overhaul the way moves are made, remove some cruft from the engine, add missing game rule (promoted pieces can't carry on capture chain). Start doing some tidying. --- Projects/Checkers/Board.cs | 18 +- Projects/Checkers/Data/KnowledgeBase.cs | 90 +- Projects/Checkers/Display.cs | 165 +-- Projects/Checkers/Engine.cs | 991 +++++++++---------- Projects/Checkers/Game.cs | 219 ++-- Projects/Checkers/Helpers/BoardHelper.cs | 89 +- Projects/Checkers/Helpers/DisplayHelper.cs | 15 + Projects/Checkers/Helpers/LoggingHelper.cs | 96 +- Projects/Checkers/Helpers/PlayerHelper.cs | 63 +- Projects/Checkers/Helpers/PositionHelper.cs | 79 +- Projects/Checkers/Helpers/VectorHelper.cs | 167 ++-- Projects/Checkers/Move.cs | 17 +- Projects/Checkers/Piece.cs | 49 +- Projects/Checkers/Player.cs | 5 +- Projects/Checkers/Program.cs | 317 +++--- Projects/Checkers/Types/DirectionType.cs | 9 +- Projects/Checkers/Types/GameStateType.cs | 9 +- Projects/Checkers/Types/MoveOutcomeType.cs | 17 +- Projects/Checkers/Types/MoveType.cs | 9 +- Projects/Checkers/Types/PieceColourType.cs | 7 +- Projects/Checkers/Types/PlayerActionType.cs | 9 +- Projects/Checkers/Types/PlayerControlType.cs | 5 +- 22 files changed, 1247 insertions(+), 1198 deletions(-) create mode 100644 Projects/Checkers/Helpers/DisplayHelper.cs diff --git a/Projects/Checkers/Board.cs b/Projects/Checkers/Board.cs index 4340c6f0..b9ad0160 100644 --- a/Projects/Checkers/Board.cs +++ b/Projects/Checkers/Board.cs @@ -4,15 +4,15 @@ namespace Checkers; public class Board { - public List Pieces { get; set; } + public List Pieces { get; set; } - public Board() - { - Pieces = BoardHelper.GetStartingPosition(); - } + public Board() + { + Pieces = BoardHelper.GetStartingPosition(); + } - public Board(List startingPosition) - { - Pieces = startingPosition; - } + public Board(List startingPosition) + { + Pieces = startingPosition; + } } diff --git a/Projects/Checkers/Data/KnowledgeBase.cs b/Projects/Checkers/Data/KnowledgeBase.cs index 0c76ccae..fba9e5b5 100644 --- a/Projects/Checkers/Data/KnowledgeBase.cs +++ b/Projects/Checkers/Data/KnowledgeBase.cs @@ -7,51 +7,51 @@ namespace Checkers.Data; /// public static class KnowledgeBase { - public static List GetStartingPosition() - { - var retVal = new List - { - new() { InitialPosition ="A3", Side = PieceColour.Black}, - new() { InitialPosition ="A1", Side = PieceColour.Black}, - new() { InitialPosition ="B2", Side = PieceColour.Black}, - new() { InitialPosition ="C3", Side = PieceColour.Black}, - new() { InitialPosition ="C1", Side = PieceColour.Black}, - new() { InitialPosition ="D2", Side = PieceColour.Black}, - new() { InitialPosition ="E3", Side = PieceColour.Black}, - new() { InitialPosition ="E1", Side = PieceColour.Black}, - new() { InitialPosition ="F2", Side = PieceColour.Black}, - new() { InitialPosition ="G3", Side = PieceColour.Black}, - new() { InitialPosition ="G1", Side = PieceColour.Black}, - new() { InitialPosition ="H2", Side = PieceColour.Black}, - new() { InitialPosition ="A7",Side = PieceColour.White}, - new() { InitialPosition ="B8",Side = PieceColour.White}, - new() { InitialPosition ="B6",Side = PieceColour.White}, - new() { InitialPosition ="C7",Side = PieceColour.White}, - new() { InitialPosition ="D8",Side = PieceColour.White}, - new() { InitialPosition ="D6",Side = PieceColour.White}, - new() { InitialPosition ="E7",Side = PieceColour.White}, - new() { InitialPosition ="F8",Side = PieceColour.White}, - new() { InitialPosition ="F6",Side = PieceColour.White}, - new() { InitialPosition ="G7",Side = PieceColour.White}, - new() { InitialPosition ="H8",Side = PieceColour.White}, - new() { InitialPosition ="H6",Side = PieceColour.White} - }; - - return retVal; - } - - public static List GetLimitedStartingPosition() - { - var retVal = new List - { - new () { InitialPosition ="H2", Side = PieceColour.Black}, - new () { InitialPosition ="A1", Side = PieceColour.Black}, - new () { InitialPosition ="G3", Side = PieceColour.White}, - new() { InitialPosition = "E5", Side = PieceColour.White} - }; - - return retVal; - } + public static List GetStartingPosition() + { + var retVal = new List + { + new() { InitialPosition ="A3", Side = PieceColour.Black}, + new() { InitialPosition ="A1", Side = PieceColour.Black}, + new() { InitialPosition ="B2", Side = PieceColour.Black}, + new() { InitialPosition ="C3", Side = PieceColour.Black}, + new() { InitialPosition ="C1", Side = PieceColour.Black}, + new() { InitialPosition ="D2", Side = PieceColour.Black}, + new() { InitialPosition ="E3", Side = PieceColour.Black}, + new() { InitialPosition ="E1", Side = PieceColour.Black}, + new() { InitialPosition ="F2", Side = PieceColour.Black}, + new() { InitialPosition ="G3", Side = PieceColour.Black}, + new() { InitialPosition ="G1", Side = PieceColour.Black}, + new() { InitialPosition ="H2", Side = PieceColour.Black}, + new() { InitialPosition ="A7",Side = PieceColour.White}, + new() { InitialPosition ="B8",Side = PieceColour.White}, + new() { InitialPosition ="B6",Side = PieceColour.White}, + new() { InitialPosition ="C7",Side = PieceColour.White}, + new() { InitialPosition ="D8",Side = PieceColour.White}, + new() { InitialPosition ="D6",Side = PieceColour.White}, + new() { InitialPosition ="E7",Side = PieceColour.White}, + new() { InitialPosition ="F8",Side = PieceColour.White}, + new() { InitialPosition ="F6",Side = PieceColour.White}, + new() { InitialPosition ="G7",Side = PieceColour.White}, + new() { InitialPosition ="H8",Side = PieceColour.White}, + new() { InitialPosition ="H6",Side = PieceColour.White} + }; + + return retVal; + } + + public static List GetLimitedStartingPosition() + { + var retVal = new List + { + new () { InitialPosition ="H2", Side = PieceColour.Black}, + new () { InitialPosition ="A1", Side = PieceColour.Black}, + new () { InitialPosition ="G3", Side = PieceColour.White}, + new() { InitialPosition = "E5", Side = PieceColour.White} + }; + + return retVal; + } } diff --git a/Projects/Checkers/Display.cs b/Projects/Checkers/Display.cs index 9820747f..21967932 100644 --- a/Projects/Checkers/Display.cs +++ b/Projects/Checkers/Display.cs @@ -1,99 +1,102 @@ -using Checkers.Types; +using System.Drawing; +using Checkers.Helpers; +using Checkers.Types; namespace Checkers; public static class Display { - private const string BlackPiece = "○"; - private const string BlackKing = "☺"; - private const string WhitePiece = "◙"; - private const string WhiteKing = "☻"; + private const string BlackPiece = "○"; + private const string BlackKing = "☺"; + private const string WhitePiece = "◙"; + private const string WhiteKing = "☻"; - public static void DisplayBoard(Board currentBoard) - { - PrintBoard(); - PrintPieces(currentBoard); - Console.CursorVisible = false; - } + public static void DisplayBoard(Board currentBoard) + { + PrintBoard(); + PrintPieces(currentBoard); + Console.CursorVisible = false; + } - public static void DisplayStats(int whitesTaken, int blacksTaken) - { - Console.SetCursorPosition(20, 6); - Console.WriteLine(" Taken:"); - Console.SetCursorPosition(20, 7); - Console.WriteLine($"{whitesTaken.ToString(),2} x {WhitePiece}"); - Console.SetCursorPosition(20, 8); - Console.WriteLine($"{blacksTaken.ToString(),2} x {BlackPiece}"); - } + public static void DisplayStats(int whitesTaken, int blacksTaken) + { + Console.SetCursorPosition(22, 6); + Console.WriteLine(" Taken:"); + Console.SetCursorPosition(22, 7); + Console.WriteLine($"{whitesTaken.ToString(),2} x {WhitePiece}"); + Console.SetCursorPosition(22, 8); + Console.WriteLine($"{blacksTaken.ToString(),2} x {BlackPiece}"); + } - public static void DisplayCurrentPlayer(PieceColour currentSide) - { - Console.SetCursorPosition(0, 11); - Console.WriteLine($"{currentSide} to play"); - } + public static void DisplayCurrentPlayer(PieceColour currentSide) + { + Console.SetCursorPosition(0, 11); + Console.WriteLine($"{currentSide} to play"); + } - public static void DisplayWinner(PieceColour winningSide) - { - Console.SetCursorPosition(0, 11); - Console.WriteLine($"*** {winningSide} wins ***"); - } + public static void DisplayWinner(PieceColour winningSide) + { + Console.SetCursorPosition(0, 11); + Console.WriteLine($"*** {winningSide} wins ***"); + } - private static void PrintPieces(Board currentBoard) - { - foreach (var piece in currentBoard.Pieces.Where(x => x.InPlay)) - { - var actualX = piece.XPosition * 2 + 1; - var actualY = piece.YPosition + 0; - var displayPiece = GetDisplayPiece(piece); - Console.SetCursorPosition(actualX, actualY); - Console.Write(displayPiece); - } - } + private static void PrintPieces(Board currentBoard) + { + foreach (var piece in currentBoard.Pieces.Where(x => x.InPlay)) + { + var screenPosition = + DisplayHelper.GetScreenPositionFromBoardPosition(new Point(piece.XPosition, piece.YPosition)); + var displayPiece = GetDisplayPiece(piece); + Console.SetCursorPosition(screenPosition.X, screenPosition.Y); + Console.Write(displayPiece); + } + } - private static string GetDisplayPiece(Piece currentPiece) - { - string retVal; + private static string GetDisplayPiece(Piece currentPiece) + { + string retVal; - if (currentPiece.Side == PieceColour.Black) - { - retVal = currentPiece.Promoted ? BlackKing : BlackPiece; - } - else - { - retVal = currentPiece.Promoted ? WhiteKing : WhitePiece; - } + if (currentPiece.Side == PieceColour.Black) + { + retVal = currentPiece.Promoted ? BlackKing : BlackPiece; + } + else + { + retVal = currentPiece.Promoted ? WhiteKing : WhitePiece; + } - return retVal; - } + return retVal; + } - private static void PrintBoard() - { - Console.Clear(); - var emptyBoard = GetBlankBoard(); + private static void PrintBoard() + { + Console.Clear(); + var emptyBoard = GetBlankBoard(); - foreach (var rank in emptyBoard) - { - Console.WriteLine(rank); - } - } + foreach (var rank in emptyBoard) + { + Console.WriteLine(rank); + } + } - private static List GetBlankBoard() - { - var retVal = new List - { - $" ╔═════════════════╗", - $"8║ . . . . . . . . ║ {BlackPiece} = Black", - $"7║ . . . . . . . . ║ {BlackKing} = Black King", - $"6║ . . . . . . . . ║ {WhitePiece} = White", - $"5║ . . . . . . . . ║ {WhiteKing} = White King", - $"4║ . . . . . . . . ║", - $"3║ . . . . . . . . ║", - $"2║ . . . . . . . . ║", - $"1║ . . . . . . . . ║", - $" ╚═════════════════╝", - $" A B C D E F G H" - }; + private static List GetBlankBoard() + { + var retVal = new List + { + $" ╔═══════════════════╗", + $"8║ . . . . . . . . ║ {BlackPiece} = Black", + $"7║ . . . . . . . . ║ {BlackKing} = Black King", + $"6║ . . . . . . . . ║ {WhitePiece} = White", + $"5║ . . . . . . . . ║ {WhiteKing} = White King", + $"4║ . . . . . . . . ║", + $"3║ . . . . . . . . ║", + $"2║ . . . . . . . . ║", + $"1║ . . . . . . . . ║", + $" ╚═══════════════════╝", + $" A B C D E F G H" + }; - return retVal; - } + return retVal; + } } + diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index 2dc04cbd..051ec280 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -1,523 +1,482 @@ -using System.Drawing; -using Checkers.Helpers; +using Checkers.Helpers; using Checkers.Types; +using System.Drawing; namespace Checkers; public static class Engine { - public static MoveOutcome PlayNextMove(PieceColour currentSide, Board currentBoard, Point? playerFrom = null, Point? playerTo = null) - { - List possibleMoves; - MoveOutcome outcome; - - if (playerFrom != null && playerTo != null) - { - var playerMove = BuildPlayerMove(playerFrom, playerTo, currentBoard); - if (playerMove != null) possibleMoves = new List { playerMove }; - outcome = GetAllPossiblePlayerMoves(currentSide, currentBoard, out possibleMoves); - - if (possibleMoves.Count == 0) - { - outcome = MoveOutcome.NoMoveAvailable; - } - else - { - if (MoveIsValid(playerFrom, (Point)playerTo, possibleMoves, currentBoard, currentSide, out var selectedMove)) - { - possibleMoves.Clear(); - - if (selectedMove != null) - { - possibleMoves.Add(selectedMove); - - switch (selectedMove.TypeOfMove) - { - case MoveType.Unknown: - break; - case MoveType.StandardMove: - outcome = MoveOutcome.ValidMoves; - break; - case MoveType.Capture: - outcome = MoveOutcome.Capture; - break; - case MoveType.EndGame: - outcome = MoveOutcome.EndGame; - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - } - } - } - else - { - outcome = AnalysePosition(currentSide, currentBoard, out possibleMoves); - } - - // If a side can't play then other side wins - if (outcome == MoveOutcome.NoMoveAvailable) - { - if (currentSide == PieceColour.Black) - { - outcome = MoveOutcome.WhiteWin; - } - else - { - outcome = MoveOutcome.BlackWin; - } - } - - if (outcome == MoveOutcome.EndGame || outcome == MoveOutcome.ValidMoves) - { - var bestMove = possibleMoves.MinBy(x => x.Weighting); - - if (bestMove == null) - { - throw new ArgumentNullException("No best move selected"); - } - - var pieceToMove = bestMove.PieceToMove; - - if (pieceToMove == null) - { - throw new ArgumentNullException("No piece selected"); - } - - var newX = bestMove.To.X; - var newY = bestMove.To.Y; - - var from = PositionHelper.GetNotationPosition(pieceToMove.XPosition, pieceToMove.YPosition); - - pieceToMove.XPosition = newX; - pieceToMove.YPosition = newY; - - var to = PositionHelper.GetNotationPosition(pieceToMove.XPosition, pieceToMove.YPosition); - - var blackPieces = BoardHelper.GetNumberOfBlackPiecesInPlay(currentBoard); - var whitePieces = BoardHelper.GetNumberOfWhitePiecesInPlay(currentBoard); - - // Promotion can only happen if not already a king and you have reached the far side - if (newY is 1 or 8 && pieceToMove.Promoted == false) - { - pieceToMove.Promoted = true; - - LoggingHelper.LogMove(from, to, PlayerAction.Promotion, currentSide, blackPieces, whitePieces); - } - else - { - LoggingHelper.LogMove(from, to, PlayerAction.Move, currentSide, blackPieces, whitePieces); - } - } - - if (outcome == MoveOutcome.Capture) - { - PerformCapture(currentSide, possibleMoves, currentBoard); - var moreAvailable = MoreCapturesAvailable(currentSide, currentBoard); - - if (moreAvailable) - { - outcome = MoveOutcome.CaptureMoreAvailable; - } - } - - if (outcome != MoveOutcome.CaptureMoreAvailable) - { - ResetCapturePiece(currentBoard); - } - - return outcome; - } - - private static void ResetCapturePiece(Board currentBoard) - { - var capturePiece = GetAggressor(currentBoard); - - if (capturePiece != null) - { - capturePiece.Aggressor = false; - } - } - - private static bool MoreCapturesAvailable(PieceColour currentSide, Board currentBoard) - { - MoveOutcome outcome; - List possibleMoves; - var aggressor = GetAggressor(currentBoard); - - if (aggressor == null) - { - return false; - } - - outcome = GetAllPossiblePlayerMoves(currentSide, currentBoard, out possibleMoves); - - foreach (var move in possibleMoves) - { - if (move.PieceToMove == aggressor && move.Capturing != null) - { - return true; - } - - } - - return false; - } - - private static Piece? GetAggressor(Board currentBoard) - { - return currentBoard.Pieces.FirstOrDefault(x => x.Aggressor); - } - - private static bool MoveIsValid(Point? from, Point to, List possibleMoves, Board currentBoard, PieceColour currentSide, out Move? selectedMove) - { - selectedMove = default; - - if (from != null) - { - var selectedPiece = BoardHelper.GetPieceAt(from.Value.X, from.Value.Y, currentBoard); - - foreach (var move in possibleMoves.Where(move => move.PieceToMove == selectedPiece && move.To == to)) - { - selectedMove = move; - - return true; - } - } - - return false; - } - - private static MoveOutcome GetAllPossiblePlayerMoves(PieceColour currentSide, Board currentBoard, - out List possibleMoves) - { - var aggressor = GetAggressor(currentBoard); - - var result = MoveOutcome.Unknown; - - possibleMoves = new List(); - - if (PlayingWithJustKings(currentSide, currentBoard, out var endGameMoves)) - { - result = MoveOutcome.EndGame; - possibleMoves.AddRange(endGameMoves); - } - - GetPossibleMovesAndAttacks(currentSide, currentBoard, out var nonEndGameMoves); - - if (nonEndGameMoves != null) - { - possibleMoves.AddRange(nonEndGameMoves); - } - - if (aggressor != null) - { - var tempMoves = possibleMoves.Where(x => x.PieceToMove == aggressor).ToList(); - possibleMoves = tempMoves; - } - - return result; - } - - private static Move? BuildPlayerMove(Point? from, Point? to, Board currentBoard) - { - if (from != null) - { - var piece = BoardHelper.GetPieceAt(from.Value.X, from.Value.Y, currentBoard); - - if (to != null) - { - var move = new Move { PieceToMove = piece, To = (Point)to }; - return move; - } - } - - return default; - } - - private static void PerformCapture(PieceColour currentSide, List possibleCaptures, Board currentBoard) - { - var captureMove = possibleCaptures.FirstOrDefault(x => x.Capturing != null); - var from = string.Empty; - var to = string.Empty; - - if (captureMove != null) - { - var squareToCapture = captureMove.Capturing; - - if (squareToCapture != null) - { - var deadMan = - BoardHelper.GetPieceAt(squareToCapture.Value.X, squareToCapture.Value.Y, currentBoard); - - if (deadMan != null) - { - deadMan.InPlay = false; - } - - if (captureMove.PieceToMove != null) - { - captureMove.PieceToMove.Aggressor = true; - from = PositionHelper.GetNotationPosition(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); - - captureMove.PieceToMove.XPosition = captureMove.To.X; - captureMove.PieceToMove.YPosition = captureMove.To.Y; - - to = PositionHelper.GetNotationPosition(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); - - } - } - } - - var anyPromoted = CheckForPiecesToPromote(currentSide, currentBoard); - var blackPieces = BoardHelper.GetNumberOfBlackPiecesInPlay(currentBoard); - var whitePieces = BoardHelper.GetNumberOfWhitePiecesInPlay(currentBoard); - - if (anyPromoted) - { - LoggingHelper.LogMove(from, to, PlayerAction.CapturePromotion, currentSide, blackPieces, whitePieces); - } - else - { - LoggingHelper.LogMove(from, to, PlayerAction.Capture, currentSide, blackPieces, whitePieces); - } - } - - private static bool CheckForPiecesToPromote(PieceColour currentSide, Board currentBoard) - { - var retVal = false; - var promotionSpot = currentSide == PieceColour.White ? 8 : 1; - - foreach (var piece in currentBoard.Pieces.Where(x => x.Side == currentSide)) - { - if (promotionSpot == piece.YPosition && !piece.Promoted) - { - piece.Promoted = retVal = true; - } - } - - return retVal; - } - - private static MoveOutcome AnalysePosition(PieceColour currentSide, Board currentBoard, - out List possibleMoves) - { - var result = MoveOutcome.Unknown; - - possibleMoves = new List(); - - // Check for endgame first - if (PlayingWithJustKings(currentSide, currentBoard, out var endGameMoves)) - { - result = MoveOutcome.EndGame; - possibleMoves.AddRange(endGameMoves); - - return result; - } - - GetPossibleMovesAndAttacks(currentSide, currentBoard, out var nonEndGameMoves); - - if (nonEndGameMoves == null) - { - result = MoveOutcome.NoMoveAvailable; - } - else - { - if (nonEndGameMoves.Any(x => x.Capturing != null)) - { - result = MoveOutcome.Capture; - } - else - { - if (nonEndGameMoves.Count > 0) - { - result = MoveOutcome.ValidMoves; - } - else - { - result = MoveOutcome.NoMoveAvailable; - } - } - } - - if (nonEndGameMoves != null) - { - possibleMoves.AddRange(nonEndGameMoves); - } - - return result; - } - - private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board currentBoard, - out List possibleMoves) - { - possibleMoves = new List(); - - foreach (var piece in currentBoard.Pieces.Where(c => c.Side == currentSide && c.InPlay)) - { - for (var x = -1; x < 2; x++) - { - for (var y = -1; y < 2; y++) - { - if (x == 0 || y == 0) - { - continue; - } - - if (!piece.Promoted) - { - if (currentSide == PieceColour.White && y == -1) - { - continue; - } - - if (currentSide == PieceColour.Black && y == 1) - { - continue; - } - } - - var currentX = piece.XPosition + x; - var currentY = piece.YPosition + y; - - if (!PositionHelper.PointValid(currentX, currentY)) - { - continue; - } - - var targetSquare = BoardHelper.GetSquareOccupancy(currentX, currentY, currentBoard); - - if (targetSquare == PieceColour.NotSet) - { - if (PositionHelper.PointValid(currentX, currentY)) - { - var newMove = new Move { PieceToMove = piece, TypeOfMove = MoveType.StandardMove, To = new Point { X = currentX, Y = currentY } }; - possibleMoves.Add(newMove); - } - } - else - { - var haveTarget = targetSquare != currentSide; - - if (!haveTarget) - { - continue; - } - - var toLocation = DeriveToPosition(piece.XPosition, piece.YPosition, currentX, currentY); - - var beyondX = toLocation.X; - var beyondY = toLocation.Y; - - if (!PositionHelper.PointValid(beyondX, beyondY)) - { - continue; - } - - var beyondSquare = BoardHelper.GetSquareOccupancy(beyondX, beyondY, currentBoard); - - if (beyondSquare != PieceColour.NotSet) - { - continue; - } - - var attack = new Move { PieceToMove = piece, TypeOfMove = MoveType.Capture, To = new Point { X = beyondX, Y = beyondY }, Capturing = new Point { X = currentX, Y = currentY } }; - possibleMoves.Add(attack); - } - } - } - } - } - - private static Point DeriveToPosition(int pieceX, int pieceY, int captureX, int captureY) - { - var newX = 0; - var newY = 0; - - if (captureX > pieceX) - { - newX = captureX + 1; - } - else - { - newX = captureX - 1; - } - - if (captureY > pieceY) - { - newY = captureY + 1; - } - else - { - newY = captureY - 1; - } - - return new Point(newX, newY); - } - - private static bool PlayingWithJustKings(PieceColour currentSide, Board currentBoard, out List possibleMoves) - { - possibleMoves = new List(); - var piecesInPlay = currentBoard.Pieces.Count(x => x.Side == currentSide && x.InPlay); - var kingsInPlay = currentBoard.Pieces.Count(x => x.Side == currentSide && x.InPlay && x.Promoted); - - var playingWithJustKings = piecesInPlay == kingsInPlay; - - if (playingWithJustKings) - { - var shortestDistance = 12.0; - - Piece? currentHero = null; - Piece? currentVillain = null; - - foreach (var king in currentBoard.Pieces.Where(x => x.Side == currentSide && x.InPlay)) - { - foreach (var target in currentBoard.Pieces.Where(x => x.Side != currentSide && x.InPlay)) - { - var kingPoint = new Point(king.XPosition, king.YPosition); - var targetPoint = new Point(target.XPosition, target.YPosition); - var distance = VectorHelper.GetPointDistance(kingPoint, targetPoint); - - if (distance < shortestDistance) - { - shortestDistance = distance; - currentHero = king; - currentVillain = target; - } - } - } - - if (currentHero != null && currentVillain != null) - { - var movementOptions = VectorHelper.WhereIsVillain(currentHero, currentVillain); - - foreach (var movementOption in movementOptions) - { - var squareStatus = BoardHelper.GetSquareOccupancy(movementOption.X, movementOption.Y, currentBoard); - - if (squareStatus == PieceColour.NotSet) - { - var theMove = new Move - { - PieceToMove = currentHero, - TypeOfMove = MoveType.EndGame, - To = new Point(movementOption.X, movementOption.Y) - }; - - if (!PositionHelper.PointValid(movementOption.X, movementOption.Y)) - { - continue; - } - - possibleMoves.Add(theMove); - - break; - } - } - } - } - - return possibleMoves.Count > 0; - } + public static MoveOutcome PlayNextMove(PieceColour currentSide, Board currentBoard, Point? playerFrom = null, Point? playerTo = null) + { + List possibleMoves; + MoveOutcome outcome; + + if (playerFrom != null && playerTo != null) + { + outcome = GetAllPossiblePlayerMoves(currentSide, currentBoard, out possibleMoves); + + if (possibleMoves.Count == 0) + { + outcome = MoveOutcome.NoMoveAvailable; + } + else + { + if (MoveIsValid(playerFrom, (Point)playerTo, possibleMoves, currentBoard, out var selectedMove)) + { + possibleMoves.Clear(); + + if (selectedMove != null) + { + possibleMoves.Add(selectedMove); + + switch (selectedMove.TypeOfMove) + { + case MoveType.Unknown: + break; + case MoveType.StandardMove: + outcome = MoveOutcome.ValidMoves; + break; + case MoveType.Capture: + outcome = MoveOutcome.Capture; + break; + case MoveType.EndGame: + outcome = MoveOutcome.EndGame; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + } + } + else + { + outcome = AnalysePosition(currentSide, currentBoard, out possibleMoves); + } + + // If a side can't play then other side wins + if (outcome == MoveOutcome.NoMoveAvailable) + { + outcome = currentSide == PieceColour.Black ? MoveOutcome.WhiteWin : MoveOutcome.BlackWin; + } + + switch (outcome) + { + case MoveOutcome.EndGame: + case MoveOutcome.ValidMoves: + { + var bestMove = possibleMoves.MinBy(x => x.Weighting); + + if (bestMove == null) + { + throw new ArgumentNullException(nameof(bestMove)); + } + + var pieceToMove = bestMove.PieceToMove; + + if (pieceToMove == null) + { + throw new ArgumentNullException(nameof(pieceToMove)); + } + + var newX = bestMove.To.X; + var newY = bestMove.To.Y; + + var from = PositionHelper.GetNotationPosition(pieceToMove.XPosition, pieceToMove.YPosition); + + pieceToMove.XPosition = newX; + pieceToMove.YPosition = newY; + + var to = PositionHelper.GetNotationPosition(pieceToMove.XPosition, pieceToMove.YPosition); + + var blackPieces = BoardHelper.GetNumberOfBlackPiecesInPlay(currentBoard); + var whitePieces = BoardHelper.GetNumberOfWhitePiecesInPlay(currentBoard); + + // Promotion can only happen if not already a king and you have reached the far side + if (newY is 1 or 8 && pieceToMove.Promoted == false) + { + pieceToMove.Promoted = true; + + LoggingHelper.LogMove(from, to, PlayerAction.Promotion, currentSide, blackPieces, whitePieces); + } + else + { + LoggingHelper.LogMove(from, to, PlayerAction.Move, currentSide, blackPieces, whitePieces); + } + + break; + } + case MoveOutcome.Capture: + { + var anyPromoted = PerformCapture(currentSide, possibleMoves, currentBoard); + var moreAvailable = MoreCapturesAvailable(currentSide, currentBoard); + + if (moreAvailable && !anyPromoted) + { + outcome = MoveOutcome.CaptureMoreAvailable; + } + + break; + } + } + + if (outcome != MoveOutcome.CaptureMoreAvailable) + { + ResetCapturePiece(currentBoard); + } + + return outcome; + } + + private static void ResetCapturePiece(Board currentBoard) + { + var capturePiece = GetAggressor(currentBoard); + + if (capturePiece != null) + { + capturePiece.Aggressor = false; + } + } + + private static bool MoreCapturesAvailable(PieceColour currentSide, Board currentBoard) + { + var aggressor = GetAggressor(currentBoard); + + if (aggressor == null) + { + return false; + } + + _ = GetAllPossiblePlayerMoves(currentSide, currentBoard, out var possibleMoves); + + return possibleMoves.Any(move => move.PieceToMove == aggressor && move.Capturing != null); + } + + private static Piece? GetAggressor(Board currentBoard) + { + return currentBoard.Pieces.FirstOrDefault(x => x.Aggressor); + } + + private static bool MoveIsValid(Point? from, Point to, List possibleMoves, Board currentBoard, out Move? selectedMove) + { + selectedMove = default; + + if (from == null) + { + return false; + } + + var selectedPiece = BoardHelper.GetPieceAt(from.Value.X, from.Value.Y, currentBoard); + + foreach (var move in possibleMoves.Where(move => move.PieceToMove == selectedPiece && move.To == to)) + { + selectedMove = move; + + return true; + } + + return false; + } + + private static MoveOutcome GetAllPossiblePlayerMoves(PieceColour currentSide, Board currentBoard, + out List possibleMoves) + { + var aggressor = GetAggressor(currentBoard); + + var result = MoveOutcome.Unknown; + + possibleMoves = new List(); + + if (PlayingWithJustKings(currentSide, currentBoard, out var endGameMoves)) + { + result = MoveOutcome.EndGame; + possibleMoves.AddRange(endGameMoves); + } + + GetPossibleMovesAndAttacks(currentSide, currentBoard, out var nonEndGameMoves); + + possibleMoves.AddRange(nonEndGameMoves); + + if (aggressor != null) + { + var tempMoves = possibleMoves.Where(x => x.PieceToMove == aggressor).ToList(); + possibleMoves = tempMoves; + } + + return result; + } + + private static bool PerformCapture(PieceColour currentSide, List possibleCaptures, Board currentBoard) + { + var captureMove = possibleCaptures.FirstOrDefault(x => x.Capturing != null); + var from = string.Empty; + var to = string.Empty; + + if (captureMove != null) + { + var squareToCapture = captureMove.Capturing; + + if (squareToCapture != null) + { + var deadMan = + BoardHelper.GetPieceAt(squareToCapture.Value.X, squareToCapture.Value.Y, currentBoard); + + if (deadMan != null) + { + deadMan.InPlay = false; + } + + if (captureMove.PieceToMove != null) + { + captureMove.PieceToMove.Aggressor = true; + from = PositionHelper.GetNotationPosition(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); + + captureMove.PieceToMove.XPosition = captureMove.To.X; + captureMove.PieceToMove.YPosition = captureMove.To.Y; + + to = PositionHelper.GetNotationPosition(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); + + } + } + } + + var anyPromoted = CheckForPiecesToPromote(currentSide, currentBoard); + var blackPieces = BoardHelper.GetNumberOfBlackPiecesInPlay(currentBoard); + var whitePieces = BoardHelper.GetNumberOfWhitePiecesInPlay(currentBoard); + + var playerAction = anyPromoted ? PlayerAction.CapturePromotion : PlayerAction.Capture; + + LoggingHelper.LogMove(from, to, playerAction, currentSide, blackPieces, whitePieces); + + return anyPromoted; + } + + private static bool CheckForPiecesToPromote(PieceColour currentSide, Board currentBoard) + { + var retVal = false; + var promotionSpot = currentSide == PieceColour.White ? 8 : 1; + + foreach (var piece in currentBoard.Pieces.Where(x => x.Side == currentSide)) + { + if (promotionSpot == piece.YPosition && !piece.Promoted) + { + piece.Promoted = retVal = true; + } + } + + return retVal; + } + + private static MoveOutcome AnalysePosition(PieceColour currentSide, Board currentBoard, + out List possibleMoves) + { + MoveOutcome result; + + possibleMoves = new List(); + + // Check for endgame first + if (PlayingWithJustKings(currentSide, currentBoard, out var endGameMoves)) + { + result = MoveOutcome.EndGame; + possibleMoves.AddRange(endGameMoves); + + return result; + } + + GetPossibleMovesAndAttacks(currentSide, currentBoard, out var nonEndGameMoves); + + if (nonEndGameMoves.Count == 0) + { + result = MoveOutcome.NoMoveAvailable; + } + else + { + if (nonEndGameMoves.Any(x => x.Capturing != null)) + { + result = MoveOutcome.Capture; + } + else + { + result = nonEndGameMoves.Count > 0 ? MoveOutcome.ValidMoves : MoveOutcome.NoMoveAvailable; + } + } + + if (nonEndGameMoves.Count > 0) + { + possibleMoves.AddRange(nonEndGameMoves); + } + + return result; + } + + private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board currentBoard, + out List possibleMoves) + { + possibleMoves = new List(); + + foreach (var piece in currentBoard.Pieces.Where(c => c.Side == currentSide && c.InPlay)) + { + for (var x = -1; x < 2; x++) + { + for (var y = -1; y < 2; y++) + { + if (x == 0 || y == 0) + { + continue; + } + + if (!piece.Promoted) + { + switch (currentSide) + { + case PieceColour.White when y == -1: + case PieceColour.Black when y == 1: + continue; + } + } + + var currentX = piece.XPosition + x; + var currentY = piece.YPosition + y; + + if (!PositionHelper.PointValid(currentX, currentY)) + { + continue; + } + + var targetSquare = BoardHelper.GetSquareOccupancy(currentX, currentY, currentBoard); + + if (targetSquare == PieceColour.NotSet) + { + if (!PositionHelper.PointValid(currentX, currentY)) + { + continue; + } + + var newMove = new Move { PieceToMove = piece, TypeOfMove = MoveType.StandardMove, To = new Point { X = currentX, Y = currentY } }; + possibleMoves.Add(newMove); + } + else + { + var haveTarget = targetSquare != currentSide; + + if (!haveTarget) + { + continue; + } + + var toLocation = DeriveToPosition(piece.XPosition, piece.YPosition, currentX, currentY); + + var beyondX = toLocation.X; + var beyondY = toLocation.Y; + + if (!PositionHelper.PointValid(beyondX, beyondY)) + { + continue; + } + + var beyondSquare = BoardHelper.GetSquareOccupancy(beyondX, beyondY, currentBoard); + + if (beyondSquare != PieceColour.NotSet) + { + continue; + } + + var attack = new Move { PieceToMove = piece, TypeOfMove = MoveType.Capture, To = new Point { X = beyondX, Y = beyondY }, Capturing = new Point { X = currentX, Y = currentY } }; + possibleMoves.Add(attack); + } + } + } + } + } + + private static Point DeriveToPosition(int pieceX, int pieceY, int captureX, int captureY) + { + int newX; + if (captureX > pieceX) + { + newX = captureX + 1; + } + else + { + newX = captureX - 1; + } + + int newY; + if (captureY > pieceY) + { + newY = captureY + 1; + } + else + { + newY = captureY - 1; + } + + return new Point(newX, newY); + } + + private static bool PlayingWithJustKings(PieceColour currentSide, Board currentBoard, out List possibleMoves) + { + possibleMoves = new List(); + var piecesInPlay = currentBoard.Pieces.Count(x => x.Side == currentSide && x.InPlay); + var kingsInPlay = currentBoard.Pieces.Count(x => x.Side == currentSide && x.InPlay && x.Promoted); + + var playingWithJustKings = piecesInPlay == kingsInPlay; + + if (playingWithJustKings) + { + var shortestDistance = 12.0; + + Piece? currentHero = null; + Piece? currentVillain = null; + + foreach (var king in currentBoard.Pieces.Where(x => x.Side == currentSide && x.InPlay)) + { + foreach (var target in currentBoard.Pieces.Where(x => x.Side != currentSide && x.InPlay)) + { + var kingPoint = new Point(king.XPosition, king.YPosition); + var targetPoint = new Point(target.XPosition, target.YPosition); + var distance = VectorHelper.GetPointDistance(kingPoint, targetPoint); + + if (distance < shortestDistance) + { + shortestDistance = distance; + currentHero = king; + currentVillain = target; + } + } + } + + if (currentHero != null && currentVillain != null) + { + var movementOptions = VectorHelper.WhereIsVillain(currentHero, currentVillain); + + foreach (var movementOption in movementOptions) + { + var squareStatus = BoardHelper.GetSquareOccupancy(movementOption.X, movementOption.Y, currentBoard); + + if (squareStatus == PieceColour.NotSet) + { + var theMove = new Move + { + PieceToMove = currentHero, + TypeOfMove = MoveType.EndGame, + To = new Point(movementOption.X, movementOption.Y) + }; + + if (!PositionHelper.PointValid(movementOption.X, movementOption.Y)) + { + continue; + } + + possibleMoves.Add(theMove); + + break; + } + } + } + } + + return possibleMoves.Count > 0; + } } + diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index aadf7f4a..cf9df45f 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -1,115 +1,116 @@ -using System.Drawing; -using Checkers.Types; +using Checkers.Types; +using System.Drawing; namespace Checkers; public class Game { - private const int PiecesPerSide = 12; - - public PieceColour CurrentGo { get; set; } - public Board GameBoard { get; } - public int MovesSoFar { get; private set; } - public PieceColour GameWinner { get; set; } = PieceColour.NotSet; - - private bool NoDisplay { get; set; } - - public List Players { get; set; } = new List - { - new Player { ControlledBy = PlayerControl.Computer, Side = PieceColour.Black }, - new Player { ControlledBy = PlayerControl.Computer, Side = PieceColour.White } - }; - - public Game(List startingPosition, PieceColour toMove) - { - GameBoard = new Board(startingPosition); - CurrentGo = toMove; - NoDisplay = true; - } - - public Game() - { - GameBoard = new Board(); - CurrentGo = PieceColour.Black; - NoDisplay = false; - ShowBoard(); - } - - public MoveOutcome NextRound(Point? from = null, Point? to = null) - { - MovesSoFar++; - var res = Engine.PlayNextMove(CurrentGo, GameBoard, from, to); - - while (from == null & to == null && res == MoveOutcome.CaptureMoreAvailable) - { - res = Engine.PlayNextMove(CurrentGo, GameBoard, from, to); - } - - if (res == MoveOutcome.BlackWin) - { - GameWinner = PieceColour.Black; - } - - if (res == MoveOutcome.WhiteWin) - { - GameWinner = PieceColour.White; - } - - if (GameWinner == PieceColour.NotSet && res != MoveOutcome.CaptureMoreAvailable) - { - CheckSidesHavePiecesLeft(); - CurrentGo = CurrentGo == PieceColour.Black ? PieceColour.White : PieceColour.Black; - } - - if (res == MoveOutcome.Unknown) - { - CurrentGo = CurrentGo == PieceColour.Black - ? PieceColour.White - : PieceColour.Black; - } - - ShowBoard(); - - return res; - } - - public void CheckSidesHavePiecesLeft() - { - var retVal = GameBoard.Pieces.Where(x => x.InPlay).Select(y => y.Side).Distinct().Count() == 2; - - if (!retVal) - { - GameWinner = GameBoard.Pieces.Where(x => x.InPlay).Select(y => y.Side).Distinct().FirstOrDefault(); - } - } - - public string GetCurrentPlayer() - { - return CurrentGo.ToString(); - } - - public int GetWhitePiecesTaken() - { - return GetPiecesTakenForSide(PieceColour.White); - } - - public int GetBlackPiecesTaken() - { - return GetPiecesTakenForSide(PieceColour.Black); - } - - public int GetPiecesTakenForSide(PieceColour colour) - { - return PiecesPerSide - GameBoard.Pieces.Count(x => x.InPlay && x.Side == colour); - } - - private void ShowBoard() - { - if (!NoDisplay) - { - Display.DisplayBoard(GameBoard); - Display.DisplayStats(GetWhitePiecesTaken(), GetBlackPiecesTaken()); - Display.DisplayCurrentPlayer(CurrentGo); - } - } + private const int PiecesPerSide = 12; + + public PieceColour CurrentGo { get; set; } + public Board GameBoard { get; } + public int MovesSoFar { get; private set; } + public PieceColour GameWinner { get; set; } = PieceColour.NotSet; + + private bool NoDisplay { get; } + + public List Players { get; set; } = new() + { + new Player { ControlledBy = PlayerControl.Computer, Side = PieceColour.Black }, + new Player { ControlledBy = PlayerControl.Computer, Side = PieceColour.White } + }; + + public Game(List startingPosition, PieceColour toMove) + { + GameBoard = new Board(startingPosition); + CurrentGo = toMove; + NoDisplay = true; + } + + public Game() + { + GameBoard = new Board(); + CurrentGo = PieceColour.Black; + NoDisplay = false; + ShowBoard(); + } + + public MoveOutcome NextRound(Point? from = null, Point? to = null) + { + MovesSoFar++; + var res = Engine.PlayNextMove(CurrentGo, GameBoard, from, to); + + while (from == null & to == null && res == MoveOutcome.CaptureMoreAvailable) + { + res = Engine.PlayNextMove(CurrentGo, GameBoard, from, to); + } + + if (res == MoveOutcome.BlackWin) + { + GameWinner = PieceColour.Black; + } + + if (res == MoveOutcome.WhiteWin) + { + GameWinner = PieceColour.White; + } + + if (GameWinner == PieceColour.NotSet && res != MoveOutcome.CaptureMoreAvailable) + { + CheckSidesHavePiecesLeft(); + CurrentGo = CurrentGo == PieceColour.Black ? PieceColour.White : PieceColour.Black; + } + + if (res == MoveOutcome.Unknown) + { + CurrentGo = CurrentGo == PieceColour.Black + ? PieceColour.White + : PieceColour.Black; + } + + ShowBoard(); + + return res; + } + + public void CheckSidesHavePiecesLeft() + { + var retVal = GameBoard.Pieces.Where(x => x.InPlay).Select(y => y.Side).Distinct().Count() == 2; + + if (!retVal) + { + GameWinner = GameBoard.Pieces.Where(x => x.InPlay).Select(y => y.Side).Distinct().FirstOrDefault(); + } + } + + public string GetCurrentPlayer() + { + return CurrentGo.ToString(); + } + + public int GetWhitePiecesTaken() + { + return GetPiecesTakenForSide(PieceColour.White); + } + + public int GetBlackPiecesTaken() + { + return GetPiecesTakenForSide(PieceColour.Black); + } + + public int GetPiecesTakenForSide(PieceColour colour) + { + return PiecesPerSide - GameBoard.Pieces.Count(x => x.InPlay && x.Side == colour); + } + + private void ShowBoard() + { + if (!NoDisplay) + { + Display.DisplayBoard(GameBoard); + Display.DisplayStats(GetWhitePiecesTaken(), GetBlackPiecesTaken()); + Display.DisplayCurrentPlayer(CurrentGo); + } + } } + diff --git a/Projects/Checkers/Helpers/BoardHelper.cs b/Projects/Checkers/Helpers/BoardHelper.cs index f265e2bb..447a9a88 100644 --- a/Projects/Checkers/Helpers/BoardHelper.cs +++ b/Projects/Checkers/Helpers/BoardHelper.cs @@ -3,62 +3,63 @@ namespace Checkers.Helpers; - /// - /// Board related routines - /// - public static class BoardHelper +/// +/// Board related routines +/// +public static class BoardHelper +{ + public static List GetStartingPosition() { - public static List GetStartingPosition() - { - // HACK: Can set to false and define a custom start position in GetLimitedStartingPosition - const bool UseDefault = true; + // HACK: Can set to false and define a custom start position in GetLimitedStartingPosition + const bool UseDefault = true; #pragma warning disable CS0162 - return UseDefault ? GetDefaultStartingPosition() : GetLimitedStartingPosition(); + return UseDefault ? GetDefaultStartingPosition() : GetLimitedStartingPosition(); #pragma warning restore CS0162 - } - - private static List GetLimitedStartingPosition() - { - return KnowledgeBase.GetLimitedStartingPosition(); - } - - public static List GetDefaultStartingPosition() - { - return KnowledgeBase.GetStartingPosition(); - } + } - public static PieceColour GetSquareOccupancy(int xp, int yp, Board currentBoard) - { - var piece = currentBoard.Pieces.FirstOrDefault(x => x.XPosition == xp && x.InPlay && x.YPosition == yp); + private static List GetLimitedStartingPosition() + { + return KnowledgeBase.GetLimitedStartingPosition(); + } - return piece == null ? PieceColour.NotSet : piece.Side; - } + public static List GetDefaultStartingPosition() + { + return KnowledgeBase.GetStartingPosition(); + } - public static Piece? GetPieceAt(int xp, int yp, Board currentBoard) - { - var retVal = currentBoard.Pieces.FirstOrDefault(x => x.XPosition == xp && x.InPlay && x.YPosition == yp); + public static PieceColour GetSquareOccupancy(int xp, int yp, Board currentBoard) + { + var piece = currentBoard.Pieces.FirstOrDefault(x => x.XPosition == xp && x.InPlay && x.YPosition == yp); - if (retVal == null) - { - return default; - } + return piece == null ? PieceColour.NotSet : piece.Side; + } - return retVal; - } + public static Piece? GetPieceAt(int xp, int yp, Board currentBoard) + { + var retVal = currentBoard.Pieces.FirstOrDefault(x => x.XPosition == xp && x.InPlay && x.YPosition == yp); - public static int GetNumberOfWhitePiecesInPlay(Board currentBoard) + if (retVal == null) { - return GetNumberOfPiecesInPlay(currentBoard, PieceColour.White); + return default; } - public static int GetNumberOfBlackPiecesInPlay(Board currentBoard) - { - return GetNumberOfPiecesInPlay(currentBoard, PieceColour.Black); - } + return retVal; + } - private static int GetNumberOfPiecesInPlay(Board currentBoard, PieceColour currentSide) - { - return currentBoard.Pieces.Count(x => x.Side == currentSide && x.InPlay); - } + public static int GetNumberOfWhitePiecesInPlay(Board currentBoard) + { + return GetNumberOfPiecesInPlay(currentBoard, PieceColour.White); + } + + public static int GetNumberOfBlackPiecesInPlay(Board currentBoard) + { + return GetNumberOfPiecesInPlay(currentBoard, PieceColour.Black); } + + private static int GetNumberOfPiecesInPlay(Board currentBoard, PieceColour currentSide) + { + return currentBoard.Pieces.Count(x => x.Side == currentSide && x.InPlay); + } +} + diff --git a/Projects/Checkers/Helpers/DisplayHelper.cs b/Projects/Checkers/Helpers/DisplayHelper.cs new file mode 100644 index 00000000..10a19d99 --- /dev/null +++ b/Projects/Checkers/Helpers/DisplayHelper.cs @@ -0,0 +1,15 @@ +using System.Drawing; + +namespace Checkers.Helpers; + +public static class DisplayHelper +{ + public static Point GetScreenPositionFromBoardPosition(Point boardPosition) + { + var actualX = (boardPosition.X * 2) + 2; + var actualY = boardPosition.Y + 0; + + return new Point(actualX, actualY); + } +} + diff --git a/Projects/Checkers/Helpers/LoggingHelper.cs b/Projects/Checkers/Helpers/LoggingHelper.cs index f719dd6e..2b3bc7a8 100644 --- a/Projects/Checkers/Helpers/LoggingHelper.cs +++ b/Projects/Checkers/Helpers/LoggingHelper.cs @@ -1,63 +1,63 @@ -using System.Diagnostics; -using Checkers.Types; +using Checkers.Types; +using System.Diagnostics; namespace Checkers.Helpers; - /// - /// Used for logging games for analysis - see flag in Program.cs - /// - public static class LoggingHelper +/// +/// Used for logging games for analysis - see flag in Program.cs +/// +public static class LoggingHelper +{ + public static void LogMove(string from, string to, PlayerAction action, PieceColour sidePlaying, int blacksInPlay, int whitesInPlay) { - public static void LogMove(string from, string to, PlayerAction action, PieceColour sidePlaying, int blacksInPlay, int whitesInPlay) - { - var colour = sidePlaying == PieceColour.Black ? "B" : "W"; - var suffix = DecodePlayerAction(action); - - var outputLine = $"Move : {colour} {from}-{to} {suffix,2}"; - - if (action == PlayerAction.Capture || action == PlayerAction.CapturePromotion) - { - var piecesCount = $" B:{blacksInPlay,2} W:{whitesInPlay,2}"; - outputLine += piecesCount; - } + var colour = sidePlaying == PieceColour.Black ? "B" : "W"; + var suffix = DecodePlayerAction(action); - Trace.WriteLine(outputLine); - } + var outputLine = $"Move : {colour} {from}-{to} {suffix,2}"; - public static void LogMoves(int numberOfMoves) + if (action == PlayerAction.Capture || action == PlayerAction.CapturePromotion) { - Trace.WriteLine($"Moves : {numberOfMoves}"); + var piecesCount = $" B:{blacksInPlay,2} W:{whitesInPlay,2}"; + outputLine += piecesCount; } - public static void LogStart() - { - Trace.WriteLine($"Started: {DateTime.Now}"); - } + Trace.WriteLine(outputLine); + } - public static void LogFinish() - { - Trace.WriteLine($"Stopped: {DateTime.Now}"); - } + public static void LogMoves(int numberOfMoves) + { + Trace.WriteLine($"Moves : {numberOfMoves}"); + } - public static void LogOutcome(PieceColour winner) - { - Trace.WriteLine($"Winner : {winner}"); - } + public static void LogStart() + { + Trace.WriteLine($"Started: {DateTime.Now}"); + } - private static string DecodePlayerAction(PlayerAction action) + public static void LogFinish() + { + Trace.WriteLine($"Stopped: {DateTime.Now}"); + } + + public static void LogOutcome(PieceColour winner) + { + Trace.WriteLine($"Winner : {winner}"); + } + + private static string DecodePlayerAction(PlayerAction action) + { + switch (action) { - switch (action) - { - case PlayerAction.Move: - return string.Empty; - case PlayerAction.Promotion: - return "K"; - case PlayerAction.Capture: - return "X"; - case PlayerAction.CapturePromotion: - return "KX"; - default: - return String.Empty; - } + case PlayerAction.Move: + return string.Empty; + case PlayerAction.Promotion: + return "K"; + case PlayerAction.Capture: + return "X"; + case PlayerAction.CapturePromotion: + return "KX"; + default: + return String.Empty; } } +} diff --git a/Projects/Checkers/Helpers/PlayerHelper.cs b/Projects/Checkers/Helpers/PlayerHelper.cs index 87541113..40cdaf16 100644 --- a/Projects/Checkers/Helpers/PlayerHelper.cs +++ b/Projects/Checkers/Helpers/PlayerHelper.cs @@ -2,42 +2,43 @@ namespace Checkers.Helpers; - /// - /// Assigns AI/human players - /// N.B. currently can only play as black in a one player game - /// - public static class PlayerHelper +/// +/// Assigns AI/human players +/// N.B. currently can only play as black in a one player game +/// +public static class PlayerHelper +{ + public static void AssignPlayersToSide(int numberOfPlayers, Game currentGame) { - public static void AssignPlayersToSide(int numberOfPlayers, Game currentGame) + switch (numberOfPlayers) { - switch (numberOfPlayers) - { - case 0: - foreach (var player in currentGame.Players) - { - player.ControlledBy = PlayerControl.Computer; - } + case 0: + foreach (var player in currentGame.Players) + { + player.ControlledBy = PlayerControl.Computer; + } - break; - case 1: - foreach (var player in currentGame.Players.Where(x => x.Side == PieceColour.White)) - { - player.ControlledBy = PlayerControl.Computer; - } + break; + case 1: + foreach (var player in currentGame.Players.Where(x => x.Side == PieceColour.White)) + { + player.ControlledBy = PlayerControl.Computer; + } - foreach (var player in currentGame.Players.Where(x => x.Side == PieceColour.Black)) - { - player.ControlledBy = PlayerControl.Human; - } + foreach (var player in currentGame.Players.Where(x => x.Side == PieceColour.Black)) + { + player.ControlledBy = PlayerControl.Human; + } - break; - case 2: - foreach (var player in currentGame.Players) - { - player.ControlledBy = PlayerControl.Human; - } + break; + case 2: + foreach (var player in currentGame.Players) + { + player.ControlledBy = PlayerControl.Human; + } - break; - } + break; } } +} + diff --git a/Projects/Checkers/Helpers/PositionHelper.cs b/Projects/Checkers/Helpers/PositionHelper.cs index 6c732da5..c14734f9 100644 --- a/Projects/Checkers/Helpers/PositionHelper.cs +++ b/Projects/Checkers/Helpers/PositionHelper.cs @@ -2,54 +2,55 @@ namespace Checkers.Helpers; - /// - /// Position routines - /// - public static class PositionHelper +/// +/// Position routines +/// +public static class PositionHelper +{ + public static string GetNotationPosition(int x, int y) { - public static string GetNotationPosition(int x, int y) + if (!PointValid(x, y)) { - if (!PointValid(x, y)) - { - throw new ArgumentException("Not a valid position!"); - } + throw new ArgumentException("Not a valid position!"); + } - var yPortion = 9 - y; + var yPortion = 9 - y; - var xPortion = Convert.ToChar(x + 64); + var xPortion = Convert.ToChar(x + 64); - return xPortion + yPortion.ToString(); - } + return xPortion + yPortion.ToString(); + } - public static Point? GetPositionByNotation(string notation) + public static Point? GetPositionByNotation(string notation) + { + const int ExpectedPositionLength = 2; + + notation = notation.Trim(); + + if (notation.Length != ExpectedPositionLength) { - const int ExpectedPositionLength = 2; - - notation = notation.Trim(); - - if (notation.Length != ExpectedPositionLength) - { - return default; - } - - try - { - var letterPart = notation.Substring(0, 1); - var numberPart = notation.Substring(1, 1); - - var x = letterPart.ToUpper().ToCharArray()[0] - 64; - var y = 9 - Convert.ToInt32(numberPart); - - return new Point(x, y); - } - catch - { - return default; - } + return default; } - public static bool PointValid(int x, int y) + try + { + var letterPart = notation.Substring(0, 1); + var numberPart = notation.Substring(1, 1); + + var x = letterPart.ToUpper().ToCharArray()[0] - 64; + var y = 9 - Convert.ToInt32(numberPart); + + return new Point(x, y); + } + catch { - return x is > 0 and < 9 && y is > 0 and < 9; + return default; } } + + public static bool PointValid(int x, int y) + { + return x is > 0 and < 9 && y is > 0 and < 9; + } +} + diff --git a/Projects/Checkers/Helpers/VectorHelper.cs b/Projects/Checkers/Helpers/VectorHelper.cs index 2d0dd7e1..4a1e446f 100644 --- a/Projects/Checkers/Helpers/VectorHelper.cs +++ b/Projects/Checkers/Helpers/VectorHelper.cs @@ -1,111 +1,112 @@ -using System.Drawing; -using Checkers.Types; +using Checkers.Types; +using System.Drawing; namespace Checkers.Helpers; - /// - /// Track distance between 2 points - /// Used in endgame for kings to hunt down closest victim - /// - public static class VectorHelper +/// +/// Track distance between 2 points +/// Used in endgame for kings to hunt down closest victim +/// +public static class VectorHelper +{ + public static double GetPointDistance(Point first, Point second) { - public static double GetPointDistance(Point first, Point second) + // Easiest cases are points on the same vertical or horizontal axis + if (first.X == second.X) { - // Easiest cases are points on the same vertical or horizontal axis - if (first.X == second.X) - { - return Math.Abs(first.Y - second.Y); - } + return Math.Abs(first.Y - second.Y); + } - if (first.Y == second.Y) - { - return Math.Abs(first.X - second.X); - } + if (first.Y == second.Y) + { + return Math.Abs(first.X - second.X); + } + + // Pythagoras baby + var sideA = (double)Math.Abs(first.Y - second.Y); + var sideB = (double)Math.Abs(first.X - second.X); + + return Math.Sqrt(Math.Pow(sideA, 2) + Math.Pow(sideB, 2)); + } + + public static List WhereIsVillain(Piece hero, Piece villain) + { + var retVal = new List(); - // Pythagoras baby - var sideA = (double)Math.Abs(first.Y - second.Y); - var sideB = (double)Math.Abs(first.X - second.X); + var directions = new List(); - return Math.Sqrt(Math.Pow(sideA, 2) + Math.Pow(sideB, 2)); + if (hero.XPosition > villain.XPosition) + { + directions.Add(Direction.Left); } - public static List WhereIsVillain(Piece hero, Piece villain) + if (hero.XPosition < villain.XPosition) { - var retVal = new List(); + directions.Add(Direction.Right); + } - var directions = new List(); + if (hero.YPosition > villain.YPosition) + { + directions.Add(Direction.Up); + } - if (hero.XPosition > villain.XPosition) - { - directions.Add(Direction.Left); - } + if (hero.YPosition < villain.YPosition) + { + directions.Add(Direction.Down); + } - if (hero.XPosition < villain.XPosition) + if (directions.Count == 1) + { + switch (directions[0]) { - directions.Add(Direction.Right); - } + case Direction.Up: + retVal.Add(new Point(hero.XPosition - 1, hero.YPosition - 1)); + retVal.Add(new Point(hero.XPosition + 1, hero.YPosition - 1)); - if (hero.YPosition > villain.YPosition) + break; + case Direction.Down: + retVal.Add(new Point(hero.XPosition - 1, hero.YPosition + 1)); + retVal.Add(new Point(hero.XPosition + 1, hero.YPosition + 1)); + + break; + case Direction.Left: + retVal.Add(new Point(hero.XPosition - 1, hero.YPosition - 1)); + retVal.Add(new Point(hero.XPosition - 1, hero.YPosition + 1)); + + break; + case Direction.Right: + retVal.Add(new Point(hero.XPosition + 1, hero.YPosition - 1)); + retVal.Add(new Point(hero.XPosition + 1, hero.YPosition + 1)); + + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + else + { + if (directions.Contains(Direction.Left) && directions.Contains(Direction.Up)) { - directions.Add(Direction.Up); + retVal.Add(new Point(hero.XPosition - 1, hero.YPosition - 1)); } - if (hero.YPosition < villain.YPosition) + if (directions.Contains(Direction.Left) && directions.Contains(Direction.Down)) { - directions.Add(Direction.Down); + retVal.Add(new Point(hero.XPosition - 1, hero.YPosition + 1)); } - if (directions.Count == 1) + if (directions.Contains(Direction.Right) && directions.Contains(Direction.Up)) { - switch (directions[0]) - { - case Direction.Up: - retVal.Add(new Point(hero.XPosition - 1, hero.YPosition - 1)); - retVal.Add(new Point(hero.XPosition + 1, hero.YPosition - 1)); - - break; - case Direction.Down: - retVal.Add(new Point(hero.XPosition - 1, hero.YPosition + 1)); - retVal.Add(new Point(hero.XPosition + 1, hero.YPosition + 1)); - - break; - case Direction.Left: - retVal.Add(new Point(hero.XPosition - 1, hero.YPosition - 1)); - retVal.Add(new Point(hero.XPosition - 1, hero.YPosition + 1)); - - break; - case Direction.Right: - retVal.Add(new Point(hero.XPosition + 1, hero.YPosition - 1)); - retVal.Add(new Point(hero.XPosition + 1, hero.YPosition + 1)); - - break; - default: - throw new ArgumentOutOfRangeException(); - } + retVal.Add(new Point(hero.XPosition + 1, hero.YPosition - 1)); } - else - { - if (directions.Contains(Direction.Left) && directions.Contains(Direction.Up)) - { - retVal.Add(new Point(hero.XPosition - 1, hero.YPosition - 1)); - } - - if (directions.Contains(Direction.Left) && directions.Contains(Direction.Down)) - { - retVal.Add(new Point(hero.XPosition - 1, hero.YPosition + 1)); - } - - if (directions.Contains(Direction.Right) && directions.Contains(Direction.Up)) - { - retVal.Add(new Point(hero.XPosition + 1, hero.YPosition - 1)); - } - if (directions.Contains(Direction.Right) && directions.Contains(Direction.Down)) - { - retVal.Add(new Point(hero.XPosition + 1, hero.YPosition + 1)); - } + if (directions.Contains(Direction.Right) && directions.Contains(Direction.Down)) + { + retVal.Add(new Point(hero.XPosition + 1, hero.YPosition + 1)); } - - return retVal; } + + return retVal; } +} + diff --git a/Projects/Checkers/Move.cs b/Projects/Checkers/Move.cs index 15fb8ae7..8906f336 100644 --- a/Projects/Checkers/Move.cs +++ b/Projects/Checkers/Move.cs @@ -1,18 +1,19 @@ -using System.Drawing; -using Checkers.Types; +using Checkers.Types; +using System.Drawing; namespace Checkers; public class Move { - public Piece? PieceToMove { get; set; } + public Piece? PieceToMove { get; set; } - public Point To { get; set; } + public Point To { get; set; } - public Point? Capturing { get; set; } + public Point? Capturing { get; set; } - public MoveType TypeOfMove { get; set; } = MoveType.Unknown; + public MoveType TypeOfMove { get; set; } = MoveType.Unknown; - // Moves are sorted by guid to give computer vs computer games a bit of variety - public string Weighting => Guid.NewGuid().ToString(); + // Moves are sorted by guid to give computer vs computer games a bit of variety + public string Weighting => Guid.NewGuid().ToString(); } + diff --git a/Projects/Checkers/Piece.cs b/Projects/Checkers/Piece.cs index e83bf577..47bd4406 100644 --- a/Projects/Checkers/Piece.cs +++ b/Projects/Checkers/Piece.cs @@ -5,37 +5,38 @@ namespace Checkers; public class Piece { - public Piece() - { - InPlay = true; - Promoted = false; - } + public Piece() + { + InPlay = true; + Promoted = false; + } - public int XPosition { get; set; } + public int XPosition { get; set; } - public int YPosition { get; set; } + public int YPosition { get; set; } - public string InitialPosition - { - set - { - var position = PositionHelper.GetPositionByNotation(value); + public string InitialPosition + { + set + { + var position = PositionHelper.GetPositionByNotation(value); - if (position == null) - { - return; - } + if (position == null) + { + return; + } - XPosition = position.Value.X; - YPosition = position.Value.Y; - } - } + XPosition = position.Value.X; + YPosition = position.Value.Y; + } + } - public PieceColour Side { get; set; } + public PieceColour Side { get; set; } - public bool InPlay { get; set; } + public bool InPlay { get; set; } - public bool Promoted { get; set; } + public bool Promoted { get; set; } - public bool Aggressor { get; set; } + public bool Aggressor { get; set; } } + diff --git a/Projects/Checkers/Player.cs b/Projects/Checkers/Player.cs index d17fd6b5..11b2d355 100644 --- a/Projects/Checkers/Player.cs +++ b/Projects/Checkers/Player.cs @@ -4,6 +4,7 @@ namespace Checkers; public class Player { - public PlayerControl ControlledBy { get; set; } - public PieceColour Side { get; set; } + public PlayerControl ControlledBy { get; set; } + public PieceColour Side { get; set; } } + diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index e91e414f..cd412fc8 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -1,22 +1,25 @@ // See https://aka.ms/new-console-template for more information -using System.Diagnostics; using Checkers; using Checkers.Helpers; using Checkers.Types; +using System.Diagnostics; +using System.Drawing; + +Console.CursorVisible = false; // HACK: Set to true to create output file for game analysis -const bool createOutputFileForAnalysis = false; +const bool CreateOutputFileForAnalysis = false; -if (createOutputFileForAnalysis) +if (CreateOutputFileForAnalysis) +#pragma warning disable CS0162 { -#pragma warning disable CS0162 // Unreachable code detected - var prefix = Path.GetTempPath(); -#pragma warning restore CS0162 // Unreachable code detected - var traceFile = $"{prefix}checkers_game_{Guid.NewGuid()}.txt"; + var prefix = Path.GetTempPath(); + var traceFile = $"{prefix}checkers_game_{Guid.NewGuid()}.txt"; - Trace.Listeners.Add((new TextWriterTraceListener(File.Create((traceFile))))); + Trace.Listeners.Add((new TextWriterTraceListener(File.Create((traceFile))))); } +#pragma warning restore CS0162 Trace.AutoFlush = true; @@ -32,134 +35,188 @@ var gameState = GameState.IntroScreen; +var xSelection = 4; +var ySelection = 4; +Point? selectedFromPoint = null; +Point? selectedToPoint = null; + while (gameState != GameState.Stopped) { - switch (gameState) - { - case GameState.IntroScreen: - numberOfPlayers = ShowIntroScreenAndGetOption(); - gameState = GameState.GameInProgress; - - break; - case GameState.GameInProgress: - - game = new Game(); - PlayerHelper.AssignPlayersToSide(numberOfPlayers, game); - - while (game.GameWinner == PieceColour.NotSet) - { - var currentPlayer = game.Players.FirstOrDefault(x => x.Side == game.CurrentGo); - - if (currentPlayer != null && currentPlayer.ControlledBy == PlayerControl.Human) - { - Console.SetCursorPosition(0, 12); - Console.Write($"Enter FROM square: "); - var fromSquare = Console.ReadLine(); - Console.Write($"Enter TO square : "); - var toSquare = Console.ReadLine(); - - if (fromSquare != null) - { - var fromPoint = PositionHelper.GetPositionByNotation(fromSquare); - - if (toSquare != null) - { - var toPoint = PositionHelper.GetPositionByNotation(toSquare); - - if (fromPoint != null) - { - var actualFromPiece = - BoardHelper.GetPieceAt(fromPoint.Value.X, fromPoint.Value.Y, game.GameBoard); - - if (actualFromPiece == null || actualFromPiece.Side != game.CurrentGo) - { - fromPoint = toPoint = null; - } - } - - if (fromPoint != null && toPoint != null) - { - var moveOutcome = game.NextRound(fromPoint, toPoint); - } - else - { - Console.SetCursorPosition(19, 12); - Console.Write(new string(' ', 10)); - Console.SetCursorPosition(19, 13); - Console.Write(new string(' ', 10)); - } - } - } - } - else - { - game.NextRound(); - } - - Thread.Sleep(100); - } - - LoggingHelper.LogOutcome(game.GameWinner); - - gameState = GameState.GameOver; - - break; - case GameState.GameOver: - if (game != null) - { - LoggingHelper.LogMoves(game.MovesSoFar); - } - - LoggingHelper.LogFinish(); - sw.Stop(); - - if (game != null) - { - Display.DisplayWinner(game.GameWinner); - } - - gameState = GameState.Stopped; - - break; - default: - throw new ArgumentOutOfRangeException(); - } + switch (gameState) + { + case GameState.IntroScreen: + numberOfPlayers = ShowIntroScreenAndGetOption(); + gameState = GameState.GameInProgress; + + break; + case GameState.GameInProgress: + + game = new Game(); + PlayerHelper.AssignPlayersToSide(numberOfPlayers, game); + + while (game.GameWinner == PieceColour.NotSet) + { + var currentPlayer = game.Players.FirstOrDefault(x => x.Side == game.CurrentGo); + + if (currentPlayer != null && currentPlayer.ControlledBy == PlayerControl.Human) + { + while (selectedFromPoint == null || selectedToPoint == null) + { + var selection = DisplayHelper.GetScreenPositionFromBoardPosition(new Point(xSelection, ySelection)); + Console.SetCursorPosition(selection.X - 1, selection.Y); + Console.Write("["); + Console.SetCursorPosition(selection.X + 1, selection.Y); + Console.Write("]"); + + var oldY = ySelection; + var oldX = xSelection; + switch (Console.ReadKey(true).Key) + { + case ConsoleKey.UpArrow: + if (ySelection > 1) + { + oldY = ySelection; + ySelection--; + } + break; + case ConsoleKey.DownArrow: + if (ySelection < 8) + { + oldY = ySelection; + ySelection++; + } + break; + case ConsoleKey.LeftArrow: + if (xSelection > 1) + { + oldX = xSelection; + xSelection--; + } + break; + case ConsoleKey.RightArrow: + if (xSelection < 8) + { + oldX = xSelection; + xSelection++; + } + break; + case ConsoleKey.Enter: + if (selectedFromPoint == null) + { + selectedFromPoint = new Point(xSelection, ySelection); + } + else + { + selectedToPoint = new Point(xSelection, ySelection); + } + break; + case ConsoleKey.Escape: + selectedFromPoint = null; + selectedToPoint = null; + break; + } + + var oldSelection = DisplayHelper.GetScreenPositionFromBoardPosition(new Point(oldX, oldY)); + Console.SetCursorPosition(oldSelection.X - 1, oldSelection.Y); + Console.Write(" "); + Console.SetCursorPosition(oldSelection.X + 1, oldSelection.Y); + Console.Write(" "); + + } + + var fromPoint = selectedFromPoint; + var toPoint = selectedToPoint; + + var actualFromPiece = + BoardHelper.GetPieceAt(fromPoint.Value.X, fromPoint.Value.Y, game.GameBoard); + + if (actualFromPiece == null || actualFromPiece.Side != game.CurrentGo) + { + fromPoint = toPoint = selectedToPoint = selectedFromPoint = null; + } + + if (fromPoint != null && toPoint != null) + { + _ = game.NextRound(fromPoint, toPoint); + selectedFromPoint = selectedToPoint = null; + } + else + { + Console.SetCursorPosition(19, 12); + Console.Write(new string(' ', 10)); + Console.SetCursorPosition(19, 13); + Console.Write(new string(' ', 10)); + } + } + else + { + game.NextRound(); + } + + Thread.Sleep(100); + } + + LoggingHelper.LogOutcome(game.GameWinner); + + gameState = GameState.GameOver; + + break; + case GameState.GameOver: + if (game != null) + { + LoggingHelper.LogMoves(game.MovesSoFar); + } + + LoggingHelper.LogFinish(); + sw.Stop(); + + if (game != null) + { + Display.DisplayWinner(game.GameWinner); + } + + gameState = GameState.Stopped; + + break; + default: + throw new ArgumentOutOfRangeException(); + } } int ShowIntroScreenAndGetOption() { - var validPlayers = new List() { 0, 1, 2 }; - Console.Clear(); - Console.WriteLine("CHECKERS"); - Console.WriteLine(); - Console.WriteLine("Checkers is played on an 8x8 board between two sides commonly known as black"); - Console.WriteLine("and white. The objective is simple - capture all your opponent's pieces. An"); - Console.WriteLine("alternative way to win is to trap your opponent so that they have no valid"); - Console.WriteLine("moves left."); - Console.WriteLine(); - Console.WriteLine("Black starts first and players take it in turns to move their pieces forward"); - Console.WriteLine("across the board diagonally. Should a piece reach the other side of the board"); - Console.WriteLine("the piece becomes a `king` and can then move diagonally backwards as well as"); - Console.WriteLine("forwards."); - Console.WriteLine(); - Console.WriteLine("Pieces are captured by jumping over them diagonally. More than one enemy piece"); - Console.WriteLine("can be captured in the same turn by the same piece."); - Console.WriteLine(); - Console.WriteLine("Moves are entered in `algebraic notation` e.g. A1 is the bottom left square,"); - Console.WriteLine("and H8 is the top right square. Invalid moves are ignored."); - Console.WriteLine(); - Console.WriteLine("3 modes of play are possible depending on the number of players entered:"); - Console.WriteLine(" 0 - black and white are controlled by the computer"); - Console.WriteLine(" 1 - black is controlled by the player and white by the computer"); - Console.WriteLine(" 2 - allows 2 players"); - Console.WriteLine(); - Console.Write("Enter the number of players (0-2): "); - - var entry = Console.ReadLine(); - - var conversionPassed = int.TryParse(entry, out numberOfPlayers); - - return conversionPassed && validPlayers.Contains(numberOfPlayers) ? numberOfPlayers : 0; + var validPlayers = new List() { 0, 1, 2 }; + Console.Clear(); + Console.WriteLine("CHECKERS"); + Console.WriteLine(); + Console.WriteLine("Checkers is played on an 8x8 board between two sides commonly known as black"); + Console.WriteLine("and white. The objective is simple - capture all your opponent's pieces. An"); + Console.WriteLine("alternative way to win is to trap your opponent so that they have no valid"); + Console.WriteLine("moves left."); + Console.WriteLine(); + Console.WriteLine("Black starts first and players take it in turns to move their pieces forward"); + Console.WriteLine("across the board diagonally. Should a piece reach the other side of the board"); + Console.WriteLine("the piece becomes a `king` and can then move diagonally backwards as well as"); + Console.WriteLine("forwards."); + Console.WriteLine(); + Console.WriteLine("Pieces are captured by jumping over them diagonally. More than one enemy piece"); + Console.WriteLine("can be captured in the same turn by the same piece."); + Console.WriteLine(); + Console.WriteLine("Moves are selected with the arrow keys. Use the [enter] button to select the"); + Console.WriteLine("from and to squares. Invalid moves are ignored."); + Console.WriteLine(); + Console.WriteLine("3 modes of play are possible depending on the number of players entered:"); + Console.WriteLine(" 0 - black and white are controlled by the computer"); + Console.WriteLine(" 1 - black is controlled by the player and white by the computer"); + Console.WriteLine(" 2 - allows 2 players"); + Console.WriteLine(); + Console.Write("Enter the number of players (0-2): "); + + var entry = Console.ReadLine(); + + var conversionPassed = int.TryParse(entry, out numberOfPlayers); + + return conversionPassed && validPlayers.Contains(numberOfPlayers) ? numberOfPlayers : 0; } diff --git a/Projects/Checkers/Types/DirectionType.cs b/Projects/Checkers/Types/DirectionType.cs index aa5a2ec8..032fc810 100644 --- a/Projects/Checkers/Types/DirectionType.cs +++ b/Projects/Checkers/Types/DirectionType.cs @@ -2,8 +2,9 @@ public enum Direction { - Up, - Down, - Left, - Right + Up, + Down, + Left, + Right } + diff --git a/Projects/Checkers/Types/GameStateType.cs b/Projects/Checkers/Types/GameStateType.cs index 5f0ae720..586bffb4 100644 --- a/Projects/Checkers/Types/GameStateType.cs +++ b/Projects/Checkers/Types/GameStateType.cs @@ -2,8 +2,9 @@ public enum GameState { - IntroScreen, - GameInProgress, - GameOver, - Stopped + IntroScreen, + GameInProgress, + GameOver, + Stopped } + diff --git a/Projects/Checkers/Types/MoveOutcomeType.cs b/Projects/Checkers/Types/MoveOutcomeType.cs index f5d495d0..a2e8127b 100644 --- a/Projects/Checkers/Types/MoveOutcomeType.cs +++ b/Projects/Checkers/Types/MoveOutcomeType.cs @@ -2,12 +2,13 @@ public enum MoveOutcome { - Unknown, - ValidMoves, - Capture, - CaptureMoreAvailable, - EndGame, // Playing with kings with prey to hunt - NoMoveAvailable, - WhiteWin, - BlackWin + Unknown, + ValidMoves, + Capture, + CaptureMoreAvailable, + EndGame, // Playing with kings with prey to hunt + NoMoveAvailable, + WhiteWin, + BlackWin } + diff --git a/Projects/Checkers/Types/MoveType.cs b/Projects/Checkers/Types/MoveType.cs index 186a6ac7..be4f523a 100644 --- a/Projects/Checkers/Types/MoveType.cs +++ b/Projects/Checkers/Types/MoveType.cs @@ -2,8 +2,9 @@ public enum MoveType { - Unknown, - StandardMove, - Capture, - EndGame + Unknown, + StandardMove, + Capture, + EndGame } + diff --git a/Projects/Checkers/Types/PieceColourType.cs b/Projects/Checkers/Types/PieceColourType.cs index cfab2492..1cc5c1ea 100644 --- a/Projects/Checkers/Types/PieceColourType.cs +++ b/Projects/Checkers/Types/PieceColourType.cs @@ -2,7 +2,8 @@ public enum PieceColour { - Black, - White, - NotSet + Black, + White, + NotSet } + diff --git a/Projects/Checkers/Types/PlayerActionType.cs b/Projects/Checkers/Types/PlayerActionType.cs index 60c2363e..a89632ee 100644 --- a/Projects/Checkers/Types/PlayerActionType.cs +++ b/Projects/Checkers/Types/PlayerActionType.cs @@ -5,8 +5,9 @@ /// public enum PlayerAction { - Move, - Promotion, - Capture, - CapturePromotion + Move, + Promotion, + Capture, + CapturePromotion } + diff --git a/Projects/Checkers/Types/PlayerControlType.cs b/Projects/Checkers/Types/PlayerControlType.cs index 5d3d4cf8..76c0415d 100644 --- a/Projects/Checkers/Types/PlayerControlType.cs +++ b/Projects/Checkers/Types/PlayerControlType.cs @@ -2,6 +2,7 @@ public enum PlayerControl { - Human, - Computer + Human, + Computer } + From e17116851553367d3a9822b27758628cdb115007 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sat, 18 Jun 2022 13:43:21 -0400 Subject: [PATCH 03/68] prompt user on invalid input rather than default --- Projects/Checkers/Program.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index cd412fc8..adefafd9 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -212,11 +212,14 @@ int ShowIntroScreenAndGetOption() Console.WriteLine(); Console.Write("Enter the number of players (0-2): "); - var entry = Console.ReadLine(); - - var conversionPassed = int.TryParse(entry, out numberOfPlayers); - - return conversionPassed && validPlayers.Contains(numberOfPlayers) ? numberOfPlayers : 0; + var entry = Console.ReadLine()?.Trim(); + while (entry is not "0" and not "1" and not "2") + { + Console.WriteLine("Invalid Input. Try Again."); + Console.Write("Enter the number of players (0-2): "); + entry = Console.ReadLine()?.Trim(); + } + return entry[0] - '0'; } From 757488ce7e4626096f443d7719933e58263f09e2 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sat, 18 Jun 2022 13:48:34 -0400 Subject: [PATCH 04/68] implicit usings -> global usings --- Projects/Checkers/Board.cs | 4 +--- Projects/Checkers/Checkers.csproj | 14 ++++++-------- Projects/Checkers/Data/KnowledgeBase.cs | 4 +--- Projects/Checkers/Display.cs | 2 -- Projects/Checkers/Engine.cs | 4 +--- Projects/Checkers/Game.cs | 3 +-- Projects/Checkers/Helpers/BoardHelper.cs | 1 - Projects/Checkers/Helpers/LoggingHelper.cs | 3 +-- Projects/Checkers/Helpers/PlayerHelper.cs | 4 +--- Projects/Checkers/Helpers/VectorHelper.cs | 3 +-- Projects/Checkers/Move.cs | 3 +-- Projects/Checkers/Piece.cs | 5 +---- Projects/Checkers/Player.cs | 4 +--- Projects/Checkers/Program.cs | 4 ++-- Projects/Checkers/_using.cs | 6 ++++++ 15 files changed, 24 insertions(+), 40 deletions(-) create mode 100644 Projects/Checkers/_using.cs diff --git a/Projects/Checkers/Board.cs b/Projects/Checkers/Board.cs index b9ad0160..ddc47498 100644 --- a/Projects/Checkers/Board.cs +++ b/Projects/Checkers/Board.cs @@ -1,6 +1,4 @@ -using Checkers.Helpers; - -namespace Checkers; +namespace Checkers; public class Board { diff --git a/Projects/Checkers/Checkers.csproj b/Projects/Checkers/Checkers.csproj index 74abf5c9..89193e26 100644 --- a/Projects/Checkers/Checkers.csproj +++ b/Projects/Checkers/Checkers.csproj @@ -1,10 +1,8 @@ - - - Exe - net6.0 - enable - enable - - + + Exe + net6.0 + disable + enable + diff --git a/Projects/Checkers/Data/KnowledgeBase.cs b/Projects/Checkers/Data/KnowledgeBase.cs index fba9e5b5..8e7cf149 100644 --- a/Projects/Checkers/Data/KnowledgeBase.cs +++ b/Projects/Checkers/Data/KnowledgeBase.cs @@ -1,6 +1,4 @@ -using Checkers.Types; - -namespace Checkers.Data; +namespace Checkers.Data; /// /// Stores the starting position and any custom permutation for testing diff --git a/Projects/Checkers/Display.cs b/Projects/Checkers/Display.cs index 21967932..3129d326 100644 --- a/Projects/Checkers/Display.cs +++ b/Projects/Checkers/Display.cs @@ -1,6 +1,4 @@ using System.Drawing; -using Checkers.Helpers; -using Checkers.Types; namespace Checkers; diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index 051ec280..f772b524 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -1,6 +1,4 @@ -using Checkers.Helpers; -using Checkers.Types; -using System.Drawing; +using System.Drawing; namespace Checkers; diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index cf9df45f..65123b53 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -1,5 +1,4 @@ -using Checkers.Types; -using System.Drawing; +using System.Drawing; namespace Checkers; diff --git a/Projects/Checkers/Helpers/BoardHelper.cs b/Projects/Checkers/Helpers/BoardHelper.cs index 447a9a88..50bb3835 100644 --- a/Projects/Checkers/Helpers/BoardHelper.cs +++ b/Projects/Checkers/Helpers/BoardHelper.cs @@ -1,5 +1,4 @@ using Checkers.Data; -using Checkers.Types; namespace Checkers.Helpers; diff --git a/Projects/Checkers/Helpers/LoggingHelper.cs b/Projects/Checkers/Helpers/LoggingHelper.cs index 2b3bc7a8..1a8643f6 100644 --- a/Projects/Checkers/Helpers/LoggingHelper.cs +++ b/Projects/Checkers/Helpers/LoggingHelper.cs @@ -1,5 +1,4 @@ -using Checkers.Types; -using System.Diagnostics; +using System.Diagnostics; namespace Checkers.Helpers; diff --git a/Projects/Checkers/Helpers/PlayerHelper.cs b/Projects/Checkers/Helpers/PlayerHelper.cs index 40cdaf16..9f48e7c5 100644 --- a/Projects/Checkers/Helpers/PlayerHelper.cs +++ b/Projects/Checkers/Helpers/PlayerHelper.cs @@ -1,6 +1,4 @@ -using Checkers.Types; - -namespace Checkers.Helpers; +namespace Checkers.Helpers; /// /// Assigns AI/human players diff --git a/Projects/Checkers/Helpers/VectorHelper.cs b/Projects/Checkers/Helpers/VectorHelper.cs index 4a1e446f..67a6f750 100644 --- a/Projects/Checkers/Helpers/VectorHelper.cs +++ b/Projects/Checkers/Helpers/VectorHelper.cs @@ -1,5 +1,4 @@ -using Checkers.Types; -using System.Drawing; +using System.Drawing; namespace Checkers.Helpers; diff --git a/Projects/Checkers/Move.cs b/Projects/Checkers/Move.cs index 8906f336..3a3b08fd 100644 --- a/Projects/Checkers/Move.cs +++ b/Projects/Checkers/Move.cs @@ -1,5 +1,4 @@ -using Checkers.Types; -using System.Drawing; +using System.Drawing; namespace Checkers; diff --git a/Projects/Checkers/Piece.cs b/Projects/Checkers/Piece.cs index 47bd4406..888c87bb 100644 --- a/Projects/Checkers/Piece.cs +++ b/Projects/Checkers/Piece.cs @@ -1,7 +1,4 @@ -using Checkers.Helpers; -using Checkers.Types; - -namespace Checkers; +namespace Checkers; public class Piece { diff --git a/Projects/Checkers/Player.cs b/Projects/Checkers/Player.cs index 11b2d355..d50c70cd 100644 --- a/Projects/Checkers/Player.cs +++ b/Projects/Checkers/Player.cs @@ -1,6 +1,4 @@ -using Checkers.Types; - -namespace Checkers; +namespace Checkers; public class Player { diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index adefafd9..ab7b062b 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -1,10 +1,10 @@ // See https://aka.ms/new-console-template for more information using Checkers; -using Checkers.Helpers; -using Checkers.Types; using System.Diagnostics; using System.Drawing; +using System.IO; +using System.Threading; Console.CursorVisible = false; diff --git a/Projects/Checkers/_using.cs b/Projects/Checkers/_using.cs new file mode 100644 index 00000000..42fce8d2 --- /dev/null +++ b/Projects/Checkers/_using.cs @@ -0,0 +1,6 @@ +global using System; +global using System.Collections.Generic; +global using System.Linq; + +global using Checkers.Helpers; +global using Checkers.Types; \ No newline at end of file From 3ab45292e97d3a5d01d03885e455bee5a59c32c3 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sat, 18 Jun 2022 15:13:35 -0400 Subject: [PATCH 05/68] minor clean up --- Projects/Checkers/Program.cs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index ab7b062b..518ee4d7 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -17,7 +17,7 @@ var prefix = Path.GetTempPath(); var traceFile = $"{prefix}checkers_game_{Guid.NewGuid()}.txt"; - Trace.Listeners.Add((new TextWriterTraceListener(File.Create((traceFile))))); + Trace.Listeners.Add(new TextWriterTraceListener(File.Create(traceFile))); } #pragma warning restore CS0162 @@ -47,17 +47,13 @@ case GameState.IntroScreen: numberOfPlayers = ShowIntroScreenAndGetOption(); gameState = GameState.GameInProgress; - break; case GameState.GameInProgress: - game = new Game(); PlayerHelper.AssignPlayersToSide(numberOfPlayers, game); - while (game.GameWinner == PieceColour.NotSet) { var currentPlayer = game.Players.FirstOrDefault(x => x.Side == game.CurrentGo); - if (currentPlayer != null && currentPlayer.ControlledBy == PlayerControl.Human) { while (selectedFromPoint == null || selectedToPoint == null) @@ -121,14 +117,12 @@ Console.Write(" "); Console.SetCursorPosition(oldSelection.X + 1, oldSelection.Y); Console.Write(" "); - } var fromPoint = selectedFromPoint; var toPoint = selectedToPoint; - var actualFromPiece = - BoardHelper.GetPieceAt(fromPoint.Value.X, fromPoint.Value.Y, game.GameBoard); + var actualFromPiece = BoardHelper.GetPieceAt(fromPoint.Value.X, fromPoint.Value.Y, game.GameBoard); if (actualFromPiece == null || actualFromPiece.Side != game.CurrentGo) { @@ -166,17 +160,13 @@ { LoggingHelper.LogMoves(game.MovesSoFar); } - LoggingHelper.LogFinish(); sw.Stop(); - if (game != null) { Display.DisplayWinner(game.GameWinner); } - gameState = GameState.Stopped; - break; default: throw new ArgumentOutOfRangeException(); From 42b44a1e2f4d8ab10f72035ef3995e7f324279c3 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sat, 18 Jun 2022 15:13:57 -0400 Subject: [PATCH 06/68] mor appropriate exception --- Projects/Checkers/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 518ee4d7..9d21fc50 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -169,7 +169,7 @@ gameState = GameState.Stopped; break; default: - throw new ArgumentOutOfRangeException(); + throw new NotImplementedException(); } } From 33c05b9ab08e74a9cd6ebaed875d714cf93c9370 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sat, 18 Jun 2022 15:21:58 -0400 Subject: [PATCH 07/68] game state loop clean up: handle each state in a seperate method --- Projects/Checkers/Program.cs | 250 +++++++++++++++++------------------ 1 file changed, 123 insertions(+), 127 deletions(-) diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 9d21fc50..e680e81d 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -1,6 +1,4 @@ -// See https://aka.ms/new-console-template for more information - -using Checkers; +using Checkers; using System.Diagnostics; using System.Drawing; using System.IO; @@ -22,19 +20,13 @@ #pragma warning restore CS0162 Trace.AutoFlush = true; - Console.OutputEncoding = System.Text.Encoding.UTF8; - var sw = new Stopwatch(); - sw.Start(); LoggingHelper.LogStart(); Game? game = null; - var numberOfPlayers = 0; - var gameState = GameState.IntroScreen; - var xSelection = 4; var ySelection = 4; Point? selectedFromPoint = null; @@ -45,127 +37,15 @@ switch (gameState) { case GameState.IntroScreen: - numberOfPlayers = ShowIntroScreenAndGetOption(); + ShowIntroScreenAndGetOption(); gameState = GameState.GameInProgress; break; case GameState.GameInProgress: - game = new Game(); - PlayerHelper.AssignPlayersToSide(numberOfPlayers, game); - while (game.GameWinner == PieceColour.NotSet) - { - var currentPlayer = game.Players.FirstOrDefault(x => x.Side == game.CurrentGo); - if (currentPlayer != null && currentPlayer.ControlledBy == PlayerControl.Human) - { - while (selectedFromPoint == null || selectedToPoint == null) - { - var selection = DisplayHelper.GetScreenPositionFromBoardPosition(new Point(xSelection, ySelection)); - Console.SetCursorPosition(selection.X - 1, selection.Y); - Console.Write("["); - Console.SetCursorPosition(selection.X + 1, selection.Y); - Console.Write("]"); - - var oldY = ySelection; - var oldX = xSelection; - switch (Console.ReadKey(true).Key) - { - case ConsoleKey.UpArrow: - if (ySelection > 1) - { - oldY = ySelection; - ySelection--; - } - break; - case ConsoleKey.DownArrow: - if (ySelection < 8) - { - oldY = ySelection; - ySelection++; - } - break; - case ConsoleKey.LeftArrow: - if (xSelection > 1) - { - oldX = xSelection; - xSelection--; - } - break; - case ConsoleKey.RightArrow: - if (xSelection < 8) - { - oldX = xSelection; - xSelection++; - } - break; - case ConsoleKey.Enter: - if (selectedFromPoint == null) - { - selectedFromPoint = new Point(xSelection, ySelection); - } - else - { - selectedToPoint = new Point(xSelection, ySelection); - } - break; - case ConsoleKey.Escape: - selectedFromPoint = null; - selectedToPoint = null; - break; - } - - var oldSelection = DisplayHelper.GetScreenPositionFromBoardPosition(new Point(oldX, oldY)); - Console.SetCursorPosition(oldSelection.X - 1, oldSelection.Y); - Console.Write(" "); - Console.SetCursorPosition(oldSelection.X + 1, oldSelection.Y); - Console.Write(" "); - } - - var fromPoint = selectedFromPoint; - var toPoint = selectedToPoint; - - var actualFromPiece = BoardHelper.GetPieceAt(fromPoint.Value.X, fromPoint.Value.Y, game.GameBoard); - - if (actualFromPiece == null || actualFromPiece.Side != game.CurrentGo) - { - fromPoint = toPoint = selectedToPoint = selectedFromPoint = null; - } - - if (fromPoint != null && toPoint != null) - { - _ = game.NextRound(fromPoint, toPoint); - selectedFromPoint = selectedToPoint = null; - } - else - { - Console.SetCursorPosition(19, 12); - Console.Write(new string(' ', 10)); - Console.SetCursorPosition(19, 13); - Console.Write(new string(' ', 10)); - } - } - else - { - game.NextRound(); - } - - Thread.Sleep(100); - } - - LoggingHelper.LogOutcome(game.GameWinner); - + RunGameLoop(); gameState = GameState.GameOver; - break; case GameState.GameOver: - if (game != null) - { - LoggingHelper.LogMoves(game.MovesSoFar); - } - LoggingHelper.LogFinish(); - sw.Stop(); - if (game != null) - { - Display.DisplayWinner(game.GameWinner); - } + HandleGameOver(); gameState = GameState.Stopped; break; default: @@ -173,9 +53,8 @@ } } -int ShowIntroScreenAndGetOption() +void ShowIntroScreenAndGetOption() { - var validPlayers = new List() { 0, 1, 2 }; Console.Clear(); Console.WriteLine("CHECKERS"); Console.WriteLine(); @@ -209,7 +88,124 @@ int ShowIntroScreenAndGetOption() Console.Write("Enter the number of players (0-2): "); entry = Console.ReadLine()?.Trim(); } - return entry[0] - '0'; + numberOfPlayers = entry[0] - '0'; } +void RunGameLoop() +{ + game = new Game(); + PlayerHelper.AssignPlayersToSide(numberOfPlayers, game); + while (game.GameWinner == PieceColour.NotSet) + { + var currentPlayer = game.Players.FirstOrDefault(x => x.Side == game.CurrentGo); + if (currentPlayer != null && currentPlayer.ControlledBy == PlayerControl.Human) + { + while (selectedFromPoint == null || selectedToPoint == null) + { + var selection = DisplayHelper.GetScreenPositionFromBoardPosition(new Point(xSelection, ySelection)); + Console.SetCursorPosition(selection.X - 1, selection.Y); + Console.Write("["); + Console.SetCursorPosition(selection.X + 1, selection.Y); + Console.Write("]"); + + var oldY = ySelection; + var oldX = xSelection; + switch (Console.ReadKey(true).Key) + { + case ConsoleKey.UpArrow: + if (ySelection > 1) + { + oldY = ySelection; + ySelection--; + } + break; + case ConsoleKey.DownArrow: + if (ySelection < 8) + { + oldY = ySelection; + ySelection++; + } + break; + case ConsoleKey.LeftArrow: + if (xSelection > 1) + { + oldX = xSelection; + xSelection--; + } + break; + case ConsoleKey.RightArrow: + if (xSelection < 8) + { + oldX = xSelection; + xSelection++; + } + break; + case ConsoleKey.Enter: + if (selectedFromPoint == null) + { + selectedFromPoint = new Point(xSelection, ySelection); + } + else + { + selectedToPoint = new Point(xSelection, ySelection); + } + break; + case ConsoleKey.Escape: + selectedFromPoint = null; + selectedToPoint = null; + break; + } + + var oldSelection = DisplayHelper.GetScreenPositionFromBoardPosition(new Point(oldX, oldY)); + Console.SetCursorPosition(oldSelection.X - 1, oldSelection.Y); + Console.Write(" "); + Console.SetCursorPosition(oldSelection.X + 1, oldSelection.Y); + Console.Write(" "); + } + + var fromPoint = selectedFromPoint; + var toPoint = selectedToPoint; + + var actualFromPiece = BoardHelper.GetPieceAt(fromPoint.Value.X, fromPoint.Value.Y, game.GameBoard); + + if (actualFromPiece == null || actualFromPiece.Side != game.CurrentGo) + { + fromPoint = toPoint = selectedToPoint = selectedFromPoint = null; + } + + if (fromPoint != null && toPoint != null) + { + _ = game.NextRound(fromPoint, toPoint); + selectedFromPoint = selectedToPoint = null; + } + else + { + Console.SetCursorPosition(19, 12); + Console.Write(new string(' ', 10)); + Console.SetCursorPosition(19, 13); + Console.Write(new string(' ', 10)); + } + } + else + { + game.NextRound(); + } + + Thread.Sleep(100); + } + LoggingHelper.LogOutcome(game.GameWinner); +} +void HandleGameOver() +{ + if (game != null) + { + LoggingHelper.LogMoves(game.MovesSoFar); + } + LoggingHelper.LogFinish(); + sw.Stop(); + if (game != null) + { + Display.DisplayWinner(game.GameWinner); + } +} From 0b2168cff65b0bb8ba22a6359b1396dada731148 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sat, 18 Jun 2022 15:50:56 -0400 Subject: [PATCH 08/68] NewGuid for random selection is unnecessary --- Projects/Checkers/Engine.cs | 2 +- Projects/Checkers/Move.cs | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index f772b524..3688dccb 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -63,7 +63,7 @@ public static MoveOutcome PlayNextMove(PieceColour currentSide, Board currentBoa case MoveOutcome.EndGame: case MoveOutcome.ValidMoves: { - var bestMove = possibleMoves.MinBy(x => x.Weighting); + var bestMove = possibleMoves[Random.Shared.Next(possibleMoves.Count)]; if (bestMove == null) { diff --git a/Projects/Checkers/Move.cs b/Projects/Checkers/Move.cs index 3a3b08fd..cd74360d 100644 --- a/Projects/Checkers/Move.cs +++ b/Projects/Checkers/Move.cs @@ -11,8 +11,5 @@ public class Move public Point? Capturing { get; set; } public MoveType TypeOfMove { get; set; } = MoveType.Unknown; - - // Moves are sorted by guid to give computer vs computer games a bit of variety - public string Weighting => Guid.NewGuid().ToString(); } From 3a73f3276e5d3ee396ea9706da972b0e78c48106 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sat, 18 Jun 2022 16:21:45 -0400 Subject: [PATCH 09/68] user confirmation > Thread.Sleep --- Projects/Checkers/Program.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index e680e81d..d6d31bac 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -191,7 +191,7 @@ void RunGameLoop() game.NextRound(); } - Thread.Sleep(100); + PressAnyKeyToContinue(); } LoggingHelper.LogOutcome(game.GameWinner); } @@ -209,3 +209,12 @@ void HandleGameOver() Display.DisplayWinner(game.GameWinner); } } + +void PressAnyKeyToContinue() +{ + (int left, int top) = (Console.CursorLeft, Console.CursorTop); + Console.Write("Press any key to cotinue..."); + Console.ReadKey(true); + Console.SetCursorPosition(left, top); + Console.Write(" "); +} From b098a1501f4f06a85275678a3563911db645ed9c Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sat, 18 Jun 2022 22:01:24 -0400 Subject: [PATCH 10/68] root readme +Checkers --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f2e191d2..aa3e2ee3 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ |[Tents](Projects/Tents)|4|[![Play Now](.github/resources/play-badge.svg)](https://zacharypatten.github.io/dotnet-console-games/Tents) [![Status](https://github.com/ZacharyPatten/dotnet-console-games/workflows/Tents%20Build/badge.svg)](https://github.com/ZacharyPatten/dotnet-console-games/actions)| |[Battleship](Projects/Battleship)|4|[![Play Now](.github/resources/play-badge.svg)](https://zacharypatten.github.io/dotnet-console-games/Battleship) [![Status](https://github.com/ZacharyPatten/dotnet-console-games/workflows/Battleship%20Build/badge.svg)](https://github.com/ZacharyPatten/dotnet-console-games/actions)| |[Duck Hunt](Projects/Duck%20Hunt)|5|[![Play Now](.github/resources/play-badge.svg)](https://zacharypatten.github.io/dotnet-console-games/Duck%20Hunt) [![Status](https://github.com/ZacharyPatten/dotnet-console-games/workflows/Duck%20Hunt%20Build/badge.svg)](https://github.com/ZacharyPatten/dotnet-console-games/actions)
*_[Community Contribution](https://github.com/ZacharyPatten/dotnet-console-games/pull/39)_| +|[Checkers](Projects/Checkers)|5|[![Play Now](.github/resources/play-badge.svg)](https://zacharypatten.github.io/dotnet-console-games/Checkers) [![Status](https://github.com/ZacharyPatten/dotnet-console-games/workflows/Checkers%20Build/badge.svg)](https://github.com/ZacharyPatten/dotnet-console-games/actions)
*_[Community Contribution](https://github.com/ZacharyPatten/dotnet-console-games/pull/40)_| |[Blackjack](Projects/Blackjack)|5|[![Play Now](.github/resources/play-badge.svg)](https://zacharypatten.github.io/dotnet-console-games/Blackjack) [![Status](https://github.com/ZacharyPatten/dotnet-console-games/workflows/Blackjack%20Build/badge.svg)](https://github.com/ZacharyPatten/dotnet-console-games/actions)| |[Fighter](Projects/Fighter)|5|[![Play Now](.github/resources/play-badge.svg)](https://zacharypatten.github.io/dotnet-console-games/Fighter) [![Status](https://github.com/ZacharyPatten/dotnet-console-games/workflows/Fighter%20Build/badge.svg)](https://github.com/ZacharyPatten/dotnet-console-games/actions)| |[Maze](Projects/Maze)|5|[![Play Now](.github/resources/play-badge.svg)](https://zacharypatten.github.io/dotnet-console-games/Maze) [![Status](https://github.com/ZacharyPatten/dotnet-console-games/workflows/Maze%20Build/badge.svg)](https://github.com/ZacharyPatten/dotnet-console-games/actions)| From 2573823100710d9e5b78c84db37576ebac9007f2 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sat, 18 Jun 2022 22:01:35 -0400 Subject: [PATCH 11/68] slnf +Checkers --- dotnet-console-games.slnf | 1 + 1 file changed, 1 insertion(+) diff --git a/dotnet-console-games.slnf b/dotnet-console-games.slnf index 85430ada..c15dda36 100644 --- a/dotnet-console-games.slnf +++ b/dotnet-console-games.slnf @@ -7,6 +7,7 @@ "Projects\\Beep Pad\\Beep Pad.csproj", "Projects\\Bound\\Bound.csproj", "Projects\\Blackjack\\Blackjack.csproj", + "Projects\\Checkers\\Checkers.csproj", "Projects\\Connect 4\\Connect 4.csproj", "Projects\\Dice Game\\Dice Game.csproj", "Projects\\Draw\\Draw.csproj", From 4ace7b906d4a9081c8e770e3e2845f45750456bf Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sat, 18 Jun 2022 22:01:50 -0400 Subject: [PATCH 12/68] Checkers Build Action --- .github/workflows/Checkers Build.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/Checkers Build.yml diff --git a/.github/workflows/Checkers Build.yml b/.github/workflows/Checkers Build.yml new file mode 100644 index 00000000..ce2b4242 --- /dev/null +++ b/.github/workflows/Checkers Build.yml @@ -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 From 62002c7a2b82ee74ffc381a283bb8b4e30b108bf Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sat, 18 Jun 2022 22:02:17 -0400 Subject: [PATCH 13/68] Visual Studio Code Checkers launch --- .vscode/launch.json | 10 ++++++++++ .vscode/tasks.json | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index ff6845ec..cef7bba1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -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", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2aa4809e..42cd0319 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -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", From f82f271ff06abc18028a09242e24baf10fb48004 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sat, 18 Jun 2022 22:02:41 -0400 Subject: [PATCH 14/68] Checkers readme --- Projects/Checkers/README.md | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Projects/Checkers/README.md diff --git a/Projects/Checkers/README.md b/Projects/Checkers/README.md new file mode 100644 index 00000000..d78cee7b --- /dev/null +++ b/Projects/Checkers/README.md @@ -0,0 +1,46 @@ +

+ Checkers +

+ +

+ flat + Language C# + Target Framework + Build + Discord + +

+ +> **Note** This game was a *[Community Contribution](https://github.com/ZacharyPatten/dotnet-console-games/pull/40)! + +Checkers is a classic 1v1 board game where you try to take all of your opponent's pieces. You may move pieces diagonally, and you may take your opponent's piece by jumping over their pieces diagonally. You may jump multiple pieces in a single move. You may not move a piece onto a tile that already has a piece on it. + +``` + ╔═══════════════════╗ +8║ . ◙ . ◙ . ◙ . ◙ ║ ○ = Black +7║ ◙ . ◙ . ◙ . ◙ . ║ ☺ = Black King +6║ . ◙ . ◙ . . . ◙ ║ ◙ = White +5║ . . . . . . ◙ . ║ ☻ = White King +4║ . . . ○ . . . . ║ +3║ ○ . ○ . . . ○ . ║ Taken: +2║ . ○ . ○ . ○ . ○ ║ 0 x ◙ +1║ ○ . ○ . ○ . ○ . ║ 0 x ○ + ╚═══════════════════╝ + A B C D E F G H +``` + +## Input + +- `↑`, `↓`, `←`, `→`: move selection +- `enter`: confirm +- `escape`: close + +

+ You can play this game in your browser: +
+ + Play Now + +
+ Hosted On GitHub Pages +

\ No newline at end of file From e3a82ddedefe841b17b05133efa7714dab812a51 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sat, 18 Jun 2022 22:14:23 -0400 Subject: [PATCH 15/68] always list the default enum value first with value 0 --- Projects/Checkers/Types/PieceColourType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Checkers/Types/PieceColourType.cs b/Projects/Checkers/Types/PieceColourType.cs index 1cc5c1ea..af9d24bf 100644 --- a/Projects/Checkers/Types/PieceColourType.cs +++ b/Projects/Checkers/Types/PieceColourType.cs @@ -2,8 +2,8 @@ public enum PieceColour { + NotSet = 0, Black, White, - NotSet } From 07db90660a9cf8a2604ad64c176095d9c2fac648 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sat, 18 Jun 2022 22:22:21 -0400 Subject: [PATCH 16/68] file names should match class names --- Projects/Checkers/Types/{DirectionType.cs => Direction.cs} | 0 Projects/Checkers/Types/{GameStateType.cs => GameState.cs} | 0 Projects/Checkers/Types/{MoveOutcomeType.cs => MoveOutcome.cs} | 0 Projects/Checkers/Types/{PieceColourType.cs => PieceColour.cs} | 0 Projects/Checkers/Types/{PlayerActionType.cs => PlayerAction.cs} | 0 .../Checkers/Types/{PlayerControlType.cs => PlayerControl.cs} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename Projects/Checkers/Types/{DirectionType.cs => Direction.cs} (100%) rename Projects/Checkers/Types/{GameStateType.cs => GameState.cs} (100%) rename Projects/Checkers/Types/{MoveOutcomeType.cs => MoveOutcome.cs} (100%) rename Projects/Checkers/Types/{PieceColourType.cs => PieceColour.cs} (100%) rename Projects/Checkers/Types/{PlayerActionType.cs => PlayerAction.cs} (100%) rename Projects/Checkers/Types/{PlayerControlType.cs => PlayerControl.cs} (100%) diff --git a/Projects/Checkers/Types/DirectionType.cs b/Projects/Checkers/Types/Direction.cs similarity index 100% rename from Projects/Checkers/Types/DirectionType.cs rename to Projects/Checkers/Types/Direction.cs diff --git a/Projects/Checkers/Types/GameStateType.cs b/Projects/Checkers/Types/GameState.cs similarity index 100% rename from Projects/Checkers/Types/GameStateType.cs rename to Projects/Checkers/Types/GameState.cs diff --git a/Projects/Checkers/Types/MoveOutcomeType.cs b/Projects/Checkers/Types/MoveOutcome.cs similarity index 100% rename from Projects/Checkers/Types/MoveOutcomeType.cs rename to Projects/Checkers/Types/MoveOutcome.cs diff --git a/Projects/Checkers/Types/PieceColourType.cs b/Projects/Checkers/Types/PieceColour.cs similarity index 100% rename from Projects/Checkers/Types/PieceColourType.cs rename to Projects/Checkers/Types/PieceColour.cs diff --git a/Projects/Checkers/Types/PlayerActionType.cs b/Projects/Checkers/Types/PlayerAction.cs similarity index 100% rename from Projects/Checkers/Types/PlayerActionType.cs rename to Projects/Checkers/Types/PlayerAction.cs diff --git a/Projects/Checkers/Types/PlayerControlType.cs b/Projects/Checkers/Types/PlayerControl.cs similarity index 100% rename from Projects/Checkers/Types/PlayerControlType.cs rename to Projects/Checkers/Types/PlayerControl.cs From 57e559bb73ebc25247bd27a803ea0eeaa25b2c78 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sat, 18 Jun 2022 22:31:39 -0400 Subject: [PATCH 17/68] remove unnecessary using (the Thread.Sleep was previously removed) --- Projects/Checkers/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index d6d31bac..cb682a01 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -2,7 +2,6 @@ using System.Diagnostics; using System.Drawing; using System.IO; -using System.Threading; Console.CursorVisible = false; From a94140e878ae0cc5d84feaa007d973c44702d878 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sun, 19 Jun 2022 10:34:28 -0400 Subject: [PATCH 18/68] refactor coordinates (1..8->0..7) and position helper and move BoardHelper members into Board --- Projects/Checkers/Board.cs | 37 ++++++- Projects/Checkers/Data/KnowledgeBase.cs | 68 ++++++------ Projects/Checkers/Display.cs | 116 +++++++------------- Projects/Checkers/Engine.cs | 64 ++++++----- Projects/Checkers/Game.cs | 5 +- Projects/Checkers/Helpers/BoardHelper.cs | 64 ----------- Projects/Checkers/Helpers/DisplayHelper.cs | 13 +-- Projects/Checkers/Helpers/PositionHelper.cs | 61 +++------- Projects/Checkers/Helpers/VectorHelper.cs | 34 +++--- Projects/Checkers/Move.cs | 8 +- Projects/Checkers/Piece.cs | 16 +-- Projects/Checkers/Program.cs | 90 +++++---------- 12 files changed, 210 insertions(+), 366 deletions(-) delete mode 100644 Projects/Checkers/Helpers/BoardHelper.cs diff --git a/Projects/Checkers/Board.cs b/Projects/Checkers/Board.cs index ddc47498..7b5989a5 100644 --- a/Projects/Checkers/Board.cs +++ b/Projects/Checkers/Board.cs @@ -1,4 +1,6 @@ -namespace Checkers; +using Checkers.Data; + +namespace Checkers; public class Board { @@ -6,11 +8,42 @@ public class Board public Board() { - Pieces = BoardHelper.GetStartingPosition(); + Pieces = GetStartingPosition(); } public Board(List startingPosition) { Pieces = startingPosition; } + + public static List GetStartingPosition() + { + // HACK: Can set to false and define a custom start position in GetLimitedStartingPosition + const bool UseDefault = true; + +#pragma warning disable CS0162 + return UseDefault ? GetDefaultStartingPosition() : GetLimitedStartingPosition(); +#pragma warning restore CS0162 + } + + private static List GetLimitedStartingPosition() => + KnowledgeBase.GetLimitedStartingPosition(); + + public static List GetDefaultStartingPosition() => + KnowledgeBase.GetStartingPosition(); + + public PieceColour GetSquareOccupancy(int x, int y) => + GetPieceAt(x, y)?.Side ?? default; + + public Piece? GetPieceAt(int x, int y) => + Pieces.FirstOrDefault(p => p.XPosition == x && p.InPlay && p.YPosition == y); + + public int GetNumberOfWhitePiecesInPlay() => + GetNumberOfPiecesInPlay(PieceColour.White); + + public int GetNumberOfBlackPiecesInPlay() => + GetNumberOfPiecesInPlay(PieceColour.Black); + + private int GetNumberOfPiecesInPlay(PieceColour currentSide) => + Pieces.Count(x => x.Side == currentSide && x.InPlay); } diff --git a/Projects/Checkers/Data/KnowledgeBase.cs b/Projects/Checkers/Data/KnowledgeBase.cs index 8e7cf149..f79f929b 100644 --- a/Projects/Checkers/Data/KnowledgeBase.cs +++ b/Projects/Checkers/Data/KnowledgeBase.cs @@ -9,48 +9,42 @@ public static List GetStartingPosition() { var retVal = new List { - new() { InitialPosition ="A3", Side = PieceColour.Black}, - new() { InitialPosition ="A1", Side = PieceColour.Black}, - new() { InitialPosition ="B2", Side = PieceColour.Black}, - new() { InitialPosition ="C3", Side = PieceColour.Black}, - new() { InitialPosition ="C1", Side = PieceColour.Black}, - new() { InitialPosition ="D2", Side = PieceColour.Black}, - new() { InitialPosition ="E3", Side = PieceColour.Black}, - new() { InitialPosition ="E1", Side = PieceColour.Black}, - new() { InitialPosition ="F2", Side = PieceColour.Black}, - new() { InitialPosition ="G3", Side = PieceColour.Black}, - new() { InitialPosition ="G1", Side = PieceColour.Black}, - new() { InitialPosition ="H2", Side = PieceColour.Black}, - new() { InitialPosition ="A7",Side = PieceColour.White}, - new() { InitialPosition ="B8",Side = PieceColour.White}, - new() { InitialPosition ="B6",Side = PieceColour.White}, - new() { InitialPosition ="C7",Side = PieceColour.White}, - new() { InitialPosition ="D8",Side = PieceColour.White}, - new() { InitialPosition ="D6",Side = PieceColour.White}, - new() { InitialPosition ="E7",Side = PieceColour.White}, - new() { InitialPosition ="F8",Side = PieceColour.White}, - new() { InitialPosition ="F6",Side = PieceColour.White}, - new() { InitialPosition ="G7",Side = PieceColour.White}, - new() { InitialPosition ="H8",Side = PieceColour.White}, - new() { InitialPosition ="H6",Side = PieceColour.White} - }; - - return retVal; - } + new() { NotationPosition ="A3", Side = PieceColour.Black}, + new() { NotationPosition ="A1", Side = PieceColour.Black}, + new() { NotationPosition ="B2", Side = PieceColour.Black}, + new() { NotationPosition ="C3", Side = PieceColour.Black}, + new() { NotationPosition ="C1", Side = PieceColour.Black}, + new() { NotationPosition ="D2", Side = PieceColour.Black}, + new() { NotationPosition ="E3", Side = PieceColour.Black}, + new() { NotationPosition ="E1", Side = PieceColour.Black}, + new() { NotationPosition ="F2", Side = PieceColour.Black}, + new() { NotationPosition ="G3", Side = PieceColour.Black}, + new() { NotationPosition ="G1", Side = PieceColour.Black}, + new() { NotationPosition ="H2", Side = PieceColour.Black}, - public static List GetLimitedStartingPosition() - { - var retVal = new List - { - new () { InitialPosition ="H2", Side = PieceColour.Black}, - new () { InitialPosition ="A1", Side = PieceColour.Black}, - new () { InitialPosition ="G3", Side = PieceColour.White}, - new() { InitialPosition = "E5", Side = PieceColour.White} + new() { NotationPosition ="A7", Side = PieceColour.White}, + new() { NotationPosition ="B8", Side = PieceColour.White}, + new() { NotationPosition ="B6", Side = PieceColour.White}, + new() { NotationPosition ="C7", Side = PieceColour.White}, + new() { NotationPosition ="D8", Side = PieceColour.White}, + new() { NotationPosition ="D6", Side = PieceColour.White}, + new() { NotationPosition ="E7", Side = PieceColour.White}, + new() { NotationPosition ="F8", Side = PieceColour.White}, + new() { NotationPosition ="F6", Side = PieceColour.White}, + new() { NotationPosition ="G7", Side = PieceColour.White}, + new() { NotationPosition ="H8", Side = PieceColour.White}, + new() { NotationPosition ="H6", Side = PieceColour.White} }; return retVal; } - + public static List GetLimitedStartingPosition() => new() + { + new() { NotationPosition = "H2", Side = PieceColour.Black}, + new() { NotationPosition = "A1", Side = PieceColour.Black}, + new() { NotationPosition = "G3", Side = PieceColour.White}, + new() { NotationPosition = "E5", Side = PieceColour.White} + }; } diff --git a/Projects/Checkers/Display.cs b/Projects/Checkers/Display.cs index 3129d326..b8f93805 100644 --- a/Projects/Checkers/Display.cs +++ b/Projects/Checkers/Display.cs @@ -1,31 +1,40 @@ -using System.Drawing; - -namespace Checkers; +namespace Checkers; public static class Display { - private const string BlackPiece = "○"; - private const string BlackKing = "☺"; - private const string WhitePiece = "◙"; - private const string WhiteKing = "☻"; + private const char BlackPiece = '○'; + private const char BlackKing = '☺'; + private const char WhitePiece = '◙'; + private const char WhiteKing = '☻'; + + public static void DisplayBoard(Board currentBoard, int whitesTaken, int blacksTaken) + { + Console.Clear(); + Dictionary<(int X, int Y), char> tiles = new(); + foreach (Piece piece in currentBoard.Pieces) + { + if (piece.InPlay) + { + tiles[(piece.XPosition, piece.YPosition)] = ToChar(piece); + } + } + char C(int x, int y) => tiles.GetValueOrDefault((x, y), '.'); + Console.Write( + $" ╔═══════════════════╗\n" + + $" 8 ║ {C(0, 0)} {C(1, 0)} {C(2, 0)} {C(3, 0)} {C(4, 0)} {C(5, 0)} {C(6, 0)} {C(7, 0)} ║ {BlackPiece} = Black\n" + + $" 7 ║ {C(0, 1)} {C(1, 1)} {C(2, 1)} {C(3, 1)} {C(4, 1)} {C(5, 1)} {C(6, 1)} {C(7, 1)} ║ {BlackKing} = Black King\n" + + $" 6 ║ {C(0, 2)} {C(1, 2)} {C(2, 2)} {C(3, 2)} {C(4, 2)} {C(5, 2)} {C(6, 2)} {C(7, 2)} ║ {WhitePiece} = White\n" + + $" 5 ║ {C(0, 3)} {C(1, 3)} {C(2, 3)} {C(3, 3)} {C(4, 3)} {C(5, 3)} {C(6, 3)} {C(7, 3)} ║ {WhiteKing} = White King\n" + + $" 4 ║ {C(0, 4)} {C(1, 4)} {C(2, 4)} {C(3, 4)} {C(4, 4)} {C(5, 4)} {C(6, 4)} {C(7, 4)} ║\n" + + $" 3 ║ {C(0, 5)} {C(1, 5)} {C(2, 5)} {C(3, 5)} {C(4, 5)} {C(5, 5)} {C(6, 5)} {C(7, 5)} ║ Taken:\n" + + $" 2 ║ {C(0, 6)} {C(1, 6)} {C(2, 6)} {C(3, 6)} {C(4, 6)} {C(5, 6)} {C(6, 6)} {C(7, 6)} ║ {whitesTaken,2} x {WhitePiece}\n" + + $" 1 ║ {C(0, 7)} {C(1, 7)} {C(2, 7)} {C(3, 7)} {C(4, 7)} {C(5, 7)} {C(6, 7)} {C(7, 7)} ║ {blacksTaken,2} x {BlackPiece}\n" + + $" ╚═══════════════════╝\n" + + $" A B C D E F G H"); - public static void DisplayBoard(Board currentBoard) - { - PrintBoard(); - PrintPieces(currentBoard); Console.CursorVisible = false; } - public static void DisplayStats(int whitesTaken, int blacksTaken) - { - Console.SetCursorPosition(22, 6); - Console.WriteLine(" Taken:"); - Console.SetCursorPosition(22, 7); - Console.WriteLine($"{whitesTaken.ToString(),2} x {WhitePiece}"); - Console.SetCursorPosition(22, 8); - Console.WriteLine($"{blacksTaken.ToString(),2} x {BlackPiece}"); - } - public static void DisplayCurrentPlayer(PieceColour currentSide) { Console.SetCursorPosition(0, 11); @@ -38,63 +47,14 @@ public static void DisplayWinner(PieceColour winningSide) Console.WriteLine($"*** {winningSide} wins ***"); } - private static void PrintPieces(Board currentBoard) - { - foreach (var piece in currentBoard.Pieces.Where(x => x.InPlay)) - { - var screenPosition = - DisplayHelper.GetScreenPositionFromBoardPosition(new Point(piece.XPosition, piece.YPosition)); - var displayPiece = GetDisplayPiece(piece); - Console.SetCursorPosition(screenPosition.X, screenPosition.Y); - Console.Write(displayPiece); - } - } - - private static string GetDisplayPiece(Piece currentPiece) - { - string retVal; - - if (currentPiece.Side == PieceColour.Black) - { - retVal = currentPiece.Promoted ? BlackKing : BlackPiece; - } - else + private static char ToChar(Piece piece) => + (piece.Side, piece.Promoted) switch { - retVal = currentPiece.Promoted ? WhiteKing : WhitePiece; - } - - return retVal; - } - - private static void PrintBoard() - { - Console.Clear(); - var emptyBoard = GetBlankBoard(); - - foreach (var rank in emptyBoard) - { - Console.WriteLine(rank); - } - } - - private static List GetBlankBoard() - { - var retVal = new List - { - $" ╔═══════════════════╗", - $"8║ . . . . . . . . ║ {BlackPiece} = Black", - $"7║ . . . . . . . . ║ {BlackKing} = Black King", - $"6║ . . . . . . . . ║ {WhitePiece} = White", - $"5║ . . . . . . . . ║ {WhiteKing} = White King", - $"4║ . . . . . . . . ║", - $"3║ . . . . . . . . ║", - $"2║ . . . . . . . . ║", - $"1║ . . . . . . . . ║", - $" ╚═══════════════════╝", - $" A B C D E F G H" - }; - - return retVal; - } + (PieceColour.Black, false) => BlackPiece, + (PieceColour.Black, true) => BlackKing, + (PieceColour.White, false) => WhitePiece, + (PieceColour.White, true) => WhiteKing, + _ => throw new NotImplementedException(), + }; } diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index 3688dccb..94a597fc 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -1,10 +1,8 @@ -using System.Drawing; - -namespace Checkers; +namespace Checkers; public static class Engine { - public static MoveOutcome PlayNextMove(PieceColour currentSide, Board currentBoard, Point? playerFrom = null, Point? playerTo = null) + public static MoveOutcome PlayNextMove(PieceColour currentSide, Board currentBoard, (int X, int Y)? playerFrom = null, (int X, int Y)? playerTo = null) { List possibleMoves; MoveOutcome outcome; @@ -19,7 +17,7 @@ public static MoveOutcome PlayNextMove(PieceColour currentSide, Board currentBoa } else { - if (MoveIsValid(playerFrom, (Point)playerTo, possibleMoves, currentBoard, out var selectedMove)) + if (MoveIsValid(playerFrom, playerTo.Value, possibleMoves, currentBoard, out var selectedMove)) { possibleMoves.Clear(); @@ -41,7 +39,7 @@ public static MoveOutcome PlayNextMove(PieceColour currentSide, Board currentBoa outcome = MoveOutcome.EndGame; break; default: - throw new ArgumentOutOfRangeException(); + throw new NotImplementedException(); } } } @@ -77,21 +75,21 @@ public static MoveOutcome PlayNextMove(PieceColour currentSide, Board currentBoa throw new ArgumentNullException(nameof(pieceToMove)); } - var newX = bestMove.To.X; - var newY = bestMove.To.Y; + int newX = bestMove.To.X; + int newY = bestMove.To.Y; - var from = PositionHelper.GetNotationPosition(pieceToMove.XPosition, pieceToMove.YPosition); + var from = PositionHelper.ToPositionNotationString(pieceToMove.XPosition, pieceToMove.YPosition); pieceToMove.XPosition = newX; pieceToMove.YPosition = newY; - var to = PositionHelper.GetNotationPosition(pieceToMove.XPosition, pieceToMove.YPosition); + var to = PositionHelper.ToPositionNotationString(pieceToMove.XPosition, pieceToMove.YPosition); - var blackPieces = BoardHelper.GetNumberOfBlackPiecesInPlay(currentBoard); - var whitePieces = BoardHelper.GetNumberOfWhitePiecesInPlay(currentBoard); + int blackPieces = currentBoard.GetNumberOfBlackPiecesInPlay(); + int whitePieces = currentBoard.GetNumberOfWhitePiecesInPlay(); // Promotion can only happen if not already a king and you have reached the far side - if (newY is 1 or 8 && pieceToMove.Promoted == false) + if (newY is 0 or 7 && pieceToMove.Promoted == false) { pieceToMove.Promoted = true; @@ -155,7 +153,7 @@ private static bool MoreCapturesAvailable(PieceColour currentSide, Board current return currentBoard.Pieces.FirstOrDefault(x => x.Aggressor); } - private static bool MoveIsValid(Point? from, Point to, List possibleMoves, Board currentBoard, out Move? selectedMove) + private static bool MoveIsValid((int X, int Y)? from, (int X, int Y) to, List possibleMoves, Board currentBoard, out Move? selectedMove) { selectedMove = default; @@ -164,7 +162,7 @@ private static bool MoveIsValid(Point? from, Point to, List possibleMoves, return false; } - var selectedPiece = BoardHelper.GetPieceAt(from.Value.X, from.Value.Y, currentBoard); + var selectedPiece = currentBoard.GetPieceAt(from.Value.X, from.Value.Y); foreach (var move in possibleMoves.Where(move => move.PieceToMove == selectedPiece && move.To == to)) { @@ -217,7 +215,7 @@ private static bool PerformCapture(PieceColour currentSide, List possibleC if (squareToCapture != null) { var deadMan = - BoardHelper.GetPieceAt(squareToCapture.Value.X, squareToCapture.Value.Y, currentBoard); + currentBoard.GetPieceAt(squareToCapture.Value.X, squareToCapture.Value.Y); if (deadMan != null) { @@ -227,20 +225,20 @@ private static bool PerformCapture(PieceColour currentSide, List possibleC if (captureMove.PieceToMove != null) { captureMove.PieceToMove.Aggressor = true; - from = PositionHelper.GetNotationPosition(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); + from = PositionHelper.ToPositionNotationString(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); captureMove.PieceToMove.XPosition = captureMove.To.X; captureMove.PieceToMove.YPosition = captureMove.To.Y; - to = PositionHelper.GetNotationPosition(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); + to = PositionHelper.ToPositionNotationString(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); } } } - var anyPromoted = CheckForPiecesToPromote(currentSide, currentBoard); - var blackPieces = BoardHelper.GetNumberOfBlackPiecesInPlay(currentBoard); - var whitePieces = BoardHelper.GetNumberOfWhitePiecesInPlay(currentBoard); + bool anyPromoted = CheckForPiecesToPromote(currentSide, currentBoard); + int blackPieces = currentBoard.GetNumberOfBlackPiecesInPlay(); + int whitePieces = currentBoard.GetNumberOfWhitePiecesInPlay(); var playerAction = anyPromoted ? PlayerAction.CapturePromotion : PlayerAction.Capture; @@ -251,8 +249,8 @@ private static bool PerformCapture(PieceColour currentSide, List possibleC private static bool CheckForPiecesToPromote(PieceColour currentSide, Board currentBoard) { - var retVal = false; - var promotionSpot = currentSide == PieceColour.White ? 8 : 1; + bool retVal = false; + int promotionSpot = currentSide == PieceColour.White ? 7 : 0; foreach (var piece in currentBoard.Pieces.Where(x => x.Side == currentSide)) { @@ -341,7 +339,7 @@ private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board cu continue; } - var targetSquare = BoardHelper.GetSquareOccupancy(currentX, currentY, currentBoard); + var targetSquare = currentBoard.GetSquareOccupancy(currentX, currentY); if (targetSquare == PieceColour.NotSet) { @@ -350,7 +348,7 @@ private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board cu continue; } - var newMove = new Move { PieceToMove = piece, TypeOfMove = MoveType.StandardMove, To = new Point { X = currentX, Y = currentY } }; + var newMove = new Move { PieceToMove = piece, TypeOfMove = MoveType.StandardMove, To = (currentX, currentY) }; possibleMoves.Add(newMove); } else @@ -372,14 +370,14 @@ private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board cu continue; } - var beyondSquare = BoardHelper.GetSquareOccupancy(beyondX, beyondY, currentBoard); + var beyondSquare = currentBoard.GetSquareOccupancy(beyondX, beyondY); if (beyondSquare != PieceColour.NotSet) { continue; } - var attack = new Move { PieceToMove = piece, TypeOfMove = MoveType.Capture, To = new Point { X = beyondX, Y = beyondY }, Capturing = new Point { X = currentX, Y = currentY } }; + var attack = new Move { PieceToMove = piece, TypeOfMove = MoveType.Capture, To = (beyondX, beyondY), Capturing = (currentX, currentY) }; possibleMoves.Add(attack); } } @@ -387,7 +385,7 @@ private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board cu } } - private static Point DeriveToPosition(int pieceX, int pieceY, int captureX, int captureY) + private static (int X, int Y) DeriveToPosition(int pieceX, int pieceY, int captureX, int captureY) { int newX; if (captureX > pieceX) @@ -409,7 +407,7 @@ private static Point DeriveToPosition(int pieceX, int pieceY, int captureX, int newY = captureY - 1; } - return new Point(newX, newY); + return (newX, newY); } private static bool PlayingWithJustKings(PieceColour currentSide, Board currentBoard, out List possibleMoves) @@ -431,8 +429,8 @@ private static bool PlayingWithJustKings(PieceColour currentSide, Board currentB { foreach (var target in currentBoard.Pieces.Where(x => x.Side != currentSide && x.InPlay)) { - var kingPoint = new Point(king.XPosition, king.YPosition); - var targetPoint = new Point(target.XPosition, target.YPosition); + var kingPoint = (king.XPosition, king.YPosition); + var targetPoint = (target.XPosition, target.YPosition); var distance = VectorHelper.GetPointDistance(kingPoint, targetPoint); if (distance < shortestDistance) @@ -450,7 +448,7 @@ private static bool PlayingWithJustKings(PieceColour currentSide, Board currentB foreach (var movementOption in movementOptions) { - var squareStatus = BoardHelper.GetSquareOccupancy(movementOption.X, movementOption.Y, currentBoard); + var squareStatus = currentBoard.GetSquareOccupancy(movementOption.X, movementOption.Y); if (squareStatus == PieceColour.NotSet) { @@ -458,7 +456,7 @@ private static bool PlayingWithJustKings(PieceColour currentSide, Board currentB { PieceToMove = currentHero, TypeOfMove = MoveType.EndGame, - To = new Point(movementOption.X, movementOption.Y) + To = (movementOption.X, movementOption.Y) }; if (!PositionHelper.PointValid(movementOption.X, movementOption.Y)) diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index 65123b53..14340a27 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -34,7 +34,7 @@ public Game() ShowBoard(); } - public MoveOutcome NextRound(Point? from = null, Point? to = null) + public MoveOutcome NextRound((int X, int Y)? from = null, (int X, int Y)? to = null) { MovesSoFar++; var res = Engine.PlayNextMove(CurrentGo, GameBoard, from, to); @@ -106,8 +106,7 @@ private void ShowBoard() { if (!NoDisplay) { - Display.DisplayBoard(GameBoard); - Display.DisplayStats(GetWhitePiecesTaken(), GetBlackPiecesTaken()); + Display.DisplayBoard(GameBoard, GetWhitePiecesTaken(), GetBlackPiecesTaken()); Display.DisplayCurrentPlayer(CurrentGo); } } diff --git a/Projects/Checkers/Helpers/BoardHelper.cs b/Projects/Checkers/Helpers/BoardHelper.cs deleted file mode 100644 index 50bb3835..00000000 --- a/Projects/Checkers/Helpers/BoardHelper.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Checkers.Data; - -namespace Checkers.Helpers; - -/// -/// Board related routines -/// -public static class BoardHelper -{ - public static List GetStartingPosition() - { - // HACK: Can set to false and define a custom start position in GetLimitedStartingPosition - const bool UseDefault = true; - -#pragma warning disable CS0162 - return UseDefault ? GetDefaultStartingPosition() : GetLimitedStartingPosition(); -#pragma warning restore CS0162 - } - - private static List GetLimitedStartingPosition() - { - return KnowledgeBase.GetLimitedStartingPosition(); - } - - public static List GetDefaultStartingPosition() - { - return KnowledgeBase.GetStartingPosition(); - } - - public static PieceColour GetSquareOccupancy(int xp, int yp, Board currentBoard) - { - var piece = currentBoard.Pieces.FirstOrDefault(x => x.XPosition == xp && x.InPlay && x.YPosition == yp); - - return piece == null ? PieceColour.NotSet : piece.Side; - } - - public static Piece? GetPieceAt(int xp, int yp, Board currentBoard) - { - var retVal = currentBoard.Pieces.FirstOrDefault(x => x.XPosition == xp && x.InPlay && x.YPosition == yp); - - if (retVal == null) - { - return default; - } - - return retVal; - } - - public static int GetNumberOfWhitePiecesInPlay(Board currentBoard) - { - return GetNumberOfPiecesInPlay(currentBoard, PieceColour.White); - } - - public static int GetNumberOfBlackPiecesInPlay(Board currentBoard) - { - return GetNumberOfPiecesInPlay(currentBoard, PieceColour.Black); - } - - private static int GetNumberOfPiecesInPlay(Board currentBoard, PieceColour currentSide) - { - return currentBoard.Pieces.Count(x => x.Side == currentSide && x.InPlay); - } -} - diff --git a/Projects/Checkers/Helpers/DisplayHelper.cs b/Projects/Checkers/Helpers/DisplayHelper.cs index 10a19d99..67c387bd 100644 --- a/Projects/Checkers/Helpers/DisplayHelper.cs +++ b/Projects/Checkers/Helpers/DisplayHelper.cs @@ -1,15 +1,12 @@ -using System.Drawing; - -namespace Checkers.Helpers; +namespace Checkers.Helpers; public static class DisplayHelper { - public static Point GetScreenPositionFromBoardPosition(Point boardPosition) + public static (int X, int Y) GetScreenPositionFromBoardPosition((int X, int Y) boardPosition) { - var actualX = (boardPosition.X * 2) + 2; - var actualY = boardPosition.Y + 0; - - return new Point(actualX, actualY); + var actualX = (boardPosition.X * 2) + 7; + var actualY = boardPosition.Y + 0 + 1; + return (actualX, actualY); } } diff --git a/Projects/Checkers/Helpers/PositionHelper.cs b/Projects/Checkers/Helpers/PositionHelper.cs index c14734f9..b5e23231 100644 --- a/Projects/Checkers/Helpers/PositionHelper.cs +++ b/Projects/Checkers/Helpers/PositionHelper.cs @@ -1,56 +1,27 @@ -using System.Drawing; +namespace Checkers.Helpers; -namespace Checkers.Helpers; - -/// -/// Position routines -/// +/// Position routines public static class PositionHelper { - public static string GetNotationPosition(int x, int y) + public static string ToPositionNotationString(int x, int y) { - if (!PointValid(x, y)) - { - throw new ArgumentException("Not a valid position!"); - } - - var yPortion = 9 - y; - - var xPortion = Convert.ToChar(x + 64); - - return xPortion + yPortion.ToString(); + if (!PointValid(x, y)) throw new ArgumentException("Not a valid position!"); + return $"{(char)('A' + x)}{y + 1}"; } - public static Point? GetPositionByNotation(string notation) + public static (int X, int Y) ParsePositionNotation(string notation) { - const int ExpectedPositionLength = 2; - - notation = notation.Trim(); - - if (notation.Length != ExpectedPositionLength) - { - return default; - } - - try - { - var letterPart = notation.Substring(0, 1); - var numberPart = notation.Substring(1, 1); - - var x = letterPart.ToUpper().ToCharArray()[0] - 64; - var y = 9 - Convert.ToInt32(numberPart); - - return new Point(x, y); - } - catch - { - return default; - } + 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', '8' - notation[1]); } - public static bool PointValid(int x, int y) - { - return x is > 0 and < 9 && y is > 0 and < 9; - } + public static bool PointValid(int x, int y) => + 0 <= x && x < 8 && + 0 <= y && y < 8; } diff --git a/Projects/Checkers/Helpers/VectorHelper.cs b/Projects/Checkers/Helpers/VectorHelper.cs index 67a6f750..43c82a10 100644 --- a/Projects/Checkers/Helpers/VectorHelper.cs +++ b/Projects/Checkers/Helpers/VectorHelper.cs @@ -1,6 +1,4 @@ -using System.Drawing; - -namespace Checkers.Helpers; +namespace Checkers.Helpers; /// /// Track distance between 2 points @@ -8,7 +6,7 @@ namespace Checkers.Helpers; /// public static class VectorHelper { - public static double GetPointDistance(Point first, Point second) + public static double GetPointDistance((int X, int Y) first, (int X, int Y) second) { // Easiest cases are points on the same vertical or horizontal axis if (first.X == second.X) @@ -28,9 +26,9 @@ public static double GetPointDistance(Point first, Point second) return Math.Sqrt(Math.Pow(sideA, 2) + Math.Pow(sideB, 2)); } - public static List WhereIsVillain(Piece hero, Piece villain) + public static List<(int X, int Y)> WhereIsVillain(Piece hero, Piece villain) { - var retVal = new List(); + var retVal = new List<(int X, int Y)>(); var directions = new List(); @@ -59,23 +57,23 @@ public static List WhereIsVillain(Piece hero, Piece villain) switch (directions[0]) { case Direction.Up: - retVal.Add(new Point(hero.XPosition - 1, hero.YPosition - 1)); - retVal.Add(new Point(hero.XPosition + 1, hero.YPosition - 1)); + retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); + retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); break; case Direction.Down: - retVal.Add(new Point(hero.XPosition - 1, hero.YPosition + 1)); - retVal.Add(new Point(hero.XPosition + 1, hero.YPosition + 1)); + retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); + retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); break; case Direction.Left: - retVal.Add(new Point(hero.XPosition - 1, hero.YPosition - 1)); - retVal.Add(new Point(hero.XPosition - 1, hero.YPosition + 1)); + retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); + retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); break; case Direction.Right: - retVal.Add(new Point(hero.XPosition + 1, hero.YPosition - 1)); - retVal.Add(new Point(hero.XPosition + 1, hero.YPosition + 1)); + retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); + retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); break; default: @@ -86,22 +84,22 @@ public static List WhereIsVillain(Piece hero, Piece villain) { if (directions.Contains(Direction.Left) && directions.Contains(Direction.Up)) { - retVal.Add(new Point(hero.XPosition - 1, hero.YPosition - 1)); + retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); } if (directions.Contains(Direction.Left) && directions.Contains(Direction.Down)) { - retVal.Add(new Point(hero.XPosition - 1, hero.YPosition + 1)); + retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); } if (directions.Contains(Direction.Right) && directions.Contains(Direction.Up)) { - retVal.Add(new Point(hero.XPosition + 1, hero.YPosition - 1)); + retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); } if (directions.Contains(Direction.Right) && directions.Contains(Direction.Down)) { - retVal.Add(new Point(hero.XPosition + 1, hero.YPosition + 1)); + retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); } } diff --git a/Projects/Checkers/Move.cs b/Projects/Checkers/Move.cs index cd74360d..5b550d54 100644 --- a/Projects/Checkers/Move.cs +++ b/Projects/Checkers/Move.cs @@ -1,14 +1,12 @@ -using System.Drawing; - -namespace Checkers; +namespace Checkers; public class Move { public Piece? PieceToMove { get; set; } - public Point To { get; set; } + public (int X, int Y) To { get; set; } - public Point? Capturing { get; set; } + public (int X, int Y)? Capturing { get; set; } public MoveType TypeOfMove { get; set; } = MoveType.Unknown; } diff --git a/Projects/Checkers/Piece.cs b/Projects/Checkers/Piece.cs index 888c87bb..717fb9be 100644 --- a/Projects/Checkers/Piece.cs +++ b/Projects/Checkers/Piece.cs @@ -12,20 +12,10 @@ public Piece() public int YPosition { get; set; } - public string InitialPosition + public string NotationPosition { - set - { - var position = PositionHelper.GetPositionByNotation(value); - - if (position == null) - { - return; - } - - XPosition = position.Value.X; - YPosition = position.Value.Y; - } + get => PositionHelper.ToPositionNotationString(XPosition, YPosition); + set => (XPosition, YPosition) = PositionHelper.ParsePositionNotation(value); } public PieceColour Side { get; set; } diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index cb682a01..5aede605 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -26,10 +26,7 @@ Game? game = null; var numberOfPlayers = 0; var gameState = GameState.IntroScreen; -var xSelection = 4; -var ySelection = 4; -Point? selectedFromPoint = null; -Point? selectedToPoint = null; +(int X, int Y) selection = (4, 5); while (gameState != GameState.Stopped) { @@ -99,83 +96,56 @@ void RunGameLoop() var currentPlayer = game.Players.FirstOrDefault(x => x.Side == game.CurrentGo); if (currentPlayer != null && currentPlayer.ControlledBy == PlayerControl.Human) { - while (selectedFromPoint == null || selectedToPoint == null) + (int X, int Y)? from = null; + (int X, int Y)? to = null; + while (from is null || to is null) { - var selection = DisplayHelper.GetScreenPositionFromBoardPosition(new Point(xSelection, ySelection)); - Console.SetCursorPosition(selection.X - 1, selection.Y); + (int X, int Y) screenSelection = DisplayHelper.GetScreenPositionFromBoardPosition(selection); + Console.SetCursorPosition(screenSelection.X - 1, screenSelection.Y); Console.Write("["); - Console.SetCursorPosition(selection.X + 1, selection.Y); + Console.SetCursorPosition(screenSelection.X + 1, screenSelection.Y); Console.Write("]"); - var oldY = ySelection; - var oldX = xSelection; - switch (Console.ReadKey(true).Key) + ConsoleKey key = Console.ReadKey(true).Key; + + var screenPreviousSelection = DisplayHelper.GetScreenPositionFromBoardPosition(selection); + Console.SetCursorPosition(screenPreviousSelection.X - 1, screenPreviousSelection.Y); + Console.Write(" "); + Console.SetCursorPosition(screenPreviousSelection.X + 1, screenPreviousSelection.Y); + Console.Write(" "); + + switch (key) { - case ConsoleKey.UpArrow: - if (ySelection > 1) - { - oldY = ySelection; - ySelection--; - } - break; - case ConsoleKey.DownArrow: - if (ySelection < 8) - { - oldY = ySelection; - ySelection++; - } - break; - case ConsoleKey.LeftArrow: - if (xSelection > 1) - { - oldX = xSelection; - xSelection--; - } - break; - case ConsoleKey.RightArrow: - if (xSelection < 8) - { - oldX = xSelection; - xSelection++; - } - break; + case ConsoleKey.DownArrow: selection.Y = Math.Min(7, selection.Y + 1); break; + case ConsoleKey.UpArrow: selection.Y = Math.Max(0, selection.Y - 1); break; + case ConsoleKey.LeftArrow: selection.X = Math.Max(0, selection.X - 1); break; + case ConsoleKey.RightArrow: selection.X = Math.Min(7, selection.X + 1); break; case ConsoleKey.Enter: - if (selectedFromPoint == null) + if (from is null) { - selectedFromPoint = new Point(xSelection, ySelection); + from = (selection.X, selection.Y); } else { - selectedToPoint = new Point(xSelection, ySelection); + to = (selection.X, selection.Y); } break; case ConsoleKey.Escape: - selectedFromPoint = null; - selectedToPoint = null; + from = null; + to = null; break; } - - var oldSelection = DisplayHelper.GetScreenPositionFromBoardPosition(new Point(oldX, oldY)); - Console.SetCursorPosition(oldSelection.X - 1, oldSelection.Y); - Console.Write(" "); - Console.SetCursorPosition(oldSelection.X + 1, oldSelection.Y); - Console.Write(" "); } - var fromPoint = selectedFromPoint; - var toPoint = selectedToPoint; - - var actualFromPiece = BoardHelper.GetPieceAt(fromPoint.Value.X, fromPoint.Value.Y, game.GameBoard); - + var actualFromPiece = game.GameBoard.GetPieceAt(from.Value.X, from.Value.Y); if (actualFromPiece == null || actualFromPiece.Side != game.CurrentGo) { - fromPoint = toPoint = selectedToPoint = selectedFromPoint = null; + from = null; + to = null; } - - if (fromPoint != null && toPoint != null) + if (from != null && to != null) { - _ = game.NextRound(fromPoint, toPoint); - selectedFromPoint = selectedToPoint = null; + _ = game.NextRound(from, to); } else { From 37436f5e2f1ca738a93e26b78e3334c439dbd14a Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sun, 19 Jun 2022 10:41:02 -0400 Subject: [PATCH 19/68] moved DisplayHelper into Display --- Projects/Checkers/Display.cs | 47 ++++++++++++---------- Projects/Checkers/Helpers/DisplayHelper.cs | 12 ------ Projects/Checkers/Program.cs | 4 +- 3 files changed, 27 insertions(+), 36 deletions(-) delete mode 100644 Projects/Checkers/Helpers/DisplayHelper.cs diff --git a/Projects/Checkers/Display.cs b/Projects/Checkers/Display.cs index b8f93805..5308523d 100644 --- a/Projects/Checkers/Display.cs +++ b/Projects/Checkers/Display.cs @@ -9,28 +9,28 @@ public static class Display public static void DisplayBoard(Board currentBoard, int whitesTaken, int blacksTaken) { - Console.Clear(); - Dictionary<(int X, int Y), char> tiles = new(); - foreach (Piece piece in currentBoard.Pieces) - { - if (piece.InPlay) - { - tiles[(piece.XPosition, piece.YPosition)] = ToChar(piece); - } - } - char C(int x, int y) => tiles.GetValueOrDefault((x, y), '.'); - Console.Write( - $" ╔═══════════════════╗\n" + - $" 8 ║ {C(0, 0)} {C(1, 0)} {C(2, 0)} {C(3, 0)} {C(4, 0)} {C(5, 0)} {C(6, 0)} {C(7, 0)} ║ {BlackPiece} = Black\n" + - $" 7 ║ {C(0, 1)} {C(1, 1)} {C(2, 1)} {C(3, 1)} {C(4, 1)} {C(5, 1)} {C(6, 1)} {C(7, 1)} ║ {BlackKing} = Black King\n" + - $" 6 ║ {C(0, 2)} {C(1, 2)} {C(2, 2)} {C(3, 2)} {C(4, 2)} {C(5, 2)} {C(6, 2)} {C(7, 2)} ║ {WhitePiece} = White\n" + - $" 5 ║ {C(0, 3)} {C(1, 3)} {C(2, 3)} {C(3, 3)} {C(4, 3)} {C(5, 3)} {C(6, 3)} {C(7, 3)} ║ {WhiteKing} = White King\n" + - $" 4 ║ {C(0, 4)} {C(1, 4)} {C(2, 4)} {C(3, 4)} {C(4, 4)} {C(5, 4)} {C(6, 4)} {C(7, 4)} ║\n" + - $" 3 ║ {C(0, 5)} {C(1, 5)} {C(2, 5)} {C(3, 5)} {C(4, 5)} {C(5, 5)} {C(6, 5)} {C(7, 5)} ║ Taken:\n" + - $" 2 ║ {C(0, 6)} {C(1, 6)} {C(2, 6)} {C(3, 6)} {C(4, 6)} {C(5, 6)} {C(6, 6)} {C(7, 6)} ║ {whitesTaken,2} x {WhitePiece}\n" + - $" 1 ║ {C(0, 7)} {C(1, 7)} {C(2, 7)} {C(3, 7)} {C(4, 7)} {C(5, 7)} {C(6, 7)} {C(7, 7)} ║ {blacksTaken,2} x {BlackPiece}\n" + - $" ╚═══════════════════╝\n" + - $" A B C D E F G H"); + Console.Clear(); + Dictionary<(int X, int Y), char> tiles = new(); + foreach (Piece piece in currentBoard.Pieces) + { + if (piece.InPlay) + { + tiles[(piece.XPosition, piece.YPosition)] = ToChar(piece); + } + } + char C(int x, int y) => tiles.GetValueOrDefault((x, y), '.'); + Console.Write( + $" ╔═══════════════════╗\n" + + $" 8 ║ {C(0, 0)} {C(1, 0)} {C(2, 0)} {C(3, 0)} {C(4, 0)} {C(5, 0)} {C(6, 0)} {C(7, 0)} ║ {BlackPiece} = Black\n" + + $" 7 ║ {C(0, 1)} {C(1, 1)} {C(2, 1)} {C(3, 1)} {C(4, 1)} {C(5, 1)} {C(6, 1)} {C(7, 1)} ║ {BlackKing} = Black King\n" + + $" 6 ║ {C(0, 2)} {C(1, 2)} {C(2, 2)} {C(3, 2)} {C(4, 2)} {C(5, 2)} {C(6, 2)} {C(7, 2)} ║ {WhitePiece} = White\n" + + $" 5 ║ {C(0, 3)} {C(1, 3)} {C(2, 3)} {C(3, 3)} {C(4, 3)} {C(5, 3)} {C(6, 3)} {C(7, 3)} ║ {WhiteKing} = White King\n" + + $" 4 ║ {C(0, 4)} {C(1, 4)} {C(2, 4)} {C(3, 4)} {C(4, 4)} {C(5, 4)} {C(6, 4)} {C(7, 4)} ║\n" + + $" 3 ║ {C(0, 5)} {C(1, 5)} {C(2, 5)} {C(3, 5)} {C(4, 5)} {C(5, 5)} {C(6, 5)} {C(7, 5)} ║ Taken:\n" + + $" 2 ║ {C(0, 6)} {C(1, 6)} {C(2, 6)} {C(3, 6)} {C(4, 6)} {C(5, 6)} {C(6, 6)} {C(7, 6)} ║ {whitesTaken,2} x {WhitePiece}\n" + + $" 1 ║ {C(0, 7)} {C(1, 7)} {C(2, 7)} {C(3, 7)} {C(4, 7)} {C(5, 7)} {C(6, 7)} {C(7, 7)} ║ {blacksTaken,2} x {BlackPiece}\n" + + $" ╚═══════════════════╝\n" + + $" A B C D E F G H"); Console.CursorVisible = false; } @@ -56,5 +56,8 @@ private static char ToChar(Piece piece) => (PieceColour.White, true) => WhiteKing, _ => throw new NotImplementedException(), }; + + public static (int X, int Y) GetScreenPositionFromBoardPosition((int X, int Y) boardPosition) => + ((boardPosition.X * 2) + 7, boardPosition.Y + 1); } diff --git a/Projects/Checkers/Helpers/DisplayHelper.cs b/Projects/Checkers/Helpers/DisplayHelper.cs deleted file mode 100644 index 67c387bd..00000000 --- a/Projects/Checkers/Helpers/DisplayHelper.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Checkers.Helpers; - -public static class DisplayHelper -{ - public static (int X, int Y) GetScreenPositionFromBoardPosition((int X, int Y) boardPosition) - { - var actualX = (boardPosition.X * 2) + 7; - var actualY = boardPosition.Y + 0 + 1; - return (actualX, actualY); - } -} - diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 5aede605..05470e72 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -100,7 +100,7 @@ void RunGameLoop() (int X, int Y)? to = null; while (from is null || to is null) { - (int X, int Y) screenSelection = DisplayHelper.GetScreenPositionFromBoardPosition(selection); + (int X, int Y) screenSelection = Display.GetScreenPositionFromBoardPosition(selection); Console.SetCursorPosition(screenSelection.X - 1, screenSelection.Y); Console.Write("["); Console.SetCursorPosition(screenSelection.X + 1, screenSelection.Y); @@ -108,7 +108,7 @@ void RunGameLoop() ConsoleKey key = Console.ReadKey(true).Key; - var screenPreviousSelection = DisplayHelper.GetScreenPositionFromBoardPosition(selection); + var screenPreviousSelection = Display.GetScreenPositionFromBoardPosition(selection); Console.SetCursorPosition(screenPreviousSelection.X - 1, screenPreviousSelection.Y); Console.Write(" "); Console.SetCursorPosition(screenPreviousSelection.X + 1, screenPreviousSelection.Y); From 15b9abc4fbdba9b769b5ff2853905791a931abf2 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sun, 19 Jun 2022 10:50:19 -0400 Subject: [PATCH 20/68] press key to continue clean up --- Projects/Checkers/Program.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 05470e72..acd12c63 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -181,9 +181,10 @@ void HandleGameOver() void PressAnyKeyToContinue() { + const string prompt = "Press any key to cotinue..."; (int left, int top) = (Console.CursorLeft, Console.CursorTop); - Console.Write("Press any key to cotinue..."); + Console.Write(prompt); Console.ReadKey(true); Console.SetCursorPosition(left, top); - Console.Write(" "); + Console.Write(new string(' ', prompt.Length)); } From fe4f45e140e0a23505c0a33fecc65323e2458469 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sun, 19 Jun 2022 10:52:08 -0400 Subject: [PATCH 21/68] log file based on command line args rather than a const variable --- Projects/Checkers/Program.cs | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index acd12c63..a732337d 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -1,31 +1,21 @@ using Checkers; using System.Diagnostics; -using System.Drawing; using System.IO; Console.CursorVisible = false; -// HACK: Set to true to create output file for game analysis -const bool CreateOutputFileForAnalysis = false; - -if (CreateOutputFileForAnalysis) -#pragma warning disable CS0162 +if (args is not null && args.Contains("--trace")) { - var prefix = Path.GetTempPath(); - var traceFile = $"{prefix}checkers_game_{Guid.NewGuid()}.txt"; - + string traceFile = $"CheckersLog.{DateTime.Now}.log"; Trace.Listeners.Add(new TextWriterTraceListener(File.Create(traceFile))); } -#pragma warning restore CS0162 Trace.AutoFlush = true; Console.OutputEncoding = System.Text.Encoding.UTF8; -var sw = new Stopwatch(); -sw.Start(); LoggingHelper.LogStart(); Game? game = null; -var numberOfPlayers = 0; -var gameState = GameState.IntroScreen; +int numberOfPlayers = 0; +GameState gameState = GameState.IntroScreen; (int X, int Y) selection = (4, 5); while (gameState != GameState.Stopped) @@ -77,7 +67,7 @@ void ShowIntroScreenAndGetOption() Console.WriteLine(); Console.Write("Enter the number of players (0-2): "); - var entry = Console.ReadLine()?.Trim(); + string? entry = Console.ReadLine()?.Trim(); while (entry is not "0" and not "1" and not "2") { Console.WriteLine("Invalid Input. Try Again."); @@ -172,7 +162,6 @@ void HandleGameOver() LoggingHelper.LogMoves(game.MovesSoFar); } LoggingHelper.LogFinish(); - sw.Stop(); if (game != null) { Display.DisplayWinner(game.GameWinner); From 9ea5e69cbc3d96d346b3327f4cbaf3df818a1a31 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sun, 19 Jun 2022 10:57:29 -0400 Subject: [PATCH 22/68] white space --- Projects/Checkers/Board.cs | 18 +- Projects/Checkers/Data/KnowledgeBase.cs | 77 +- Projects/Checkers/Display.cs | 115 ++- Projects/Checkers/Engine.cs | 911 ++++++++++---------- Projects/Checkers/Game.cs | 213 +++-- Projects/Checkers/Helpers/LoggingHelper.cs | 104 +-- Projects/Checkers/Helpers/PlayerHelper.cs | 57 +- Projects/Checkers/Helpers/PositionHelper.cs | 37 +- Projects/Checkers/Helpers/VectorHelper.cs | 199 +++-- Projects/Checkers/Move.cs | 9 +- Projects/Checkers/Piece.cs | 33 +- Projects/Checkers/Player.cs | 5 +- Projects/Checkers/Program.cs | 94 +- Projects/Checkers/Types/Direction.cs | 9 +- Projects/Checkers/Types/GameState.cs | 9 +- Projects/Checkers/Types/MoveOutcome.cs | 17 +- Projects/Checkers/Types/MoveType.cs | 9 +- Projects/Checkers/Types/PieceColour.cs | 7 +- Projects/Checkers/Types/PlayerAction.cs | 9 +- Projects/Checkers/Types/PlayerControl.cs | 5 +- 20 files changed, 960 insertions(+), 977 deletions(-) diff --git a/Projects/Checkers/Board.cs b/Projects/Checkers/Board.cs index 7b5989a5..5647a456 100644 --- a/Projects/Checkers/Board.cs +++ b/Projects/Checkers/Board.cs @@ -4,17 +4,17 @@ namespace Checkers; public class Board { - public List Pieces { get; set; } + public List Pieces { get; set; } - public Board() - { - Pieces = GetStartingPosition(); - } + public Board() + { + Pieces = GetStartingPosition(); + } - public Board(List startingPosition) - { - Pieces = startingPosition; - } + public Board(List startingPosition) + { + Pieces = startingPosition; + } public static List GetStartingPosition() { diff --git a/Projects/Checkers/Data/KnowledgeBase.cs b/Projects/Checkers/Data/KnowledgeBase.cs index f79f929b..b4848cfb 100644 --- a/Projects/Checkers/Data/KnowledgeBase.cs +++ b/Projects/Checkers/Data/KnowledgeBase.cs @@ -5,46 +5,45 @@ ///
public static class KnowledgeBase { - public static List GetStartingPosition() - { - var retVal = new List - { - new() { NotationPosition ="A3", Side = PieceColour.Black}, - new() { NotationPosition ="A1", Side = PieceColour.Black}, - new() { NotationPosition ="B2", Side = PieceColour.Black}, - new() { NotationPosition ="C3", Side = PieceColour.Black}, - new() { NotationPosition ="C1", Side = PieceColour.Black}, - new() { NotationPosition ="D2", Side = PieceColour.Black}, - new() { NotationPosition ="E3", Side = PieceColour.Black}, - new() { NotationPosition ="E1", Side = PieceColour.Black}, - new() { NotationPosition ="F2", Side = PieceColour.Black}, - new() { NotationPosition ="G3", Side = PieceColour.Black}, - new() { NotationPosition ="G1", Side = PieceColour.Black}, - new() { NotationPosition ="H2", Side = PieceColour.Black}, + public static List GetStartingPosition() + { + var retVal = new List + { + new() { NotationPosition ="A3", Side = PieceColour.Black}, + new() { NotationPosition ="A1", Side = PieceColour.Black}, + new() { NotationPosition ="B2", Side = PieceColour.Black}, + new() { NotationPosition ="C3", Side = PieceColour.Black}, + new() { NotationPosition ="C1", Side = PieceColour.Black}, + new() { NotationPosition ="D2", Side = PieceColour.Black}, + new() { NotationPosition ="E3", Side = PieceColour.Black}, + new() { NotationPosition ="E1", Side = PieceColour.Black}, + new() { NotationPosition ="F2", Side = PieceColour.Black}, + new() { NotationPosition ="G3", Side = PieceColour.Black}, + new() { NotationPosition ="G1", Side = PieceColour.Black}, + new() { NotationPosition ="H2", Side = PieceColour.Black}, - new() { NotationPosition ="A7", Side = PieceColour.White}, - new() { NotationPosition ="B8", Side = PieceColour.White}, - new() { NotationPosition ="B6", Side = PieceColour.White}, - new() { NotationPosition ="C7", Side = PieceColour.White}, - new() { NotationPosition ="D8", Side = PieceColour.White}, - new() { NotationPosition ="D6", Side = PieceColour.White}, - new() { NotationPosition ="E7", Side = PieceColour.White}, - new() { NotationPosition ="F8", Side = PieceColour.White}, - new() { NotationPosition ="F6", Side = PieceColour.White}, - new() { NotationPosition ="G7", Side = PieceColour.White}, - new() { NotationPosition ="H8", Side = PieceColour.White}, - new() { NotationPosition ="H6", Side = PieceColour.White} - }; + new() { NotationPosition ="A7", Side = PieceColour.White}, + new() { NotationPosition ="B8", Side = PieceColour.White}, + new() { NotationPosition ="B6", Side = PieceColour.White}, + new() { NotationPosition ="C7", Side = PieceColour.White}, + new() { NotationPosition ="D8", Side = PieceColour.White}, + new() { NotationPosition ="D6", Side = PieceColour.White}, + new() { NotationPosition ="E7", Side = PieceColour.White}, + new() { NotationPosition ="F8", Side = PieceColour.White}, + new() { NotationPosition ="F6", Side = PieceColour.White}, + new() { NotationPosition ="G7", Side = PieceColour.White}, + new() { NotationPosition ="H8", Side = PieceColour.White}, + new() { NotationPosition ="H6", Side = PieceColour.White} + }; - return retVal; - } + return retVal; + } - public static List GetLimitedStartingPosition() => new() - { - new() { NotationPosition = "H2", Side = PieceColour.Black}, - new() { NotationPosition = "A1", Side = PieceColour.Black}, - new() { NotationPosition = "G3", Side = PieceColour.White}, - new() { NotationPosition = "E5", Side = PieceColour.White} - }; + public static List GetLimitedStartingPosition() => new() + { + new() { NotationPosition = "H2", Side = PieceColour.Black}, + new() { NotationPosition = "A1", Side = PieceColour.Black}, + new() { NotationPosition = "G3", Side = PieceColour.White}, + new() { NotationPosition = "E5", Side = PieceColour.White} + }; } - diff --git a/Projects/Checkers/Display.cs b/Projects/Checkers/Display.cs index 5308523d..67f056c7 100644 --- a/Projects/Checkers/Display.cs +++ b/Projects/Checkers/Display.cs @@ -2,62 +2,61 @@ public static class Display { - private const char BlackPiece = '○'; - private const char BlackKing = '☺'; - private const char WhitePiece = '◙'; - private const char WhiteKing = '☻'; - - public static void DisplayBoard(Board currentBoard, int whitesTaken, int blacksTaken) - { - Console.Clear(); - Dictionary<(int X, int Y), char> tiles = new(); - foreach (Piece piece in currentBoard.Pieces) - { - if (piece.InPlay) - { - tiles[(piece.XPosition, piece.YPosition)] = ToChar(piece); - } - } - char C(int x, int y) => tiles.GetValueOrDefault((x, y), '.'); - Console.Write( - $" ╔═══════════════════╗\n" + - $" 8 ║ {C(0, 0)} {C(1, 0)} {C(2, 0)} {C(3, 0)} {C(4, 0)} {C(5, 0)} {C(6, 0)} {C(7, 0)} ║ {BlackPiece} = Black\n" + - $" 7 ║ {C(0, 1)} {C(1, 1)} {C(2, 1)} {C(3, 1)} {C(4, 1)} {C(5, 1)} {C(6, 1)} {C(7, 1)} ║ {BlackKing} = Black King\n" + - $" 6 ║ {C(0, 2)} {C(1, 2)} {C(2, 2)} {C(3, 2)} {C(4, 2)} {C(5, 2)} {C(6, 2)} {C(7, 2)} ║ {WhitePiece} = White\n" + - $" 5 ║ {C(0, 3)} {C(1, 3)} {C(2, 3)} {C(3, 3)} {C(4, 3)} {C(5, 3)} {C(6, 3)} {C(7, 3)} ║ {WhiteKing} = White King\n" + - $" 4 ║ {C(0, 4)} {C(1, 4)} {C(2, 4)} {C(3, 4)} {C(4, 4)} {C(5, 4)} {C(6, 4)} {C(7, 4)} ║\n" + - $" 3 ║ {C(0, 5)} {C(1, 5)} {C(2, 5)} {C(3, 5)} {C(4, 5)} {C(5, 5)} {C(6, 5)} {C(7, 5)} ║ Taken:\n" + - $" 2 ║ {C(0, 6)} {C(1, 6)} {C(2, 6)} {C(3, 6)} {C(4, 6)} {C(5, 6)} {C(6, 6)} {C(7, 6)} ║ {whitesTaken,2} x {WhitePiece}\n" + - $" 1 ║ {C(0, 7)} {C(1, 7)} {C(2, 7)} {C(3, 7)} {C(4, 7)} {C(5, 7)} {C(6, 7)} {C(7, 7)} ║ {blacksTaken,2} x {BlackPiece}\n" + - $" ╚═══════════════════╝\n" + - $" A B C D E F G H"); - - Console.CursorVisible = false; - } - - public static void DisplayCurrentPlayer(PieceColour currentSide) - { - Console.SetCursorPosition(0, 11); - Console.WriteLine($"{currentSide} to play"); - } - - public static void DisplayWinner(PieceColour winningSide) - { - Console.SetCursorPosition(0, 11); - Console.WriteLine($"*** {winningSide} wins ***"); - } - - private static char ToChar(Piece piece) => - (piece.Side, piece.Promoted) switch - { - (PieceColour.Black, false) => BlackPiece, - (PieceColour.Black, true) => BlackKing, - (PieceColour.White, false) => WhitePiece, - (PieceColour.White, true) => WhiteKing, - _ => throw new NotImplementedException(), - }; - - public static (int X, int Y) GetScreenPositionFromBoardPosition((int X, int Y) boardPosition) => - ((boardPosition.X * 2) + 7, boardPosition.Y + 1); + private const char BlackPiece = '○'; + private const char BlackKing = '☺'; + private const char WhitePiece = '◙'; + private const char WhiteKing = '☻'; + + public static void DisplayBoard(Board currentBoard, int whitesTaken, int blacksTaken) + { + Console.Clear(); + Dictionary<(int X, int Y), char> tiles = new(); + foreach (Piece piece in currentBoard.Pieces) + { + if (piece.InPlay) + { + tiles[(piece.XPosition, piece.YPosition)] = ToChar(piece); + } + } + char C(int x, int y) => tiles.GetValueOrDefault((x, y), '.'); + Console.Write( + $" ╔═══════════════════╗\n" + + $" 8 ║ {C(0, 0)} {C(1, 0)} {C(2, 0)} {C(3, 0)} {C(4, 0)} {C(5, 0)} {C(6, 0)} {C(7, 0)} ║ {BlackPiece} = Black\n" + + $" 7 ║ {C(0, 1)} {C(1, 1)} {C(2, 1)} {C(3, 1)} {C(4, 1)} {C(5, 1)} {C(6, 1)} {C(7, 1)} ║ {BlackKing} = Black King\n" + + $" 6 ║ {C(0, 2)} {C(1, 2)} {C(2, 2)} {C(3, 2)} {C(4, 2)} {C(5, 2)} {C(6, 2)} {C(7, 2)} ║ {WhitePiece} = White\n" + + $" 5 ║ {C(0, 3)} {C(1, 3)} {C(2, 3)} {C(3, 3)} {C(4, 3)} {C(5, 3)} {C(6, 3)} {C(7, 3)} ║ {WhiteKing} = White King\n" + + $" 4 ║ {C(0, 4)} {C(1, 4)} {C(2, 4)} {C(3, 4)} {C(4, 4)} {C(5, 4)} {C(6, 4)} {C(7, 4)} ║\n" + + $" 3 ║ {C(0, 5)} {C(1, 5)} {C(2, 5)} {C(3, 5)} {C(4, 5)} {C(5, 5)} {C(6, 5)} {C(7, 5)} ║ Taken:\n" + + $" 2 ║ {C(0, 6)} {C(1, 6)} {C(2, 6)} {C(3, 6)} {C(4, 6)} {C(5, 6)} {C(6, 6)} {C(7, 6)} ║ {whitesTaken,2} x {WhitePiece}\n" + + $" 1 ║ {C(0, 7)} {C(1, 7)} {C(2, 7)} {C(3, 7)} {C(4, 7)} {C(5, 7)} {C(6, 7)} {C(7, 7)} ║ {blacksTaken,2} x {BlackPiece}\n" + + $" ╚═══════════════════╝\n" + + $" A B C D E F G H"); + + Console.CursorVisible = false; + } + + public static void DisplayCurrentPlayer(PieceColour currentSide) + { + Console.SetCursorPosition(0, 11); + Console.WriteLine($"{currentSide} to play"); + } + + public static void DisplayWinner(PieceColour winningSide) + { + Console.SetCursorPosition(0, 11); + Console.WriteLine($"*** {winningSide} wins ***"); + } + + private static char ToChar(Piece piece) => + (piece.Side, piece.Promoted) switch + { + (PieceColour.Black, false) => BlackPiece, + (PieceColour.Black, true) => BlackKing, + (PieceColour.White, false) => WhitePiece, + (PieceColour.White, true) => WhiteKing, + _ => throw new NotImplementedException(), + }; + + public static (int X, int Y) GetScreenPositionFromBoardPosition((int X, int Y) boardPosition) => + ((boardPosition.X * 2) + 7, boardPosition.Y + 1); } - diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index 94a597fc..90226bc0 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -2,477 +2,476 @@ public static class Engine { - public static MoveOutcome PlayNextMove(PieceColour currentSide, Board currentBoard, (int X, int Y)? playerFrom = null, (int X, int Y)? playerTo = null) - { - List possibleMoves; - MoveOutcome outcome; - - if (playerFrom != null && playerTo != null) - { - outcome = GetAllPossiblePlayerMoves(currentSide, currentBoard, out possibleMoves); - - if (possibleMoves.Count == 0) - { - outcome = MoveOutcome.NoMoveAvailable; - } - else - { - if (MoveIsValid(playerFrom, playerTo.Value, possibleMoves, currentBoard, out var selectedMove)) - { - possibleMoves.Clear(); - - if (selectedMove != null) - { - possibleMoves.Add(selectedMove); - - switch (selectedMove.TypeOfMove) - { - case MoveType.Unknown: - break; - case MoveType.StandardMove: - outcome = MoveOutcome.ValidMoves; - break; - case MoveType.Capture: - outcome = MoveOutcome.Capture; - break; - case MoveType.EndGame: - outcome = MoveOutcome.EndGame; - break; - default: - throw new NotImplementedException(); - } - } - } - } - } - else - { - outcome = AnalysePosition(currentSide, currentBoard, out possibleMoves); - } - - // If a side can't play then other side wins - if (outcome == MoveOutcome.NoMoveAvailable) - { - outcome = currentSide == PieceColour.Black ? MoveOutcome.WhiteWin : MoveOutcome.BlackWin; - } - - switch (outcome) - { - case MoveOutcome.EndGame: - case MoveOutcome.ValidMoves: - { - var bestMove = possibleMoves[Random.Shared.Next(possibleMoves.Count)]; - - if (bestMove == null) - { - throw new ArgumentNullException(nameof(bestMove)); - } - - var pieceToMove = bestMove.PieceToMove; - - if (pieceToMove == null) - { - throw new ArgumentNullException(nameof(pieceToMove)); - } - - int newX = bestMove.To.X; - int newY = bestMove.To.Y; - - var from = PositionHelper.ToPositionNotationString(pieceToMove.XPosition, pieceToMove.YPosition); - - pieceToMove.XPosition = newX; - pieceToMove.YPosition = newY; - - var to = PositionHelper.ToPositionNotationString(pieceToMove.XPosition, pieceToMove.YPosition); - - int blackPieces = currentBoard.GetNumberOfBlackPiecesInPlay(); - int whitePieces = currentBoard.GetNumberOfWhitePiecesInPlay(); - - // Promotion can only happen if not already a king and you have reached the far side - if (newY is 0 or 7 && pieceToMove.Promoted == false) - { - pieceToMove.Promoted = true; - - LoggingHelper.LogMove(from, to, PlayerAction.Promotion, currentSide, blackPieces, whitePieces); - } - else - { - LoggingHelper.LogMove(from, to, PlayerAction.Move, currentSide, blackPieces, whitePieces); - } - - break; - } - case MoveOutcome.Capture: - { - var anyPromoted = PerformCapture(currentSide, possibleMoves, currentBoard); - var moreAvailable = MoreCapturesAvailable(currentSide, currentBoard); - - if (moreAvailable && !anyPromoted) - { - outcome = MoveOutcome.CaptureMoreAvailable; - } - - break; - } - } - - if (outcome != MoveOutcome.CaptureMoreAvailable) - { - ResetCapturePiece(currentBoard); - } - - return outcome; - } - - private static void ResetCapturePiece(Board currentBoard) - { - var capturePiece = GetAggressor(currentBoard); - - if (capturePiece != null) - { - capturePiece.Aggressor = false; - } - } - - private static bool MoreCapturesAvailable(PieceColour currentSide, Board currentBoard) - { - var aggressor = GetAggressor(currentBoard); - - if (aggressor == null) - { - return false; - } - - _ = GetAllPossiblePlayerMoves(currentSide, currentBoard, out var possibleMoves); - - return possibleMoves.Any(move => move.PieceToMove == aggressor && move.Capturing != null); - } - - private static Piece? GetAggressor(Board currentBoard) - { - return currentBoard.Pieces.FirstOrDefault(x => x.Aggressor); - } + public static MoveOutcome PlayNextMove(PieceColour currentSide, Board currentBoard, (int X, int Y)? playerFrom = null, (int X, int Y)? playerTo = null) + { + List possibleMoves; + MoveOutcome outcome; + + if (playerFrom != null && playerTo != null) + { + outcome = GetAllPossiblePlayerMoves(currentSide, currentBoard, out possibleMoves); + + if (possibleMoves.Count == 0) + { + outcome = MoveOutcome.NoMoveAvailable; + } + else + { + if (MoveIsValid(playerFrom, playerTo.Value, possibleMoves, currentBoard, out var selectedMove)) + { + possibleMoves.Clear(); + + if (selectedMove != null) + { + possibleMoves.Add(selectedMove); + + switch (selectedMove.TypeOfMove) + { + case MoveType.Unknown: + break; + case MoveType.StandardMove: + outcome = MoveOutcome.ValidMoves; + break; + case MoveType.Capture: + outcome = MoveOutcome.Capture; + break; + case MoveType.EndGame: + outcome = MoveOutcome.EndGame; + break; + default: + throw new NotImplementedException(); + } + } + } + } + } + else + { + outcome = AnalysePosition(currentSide, currentBoard, out possibleMoves); + } + + // If a side can't play then other side wins + if (outcome == MoveOutcome.NoMoveAvailable) + { + outcome = currentSide == PieceColour.Black ? MoveOutcome.WhiteWin : MoveOutcome.BlackWin; + } + + switch (outcome) + { + case MoveOutcome.EndGame: + case MoveOutcome.ValidMoves: + { + var bestMove = possibleMoves[Random.Shared.Next(possibleMoves.Count)]; + + if (bestMove == null) + { + throw new ArgumentNullException(nameof(bestMove)); + } + + var pieceToMove = bestMove.PieceToMove; + + if (pieceToMove == null) + { + throw new ArgumentNullException(nameof(pieceToMove)); + } + + int newX = bestMove.To.X; + int newY = bestMove.To.Y; + + var from = PositionHelper.ToPositionNotationString(pieceToMove.XPosition, pieceToMove.YPosition); + + pieceToMove.XPosition = newX; + pieceToMove.YPosition = newY; + + var to = PositionHelper.ToPositionNotationString(pieceToMove.XPosition, pieceToMove.YPosition); + + int blackPieces = currentBoard.GetNumberOfBlackPiecesInPlay(); + int whitePieces = currentBoard.GetNumberOfWhitePiecesInPlay(); + + // Promotion can only happen if not already a king and you have reached the far side + if (newY is 0 or 7 && pieceToMove.Promoted == false) + { + pieceToMove.Promoted = true; + + LoggingHelper.LogMove(from, to, PlayerAction.Promotion, currentSide, blackPieces, whitePieces); + } + else + { + LoggingHelper.LogMove(from, to, PlayerAction.Move, currentSide, blackPieces, whitePieces); + } + + break; + } + case MoveOutcome.Capture: + { + var anyPromoted = PerformCapture(currentSide, possibleMoves, currentBoard); + var moreAvailable = MoreCapturesAvailable(currentSide, currentBoard); + + if (moreAvailable && !anyPromoted) + { + outcome = MoveOutcome.CaptureMoreAvailable; + } + + break; + } + } + + if (outcome != MoveOutcome.CaptureMoreAvailable) + { + ResetCapturePiece(currentBoard); + } + + return outcome; + } + + private static void ResetCapturePiece(Board currentBoard) + { + var capturePiece = GetAggressor(currentBoard); + + if (capturePiece != null) + { + capturePiece.Aggressor = false; + } + } + + private static bool MoreCapturesAvailable(PieceColour currentSide, Board currentBoard) + { + var aggressor = GetAggressor(currentBoard); + + if (aggressor == null) + { + return false; + } + + _ = GetAllPossiblePlayerMoves(currentSide, currentBoard, out var possibleMoves); + + return possibleMoves.Any(move => move.PieceToMove == aggressor && move.Capturing != null); + } + + private static Piece? GetAggressor(Board currentBoard) + { + return currentBoard.Pieces.FirstOrDefault(x => x.Aggressor); + } - private static bool MoveIsValid((int X, int Y)? from, (int X, int Y) to, List possibleMoves, Board currentBoard, out Move? selectedMove) - { - selectedMove = default; + private static bool MoveIsValid((int X, int Y)? from, (int X, int Y) to, List possibleMoves, Board currentBoard, out Move? selectedMove) + { + selectedMove = default; - if (from == null) - { - return false; - } + if (from == null) + { + return false; + } - var selectedPiece = currentBoard.GetPieceAt(from.Value.X, from.Value.Y); + var selectedPiece = currentBoard.GetPieceAt(from.Value.X, from.Value.Y); - foreach (var move in possibleMoves.Where(move => move.PieceToMove == selectedPiece && move.To == to)) - { - selectedMove = move; + foreach (var move in possibleMoves.Where(move => move.PieceToMove == selectedPiece && move.To == to)) + { + selectedMove = move; - return true; - } + return true; + } - return false; - } + return false; + } - private static MoveOutcome GetAllPossiblePlayerMoves(PieceColour currentSide, Board currentBoard, - out List possibleMoves) - { - var aggressor = GetAggressor(currentBoard); + private static MoveOutcome GetAllPossiblePlayerMoves(PieceColour currentSide, Board currentBoard, + out List possibleMoves) + { + var aggressor = GetAggressor(currentBoard); - var result = MoveOutcome.Unknown; + var result = MoveOutcome.Unknown; - possibleMoves = new List(); + possibleMoves = new List(); - if (PlayingWithJustKings(currentSide, currentBoard, out var endGameMoves)) - { - result = MoveOutcome.EndGame; - possibleMoves.AddRange(endGameMoves); - } - - GetPossibleMovesAndAttacks(currentSide, currentBoard, out var nonEndGameMoves); - - possibleMoves.AddRange(nonEndGameMoves); + if (PlayingWithJustKings(currentSide, currentBoard, out var endGameMoves)) + { + result = MoveOutcome.EndGame; + possibleMoves.AddRange(endGameMoves); + } + + GetPossibleMovesAndAttacks(currentSide, currentBoard, out var nonEndGameMoves); + + possibleMoves.AddRange(nonEndGameMoves); - if (aggressor != null) - { - var tempMoves = possibleMoves.Where(x => x.PieceToMove == aggressor).ToList(); - possibleMoves = tempMoves; - } + if (aggressor != null) + { + var tempMoves = possibleMoves.Where(x => x.PieceToMove == aggressor).ToList(); + possibleMoves = tempMoves; + } - return result; - } + return result; + } - private static bool PerformCapture(PieceColour currentSide, List possibleCaptures, Board currentBoard) - { - var captureMove = possibleCaptures.FirstOrDefault(x => x.Capturing != null); - var from = string.Empty; - var to = string.Empty; + private static bool PerformCapture(PieceColour currentSide, List possibleCaptures, Board currentBoard) + { + var captureMove = possibleCaptures.FirstOrDefault(x => x.Capturing != null); + var from = string.Empty; + var to = string.Empty; - if (captureMove != null) - { - var squareToCapture = captureMove.Capturing; + if (captureMove != null) + { + var squareToCapture = captureMove.Capturing; - if (squareToCapture != null) - { - var deadMan = + if (squareToCapture != null) + { + var deadMan = currentBoard.GetPieceAt(squareToCapture.Value.X, squareToCapture.Value.Y); - if (deadMan != null) - { - deadMan.InPlay = false; - } - - if (captureMove.PieceToMove != null) - { - captureMove.PieceToMove.Aggressor = true; - from = PositionHelper.ToPositionNotationString(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); - - captureMove.PieceToMove.XPosition = captureMove.To.X; - captureMove.PieceToMove.YPosition = captureMove.To.Y; - - to = PositionHelper.ToPositionNotationString(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); - - } - } - } - - bool anyPromoted = CheckForPiecesToPromote(currentSide, currentBoard); - int blackPieces = currentBoard.GetNumberOfBlackPiecesInPlay(); - int whitePieces = currentBoard.GetNumberOfWhitePiecesInPlay(); - - var playerAction = anyPromoted ? PlayerAction.CapturePromotion : PlayerAction.Capture; - - LoggingHelper.LogMove(from, to, playerAction, currentSide, blackPieces, whitePieces); - - return anyPromoted; - } - - private static bool CheckForPiecesToPromote(PieceColour currentSide, Board currentBoard) - { - bool retVal = false; - int promotionSpot = currentSide == PieceColour.White ? 7 : 0; - - foreach (var piece in currentBoard.Pieces.Where(x => x.Side == currentSide)) - { - if (promotionSpot == piece.YPosition && !piece.Promoted) - { - piece.Promoted = retVal = true; - } - } - - return retVal; - } - - private static MoveOutcome AnalysePosition(PieceColour currentSide, Board currentBoard, - out List possibleMoves) - { - MoveOutcome result; - - possibleMoves = new List(); - - // Check for endgame first - if (PlayingWithJustKings(currentSide, currentBoard, out var endGameMoves)) - { - result = MoveOutcome.EndGame; - possibleMoves.AddRange(endGameMoves); - - return result; - } - - GetPossibleMovesAndAttacks(currentSide, currentBoard, out var nonEndGameMoves); - - if (nonEndGameMoves.Count == 0) - { - result = MoveOutcome.NoMoveAvailable; - } - else - { - if (nonEndGameMoves.Any(x => x.Capturing != null)) - { - result = MoveOutcome.Capture; - } - else - { - result = nonEndGameMoves.Count > 0 ? MoveOutcome.ValidMoves : MoveOutcome.NoMoveAvailable; - } - } - - if (nonEndGameMoves.Count > 0) - { - possibleMoves.AddRange(nonEndGameMoves); - } - - return result; - } - - private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board currentBoard, - out List possibleMoves) - { - possibleMoves = new List(); - - foreach (var piece in currentBoard.Pieces.Where(c => c.Side == currentSide && c.InPlay)) - { - for (var x = -1; x < 2; x++) - { - for (var y = -1; y < 2; y++) - { - if (x == 0 || y == 0) - { - continue; - } - - if (!piece.Promoted) - { - switch (currentSide) - { - case PieceColour.White when y == -1: - case PieceColour.Black when y == 1: - continue; - } - } - - var currentX = piece.XPosition + x; - var currentY = piece.YPosition + y; - - if (!PositionHelper.PointValid(currentX, currentY)) - { - continue; - } - - var targetSquare = currentBoard.GetSquareOccupancy(currentX, currentY); - - if (targetSquare == PieceColour.NotSet) - { - if (!PositionHelper.PointValid(currentX, currentY)) - { - continue; - } - - var newMove = new Move { PieceToMove = piece, TypeOfMove = MoveType.StandardMove, To = (currentX, currentY) }; - possibleMoves.Add(newMove); - } - else - { - var haveTarget = targetSquare != currentSide; - - if (!haveTarget) - { - continue; - } - - var toLocation = DeriveToPosition(piece.XPosition, piece.YPosition, currentX, currentY); - - var beyondX = toLocation.X; - var beyondY = toLocation.Y; - - if (!PositionHelper.PointValid(beyondX, beyondY)) - { - continue; - } - - var beyondSquare = currentBoard.GetSquareOccupancy(beyondX, beyondY); - - if (beyondSquare != PieceColour.NotSet) - { - continue; - } - - var attack = new Move { PieceToMove = piece, TypeOfMove = MoveType.Capture, To = (beyondX, beyondY), Capturing = (currentX, currentY) }; - possibleMoves.Add(attack); - } - } - } - } - } - - private static (int X, int Y) DeriveToPosition(int pieceX, int pieceY, int captureX, int captureY) - { - int newX; - if (captureX > pieceX) - { - newX = captureX + 1; - } - else - { - newX = captureX - 1; - } - - int newY; - if (captureY > pieceY) - { - newY = captureY + 1; - } - else - { - newY = captureY - 1; - } - - return (newX, newY); - } - - private static bool PlayingWithJustKings(PieceColour currentSide, Board currentBoard, out List possibleMoves) - { - possibleMoves = new List(); - var piecesInPlay = currentBoard.Pieces.Count(x => x.Side == currentSide && x.InPlay); - var kingsInPlay = currentBoard.Pieces.Count(x => x.Side == currentSide && x.InPlay && x.Promoted); - - var playingWithJustKings = piecesInPlay == kingsInPlay; - - if (playingWithJustKings) - { - var shortestDistance = 12.0; - - Piece? currentHero = null; - Piece? currentVillain = null; - - foreach (var king in currentBoard.Pieces.Where(x => x.Side == currentSide && x.InPlay)) - { - foreach (var target in currentBoard.Pieces.Where(x => x.Side != currentSide && x.InPlay)) - { - var kingPoint = (king.XPosition, king.YPosition); - var targetPoint = (target.XPosition, target.YPosition); - var distance = VectorHelper.GetPointDistance(kingPoint, targetPoint); - - if (distance < shortestDistance) - { - shortestDistance = distance; - currentHero = king; - currentVillain = target; - } - } - } - - if (currentHero != null && currentVillain != null) - { - var movementOptions = VectorHelper.WhereIsVillain(currentHero, currentVillain); - - foreach (var movementOption in movementOptions) - { - var squareStatus = currentBoard.GetSquareOccupancy(movementOption.X, movementOption.Y); - - if (squareStatus == PieceColour.NotSet) - { - var theMove = new Move - { - PieceToMove = currentHero, - TypeOfMove = MoveType.EndGame, - To = (movementOption.X, movementOption.Y) - }; - - if (!PositionHelper.PointValid(movementOption.X, movementOption.Y)) - { - continue; - } - - possibleMoves.Add(theMove); - - break; - } - } - } - } - - return possibleMoves.Count > 0; - } + if (deadMan != null) + { + deadMan.InPlay = false; + } + + if (captureMove.PieceToMove != null) + { + captureMove.PieceToMove.Aggressor = true; + from = PositionHelper.ToPositionNotationString(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); + + captureMove.PieceToMove.XPosition = captureMove.To.X; + captureMove.PieceToMove.YPosition = captureMove.To.Y; + + to = PositionHelper.ToPositionNotationString(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); + + } + } + } + + bool anyPromoted = CheckForPiecesToPromote(currentSide, currentBoard); + int blackPieces = currentBoard.GetNumberOfBlackPiecesInPlay(); + int whitePieces = currentBoard.GetNumberOfWhitePiecesInPlay(); + + var playerAction = anyPromoted ? PlayerAction.CapturePromotion : PlayerAction.Capture; + + LoggingHelper.LogMove(from, to, playerAction, currentSide, blackPieces, whitePieces); + + return anyPromoted; + } + + private static bool CheckForPiecesToPromote(PieceColour currentSide, Board currentBoard) + { + bool retVal = false; + int promotionSpot = currentSide == PieceColour.White ? 7 : 0; + + foreach (var piece in currentBoard.Pieces.Where(x => x.Side == currentSide)) + { + if (promotionSpot == piece.YPosition && !piece.Promoted) + { + piece.Promoted = retVal = true; + } + } + + return retVal; + } + + private static MoveOutcome AnalysePosition(PieceColour currentSide, Board currentBoard, + out List possibleMoves) + { + MoveOutcome result; + + possibleMoves = new List(); + + // Check for endgame first + if (PlayingWithJustKings(currentSide, currentBoard, out var endGameMoves)) + { + result = MoveOutcome.EndGame; + possibleMoves.AddRange(endGameMoves); + + return result; + } + + GetPossibleMovesAndAttacks(currentSide, currentBoard, out var nonEndGameMoves); + + if (nonEndGameMoves.Count == 0) + { + result = MoveOutcome.NoMoveAvailable; + } + else + { + if (nonEndGameMoves.Any(x => x.Capturing != null)) + { + result = MoveOutcome.Capture; + } + else + { + result = nonEndGameMoves.Count > 0 ? MoveOutcome.ValidMoves : MoveOutcome.NoMoveAvailable; + } + } + + if (nonEndGameMoves.Count > 0) + { + possibleMoves.AddRange(nonEndGameMoves); + } + + return result; + } + + private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board currentBoard, + out List possibleMoves) + { + possibleMoves = new List(); + + foreach (var piece in currentBoard.Pieces.Where(c => c.Side == currentSide && c.InPlay)) + { + for (var x = -1; x < 2; x++) + { + for (var y = -1; y < 2; y++) + { + if (x == 0 || y == 0) + { + continue; + } + + if (!piece.Promoted) + { + switch (currentSide) + { + case PieceColour.White when y == -1: + case PieceColour.Black when y == 1: + continue; + } + } + + var currentX = piece.XPosition + x; + var currentY = piece.YPosition + y; + + if (!PositionHelper.PointValid(currentX, currentY)) + { + continue; + } + + var targetSquare = currentBoard.GetSquareOccupancy(currentX, currentY); + + if (targetSquare == PieceColour.NotSet) + { + if (!PositionHelper.PointValid(currentX, currentY)) + { + continue; + } + + var newMove = new Move { PieceToMove = piece, TypeOfMove = MoveType.StandardMove, To = (currentX, currentY) }; + possibleMoves.Add(newMove); + } + else + { + var haveTarget = targetSquare != currentSide; + + if (!haveTarget) + { + continue; + } + + var toLocation = DeriveToPosition(piece.XPosition, piece.YPosition, currentX, currentY); + + var beyondX = toLocation.X; + var beyondY = toLocation.Y; + + if (!PositionHelper.PointValid(beyondX, beyondY)) + { + continue; + } + + var beyondSquare = currentBoard.GetSquareOccupancy(beyondX, beyondY); + + if (beyondSquare != PieceColour.NotSet) + { + continue; + } + + var attack = new Move { PieceToMove = piece, TypeOfMove = MoveType.Capture, To = (beyondX, beyondY), Capturing = (currentX, currentY) }; + possibleMoves.Add(attack); + } + } + } + } + } + + private static (int X, int Y) DeriveToPosition(int pieceX, int pieceY, int captureX, int captureY) + { + int newX; + if (captureX > pieceX) + { + newX = captureX + 1; + } + else + { + newX = captureX - 1; + } + + int newY; + if (captureY > pieceY) + { + newY = captureY + 1; + } + else + { + newY = captureY - 1; + } + + return (newX, newY); + } + + private static bool PlayingWithJustKings(PieceColour currentSide, Board currentBoard, out List possibleMoves) + { + possibleMoves = new List(); + var piecesInPlay = currentBoard.Pieces.Count(x => x.Side == currentSide && x.InPlay); + var kingsInPlay = currentBoard.Pieces.Count(x => x.Side == currentSide && x.InPlay && x.Promoted); + + var playingWithJustKings = piecesInPlay == kingsInPlay; + + if (playingWithJustKings) + { + var shortestDistance = 12.0; + + Piece? currentHero = null; + Piece? currentVillain = null; + + foreach (var king in currentBoard.Pieces.Where(x => x.Side == currentSide && x.InPlay)) + { + foreach (var target in currentBoard.Pieces.Where(x => x.Side != currentSide && x.InPlay)) + { + var kingPoint = (king.XPosition, king.YPosition); + var targetPoint = (target.XPosition, target.YPosition); + var distance = VectorHelper.GetPointDistance(kingPoint, targetPoint); + + if (distance < shortestDistance) + { + shortestDistance = distance; + currentHero = king; + currentVillain = target; + } + } + } + + if (currentHero != null && currentVillain != null) + { + var movementOptions = VectorHelper.WhereIsVillain(currentHero, currentVillain); + + foreach (var movementOption in movementOptions) + { + var squareStatus = currentBoard.GetSquareOccupancy(movementOption.X, movementOption.Y); + + if (squareStatus == PieceColour.NotSet) + { + var theMove = new Move + { + PieceToMove = currentHero, + TypeOfMove = MoveType.EndGame, + To = (movementOption.X, movementOption.Y) + }; + + if (!PositionHelper.PointValid(movementOption.X, movementOption.Y)) + { + continue; + } + + possibleMoves.Add(theMove); + + break; + } + } + } + } + + return possibleMoves.Count > 0; + } } - diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index 14340a27..042f8c54 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -4,111 +4,110 @@ namespace Checkers; public class Game { - private const int PiecesPerSide = 12; - - public PieceColour CurrentGo { get; set; } - public Board GameBoard { get; } - public int MovesSoFar { get; private set; } - public PieceColour GameWinner { get; set; } = PieceColour.NotSet; - - private bool NoDisplay { get; } - - public List Players { get; set; } = new() - { - new Player { ControlledBy = PlayerControl.Computer, Side = PieceColour.Black }, - new Player { ControlledBy = PlayerControl.Computer, Side = PieceColour.White } - }; - - public Game(List startingPosition, PieceColour toMove) - { - GameBoard = new Board(startingPosition); - CurrentGo = toMove; - NoDisplay = true; - } - - public Game() - { - GameBoard = new Board(); - CurrentGo = PieceColour.Black; - NoDisplay = false; - ShowBoard(); - } - - public MoveOutcome NextRound((int X, int Y)? from = null, (int X, int Y)? to = null) - { - MovesSoFar++; - var res = Engine.PlayNextMove(CurrentGo, GameBoard, from, to); - - while (from == null & to == null && res == MoveOutcome.CaptureMoreAvailable) - { - res = Engine.PlayNextMove(CurrentGo, GameBoard, from, to); - } - - if (res == MoveOutcome.BlackWin) - { - GameWinner = PieceColour.Black; - } - - if (res == MoveOutcome.WhiteWin) - { - GameWinner = PieceColour.White; - } - - if (GameWinner == PieceColour.NotSet && res != MoveOutcome.CaptureMoreAvailable) - { - CheckSidesHavePiecesLeft(); - CurrentGo = CurrentGo == PieceColour.Black ? PieceColour.White : PieceColour.Black; - } - - if (res == MoveOutcome.Unknown) - { - CurrentGo = CurrentGo == PieceColour.Black - ? PieceColour.White - : PieceColour.Black; - } - - ShowBoard(); - - return res; - } - - public void CheckSidesHavePiecesLeft() - { - var retVal = GameBoard.Pieces.Where(x => x.InPlay).Select(y => y.Side).Distinct().Count() == 2; - - if (!retVal) - { - GameWinner = GameBoard.Pieces.Where(x => x.InPlay).Select(y => y.Side).Distinct().FirstOrDefault(); - } - } - - public string GetCurrentPlayer() - { - return CurrentGo.ToString(); - } - - public int GetWhitePiecesTaken() - { - return GetPiecesTakenForSide(PieceColour.White); - } - - public int GetBlackPiecesTaken() - { - return GetPiecesTakenForSide(PieceColour.Black); - } - - public int GetPiecesTakenForSide(PieceColour colour) - { - return PiecesPerSide - GameBoard.Pieces.Count(x => x.InPlay && x.Side == colour); - } - - private void ShowBoard() - { - if (!NoDisplay) - { - Display.DisplayBoard(GameBoard, GetWhitePiecesTaken(), GetBlackPiecesTaken()); - Display.DisplayCurrentPlayer(CurrentGo); - } - } + private const int PiecesPerSide = 12; + + public PieceColour CurrentGo { get; set; } + public Board GameBoard { get; } + public int MovesSoFar { get; private set; } + public PieceColour GameWinner { get; set; } = PieceColour.NotSet; + + private bool NoDisplay { get; } + + public List Players { get; set; } = new() + { + new Player { ControlledBy = PlayerControl.Computer, Side = PieceColour.Black }, + new Player { ControlledBy = PlayerControl.Computer, Side = PieceColour.White } + }; + + public Game(List startingPosition, PieceColour toMove) + { + GameBoard = new Board(startingPosition); + CurrentGo = toMove; + NoDisplay = true; + } + + public Game() + { + GameBoard = new Board(); + CurrentGo = PieceColour.Black; + NoDisplay = false; + ShowBoard(); + } + + public MoveOutcome NextRound((int X, int Y)? from = null, (int X, int Y)? to = null) + { + MovesSoFar++; + var res = Engine.PlayNextMove(CurrentGo, GameBoard, from, to); + + while (from == null & to == null && res == MoveOutcome.CaptureMoreAvailable) + { + res = Engine.PlayNextMove(CurrentGo, GameBoard, from, to); + } + + if (res == MoveOutcome.BlackWin) + { + GameWinner = PieceColour.Black; + } + + if (res == MoveOutcome.WhiteWin) + { + GameWinner = PieceColour.White; + } + + if (GameWinner == PieceColour.NotSet && res != MoveOutcome.CaptureMoreAvailable) + { + CheckSidesHavePiecesLeft(); + CurrentGo = CurrentGo == PieceColour.Black ? PieceColour.White : PieceColour.Black; + } + + if (res == MoveOutcome.Unknown) + { + CurrentGo = CurrentGo == PieceColour.Black + ? PieceColour.White + : PieceColour.Black; + } + + ShowBoard(); + + return res; + } + + public void CheckSidesHavePiecesLeft() + { + var retVal = GameBoard.Pieces.Where(x => x.InPlay).Select(y => y.Side).Distinct().Count() == 2; + + if (!retVal) + { + GameWinner = GameBoard.Pieces.Where(x => x.InPlay).Select(y => y.Side).Distinct().FirstOrDefault(); + } + } + + public string GetCurrentPlayer() + { + return CurrentGo.ToString(); + } + + public int GetWhitePiecesTaken() + { + return GetPiecesTakenForSide(PieceColour.White); + } + + public int GetBlackPiecesTaken() + { + return GetPiecesTakenForSide(PieceColour.Black); + } + + public int GetPiecesTakenForSide(PieceColour colour) + { + return PiecesPerSide - GameBoard.Pieces.Count(x => x.InPlay && x.Side == colour); + } + + private void ShowBoard() + { + if (!NoDisplay) + { + Display.DisplayBoard(GameBoard, GetWhitePiecesTaken(), GetBlackPiecesTaken()); + Display.DisplayCurrentPlayer(CurrentGo); + } + } } - diff --git a/Projects/Checkers/Helpers/LoggingHelper.cs b/Projects/Checkers/Helpers/LoggingHelper.cs index 1a8643f6..cedfc7d7 100644 --- a/Projects/Checkers/Helpers/LoggingHelper.cs +++ b/Projects/Checkers/Helpers/LoggingHelper.cs @@ -7,56 +7,56 @@ namespace Checkers.Helpers; ///
public static class LoggingHelper { - public static void LogMove(string from, string to, PlayerAction action, PieceColour sidePlaying, int blacksInPlay, int whitesInPlay) - { - var colour = sidePlaying == PieceColour.Black ? "B" : "W"; - var suffix = DecodePlayerAction(action); - - var outputLine = $"Move : {colour} {from}-{to} {suffix,2}"; - - if (action == PlayerAction.Capture || action == PlayerAction.CapturePromotion) - { - var piecesCount = $" B:{blacksInPlay,2} W:{whitesInPlay,2}"; - outputLine += piecesCount; - } - - Trace.WriteLine(outputLine); - } - - public static void LogMoves(int numberOfMoves) - { - Trace.WriteLine($"Moves : {numberOfMoves}"); - } - - public static void LogStart() - { - Trace.WriteLine($"Started: {DateTime.Now}"); - } - - public static void LogFinish() - { - Trace.WriteLine($"Stopped: {DateTime.Now}"); - } - - public static void LogOutcome(PieceColour winner) - { - Trace.WriteLine($"Winner : {winner}"); - } - - private static string DecodePlayerAction(PlayerAction action) - { - switch (action) - { - case PlayerAction.Move: - return string.Empty; - case PlayerAction.Promotion: - return "K"; - case PlayerAction.Capture: - return "X"; - case PlayerAction.CapturePromotion: - return "KX"; - default: - return String.Empty; - } - } + public static void LogMove(string from, string to, PlayerAction action, PieceColour sidePlaying, int blacksInPlay, int whitesInPlay) + { + var colour = sidePlaying == PieceColour.Black ? "B" : "W"; + var suffix = DecodePlayerAction(action); + + var outputLine = $"Move : {colour} {from}-{to} {suffix,2}"; + + if (action == PlayerAction.Capture || action == PlayerAction.CapturePromotion) + { + var piecesCount = $" B:{blacksInPlay,2} W:{whitesInPlay,2}"; + outputLine += piecesCount; + } + + Trace.WriteLine(outputLine); + } + + public static void LogMoves(int numberOfMoves) + { + Trace.WriteLine($"Moves : {numberOfMoves}"); + } + + public static void LogStart() + { + Trace.WriteLine($"Started: {DateTime.Now}"); + } + + public static void LogFinish() + { + Trace.WriteLine($"Stopped: {DateTime.Now}"); + } + + public static void LogOutcome(PieceColour winner) + { + Trace.WriteLine($"Winner : {winner}"); + } + + private static string DecodePlayerAction(PlayerAction action) + { + switch (action) + { + case PlayerAction.Move: + return string.Empty; + case PlayerAction.Promotion: + return "K"; + case PlayerAction.Capture: + return "X"; + case PlayerAction.CapturePromotion: + return "KX"; + default: + return String.Empty; + } + } } diff --git a/Projects/Checkers/Helpers/PlayerHelper.cs b/Projects/Checkers/Helpers/PlayerHelper.cs index 9f48e7c5..0122b6ad 100644 --- a/Projects/Checkers/Helpers/PlayerHelper.cs +++ b/Projects/Checkers/Helpers/PlayerHelper.cs @@ -6,37 +6,36 @@ /// public static class PlayerHelper { - public static void AssignPlayersToSide(int numberOfPlayers, Game currentGame) - { - switch (numberOfPlayers) - { - case 0: - foreach (var player in currentGame.Players) - { - player.ControlledBy = PlayerControl.Computer; - } + public static void AssignPlayersToSide(int numberOfPlayers, Game currentGame) + { + switch (numberOfPlayers) + { + case 0: + foreach (var player in currentGame.Players) + { + player.ControlledBy = PlayerControl.Computer; + } - break; - case 1: - foreach (var player in currentGame.Players.Where(x => x.Side == PieceColour.White)) - { - player.ControlledBy = PlayerControl.Computer; - } + break; + case 1: + foreach (var player in currentGame.Players.Where(x => x.Side == PieceColour.White)) + { + player.ControlledBy = PlayerControl.Computer; + } - foreach (var player in currentGame.Players.Where(x => x.Side == PieceColour.Black)) - { - player.ControlledBy = PlayerControl.Human; - } + foreach (var player in currentGame.Players.Where(x => x.Side == PieceColour.Black)) + { + player.ControlledBy = PlayerControl.Human; + } - break; - case 2: - foreach (var player in currentGame.Players) - { - player.ControlledBy = PlayerControl.Human; - } + break; + case 2: + foreach (var player in currentGame.Players) + { + player.ControlledBy = PlayerControl.Human; + } - break; - } - } + break; + } + } } - diff --git a/Projects/Checkers/Helpers/PositionHelper.cs b/Projects/Checkers/Helpers/PositionHelper.cs index b5e23231..74d3c4df 100644 --- a/Projects/Checkers/Helpers/PositionHelper.cs +++ b/Projects/Checkers/Helpers/PositionHelper.cs @@ -3,25 +3,24 @@ /// Position routines public static class PositionHelper { - public static string ToPositionNotationString(int x, int y) - { - if (!PointValid(x, y)) throw new ArgumentException("Not a valid position!"); - return $"{(char)('A' + x)}{y + 1}"; - } + public static string ToPositionNotationString(int x, int y) + { + if (!PointValid(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', '8' - notation[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', '8' - notation[1]); + } - public static bool PointValid(int x, int y) => - 0 <= x && x < 8 && - 0 <= y && y < 8; + public static bool PointValid(int x, int y) => + 0 <= x && x < 8 && + 0 <= y && y < 8; } - diff --git a/Projects/Checkers/Helpers/VectorHelper.cs b/Projects/Checkers/Helpers/VectorHelper.cs index 43c82a10..0fc93c5d 100644 --- a/Projects/Checkers/Helpers/VectorHelper.cs +++ b/Projects/Checkers/Helpers/VectorHelper.cs @@ -6,104 +6,103 @@ /// public static class VectorHelper { - public static double GetPointDistance((int X, int Y) first, (int X, int Y) second) - { - // Easiest cases are points on the same vertical or horizontal axis - if (first.X == second.X) - { - return Math.Abs(first.Y - second.Y); - } - - if (first.Y == second.Y) - { - return Math.Abs(first.X - second.X); - } - - // Pythagoras baby - var sideA = (double)Math.Abs(first.Y - second.Y); - var sideB = (double)Math.Abs(first.X - second.X); - - return Math.Sqrt(Math.Pow(sideA, 2) + Math.Pow(sideB, 2)); - } - - public static List<(int X, int Y)> WhereIsVillain(Piece hero, Piece villain) - { - var retVal = new List<(int X, int Y)>(); - - var directions = new List(); - - if (hero.XPosition > villain.XPosition) - { - directions.Add(Direction.Left); - } - - if (hero.XPosition < villain.XPosition) - { - directions.Add(Direction.Right); - } - - if (hero.YPosition > villain.YPosition) - { - directions.Add(Direction.Up); - } - - if (hero.YPosition < villain.YPosition) - { - directions.Add(Direction.Down); - } - - if (directions.Count == 1) - { - switch (directions[0]) - { - case Direction.Up: - retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); - retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); - - break; - case Direction.Down: - retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); - retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); - - break; - case Direction.Left: - retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); - retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); - - break; - case Direction.Right: - retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); - retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); - - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - else - { - if (directions.Contains(Direction.Left) && directions.Contains(Direction.Up)) - { - retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); - } - - if (directions.Contains(Direction.Left) && directions.Contains(Direction.Down)) - { - retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); - } - - if (directions.Contains(Direction.Right) && directions.Contains(Direction.Up)) - { - retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); - } - - if (directions.Contains(Direction.Right) && directions.Contains(Direction.Down)) - { - retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); - } - } - - return retVal; - } + public static double GetPointDistance((int X, int Y) first, (int X, int Y) second) + { + // Easiest cases are points on the same vertical or horizontal axis + if (first.X == second.X) + { + return Math.Abs(first.Y - second.Y); + } + + if (first.Y == second.Y) + { + return Math.Abs(first.X - second.X); + } + + // Pythagoras baby + var sideA = (double)Math.Abs(first.Y - second.Y); + var sideB = (double)Math.Abs(first.X - second.X); + + return Math.Sqrt(Math.Pow(sideA, 2) + Math.Pow(sideB, 2)); + } + + public static List<(int X, int Y)> WhereIsVillain(Piece hero, Piece villain) + { + var retVal = new List<(int X, int Y)>(); + + var directions = new List(); + + if (hero.XPosition > villain.XPosition) + { + directions.Add(Direction.Left); + } + + if (hero.XPosition < villain.XPosition) + { + directions.Add(Direction.Right); + } + + if (hero.YPosition > villain.YPosition) + { + directions.Add(Direction.Up); + } + + if (hero.YPosition < villain.YPosition) + { + directions.Add(Direction.Down); + } + + if (directions.Count == 1) + { + switch (directions[0]) + { + case Direction.Up: + retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); + retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); + + break; + case Direction.Down: + retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); + retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); + + break; + case Direction.Left: + retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); + retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); + + break; + case Direction.Right: + retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); + retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); + + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + else + { + if (directions.Contains(Direction.Left) && directions.Contains(Direction.Up)) + { + retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); + } + + if (directions.Contains(Direction.Left) && directions.Contains(Direction.Down)) + { + retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); + } + + if (directions.Contains(Direction.Right) && directions.Contains(Direction.Up)) + { + retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); + } + + if (directions.Contains(Direction.Right) && directions.Contains(Direction.Down)) + { + retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); + } + } + + return retVal; + } } - diff --git a/Projects/Checkers/Move.cs b/Projects/Checkers/Move.cs index 5b550d54..fc703c4c 100644 --- a/Projects/Checkers/Move.cs +++ b/Projects/Checkers/Move.cs @@ -2,12 +2,11 @@ public class Move { - public Piece? PieceToMove { get; set; } + public Piece? PieceToMove { get; set; } - public (int X, int Y) To { get; set; } + public (int X, int Y) To { get; set; } - public (int X, int Y)? Capturing { get; set; } + public (int X, int Y)? Capturing { get; set; } - public MoveType TypeOfMove { get; set; } = MoveType.Unknown; + public MoveType TypeOfMove { get; set; } = MoveType.Unknown; } - diff --git a/Projects/Checkers/Piece.cs b/Projects/Checkers/Piece.cs index 717fb9be..a0c4312e 100644 --- a/Projects/Checkers/Piece.cs +++ b/Projects/Checkers/Piece.cs @@ -2,28 +2,27 @@ public class Piece { - public Piece() - { - InPlay = true; - Promoted = false; - } + public Piece() + { + InPlay = true; + Promoted = false; + } - public int XPosition { get; set; } + public int XPosition { get; set; } - public int YPosition { get; set; } + public int YPosition { get; set; } - public string NotationPosition - { - get => PositionHelper.ToPositionNotationString(XPosition, YPosition); - set => (XPosition, YPosition) = PositionHelper.ParsePositionNotation(value); - } + public string NotationPosition + { + get => PositionHelper.ToPositionNotationString(XPosition, YPosition); + set => (XPosition, YPosition) = PositionHelper.ParsePositionNotation(value); + } - public PieceColour Side { get; set; } + public PieceColour Side { get; set; } - public bool InPlay { get; set; } + public bool InPlay { get; set; } - public bool Promoted { get; set; } + public bool Promoted { get; set; } - public bool Aggressor { get; set; } + public bool Aggressor { get; set; } } - diff --git a/Projects/Checkers/Player.cs b/Projects/Checkers/Player.cs index d50c70cd..639e56c4 100644 --- a/Projects/Checkers/Player.cs +++ b/Projects/Checkers/Player.cs @@ -2,7 +2,6 @@ public class Player { - public PlayerControl ControlledBy { get; set; } - public PieceColour Side { get; set; } + public PlayerControl ControlledBy { get; set; } + public PieceColour Side { get; set; } } - diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index a732337d..3d7da8ca 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -6,8 +6,8 @@ if (args is not null && args.Contains("--trace")) { - string traceFile = $"CheckersLog.{DateTime.Now}.log"; - Trace.Listeners.Add(new TextWriterTraceListener(File.Create(traceFile))); + string traceFile = $"CheckersLog.{DateTime.Now}.log"; + Trace.Listeners.Add(new TextWriterTraceListener(File.Create(traceFile))); } Trace.AutoFlush = true; @@ -20,52 +20,52 @@ while (gameState != GameState.Stopped) { - switch (gameState) - { - case GameState.IntroScreen: - ShowIntroScreenAndGetOption(); - gameState = GameState.GameInProgress; - break; - case GameState.GameInProgress: - RunGameLoop(); - gameState = GameState.GameOver; - break; - case GameState.GameOver: - HandleGameOver(); - gameState = GameState.Stopped; - break; - default: - throw new NotImplementedException(); - } + switch (gameState) + { + case GameState.IntroScreen: + ShowIntroScreenAndGetOption(); + gameState = GameState.GameInProgress; + break; + case GameState.GameInProgress: + RunGameLoop(); + gameState = GameState.GameOver; + break; + case GameState.GameOver: + HandleGameOver(); + gameState = GameState.Stopped; + break; + default: + throw new NotImplementedException(); + } } void ShowIntroScreenAndGetOption() { - Console.Clear(); - Console.WriteLine("CHECKERS"); - Console.WriteLine(); - Console.WriteLine("Checkers is played on an 8x8 board between two sides commonly known as black"); - Console.WriteLine("and white. The objective is simple - capture all your opponent's pieces. An"); - Console.WriteLine("alternative way to win is to trap your opponent so that they have no valid"); - Console.WriteLine("moves left."); - Console.WriteLine(); - Console.WriteLine("Black starts first and players take it in turns to move their pieces forward"); - Console.WriteLine("across the board diagonally. Should a piece reach the other side of the board"); - Console.WriteLine("the piece becomes a `king` and can then move diagonally backwards as well as"); - Console.WriteLine("forwards."); - Console.WriteLine(); - Console.WriteLine("Pieces are captured by jumping over them diagonally. More than one enemy piece"); - Console.WriteLine("can be captured in the same turn by the same piece."); - Console.WriteLine(); - Console.WriteLine("Moves are selected with the arrow keys. Use the [enter] button to select the"); - Console.WriteLine("from and to squares. Invalid moves are ignored."); - Console.WriteLine(); - Console.WriteLine("3 modes of play are possible depending on the number of players entered:"); - Console.WriteLine(" 0 - black and white are controlled by the computer"); - Console.WriteLine(" 1 - black is controlled by the player and white by the computer"); - Console.WriteLine(" 2 - allows 2 players"); - Console.WriteLine(); - Console.Write("Enter the number of players (0-2): "); + Console.Clear(); + Console.WriteLine("CHECKERS"); + Console.WriteLine(); + Console.WriteLine("Checkers is played on an 8x8 board between two sides commonly known as black"); + Console.WriteLine("and white. The objective is simple - capture all your opponent's pieces. An"); + Console.WriteLine("alternative way to win is to trap your opponent so that they have no valid"); + Console.WriteLine("moves left."); + Console.WriteLine(); + Console.WriteLine("Black starts first and players take it in turns to move their pieces forward"); + Console.WriteLine("across the board diagonally. Should a piece reach the other side of the board"); + Console.WriteLine("the piece becomes a `king` and can then move diagonally backwards as well as"); + Console.WriteLine("forwards."); + Console.WriteLine(); + Console.WriteLine("Pieces are captured by jumping over them diagonally. More than one enemy piece"); + Console.WriteLine("can be captured in the same turn by the same piece."); + Console.WriteLine(); + Console.WriteLine("Moves are selected with the arrow keys. Use the [enter] button to select the"); + Console.WriteLine("from and to squares. Invalid moves are ignored."); + Console.WriteLine(); + Console.WriteLine("3 modes of play are possible depending on the number of players entered:"); + Console.WriteLine(" 0 - black and white are controlled by the computer"); + Console.WriteLine(" 1 - black is controlled by the player and white by the computer"); + Console.WriteLine(" 2 - allows 2 players"); + Console.WriteLine(); + Console.Write("Enter the number of players (0-2): "); string? entry = Console.ReadLine()?.Trim(); while (entry is not "0" and not "1" and not "2") @@ -106,9 +106,9 @@ void RunGameLoop() switch (key) { - case ConsoleKey.DownArrow: selection.Y = Math.Min(7, selection.Y + 1); break; - case ConsoleKey.UpArrow: selection.Y = Math.Max(0, selection.Y - 1); break; - case ConsoleKey.LeftArrow: selection.X = Math.Max(0, selection.X - 1); break; + case ConsoleKey.DownArrow: selection.Y = Math.Min(7, selection.Y + 1); break; + case ConsoleKey.UpArrow: selection.Y = Math.Max(0, selection.Y - 1); break; + case ConsoleKey.LeftArrow: selection.X = Math.Max(0, selection.X - 1); break; case ConsoleKey.RightArrow: selection.X = Math.Min(7, selection.X + 1); break; case ConsoleKey.Enter: if (from is null) diff --git a/Projects/Checkers/Types/Direction.cs b/Projects/Checkers/Types/Direction.cs index 032fc810..aa5a2ec8 100644 --- a/Projects/Checkers/Types/Direction.cs +++ b/Projects/Checkers/Types/Direction.cs @@ -2,9 +2,8 @@ public enum Direction { - Up, - Down, - Left, - Right + Up, + Down, + Left, + Right } - diff --git a/Projects/Checkers/Types/GameState.cs b/Projects/Checkers/Types/GameState.cs index 586bffb4..5f0ae720 100644 --- a/Projects/Checkers/Types/GameState.cs +++ b/Projects/Checkers/Types/GameState.cs @@ -2,9 +2,8 @@ public enum GameState { - IntroScreen, - GameInProgress, - GameOver, - Stopped + IntroScreen, + GameInProgress, + GameOver, + Stopped } - diff --git a/Projects/Checkers/Types/MoveOutcome.cs b/Projects/Checkers/Types/MoveOutcome.cs index a2e8127b..f5d495d0 100644 --- a/Projects/Checkers/Types/MoveOutcome.cs +++ b/Projects/Checkers/Types/MoveOutcome.cs @@ -2,13 +2,12 @@ public enum MoveOutcome { - Unknown, - ValidMoves, - Capture, - CaptureMoreAvailable, - EndGame, // Playing with kings with prey to hunt - NoMoveAvailable, - WhiteWin, - BlackWin + Unknown, + ValidMoves, + Capture, + CaptureMoreAvailable, + EndGame, // Playing with kings with prey to hunt + NoMoveAvailable, + WhiteWin, + BlackWin } - diff --git a/Projects/Checkers/Types/MoveType.cs b/Projects/Checkers/Types/MoveType.cs index be4f523a..186a6ac7 100644 --- a/Projects/Checkers/Types/MoveType.cs +++ b/Projects/Checkers/Types/MoveType.cs @@ -2,9 +2,8 @@ public enum MoveType { - Unknown, - StandardMove, - Capture, - EndGame + Unknown, + StandardMove, + Capture, + EndGame } - diff --git a/Projects/Checkers/Types/PieceColour.cs b/Projects/Checkers/Types/PieceColour.cs index af9d24bf..d517f3a8 100644 --- a/Projects/Checkers/Types/PieceColour.cs +++ b/Projects/Checkers/Types/PieceColour.cs @@ -2,8 +2,7 @@ public enum PieceColour { - NotSet = 0, - Black, - White, + NotSet = 0, + Black, + White, } - diff --git a/Projects/Checkers/Types/PlayerAction.cs b/Projects/Checkers/Types/PlayerAction.cs index a89632ee..60c2363e 100644 --- a/Projects/Checkers/Types/PlayerAction.cs +++ b/Projects/Checkers/Types/PlayerAction.cs @@ -5,9 +5,8 @@ /// public enum PlayerAction { - Move, - Promotion, - Capture, - CapturePromotion + Move, + Promotion, + Capture, + CapturePromotion } - diff --git a/Projects/Checkers/Types/PlayerControl.cs b/Projects/Checkers/Types/PlayerControl.cs index 76c0415d..5d3d4cf8 100644 --- a/Projects/Checkers/Types/PlayerControl.cs +++ b/Projects/Checkers/Types/PlayerControl.cs @@ -2,7 +2,6 @@ public enum PlayerControl { - Human, - Computer + Human, + Computer } - From 1133813aec35a5f09f5e45f96ad14244c7f53fae Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Mon, 20 Jun 2022 21:24:21 -0400 Subject: [PATCH 23/68] moved input/output into Program.cs --- Projects/Checkers/Display.cs | 62 --------- Projects/Checkers/Game.cs | 63 ++++------ Projects/Checkers/Program.cs | 237 +++++++++++++++++++++-------------- 3 files changed, 171 insertions(+), 191 deletions(-) delete mode 100644 Projects/Checkers/Display.cs diff --git a/Projects/Checkers/Display.cs b/Projects/Checkers/Display.cs deleted file mode 100644 index 67f056c7..00000000 --- a/Projects/Checkers/Display.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace Checkers; - -public static class Display -{ - private const char BlackPiece = '○'; - private const char BlackKing = '☺'; - private const char WhitePiece = '◙'; - private const char WhiteKing = '☻'; - - public static void DisplayBoard(Board currentBoard, int whitesTaken, int blacksTaken) - { - Console.Clear(); - Dictionary<(int X, int Y), char> tiles = new(); - foreach (Piece piece in currentBoard.Pieces) - { - if (piece.InPlay) - { - tiles[(piece.XPosition, piece.YPosition)] = ToChar(piece); - } - } - char C(int x, int y) => tiles.GetValueOrDefault((x, y), '.'); - Console.Write( - $" ╔═══════════════════╗\n" + - $" 8 ║ {C(0, 0)} {C(1, 0)} {C(2, 0)} {C(3, 0)} {C(4, 0)} {C(5, 0)} {C(6, 0)} {C(7, 0)} ║ {BlackPiece} = Black\n" + - $" 7 ║ {C(0, 1)} {C(1, 1)} {C(2, 1)} {C(3, 1)} {C(4, 1)} {C(5, 1)} {C(6, 1)} {C(7, 1)} ║ {BlackKing} = Black King\n" + - $" 6 ║ {C(0, 2)} {C(1, 2)} {C(2, 2)} {C(3, 2)} {C(4, 2)} {C(5, 2)} {C(6, 2)} {C(7, 2)} ║ {WhitePiece} = White\n" + - $" 5 ║ {C(0, 3)} {C(1, 3)} {C(2, 3)} {C(3, 3)} {C(4, 3)} {C(5, 3)} {C(6, 3)} {C(7, 3)} ║ {WhiteKing} = White King\n" + - $" 4 ║ {C(0, 4)} {C(1, 4)} {C(2, 4)} {C(3, 4)} {C(4, 4)} {C(5, 4)} {C(6, 4)} {C(7, 4)} ║\n" + - $" 3 ║ {C(0, 5)} {C(1, 5)} {C(2, 5)} {C(3, 5)} {C(4, 5)} {C(5, 5)} {C(6, 5)} {C(7, 5)} ║ Taken:\n" + - $" 2 ║ {C(0, 6)} {C(1, 6)} {C(2, 6)} {C(3, 6)} {C(4, 6)} {C(5, 6)} {C(6, 6)} {C(7, 6)} ║ {whitesTaken,2} x {WhitePiece}\n" + - $" 1 ║ {C(0, 7)} {C(1, 7)} {C(2, 7)} {C(3, 7)} {C(4, 7)} {C(5, 7)} {C(6, 7)} {C(7, 7)} ║ {blacksTaken,2} x {BlackPiece}\n" + - $" ╚═══════════════════╝\n" + - $" A B C D E F G H"); - - Console.CursorVisible = false; - } - - public static void DisplayCurrentPlayer(PieceColour currentSide) - { - Console.SetCursorPosition(0, 11); - Console.WriteLine($"{currentSide} to play"); - } - - public static void DisplayWinner(PieceColour winningSide) - { - Console.SetCursorPosition(0, 11); - Console.WriteLine($"*** {winningSide} wins ***"); - } - - private static char ToChar(Piece piece) => - (piece.Side, piece.Promoted) switch - { - (PieceColour.Black, false) => BlackPiece, - (PieceColour.Black, true) => BlackKing, - (PieceColour.White, false) => WhitePiece, - (PieceColour.White, true) => WhiteKing, - _ => throw new NotImplementedException(), - }; - - public static (int X, int Y) GetScreenPositionFromBoardPosition((int X, int Y) boardPosition) => - ((boardPosition.X * 2) + 7, boardPosition.Y + 1); -} diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index 042f8c54..07eecb5e 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -6,12 +6,10 @@ public class Game { private const int PiecesPerSide = 12; - public PieceColour CurrentGo { get; set; } - public Board GameBoard { get; } - public int MovesSoFar { get; private set; } - public PieceColour GameWinner { get; set; } = PieceColour.NotSet; - - private bool NoDisplay { get; } + public PieceColour Turn { get; set; } + public Board Board { get; } + public int MoveCount { get; private set; } + public PieceColour Winner { get; set; } = PieceColour.NotSet; public List Players { get; set; } = new() { @@ -19,72 +17,64 @@ public class Game new Player { ControlledBy = PlayerControl.Computer, Side = PieceColour.White } }; - public Game(List startingPosition, PieceColour toMove) - { - GameBoard = new Board(startingPosition); - CurrentGo = toMove; - NoDisplay = true; - } - public Game() { - GameBoard = new Board(); - CurrentGo = PieceColour.Black; - NoDisplay = false; - ShowBoard(); + Board = new Board(); + Turn = PieceColour.Black; + //ShowBoard(); } public MoveOutcome NextRound((int X, int Y)? from = null, (int X, int Y)? to = null) { - MovesSoFar++; - var res = Engine.PlayNextMove(CurrentGo, GameBoard, from, to); + MoveCount++; + var res = Engine.PlayNextMove(Turn, Board, from, to); while (from == null & to == null && res == MoveOutcome.CaptureMoreAvailable) { - res = Engine.PlayNextMove(CurrentGo, GameBoard, from, to); + res = Engine.PlayNextMove(Turn, Board, from, to); } if (res == MoveOutcome.BlackWin) { - GameWinner = PieceColour.Black; + Winner = PieceColour.Black; } if (res == MoveOutcome.WhiteWin) { - GameWinner = PieceColour.White; + Winner = PieceColour.White; } - if (GameWinner == PieceColour.NotSet && res != MoveOutcome.CaptureMoreAvailable) + if (Winner == PieceColour.NotSet && res != MoveOutcome.CaptureMoreAvailable) { CheckSidesHavePiecesLeft(); - CurrentGo = CurrentGo == PieceColour.Black ? PieceColour.White : PieceColour.Black; + Turn = Turn == PieceColour.Black ? PieceColour.White : PieceColour.Black; } if (res == MoveOutcome.Unknown) { - CurrentGo = CurrentGo == PieceColour.Black + Turn = Turn == PieceColour.Black ? PieceColour.White : PieceColour.Black; } - ShowBoard(); + //ShowBoard(); return res; } public void CheckSidesHavePiecesLeft() { - var retVal = GameBoard.Pieces.Where(x => x.InPlay).Select(y => y.Side).Distinct().Count() == 2; + var retVal = Board.Pieces.Where(x => x.InPlay).Select(y => y.Side).Distinct().Count() == 2; if (!retVal) { - GameWinner = GameBoard.Pieces.Where(x => x.InPlay).Select(y => y.Side).Distinct().FirstOrDefault(); + Winner = Board.Pieces.Where(x => x.InPlay).Select(y => y.Side).Distinct().FirstOrDefault(); } } public string GetCurrentPlayer() { - return CurrentGo.ToString(); + return Turn.ToString(); } public int GetWhitePiecesTaken() @@ -99,15 +89,12 @@ public int GetBlackPiecesTaken() public int GetPiecesTakenForSide(PieceColour colour) { - return PiecesPerSide - GameBoard.Pieces.Count(x => x.InPlay && x.Side == colour); + return PiecesPerSide - Board.Pieces.Count(x => x.InPlay && x.Side == colour); } - private void ShowBoard() - { - if (!NoDisplay) - { - Display.DisplayBoard(GameBoard, GetWhitePiecesTaken(), GetBlackPiecesTaken()); - Display.DisplayCurrentPlayer(CurrentGo); - } - } + //private void ShowBoard() + //{ + // Display.DisplayBoard(Board, GetWhitePiecesTaken(), GetBlackPiecesTaken()); + // Display.DisplayCurrentPlayer(Turn); + //} } diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 3d7da8ca..549530e8 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -1,42 +1,58 @@ using Checkers; using System.Diagnostics; using System.IO; +using System.Text; -Console.CursorVisible = false; +const char BlackPiece = '○'; +const char BlackKing = '☺'; +const char WhitePiece = '◙'; +const char WhiteKing = '☻'; -if (args is not null && args.Contains("--trace")) -{ - string traceFile = $"CheckersLog.{DateTime.Now}.log"; - Trace.Listeners.Add(new TextWriterTraceListener(File.Create(traceFile))); -} - -Trace.AutoFlush = true; -Console.OutputEncoding = System.Text.Encoding.UTF8; -LoggingHelper.LogStart(); +Encoding encoding = Console.OutputEncoding; Game? game = null; int numberOfPlayers = 0; -GameState gameState = GameState.IntroScreen; -(int X, int Y) selection = (4, 5); -while (gameState != GameState.Stopped) +try { - switch (gameState) + Console.OutputEncoding = Encoding.UTF8; + if (args is not null && args.Contains("--trace")) { - case GameState.IntroScreen: - ShowIntroScreenAndGetOption(); - gameState = GameState.GameInProgress; - break; - case GameState.GameInProgress: - RunGameLoop(); - gameState = GameState.GameOver; - break; - case GameState.GameOver: - HandleGameOver(); - gameState = GameState.Stopped; - break; - default: - throw new NotImplementedException(); + string traceFile = $"CheckersLog.{DateTime.Now}.log"; + Trace.Listeners.Add(new TextWriterTraceListener(File.Create(traceFile))); } + + Trace.AutoFlush = true; + Console.OutputEncoding = Encoding.UTF8; + LoggingHelper.LogStart(); + GameState gameState = GameState.IntroScreen; + + while (gameState != GameState.Stopped) + { + switch (gameState) + { + case GameState.IntroScreen: + ShowIntroScreenAndGetOption(); + gameState = GameState.GameInProgress; + break; + case GameState.GameInProgress: + RunGameLoop(); + gameState = GameState.GameOver; + break; + case GameState.GameOver: + HandleGameOver(); + gameState = GameState.Stopped; + break; + default: + throw new NotImplementedException(); + } + } +} +finally +{ + Console.OutputEncoding = encoding; + Console.CursorVisible = true; + Console.Clear(); + Console.Write("Checkers was closed."); } void ShowIntroScreenAndGetOption() @@ -81,91 +97,58 @@ void RunGameLoop() { game = new Game(); PlayerHelper.AssignPlayersToSide(numberOfPlayers, game); - while (game.GameWinner == PieceColour.NotSet) + while (game.Winner == PieceColour.NotSet) { - var currentPlayer = game.Players.FirstOrDefault(x => x.Side == game.CurrentGo); + Player? currentPlayer = game.Players.FirstOrDefault(x => x.Side == game.Turn); if (currentPlayer != null && currentPlayer.ControlledBy == PlayerControl.Human) { - (int X, int Y)? from = null; - (int X, int Y)? to = null; - while (from is null || to is null) + while (game.Turn == currentPlayer.Side) { - (int X, int Y) screenSelection = Display.GetScreenPositionFromBoardPosition(selection); - Console.SetCursorPosition(screenSelection.X - 1, screenSelection.Y); - Console.Write("["); - Console.SetCursorPosition(screenSelection.X + 1, screenSelection.Y); - Console.Write("]"); - - ConsoleKey key = Console.ReadKey(true).Key; - - var screenPreviousSelection = Display.GetScreenPositionFromBoardPosition(selection); - Console.SetCursorPosition(screenPreviousSelection.X - 1, screenPreviousSelection.Y); - Console.Write(" "); - Console.SetCursorPosition(screenPreviousSelection.X + 1, screenPreviousSelection.Y); - Console.Write(" "); - - switch (key) + (int X, int Y)? from = null; + (int X, int Y)? to = null; + while (to is null) { - case ConsoleKey.DownArrow: selection.Y = Math.Min(7, selection.Y + 1); break; - case ConsoleKey.UpArrow: selection.Y = Math.Max(0, selection.Y - 1); break; - case ConsoleKey.LeftArrow: selection.X = Math.Max(0, selection.X - 1); break; - case ConsoleKey.RightArrow: selection.X = Math.Min(7, selection.X + 1); break; - case ConsoleKey.Enter: - if (from is null) - { - from = (selection.X, selection.Y); - } - else - { - to = (selection.X, selection.Y); - } - break; - case ConsoleKey.Escape: - from = null; - to = null; - break; + while (from is null) + { + from = HumanMoveSelection(); + } + to = HumanMoveSelection(selectionStart: from); + } + Piece? piece = null; + if (from is not null) + { + piece = game.Board.GetPieceAt(from.Value.X, from.Value.Y); + } + if (piece is null || piece.Side != game.Turn) + { + from = null; + to = null; + } + if (from != null && to != null) + { + _ = game.NextRound(from, to); } - } - - var actualFromPiece = game.GameBoard.GetPieceAt(from.Value.X, from.Value.Y); - if (actualFromPiece == null || actualFromPiece.Side != game.CurrentGo) - { - from = null; - to = null; - } - if (from != null && to != null) - { - _ = game.NextRound(from, to); - } - else - { - Console.SetCursorPosition(19, 12); - Console.Write(new string(' ', 10)); - Console.SetCursorPosition(19, 13); - Console.Write(new string(' ', 10)); } } else - { + { game.NextRound(); } + RenderGameState(playerMoved: currentPlayer); PressAnyKeyToContinue(); } - LoggingHelper.LogOutcome(game.GameWinner); + LoggingHelper.LogOutcome(game.Winner); } void HandleGameOver() { + RenderGameState(); if (game != null) { - LoggingHelper.LogMoves(game.MovesSoFar); + LoggingHelper.LogMoves(game.MoveCount); } LoggingHelper.LogFinish(); - if (game != null) - { - Display.DisplayWinner(game.GameWinner); - } } void PressAnyKeyToContinue() @@ -177,3 +160,75 @@ void PressAnyKeyToContinue() Console.SetCursorPosition(left, top); Console.Write(new string(' ', prompt.Length)); } + +void RenderGameState(Player? playerMoved = null, (int X, int Y)? selection = null) +{ + Console.CursorVisible = false; + Console.Clear(); + Dictionary<(int X, int Y), char> tiles = new(); + foreach (Piece piece in game!.Board.Pieces) + { + if (piece.InPlay) + { + tiles[(piece.XPosition, piece.YPosition)] = ToChar(piece); + } + } + char C(int x, int y) => (x, y) == selection ? '$' : tiles.GetValueOrDefault((x, y), '.'); + StringBuilder sb = new(); + sb.AppendLine($" ╔═══════════════════╗"); + sb.AppendLine($" 8 ║ {C(0, 0)} {C(1, 0)} {C(2, 0)} {C(3, 0)} {C(4, 0)} {C(5, 0)} {C(6, 0)} {C(7, 0)} ║ {BlackPiece} = Black"); + sb.AppendLine($" 7 ║ {C(0, 1)} {C(1, 1)} {C(2, 1)} {C(3, 1)} {C(4, 1)} {C(5, 1)} {C(6, 1)} {C(7, 1)} ║ {BlackKing} = Black King"); + sb.AppendLine($" 6 ║ {C(0, 2)} {C(1, 2)} {C(2, 2)} {C(3, 2)} {C(4, 2)} {C(5, 2)} {C(6, 2)} {C(7, 2)} ║ {WhitePiece} = White"); + sb.AppendLine($" 5 ║ {C(0, 3)} {C(1, 3)} {C(2, 3)} {C(3, 3)} {C(4, 3)} {C(5, 3)} {C(6, 3)} {C(7, 3)} ║ {WhiteKing} = White King"); + sb.AppendLine($" 4 ║ {C(0, 4)} {C(1, 4)} {C(2, 4)} {C(3, 4)} {C(4, 4)} {C(5, 4)} {C(6, 4)} {C(7, 4)} ║"); + sb.AppendLine($" 3 ║ {C(0, 5)} {C(1, 5)} {C(2, 5)} {C(3, 5)} {C(4, 5)} {C(5, 5)} {C(6, 5)} {C(7, 5)} ║ Taken:"); + sb.AppendLine($" 2 ║ {C(0, 6)} {C(1, 6)} {C(2, 6)} {C(3, 6)} {C(4, 6)} {C(5, 6)} {C(6, 6)} {C(7, 6)} ║ {game.GetWhitePiecesTaken(),2} x {WhitePiece}"); + sb.AppendLine($" 1 ║ {C(0, 7)} {C(1, 7)} {C(2, 7)} {C(3, 7)} {C(4, 7)} {C(5, 7)} {C(6, 7)} {C(7, 7)} ║ {game.GetBlackPiecesTaken(),2} x {BlackPiece}"); + sb.AppendLine($" ╚═══════════════════╝"); + sb.AppendLine($" A B C D E F G H"); + if (selection is not null) + { + sb.Replace(" $ ", $"[{tiles.GetValueOrDefault(selection.Value, '.')}]"); + } + if (game.Winner is not PieceColour.NotSet) + { + sb.AppendLine($"*** {game.Winner} wins ***"); + } + else if (playerMoved is not null) + { + sb.AppendLine($"{playerMoved.Side} moved"); + } + else + { + sb.AppendLine($"{game.Turn}'s turn"); + } + Console.Write(sb); +} + +static char ToChar(Piece piece) => + (piece.Side, piece.Promoted) switch + { + (PieceColour.Black, false) => BlackPiece, + (PieceColour.Black, true) => BlackKing, + (PieceColour.White, false) => WhitePiece, + (PieceColour.White, true) => WhiteKing, + _ => throw new NotImplementedException(), + }; + +(int X, int Y)? HumanMoveSelection((int X, int y)? selectionStart = null) +{ + (int X, int Y) selection = selectionStart ?? (3, 3); + while (true) + { + RenderGameState(selection: selection); + switch (Console.ReadKey(true).Key) + { + case ConsoleKey.DownArrow: selection.Y = Math.Min(7, selection.Y + 1); break; + case ConsoleKey.UpArrow: selection.Y = Math.Max(0, selection.Y - 1); break; + case ConsoleKey.LeftArrow: selection.X = Math.Max(0, selection.X - 1); break; + case ConsoleKey.RightArrow: selection.X = Math.Min(7, selection.X + 1); break; + case ConsoleKey.Enter: return selection; + case ConsoleKey.Escape: return null; + } + } +} From 9897b5cf9f1410c0af52595c29f78d8b5ecb17a9 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Mon, 20 Jun 2022 21:39:37 -0400 Subject: [PATCH 24/68] moved position logic into Board.cs --- Projects/Checkers/Board.cs | 21 +++++++++++++++++ Projects/Checkers/Engine.cs | 16 ++++++------- Projects/Checkers/Helpers/PositionHelper.cs | 26 --------------------- Projects/Checkers/Piece.cs | 4 ++-- 4 files changed, 31 insertions(+), 36 deletions(-) delete mode 100644 Projects/Checkers/Helpers/PositionHelper.cs diff --git a/Projects/Checkers/Board.cs b/Projects/Checkers/Board.cs index 5647a456..a05a8904 100644 --- a/Projects/Checkers/Board.cs +++ b/Projects/Checkers/Board.cs @@ -46,4 +46,25 @@ public int GetNumberOfBlackPiecesInPlay() => private int GetNumberOfPiecesInPlay(PieceColour currentSide) => Pieces.Count(x => x.Side == currentSide && x.InPlay); + + 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', '8' - notation[1]); + } + + public static bool IsValidPosition(int x, int y) => + 0 <= x && x < 8 && + 0 <= y && y < 8; } diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index 90226bc0..f9c975da 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -78,12 +78,12 @@ public static MoveOutcome PlayNextMove(PieceColour currentSide, Board currentBoa int newX = bestMove.To.X; int newY = bestMove.To.Y; - var from = PositionHelper.ToPositionNotationString(pieceToMove.XPosition, pieceToMove.YPosition); + string from = Board.ToPositionNotationString(pieceToMove.XPosition, pieceToMove.YPosition); pieceToMove.XPosition = newX; pieceToMove.YPosition = newY; - var to = PositionHelper.ToPositionNotationString(pieceToMove.XPosition, pieceToMove.YPosition); + string to = Board.ToPositionNotationString(pieceToMove.XPosition, pieceToMove.YPosition); int blackPieces = currentBoard.GetNumberOfBlackPiecesInPlay(); int whitePieces = currentBoard.GetNumberOfWhitePiecesInPlay(); @@ -225,12 +225,12 @@ private static bool PerformCapture(PieceColour currentSide, List possibleC if (captureMove.PieceToMove != null) { captureMove.PieceToMove.Aggressor = true; - from = PositionHelper.ToPositionNotationString(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); + from = Board.ToPositionNotationString(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); captureMove.PieceToMove.XPosition = captureMove.To.X; captureMove.PieceToMove.YPosition = captureMove.To.Y; - to = PositionHelper.ToPositionNotationString(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); + to = Board.ToPositionNotationString(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); } } @@ -334,7 +334,7 @@ private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board cu var currentX = piece.XPosition + x; var currentY = piece.YPosition + y; - if (!PositionHelper.PointValid(currentX, currentY)) + if (!Board.IsValidPosition(currentX, currentY)) { continue; } @@ -343,7 +343,7 @@ private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board cu if (targetSquare == PieceColour.NotSet) { - if (!PositionHelper.PointValid(currentX, currentY)) + if (!Board.IsValidPosition(currentX, currentY)) { continue; } @@ -365,7 +365,7 @@ private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board cu var beyondX = toLocation.X; var beyondY = toLocation.Y; - if (!PositionHelper.PointValid(beyondX, beyondY)) + if (!Board.IsValidPosition(beyondX, beyondY)) { continue; } @@ -459,7 +459,7 @@ private static bool PlayingWithJustKings(PieceColour currentSide, Board currentB To = (movementOption.X, movementOption.Y) }; - if (!PositionHelper.PointValid(movementOption.X, movementOption.Y)) + if (!Board.IsValidPosition(movementOption.X, movementOption.Y)) { continue; } diff --git a/Projects/Checkers/Helpers/PositionHelper.cs b/Projects/Checkers/Helpers/PositionHelper.cs deleted file mode 100644 index 74d3c4df..00000000 --- a/Projects/Checkers/Helpers/PositionHelper.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Checkers.Helpers; - -/// Position routines -public static class PositionHelper -{ - public static string ToPositionNotationString(int x, int y) - { - if (!PointValid(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', '8' - notation[1]); - } - - public static bool PointValid(int x, int y) => - 0 <= x && x < 8 && - 0 <= y && y < 8; -} diff --git a/Projects/Checkers/Piece.cs b/Projects/Checkers/Piece.cs index a0c4312e..33a0a64b 100644 --- a/Projects/Checkers/Piece.cs +++ b/Projects/Checkers/Piece.cs @@ -14,8 +14,8 @@ public Piece() public string NotationPosition { - get => PositionHelper.ToPositionNotationString(XPosition, YPosition); - set => (XPosition, YPosition) = PositionHelper.ParsePositionNotation(value); + get => Board.ToPositionNotationString(XPosition, YPosition); + set => (XPosition, YPosition) = Board.ParsePositionNotation(value); } public PieceColour Side { get; set; } From 2e305354e568d641e43783eb52b82a96a3d1279b Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Mon, 20 Jun 2022 21:42:42 -0400 Subject: [PATCH 25/68] moved initial board layout into Board.cs --- Projects/Checkers/Board.cs | 54 +++++++++++++------------ Projects/Checkers/Data/KnowledgeBase.cs | 49 ---------------------- 2 files changed, 29 insertions(+), 74 deletions(-) delete mode 100644 Projects/Checkers/Data/KnowledgeBase.cs diff --git a/Projects/Checkers/Board.cs b/Projects/Checkers/Board.cs index a05a8904..151c4a67 100644 --- a/Projects/Checkers/Board.cs +++ b/Projects/Checkers/Board.cs @@ -1,6 +1,4 @@ -using Checkers.Data; - -namespace Checkers; +namespace Checkers; public class Board { @@ -8,30 +6,36 @@ public class Board public Board() { - Pieces = GetStartingPosition(); - } - - public Board(List startingPosition) - { - Pieces = startingPosition; - } - - public static List GetStartingPosition() - { - // HACK: Can set to false and define a custom start position in GetLimitedStartingPosition - const bool UseDefault = true; - -#pragma warning disable CS0162 - return UseDefault ? GetDefaultStartingPosition() : GetLimitedStartingPosition(); -#pragma warning restore CS0162 + Pieces = new List + { + new() { NotationPosition ="A3", Side = PieceColour.Black}, + new() { NotationPosition ="A1", Side = PieceColour.Black}, + new() { NotationPosition ="B2", Side = PieceColour.Black}, + new() { NotationPosition ="C3", Side = PieceColour.Black}, + new() { NotationPosition ="C1", Side = PieceColour.Black}, + new() { NotationPosition ="D2", Side = PieceColour.Black}, + new() { NotationPosition ="E3", Side = PieceColour.Black}, + new() { NotationPosition ="E1", Side = PieceColour.Black}, + new() { NotationPosition ="F2", Side = PieceColour.Black}, + new() { NotationPosition ="G3", Side = PieceColour.Black}, + new() { NotationPosition ="G1", Side = PieceColour.Black}, + new() { NotationPosition ="H2", Side = PieceColour.Black}, + + new() { NotationPosition ="A7", Side = PieceColour.White}, + new() { NotationPosition ="B8", Side = PieceColour.White}, + new() { NotationPosition ="B6", Side = PieceColour.White}, + new() { NotationPosition ="C7", Side = PieceColour.White}, + new() { NotationPosition ="D8", Side = PieceColour.White}, + new() { NotationPosition ="D6", Side = PieceColour.White}, + new() { NotationPosition ="E7", Side = PieceColour.White}, + new() { NotationPosition ="F8", Side = PieceColour.White}, + new() { NotationPosition ="F6", Side = PieceColour.White}, + new() { NotationPosition ="G7", Side = PieceColour.White}, + new() { NotationPosition ="H8", Side = PieceColour.White}, + new() { NotationPosition ="H6", Side = PieceColour.White} + }; } - private static List GetLimitedStartingPosition() => - KnowledgeBase.GetLimitedStartingPosition(); - - public static List GetDefaultStartingPosition() => - KnowledgeBase.GetStartingPosition(); - public PieceColour GetSquareOccupancy(int x, int y) => GetPieceAt(x, y)?.Side ?? default; diff --git a/Projects/Checkers/Data/KnowledgeBase.cs b/Projects/Checkers/Data/KnowledgeBase.cs deleted file mode 100644 index b4848cfb..00000000 --- a/Projects/Checkers/Data/KnowledgeBase.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace Checkers.Data; - -/// -/// Stores the starting position and any custom permutation for testing -/// -public static class KnowledgeBase -{ - public static List GetStartingPosition() - { - var retVal = new List - { - new() { NotationPosition ="A3", Side = PieceColour.Black}, - new() { NotationPosition ="A1", Side = PieceColour.Black}, - new() { NotationPosition ="B2", Side = PieceColour.Black}, - new() { NotationPosition ="C3", Side = PieceColour.Black}, - new() { NotationPosition ="C1", Side = PieceColour.Black}, - new() { NotationPosition ="D2", Side = PieceColour.Black}, - new() { NotationPosition ="E3", Side = PieceColour.Black}, - new() { NotationPosition ="E1", Side = PieceColour.Black}, - new() { NotationPosition ="F2", Side = PieceColour.Black}, - new() { NotationPosition ="G3", Side = PieceColour.Black}, - new() { NotationPosition ="G1", Side = PieceColour.Black}, - new() { NotationPosition ="H2", Side = PieceColour.Black}, - - new() { NotationPosition ="A7", Side = PieceColour.White}, - new() { NotationPosition ="B8", Side = PieceColour.White}, - new() { NotationPosition ="B6", Side = PieceColour.White}, - new() { NotationPosition ="C7", Side = PieceColour.White}, - new() { NotationPosition ="D8", Side = PieceColour.White}, - new() { NotationPosition ="D6", Side = PieceColour.White}, - new() { NotationPosition ="E7", Side = PieceColour.White}, - new() { NotationPosition ="F8", Side = PieceColour.White}, - new() { NotationPosition ="F6", Side = PieceColour.White}, - new() { NotationPosition ="G7", Side = PieceColour.White}, - new() { NotationPosition ="H8", Side = PieceColour.White}, - new() { NotationPosition ="H6", Side = PieceColour.White} - }; - - return retVal; - } - - public static List GetLimitedStartingPosition() => new() - { - new() { NotationPosition = "H2", Side = PieceColour.Black}, - new() { NotationPosition = "A1", Side = PieceColour.Black}, - new() { NotationPosition = "G3", Side = PieceColour.White}, - new() { NotationPosition = "E5", Side = PieceColour.White} - }; -} From fbc72ab60dbbd7413731f823cd2a1b7cab78a7ec Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Mon, 20 Jun 2022 21:49:54 -0400 Subject: [PATCH 26/68] removed InPlay from Piece --- Projects/Checkers/Board.cs | 4 ++-- Projects/Checkers/Engine.cs | 25 ++++++++++++------------- Projects/Checkers/Game.cs | 20 ++++---------------- Projects/Checkers/Piece.cs | 8 -------- Projects/Checkers/Program.cs | 5 +---- 5 files changed, 19 insertions(+), 43 deletions(-) diff --git a/Projects/Checkers/Board.cs b/Projects/Checkers/Board.cs index 151c4a67..48decede 100644 --- a/Projects/Checkers/Board.cs +++ b/Projects/Checkers/Board.cs @@ -40,7 +40,7 @@ public PieceColour GetSquareOccupancy(int x, int y) => GetPieceAt(x, y)?.Side ?? default; public Piece? GetPieceAt(int x, int y) => - Pieces.FirstOrDefault(p => p.XPosition == x && p.InPlay && p.YPosition == y); + Pieces.FirstOrDefault(p => p.XPosition == x && p.YPosition == y); public int GetNumberOfWhitePiecesInPlay() => GetNumberOfPiecesInPlay(PieceColour.White); @@ -49,7 +49,7 @@ public int GetNumberOfBlackPiecesInPlay() => GetNumberOfPiecesInPlay(PieceColour.Black); private int GetNumberOfPiecesInPlay(PieceColour currentSide) => - Pieces.Count(x => x.Side == currentSide && x.InPlay); + Pieces.Count(x => x.Side == currentSide); public static string ToPositionNotationString(int x, int y) { diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index f9c975da..2d5d6aaf 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -214,12 +214,11 @@ private static bool PerformCapture(PieceColour currentSide, List possibleC if (squareToCapture != null) { - var deadMan = - currentBoard.GetPieceAt(squareToCapture.Value.X, squareToCapture.Value.Y); + Piece? deadMan = currentBoard.GetPieceAt(squareToCapture.Value.X, squareToCapture.Value.Y); if (deadMan != null) { - deadMan.InPlay = false; + currentBoard.Pieces.Remove(deadMan); } if (captureMove.PieceToMove != null) @@ -310,11 +309,11 @@ private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board cu { possibleMoves = new List(); - foreach (var piece in currentBoard.Pieces.Where(c => c.Side == currentSide && c.InPlay)) + foreach (Piece piece in currentBoard.Pieces.Where(c => c.Side == currentSide)) { - for (var x = -1; x < 2; x++) + for (int x = -1; x < 2; x++) { - for (var y = -1; y < 2; y++) + for (int y = -1; y < 2; y++) { if (x == 0 || y == 0) { @@ -339,7 +338,7 @@ private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board cu continue; } - var targetSquare = currentBoard.GetSquareOccupancy(currentX, currentY); + PieceColour targetSquare = currentBoard.GetSquareOccupancy(currentX, currentY); if (targetSquare == PieceColour.NotSet) { @@ -348,7 +347,7 @@ private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board cu continue; } - var newMove = new Move { PieceToMove = piece, TypeOfMove = MoveType.StandardMove, To = (currentX, currentY) }; + Move newMove = new Move { PieceToMove = piece, TypeOfMove = MoveType.StandardMove, To = (currentX, currentY) }; possibleMoves.Add(newMove); } else @@ -377,7 +376,7 @@ private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board cu continue; } - var attack = new Move { PieceToMove = piece, TypeOfMove = MoveType.Capture, To = (beyondX, beyondY), Capturing = (currentX, currentY) }; + Move attack = new Move { PieceToMove = piece, TypeOfMove = MoveType.Capture, To = (beyondX, beyondY), Capturing = (currentX, currentY) }; possibleMoves.Add(attack); } } @@ -413,8 +412,8 @@ private static (int X, int Y) DeriveToPosition(int pieceX, int pieceY, int captu private static bool PlayingWithJustKings(PieceColour currentSide, Board currentBoard, out List possibleMoves) { possibleMoves = new List(); - var piecesInPlay = currentBoard.Pieces.Count(x => x.Side == currentSide && x.InPlay); - var kingsInPlay = currentBoard.Pieces.Count(x => x.Side == currentSide && x.InPlay && x.Promoted); + var piecesInPlay = currentBoard.Pieces.Count(x => x.Side == currentSide); + var kingsInPlay = currentBoard.Pieces.Count(x => x.Side == currentSide && x.Promoted); var playingWithJustKings = piecesInPlay == kingsInPlay; @@ -425,9 +424,9 @@ private static bool PlayingWithJustKings(PieceColour currentSide, Board currentB Piece? currentHero = null; Piece? currentVillain = null; - foreach (var king in currentBoard.Pieces.Where(x => x.Side == currentSide && x.InPlay)) + foreach (var king in currentBoard.Pieces.Where(x => x.Side == currentSide)) { - foreach (var target in currentBoard.Pieces.Where(x => x.Side != currentSide && x.InPlay)) + foreach (var target in currentBoard.Pieces.Where(x => x.Side != currentSide)) { var kingPoint = (king.XPosition, king.YPosition); var targetPoint = (target.XPosition, target.YPosition); diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index 07eecb5e..614cbbf2 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -1,6 +1,4 @@ -using System.Drawing; - -namespace Checkers; +namespace Checkers; public class Game { @@ -21,7 +19,6 @@ public Game() { Board = new Board(); Turn = PieceColour.Black; - //ShowBoard(); } public MoveOutcome NextRound((int X, int Y)? from = null, (int X, int Y)? to = null) @@ -56,19 +53,16 @@ public MoveOutcome NextRound((int X, int Y)? from = null, (int X, int Y)? to = n ? PieceColour.White : PieceColour.Black; } - - //ShowBoard(); - return res; } public void CheckSidesHavePiecesLeft() { - var retVal = Board.Pieces.Where(x => x.InPlay).Select(y => y.Side).Distinct().Count() == 2; + var retVal = Board.Pieces.Select(y => y.Side).Distinct().Count() == 2; if (!retVal) { - Winner = Board.Pieces.Where(x => x.InPlay).Select(y => y.Side).Distinct().FirstOrDefault(); + Winner = Board.Pieces.Select(y => y.Side).Distinct().FirstOrDefault(); } } @@ -89,12 +83,6 @@ public int GetBlackPiecesTaken() public int GetPiecesTakenForSide(PieceColour colour) { - return PiecesPerSide - Board.Pieces.Count(x => x.InPlay && x.Side == colour); + return PiecesPerSide - Board.Pieces.Count(x => x.Side == colour); } - - //private void ShowBoard() - //{ - // Display.DisplayBoard(Board, GetWhitePiecesTaken(), GetBlackPiecesTaken()); - // Display.DisplayCurrentPlayer(Turn); - //} } diff --git a/Projects/Checkers/Piece.cs b/Projects/Checkers/Piece.cs index 33a0a64b..678118a0 100644 --- a/Projects/Checkers/Piece.cs +++ b/Projects/Checkers/Piece.cs @@ -2,12 +2,6 @@ public class Piece { - public Piece() - { - InPlay = true; - Promoted = false; - } - public int XPosition { get; set; } public int YPosition { get; set; } @@ -20,8 +14,6 @@ public string NotationPosition public PieceColour Side { get; set; } - public bool InPlay { get; set; } - public bool Promoted { get; set; } public bool Aggressor { get; set; } diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 549530e8..e7cb6784 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -168,10 +168,7 @@ void RenderGameState(Player? playerMoved = null, (int X, int Y)? selection = nul Dictionary<(int X, int Y), char> tiles = new(); foreach (Piece piece in game!.Board.Pieces) { - if (piece.InPlay) - { - tiles[(piece.XPosition, piece.YPosition)] = ToChar(piece); - } + tiles[(piece.XPosition, piece.YPosition)] = ToChar(piece); } char C(int x, int y) => (x, y) == selection ? '$' : tiles.GetValueOrDefault((x, y), '.'); StringBuilder sb = new(); From 7b05b541320d09f412a63ad20e52311d8fa98ad7 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Mon, 20 Jun 2022 21:55:00 -0400 Subject: [PATCH 27/68] rename Color types and properties --- Projects/Checkers/Board.cs | 60 +++++++++---------- Projects/Checkers/Engine.cs | 44 +++++++------- Projects/Checkers/Game.cs | 36 +++++------ Projects/Checkers/Helpers/LoggingHelper.cs | 6 +- Projects/Checkers/Helpers/PlayerHelper.cs | 4 +- Projects/Checkers/Piece.cs | 2 +- Projects/Checkers/Player.cs | 2 +- Projects/Checkers/Program.cs | 22 +++---- .../Types/{PieceColour.cs => PieceColor.cs} | 2 +- 9 files changed, 89 insertions(+), 89 deletions(-) rename Projects/Checkers/Types/{PieceColour.cs => PieceColor.cs} (72%) diff --git a/Projects/Checkers/Board.cs b/Projects/Checkers/Board.cs index 48decede..c4942880 100644 --- a/Projects/Checkers/Board.cs +++ b/Projects/Checkers/Board.cs @@ -8,48 +8,48 @@ public Board() { Pieces = new List { - new() { NotationPosition ="A3", Side = PieceColour.Black}, - new() { NotationPosition ="A1", Side = PieceColour.Black}, - new() { NotationPosition ="B2", Side = PieceColour.Black}, - new() { NotationPosition ="C3", Side = PieceColour.Black}, - new() { NotationPosition ="C1", Side = PieceColour.Black}, - new() { NotationPosition ="D2", Side = PieceColour.Black}, - new() { NotationPosition ="E3", Side = PieceColour.Black}, - new() { NotationPosition ="E1", Side = PieceColour.Black}, - new() { NotationPosition ="F2", Side = PieceColour.Black}, - new() { NotationPosition ="G3", Side = PieceColour.Black}, - new() { NotationPosition ="G1", Side = PieceColour.Black}, - new() { NotationPosition ="H2", Side = PieceColour.Black}, + new() { NotationPosition ="A3", Color = PieceColor.Black}, + new() { NotationPosition ="A1", Color = PieceColor.Black}, + new() { NotationPosition ="B2", Color = PieceColor.Black}, + new() { NotationPosition ="C3", Color = PieceColor.Black}, + new() { NotationPosition ="C1", Color = PieceColor.Black}, + new() { NotationPosition ="D2", Color = PieceColor.Black}, + new() { NotationPosition ="E3", Color = PieceColor.Black}, + new() { NotationPosition ="E1", Color = PieceColor.Black}, + new() { NotationPosition ="F2", Color = PieceColor.Black}, + new() { NotationPosition ="G3", Color = PieceColor.Black}, + new() { NotationPosition ="G1", Color = PieceColor.Black}, + new() { NotationPosition ="H2", Color = PieceColor.Black}, - new() { NotationPosition ="A7", Side = PieceColour.White}, - new() { NotationPosition ="B8", Side = PieceColour.White}, - new() { NotationPosition ="B6", Side = PieceColour.White}, - new() { NotationPosition ="C7", Side = PieceColour.White}, - new() { NotationPosition ="D8", Side = PieceColour.White}, - new() { NotationPosition ="D6", Side = PieceColour.White}, - new() { NotationPosition ="E7", Side = PieceColour.White}, - new() { NotationPosition ="F8", Side = PieceColour.White}, - new() { NotationPosition ="F6", Side = PieceColour.White}, - new() { NotationPosition ="G7", Side = PieceColour.White}, - new() { NotationPosition ="H8", Side = PieceColour.White}, - new() { NotationPosition ="H6", Side = PieceColour.White} + new() { NotationPosition ="A7", Color = PieceColor.White}, + new() { NotationPosition ="B8", Color = PieceColor.White}, + new() { NotationPosition ="B6", Color = PieceColor.White}, + new() { NotationPosition ="C7", Color = PieceColor.White}, + new() { NotationPosition ="D8", Color = PieceColor.White}, + new() { NotationPosition ="D6", Color = PieceColor.White}, + new() { NotationPosition ="E7", Color = PieceColor.White}, + new() { NotationPosition ="F8", Color = PieceColor.White}, + new() { NotationPosition ="F6", Color = PieceColor.White}, + new() { NotationPosition ="G7", Color = PieceColor.White}, + new() { NotationPosition ="H8", Color = PieceColor.White}, + new() { NotationPosition ="H6", Color = PieceColor.White} }; } - public PieceColour GetSquareOccupancy(int x, int y) => - GetPieceAt(x, y)?.Side ?? default; + public PieceColor GetSquareOccupancy(int x, int y) => + GetPieceAt(x, y)?.Color ?? default; public Piece? GetPieceAt(int x, int y) => Pieces.FirstOrDefault(p => p.XPosition == x && p.YPosition == y); public int GetNumberOfWhitePiecesInPlay() => - GetNumberOfPiecesInPlay(PieceColour.White); + GetNumberOfPiecesInPlay(PieceColor.White); public int GetNumberOfBlackPiecesInPlay() => - GetNumberOfPiecesInPlay(PieceColour.Black); + GetNumberOfPiecesInPlay(PieceColor.Black); - private int GetNumberOfPiecesInPlay(PieceColour currentSide) => - Pieces.Count(x => x.Side == currentSide); + private int GetNumberOfPiecesInPlay(PieceColor currentSide) => + Pieces.Count(x => x.Color == currentSide); public static string ToPositionNotationString(int x, int y) { diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index 2d5d6aaf..c0d84c48 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -2,7 +2,7 @@ public static class Engine { - public static MoveOutcome PlayNextMove(PieceColour currentSide, Board currentBoard, (int X, int Y)? playerFrom = null, (int X, int Y)? playerTo = null) + public static MoveOutcome PlayNextMove(PieceColor currentSide, Board currentBoard, (int X, int Y)? playerFrom = null, (int X, int Y)? playerTo = null) { List possibleMoves; MoveOutcome outcome; @@ -53,7 +53,7 @@ public static MoveOutcome PlayNextMove(PieceColour currentSide, Board currentBoa // If a side can't play then other side wins if (outcome == MoveOutcome.NoMoveAvailable) { - outcome = currentSide == PieceColour.Black ? MoveOutcome.WhiteWin : MoveOutcome.BlackWin; + outcome = currentSide == PieceColor.Black ? MoveOutcome.WhiteWin : MoveOutcome.BlackWin; } switch (outcome) @@ -134,7 +134,7 @@ private static void ResetCapturePiece(Board currentBoard) } } - private static bool MoreCapturesAvailable(PieceColour currentSide, Board currentBoard) + private static bool MoreCapturesAvailable(PieceColor currentSide, Board currentBoard) { var aggressor = GetAggressor(currentBoard); @@ -174,7 +174,7 @@ private static bool MoveIsValid((int X, int Y)? from, (int X, int Y) to, List possibleMoves) { var aggressor = GetAggressor(currentBoard); @@ -202,7 +202,7 @@ private static MoveOutcome GetAllPossiblePlayerMoves(PieceColour currentSide, Bo return result; } - private static bool PerformCapture(PieceColour currentSide, List possibleCaptures, Board currentBoard) + private static bool PerformCapture(PieceColor currentSide, List possibleCaptures, Board currentBoard) { var captureMove = possibleCaptures.FirstOrDefault(x => x.Capturing != null); var from = string.Empty; @@ -246,12 +246,12 @@ private static bool PerformCapture(PieceColour currentSide, List possibleC return anyPromoted; } - private static bool CheckForPiecesToPromote(PieceColour currentSide, Board currentBoard) + private static bool CheckForPiecesToPromote(PieceColor currentSide, Board currentBoard) { bool retVal = false; - int promotionSpot = currentSide == PieceColour.White ? 7 : 0; + int promotionSpot = currentSide == PieceColor.White ? 7 : 0; - foreach (var piece in currentBoard.Pieces.Where(x => x.Side == currentSide)) + foreach (var piece in currentBoard.Pieces.Where(x => x.Color == currentSide)) { if (promotionSpot == piece.YPosition && !piece.Promoted) { @@ -262,7 +262,7 @@ private static bool CheckForPiecesToPromote(PieceColour currentSide, Board curre return retVal; } - private static MoveOutcome AnalysePosition(PieceColour currentSide, Board currentBoard, + private static MoveOutcome AnalysePosition(PieceColor currentSide, Board currentBoard, out List possibleMoves) { MoveOutcome result; @@ -304,12 +304,12 @@ private static MoveOutcome AnalysePosition(PieceColour currentSide, Board curren return result; } - private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board currentBoard, + private static void GetPossibleMovesAndAttacks(PieceColor currentSide, Board currentBoard, out List possibleMoves) { possibleMoves = new List(); - foreach (Piece piece in currentBoard.Pieces.Where(c => c.Side == currentSide)) + foreach (Piece piece in currentBoard.Pieces.Where(c => c.Color == currentSide)) { for (int x = -1; x < 2; x++) { @@ -324,8 +324,8 @@ private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board cu { switch (currentSide) { - case PieceColour.White when y == -1: - case PieceColour.Black when y == 1: + case PieceColor.White when y == -1: + case PieceColor.Black when y == 1: continue; } } @@ -338,9 +338,9 @@ private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board cu continue; } - PieceColour targetSquare = currentBoard.GetSquareOccupancy(currentX, currentY); + PieceColor targetSquare = currentBoard.GetSquareOccupancy(currentX, currentY); - if (targetSquare == PieceColour.NotSet) + if (targetSquare == PieceColor.NotSet) { if (!Board.IsValidPosition(currentX, currentY)) { @@ -371,7 +371,7 @@ private static void GetPossibleMovesAndAttacks(PieceColour currentSide, Board cu var beyondSquare = currentBoard.GetSquareOccupancy(beyondX, beyondY); - if (beyondSquare != PieceColour.NotSet) + if (beyondSquare != PieceColor.NotSet) { continue; } @@ -409,11 +409,11 @@ private static (int X, int Y) DeriveToPosition(int pieceX, int pieceY, int captu return (newX, newY); } - private static bool PlayingWithJustKings(PieceColour currentSide, Board currentBoard, out List possibleMoves) + private static bool PlayingWithJustKings(PieceColor currentSide, Board currentBoard, out List possibleMoves) { possibleMoves = new List(); - var piecesInPlay = currentBoard.Pieces.Count(x => x.Side == currentSide); - var kingsInPlay = currentBoard.Pieces.Count(x => x.Side == currentSide && x.Promoted); + var piecesInPlay = currentBoard.Pieces.Count(x => x.Color == currentSide); + var kingsInPlay = currentBoard.Pieces.Count(x => x.Color == currentSide && x.Promoted); var playingWithJustKings = piecesInPlay == kingsInPlay; @@ -424,9 +424,9 @@ private static bool PlayingWithJustKings(PieceColour currentSide, Board currentB Piece? currentHero = null; Piece? currentVillain = null; - foreach (var king in currentBoard.Pieces.Where(x => x.Side == currentSide)) + foreach (var king in currentBoard.Pieces.Where(x => x.Color == currentSide)) { - foreach (var target in currentBoard.Pieces.Where(x => x.Side != currentSide)) + foreach (var target in currentBoard.Pieces.Where(x => x.Color != currentSide)) { var kingPoint = (king.XPosition, king.YPosition); var targetPoint = (target.XPosition, target.YPosition); @@ -449,7 +449,7 @@ private static bool PlayingWithJustKings(PieceColour currentSide, Board currentB { var squareStatus = currentBoard.GetSquareOccupancy(movementOption.X, movementOption.Y); - if (squareStatus == PieceColour.NotSet) + if (squareStatus == PieceColor.NotSet) { var theMove = new Move { diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index 614cbbf2..ffe152d9 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -4,21 +4,21 @@ public class Game { private const int PiecesPerSide = 12; - public PieceColour Turn { get; set; } + public PieceColor Turn { get; set; } public Board Board { get; } public int MoveCount { get; private set; } - public PieceColour Winner { get; set; } = PieceColour.NotSet; + public PieceColor Winner { get; set; } = PieceColor.NotSet; public List Players { get; set; } = new() { - new Player { ControlledBy = PlayerControl.Computer, Side = PieceColour.Black }, - new Player { ControlledBy = PlayerControl.Computer, Side = PieceColour.White } + new Player { ControlledBy = PlayerControl.Computer, Color = PieceColor.Black }, + new Player { ControlledBy = PlayerControl.Computer, Color = PieceColor.White } }; public Game() { Board = new Board(); - Turn = PieceColour.Black; + Turn = PieceColor.Black; } public MoveOutcome NextRound((int X, int Y)? from = null, (int X, int Y)? to = null) @@ -33,36 +33,36 @@ public MoveOutcome NextRound((int X, int Y)? from = null, (int X, int Y)? to = n if (res == MoveOutcome.BlackWin) { - Winner = PieceColour.Black; + Winner = PieceColor.Black; } if (res == MoveOutcome.WhiteWin) { - Winner = PieceColour.White; + Winner = PieceColor.White; } - if (Winner == PieceColour.NotSet && res != MoveOutcome.CaptureMoreAvailable) + if (Winner == PieceColor.NotSet && res != MoveOutcome.CaptureMoreAvailable) { CheckSidesHavePiecesLeft(); - Turn = Turn == PieceColour.Black ? PieceColour.White : PieceColour.Black; + Turn = Turn == PieceColor.Black ? PieceColor.White : PieceColor.Black; } if (res == MoveOutcome.Unknown) { - Turn = Turn == PieceColour.Black - ? PieceColour.White - : PieceColour.Black; + Turn = Turn == PieceColor.Black + ? PieceColor.White + : PieceColor.Black; } return res; } public void CheckSidesHavePiecesLeft() { - var retVal = Board.Pieces.Select(y => y.Side).Distinct().Count() == 2; + var retVal = Board.Pieces.Select(y => y.Color).Distinct().Count() == 2; if (!retVal) { - Winner = Board.Pieces.Select(y => y.Side).Distinct().FirstOrDefault(); + Winner = Board.Pieces.Select(y => y.Color).Distinct().FirstOrDefault(); } } @@ -73,16 +73,16 @@ public string GetCurrentPlayer() public int GetWhitePiecesTaken() { - return GetPiecesTakenForSide(PieceColour.White); + return GetPiecesTakenForSide(PieceColor.White); } public int GetBlackPiecesTaken() { - return GetPiecesTakenForSide(PieceColour.Black); + return GetPiecesTakenForSide(PieceColor.Black); } - public int GetPiecesTakenForSide(PieceColour colour) + public int GetPiecesTakenForSide(PieceColor colour) { - return PiecesPerSide - Board.Pieces.Count(x => x.Side == colour); + return PiecesPerSide - Board.Pieces.Count(x => x.Color == colour); } } diff --git a/Projects/Checkers/Helpers/LoggingHelper.cs b/Projects/Checkers/Helpers/LoggingHelper.cs index cedfc7d7..87b021ab 100644 --- a/Projects/Checkers/Helpers/LoggingHelper.cs +++ b/Projects/Checkers/Helpers/LoggingHelper.cs @@ -7,9 +7,9 @@ namespace Checkers.Helpers; /// public static class LoggingHelper { - public static void LogMove(string from, string to, PlayerAction action, PieceColour sidePlaying, int blacksInPlay, int whitesInPlay) + public static void LogMove(string from, string to, PlayerAction action, PieceColor sidePlaying, int blacksInPlay, int whitesInPlay) { - var colour = sidePlaying == PieceColour.Black ? "B" : "W"; + var colour = sidePlaying == PieceColor.Black ? "B" : "W"; var suffix = DecodePlayerAction(action); var outputLine = $"Move : {colour} {from}-{to} {suffix,2}"; @@ -38,7 +38,7 @@ public static void LogFinish() Trace.WriteLine($"Stopped: {DateTime.Now}"); } - public static void LogOutcome(PieceColour winner) + public static void LogOutcome(PieceColor winner) { Trace.WriteLine($"Winner : {winner}"); } diff --git a/Projects/Checkers/Helpers/PlayerHelper.cs b/Projects/Checkers/Helpers/PlayerHelper.cs index 0122b6ad..7ff00665 100644 --- a/Projects/Checkers/Helpers/PlayerHelper.cs +++ b/Projects/Checkers/Helpers/PlayerHelper.cs @@ -18,12 +18,12 @@ public static void AssignPlayersToSide(int numberOfPlayers, Game currentGame) break; case 1: - foreach (var player in currentGame.Players.Where(x => x.Side == PieceColour.White)) + foreach (var player in currentGame.Players.Where(x => x.Color == PieceColor.White)) { player.ControlledBy = PlayerControl.Computer; } - foreach (var player in currentGame.Players.Where(x => x.Side == PieceColour.Black)) + foreach (var player in currentGame.Players.Where(x => x.Color == PieceColor.Black)) { player.ControlledBy = PlayerControl.Human; } diff --git a/Projects/Checkers/Piece.cs b/Projects/Checkers/Piece.cs index 678118a0..ad85f301 100644 --- a/Projects/Checkers/Piece.cs +++ b/Projects/Checkers/Piece.cs @@ -12,7 +12,7 @@ public string NotationPosition set => (XPosition, YPosition) = Board.ParsePositionNotation(value); } - public PieceColour Side { get; set; } + public PieceColor Color { get; set; } public bool Promoted { get; set; } diff --git a/Projects/Checkers/Player.cs b/Projects/Checkers/Player.cs index 639e56c4..7735d14e 100644 --- a/Projects/Checkers/Player.cs +++ b/Projects/Checkers/Player.cs @@ -3,5 +3,5 @@ public class Player { public PlayerControl ControlledBy { get; set; } - public PieceColour Side { get; set; } + public PieceColor Color { get; set; } } diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index e7cb6784..52f9667b 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -97,12 +97,12 @@ void RunGameLoop() { game = new Game(); PlayerHelper.AssignPlayersToSide(numberOfPlayers, game); - while (game.Winner == PieceColour.NotSet) + while (game.Winner == PieceColor.NotSet) { - Player? currentPlayer = game.Players.FirstOrDefault(x => x.Side == game.Turn); + Player? currentPlayer = game.Players.FirstOrDefault(x => x.Color == game.Turn); if (currentPlayer != null && currentPlayer.ControlledBy == PlayerControl.Human) { - while (game.Turn == currentPlayer.Side) + while (game.Turn == currentPlayer.Color) { (int X, int Y)? from = null; (int X, int Y)? to = null; @@ -119,7 +119,7 @@ void RunGameLoop() { piece = game.Board.GetPieceAt(from.Value.X, from.Value.Y); } - if (piece is null || piece.Side != game.Turn) + if (piece is null || piece.Color != game.Turn) { from = null; to = null; @@ -187,13 +187,13 @@ void RenderGameState(Player? playerMoved = null, (int X, int Y)? selection = nul { sb.Replace(" $ ", $"[{tiles.GetValueOrDefault(selection.Value, '.')}]"); } - if (game.Winner is not PieceColour.NotSet) + if (game.Winner is not PieceColor.NotSet) { sb.AppendLine($"*** {game.Winner} wins ***"); } else if (playerMoved is not null) { - sb.AppendLine($"{playerMoved.Side} moved"); + sb.AppendLine($"{playerMoved.Color} moved"); } else { @@ -203,12 +203,12 @@ void RenderGameState(Player? playerMoved = null, (int X, int Y)? selection = nul } static char ToChar(Piece piece) => - (piece.Side, piece.Promoted) switch + (piece.Color, piece.Promoted) switch { - (PieceColour.Black, false) => BlackPiece, - (PieceColour.Black, true) => BlackKing, - (PieceColour.White, false) => WhitePiece, - (PieceColour.White, true) => WhiteKing, + (PieceColor.Black, false) => BlackPiece, + (PieceColor.Black, true) => BlackKing, + (PieceColor.White, false) => WhitePiece, + (PieceColor.White, true) => WhiteKing, _ => throw new NotImplementedException(), }; diff --git a/Projects/Checkers/Types/PieceColour.cs b/Projects/Checkers/Types/PieceColor.cs similarity index 72% rename from Projects/Checkers/Types/PieceColour.cs rename to Projects/Checkers/Types/PieceColor.cs index d517f3a8..999bae41 100644 --- a/Projects/Checkers/Types/PieceColour.cs +++ b/Projects/Checkers/Types/PieceColor.cs @@ -1,6 +1,6 @@ namespace Checkers.Types; -public enum PieceColour +public enum PieceColor { NotSet = 0, Black, From 60f53a23f687098a5889f16123279ef412501e18 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Mon, 20 Jun 2022 21:58:30 -0400 Subject: [PATCH 28/68] rename GameState to ProgramState --- Projects/Checkers/Program.cs | 16 ++++++++-------- .../Types/{GameState.cs => ProgramState.cs} | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) rename Projects/Checkers/Types/{GameState.cs => ProgramState.cs} (77%) diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 52f9667b..85af4c43 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -24,23 +24,23 @@ Trace.AutoFlush = true; Console.OutputEncoding = Encoding.UTF8; LoggingHelper.LogStart(); - GameState gameState = GameState.IntroScreen; + ProgramState gameState = ProgramState.IntroScreen; - while (gameState != GameState.Stopped) + while (gameState != ProgramState.Stopped) { switch (gameState) { - case GameState.IntroScreen: + case ProgramState.IntroScreen: ShowIntroScreenAndGetOption(); - gameState = GameState.GameInProgress; + gameState = ProgramState.GameInProgress; break; - case GameState.GameInProgress: + case ProgramState.GameInProgress: RunGameLoop(); - gameState = GameState.GameOver; + gameState = ProgramState.GameOver; break; - case GameState.GameOver: + case ProgramState.GameOver: HandleGameOver(); - gameState = GameState.Stopped; + gameState = ProgramState.Stopped; break; default: throw new NotImplementedException(); diff --git a/Projects/Checkers/Types/GameState.cs b/Projects/Checkers/Types/ProgramState.cs similarity index 77% rename from Projects/Checkers/Types/GameState.cs rename to Projects/Checkers/Types/ProgramState.cs index 5f0ae720..3eaabdd1 100644 --- a/Projects/Checkers/Types/GameState.cs +++ b/Projects/Checkers/Types/ProgramState.cs @@ -1,6 +1,6 @@ namespace Checkers.Types; -public enum GameState +public enum ProgramState { IntroScreen, GameInProgress, From d235f3657fb3e2cabb618ca0d0fc18672fed62f3 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Mon, 20 Jun 2022 22:17:40 -0400 Subject: [PATCH 29/68] move Player initialization logic into Game.cs --- Projects/Checkers/Game.cs | 22 ++++++------ Projects/Checkers/Helpers/PlayerHelper.cs | 41 ----------------------- Projects/Checkers/Program.cs | 8 ++--- 3 files changed, 15 insertions(+), 56 deletions(-) delete mode 100644 Projects/Checkers/Helpers/PlayerHelper.cs diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index ffe152d9..eea56230 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -4,21 +4,23 @@ public class Game { private const int PiecesPerSide = 12; - public PieceColor Turn { get; set; } - public Board Board { get; } + public PieceColor Turn { get; private set; } + public Board Board { get; private set; } public int MoveCount { get; private set; } - public PieceColor Winner { get; set; } = PieceColor.NotSet; + public PieceColor Winner { get; private set; } + public List Players { get; private set; } - public List Players { get; set; } = new() - { - new Player { ControlledBy = PlayerControl.Computer, Color = PieceColor.Black }, - new Player { ControlledBy = PlayerControl.Computer, Color = PieceColor.White } - }; - - public Game() + public Game(int humanPlayerCount) { Board = new Board(); + Players = new() + { + new Player { ControlledBy = humanPlayerCount < 1 ? PlayerControl.Computer : PlayerControl.Human, Color = PieceColor.Black }, + new Player { ControlledBy = humanPlayerCount < 2 ? PlayerControl.Computer : PlayerControl.Human, Color = PieceColor.White }, + }; Turn = PieceColor.Black; + MoveCount = 0; + Winner = PieceColor.NotSet; } public MoveOutcome NextRound((int X, int Y)? from = null, (int X, int Y)? to = null) diff --git a/Projects/Checkers/Helpers/PlayerHelper.cs b/Projects/Checkers/Helpers/PlayerHelper.cs deleted file mode 100644 index 7ff00665..00000000 --- a/Projects/Checkers/Helpers/PlayerHelper.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Checkers.Helpers; - -/// -/// Assigns AI/human players -/// N.B. currently can only play as black in a one player game -/// -public static class PlayerHelper -{ - public static void AssignPlayersToSide(int numberOfPlayers, Game currentGame) - { - switch (numberOfPlayers) - { - case 0: - foreach (var player in currentGame.Players) - { - player.ControlledBy = PlayerControl.Computer; - } - - break; - case 1: - foreach (var player in currentGame.Players.Where(x => x.Color == PieceColor.White)) - { - player.ControlledBy = PlayerControl.Computer; - } - - foreach (var player in currentGame.Players.Where(x => x.Color == PieceColor.Black)) - { - player.ControlledBy = PlayerControl.Human; - } - - break; - case 2: - foreach (var player in currentGame.Players) - { - player.ControlledBy = PlayerControl.Human; - } - - break; - } - } -} diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 85af4c43..2d39ba87 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -10,7 +10,6 @@ Encoding encoding = Console.OutputEncoding; Game? game = null; -int numberOfPlayers = 0; try { @@ -90,14 +89,13 @@ void ShowIntroScreenAndGetOption() Console.Write("Enter the number of players (0-2): "); entry = Console.ReadLine()?.Trim(); } - numberOfPlayers = entry[0] - '0'; + int humanPlayerCount = entry[0] - '0'; + game = new Game(humanPlayerCount); } void RunGameLoop() { - game = new Game(); - PlayerHelper.AssignPlayersToSide(numberOfPlayers, game); - while (game.Winner == PieceColor.NotSet) + while (game!.Winner == PieceColor.NotSet) { Player? currentPlayer = game.Players.FirstOrDefault(x => x.Color == game.Turn); if (currentPlayer != null && currentPlayer.ControlledBy == PlayerControl.Human) From 65548ef00a27103b42a42f88c91f1f6e525f5ed9 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Mon, 20 Jun 2022 22:47:18 -0400 Subject: [PATCH 30/68] various clean up --- .editorconfig | 3 + Projects/Checkers/Board.cs | 4 +- Projects/Checkers/Engine.cs | 276 +++++++-------------- Projects/Checkers/Game.cs | 18 +- Projects/Checkers/Helpers/LoggingHelper.cs | 8 +- Projects/Checkers/Helpers/VectorHelper.cs | 23 +- Projects/Checkers/Program.cs | 11 +- 7 files changed, 116 insertions(+), 227 deletions(-) diff --git a/.editorconfig b/.editorconfig index e951c9d7..83e830ef 100644 --- a/.editorconfig +++ b/.editorconfig @@ -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 \ No newline at end of file diff --git a/Projects/Checkers/Board.cs b/Projects/Checkers/Board.cs index c4942880..6a9342f7 100644 --- a/Projects/Checkers/Board.cs +++ b/Projects/Checkers/Board.cs @@ -40,7 +40,7 @@ public PieceColor GetSquareOccupancy(int x, int y) => GetPieceAt(x, y)?.Color ?? default; public Piece? GetPieceAt(int x, int y) => - Pieces.FirstOrDefault(p => p.XPosition == x && p.YPosition == y); + Pieces.FirstOrDefault(piece => piece.XPosition == x && piece.YPosition == y); public int GetNumberOfWhitePiecesInPlay() => GetNumberOfPiecesInPlay(PieceColor.White); @@ -49,7 +49,7 @@ public int GetNumberOfBlackPiecesInPlay() => GetNumberOfPiecesInPlay(PieceColor.Black); private int GetNumberOfPiecesInPlay(PieceColor currentSide) => - Pieces.Count(x => x.Color == currentSide); + Pieces.Count(piece => piece.Color == currentSide); public static string ToPositionNotationString(int x, int y) { diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index c0d84c48..e3daadae 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -2,26 +2,23 @@ public static class Engine { - public static MoveOutcome PlayNextMove(PieceColor currentSide, Board currentBoard, (int X, int Y)? playerFrom = null, (int X, int Y)? playerTo = null) + public static MoveOutcome PlayNextMove(PieceColor side, Board board, (int X, int Y)? playerFrom = null, (int X, int Y)? playerTo = null) { List possibleMoves; MoveOutcome outcome; - - if (playerFrom != null && playerTo != null) + if (playerFrom is not null && playerTo is not null) { - outcome = GetAllPossiblePlayerMoves(currentSide, currentBoard, out possibleMoves); - + outcome = GetAllPossiblePlayerMoves(side, board, out possibleMoves); if (possibleMoves.Count == 0) { outcome = MoveOutcome.NoMoveAvailable; } else { - if (MoveIsValid(playerFrom, playerTo.Value, possibleMoves, currentBoard, out var selectedMove)) + if (MoveIsValid(playerFrom, playerTo.Value, possibleMoves, board, out Move? selectedMove)) { possibleMoves.Clear(); - - if (selectedMove != null) + if (selectedMove is not null) { possibleMoves.Add(selectedMove); @@ -47,65 +44,45 @@ public static MoveOutcome PlayNextMove(PieceColor currentSide, Board currentBoar } else { - outcome = AnalysePosition(currentSide, currentBoard, out possibleMoves); + outcome = AnalysePosition(side, board, out possibleMoves); } - // If a side can't play then other side wins if (outcome == MoveOutcome.NoMoveAvailable) { - outcome = currentSide == PieceColor.Black ? MoveOutcome.WhiteWin : MoveOutcome.BlackWin; + outcome = side == PieceColor.Black ? MoveOutcome.WhiteWin : MoveOutcome.BlackWin; } - switch (outcome) { case MoveOutcome.EndGame: case MoveOutcome.ValidMoves: { - var bestMove = possibleMoves[Random.Shared.Next(possibleMoves.Count)]; - - if (bestMove == null) - { - throw new ArgumentNullException(nameof(bestMove)); - } - - var pieceToMove = bestMove.PieceToMove; - - if (pieceToMove == null) - { - throw new ArgumentNullException(nameof(pieceToMove)); - } - + Move bestMove = possibleMoves[Random.Shared.Next(possibleMoves.Count)]; + Piece pieceToMove = bestMove.PieceToMove!; int newX = bestMove.To.X; int newY = bestMove.To.Y; - string from = Board.ToPositionNotationString(pieceToMove.XPosition, pieceToMove.YPosition); - pieceToMove.XPosition = newX; pieceToMove.YPosition = newY; - string to = Board.ToPositionNotationString(pieceToMove.XPosition, pieceToMove.YPosition); - - int blackPieces = currentBoard.GetNumberOfBlackPiecesInPlay(); - int whitePieces = currentBoard.GetNumberOfWhitePiecesInPlay(); - + int blackPieces = board.GetNumberOfBlackPiecesInPlay(); + int whitePieces = board.GetNumberOfWhitePiecesInPlay(); // Promotion can only happen if not already a king and you have reached the far side if (newY is 0 or 7 && pieceToMove.Promoted == false) { pieceToMove.Promoted = true; - LoggingHelper.LogMove(from, to, PlayerAction.Promotion, currentSide, blackPieces, whitePieces); + LoggingHelper.LogMove(from, to, PlayerAction.Promotion, side, blackPieces, whitePieces); } else { - LoggingHelper.LogMove(from, to, PlayerAction.Move, currentSide, blackPieces, whitePieces); + LoggingHelper.LogMove(from, to, PlayerAction.Move, side, blackPieces, whitePieces); } - break; } case MoveOutcome.Capture: { - var anyPromoted = PerformCapture(currentSide, possibleMoves, currentBoard); - var moreAvailable = MoreCapturesAvailable(currentSide, currentBoard); + bool anyPromoted = PerformCapture(side, possibleMoves, board); + bool moreAvailable = MoreCapturesAvailable(side, board); if (moreAvailable && !anyPromoted) { @@ -116,177 +93,143 @@ public static MoveOutcome PlayNextMove(PieceColor currentSide, Board currentBoar } } - if (outcome != MoveOutcome.CaptureMoreAvailable) + if (outcome is not MoveOutcome.CaptureMoreAvailable) { - ResetCapturePiece(currentBoard); + ResetCapturePiece(board); } return outcome; } - private static void ResetCapturePiece(Board currentBoard) + private static void ResetCapturePiece(Board board) { - var capturePiece = GetAggressor(currentBoard); - - if (capturePiece != null) + Piece? capturePiece = GetAggressor(board); + if (capturePiece is not null) { capturePiece.Aggressor = false; } } - private static bool MoreCapturesAvailable(PieceColor currentSide, Board currentBoard) + private static bool MoreCapturesAvailable(PieceColor side, Board board) { - var aggressor = GetAggressor(currentBoard); - - if (aggressor == null) + Piece? aggressor = GetAggressor(board); + if (aggressor is null) { return false; } - - _ = GetAllPossiblePlayerMoves(currentSide, currentBoard, out var possibleMoves); - - return possibleMoves.Any(move => move.PieceToMove == aggressor && move.Capturing != null); + _ = GetAllPossiblePlayerMoves(side, board, out List? possibleMoves); + return possibleMoves.Any(move => move.PieceToMove == aggressor && move.Capturing is not null); } - private static Piece? GetAggressor(Board currentBoard) + private static Piece? GetAggressor(Board board) { - return currentBoard.Pieces.FirstOrDefault(x => x.Aggressor); + return board.Pieces.FirstOrDefault(piece => piece.Aggressor); } - private static bool MoveIsValid((int X, int Y)? from, (int X, int Y) to, List possibleMoves, Board currentBoard, out Move? selectedMove) + private static bool MoveIsValid((int X, int Y)? from, (int X, int Y) to, List possibleMoves, Board board, out Move? selectedMove) { selectedMove = default; - - if (from == null) + if (from is null) { return false; } - - var selectedPiece = currentBoard.GetPieceAt(from.Value.X, from.Value.Y); - - foreach (var move in possibleMoves.Where(move => move.PieceToMove == selectedPiece && move.To == to)) + Piece? selectedPiece = board.GetPieceAt(from.Value.X, from.Value.Y); + foreach (Move move in possibleMoves.Where(move => move.PieceToMove == selectedPiece && move.To == to)) { selectedMove = move; - return true; } - return false; } - private static MoveOutcome GetAllPossiblePlayerMoves(PieceColor currentSide, Board currentBoard, - out List possibleMoves) + private static MoveOutcome GetAllPossiblePlayerMoves(PieceColor side, Board board, out List possibleMoves) { - var aggressor = GetAggressor(currentBoard); - - var result = MoveOutcome.Unknown; - + Piece? aggressor = GetAggressor(board); + MoveOutcome result = MoveOutcome.Unknown; possibleMoves = new List(); - - if (PlayingWithJustKings(currentSide, currentBoard, out var endGameMoves)) + if (PlayingWithJustKings(side, board, out List? endGameMoves)) { result = MoveOutcome.EndGame; possibleMoves.AddRange(endGameMoves); } - - GetPossibleMovesAndAttacks(currentSide, currentBoard, out var nonEndGameMoves); - + GetPossibleMovesAndAttacks(side, board, out List? nonEndGameMoves); possibleMoves.AddRange(nonEndGameMoves); - - if (aggressor != null) + if (aggressor is not null) { - var tempMoves = possibleMoves.Where(x => x.PieceToMove == aggressor).ToList(); + List? tempMoves = possibleMoves.Where(move => move.PieceToMove == aggressor).ToList(); possibleMoves = tempMoves; } - return result; } - private static bool PerformCapture(PieceColor currentSide, List possibleCaptures, Board currentBoard) + private static bool PerformCapture(PieceColor side, List possibleCaptures, Board board) { - var captureMove = possibleCaptures.FirstOrDefault(x => x.Capturing != null); - var from = string.Empty; - var to = string.Empty; + Move? captureMove = possibleCaptures.FirstOrDefault(move => move.Capturing is not null); + string from = string.Empty; + string to = string.Empty; - if (captureMove != null) + if (captureMove is not null) { - var squareToCapture = captureMove.Capturing; + (int X, int Y)? squareToCapture = captureMove.Capturing; - if (squareToCapture != null) + if (squareToCapture is not null) { - Piece? deadMan = currentBoard.GetPieceAt(squareToCapture.Value.X, squareToCapture.Value.Y); - - if (deadMan != null) + Piece? deadMan = board.GetPieceAt(squareToCapture.Value.X, squareToCapture.Value.Y); + if (deadMan is not null) { - currentBoard.Pieces.Remove(deadMan); + board.Pieces.Remove(deadMan); } - - if (captureMove.PieceToMove != null) + if (captureMove.PieceToMove is not null) { captureMove.PieceToMove.Aggressor = true; from = Board.ToPositionNotationString(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); - captureMove.PieceToMove.XPosition = captureMove.To.X; captureMove.PieceToMove.YPosition = captureMove.To.Y; - to = Board.ToPositionNotationString(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); - } } } - - bool anyPromoted = CheckForPiecesToPromote(currentSide, currentBoard); - int blackPieces = currentBoard.GetNumberOfBlackPiecesInPlay(); - int whitePieces = currentBoard.GetNumberOfWhitePiecesInPlay(); - - var playerAction = anyPromoted ? PlayerAction.CapturePromotion : PlayerAction.Capture; - - LoggingHelper.LogMove(from, to, playerAction, currentSide, blackPieces, whitePieces); - + bool anyPromoted = CheckForPiecesToPromote(side, board); + int blackPieces = board.GetNumberOfBlackPiecesInPlay(); + int whitePieces = board.GetNumberOfWhitePiecesInPlay(); + PlayerAction playerAction = anyPromoted ? PlayerAction.CapturePromotion : PlayerAction.Capture; + LoggingHelper.LogMove(from, to, playerAction, side, blackPieces, whitePieces); return anyPromoted; } - private static bool CheckForPiecesToPromote(PieceColor currentSide, Board currentBoard) + private static bool CheckForPiecesToPromote(PieceColor currentSide, Board board) { bool retVal = false; int promotionSpot = currentSide == PieceColor.White ? 7 : 0; - - foreach (var piece in currentBoard.Pieces.Where(x => x.Color == currentSide)) + foreach (Piece piece in board.Pieces.Where(piece => piece.Color == currentSide)) { if (promotionSpot == piece.YPosition && !piece.Promoted) { piece.Promoted = retVal = true; } } - return retVal; } - private static MoveOutcome AnalysePosition(PieceColor currentSide, Board currentBoard, - out List possibleMoves) + private static MoveOutcome AnalysePosition(PieceColor side, Board board, out List possibleMoves) { MoveOutcome result; - possibleMoves = new List(); - // Check for endgame first - if (PlayingWithJustKings(currentSide, currentBoard, out var endGameMoves)) + if (PlayingWithJustKings(side, board, out List? endGameMoves)) { result = MoveOutcome.EndGame; possibleMoves.AddRange(endGameMoves); - return result; } - - GetPossibleMovesAndAttacks(currentSide, currentBoard, out var nonEndGameMoves); - + GetPossibleMovesAndAttacks(side, board, out List? nonEndGameMoves); if (nonEndGameMoves.Count == 0) { result = MoveOutcome.NoMoveAvailable; } else { - if (nonEndGameMoves.Any(x => x.Capturing != null)) + if (nonEndGameMoves.Any(move => move.Capturing is not null)) { result = MoveOutcome.Capture; } @@ -295,21 +238,18 @@ private static MoveOutcome AnalysePosition(PieceColor currentSide, Board current result = nonEndGameMoves.Count > 0 ? MoveOutcome.ValidMoves : MoveOutcome.NoMoveAvailable; } } - if (nonEndGameMoves.Count > 0) { possibleMoves.AddRange(nonEndGameMoves); } - return result; } - private static void GetPossibleMovesAndAttacks(PieceColor currentSide, Board currentBoard, - out List possibleMoves) + private static void GetPossibleMovesAndAttacks(PieceColor side, Board board, out List possibleMoves) { possibleMoves = new List(); - foreach (Piece piece in currentBoard.Pieces.Where(c => c.Color == currentSide)) + foreach (Piece piece in board.Pieces.Where(piece => piece.Color == side)) { for (int x = -1; x < 2; x++) { @@ -319,64 +259,44 @@ private static void GetPossibleMovesAndAttacks(PieceColor currentSide, Board cur { continue; } - if (!piece.Promoted) { - switch (currentSide) + switch (side) { case PieceColor.White when y == -1: case PieceColor.Black when y == 1: continue; } } - - var currentX = piece.XPosition + x; - var currentY = piece.YPosition + y; - + int currentX = piece.XPosition + x; + int currentY = piece.YPosition + y; if (!Board.IsValidPosition(currentX, currentY)) { continue; } - - PieceColor targetSquare = currentBoard.GetSquareOccupancy(currentX, currentY); - - if (targetSquare == PieceColor.NotSet) + PieceColor targetSquare = board.GetSquareOccupancy(currentX, currentY); + if (targetSquare is PieceColor.NotSet) { if (!Board.IsValidPosition(currentX, currentY)) { continue; } - - Move newMove = new Move { PieceToMove = piece, TypeOfMove = MoveType.StandardMove, To = (currentX, currentY) }; + Move newMove = new() { PieceToMove = piece, TypeOfMove = MoveType.StandardMove, To = (currentX, currentY) }; possibleMoves.Add(newMove); } - else + else if (targetSquare != side) { - var haveTarget = targetSquare != currentSide; - - if (!haveTarget) - { - continue; - } - - var toLocation = DeriveToPosition(piece.XPosition, piece.YPosition, currentX, currentY); - - var beyondX = toLocation.X; - var beyondY = toLocation.Y; - - if (!Board.IsValidPosition(beyondX, beyondY)) + (int X, int Y) toLocation = DeriveToPosition(piece.XPosition, piece.YPosition, currentX, currentY); + if (!Board.IsValidPosition(toLocation.X, toLocation.Y)) { continue; } - - var beyondSquare = currentBoard.GetSquareOccupancy(beyondX, beyondY); - - if (beyondSquare != PieceColor.NotSet) + PieceColor beyondSquare = board.GetSquareOccupancy(toLocation.X, toLocation.Y); + if (beyondSquare is not PieceColor.NotSet) { continue; } - - Move attack = new Move { PieceToMove = piece, TypeOfMove = MoveType.Capture, To = (beyondX, beyondY), Capturing = (currentX, currentY) }; + Move attack = new() { PieceToMove = piece, TypeOfMove = MoveType.Capture, To = (toLocation.X, toLocation.Y), Capturing = (currentX, currentY) }; possibleMoves.Add(attack); } } @@ -395,7 +315,6 @@ private static (int X, int Y) DeriveToPosition(int pieceX, int pieceY, int captu { newX = captureX - 1; } - int newY; if (captureY > pieceY) { @@ -405,33 +324,27 @@ private static (int X, int Y) DeriveToPosition(int pieceX, int pieceY, int captu { newY = captureY - 1; } - return (newX, newY); } - private static bool PlayingWithJustKings(PieceColor currentSide, Board currentBoard, out List possibleMoves) + private static bool PlayingWithJustKings(PieceColor side, Board board, out List possibleMoves) { possibleMoves = new List(); - var piecesInPlay = currentBoard.Pieces.Count(x => x.Color == currentSide); - var kingsInPlay = currentBoard.Pieces.Count(x => x.Color == currentSide && x.Promoted); - - var playingWithJustKings = piecesInPlay == kingsInPlay; - + int piecesInPlay = board.Pieces.Count(piece => piece.Color == side); + int kingsInPlay = board.Pieces.Count(piece => piece.Color == side && piece.Promoted); + bool playingWithJustKings = piecesInPlay == kingsInPlay; if (playingWithJustKings) { - var shortestDistance = 12.0; - + double shortestDistance = 12.0; Piece? currentHero = null; Piece? currentVillain = null; - - foreach (var king in currentBoard.Pieces.Where(x => x.Color == currentSide)) + foreach (Piece king in board.Pieces.Where(piece => piece.Color == side)) { - foreach (var target in currentBoard.Pieces.Where(x => x.Color != currentSide)) + foreach (Piece target in board.Pieces.Where(piece => piece.Color != side)) { - var kingPoint = (king.XPosition, king.YPosition); - var targetPoint = (target.XPosition, target.YPosition); - var distance = VectorHelper.GetPointDistance(kingPoint, targetPoint); - + (int X, int Y) kingPoint = (king.XPosition, king.YPosition); + (int X, int Y) targetPoint = (target.XPosition, target.YPosition); + double distance = VectorHelper.GetPointDistance(kingPoint, targetPoint); if (distance < shortestDistance) { shortestDistance = distance; @@ -440,37 +353,30 @@ private static bool PlayingWithJustKings(PieceColor currentSide, Board currentBo } } } - - if (currentHero != null && currentVillain != null) + if (currentHero is not null && currentVillain is not null) { - var movementOptions = VectorHelper.WhereIsVillain(currentHero, currentVillain); - - foreach (var movementOption in movementOptions) + List<(int X, int Y)>? movementOptions = VectorHelper.WhereIsVillain(currentHero, currentVillain); + foreach ((int X, int Y) movementOption in movementOptions) { - var squareStatus = currentBoard.GetSquareOccupancy(movementOption.X, movementOption.Y); - - if (squareStatus == PieceColor.NotSet) + PieceColor squareStatus = board.GetSquareOccupancy(movementOption.X, movementOption.Y); + if (squareStatus is PieceColor.NotSet) { - var theMove = new Move + Move move = new() { PieceToMove = currentHero, TypeOfMove = MoveType.EndGame, To = (movementOption.X, movementOption.Y) }; - if (!Board.IsValidPosition(movementOption.X, movementOption.Y)) { continue; } - - possibleMoves.Add(theMove); - + possibleMoves.Add(move); break; } } } } - return possibleMoves.Count > 0; } } diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index eea56230..81175842 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -26,29 +26,24 @@ public Game(int humanPlayerCount) public MoveOutcome NextRound((int X, int Y)? from = null, (int X, int Y)? to = null) { MoveCount++; - var res = Engine.PlayNextMove(Turn, Board, from, to); - - while (from == null & to == null && res == MoveOutcome.CaptureMoreAvailable) + MoveOutcome res = Engine.PlayNextMove(Turn, Board, from, to); + while (from is null && to is null && res is MoveOutcome.CaptureMoreAvailable) { res = Engine.PlayNextMove(Turn, Board, from, to); } - if (res == MoveOutcome.BlackWin) { Winner = PieceColor.Black; } - if (res == MoveOutcome.WhiteWin) { Winner = PieceColor.White; } - - if (Winner == PieceColor.NotSet && res != MoveOutcome.CaptureMoreAvailable) + if (Winner == PieceColor.NotSet && res is not MoveOutcome.CaptureMoreAvailable) { CheckSidesHavePiecesLeft(); Turn = Turn == PieceColor.Black ? PieceColor.White : PieceColor.Black; } - if (res == MoveOutcome.Unknown) { Turn = Turn == PieceColor.Black @@ -60,11 +55,10 @@ public MoveOutcome NextRound((int X, int Y)? from = null, (int X, int Y)? to = n public void CheckSidesHavePiecesLeft() { - var retVal = Board.Pieces.Select(y => y.Color).Distinct().Count() == 2; - + bool retVal = Board.Pieces.Select(piece => piece.Color).Distinct().Count() == 2; if (!retVal) { - Winner = Board.Pieces.Select(y => y.Color).Distinct().FirstOrDefault(); + Winner = Board.Pieces.Select(piece => piece.Color).Distinct().FirstOrDefault(); } } @@ -85,6 +79,6 @@ public int GetBlackPiecesTaken() public int GetPiecesTakenForSide(PieceColor colour) { - return PiecesPerSide - Board.Pieces.Count(x => x.Color == colour); + return PiecesPerSide - Board.Pieces.Count(piece => piece.Color == colour); } } diff --git a/Projects/Checkers/Helpers/LoggingHelper.cs b/Projects/Checkers/Helpers/LoggingHelper.cs index 87b021ab..03b32ff7 100644 --- a/Projects/Checkers/Helpers/LoggingHelper.cs +++ b/Projects/Checkers/Helpers/LoggingHelper.cs @@ -9,14 +9,14 @@ public static class LoggingHelper { public static void LogMove(string from, string to, PlayerAction action, PieceColor sidePlaying, int blacksInPlay, int whitesInPlay) { - var colour = sidePlaying == PieceColor.Black ? "B" : "W"; - var suffix = DecodePlayerAction(action); + string colour = sidePlaying == PieceColor.Black ? "B" : "W"; + string suffix = DecodePlayerAction(action); - var outputLine = $"Move : {colour} {from}-{to} {suffix,2}"; + string outputLine = $"Move : {colour} {from}-{to} {suffix,2}"; if (action == PlayerAction.Capture || action == PlayerAction.CapturePromotion) { - var piecesCount = $" B:{blacksInPlay,2} W:{whitesInPlay,2}"; + string piecesCount = $" B:{blacksInPlay,2} W:{whitesInPlay,2}"; outputLine += piecesCount; } diff --git a/Projects/Checkers/Helpers/VectorHelper.cs b/Projects/Checkers/Helpers/VectorHelper.cs index 0fc93c5d..af6fee97 100644 --- a/Projects/Checkers/Helpers/VectorHelper.cs +++ b/Projects/Checkers/Helpers/VectorHelper.cs @@ -13,45 +13,36 @@ public static double GetPointDistance((int X, int Y) first, (int X, int Y) secon { return Math.Abs(first.Y - second.Y); } - if (first.Y == second.Y) { return Math.Abs(first.X - second.X); } - // Pythagoras baby - var sideA = (double)Math.Abs(first.Y - second.Y); - var sideB = (double)Math.Abs(first.X - second.X); - + double sideA = Math.Abs(first.Y - second.Y); + double sideB = Math.Abs(first.X - second.X); return Math.Sqrt(Math.Pow(sideA, 2) + Math.Pow(sideB, 2)); } public static List<(int X, int Y)> WhereIsVillain(Piece hero, Piece villain) { - var retVal = new List<(int X, int Y)>(); - - var directions = new List(); - + List<(int X, int Y)>? retVal = new(); + List? directions = new(); if (hero.XPosition > villain.XPosition) { directions.Add(Direction.Left); } - if (hero.XPosition < villain.XPosition) { directions.Add(Direction.Right); } - if (hero.YPosition > villain.YPosition) { directions.Add(Direction.Up); } - if (hero.YPosition < villain.YPosition) { directions.Add(Direction.Down); } - if (directions.Count == 1) { switch (directions[0]) @@ -77,7 +68,7 @@ public static double GetPointDistance((int X, int Y) first, (int X, int Y) secon break; default: - throw new ArgumentOutOfRangeException(); + throw new NotImplementedException(); } } else @@ -86,23 +77,19 @@ public static double GetPointDistance((int X, int Y) first, (int X, int Y) secon { retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); } - if (directions.Contains(Direction.Left) && directions.Contains(Direction.Down)) { retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); } - if (directions.Contains(Direction.Right) && directions.Contains(Direction.Up)) { retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); } - if (directions.Contains(Direction.Right) && directions.Contains(Direction.Down)) { retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); } } - return retVal; } } diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 2d39ba87..1306d6ba 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -21,11 +21,10 @@ } Trace.AutoFlush = true; - Console.OutputEncoding = Encoding.UTF8; LoggingHelper.LogStart(); ProgramState gameState = ProgramState.IntroScreen; - while (gameState != ProgramState.Stopped) + while (gameState is not ProgramState.Stopped) { switch (gameState) { @@ -97,8 +96,8 @@ void RunGameLoop() { while (game!.Winner == PieceColor.NotSet) { - Player? currentPlayer = game.Players.FirstOrDefault(x => x.Color == game.Turn); - if (currentPlayer != null && currentPlayer.ControlledBy == PlayerControl.Human) + Player? currentPlayer = game.Players.FirstOrDefault(player => player.Color == game.Turn); + if (currentPlayer is not null && currentPlayer.ControlledBy == PlayerControl.Human) { while (game.Turn == currentPlayer.Color) { @@ -122,7 +121,7 @@ void RunGameLoop() from = null; to = null; } - if (from != null && to != null) + if (from is not null && to is not null) { _ = game.NextRound(from, to); } @@ -142,7 +141,7 @@ void RunGameLoop() void HandleGameOver() { RenderGameState(); - if (game != null) + if (game is not null) { LoggingHelper.LogMoves(game.MoveCount); } From 03c294d27f317fcd526954bf38ab054cbe772550 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Mon, 20 Jun 2022 23:08:51 -0400 Subject: [PATCH 31/68] fix inverted y-axis --- Projects/Checkers/Board.cs | 2 +- Projects/Checkers/Engine.cs | 6 +++--- Projects/Checkers/Program.cs | 22 +++++++++++----------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Projects/Checkers/Board.cs b/Projects/Checkers/Board.cs index 6a9342f7..98b5565c 100644 --- a/Projects/Checkers/Board.cs +++ b/Projects/Checkers/Board.cs @@ -65,7 +65,7 @@ public static (int X, int Y) ParsePositionNotation(string notation) 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', '8' - notation[1]); + return (notation[0] - 'A', notation[1] - '1'); } public static bool IsValidPosition(int x, int y) => diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index e3daadae..f034f0fd 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -200,7 +200,7 @@ private static bool PerformCapture(PieceColor side, List possibleCaptures, private static bool CheckForPiecesToPromote(PieceColor currentSide, Board board) { bool retVal = false; - int promotionSpot = currentSide == PieceColor.White ? 7 : 0; + int promotionSpot = currentSide == PieceColor.White ? 0 : 7; foreach (Piece piece in board.Pieces.Where(piece => piece.Color == currentSide)) { if (promotionSpot == piece.YPosition && !piece.Promoted) @@ -263,8 +263,8 @@ private static void GetPossibleMovesAndAttacks(PieceColor side, Board board, out { switch (side) { - case PieceColor.White when y == -1: - case PieceColor.Black when y == 1: + case PieceColor.White when y == 1: + case PieceColor.Black when y == -1: continue; } } diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 1306d6ba..127d9c5b 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -170,14 +170,14 @@ void RenderGameState(Player? playerMoved = null, (int X, int Y)? selection = nul char C(int x, int y) => (x, y) == selection ? '$' : tiles.GetValueOrDefault((x, y), '.'); StringBuilder sb = new(); sb.AppendLine($" ╔═══════════════════╗"); - sb.AppendLine($" 8 ║ {C(0, 0)} {C(1, 0)} {C(2, 0)} {C(3, 0)} {C(4, 0)} {C(5, 0)} {C(6, 0)} {C(7, 0)} ║ {BlackPiece} = Black"); - sb.AppendLine($" 7 ║ {C(0, 1)} {C(1, 1)} {C(2, 1)} {C(3, 1)} {C(4, 1)} {C(5, 1)} {C(6, 1)} {C(7, 1)} ║ {BlackKing} = Black King"); - sb.AppendLine($" 6 ║ {C(0, 2)} {C(1, 2)} {C(2, 2)} {C(3, 2)} {C(4, 2)} {C(5, 2)} {C(6, 2)} {C(7, 2)} ║ {WhitePiece} = White"); - sb.AppendLine($" 5 ║ {C(0, 3)} {C(1, 3)} {C(2, 3)} {C(3, 3)} {C(4, 3)} {C(5, 3)} {C(6, 3)} {C(7, 3)} ║ {WhiteKing} = White King"); - sb.AppendLine($" 4 ║ {C(0, 4)} {C(1, 4)} {C(2, 4)} {C(3, 4)} {C(4, 4)} {C(5, 4)} {C(6, 4)} {C(7, 4)} ║"); - sb.AppendLine($" 3 ║ {C(0, 5)} {C(1, 5)} {C(2, 5)} {C(3, 5)} {C(4, 5)} {C(5, 5)} {C(6, 5)} {C(7, 5)} ║ Taken:"); - sb.AppendLine($" 2 ║ {C(0, 6)} {C(1, 6)} {C(2, 6)} {C(3, 6)} {C(4, 6)} {C(5, 6)} {C(6, 6)} {C(7, 6)} ║ {game.GetWhitePiecesTaken(),2} x {WhitePiece}"); - sb.AppendLine($" 1 ║ {C(0, 7)} {C(1, 7)} {C(2, 7)} {C(3, 7)} {C(4, 7)} {C(5, 7)} {C(6, 7)} {C(7, 7)} ║ {game.GetBlackPiecesTaken(),2} x {BlackPiece}"); + sb.AppendLine($" 8 ║ {C(0, 7)} {C(1, 7)} {C(2, 7)} {C(3, 7)} {C(4, 7)} {C(5, 7)} {C(6, 7)} {C(7, 7)} ║ {BlackPiece} = Black"); + sb.AppendLine($" 7 ║ {C(0, 6)} {C(1, 6)} {C(2, 6)} {C(3, 6)} {C(4, 6)} {C(5, 6)} {C(6, 6)} {C(7, 6)} ║ {BlackKing} = Black King"); + sb.AppendLine($" 6 ║ {C(0, 5)} {C(1, 5)} {C(2, 5)} {C(3, 5)} {C(4, 5)} {C(5, 5)} {C(6, 5)} {C(7, 5)} ║ {WhitePiece} = White"); + sb.AppendLine($" 5 ║ {C(0, 4)} {C(1, 4)} {C(2, 4)} {C(3, 4)} {C(4, 4)} {C(5, 4)} {C(6, 4)} {C(7, 4)} ║ {WhiteKing} = White King"); + sb.AppendLine($" 4 ║ {C(0, 3)} {C(1, 3)} {C(2, 3)} {C(3, 3)} {C(4, 3)} {C(5, 3)} {C(6, 3)} {C(7, 3)} ║"); + sb.AppendLine($" 3 ║ {C(0, 2)} {C(1, 2)} {C(2, 2)} {C(3, 2)} {C(4, 2)} {C(5, 2)} {C(6, 2)} {C(7, 2)} ║ Taken:"); + sb.AppendLine($" 2 ║ {C(0, 1)} {C(1, 1)} {C(2, 1)} {C(3, 1)} {C(4, 1)} {C(5, 1)} {C(6, 1)} {C(7, 1)} ║ {game.GetWhitePiecesTaken(),2} x {WhitePiece}"); + sb.AppendLine($" 1 ║ {C(0, 0)} {C(1, 0)} {C(2, 0)} {C(3, 0)} {C(4, 0)} {C(5, 0)} {C(6, 0)} {C(7, 0)} ║ {game.GetBlackPiecesTaken(),2} x {BlackPiece}"); sb.AppendLine($" ╚═══════════════════╝"); sb.AppendLine($" A B C D E F G H"); if (selection is not null) @@ -217,9 +217,9 @@ static char ToChar(Piece piece) => RenderGameState(selection: selection); switch (Console.ReadKey(true).Key) { - case ConsoleKey.DownArrow: selection.Y = Math.Min(7, selection.Y + 1); break; - case ConsoleKey.UpArrow: selection.Y = Math.Max(0, selection.Y - 1); break; - case ConsoleKey.LeftArrow: selection.X = Math.Max(0, selection.X - 1); break; + case ConsoleKey.DownArrow: selection.Y = Math.Max(0, selection.Y - 1); break; + case ConsoleKey.UpArrow: selection.Y = Math.Min(7, selection.Y + 1); break; + case ConsoleKey.LeftArrow: selection.X = Math.Max(0, selection.X - 1); break; case ConsoleKey.RightArrow: selection.X = Math.Min(7, selection.X + 1); break; case ConsoleKey.Enter: return selection; case ConsoleKey.Escape: return null; From 7cf9a87a933e7b8c0d30431d7ef6c253fb890102 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Mon, 20 Jun 2022 23:36:29 -0400 Subject: [PATCH 32/68] removed "NotSet" PieceColor in favor of nullability --- Projects/Checkers/Board.cs | 3 +-- Projects/Checkers/Engine.cs | 12 ++++++------ Projects/Checkers/Game.cs | 6 +++--- Projects/Checkers/Helpers/LoggingHelper.cs | 2 +- Projects/Checkers/Program.cs | 4 ++-- Projects/Checkers/Types/PieceColor.cs | 5 ++--- 6 files changed, 15 insertions(+), 17 deletions(-) diff --git a/Projects/Checkers/Board.cs b/Projects/Checkers/Board.cs index 98b5565c..504cb2fd 100644 --- a/Projects/Checkers/Board.cs +++ b/Projects/Checkers/Board.cs @@ -36,8 +36,7 @@ public Board() }; } - public PieceColor GetSquareOccupancy(int x, int y) => - GetPieceAt(x, y)?.Color ?? default; + public PieceColor? GetSquareOccupancy(int x, int y) => GetPieceAt(x, y)?.Color; public Piece? GetPieceAt(int x, int y) => Pieces.FirstOrDefault(piece => piece.XPosition == x && piece.YPosition == y); diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index f034f0fd..04aef26f 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -274,8 +274,8 @@ private static void GetPossibleMovesAndAttacks(PieceColor side, Board board, out { continue; } - PieceColor targetSquare = board.GetSquareOccupancy(currentX, currentY); - if (targetSquare is PieceColor.NotSet) + PieceColor? targetSquare = board.GetSquareOccupancy(currentX, currentY); + if (targetSquare is null) { if (!Board.IsValidPosition(currentX, currentY)) { @@ -291,8 +291,8 @@ private static void GetPossibleMovesAndAttacks(PieceColor side, Board board, out { continue; } - PieceColor beyondSquare = board.GetSquareOccupancy(toLocation.X, toLocation.Y); - if (beyondSquare is not PieceColor.NotSet) + PieceColor? beyondSquare = board.GetSquareOccupancy(toLocation.X, toLocation.Y); + if (beyondSquare is not null) { continue; } @@ -358,8 +358,8 @@ private static bool PlayingWithJustKings(PieceColor side, Board board, out List< List<(int X, int Y)>? movementOptions = VectorHelper.WhereIsVillain(currentHero, currentVillain); foreach ((int X, int Y) movementOption in movementOptions) { - PieceColor squareStatus = board.GetSquareOccupancy(movementOption.X, movementOption.Y); - if (squareStatus is PieceColor.NotSet) + PieceColor? squareStatus = board.GetSquareOccupancy(movementOption.X, movementOption.Y); + if (squareStatus is null) { Move move = new() { diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index 81175842..e95a93d1 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -7,7 +7,7 @@ public class Game public PieceColor Turn { get; private set; } public Board Board { get; private set; } public int MoveCount { get; private set; } - public PieceColor Winner { get; private set; } + public PieceColor? Winner { get; private set; } public List Players { get; private set; } public Game(int humanPlayerCount) @@ -20,7 +20,7 @@ public Game(int humanPlayerCount) }; Turn = PieceColor.Black; MoveCount = 0; - Winner = PieceColor.NotSet; + Winner = null; } public MoveOutcome NextRound((int X, int Y)? from = null, (int X, int Y)? to = null) @@ -39,7 +39,7 @@ public MoveOutcome NextRound((int X, int Y)? from = null, (int X, int Y)? to = n { Winner = PieceColor.White; } - if (Winner == PieceColor.NotSet && res is not MoveOutcome.CaptureMoreAvailable) + if (Winner is null && res is not MoveOutcome.CaptureMoreAvailable) { CheckSidesHavePiecesLeft(); Turn = Turn == PieceColor.Black ? PieceColor.White : PieceColor.Black; diff --git a/Projects/Checkers/Helpers/LoggingHelper.cs b/Projects/Checkers/Helpers/LoggingHelper.cs index 03b32ff7..a2398d0b 100644 --- a/Projects/Checkers/Helpers/LoggingHelper.cs +++ b/Projects/Checkers/Helpers/LoggingHelper.cs @@ -38,7 +38,7 @@ public static void LogFinish() Trace.WriteLine($"Stopped: {DateTime.Now}"); } - public static void LogOutcome(PieceColor winner) + public static void LogOutcome(PieceColor? winner) { Trace.WriteLine($"Winner : {winner}"); } diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 127d9c5b..d70ff956 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -94,7 +94,7 @@ void ShowIntroScreenAndGetOption() void RunGameLoop() { - while (game!.Winner == PieceColor.NotSet) + while (game!.Winner is null) { Player? currentPlayer = game.Players.FirstOrDefault(player => player.Color == game.Turn); if (currentPlayer is not null && currentPlayer.ControlledBy == PlayerControl.Human) @@ -184,7 +184,7 @@ void RenderGameState(Player? playerMoved = null, (int X, int Y)? selection = nul { sb.Replace(" $ ", $"[{tiles.GetValueOrDefault(selection.Value, '.')}]"); } - if (game.Winner is not PieceColor.NotSet) + if (game.Winner is not null) { sb.AppendLine($"*** {game.Winner} wins ***"); } diff --git a/Projects/Checkers/Types/PieceColor.cs b/Projects/Checkers/Types/PieceColor.cs index 999bae41..c31840b9 100644 --- a/Projects/Checkers/Types/PieceColor.cs +++ b/Projects/Checkers/Types/PieceColor.cs @@ -2,7 +2,6 @@ public enum PieceColor { - NotSet = 0, - Black, - White, + Black = 1, + White = 2, } From d044761a401bb9ce9837ba36a87e0ff488844174 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Mon, 20 Jun 2022 23:39:50 -0400 Subject: [PATCH 33/68] added human player count exception check on game constructor --- Projects/Checkers/Game.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index e95a93d1..ac0721a9 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -12,6 +12,7 @@ public class Game public Game(int humanPlayerCount) { + if (humanPlayerCount < 0 || 2 < humanPlayerCount) throw new ArgumentOutOfRangeException(nameof(humanPlayerCount)); Board = new Board(); Players = new() { From d55cf1a69682757b0d82ca6914db31a3301ce8b7 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Mon, 20 Jun 2022 23:57:25 -0400 Subject: [PATCH 34/68] various clean up --- Projects/Checkers/Board.cs | 17 ++------ Projects/Checkers/Engine.cs | 18 ++++----- Projects/Checkers/Game.cs | 48 ++++++++--------------- Projects/Checkers/Helpers/VectorHelper.cs | 4 -- Projects/Checkers/Program.cs | 12 +++--- Projects/Checkers/Types/Direction.cs | 8 ++-- 6 files changed, 39 insertions(+), 68 deletions(-) diff --git a/Projects/Checkers/Board.cs b/Projects/Checkers/Board.cs index 504cb2fd..cdf87455 100644 --- a/Projects/Checkers/Board.cs +++ b/Projects/Checkers/Board.cs @@ -4,6 +4,9 @@ public class Board { public List Pieces { get; set; } + public Piece? this[int x, int y] => + Pieces.FirstOrDefault(piece => piece.XPosition == x && piece.YPosition == y); + public Board() { Pieces = new List @@ -36,20 +39,6 @@ public Board() }; } - public PieceColor? GetSquareOccupancy(int x, int y) => GetPieceAt(x, y)?.Color; - - public Piece? GetPieceAt(int x, int y) => - Pieces.FirstOrDefault(piece => piece.XPosition == x && piece.YPosition == y); - - public int GetNumberOfWhitePiecesInPlay() => - GetNumberOfPiecesInPlay(PieceColor.White); - - public int GetNumberOfBlackPiecesInPlay() => - GetNumberOfPiecesInPlay(PieceColor.Black); - - private int GetNumberOfPiecesInPlay(PieceColor currentSide) => - Pieces.Count(piece => piece.Color == currentSide); - public static string ToPositionNotationString(int x, int y) { if (!IsValidPosition(x, y)) throw new ArgumentException("Not a valid position!"); diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index 04aef26f..d724cf28 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -64,8 +64,8 @@ public static MoveOutcome PlayNextMove(PieceColor side, Board board, (int X, int pieceToMove.XPosition = newX; pieceToMove.YPosition = newY; string to = Board.ToPositionNotationString(pieceToMove.XPosition, pieceToMove.YPosition); - int blackPieces = board.GetNumberOfBlackPiecesInPlay(); - int whitePieces = board.GetNumberOfWhitePiecesInPlay(); + int blackPieces = board.Pieces.Count(piece => piece.Color is PieceColor.Black); + int whitePieces = board.Pieces.Count(piece => piece.Color is PieceColor.White); // Promotion can only happen if not already a king and you have reached the far side if (newY is 0 or 7 && pieceToMove.Promoted == false) { @@ -133,7 +133,7 @@ private static bool MoveIsValid((int X, int Y)? from, (int X, int Y) to, List move.PieceToMove == selectedPiece && move.To == to)) { selectedMove = move; @@ -174,7 +174,7 @@ private static bool PerformCapture(PieceColor side, List possibleCaptures, if (squareToCapture is not null) { - Piece? deadMan = board.GetPieceAt(squareToCapture.Value.X, squareToCapture.Value.Y); + Piece? deadMan = board[squareToCapture.Value.X, squareToCapture.Value.Y]; if (deadMan is not null) { board.Pieces.Remove(deadMan); @@ -190,8 +190,8 @@ private static bool PerformCapture(PieceColor side, List possibleCaptures, } } bool anyPromoted = CheckForPiecesToPromote(side, board); - int blackPieces = board.GetNumberOfBlackPiecesInPlay(); - int whitePieces = board.GetNumberOfWhitePiecesInPlay(); + int blackPieces = board.Pieces.Count(piece => piece.Color is PieceColor.Black); + int whitePieces = board.Pieces.Count(piece => piece.Color is PieceColor.White); PlayerAction playerAction = anyPromoted ? PlayerAction.CapturePromotion : PlayerAction.Capture; LoggingHelper.LogMove(from, to, playerAction, side, blackPieces, whitePieces); return anyPromoted; @@ -274,7 +274,7 @@ private static void GetPossibleMovesAndAttacks(PieceColor side, Board board, out { continue; } - PieceColor? targetSquare = board.GetSquareOccupancy(currentX, currentY); + PieceColor? targetSquare = board[currentX, currentY]?.Color; if (targetSquare is null) { if (!Board.IsValidPosition(currentX, currentY)) @@ -291,7 +291,7 @@ private static void GetPossibleMovesAndAttacks(PieceColor side, Board board, out { continue; } - PieceColor? beyondSquare = board.GetSquareOccupancy(toLocation.X, toLocation.Y); + PieceColor? beyondSquare = board[toLocation.X, toLocation.Y]?.Color; if (beyondSquare is not null) { continue; @@ -358,7 +358,7 @@ private static bool PlayingWithJustKings(PieceColor side, Board board, out List< List<(int X, int Y)>? movementOptions = VectorHelper.WhereIsVillain(currentHero, currentVillain); foreach ((int X, int Y) movementOption in movementOptions) { - PieceColor? squareStatus = board.GetSquareOccupancy(movementOption.X, movementOption.Y); + PieceColor? squareStatus = board[movementOption.X, movementOption.Y]?.Color; if (squareStatus is null) { Move move = new() diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index ac0721a9..ac682761 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -2,7 +2,7 @@ public class Game { - private const int PiecesPerSide = 12; + private const int PiecesPerColor = 12; public PieceColor Turn { get; private set; } public Board Board { get; private set; } @@ -27,59 +27,45 @@ public Game(int humanPlayerCount) public MoveOutcome NextRound((int X, int Y)? from = null, (int X, int Y)? to = null) { MoveCount++; - MoveOutcome res = Engine.PlayNextMove(Turn, Board, from, to); - while (from is null && to is null && res is MoveOutcome.CaptureMoreAvailable) + MoveOutcome moveOutcome = Engine.PlayNextMove(Turn, Board, from, to); + while (from is null && to is null && moveOutcome is MoveOutcome.CaptureMoreAvailable) { - res = Engine.PlayNextMove(Turn, Board, from, to); + moveOutcome = Engine.PlayNextMove(Turn, Board, from, to); } - if (res == MoveOutcome.BlackWin) + if (moveOutcome == MoveOutcome.BlackWin) { Winner = PieceColor.Black; } - if (res == MoveOutcome.WhiteWin) + if (moveOutcome == MoveOutcome.WhiteWin) { Winner = PieceColor.White; } - if (Winner is null && res is not MoveOutcome.CaptureMoreAvailable) + if (Winner is null && moveOutcome is not MoveOutcome.CaptureMoreAvailable) { CheckSidesHavePiecesLeft(); Turn = Turn == PieceColor.Black ? PieceColor.White : PieceColor.Black; } - if (res == MoveOutcome.Unknown) + if (moveOutcome == MoveOutcome.Unknown) { Turn = Turn == PieceColor.Black ? PieceColor.White : PieceColor.Black; } - return res; + return moveOutcome; } public void CheckSidesHavePiecesLeft() { - bool retVal = Board.Pieces.Select(piece => piece.Color).Distinct().Count() == 2; - if (!retVal) + if (!Board.Pieces.Any(piece => piece.Color is PieceColor.Black)) { - Winner = Board.Pieces.Select(piece => piece.Color).Distinct().FirstOrDefault(); + Winner = PieceColor.White; + } + if (!Board.Pieces.Any(piece => piece.Color is PieceColor.White)) + { + Winner = PieceColor.Black; } } - public string GetCurrentPlayer() - { - return Turn.ToString(); - } - - public int GetWhitePiecesTaken() - { - return GetPiecesTakenForSide(PieceColor.White); - } - - public int GetBlackPiecesTaken() - { - return GetPiecesTakenForSide(PieceColor.Black); - } - - public int GetPiecesTakenForSide(PieceColor colour) - { - return PiecesPerSide - Board.Pieces.Count(piece => piece.Color == colour); - } + public int TakenCount(PieceColor colour) => + PiecesPerColor - Board.Pieces.Count(piece => piece.Color == colour); } diff --git a/Projects/Checkers/Helpers/VectorHelper.cs b/Projects/Checkers/Helpers/VectorHelper.cs index af6fee97..66b9aea9 100644 --- a/Projects/Checkers/Helpers/VectorHelper.cs +++ b/Projects/Checkers/Helpers/VectorHelper.cs @@ -50,22 +50,18 @@ public static double GetPointDistance((int X, int Y) first, (int X, int Y) secon case Direction.Up: retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); - break; case Direction.Down: retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); - break; case Direction.Left: retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); - break; case Direction.Right: retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); - break; default: throw new NotImplementedException(); diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index d70ff956..c203e6f3 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -114,7 +114,7 @@ void RunGameLoop() Piece? piece = null; if (from is not null) { - piece = game.Board.GetPieceAt(from.Value.X, from.Value.Y); + piece = game.Board[from.Value.X, from.Value.Y]; } if (piece is null || piece.Color != game.Turn) { @@ -128,7 +128,7 @@ void RunGameLoop() } } else - { + { game.NextRound(); } @@ -176,8 +176,8 @@ void RenderGameState(Player? playerMoved = null, (int X, int Y)? selection = nul sb.AppendLine($" 5 ║ {C(0, 4)} {C(1, 4)} {C(2, 4)} {C(3, 4)} {C(4, 4)} {C(5, 4)} {C(6, 4)} {C(7, 4)} ║ {WhiteKing} = White King"); sb.AppendLine($" 4 ║ {C(0, 3)} {C(1, 3)} {C(2, 3)} {C(3, 3)} {C(4, 3)} {C(5, 3)} {C(6, 3)} {C(7, 3)} ║"); sb.AppendLine($" 3 ║ {C(0, 2)} {C(1, 2)} {C(2, 2)} {C(3, 2)} {C(4, 2)} {C(5, 2)} {C(6, 2)} {C(7, 2)} ║ Taken:"); - sb.AppendLine($" 2 ║ {C(0, 1)} {C(1, 1)} {C(2, 1)} {C(3, 1)} {C(4, 1)} {C(5, 1)} {C(6, 1)} {C(7, 1)} ║ {game.GetWhitePiecesTaken(),2} x {WhitePiece}"); - sb.AppendLine($" 1 ║ {C(0, 0)} {C(1, 0)} {C(2, 0)} {C(3, 0)} {C(4, 0)} {C(5, 0)} {C(6, 0)} {C(7, 0)} ║ {game.GetBlackPiecesTaken(),2} x {BlackPiece}"); + sb.AppendLine($" 2 ║ {C(0, 1)} {C(1, 1)} {C(2, 1)} {C(3, 1)} {C(4, 1)} {C(5, 1)} {C(6, 1)} {C(7, 1)} ║ {game.TakenCount(PieceColor.White),2} x {WhitePiece}"); + sb.AppendLine($" 1 ║ {C(0, 0)} {C(1, 0)} {C(2, 0)} {C(3, 0)} {C(4, 0)} {C(5, 0)} {C(6, 0)} {C(7, 0)} ║ {game.TakenCount(PieceColor.Black),2} x {BlackPiece}"); sb.AppendLine($" ╚═══════════════════╝"); sb.AppendLine($" A B C D E F G H"); if (selection is not null) @@ -218,8 +218,8 @@ static char ToChar(Piece piece) => switch (Console.ReadKey(true).Key) { case ConsoleKey.DownArrow: selection.Y = Math.Max(0, selection.Y - 1); break; - case ConsoleKey.UpArrow: selection.Y = Math.Min(7, selection.Y + 1); break; - case ConsoleKey.LeftArrow: selection.X = Math.Max(0, selection.X - 1); break; + case ConsoleKey.UpArrow: selection.Y = Math.Min(7, selection.Y + 1); break; + case ConsoleKey.LeftArrow: selection.X = Math.Max(0, selection.X - 1); break; case ConsoleKey.RightArrow: selection.X = Math.Min(7, selection.X + 1); break; case ConsoleKey.Enter: return selection; case ConsoleKey.Escape: return null; diff --git a/Projects/Checkers/Types/Direction.cs b/Projects/Checkers/Types/Direction.cs index aa5a2ec8..85bdd3bb 100644 --- a/Projects/Checkers/Types/Direction.cs +++ b/Projects/Checkers/Types/Direction.cs @@ -2,8 +2,8 @@ public enum Direction { - Up, - Down, - Left, - Right + Up = 1, + Down = 2, + Left = 3, + Right = 4, } From 5a63595bdc6c45d4be0ec7812a3a225135d4b935 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 00:15:25 -0400 Subject: [PATCH 35/68] renamed Types -> Enums --- Projects/Checkers/{Types => Enums}/Direction.cs | 2 +- Projects/Checkers/{Types => Enums}/MoveOutcome.cs | 2 +- Projects/Checkers/{Types => Enums}/MoveType.cs | 2 +- Projects/Checkers/{Types => Enums}/PieceColor.cs | 2 +- Projects/Checkers/{Types => Enums}/PlayerAction.cs | 2 +- Projects/Checkers/{Types => Enums}/PlayerControl.cs | 2 +- Projects/Checkers/{Types => Enums}/ProgramState.cs | 2 +- Projects/Checkers/_using.cs | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) rename Projects/Checkers/{Types => Enums}/Direction.cs (72%) rename Projects/Checkers/{Types => Enums}/MoveOutcome.cs (85%) rename Projects/Checkers/{Types => Enums}/MoveType.cs (70%) rename Projects/Checkers/{Types => Enums}/PieceColor.cs (64%) rename Projects/Checkers/{Types => Enums}/PlayerAction.cs (81%) rename Projects/Checkers/{Types => Enums}/PlayerControl.cs (62%) rename Projects/Checkers/{Types => Enums}/ProgramState.cs (73%) diff --git a/Projects/Checkers/Types/Direction.cs b/Projects/Checkers/Enums/Direction.cs similarity index 72% rename from Projects/Checkers/Types/Direction.cs rename to Projects/Checkers/Enums/Direction.cs index 85bdd3bb..072ca49e 100644 --- a/Projects/Checkers/Types/Direction.cs +++ b/Projects/Checkers/Enums/Direction.cs @@ -1,4 +1,4 @@ -namespace Checkers.Types; +namespace Checkers.Enums; public enum Direction { diff --git a/Projects/Checkers/Types/MoveOutcome.cs b/Projects/Checkers/Enums/MoveOutcome.cs similarity index 85% rename from Projects/Checkers/Types/MoveOutcome.cs rename to Projects/Checkers/Enums/MoveOutcome.cs index f5d495d0..9a6d2548 100644 --- a/Projects/Checkers/Types/MoveOutcome.cs +++ b/Projects/Checkers/Enums/MoveOutcome.cs @@ -1,4 +1,4 @@ -namespace Checkers.Types; +namespace Checkers.Enums; public enum MoveOutcome { diff --git a/Projects/Checkers/Types/MoveType.cs b/Projects/Checkers/Enums/MoveType.cs similarity index 70% rename from Projects/Checkers/Types/MoveType.cs rename to Projects/Checkers/Enums/MoveType.cs index 186a6ac7..fdec41d1 100644 --- a/Projects/Checkers/Types/MoveType.cs +++ b/Projects/Checkers/Enums/MoveType.cs @@ -1,4 +1,4 @@ -namespace Checkers.Types; +namespace Checkers.Enums; public enum MoveType { diff --git a/Projects/Checkers/Types/PieceColor.cs b/Projects/Checkers/Enums/PieceColor.cs similarity index 64% rename from Projects/Checkers/Types/PieceColor.cs rename to Projects/Checkers/Enums/PieceColor.cs index c31840b9..fde5ee7b 100644 --- a/Projects/Checkers/Types/PieceColor.cs +++ b/Projects/Checkers/Enums/PieceColor.cs @@ -1,4 +1,4 @@ -namespace Checkers.Types; +namespace Checkers.Enums; public enum PieceColor { diff --git a/Projects/Checkers/Types/PlayerAction.cs b/Projects/Checkers/Enums/PlayerAction.cs similarity index 81% rename from Projects/Checkers/Types/PlayerAction.cs rename to Projects/Checkers/Enums/PlayerAction.cs index 60c2363e..0e688683 100644 --- a/Projects/Checkers/Types/PlayerAction.cs +++ b/Projects/Checkers/Enums/PlayerAction.cs @@ -1,4 +1,4 @@ -namespace Checkers.Types; +namespace Checkers.Enums; /// /// Used for logging diff --git a/Projects/Checkers/Types/PlayerControl.cs b/Projects/Checkers/Enums/PlayerControl.cs similarity index 62% rename from Projects/Checkers/Types/PlayerControl.cs rename to Projects/Checkers/Enums/PlayerControl.cs index 5d3d4cf8..d0c5eea4 100644 --- a/Projects/Checkers/Types/PlayerControl.cs +++ b/Projects/Checkers/Enums/PlayerControl.cs @@ -1,4 +1,4 @@ -namespace Checkers.Types; +namespace Checkers.Enums; public enum PlayerControl { diff --git a/Projects/Checkers/Types/ProgramState.cs b/Projects/Checkers/Enums/ProgramState.cs similarity index 73% rename from Projects/Checkers/Types/ProgramState.cs rename to Projects/Checkers/Enums/ProgramState.cs index 3eaabdd1..05082d2a 100644 --- a/Projects/Checkers/Types/ProgramState.cs +++ b/Projects/Checkers/Enums/ProgramState.cs @@ -1,4 +1,4 @@ -namespace Checkers.Types; +namespace Checkers.Enums; public enum ProgramState { diff --git a/Projects/Checkers/_using.cs b/Projects/Checkers/_using.cs index 42fce8d2..bf88c2bc 100644 --- a/Projects/Checkers/_using.cs +++ b/Projects/Checkers/_using.cs @@ -3,4 +3,4 @@ global using System.Linq; global using Checkers.Helpers; -global using Checkers.Types; \ No newline at end of file +global using Checkers.Enums; \ No newline at end of file From 72194785cc971a65984cadce62d114e1437a69e0 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 00:23:35 -0400 Subject: [PATCH 36/68] removed logging (might add back in future) --- Projects/Checkers/Engine.cs | 7 --- Projects/Checkers/Helpers/LoggingHelper.cs | 62 ---------------------- Projects/Checkers/Program.cs | 16 ------ 3 files changed, 85 deletions(-) delete mode 100644 Projects/Checkers/Helpers/LoggingHelper.cs diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index d724cf28..658cc33e 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -70,12 +70,6 @@ public static MoveOutcome PlayNextMove(PieceColor side, Board board, (int X, int if (newY is 0 or 7 && pieceToMove.Promoted == false) { pieceToMove.Promoted = true; - - LoggingHelper.LogMove(from, to, PlayerAction.Promotion, side, blackPieces, whitePieces); - } - else - { - LoggingHelper.LogMove(from, to, PlayerAction.Move, side, blackPieces, whitePieces); } break; } @@ -193,7 +187,6 @@ private static bool PerformCapture(PieceColor side, List possibleCaptures, int blackPieces = board.Pieces.Count(piece => piece.Color is PieceColor.Black); int whitePieces = board.Pieces.Count(piece => piece.Color is PieceColor.White); PlayerAction playerAction = anyPromoted ? PlayerAction.CapturePromotion : PlayerAction.Capture; - LoggingHelper.LogMove(from, to, playerAction, side, blackPieces, whitePieces); return anyPromoted; } diff --git a/Projects/Checkers/Helpers/LoggingHelper.cs b/Projects/Checkers/Helpers/LoggingHelper.cs deleted file mode 100644 index a2398d0b..00000000 --- a/Projects/Checkers/Helpers/LoggingHelper.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Diagnostics; - -namespace Checkers.Helpers; - -/// -/// Used for logging games for analysis - see flag in Program.cs -/// -public static class LoggingHelper -{ - public static void LogMove(string from, string to, PlayerAction action, PieceColor sidePlaying, int blacksInPlay, int whitesInPlay) - { - string colour = sidePlaying == PieceColor.Black ? "B" : "W"; - string suffix = DecodePlayerAction(action); - - string outputLine = $"Move : {colour} {from}-{to} {suffix,2}"; - - if (action == PlayerAction.Capture || action == PlayerAction.CapturePromotion) - { - string piecesCount = $" B:{blacksInPlay,2} W:{whitesInPlay,2}"; - outputLine += piecesCount; - } - - Trace.WriteLine(outputLine); - } - - public static void LogMoves(int numberOfMoves) - { - Trace.WriteLine($"Moves : {numberOfMoves}"); - } - - public static void LogStart() - { - Trace.WriteLine($"Started: {DateTime.Now}"); - } - - public static void LogFinish() - { - Trace.WriteLine($"Stopped: {DateTime.Now}"); - } - - public static void LogOutcome(PieceColor? winner) - { - Trace.WriteLine($"Winner : {winner}"); - } - - private static string DecodePlayerAction(PlayerAction action) - { - switch (action) - { - case PlayerAction.Move: - return string.Empty; - case PlayerAction.Promotion: - return "K"; - case PlayerAction.Capture: - return "X"; - case PlayerAction.CapturePromotion: - return "KX"; - default: - return String.Empty; - } - } -} diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index c203e6f3..c9494b71 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -1,6 +1,4 @@ using Checkers; -using System.Diagnostics; -using System.IO; using System.Text; const char BlackPiece = '○'; @@ -14,14 +12,6 @@ try { Console.OutputEncoding = Encoding.UTF8; - if (args is not null && args.Contains("--trace")) - { - string traceFile = $"CheckersLog.{DateTime.Now}.log"; - Trace.Listeners.Add(new TextWriterTraceListener(File.Create(traceFile))); - } - - Trace.AutoFlush = true; - LoggingHelper.LogStart(); ProgramState gameState = ProgramState.IntroScreen; while (gameState is not ProgramState.Stopped) @@ -135,17 +125,11 @@ void RunGameLoop() RenderGameState(playerMoved: currentPlayer); PressAnyKeyToContinue(); } - LoggingHelper.LogOutcome(game.Winner); } void HandleGameOver() { RenderGameState(); - if (game is not null) - { - LoggingHelper.LogMoves(game.MoveCount); - } - LoggingHelper.LogFinish(); } void PressAnyKeyToContinue() From 7944adda6d3cefa3373f4a10f86742a4702f6f97 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 00:29:16 -0400 Subject: [PATCH 37/68] various clean up --- Projects/Checkers/Engine.cs | 42 +++++++++++++------------------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index 658cc33e..7b91e481 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -55,36 +55,26 @@ public static MoveOutcome PlayNextMove(PieceColor side, Board board, (int X, int { case MoveOutcome.EndGame: case MoveOutcome.ValidMoves: + Move bestMove = possibleMoves[Random.Shared.Next(possibleMoves.Count)]; + Piece pieceToMove = bestMove.PieceToMove!; + int newX = bestMove.To.X; + int newY = bestMove.To.Y; + pieceToMove.XPosition = newX; + pieceToMove.YPosition = newY; + // Promotion can only happen if not already a king and you have reached the far side + if (newY is 0 or 7 && !pieceToMove.Promoted) { - Move bestMove = possibleMoves[Random.Shared.Next(possibleMoves.Count)]; - Piece pieceToMove = bestMove.PieceToMove!; - int newX = bestMove.To.X; - int newY = bestMove.To.Y; - string from = Board.ToPositionNotationString(pieceToMove.XPosition, pieceToMove.YPosition); - pieceToMove.XPosition = newX; - pieceToMove.YPosition = newY; - string to = Board.ToPositionNotationString(pieceToMove.XPosition, pieceToMove.YPosition); - int blackPieces = board.Pieces.Count(piece => piece.Color is PieceColor.Black); - int whitePieces = board.Pieces.Count(piece => piece.Color is PieceColor.White); - // Promotion can only happen if not already a king and you have reached the far side - if (newY is 0 or 7 && pieceToMove.Promoted == false) - { - pieceToMove.Promoted = true; - } - break; + pieceToMove.Promoted = true; } + break; case MoveOutcome.Capture: + bool anyPromoted = PerformCapture(side, possibleMoves, board); + bool moreAvailable = MoreCapturesAvailable(side, board); + if (moreAvailable && !anyPromoted) { - bool anyPromoted = PerformCapture(side, possibleMoves, board); - bool moreAvailable = MoreCapturesAvailable(side, board); - - if (moreAvailable && !anyPromoted) - { - outcome = MoveOutcome.CaptureMoreAvailable; - } - - break; + outcome = MoveOutcome.CaptureMoreAvailable; } + break; } if (outcome is not MoveOutcome.CaptureMoreAvailable) @@ -184,8 +174,6 @@ private static bool PerformCapture(PieceColor side, List possibleCaptures, } } bool anyPromoted = CheckForPiecesToPromote(side, board); - int blackPieces = board.Pieces.Count(piece => piece.Color is PieceColor.Black); - int whitePieces = board.Pieces.Count(piece => piece.Color is PieceColor.White); PlayerAction playerAction = anyPromoted ? PlayerAction.CapturePromotion : PlayerAction.Capture; return anyPromoted; } From b29482e020f00d5c6cd0b7f3b1c0bee83ad5baeb Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 00:35:42 -0400 Subject: [PATCH 38/68] remove DeriveToPosition (you can just multiply by 2) --- Projects/Checkers/Engine.cs | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index 7b91e481..481defc3 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -267,7 +267,7 @@ private static void GetPossibleMovesAndAttacks(PieceColor side, Board board, out } else if (targetSquare != side) { - (int X, int Y) toLocation = DeriveToPosition(piece.XPosition, piece.YPosition, currentX, currentY); + (int X, int Y) toLocation = (piece.XPosition + 2 * x, piece.YPosition + 2 * y); if (!Board.IsValidPosition(toLocation.X, toLocation.Y)) { continue; @@ -285,29 +285,6 @@ private static void GetPossibleMovesAndAttacks(PieceColor side, Board board, out } } - private static (int X, int Y) DeriveToPosition(int pieceX, int pieceY, int captureX, int captureY) - { - int newX; - if (captureX > pieceX) - { - newX = captureX + 1; - } - else - { - newX = captureX - 1; - } - int newY; - if (captureY > pieceY) - { - newY = captureY + 1; - } - else - { - newY = captureY - 1; - } - return (newX, newY); - } - private static bool PlayingWithJustKings(PieceColor side, Board board, out List possibleMoves) { possibleMoves = new List(); From d02499f6964dbb86e81a2bc07a48b81adfc4fb3c Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 00:38:25 -0400 Subject: [PATCH 39/68] removed MoveCount because it wasn't calculating correctly --- Projects/Checkers/Engine.cs | 3 --- Projects/Checkers/Game.cs | 3 --- 2 files changed, 6 deletions(-) diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index 481defc3..3df9b3c2 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -21,7 +21,6 @@ public static MoveOutcome PlayNextMove(PieceColor side, Board board, (int X, int if (selectedMove is not null) { possibleMoves.Add(selectedMove); - switch (selectedMove.TypeOfMove) { case MoveType.Unknown: @@ -76,12 +75,10 @@ public static MoveOutcome PlayNextMove(PieceColor side, Board board, (int X, int } break; } - if (outcome is not MoveOutcome.CaptureMoreAvailable) { ResetCapturePiece(board); } - return outcome; } diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index ac682761..4bf12b22 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -6,7 +6,6 @@ public class Game public PieceColor Turn { get; private set; } public Board Board { get; private set; } - public int MoveCount { get; private set; } public PieceColor? Winner { get; private set; } public List Players { get; private set; } @@ -20,13 +19,11 @@ public Game(int humanPlayerCount) new Player { ControlledBy = humanPlayerCount < 2 ? PlayerControl.Computer : PlayerControl.Human, Color = PieceColor.White }, }; Turn = PieceColor.Black; - MoveCount = 0; Winner = null; } public MoveOutcome NextRound((int X, int Y)? from = null, (int X, int Y)? to = null) { - MoveCount++; MoveOutcome moveOutcome = Engine.PlayNextMove(Turn, Board, from, to); while (from is null && to is null && moveOutcome is MoveOutcome.CaptureMoreAvailable) { From 47af07b72e841ad4596f123791d0d26e159f91bc Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 01:09:51 -0400 Subject: [PATCH 40/68] clean up --- Projects/Checkers/Engine.cs | 115 +++++++++++++----------------------- 1 file changed, 40 insertions(+), 75 deletions(-) diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index 3df9b3c2..fb545bc3 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -2,20 +2,20 @@ public static class Engine { - public static MoveOutcome PlayNextMove(PieceColor side, Board board, (int X, int Y)? playerFrom = null, (int X, int Y)? playerTo = null) + public static MoveOutcome PlayNextMove(PieceColor side, Board board, (int X, int Y)? from = null, (int X, int Y)? to = null) { List possibleMoves; MoveOutcome outcome; - if (playerFrom is not null && playerTo is not null) + if (from is not null && to is not null) { outcome = GetAllPossiblePlayerMoves(side, board, out possibleMoves); - if (possibleMoves.Count == 0) + if (possibleMoves.Count is 0) { outcome = MoveOutcome.NoMoveAvailable; } else { - if (MoveIsValid(playerFrom, playerTo.Value, possibleMoves, board, out Move? selectedMove)) + if (MoveIsValid(from, to.Value, possibleMoves, board, out Move? selectedMove)) { possibleMoves.Clear(); if (selectedMove is not null) @@ -23,19 +23,10 @@ public static MoveOutcome PlayNextMove(PieceColor side, Board board, (int X, int possibleMoves.Add(selectedMove); switch (selectedMove.TypeOfMove) { - case MoveType.Unknown: - break; - case MoveType.StandardMove: - outcome = MoveOutcome.ValidMoves; - break; - case MoveType.Capture: - outcome = MoveOutcome.Capture; - break; - case MoveType.EndGame: - outcome = MoveOutcome.EndGame; - break; - default: - throw new NotImplementedException(); + case MoveType.StandardMove: outcome = MoveOutcome.ValidMoves; break; + case MoveType.Capture: outcome = MoveOutcome.Capture; break; + case MoveType.EndGame: outcome = MoveOutcome.EndGame; break; + default: throw new NotImplementedException(); } } } @@ -146,16 +137,12 @@ private static MoveOutcome GetAllPossiblePlayerMoves(PieceColor side, Board boar private static bool PerformCapture(PieceColor side, List possibleCaptures, Board board) { Move? captureMove = possibleCaptures.FirstOrDefault(move => move.Capturing is not null); - string from = string.Empty; - string to = string.Empty; - if (captureMove is not null) { - (int X, int Y)? squareToCapture = captureMove.Capturing; - - if (squareToCapture is not null) + (int X, int Y)? positionToCapture = captureMove.Capturing; + if (positionToCapture is not null) { - Piece? deadMan = board[squareToCapture.Value.X, squareToCapture.Value.Y]; + Piece? deadMan = board[positionToCapture.Value.X, positionToCapture.Value.Y]; if (deadMan is not null) { board.Pieces.Remove(deadMan); @@ -163,10 +150,8 @@ private static bool PerformCapture(PieceColor side, List possibleCaptures, if (captureMove.PieceToMove is not null) { captureMove.PieceToMove.Aggressor = true; - from = Board.ToPositionNotationString(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); captureMove.PieceToMove.XPosition = captureMove.To.X; captureMove.PieceToMove.YPosition = captureMove.To.Y; - to = Board.ToPositionNotationString(captureMove.PieceToMove.XPosition, captureMove.PieceToMove.YPosition); } } } @@ -223,63 +208,43 @@ private static MoveOutcome AnalysePosition(PieceColor side, Board board, out Lis return result; } - private static void GetPossibleMovesAndAttacks(PieceColor side, Board board, out List possibleMoves) + private static void GetPossibleMovesAndAttacks(PieceColor color, Board board, out List possibleMoves) { - possibleMoves = new List(); + List moves = new(); - foreach (Piece piece in board.Pieces.Where(piece => piece.Color == side)) + foreach (Piece piece in board.Pieces.Where(piece => piece.Color == color)) { - for (int x = -1; x < 2; x++) + ValidateMove(-1, -1); + ValidateMove(-1, 1); + ValidateMove( 1, -1); + ValidateMove( 1, 1); + + void ValidateMove(int dx, int dy) { - for (int y = -1; y < 2; y++) + if (!piece.Promoted && color is PieceColor.Black && dy is -1) return; + if (!piece.Promoted && color is PieceColor.White && dy is 1) return; + (int X, int Y) target = (piece.XPosition + dx, piece.YPosition + dy); + if (!Board.IsValidPosition(target.X, target.Y)) return; + PieceColor? targetColor = board[target.X, target.Y]?.Color; + if (targetColor is null) { - if (x == 0 || y == 0) - { - continue; - } - if (!piece.Promoted) - { - switch (side) - { - case PieceColor.White when y == 1: - case PieceColor.Black when y == -1: - continue; - } - } - int currentX = piece.XPosition + x; - int currentY = piece.YPosition + y; - if (!Board.IsValidPosition(currentX, currentY)) - { - continue; - } - PieceColor? targetSquare = board[currentX, currentY]?.Color; - if (targetSquare is null) - { - if (!Board.IsValidPosition(currentX, currentY)) - { - continue; - } - Move newMove = new() { PieceToMove = piece, TypeOfMove = MoveType.StandardMove, To = (currentX, currentY) }; - possibleMoves.Add(newMove); - } - else if (targetSquare != side) - { - (int X, int Y) toLocation = (piece.XPosition + 2 * x, piece.YPosition + 2 * y); - if (!Board.IsValidPosition(toLocation.X, toLocation.Y)) - { - continue; - } - PieceColor? beyondSquare = board[toLocation.X, toLocation.Y]?.Color; - if (beyondSquare is not null) - { - continue; - } - Move attack = new() { PieceToMove = piece, TypeOfMove = MoveType.Capture, To = (toLocation.X, toLocation.Y), Capturing = (currentX, currentY) }; - possibleMoves.Add(attack); - } + if (!Board.IsValidPosition(target.X, target.Y)) return; + Move newMove = new() { PieceToMove = piece, TypeOfMove = MoveType.StandardMove, To = (target.X, target.Y) }; + moves.Add(newMove); + } + else if (targetColor != color) + { + (int X, int Y) jump = (piece.XPosition + 2 * dx, piece.YPosition + 2 * dy); + if (!Board.IsValidPosition(jump.X, jump.Y)) return; + PieceColor? jumpColor = board[jump.X, jump.Y]?.Color; + if (jumpColor is not null) return; + Move attack = new() { PieceToMove = piece, TypeOfMove = MoveType.Capture, To = (jump.X, jump.Y), Capturing = (target.X, target.Y) }; + moves.Add(attack); } } } + + possibleMoves = moves; } private static bool PlayingWithJustKings(PieceColor side, Board board, out List possibleMoves) From 857b2df8da094fc8fe3d9852ba17dc42e59ac68d Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 01:12:51 -0400 Subject: [PATCH 41/68] remove PlayerAction (missed when removed logging) --- Projects/Checkers/Engine.cs | 1 - Projects/Checkers/Enums/PlayerAction.cs | 12 ------------ 2 files changed, 13 deletions(-) delete mode 100644 Projects/Checkers/Enums/PlayerAction.cs diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index fb545bc3..55b750f8 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -156,7 +156,6 @@ private static bool PerformCapture(PieceColor side, List possibleCaptures, } } bool anyPromoted = CheckForPiecesToPromote(side, board); - PlayerAction playerAction = anyPromoted ? PlayerAction.CapturePromotion : PlayerAction.Capture; return anyPromoted; } diff --git a/Projects/Checkers/Enums/PlayerAction.cs b/Projects/Checkers/Enums/PlayerAction.cs deleted file mode 100644 index 0e688683..00000000 --- a/Projects/Checkers/Enums/PlayerAction.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Checkers.Enums; - -/// -/// Used for logging -/// -public enum PlayerAction -{ - Move, - Promotion, - Capture, - CapturePromotion -} From f0bf9e08e3f029ba3bbc41cbf1bebf9155b4068d Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 01:54:32 -0400 Subject: [PATCH 42/68] removed MoveType --- Projects/Checkers/Engine.cs | 25 ++++++------------------- Projects/Checkers/Enums/MoveType.cs | 9 --------- Projects/Checkers/Game.cs | 4 ++-- Projects/Checkers/Move.cs | 9 +++++++-- Projects/Checkers/Player.cs | 10 ++++++++-- 5 files changed, 23 insertions(+), 34 deletions(-) delete mode 100644 Projects/Checkers/Enums/MoveType.cs diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index 55b750f8..b7cea861 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -21,13 +21,7 @@ public static MoveOutcome PlayNextMove(PieceColor side, Board board, (int X, int if (selectedMove is not null) { possibleMoves.Add(selectedMove); - switch (selectedMove.TypeOfMove) - { - case MoveType.StandardMove: outcome = MoveOutcome.ValidMoves; break; - case MoveType.Capture: outcome = MoveOutcome.Capture; break; - case MoveType.EndGame: outcome = MoveOutcome.EndGame; break; - default: throw new NotImplementedException(); - } + outcome = selectedMove.Capturing is not null ? MoveOutcome.Capture : MoveOutcome.ValidMoves; } } } @@ -228,7 +222,7 @@ void ValidateMove(int dx, int dy) if (targetColor is null) { if (!Board.IsValidPosition(target.X, target.Y)) return; - Move newMove = new() { PieceToMove = piece, TypeOfMove = MoveType.StandardMove, To = (target.X, target.Y) }; + Move newMove = new(piece, target); moves.Add(newMove); } else if (targetColor != color) @@ -237,7 +231,7 @@ void ValidateMove(int dx, int dy) if (!Board.IsValidPosition(jump.X, jump.Y)) return; PieceColor? jumpColor = board[jump.X, jump.Y]?.Color; if (jumpColor is not null) return; - Move attack = new() { PieceToMove = piece, TypeOfMove = MoveType.Capture, To = (jump.X, jump.Y), Capturing = (target.X, target.Y) }; + Move attack = new(piece, jump, target); moves.Add(attack); } } @@ -277,20 +271,13 @@ private static bool PlayingWithJustKings(PieceColor side, Board board, out List< List<(int X, int Y)>? movementOptions = VectorHelper.WhereIsVillain(currentHero, currentVillain); foreach ((int X, int Y) movementOption in movementOptions) { - PieceColor? squareStatus = board[movementOption.X, movementOption.Y]?.Color; - if (squareStatus is null) + PieceColor? targetColor = board[movementOption.X, movementOption.Y]?.Color; + if (targetColor is null) { - Move move = new() - { - PieceToMove = currentHero, - TypeOfMove = MoveType.EndGame, - To = (movementOption.X, movementOption.Y) - }; if (!Board.IsValidPosition(movementOption.X, movementOption.Y)) { - continue; + possibleMoves.Add(new Move(currentHero, movementOption)); } - possibleMoves.Add(move); break; } } diff --git a/Projects/Checkers/Enums/MoveType.cs b/Projects/Checkers/Enums/MoveType.cs deleted file mode 100644 index fdec41d1..00000000 --- a/Projects/Checkers/Enums/MoveType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Checkers.Enums; - -public enum MoveType -{ - Unknown, - StandardMove, - Capture, - EndGame -} diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index 4bf12b22..c343f06f 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -15,8 +15,8 @@ public Game(int humanPlayerCount) Board = new Board(); Players = new() { - new Player { ControlledBy = humanPlayerCount < 1 ? PlayerControl.Computer : PlayerControl.Human, Color = PieceColor.Black }, - new Player { ControlledBy = humanPlayerCount < 2 ? PlayerControl.Computer : PlayerControl.Human, Color = PieceColor.White }, + new Player(humanPlayerCount < 1 ? PlayerControl.Computer : PlayerControl.Human, PieceColor.Black), + new Player(humanPlayerCount < 2 ? PlayerControl.Computer : PlayerControl.Human, PieceColor.White), }; Turn = PieceColor.Black; Winner = null; diff --git a/Projects/Checkers/Move.cs b/Projects/Checkers/Move.cs index fc703c4c..cbd56903 100644 --- a/Projects/Checkers/Move.cs +++ b/Projects/Checkers/Move.cs @@ -2,11 +2,16 @@ public class Move { - public Piece? PieceToMove { get; set; } + public Piece PieceToMove { get; set; } public (int X, int Y) To { get; set; } public (int X, int Y)? Capturing { get; set; } - public MoveType TypeOfMove { get; set; } = MoveType.Unknown; + public Move(Piece piece, (int X, int Y) to, (int X, int Y)? capturing = null) + { + PieceToMove = piece; + To = to; + Capturing = capturing; + } } diff --git a/Projects/Checkers/Player.cs b/Projects/Checkers/Player.cs index 7735d14e..e6d1f849 100644 --- a/Projects/Checkers/Player.cs +++ b/Projects/Checkers/Player.cs @@ -2,6 +2,12 @@ public class Player { - public PlayerControl ControlledBy { get; set; } - public PieceColor Color { get; set; } + public PlayerControl ControlledBy { get; private set; } + public PieceColor Color { get; private set; } + + public Player(PlayerControl controlledBy, PieceColor color) + { + ControlledBy = controlledBy; + Color = color; + } } From 7c9e2ff870584ad6e944a0cb5fb67569d78b7508 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 02:04:12 -0400 Subject: [PATCH 43/68] removed MoveOutcome.Unkown --- Projects/Checkers/Engine.cs | 8 ++++---- Projects/Checkers/Enums/MoveOutcome.cs | 15 +++++++-------- Projects/Checkers/Game.cs | 11 ++++------- Projects/Checkers/Program.cs | 2 +- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index b7cea861..6afdd1e8 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -2,10 +2,10 @@ public static class Engine { - public static MoveOutcome PlayNextMove(PieceColor side, Board board, (int X, int Y)? from = null, (int X, int Y)? to = null) + public static MoveOutcome? PlayNextMove(PieceColor side, Board board, (int X, int Y)? from = null, (int X, int Y)? to = null) { List possibleMoves; - MoveOutcome outcome; + MoveOutcome? outcome; if (from is not null && to is not null) { outcome = GetAllPossiblePlayerMoves(side, board, out possibleMoves); @@ -108,10 +108,10 @@ private static bool MoveIsValid((int X, int Y)? from, (int X, int Y) to, List possibleMoves) + private static MoveOutcome? GetAllPossiblePlayerMoves(PieceColor side, Board board, out List possibleMoves) { Piece? aggressor = GetAggressor(board); - MoveOutcome result = MoveOutcome.Unknown; + MoveOutcome? result = null; possibleMoves = new List(); if (PlayingWithJustKings(side, board, out List? endGameMoves)) { diff --git a/Projects/Checkers/Enums/MoveOutcome.cs b/Projects/Checkers/Enums/MoveOutcome.cs index 9a6d2548..52b29271 100644 --- a/Projects/Checkers/Enums/MoveOutcome.cs +++ b/Projects/Checkers/Enums/MoveOutcome.cs @@ -2,12 +2,11 @@ public enum MoveOutcome { - Unknown, - ValidMoves, - Capture, - CaptureMoreAvailable, - EndGame, // Playing with kings with prey to hunt - NoMoveAvailable, - WhiteWin, - BlackWin + ValidMoves = 1, + Capture = 2, + CaptureMoreAvailable = 3, + EndGame = 4, // Playing with kings with prey to hunt + NoMoveAvailable = 5, + WhiteWin = 6, + BlackWin = 7, } diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index c343f06f..c9960219 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -22,9 +22,9 @@ public Game(int humanPlayerCount) Winner = null; } - public MoveOutcome NextRound((int X, int Y)? from = null, (int X, int Y)? to = null) + public void NextRound((int X, int Y)? from = null, (int X, int Y)? to = null) { - MoveOutcome moveOutcome = Engine.PlayNextMove(Turn, Board, from, to); + MoveOutcome? moveOutcome = Engine.PlayNextMove(Turn, Board, from, to); while (from is null && to is null && moveOutcome is MoveOutcome.CaptureMoreAvailable) { moveOutcome = Engine.PlayNextMove(Turn, Board, from, to); @@ -42,13 +42,10 @@ public MoveOutcome NextRound((int X, int Y)? from = null, (int X, int Y)? to = n CheckSidesHavePiecesLeft(); Turn = Turn == PieceColor.Black ? PieceColor.White : PieceColor.Black; } - if (moveOutcome == MoveOutcome.Unknown) + if (moveOutcome is null) { - Turn = Turn == PieceColor.Black - ? PieceColor.White - : PieceColor.Black; + Turn = Turn is PieceColor.Black ? PieceColor.White : PieceColor.Black; } - return moveOutcome; } public void CheckSidesHavePiecesLeft() diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index c9494b71..693184a6 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -113,7 +113,7 @@ void RunGameLoop() } if (from is not null && to is not null) { - _ = game.NextRound(from, to); + game.NextRound(from, to); } } } From 83f5c7f20af2d55247e60f83e6e87099c3d97852 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 02:06:31 -0400 Subject: [PATCH 44/68] rename NextRound to NextTurn --- Projects/Checkers/Game.cs | 2 +- Projects/Checkers/Program.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index c9960219..c467a45e 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -22,7 +22,7 @@ public Game(int humanPlayerCount) Winner = null; } - public void NextRound((int X, int Y)? from = null, (int X, int Y)? to = null) + public void NextTurn((int X, int Y)? from = null, (int X, int Y)? to = null) { MoveOutcome? moveOutcome = Engine.PlayNextMove(Turn, Board, from, to); while (from is null && to is null && moveOutcome is MoveOutcome.CaptureMoreAvailable) diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 693184a6..59aa21d4 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -113,13 +113,13 @@ void RunGameLoop() } if (from is not null && to is not null) { - game.NextRound(from, to); + game.NextTurn(from, to); } } } else { - game.NextRound(); + game.NextTurn(); } RenderGameState(playerMoved: currentPlayer); From 99590bb0f9d07c96a8f69dc92347e994c87a1821 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 02:14:07 -0400 Subject: [PATCH 45/68] moved Aggressor from Piece to Board (there can only be one aggressor at a time so it should be on the board not the pieces) --- Projects/Checkers/Board.cs | 3 +++ Projects/Checkers/Engine.cs | 22 ++++------------------ Projects/Checkers/Piece.cs | 2 -- 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/Projects/Checkers/Board.cs b/Projects/Checkers/Board.cs index cdf87455..431e94cc 100644 --- a/Projects/Checkers/Board.cs +++ b/Projects/Checkers/Board.cs @@ -4,11 +4,14 @@ public class Board { public List Pieces { get; set; } + public Piece? Aggressor { get; set; } + public Piece? this[int x, int y] => Pieces.FirstOrDefault(piece => piece.XPosition == x && piece.YPosition == y); public Board() { + Aggressor = null; Pieces = new List { new() { NotationPosition ="A3", Color = PieceColor.Black}, diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index 6afdd1e8..f1d5a687 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -62,23 +62,14 @@ public static class Engine } if (outcome is not MoveOutcome.CaptureMoreAvailable) { - ResetCapturePiece(board); + board.Aggressor = null; } return outcome; } - private static void ResetCapturePiece(Board board) - { - Piece? capturePiece = GetAggressor(board); - if (capturePiece is not null) - { - capturePiece.Aggressor = false; - } - } - private static bool MoreCapturesAvailable(PieceColor side, Board board) { - Piece? aggressor = GetAggressor(board); + Piece? aggressor = board.Aggressor; if (aggressor is null) { return false; @@ -87,11 +78,6 @@ private static bool MoreCapturesAvailable(PieceColor side, Board board) return possibleMoves.Any(move => move.PieceToMove == aggressor && move.Capturing is not null); } - private static Piece? GetAggressor(Board board) - { - return board.Pieces.FirstOrDefault(piece => piece.Aggressor); - } - private static bool MoveIsValid((int X, int Y)? from, (int X, int Y) to, List possibleMoves, Board board, out Move? selectedMove) { selectedMove = default; @@ -110,7 +96,7 @@ private static bool MoveIsValid((int X, int Y)? from, (int X, int Y) to, List possibleMoves) { - Piece? aggressor = GetAggressor(board); + Piece? aggressor = board.Aggressor; MoveOutcome? result = null; possibleMoves = new List(); if (PlayingWithJustKings(side, board, out List? endGameMoves)) @@ -143,7 +129,7 @@ private static bool PerformCapture(PieceColor side, List possibleCaptures, } if (captureMove.PieceToMove is not null) { - captureMove.PieceToMove.Aggressor = true; + board.Aggressor = captureMove.PieceToMove; captureMove.PieceToMove.XPosition = captureMove.To.X; captureMove.PieceToMove.YPosition = captureMove.To.Y; } diff --git a/Projects/Checkers/Piece.cs b/Projects/Checkers/Piece.cs index ad85f301..34a10e48 100644 --- a/Projects/Checkers/Piece.cs +++ b/Projects/Checkers/Piece.cs @@ -15,6 +15,4 @@ public string NotationPosition public PieceColor Color { get; set; } public bool Promoted { get; set; } - - public bool Aggressor { get; set; } } From b4675fc917a579c078048b96c714b0d8dd3f0243 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 02:23:00 -0400 Subject: [PATCH 46/68] fix aggressor reset bug --- Projects/Checkers/Engine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index f1d5a687..94326710 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -60,7 +60,7 @@ public static class Engine } break; } - if (outcome is not MoveOutcome.CaptureMoreAvailable) + if (outcome is not null && outcome is not MoveOutcome.CaptureMoreAvailable) { board.Aggressor = null; } From 8eb2db8627164e0c3b8eea49d9557176e62f66e7 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 02:39:03 -0400 Subject: [PATCH 47/68] removed ProgramState --- Projects/Checkers/Enums/ProgramState.cs | 9 --------- Projects/Checkers/Program.cs | 25 +++---------------------- 2 files changed, 3 insertions(+), 31 deletions(-) delete mode 100644 Projects/Checkers/Enums/ProgramState.cs diff --git a/Projects/Checkers/Enums/ProgramState.cs b/Projects/Checkers/Enums/ProgramState.cs deleted file mode 100644 index 05082d2a..00000000 --- a/Projects/Checkers/Enums/ProgramState.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Checkers.Enums; - -public enum ProgramState -{ - IntroScreen, - GameInProgress, - GameOver, - Stopped -} diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 59aa21d4..4448bd1f 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -12,28 +12,9 @@ try { Console.OutputEncoding = Encoding.UTF8; - ProgramState gameState = ProgramState.IntroScreen; - - while (gameState is not ProgramState.Stopped) - { - switch (gameState) - { - case ProgramState.IntroScreen: - ShowIntroScreenAndGetOption(); - gameState = ProgramState.GameInProgress; - break; - case ProgramState.GameInProgress: - RunGameLoop(); - gameState = ProgramState.GameOver; - break; - case ProgramState.GameOver: - HandleGameOver(); - gameState = ProgramState.Stopped; - break; - default: - throw new NotImplementedException(); - } - } + ShowIntroScreenAndGetOption(); + RunGameLoop(); + HandleGameOver(); } finally { From 567db323fe7ab912c1320db5b69849352ca8b88f Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 02:42:50 -0400 Subject: [PATCH 48/68] moved VectorHelper into Engine --- Projects/Checkers/Engine.cs | 87 +++++++++++++++++++++- Projects/Checkers/Helpers/VectorHelper.cs | 91 ----------------------- Projects/Checkers/_using.cs | 4 +- 3 files changed, 86 insertions(+), 96 deletions(-) delete mode 100644 Projects/Checkers/Helpers/VectorHelper.cs diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index 94326710..ff0d43fc 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -243,7 +243,7 @@ private static bool PlayingWithJustKings(PieceColor side, Board board, out List< { (int X, int Y) kingPoint = (king.XPosition, king.YPosition); (int X, int Y) targetPoint = (target.XPosition, target.YPosition); - double distance = VectorHelper.GetPointDistance(kingPoint, targetPoint); + double distance = GetPointDistance(kingPoint, targetPoint); if (distance < shortestDistance) { shortestDistance = distance; @@ -254,7 +254,7 @@ private static bool PlayingWithJustKings(PieceColor side, Board board, out List< } if (currentHero is not null && currentVillain is not null) { - List<(int X, int Y)>? movementOptions = VectorHelper.WhereIsVillain(currentHero, currentVillain); + List<(int X, int Y)>? movementOptions = WhereIsVillain(currentHero, currentVillain); foreach ((int X, int Y) movementOption in movementOptions) { PieceColor? targetColor = board[movementOption.X, movementOption.Y]?.Color; @@ -271,4 +271,87 @@ private static bool PlayingWithJustKings(PieceColor side, Board board, out List< } return possibleMoves.Count > 0; } + + public static double GetPointDistance((int X, int Y) first, (int X, int Y) second) + { + // Easiest cases are points on the same vertical or horizontal axis + if (first.X == second.X) + { + return Math.Abs(first.Y - second.Y); + } + if (first.Y == second.Y) + { + return Math.Abs(first.X - second.X); + } + // Pythagoras baby + double sideA = Math.Abs(first.Y - second.Y); + double sideB = Math.Abs(first.X - second.X); + return Math.Sqrt(Math.Pow(sideA, 2) + Math.Pow(sideB, 2)); + } + + public static List<(int X, int Y)> WhereIsVillain(Piece hero, Piece villain) + { + List<(int X, int Y)>? retVal = new(); + List? directions = new(); + if (hero.XPosition > villain.XPosition) + { + directions.Add(Direction.Left); + } + if (hero.XPosition < villain.XPosition) + { + directions.Add(Direction.Right); + } + if (hero.YPosition > villain.YPosition) + { + directions.Add(Direction.Up); + } + if (hero.YPosition < villain.YPosition) + { + directions.Add(Direction.Down); + } + if (directions.Count == 1) + { + switch (directions[0]) + { + case Direction.Up: + retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); + retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); + break; + case Direction.Down: + retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); + retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); + break; + case Direction.Left: + retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); + retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); + break; + case Direction.Right: + retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); + retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); + break; + default: + throw new NotImplementedException(); + } + } + else + { + if (directions.Contains(Direction.Left) && directions.Contains(Direction.Up)) + { + retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); + } + if (directions.Contains(Direction.Left) && directions.Contains(Direction.Down)) + { + retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); + } + if (directions.Contains(Direction.Right) && directions.Contains(Direction.Up)) + { + retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); + } + if (directions.Contains(Direction.Right) && directions.Contains(Direction.Down)) + { + retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); + } + } + return retVal; + } } diff --git a/Projects/Checkers/Helpers/VectorHelper.cs b/Projects/Checkers/Helpers/VectorHelper.cs deleted file mode 100644 index 66b9aea9..00000000 --- a/Projects/Checkers/Helpers/VectorHelper.cs +++ /dev/null @@ -1,91 +0,0 @@ -namespace Checkers.Helpers; - -/// -/// Track distance between 2 points -/// Used in endgame for kings to hunt down closest victim -/// -public static class VectorHelper -{ - public static double GetPointDistance((int X, int Y) first, (int X, int Y) second) - { - // Easiest cases are points on the same vertical or horizontal axis - if (first.X == second.X) - { - return Math.Abs(first.Y - second.Y); - } - if (first.Y == second.Y) - { - return Math.Abs(first.X - second.X); - } - // Pythagoras baby - double sideA = Math.Abs(first.Y - second.Y); - double sideB = Math.Abs(first.X - second.X); - return Math.Sqrt(Math.Pow(sideA, 2) + Math.Pow(sideB, 2)); - } - - public static List<(int X, int Y)> WhereIsVillain(Piece hero, Piece villain) - { - List<(int X, int Y)>? retVal = new(); - List? directions = new(); - if (hero.XPosition > villain.XPosition) - { - directions.Add(Direction.Left); - } - if (hero.XPosition < villain.XPosition) - { - directions.Add(Direction.Right); - } - if (hero.YPosition > villain.YPosition) - { - directions.Add(Direction.Up); - } - if (hero.YPosition < villain.YPosition) - { - directions.Add(Direction.Down); - } - if (directions.Count == 1) - { - switch (directions[0]) - { - case Direction.Up: - retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); - retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); - break; - case Direction.Down: - retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); - retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); - break; - case Direction.Left: - retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); - retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); - break; - case Direction.Right: - retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); - retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); - break; - default: - throw new NotImplementedException(); - } - } - else - { - if (directions.Contains(Direction.Left) && directions.Contains(Direction.Up)) - { - retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); - } - if (directions.Contains(Direction.Left) && directions.Contains(Direction.Down)) - { - retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); - } - if (directions.Contains(Direction.Right) && directions.Contains(Direction.Up)) - { - retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); - } - if (directions.Contains(Direction.Right) && directions.Contains(Direction.Down)) - { - retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); - } - } - return retVal; - } -} diff --git a/Projects/Checkers/_using.cs b/Projects/Checkers/_using.cs index bf88c2bc..708bc081 100644 --- a/Projects/Checkers/_using.cs +++ b/Projects/Checkers/_using.cs @@ -1,6 +1,4 @@ global using System; global using System.Collections.Generic; global using System.Linq; - -global using Checkers.Helpers; -global using Checkers.Enums; \ No newline at end of file +global using Checkers.Enums; From c08b7db00ea7d57320f359902fca754443b6387f Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 02:44:45 -0400 Subject: [PATCH 49/68] renamed Piece XPosition & YPosition to X & Y --- Projects/Checkers/Board.cs | 2 +- Projects/Checkers/Engine.cs | 50 ++++++++++++++++++------------------ Projects/Checkers/Piece.cs | 8 +++--- Projects/Checkers/Program.cs | 2 +- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Projects/Checkers/Board.cs b/Projects/Checkers/Board.cs index 431e94cc..c2b179ea 100644 --- a/Projects/Checkers/Board.cs +++ b/Projects/Checkers/Board.cs @@ -7,7 +7,7 @@ public class Board public Piece? Aggressor { get; set; } public Piece? this[int x, int y] => - Pieces.FirstOrDefault(piece => piece.XPosition == x && piece.YPosition == y); + Pieces.FirstOrDefault(piece => piece.X == x && piece.Y == y); public Board() { diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index ff0d43fc..73624289 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -43,8 +43,8 @@ public static class Engine Piece pieceToMove = bestMove.PieceToMove!; int newX = bestMove.To.X; int newY = bestMove.To.Y; - pieceToMove.XPosition = newX; - pieceToMove.YPosition = newY; + pieceToMove.X = newX; + pieceToMove.Y = newY; // Promotion can only happen if not already a king and you have reached the far side if (newY is 0 or 7 && !pieceToMove.Promoted) { @@ -130,8 +130,8 @@ private static bool PerformCapture(PieceColor side, List possibleCaptures, if (captureMove.PieceToMove is not null) { board.Aggressor = captureMove.PieceToMove; - captureMove.PieceToMove.XPosition = captureMove.To.X; - captureMove.PieceToMove.YPosition = captureMove.To.Y; + captureMove.PieceToMove.X = captureMove.To.X; + captureMove.PieceToMove.Y = captureMove.To.Y; } } } @@ -145,7 +145,7 @@ private static bool CheckForPiecesToPromote(PieceColor currentSide, Board board) int promotionSpot = currentSide == PieceColor.White ? 0 : 7; foreach (Piece piece in board.Pieces.Where(piece => piece.Color == currentSide)) { - if (promotionSpot == piece.YPosition && !piece.Promoted) + if (promotionSpot == piece.Y && !piece.Promoted) { piece.Promoted = retVal = true; } @@ -202,7 +202,7 @@ void ValidateMove(int dx, int dy) { if (!piece.Promoted && color is PieceColor.Black && dy is -1) return; if (!piece.Promoted && color is PieceColor.White && dy is 1) return; - (int X, int Y) target = (piece.XPosition + dx, piece.YPosition + dy); + (int X, int Y) target = (piece.X + dx, piece.Y + dy); if (!Board.IsValidPosition(target.X, target.Y)) return; PieceColor? targetColor = board[target.X, target.Y]?.Color; if (targetColor is null) @@ -213,7 +213,7 @@ void ValidateMove(int dx, int dy) } else if (targetColor != color) { - (int X, int Y) jump = (piece.XPosition + 2 * dx, piece.YPosition + 2 * dy); + (int X, int Y) jump = (piece.X + 2 * dx, piece.Y + 2 * dy); if (!Board.IsValidPosition(jump.X, jump.Y)) return; PieceColor? jumpColor = board[jump.X, jump.Y]?.Color; if (jumpColor is not null) return; @@ -241,8 +241,8 @@ private static bool PlayingWithJustKings(PieceColor side, Board board, out List< { foreach (Piece target in board.Pieces.Where(piece => piece.Color != side)) { - (int X, int Y) kingPoint = (king.XPosition, king.YPosition); - (int X, int Y) targetPoint = (target.XPosition, target.YPosition); + (int X, int Y) kingPoint = (king.X, king.Y); + (int X, int Y) targetPoint = (target.X, target.Y); double distance = GetPointDistance(kingPoint, targetPoint); if (distance < shortestDistance) { @@ -293,19 +293,19 @@ public static double GetPointDistance((int X, int Y) first, (int X, int Y) secon { List<(int X, int Y)>? retVal = new(); List? directions = new(); - if (hero.XPosition > villain.XPosition) + if (hero.X > villain.X) { directions.Add(Direction.Left); } - if (hero.XPosition < villain.XPosition) + if (hero.X < villain.X) { directions.Add(Direction.Right); } - if (hero.YPosition > villain.YPosition) + if (hero.Y > villain.Y) { directions.Add(Direction.Up); } - if (hero.YPosition < villain.YPosition) + if (hero.Y < villain.Y) { directions.Add(Direction.Down); } @@ -314,20 +314,20 @@ public static double GetPointDistance((int X, int Y) first, (int X, int Y) secon switch (directions[0]) { case Direction.Up: - retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); - retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); + retVal.Add((hero.X - 1, hero.Y - 1)); + retVal.Add((hero.X + 1, hero.Y - 1)); break; case Direction.Down: - retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); - retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); + retVal.Add((hero.X - 1, hero.Y + 1)); + retVal.Add((hero.X + 1, hero.Y + 1)); break; case Direction.Left: - retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); - retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); + retVal.Add((hero.X - 1, hero.Y - 1)); + retVal.Add((hero.X - 1, hero.Y + 1)); break; case Direction.Right: - retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); - retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); + retVal.Add((hero.X + 1, hero.Y - 1)); + retVal.Add((hero.X + 1, hero.Y + 1)); break; default: throw new NotImplementedException(); @@ -337,19 +337,19 @@ public static double GetPointDistance((int X, int Y) first, (int X, int Y) secon { if (directions.Contains(Direction.Left) && directions.Contains(Direction.Up)) { - retVal.Add((hero.XPosition - 1, hero.YPosition - 1)); + retVal.Add((hero.X - 1, hero.Y - 1)); } if (directions.Contains(Direction.Left) && directions.Contains(Direction.Down)) { - retVal.Add((hero.XPosition - 1, hero.YPosition + 1)); + retVal.Add((hero.X - 1, hero.Y + 1)); } if (directions.Contains(Direction.Right) && directions.Contains(Direction.Up)) { - retVal.Add((hero.XPosition + 1, hero.YPosition - 1)); + retVal.Add((hero.X + 1, hero.Y - 1)); } if (directions.Contains(Direction.Right) && directions.Contains(Direction.Down)) { - retVal.Add((hero.XPosition + 1, hero.YPosition + 1)); + retVal.Add((hero.X + 1, hero.Y + 1)); } } return retVal; diff --git a/Projects/Checkers/Piece.cs b/Projects/Checkers/Piece.cs index 34a10e48..a189ccbc 100644 --- a/Projects/Checkers/Piece.cs +++ b/Projects/Checkers/Piece.cs @@ -2,14 +2,14 @@ public class Piece { - public int XPosition { get; set; } + public int X { get; set; } - public int YPosition { get; set; } + public int Y { get; set; } public string NotationPosition { - get => Board.ToPositionNotationString(XPosition, YPosition); - set => (XPosition, YPosition) = Board.ParsePositionNotation(value); + get => Board.ToPositionNotationString(X, Y); + set => (X, Y) = Board.ParsePositionNotation(value); } public PieceColor Color { get; set; } diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 4448bd1f..ac4cc708 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -130,7 +130,7 @@ void RenderGameState(Player? playerMoved = null, (int X, int Y)? selection = nul Dictionary<(int X, int Y), char> tiles = new(); foreach (Piece piece in game!.Board.Pieces) { - tiles[(piece.XPosition, piece.YPosition)] = ToChar(piece); + tiles[(piece.X, piece.Y)] = ToChar(piece); } char C(int x, int y) => (x, y) == selection ? '$' : tiles.GetValueOrDefault((x, y), '.'); StringBuilder sb = new(); From 9ff992f483c21ff40697409e19df6a66f609e4b9 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 02:58:22 -0400 Subject: [PATCH 50/68] adjustments to output --- Projects/Checkers/Program.cs | 47 ++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index ac4cc708..d94cc534 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -27,36 +27,37 @@ void ShowIntroScreenAndGetOption() { Console.Clear(); - Console.WriteLine("CHECKERS"); Console.WriteLine(); - Console.WriteLine("Checkers is played on an 8x8 board between two sides commonly known as black"); - Console.WriteLine("and white. The objective is simple - capture all your opponent's pieces. An"); - Console.WriteLine("alternative way to win is to trap your opponent so that they have no valid"); - Console.WriteLine("moves left."); + Console.WriteLine(" Checkers"); Console.WriteLine(); - Console.WriteLine("Black starts first and players take it in turns to move their pieces forward"); - Console.WriteLine("across the board diagonally. Should a piece reach the other side of the board"); - Console.WriteLine("the piece becomes a `king` and can then move diagonally backwards as well as"); - Console.WriteLine("forwards."); + Console.WriteLine(" Checkers is played on an 8x8 board between two sides commonly known as black"); + Console.WriteLine(" and white. The objective is simple - capture all your opponent's pieces. An"); + Console.WriteLine(" alternative way to win is to trap your opponent so that they have no valid"); + Console.WriteLine(" moves left."); Console.WriteLine(); - Console.WriteLine("Pieces are captured by jumping over them diagonally. More than one enemy piece"); - Console.WriteLine("can be captured in the same turn by the same piece."); + Console.WriteLine(" Black starts first and players take it in turns to move their pieces forward"); + Console.WriteLine(" across the board diagonally. Should a piece reach the other side of the board"); + Console.WriteLine(" the piece becomes a `king` and can then move diagonally backwards as well as"); + Console.WriteLine(" forwards."); Console.WriteLine(); - Console.WriteLine("Moves are selected with the arrow keys. Use the [enter] button to select the"); - Console.WriteLine("from and to squares. Invalid moves are ignored."); + Console.WriteLine(" Pieces are captured by jumping over them diagonally. More than one enemy piece"); + Console.WriteLine(" can be captured in the same turn by the same piece."); Console.WriteLine(); - Console.WriteLine("3 modes of play are possible depending on the number of players entered:"); + Console.WriteLine(" Moves are selected with the arrow keys. Use the [enter] button to select the"); + Console.WriteLine(" from and to squares. Invalid moves are ignored."); + Console.WriteLine(); + Console.WriteLine(" 3 modes of play are possible depending on the number of players entered:"); Console.WriteLine(" 0 - black and white are controlled by the computer"); Console.WriteLine(" 1 - black is controlled by the player and white by the computer"); Console.WriteLine(" 2 - allows 2 players"); Console.WriteLine(); - Console.Write("Enter the number of players (0-2): "); + Console.Write(" Enter the number of players (0-2): "); string? entry = Console.ReadLine()?.Trim(); while (entry is not "0" and not "1" and not "2") { - Console.WriteLine("Invalid Input. Try Again."); - Console.Write("Enter the number of players (0-2): "); + Console.WriteLine(" Invalid Input. Try Again."); + Console.Write(" Enter the number of players (0-2): "); entry = Console.ReadLine()?.Trim(); } int humanPlayerCount = entry[0] - '0'; @@ -115,7 +116,7 @@ void HandleGameOver() void PressAnyKeyToContinue() { - const string prompt = "Press any key to cotinue..."; + const string prompt = " Press any key to cotinue..."; (int left, int top) = (Console.CursorLeft, Console.CursorTop); Console.Write(prompt); Console.ReadKey(true); @@ -134,6 +135,9 @@ void RenderGameState(Player? playerMoved = null, (int X, int Y)? selection = nul } char C(int x, int y) => (x, y) == selection ? '$' : tiles.GetValueOrDefault((x, y), '.'); StringBuilder sb = new(); + sb.AppendLine(); + sb.AppendLine(" Checkers"); + sb.AppendLine(); sb.AppendLine($" ╔═══════════════════╗"); sb.AppendLine($" 8 ║ {C(0, 7)} {C(1, 7)} {C(2, 7)} {C(3, 7)} {C(4, 7)} {C(5, 7)} {C(6, 7)} {C(7, 7)} ║ {BlackPiece} = Black"); sb.AppendLine($" 7 ║ {C(0, 6)} {C(1, 6)} {C(2, 6)} {C(3, 6)} {C(4, 6)} {C(5, 6)} {C(6, 6)} {C(7, 6)} ║ {BlackKing} = Black King"); @@ -145,21 +149,22 @@ void RenderGameState(Player? playerMoved = null, (int X, int Y)? selection = nul sb.AppendLine($" 1 ║ {C(0, 0)} {C(1, 0)} {C(2, 0)} {C(3, 0)} {C(4, 0)} {C(5, 0)} {C(6, 0)} {C(7, 0)} ║ {game.TakenCount(PieceColor.Black),2} x {BlackPiece}"); sb.AppendLine($" ╚═══════════════════╝"); sb.AppendLine($" A B C D E F G H"); + sb.AppendLine(); if (selection is not null) { sb.Replace(" $ ", $"[{tiles.GetValueOrDefault(selection.Value, '.')}]"); } if (game.Winner is not null) { - sb.AppendLine($"*** {game.Winner} wins ***"); + sb.AppendLine($" *** {game.Winner} wins ***"); } else if (playerMoved is not null) { - sb.AppendLine($"{playerMoved.Color} moved"); + sb.AppendLine($" {playerMoved.Color} moved"); } else { - sb.AppendLine($"{game.Turn}'s turn"); + sb.AppendLine($" {game.Turn}'s turn"); } Console.Write(sb); } From cf4b4bd72744794c21e3c0f82b29539667af1617 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 03:04:08 -0400 Subject: [PATCH 51/68] removed Direction; enums are great, but if it is only used inside one method, then I think it is fine to just use const int values instead --- Projects/Checkers/Engine.cs | 31 ++++++++++++++++------------ Projects/Checkers/Enums/Direction.cs | 9 -------- 2 files changed, 18 insertions(+), 22 deletions(-) delete mode 100644 Projects/Checkers/Enums/Direction.cs diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index 73624289..25010d49 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -291,41 +291,46 @@ public static double GetPointDistance((int X, int Y) first, (int X, int Y) secon public static List<(int X, int Y)> WhereIsVillain(Piece hero, Piece villain) { + const int Up = 1; + const int Down = 2; + const int Left = 3; + const int Right = 4; + List<(int X, int Y)>? retVal = new(); - List? directions = new(); + List directions = new(); if (hero.X > villain.X) { - directions.Add(Direction.Left); + directions.Add(Left); } if (hero.X < villain.X) { - directions.Add(Direction.Right); + directions.Add(Right); } if (hero.Y > villain.Y) { - directions.Add(Direction.Up); + directions.Add(Up); } if (hero.Y < villain.Y) { - directions.Add(Direction.Down); + directions.Add(Down); } if (directions.Count == 1) { switch (directions[0]) { - case Direction.Up: + case Up: retVal.Add((hero.X - 1, hero.Y - 1)); retVal.Add((hero.X + 1, hero.Y - 1)); break; - case Direction.Down: + case Down: retVal.Add((hero.X - 1, hero.Y + 1)); retVal.Add((hero.X + 1, hero.Y + 1)); break; - case Direction.Left: + case Left: retVal.Add((hero.X - 1, hero.Y - 1)); retVal.Add((hero.X - 1, hero.Y + 1)); break; - case Direction.Right: + case Right: retVal.Add((hero.X + 1, hero.Y - 1)); retVal.Add((hero.X + 1, hero.Y + 1)); break; @@ -335,19 +340,19 @@ public static double GetPointDistance((int X, int Y) first, (int X, int Y) secon } else { - if (directions.Contains(Direction.Left) && directions.Contains(Direction.Up)) + if (directions.Contains(Left) && directions.Contains(Up)) { retVal.Add((hero.X - 1, hero.Y - 1)); } - if (directions.Contains(Direction.Left) && directions.Contains(Direction.Down)) + if (directions.Contains(Left) && directions.Contains(Down)) { retVal.Add((hero.X - 1, hero.Y + 1)); } - if (directions.Contains(Direction.Right) && directions.Contains(Direction.Up)) + if (directions.Contains(Right) && directions.Contains(Up)) { retVal.Add((hero.X + 1, hero.Y - 1)); } - if (directions.Contains(Direction.Right) && directions.Contains(Direction.Down)) + if (directions.Contains(Right) && directions.Contains(Down)) { retVal.Add((hero.X + 1, hero.Y + 1)); } diff --git a/Projects/Checkers/Enums/Direction.cs b/Projects/Checkers/Enums/Direction.cs deleted file mode 100644 index 072ca49e..00000000 --- a/Projects/Checkers/Enums/Direction.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Checkers.Enums; - -public enum Direction -{ - Up = 1, - Down = 2, - Left = 3, - Right = 4, -} From 28c4979288f00a1f67f74422b937b6c6e28e8cdb Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 03:09:46 -0400 Subject: [PATCH 52/68] removed PlayerControl; enums are great, but if there are only 2 values, a bool value is usually sufficient --- Projects/Checkers/Enums/PlayerControl.cs | 7 ------- Projects/Checkers/Game.cs | 4 ++-- Projects/Checkers/Player.cs | 6 +++--- Projects/Checkers/Program.cs | 2 +- 4 files changed, 6 insertions(+), 13 deletions(-) delete mode 100644 Projects/Checkers/Enums/PlayerControl.cs diff --git a/Projects/Checkers/Enums/PlayerControl.cs b/Projects/Checkers/Enums/PlayerControl.cs deleted file mode 100644 index d0c5eea4..00000000 --- a/Projects/Checkers/Enums/PlayerControl.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Checkers.Enums; - -public enum PlayerControl -{ - Human, - Computer -} diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index c467a45e..6fbc862c 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -15,8 +15,8 @@ public Game(int humanPlayerCount) Board = new Board(); Players = new() { - new Player(humanPlayerCount < 1 ? PlayerControl.Computer : PlayerControl.Human, PieceColor.Black), - new Player(humanPlayerCount < 2 ? PlayerControl.Computer : PlayerControl.Human, PieceColor.White), + new Player(humanPlayerCount >= 1, PieceColor.Black), + new Player(humanPlayerCount >= 2, PieceColor.White), }; Turn = PieceColor.Black; Winner = null; diff --git a/Projects/Checkers/Player.cs b/Projects/Checkers/Player.cs index e6d1f849..63f7103f 100644 --- a/Projects/Checkers/Player.cs +++ b/Projects/Checkers/Player.cs @@ -2,12 +2,12 @@ public class Player { - public PlayerControl ControlledBy { get; private set; } + public bool IsHuman { get; private set; } public PieceColor Color { get; private set; } - public Player(PlayerControl controlledBy, PieceColor color) + public Player(bool isHuman, PieceColor color) { - ControlledBy = controlledBy; + IsHuman = isHuman; Color = color; } } diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index d94cc534..ecc36e00 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -69,7 +69,7 @@ void RunGameLoop() while (game!.Winner is null) { Player? currentPlayer = game.Players.FirstOrDefault(player => player.Color == game.Turn); - if (currentPlayer is not null && currentPlayer.ControlledBy == PlayerControl.Human) + if (currentPlayer is not null && currentPlayer.IsHuman) { while (game.Turn == currentPlayer.Color) { From aa1ddf68eae68966bf2af5fdd1579db931719d92 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 03:11:39 -0400 Subject: [PATCH 53/68] moved enums out of Enums folder (if there are only two types in there I don't think a folder is necessary) --- Projects/Checkers/{Enums => }/MoveOutcome.cs | 2 +- Projects/Checkers/{Enums => }/PieceColor.cs | 2 +- Projects/Checkers/_using.cs | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) rename Projects/Checkers/{Enums => }/MoveOutcome.cs (89%) rename Projects/Checkers/{Enums => }/PieceColor.cs (64%) diff --git a/Projects/Checkers/Enums/MoveOutcome.cs b/Projects/Checkers/MoveOutcome.cs similarity index 89% rename from Projects/Checkers/Enums/MoveOutcome.cs rename to Projects/Checkers/MoveOutcome.cs index 52b29271..69cfde2f 100644 --- a/Projects/Checkers/Enums/MoveOutcome.cs +++ b/Projects/Checkers/MoveOutcome.cs @@ -1,4 +1,4 @@ -namespace Checkers.Enums; +namespace Checkers; public enum MoveOutcome { diff --git a/Projects/Checkers/Enums/PieceColor.cs b/Projects/Checkers/PieceColor.cs similarity index 64% rename from Projects/Checkers/Enums/PieceColor.cs rename to Projects/Checkers/PieceColor.cs index fde5ee7b..b3d48ef3 100644 --- a/Projects/Checkers/Enums/PieceColor.cs +++ b/Projects/Checkers/PieceColor.cs @@ -1,4 +1,4 @@ -namespace Checkers.Enums; +namespace Checkers; public enum PieceColor { diff --git a/Projects/Checkers/_using.cs b/Projects/Checkers/_using.cs index 708bc081..22346601 100644 --- a/Projects/Checkers/_using.cs +++ b/Projects/Checkers/_using.cs @@ -1,4 +1,3 @@ global using System; global using System.Collections.Generic; global using System.Linq; -global using Checkers.Enums; From c883903b68aa85c53f51636a0d72d166f8d0b7b5 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 19:58:30 -0400 Subject: [PATCH 54/68] global using static Black/White --- Projects/Checkers/Board.cs | 48 ++++++++++++++++++------------------ Projects/Checkers/Engine.cs | 14 +++++------ Projects/Checkers/Game.cs | 26 +++++++++---------- Projects/Checkers/Program.cs | 17 ++++++------- Projects/Checkers/_using.cs | 9 +++++++ 5 files changed, 60 insertions(+), 54 deletions(-) diff --git a/Projects/Checkers/Board.cs b/Projects/Checkers/Board.cs index c2b179ea..4d6d53d8 100644 --- a/Projects/Checkers/Board.cs +++ b/Projects/Checkers/Board.cs @@ -14,31 +14,31 @@ public Board() Aggressor = null; Pieces = new List { - new() { NotationPosition ="A3", Color = PieceColor.Black}, - new() { NotationPosition ="A1", Color = PieceColor.Black}, - new() { NotationPosition ="B2", Color = PieceColor.Black}, - new() { NotationPosition ="C3", Color = PieceColor.Black}, - new() { NotationPosition ="C1", Color = PieceColor.Black}, - new() { NotationPosition ="D2", Color = PieceColor.Black}, - new() { NotationPosition ="E3", Color = PieceColor.Black}, - new() { NotationPosition ="E1", Color = PieceColor.Black}, - new() { NotationPosition ="F2", Color = PieceColor.Black}, - new() { NotationPosition ="G3", Color = PieceColor.Black}, - new() { NotationPosition ="G1", Color = PieceColor.Black}, - new() { NotationPosition ="H2", Color = PieceColor.Black}, + 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 = PieceColor.White}, - new() { NotationPosition ="B8", Color = PieceColor.White}, - new() { NotationPosition ="B6", Color = PieceColor.White}, - new() { NotationPosition ="C7", Color = PieceColor.White}, - new() { NotationPosition ="D8", Color = PieceColor.White}, - new() { NotationPosition ="D6", Color = PieceColor.White}, - new() { NotationPosition ="E7", Color = PieceColor.White}, - new() { NotationPosition ="F8", Color = PieceColor.White}, - new() { NotationPosition ="F6", Color = PieceColor.White}, - new() { NotationPosition ="G7", Color = PieceColor.White}, - new() { NotationPosition ="H8", Color = PieceColor.White}, - new() { NotationPosition ="H6", Color = PieceColor.White} + 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} }; } diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index 25010d49..38ec6195 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -31,9 +31,9 @@ public static class Engine outcome = AnalysePosition(side, board, out possibleMoves); } // If a side can't play then other side wins - if (outcome == MoveOutcome.NoMoveAvailable) + if (outcome is MoveOutcome.NoMoveAvailable) { - outcome = side == PieceColor.Black ? MoveOutcome.WhiteWin : MoveOutcome.BlackWin; + outcome = side is Black ? MoveOutcome.WhiteWin : MoveOutcome.BlackWin; } switch (outcome) { @@ -142,7 +142,7 @@ private static bool PerformCapture(PieceColor side, List possibleCaptures, private static bool CheckForPiecesToPromote(PieceColor currentSide, Board board) { bool retVal = false; - int promotionSpot = currentSide == PieceColor.White ? 0 : 7; + int promotionSpot = currentSide is White ? 0 : 7; foreach (Piece piece in board.Pieces.Where(piece => piece.Color == currentSide)) { if (promotionSpot == piece.Y && !piece.Promoted) @@ -165,7 +165,7 @@ private static MoveOutcome AnalysePosition(PieceColor side, Board board, out Lis return result; } GetPossibleMovesAndAttacks(side, board, out List? nonEndGameMoves); - if (nonEndGameMoves.Count == 0) + if (nonEndGameMoves.Count is 0) { result = MoveOutcome.NoMoveAvailable; } @@ -200,8 +200,8 @@ private static void GetPossibleMovesAndAttacks(PieceColor color, Board board, ou void ValidateMove(int dx, int dy) { - if (!piece.Promoted && color is PieceColor.Black && dy is -1) return; - if (!piece.Promoted && color is PieceColor.White && dy is 1) return; + if (!piece.Promoted && color is Black && dy is -1) return; + if (!piece.Promoted && color is White && dy is 1) return; (int X, int Y) target = (piece.X + dx, piece.Y + dy); if (!Board.IsValidPosition(target.X, target.Y)) return; PieceColor? targetColor = board[target.X, target.Y]?.Color; @@ -314,7 +314,7 @@ public static double GetPointDistance((int X, int Y) first, (int X, int Y) secon { directions.Add(Down); } - if (directions.Count == 1) + if (directions.Count is 1) { switch (directions[0]) { diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index 6fbc862c..7e8b4f29 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -15,10 +15,10 @@ public Game(int humanPlayerCount) Board = new Board(); Players = new() { - new Player(humanPlayerCount >= 1, PieceColor.Black), - new Player(humanPlayerCount >= 2, PieceColor.White), + new Player(humanPlayerCount >= 1, Black), + new Player(humanPlayerCount >= 2, White), }; - Turn = PieceColor.Black; + Turn = Black; Winner = null; } @@ -29,34 +29,34 @@ public void NextTurn((int X, int Y)? from = null, (int X, int Y)? to = null) { moveOutcome = Engine.PlayNextMove(Turn, Board, from, to); } - if (moveOutcome == MoveOutcome.BlackWin) + if (moveOutcome is MoveOutcome.BlackWin) { - Winner = PieceColor.Black; + Winner = Black; } - if (moveOutcome == MoveOutcome.WhiteWin) + if (moveOutcome is MoveOutcome.WhiteWin) { - Winner = PieceColor.White; + Winner = White; } if (Winner is null && moveOutcome is not MoveOutcome.CaptureMoreAvailable) { CheckSidesHavePiecesLeft(); - Turn = Turn == PieceColor.Black ? PieceColor.White : PieceColor.Black; + Turn = Turn is Black ? White : Black; } if (moveOutcome is null) { - Turn = Turn is PieceColor.Black ? PieceColor.White : PieceColor.Black; + Turn = Turn is Black ? White : Black; } } public void CheckSidesHavePiecesLeft() { - if (!Board.Pieces.Any(piece => piece.Color is PieceColor.Black)) + if (!Board.Pieces.Any(piece => piece.Color is Black)) { - Winner = PieceColor.White; + Winner = White; } - if (!Board.Pieces.Any(piece => piece.Color is PieceColor.White)) + if (!Board.Pieces.Any(piece => piece.Color is White)) { - Winner = PieceColor.Black; + Winner = Black; } } diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index ecc36e00..eecd56fe 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -1,7 +1,4 @@ -using Checkers; -using System.Text; - -const char BlackPiece = '○'; +const char BlackPiece = '○'; const char BlackKing = '☺'; const char WhitePiece = '◙'; const char WhiteKing = '☻'; @@ -145,8 +142,8 @@ void RenderGameState(Player? playerMoved = null, (int X, int Y)? selection = nul sb.AppendLine($" 5 ║ {C(0, 4)} {C(1, 4)} {C(2, 4)} {C(3, 4)} {C(4, 4)} {C(5, 4)} {C(6, 4)} {C(7, 4)} ║ {WhiteKing} = White King"); sb.AppendLine($" 4 ║ {C(0, 3)} {C(1, 3)} {C(2, 3)} {C(3, 3)} {C(4, 3)} {C(5, 3)} {C(6, 3)} {C(7, 3)} ║"); sb.AppendLine($" 3 ║ {C(0, 2)} {C(1, 2)} {C(2, 2)} {C(3, 2)} {C(4, 2)} {C(5, 2)} {C(6, 2)} {C(7, 2)} ║ Taken:"); - sb.AppendLine($" 2 ║ {C(0, 1)} {C(1, 1)} {C(2, 1)} {C(3, 1)} {C(4, 1)} {C(5, 1)} {C(6, 1)} {C(7, 1)} ║ {game.TakenCount(PieceColor.White),2} x {WhitePiece}"); - sb.AppendLine($" 1 ║ {C(0, 0)} {C(1, 0)} {C(2, 0)} {C(3, 0)} {C(4, 0)} {C(5, 0)} {C(6, 0)} {C(7, 0)} ║ {game.TakenCount(PieceColor.Black),2} x {BlackPiece}"); + sb.AppendLine($" 2 ║ {C(0, 1)} {C(1, 1)} {C(2, 1)} {C(3, 1)} {C(4, 1)} {C(5, 1)} {C(6, 1)} {C(7, 1)} ║ {game.TakenCount(White),2} x {WhitePiece}"); + sb.AppendLine($" 1 ║ {C(0, 0)} {C(1, 0)} {C(2, 0)} {C(3, 0)} {C(4, 0)} {C(5, 0)} {C(6, 0)} {C(7, 0)} ║ {game.TakenCount(Black),2} x {BlackPiece}"); sb.AppendLine($" ╚═══════════════════╝"); sb.AppendLine($" A B C D E F G H"); sb.AppendLine(); @@ -172,10 +169,10 @@ void RenderGameState(Player? playerMoved = null, (int X, int Y)? selection = nul static char ToChar(Piece piece) => (piece.Color, piece.Promoted) switch { - (PieceColor.Black, false) => BlackPiece, - (PieceColor.Black, true) => BlackKing, - (PieceColor.White, false) => WhitePiece, - (PieceColor.White, true) => WhiteKing, + (Black, false) => BlackPiece, + (Black, true) => BlackKing, + (White, false) => WhitePiece, + (White, true) => WhiteKing, _ => throw new NotImplementedException(), }; diff --git a/Projects/Checkers/_using.cs b/Projects/Checkers/_using.cs index 22346601..30586f36 100644 --- a/Projects/Checkers/_using.cs +++ b/Projects/Checkers/_using.cs @@ -1,3 +1,12 @@ global using System; global using System.Collections.Generic; global using System.Linq; +global using System.Text; +global using Checkers; +global using static _using; + +public static class _using +{ + public const PieceColor Black = PieceColor.Black; + public const PieceColor White = PieceColor.White; +} From 6757b2a879941f772043babfa8a4b3b404578fa9 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 20:03:02 -0400 Subject: [PATCH 55/68] removed GetPointDistance; the distance between points is only used for comparison purposes to find the closets rivals, so you can leave the distances squared as there is no need to perform the costly square root function --- Projects/Checkers/Engine.cs | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index 38ec6195..af0b1092 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -234,19 +234,18 @@ private static bool PlayingWithJustKings(PieceColor side, Board board, out List< bool playingWithJustKings = piecesInPlay == kingsInPlay; if (playingWithJustKings) { - double shortestDistance = 12.0; + double minDistanceSquared = double.MaxValue; Piece? currentHero = null; Piece? currentVillain = null; foreach (Piece king in board.Pieces.Where(piece => piece.Color == side)) { foreach (Piece target in board.Pieces.Where(piece => piece.Color != side)) { - (int X, int Y) kingPoint = (king.X, king.Y); - (int X, int Y) targetPoint = (target.X, target.Y); - double distance = GetPointDistance(kingPoint, targetPoint); - if (distance < shortestDistance) + (int X, int Y) vector = (king.X - target.X, king.Y - target.Y); + double distanceSquared = vector.X * vector.X + vector.Y * vector.Y; + if (distanceSquared < minDistanceSquared) { - shortestDistance = distance; + minDistanceSquared = distanceSquared; currentHero = king; currentVillain = target; } @@ -272,23 +271,6 @@ private static bool PlayingWithJustKings(PieceColor side, Board board, out List< return possibleMoves.Count > 0; } - public static double GetPointDistance((int X, int Y) first, (int X, int Y) second) - { - // Easiest cases are points on the same vertical or horizontal axis - if (first.X == second.X) - { - return Math.Abs(first.Y - second.Y); - } - if (first.Y == second.Y) - { - return Math.Abs(first.X - second.X); - } - // Pythagoras baby - double sideA = Math.Abs(first.Y - second.Y); - double sideB = Math.Abs(first.X - second.X); - return Math.Sqrt(Math.Pow(sideA, 2) + Math.Pow(sideB, 2)); - } - public static List<(int X, int Y)> WhereIsVillain(Piece hero, Piece villain) { const int Up = 1; From 1e70415e6cb5c44ef606daf2cfdf6df0560f54f0 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 20:24:06 -0400 Subject: [PATCH 56/68] "side" -> "color" --- Projects/Checkers/Engine.cs | 52 ++++++++++++++++++------------------- Projects/Checkers/Game.cs | 4 +-- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs index af0b1092..cb80cda3 100644 --- a/Projects/Checkers/Engine.cs +++ b/Projects/Checkers/Engine.cs @@ -2,13 +2,13 @@ public static class Engine { - public static MoveOutcome? PlayNextMove(PieceColor side, Board board, (int X, int Y)? from = null, (int X, int Y)? to = null) + public static MoveOutcome? PlayNextMove(PieceColor color, Board board, (int X, int Y)? from = null, (int X, int Y)? to = null) { List possibleMoves; MoveOutcome? outcome; if (from is not null && to is not null) { - outcome = GetAllPossiblePlayerMoves(side, board, out possibleMoves); + outcome = GetAllPossiblePlayerMoves(color, board, out possibleMoves); if (possibleMoves.Count is 0) { outcome = MoveOutcome.NoMoveAvailable; @@ -28,12 +28,12 @@ public static class Engine } else { - outcome = AnalysePosition(side, board, out possibleMoves); + outcome = AnalysePosition(color, board, out possibleMoves); } - // If a side can't play then other side wins + // If a color can't play then other color wins if (outcome is MoveOutcome.NoMoveAvailable) { - outcome = side is Black ? MoveOutcome.WhiteWin : MoveOutcome.BlackWin; + outcome = color is Black ? MoveOutcome.WhiteWin : MoveOutcome.BlackWin; } switch (outcome) { @@ -45,15 +45,15 @@ public static class Engine int newY = bestMove.To.Y; pieceToMove.X = newX; pieceToMove.Y = newY; - // Promotion can only happen if not already a king and you have reached the far side + // Promotion can only happen if not already a king and you have reached the far color if (newY is 0 or 7 && !pieceToMove.Promoted) { pieceToMove.Promoted = true; } break; case MoveOutcome.Capture: - bool anyPromoted = PerformCapture(side, possibleMoves, board); - bool moreAvailable = MoreCapturesAvailable(side, board); + bool anyPromoted = PerformCapture(color, possibleMoves, board); + bool moreAvailable = MoreCapturesAvailable(color, board); if (moreAvailable && !anyPromoted) { outcome = MoveOutcome.CaptureMoreAvailable; @@ -67,14 +67,14 @@ public static class Engine return outcome; } - private static bool MoreCapturesAvailable(PieceColor side, Board board) + private static bool MoreCapturesAvailable(PieceColor color, Board board) { Piece? aggressor = board.Aggressor; if (aggressor is null) { return false; } - _ = GetAllPossiblePlayerMoves(side, board, out List? possibleMoves); + _ = GetAllPossiblePlayerMoves(color, board, out List? possibleMoves); return possibleMoves.Any(move => move.PieceToMove == aggressor && move.Capturing is not null); } @@ -94,17 +94,17 @@ private static bool MoveIsValid((int X, int Y)? from, (int X, int Y) to, List possibleMoves) + private static MoveOutcome? GetAllPossiblePlayerMoves(PieceColor color, Board board, out List possibleMoves) { Piece? aggressor = board.Aggressor; MoveOutcome? result = null; possibleMoves = new List(); - if (PlayingWithJustKings(side, board, out List? endGameMoves)) + if (PlayingWithJustKings(color, board, out List? endGameMoves)) { result = MoveOutcome.EndGame; possibleMoves.AddRange(endGameMoves); } - GetPossibleMovesAndAttacks(side, board, out List? nonEndGameMoves); + GetPossibleMovesAndAttacks(color, board, out List? nonEndGameMoves); possibleMoves.AddRange(nonEndGameMoves); if (aggressor is not null) { @@ -114,7 +114,7 @@ private static bool MoveIsValid((int X, int Y)? from, (int X, int Y) to, List possibleCaptures, Board board) + private static bool PerformCapture(PieceColor color, List possibleCaptures, Board board) { Move? captureMove = possibleCaptures.FirstOrDefault(move => move.Capturing is not null); if (captureMove is not null) @@ -135,15 +135,15 @@ private static bool PerformCapture(PieceColor side, List possibleCaptures, } } } - bool anyPromoted = CheckForPiecesToPromote(side, board); + bool anyPromoted = CheckForPiecesToPromote(color, board); return anyPromoted; } - private static bool CheckForPiecesToPromote(PieceColor currentSide, Board board) + private static bool CheckForPiecesToPromote(PieceColor color, Board board) { bool retVal = false; - int promotionSpot = currentSide is White ? 0 : 7; - foreach (Piece piece in board.Pieces.Where(piece => piece.Color == currentSide)) + int promotionSpot = color is White ? 0 : 7; + foreach (Piece piece in board.Pieces.Where(piece => piece.Color == color)) { if (promotionSpot == piece.Y && !piece.Promoted) { @@ -153,18 +153,18 @@ private static bool CheckForPiecesToPromote(PieceColor currentSide, Board board) return retVal; } - private static MoveOutcome AnalysePosition(PieceColor side, Board board, out List possibleMoves) + private static MoveOutcome AnalysePosition(PieceColor color, Board board, out List possibleMoves) { MoveOutcome result; possibleMoves = new List(); // Check for endgame first - if (PlayingWithJustKings(side, board, out List? endGameMoves)) + if (PlayingWithJustKings(color, board, out List? endGameMoves)) { result = MoveOutcome.EndGame; possibleMoves.AddRange(endGameMoves); return result; } - GetPossibleMovesAndAttacks(side, board, out List? nonEndGameMoves); + GetPossibleMovesAndAttacks(color, board, out List? nonEndGameMoves); if (nonEndGameMoves.Count is 0) { result = MoveOutcome.NoMoveAvailable; @@ -226,20 +226,20 @@ void ValidateMove(int dx, int dy) possibleMoves = moves; } - private static bool PlayingWithJustKings(PieceColor side, Board board, out List possibleMoves) + private static bool PlayingWithJustKings(PieceColor color, Board board, out List possibleMoves) { possibleMoves = new List(); - int piecesInPlay = board.Pieces.Count(piece => piece.Color == side); - int kingsInPlay = board.Pieces.Count(piece => piece.Color == side && piece.Promoted); + int piecesInPlay = board.Pieces.Count(piece => piece.Color == color); + int kingsInPlay = board.Pieces.Count(piece => piece.Color == color && piece.Promoted); bool playingWithJustKings = piecesInPlay == kingsInPlay; if (playingWithJustKings) { double minDistanceSquared = double.MaxValue; Piece? currentHero = null; Piece? currentVillain = null; - foreach (Piece king in board.Pieces.Where(piece => piece.Color == side)) + foreach (Piece king in board.Pieces.Where(piece => piece.Color == color)) { - foreach (Piece target in board.Pieces.Where(piece => piece.Color != side)) + foreach (Piece target in board.Pieces.Where(piece => piece.Color != color)) { (int X, int Y) vector = (king.X - target.X, king.Y - target.Y); double distanceSquared = vector.X * vector.X + vector.Y * vector.Y; diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index 7e8b4f29..c7ebb34c 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -39,7 +39,7 @@ public void NextTurn((int X, int Y)? from = null, (int X, int Y)? to = null) } if (Winner is null && moveOutcome is not MoveOutcome.CaptureMoreAvailable) { - CheckSidesHavePiecesLeft(); + CheckColorsHavePiecesLeft(); Turn = Turn is Black ? White : Black; } if (moveOutcome is null) @@ -48,7 +48,7 @@ public void NextTurn((int X, int Y)? from = null, (int X, int Y)? to = null) } } - public void CheckSidesHavePiecesLeft() + public void CheckColorsHavePiecesLeft() { if (!Board.Pieces.Any(piece => piece.Color is Black)) { From 6515f2caf5357127054b25010ad5990ff17cc76e Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 20:25:45 -0400 Subject: [PATCH 57/68] =?UTF-8?q?'.'=20->=20'=C2=B7'=20(period=20to=20midd?= =?UTF-8?q?le=20dot=20for=20vacant=20tiles)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Checkers/Program.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index eecd56fe..23575661 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -2,6 +2,7 @@ const char BlackKing = '☺'; const char WhitePiece = '◙'; const char WhiteKing = '☻'; +const char Vacant = '·'; Encoding encoding = Console.OutputEncoding; Game? game = null; @@ -130,7 +131,7 @@ void RenderGameState(Player? playerMoved = null, (int X, int Y)? selection = nul { tiles[(piece.X, piece.Y)] = ToChar(piece); } - char C(int x, int y) => (x, y) == selection ? '$' : tiles.GetValueOrDefault((x, y), '.'); + char C(int x, int y) => (x, y) == selection ? '$' : tiles.GetValueOrDefault((x, y), Vacant); StringBuilder sb = new(); sb.AppendLine(); sb.AppendLine(" Checkers"); @@ -149,7 +150,7 @@ void RenderGameState(Player? playerMoved = null, (int X, int Y)? selection = nul sb.AppendLine(); if (selection is not null) { - sb.Replace(" $ ", $"[{tiles.GetValueOrDefault(selection.Value, '.')}]"); + sb.Replace(" $ ", $"[{tiles.GetValueOrDefault(selection.Value, Vacant)}]"); } if (game.Winner is not null) { From e54af78e4d431906843d11ab39fd2e8019283cb3 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 21:07:26 -0400 Subject: [PATCH 58/68] clean up Program.cs --- Projects/Checkers/Program.cs | 131 ++++++++++++++++------------------- 1 file changed, 58 insertions(+), 73 deletions(-) diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 23575661..67fc50bf 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -1,18 +1,13 @@ -const char BlackPiece = '○'; -const char BlackKing = '☺'; -const char WhitePiece = '◙'; -const char WhiteKing = '☻'; -const char Vacant = '·'; - -Encoding encoding = Console.OutputEncoding; -Game? game = null; +Encoding encoding = Console.OutputEncoding; try { Console.OutputEncoding = Encoding.UTF8; - ShowIntroScreenAndGetOption(); - RunGameLoop(); - HandleGameOver(); + Game game = ShowIntroScreenAndGetOption(); + Console.Clear(); + RunGameLoop(game); + RenderGameState(game, promptPressKey: true); + Console.ReadKey(true); } finally { @@ -22,7 +17,7 @@ Console.Write("Checkers was closed."); } -void ShowIntroScreenAndGetOption() +Game ShowIntroScreenAndGetOption() { Console.Clear(); Console.WriteLine(); @@ -59,10 +54,10 @@ void ShowIntroScreenAndGetOption() entry = Console.ReadLine()?.Trim(); } int humanPlayerCount = entry[0] - '0'; - game = new Game(humanPlayerCount); + return new Game(humanPlayerCount); } -void RunGameLoop() +void RunGameLoop(Game game) { while (game!.Winner is null) { @@ -77,9 +72,9 @@ void RunGameLoop() { while (from is null) { - from = HumanMoveSelection(); + from = HumanMoveSelection(game); } - to = HumanMoveSelection(selectionStart: from); + to = HumanMoveSelection(game, selectionStart: from); } Piece? piece = null; if (from is not null) @@ -102,87 +97,77 @@ void RunGameLoop() game.NextTurn(); } - RenderGameState(playerMoved: currentPlayer); - PressAnyKeyToContinue(); + RenderGameState(game, playerMoved: currentPlayer, promptPressKey: true); + Console.ReadKey(true); } } -void HandleGameOver() +void RenderGameState(Game game, Player? playerMoved = null, (int X, int Y)? selection = null, bool promptPressKey = false) { - RenderGameState(); -} + const char BlackPiece = '○'; + const char BlackKing = '☺'; + const char WhitePiece = '◙'; + const char WhiteKing = '☻'; + const char Vacant = '·'; -void PressAnyKeyToContinue() -{ - const string prompt = " Press any key to cotinue..."; - (int left, int top) = (Console.CursorLeft, Console.CursorTop); - Console.Write(prompt); - Console.ReadKey(true); - Console.SetCursorPosition(left, top); - Console.Write(new string(' ', prompt.Length)); -} - -void RenderGameState(Player? playerMoved = null, (int X, int Y)? selection = null) -{ Console.CursorVisible = false; - Console.Clear(); - Dictionary<(int X, int Y), char> tiles = new(); - foreach (Piece piece in game!.Board.Pieces) - { - tiles[(piece.X, piece.Y)] = ToChar(piece); - } - char C(int x, int y) => (x, y) == selection ? '$' : tiles.GetValueOrDefault((x, y), Vacant); + Console.SetCursorPosition(0, 0); + char B(int x, int y) => (x, y) == selection ? '$' : ToChar(game.Board[x, y]); StringBuilder sb = new(); sb.AppendLine(); sb.AppendLine(" Checkers"); sb.AppendLine(); sb.AppendLine($" ╔═══════════════════╗"); - sb.AppendLine($" 8 ║ {C(0, 7)} {C(1, 7)} {C(2, 7)} {C(3, 7)} {C(4, 7)} {C(5, 7)} {C(6, 7)} {C(7, 7)} ║ {BlackPiece} = Black"); - sb.AppendLine($" 7 ║ {C(0, 6)} {C(1, 6)} {C(2, 6)} {C(3, 6)} {C(4, 6)} {C(5, 6)} {C(6, 6)} {C(7, 6)} ║ {BlackKing} = Black King"); - sb.AppendLine($" 6 ║ {C(0, 5)} {C(1, 5)} {C(2, 5)} {C(3, 5)} {C(4, 5)} {C(5, 5)} {C(6, 5)} {C(7, 5)} ║ {WhitePiece} = White"); - sb.AppendLine($" 5 ║ {C(0, 4)} {C(1, 4)} {C(2, 4)} {C(3, 4)} {C(4, 4)} {C(5, 4)} {C(6, 4)} {C(7, 4)} ║ {WhiteKing} = White King"); - sb.AppendLine($" 4 ║ {C(0, 3)} {C(1, 3)} {C(2, 3)} {C(3, 3)} {C(4, 3)} {C(5, 3)} {C(6, 3)} {C(7, 3)} ║"); - sb.AppendLine($" 3 ║ {C(0, 2)} {C(1, 2)} {C(2, 2)} {C(3, 2)} {C(4, 2)} {C(5, 2)} {C(6, 2)} {C(7, 2)} ║ Taken:"); - sb.AppendLine($" 2 ║ {C(0, 1)} {C(1, 1)} {C(2, 1)} {C(3, 1)} {C(4, 1)} {C(5, 1)} {C(6, 1)} {C(7, 1)} ║ {game.TakenCount(White),2} x {WhitePiece}"); - sb.AppendLine($" 1 ║ {C(0, 0)} {C(1, 0)} {C(2, 0)} {C(3, 0)} {C(4, 0)} {C(5, 0)} {C(6, 0)} {C(7, 0)} ║ {game.TakenCount(Black),2} x {BlackPiece}"); + sb.AppendLine($" 8 ║ {B(0, 7)} {B(1, 7)} {B(2, 7)} {B(3, 7)} {B(4, 7)} {B(5, 7)} {B(6, 7)} {B(7, 7)} ║ {BlackPiece} = Black"); + sb.AppendLine($" 7 ║ {B(0, 6)} {B(1, 6)} {B(2, 6)} {B(3, 6)} {B(4, 6)} {B(5, 6)} {B(6, 6)} {B(7, 6)} ║ {BlackKing} = Black King"); + sb.AppendLine($" 6 ║ {B(0, 5)} {B(1, 5)} {B(2, 5)} {B(3, 5)} {B(4, 5)} {B(5, 5)} {B(6, 5)} {B(7, 5)} ║ {WhitePiece} = White"); + sb.AppendLine($" 5 ║ {B(0, 4)} {B(1, 4)} {B(2, 4)} {B(3, 4)} {B(4, 4)} {B(5, 4)} {B(6, 4)} {B(7, 4)} ║ {WhiteKing} = White King"); + sb.AppendLine($" 4 ║ {B(0, 3)} {B(1, 3)} {B(2, 3)} {B(3, 3)} {B(4, 3)} {B(5, 3)} {B(6, 3)} {B(7, 3)} ║"); + sb.AppendLine($" 3 ║ {B(0, 2)} {B(1, 2)} {B(2, 2)} {B(3, 2)} {B(4, 2)} {B(5, 2)} {B(6, 2)} {B(7, 2)} ║ Taken:"); + sb.AppendLine($" 2 ║ {B(0, 1)} {B(1, 1)} {B(2, 1)} {B(3, 1)} {B(4, 1)} {B(5, 1)} {B(6, 1)} {B(7, 1)} ║ {game.TakenCount(White),2} x {WhitePiece}"); + sb.AppendLine($" 1 ║ {B(0, 0)} {B(1, 0)} {B(2, 0)} {B(3, 0)} {B(4, 0)} {B(5, 0)} {B(6, 0)} {B(7, 0)} ║ {game.TakenCount(Black),2} x {BlackPiece}"); sb.AppendLine($" ╚═══════════════════╝"); sb.AppendLine($" A B C D E F G H"); sb.AppendLine(); if (selection is not null) { - sb.Replace(" $ ", $"[{tiles.GetValueOrDefault(selection.Value, Vacant)}]"); - } - if (game.Winner is not null) - { - sb.AppendLine($" *** {game.Winner} wins ***"); - } - else if (playerMoved is not null) - { - sb.AppendLine($" {playerMoved.Color} moved"); - } - else - { - sb.AppendLine($" {game.Turn}'s turn"); + sb.Replace(" $ ", $"[{ToChar(game.Board[selection.Value.X, selection.Value.Y])}]"); } + PieceColor? wc = game.Winner; + PieceColor? mc = playerMoved?.Color; + PieceColor? tc = game.Turn; + // Note: these strings need to match in length + // so they overwrite each other. + string w = $" *** {wc} wins ***"; + string m = $" {mc} moved "; + string t = $" {tc}'s turn "; + sb.AppendLine( + game.Winner is not null ? w : + playerMoved is not null ? m : + t); + string p = " Press any key to continue..."; + string s = " "; + sb.AppendLine(promptPressKey ? p : s); Console.Write(sb); -} -static char ToChar(Piece piece) => - (piece.Color, piece.Promoted) switch - { - (Black, false) => BlackPiece, - (Black, true) => BlackKing, - (White, false) => WhitePiece, - (White, true) => WhiteKing, - _ => throw new NotImplementedException(), - }; + static char ToChar(Piece? piece) => + piece is null ? Vacant : + (piece.Color, piece.Promoted) switch + { + (Black, false) => BlackPiece, + (Black, true) => BlackKing, + (White, false) => WhitePiece, + (White, true) => WhiteKing, + _ => throw new NotImplementedException(), + }; +} -(int X, int Y)? HumanMoveSelection((int X, int y)? selectionStart = null) +(int X, int Y)? HumanMoveSelection(Game game, (int X, int y)? selectionStart = null) { (int X, int Y) selection = selectionStart ?? (3, 3); while (true) { - RenderGameState(selection: selection); + RenderGameState(game, selection: selection); switch (Console.ReadKey(true).Key) { case ConsoleKey.DownArrow: selection.Y = Math.Max(0, selection.Y - 1); break; From 36175e47c59e6013fdc422125c3d5694c04e8225 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 21 Jun 2022 21:34:55 -0400 Subject: [PATCH 59/68] added Visualized for Aggressor --- Projects/Checkers/Program.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 67fc50bf..498c3b6e 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -112,7 +112,6 @@ void RenderGameState(Game game, Player? playerMoved = null, (int X, int Y)? sele Console.CursorVisible = false; Console.SetCursorPosition(0, 0); - char B(int x, int y) => (x, y) == selection ? '$' : ToChar(game.Board[x, y]); StringBuilder sb = new(); sb.AppendLine(); sb.AppendLine(" Checkers"); @@ -133,6 +132,12 @@ void RenderGameState(Game game, Player? playerMoved = null, (int X, int Y)? sele { sb.Replace(" $ ", $"[{ToChar(game.Board[selection.Value.X, selection.Value.Y])}]"); } + if (game.Board.Aggressor is not null) + { + sb.Replace(" @ ", $"<{ToChar(game.Board.Aggressor)}>"); + sb.Replace("@ ", $"{ToChar(game.Board.Aggressor)}>"); + sb.Replace(" @", $"<{ToChar(game.Board.Aggressor)}"); + } PieceColor? wc = game.Winner; PieceColor? mc = playerMoved?.Color; PieceColor? tc = game.Turn; @@ -150,6 +155,11 @@ void RenderGameState(Game game, Player? playerMoved = null, (int X, int Y)? sele sb.AppendLine(promptPressKey ? p : s); Console.Write(sb); + char B(int x, int y) => + (x, y) == selection ? '$' : + (game.Board.Aggressor is not null && game.Board[x, y] == game.Board.Aggressor) ? '@' : + ToChar(game.Board[x, y]); + static char ToChar(Piece? piece) => piece is null ? Vacant : (piece.Color, piece.Promoted) switch From b79c2ed6e1401e5c8298e8c3332b6265930fae47 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Wed, 22 Jun 2022 18:37:27 -0400 Subject: [PATCH 60/68] reimplementations --- Projects/Checkers/Board.cs | 106 ++++++++++ Projects/Checkers/Engine.cs | 344 ------------------------------- Projects/Checkers/Game.cs | 33 +-- Projects/Checkers/Move.cs | 8 +- Projects/Checkers/MoveOutcome.cs | 12 -- Projects/Checkers/Program.cs | 65 ++++-- 6 files changed, 179 insertions(+), 389 deletions(-) delete mode 100644 Projects/Checkers/Engine.cs delete mode 100644 Projects/Checkers/MoveOutcome.cs diff --git a/Projects/Checkers/Board.cs b/Projects/Checkers/Board.cs index 4d6d53d8..3580655f 100644 --- a/Projects/Checkers/Board.cs +++ b/Projects/Checkers/Board.cs @@ -62,4 +62,110 @@ public static (int X, int Y) ParsePositionNotation(string notation) 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 GetPossibleMoves(PieceColor color) + { + List moves = new(); + if (Aggressor is not null) + { + if (Aggressor.Color != color) + { + throw new Exception(); + } + 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 GetPossibleMoves(Piece piece) + { + List moves = new(); + ValidateMove(-1, -1); + ValidateMove(-1, 1); + ValidateMove(1, -1); + ValidateMove(1, 1); + return moves.Any(move => move.PieceToCapture is not null) + ? moves.Where(move => move.PieceToCapture is not null).ToList() + : moves; + + void ValidateMove(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 (!Board.IsValidPosition(target.X, target.Y)) return; + PieceColor? targetColor = this[target.X, target.Y]?.Color; + if (targetColor is null) + { + if (!Board.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 (!Board.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); + } + } + } + + /// Returns a if -> is valid or null if not. + 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 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; + } } diff --git a/Projects/Checkers/Engine.cs b/Projects/Checkers/Engine.cs deleted file mode 100644 index cb80cda3..00000000 --- a/Projects/Checkers/Engine.cs +++ /dev/null @@ -1,344 +0,0 @@ -namespace Checkers; - -public static class Engine -{ - public static MoveOutcome? PlayNextMove(PieceColor color, Board board, (int X, int Y)? from = null, (int X, int Y)? to = null) - { - List possibleMoves; - MoveOutcome? outcome; - if (from is not null && to is not null) - { - outcome = GetAllPossiblePlayerMoves(color, board, out possibleMoves); - if (possibleMoves.Count is 0) - { - outcome = MoveOutcome.NoMoveAvailable; - } - else - { - if (MoveIsValid(from, to.Value, possibleMoves, board, out Move? selectedMove)) - { - possibleMoves.Clear(); - if (selectedMove is not null) - { - possibleMoves.Add(selectedMove); - outcome = selectedMove.Capturing is not null ? MoveOutcome.Capture : MoveOutcome.ValidMoves; - } - } - } - } - else - { - outcome = AnalysePosition(color, board, out possibleMoves); - } - // If a color can't play then other color wins - if (outcome is MoveOutcome.NoMoveAvailable) - { - outcome = color is Black ? MoveOutcome.WhiteWin : MoveOutcome.BlackWin; - } - switch (outcome) - { - case MoveOutcome.EndGame: - case MoveOutcome.ValidMoves: - Move bestMove = possibleMoves[Random.Shared.Next(possibleMoves.Count)]; - Piece pieceToMove = bestMove.PieceToMove!; - int newX = bestMove.To.X; - int newY = bestMove.To.Y; - pieceToMove.X = newX; - pieceToMove.Y = newY; - // Promotion can only happen if not already a king and you have reached the far color - if (newY is 0 or 7 && !pieceToMove.Promoted) - { - pieceToMove.Promoted = true; - } - break; - case MoveOutcome.Capture: - bool anyPromoted = PerformCapture(color, possibleMoves, board); - bool moreAvailable = MoreCapturesAvailable(color, board); - if (moreAvailable && !anyPromoted) - { - outcome = MoveOutcome.CaptureMoreAvailable; - } - break; - } - if (outcome is not null && outcome is not MoveOutcome.CaptureMoreAvailable) - { - board.Aggressor = null; - } - return outcome; - } - - private static bool MoreCapturesAvailable(PieceColor color, Board board) - { - Piece? aggressor = board.Aggressor; - if (aggressor is null) - { - return false; - } - _ = GetAllPossiblePlayerMoves(color, board, out List? possibleMoves); - return possibleMoves.Any(move => move.PieceToMove == aggressor && move.Capturing is not null); - } - - private static bool MoveIsValid((int X, int Y)? from, (int X, int Y) to, List possibleMoves, Board board, out Move? selectedMove) - { - selectedMove = default; - if (from is null) - { - return false; - } - Piece? selectedPiece = board[from.Value.X, from.Value.Y]; - foreach (Move move in possibleMoves.Where(move => move.PieceToMove == selectedPiece && move.To == to)) - { - selectedMove = move; - return true; - } - return false; - } - - private static MoveOutcome? GetAllPossiblePlayerMoves(PieceColor color, Board board, out List possibleMoves) - { - Piece? aggressor = board.Aggressor; - MoveOutcome? result = null; - possibleMoves = new List(); - if (PlayingWithJustKings(color, board, out List? endGameMoves)) - { - result = MoveOutcome.EndGame; - possibleMoves.AddRange(endGameMoves); - } - GetPossibleMovesAndAttacks(color, board, out List? nonEndGameMoves); - possibleMoves.AddRange(nonEndGameMoves); - if (aggressor is not null) - { - List? tempMoves = possibleMoves.Where(move => move.PieceToMove == aggressor).ToList(); - possibleMoves = tempMoves; - } - return result; - } - - private static bool PerformCapture(PieceColor color, List possibleCaptures, Board board) - { - Move? captureMove = possibleCaptures.FirstOrDefault(move => move.Capturing is not null); - if (captureMove is not null) - { - (int X, int Y)? positionToCapture = captureMove.Capturing; - if (positionToCapture is not null) - { - Piece? deadMan = board[positionToCapture.Value.X, positionToCapture.Value.Y]; - if (deadMan is not null) - { - board.Pieces.Remove(deadMan); - } - if (captureMove.PieceToMove is not null) - { - board.Aggressor = captureMove.PieceToMove; - captureMove.PieceToMove.X = captureMove.To.X; - captureMove.PieceToMove.Y = captureMove.To.Y; - } - } - } - bool anyPromoted = CheckForPiecesToPromote(color, board); - return anyPromoted; - } - - private static bool CheckForPiecesToPromote(PieceColor color, Board board) - { - bool retVal = false; - int promotionSpot = color is White ? 0 : 7; - foreach (Piece piece in board.Pieces.Where(piece => piece.Color == color)) - { - if (promotionSpot == piece.Y && !piece.Promoted) - { - piece.Promoted = retVal = true; - } - } - return retVal; - } - - private static MoveOutcome AnalysePosition(PieceColor color, Board board, out List possibleMoves) - { - MoveOutcome result; - possibleMoves = new List(); - // Check for endgame first - if (PlayingWithJustKings(color, board, out List? endGameMoves)) - { - result = MoveOutcome.EndGame; - possibleMoves.AddRange(endGameMoves); - return result; - } - GetPossibleMovesAndAttacks(color, board, out List? nonEndGameMoves); - if (nonEndGameMoves.Count is 0) - { - result = MoveOutcome.NoMoveAvailable; - } - else - { - if (nonEndGameMoves.Any(move => move.Capturing is not null)) - { - result = MoveOutcome.Capture; - } - else - { - result = nonEndGameMoves.Count > 0 ? MoveOutcome.ValidMoves : MoveOutcome.NoMoveAvailable; - } - } - if (nonEndGameMoves.Count > 0) - { - possibleMoves.AddRange(nonEndGameMoves); - } - return result; - } - - private static void GetPossibleMovesAndAttacks(PieceColor color, Board board, out List possibleMoves) - { - List moves = new(); - - foreach (Piece piece in board.Pieces.Where(piece => piece.Color == color)) - { - ValidateMove(-1, -1); - ValidateMove(-1, 1); - ValidateMove( 1, -1); - ValidateMove( 1, 1); - - void ValidateMove(int dx, int dy) - { - if (!piece.Promoted && color is Black && dy is -1) return; - if (!piece.Promoted && color is White && dy is 1) return; - (int X, int Y) target = (piece.X + dx, piece.Y + dy); - if (!Board.IsValidPosition(target.X, target.Y)) return; - PieceColor? targetColor = board[target.X, target.Y]?.Color; - if (targetColor is null) - { - if (!Board.IsValidPosition(target.X, target.Y)) return; - Move newMove = new(piece, target); - moves.Add(newMove); - } - else if (targetColor != color) - { - (int X, int Y) jump = (piece.X + 2 * dx, piece.Y + 2 * dy); - if (!Board.IsValidPosition(jump.X, jump.Y)) return; - PieceColor? jumpColor = board[jump.X, jump.Y]?.Color; - if (jumpColor is not null) return; - Move attack = new(piece, jump, target); - moves.Add(attack); - } - } - } - - possibleMoves = moves; - } - - private static bool PlayingWithJustKings(PieceColor color, Board board, out List possibleMoves) - { - possibleMoves = new List(); - int piecesInPlay = board.Pieces.Count(piece => piece.Color == color); - int kingsInPlay = board.Pieces.Count(piece => piece.Color == color && piece.Promoted); - bool playingWithJustKings = piecesInPlay == kingsInPlay; - if (playingWithJustKings) - { - double minDistanceSquared = double.MaxValue; - Piece? currentHero = null; - Piece? currentVillain = null; - foreach (Piece king in board.Pieces.Where(piece => piece.Color == color)) - { - foreach (Piece target in board.Pieces.Where(piece => piece.Color != color)) - { - (int X, int Y) vector = (king.X - target.X, king.Y - target.Y); - double distanceSquared = vector.X * vector.X + vector.Y * vector.Y; - if (distanceSquared < minDistanceSquared) - { - minDistanceSquared = distanceSquared; - currentHero = king; - currentVillain = target; - } - } - } - if (currentHero is not null && currentVillain is not null) - { - List<(int X, int Y)>? movementOptions = WhereIsVillain(currentHero, currentVillain); - foreach ((int X, int Y) movementOption in movementOptions) - { - PieceColor? targetColor = board[movementOption.X, movementOption.Y]?.Color; - if (targetColor is null) - { - if (!Board.IsValidPosition(movementOption.X, movementOption.Y)) - { - possibleMoves.Add(new Move(currentHero, movementOption)); - } - break; - } - } - } - } - return possibleMoves.Count > 0; - } - - public static List<(int X, int Y)> WhereIsVillain(Piece hero, Piece villain) - { - const int Up = 1; - const int Down = 2; - const int Left = 3; - const int Right = 4; - - List<(int X, int Y)>? retVal = new(); - List directions = new(); - if (hero.X > villain.X) - { - directions.Add(Left); - } - if (hero.X < villain.X) - { - directions.Add(Right); - } - if (hero.Y > villain.Y) - { - directions.Add(Up); - } - if (hero.Y < villain.Y) - { - directions.Add(Down); - } - if (directions.Count is 1) - { - switch (directions[0]) - { - case Up: - retVal.Add((hero.X - 1, hero.Y - 1)); - retVal.Add((hero.X + 1, hero.Y - 1)); - break; - case Down: - retVal.Add((hero.X - 1, hero.Y + 1)); - retVal.Add((hero.X + 1, hero.Y + 1)); - break; - case Left: - retVal.Add((hero.X - 1, hero.Y - 1)); - retVal.Add((hero.X - 1, hero.Y + 1)); - break; - case Right: - retVal.Add((hero.X + 1, hero.Y - 1)); - retVal.Add((hero.X + 1, hero.Y + 1)); - break; - default: - throw new NotImplementedException(); - } - } - else - { - if (directions.Contains(Left) && directions.Contains(Up)) - { - retVal.Add((hero.X - 1, hero.Y - 1)); - } - if (directions.Contains(Left) && directions.Contains(Down)) - { - retVal.Add((hero.X - 1, hero.Y + 1)); - } - if (directions.Contains(Right) && directions.Contains(Up)) - { - retVal.Add((hero.X + 1, hero.Y - 1)); - } - if (directions.Contains(Right) && directions.Contains(Down)) - { - retVal.Add((hero.X + 1, hero.Y + 1)); - } - } - return retVal; - } -} diff --git a/Projects/Checkers/Game.cs b/Projects/Checkers/Game.cs index c7ebb34c..d2c50952 100644 --- a/Projects/Checkers/Game.cs +++ b/Projects/Checkers/Game.cs @@ -22,33 +22,32 @@ public Game(int humanPlayerCount) Winner = null; } - public void NextTurn((int X, int Y)? from = null, (int X, int Y)? to = null) + public void PerformMove(Move move) { - MoveOutcome? moveOutcome = Engine.PlayNextMove(Turn, Board, from, to); - while (from is null && to is null && moveOutcome is MoveOutcome.CaptureMoreAvailable) + (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)) { - moveOutcome = Engine.PlayNextMove(Turn, Board, from, to); + move.PieceToMove.Promoted = true; } - if (moveOutcome is MoveOutcome.BlackWin) + if (move.PieceToCapture is not null) { - Winner = Black; + Board.Pieces.Remove(move.PieceToCapture); } - if (moveOutcome is MoveOutcome.WhiteWin) + if (move.PieceToCapture is not null && + Board.GetPossibleMoves(move.PieceToMove).Any(move => move.PieceToCapture is not null)) { - Winner = White; + Board.Aggressor = move.PieceToMove; } - if (Winner is null && moveOutcome is not MoveOutcome.CaptureMoreAvailable) - { - CheckColorsHavePiecesLeft(); - Turn = Turn is Black ? White : Black; - } - if (moveOutcome is null) + else { + Board.Aggressor = null; Turn = Turn is Black ? White : Black; } + CheckForWinner(); } - public void CheckColorsHavePiecesLeft() + public void CheckForWinner() { if (!Board.Pieces.Any(piece => piece.Color is Black)) { @@ -58,6 +57,10 @@ public void CheckColorsHavePiecesLeft() { Winner = Black; } + if (Winner is null && Board.GetPossibleMoves(Turn).Count is 0) + { + Winner = Turn is Black ? White : Black; + } } public int TakenCount(PieceColor colour) => diff --git a/Projects/Checkers/Move.cs b/Projects/Checkers/Move.cs index cbd56903..f5eb9d7f 100644 --- a/Projects/Checkers/Move.cs +++ b/Projects/Checkers/Move.cs @@ -6,12 +6,12 @@ public class Move public (int X, int Y) To { get; set; } - public (int X, int Y)? Capturing { get; set; } + public Piece? PieceToCapture { get; set; } - public Move(Piece piece, (int X, int Y) to, (int X, int Y)? capturing = null) + public Move(Piece pieceToMove, (int X, int Y) to, Piece? pieceToCapture = null) { - PieceToMove = piece; + PieceToMove = pieceToMove; To = to; - Capturing = capturing; + PieceToCapture = pieceToCapture; } } diff --git a/Projects/Checkers/MoveOutcome.cs b/Projects/Checkers/MoveOutcome.cs deleted file mode 100644 index 69cfde2f..00000000 --- a/Projects/Checkers/MoveOutcome.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Checkers; - -public enum MoveOutcome -{ - ValidMoves = 1, - Capture = 2, - CaptureMoreAvailable = 3, - EndGame = 4, // Playing with kings with prey to hunt - NoMoveAvailable = 5, - WhiteWin = 6, - BlackWin = 7, -} diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 498c3b6e..88934018 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -59,22 +59,31 @@ Game ShowIntroScreenAndGetOption() void RunGameLoop(Game game) { - while (game!.Winner is null) + while (game.Winner is null) { - Player? currentPlayer = game.Players.FirstOrDefault(player => player.Color == game.Turn); + Player currentPlayer = game.Players.First(player => player.Color == game.Turn); if (currentPlayer is not null && currentPlayer.IsHuman) { while (game.Turn == currentPlayer.Color) { - (int X, int Y)? from = null; + (int X, int Y)? selectionStart = null; + (int X, int Y)? from = game.Board.Aggressor is not null ? (game.Board.Aggressor.X, game.Board.Aggressor.Y) : null; + List moves = game.Board.GetPossibleMoves(game.Turn); + if (moves.Select(move => move.PieceToMove).Distinct().Count() is 1) + { + Move must = moves.First(); + from = (must.PieceToMove.X, must.PieceToMove.Y); + selectionStart = must.To; + } (int X, int Y)? to = null; while (to is null) { while (from is null) { from = HumanMoveSelection(game); + selectionStart = from; } - to = HumanMoveSelection(game, selectionStart: from); + to = HumanMoveSelection(game, selectionStart: selectionStart, from: from); } Piece? piece = null; if (from is not null) @@ -88,13 +97,40 @@ void RunGameLoop(Game game) } if (from is not null && to is not null) { - game.NextTurn(from, to); + Move? move = game.Board.ValidateMove(game.Turn, from.Value, to.Value); + if (move is not null && + (game.Board.Aggressor is null || move.PieceToMove == game.Board.Aggressor)) + { + game.PerformMove(move); + } } } } else { - game.NextTurn(); + List? moves = game.Board.GetPossibleMoves(game.Turn); + List? captures = moves.Where(move => move.PieceToCapture is not null).ToList(); + if (captures.Count > 0) + { + game.PerformMove(captures[Random.Shared.Next(captures.Count)]); + } + else if(!game.Board.Pieces.Any(piece => piece.Color == game.Turn && !piece.Promoted)) + { + var (a, b) = game.Board.GetClosestRivalPieces(game.Turn); + Move? priorityMove = moves.FirstOrDefault(move => move.PieceToMove == a && game.Board.IsTowards(move, b)); + if (priorityMove is not null) + { + game.PerformMove(priorityMove); + } + else + { + game.PerformMove(moves[Random.Shared.Next(moves.Count)]); + } + } + else + { + game.PerformMove(moves[Random.Shared.Next(moves.Count)]); + } } RenderGameState(game, playerMoved: currentPlayer, promptPressKey: true); @@ -102,7 +138,7 @@ void RunGameLoop(Game game) } } -void RenderGameState(Game game, Player? playerMoved = null, (int X, int Y)? selection = null, bool promptPressKey = false) +void RenderGameState(Game game, Player? playerMoved = null, (int X, int Y)? selection = null, (int X, int Y)? from = null, bool promptPressKey = false) { const char BlackPiece = '○'; const char BlackKing = '☺'; @@ -132,11 +168,12 @@ void RenderGameState(Game game, Player? playerMoved = null, (int X, int Y)? sele { sb.Replace(" $ ", $"[{ToChar(game.Board[selection.Value.X, selection.Value.Y])}]"); } - if (game.Board.Aggressor is not null) + if (from is not null) { - sb.Replace(" @ ", $"<{ToChar(game.Board.Aggressor)}>"); - sb.Replace("@ ", $"{ToChar(game.Board.Aggressor)}>"); - sb.Replace(" @", $"<{ToChar(game.Board.Aggressor)}"); + char fromChar = ToChar(game.Board[from.Value.X, from.Value.Y]); + sb.Replace(" @ ", $"<{fromChar}>"); + sb.Replace("@ ", $"{fromChar}>"); + sb.Replace(" @", $"<{fromChar}"); } PieceColor? wc = game.Winner; PieceColor? mc = playerMoved?.Color; @@ -157,7 +194,7 @@ void RenderGameState(Game game, Player? playerMoved = null, (int X, int Y)? sele char B(int x, int y) => (x, y) == selection ? '$' : - (game.Board.Aggressor is not null && game.Board[x, y] == game.Board.Aggressor) ? '@' : + (x, y) == from ? '@' : ToChar(game.Board[x, y]); static char ToChar(Piece? piece) => @@ -172,12 +209,12 @@ static char ToChar(Piece? piece) => }; } -(int X, int Y)? HumanMoveSelection(Game game, (int X, int y)? selectionStart = null) +(int X, int Y)? HumanMoveSelection(Game game, (int X, int y)? selectionStart = null, (int X, int Y)? from = null) { (int X, int Y) selection = selectionStart ?? (3, 3); while (true) { - RenderGameState(game, selection: selection); + RenderGameState(game, selection: selection, from: from); switch (Console.ReadKey(true).Key) { case ConsoleKey.DownArrow: selection.Y = Math.Max(0, selection.Y - 1); break; From b5c477a4c397e56f2905806dd2009ba924323ce9 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Wed, 22 Jun 2022 19:56:04 -0400 Subject: [PATCH 61/68] menu input adjustment --- Projects/Checkers/Program.cs | 52 +++++++++++++++++------------------- Projects/Checkers/README.md | 3 ++- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 88934018..9e15b53a 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -23,38 +23,39 @@ Game ShowIntroScreenAndGetOption() Console.WriteLine(); Console.WriteLine(" Checkers"); Console.WriteLine(); - Console.WriteLine(" Checkers is played on an 8x8 board between two sides commonly known as black"); - Console.WriteLine(" and white. The objective is simple - capture all your opponent's pieces. An"); - Console.WriteLine(" alternative way to win is to trap your opponent so that they have no valid"); + Console.WriteLine(" Checkers is played on an 8x8 board between two sides commonly known as black"); + Console.WriteLine(" and white. The objective is simple - capture all your opponent's pieces. An"); + Console.WriteLine(" alternative way to win is to trap your opponent so that they have no valid"); Console.WriteLine(" moves left."); Console.WriteLine(); - Console.WriteLine(" Black starts first and players take it in turns to move their pieces forward"); - Console.WriteLine(" across the board diagonally. Should a piece reach the other side of the board"); - Console.WriteLine(" the piece becomes a `king` and can then move diagonally backwards as well as"); + Console.WriteLine(" Black starts first and players take it in turns to move their pieces forward"); + Console.WriteLine(" across the board diagonally. Should a piece reach the other side of the board"); + Console.WriteLine(" the piece becomes a king and can then move diagonally backwards as well as"); Console.WriteLine(" forwards."); Console.WriteLine(); Console.WriteLine(" Pieces are captured by jumping over them diagonally. More than one enemy piece"); Console.WriteLine(" can be captured in the same turn by the same piece."); Console.WriteLine(); - Console.WriteLine(" Moves are selected with the arrow keys. Use the [enter] button to select the"); + Console.WriteLine(" Moves are selected with the arrow keys. Use the [enter] button to select the"); Console.WriteLine(" from and to squares. Invalid moves are ignored."); Console.WriteLine(); - Console.WriteLine(" 3 modes of play are possible depending on the number of players entered:"); - Console.WriteLine(" 0 - black and white are controlled by the computer"); - Console.WriteLine(" 1 - black is controlled by the player and white by the computer"); - Console.WriteLine(" 2 - allows 2 players"); - Console.WriteLine(); - Console.Write(" Enter the number of players (0-2): "); + Console.WriteLine(" Press a number key to choose number of human players:"); + Console.WriteLine(" [0] Black (computer) vs White (computer)"); + Console.WriteLine(" [1] Black (human) vs White (computer)"); + Console.Write(" [2] Black (human) vs White (human)"); - string? entry = Console.ReadLine()?.Trim(); - while (entry is not "0" and not "1" and not "2") + int? humanPlayerCount = null; + while (humanPlayerCount is null) { - Console.WriteLine(" Invalid Input. Try Again."); - Console.Write(" Enter the number of players (0-2): "); - entry = Console.ReadLine()?.Trim(); + Console.CursorVisible = false; + switch (Console.ReadKey(true).Key) + { + case ConsoleKey.D0 or ConsoleKey.NumPad0: humanPlayerCount = 0; break; + case ConsoleKey.D1 or ConsoleKey.NumPad1: humanPlayerCount = 1; break; + case ConsoleKey.D2 or ConsoleKey.NumPad2: humanPlayerCount = 2; break; + } } - int humanPlayerCount = entry[0] - '0'; - return new Game(humanPlayerCount); + return new Game(humanPlayerCount.Value); } void RunGameLoop(Game game) @@ -76,15 +77,12 @@ void RunGameLoop(Game game) selectionStart = must.To; } (int X, int Y)? to = null; - while (to is null) + while (from is null) { - while (from is null) - { - from = HumanMoveSelection(game); - selectionStart = from; - } - to = HumanMoveSelection(game, selectionStart: selectionStart, from: from); + from = HumanMoveSelection(game); + selectionStart = from; } + to = HumanMoveSelection(game, selectionStart: selectionStart, from: from); Piece? piece = null; if (from is not null) { diff --git a/Projects/Checkers/README.md b/Projects/Checkers/README.md index d78cee7b..e51c5bb0 100644 --- a/Projects/Checkers/README.md +++ b/Projects/Checkers/README.md @@ -31,9 +31,10 @@ Checkers is a classic 1v1 board game where you try to take all of your opponent' ## Input +- `0`, `1`, `2`: select number of human players in menu - `↑`, `↓`, `←`, `→`: move selection - `enter`: confirm -- `escape`: close +- `escape`: cancel current move

You can play this game in your browser: From ace71b7b07e399ee22ecc616a980030a669afc1b Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Wed, 22 Jun 2022 20:26:55 -0400 Subject: [PATCH 62/68] intro screen and readme update --- Projects/Checkers/Program.cs | 3 ++- Projects/Checkers/README.md | 39 +++++++++++++++++++++++++----------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/Projects/Checkers/Program.cs b/Projects/Checkers/Program.cs index 9e15b53a..a2802f09 100644 --- a/Projects/Checkers/Program.cs +++ b/Projects/Checkers/Program.cs @@ -34,7 +34,8 @@ Game ShowIntroScreenAndGetOption() Console.WriteLine(" forwards."); Console.WriteLine(); Console.WriteLine(" Pieces are captured by jumping over them diagonally. More than one enemy piece"); - Console.WriteLine(" can be captured in the same turn by the same piece."); + Console.WriteLine(" can be captured in the same turn by the same piece. If you can capture a piece"); + Console.WriteLine(" you must capture a piece."); Console.WriteLine(); Console.WriteLine(" Moves are selected with the arrow keys. Use the [enter] button to select the"); Console.WriteLine(" from and to squares. Invalid moves are ignored."); diff --git a/Projects/Checkers/README.md b/Projects/Checkers/README.md index e51c5bb0..03f30dce 100644 --- a/Projects/Checkers/README.md +++ b/Projects/Checkers/README.md @@ -13,20 +13,35 @@ > **Note** This game was a *[Community Contribution](https://github.com/ZacharyPatten/dotnet-console-games/pull/40)! -Checkers is a classic 1v1 board game where you try to take all of your opponent's pieces. You may move pieces diagonally, and you may take your opponent's piece by jumping over their pieces diagonally. You may jump multiple pieces in a single move. You may not move a piece onto a tile that already has a piece on it. +Checkers is played on an 8x8 board between two sides commonly known as black +and white. The objective is simple - capture all your opponent's pieces. An +alternative way to win is to trap your opponent so that they have no valid +moves left. + +Black starts first and players take it in turns to move their pieces forward +across the board diagonally. Should a piece reach the other side of the board +the piece becomes a king and can then move diagonally backwards as well as +forwards. + +Pieces are captured by jumping over them diagonally. More than one enemy piece +can be captured in the same turn by the same piece. If you can capture a piece +you must capture a piece. + +Moves are selected with the arrow keys. Use the [enter] button to select the +from and to squares. Invalid moves are ignored. ``` - ╔═══════════════════╗ -8║ . ◙ . ◙ . ◙ . ◙ ║ ○ = Black -7║ ◙ . ◙ . ◙ . ◙ . ║ ☺ = Black King -6║ . ◙ . ◙ . . . ◙ ║ ◙ = White -5║ . . . . . . ◙ . ║ ☻ = White King -4║ . . . ○ . . . . ║ -3║ ○ . ○ . . . ○ . ║ Taken: -2║ . ○ . ○ . ○ . ○ ║ 0 x ◙ -1║ ○ . ○ . ○ . ○ . ║ 0 x ○ - ╚═══════════════════╝ - A B C D E F G H + ╔═══════════════════╗ + 8 ║ . ◙ . ◙ . ◙ . ◙ ║ ○ = Black + 7 ║ ◙ . ◙ . ◙ . ◙ . ║ ☺ = Black King + 6 ║ . ◙ . ◙ . . . ◙ ║ ◙ = White + 5 ║ . . . . . . ◙ . ║ ☻ = White King + 4 ║ . . . ○ . . . . ║ + 3 ║ ○ . ○ . . . ○ . ║ Taken: + 2 ║ . ○ . ○ . ○ . ○ ║ 0 x ◙ + 1 ║ ○ . ○ . ○ . ○ . ║ 0 x ○ + ╚═══════════════════╝ + A B C D E F G H ``` ## Input From 04e726656acf8eade9bdbc2374fd62320735bd2a Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Wed, 22 Jun 2022 20:29:09 -0400 Subject: [PATCH 63/68] moved Checkers to Weight of 4 in root README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa3e2ee3..68a1b7e2 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,8 @@ |[Bound](Projects/Bound)|4|[![Play Now](.github/resources/play-badge.svg)](https://zacharypatten.github.io/dotnet-console-games/Bound) [![Status](https://github.com/ZacharyPatten/dotnet-console-games/workflows/Bound%20Build/badge.svg)](https://github.com/ZacharyPatten/dotnet-console-games/actions)| |[Tents](Projects/Tents)|4|[![Play Now](.github/resources/play-badge.svg)](https://zacharypatten.github.io/dotnet-console-games/Tents) [![Status](https://github.com/ZacharyPatten/dotnet-console-games/workflows/Tents%20Build/badge.svg)](https://github.com/ZacharyPatten/dotnet-console-games/actions)| |[Battleship](Projects/Battleship)|4|[![Play Now](.github/resources/play-badge.svg)](https://zacharypatten.github.io/dotnet-console-games/Battleship) [![Status](https://github.com/ZacharyPatten/dotnet-console-games/workflows/Battleship%20Build/badge.svg)](https://github.com/ZacharyPatten/dotnet-console-games/actions)| +|[Checkers](Projects/Checkers)|4|[![Play Now](.github/resources/play-badge.svg)](https://zacharypatten.github.io/dotnet-console-games/Checkers) [![Status](https://github.com/ZacharyPatten/dotnet-console-games/workflows/Checkers%20Build/badge.svg)](https://github.com/ZacharyPatten/dotnet-console-games/actions)
*_[Community Contribution](https://github.com/ZacharyPatten/dotnet-console-games/pull/40)_| |[Duck Hunt](Projects/Duck%20Hunt)|5|[![Play Now](.github/resources/play-badge.svg)](https://zacharypatten.github.io/dotnet-console-games/Duck%20Hunt) [![Status](https://github.com/ZacharyPatten/dotnet-console-games/workflows/Duck%20Hunt%20Build/badge.svg)](https://github.com/ZacharyPatten/dotnet-console-games/actions)
*_[Community Contribution](https://github.com/ZacharyPatten/dotnet-console-games/pull/39)_| -|[Checkers](Projects/Checkers)|5|[![Play Now](.github/resources/play-badge.svg)](https://zacharypatten.github.io/dotnet-console-games/Checkers) [![Status](https://github.com/ZacharyPatten/dotnet-console-games/workflows/Checkers%20Build/badge.svg)](https://github.com/ZacharyPatten/dotnet-console-games/actions)
*_[Community Contribution](https://github.com/ZacharyPatten/dotnet-console-games/pull/40)_| |[Blackjack](Projects/Blackjack)|5|[![Play Now](.github/resources/play-badge.svg)](https://zacharypatten.github.io/dotnet-console-games/Blackjack) [![Status](https://github.com/ZacharyPatten/dotnet-console-games/workflows/Blackjack%20Build/badge.svg)](https://github.com/ZacharyPatten/dotnet-console-games/actions)| |[Fighter](Projects/Fighter)|5|[![Play Now](.github/resources/play-badge.svg)](https://zacharypatten.github.io/dotnet-console-games/Fighter) [![Status](https://github.com/ZacharyPatten/dotnet-console-games/workflows/Fighter%20Build/badge.svg)](https://github.com/ZacharyPatten/dotnet-console-games/actions)| |[Maze](Projects/Maze)|5|[![Play Now](.github/resources/play-badge.svg)](https://zacharypatten.github.io/dotnet-console-games/Maze) [![Status](https://github.com/ZacharyPatten/dotnet-console-games/workflows/Maze%20Build/badge.svg)](https://github.com/ZacharyPatten/dotnet-console-games/actions)| From 6fa45705c4c2173e40a2e2c8622b2895a051a417 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Wed, 22 Jun 2022 22:12:10 -0400 Subject: [PATCH 64/68] Checkers blazor port --- Projects/Website/BlazorConsole.cs | 2 + Projects/Website/Games/Checkers/Board.cs | 176 +++++++++++++ Projects/Website/Games/Checkers/Checkers.cs | 245 ++++++++++++++++++ Projects/Website/Games/Checkers/Game.cs | 73 ++++++ Projects/Website/Games/Checkers/Move.cs | 17 ++ Projects/Website/Games/Checkers/Piece.cs | 18 ++ Projects/Website/Games/Checkers/PieceColor.cs | 7 + Projects/Website/Games/Checkers/Player.cs | 13 + Projects/Website/Games/Checkers/_using.cs | 7 + Projects/Website/Pages/Checkers.razor | 55 ++++ Projects/Website/Shared/NavMenu.razor | 5 + 11 files changed, 618 insertions(+) create mode 100644 Projects/Website/Games/Checkers/Board.cs create mode 100644 Projects/Website/Games/Checkers/Checkers.cs create mode 100644 Projects/Website/Games/Checkers/Game.cs create mode 100644 Projects/Website/Games/Checkers/Move.cs create mode 100644 Projects/Website/Games/Checkers/Piece.cs create mode 100644 Projects/Website/Games/Checkers/PieceColor.cs create mode 100644 Projects/Website/Games/Checkers/Player.cs create mode 100644 Projects/Website/Games/Checkers/_using.cs create mode 100644 Projects/Website/Pages/Checkers.razor diff --git a/Projects/Website/BlazorConsole.cs b/Projects/Website/BlazorConsole.cs index f1460067..9850b002 100644 --- a/Projects/Website/BlazorConsole.cs +++ b/Projects/Website/BlazorConsole.cs @@ -39,6 +39,8 @@ public struct Pixel public int _windowHeight = 35; public int _windowWidth = 80; + public Encoding OutputEncoding; + public int WindowHeight { get => _windowHeight; diff --git a/Projects/Website/Games/Checkers/Board.cs b/Projects/Website/Games/Checkers/Board.cs new file mode 100644 index 00000000..442aeff0 --- /dev/null +++ b/Projects/Website/Games/Checkers/Board.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using static Website.Games.Checkers._using; + +namespace Website.Games.Checkers; + +public class Board +{ + public List Pieces { get; set; } + + 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 + { + 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 GetPossibleMoves(PieceColor color) + { + List moves = new(); + if (Aggressor is not null) + { + if (Aggressor.Color != color) + { + throw new Exception(); + } + 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 GetPossibleMoves(Piece piece) + { + List moves = new(); + ValidateMove(-1, -1); + ValidateMove(-1, 1); + ValidateMove(1, -1); + ValidateMove(1, 1); + return moves.Any(move => move.PieceToCapture is not null) + ? moves.Where(move => move.PieceToCapture is not null).ToList() + : moves; + + void ValidateMove(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 (!Board.IsValidPosition(target.X, target.Y)) return; + PieceColor? targetColor = this[target.X, target.Y]?.Color; + if (targetColor is null) + { + if (!Board.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 (!Board.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); + } + } + } + + ///

Returns a if -> is valid or null if not. + 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 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; + } +} diff --git a/Projects/Website/Games/Checkers/Checkers.cs b/Projects/Website/Games/Checkers/Checkers.cs new file mode 100644 index 00000000..4f7f1a51 --- /dev/null +++ b/Projects/Website/Games/Checkers/Checkers.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static Website.Games.Checkers._using; + +namespace Website.Games.Checkers; + +public class Checkers +{ + public readonly BlazorConsole Console = new(); + + public async Task Run() + { + Encoding encoding = Console.OutputEncoding; + + try + { + Console.OutputEncoding = Encoding.UTF8; + Game game = await ShowIntroScreenAndGetOption(); + await Console.Clear(); + await RunGameLoop(game); + await RenderGameState(game, promptPressKey: true); + await Console.ReadKey(true); + } + finally + { + Console.OutputEncoding = encoding; + Console.CursorVisible = true; + await Console.Clear(); + await Console.Write("Checkers was closed."); + } + + async Task ShowIntroScreenAndGetOption() + { + await Console.Clear(); + await Console.WriteLine(); + await Console.WriteLine(" Checkers"); + await Console.WriteLine(); + await Console.WriteLine(" Checkers is played on an 8x8 board between two sides commonly known as black"); + await Console.WriteLine(" and white. The objective is simple - capture all your opponent's pieces. An"); + await Console.WriteLine(" alternative way to win is to trap your opponent so that they have no valid"); + await Console.WriteLine(" moves left."); + await Console.WriteLine(); + await Console.WriteLine(" Black starts first and players take it in turns to move their pieces forward"); + await Console.WriteLine(" across the board diagonally. Should a piece reach the other side of the board"); + await Console.WriteLine(" the piece becomes a king and can then move diagonally backwards as well as"); + await Console.WriteLine(" forwards."); + await Console.WriteLine(); + await Console.WriteLine(" Pieces are captured by jumping over them diagonally. More than one enemy piece"); + await Console.WriteLine(" can be captured in the same turn by the same piece. If you can capture a piece"); + await Console.WriteLine(" you must capture a piece."); + await Console.WriteLine(); + await Console.WriteLine(" Moves are selected with the arrow keys. Use the [enter] button to select the"); + await Console.WriteLine(" from and to squares. Invalid moves are ignored."); + await Console.WriteLine(); + await Console.WriteLine(" Press a number key to choose number of human players:"); + await Console.WriteLine(" [0] Black (computer) vs White (computer)"); + await Console.WriteLine(" [1] Black (human) vs White (computer)"); + await Console.Write(" [2] Black (human) vs White (human)"); + + int? humanPlayerCount = null; + while (humanPlayerCount is null) + { + Console.CursorVisible = false; + switch ((await Console.ReadKey(true)).Key) + { + case ConsoleKey.D0 or ConsoleKey.NumPad0: humanPlayerCount = 0; break; + case ConsoleKey.D1 or ConsoleKey.NumPad1: humanPlayerCount = 1; break; + case ConsoleKey.D2 or ConsoleKey.NumPad2: humanPlayerCount = 2; break; + } + } + return new Game(humanPlayerCount.Value); + } + + async Task RunGameLoop(Game game) + { + while (game.Winner is null) + { + Player currentPlayer = game.Players.First(player => player.Color == game.Turn); + if (currentPlayer is not null && currentPlayer.IsHuman) + { + while (game.Turn == currentPlayer.Color) + { + (int X, int Y)? selectionStart = null; + (int X, int Y)? from = game.Board.Aggressor is not null ? (game.Board.Aggressor.X, game.Board.Aggressor.Y) : null; + List moves = game.Board.GetPossibleMoves(game.Turn); + if (moves.Select(move => move.PieceToMove).Distinct().Count() is 1) + { + Move must = moves.First(); + from = (must.PieceToMove.X, must.PieceToMove.Y); + selectionStart = must.To; + } + (int X, int Y)? to = null; + while (from is null) + { + from = await HumanMoveSelection(game); + selectionStart = from; + } + to = await HumanMoveSelection(game, selectionStart: selectionStart, from: from); + Piece? piece = null; + if (from is not null) + { + piece = game.Board[from.Value.X, from.Value.Y]; + } + if (piece is null || piece.Color != game.Turn) + { + from = null; + to = null; + } + if (from is not null && to is not null) + { + Move? move = game.Board.ValidateMove(game.Turn, from.Value, to.Value); + if (move is not null && + (game.Board.Aggressor is null || move.PieceToMove == game.Board.Aggressor)) + { + game.PerformMove(move); + } + } + } + } + else + { + List? moves = game.Board.GetPossibleMoves(game.Turn); + List? captures = moves.Where(move => move.PieceToCapture is not null).ToList(); + if (captures.Count > 0) + { + game.PerformMove(captures[Random.Shared.Next(captures.Count)]); + } + else if (!game.Board.Pieces.Any(piece => piece.Color == game.Turn && !piece.Promoted)) + { + var (a, b) = game.Board.GetClosestRivalPieces(game.Turn); + Move? priorityMove = moves.FirstOrDefault(move => move.PieceToMove == a && game.Board.IsTowards(move, b)); + if (priorityMove is not null) + { + game.PerformMove(priorityMove); + } + else + { + game.PerformMove(moves[Random.Shared.Next(moves.Count)]); + } + } + else + { + game.PerformMove(moves[Random.Shared.Next(moves.Count)]); + } + } + + await RenderGameState(game, playerMoved: currentPlayer, promptPressKey: true); + await Console.ReadKey(true); + } + } + + async Task RenderGameState(Game game, Player? playerMoved = null, (int X, int Y)? selection = null, (int X, int Y)? from = null, bool promptPressKey = false) + { + const char BlackPiece = '○'; + const char BlackKing = '☺'; + const char WhitePiece = '◙'; + const char WhiteKing = '☻'; + const char Vacant = '·'; + + Console.CursorVisible = false; + await Console.SetCursorPosition(0, 0); + StringBuilder sb = new(); + sb.AppendLine(); + sb.AppendLine(" Checkers"); + sb.AppendLine(); + sb.AppendLine($" ╔═══════════════════╗"); + sb.AppendLine($" 8 ║ {B(0, 7)} {B(1, 7)} {B(2, 7)} {B(3, 7)} {B(4, 7)} {B(5, 7)} {B(6, 7)} {B(7, 7)} ║ {BlackPiece} = Black"); + sb.AppendLine($" 7 ║ {B(0, 6)} {B(1, 6)} {B(2, 6)} {B(3, 6)} {B(4, 6)} {B(5, 6)} {B(6, 6)} {B(7, 6)} ║ {BlackKing} = Black King"); + sb.AppendLine($" 6 ║ {B(0, 5)} {B(1, 5)} {B(2, 5)} {B(3, 5)} {B(4, 5)} {B(5, 5)} {B(6, 5)} {B(7, 5)} ║ {WhitePiece} = White"); + sb.AppendLine($" 5 ║ {B(0, 4)} {B(1, 4)} {B(2, 4)} {B(3, 4)} {B(4, 4)} {B(5, 4)} {B(6, 4)} {B(7, 4)} ║ {WhiteKing} = White King"); + sb.AppendLine($" 4 ║ {B(0, 3)} {B(1, 3)} {B(2, 3)} {B(3, 3)} {B(4, 3)} {B(5, 3)} {B(6, 3)} {B(7, 3)} ║"); + sb.AppendLine($" 3 ║ {B(0, 2)} {B(1, 2)} {B(2, 2)} {B(3, 2)} {B(4, 2)} {B(5, 2)} {B(6, 2)} {B(7, 2)} ║ Taken:"); + sb.AppendLine($" 2 ║ {B(0, 1)} {B(1, 1)} {B(2, 1)} {B(3, 1)} {B(4, 1)} {B(5, 1)} {B(6, 1)} {B(7, 1)} ║ {game.TakenCount(White),2} x {WhitePiece}"); + sb.AppendLine($" 1 ║ {B(0, 0)} {B(1, 0)} {B(2, 0)} {B(3, 0)} {B(4, 0)} {B(5, 0)} {B(6, 0)} {B(7, 0)} ║ {game.TakenCount(Black),2} x {BlackPiece}"); + sb.AppendLine($" ╚═══════════════════╝"); + sb.AppendLine($" A B C D E F G H"); + sb.AppendLine(); + if (selection is not null) + { + sb.Replace(" $ ", $"[{ToChar(game.Board[selection.Value.X, selection.Value.Y])}]"); + } + if (from is not null) + { + char fromChar = ToChar(game.Board[from.Value.X, from.Value.Y]); + sb.Replace(" @ ", $"<{fromChar}>"); + sb.Replace("@ ", $"{fromChar}>"); + sb.Replace(" @", $"<{fromChar}"); + } + PieceColor? wc = game.Winner; + PieceColor? mc = playerMoved?.Color; + PieceColor? tc = game.Turn; + // Note: these strings need to match in length + // so they overwrite each other. + string w = $" *** {wc} wins ***"; + string m = $" {mc} moved "; + string t = $" {tc}'s turn "; + sb.AppendLine( + game.Winner is not null ? w : + playerMoved is not null ? m : + t); + string p = " Press any key to continue..."; + string s = " "; + sb.AppendLine(promptPressKey ? p : s); + await Console.Write(sb); + + char B(int x, int y) => + (x, y) == selection ? '$' : + (x, y) == from ? '@' : + ToChar(game.Board[x, y]); + + static char ToChar(Piece? piece) => + piece is null ? Vacant : + (piece.Color, piece.Promoted) switch + { + (Black, false) => BlackPiece, + (Black, true) => BlackKing, + (White, false) => WhitePiece, + (White, true) => WhiteKing, + _ => throw new NotImplementedException(), + }; + } + + async Task<(int X, int Y)?> HumanMoveSelection(Game game, (int X, int y)? selectionStart = null, (int X, int Y)? from = null) + { + (int X, int Y) selection = selectionStart ?? (3, 3); + while (true) + { + await RenderGameState(game, selection: selection, from: from); + switch ((await Console.ReadKey(true)).Key) + { + case ConsoleKey.DownArrow: selection.Y = Math.Max(0, selection.Y - 1); break; + case ConsoleKey.UpArrow: selection.Y = Math.Min(7, selection.Y + 1); break; + case ConsoleKey.LeftArrow: selection.X = Math.Max(0, selection.X - 1); break; + case ConsoleKey.RightArrow: selection.X = Math.Min(7, selection.X + 1); break; + case ConsoleKey.Enter: return selection; + case ConsoleKey.Escape: return null; + } + } + } + + } +} diff --git a/Projects/Website/Games/Checkers/Game.cs b/Projects/Website/Games/Checkers/Game.cs new file mode 100644 index 00000000..48825df7 --- /dev/null +++ b/Projects/Website/Games/Checkers/Game.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using static Website.Games.Checkers._using; + +namespace Website.Games.Checkers; + +public class Game +{ + private const int PiecesPerColor = 12; + + public PieceColor Turn { get; private set; } + public Board Board { get; private set; } + public PieceColor? Winner { get; private set; } + public List Players { get; private set; } + + 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(move => move.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); +} diff --git a/Projects/Website/Games/Checkers/Move.cs b/Projects/Website/Games/Checkers/Move.cs new file mode 100644 index 00000000..a2555d4c --- /dev/null +++ b/Projects/Website/Games/Checkers/Move.cs @@ -0,0 +1,17 @@ +namespace Website.Games.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; + } +} diff --git a/Projects/Website/Games/Checkers/Piece.cs b/Projects/Website/Games/Checkers/Piece.cs new file mode 100644 index 00000000..30c842d6 --- /dev/null +++ b/Projects/Website/Games/Checkers/Piece.cs @@ -0,0 +1,18 @@ +namespace Website.Games.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; } +} diff --git a/Projects/Website/Games/Checkers/PieceColor.cs b/Projects/Website/Games/Checkers/PieceColor.cs new file mode 100644 index 00000000..a8c57337 --- /dev/null +++ b/Projects/Website/Games/Checkers/PieceColor.cs @@ -0,0 +1,7 @@ +namespace Website.Games.Checkers; + +public enum PieceColor +{ + Black = 1, + White = 2, +} diff --git a/Projects/Website/Games/Checkers/Player.cs b/Projects/Website/Games/Checkers/Player.cs new file mode 100644 index 00000000..8c4fb5a5 --- /dev/null +++ b/Projects/Website/Games/Checkers/Player.cs @@ -0,0 +1,13 @@ +namespace Website.Games.Checkers; + +public class Player +{ + public bool IsHuman { get; private set; } + public PieceColor Color { get; private set; } + + public Player(bool isHuman, PieceColor color) + { + IsHuman = isHuman; + Color = color; + } +} diff --git a/Projects/Website/Games/Checkers/_using.cs b/Projects/Website/Games/Checkers/_using.cs new file mode 100644 index 00000000..3d47339b --- /dev/null +++ b/Projects/Website/Games/Checkers/_using.cs @@ -0,0 +1,7 @@ +namespace Website.Games.Checkers; + +public static class _using +{ + public const PieceColor Black = PieceColor.Black; + public const PieceColor White = PieceColor.White; +} diff --git a/Projects/Website/Pages/Checkers.razor b/Projects/Website/Pages/Checkers.razor new file mode 100644 index 00000000..2cbc8ade --- /dev/null +++ b/Projects/Website/Pages/Checkers.razor @@ -0,0 +1,55 @@ +@using System + +@page "/Checkers" + +Checkers + +

Checkers

+ + + Go To Readme + + +
+
+
+			@Console.State
+		
+
+
+ + + + + + + + + +
+
+ + + + + +@code +{ + Games.Checkers.Checkers Game; + BlazorConsole Console; + + public Checkers() + { + Game = new(); + Console = Game.Console; + Console.WindowWidth = 82; + Console.WindowHeight = 25; + Console.StateHasChanged = StateHasChanged; + } + + protected override void OnInitialized() => InvokeAsync(Game.Run); +} diff --git a/Projects/Website/Shared/NavMenu.razor b/Projects/Website/Shared/NavMenu.razor index 06d216f6..6f6699f2 100644 --- a/Projects/Website/Shared/NavMenu.razor +++ b/Projects/Website/Shared/NavMenu.razor @@ -178,6 +178,11 @@ Battleship +