mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-26 12:51:29 -08:00
239 lines
8.8 KiB
D
239 lines
8.8 KiB
D
@safe: // Make @safe the default for this file, enforcing memory-safety.
|
|
import std;
|
|
|
|
void main()
|
|
{
|
|
enum width = 50;
|
|
writeln(center("H-I-Q", width));
|
|
writeln(center("(After Creative Computing Morristown, New Jersey)\n\n", width));
|
|
writeln(wrap("Fields are identified by 2-digit numbers, each between 1 and 7. " ~
|
|
"Example: the middle field is 44, the bottom middle is 74.", width));
|
|
|
|
Board board;
|
|
|
|
while (true)
|
|
{
|
|
while (!board.isGameOver)
|
|
{
|
|
writeln(board); // Calls board.toString().
|
|
board.makeMove;
|
|
}
|
|
writeln(board, "\nThe game is over.\nYou had ", board.numPegs, " pieces remaining.");
|
|
if (board.numPegs == 1)
|
|
writeln("Bravo! You made a perfect score!\n",
|
|
"Make a screen dump as a record of your accomplishment!\n");
|
|
write("Play again? (Yes or No) ");
|
|
if (readString.toLower != "yes")
|
|
break;
|
|
writeln; writeln;
|
|
board = Board.init;
|
|
}
|
|
writeln("\nSo long for now.\n");
|
|
}
|
|
|
|
/// Representation of the game board with pegs.
|
|
struct Board
|
|
{
|
|
enum {outside, taken, empty};
|
|
int[8][8] state = [
|
|
1: [ 3: taken, 4: taken, 5: taken],
|
|
2: [ 3: taken, 4: taken, 5: taken],
|
|
3: [1: taken, 2: taken, 3: taken, 4: taken, 5: taken, 6: taken, 7: taken],
|
|
4: [1: taken, 2: taken, 3: taken, 4: empty, 5: taken, 6: taken, 7: taken],
|
|
5: [1: taken, 2: taken, 3: taken, 4: taken, 5: taken, 6: taken, 7: taken],
|
|
6: [ 3: taken, 4: taken, 5: taken],
|
|
7: [ 3: taken, 4: taken, 5: taken]
|
|
]; // Row 0 and column 0 are unused. Default is 0 (outside).
|
|
|
|
/// Returns a string representing the board and its current state.
|
|
string toString() const
|
|
{
|
|
dchar[][] lines = [(" _1 _2 _3 _4 _5 _6 _7 ").to!(dchar[]),
|
|
(" ┌───┬───┬───┐ ").to!(dchar[]),
|
|
(" 1_ │ │ │ │ ").to!(dchar[]),
|
|
(" ├───┼───┼───┤ ").to!(dchar[]),
|
|
(" 2_ │ │ │ │ ").to!(dchar[]),
|
|
(" ┌───┬───┼───┼───┼───┼───┬───┐").to!(dchar[]),
|
|
(" 3_ │ │ │ │ │ │ │ │").to!(dchar[]),
|
|
(" ├───┼───┼───┼───┼───┼───┼───┤").to!(dchar[]),
|
|
(" 4_ │ │ │ │ │ │ │ │").to!(dchar[]),
|
|
(" ├───┼───┼───┼───┼───┼───┼───┤").to!(dchar[]),
|
|
(" 5_ │ │ │ │ │ │ │ │").to!(dchar[]),
|
|
(" └───┴───┼───┼───┼───┼───┴───┘").to!(dchar[]),
|
|
(" 6_ │ │ │ │ ").to!(dchar[]),
|
|
(" ├───┼───┼───┤ ").to!(dchar[]),
|
|
(" 7_ │ │ │ │ ").to!(dchar[]),
|
|
(" └───┴───┴───┘ ").to!(dchar[])];
|
|
foreach (y, row; state)
|
|
foreach (x, field; row)
|
|
if (field == taken)
|
|
lines[y * 2][x * 4 + 2] = '■';
|
|
return lines.join("\n").to!string;
|
|
}
|
|
|
|
/// Tests for possible moves.
|
|
bool isGameOver() const
|
|
{
|
|
foreach (r, row; state)
|
|
foreach (c, field; row)
|
|
if (field == taken && canMoveFrom(r, c))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool canMoveFrom(int row, int col) const
|
|
{
|
|
if (row >= 3 && state[row - 2][col] == empty) // Up
|
|
return state[row - 1][col] == taken;
|
|
if (row <= 5 && state[row + 2][col] == empty) // Down
|
|
return state[row + 1][col] == taken;
|
|
if (col >= 3 && state[row][col - 2] == empty) // Left
|
|
return state[row][col - 1] == taken;
|
|
if (col <= 5 && state[row][col + 2] == empty) // Right
|
|
return state[row][col + 1] == taken;
|
|
return false;
|
|
}
|
|
|
|
/// Asks for input, validates the move and updates the board.
|
|
void makeMove()
|
|
{
|
|
bool isOutside(int row, int col)
|
|
{
|
|
if (row < 1 || row > 7 ||
|
|
col < 1 || col > 7 ||
|
|
state[row][col] == outside)
|
|
{
|
|
writeln("Field ", row, col, " is ouside the board. Try again.");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
auto from = (){
|
|
while (true)
|
|
{
|
|
write("\nMove which peg? ");
|
|
int field = readInt;
|
|
int row = field / 10, col = field % 10;
|
|
if (isOutside(row, col))
|
|
continue;
|
|
if (state[row][col] != taken)
|
|
{
|
|
writeln("There is no peg at ", field, ". Try again.");
|
|
continue;
|
|
}
|
|
if (!canMoveFrom(row, col))
|
|
{
|
|
writeln("The peg at ", field, " has nowhere to go. Try again.");
|
|
continue;
|
|
}
|
|
return tuple!("row", "col")(row, col);
|
|
}
|
|
}();
|
|
auto to = (){
|
|
while (true)
|
|
{
|
|
write("To where? ");
|
|
int field = readInt;
|
|
int row = field / 10, col = field % 10;
|
|
if (isOutside(row, col))
|
|
continue;
|
|
if (state[row][col] == taken)
|
|
{
|
|
writeln("Field ", field, " is occupied. Try again.");
|
|
continue;
|
|
}
|
|
if (row != from.row && col != from.col)
|
|
{
|
|
writeln("You cannot move diagonally. Try again.");
|
|
continue;
|
|
}
|
|
if (row == from.row && col == from.col)
|
|
{
|
|
writeln("You aren't going anywhere. Try again.");
|
|
continue;
|
|
}
|
|
if (abs(row - from.row) + abs(col - from.col) > 2)
|
|
{
|
|
writeln("You can't jump that far. Try again.");
|
|
continue;
|
|
}
|
|
if (abs(row - from.row) + abs(col - from.col) < 2 ||
|
|
state[(row + from.row) / 2][(col + from.col) / 2] != taken)
|
|
{
|
|
writeln("You need to jump over another peg. Try again.");
|
|
continue;
|
|
}
|
|
return tuple!("row", "col")(row, col);
|
|
}
|
|
}();
|
|
// The move is legal. Update the board state.
|
|
state[from.row][from.col] = empty;
|
|
state[ to.row][ to.col] = taken;
|
|
state[(from.row + to.row) / 2][(from.col + to.col) / 2] = empty;
|
|
writeln;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// Returns the number of remaining pegs on the board.
|
|
int numPegs() const
|
|
{
|
|
int num = 0;
|
|
foreach (row; state)
|
|
foreach (field; row)
|
|
if (field == taken)
|
|
num++;
|
|
return num;
|
|
}
|
|
}
|
|
|
|
/// Reads an integer from standard input.
|
|
int readInt() nothrow
|
|
{
|
|
try
|
|
return readString.to!int;
|
|
catch (Exception) // Not an integer.
|
|
return 0;
|
|
}
|
|
|
|
/// Reads a string from standard input.
|
|
string readString() nothrow
|
|
{
|
|
try
|
|
return trustedReadln.strip;
|
|
catch (Exception) // readln throws on I/O and Unicode errors, which we handle here.
|
|
return "";
|
|
}
|
|
|
|
/** An @trusted wrapper around readln.
|
|
*
|
|
* This is the only function that formally requires manual review for memory-safety.
|
|
* [Arguably readln should be safe already](https://forum.dlang.org/post/rab398$1up$1@digitalmars.com)
|
|
* which would remove the need to have any @trusted code in this program.
|
|
*/
|
|
string trustedReadln() @trusted
|
|
{
|
|
return readln;
|
|
}
|
|
|
|
version (Windows)
|
|
{
|
|
// Make the Windows console do a better job at printing UTF-8 strings,
|
|
// and restore the default upon termination.
|
|
|
|
import core.sys.windows.windows;
|
|
|
|
shared static this() @trusted
|
|
{
|
|
SetConsoleOutputCP(CP_UTF8);
|
|
}
|
|
|
|
shared static ~this() @trusted
|
|
{
|
|
SetConsoleOutputCP(GetACP);
|
|
}
|
|
}
|