mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-23 15:37:51 -08:00
Convert Hexapawn to common library
This commit is contained in:
@@ -82,9 +82,21 @@ public interface IReadWrite
|
|||||||
/// <param name="value">The <see cref="float" /> to be written.</param>
|
/// <param name="value">The <see cref="float" /> to be written.</param>
|
||||||
void WriteLine(float value);
|
void WriteLine(float value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes an <see cref="object" /> to output.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The <see cref="object" /> to be written.</param>
|
||||||
|
void Write(object value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes an <see cref="object" /> to output.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The <see cref="object" /> to be written.</param>
|
||||||
|
void WriteLine(object value);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes the contents of a <see cref="Stream" /> to output.
|
/// Writes the contents of a <see cref="Stream" /> to output.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="stream">The <see cref="Stream" /> to be written.</param>
|
/// <param name="stream">The <see cref="Stream" /> to be written.</param>
|
||||||
void Write(Stream stream);
|
void Write(Stream stream, bool keepOpen = false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,13 +95,19 @@ public class TextIO : IReadWrite
|
|||||||
|
|
||||||
public void WriteLine(float value) => _output.WriteLine(GetString(value));
|
public void WriteLine(float value) => _output.WriteLine(GetString(value));
|
||||||
|
|
||||||
public void Write(Stream stream)
|
public void Write(object value) => _output.Write(value.ToString());
|
||||||
|
|
||||||
|
public void WriteLine(object value) => _output.WriteLine(value.ToString());
|
||||||
|
|
||||||
|
public void Write(Stream stream, bool keepOpen = false)
|
||||||
{
|
{
|
||||||
using var reader = new StreamReader(stream);
|
using var reader = new StreamReader(stream);
|
||||||
while (!reader.EndOfStream)
|
while (!reader.EndOfStream)
|
||||||
{
|
{
|
||||||
_output.WriteLine(reader.ReadLine());
|
_output.WriteLine(reader.ReadLine());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!keepOpen) { stream?.Dispose(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetString(float value) => value < 0 ? $"{value} " : $" {value} ";
|
private string GetString(float value) => value < 0 ? $"{value} " : $" {value} ";
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ using System.Text;
|
|||||||
|
|
||||||
using static Hexapawn.Pawn;
|
using static Hexapawn.Pawn;
|
||||||
|
|
||||||
namespace Hexapawn
|
namespace Hexapawn;
|
||||||
|
|
||||||
|
internal class Board : IEnumerable<Pawn>, IEquatable<Board>
|
||||||
{
|
{
|
||||||
internal class Board : IEnumerable<Pawn>, IEquatable<Board>
|
|
||||||
{
|
|
||||||
private readonly Pawn[] _cells;
|
private readonly Pawn[] _cells;
|
||||||
|
|
||||||
public Board()
|
public Board()
|
||||||
@@ -69,5 +69,4 @@ namespace Hexapawn
|
|||||||
|
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,25 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Hexapawn
|
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
|
||||||
{
|
{
|
||||||
// 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[] _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 static readonly Cell[] _reflected = new Cell[] { 3, 2, 1, 6, 5, 4, 9, 8, 7 };
|
||||||
|
|
||||||
private readonly int _number;
|
private readonly int _number;
|
||||||
|
|
||||||
private Cell(int number)
|
private Cell(int number)
|
||||||
{
|
{
|
||||||
if (number < 1 || number > 9)
|
if (number < 1 || number > 9)
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(number), number, "Must be from 1 to 9");
|
throw new ArgumentOutOfRangeException(nameof(number), number, "Must be from 1 to 9");
|
||||||
}
|
}
|
||||||
|
|
||||||
_number = number;
|
_number = number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Facilitates enumerating all the cells.
|
// Facilitates enumerating all the cells.
|
||||||
public static IEnumerable<Cell> AllCells => _cells;
|
public static IEnumerable<Cell> AllCells => _cells;
|
||||||
|
|
||||||
// Takes a value input by the user and attempts to create a Cell reference
|
// Takes a value input by the user and attempts to create a Cell reference
|
||||||
public static bool TryCreate(float input, out Cell cell)
|
public static bool TryCreate(float input, out Cell cell)
|
||||||
{
|
{
|
||||||
@@ -33,21 +28,14 @@ namespace Hexapawn
|
|||||||
cell = (int)input;
|
cell = (int)input;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
cell = default;
|
cell = default;
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
static bool IsInteger(float value) => value - (int)value == 0;
|
static bool IsInteger(float value) => value - (int)value == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the reflection of the cell reference about the middle column of the board.
|
// Returns the reflection of the cell reference about the middle column of the board.
|
||||||
public Cell Reflected => _reflected[_number - 1];
|
public Cell Reflected => _reflected[_number - 1];
|
||||||
|
|
||||||
// Allows the cell reference to be used where an int is expected, such as the indexer in Board.
|
// 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 int(Cell c) => c._number;
|
||||||
|
|
||||||
public static implicit operator Cell(int number) => new(number);
|
public static implicit operator Cell(int number) => new(number);
|
||||||
|
|
||||||
public override string ToString() => _number.ToString();
|
public override string ToString() => _number.ToString();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Games.Common.IO;
|
||||||
|
using Games.Common.Randomness;
|
||||||
using static Hexapawn.Pawn;
|
using static Hexapawn.Pawn;
|
||||||
using static Hexapawn.Cell;
|
|
||||||
|
|
||||||
namespace Hexapawn
|
namespace Hexapawn;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encapsulates the logic of the computer player.
|
||||||
|
/// </summary>
|
||||||
|
internal class Computer
|
||||||
{
|
{
|
||||||
/// <summary>
|
private readonly TextIO _io;
|
||||||
/// Encapsulates the logic of the computer player.
|
private readonly IRandom _random;
|
||||||
/// </summary>
|
|
||||||
internal class Computer : IPlayer
|
|
||||||
{
|
|
||||||
private readonly Random _random = new();
|
|
||||||
private readonly Dictionary<Board, List<Move>> _potentialMoves;
|
private readonly Dictionary<Board, List<Move>> _potentialMoves;
|
||||||
private (List<Move>, Move) _lastMove;
|
private (List<Move>, Move) _lastMove;
|
||||||
|
public Computer(TextIO io, IRandom random)
|
||||||
public Computer()
|
|
||||||
{
|
{
|
||||||
|
_io = io;
|
||||||
|
_random = random;
|
||||||
|
|
||||||
// This dictionary implements the data in the original code, which encodes board positions for which the
|
// 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:
|
// 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
|
// 900 DATA -1,-1,-1,1,0,0,0,1,1,-1,-1,-1,0,1,0,1,0,1
|
||||||
@@ -67,10 +71,6 @@ namespace Hexapawn
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// Try to make a move. We first try to find a legal move for the current board position.
|
||||||
public bool TryMove(Board board)
|
public bool TryMove(Board board)
|
||||||
{
|
{
|
||||||
@@ -79,20 +79,16 @@ namespace Hexapawn
|
|||||||
{
|
{
|
||||||
// We've found a move, so we record it as the last move made, and then announce and make the 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);
|
_lastMove = (moves, move);
|
||||||
|
|
||||||
// If we found the move from a reflacted match of the board we need to make the reflected 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; }
|
if (reflected) { move = move.Reflected; }
|
||||||
|
_io.WriteLine($"I move {move}");
|
||||||
Console.WriteLine($"I move {move}");
|
|
||||||
move.Execute(board);
|
move.Execute(board);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We haven't found a move for this board position, so remove the previous move that led to this board
|
// 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
|
// position from future consideration. We don't want to make that move again, because we now know it's a
|
||||||
// non-winning move.
|
// non-winning move.
|
||||||
ExcludeLastMoveFromFuturePlay();
|
ExcludeLastMoveFromFuturePlay();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,13 +102,11 @@ namespace Hexapawn
|
|||||||
reflected = false;
|
reflected = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_potentialMoves.TryGetValue(board.Reflected, out moves))
|
if (_potentialMoves.TryGetValue(board.Reflected, out moves))
|
||||||
{
|
{
|
||||||
reflected = true;
|
reflected = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
reflected = default;
|
reflected = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -126,8 +120,7 @@ namespace Hexapawn
|
|||||||
move = moves[_random.Next(moves.Count)];
|
move = moves[_random.Next(moves.Count)];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
_io.WriteLine("I resign.");
|
||||||
Console.Write("I resign.");
|
|
||||||
move = null;
|
move = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -142,5 +135,4 @@ namespace Hexapawn
|
|||||||
|
|
||||||
public bool IsFullyAdvanced(Board board) =>
|
public bool IsFullyAdvanced(Board board) =>
|
||||||
board[9] == Black || board[8] == Black || board[7] == Black;
|
board[9] == Black || board[8] == Black || board[7] == Black;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,40 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Games.Common.IO;
|
||||||
|
|
||||||
namespace Hexapawn
|
namespace Hexapawn;
|
||||||
|
|
||||||
|
// A single game of Hexapawn
|
||||||
|
internal class Game
|
||||||
{
|
{
|
||||||
// Runs a single game of Hexapawn
|
private readonly TextIO _io;
|
||||||
internal class Game
|
|
||||||
{
|
|
||||||
private readonly Board _board;
|
private readonly Board _board;
|
||||||
private readonly Human _human;
|
|
||||||
private readonly Computer _computer;
|
|
||||||
|
|
||||||
public Game(Human human, Computer computer)
|
public Game(TextIO io)
|
||||||
{
|
{
|
||||||
_board = new Board();
|
_board = new Board();
|
||||||
_human = human;
|
_io = io;
|
||||||
_computer = computer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IPlayer Play()
|
public object Play(Human human, Computer computer)
|
||||||
{
|
{
|
||||||
Console.WriteLine(_board);
|
_io.WriteLine(_board);
|
||||||
|
|
||||||
while(true)
|
while(true)
|
||||||
{
|
{
|
||||||
_human.Move(_board);
|
human.Move(_board);
|
||||||
|
_io.WriteLine(_board);
|
||||||
Console.WriteLine(_board);
|
if (!computer.TryMove(_board))
|
||||||
|
|
||||||
if (!_computer.TryMove(_board))
|
|
||||||
{
|
{
|
||||||
return _human;
|
return human;
|
||||||
}
|
}
|
||||||
|
_io.WriteLine(_board);
|
||||||
Console.WriteLine(_board);
|
if (computer.IsFullyAdvanced(_board) || human.HasNoPawns(_board))
|
||||||
|
|
||||||
if (_computer.IsFullyAdvanced(_board) || _human.HasNoPawns(_board))
|
|
||||||
{
|
{
|
||||||
return _computer;
|
return computer;
|
||||||
}
|
}
|
||||||
|
if (!human.HasLegalMove(_board))
|
||||||
if (!_human.HasLegalMove(_board))
|
|
||||||
{
|
{
|
||||||
Console.Write("You can't move, so ");
|
_io.Write("You can't move, so ");
|
||||||
return _computer;
|
return computer;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,47 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Games.Common.IO;
|
||||||
|
using Games.Common.Randomness;
|
||||||
|
using Hexapawn.Resources;
|
||||||
|
|
||||||
namespace Hexapawn
|
namespace Hexapawn;
|
||||||
|
|
||||||
|
// Runs series of games between the computer and the human player
|
||||||
|
internal class GameSeries
|
||||||
{
|
{
|
||||||
// Runs series of games between the computer and the human player
|
private readonly TextIO _io;
|
||||||
internal class GameSeries
|
private readonly Computer _computer;
|
||||||
|
private readonly Human _human;
|
||||||
|
private readonly Dictionary<object, int> _wins;
|
||||||
|
|
||||||
|
public GameSeries(TextIO io, IRandom random)
|
||||||
{
|
{
|
||||||
private readonly Computer _computer = new();
|
_io = io;
|
||||||
private readonly Human _human = new();
|
_computer = new(io, random);
|
||||||
|
_human = new(io);
|
||||||
|
_wins = new() { [_computer] = 0, [_human] = 0 };
|
||||||
|
}
|
||||||
|
|
||||||
public void Play()
|
public void Play()
|
||||||
{
|
{
|
||||||
|
_io.Write(Resource.Streams.Title);
|
||||||
|
|
||||||
|
if (_io.GetYesNo("Instructions") == 'Y')
|
||||||
|
{
|
||||||
|
_io.Write(Resource.Streams.Instructions);
|
||||||
|
}
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var game = new Game(_human, _computer);
|
var game = new Game(_io);
|
||||||
|
|
||||||
var winner = game.Play();
|
var winner = game.Play(_human, _computer);
|
||||||
winner.AddWin();
|
_wins[winner]++;
|
||||||
Console.WriteLine(winner == _computer ? "I win." : "You win.");
|
_io.WriteLine(winner == _computer ? "I win." : "You win.");
|
||||||
|
|
||||||
Console.Write($"I have won {_computer.Wins} and you {_human.Wins}");
|
_io.Write($"I have won {_wins[_computer]} and you {_wins[_human]}");
|
||||||
Console.WriteLine($" out of {_computer.Wins + _human.Wins} games.");
|
_io.WriteLine($" out of {_wins.Values.Sum()} games.");
|
||||||
Console.WriteLine();
|
_io.WriteLine();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Resources\*.txt" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
|
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,29 +1,33 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Games.Common.IO;
|
||||||
using static Hexapawn.Cell;
|
using static Hexapawn.Cell;
|
||||||
using static Hexapawn.Move;
|
using static Hexapawn.Move;
|
||||||
using static Hexapawn.Pawn;
|
using static Hexapawn.Pawn;
|
||||||
|
|
||||||
namespace Hexapawn
|
namespace Hexapawn;
|
||||||
|
|
||||||
|
internal class Human
|
||||||
{
|
{
|
||||||
internal class Human : IPlayer
|
private readonly TextIO _io;
|
||||||
|
|
||||||
|
public Human(TextIO io)
|
||||||
{
|
{
|
||||||
public int Wins { get; private set; }
|
_io = io;
|
||||||
|
}
|
||||||
|
|
||||||
public void Move(Board board)
|
public void Move(Board board)
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var move = Input.GetMove("Your move");
|
var move = _io.ReadMove("Your move");
|
||||||
|
|
||||||
if (TryExecute(board, move)) { return; }
|
if (TryExecute(board, move)) { return; }
|
||||||
|
|
||||||
Console.WriteLine("Illegal move.");
|
_io.WriteLine("Illegal move.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddWin() => Wins++;
|
|
||||||
|
|
||||||
public bool HasLegalMove(Board board)
|
public bool HasLegalMove(Board board)
|
||||||
{
|
{
|
||||||
foreach (var from in AllCells.Where(c => c > 3))
|
foreach (var from in AllCells.Where(c => c > 3))
|
||||||
@@ -60,5 +64,4 @@ namespace Hexapawn
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace Hexapawn
|
|
||||||
{
|
|
||||||
// An interface implemented by a player of the game to track the number of wins.
|
|
||||||
internal interface IPlayer
|
|
||||||
{
|
|
||||||
void AddWin();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
42
46_Hexapawn/csharp/IReadWriteExtensions.cs
Normal file
42
46_Hexapawn/csharp/IReadWriteExtensions.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Games.Common.IO;
|
||||||
|
|
||||||
|
namespace Hexapawn;
|
||||||
|
|
||||||
|
// Provides input methods which emulate the BASIC interpreter's keyboard input routines
|
||||||
|
internal static class IReadWriteExtensions
|
||||||
|
{
|
||||||
|
internal static char GetYesNo(this IReadWrite io, string prompt)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var response = io.ReadString($"{prompt} (Y-N)").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 ReadMove(this IReadWrite io, string prompt)
|
||||||
|
{
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
var (from, to) = io.Read2Numbers(prompt);
|
||||||
|
|
||||||
|
if (Move.TryCreate(from, to, out var move))
|
||||||
|
{
|
||||||
|
return move;
|
||||||
|
}
|
||||||
|
|
||||||
|
io.WriteLine("Illegal Coordinates.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
using static Hexapawn.Pawn;
|
using static Hexapawn.Pawn;
|
||||||
|
|
||||||
namespace Hexapawn
|
namespace Hexapawn;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a move which may, or may not, be legal.
|
||||||
|
/// </summary>
|
||||||
|
internal class Move
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Represents a move which may, or may not, be legal.
|
|
||||||
/// </summary>
|
|
||||||
internal class Move
|
|
||||||
{
|
|
||||||
private readonly Cell _from;
|
private readonly Cell _from;
|
||||||
private readonly Cell _to;
|
private readonly Cell _to;
|
||||||
private readonly int _metric;
|
private readonly int _metric;
|
||||||
@@ -64,5 +64,4 @@ namespace Hexapawn
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"from {_from} to {_to}";
|
public override string ToString() => $"from {_from} to {_to}";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
namespace Hexapawn
|
namespace Hexapawn;
|
||||||
|
|
||||||
|
// Represents the contents of a cell on the board
|
||||||
|
internal class Pawn
|
||||||
{
|
{
|
||||||
// Represents the contents of a cell on the board
|
|
||||||
internal class Pawn
|
|
||||||
{
|
|
||||||
public static readonly Pawn Black = new('X');
|
public static readonly Pawn Black = new('X');
|
||||||
public static readonly Pawn White = new('O');
|
public static readonly Pawn White = new('O');
|
||||||
public static readonly Pawn None = new('.');
|
public static readonly Pawn None = new('.');
|
||||||
@@ -15,5 +15,5 @@ namespace Hexapawn
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => _symbol.ToString();
|
public override string ToString() => _symbol.ToString();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,71 +1,6 @@
|
|||||||
using System;
|
using Games.Common.IO;
|
||||||
|
using Games.Common.Randomness;
|
||||||
|
using Hexapawn;
|
||||||
|
|
||||||
namespace Hexapawn
|
new GameSeries(new ConsoleIO(), new RandomNumberGenerator()).Play();
|
||||||
{
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
30
46_Hexapawn/csharp/Resources/Instructions.txt
Normal file
30
46_Hexapawn/csharp/Resources/Instructions.txt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
This program plays the game of Hexapawn.
|
||||||
|
Hexapawn is played with Chess pawns on a 3 by 3 board.
|
||||||
|
The pawns are move as in Chess - one space forward to
|
||||||
|
an empty space, or one space forward and diagonally to
|
||||||
|
capture an opposing man. On the board, your pawns
|
||||||
|
are 'O', the computer's pawns are 'X', and empty
|
||||||
|
squares are '.'. To enter a move, type the number of
|
||||||
|
the square you are moving from, followed by the number
|
||||||
|
of the square you will move to. The numbers must be
|
||||||
|
separated by a comma.
|
||||||
|
|
||||||
|
The computer starts a series of games knowing only when
|
||||||
|
the game is won (a draw is impossible) and how to move.
|
||||||
|
It has no strategy at first and just moves randomly.
|
||||||
|
However, it learns from each game. Thus winning becomes
|
||||||
|
more and more difficult. Also, to help offset your
|
||||||
|
initial advantage, you will not be told how to win the
|
||||||
|
game but must learn this by playing.
|
||||||
|
|
||||||
|
The numbering of the board is as follows:
|
||||||
|
123
|
||||||
|
456
|
||||||
|
789
|
||||||
|
|
||||||
|
For example, to move your rightmost pawn forward,
|
||||||
|
you would type 9,6 in response to the question
|
||||||
|
'Your move ?'. Since I'm a good sport, you'll always
|
||||||
|
go first.
|
||||||
|
|
||||||
17
46_Hexapawn/csharp/Resources/Resource.cs
Normal file
17
46_Hexapawn/csharp/Resources/Resource.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Hexapawn.Resources;
|
||||||
|
|
||||||
|
internal static class Resource
|
||||||
|
{
|
||||||
|
internal static class Streams
|
||||||
|
{
|
||||||
|
public static Stream Instructions => GetStream();
|
||||||
|
public static Stream Title => GetStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream GetStream([CallerMemberName] string name = null)
|
||||||
|
=> Assembly.GetExecutingAssembly().GetManifestResourceStream($"Hexapawn.Resources.{name}.txt");
|
||||||
|
}
|
||||||
5
46_Hexapawn/csharp/Resources/Title.txt
Normal file
5
46_Hexapawn/csharp/Resources/Title.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Hexapawn
|
||||||
|
Creative Computing Morristown, New Jersey
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user