Merge pull request #478 from veelo/highiq

Add D version of High_IQ (48).
This commit is contained in:
Jeff Atwood
2022-01-09 18:57:06 -08:00
committed by GitHub
3 changed files with 454 additions and 0 deletions

2
48_High_IQ/d/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*.exe
*.obj

214
48_High_IQ/d/README.md Normal file
View 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
View 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);
}
}