diff --git a/00_Utilities/markdown_todo.py b/00_Utilities/markdown_todo.py new file mode 100644 index 00000000..74e358c0 --- /dev/null +++ b/00_Utilities/markdown_todo.py @@ -0,0 +1,50 @@ +import os + + +lang_pos = { + "csharp": 1, "java": 2, "javascript": 3, + "pascal": 4, "perl": 5, "python": 6, "ruby": 7, "vbnet": 8 +} + +write_string = "# TODO list \n game | csharp | java | javascript | pascal | perl | python | ruby | vbnet \n --- | --- | --- | --- | --- | --- | --- | --- | --- \n" +# Set the directory you want to start from +rootDir = '..' + +strings_done = [] + +checklist = ["game", "csharp", "java", "javascript", + "pascal", "perl", "python", "ruby", "vbnet"] + +prev_game = "" + +for dirName, subdirList, fileList in os.walk(rootDir): + split_dir = dirName.split("/") + + if len(split_dir) == 2 and not split_dir[1] in ['.git', '00_Utilities']: + if prev_game == "": + prev_game = split_dir[1] + checklist[0] = split_dir[1] + + if prev_game != split_dir[1]: + # it's a new dir + strings_done.append(checklist) + checklist = [split_dir[1], "csharp", "java", "javascript", + "pascal", "perl", "python", "ruby", "vbnet"] + prev_game = split_dir[1] + + elif len(split_dir) == 3 and split_dir[1] != '.git': + if split_dir[2] in lang_pos.keys(): + if len(fileList) > 1 or len(subdirList) > 0: + # there is more files than the readme + checklist[lang_pos[split_dir[2]]] = "✅" + else: + checklist[lang_pos[split_dir[2]]] = "⬜️" + + +sorted_strings = list(map(lambda l: " | ".join(l) + "\n", + sorted(strings_done, key=lambda x: x[0]))) +write_string += ''.join(sorted_strings) + + +with open("README.md", "w") as f: + f.write(write_string) diff --git a/01_Acey_Ducey/README.md b/01_Acey_Ducey/README.md index 3434eb8e..77ff8621 100644 --- a/01_Acey_Ducey/README.md +++ b/01_Acey_Ducey/README.md @@ -15,4 +15,5 @@ As published in Basic Computer Games (1978): Downloaded from Vintage Basic at http://www.vintage-basic.net/games.html -A Common Lisp port is [here](https://github.com/koalahedron/lisp-computer-games/blob/master/01%20Acey%20Ducey/common-lisp/acey-deucy.lisp). +#### External Links + - Common Lisp: https://github.com/koalahedron/lisp-computer-games/blob/master/01%20Acey%20Ducey/common-lisp/acey-deucy.lisp diff --git a/01_Acey_Ducey/d/.gitignore b/01_Acey_Ducey/d/.gitignore new file mode 100644 index 00000000..d969f6b2 --- /dev/null +++ b/01_Acey_Ducey/d/.gitignore @@ -0,0 +1,2 @@ +*.exe +*.obj diff --git a/01_Acey_Ducey/d/README.md b/01_Acey_Ducey/d/README.md new file mode 100644 index 00000000..a45fcb2c --- /dev/null +++ b/01_Acey_Ducey/d/README.md @@ -0,0 +1,29 @@ +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). + +Two versions are supplied that are functionally equivalent, but differ in source layout: + +
+
aceyducey_literal.d
+
A largely literal transcription of the original Basic source. All unnecessary uglyness is preserved.
+
aceyducey.d
+
An idiomatic D refactoring of the original, with a focus on increasing the readability and robustness. + Memory-safety is ensured by the language, thanks to the + @safe annotation.
+
+ +## Running the code + +Assuming the reference [dmd](https://dlang.org/download.html#dmd) compiler: +```shell +dmd -run aceyducey.d +``` + +[Other compilers](https://dlang.org/download.html) also exist. + +Note that there are compiler switches related to memory-safety (`-preview=dip25` and `-preview=dip1000`) that are not +used here because they are unnecessary in this case. What these do is to make the analysis more thorough, so that with +them some code that needed to be `@system` can then be inferred to be in fact `@safe`. [Code that compiles without +these switches is just as safe as when compiled with them] +(https://forum.dlang.org/post/dftgjalswvwfjpyushgn@forum.dlang.org). diff --git a/01_Acey_Ducey/d/aceyducey.d b/01_Acey_Ducey/d/aceyducey.d new file mode 100644 index 00000000..036786c5 --- /dev/null +++ b/01_Acey_Ducey/d/aceyducey.d @@ -0,0 +1,131 @@ +@safe: // Make @safe the default for this file, enforcing memory-safety. + +void main() +{ + import std.stdio : write, writeln; + import std.string : center, toUpper, wrap; + import std.exception : ifThrown; + + enum width = 80; + writeln(center("Acey Ducey Card Game", width)); + writeln(center("(After Creative Computing Morristown, New Jersey)\n", width)); + writeln(wrap("Acey-Ducey is played in the following manner: The dealer (computer) deals two cards face up. " ~ + "You have an option to bet or not bet depending on whether or not you feel the third card will " ~ + "have a value between the first two. If you do not want to bet, input a 0.", width)); + + enum Hand {low, middle, high} + Card[Hand.max + 1] cards; // Three cards. + bool play = true; + + while (play) + { + int cash = 100; + while (cash > 0) + { + writeln("\nYou now have ", cash, " dollars."); + int bet = 0; + while (bet <= 0) + { + do // Draw new cards, until the first card has a smaller value than the last card. + { + foreach (ref card; cards) + card.drawNew; + } while (cards[Hand.low] >= cards[Hand.high]); + writeln("Here are your next two cards:\n", cards[Hand.low], "\n", cards[Hand.high]); + + int askBet() // A nested function. + { + import std.conv : to; + + write("\nWhat is your bet? "); + int answer = readString.to!int. + ifThrown!Exception(askBet); // Try again when answer does not convert to int. + if (answer <= cash) + return answer; + writeln("Sorry, my friend, but you bet too much.\nYou have only ", cash, " dollars to bet."); + return askBet; // Recurse: Ask again. + } + bet = askBet; + if (bet <= 0) // Negative bets are interpreted as 0. + writeln("CHICKEN!!"); + } // bet is now > 0. + + writeln(cards[Hand.middle]); + if (cards[Hand.low] < cards[Hand.middle] && cards[Hand.middle] < cards[Hand.high]) + { + writeln("YOU WIN!!!"); + cash += bet; + } + else + { + writeln("Sorry, you lose."); + cash -= bet; + if (cash <= 0) + { + writeln("\n\nSorry, friend, but you blew your wad."); + write("\n\nTry again (Yes or No)? "); + play = readString.toUpper == "YES"; + } + } + } + } + writeln("O.K., hope you had fun!"); +} + +struct Card +{ + int value = 2; + alias value this; // Enables Card to stand in as an int, so that cards can be compared as ints. + + invariant + { + assert(2 <= value && value <= 14); // Ensure cards always have a valid value. + } + + /// Adopt a new value. + void drawNew() + { + import std.random : uniform; + + value = uniform!("[]", int, int)(2, 14); // A random int between inclusive bounds. + } + + /// Called for implicit conversion to string. + string toString() const pure + { + import std.conv : text; + + switch (value) + { + case 11: return "Jack"; + case 12: return "Queen"; + case 13: return "King"; + case 14: return "Ace"; + default: return text(" ", value); // Basic prepends a space. + } + } +} + +/// Read a string from standard input, stripping newline and other enclosing whitespace. +string readString() nothrow +{ + import std.string : strip; + + 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 +{ + import std.stdio : readln; + + return readln; +} diff --git a/01_Acey_Ducey/d/aceyducey_literal.d b/01_Acey_Ducey/d/aceyducey_literal.d new file mode 100644 index 00000000..a51028de --- /dev/null +++ b/01_Acey_Ducey/d/aceyducey_literal.d @@ -0,0 +1,104 @@ +void main() +{ + import std; + + L10: writef("%26s", ' '); writeln("ACEY DUCEY CARD GAME"); + L20: writef("%15s", ' '); writeln("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); + L21: writeln; + L22: writeln; + L30: writeln("ACEY-DUCEY IS PLAYED IN THE FOLLOWING MANNER "); + L40: writeln("THE DEALER (COMPUTER) DEALS TWO CARDS FACE UP"); + L50: writeln("YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING"); + L60: writeln("ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE"); + L70: writeln("A VALUE BETWEEN THE FIRST TWO."); + L80: writeln("IF YOU DO NOT WANT TO BET, INPUT A 0"); + L100: int N=100; + L110: int Q=100, M; + L120: writeln("YOU NOW HAVE ",Q," DOLLARS."); + L130: writeln; + L140: goto L260; + L210: Q=Q+M; + L220: goto L120; + L240: Q=Q-M; + L250: goto L120; + L260: writeln("HERE ARE YOUR NEXT TWO CARDS: "); + L270: auto A=to!int(14*uniform01)+2; + L280: if (A<2) goto L270; + L290: if (A>14) goto L270; + L300: auto B=to!int(14*uniform01)+2; + L310: if (B<2) goto L300; + L320: if (B>14) goto L300; + L330: if (A>=B) goto L270; + L350: if (A<11) goto L400; + L360: if (A==11) goto L420; + L370: if (A==12) goto L440; + L380: if (A==13) goto L460; + L390: if (A==14) goto L480; + L400: writefln("%2d", A); + L410: goto L500; + L420: writeln("JACK"); + L430: goto L500; + L440: writeln("QUEEN"); + L450: goto L500; + L460: writeln("KING"); + L470: goto L500; + L480: writeln("ACE"); + L500: if (B<11) goto L550; + L510: if (B==11) goto L570; + L520: if (B==12) goto L590; + L530: if (B==13) goto L610; + L540: if (B==14) goto L630; + L550: writefln("%2d", B); + L560: goto L650; + L570: writeln("JACK"); + L580: goto L650; + L590: writeln("QUEEN"); + L600: goto L650; + L610: writeln("KING"); + L620: goto L650; + L630: writeln("ACE"); + L640: writeln; + L650: writeln; + L660: write("WHAT IS YOUR BET? "); M = stdin.readln.strip.to!int; + L670: if (M!=0) goto L680; + L675: writeln("CHICKEN!!"); + L676: writeln; + L677: goto L260; + L680: if (M<=Q) goto L730; + L690: writeln("SORRY, MY FRIEND, BUT YOU BET TOO MUCH."); + L700: writeln("YOU HAVE ONLY ",Q," DOLLARS TO BET."); + L710: goto L650; + L730: auto C=to!int(14*uniform01)+2; + L740: if (C<2) goto L730; + L750: if (C>14) goto L730; + L760: if (C<11) goto L810; + L770: if (C==11) goto L830; + L780: if (C==12) goto L850; + L790: if (C==13) goto L870; + L800: if (C==14) goto L890; + L810: writeln(C); + L820: goto L910; + L830: writeln("JACK"); + L840: goto L910; + L850: writeln("QUEEN"); + L860: goto L910; + L870: writeln("KING"); + L880: goto L910; + L890: writeln( "ACE"); + L900: writeln; + L910: if (C>A) goto L930; + L920: goto L970; + L930: if (C>=B) goto L970; + L950: writeln("YOU WIN!!!"); + L960: goto L210; + L970: writeln("SORRY, YOU LOSE"); + L980: if (M $b } @shuffledDeck[ 0 .. 1 ]; + my ( $firstCard, $secondCard ) = sort { $a <=> $b } @shuffledDeck[ 0 .. 1 ]; print "I drew ", nameOfCard($firstCard), " and ", nameOfCard($secondCard), ".\n"; my $bet = getValidBet($playerBalance); - if ($bet == 0) - { + if ( $bet == 0 ) { print "Chicken!\n\n"; next HAND; } - if ($bet < 0) - { + if ( $bet < 0 ) { last GAME; } @@ -72,19 +68,16 @@ while ($keepPlaying) print "I drew ", nameOfCard($thirdCard), "!\n"; - if (($firstCard < $thirdCard) && ($thirdCard < $secondCard)) - { + if ( ( $firstCard < $thirdCard ) && ( $thirdCard < $secondCard ) ) { print "You win!\n\n"; $playerBalance += $bet; } - else - { + else { print "You lose!\n\n"; $playerBalance -= $bet; } - if ($playerBalance <= 0) - { + if ( $playerBalance <= 0 ) { print "Sorry, buddy, you blew your wad!\n\n"; last HAND; } @@ -96,49 +89,43 @@ while ($keepPlaying) print "Thanks for playing!\n"; ############### -sub getValidBet -{ +sub getValidBet { my $maxBet = shift; - print "\nWhat's your bet? "; - - my $input = ; - chomp $input; - - # This regular expression will validate that the player entered an integer. - # The !~ match operate *negates* the match, so if the player did NOT enter - # an integer, they'll be given an error and prompted again. - if ($input !~ /^ # Match the beginning of the string - [+-]? # Optional plus or minus... - \d+ # followed by one more more digits... - $ # and then the end of the string - /x # The x modifier ignores whitespace in this regex... - ) + INPUT: { - print "Sorry, numbers only!\n"; - $input = getValidBet($maxBet); - } + print "\nWhat's your bet? "; - if ($input > $maxBet) - { - print "Sorry, my friend, you can't bet more money than you have.\n"; - print "You only have $maxBet dollars to spend!\n"; - $input = getValidBet($maxBet); - } + chomp( my $input = ); - if ($input != int($input)) - { - print "Sorry, you must bet in whole dollars. No change!\n"; - $input = getValidBet($maxBet); - } + # This regular expression will validate that the player entered an integer. + # The !~ match operate *negates* the match, so if the player did NOT enter + # an integer, they'll be given an error and prompted again. + if ( + $input !~ /^ # Match the beginning of the string + [+-]? # Optional plus or minus... + \d+ # followed by one more more digits... + $ # and then the end of the string + /x # The x modifier ignores whitespace in this regex... + ) + { + print "Sorry, numbers only!\n"; + redo INPUT; + } - return $input; + if ( $input > $maxBet ) { + print "Sorry, my friend, you can't bet more money than you have.\n"; + print "You only have $maxBet dollars to spend!\n"; + redo INPUT; + } + + return $input; + } } # Since arrays in Perl are 0-based, we need to convert the value that we drew from # the array to its proper position in the deck. -sub nameOfCard -{ +sub nameOfCard { my $value = shift; # Note that the Joker isn't used in this game, but since arrays in Perl are @@ -150,25 +137,21 @@ sub nameOfCard return $cardlist[$value]; } -sub promptUserToKeepPlaying -{ - print "Try again (Y/N)? "; - my $input = ; - chomp $input; +sub promptUserToKeepPlaying { + YESNO: + { + print "Try again (Y/N)? "; - my $keepGoing; - if (uc($input) eq 'Y') - { - $keepGoing = 1; - } - elsif (uc($input) eq 'N') - { - $keepGoing = 0; - } - else - { - $keepGoing = promptUserToKeepPlaying(); - } + chomp( my $input = uc ); - return $keepGoing; + if ( $input eq 'Y' ) { + return 1; + } + elsif ( $input eq 'N' ) { + return 0; + } + else { + redo YESNO; + } + } } diff --git a/02_Amazing/README.md b/02_Amazing/README.md index 8f7b271b..e94949bd 100644 --- a/02_Amazing/README.md +++ b/02_Amazing/README.md @@ -12,3 +12,7 @@ As published in Basic Computer Games (1978): Downloaded from Vintage Basic at http://www.vintage-basic.net/games.html + +--- + +**2022-01-04:** patched original source in [#400](https://github.com/coding-horror/basic-computer-games/pull/400) to fix a minor bug where a generated maze may be missing an exit, particularly at small maze sizes. diff --git a/02_Amazing/amazing.bas b/02_Amazing/amazing.bas index 63255319..3b39c93a 100644 --- a/02_Amazing/amazing.bas +++ b/02_Amazing/amazing.bas @@ -117,10 +117,15 @@ 975 V(R,S)=3:Q=0:GOTO 1000 980 V(R,S)=1:Q=0:R=1:S=1:GOTO 250 1000 GOTO 210 -1010 FOR J=1 TO V -1011 PRINT "I"; -1012 FOR I=1 TO H -1013 IF V(I,J)<2 THEN 1030 +1010 IF Z=1 THEN 1015 +1011 X=INT(RND(1)*H+1) +1012 IF V(X,V)=0 THEN 1014 +1013 V(X,V)=3: GOTO 1015 +1014 V(X,V)=1 +1015 FOR J=1 TO V +1016 PRINT "I"; +1017 FOR I=1 TO H +1018 IF V(I,J)<2 THEN 1030 1020 PRINT " "; 1021 GOTO 1040 1030 PRINT " I"; diff --git a/02_Amazing/perl/amazing.pl b/02_Amazing/perl/amazing.pl new file mode 100755 index 00000000..c7cb5f44 --- /dev/null +++ b/02_Amazing/perl/amazing.pl @@ -0,0 +1,159 @@ +#! /usr/bin/perl +use strict; +use warnings; + +# Translated from BASIC by Alex Kapranoff + +use feature qw/say/; + +# width and height of the maze +my ($width, $height) = input_dimensions(); + +# wall masks for all cells +my @walls; + +# flags of previous visitation for all cells +my %is_visited; + +# was the path out of the maze found? +my $path_found = 0; + +# column of entry to the maze in the top line +my $entry_col = int(rand($width)); + +# cell coordinates for traversal +my $col = $entry_col; +my $row = 0; + +$is_visited{$row, $col} = 1; + +# looping until we visit every cell +while (keys %is_visited < $width * $height) { + if (my @dirs = get_possible_directions()) { + my $dir = $dirs[rand @dirs]; + + # modify current cell wall if needed + $walls[$row]->[$col] |= $dir->[2]; + + # move the position + $row += $dir->[0]; + $col += $dir->[1]; + + # we found the exit! + if ($row == $height) { + $path_found = 1; + --$row; + + if ($walls[$row]->[$col] == 1) { + ($row, $col) = get_next_branch(0, 0); + } + } + else { + # modify the new cell wall if needed + $walls[$row]->[$col] |= $dir->[3]; + + $is_visited{$row, $col} = 1; + } + } + else { + ($row, $col) = get_next_branch($row, $col); + } +} + +unless ($path_found) { + $walls[-1]->[rand $width] |= 1; +} + +print_maze(); + +sub input_dimensions { + # Print the banner and returns the dimensions as two integers > 1. + # The integers are parsed from the first line of standard input. + say ' ' x 28, 'AMAZING PROGRAM'; + say ' ' x 15, 'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY'; + print "\n" x 4; + + my ($w, $h) = (0, 0); + + while ($w <= 1 || $h <= 1) { + print 'WHAT ARE YOUR WIDTH AND LENGTH? '; + + ($w, $h) = =~ / \d+ /xg; + + if ($w < 1 || $h < 1) { + say "MEANINGLESS DIMENSIONS. TRY AGAIN." + } + } + + print "\n" x 4; + + return ($w, $h); +} + +sub get_possible_directions { + # Returns a list of all directions that are available to go to + # from the current coordinates. "Down" is available on the last line + # until we go there once and mark it as the path through the maze. + # + # Each returned direction element contains changes to the coordindates and to + # the wall masks of the previous and next cell after the move. + + my @rv; + # up + if ($row > 0 && !$is_visited{$row - 1, $col}) { + push @rv, [-1, 0, 0, 1]; + } + # left + if ($col > 0 && !$is_visited{$row, $col - 1}) { + push @rv, [0, -1, 0, 2]; + } + # right + if ($col < $width - 1 && !$is_visited{$row, $col + 1}) { + push @rv, [0, 1, 2, 0]; + } + # down + if ($row < $height - 1 && !$is_visited{$row + 1, $col} + || $row == $height - 1 && !$path_found + ) { + push @rv, [1, 0, 1, 0]; + } + + return @rv; +} + +sub get_next_branch { + # Returns the cell coordinates to start a new maze branch from. + # It looks for a visited cell starting from passed position and + # going down in the natural traversal order incrementing column and + # rows with a rollover to start at the bottom right corner. + my ($y, $x) = @_; + do { + if ($x < $width - 1) { + ++$x; + } elsif ($y < $height - 1) { + ($y, $x) = ($y + 1, 0); + } else { + ($y, $x) = (0, 0); + } + } while (!$is_visited{$y, $x}); + + return ($y, $x); +} + +sub print_maze { + # Print the full maze based on wall masks. + # For each cell, we mark the absense of the wall to the right with + # bit 2 and the absense of the wall down with bit 1. Full table: + # 0 -> both walls are present + # 1 -> wall down is absent + # 2 -> wall to the right is absent + # 3 -> both walls are absent + say join('.', '', map { $_ == $entry_col ? ' ' : '--' } 0 .. $width - 1), '.'; + + for my $row (@walls) { + say join(' ', map { $_ & 2 ? ' ' : 'I' } 0, @$row); + say join(':', '', map { $_ & 1 ? ' ' : '--' } @$row), '.'; + } + + return; +} diff --git a/03_Animal/kotlin/Animal.kt b/03_Animal/kotlin/Animal.kt index bac9af08..90fc9f20 100644 --- a/03_Animal/kotlin/Animal.kt +++ b/03_Animal/kotlin/Animal.kt @@ -37,14 +37,14 @@ fun main() { // an answer or a blank string fun ask(question: String): String { print("$question? ") - return readLine()?.uppercase() ?: "" + return readln().uppercase() ?: "" } // Special case for a "yes or no" question, returns true of yes fun askYesOrNo(question: String): Boolean { return generateSequence { print("$question? ") - readLine() + readln() }.firstNotNullOf { yesOrNo(it) } } diff --git a/04_Awari/perl/awari.pl b/04_Awari/perl/awari.pl new file mode 100644 index 00000000..3d70452c --- /dev/null +++ b/04_Awari/perl/awari.pl @@ -0,0 +1,267 @@ +#!/usr/bin/env perl +use v5.24; +use warnings; +use experimental 'signatures'; +no warnings 'experimental::signatures'; +use List::Util 'none'; + +# our board will be represented with an array of 14 slots, from 0 to 13. +# Positions 6 and 13 represent the "home pit" for the human and the +# computer, respectively. +use constant PLAYER_HOME => 6; +use constant COMPUTER_HOME => 13; + +use constant FIRST => 0; +use constant AGAIN => 1; + +exit main(@ARGV); + +sub main { + $|++; # disable buffering on standard output, every print will be + # done immediately + + welcome(); # startup message + + # this array will keep track of computer-side failures, defined as + # "the computer did not win". Whenever the computer loses or draws, the + # specific sequence of moves will be saved and then used to drive + # the search for a (hopefully) optimal move. + my $failures = []; + while ('enjoying') { + + # a new game starts, let's reset the board to the initial condition + my $board = [ (3) x 6, 0, (3) x 6, 0 ]; + + # this string will keep track of all moves performed + my $moves = '/'; + + # the human player starts + my $turn = 'player'; + + say "\n"; + print_board($board); + + while (not is_game_over($board)) { + + my $move; # this will collect the move in this turn + + if ($turn eq 'player') { # "first" move for player + + # player_move(...) does the move selected by the player, + # returning both the selected move as well as the pit id + # where the last seed landed + ($move, my $landing) = player_move($board); + + # if we landed on the Player's Home Pit we get another move + $turn = $landing == PLAYER_HOME ? 'player-again' : 'computer'; + } + elsif ($turn eq 'player-again') { # "second" move for player + + # here we call player_move making it clear that it's the + # second move, to get the right prompt eventually. We only + # care for the $move as the result, so we ignore the other. + ($move) = player_move($board, AGAIN); + $turn = 'computer'; + } + else { + + # the computer_move(...) function analyzes the $board as well + # as adapting the strategy based on past "failures" (i.e. + # matches where the computer did not win). For this it's + # important to pass the log of these failures, as well as the + # full record of moves in this specific match. + ($move, my $landing) = computer_move($board, $failures, $moves); + print "\nMY MOVE IS ", $move - 6; + + # do the second move in the turn if conditions apply + if ($landing == COMPUTER_HOME && ! is_game_over($board)) { + + # save the first move before doing the second one! + $moves .= "$move/"; + + my ($move) = computer_move($board, $failures, $moves); + print ',', $move - 6; + } + $turn = 'player'; + } + + # append the last selected move by either party, to track this + # specific match (useful for computer's AI and ML) + $moves .= "$move/"; + print_board($board); + } + + # assess_victory() returns the difference between player's and + # computer's seeds, so a negative value is a win for the computer. + my $computer_won = assess_victory($board) < 0; + + # if this last match was a "failure" (read: not a win for the + # computer), then record it for future memory. + push $failures->@*, $moves unless $computer_won; + } + + return 0; +} + +# calculate the difference between the two home pits. Negative values mean +# that the computer won, 0 is a draw, positive values is a player's win. +# The difference is also returned back, in case of need. +sub assess_victory ($board) { + say "\nGAME OVER"; + my $difference = $board->[PLAYER_HOME] - $board->[COMPUTER_HOME]; + if ($difference < 0) { + say 'I WIN BY ', -$difference, ' POINTS'; + } + else { + say $difference ? "YOU WIN BY $difference POINTS" : 'DRAWN GAME'; + } + return $difference; +} + +# move the seeds from $pit and take into account possible bonuses +sub move_seeds ($board, $pit) { + + # get the seeds from the selected pit $pit + my $seeds = $board->[$pit]; + $board->[$pit] = 0; + + # $landing will be our "moving cursor" to place seeds around + my $landing = $pit; + while ($seeds > 0) { + $landing = ($landing + 1) % 14; # 12 --> 13 -[wrap]-> 0 --> 1 + --$seeds; + ++$board->[$landing]; + } + + # check for "stealing seeds" condition. This cannot happen in home pits + if ($landing != PLAYER_HOME && $landing != COMPUTER_HOME + && $board->[$landing] == 1 && $board->[12 - $landing] > 0) { + my $home = $pit < 7 ? PLAYER_HOME : COMPUTER_HOME; + $board->[$home] += 1 + $board->[12 - $landing]; + $board->@[$landing, 12 - $landing] = (0, 0); + } + + return ($pit, $landing); +} + +sub get_player_move ($board, $prompt) { + print "\n$prompt? "; + while (defined(my $move = )) { + chomp($move); # remove newline + return $move - 1 if $move =~ m{\A[1-6]\z}mxs && $board->[$move - 1]; + print 'ILLEGAL MOVE\nAGAIN? '; + } + die "goodbye\n"; +} + +sub player_move ($board, $stage = FIRST) { + my $prompt = $stage == FIRST ? 'YOUR MOVE' : 'AGAIN'; + my $selected_move = get_player_move($board, $prompt); + return move_seeds($board, $selected_move); +} + +sub computer_move ($board, $failures, $moves) { + + # we will go through all possible moves for the computer and all + # possible responses by the player, collecting the "best" move in terms + # of reasonable outcome (assuming that each side wants to maximize their + # outcome. $best_move will eventually contain the best move for the + # computer, and $best_difference the best difference in scoring (as + # seen from the computer). + my ($best_move, $best_difference); + for my $c_move (7 .. 12) { + next unless $board->[$c_move]; # only consider pits with seeds inside + + # we work on a copy of the board to do all our trial-and-errors + my $copy = [ $board->@* ]; + move_seeds($copy, $c_move); + + # it's time to "think like a player" and see what's the "best" move + # for the player in this situation. This heuristic is "not perfect" + # but it seems OK anyway. + my $best_player_score = 0; + for my $p_move (0 .. 5) { + next unless $copy->[$p_move]; # only pits with seeds inside + my $landing = $copy->[$p_move] + $p_move; + + # the player's score for this move, calculated as additional seeds + # placed in the player's pit. The original algorithm sets this to + # 1 only if the $landing position is greater than 13, which can + # be obtained by setting the ORIGINAL environment variable to a + # "true" value (in Perl terms). Otherwise it is calculated + # according to the real rules for the game. + my $p_score = $ENV{ORIGINAL} ? $landing > 13 : int(($landing - 5) / 14); + + # whatever, the landing position must be within the bounds + $landing %= 14; + + # if the conditions apply, the player's move might win additional + # seeds, which we have to to take into account. + $p_score += $copy->[12 - $landing] + if $copy->[$landing] == 0 + && $landing != PLAYER_HOME && $landing != COMPUTER_HOME; + + # let's compare this move's score against the best collected + # so far (as a response to a specific computer's move). + $best_player_score = $p_score if $p_score > $best_player_score; + } + + # the overall score for the player is the additional seeds we just + # calculated into $best_player_score plus the seeds that were already + # in the player's pit + $best_player_score += $copy->[PLAYER_HOME]; + + # the best difference we can aim for with this computer's move must + # assume that the player will try its best + my $difference = $copy->[COMPUTER_HOME] - $best_player_score; + + # now it's time to check this computer's move against the history + # of failed matches. $candidate_moves will be the "candidate" list + # of moves if we accept this one. + my $candidate_moves = $moves . $c_move . '/'; + for my $failure ($failures->@*) { + + # index(.) returns 0 if and only if $candidate_moves appears at + # the very beginning of $failure, i.e. it matches a previous + # behaviour. + next if index($failure, $candidate_moves) != 0; + + # same sequence of moves as before... assign a penalty + $difference -= 2; + } + + # update $best_move and $best_difference if they need to + ($best_move, $best_difference) = ($c_move, $difference) + if (! defined $best_move) || ($best_difference < $difference); + } + + # apply the selected move and return + return move_seeds($board, $best_move); +} + +sub welcome { + say ' ' x 34, 'AWARI'; + say ' ' x 15, 'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY'; +} + +sub print_board ($board) { + my $template = ' + %2d %2d %2d %2d %2d %2d + %2d %d + %2d %2d %2d %2d %2d %2d +'; + printf $template, $board->@[12, 11, 10, 9, 8 , 7, 13, 6, 0 .. 5]; + return; +} + +sub is_game_over ($board) { + + # game over if the player's side is empty + return 1 if none { $_ } $board->@[0 .. 5]; + + # game over if the computers' side is empty + return 1 if none { $_ } $board->@[7 .. 12]; + + # not game over + return 0; +} diff --git a/11_Bombardment/csharp/Bombardment.cs b/11_Bombardment/csharp/Bombardment.cs new file mode 100644 index 00000000..bcbdcc26 --- /dev/null +++ b/11_Bombardment/csharp/Bombardment.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; + +namespace Bombardment +{ + // + // Game of Bombardment + // Based on the Basic game of Bombardment here + // https://github.com/coding-horror/basic-computer-games/blob/main/11%20Bombardment/bombardment.bas + // Note: The idea was to create a version of the 1970's Basic game in C#, without introducing + // new features - no additional text, error checking, etc has been added. + // + internal class Bombardment + { + private static int MAX_GRID_SIZE = 25; + private static int MAX_PLATOONS = 4; + private static Random random = new Random(); + private List computerPositions = new List(); + private List playerPositions = new List(); + private List computerGuesses = new List(); + + private void PrintStartingMessage() + { + Console.WriteLine("{0}BOMBARDMENT", new string(' ', 33)); + Console.WriteLine("{0}CREATIVE COMPUTING MORRISTOWN, NEW JERSEY", new string(' ', 15)); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + + Console.WriteLine("YOU ARE ON A BATTLEFIELD WITH 4 PLATOONS AND YOU"); + Console.WriteLine("HAVE 25 OUTPOSTS AVAILABLE WHERE THEY MAY BE PLACED."); + Console.WriteLine("YOU CAN ONLY PLACE ONE PLATOON AT ANY ONE OUTPOST."); + Console.WriteLine("THE COMPUTER DOES THE SAME WITH ITS FOUR PLATOONS."); + Console.WriteLine(); + Console.WriteLine("THE OBJECT OF THE GAME IS TO FIRE MISSLES AT THE"); + Console.WriteLine("OUTPOSTS OF THE COMPUTER. IT WILL DO THE SAME TO YOU."); + Console.WriteLine("THE ONE WHO DESTROYS ALL FOUR OF THE ENEMY'S PLATOONS"); + Console.WriteLine("FIRST IS THE WINNER."); + Console.WriteLine(); + Console.WriteLine("GOOD LUCK... AND TELL US WHERE YOU WANT THE BODIES SENT!"); + Console.WriteLine(); + Console.WriteLine("TEAR OFF MATRIX AND USE IT TO CHECK OFF THE NUMBERS."); + + // As an alternative to repeating the call to WriteLine(), + // we can print the new line character five times. + Console.Write(new string('\n', 5)); + + // Print a sample board (presumably the game was originally designed to be + // physically printed on paper while played). + for (var i = 1; i <= 25; i += 5) + { + // The token replacement can be padded by using the format {tokenPosition, padding} + // Negative values for the padding cause the output to be left-aligned. + Console.WriteLine("{0,-3}{1,-3}{2,-3}{3,-3}{4,-3}", i, i + 1, i + 2, i + 3, i + 4); + } + + Console.WriteLine("\n"); + } + + // Generate 5 random positions for the computer's platoons. + private void PlaceComputerPlatoons() + { + do + { + var nextPosition = random.Next(1, MAX_GRID_SIZE); + if (!computerPositions.Contains(nextPosition)) + { + computerPositions.Add(nextPosition); + } + + } while (computerPositions.Count < MAX_PLATOONS); + } + + private void StoreHumanPositions() + { + Console.WriteLine("WHAT ARE YOUR FOUR POSITIONS"); + + // The original game assumed that the input would be five comma-separated values, all on one line. + // For example: 12,22,1,4,17 + var input = Console.ReadLine(); + var playerPositionsAsStrings = input.Split(","); + foreach (var playerPosition in playerPositionsAsStrings) { + playerPositions.Add(int.Parse(playerPosition)); + } + } + + private void HumanTurn() + { + Console.WriteLine("WHERE DO YOU WISH TO FIRE YOUR MISSLE"); + var input = Console.ReadLine(); + var humanGuess = int.Parse(input); + + if(computerPositions.Contains(humanGuess)) + { + Console.WriteLine("YOU GOT ONE OF MY OUTPOSTS!"); + computerPositions.Remove(humanGuess); + + switch(computerPositions.Count) + { + case 3: + Console.WriteLine("ONE DOWN, THREE TO GO."); + break; + case 2: + Console.WriteLine("TWO DOWN, TWO TO GO."); + break; + case 1: + Console.WriteLine("THREE DOWN, ONE TO GO."); + break; + case 0: + Console.WriteLine("YOU GOT ME, I'M GOING FAST."); + Console.WriteLine("BUT I'LL GET YOU WHEN MY TRANSISTO&S RECUP%RA*E!"); + break; + } + } + else + { + Console.WriteLine("HA, HA YOU MISSED. MY TURN NOW:"); + } + } + + private int GenerateComputerGuess() + { + int computerGuess; + do + { + computerGuess = random.Next(1, 25); + } + while(computerGuesses.Contains(computerGuess)); + computerGuesses.Add(computerGuess); + + return computerGuess; + } + + private void ComputerTurn() + { + var computerGuess = GenerateComputerGuess(); + + if (playerPositions.Contains(computerGuess)) + { + Console.WriteLine("I GOT YOU. IT WON'T BE LONG NOW. POST {0} WAS HIT.", computerGuess); + playerPositions.Remove(computerGuess); + + switch(playerPositions.Count) + { + case 3: + Console.WriteLine("YOU HAVE ONLY THREE OUTPOSTS LEFT."); + break; + case 2: + Console.WriteLine("YOU HAVE ONLY TWO OUTPOSTS LEFT."); + break; + case 1: + Console.WriteLine("YOU HAVE ONLY ONE OUTPOST LEFT."); + break; + case 0: + Console.WriteLine("YOU'RE DEAD. YOUR LAST OUTPOST WAS AT {0}. HA, HA, HA.", computerGuess); + Console.WriteLine("BETTER LUCK NEXT TIME."); + break; + } + } + else + { + Console.WriteLine("I MISSED YOU, YOU DIRTY RAT. I PICKED {0}. YOUR TURN:", computerGuess); + } + } + + public void Play() + { + PrintStartingMessage(); + PlaceComputerPlatoons(); + StoreHumanPositions(); + + while (playerPositions.Count > 0 && computerPositions.Count > 0) + { + HumanTurn(); + + if (computerPositions.Count > 0) + { + ComputerTurn(); + } + } + } + } +} diff --git a/11_Bombardment/csharp/Bombardment.csproj b/11_Bombardment/csharp/Bombardment.csproj new file mode 100644 index 00000000..e5e0e164 --- /dev/null +++ b/11_Bombardment/csharp/Bombardment.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp2.1 + + + diff --git a/11_Bombardment/csharp/Program.cs b/11_Bombardment/csharp/Program.cs new file mode 100644 index 00000000..acc438ba --- /dev/null +++ b/11_Bombardment/csharp/Program.cs @@ -0,0 +1,13 @@ +using System; + +namespace Bombardment +{ + class Program + { + static void Main(string[] args) + { + var bombardment = new Bombardment(); + bombardment.Play(); + } + } +} diff --git a/11_Bombardment/perl/bombardment.pl b/11_Bombardment/perl/bombardment.pl new file mode 100644 index 00000000..0641102e --- /dev/null +++ b/11_Bombardment/perl/bombardment.pl @@ -0,0 +1,129 @@ +#!/usr/bin/perl + +use strict; +use warnings; + + +#GLOBAL +my %player_bases; +my %computer_bases; +my %player_choices; +my %computer_choices; + +&main; + +sub main { + &print_intro; + &display_field; + &populate_computer_bases; + &populate_player_bases; + &game_play; +} + +sub game_play { + until (keys %computer_bases == 0 || keys %player_bases == 0) { + &player_turn; + if (keys %computer_bases == 0) { + exit; + } + &computer_turn; + } + exit; +} + +sub computer_turn { + # There is logic in here to ensure that the computer doesn't try to pick a target it has already picked + my $valid_choice = 0; + until ($valid_choice == 1) { + my $target = int(rand(25)+1); + if (exists $computer_choices{$target}) { + $valid_choice = 0; + } + else { + $valid_choice = 1; + $computer_choices{$target}=1; + if (exists $player_bases{$target}) { + delete($player_bases{$target}); + my $size = keys %player_bases; + if ($size > 0) { + print "I GOT YOU. IT WON'T BE LONG NOW. POST $target WAS HIT.\n"; + if ($size == 3) { print "YOU HAVE ONLY THREE OUTPOSTS LEFT.\n"}; + if ($size == 2) { print "YOU HAVE ONLY TWO OUTPOSTS LEFT.\n"}; + if ($size == 1) { print "YOU HAVE ONLY ONE OUTPOSTS LEFT.\n"}; + } + else { + print "YOU'RE DEAD. YOUR LAST OUTPOST WAS AT $target. HA, HA, HA.\nBETTER LUCK NEXT TIME\n"; + } + + } + else { + print "I MISSED YOU, YOU DIRTY RAT. I PICKED $target. YOUR TURN:\n"; + } + } + } +} + +sub player_turn { + print "WHERE DO YOU WISH TO FIRE YOUR MISSILE\n"; + chomp(my $target=); + if (exists $computer_bases{$target}) { + print "YOU GOT ONE OF MY OUTPOSTS!\n"; + delete($computer_bases{$target}); + my $size = keys %computer_bases; + if ($size == 3) { print "ONE DOWN, THREE TO GO.\n"}; + if ($size == 2) { print "TWO DOWN, TWO TO GO.\n"}; + if ($size == 1) { print "THREE DOWN, ONE TO GO.\n"}; + if ($size == 0) { print "YOU GOT ME, I'M GOING FAST. BUT I'LL GET YOU WHEN\nMY TRANSISTO&S RECUP%RA*E!\n"}; + } + else { + print "HA, HA YOU MISSED. MY TURN NOW:\n"; + } +} + +sub populate_player_bases { + print "WHAT ARE YOUR FOUR POSITIONS\n"; + my $positions=; + chomp($positions); + my @positions = split/ /,$positions; + foreach my $base (@positions) { + $player_bases{$base}=0; + } +} + +sub display_field { + for my $num (1..25) { + if (length($num) < 2) { + $num = " $num"; + } + print "$num "; + if ($num % 5 == 0) { + print "\n"; + } + } +} + +sub populate_computer_bases { + my $size = 0; + until ($size == 4) { + my $base = int(rand(25)+1); + $computer_bases{$base}=0; + $size = keys %computer_bases; + } +} + +sub print_intro { + print " " x 33, "BOMBARDMENT\n"; + print " " x 15, " CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n"; + print "\n\n"; + print "YOU ARE ON A BATTLEFIELD WITH 4 PLATOONS AND YOU\n"; + print "HAVE 25 OUTPOSTS AVAILABLE WHERE THEY MAY BE PLACED.\n"; + print "YOU CAN ONLY PLACE ONE PLATOON AT ANY ONE OUTPOST.\n"; + print "THE COMPUTER DOES THE SAME WITH ITS FOUR PLATOONS.\n\n"; + print "THE OBJECT OF THE GAME IS TO FIRE MISSLES AT THE\n"; + print "OUTPOSTS OF THE COMPUTER. IT WILL DO THE SAME TO YOU.\n"; + print "THE ONE WHO DESTROYS ALL FOUR OF THE ENEMY'S PLATOONS\n"; + print "FIRST IS THE WINNER.\n\n"; + print "GOOD LUCK... AND TELL US WHERE YOU WANT THE BODIES SENT!\n\n"; + print "TEAR OFF MATRIX AND USE IT TO CHECK OFF THE NUMBERS.\n"; + print "\n\n\n\n"; +} diff --git a/12_Bombs_Away/perl/bombsaway.pl b/12_Bombs_Away/perl/bombsaway.pl new file mode 100644 index 00000000..478d01a7 --- /dev/null +++ b/12_Bombs_Away/perl/bombsaway.pl @@ -0,0 +1,140 @@ +#!/usr/bin/env perl +use v5.24; +use warnings; +use experimental 'signatures'; +no warnings 'experimental::signatures'; + +exit main(@ARGV); + +sub main { + $|++; + my $mission = 'y'; + + my @choices = ( + { # 1 - Italy + ask => 'YOUR TARGET -- ALBANIA(1), GREECE(2), NORTH AFRICA(3)', + comments => [ + q{SHOULD BE EASY -- YOU'RE FLYING A NAZI-MADE PLANE.}, + 'BE CAREFUL!!!', + q{YOU'RE GOING FOR THE OIL, EH?}, + ], + }, + { # 2 - Allies + ask => 'AIRCRAFT -- LIBERATOR(1), B-29(2), B-17(3), LANCASTER(4)', + comments => [ + q{YOU'VE GOT 2 TONS OF BOMBS FLYING FOR PLOESTI.}, + q{YOU'RE DUMPING THE A-BOMB ON HIROSHIMA.}, + q{YOU'RE CHASING THE BISMARK IN THE NORTH SEA.}, + q{YOU'RE BUSTING A GERMAN HEAVY WATER PLANT IN THE RUHR.}, + ], + }, + \&japan, + { # 4 - Germany + ask => "A NAZI, EH? OH WELL. ARE YOU GOING FOR RUSSIA(1),\n" + . 'ENGLAND(2), OR FRANCE(3)', + comments => [ + q{YOU'RE NEARING STALINGRAD.}, + q{NEARING LONDON. BE CAREFUL, THEY'VE GOT RADAR.}, + q{NEARING VERSAILLES. DUCK SOUP. THEY'RE NEARLY DEFENSELESS.}, + ], + }, + ); + + while (fc($mission // 'n') eq fc('y')) { + say 'YOU ARE A PILOT IN A WORLD WAR II BOMBER.'; + + my $side = choose( + 'WHAT SIDE -- ITALY(1), ALLIES(2), JAPAN(3), GERMANY(4)? ', 4); + my $choice = $choices[$side - 1]; + ref($choice) eq 'HASH' ? multiple($choice) : $choice->(); + + print "\n\n\nANOTHER MISSION (Y OR N)? "; + chomp($mission = ); + } + say "CHICKEN !!!\n"; + return 0; +} + +sub choose ($prompt, $n_max) { + while ('necessary') { + print "$prompt? "; + chomp(my $side = ); + return $side if $side =~ m{\A [1-9]\d* \z}mxs && $side <= $n_max; + say 'TRY AGAIN...'; + } +} + +sub multiple ($spec) { + my $target = choose("$spec->{ask}? ", scalar $spec->{comments}->@*); + say $spec->{comments}->[$target - 1]; + say ''; + + my $missions; + while ('necessary') { + print 'HOW MANY MISSIONS HAVE YOU FLOWN? '; + chomp($missions = ); + last if $missions < 160; + print 'MISSIONS, NOT MILES... +150 MISSIONS IS HIGH EVEN FOR OLD-TIMERS. +NOW THEN, '; + } + if ($missions < 25) { say 'FRESH OUT OF TRANING, EH?' } + elsif ($missions >= 100) { say q{THAT'S PUSHING THE ODDS!} } + + return direct_hit() if $missions >= rand(160); + + my $miss = 2 + int rand(30); + say "MISSED TARGET BY $miss MILES!"; + say "NOW YOU'RE REALLY IN FOR IT !!\n"; + our $double_fire = 0; + my $response = choose( + 'DOES THE ENEMY HAVE GUNS(1), MISSILES(2), OR BOTH(3)', 3); + if ($response != 2) { + print q{WHAT'S THE PERCENT HIT RATE OF ENEMY GUNNERS (10 TO 50)? }; + chomp (our $hit_rate = ); + if ($hit_rate < 10) { + say q{YOU LIE, BUT YOU'LL PAY...}; + return endgame('fail'); # sure failure + } + say ''; + } + if ($response > 1) { + $double_fire = 35; + } + return endgame(); +} + +sub direct_hit { + my $killed = int rand(100); + say "DIRECT HIT!!!! $killed KILLED.\nMISSION SUCCESSFUL"; + return; +} + +sub endgame ($fail = 0) { + our $double_fire //= 0; + our $hit_rate //= 0; + $fail ||= ($double_fire + $hit_rate) > rand(100); + if ($fail) { + say '* * * * BOOM * * * * +YOU HAVE BEEN SHOT DOWN..... +DEARLY BELOVED, WE ARE GATHERED HERE TODAY TO PAY OUR +LAST TRIBUTE...'; + } + else { + say 'YOU MADE IT THROUGH TREMENDOUS FLAK!!'; + } + return; +} + +sub japan { + say q{YOU'RE FLYING A KAMIKAZE MISSION OVER THE USS LEXINGTON.}; + print q{YOUR FIRST KAMIKAZE MISSION(Y OR N)? }; + chomp(my $is_first_kamikaze = ); + if (fc($is_first_kamikaze) eq fc('n')) { + our $hit_rate = 0; + say ''; + return endgame(); + } + return direct_hit() if rand(1) > 0.65; + return endgame('fail'); +} diff --git a/13_Bounce/ruby/bounce.rb b/13_Bounce/ruby/bounce.rb new file mode 100644 index 00000000..e215a897 --- /dev/null +++ b/13_Bounce/ruby/bounce.rb @@ -0,0 +1,184 @@ +## Global constants + +# Gravity accelaration (F/S^2) ~= 32 +G = 32 + +# Used to indent the plotting of ball positions +# so that the height digits don't affect +# where we start plotting ball positions +BALL_PLOT_INDENT = "\t" + +# The deviation between current plotted height and the actual +# height of the ball that we will accept to plot the ball in +# that plotted height +BALL_PLOT_DEVIATION = 0.25 + +# The step we will take as we move down vertically while +# plotting ball positions +BALL_PLOT_HEIGHT_STEP = 0.5 + + +## Helper functions + +# Calculates the bounce speed (up) of the ball for a given +# bounce number and coefficient +def calc_velocity_for_bounce(v0, bounce, coefficient) + v = v0 * coefficient**bounce +end + +# Check https://physics.stackexchange.com/a/333436 for nice explanation +def calc_bounce_total_time(v0, bounce, coefficient) + v = calc_velocity_for_bounce(v0, bounce, coefficient) + t = 2 * v / G +end + +# Check https://physics.stackexchange.com/a/333436 for nice explanation +def calc_ball_height(v0, bounce, coefficient, t) + v = calc_velocity_for_bounce(v0, bounce, coefficient) + h = v * t - 0.5 * G * t**2 +end + +def heighest_position_in_next_bounce(time_in_bounce, v0, i, c) + time_in_next_bounce = time_in_bounce[i+1] + return -1 if time_in_next_bounce.nil? + return calc_ball_height(v0, i, c, time_in_next_bounce / 2) unless time_in_next_bounce.nil? +end + +def intro + puts <<~INSTRUCTIONS + BOUNCE + CREATIVE COMPUTING MORRISTOWN, NEW JERSEY + + + THIS SIMULATION LETS YOU SPECIFY THE INITIAL VELOCITY + OF A BALL THROWN STRAIGHT UP, AND THE COEFFICIENT OF + ELASTICITY OF THE BALL. PLEASE USE A DECIMAL FRACTION + COEFFICIENCY (LESS THAN 1). + + YOU ALSO SPECIFY THE TIME INCREMENT TO BE USED IN + 'STROBING' THE BALL'S FLIGHT (TRY .1 INITIALLY). + INSTRUCTIONS +end + + +## Plottin functions + +def plot_header + puts + puts "FEET" +end + +def plot_bouncing_ball(strobbing_time, v0, c) + ## Initializing helper values + + # How many bounces we want to plot + # original BASIC version is 70 / (V / (16 * S2)) + # 70 is assumed to be an arbitrary number higher than 2G and 16 is 1/2G + bounces_to_plot = (G**2 / (v0 / strobbing_time)).to_i + + # Holds the total time the ball spends in the air in every bounce + time_in_bounce = bounces_to_plot.times.map { |i| calc_bounce_total_time v0, i, c } + + plot_width = 0 + + # Calculate the highest position for the ball after the very first bounce + plotted_height = (calc_ball_height(v0, 0, c, v0/G) + 0.5).to_i + + ## Plotting bouncing ball + while plotted_height >= 0 do + # We will print only whole-number heights + print plotted_height.to_i if plotted_height.to_i === plotted_height + + print BALL_PLOT_INDENT + + bounces_to_plot.times { |i| + (0..time_in_bounce[i]).step(strobbing_time) { |t| + ball_pos = calc_ball_height v0, i, c, t + + # If the ball is within the acceptable deviation + # from the current height, we will plot it + if (plotted_height - ball_pos).abs <= BALL_PLOT_DEVIATION then + print "0" + else + print " " + end + + # Increment the plot width when we are plotting height = 0 + # which will definitely be the longest since it never gets + # skipped by line 98 + plot_width += 1 if plotted_height == 0 + } + + if heighest_position_in_next_bounce(time_in_bounce, v0, i, c) < plotted_height then + # If we got no more ball positions at or above current height in the next bounce, + # we can skip the rest of the bounces and move down to the next height to plot + puts + break + end + } + + plotted_height -= BALL_PLOT_HEIGHT_STEP + end + + # Return plot_width to be used by the plot_footer + plot_width +end + +def plot_footer (plot_width, strobbing_time) + # Dotted separator line + puts + print BALL_PLOT_INDENT + (plot_width).times { |_| print "." } + puts + + # Time values line + print BALL_PLOT_INDENT + points_in_sec = (1 / strobbing_time).to_i + plot_width.times { |i| + if i % points_in_sec == 0 then + print (i / points_in_sec).to_i + else + print " " + end + } + puts + + # Time unit line + print BALL_PLOT_INDENT + (plot_width / 2 - 4).to_i.times { |_| print " " } + puts "SECONDS" + puts +end + +def game_loop + # Read strobing, velocity and coefficient parameters from user input + puts "TIME INCREMENT (SEC)" + strobbing_time = gets.to_f + + puts "VELOCITY (FPS)" + v0 = gets.to_f + + puts "COEFFICIENT" + c = gets.to_f + + # Plotting + plot_header + + plot_width = plot_bouncing_ball strobbing_time, v0, c + + plot_footer plot_width, strobbing_time +end + + +## Entry point + +begin + intro + loop do + game_loop + end +rescue SystemExit, Interrupt + exit +rescue => exception + p exception +end diff --git a/21_Calendar/python/calendar.py b/21_Calendar/python/calendar.py new file mode 100644 index 00000000..6f3f572b --- /dev/null +++ b/21_Calendar/python/calendar.py @@ -0,0 +1,156 @@ +######################################################## +# Calendar +# +# From: BASIC Computer Games (1978) +# Edited by David Ahl# +# +# This program prints out a calendar +# for any year. You must specify the +# starting day of the week of the year in +# statement 130. (Sunday(0), Monday +# (-1), Tuesday(-2), etc.) You can determine +# this by using the program WEEKDAY. +# You must also make two changes +# for leap years in statement 360 and 620. +# The program listing describes the necessary +# changes. Running the program produces a +# nice 12-month calendar. +# The program was written by Geofrey +# Chase of the Abbey, Portsmouth, Rhode Island. +# +######################################################## + +def parse_input(): + """ + function to parse input for weekday and leap year boolean + """ + + days_mapping = { + "sunday": 0, + "monday": -1, + "tuesday": -2, + "wednesday": -3, + "thursday": -4, + "friday": -5, + "saturday": -6 + } + + day = 0 + leap_day = False + + correct_day_input = False + while not correct_day_input: + weekday = input("INSERT THE STARTING DAY OF THE WEEK OF THE YEAR:") + + for day_k in days_mapping.keys(): + if weekday.lower() in day_k: + day = days_mapping[day_k] + correct_day_input = True + break + + while True: + leap = input("IS IT A LEAP YEAR?:") + + if 'y' in leap.lower(): + leap_day = True + break + + if 'n' in leap.lower(): + leap_day = False + break + + return day, leap_day + + +def calendar(weekday, leap_year): + """ + function to print a year's calendar. + + input: + _weekday_: int - the initial day of the week (0=SUN, -1=MON, -2=TUES...) + _leap_year_: bool - indicates if the year is a leap year + """ + months_days = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + days = 'S M T W T F S\n' + sep = "*" * 59 + years_day = 365 + d = weekday + + if leap_year: + months_days[2] = 29 + years_day = 366 + + months_names = [" JANUARY ", + " FEBRUARY", + " MARCH ", + " APRIL ", + " MAY ", + " JUNE ", + " JULY ", + " AUGUST ", + "SEPTEMBER", + " OCTOBER ", + " NOVEMBER", + " DECEMBER"] + + days_count = 0 # S in the original program + + # main loop + for n in range(1, 13): + days_count += months_days[n-1] + print("** {} ****************** {} ****************** {} **\n".format(days_count, + months_names[n-1], years_day-days_count)) + print(days) + print(sep) + + for w in range(1, 7): + print("\n") + for g in range(1, 8): + d += 1 + d2 = d - days_count + + if d2 > months_days[n]: + break + + if d2 <= 0: + print("{}".format(' '), end=' ') + elif d2 < 10: + print(" {}".format(d2), end=' ') + else: + print("{}".format(d2), end=' ') + print() + + if d2 >= months_days[n]: + break + + if d2 > months_days[n]: + d -= g + + print("\n") + + print("\n") + + +def main(): + print(" "*32 + "CALENDAR") + print(" "*15 + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + print("\n"*11) + + day, leap_year = parse_input() + calendar(day, leap_year) + + +if __name__ == "__main__": + main() + +######################################################## +# +######################################################## +# +# Porting notes: +# +# It has been added an input at the beginning of the +# program so the user can specify the first day of the +# week of the year and if the year is leap or not. +# +######################################################## diff --git a/24_Chemist/README.md b/24_Chemist/README.md index 95db3f66..b93c1431 100644 --- a/24_Chemist/README.md +++ b/24_Chemist/README.md @@ -13,4 +13,5 @@ As published in Basic Computer Games (1978): Downloaded from Vintage Basic at http://www.vintage-basic.net/games.html -[Conversion to C](https://github.com/ericfischer/basic-computer-games/blob/main/24%20Chemist/c/chemist.c) +#### External Links + - C: https://github.com/ericfischer/basic-computer-games/blob/main/24%20Chemist/c/chemist.c diff --git a/25_Chief/perl/chief.pl b/25_Chief/perl/chief.pl new file mode 100644 index 00000000..d3462514 --- /dev/null +++ b/25_Chief/perl/chief.pl @@ -0,0 +1,69 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +print ' ' x 30 . "CHIEF\n"; +print ' ' x 15 . "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n"; +print "\n\n\n"; + +print "I AM CHIEF NUMBERS FREEK, THE GREAT INDIAN MATH GOD.\n"; +print "ARE YOU READY TO TAKE THE TEST YOU CALLED ME OUT FOR?\n"; + +chomp( my $A = uc ); +print "SHUT UP, PALE FACE WITH WISE TONGUE.\n" unless ( $A eq 'YES' ); + +print " TAKE A NUMBER AND ADD 3. DIVIDE THIS NUMBER BY 5 AND\n"; +print "MULTIPLY BY 8. DIVIDE BY 5 AND ADD THE SAME. SUBTRACT 1.\n"; +print " WHAT DO YOU HAVE?\n"; + +chomp( my $B = ); +my $C = ( $B + 1 - 5 ) * 5 / 8 * 5 - 3; + +print "I BET YOUR NUMBER WAS $C. AM I RIGHT?\n"; + +chomp( my $D = uc ); +if ( $D eq 'YES' ) { + print "BYE!!!\n"; + exit; +} + +print "WHAT WAS YOUR ORIGINAL NUMBER?\n"; + +chomp( my $K = ); +my $F = $K + 3; +my $G = $F / 5; +my $H = $G * 8; +my $I = $H / 5 + 5; +my $J = $I - 1; + +print "SO YOU THINK YOU'RE SO SMART, EH?\n"; +print "NOW WATCH.\n"; +print "$K PLUS 3 EQUALS $F. THIS DIVIDED BY 5 EQUALS $G;\n"; +print "THIS TIMES 8 EQUALS $H. IF WE DIVIDE BY 5 AND ADD 5,\n"; +print "WE GET $I , WHICH, MINUS 1, EQUALS $J.\n"; +print "NOW DO YOU BELIEVE ME?\n"; + +chomp( my $Z = uc ); +if ( $Z eq 'YES' ) { + print "BYE!!!\n"; + exit; +} + +print "YOU HAVE MADE ME MAD!!!\n"; +print "THERE MUST BE A GREAT LIGHTNING BOLT!\n\n\n"; + +for my $i ( reverse 22 .. 30 ) { + print ' ' x $i . "X X\n"; +} +print ' ' x 21 . "X XXX\n"; +print ' ' x 20 . "X X\n"; +print ' ' x 19 . "XX X\n"; +for my $i ( reverse 13 .. 20 ) { + print ' ' x $i . "X X\n"; +} +print ' ' x 12 . "XX\n"; +print ' ' x 11 . "X\n"; +print ' ' x 10 . "*\n"; +print "\n#########################\n\n"; +print "I HOPE YOU BELIEVE ME NOW, FOR YOUR SAKE!!\n"; diff --git a/25_Chief/python/Chief.py b/25_Chief/python/Chief.py new file mode 100644 index 00000000..67b4eab8 --- /dev/null +++ b/25_Chief/python/Chief.py @@ -0,0 +1,77 @@ +def print_lightning_bolt(): + + print('*'*36) + n = 24 + while n > 16: + print(' '*n + 'x x') + n -=1 + print(' '*16 + 'x xxx') + print(' '*15 + 'x x') + print(' '*14+ 'xxx x') + n -=1 + while n > 8: + print(' '*n + 'x x') + n -=1 + print(' '*8 + 'xx') + print(' '*7 +'x') + print('*'*36) + + +def print_solution(n): + + print('\n{} plus 3 gives {}. This Divided by 5 equals {}'.format(n, n+3, (n+3)/5)) + print('This times 8 gives {}. If we divide 5 and add 5.'.format(( (n+3)/5 )*8 )) + print('We get {}, which, minus 1 equals {}'.format(( ((n+3)/5)*8)/5+5, ((((n+3)/5)*8)/5+5)-1 )) + +def Game(): + print('\nTake a Number and ADD 3. Now, Divide this number by 5 and') + print('multiply by 8. Now, Divide by 5 and add the same. Subtract 1') + + resp = float(input('\nWhat do you have? ')) + comp_guess = (((resp - 4)*5)/8)*5 -3 + resp2 = input('\nI bet your number was {} was i right(Yes or No)? '.format(comp_guess)) + + if resp2 == 'Yes' or resp2 == 'YES' or resp2 == 'yes': + print('\nHuh, I Knew I was unbeatable') + print('And here is how i did it') + print_solution(comp_guess) + input('') + + else: + resp3 = float(input('\nHUH!! what was you original number? ')) + + if resp3 == comp_guess: + print('\nThat was my guess, AHA i was right') + print("Shamed to accept defeat i guess, don't worry you can master mathematics too") + print('Here is how i did it') + print_solution(comp_guess) + input('') + + else: + print('\nSo you think you\'re so smart, EH?') + print('Now, Watch') + print_solution(resp3) + + resp4 = input('\nNow do you believe me? ') + + if resp4 == 'Yes' or resp4 == 'YES' or resp4 == 'yes': + print('\nOk, Lets play again sometime bye!!!!') + input('') + + else: + print('\nYOU HAVE MADE ME VERY MAD!!!!!') + print("BY THE WRATH OF THE MATHEMATICS AND THE RAGE OF THE GODS") + print("THERE SHALL BE LIGHTNING!!!!!!!") + print_lightning_bolt() + print('\nI Hope you believe me now, for your own sake') + input('') + +if __name__ == '__main__': + + print('I am CHIEF NUMBERS FREEK, The GREAT INDIAN MATH GOD.') + play = input('\nAre you ready to take the test you called me out for(Yes or No)? ') + if play == 'Yes' or play == 'YES' or play == 'yes': + Game() + else: + print('Ok, Nevermind. Let me go back to my great slumber, Bye') + input('') diff --git a/30_Cube/java/src/Cube.java b/30_Cube/java/src/Cube.java new file mode 100644 index 00000000..0100682a --- /dev/null +++ b/30_Cube/java/src/Cube.java @@ -0,0 +1,226 @@ +import java.io.PrintStream; +import java.util.HashSet; +import java.util.Random; +import java.util.Scanner; +import java.util.Set; + +/** + * Game of Cube + *

