mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-22 23:26:40 -08:00
Merge pull request #478 from veelo/highiq
Add D version of High_IQ (48).
This commit is contained in:
2
48_High_IQ/d/.gitignore
vendored
Normal file
2
48_High_IQ/d/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.exe
|
||||
*.obj
|
||||
214
48_High_IQ/d/README.md
Normal file
214
48_High_IQ/d/README.md
Normal file
@@ -0,0 +1,214 @@
|
||||
Original source downloaded from [Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||
|
||||
Converted to [D](https://dlang.org/) by [Bastiaan Veelo](https://github.com/veelo).
|
||||
|
||||
## Running the code
|
||||
|
||||
Assuming the reference [dmd](https://dlang.org/download.html#dmd) compiler:
|
||||
```shell
|
||||
dmd -dip1000 -run highiq.d
|
||||
```
|
||||
|
||||
[Other compilers](https://dlang.org/download.html) also exist.
|
||||
|
||||
|
||||
## Discussion
|
||||
|
||||
The original BASIC game code made use of calculus and clever choises of field IDs to determine the validity of moves.
|
||||
This is the original layout of IDs over the board:
|
||||
|
||||
```
|
||||
13 14 15
|
||||
|
||||
22 23 24
|
||||
|
||||
29 30 31 32 33 34 35
|
||||
|
||||
38 39 40 41 42 43 44
|
||||
|
||||
47 48 49 50 51 52 53
|
||||
|
||||
58 59 60
|
||||
|
||||
67 68 69
|
||||
```
|
||||
|
||||
This seems not very logical, because, wouldn't it make much more sense to let columns increase with 1 and rows increase
|
||||
with 10, so you'd get a consistent coordinate system? It seems that the original author's first step in validating
|
||||
moves was to check that moves jumped from one field over another one onto the next. He did this by making sure that
|
||||
adjacent IDs alter between even and odd horizontally *and* vertically. So a valid move was always from an even ID to an
|
||||
even ID *or* from an odd ID to an odd ID. So one of the checks that the BASIC code made was that the sum of both IDs
|
||||
was even. This is of course not a sufficient test, because moves that jump over three fields are illegal. Therefore the
|
||||
IDs seem to have been carefully laid oud so that the IDs increase with 1 horizontally, and 9 vertically, everywhere. So
|
||||
the only valid difference between IDs for a horizontal move was always 2, and the only valid difference for a vertical
|
||||
move was always 18.
|
||||
|
||||
Fact of the matter is, however, that checking for difference is sufficient and the even sum rule is superfluous, so
|
||||
there is no need for the peculiar distribution of field IDs. Therefore I have chosen the following more logical
|
||||
distribution:
|
||||
|
||||
```
|
||||
13 14 15
|
||||
|
||||
23 24 25
|
||||
|
||||
31 32 33 34 35 36 37
|
||||
|
||||
41 42 43 44 45 46 47
|
||||
|
||||
51 52 53 54 55 56 57
|
||||
|
||||
63 64 65
|
||||
|
||||
73 74 75
|
||||
```
|
||||
|
||||
As a consequence, the implementation of the game code has become much simpler; Not alone due to one less check, but due
|
||||
to the fact that conversions between IDs and board coordinates have become unnecessary and thus we can work with a single
|
||||
representation of the board state.
|
||||
|
||||
This version makes a prettier print of the board than the BASIC original, with coordinates for every move, and explains
|
||||
illegal moves.
|
||||
|
||||
|
||||
## Demo
|
||||
|
||||
```
|
||||
H-I-Q
|
||||
(After Creative Computing Morristown, New Jersey)
|
||||
|
||||
|
||||
Fields are identified by 2-digit numbers, each
|
||||
between 1 and 7. Example: the middle field is 44,
|
||||
the bottom middle is 74.
|
||||
|
||||
_1 _2 _3 _4 _5 _6 _7
|
||||
┌───┬───┬───┐
|
||||
1_ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┤
|
||||
2_ │ ■ │ ■ │ ■ │
|
||||
┌───┬───┼───┼───┼───┼───┬───┐
|
||||
3_ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┼───┼───┼───┼───┤
|
||||
4_ │ ■ │ ■ │ ■ │ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┼───┼───┼───┼───┤
|
||||
5_ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │
|
||||
└───┴───┼───┼───┼───┼───┴───┘
|
||||
6_ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┤
|
||||
7_ │ ■ │ ■ │ ■ │
|
||||
└───┴───┴───┘
|
||||
|
||||
Move which peg? 23
|
||||
The peg at 23 has nowhere to go. Try again.
|
||||
|
||||
Move which peg? 24
|
||||
To where? 34
|
||||
Field 34 is occupied. Try again.
|
||||
To where? 54
|
||||
Field 54 is occupied. Try again.
|
||||
To where? 44
|
||||
|
||||
_1 _2 _3 _4 _5 _6 _7
|
||||
┌───┬───┬───┐
|
||||
1_ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┤
|
||||
2_ │ ■ │ │ ■ │
|
||||
┌───┬───┼───┼───┼───┼───┬───┐
|
||||
3_ │ ■ │ ■ │ ■ │ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┼───┼───┼───┼───┤
|
||||
4_ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┼───┼───┼───┼───┤
|
||||
5_ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │
|
||||
└───┴───┼───┼───┼───┼───┴───┘
|
||||
6_ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┤
|
||||
7_ │ ■ │ ■ │ ■ │
|
||||
└───┴───┴───┘
|
||||
|
||||
Move which peg? 14
|
||||
The peg at 14 has nowhere to go. Try again.
|
||||
|
||||
Move which peg? 24
|
||||
There is no peg at 24. Try again.
|
||||
|
||||
Move which peg? 44
|
||||
The peg at 44 has nowhere to go. Try again.
|
||||
|
||||
Move which peg? 32
|
||||
To where? 22
|
||||
Field 22 is ouside the board. Try again.
|
||||
To where? 33
|
||||
Field 33 is occupied. Try again.
|
||||
To where? 34
|
||||
|
||||
_1 _2 _3 _4 _5 _6 _7
|
||||
┌───┬───┬───┐
|
||||
1_ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┤
|
||||
2_ │ ■ │ │ ■ │
|
||||
┌───┬───┼───┼───┼───┼───┬───┐
|
||||
3_ │ ■ │ │ │ ■ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┼───┼───┼───┼───┤
|
||||
4_ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┼───┼───┼───┼───┤
|
||||
5_ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │
|
||||
└───┴───┼───┼───┼───┼───┴───┘
|
||||
6_ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┤
|
||||
7_ │ ■ │ ■ │ ■ │
|
||||
└───┴───┴───┘
|
||||
|
||||
Move which peg? 44
|
||||
To where? 33
|
||||
You cannot move diagonally. Try again.
|
||||
To where? 24
|
||||
|
||||
_1 _2 _3 _4 _5 _6 _7
|
||||
┌───┬───┬───┐
|
||||
1_ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┤
|
||||
2_ │ ■ │ ■ │ ■ │
|
||||
┌───┬───┼───┼───┼───┼───┬───┐
|
||||
3_ │ ■ │ │ │ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┼───┼───┼───┼───┤
|
||||
4_ │ ■ │ ■ │ ■ │ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┼───┼───┼───┼───┤
|
||||
5_ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │
|
||||
└───┴───┼───┼───┼───┼───┴───┘
|
||||
6_ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┤
|
||||
7_ │ ■ │ ■ │ ■ │
|
||||
└───┴───┴───┘
|
||||
|
||||
Move which peg? 36
|
||||
To where? 33
|
||||
You can't jump that far. Try again.
|
||||
To where? 35
|
||||
Field 35 is occupied. Try again.
|
||||
To where? 34
|
||||
|
||||
_1 _2 _3 _4 _5 _6 _7
|
||||
┌───┬───┬───┐
|
||||
1_ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┤
|
||||
2_ │ ■ │ ■ │ ■ │
|
||||
┌───┬───┼───┼───┼───┼───┬───┐
|
||||
3_ │ ■ │ │ │ ■ │ │ │ ■ │
|
||||
├───┼───┼───┼───┼───┼───┼───┤
|
||||
4_ │ ■ │ ■ │ ■ │ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┼───┼───┼───┼───┤
|
||||
5_ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │
|
||||
└───┴───┼───┼───┼───┼───┴───┘
|
||||
6_ │ ■ │ ■ │ ■ │
|
||||
├───┼───┼───┤
|
||||
7_ │ ■ │ ■ │ ■ │
|
||||
└───┴───┴───┘
|
||||
|
||||
Move which peg? 46
|
||||
To where? 36
|
||||
You need to jump over another peg. Try again.
|
||||
To where? down
|
||||
Field 00 is ouside the board. Try again.
|
||||
To where?
|
||||
```
|
||||
238
48_High_IQ/d/highiq.d
Normal file
238
48_High_IQ/d/highiq.d
Normal file
@@ -0,0 +1,238 @@
|
||||
@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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user