mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-26 04:41:52 -08:00
Merge branch 'main' of https://github.com/jjwalter2001/basic-computer-games
This commit is contained in:
34
46 Hexapawn/csharp/Hexapawn.sln
Normal file
34
46 Hexapawn/csharp/Hexapawn.sln
Normal file
@@ -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
|
||||
73
46 Hexapawn/csharp/Hexapawn/Board.cs
Normal file
73
46 Hexapawn/csharp/Hexapawn/Board.cs
Normal file
@@ -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<Pawn>, IEquatable<Board>
|
||||
{
|
||||
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<Pawn> GetEnumerator() => _cells.OfType<Pawn>().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;
|
||||
}
|
||||
}
|
||||
}
|
||||
53
46 Hexapawn/csharp/Hexapawn/Cell.cs
Normal file
53
46 Hexapawn/csharp/Hexapawn/Cell.cs
Normal file
@@ -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<Cell> 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();
|
||||
}
|
||||
}
|
||||
146
46 Hexapawn/csharp/Hexapawn/Computer.cs
Normal file
146
46 Hexapawn/csharp/Hexapawn/Computer.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static Hexapawn.Pawn;
|
||||
using static Hexapawn.Cell;
|
||||
|
||||
namespace Hexapawn
|
||||
{
|
||||
/// <summary>
|
||||
/// Encapsulates the logic of the computer player.
|
||||
/// </summary>
|
||||
internal class Computer : IPlayer
|
||||
{
|
||||
private readonly Random _random = new();
|
||||
private readonly Dictionary<Board, List<Move>> _potentialMoves;
|
||||
private (List<Move>, 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<Move> 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<Move> 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<Move> Moves(params Move[] moves) => moves.ToList();
|
||||
|
||||
public bool IsFullyAdvanced(Board board) =>
|
||||
board[9] == Black || board[8] == Black || board[7] == Black;
|
||||
}
|
||||
}
|
||||
49
46 Hexapawn/csharp/Hexapawn/Game.cs
Normal file
49
46 Hexapawn/csharp/Hexapawn/Game.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
46 Hexapawn/csharp/Hexapawn/GameSeries.cs
Normal file
27
46 Hexapawn/csharp/Hexapawn/GameSeries.cs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
46 Hexapawn/csharp/Hexapawn/Hexapawn.csproj
Normal file
8
46 Hexapawn/csharp/Hexapawn/Hexapawn.csproj
Normal file
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
64
46 Hexapawn/csharp/Hexapawn/Human.cs
Normal file
64
46 Hexapawn/csharp/Hexapawn/Human.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
46 Hexapawn/csharp/Hexapawn/IPlayer.cs
Normal file
8
46 Hexapawn/csharp/Hexapawn/IPlayer.cs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
112
46 Hexapawn/csharp/Hexapawn/Input.cs
Normal file
112
46 Hexapawn/csharp/Hexapawn/Input.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
68
46 Hexapawn/csharp/Hexapawn/Move.cs
Normal file
68
46 Hexapawn/csharp/Hexapawn/Move.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using static Hexapawn.Pawn;
|
||||
|
||||
namespace Hexapawn
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a move which may, or may not, be legal.
|
||||
/// </summary>
|
||||
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}";
|
||||
}
|
||||
}
|
||||
19
46 Hexapawn/csharp/Hexapawn/Pawn.cs
Normal file
19
46 Hexapawn/csharp/Hexapawn/Pawn.cs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
71
46 Hexapawn/csharp/Hexapawn/Program.cs
Normal file
71
46 Hexapawn/csharp/Hexapawn/Program.cs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user