From 1e802915e5b2c33d552f28b87261df21e6ae5347 Mon Sep 17 00:00:00 2001 From: pdagosta <8269326+pdagosta@users.noreply.github.com> Date: Sat, 5 Mar 2022 02:24:44 -0600 Subject: [PATCH] initial compilable version --- 23_Checkers/csharp/Program.cs | 464 ++++++++++++++++++++++++++++++++++ 1 file changed, 464 insertions(+) create mode 100644 23_Checkers/csharp/Program.cs diff --git a/23_Checkers/csharp/Program.cs b/23_Checkers/csharp/Program.cs new file mode 100644 index 00000000..b2118267 --- /dev/null +++ b/23_Checkers/csharp/Program.cs @@ -0,0 +1,464 @@ +const int LineLength = 80; +Dictionary Pieces = new Dictionary() +{ + { -2, "X*" }, + { -1, "X " }, + { 0, ". " }, + { 1, "O " }, + { 2, "O*" }, +}; + +void PrintBoard(int[,] state) +{ + SkipLines(3); + for (int y = 7; y >= 0; y--) + { + for (int x = 0; x < 8; x++) + { + Console.Write(Pieces[state[x, y]]); + Console.Write(" "); + } + Console.WriteLine(); + } +} + +void WriteCenter(string text) +{ + var spaces = (LineLength - text.Length) / 2; + Console.WriteLine($"{"".PadLeft(spaces)}{text}"); +} + +void SkipLines(int count) +{ + for (int i = 0; i < count; i++) + { + Console.WriteLine(); + } +} + +bool IsPointOutOfBounds(int x) +{ + return x < 0 || x > 7; +} + +bool IsOutOfBounds((int x, int y) position) +{ + return IsPointOutOfBounds(position.x) || IsPointOutOfBounds(position.y); +} + +(int x, int y)? GetCandidateMove(int[,] state, (int x, int y) from, (int x, int y) direction) +{ + var to = (x: from.x + direction.x, y: from.y + direction.y); + if (IsOutOfBounds(to)) + return null; + if (state[to.x, to.y] > 0) + { + // potential jump + to = (x: to.x + direction.x, y: to.y + direction.y); + if (IsOutOfBounds(to)) + return null; + } + if (state[to.x, to.y] != 0) + // space already occupied by another piece + return null; + + return to; +} +bool IsJumpMove((int x, int y) from, (int x, int y) to) +{ + return Math.Abs(from.y - to.y) == 2; +} + +int AnalyzeMove(int[,] state, (int x, int y) from, (int x, int y) to) +{ + int rank = 0; + + if (to.y == 0 && state[from.x, from.y] == -1) + { + // getting a king + rank += 2; + } + if (IsJumpMove(from, to)) + { + // making a jump + rank += 5; + } + if (from.y == 7) + { + // leaving home row + rank -= 2; + } + if (to.x == 0 || to.x == 7) + { + // move to edge + rank += 1; + } + for (int c = -1; c <=1; c++) + { + var inFront = (x: to.x + c, y: to.y - 1); + if (IsOutOfBounds(inFront)) + continue; + if (state[inFront.x, inFront.y] < 0) + { + // protected by our piece in front + rank++; + continue; + } + var inBack = (x: to.x - c, y: to.y + 1); + if (IsOutOfBounds(inBack)) + { + continue; + } + if (inBack == from || + (state[inFront.x, inFront.y] > 0 && state[inBack.x, inBack.y] == 0)) + { + // the player can jump us + rank -= 2; + } + } + return rank; +}; + +IEnumerable<(int x, int y)> GetPossibleMoves(int[,] state, (int x, int y) from) +{ + int maxB; + switch (state[from.x, from.y]) + { + case -2: + // kings can go backwards too + maxB = 1; + break; + case -1: + maxB = -1; + break; + default: + // not one of our pieces + yield break; + } + + for (int a = -1; a <= 1; a += 2) + { + // a + // -1 = left + // +1 = right + for (int b = -1; b <= maxB; b += 2) + { + // b + // -1 = forwards + // +1 = backwards (only kings allowed to make this move) + var to = GetCandidateMove(state, from, (a, b)); + if (to == null) + { + // no valid move in this direction + continue; + } + yield return to.Value; + } + } +} + +((int x, int y) from, (int x, int y) to)? GetBestMove(int[,] state, IEnumerable<((int x, int y) from, (int x, int y) to)> possibleMoves) +{ + int? bestRank = null; + ((int x, int y) from, (int x, int y) to)? bestMove = null; + + foreach (var move in possibleMoves) + { + int rank = AnalyzeMove(state, move.from, move.to); + + if (rank > bestRank) + { + bestRank = rank; + bestMove = move; + } + } + + return bestMove; +} + +((int x, int y) from, (int x, int y) to)? CalculateMove(int[,] state) +{ + var possibleMoves = new List<((int x, int y) from, (int x, int y) to)>(); + for (int x = 0; x < 8; x++) + { + for (int y = 0; y < 8; y++) + { + var from = (x, y); + foreach (var to in GetPossibleMoves(state, from)) + { + possibleMoves.Add((from, to)); + } + } + } + var bestMove = GetBestMove(state, possibleMoves); + return bestMove; +} +(int x, int y) GetJumpedPiece((int x, int y) from, (int x, int y) to) +{ + var midX = (to.x + from.x) / 2; + var midY = (to.y + from.y) / 2; + return (midX, midY); +} +int[,] ApplyMove(int[,] state, (int x, int y) from, (int x, int y) to) +{ + state[to.x, to.y] = state[from.x, from.y]; + state[from.x, from.y] = 0; + if ( (to.y == 0 && state[to.x, to.y] == -1) + ||(to.y == 7 && state[to.x, to.y] == 1)) + { + // make the piece a king + state[to.x, to.y] *= 2; + } + + if (IsJumpMove(from, to)) + { + // a jump was made + // remove the jumped piece from the board + var jump = GetJumpedPiece(from, to); + state[jump.x, jump.y] = 0; + } + return state; +} + +(bool moveMade, int[,] state) ComputerTurn(int[,] state) +{ + var move = CalculateMove(state); + if (move == null) + { + // No move can be made + return (false, state); + } + Console.Write($"FROM {move.Value.from.x} {move.Value.from.y} "); + while (move != null) + { + Console.WriteLine($"TO {move.Value.to.x} {move.Value.to.y}"); + state = ApplyMove(state, move.Value.from, move.Value.to); + if (IsJumpMove(move.Value.from, move.Value.to)) + { + // check for double / triple / etc. jump + var possibleMoves = new List<((int x, int y) from, (int x, int y) to)>(); + var from = move.Value.to; + foreach (var to in GetPossibleMoves(state, from)) + { + if (IsJumpMove(from, to)) + { + possibleMoves.Add((from, to)); + } + } + move = GetBestMove(state, possibleMoves); + } + } + return (true, state); +} + +(int x, int y)? GetCoordinate(string prompt) +{ + Console.Write(prompt + "? "); + var input = Console.ReadLine(); + var parts = input.Split(","); + if (parts.Length != 2) + return null; + int x; + if (!int.TryParse(parts[0], out x)) + return null; + int y; + if (!int.TryParse(parts[1], out y)) + return null; + + return (x, y); +} + +bool IsValidMove(int[,] state, (int x, int y) from, (int x, int y) to) +{ + if (state[to.x, to.y] != 0) + { + return false; + } + var deltaX = Math.Abs(to.x - from.x); + var deltaY = Math.Abs(to.y - from.y); + if (deltaX != 1 || deltaX != 2) + { + return false; + } + if (deltaX != deltaY) + { + return false; + } + if (state[from.x, from.y] == 1 && Math.Sign(to.y - from.y) <= 0) + { + // only kings can move downwards + return false; + } + if (deltaX == 2) + { + var jump = GetJumpedPiece(from, to); + if (state[jump.x, jump.y] >= 0) + { + // no valid piece to jump + return false; + } + } + return true; +} + +int [,] PlayerTurn(int[,] state) +{ + // The original program has some issues regarding user input + // 1) There is minimal data sanity checks + // a) FROM piece must be owned by player + // b) TO location must be empty + // c) the FROM and TO x's must be less than 2 squares away + // d) the FROM and TO y's must be same distance as x's + // No checks are made for direction, if a jump is valid, or + // if the piece even moves. + // 2) Once a valid FROM is selected, a TO must be selected. + // If there are no valid TO locations, you are soft-locked + // This approach is intentionally different + // 1) Select a FROM location + // 2) If FROM is invalid, return to step 1 + // 3) Select a TO location + // 4) If TO is invalid or the implied move is invalid, + // return to step 1 + + (int x, int y)? from = null; + (int x, int y)? to = null; + var valid = false; + do + { + from = GetCoordinate("FROM"); + if ((from != null) + && !IsOutOfBounds(from.Value) + && (state[from.Value.x, from.Value.y] > 0)) + { + to = GetCoordinate("TO"); + if ((to != null) + && !IsOutOfBounds(to.Value) + && IsValidMove(state, from.Value, to.Value)) + { + valid = true; + } + } + } while (!valid); + bool jumping = false; + do + { + state = ApplyMove(state, from.Value, to.Value); + jumping = IsJumpMove(from.Value, to.Value); + if (jumping) + { + from = to; + valid = false; + do + { + to = GetCoordinate("+TO"); + if ((to != null) + && !IsOutOfBounds(to.Value) + && IsValidMove(state, from.Value, to.Value) + && IsJumpMove(from.Value, to.Value)) + { + valid = true; + } + + if (to != null && to.Value.x < 0 && to.Value.y < 0) + { + jumping = false; + break; + } + } + while (!valid); + } + } + while (jumping); + return state; +} + +bool CheckForComputerWin(int[,] state) +{ + bool playerAlive = false; + foreach (var piece in state) + { + if (piece > 0) + { + playerAlive = true; + break; + } + } + return !playerAlive; +} +bool CheckForPlayerWin(int[,] state) +{ + bool computerAlive = false; + foreach (var piece in state) + { + if (piece < 0) + { + computerAlive = true; + break; + } + } + return !computerAlive; +} + +void ComputerWins() +{ + Console.WriteLine("I WIN."); +} +void PlayerWins() +{ + Console.WriteLine("YOU WIN."); +} + +// Main program starts here + +WriteCenter("CHECKERS"); +WriteCenter("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); +SkipLines(3); +Console.WriteLine("THIS IS THE GAME OF CHECKERS. THE COMPUTER IS X,"); +Console.WriteLine("AND YOU ARE O. THE COMPUTER WILL MOVE FIRST."); +Console.WriteLine("SQUARES ARE REFERRED TO BY A COORDINATE SYSTEM."); +Console.WriteLine("(0,0) IS THE LOWER LEFT CORNER"); +Console.WriteLine("(0,7) IS THE UPPER LEFT CORNER"); +Console.WriteLine("(7,0) IS THE LOWER RIGHT CORNER"); +Console.WriteLine("(7,7) IS THE UPPER RIGHT CORNER"); +Console.WriteLine("THE COMPUTER WILL TYPE '+TO' WHEN YOU HAVE ANOTHER"); +Console.WriteLine("JUMP. TYPE TWO NEGATIVE NUMBERS IF YOU CANNOT JUMP."); +SkipLines(3); + +// initalize state - empty spots initialize to 0 +// set player pieces to 1, computer pieces to -1 +int[,] state = new int[8, 8] { + { 1, 0, 1, 0, 0, 0,-1, 0 }, + { 0, 1, 0, 0, 0,-1, 0,-1 }, + { 1, 0, 1, 0, 0, 0,-1, 0 }, + { 0, 1, 0, 0, 0,-1, 0,-1 }, + { 1, 0, 1, 0, 0, 0,-1, 0 }, + { 0, 1, 0, 0, 0,-1, 0,-1 }, + { 1, 0, 1, 0, 0, 0,-1, 0 }, + { 0, 1, 0, 0, 0,-1, 0,-1 }, +}; + +while (true) +{ + bool moveMade; + (moveMade, state) = ComputerTurn(state); + if (!moveMade) + { + // in the original program the computer wins if it cannot make a move + // I believe the player should win in this case, assuming the player can make a move + // if neither player can make a move, the game should be draw. + ComputerWins(); + break; + } + PrintBoard(state); + if (CheckForComputerWin(state)) + { + ComputerWins(); + break; + } + state = PlayerTurn(state); + if (CheckForPlayerWin(state)) + { + PlayerWins(); + break; + } +}