mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-12 07:40:50 -08:00
Add D version of War (94).
This commit is contained in:
2
94_War/d/.gitignore
vendored
Normal file
2
94_War/d/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.exe
|
||||
*.obj
|
||||
125
94_War/d/README.md
Normal file
125
94_War/d/README.md
Normal file
@@ -0,0 +1,125 @@
|
||||
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 -preview=dip1000 -run war.d
|
||||
```
|
||||
|
||||
[Other compilers](https://dlang.org/download.html) also exist.
|
||||
|
||||
## Specialties explained
|
||||
|
||||
This game code contains some specialties that you might want to know more about. Here goes.
|
||||
|
||||
### Suits
|
||||
|
||||
Most modern consoles are capable of displaying more than just ASCII, and so I have chosen to display the actual ♠, ♥, ♦
|
||||
and ♣ instead of substituting them by letters like the BASIC original did. Only the Windows console needs a nudge in
|
||||
the right direction with these instructions:
|
||||
```d
|
||||
SetConsoleOutputCP(CP_UTF8); // Set code page
|
||||
SetConsoleOutputCP(GetACP); // Restore the default
|
||||
```
|
||||
Instead of cluttering the `main()` function with these lesser important details, we can move them into a
|
||||
[module constructor and module destructor](https://dlang.org/spec/module.html#staticorder), which run before and after
|
||||
`main()` respectively. And because order of declaration is irrelevant in a D module, we can push those all the way
|
||||
down to the bottom of the file. This is of course only necessary on Windows (and won't even work anywhere else) so
|
||||
we'll need to wrap this in a `version (Windows)` conditional code block:
|
||||
```d
|
||||
version (Windows)
|
||||
{
|
||||
import core.sys.windows.windows;
|
||||
|
||||
shared static this() @trusted
|
||||
{
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
}
|
||||
|
||||
shared static ~this() @trusted
|
||||
{
|
||||
SetConsoleOutputCP(GetACP);
|
||||
}
|
||||
}
|
||||
```
|
||||
Although it doesn't matter much in this single-threaded program, the `shared` attribute makes that these
|
||||
constructors/destructors are run once per program invocation; non-shared module constructors and module destructors are
|
||||
run for every thread. The `@trusted` annotation is necessary because these are system API calls; The compiler cannot
|
||||
check these for memory-safety, and so we must indicate that we have reviewed the safety manually.
|
||||
|
||||
### Uniform Function Call Syntax
|
||||
|
||||
In case you wonder why this line works:
|
||||
```d
|
||||
if ("Do you want instructions?".yes)
|
||||
// ...
|
||||
```
|
||||
then it is because this is equivalent to
|
||||
```d
|
||||
if (yes("Do you want instructions?"))
|
||||
// ...
|
||||
```
|
||||
where `yes()` is a Boolean function that is defined below `main()`. This is made possible by the language feature that
|
||||
is called [uniform function call syntax (UFCS)](https://dlang.org/spec/function.html#pseudo-member). UFCS works by
|
||||
passing what is in front of the dot as the first parameter to the function, and it was invented to make it possible to
|
||||
call free functions on objects as if they were member functions. UFCS can also be used to obtain a more natural order
|
||||
of function calls, such as this line inside `yes()`:
|
||||
```d
|
||||
return trustedReadln.strip.toLower.startsWith("y");
|
||||
```
|
||||
which reads easier than the equivalent
|
||||
```d
|
||||
return startsWith(toLower(strip(trustedReadln())), "y");
|
||||
```
|
||||
|
||||
### Type a lot or not?
|
||||
|
||||
It would have been straight forward to define the `cards` array explicitly like so:
|
||||
```d
|
||||
const cards = ["2♠", "2♥", "2♦", "2♣", "3♠", "3♥", "3♦", "3♣",
|
||||
"4♠", "4♥", "4♦", "4♣", "5♠", "5♥", "5♦", "5♣",
|
||||
"6♠", "6♥", "6♦", "6♣", "7♠", "7♥", "7♦", "7♣",
|
||||
"8♠", "8♥", "8♦", "8♣", "9♠", "9♥", "9♦", "9♣",
|
||||
"10♠", "10♥", "10♦", "10♣", "J♥", "J♦", "J♣", "J♣",
|
||||
"Q♠", "Q♥", "Q♦", "Q♣", "K♠", "K♥", "K♦", "K♣",
|
||||
"A♠", "A♥", "A♦", "A♣"];
|
||||
```
|
||||
but that's tedious, difficult to spot errors in (*can you?*) and looks like something a computer can automate. Indeed
|
||||
it can:
|
||||
```d
|
||||
static const cards = cartesianProduct(["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"],
|
||||
["♠", "♥", "♦", "♣"]).map!(a => a.expand.only.join).array;
|
||||
```
|
||||
The function [`cartesianProduct`](https://dlang.org/phobos/std_algorithm_setops.html#cartesianProduct) takes two
|
||||
ranges, like the horizontal and vertical headers of a spreadsheet, and fills the table with the combinations that form
|
||||
the coordinates of the cells. But the output of that function is in the form of an array of
|
||||
[`Tuple`](https://dlang.org/phobos/std_typecons.html#Tuple)s, which looks like `[Tuple!(string, string)("2", "♠"),
|
||||
Tuple!(string, string)("2", "♥"), ... etc]`. [`map`](https://dlang.org/phobos/std_algorithm_iteration.html#map)
|
||||
comes to the rescue, converting each Tuple to a string, by calling
|
||||
[`expand`](https://dlang.org/phobos/std_typecons.html#.Tuple.expand), then
|
||||
[`only`](https://dlang.org/phobos/std_range.html#only) and then [`join`](https://dlang.org/phobos/std_array.html#join)
|
||||
on them. The result is a lazily evaluated range of strings. Finally,
|
||||
[`array`](https://dlang.org/phobos/std_array.html#array) turns the range into a random access array. The `static`
|
||||
attribute makes that all this is performed at compile-time, so the result is exactly the same as the manually entered
|
||||
data, but without the typo's.
|
||||
|
||||
### Shuffle the cards or not?
|
||||
|
||||
The original BASIC code works with a constant array of cards, ordered by increasing numerical value, and indexing it
|
||||
with indices that have been shuffled. This is efficient because in comparing who wins, the indices can be compared
|
||||
directly, since a higher index correlates to a card with a higher numerical value (when divided by the number of suits,
|
||||
4). Some of the other reimplementations in other languages have been written in a lesser efficient way by shuffling the
|
||||
array of cards itself. This then requires the use of a lookup table or searching for equality in an auxiliary array
|
||||
when comparing cards.
|
||||
|
||||
I find the original more elegant, so that's what you see here:
|
||||
```d
|
||||
const indices = iota(0, cards.length).array.randomShuffle;
|
||||
```
|
||||
[`iota`](https://dlang.org/phobos/std_range.html#iota) produces a range of integers, in this case starting at 0 and
|
||||
increasing up to the number of cards in the deck (exclusive). [`array`](https://dlang.org/phobos/std_array.html#array)
|
||||
turns the range into an array, so that [`randomShuffle`](https://dlang.org/phobos/std_random.html#randomShuffle) can
|
||||
do its work.
|
||||
80
94_War/d/war.d
Normal file
80
94_War/d/war.d
Normal file
@@ -0,0 +1,80 @@
|
||||
@safe: // Make @safe the default for this file, enforcing memory-safety.
|
||||
import std;
|
||||
|
||||
void main()
|
||||
{
|
||||
enum width = 50;
|
||||
writeln(center("War", width));
|
||||
writeln(center("(After Creative Computing Morristown, New Jersey)\n\n", width));
|
||||
writeln(wrap("This is the card game of war. Each card is given by suit-# " ~
|
||||
"as 7♠ for seven of spades. ", width));
|
||||
|
||||
if ("Do you want instructions?".yes)
|
||||
writeln("\n", wrap("The computer gives you and it a 'card'. The higher card " ~
|
||||
"(numerically) wins. The game ends when you choose not to " ~
|
||||
"continue or when you have finished the pack.\n", width));
|
||||
|
||||
static const cards = cartesianProduct(["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"],
|
||||
["♠", "♥", "♦", "♣"]).map!(a => a.expand.only.join).array;
|
||||
const indices = iota(0, cards.length).array.randomShuffle;
|
||||
int yourScore = 0, compScore = 0, i = 0;
|
||||
while (i < indices.length)
|
||||
{
|
||||
size_t your = indices[i++], comp = indices[i++];
|
||||
writeln("\nYou: ", cards[your].leftJustify(9), "Computer: ", cards[comp]);
|
||||
your /= 4; comp /= 4;
|
||||
if (your == comp)
|
||||
writeln("Tie. No score change.");
|
||||
else if (your < comp)
|
||||
writeln("The computer wins!!! You have ", yourScore,
|
||||
" and the computer has ", ++compScore, ".");
|
||||
else
|
||||
writeln("You win. You have ", ++yourScore,
|
||||
" and the computer has ", compScore, ".");
|
||||
if (i == indices.length)
|
||||
writeln("\nWe have run out of cards. Final score: You: ", yourScore,
|
||||
", the computer: ", compScore, ".");
|
||||
else if (!"Do you want to continue?".yes)
|
||||
break;
|
||||
}
|
||||
writeln("\nThanks for playing. It was fun.");
|
||||
}
|
||||
|
||||
/// Returns whether question was answered positively.
|
||||
bool yes(string question)
|
||||
{
|
||||
writef!"%s "(question);
|
||||
try
|
||||
return trustedReadln.strip.toLower.startsWith("y");
|
||||
catch (Exception) // readln throws on I/O and Unicode errors, which we handle here.
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 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