+ * Based on game of Cube at: + * https://github.com/coding-horror/basic-computer-games/blob/main/30_Cube/cube.bas + * + * + */ +public class Cube { + + //Current player location + private Location playerLocation; + + //Current list of mines + private Set mines; + + //System input / output objects + private PrintStream out; + private Scanner scanner; + + //Player's current money + private int money; + + /** + * Entry point, creates a new Cube object and calls the play method + * @param args Java execution arguments, not used in application + */ + public static void main(String[] args) { + new Cube().play(); + } + + public Cube() { + out = System.out; + scanner = new Scanner(System.in); + money = 500; + mines = new HashSet<>(5); + } + + /** + * Clears mines and places 5 new mines on the board + */ + private void placeMines() { + mines.clear(); + Random random = new Random(); + for(int i = 0; i < 5; i++) { + int x = random.nextInt(1,4); + int y = random.nextInt(1,4); + int z = random.nextInt(1,4); + mines.add(new Location(x,y,z)); + } + } + + /** + * Runs the entire game until the player runs out of money or chooses to stop + */ + public void play() { + out.println("DO YOU WANT TO SEE INSTRUCTIONS? (YES--1,NO--0)"); + if(readParsedBoolean()) { + printInstructions(); + } + do { + placeMines(); + out.println("WANT TO MAKE A WAGER?"); + int wager = 0 ; + + if(readParsedBoolean()) { + out.println("HOW MUCH?"); + do { + wager = Integer.parseInt(scanner.nextLine()); + if(wager > money) { + out.println("TRIED TO FOOL ME; BET AGAIN"); + } + } while(wager > money); + } + + playerLocation = new Location(1,1,1); + while(playerLocation.x + playerLocation.y + playerLocation.z != 9) { + out.println("\nNEXT MOVE"); + String input = scanner.nextLine(); + + String[] stringValues = input.split(","); + + if(stringValues.length < 3) { + out.println("ILLEGAL MOVE, YOU LOSE."); + return; + } + + int x = Integer.parseInt(stringValues[0]); + int y = Integer.parseInt(stringValues[1]); + int z = Integer.parseInt(stringValues[2]); + + Location location = new Location(x,y,z); + + if(x < 1 || x > 3 || y < 1 || y > 3 || z < 1 || z > 3 || !isMoveValid(playerLocation,location)) { + out.println("ILLEGAL MOVE, YOU LOSE."); + return; + } + + playerLocation = location; + + if(mines.contains(location)) { + out.println("******BANG******"); + out.println("YOU LOSE!\n\n"); + money -= wager; + break; + } + } + + if(wager > 0) { + out.printf("YOU NOW HAVE %d DOLLARS\n",money); + } + + } while(money > 0 && doAnotherRound()); + + out.println("TOUGH LUCK!"); + out.println("\nGOODBYE."); + } + + /** + * Queries the user whether they want to play another round + * @return True if the player decides to play another round, + * False if the player would not like to play again + */ + private boolean doAnotherRound() { + if(money > 0) { + out.println("DO YOU WANT TO TRY AGAIN?"); + return readParsedBoolean(); + } else { + return false; + } + } + + /** + * Prints the instructions to the game, copied from the original code. + */ + public void printInstructions() { + out.println("THIS IS A GAME IN WHICH YOU WILL BE PLAYING AGAINST THE"); + out.println("RANDOM DECISION OF THE COMPUTER. THE FIELD OF PLAY IS A"); + out.println("CUBE OF SIDE 3. ANY OF THE 27 LOCATIONS CAN BE DESIGNATED"); + out.println("BY INPUTTING THREE NUMBERS SUCH AS 2,3,1. AT THE START"); + out.println("YOU ARE AUTOMATICALLY AT LOCATION 1,1,1. THE OBJECT OF"); + out.println("THE GAME IS TO GET TO LOCATION 3,3,3. ONE MINOR DETAIL:"); + out.println("THE COMPUTER WILL PICK, AT RANDOM, 5 LOCATIONS AT WHICH"); + out.println("IT WILL PLANT LAND MINES. IF YOU HIT ONE OF THESE LOCATIONS"); + out.println("YOU LOSE. ONE OTHER DETAIL: YOU MAY MOVE ONLY ONE SPACE"); + out.println("IN ONE DIRECTION EACH MOVE. FOR EXAMPLE: FROM 1,1,2 YOU"); + out.println("MAY MOVE TO 2,1,2 OR 1,1,3. YOU MAY NOT CHANGE"); + out.println("TWO OF THE NUMBERS ON THE SAME MOVE. IF YOU MAKE AN ILLEGAL"); + out.println("MOVE, YOU LOSE AND THE COMPUTER TAKES THE MONEY YOU MAY"); + out.println("\n"); + out.println("ALL YES OR NO QUESTIONS WILL BE ANSWERED BY A 1 FOR YES"); + out.println("OR A 0 (ZERO) FOR NO."); + out.println(); + out.println("WHEN STATING THE AMOUNT OF A WAGER, PRINT ONLY THE NUMBER"); + out.println("OF DOLLARS (EXAMPLE: 250) YOU ARE AUTOMATICALLY STARTED WITH"); + out.println("500 DOLLARS IN YOUR ACCOUNT."); + out.println(); + out.println("GOOD LUCK!"); + } + + /** + * Waits for the user to input a boolean value. This could either be (true,false), (1,0), (y,n), (yes,no), etc. + * By default, it will return false + * @return Parsed boolean value of the user input + */ + private boolean readParsedBoolean() { + String in = scanner.nextLine(); + try { + return in.toLowerCase().charAt(0) == 'y' || Boolean.parseBoolean(in) || Integer.parseInt(in) == 1; + } catch(NumberFormatException exception) { + return false; + } + } + + /** + * Checks if a move is valid + * @param from The point that the player is at + * @param to The point that the player wishes to move to + * @return True if the player is only moving, at most, 1 location in any direction, False if the move is invalid + */ + private boolean isMoveValid(Location from, Location to) { + return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) + Math.abs(from.z - to.z) <= 1; + } + + public class Location { + int x,y,z; + + public Location(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + /* + For use in HashSet and checking if two Locations are the same + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Location location = (Location) o; + + if (x != location.x) return false; + if (y != location.y) return false; + return z == location.z; + } + + /* + For use in the HashSet to accordingly index the set + */ + @Override + public int hashCode() { + int result = x; + result = 31 * result + y; + result = 31 * result + z; + return result; + } + } +} diff --git a/31_Depth_Charge/ruby/.gitignore b/31_Depth_Charge/ruby/.gitignore new file mode 100644 index 00000000..d25ad7bc --- /dev/null +++ b/31_Depth_Charge/ruby/.gitignore @@ -0,0 +1,57 @@ +# Package Shell Specific Things + +## Build Process + +/build + +## Transient files + +/src/input +/src/output +/src/log +/drop + +# Programming Languages + +## Java +/src/java/**/*.class + +## Rakudo / Perl6 +/src/**/.precomp + +## PHP +/vendor/ + +## Python +*.swp +__pycache__/ +*.pyc +*.egg-info +/dist + +## Ruby +*.gem +.bundle + +# Editors + +## Dia +*.dia.autosave + +## Emacs +\#*\# +.\#* + + +## Vi / Vim +*.swp + +# Filesystem Artifacts + +## Mac + +.DS_Store + +## NFS + +.nfs* diff --git a/31_Depth_Charge/ruby/depthcharge.rb b/31_Depth_Charge/ruby/depthcharge.rb index 5ba36ba6..ba2a5d83 100755 --- a/31_Depth_Charge/ruby/depthcharge.rb +++ b/31_Depth_Charge/ruby/depthcharge.rb @@ -13,7 +13,6 @@ class DepthCharge break if ! get_input_another_game() end - # 420 PRINT "OK. HOPE YOU ENJOYED YOURSELF." : GOTO 600 printf("OK. HOPE YOU ENJOYED YOURSELF.\n") end @@ -51,7 +50,7 @@ class DepthCharge the_input = Integer(value) rescue nil - if the_input == nil || the_input < 0 + if the_input == nil || the_input < 1 printf("PLEASE ENTER A POSITIVE NUMBER\n\n") next @@ -61,25 +60,7 @@ class DepthCharge end end - def get_search_area_dimension - # 20 INPUT "DIMENSION OF SEARCH AREA";G: PRINT - @search_area_dimension = get_input_positive_integer("DIMENSION OF SEARCH AREA: ") - # 30 N=INT(LOG(G)/LOG(2))+1 - - @num_tries = Integer( - Math.log(@search_area_dimension)/Math.log(2) - ) - - end - def print_instructions - # 40 PRINT "YOU ARE THE CAPTAIN OF THE DESTROYER USS COMPUTER" - # 50 PRINT "AN ENEMY SUB HAS BEEN CAUSING YOU TROUBLE. YOUR" - # 60 PRINT "MISSION IS TO DESTROY IT. YOU HAVE";N;"SHOTS." - # 70 PRINT "SPECIFY DEPTH CHARGE EXPLOSION POINT WITH A" - # 80 PRINT "TRIO OF NUMBERS -- THE FIRST TWO ARE THE" - # 90 PRINT "SURFACE COORDINATES; THE THIRD IS THE DEPTH." - # 100 PRINT : PRINT "GOOD LUCK !": PRINT printf( <<~INSTRUCTIONS YOU ARE THE CAPTAIN OF THE DESTROYER USS COMPUTER AN ENEMY SUB HAS BEEN CAUSING YOU TROUBLE. YOUR @@ -110,19 +91,21 @@ GOOD LUCK ! end def setup_game - get_search_area_dimension() + @search_area_dimension = get_input_positive_integer("DIMENSION OF SEARCH AREA: ") + + @num_tries = Integer( + Math.log(@search_area_dimension)/Math.log(2) + 1 + ) setup_enemy() end def setup_enemy - # 110 A=INT(G*RND(1)) : B=INT(G*RND(1)) : C=INT(G*RND(1)) @enemy_x = rand(1..@search_area_dimension) @enemy_y = rand(1..@search_area_dimension) @enemy_z = rand(1..@search_area_dimension) - end + end def game_loop - # 120 FOR D=1 TO N : PRINT : PRINT "TRIAL #";D; : INPUT X,Y,Z for @trial in 1..@num_tries do output_game_status() @@ -130,7 +113,6 @@ GOOD LUCK ! @shot_y = get_input_positive_integer("Y: ") @shot_z = get_input_positive_integer("Z: ") - # 130 IF ABS(X-A)+ABS(Y-B)+ABS(Z-C)=0 THEN 300 if ( (@enemy_x - @shot_x).abs \ + (@enemy_y - @shot_y).abs \ @@ -140,7 +122,6 @@ GOOD LUCK ! you_win() return else - # 140 GOSUB 500 : PRINT : NEXT D missed_shot() end end @@ -156,54 +137,41 @@ GOOD LUCK ! printf("TRIAL \#%d\n", @trial) end def you_win - printf("B O O M ! ! YOU FOUND IT IN %d TRIES!\n\n", @trial ) + printf("\nB O O M ! ! YOU FOUND IT IN %d TRIES!\n\n", @trial ) end def missed_shot missed_directions = [] - # 530 IF X>A THEN PRINT "EAST"; - # 540 IF X @enemy_x missed_directions.push('TOO FAR EAST') elsif @shot_x < @enemy_x missed_directions.push('TOO FAR WEST') end - # 510 IF Y>B THEN PRINT "NORTH"; - # 520 IF Y @enemy_y missed_directions.push('TOO FAR NORTH') elsif @shot_y < @enemy_y missed_directions.push('TOO FAR SOUTH') end - # 560 IF Z>C THEN PRINT " TOO LOW." - # 570 IF Z @enemy_z missed_directions.push('TOO DEEP') elsif @shot_z < @enemy_z missed_directions.push('TOO SHALLOW') end - # 500 PRINT "SONAR REPORTS SHOT WAS "; printf("SONAR REPORTS SHOT WAS: \n") printf("%s\n", "\t" + missed_directions.join("\n\t")) - # 550 IF Y<>B OR X<>A THEN PRINT " AND"; - # 590 RETURN end def you_lose - # You took too long! printf("YOU HAVE BEEN TORPEDOED! ABANDON SHIP!\n") printf("THE SUBMARINE WAS AT %d %d %d\n", @enemy_x, @enemy_y, @enemy_z) end def get_input_another_game - # 400 PRINT : PRINT: INPUT "ANOTHER GAME (Y OR N)";A$ return get_input_y_or_n("ANOTHER GAME (Y OR N): ") - # 410 IF A$="Y" THEN 100 end end diff --git a/35_Even_Wins/perl/evenwins.pl b/35_Even_Wins/perl/evenwins.pl new file mode 100644 index 00000000..c2fdc2d8 --- /dev/null +++ b/35_Even_Wins/perl/evenwins.pl @@ -0,0 +1,179 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +&main; + +sub main { + &print_intro; + &game_play; +} + +sub game_play { + my $marbles = 27; + my $turn = 0; + my $player_total = 0; + my $computer_total = 0; + print "TYPE A '1' IF YOU WANT TO GO FIRST AND TYPE A '0' IF YOU WANT ME TO GO FIRST\n"; + my $choice = ; + chomp($choice); + if ($choice == 0) { + until ($marbles == 0) { + + my $computer_choice = &computer_select($marbles,$turn,$player_total); + $marbles = $marbles - $computer_choice; + $computer_total = $computer_total + $computer_choice; + print "MY TOTAL IS $computer_total\n"; + + print "TOTAL= $marbles\n"; + + if ($marbles == 0) {&determine_winner($computer_total,$player_total)}; + + my $player_choice = &player_select($marbles,$turn); + $marbles = $marbles - $player_choice; + $player_total = $player_total + $player_choice; + print "YOUR TOTAL IS $player_total\n"; + $turn++; + print "TOTAL= $marbles\n"; + if ($marbles == 0) {&determine_winner($computer_total,$player_total)}; + } + } + elsif ($choice == 1) { + until ($marbles == 0) { + + my $player_choice = &player_select($marbles,$turn); + $marbles = $marbles - $player_choice; + $player_total = $player_total + $player_choice; + $turn++; + print "YOUR TOTAL IS $player_total\n"; + + print "TOTAL= $marbles\n"; + + if ($marbles == 0) {&determine_winner($computer_total,$player_total)}; + + my $computer_choice = &computer_select($marbles,$turn,$player_total); + $marbles = $marbles - $computer_choice; + $computer_total = $computer_total + $computer_choice; + print "MY TOTAL IS $computer_total\n"; + + print "TOTAL= $marbles\n"; + + if ($marbles == 0) {&determine_winner($computer_total,$player_total)}; + + } + } +} + +sub determine_winner { + my $computer = shift; + my $player = shift; + print "THAT IS ALL OF THE MARBLES.\n\n"; + print "MY TOTAL IS $computer, YOUR TOTAL IS $player\n"; + if ($player % 2 == 0) { + print " YOU WON.\n"; + } + if ($computer % 2 == 0) { + print " I WON.\n"; + } + my $answer = -1; + until ($answer == 1 || $answer == 0) { + print "DO YOU WANT TO PLAY AGAIN? TYPE 1 FOR YES AND 0 FOR NO.\n"; + $answer=; + chomp($answer); + } + if ($answer == 1) { + &game_play; + } + else { + print "OK. SEE YOU LATER.\n"; + exit; + } +} + +sub player_select { + my $marbles = shift; + my $turn = shift; + my $validity="invalid"; + if ($turn == 0) { + print "WHAT IS YOUR FIRST MOVE\n"; + } + else { + print "WHAT IS YOUR NEXT MOVE\n"; + } + until ($validity eq "valid") { + my $num = ; + chomp($num); + my $validity=&validity_check($marbles,$num); + if ($validity eq "valid") { + return $num; + } + } +} + +sub computer_select { + my $marbles = shift; + my $turn = shift; + my $player_total = shift; + my $num = 2; + my $validity = "invalid"; + if ($turn == 0) { + print "I PICK UP $num MARBLES.\n\n"; + } + else { + until ($validity eq "valid") { + my $R=$marbles-6*int(($marbles/6)); + + if (int($player_total/2) == $player_total/2) { + if ($R < 1.5 || $R > 5.3) { + $num = 1; + } + else { + $num = $R - 1; + } + } + + elsif ($marbles < 4.2) { + $num = $marbles; + } + elsif ($R > 3.4) { + if ($R < 4.7 || $R > 3.5) { + $num = 4; + } + else { + $num = 1; + } + } + $validity=&validity_check($marbles,$num); + } + print "\nI PICK UP $num MARBLES.\n\n"; + } + return $num; +} + +sub validity_check { + my $marbles = shift; + my $num = shift; + if ($num > $marbles) { + print "YOU HAVE TRIED TO TAKE MORE MARBLES THAN THERE ARE LEFT. TRY AGAIN. THERE ARE $marbles MARBLES LEFT\n"; + return "invalid"; + } + if ($num > 0 && $num <= 4) { + return "valid"; + } + if ($num < 1 || $num > 4) { + print "THE NUMBER OF MARBLES YOU TAKE MUST BE A POSITIVE INTEGER BETWEEN 1 AND 4\nWHAT IS YOUR NEXT MOVE?\n"; + return "invalid"; + } + else { + return "invalid"; + } +} + +sub print_intro { + print ' ' x 31 . "EVEN WINS\n"; + print ' ' x 15 . "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; + print "THIS IS A 2 PERSON GAME CALLED 'EVEN WINS'. TO PLAY THE GAME, THE PLAYERS NEED 27 MARBLES OR OTHER OBJECTS ON THE TABLE\n\n"; + print "THE 2 PLAYERS ALTERNATE TURNS, WITH EACH PLAYER REMOVING FROM 1 TO 4 MARBLES ON EACH MOVE. THE GAME ENDS WHEN THERE ARE NO MARBLES LEFT, AND THE WINNER IS THE ONE WITH AN EVEN NUMBER OF MARBLES\n"; + print "THE ONLY RULES ARE THAT (1) YOU MUST ALTERNATE TURNS, (2) YOU MUST TAKE BETWEEN 1 AND 4 MARBLES EACH TURN, AND (3) YOU CANNOT SKIP A TURN.\n\n"; +} diff --git a/35_Even_Wins/python/evenwins.py b/35_Even_Wins/python/evenwins.py index fba20024..3fb9d4af 100644 --- a/35_Even_Wins/python/evenwins.py +++ b/35_Even_Wins/python/evenwins.py @@ -142,17 +142,32 @@ def game_over(): print('') def computer_turn(): - global marbles_in_middle - global computer_marbles + global marbles_in_middle + global computer_marbles + global human_marbles - print("It's the computer's turn ...") - max_choice = min(4, marbles_in_middle) + marbles_to_take=0 - # choose at random - n = random.randint(1, max_choice) - print(f'Computer takes {marbles_str(n)} ...') - marbles_in_middle -= n - computer_marbles += n + print("It's the computer's turn ...") + r = marbles_in_middle - 6 * int((marbles_in_middle/6)) #line 500 + + if int(human_marbles/2) == human_marbles/2: #line 510 + if r < 1.5 or r > 5.3: #lines 710 and 720 + marbles_to_take = 1 + else: + marbles_to_take = r - 1 + + elif marbles_in_middle < 4.2: #line 580 + marbles_to_take = marbles_in_middle + elif r > 3.4: #line 530 + if r < 4.7 or r > 3.5: + marbles_to_take = 4 + else: + marbles_to_take = r + 1 + + print(f'Computer takes {marbles_str(marbles_to_take)} ...') + marbles_in_middle -= marbles_to_take + computer_marbles += marbles_to_take def play_game(): global marbles_in_middle diff --git a/36_Flip_Flop/ruby/flipflop.rb b/36_Flip_Flop/ruby/flipflop.rb new file mode 100644 index 00000000..3a65a4c2 --- /dev/null +++ b/36_Flip_Flop/ruby/flipflop.rb @@ -0,0 +1,162 @@ +#A class representing the internal state of a single game of flip flop +# state represents the list of X's (A in the original code) +# guesses represents the number of guesses the user has made (C in the original code) +# seed represents the random seed for an instance of the game (Q in the original code) +Game = Struct.new(:state, :guesses, :seed) do + + #The original BASIC program used 1 indexed arrays while Ruby has 0-indexed arrays. + #We can't use 0 indexed arrays for the flip functions or we'll get divide by zero errors. + #These convenience functions allow us to modify and access internal game state in a 1-indexed fashion + def flip_letter(letter_number) + index = letter_number -1 + if self.state[index] == 'X' + self.state[index] = 'O' + else + self.state[index] = 'X' + end + end + + def letter_at(letter_number) + self.state[letter_number - 1] + end +end + +def print_welcome + puts 'FLIPFLOP'.center(72) + puts 'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY'.center(72) + puts <<~EOS + + THE OBJECT OF THIS PUZZLE IS TO CHANGE THIS: + + X X X X X X X X X X + + TO THIS: + + O O O O O O O O O O + + BY TYPING THE NUMBER CORRESPONDING TO THE POSITION OF THE + LETTER ON SOME NUMBERS, ONE POSITION WILL CHANGE, ON + OTHERS, TWO WILL CHANGE. TO RESET LINE TO ALL X'S, TYPE 0 + (ZERO) AND TO START OVER IN THE MIDDLE OF A GAME, TYPE + 11 (ELEVEN). + EOS +end + +def print_starting_message + puts <<~EOS + + HERE IS THE STARTING LINE OF X'S. + + 1 2 3 4 5 6 7 8 9 10 + X X X X X X X X X X + + EOS +end + +#Create a new game with [X,X,X,X,X,X,X,X,X,X] as the state +#0 as the number of guesses and a random seed between 0 and 1 +def generate_new_game + Game.new(Array.new(10, 'X'), 0, rand()) +end + +#Given a game, an index, and a shuffle function, flip one or more letters +def shuffle_board(game, index, shuffle_function) + n = method(shuffle_function).call(game, index) + + if game.letter_at(n) == "O" + game.flip_letter(n) + if index == n + n = shuffle_board(game, index, shuffle_function) + end + else + game.flip_letter(n) + end + return n +end + +#Shuffle logic copied from original BASIC code +def shuffle_function1(game, index) + r = Math.tan(game.seed + index / game.seed - index) - Math.sin(game.seed / index) + 336 * Math.sin(8 * index) + n = r - r.floor + (10 * n).floor +end + +def shuffle_function2(game, index) + r = 0.592 * (1/ Math.tan(game.seed / index + game.seed)) / Math.sin(index * 2 + game.seed) - Math.cos(index) + n = r - r.floor + (10 * n) +end + +def play_game + print_starting_message + game = generate_new_game + working_index = nil + + loop do + puts "INPUT THE NUMBER" + input = gets.chomp.downcase + + #See if the user input a valid integer, fail otherwise + if numeric_input = Integer(input, exception: false) + + #If 11 is entered, we're done with this version of the game + if numeric_input == 11 + return :restart + end + + if numeric_input > 11 + puts 'ILLEGAL ENTRY--TRY AGAIN.' + next #illegal entries don't count towards your guesses + end + + if working_index == numeric_input + game.flip_letter(numeric_input) + working_index = shuffle_board(game, numeric_input, :shuffle_function2) + #If 0 is entered, we want to reset the state, but not the random seed or number of guesses and keep playing + elsif numeric_input == 0 + game.state = Array.new(10, 'X') + elsif game.letter_at(numeric_input) == "O" + game.flip_letter(numeric_input) + if numeric_input == working_index + working_index = shuffle_board(game, numeric_input, :shuffle_function1) + end + else + game.flip_letter(numeric_input) + working_index = shuffle_board(game, numeric_input, :shuffle_function1) + end + else + puts 'ILLEGAL ENTRY--TRY AGAIN.' + next #illegal entries don't count towards your guesses + end + + game.guesses += 1 + puts '1 2 3 4 5 6 7 8 9 10' + puts game.state.join(' ') + + if game.state.all? { |x| x == 'O' } + if game.guesses > 12 + puts "TRY HARDER NEXT TIME. IT TOOK YOU #{game.guesses} GUESSES." + else + puts "VERY GOOD. YOU GUESSED IT IN ONLY #{game.guesses} GUESSES." + end + #game is complete + return + end + end +end + + + +#Execution starts +print_welcome +loop do + result = play_game + if result == :restart + next + end + + puts 'DO YOU WANT TO TRY ANOTHER PUZZLE' + if gets.chomp.downcase[0] == 'n' + break + end +end diff --git a/43_Hammurabi/README.md b/43_Hammurabi/README.md index c92b7ee3..c02219b1 100644 --- a/43_Hammurabi/README.md +++ b/43_Hammurabi/README.md @@ -21,6 +21,6 @@ As published in Basic Computer Games (1978): Downloaded from Vintage Basic at http://www.vintage-basic.net/games.html -[Port to C language](https://github.com/beyonddream/hamurabi) - -[Port to Rust language](https://github.com/beyonddream/hamurabi.rs) +#### External Links + - C: https://github.com/beyonddream/hamurabi + - Rust: https://github.com/beyonddream/hamurabi.rs diff --git a/44_Hangman/perl/hangman.pl b/44_Hangman/perl/hangman.pl new file mode 100755 index 00000000..45a6da9b --- /dev/null +++ b/44_Hangman/perl/hangman.pl @@ -0,0 +1,223 @@ +#!/usr/bin/perl + +use strict; +use warnings; + + +# global variables defined here + +my(@WORDS) = qw( + GUM SIN FOR CRY LUG BYE FLY + UGLY EACH FROM WORK TALK WITH SELF + PIZZA THING FEIGN FIEND ELBOW FAULT DIRTY + BUDGET SPIRIT QUAINT MAIDEN ESCORT PICKAX + EXAMPLE TENSION QUININE KIDNEY REPLICA SLEEPER + TRIANGLE KANGAROO MAHOGANY SERGEANT SEQUENCE + MOUSTACHE DANGEROUS SCIENTIST DIFFERENT QUIESCENT + MAGISTRATE ERRONEOUSLY LOUDSPEAKER PHYTOTOXIC + MATRIMONIAL PARASYMPATHOMIMETIC THIGMOTROPISM +); +my(@PIC,$board,@guessedLetters,$guessCount,$hangCount); +my(%GUESSED); + +# Subroutines defined here. + +# init_variables: initialize all of the variables needed +# (this covers lines 50-90 in the original BASIC program) + +sub init_variables { + @guessedLetters = (); + @PIC = ( + 'XXXXXXX ', + 'X X ', + 'X ', + 'X ', + 'X ', + 'X ', + 'X ', + 'X ', + 'X ', + 'X ', + 'X ', + 'X ', + ); + $guessCount = 0; %GUESSED = (); + $hangCount = 0; +} + +# addchar: given a row & column, put the specified char in that place in @PIC +sub addchar { + my($row,$col, $c) = @_; + + substr($PIC[$row],$col,1) = $c; +} + + +# main code starts here + +print ' 'x31; print "Hangman\n"; +print ' 'x14; print "Creative Computing Morristown, New Jersey\n\n\n\n"; + +# an iteration of the PLAY block is one complete game. +# There is a continue block that will ask if the user +# wants to play another game. + +PLAY: while (1) { + + init_variables(); + # Any words left? + if (@WORDS == 0) { + print "You did all the words!\n"; + last PLAY; + } + # splice a random word out of the @WORDS array + my($thisWord) = splice(@WORDS, int(rand(scalar @WORDS)),1); + # $board is the "game board" of the filled-out word + # that the user is working on + $board = '.'x(length $thisWord); + + # GUESS loop is run for every time the user guesses a letter + GUESS: while(1) { + print "Here are the letters you used:\n"; + printf("%s\n", join(',',@guessedLetters)); + printf("\n\n%s\n", $board); + + print "What is your guess for a letter ? "; + chomp(my $guess = ); + # The %GUESSED hash allows us to quickly identify + # letters that have already been guessed + if ($GUESSED{lc $guess}) { + print "You guessed that letter before!\n\n"; + redo GUESS; + } + + # save the guessed letter + push @guessedLetters, $guess; + $GUESSED{lc $guess} = 1; + ++$guessCount; + + # now look for the letter in the $thisWord var + # and put it into the $board var wherever it + # shows up. $foundLetter is a flag that indicates + # whether or not the letter is found. + my $foundLetter = 0; + for (my $i = 0; $i < length $thisWord; ++$i) { + if (lc substr($thisWord,$i,1) eq lc $guess) { + $foundLetter = 1; + substr($board, $i, 1) = substr($thisWord, $i, 1); + } + } + + # The user found a letter in the solution! + if ($foundLetter) { + + # Are there any '.' chars left in the board? + if (index($board, '.') < 0) { + print "You found the word!\n\n"; + } else { + printf("%s\n\n", $board); + print "What is your guess for the word ? "; + chomp(my $guessword = ); + if (lc $thisWord ne lc $guessword) { + print "Wrong. Try another letter.\n"; + # Go to the next iteration of the GUESS loop + next GUESS; + } + printf("Right! It took you %d %s!\n", $guessCount, ($guessCount == 1 ? 'guess' : 'guesses')); + } + # At this point the user has discovered the word and won. + # This "next" statement takes execution down to the + # continue block for the PLAY loop; + next PLAY; + + } else { # didn't find a letter + + ++$hangCount; + print "\n\n\nSorry, that letter isn't in the word.\n"; + + # The addchar() calls in the block below piece together the + # hangman graphic, depending on how many wrong letters + # the user has. + if ($hangCount == 1) { + print "First, we draw a head\n"; + addchar(2,5,"-");addchar(2,6,"-");addchar(2,7,"-"); + addchar(3,4,"("); addchar(3,5,"."); addchar(3,7,"."); addchar(3,8,")"); + addchar(4,5,"-");addchar(4,6,"-");addchar(4,7,"-"); + } + if ($hangCount == 2) { + print "Now we draw a body.\n"; + for (5 .. 8) { + addchar($_, 6, "X"); + } + } + if ($hangCount == 3) { + print "Next we draw an arm.\n"; + for (3 .. 6) { + addchar($_, $_-1, "\\"); + } + } + if ($hangCount == 4) { + print "This time it's the other arm.\n"; + addchar(3,10, "/"); + addchar(4, 9, "/"); + addchar(5, 8, "/"); + addchar(6, 7, "/"); + } + if ($hangCount == 5) { + print "Now, let's draw the right leg.\n"; + addchar( 9,5, "/"); + addchar(10,4, "/"); + } + if ($hangCount == 6) { + print "This time we draw the left leg.\n"; + addchar(9,7,"\\"); + addchar(10,8,"\\"); + } + if ($hangCount == 7) { + print "Now we put up a hand.\n"; + addchar(2,10,"\\"); + } + if ($hangCount == 8) { + print "Next the other hand.\n"; + addchar(2,2,"/"); + } + if ($hangCount == 9) { + print "Now we draw one foot\n"; + addchar(11,9,"\\"); + addchar(11,10, "-"); + } + if ($hangCount == 10) { + print "Here's the other foot -- you're hung!!\n"; + addchar(11,2,"-"); + addchar(11,3, "/"); + } + + printf("$_\n") for @PIC; + print "\n\n"; + + # Next guess if the user has not lost + if ($hangCount < 10) { + next GUESS; + } + + printf("Sorry, you lose. The word was %s\n", $thisWord); + next PLAY; + + } # didn't find a letter block + } # GUESS block +} # PLAY block + +# This block is reached either by the player winning (see the "next PLAY") +# statement) or by the user losing (as the PLAY block is complete and +# execution naturally comes to this continue block). +continue { + print "Want another word ? "; + chomp(my $in = ); + if ($in !~ m/^y/i) { + # Exit the PLAY loop + print "\nIt's been fun! Bye for now.\n\n"; + last PLAY; + } + # At this point execution goes to the start of the PLAY block, + # meaning a new game +} diff --git a/48_High_IQ/java/src/HighIQ.java b/48_High_IQ/java/src/HighIQ.java new file mode 100644 index 00000000..d77a2ec2 --- /dev/null +++ b/48_High_IQ/java/src/HighIQ.java @@ -0,0 +1,153 @@ +import java.io.PrintStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +/** + * Game of HighIQ + *

