diff --git a/46 Hexapawn/csharp/Hexapawn.sln b/46 Hexapawn/csharp/Hexapawn.sln new file mode 100644 index 00000000..6cfc8a8f --- /dev/null +++ b/46 Hexapawn/csharp/Hexapawn.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexapawn", "Hexapawn\Hexapawn.csproj", "{679D95BE-6E0C-4D8C-A2D4-0957576B63F3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {679D95BE-6E0C-4D8C-A2D4-0957576B63F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {679D95BE-6E0C-4D8C-A2D4-0957576B63F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {679D95BE-6E0C-4D8C-A2D4-0957576B63F3}.Debug|x64.ActiveCfg = Debug|Any CPU + {679D95BE-6E0C-4D8C-A2D4-0957576B63F3}.Debug|x64.Build.0 = Debug|Any CPU + {679D95BE-6E0C-4D8C-A2D4-0957576B63F3}.Debug|x86.ActiveCfg = Debug|Any CPU + {679D95BE-6E0C-4D8C-A2D4-0957576B63F3}.Debug|x86.Build.0 = Debug|Any CPU + {679D95BE-6E0C-4D8C-A2D4-0957576B63F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {679D95BE-6E0C-4D8C-A2D4-0957576B63F3}.Release|Any CPU.Build.0 = Release|Any CPU + {679D95BE-6E0C-4D8C-A2D4-0957576B63F3}.Release|x64.ActiveCfg = Release|Any CPU + {679D95BE-6E0C-4D8C-A2D4-0957576B63F3}.Release|x64.Build.0 = Release|Any CPU + {679D95BE-6E0C-4D8C-A2D4-0957576B63F3}.Release|x86.ActiveCfg = Release|Any CPU + {679D95BE-6E0C-4D8C-A2D4-0957576B63F3}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/46 Hexapawn/csharp/Hexapawn/Board.cs b/46 Hexapawn/csharp/Hexapawn/Board.cs new file mode 100644 index 00000000..ba7d19ad --- /dev/null +++ b/46 Hexapawn/csharp/Hexapawn/Board.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using static Hexapawn.Pawn; + +namespace Hexapawn +{ + internal class Board : IEnumerable, IEquatable + { + private readonly Pawn[] _cells; + + public Board() + { + _cells = new[] + { + Black, Black, Black, + None, None, None, + White, White, White + }; + } + + public Board(params Pawn[] cells) + { + _cells = cells; + } + + public Pawn this[int index] + { + get => _cells[index - 1]; + set => _cells[index - 1] = value; + } + + public Board Reflected => new(Cell.AllCells.Select(c => this[c.Reflected]).ToArray()); + + public IEnumerator GetEnumerator() => _cells.OfType().GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public override string ToString() + { + var builder = new StringBuilder().AppendLine(); + for (int row = 0; row < 3; row++) + { + builder.Append(" "); + for (int col = 0; col < 3; col++) + { + builder.Append(_cells[row * 3 + col]); + } + builder.AppendLine(); + } + return builder.ToString(); + } + + public bool Equals(Board other) => other?.Zip(this).All(x => x.First == x.Second) ?? false; + + public override bool Equals(object obj) => Equals(obj as Board); + + public override int GetHashCode() + { + var hash = 19; + + for (int i = 0; i < 9; i++) + { + hash = hash * 53 + _cells[i].GetHashCode(); + } + + return hash; + } + } +} diff --git a/46 Hexapawn/csharp/Hexapawn/Cell.cs b/46 Hexapawn/csharp/Hexapawn/Cell.cs new file mode 100644 index 00000000..7647e0bc --- /dev/null +++ b/46 Hexapawn/csharp/Hexapawn/Cell.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; + +namespace Hexapawn +{ + // Represents a cell on the board, numbered 1 to 9, with support for finding the reflection of the reference around + // the middle column of the board. + internal class Cell + { + private static readonly Cell[] _cells = new Cell[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + private static readonly Cell[] _reflected = new Cell[] { 3, 2, 1, 6, 5, 4, 9, 8, 7 }; + + private readonly int _number; + + private Cell(int number) + { + if (number < 1 || number > 9) + { + throw new ArgumentOutOfRangeException(nameof(number), number, "Must be from 1 to 9"); + } + + _number = number; + } + + // Facilitates enumerating all the cells. + public static IEnumerable AllCells => _cells; + + // Takes a value input by the user and attempts to create a Cell reference + public static bool TryCreate(float input, out Cell cell) + { + if (IsInteger(input) && input >= 1 && input <= 9) + { + cell = (int)input; + return true; + } + + cell = default; + return false; + + static bool IsInteger(float value) => value - (int)value == 0; + } + + // Returns the reflection of the cell reference about the middle column of the board. + public Cell Reflected => _reflected[_number - 1]; + + // Allows the cell reference to be used where an int is expected, such as the indexer in Board. + public static implicit operator int(Cell c) => c._number; + + public static implicit operator Cell(int number) => new(number); + + public override string ToString() => _number.ToString(); + } +} diff --git a/46 Hexapawn/csharp/Hexapawn/Computer.cs b/46 Hexapawn/csharp/Hexapawn/Computer.cs new file mode 100644 index 00000000..41e2b4a2 --- /dev/null +++ b/46 Hexapawn/csharp/Hexapawn/Computer.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using static Hexapawn.Pawn; +using static Hexapawn.Cell; + +namespace Hexapawn +{ + /// + /// Encapsulates the logic of the computer player. + /// + internal class Computer : IPlayer + { + private readonly Random _random = new(); + private readonly Dictionary> _potentialMoves; + private (List, Move) _lastMove; + + public Computer() + { + // This dictionary implements the data in the original code, which encodes board positions for which the + // computer has a legal move, and the list of possible moves for each position: + // 900 DATA -1,-1,-1,1,0,0,0,1,1,-1,-1,-1,0,1,0,1,0,1 + // 905 DATA -1,0,-1,-1,1,0,0,0,1,0,-1,-1,1,-1,0,0,0,1 + // 910 DATA -1,0,-1,1,1,0,0,1,0,-1,-1,0,1,0,1,0,0,1 + // 915 DATA 0,-1,-1,0,-1,1,1,0,0,0,-1,-1,-1,1,1,1,0,0 + // 920 DATA -1,0,-1,-1,0,1,0,1,0,0,-1,-1,0,1,0,0,0,1 + // 925 DATA 0,-1,-1,0,1,0,1,0,0,-1,0,-1,1,0,0,0,0,1 + // 930 DATA 0,0,-1,-1,-1,1,0,0,0,-1,0,0,1,1,1,0,0,0 + // 935 DATA 0,-1,0,-1,1,1,0,0,0,-1,0,0,-1,-1,1,0,0,0 + // 940 DATA 0,0,-1,-1,1,0,0,0,0,0,-1,0,1,-1,0,0,0,0 + // 945 DATA -1,0,0,-1,1,0,0,0,0 + // 950 DATA 24,25,36,0,14,15,36,0,15,35,36,47,36,58,59,0 + // 955 DATA 15,35,36,0,24,25,26,0,26,57,58,0 + // 960 DATA 26,35,0,0,47,48,0,0,35,36,0,0,35,36,0,0 + // 965 DATA 36,0,0,0,47,58,0,0,15,0,0,0 + // 970 DATA 26,47,0,0,47,58,0,0,35,36,47,0,28,58,0,0,15,47,0,0 + // + // The original code loaded this data into two arrays. + // 40 FOR I=1 TO 19: FOR J=1 TO 9: READ B(I,J): NEXT J: NEXT I + // 45 FOR I=1 TO 19: FOR J=1 TO 4: READ M(I,J): NEXT J: NEXT I + // + // When finding moves for the computer the first array was searched for the current board position, or the + // reflection of it, and the resulting index was used in the second array to get the possible moves. + // With this dictionary we can just use the current board as the index, and retrieve a list of moves for + // consideration by the computer. + _potentialMoves = new() + { + [new(Black, Black, Black, White, None, None, None, White, White)] = Moves((2, 4), (2, 5), (3, 6)), + [new(Black, Black, Black, None, White, None, White, None, White)] = Moves((1, 4), (1, 5), (3, 6)), + [new(Black, None, Black, Black, White, None, None, None, White)] = Moves((1, 5), (3, 5), (3, 6), (4, 7)), + [new(None, Black, Black, White, Black, None, None, None, White)] = Moves((3, 6), (5, 8), (5, 9)), + [new(Black, None, Black, White, White, None, None, White, None)] = Moves((1, 5), (3, 5), (3, 6)), + [new(Black, Black, None, White, None, White, None, None, White)] = Moves((2, 4), (2, 5), (2, 6)), + [new(None, Black, Black, None, Black, White, White, None, None)] = Moves((2, 6), (5, 7), (5, 8)), + [new(None, Black, Black, Black, White, White, White, None, None)] = Moves((2, 6), (3, 5)), + [new(Black, None, Black, Black, None, White, None, White, None)] = Moves((4, 7), (4, 8)), + [new(None, Black, Black, None, White, None, None, None, White)] = Moves((3, 5), (3, 6)), + [new(None, Black, Black, None, White, None, White, None, None)] = Moves((3, 5), (3, 6)), + [new(Black, None, Black, White, None, None, None, None, White)] = Moves((3, 6)), + [new(None, None, Black, Black, Black, White, None, None, None)] = Moves((4, 7), (5, 8)), + [new(Black, None, None, White, White, White, None, None, None)] = Moves((1, 5)), + [new(None, Black, None, Black, White, White, None, None, None)] = Moves((2, 6), (4, 7)), + [new(Black, None, None, Black, Black, White, None, None, None)] = Moves((4, 7), (5, 8)), + [new(None, None, Black, Black, White, None, None, None, None)] = Moves((3, 5), (3, 6), (4, 7)), + [new(None, Black, None, White, Black, None, None, None, None)] = Moves((2, 8), (5, 8)), + [new(Black, None, None, Black, White, None, None, None, None)] = Moves((1, 5), (4, 7)) + }; + } + + public int Wins { get; private set; } + + public void AddWin() => Wins++; + + // Try to make a move. We first try to find a legal move for the current board position. + public bool TryMove(Board board) + { + if (TryGetMoves(board, out var moves, out var reflected) && + TrySelectMove(moves, out var move)) + { + // We've found a move, so we record it as the last move made, and then announce and make the move. + _lastMove = (moves, move); + + // If we found the move from a reflacted match of the board we need to make the reflected move. + if (reflected) { move = move.Reflected; } + + Console.WriteLine($"I move {move}"); + move.Execute(board); + return true; + } + + // We haven't found a move for this board position, so remove the previous move that led to this board + // position from future consideration. We don't want to make that move again, because we now know it's a + // non-winning move. + ExcludeLastMoveFromFuturePlay(); + + return false; + } + + // Looks up the given board and its reflection in the potential moves dictionary. If it's found then we have a + // list of potential moves. If the board is not found in the dictionary then the computer has no legal moves, + // and the human player wins. + private bool TryGetMoves(Board board, out List moves, out bool reflected) + { + if (_potentialMoves.TryGetValue(board, out moves)) + { + reflected = false; + return true; + } + + if (_potentialMoves.TryGetValue(board.Reflected, out moves)) + { + reflected = true; + return true; + } + + reflected = default; + return false; + } + + // Get a random move from the list. If the list is empty, then we've previously eliminated all the moves for + // this board position as being non-winning moves. We therefore resign the game. + private bool TrySelectMove(List moves, out Move move) + { + if (moves.Any()) + { + move = moves[_random.Next(moves.Count)]; + return true; + } + + Console.Write("I resign."); + move = null; + return false; + } + + private void ExcludeLastMoveFromFuturePlay() + { + var (moves, move) = _lastMove; + moves.Remove(move); + } + + private static List Moves(params Move[] moves) => moves.ToList(); + + public bool IsFullyAdvanced(Board board) => + board[9] == Black || board[8] == Black || board[7] == Black; + } +} diff --git a/46 Hexapawn/csharp/Hexapawn/Game.cs b/46 Hexapawn/csharp/Hexapawn/Game.cs new file mode 100644 index 00000000..95e321ff --- /dev/null +++ b/46 Hexapawn/csharp/Hexapawn/Game.cs @@ -0,0 +1,49 @@ +using System; + +namespace Hexapawn +{ + // Runs a single game of Hexapawn + internal class Game + { + private readonly Board _board; + private readonly Human _human; + private readonly Computer _computer; + + public Game(Human human, Computer computer) + { + _board = new Board(); + _human = human; + _computer = computer; + } + + public IPlayer Play() + { + Console.WriteLine(_board); + + while(true) + { + _human.Move(_board); + + Console.WriteLine(_board); + + if (!_computer.TryMove(_board)) + { + return _human; + } + + Console.WriteLine(_board); + + if (_computer.IsFullyAdvanced(_board) || _human.HasNoPawns(_board)) + { + return _computer; + } + + if (!_human.HasLegalMove(_board)) + { + Console.Write("You can't move, so "); + return _computer; + } + } + } + } +} diff --git a/46 Hexapawn/csharp/Hexapawn/GameSeries.cs b/46 Hexapawn/csharp/Hexapawn/GameSeries.cs new file mode 100644 index 00000000..6f45cab7 --- /dev/null +++ b/46 Hexapawn/csharp/Hexapawn/GameSeries.cs @@ -0,0 +1,27 @@ +using System; + +namespace Hexapawn +{ + // Runs series of games between the computer and the human player + internal class GameSeries + { + private readonly Computer _computer = new(); + private readonly Human _human = new(); + + public void Play() + { + while (true) + { + var game = new Game(_human, _computer); + + var winner = game.Play(); + winner.AddWin(); + Console.WriteLine(winner == _computer ? "I win." : "You win."); + + Console.Write($"I have won {_computer.Wins} and you {_human.Wins}"); + Console.WriteLine($" out of {_computer.Wins + _human.Wins} games."); + Console.WriteLine(); + } + } + } +} diff --git a/46 Hexapawn/csharp/Hexapawn/Hexapawn.csproj b/46 Hexapawn/csharp/Hexapawn/Hexapawn.csproj new file mode 100644 index 00000000..20827042 --- /dev/null +++ b/46 Hexapawn/csharp/Hexapawn/Hexapawn.csproj @@ -0,0 +1,8 @@ + + + + Exe + net5.0 + + + diff --git a/46 Hexapawn/csharp/Hexapawn/Human.cs b/46 Hexapawn/csharp/Hexapawn/Human.cs new file mode 100644 index 00000000..d2d24118 --- /dev/null +++ b/46 Hexapawn/csharp/Hexapawn/Human.cs @@ -0,0 +1,64 @@ +using System; +using System.Linq; +using static Hexapawn.Cell; +using static Hexapawn.Move; +using static Hexapawn.Pawn; + +namespace Hexapawn +{ + internal class Human : IPlayer + { + public int Wins { get; private set; } + + public void Move(Board board) + { + while (true) + { + var move = Input.GetMove("Your move"); + + if (TryExecute(board, move)) { return; } + + Console.WriteLine("Illegal move."); + } + } + + public void AddWin() => Wins++; + + public bool HasLegalMove(Board board) + { + foreach (var from in AllCells.Where(c => c > 3)) + { + if (board[from] != White) { continue; } + + if (HasLegalMove(board, from)) + { + return true; + } + } + + return false; + } + + private bool HasLegalMove(Board board, Cell from) => + Right(from).IsRightDiagonalToCapture(board) || + Straight(from).IsStraightMoveToEmptySpace(board) || + from > 4 && Left(from).IsLeftDiagonalToCapture(board); + + public bool HasNoPawns(Board board) => board.All(c => c != White); + + public bool TryExecute(Board board, Move move) + { + if (board[move.From] != White) { return false; } + + if (move.IsStraightMoveToEmptySpace(board) || + move.IsLeftDiagonalToCapture(board) || + move.IsRightDiagonalToCapture(board)) + { + move.Execute(board); + return true; + } + + return false; + } + } +} diff --git a/46 Hexapawn/csharp/Hexapawn/IPlayer.cs b/46 Hexapawn/csharp/Hexapawn/IPlayer.cs new file mode 100644 index 00000000..7313904e --- /dev/null +++ b/46 Hexapawn/csharp/Hexapawn/IPlayer.cs @@ -0,0 +1,8 @@ +namespace Hexapawn +{ + // An interface implemented by a player of the game to track the number of wins. + internal interface IPlayer + { + void AddWin(); + } +} diff --git a/46 Hexapawn/csharp/Hexapawn/Input.cs b/46 Hexapawn/csharp/Hexapawn/Input.cs new file mode 100644 index 00000000..d169f2f6 --- /dev/null +++ b/46 Hexapawn/csharp/Hexapawn/Input.cs @@ -0,0 +1,112 @@ +using System; +using System.Linq; + +namespace Hexapawn +{ + // Provides input methods which emulate the BASIC interpreter's keyboard input routines + internal static class Input + { + internal static char GetYesNo(string prompt) + { + while (true) + { + Console.Write($"{prompt} (Y-N)? "); + var response = Console.ReadLine().FirstOrDefault(); + if ("YyNn".Contains(response)) + { + return char.ToUpperInvariant(response); + } + } + } + + // Implements original code: + // 120 PRINT "YOUR MOVE"; + // 121 INPUT M1,M2 + // 122 IF M1=INT(M1)AND M2=INT(M2)AND M1>0 AND M1<10 AND M2>0 AND M2<10 THEN 130 + // 123 PRINT "ILLEGAL CO-ORDINATES." + // 124 GOTO 120 + internal static Move GetMove(string prompt) + { + while(true) + { + ReadNumbers(prompt, out var from, out var to); + + if (Move.TryCreate(from, to, out var move)) + { + return move; + } + + Console.WriteLine("Illegal Coordinates."); + } + } + + internal static void Prompt(string text = "") => Console.Write($"{text}? "); + + internal static void ReadNumbers(string prompt, out float number1, out float number2) + { + while (!TryReadNumbers(prompt, out number1, out number2)) + { + prompt = ""; + } + } + + private static bool TryReadNumbers(string prompt, out float number1, out float number2) + { + Prompt(prompt); + var inputValues = ReadStrings(); + + if (!TryParseNumber(inputValues[0], out number1)) + { + number2 = default; + return false; + } + + if (inputValues.Length == 1) + { + return TryReadNumber("?", out number2); + } + + if (!TryParseNumber(inputValues[1], out number2)) + { + number2 = default; + return false; + } + + if (inputValues.Length > 2) + { + Console.WriteLine("!Extra input ingored"); + } + + return true; + } + + private static bool TryReadNumber(string prompt, out float number) + { + Prompt(prompt); + var inputValues = ReadStrings(); + + if (!TryParseNumber(inputValues[0], out number)) + { + return false; + } + + if (inputValues.Length > 1) + { + Console.WriteLine("!Extra input ingored"); + } + + return true; + } + + private static string[] ReadStrings() => Console.ReadLine().Split(',', StringSplitOptions.TrimEntries); + + private static bool TryParseNumber(string text, out float number) + { + if (float.TryParse(text, out number)) { return true; } + + Console.WriteLine("!Number expected - retry input line"); + number = default; + return false; + } + } +} diff --git a/46 Hexapawn/csharp/Hexapawn/Move.cs b/46 Hexapawn/csharp/Hexapawn/Move.cs new file mode 100644 index 00000000..9581a959 --- /dev/null +++ b/46 Hexapawn/csharp/Hexapawn/Move.cs @@ -0,0 +1,68 @@ +using static Hexapawn.Pawn; + +namespace Hexapawn +{ + /// + /// Represents a move which may, or may not, be legal. + /// + internal class Move + { + private readonly Cell _from; + private readonly Cell _to; + private readonly int _metric; + + public Move(Cell from, Cell to) + { + _from = from; + _to = to; + _metric = _from - _to; + } + + public void Deconstruct(out Cell from, out Cell to) + { + from = _from; + to = _to; + } + + public Cell From => _from; + + // Produces the mirror image of the current moved, reflected around the central column of the board. + public Move Reflected => (_from.Reflected, _to.Reflected); + + // Allows a tuple of two ints to be implicitly converted to a Move. + public static implicit operator Move((int From, int To) value) => new(value.From, value.To); + + // Takes floating point coordinates, presumably from keyboard input, and attempts to create a Move object. + public static bool TryCreate(float input1, float input2, out Move move) + { + if (Cell.TryCreate(input1, out var from) && + Cell.TryCreate(input2, out var to)) + { + move = (from, to); + return true; + } + + move = default; + return false; + } + + public static Move Right(Cell from) => (from, from - 2); + public static Move Straight(Cell from) => (from, from - 3); + public static Move Left(Cell from) => (from, from - 4); + + public bool IsStraightMoveToEmptySpace(Board board) => _metric == 3 && board[_to] == None; + + public bool IsLeftDiagonalToCapture(Board board) => _metric == 4 && _from != 7 && board[_to] == Black; + + public bool IsRightDiagonalToCapture(Board board) => + _metric == 2 && _from != 9 && _from != 6 && board[_to] == Black; + + public void Execute(Board board) + { + board[_to] = board[_from]; + board[_from] = None; + } + + public override string ToString() => $"from {_from} to {_to}"; + } +} diff --git a/46 Hexapawn/csharp/Hexapawn/Pawn.cs b/46 Hexapawn/csharp/Hexapawn/Pawn.cs new file mode 100644 index 00000000..7a4c5633 --- /dev/null +++ b/46 Hexapawn/csharp/Hexapawn/Pawn.cs @@ -0,0 +1,19 @@ +namespace Hexapawn +{ + // Represents the contents of a cell on the board + internal class Pawn + { + public static readonly Pawn Black = new('X'); + public static readonly Pawn White = new('O'); + public static readonly Pawn None = new('.'); + + private readonly char _symbol; + + private Pawn(char symbol) + { + _symbol = symbol; + } + + public override string ToString() => _symbol.ToString(); + } +} diff --git a/46 Hexapawn/csharp/Hexapawn/Program.cs b/46 Hexapawn/csharp/Hexapawn/Program.cs new file mode 100644 index 00000000..59917dfa --- /dev/null +++ b/46 Hexapawn/csharp/Hexapawn/Program.cs @@ -0,0 +1,71 @@ +using System; + +namespace Hexapawn +{ + // Hexapawn: Interpretation of hexapawn game as presented in + // Martin Gardner's "The Unexpected Hanging and Other Mathematic + // al Diversions", Chapter Eight: A Matchbox Game-Learning Machine. + // Original version for H-P timeshare system by R.A. Kaapke 5/5/76 + // Instructions by Jeff Dalton + // Conversion to MITS BASIC by Steve North + // Conversion to C# by Andrew Cooper + class Program + { + static void Main() + { + DisplayTitle(); + + if (Input.GetYesNo("Instructions") == 'Y') + { + DisplayInstructions(); + } + + var games = new GameSeries(); + + games.Play(); + } + + private static void DisplayTitle() + { + Console.WriteLine(" Hexapawn"); + Console.WriteLine(" Creative Computing Morristown, New Jersey"); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + } + + private static void DisplayInstructions() + { + Console.WriteLine(); + Console.WriteLine("This program plays the game of Hexapawn."); + Console.WriteLine("Hexapawn is played with Chess pawns on a 3 by 3 board."); + Console.WriteLine("The pawns are move as in Chess - one space forward to"); + Console.WriteLine("an empty space, or one space forward and diagonally to"); + Console.WriteLine("capture an opposing man. On the board, your pawns"); + Console.WriteLine("are 'O', the computer's pawns are 'X', and empty"); + Console.WriteLine("squares are '.'. To enter a move, type the number of"); + Console.WriteLine("the square you are moving from, followed by the number"); + Console.WriteLine("of the square you will move to. The numbers must be"); + Console.WriteLine("separated by a comma."); + Console.WriteLine(); + Console.WriteLine("The computer starts a series of games knowing only when"); + Console.WriteLine("the game is won (a draw is impossible) and how to move."); + Console.WriteLine("It has no strategy at first and just moves randomly."); + Console.WriteLine("However, it learns from each game. Thus winning becomes"); + Console.WriteLine("more and more difficult. Also, to help offset your"); + Console.WriteLine("initial advantage, you will not be told how to win the"); + Console.WriteLine("game but must learn this by playing."); + Console.WriteLine(); + Console.WriteLine("The numbering of the board is as follows:"); + Console.WriteLine(" 123"); + Console.WriteLine(" 456"); + Console.WriteLine(" 789"); + Console.WriteLine(); + Console.WriteLine("For example, to move your rightmost pawn forward,"); + Console.WriteLine("you would type 9,6 in response to the question"); + Console.WriteLine("'Your move ?'. Since I'm a good sport, you'll always"); + Console.WriteLine("go first."); + Console.WriteLine(); + } + } +}