+ * Based on the Basic Game of HighIQ Here: + * https://github.com/coding-horror/basic-computer-games/blob/main/48_High_IQ/highiq.bas + * + * No additional functionality has been added + */ +public class HighIQ { + + //Game board, as a map of position numbers to their values + private final Map board; + + //Output stream + private final PrintStream out; + + //Input scanner to use + private final Scanner scanner; + + + public HighIQ(Scanner scanner) { + out = System.out; + this.scanner = scanner; + board = new HashMap<>(); + + //Set of all locations to put initial pegs on + int[] locations = new int[]{ + 13, 14, 15, 22, 23, 24, 29, 30, 31, 32, 33, 34, 35, 38, 39, 40, 42, 43, 44, 47, 48, 49, 50, 51, 52, 53, 58, 59, 60, 67, 68, 69 + }; + + for (int i : locations) { + board.put(i, true); + } + + board.put(41, false); + } + + /** + * Plays the actual game, from start to finish. + */ + public void play() { + do { + printBoard(); + while (!move()) { + out.println("ILLEGAL MOVE, TRY AGAIN..."); + } + } while (!isGameFinished()); + + int pegCount = 0; + for (Integer key : board.keySet()) { + if (board.getOrDefault(key, false)) { + pegCount++; + } + } + + out.println("YOU HAD " + pegCount + " PEGS REMAINING"); + + if (pegCount == 1) { + out.println("BRAVO! YOU MADE A PERFECT SCORE!"); + out.println("SAVE THIS PAPER AS A RECORD OF YOUR ACCOMPLISHMENT!"); + } + } + + /** + * Makes an individual move + * @return True if the move was valid, false if the user made an error and the move is invalid + */ + public boolean move() { + out.println("MOVE WHICH PIECE"); + int from = scanner.nextInt(); + + //using the getOrDefault, which will make the statement false if it is an invalid position + if (!board.getOrDefault(from, false)) { + return false; + } + + out.println("TO WHERE"); + int to = scanner.nextInt(); + + if (board.getOrDefault(to, true)) { + return false; + } + + //Do nothing if they are the same + if (from == to) { + return true; + } + + //using the difference to check if the relative locations are valid + int difference = Math.abs(to - from); + if (difference != 2 && difference != 18) { + return false; + } + + //check if there is a peg between from and to + if (!board.getOrDefault((to + from) / 2, false)) { + return false; + } + + //Actually move + board.put(from,false); + board.put(to,true); + board.put((from + to) / 2, false); + + return true; + } + + /** + * Checks if the game is finished + * @return True if there are no more moves, False otherwise + */ + public boolean isGameFinished() { + for (Integer key : board.keySet()) { + if (board.get(key)) { + //Spacing is either 1 or 9 + //Looking to the right and down from every point, checking for both directions of movement + for (int space : new int[]{1, 9}) { + Boolean nextToPeg = board.getOrDefault(key + space, false); + Boolean hasMovableSpace = !board.getOrDefault(key - space, true) || !board.getOrDefault(key + space * 2, true); + if (nextToPeg && hasMovableSpace) { + return false; + } + } + } + } + return true; + } + + public void printBoard() { + for (int i = 0; i < 7; i++) { + for (int j = 11; j < 18; j++) { + out.print(getChar(j + 9 * i)); + } + out.println(); + } + } + + private char getChar(int position) { + Boolean value = board.get(position); + if (value == null) { + return ' '; + } else if (value) { + return '!'; + } else { + return 'O'; + } + } +} diff --git a/48_High_IQ/java/src/HighIQGame.java b/48_High_IQ/java/src/HighIQGame.java new file mode 100644 index 00000000..43400fb3 --- /dev/null +++ b/48_High_IQ/java/src/HighIQGame.java @@ -0,0 +1,37 @@ +import java.util.Scanner; + +public class HighIQGame { + public static void main(String[] args) { + + printInstructions(); + + Scanner scanner = new Scanner(System.in); + do { + new HighIQ(scanner).play(); + System.out.println("PLAY AGAIN (YES OR NO)"); + } while(scanner.nextLine().equalsIgnoreCase("yes")); + } + + public static void printInstructions() { + System.out.println("\t\t\t H-I-Q"); + System.out.println("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); + System.out.println("HERE IS THE BOARD:"); + System.out.println(" ! ! !"); + System.out.println(" 13 14 15\n"); + System.out.println(" ! ! !"); + System.out.println(" 22 23 24\n"); + System.out.println("! ! ! ! ! ! !"); + System.out.println("29 30 31 32 33 34 35\n"); + System.out.println("! ! ! ! ! ! !"); + System.out.println("38 39 40 41 42 43 44\n"); + System.out.println("! ! ! ! ! ! !"); + System.out.println("47 48 49 50 51 52 53\n"); + System.out.println(" ! ! !"); + System.out.println(" 58 59 60\n"); + System.out.println(" ! ! !"); + System.out.println(" 67 68 69"); + System.out.println("TO SAVE TYPING TIME, A COMPRESSED VERSION OF THE GAME BOARD"); + System.out.println("WILL BE USED DURING PLAY. REFER TO THE ABOVE ONE FOR PEG"); + System.out.println("NUMBERS. OK, LET'S BEGIN."); + } +} diff --git a/54_Letter/perl/letter.pl b/54_Letter/perl/letter.pl index 54fb5fe0..56acf533 100755 --- a/54_Letter/perl/letter.pl +++ b/54_Letter/perl/letter.pl @@ -5,40 +5,37 @@ print ' 'x33 . "LETTER\n"; print ' 'x15 . "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n"; print "\n\n\n"; -print "LETTER GUESSING GAME\n"; print "\n"; +print "LETTER GUESSING GAME\n\n"; print "I'LL THINK OF A LETTER OF THE ALPHABET, A TO Z.\n"; print "TRY TO GUESS MY LETTER AND I'LL GIVE YOU CLUES\n"; print "AS TO HOW CLOSE YOU'RE GETTING TO MY LETTER.\n"; -my $A; while (1) { - my $L= 65+int(rand(1)*26); - my $G= 0; - print "\n"; print "O.K., I HAVE A LETTER. START GUESSING.\n"; + my $letter = 65 + int(rand(26)); + my $guesses = 0; + print "\nO.K., I HAVE A LETTER. START GUESSING.\n"; + my $answer; do { - print "\n"; print "WHAT IS YOUR GUESS? "; - $G=$G+1; - chomp($A= ); - $A= ord($A); + print "\nWHAT IS YOUR GUESS? "; + $guesses++; + chomp($answer = ); + $answer = ord($answer); print "\n"; - if ($A<$L) { print "TOO LOW. TRY A HIGHER LETTER.\n"; } - if ($A>$L) { print "TOO HIGH. TRY A LOWER LETTER.\n"; } - } until($A eq $L); + print "TOO LOW. TRY A HIGHER LETTER.\n" if $answer < $letter; + print "TOO HIGH. TRY A LOWER LETTER.\n" if $answer > $letter; + } until($answer eq $letter); - print "\n"; print "YOU GOT IT IN $G GUESSES!!\n"; + print "\nYOU GOT IT IN $guesses GUESSES!!\n"; - if ($G<=5) { + if ($guesses <= 5) { print "GOOD JOB !!!!!\n"; - for (my $N=1; $N<=15; $N++) { print chr(7); } #ASCII Bell. - } else { + print chr(7) x 15; # ASCII Bell + } else { print "BUT IT SHOULDN'T TAKE MORE THAN 5 GUESSES!\n"; - } - - print "\n"; - print "LET'S PLAN AGAIN.....\n"; } + print "\nLET'S PLAY AGAIN.....\n"; +} + exit; - - diff --git a/62_Mugwump/perl/mugwump.pl b/62_Mugwump/perl/mugwump.pl new file mode 100755 index 00000000..d02a9036 --- /dev/null +++ b/62_Mugwump/perl/mugwump.pl @@ -0,0 +1,96 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +# global variables defined here +my(@MUGWUMP) = (); + +# subroutines defined here + +# init_mugwump: pick the random places for the Mugwumps +sub init_mugwump() { + @MUGWUMP = (); + for (1 .. 4) { + push @MUGWUMP, [ int(rand 10), int(rand 10) ]; + } +} + + +# main code starts here + +# print introductory text +print <); + my($M,$N) = split(/,/,$in); + $M = int($M); + $N = int($N); + + for my $i (0 .. $#MUGWUMP) { + # -1 indicates a Mugwump that was already found + next if $MUGWUMP[$i]->[0] == -1; + + if ($MUGWUMP[$i]->[0] == $M && $MUGWUMP[$i]->[1] == $N) { + $MUGWUMP[$i]->[0] = -1; + printf("You have found Mugwump %d\n", $i+1); + } else { + my $d = sqrt(($MUGWUMP[$i]->[0] - $M) ** 2 + ($MUGWUMP[$i]->[1] - $N) ** 2); + printf("You are %.1f units away from Mugwump %d\n", $d, $i+1); + } + } + + # If a Mugwump still has not been found, + # go to the next turn + for my $j (0 .. $#MUGWUMP) { + if ($MUGWUMP[$j]->[0] != -1) { + next TURN; + } + } + # You win! + printf("You got all of them in %d %s!\n\n", $turn, ($turn == 1 ? 'turn' : 'turns')); + # Pass execution down to the continue block + next PLAY; + + } # end of TURN loop + + print "\nSorry, that's 10 tries. Here's where they're hiding:\n"; + for my $i (0 .. $#MUGWUMP) { + printf("Mugwump %d is at (%d, %d)\n", $i+1, $MUGWUMP[$i]->[0], $MUGWUMP[$i]->[1]) + if $MUGWUMP[$i]->[0] != -1; + } +} +continue { + print "\nThat was fun! Let's play again.......\n"; + print "Four more Mugwumps are now in hiding.\n\n"; +} + diff --git a/65_Nim/python/Traditional_NIM.py b/65_Nim/python/Traditional_NIM.py new file mode 100644 index 00000000..3507ecc2 --- /dev/null +++ b/65_Nim/python/Traditional_NIM.py @@ -0,0 +1,126 @@ +import random + +#Class of the Game +class NIM: + + def __init__(self): + + self.Piles = { + 1 : 7, + 2 : 5, + 3 : 3, + 4 : 1 + } + + def Remove_pegs(self, command): + + try: + + pile, num = command.split(',') + num = int(num) + pile = int(pile) + + except Exception as e: + + if 'not enough values' in str(e): + print('\nNot a valid command. Your command should be in the form of "1,3", Try Again\n') + + else: + print('\nError, Try again\n') + return None + + if self._command_integrity(num, pile) == True: + self.Piles[pile] -= num + else: + print('\nInvalid value of either Peg or Pile\n') + + def get_AI_move(self): + + possible_pile = [] + for k,v in self.Piles.items(): + if v != 0: + possible_pile.append(k) + + pile = random.choice(possible_pile) + + num = random.randint(1,self.Piles[pile]) + + return pile, num + + def _command_integrity(self, num, pile): + + if pile <= 4 and pile >= 1: + if num <= self.Piles[pile]: + return True + + return False + + def print_pegs(self): + + for pile, peg in self.Piles.items(): + print('Pile {} : {}'.format(pile, 'O '*peg)) + + def Help(self): + + print('-'*10) + print('\nThe Game is player with a number of Piles of Objects("O" == one peg)') + print('\nThe Piles are arranged as given below(Tradional NIM)\n') + self.print_pegs() + print('\nAny Number of of Objects are removed one pile by "YOU" and the machine alternatively') + print('\nOn your turn, you may take all the objects that remain in any pile') + print('but you must take ATLEAST one object') + print('\nAnd you may take objects from only one pile on a single turn.') + print('\nThe winner is defined as the one that picks the last remaning object') + print('-'*10) + + def Checkforwin(self): + + sum = 0 + for k,v in self.Piles.items(): + sum += v + + if sum == 0: + return True + + else: + return False + +#main program +if __name__ == "__main__": + + #Game initialization + game = NIM() + + print('Hello, This is a game of NIM') + help = input('Do You Need Instruction (YES or NO): ') + + if help == 'yes' or help == 'YES' or help == 'Yes': + + #Printing Game Instruction + game.Help() + + #Start game loop + input('\nPress Enter to start the Game:\n') + End = False + while True: + + game.print_pegs() + + #Players Move + command = input('\nYOUR MOVE - Number of PILE, Number of Object? ') + game.Remove_pegs(command) + End = game.Checkforwin() + if End == True: + print('\nPlayer Wins the Game, Congratulations!!') + input('\nPress any key to exit') + break + + #Computers Move + command = game.get_AI_move() + print('\nA.I MOVE - A.I Removed {} pegs from Pile {}'.format(command[1],command[0])) + game.Remove_pegs(str(command[0]) +',' + str(command[1])) + End = game.Checkforwin() + if End == True: + print('\nComputer Wins the Game, Better Luck Next Time\n') + input('Press any key to exit') + break diff --git a/81_Splat/java/README.md b/81_Splat/java/README.md index 51edd8d4..b8998fa6 100644 --- a/81_Splat/java/README.md +++ b/81_Splat/java/README.md @@ -1,3 +1,3 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) -Conversion to [Oracle Java](https://openjdk.java.net/) +Conversion to [Java](https://openjdk.java.net/) diff --git a/81_Splat/java/src/Splat.java b/81_Splat/java/src/Splat.java new file mode 100644 index 00000000..e042c326 --- /dev/null +++ b/81_Splat/java/src/Splat.java @@ -0,0 +1,357 @@ +import java.util.*; + +/** + * SPLAT simulates a parachute jump in which you try to open your parachute at the last possible moment without going + * splat! You may select your own terminal velocity or let the computer do it for you. You many also select the + * acceleration due to gravity or, again, let the computer do it in which case you might wind up on any of eight + * planets (out to Neptune), the moon, or the sun. + *

+ * The computer then tells you the height you’re jumping from and asks for the seconds of free fall. It then divides + * your free fall time into eight intervals and gives you progress reports on your way down. The computer also keeps + * track of all prior jumps in the array A and lets you know how you compared with previous successful jumps. If you + * want to recall information from previous runs, then you should store array A in a disk or take file and read it + * before each run. + *

+ * John Yegge created this program while at the Oak Ridge Associated Universities. + *

+ * Ported from BASIC by jason plumb (@breedx2) + *

+ */ +public class Splat { + private static final Random random = new Random(); + private final Scanner scanner = new Scanner(System.in); + private final List pastSuccessfulJumpDistances = new ArrayList<>(); + + public static void main(String[] args) { + new Splat().run(); + } + + public void run() { + showIntroduction(); + + while (true) { + + InitialJumpConditions initial = buildInitialConditions(); + + System.out.println(); + System.out.printf(" ALTITUDE = %d FT\n", initial.getAltitude()); + System.out.printf(" TERM. VELOCITY = %.2f FT/SEC +/-5%%\n", initial.getOriginalTerminalVelocity()); + System.out.printf(" ACCELERATION = %.2f FT/SEC/SEC +/-5%%\n", initial.getOriginalAcceleration()); + + System.out.println("SET THE TIMER FOR YOUR FREEFALL."); + float freefallTime = promptFloat("HOW MANY SECONDS "); + System.out.println("HERE WE GO.\n"); + System.out.println("TIME (SEC) DIST TO FALL (FT)"); + System.out.println("========== ================="); + + JumpResult jump = executeJump(initial, freefallTime); + showJumpResults(initial, jump); + + if (!playAgain()) { + System.out.println("SSSSSSSSSS."); + return; + } + } + } + + private void showIntroduction() { + System.out.printf("%33s%s\n", " ", "SPLAT"); + System.out.printf("%15s%s\n", " ", "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); + System.out.print("\n\n\n"); + System.out.println("WELCOME TO 'SPLAT' -- THE GAME THAT SIMULATES A PARACHUTE"); + System.out.println("JUMP. TRY TO OPEN YOUR CHUTE AT THE LAST POSSIBLE"); + System.out.println("MOMENT WITHOUT GOING SPLAT."); + } + + private InitialJumpConditions buildInitialConditions() { + System.out.print("\n\n"); + float terminalVelocity = promptTerminalVelocity(); + float acceleration = promptGravitationalAcceleration(); + return InitialJumpConditions.create(terminalVelocity, acceleration); + } + + private float promptTerminalVelocity() { + if (askYesNo("SELECT YOUR OWN TERMINAL VELOCITY")) { + float terminalVelocity = promptFloat("WHAT TERMINAL VELOCITY (MI/HR) "); + return mphToFeetPerSec(terminalVelocity); + } + float terminalVelocity = (int) (1000 * random.nextFloat()); + System.out.printf("OK. TERMINAL VELOCITY = %.2f MI/HR\n", terminalVelocity); + return mphToFeetPerSec(terminalVelocity); + } + + private float promptFloat(String prompt){ + while(true){ + System.out.print(prompt); + try { + return scanner.nextFloat(); + } catch (Exception e) { + scanner.next(); // clear current input + } + } + } + + private float promptGravitationalAcceleration() { + if (askYesNo("WANT TO SELECT ACCELERATION DUE TO GRAVITY")) { + return promptFloat("WHAT ACCELERATION (FT/SEC/SEC) "); + } + return chooseRandomAcceleration(); + } + + private JumpResult executeJump(InitialJumpConditions initial, float chuteOpenTime) { + JumpResult jump = new JumpResult(initial.getAltitude()); + for (float time = 0.0f; time < chuteOpenTime; time += chuteOpenTime / 8) { + if (!jump.hasReachedTerminalVelocity() && time > initial.getTimeOfTerminalAccelerationReached()) { + jump.setReachedTerminalVelocity(); + System.out.printf("TERMINAL VELOCITY REACHED AT T PLUS %f SECONDS.\n", initial.getTimeOfTerminalAccelerationReached()); + } + float newDistance = computeDistance(initial, time, jump.hasReachedTerminalVelocity()); + jump.setDistance(newDistance); + + if (jump.isSplat()) { + return jump; + } + System.out.printf("%10.2f %f\n", time, jump.getDistance()); + } + return jump; + } + + private float computeDistance(InitialJumpConditions initial, float i, boolean hasReachedTerminalVelocity) { + final float V = initial.getTerminalVelocity(); + final float A = initial.getAcceleration(); + if (hasReachedTerminalVelocity) { + return initial.getAltitude() - ((V * V / (2 * A)) + (V * (i - (V / A)))); + } + return initial.getAltitude() - ((A / 2) * i * i); + } + + private void showJumpResults(InitialJumpConditions initial, JumpResult jump) { + if (jump.isSplat()) { + showSplatMessage(initial, jump); + showCleverSplatMessage(); + return; + } + System.out.println("CHUTE OPEN"); + int worseJumpCount = countWorseHistoricalJumps(jump); + int successfulJumpCt = pastSuccessfulJumpDistances.size(); + pastSuccessfulJumpDistances.add(jump.getDistance()); + + if (pastSuccessfulJumpDistances.size() <= 2) { + List ordinals = Arrays.asList("1ST", "2ND", "3RD"); + System.out.printf("AMAZING!!! NOT BAD FOR YOUR %s SUCCESSFUL JUMP!!!\n", ordinals.get(successfulJumpCt)); + return; + } + + int betterThanCount = successfulJumpCt - worseJumpCount; + if (betterThanCount <= 0.1 * successfulJumpCt) { + System.out.printf("WOW! THAT'S SOME JUMPING. OF THE %d SUCCESSFUL JUMPS\n", successfulJumpCt); + System.out.printf("BEFORE YOURS, ONLY %d OPENED THEIR CHUTES LOWER THAN\n", betterThanCount); + System.out.println("YOU DID."); + } else if (betterThanCount <= 0.25 * successfulJumpCt) { + System.out.printf("PRETTY GOOD! %d SUCCESSFUL JUMPS PRECEDED YOURS AND ONLY\n", successfulJumpCt); + System.out.printf("%d OF THEM GOT LOWER THAN YOU DID BEFORE THEIR CHUTES\n", betterThanCount); + System.out.println("OPENED."); + } else if (betterThanCount <= 0.5 * successfulJumpCt) { + System.out.printf("NOT BAD. THERE HAVE BEEN %d SUCCESSFUL JUMPS BEFORE YOURS.\n", successfulJumpCt); + System.out.printf("YOU WERE BEATEN OUT BY %d OF THEM.\n", betterThanCount); + } else if (betterThanCount <= 0.75 * successfulJumpCt) { + System.out.printf("CONSERVATIVE, AREN'T YOU? YOU RANKED ONLY %d IN THE\n", betterThanCount); + System.out.printf("%d SUCCESSFUL JUMPS BEFORE YOURS.\n", successfulJumpCt); + } else if (betterThanCount <= -0.9 * successfulJumpCt) { + System.out.println("HUMPH! DON'T YOU HAVE ANY SPORTING BLOOD? THERE WERE"); + System.out.printf("%d SUCCESSFUL JUMPS BEFORE YOURS AND YOU CAME IN %d JUMPS\n", successfulJumpCt, worseJumpCount); + System.out.println("BETTER THAN THE WORST. SHAPE UP!!!\n"); + } else { + System.out.printf("HEY! YOU PULLED THE RIP CORD MUCH TOO SOON. %d SUCCESSFUL\n", successfulJumpCt); + System.out.printf("JUMPS BEFORE YOURS AND YOU CAME IN NUMBER %d! GET WITH IT!\n", betterThanCount); + } + } + + private void showSplatMessage(InitialJumpConditions initial, JumpResult jump) { + double timeOfSplat = computeTimeOfSplat(initial, jump); + System.out.printf("%10.2f SPLAT\n", timeOfSplat); + } + + /** + * Returns the number of jumps for which this jump was better + */ + private double computeTimeOfSplat(InitialJumpConditions initial, JumpResult jump) { + final float V = initial.getTerminalVelocity(); + final float A = initial.getAcceleration(); + if (jump.hasReachedTerminalVelocity()) { + return (V / A) + ((initial.getAltitude() - (V * V / (2 * A))) / V); + } + return Math.sqrt(2 * initial.getAltitude() / A); + } + + private int countWorseHistoricalJumps(JumpResult jump) { + return (int) pastSuccessfulJumpDistances.stream() + .filter(distance -> jump.getDistance() < distance) + .count(); + } + + private void showCleverSplatMessage() { + List messages = Arrays.asList( + "REQUIESCAT IN PACE.", + "MAY THE ANGEL OF HEAVEN LEAD YOU INTO PARADISE.", + "REST IN PEACE.", + "SON-OF-A-GUN.", + "#$%&&%!$", + "A KICK IN THE PANTS IS A BOOST IF YOU'RE HEADED RIGHT.", + "HMMM. SHOULD HAVE PICKED A SHORTER TIME.", + "MUTTER. MUTTER. MUTTER.", + "PUSHING UP DAISIES.", + "EASY COME, EASY GO." + ); + System.out.println(messages.get(random.nextInt(10))); + } + + private boolean playAgain() { + if (askYesNo("DO YOU WANT TO PLAY AGAIN ")) { + return true; + } + return askYesNo("PLEASE"); + } + + private float mphToFeetPerSec(float speed) { + return speed * (5280.0f / 3600.0f); + } + + private boolean askYesNo(String prompt) { + System.out.printf("%s (YES OR NO) ", prompt); + while (true) { + String answer = scanner.next(); + switch (answer) { + case "YES": + return true; + case "NO": + return false; + default: + System.out.print("YES OR NO "); + } + } + } + + private float chooseRandomAcceleration() { + Planet planet = Planet.pickRandom(); + System.out.printf("%s %s. ACCELERATION=%.2f FT/SEC/SEC.\n", planet.getMessage(), planet.name(), planet.getAcceleration()); + return planet.getAcceleration(); + } + + enum Planet { + MERCURY("FINE. YOU'RE ON", 12.2f), + VENUS("ALL RIGHT. YOU'RE ON", 28.3f), + EARTH("THEN YOU'RE ON", 32.16f), + MOON("FINE. YOU'RE ON THE", 5.15f), + MARS("ALL RIGHT. YOU'RE ON", 12.5f), + JUPITER("THEN YOU'RE ON", 85.2f), + SATURN("FINE. YOU'RE ON", 37.6f), + URANUS("ALL RIGHT. YOU'RE ON", 33.8f), + NEPTUNE("THEN YOU'RE ON", 39.6f), + SUN("FINE. YOU'RE ON THE", 896.0f); + + private static final Random random = new Random(); + private final String message; + private final float acceleration; + + Planet(String message, float acceleration) { + this.message = message; + this.acceleration = acceleration; + } + + static Planet pickRandom() { + return values()[random.nextInt(Planet.values().length)]; + } + + String getMessage() { + return message; + } + + float getAcceleration() { + return acceleration; + } + } + + // Mutable + static class JumpResult { + private boolean reachedTerminalVelocity = false; + private float distance; // from the ground + + public JumpResult(float distance) { + this.distance = distance; + } + + boolean isSplat() { + return distance <= 0; + } + + boolean hasReachedTerminalVelocity() { + return reachedTerminalVelocity; + } + + float getDistance() { + return distance; + } + + void setDistance(float distance) { + this.distance = distance; + } + + void setReachedTerminalVelocity() { + reachedTerminalVelocity = true; + } + } + + // Immutable + static class InitialJumpConditions { + private final float originalTerminalVelocity; + private final float originalAcceleration; + private final float terminalVelocity; + private final float acceleration; + private final int altitude; + + private InitialJumpConditions(float originalTerminalVelocity, float originalAcceleration, + float terminalVelocity, float acceleration, int altitude) { + this.originalTerminalVelocity = originalTerminalVelocity; + this.originalAcceleration = originalAcceleration; + this.terminalVelocity = terminalVelocity; + this.acceleration = acceleration; + this.altitude = altitude; + } + + // Create initial jump conditions with adjusted velocity/acceleration and a random initial altitude + private static InitialJumpConditions create(float terminalVelocity, float gravitationalAcceleration) { + final int altitude = (int) (9001.0f * random.nextFloat() + 1000); + return new InitialJumpConditions(terminalVelocity, gravitationalAcceleration, + plusMinus5Percent(terminalVelocity), plusMinus5Percent(gravitationalAcceleration), altitude); + } + + private static float plusMinus5Percent(float value) { + return value + ((value * random.nextFloat()) / 20.0f) - ((value * random.nextFloat()) / 20.0f); + } + + float getOriginalTerminalVelocity() { + return originalTerminalVelocity; + } + + float getOriginalAcceleration() { + return originalAcceleration; + } + + float getTerminalVelocity() { + return terminalVelocity; + } + + float getAcceleration() { + return acceleration; + } + + int getAltitude() { + return altitude; + } + + float getTimeOfTerminalAccelerationReached() { + return terminalVelocity / acceleration; + } + } +} \ No newline at end of file diff --git a/84_Super_Star_Trek/README.md b/84_Super_Star_Trek/README.md index 4a96f528..77fae3d8 100644 --- a/84_Super_Star_Trek/README.md +++ b/84_Super_Star_Trek/README.md @@ -101,4 +101,4 @@ Instructions in this directory at instructions.txt #### External Links - - Super Star Trek in C++ : https://www.codeproject.com/Articles/28399/The-Object-Oriented-Text-Star-Trek-Game-in-C + - C++: https://www.codeproject.com/Articles/28399/The-Object-Oriented-Text-Star-Trek-Game-in-C diff --git a/85_Synonym/kotlin/README.md b/85_Synonym/kotlin/README.md new file mode 100644 index 00000000..f43a5b70 --- /dev/null +++ b/85_Synonym/kotlin/README.md @@ -0,0 +1,3 @@ +Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) + +Conversion to [Kotlin](https://kotlinlang.org/) diff --git a/85_Synonym/kotlin/Synonym.kt b/85_Synonym/kotlin/Synonym.kt new file mode 100644 index 00000000..386a2525 --- /dev/null +++ b/85_Synonym/kotlin/Synonym.kt @@ -0,0 +1,73 @@ +/** + * Game of Synonym + * + * + * Based on the Basic game of Synonym here + * https://github.com/coding-horror/basic-computer-games/blob/main/85%20Synonym/synonym.bas + * + * + * Note: The idea was to create a version of the 1970's Basic game in Java, without introducing + * new features - no additional text, error checking, etc has been added. + */ + +fun main() { + println(introText) + synonyms.forEach { + it.testUser() + } + println("SYNONYM DRILL COMPLETED.") +} +// We could put this inside of SynonymList, but this keeps the core implementation +// right here at the top +private fun SynonymList.testUser() { + do { + val answer = ask(" WHAT IS A SYNONYM OF $word ? ") + when { + answer == "HELP" -> + println("""**** A SYNONYM OF $word IS ${synonyms.random()}.""") + synonyms.contains(answer) -> + println(AFFIRMATIONS.random()) + else -> + println("TRY AGAIN.") + } + } while (!synonyms.contains(answer)) +} + +val introText = """ +${tab(33)}SYNONYM +${tab(15)}CREATIVE COMPUTING MORRISTOWN, NEW JERSEY +A SYNONYM OF A WORD MEANS ANOTHER WORD IN THE ENGLISH +LANGUAGE WHICH HAS THE SAME OR VERY NEARLY THE SAME + MEANING. +I CHOOSE A WORD -- YOU TYPE A SYNONYM. +IF YOU CAN'T THINK OF A SYNONYM, TYPE THE WORD 'HELP' +AND I WILL TELL YOU A SYNONYM. + + """ + +// prints a question and reads a string (and converts to uppercase) +private fun ask(text: String): String { + print(text) + return readln().uppercase() +} + +// Just like TAB in BASIC +private fun tab(spaces: Int): String = " ".repeat(spaces) + +val AFFIRMATIONS = arrayOf("RIGHT", "CORRECT", "FINE", "GOOD!", "CHECK") + +// List of words and synonyms +private val synonyms = listOf( + SynonymList("FIRST", listOf("START", "BEGINNING", "ONSET", "INITIAL")), + SynonymList("SIMILAR", listOf("SAME", "LIKE", "RESEMBLING")), + SynonymList("MODEL", listOf("PATTERN", "PROTOTYPE", "STANDARD", "CRITERION")), + SynonymList("SMALL", listOf("INSIGNIFICANT", "LITTLE", "TINY", "MINUTE")), + SynonymList("STOP", listOf("HALT", "STAY", "ARREST", "CHECK", "STANDSTILL")), + SynonymList("HOUSE", listOf("DWELLING", "RESIDENCE", "DOMICILE", "LODGING", "HABITATION")), + SynonymList("PIT", listOf("HOLE", "HOLLOW", "WELL", "GULF", "CHASM", "ABYSS")), + SynonymList("PUSH", listOf("SHOVE", "THRUST", "PROD", "POKE", "BUTT", "PRESS")), + SynonymList("RED", listOf("ROUGE", "SCARLET", "CRIMSON", "FLAME", "RUBY")), + SynonymList("PAIN", listOf("SUFFERING", "HURT", "MISERY", "DISTRESS", "ACHE", "DISCOMFORT")) +) + +class SynonymList(val word: String, val synonyms: List) \ No newline at end of file diff --git a/85_Synonym/ruby/synonim.rb b/85_Synonym/ruby/synonim.rb new file mode 100644 index 00000000..92d2e8c7 --- /dev/null +++ b/85_Synonym/ruby/synonim.rb @@ -0,0 +1,94 @@ +######################################################## +# +# Synonym +# +# From Basic Computer Games (1978) +# +# A synonym of a word is another word (in the English language) which has the same, +# or very nearly the same, meaning. This program tests your knowledge of synonyms +# of a few common words. +# +# The computer chooses a word and asks you for a synonym. The computer then tells +# you whether you’re right or wrong. If you can’t think of a synonym, type “HELP” +# which causes a synonym to be printed. +# You may put in words of your choice in the data statements. +# The number following DATA in Statement 500 is the total number of data statements. +# In each data statement, the first number is the number of words in that statement. +# +# Can you think of a way to make this into a more general kind of CAI program for any subject? +# Walt Koetke of Lexington High School, Massachusetts created this program. +# +# +######################################################## + +puts <<~INSTRUCTIONS + SYNONYM + CREATIVE COMPUTING MORRISTOWN, NEW JERSEY + +A SYNONYM OF A WORD MEANS ANOTHER WORD IN THE ENGLISH +LANGUAGE WHICH HAS THE SAME OR VERY NEARLY THE SAME MEANING. +I CHOOSE A WORD -- YOU TYPE A SYNONYM. +IF YOU CAN'T THINK OF A SYNONYM, TYPE THE WORD 'HELP' +AND I WILL TELL YOU A SYNONYM. + + +INSTRUCTIONS + +right_words = ["RIGHT", "CORRECT", "FINE", "GOOD!", "CHECK"] + +synonym_words = [ + ["FIRST", "START", "BEGINNING", "ONSET", "INITIAL"], + ["SIMILAR", "ALIKE", "SAME", "LIKE", "RESEMBLING"], + ["MODEL", "PATTERN", "PROTOTYPE", "STANDARD", "CRITERION"], + ["SMALL", "INSIGNIFICANT", "LITTLE", "TINY", "MINUTE"], + ["STOP", "HALT", "STAY", "ARREST", "CHECK", "STANDSTILL"], + ["HOUSE", "DWELLING", "RESIDENCE", "DOMICILE", "LODGING", "HABITATION"], + ["PIT", "HOLE", "HOLLOW", "WELL", "GULF", "CHASM", "ABYSS"], + ["PUSH", "SHOVE", "THRUST", "PROD", "POKE", "BUTT", "PRESS"], + ["RED", "ROUGE", "SCARLET", "CRIMSON", "FLAME", "RUBY"], + ["PAIN", "SUFFERING", "HURT", "MISERY", "DISTRESS", "ACHE", "DISCOMFORT"], +] + +synonym_words.shuffle.each {|words_ar| + +} + + +synonym_words.each {|words_ar| + answer = false + keyword = words_ar.shift + + while not answer and words_ar.length != 0 + puts " WHAT IS A SYNONYM OF #{keyword}? " + inp = gets.chomp.upcase + + if inp == "HELP" + clue = words_ar.sample + puts "**** A SYNONYM OF #{keyword} IS #{clue}." + words_ar.delete(clue) + elsif words_ar.include? inp + puts right_words.sample + answer = true + else + puts "TRY AGAIN." + end + + end + +} + +puts "SYNONYM DRILL COMPLETED" + + +###################################################################### +# +# Porting notes +# +# There is a bug in the original program where if you keep asking for +# synoyms of a given word it ends up running out of synonyms +# in the array and the program crashes. +# The bug has been fixed in this version and now when +# it runs out of words it continues with the next +# array. +# +###################################################################### diff --git a/87_3-D_Plot/d/.gitignore b/87_3-D_Plot/d/.gitignore new file mode 100644 index 00000000..d969f6b2 --- /dev/null +++ b/87_3-D_Plot/d/.gitignore @@ -0,0 +1,2 @@ +*.exe +*.obj diff --git a/87_3-D_Plot/d/README.md b/87_3-D_Plot/d/README.md new file mode 100644 index 00000000..8a01faf3 --- /dev/null +++ b/87_3-D_Plot/d/README.md @@ -0,0 +1,182 @@ +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 threedeeplot.d +``` + +[Other compilers](https://dlang.org/download.html) also exist. + +## On rounding floating point values to integer values + +The D equivalent of Basic `INT` is [`floor`](https://dlang.org/phobos/std_math_rounding.html#.floor), +which rounds towards negative infinity. If you change occurrences of `floor` to +[`lrint`](https://dlang.org/phobos/std_math_rounding.html#.lrint), you'll see that the plots show a bit more detail, +as is done in the bonus below. + +## Bonus: Self-writing programs + +With a small modification to the source, the program can be extended to **plot a random function**, and **print its formula**. + +```shell +rdmd -dip1000 threedeeplot_random.d +``` +(`rdmd` caches the executable, which results in speedy execution when the source does not change.) + +### Example output +``` + 3D Plot + (After Creative Computing Morristown, New Jersey) + + + f(z) = 30 * sin(z / 10.0) + + * + * * * * + * * * * * + * * * * * + * * * * * * + * * * * * * + * * * * * ** + * * * * * * * + * * * * * * ** + * * * * * * * * + * * * * * * * * + * * * * * * * ** + * * * * * * * * * + * * * * ** * * * * + * * * * ** * * * + * * * * * * * * + * * * * * * * * + * * * ** * * ** + * * * ** * ** + * * * * * ** + * * * * * ** + * * * * * ** + * * * ** * ** + * * * ** * * ** + * * * * * * * * + * * * * * * * * + * * * * ** * * * + * * * * ** * * * * + * * * * * * * * * + * * * * * * * ** + * * * * * * * * + * * * * * * * * + * * * * * * ** + * * * * * * * + * * * * * ** + * * * * * * + * * * * * * + * * * * * + * * * * * + * * * * + * +``` + +### Breakdown of differences + +Have a look at the relevant differences between `threedeeplot.d` and `threedeeplot_random.d`. +This is the original function with the single expression that is evaluated for the plot: +```d + static float fna(float z) + { + return 30.0 * exp(-z * z / 100.0); + } +``` +Here `static` means that the nested function does not need acces to its enclosing scope. + +Now, by inserting the following: +```d + enum functions = ["30.0 * exp(-z * z / 100.0)", + "sqrt(900.01 - z * z) * .9 - 2", + "30 * (cos(z / 16.0) + .5)", + "30 - 30 * sin(z / 18.0)", + "30 * exp(-cos(z / 16.0)) - 30", + "30 * sin(z / 10.0)"]; + + size_t index = uniform(0, functions.length); + writeln(center("f(z) = " ~ functions[index], width), "\n"); +``` +and changing the implementation of `fna` to +```d + float fna(float z) + { + final switch (index) + { + static foreach (i, f; functions) + case i: + mixin("return " ~ f ~ ";"); + } + } +``` +we unlock some very special abilities of D. Let's break it down: + +```d + enum functions = ["30.0 * exp(-z * z / 100.0)", /*...*/]; +``` +This defines an array of strings, each containing a mathematical expression. Due to the `enum` keyword, this is an +array that really only exists at compile-time. + +```d + size_t index = uniform(0, functions.length); +``` +This defines a random index into the array. `functions.length` is evaluated at compile-time, due to D's compile-time +function evaluation (CTFE). + +```d + writeln(center("f(z) = " ~ functions[index], width), "\n"); +``` +Unmistakenly, this prints the formula centered on a line. What happens behind the scenes is that `functions` (which +only existed at compile-time before now) is pasted in, so that an instance of that array actually exists at run-time +at this spot, and is instantly indexed. + +```d + float fna(float z) + { + final switch (index) + { + // ... + } + } +``` +`static` has been dropped from the nested function because we want to evaluate `index` inside it. The function contains +an ordinary `switch`, with `final` providing some extra robustness. It disallows a `default` case and produces an error +when the switch doesn't handle all cases. The `switch` body is where the magic happens and consists of these three +lines: +```d + static foreach (i, f; functions) + case i: + mixin("return " ~ f ~ ";"); +``` +The `static foreach` iterates over `functions` at compile-time, producing one `case` for every element in `functions`. +`mixin` takes a string, which is constructed at compile-time, and pastes it right into the source. + +In effect, the implementation of `float fna(float z)` unrolls itself into +```d + float fna(float z) + { + final switch (index) + { + case 0: + return 30.0 * exp(-z * z / 100.0); + case 1: + return sqrt(900.01 - z * z) * .9 - 2; + case 2: + return 30 * (cos(z / 16.0) + .5); + case 3: + return 30 - 30 * sin(z / 18.0); + case 4: + return 30 * exp(-cos(z / 16.0)) - 30; + case 5: + return 30 * sin(z / 10.0)"; + } + } +``` + +So if you feel like adding another function, all you need to do is append it to the `functions` array, and the rest of +the program *rewrites itself...* diff --git a/87_3-D_Plot/d/threedeeplot.d b/87_3-D_Plot/d/threedeeplot.d new file mode 100644 index 00000000..a3660321 --- /dev/null +++ b/87_3-D_Plot/d/threedeeplot.d @@ -0,0 +1,35 @@ +@safe: // Make @safe the default for this file, enforcing memory-safety. +import std.stdio, std.string, std.math, std.range, std.conv, std.algorithm; + +void main() +{ + enum width = 80; + writeln(center("3D Plot", width)); + writeln(center("(After Creative Computing Morristown, New Jersey)\n\n\n", width)); + + static float fna(float z) + { + return 30.0 * exp(-z * z / 100.0); + } + + char[] row; + + for (float x = -30.0; x <= 30.0; x += 1.5) + { + size_t max_z = 0L; + auto y1 = 5 * floor((sqrt(900 - x * x)) / 5.0); + for (float y = y1; y >= -y1; y -= 5) + { + auto z = to!size_t(max(0, floor(25 + fna(sqrt(x * x + y * y)) - .7 * y))); + if (z > max_z) // Visible + { + max_z = z; + if (z + 1 > row.length) // row needs to grow + row ~= ' '.repeat(z + 1 - row.length).array; + row[z] = '*'; + } + } + writeln(row); + row = null; + } +} diff --git a/87_3-D_Plot/d/threedeeplot_random.d b/87_3-D_Plot/d/threedeeplot_random.d new file mode 100644 index 00000000..3db1a2cd --- /dev/null +++ b/87_3-D_Plot/d/threedeeplot_random.d @@ -0,0 +1,50 @@ +@safe: // Make @safe the default for this file, enforcing memory-safety. +import std.stdio, std.string, std.math, std.range, std.conv, std.random, std.algorithm; + +void main() +{ + enum width = 80; + writeln(center("3D Plot", width)); + writeln(center("(After Creative Computing Morristown, New Jersey)\n\n", width)); + + enum functions = ["30.0 * exp(-z * z / 100.0)", + "sqrt(900.01 - z * z) * .9 - 2", + "30 * (cos(z / 16.0) + .5)", + "30 - 30 * sin(z / 18.0)", + "30 * exp(-cos(z / 16.0)) - 30", + "30 * sin(z / 10.0)"]; + + size_t index = uniform(0, functions.length); + writeln(center("f(z) = " ~ functions[index], width), "\n"); + + float fna(float z) + { + final switch (index) + { + static foreach (i, f; functions) + case i: + mixin("return " ~ f ~ ";"); + } + } + + char[] row; + + for (float x = -30.0; x <= 30.0; x += 1.5) + { + size_t max_z = 0L; + auto y1 = 5 * lrint((sqrt(900 - x * x)) / 5.0); + for (float y = y1; y >= -y1; y -= 5) + { + auto z = to!size_t(max(0, lrint(25 + fna(sqrt(x * x + y * y)) - .7 * y))); + if (z > max_z) // Visible + { + max_z = z; + if (z + 1 > row.length) // row needs to grow + row ~= ' '.repeat(z + 1 - row.length).array; + row[z] = '*'; + } + } + writeln(row); + row = null; + } +} diff --git a/94_War/javascript/war.js b/94_War/javascript/war.js index eb74fb0c..ededa1ed 100644 --- a/94_War/javascript/war.js +++ b/94_War/javascript/war.js @@ -1,60 +1,95 @@ // WAR // -// Converted from BASIC to Javascript by Oscar Toledo G. (nanochess) +// Original conversion from BASIC to Javascript by Oscar Toledo G. (nanochess) // -function print(str) -{ +function print(str) { document.getElementById("output").appendChild(document.createTextNode(str)); } -function input() -{ - var input_element; - var input_str; - - return new Promise(function (resolve) { - input_element = document.createElement("INPUT"); - - print("? "); - input_element.setAttribute("type", "text"); - input_element.setAttribute("length", "50"); - document.getElementById("output").appendChild(input_element); - input_element.focus(); - input_str = undefined; - input_element.addEventListener("keydown", function (event) { - if (event.keyCode == 13) { - input_str = input_element.value; - document.getElementById("output").removeChild(input_element); - print(input_str); - print("\n"); - resolve(input_str); - } - }); - }); -} - -function tab(space) -{ - var str = ""; - while (space-- > 0) +function tab(space) { + let str = ""; + while (space-- > 0) { str += " "; + } return str; } -var a = [, "S-2","H-2","C-2","D-2","S-3","H-3","C-3","D-3", - "S-4","H-4","C-4","D-4","S-5","H-5","C-5","D-5", - "S-6","H-6","C-6","D-6","S-7","H-7","C-7","D-7", - "S-8","H-8","C-8","D-8","S-9","H-9","C-9","D-9", - "S-10","H-10","C-10","D-10","S-J","H-J","C-J","D-J", - "S-Q","H-Q","C-Q","D-Q","S-K","H-K","C-K","D-K", - "S-A","H-A","C-A","D-A"]; +function input() { + return new Promise(function (resolve) { + const input_element = document.createElement("INPUT"); -var l = []; + print("? "); + input_element.setAttribute("type", "text"); + input_element.setAttribute("length", "50"); + document.getElementById("output").appendChild(input_element); + input_element.focus(); + input_element.addEventListener("keydown", function (event) { + if (event.keyCode == 13) { + const input_str = input_element.value; + document.getElementById("output").removeChild(input_element); + print(input_str); + print("\n"); + resolve(input_str); + } + }); + }); +} -// Main control section -async function main() -{ +async function askYesOrNo(question) { + while (1) { + print(question); + const str = await input(); + if (str == "YES") { + return true; + } + else if (str == "NO") { + return false; + } + else { + print("YES OR NO, PLEASE. "); + } + } +} + +async function askAboutInstructions() { + const playerWantsInstructions = await askYesOrNo("DO YOU WANT DIRECTIONS"); + if (playerWantsInstructions) { + print("THE COMPUTER GIVES YOU AND IT A 'CARD'. THE HIGHER CARD\n"); + print("(NUMERICALLY) WINS. THE GAME ENDS WHEN YOU CHOOSE NOT TO\n"); + print("CONTINUE OR WHEN YOU HAVE FINISHED THE PACK.\n"); + } + print("\n"); + print("\n"); +} + +function createGameDeck(cards, gameSize) { + const deck = []; + const deckSize = cards.length; + for (let j = 0; j < gameSize; j++) { + let card; + + // Compute a new card index until we find one that isn't already in the new deck + do { + card = Math.floor(deckSize * Math.random()); + } while (deck.includes(card)); + deck.push(card); + } + return deck; +} + +function computeCardValue(cardIndex) { + return Math.floor(cardIndex / 4); +} + +function printGameOver(playerScore, computerScore) { + print("\n"); + print("\n"); + print(`WE HAVE RUN OUT OF CARDS. FINAL SCORE: YOU: ${playerScore} THE COMPUTER: ${computerScore}\n`); + print("\n"); +} + +function printTitle() { print(tab(33) + "WAR\n"); print(tab(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n"); print("\n"); @@ -62,72 +97,65 @@ async function main() print("\n"); print("THIS IS THE CARD GAME OF WAR. EACH CARD IS GIVEN BY SUIT-#\n"); print("AS S-7 FOR SPADE 7. "); - while (1) { - print("DO YOU WANT DIRECTIONS"); - str = await input(); - if (str == "YES") { - print("THE COMPUTER GIVES YOU AND IT A 'CARD'. THE HIGHER CARD\n"); - print("(NUMERICALLY) WINS. THE GAME ENDS WHEN YOU CHOOSE NOT TO\n"); - print("CONTINUE OR WHEN YOU HAVE FINISHED THE PACK.\n"); - break; - } - if (str == "NO") - break; - print("YES OR NO, PLEASE. "); - } - print("\n"); +} + +function printCards(playerCard, computerCard) { print("\n"); + print(`YOU: ${playerCard}\tCOMPUTER: ${computerCard}\n`); +} + +const cards = [ + "S-2", "H-2", "C-2", "D-2", + "S-3", "H-3", "C-3", "D-3", + "S-4", "H-4", "C-4", "D-4", + "S-5", "H-5", "C-5", "D-5", + "S-6", "H-6", "C-6", "D-6", + "S-7", "H-7", "C-7", "D-7", + "S-8", "H-8", "C-8", "D-8", + "S-9", "H-9", "C-9", "D-9", + "S-10", "H-10", "C-10", "D-10", + "S-J", "H-J", "C-J", "D-J", + "S-Q", "H-Q", "C-Q", "D-Q", + "S-K", "H-K", "C-K", "D-K", + "S-A", "H-A", "C-A", "D-A" +]; + +// Main control section +async function main() { + printTitle(); + await askAboutInstructions(); - a1 = 0; - b1 = 0; - p = 0; + let computerScore = 0; + let playerScore = 0; // Generate a random deck - for (j = 1; j <= 52; j++) { - do { - l[j] = Math.floor(52 * Math.random()) + 1; - for (k = 1; k < j; k++) { - if (l[k] == l[j]) // Already in deck? - break; - } - } while (j != 1 && k < j) ; - } - l[j] = 0; // Mark the end of the deck + const gameSize = cards.length; // Number of cards to shuffle into the game deck. Can be <= cards.length. + const deck = createGameDeck(cards, gameSize); + let shouldContinuePlaying = true; - while (1) { - m1 = l[++p]; // Take a card - m2 = l[++p]; // Take a card - print("\n"); - print("YOU: " + a[m1] + "\tCOMPUTER: " + a[m2] + "\n"); - n1 = Math.floor((m1 - 0.5) / 4); - n2 = Math.floor((m2 - 0.5) / 4); - if (n1 < n2) { - a1++; - print("THE COMPUTER WINS!!! YOU HAVE " + b1 + " AND THE COMPUTER HAS " + a1 + "\n"); - } else if (n1 > n2) { - b1++; - print("YOU WIN. YOU HAVE " + b1 + " AND THE COMPUTER HAS " + a1 + "\n"); + while (deck.length > 0 && shouldContinuePlaying) { + const playerCard = deck.shift(); // Take a card + const computerCard = deck.shift(); // Take a card + printCards(cards[playerCard], cards[computerCard]); + + const playerCardValue = computeCardValue(playerCard); + const computerCardValue = computeCardValue(computerCard); + if (playerCardValue < computerCardValue) { + computerScore++; + print("THE COMPUTER WINS!!! YOU HAVE " + playerScore + " AND THE COMPUTER HAS " + computerScore + "\n"); + } else if (playerCardValue > computerCardValue) { + playerScore++; + print("YOU WIN. YOU HAVE " + playerScore + " AND THE COMPUTER HAS " + computerScore + "\n"); } else { print("TIE. NO SCORE CHANGE.\n"); } - if (l[p + 1] == 0) { - print("\n"); - print("\n"); - print("WE HAVE RUN OUT OF CARDS. FINAL SCORE: YOU: " + b1 + " THE COMPUTER: " + a1 + "\n"); - print("\n"); - break; + + if (deck.length === 0) { + printGameOver(playerScore, computerScore); } - while (1) { - print("DO YOU WANT TO CONTINUE"); - str = await input(); - if (str == "YES") - break; - if (str == "NO") - break; - print("YES OR NO, PLEASE. "); + else { + shouldContinuePlaying = await askYesOrNo("DO YOU WANT TO CONTINUE"); } - if (str == "NO") - break; } print("THANKS FOR PLAYING. IT WAS FUN.\n"); print("\n"); diff --git a/96_Word/d/.gitignore b/96_Word/d/.gitignore new file mode 100644 index 00000000..d969f6b2 --- /dev/null +++ b/96_Word/d/.gitignore @@ -0,0 +1,2 @@ +*.exe +*.obj diff --git a/96_Word/d/README.md b/96_Word/d/README.md new file mode 100644 index 00000000..764cb141 --- /dev/null +++ b/96_Word/d/README.md @@ -0,0 +1,15 @@ +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). + +The Basic original required words to be exactly five letters in length for the program to behave correctly. +This version does not replicate that limitation, and the test for that requirement is commented out. + +## Running the code + +Assuming the reference [dmd](https://dlang.org/download.html#dmd) compiler: +```shell +dmd -dip1000 -run word.d +``` + +[Other compilers](https://dlang.org/download.html) also exist. diff --git a/96_Word/d/word.d b/96_Word/d/word.d new file mode 100644 index 00000000..6f4d46d6 --- /dev/null +++ b/96_Word/d/word.d @@ -0,0 +1,85 @@ +@safe: // Make @safe the default for this file, enforcing memory-safety. +import std; + +void main() +{ + enum width = 80; + writeln(center("Word", width)); + writeln(center("(After Creative Computing Morristown, New Jersey)\n\n\n", width)); + writeln(wrap("I am thinking of a word -- you guess it. I will give you " ~ + "clues to help you get it. Good luck!!\n\n", width)); + + string[] words = ["dinky", "smoke", "water", "grass", "train", "might", "first", + "candy", "champ", "would", "clump", "dopey"]; + + playLoop: while (true) + { + writeln("\n\nYou are starting a new game..."); + + string word = words[uniform(0, $-1)]; // $ is a short-hand for words.length. + int guesses = 0; + string knownLetters = '-'.repeat(word.length).array; + + while (true) + { + writeln("Guess a ", word.length, " letter word"); + string guess = readString.toLower; + if (guess == "?") + { + writeln("The secret word is ", word, "\n"); + continue playLoop; // Start a new game. + } + /* Uncomment this for equivalence with Basic. + if (guess.length != 5) + { + writeln("You must guess a 5 letter word. Start again."); + continue; // Ask for new guess. + } + */ + guesses++; + if (guess == word) + break; // Done guessing + string commonLetters; + foreach (i, wordLetter; word) + foreach (j, guessLetter; guess) + if (guessLetter == wordLetter) + { + commonLetters ~= guessLetter; + if (i == j) + knownLetters.replaceInPlace(i, i + 1, [guessLetter]); + } + writeln("There were ", commonLetters.length, " matches and the common letters were... ", commonLetters); + writeln("From the exact letter matches, you know................ ", knownLetters); + if (knownLetters == word) + break; // Done guessing + if (commonLetters.length < 2) + writeln("If you give up, type '?' for your next guess."); + writeln; + } + + writeln("You have guessed the word. It took ", guesses, " guesses!"); + write("\n\nWant to play again? "); + if (readString.toLower != "yes") + break; // Terminate playLoop + } +} + +/// Read a string from standard input, stripping newline and other enclosing whitespace. +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; +} diff --git a/HOW_TO_RUN_THE_GAMES.md b/HOW_TO_RUN_THE_GAMES.md new file mode 100644 index 00000000..44d82e98 --- /dev/null +++ b/HOW_TO_RUN_THE_GAMES.md @@ -0,0 +1,85 @@ +# How to run the games + +The games in this repository have been translated into a number of different languages. How to run them depends on the target language. + +## csharp + +### dotnet command-line + +The best cross-platform method for running the csharp examples is with the `dotnet` command-line tool. This can be downloaded for **MacOS**, **Windows** and **Linux** from [dotnet.microsoft.com](https://dotnet.microsoft.com/). + +From there, the program can be run by + +1. Opening a terminal window +1. Navigating to the corresponding directory +1. Starting with `dotnet run` + +### Visual Studio + +Alternatively, for non-dotnet compatible translations, you will need [Visual Studio](https://visualstudio.microsoft.com/vs/community/) which can be used to both open the project and run the example. + +1. Open the corresponding `.csproj` or `.sln` file +1. Click `Run` from within the Visual Studio IDE + +## java + +The Java translations can be run via the command line or from an IDE such as [Eclipse](https://www.eclipse.org/downloads/packages/release/kepler/sr1/eclipse-ide-java-developers) or [IntelliJ](https://www.jetbrains.com/idea/) + +To run from the command line, you will need a Java SDK (eg. [Oracle JDK](https://www.oracle.com/java/technologies/downloads/) or [Open JDK](https://openjdk.java.net/)). + +1. Navigate to the corresponding directory. +1. Compile the program with `javac`: + * eg. `javac AceyDuceyGame.java` +1. Run the compiled program with `java`: + * eg. `java AceyDuceyGame` + +## javascript + +The javascript examples can be run from within your web browser: + +1. Simply open the corresponding `.html` file from your web browser. + +## pascal + +The pascal examples can be run using [Free Pascal](https://www.freepascal.org/). Additionally, `.lsi` project files can be opened with the [Lazarus Project IDE](https://www.lazarus-ide.org/). + +The pascal examples include both *simple* (single-file) and *object-oriented* (in the `/object-pascal`directories) examples. + +1. You can compile the program from the command line with the `fpc` command. + * eg. `fpc amazing.pas` +1. The output is an executable file that can be run directly. + +## perl + +The perl translations can be run using a perl interpreter (a copy can be downloaded from [perl.org](https://www.perl.org/)) if not already installed. + +1. From the command-line, navigate to the corresponding directory. +1. Invoke with the `perl` command. + * eg. `perl aceyducey.pl` + +## python + +The python translations can be run from the command line by using the `py` interpreter. If not already installed, a copy can be downloaded from [python.org](https://www.python.org/downloads/) for **Windows**, **MacOS** and **Linux**. + +1. From the command-line, navigate to the corresponding directory. +1. Invoke with the `py` or `python` interpreter (depending on your python version). + * eg. `py acey_ducey_oo.py` + * eg. `python aceyducey.py` + +**Note** + +Some translations include multiple versions for python, such as `acey ducey` which features versions for Python 2 (`aceyducey.py`) and Python 3 (`acey_ducey.py`) as well as an extra object-oriented version (`acey_ducey_oo.py`). + +You can manage and use different versions of python with [pip](https://pypi.org/project/pip/). + +## ruby + +If you don't already have a ruby interpreter, you can download it from the [ruby project site](https://www.ruby-lang.org/en/). + +1. From the command-line, navigate to the corresponding directory. +1. Invoke with the `ruby` tool. + * eg. `ruby aceyducey.rb` + +## vbnet + +Follow the same steps as for the [csharp](#csharp) translations. This can be run with `dotnet` or `Visual Studio`.