diff --git a/01_Acey_Ducey/python/acey_ducey_oo.py b/01_Acey_Ducey/python/acey_ducey_oo.py new file mode 100644 index 00000000..d8b7c752 --- /dev/null +++ b/01_Acey_Ducey/python/acey_ducey_oo.py @@ -0,0 +1,125 @@ +# +# AceyDuchy +# +# From: BASIC Computer Games (1978) +# Edited by David Ahl +# +# "The original BASIC program author was Bill Palmby +# of Prairie View, Illinois." +# +# Python port by Aviyam Fischer, 2022 +# +###################################################### + +class Card: + def __init__(self, suit, rank): + self.suit = suit + self.rank = rank + + def __str__(self): + r = self.rank + if r == 11: + r = 'J' + elif r == 12: + r = 'Q' + elif r == 13: + r = 'K' + elif r == 14: + r = 'A' + return f'{r}{self.suit}' + + +class Deck: + def __init__(self): + self.cards = [] + self.build() + + def build(self): + for suit in ['\u2665', '\u2666', '\u2663', '\u2660']: + for rank in range(1, 14): + self.cards.append(Card(suit, rank)) + + def shuffle(self): + import random + random.shuffle(self.cards) + + def deal(self): + return self.cards.pop() + + +class Game: + def __init__(self): + self.deck = Deck() + self.deck.shuffle() + self.card_a = self.deck.deal() + self.card_b = self.deck.deal() + self.money = 100 + self.not_done = True + + def play(self): + while self.not_done: + while self.money > 0: + card_a = self.card_a + card_b = self.card_b + + if card_a.rank > card_b.rank: + card_a, card_b = card_b, card_a + + if card_a.rank == card_b.rank: + self.card_b = self.deck.deal() + card_b = self.card_b + + print(f'You have:\t ${self.money} ') + print(f'Your cards:\t {card_a} {card_b}') + + bet = int(input('What is your bet? ')) + player_card = self.deck.deal() + if 0 < bet <= self.money: + + print(f'Your deal:\t {player_card}') + if card_a.rank < player_card.rank < card_b.rank: + print('You Win!') + self.money += bet + else: + print('You Lose!') + self.money -= bet + self.not_done = False + else: + print('Chicken!') + print(f'Your deal should have been: {player_card}') + if card_a.rank < player_card.rank < card_b.rank: + print(f'You could have won!') + else: + print(f'You would lose, so it was wise of you to chicken out!') + + self.not_done = False + break + + if len(self.deck.cards) <= 1: + print('You ran out of cards. Game over.') + self.not_done = False + break + + if self.money == 0: + self.not_done = False + + +if __name__ == '__main__': + print(''' + Acey Ducey is a card game where you play against the computer. + The Dealer(computer) will deal two cards facing up. + You have an option to bet or not bet depending on whether or not you + feel the card will have a value between the first two. + If you do not want to bet input a 0 + ''') + GAME_OVER = False + + while not GAME_OVER: + game = Game() + game.play() + print(f'You have ${game.money} left') + print('Would you like to play again? (y/n)') + if input() == 'n': + GAME_OVER = True + + print('\nThanks for playing!') diff --git a/01_Acey_Ducey/ruby/aceyducey.rb b/01_Acey_Ducey/ruby/aceyducey.rb index b9648ef8..3d49754a 100644 --- a/01_Acey_Ducey/ruby/aceyducey.rb +++ b/01_Acey_Ducey/ruby/aceyducey.rb @@ -56,9 +56,9 @@ while true # Game loop puts puts "HERE ARE YOUR NEXT TWO CARDS:" - # Randomly draw two cards from 2 to 14 and make sure the first card is lower in value than the second + # Randomly draw two cards and make sure the first card is lower in value than the second # Using array destructuring, this sorted array can be assigned to `first_card` and `second_card` - first_card, second_card = [rand(2..14), rand(2..14)].sort + first_card, second_card = (2...14).to_a.shuffle.pop(2).sort # Helper method to convert a numeric card into a String for printing def card_name(card) diff --git a/01_Acey_Ducey/vbnet/AceyDucy.sln b/01_Acey_Ducey/vbnet/AceyDucy.sln new file mode 100644 index 00000000..881d7c9c --- /dev/null +++ b/01_Acey_Ducey/vbnet/AceyDucy.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "AceyDucy", "AceyDucy\AceyDucy.vbproj", "{37496710-B458-4502-ADCB-4C57203866F9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {37496710-B458-4502-ADCB-4C57203866F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37496710-B458-4502-ADCB-4C57203866F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37496710-B458-4502-ADCB-4C57203866F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37496710-B458-4502-ADCB-4C57203866F9}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C01D9DAE-644C-455F-8365-E14E49074BC3} + EndGlobalSection +EndGlobal diff --git a/01_Acey_Ducey/vbnet/AceyDucy/AceyDucy.vbproj b/01_Acey_Ducey/vbnet/AceyDucy/AceyDucy.vbproj new file mode 100644 index 00000000..98a07001 --- /dev/null +++ b/01_Acey_Ducey/vbnet/AceyDucy/AceyDucy.vbproj @@ -0,0 +1,9 @@ + + + + Exe + AceyDucy + net6.0 + + + diff --git a/01_Acey_Ducey/vbnet/AceyDucy/Program.vb b/01_Acey_Ducey/vbnet/AceyDucy/Program.vb new file mode 100644 index 00000000..bfa518ca --- /dev/null +++ b/01_Acey_Ducey/vbnet/AceyDucy/Program.vb @@ -0,0 +1,178 @@ +Imports System + +''' +''' This is a modern adapation of Acey Ducey from BASIC Computer Games. +''' +''' The structural changes primarily consist of replacing the many GOTOs with +''' Do/Loop constructs to force the continual execution of the program. +''' +''' Because modern Basic allows multi-line If/Then blocks, many GOTO jumps were +''' able to be eliminated and the logic was able to be moved to more relevant areas, +''' For example, the increment/decrement of the player's balance could be in the same +''' area as the notification of win/loss. +''' +''' Some modern improvements were added, primarily the inclusion of a function, which +''' eliminated a thrice-repeated block of logic to display the card value. The archaic +''' RND function is greatly simplified with the .NET Framework's Random class. +''' +''' Elementary comments are provided for non-programmers or novices. +''' +Module Program + Sub Main(args As String()) + ' These are the variables that will hold values during the program's execution + Dim input As String + Dim rnd As New Random ' You can create a new instance of an object during declaration + Dim currentBalance As Integer = 100 ' You can set a initial value at declaration + Dim currentWager As Integer + Dim cardA, cardB, cardC As Integer ' You can specify multiple variables of the same type in one declaration statement + + ' Display the opening title and instructions + ' Use a preceding $ to insert calculated values within the string using {} + Console.WriteLine($"{Space((Console.WindowWidth \ 2) - 10)}ACEY DUCEY CARD GAME") + Console.WriteLine($"{Space((Console.WindowWidth \ 2) - 21)}CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + Console.WriteLine("") + Console.WriteLine("") + Console.WriteLine("ACEY-DUCEY IS PLAYED IN THE FOLLOWING MANNER") + Console.WriteLine("THE DEALER (COMPUTER) DEALS TWO CARDS FACE UP") + Console.WriteLine("YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING") + Console.WriteLine("ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE") + Console.WriteLine("A VALUE BETWEEN THE FIRST TWO.") + Console.WriteLine("IF YOU DO NOT WANT TO BET, INPUT A 0") + + Do ' This loop continues as long as the player wants to keep playing + + Do ' This loop continues as long as the player has money to play + + Console.WriteLine("") + Console.WriteLine($"YOU NOW HAVE {currentBalance} DOLLARS.") + Console.WriteLine("") + + Console.WriteLine("HERE ARE YOUR NEXT TWO CARDS:") + + ' We need to ensure that card B is a higher value for our later comparison, + ' so we will loop until we have two cards that meet this criteria + Do + cardA = rnd.Next(2, 14) + cardB = rnd.Next(2, 14) + + Loop While cardA > cardB + + ' We use a function to display the text value of the numeric card value + ' because we do this 3 times and a function reduces repetition of code + Console.WriteLine(DisplayCard(cardA)) + Console.WriteLine(DisplayCard(cardB)) + + Do ' This loop continues until the player provides a valid wager value + Console.WriteLine("") + Console.WriteLine("WHAT IS YOUR BET") + + currentWager = 0 + input = Console.ReadLine + + ' Any input from the console is a string, but we require a number. + ' Test the input to make sure it is a numeric value. + If Integer.TryParse(input, currentWager) Then + ' Test to ensure the player has not wagered more than their balance + If currentWager > currentBalance Then + Console.WriteLine("SORRY, MY FRIEND, BUT YOU BET TOO MUCH.") + Console.WriteLine($"YOU HAVE ONLY {currentBalance} DOLLARS TO BET.") + + Else + ' The player has provided a numeric value that is less/equal to their balance, + ' exit the loop and continue play + Exit Do + + End If ' check player balance + + End If ' check numeric input + + Loop ' wager loop + + ' If the player is wagering, draw the third card, otherwise, mock them. + If currentWager > 0 Then + cardC = rnd.Next(2, 14) + + Console.WriteLine(DisplayCard(cardC)) + + ' The effort we made to have two cards in numeric order earlier makes this check easier, + ' otherwise we would have to have a second check in the opposite direction + If cardC < cardA OrElse cardC >= cardB Then + Console.WriteLine("SORRY, YOU LOSE") + currentBalance -= currentWager ' Shorthand code to decrement a number (currentBalance=currentBalance - currentWager) + + Else + Console.WriteLine("YOU WIN!!!") + currentBalance += currentWager ' Shorthand code to increment a number (currentBalance=currentBalance + currentWager) + + End If + + Else + Console.WriteLine("CHICKEN!!") + Console.WriteLine("") + + End If + + Loop While currentBalance > 0 ' loop as long as the player has money + + ' At this point, the player has no money (currentBalance=0). Inform them of such. + Console.WriteLine("") + Console.WriteLine("SORRY, FRIEND, BUT YOU BLEW YOUR WAD.") + Console.WriteLine("") + Console.WriteLine("") + + ' We will loop to ensure the player provides some answer. + Do + Console.WriteLine("TRY AGAIN (YES OR NO)") + Console.WriteLine("") + + input = Console.ReadLine + + Loop While String.IsNullOrWhiteSpace(input) + + ' We will assume that the player wants to play again only if they answer yes. + ' (yeah and ya are valid as well, because we only check the first letter) + If input.Substring(0, 1).Equals("y", StringComparison.CurrentCultureIgnoreCase) Then ' This allows upper and lower case to be entered. + currentBalance = 100 ' Reset the players balance before restarting + + Else + ' Exit the outer loop which will end the game. + Exit Do + + End If + + Loop ' The full game loop + + Console.WriteLine("O.K., HOPE YOU HAD FUN!") + + End Sub + + ' This function is called for each of the 3 cards used in the game. + ' The input and the output are both consistent, making it a good candidate for a function. + Private Function DisplayCard(value As Integer) As String + ' We check the value of the input and run a block of code for whichever + ' evaluation matches + Select Case value + Case 2 To 10 ' Case statements can be ranges of values, also multiple values (Case 2,3,4,5,6,7,8,9,10) + Return value.ToString + + Case 11 + Return "JACK" + + Case 12 + Return "QUEEN" + + Case 13 + Return "KING" + + Case 14 + Return "ACE" + + End Select + + ' Although we have full knowledge of the program and never plan to send an invalid + ' card value, it's important to provide a message for the next developer who won't + Throw New ArgumentOutOfRangeException("Card value must be between 2 and 14") + + End Function + +End Module diff --git a/04_Awari/csharp/Game.cs b/04_Awari/csharp/Game.cs new file mode 100644 index 00000000..8a673dea --- /dev/null +++ b/04_Awari/csharp/Game.cs @@ -0,0 +1,264 @@ +namespace Awari; + +public class Game +{ + public int[] PlayerPits => _beans[0..6]; + public int[] ComputerPits => _beans[7..13]; + public int PlayerHome => _beans[_playerHome]; + public int ComputerHome => _beans[_computerHome]; + + private bool IsDone => + PlayerPits.All(b => b == 0) // if all the player's pits are empty + || ComputerPits.All(b => b == 0); // or if all the computer's pits are empty + + public GameState State { get; private set; } + + public void Reset() + { + State = GameState.PlayerMove; + + Array.Fill(_beans, _initialPitValue); + _beans[_playerHome] = 0; + _beans[_computerHome] = 0; + + _moveCount = 0; + _notWonGameMoves[^1] = 0; + } + + public bool IsLegalPlayerMove(int move) => + move is > 0 and < 7 + && _beans[move - 1] > 0; // arrays are zero-based, but moves are one-based + + public void PlayerMove(int move) => MoveAndRegister(move - 1, _playerHome); + + public List ComputerTurn() + { + // keep a list of moves made by the computer in a single turn (1 or 2) + List moves = new(); + + moves.Add(ComputerMove()); // ComputerMove() returns the move made + + // only if a second move is possible, do it + if (State == GameState.ComputerSecondMove) + moves.Add(ComputerMove()); + + return moves; + } + + public GameOutcome GetOutcome() + { + if (State != GameState.Done) + throw new InvalidOperationException("Game is not yet done."); + + int difference = _beans[_playerHome] - _beans[_computerHome]; + var winner = difference switch + { + < 0 => GameWinner.Computer, + 0 => GameWinner.Draw, + > 0 => GameWinner.Player, + }; + + return new GameOutcome(winner, Math.Abs(difference)); + } + + private void MoveAndRegister(int pit, int homePosition) + { + int lastMovedBean = Move(_beans, pit, homePosition); + + // encode moves by player and computer into a 'base 6' number + // e.g. if the player moves 5, the computer moves 2, and the player moves 4, + // that would be encoded as ((5 * 6) * 6) + (2 * 6) + 4 = 196 + if (pit > 6) pit -= 7; + _moveCount++; + if (_moveCount < 9) + _notWonGameMoves[^1] = _notWonGameMoves[^1] * 6 + pit; + + // determine next state based on current state, whether the game's done, and whether the last moved bean moved + // into the player's home position + State = (State, IsDone, lastMovedBean == homePosition) switch + { + (_, true, _) => GameState.Done, + (GameState.PlayerMove, _, true) => GameState.PlayerSecondMove, + (GameState.PlayerMove, _, false) => GameState.ComputerMove, + (GameState.PlayerSecondMove, _, _) => GameState.ComputerMove, + (GameState.ComputerMove, _, true) => GameState.ComputerSecondMove, + (GameState.ComputerMove, _, false) => GameState.PlayerMove, + (GameState.ComputerSecondMove, _, _) => GameState.PlayerMove, + _ => throw new InvalidOperationException("Unexpected game state"), + }; + + // do some bookkeeping if the game is done, but not won by the computer + if (State == GameState.Done + && _beans[_playerHome] >= _beans[_computerHome]) + // add an entry for the next game + _notWonGameMoves.Add(0); + } + + private static int Move(int[] beans, int pit, int homePosition) + { + int beansToMove = beans[pit]; + beans[pit] = 0; + + // add the beans that were in the pit to other pits, moving clockwise around the board + for (; beansToMove >= 1; beansToMove--) + { + // wrap around if pit exceeds 13 + pit = (pit + 1) % 14; + + beans[pit]++; + } + + if (beans[pit] == 1 // if the last bean was sown in an empty pit + && pit is not _playerHome and not _computerHome // which is not either player's home + && beans[12 - pit] != 0) // and the pit opposite is not empty + { + // move the last pit sown and the _beans in the pit opposite to the player's home + beans[homePosition] = beans[homePosition] + beans[12 - pit] + 1; + beans[pit] = 0; + beans[12 - pit] = 0; + } + + return pit; + } + + private int ComputerMove() + { + int move = DetermineComputerMove(); + MoveAndRegister(move, homePosition: _computerHome); + + // the result is only used to return it to the application, so translate it from an array index (between 7 and + // 12) to a pit number (between 1 and 6) + return move - 6; + } + + private int DetermineComputerMove() + { + int bestScore = -99; + int move = 0; + + // for each of the computer's possible moves, simulate them to calculate a score and pick the best one + for (int j = 7; j < 13; j++) + { + if (_beans[j] <= 0) + continue; + + int score = SimulateMove(j); + + if (score >= bestScore) + { + move = j; + bestScore = score; + } + } + + return move; + } + + private int SimulateMove(int move) + { + // make a copy of the current state, so we can safely mess with it + var hypotheticalBeans = new int[14]; + _beans.CopyTo(hypotheticalBeans, 0); + + // simulate the move in our copy + Move(hypotheticalBeans, move, homePosition: _computerHome); + + // determine the 'best' move the player could make after this (best for them, not for the computer) + int score = ScoreBestNextPlayerMove(hypotheticalBeans); + + // score this move by calculating how far ahead we would be after the move, and subtracting the player's next + // move score + score = hypotheticalBeans[_computerHome] - hypotheticalBeans[_playerHome] - score; + + // have we seen the current set of moves before in a drawn/lost game? after 8 moves it's unlikely we'll find any + // matches, since games will have diverged. also we don't have space to store that many moves. + if (_moveCount < 8) + { + int translatedMove = move - 7; // translate from 7 through 12 to 0 through 5 + + // if the first two moves in this game were 1 and 2, and this hypothetical third move would be a 3, + // movesSoFar would be (1 * 36) + (2 * 6) + 3 = 51 + int movesSoFar = _notWonGameMoves[^1] * 6 + translatedMove; + + // since we store moves as a 'base 6' number, we need to divide stored moves by a power of 6 + // let's say we've a stored lost game where the moves were, in succession, 1 through 8, the value stored + // would be: + // 8 + (7 * 6) + (6 * 36) + (5 * 216) + (4 * 1296) + (3 * 7776) + (2 * 46656) + (1 * 279936) = 403106 + // to figure out the first three moves, we'd need to divide by 7776, resulting in 51.839... + double divisor = Math.Pow(6.0, 7 - _moveCount); + + foreach (int previousGameMoves in _notWonGameMoves) + // if this combination of moves so far ultimately resulted in a draw/loss, give it a lower score + // note that this can happen multiple times + if (movesSoFar == (int) (previousGameMoves / divisor + 0.1)) + score -= 2; + } + + return score; + } + + private static int ScoreBestNextPlayerMove(int[] hypotheticalBeans) + { + int bestScore = 0; + + for (int i = 0; i < 6; i++) + { + if (hypotheticalBeans[i] <= 0) + continue; + + int score = ScoreNextPlayerMove(hypotheticalBeans, i); + + if (score > bestScore) + bestScore = score; + } + + return bestScore; + } + + private static int ScoreNextPlayerMove(int[] hypotheticalBeans, int move) + { + // figure out where the last bean will land + int target = hypotheticalBeans[move] + move; + int score = 0; + + // if it wraps around, that means the player is adding to his own pits, which is good + if (target > 13) + { + // prevent overrunning the number of pits we have + target %= 14; + score = 1; + } + + // if the player's move ends up in an empty pit, add the value of the pit on the opposite side to the score + if (hypotheticalBeans[target] == 0 && target is not _playerHome and not _computerHome) + score += hypotheticalBeans[12 - target]; + + return score; + } + + private const int _playerHome = 6; + private const int _computerHome = 13; + private const int _initialPitValue = 3; + + private readonly int[] _beans = new int[14]; + private readonly List _notWonGameMoves = new() { 0 }; // not won means draw or lose + private int _moveCount; +} + +public enum GameState +{ + PlayerMove, + PlayerSecondMove, + ComputerMove, + ComputerSecondMove, + Done, +} + +public enum GameWinner +{ + Player, + Computer, + Draw, +} + +public record struct GameOutcome(GameWinner Winner, int Difference); \ No newline at end of file diff --git a/04_Awari/csharp/Program.cs b/04_Awari/csharp/Program.cs new file mode 100644 index 00000000..750787e7 --- /dev/null +++ b/04_Awari/csharp/Program.cs @@ -0,0 +1,98 @@ +using Awari; + +Console.WriteLine(Tab(34) + "AWARI"); +Console.WriteLine(Tab(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); + +Game game = new(); + +while (true) +{ + game.Reset(); + DisplayGame(); + + while (game.State != GameState.Done) + { + switch (game.State) + { + case GameState.PlayerMove: + PlayerMove(second: false); + break; + case GameState.PlayerSecondMove: + PlayerMove(second: true); + break; + case GameState.ComputerMove: + ComputerTurn(); + break; + } + + DisplayGame(); + } + + var outcome = game.GetOutcome(); + + string outcomeLabel = + outcome.Winner switch + { + GameWinner.Computer => $"I WIN BY {outcome.Difference} POINTS", + GameWinner.Draw => "DRAWN GAME", + GameWinner.Player => $"YOU WIN BY {outcome.Difference} POINTS", + _ => throw new InvalidOperationException($"Unexpected winner {outcome.Winner}."), + }; + Console.WriteLine(outcomeLabel); + Console.WriteLine(); +} + +void DisplayGame() +{ + // display the computer's pits + Console.Write(" "); + foreach (var pit in game.ComputerPits.Reverse()) + Console.Write($"{pit,2} "); + Console.WriteLine(); + + // display both homes + Console.WriteLine($"{game.ComputerHome,2}{Tab(19)}{game.PlayerHome,2}"); + + // display the player's pits + Console.Write(" "); + foreach (var pit in game.PlayerPits) + Console.Write($"{pit,2} "); + Console.WriteLine(); + + Console.WriteLine(); +} + +void PlayerMove(bool second = false) +{ + int move = GetMove(second); + game.PlayerMove(move); +} + +int GetMove(bool second) +{ + string prompt = second ? "AGAIN? " : "YOUR MOVE? "; + + while (true) + { + Console.Write(prompt); + + string input = Console.ReadLine() ?? ""; + + // input must be a number between 1 and 6, and the pit must have > 0 beans + if (int.TryParse(input, out int move) + && game.IsLegalPlayerMove(move)) + return move; + + Console.WriteLine("ILLEGAL MOVE"); + } +} + +void ComputerTurn() +{ + var moves = game.ComputerTurn(); + string movesString = string.Join(",", moves); + + Console.WriteLine($"MY MOVE IS {movesString}"); +} + +string Tab(int n) => new(' ', n); \ No newline at end of file diff --git a/04_Awari/csharp/csharp.csproj b/04_Awari/csharp/csharp.csproj new file mode 100644 index 00000000..6bf2a525 --- /dev/null +++ b/04_Awari/csharp/csharp.csproj @@ -0,0 +1,11 @@ + + + + Exe + net6.0 + enable + enable + Awari + + + diff --git a/05_Bagels/perl/bagels.pl b/05_Bagels/perl/bagels.pl new file mode 100755 index 00000000..739ded0e --- /dev/null +++ b/05_Bagels/perl/bagels.pl @@ -0,0 +1,195 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +# global variable declaration (just the user's score) +my($Y) = 0; + + +# yes_input is a subroutine that returns a true value +# if the first character of the user's input from STDIN +# is a 'Y' (checking case-insensitively via regex) +sub yes_input { + chomp(my $A = ); + return $A =~ m/^Y/i; +} + +# Main code starts here. + +print ' 'x32; print "Bagels\n"; +print ' 'x14; print "Creative Computing Morristown, New Jersey\n\n"; + +# Provide instructions if requested +print "Would you like the rules (yes or no)? "; +if (yes_input()) { + + # Print out the instructions using a here doc + # (useful for large blocks of text) + print <); + + # Use a regex to check if the user entered three digits, + # and complain if they did not. + if ($A !~ m{^(\d)(\d)(\d)$}) { + print "What?\n"; + # Program execution will now pass through the rest + # of the logic below and loop back to the start + # of the CHECK loop. + } else { + + # As a side effect of the regex match above, the + # $1, $2, and $3 variables are each of the digits + # of the user's guess. Perl treats numbers and + # strings interchangably, so we will not have to + # use the ASC() conversion functions required + # by the BASIC program. + my @B = ($1,$2,$3); + + # Check for duplicate digits in the user's guess + if ($B[0] == $B[1] || $B[0] == $B[2] || $B[1] == $B[2]) { + print "Oh, I forgot to tell you that the number I have in mind\n"; + print "has no two digits the same.\n"; + # Again, no further action is required here + # because we want to loop back to the start + # of the CHECK loop. + } else { + + # This code block is the actual game logic, so + # it's executed only if the user's input has + # passed all the above checks. + my($C,$D); + $C = 0; $D = 0; + + # As a replacement for the original BASIC logic, + # this for loop works over an anonymous array of + # pairs of digits to compare the computer's and + # the user's digits to see how many similar ones + # there are. Keep in mind that Perl arrays are + # zero-indexed, so we're comparing items numbered + # 0, 1, and 2, instead of 1, 2, and 3 in BASIC. + + for my $PAIR ( [0,1], [1,0], [1,2], [2,1], [0,2], [2,0] ) { + if ($A[$PAIR->[0]] == $B[$PAIR->[1]]) { + ++$C; + } + } + + # Check for digits that are correctly guessed + for my $i (0..2) { + if ($A[$i] == $B[$i]) { + ++$D; + } + } + + # If the user guessed all 3 digits they get + # a point, and the 'PLAY' loop is restarted + # (see the 'continue' loop below) + if ($D == 3) { + print "You got it!!!\n\n"; + ++$Y; + next PLAY; + } + + # Print out the clues. The 'x' operator + # prints out the string the indicated number + # of times. The "BAGELS" line uses Perl's + # ternary operator to print the word if + # the expression ($C + $D) is equal to 0. + + printf("%s%s%s\n", + "PICO " x$C, + "FERMI "x$D, + ($C+$D==0 ? "BAGELS" : '') + ); + + # Program execution leaves the CHECK loop and + # goes to the next iteration of the $i loop. + last CHECK; + + } # end of game logic else block + } # end of regex match else block + + # If program execution reaches this particular point, + # then the user's input has not been accepted (the + # only ways out of this loop are the "next PLAY" statement + # when the user wins, and the "last CHECK" statement + # when the user's input is successfully parsed). + # So the program execution goes back to the top of the + # CHECK loop, printing the request for user input + # again. + + } # end of CHECK loop + + # This location is reached by the "last CHECK" statement, + # and it's another execution of the $i loop. + + } # end of $i loop + + # If program execution reaches here, the user has guessed 20 + # times and not won. + + print "Oh well.\n"; + printf("That's twenty guesses. My number was %s\n", join('',@A)); + +} # end of the PLAY loop + +# This 'continue' block is executed before the conditional part of the +# PLAY loop is evaluated, so we can ask if the user wants another game +# (i.e., if we should restart the PLAY loop). + +continue { + + # This 'continue' loop is reached either when the PLAY loop has completed + # or via the 'next PLAY' statement when the user wins a game. In either + # case we ask if the player wants to go again, and use the 'last' + # statement to exit the loop if the response is not yes. + print "Play again (yes or no) ? "; + last unless yes_input(); +} + +# And as in the original BASIC program, print out +# the user's score only if it is > 0. +printf("A %d point bagels buff!\n", $Y) if $Y > 0; +print "Hope you had fun. Bye.\n"; diff --git a/22_Change/csharp/Change.csproj b/22_Change/csharp/Change.csproj new file mode 100644 index 00000000..1d2d39a9 --- /dev/null +++ b/22_Change/csharp/Change.csproj @@ -0,0 +1,8 @@ + + + + Exe + net5.0 + + + diff --git a/22_Change/csharp/Change.sln b/22_Change/csharp/Change.sln new file mode 100644 index 00000000..c0f2e5e8 --- /dev/null +++ b/22_Change/csharp/Change.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.810.16 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Change", "Change.csproj", "{AE094667-8496-4ECF-8B42-B1648EE26073}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AE094667-8496-4ECF-8B42-B1648EE26073}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE094667-8496-4ECF-8B42-B1648EE26073}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE094667-8496-4ECF-8B42-B1648EE26073}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE094667-8496-4ECF-8B42-B1648EE26073}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {65684CBD-CD74-46AF-8E9E-0F69DCF72697} + EndGlobalSection +EndGlobal diff --git a/22_Change/csharp/Program.cs b/22_Change/csharp/Program.cs new file mode 100644 index 00000000..12804731 --- /dev/null +++ b/22_Change/csharp/Program.cs @@ -0,0 +1,129 @@ +using System; + +namespace Change +{ + class Program + { + /// + /// Prints header. + /// + static void Header() + { + Console.WriteLine("Change".PadLeft(33)); + Console.WriteLine("Creative Computing Morristown, New Jersey".PadLeft(15)); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine("I, your friendly microcomputer, will determine\n" + + "the correct change for items costing up to $100."); + Console.WriteLine(); + Console.WriteLine(); + } + + /// + /// Gets user input for price and payment. + /// + /// + /// False if any input can't be parsed to double. Price and payment returned would be 0. + /// True if it was possible to parse inputs into doubles. Price and payment returned + /// would be as provided by the user. + /// + static (bool status, double price, double payment) GetInput() + { + Console.Write("Cost of item? "); + var priceString = Console.ReadLine(); + if (!double.TryParse(priceString, out double price)) + { + Console.WriteLine($"{priceString} isn't a number!"); + return (false, 0, 0); + } + + Console.Write("Amount of payment? "); + var paymentString = Console.ReadLine(); + if (!double.TryParse(paymentString, out double payment)) + { + Console.WriteLine($"{paymentString} isn't a number!"); + return (false, 0, 0); + } + + return (true, price, payment); + } + + /// + /// Prints bills and coins for given change. + /// + /// + static void PrintChange(double change) + { + var tens = (int)(change / 10); + if (tens > 0) + Console.WriteLine($"{tens} ten dollar bill(s)"); + + var temp = change - (tens * 10); + var fives = (int)(temp / 5); + if (fives > 0) + Console.WriteLine($"{fives} five dollar bill(s)"); + + temp -= fives * 5; + var ones = (int)temp; + if (ones > 0) + Console.WriteLine($"{ones} one dollar bill(s)"); + + temp -= ones; + var cents = temp * 100; + var half = (int)(cents / 50); + if (half > 0) + Console.WriteLine($"{half} one half dollar(s)"); + + temp = cents - (half * 50); + var quarters = (int)(temp / 25); + if (quarters > 0) + Console.WriteLine($"{quarters} quarter(s)"); + + temp -= quarters * 25; + var dimes = (int)(temp / 10); + if (dimes > 0) + Console.WriteLine($"{dimes} dime(s)"); + + temp -= dimes * 10; + var nickels = (int)(temp / 5); + if (nickels > 0) + Console.WriteLine($"{nickels} nickel(s)"); + + temp -= nickels * 5; + var pennies = (int)(temp + 0.5); + if (pennies > 0) + Console.WriteLine($"{pennies} penny(s)"); + } + + static void Main(string[] args) + { + Header(); + + while (true) + { + (bool result, double price, double payment) = GetInput(); + if (!result) + continue; + + var change = payment - price; + if (change == 0) + { + Console.WriteLine("Correct amount, thank you!"); + continue; + } + + if (change < 0) + { + Console.WriteLine($"Sorry, you have short-changed me ${price - payment:N2}!"); + continue; + } + + Console.WriteLine($"Your change ${change:N2}"); + PrintChange(change); + Console.WriteLine("Thank you, come again!"); + Console.WriteLine(); + } + } + } +} diff --git a/29_Craps/python/craps.py b/29_Craps/python/craps.py new file mode 100644 index 00000000..38faeed3 --- /dev/null +++ b/29_Craps/python/craps.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""This game simulates the games of craps played according to standard Nevada craps table rules. + +That is: + +1. A 7 or 11 on the first roll wins +2. A 2, 3, or 12 on the first roll loses +3. Any other number rolled becomes your "point." You continue to roll; if you get your point you win. If you + roll a 7, you lose and the dice change hands when this happens. + +This version of craps was modified by Steve North of Creative Computing. It is based on an original which +appeared one day one a computer at DEC. +""" +from random import randint + + +def throw_dice(): + return randint(1, 6) + randint(1, 6) + + +print(" " * 33 + "Craps") +print(" " * 15 + "Creative Computing Morristown, New Jersey") +print() +print() +print() + +winnings = 0 +print("2,3,12 are losers; 4,5,6,8,9,10 are points; 7,11 are natural winners.") + +play_again = True +while play_again: + wager = int(input("Input the amount of your wager: ")) + + print("I will now throw the dice") + roll_1 = throw_dice() + + if roll_1 in [7, 11]: + print(f"{roll_1} - natural.... a winner!!!!") + print(f"{roll_1} pays even money, you win {wager} dollars") + winnings += wager + elif roll_1 == 2: + print(f"{roll_1} - snake eyes.... you lose.") + print(f"You lose {wager} dollars") + winnings -= wager + elif roll_1 in [3, 12]: + print(f"{roll_1} - craps.... you lose.") + print(f"You lose {wager} dollars") + winnings -= wager + else: + print(f"{roll_1} is the point. I will roll again") + roll_2 = 0 + while roll_2 not in [roll_1, 7]: + roll_2 = throw_dice() + if roll_2 == 7: + print(f"{roll_2} - craps. You lose.") + print(f"You lose $ {wager}") + winnings -= wager + elif roll_2 == roll_1: + print(f"{roll_1} - a winner.........congrats!!!!!!!!") + print(f"{roll_1} at 2 to 1 odds pays you...let me see... {2 * wager} dollars") + winnings += 2 * wager + else: + print(f"{roll_2} - no point. I will roll again") + + m = input(" If you want to play again print 5 if not print 2: ") + if winnings < 0: + print(f"You are now under ${-winnings}") + elif winnings > 0: + print(f"You are now ahead ${winnings}") + else: + print("You are now even at 0") + play_again = (m == "5") + +if winnings < 0: + print(f"Too bad, you are in the hole. Come again.") +elif winnings > 0: + print(f"Congratulations---you came out a winner. Come again.") +else: + print(f"Congratulations---you came out even, not bad for an amateur") diff --git a/31_Depth_Charge/ruby/.editorconfig b/31_Depth_Charge/ruby/.editorconfig new file mode 100644 index 00000000..9d7e93d0 --- /dev/null +++ b/31_Depth_Charge/ruby/.editorconfig @@ -0,0 +1,69 @@ +# EditorConfig is awesome: https://EditorConfig.org +# .editorconfig + +# Please see doc/developer_notes.md +# If you find anything egregious or missing, please consider submitting a pull request +# to https://github.com/theias/ias_package_shell + + +# top-most EditorConfig file +root = true + +# Sensible defaults for everything +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true + +# JavaScript +[**.js] +indent_style = space +indent_size = 2 +insert_final_newline = true + +# Ruby +[**.rb] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +# Python +[**.py] +charset = utf-8 +indent_style = space +indent_size = 4 +insert_final_newline = true + +# Perl +[**.pl] +charset = utf-8 +insert_final_newline = true +[**.pm] +charset = utf-8 +insert_final_newline = true + +# PHP +[**.php] +charset = utf-8 +indent_size = 4 +indent_style = space +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true + +# Makefiles +[Makefile] +indent_style = tab + +[**.gmk] +indent_style = tab + +# Configuration Files +# Matches the exact files either package.json or .travis.yml +[{package.json,.travis.yml}] +indent_style = space +indent_size = 2 + +# Diff files +[*.{diff,patch}] +trim_trailing_whitespace = false diff --git a/31_Depth_Charge/ruby/depthcharge.rb b/31_Depth_Charge/ruby/depthcharge.rb new file mode 100755 index 00000000..5ba36ba6 --- /dev/null +++ b/31_Depth_Charge/ruby/depthcharge.rb @@ -0,0 +1,211 @@ +#!/usr/bin/ruby + +class DepthCharge + + def run_game + output_title() + while true + printf("----------\n") + print_instructions() + setup_game() + printf("\n") + game_loop() + break if ! get_input_another_game() + end + + # 420 PRINT "OK. HOPE YOU ENJOYED YOURSELF." : GOTO 600 + printf("OK. HOPE YOU ENJOYED YOURSELF.\n") + end + + def output_title + printf("--- DEPTH CHARGE ---\n") + printf("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n") + printf("\n") + end + + def get_input_y_or_n(message) + while true + print(message) + + value = gets.chomp + + if (value == 'Y' || value == 'y') + return true + elsif value == 'N' || value == 'n' + return false + end + + printf("PLEASE ENTER Y/y OR N/n...\n\n") + end + end + + def get_input_positive_integer(message) + + while true + print(message) + value = gets.chomp + if (value == 'd') + debug_game() + next + end + + the_input = Integer(value) rescue nil + + if the_input == nil || the_input < 0 + printf("PLEASE ENTER A POSITIVE NUMBER\n\n") + next + + end + + return the_input + 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 +MISSION IS TO DESTROY IT. + +SPECIFY DEPTH CHARGE EXPLOSION POINT WITH A +TRIO OF NUMBERS -- THE FIRST TWO ARE THE +SURFACE COORDINATES (X, Y): + WEST < X < EAST + SOUTH < Y < NORTH + +THE THIRD IS THE DEPTH (Z): + SHALLOW < Z < DEEP + +GOOD LUCK ! + + INSTRUCTIONS + ) + end + + def debug_game + printf("@enemy_x: %d\n", @enemy_x) + printf("@enemy_y: %d\n", @enemy_y) + printf("@enemy_z: %d\n", @enemy_z) + printf("@num_tries: %d\n", @num_tries) + printf("@trial: %d\n", @trial) + printf("\n") + end + + def setup_game + get_search_area_dimension() + 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 + + 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() + + @shot_x = get_input_positive_integer("X: ") + @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 \ + + (@enemy_z - @shot_z).abs \ + == 0 + ) + you_win() + return + else + # 140 GOSUB 500 : PRINT : NEXT D + missed_shot() + end + end + + printf("\n") + + you_lose() + + end + + def output_game_status + printf("YOU HAVE %d SHOTS REMAINING.\n", @num_tries - @trial + 1) + printf("TRIAL \#%d\n", @trial) + end + def you_win + printf("B 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 + +game = DepthCharge.new +game.run_game() diff --git a/36_Flip_Flop/python/flipflop.py b/36_Flip_Flop/python/flipflop.py new file mode 100644 index 00000000..9b6e2323 --- /dev/null +++ b/36_Flip_Flop/python/flipflop.py @@ -0,0 +1,129 @@ +# Flip Flop +# +# The object of this game is to change a row of ten X's +# X X X X X X X X X X +# to a row of ten O's: +# O O O O O O O O O O +# by typing in a number corresponding +# to the position of an "X" in the line. On +# some numbers one position will +# change while on other numbers, two +# will change. For example, inputting a 3 +# may reverse the X and O in position 3, +# but it might possibly reverse some +# other position too! You ought to be able +# to change all 10 in 12 or fewer +# moves. Can you figure out a good win- +# ning strategy? +# To reset the line to all X's (same +# game), type 0 (zero). To start a new +# game at any point, type 11. +# The original author of this game was +# Michael Kass of New Hyde Park, New +# York. +import random +import math +from typing import Callable, List, Tuple + +flip_dict = {"X": "O", "O": "X"} + + +def flip_bits( + row: List[str], m: int, n: int, r_function: Callable[[int], float] +) -> Tuple[List[str], int]: + """ + Function that flips the positions at the computed steps + """ + while m == n: + r = r_function(n) + n = r - int(math.floor(r)) + n = int(10 * n) + if row[n] == "X": + row[n] = "O" + break + elif row[n] == "O": + row[n] = "X" + return row, n + + +def print_instructions(): + print(" " * 32 + "FLIPFLOP") + print(" " * 15 + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + print("\n" * 2) + print("THE OBJECT OF THIS PUZZLE IS TO CHANGE THIS:\n") + print("X X X X X X X X X X\n") + print("TO THIS:\n") + print("O O O O O O O O O O\n") + print("BY TYPING TH NUMBER CORRESPONDING TO THE POSITION OF THE") + print("LETTER ON SOME NUMBERS, ONE POSITION WILL CHANGE, ON") + print("OTHERS, TWO WILL CHANGE. TO RESET LINE TO ALL X'S, TYPE 0") + print("(ZERO) AND TO START OVER IN THE MIDDLE OF A GAME, TYPE ") + print("11 (ELEVEN).\n") + + +def main(): + q = random.random() + + print("HERE IS THE STARTING LINE OF X'S.\n") + # We add an extra 0-th item because this sometimes is set to something + # but we never check what it is for completion of the puzzle + row = [""] + ["X"] * 10 + counter_turns = 0 + n = -1 + legal_move = True + while row[1:] != ["O"] * 10: + if legal_move: + print(" ".join([str(i) for i in range(1, 11)])) + print(" ".join(row[1:]) + "\n") + m = input("INPUT THE NUMBER\n") + try: + m = int(m) + if m > 11 or m < 0: + raise ValueError() + except ValueError: + print("ILLEGAL ENTRY--TRY AGAIN") + legal_move = False + continue + legal_move = True + if m == 11: + # completely reset the puzzle + counter_turns = 0 + row = [""] + ["X"] * 10 + q = random.random() + continue + elif m == 0: + # reset the board, but not the counter or the random number + row = [""] + ["X"] * 10 + elif m == n: + row[n] = flip_dict[row[n]] + r_function = lambda n_t: 0.592 * (1 / math.tan(q / n_t + q)) / math.sin( + n_t * 2 + q + ) - math.cos(n_t) + row, n = flip_bits(row, m, n, r_function) + else: + n = m + row[n] = flip_dict[row[n]] + r_function = lambda n_t: ( + math.tan(q + n_t / q - n_t) + - math.sin(n_t * 2 + q) + + 336 * math.sin(8 * n_t) + ) + row, n = flip_bits(row, m, n, r_function) + + counter_turns += 1 + print() + + if counter_turns <= 12: + print(f"VERY GOOD. YOU GUESSED IT IN ONLY {counter_turns} GUESSES.") + else: + print(f"TRY HARDER NEXT TIME. IT TOOK YOU {counter_turns} GUESSES.") + return + + +if __name__ == "__main__": + print_instructions() + + another = "" + while another != "NO": + main() + another = input("DO YOU WANT TO TRY ANOTHER PUZZLE\n") diff --git a/45_Hello/perl/hello.pl b/45_Hello/perl/hello.pl new file mode 100644 index 00000000..53590595 --- /dev/null +++ b/45_Hello/perl/hello.pl @@ -0,0 +1,136 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +print ' ' x 33 . "HELLO\n"; +print ' ' x 15 . "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n"; +print "\n\n\n"; + +print "HELLO. MY NAME IS CREATIVE COMPUTER.\n\n\n"; +print "WHAT'S YOUR NAME?\n"; +chomp( my $N = uc ); + +print "\nHI THERE, $N, ARE YOU ENJOYING YOURSELF HERE?\n"; + +GREET: +{ + chomp( my $B = uc ); + print "\n"; + + if ( $B eq 'YES' ) { + print "I'M GLAD TO HEAR THAT, $N.\n\n"; + } + elsif ( $B eq 'NO' ) { + print "OH, I'M SORRY TO HEAR THAT, $N. MAYBE WE CAN\n"; + print "BRIGHTEN UP YOUR VISIT A BIT.\n"; + } + else { + print "$N, I DON'T UNDERSTAND YOUR ANSWER OF '$B'.\n"; + print "PLEASE ANSWER 'YES' OR 'NO'. DO YOU LIKE IT HERE?\n"; + redo GREET; + } +} + +print "\nSAY, $N, I CAN SOLVE ALL KINDS OF PROBLEMS EXCEPT\n"; +print "THOSE DEALING WITH GREECE. WHAT KIND OF PROBLEMS DO\n"; +print "YOU HAVE (ANSWER SEX, HEALTH, MONEY, OR JOB)?\n"; + +ADVICE: +{ + chomp( my $C = uc ); + print "\n"; + + if ( $C eq 'SEX' ) { + print "IS YOUR PROBLEM TOO MUCH OR TOO LITTLE?\n"; + + SEX: + { + chomp( my $D = uc ); + print "\n"; + + if ( $D eq 'TOO MUCH' ) { + print "YOU CALL THAT A PROBLEM?!! I SHOULD HAVE SUCH PROBLEMS!\n"; + print "IF IT BOTHERS YOU, $N, TAKE A COLD SHOWER.\n"; + } + elsif ( $D eq 'TOO LITTLE' ) { + print "WHY ARE YOU HERE IN SUFFERN, $N? YOU SHOULD BE\n"; + print "IN TOKYO OR NEW YORK OR AMSTERDAM OR SOMEPLACE WITH SOME\n"; + print "REAL ACTION.\n"; + } + else { + print "DON'T GET ALL SHOOK, $N, JUST ANSWER THE QUESTION\n"; + print "WITH 'TOO MUCH' OR 'TOO LITTLE'. WHICH IS IT?\n"; + redo SEX; + } + } + } + elsif ( $C eq 'HEALTH' ) { + print "MY ADVICE TO YOU $N IS:\n"; + print " 1. TAKE TWO ASPRIN\n"; + print " 2. DRINK PLENTY OF FLUIDS (ORANGE JUICE, NOT BEER!)\n"; + print " 3. GO TO BED (ALONE)\n"; + } + elsif ( $C eq 'MONEY' ) { + print "SORRY, $N, I'M BROKE TOO. WHY DON'T YOU SELL\n"; + print "ENCYCLOPEADIAS OR MARRY SOMEONE RICH OR STOP EATING\n"; + print "SO YOU WON'T NEED SO MUCH MONEY?\n"; + } + elsif ( $C eq 'JOB' ) { + print "I CAN SYMPATHIZE WITH YOU $N. I HAVE TO WORK\n"; + print "VERY LONG HOURS FOR NO PAY -- AND SOME OF MY BOSSES\n"; + print "REALLY BEAT ON MY KEYBOARD. MY ADVICE TO YOU, $N,\n"; + print "IS TO OPEN A RETAIL COMPUTER STORE. IT'S GREAT FUN.\n"; + } + else { + print "OH, $N, YOUR ANSWER OF '$C' IS GREEK TO ME.\n"; + } + + MORE: + { + print "\nANY MORE PROBLEMS YOU WANT SOLVED, $N?\n"; + chomp( my $E = uc ); + print "\n"; + + if ( $E eq 'YES' ) { + print "WHAT KIND (SEX, MONEY, HEALTH, JOB)?\n"; + redo ADVICE; + } + elsif ( $E eq 'NO' ) { + print "\nTHAT WILL BE \$5.00 FOR THE ADVICE, $N.\n"; + print "PLEASE LEAVE THE MONEY ON THE TERMINAL.\n"; + } + else { + print "JUST A SIMPLE 'YES' OR 'NO' PLEASE, $N.\n"; + redo MORE; + } + } + + sleep 2; + print "\n\n\n"; + + MONEY: + { + print "DID YOU LEAVE THE MONEY?\n"; + chomp( my $G = uc ); + print "\n"; + + if ( $G eq 'YES' ) { + print "HEY, $N??? YOU LEFT NO MONEY AT ALL!\n"; + print "YOU ARE CHEATING ME OUT OF MY HARD-EARNED LIVING.\n"; + print "\nWHAT A RIP OFF, $N!!!\n\n"; + } + elsif ( $G eq 'NO' ) { + print "THAT'S HONEST, $N, BUT HOW DO YOU EXPECT\n"; + print "ME TO GO ON WITH MY PSYCHOLOGY STUDIES IF MY PATIENTS\n"; + print "DON'T PAY THEIR BILLS?\n"; + } + else { + print "YOUR ANSWER OF '$G' CONFUSES ME, $N.\n"; + print "PLEASE RESPOND WITH 'YES' OR 'NO'.\n"; + redo MONEY; + } + + print "\nTAKE A WALK, $N.\n\n\n"; + } +} diff --git a/90_Tower/python/tower_test.py b/90_Tower/python/tower_test.py new file mode 100644 index 00000000..a7023a0f --- /dev/null +++ b/90_Tower/python/tower_test.py @@ -0,0 +1,49 @@ +import unittest +import tower + +class MyTestCase(unittest.TestCase): + def test_something(self): + t = tower.Tower() + self.assertTrue(t.empty()) + + d = tower.Disk(3) + t.add(d) + self.assertFalse(t.empty()) + + d5 = tower.Disk(5) + self.assertRaises(Exception, t.add, d5) + self.assertFalse(t.empty()) + + def test_oksize(self): + t = tower.Tower() + self.assertTrue(t.empty()) + + d5 = tower.Disk(5) + t.add(d5) + self.assertFalse(t.empty()) + + d3 = tower.Disk(3) + t.add(d3) + self.assertFalse(t.empty()) + + self.assertEqual(t.top(), d3) + self.assertEqual(t.pop(), d3) + self.assertEqual(t.pop(), d5) + + def test_game(self): + g = tower.Game() + self.assertEqual(g.moves(), 0) + self.assertFalse(g.winner()) + + def test_format(self): + t = tower.Tower() + d3 = tower.Disk(3) + d5 = tower.Disk(5) + t.add(d5) + t.add(d3) + + f = t.vertical_format(6, 3) + self.assertEqual(f, [' ', '[ 3 ] ', '[ 5 ] ']) + +if __name__ == '__main__': + unittest.main() diff --git a/91_Train/csharp/Train/Train.sln b/91_Train/csharp/Train/Train.sln new file mode 100644 index 00000000..7735a737 --- /dev/null +++ b/91_Train/csharp/Train/Train.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31129.286 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrainGame", "Train\TrainGame.csproj", "{42617537-4E7C-4082-A17B-7F18DFA04C35}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrainTests", "..\TrainTests\TrainTests\TrainTests.csproj", "{7C740A47-99C6-44E1-BDEE-140086BCFE8B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {42617537-4E7C-4082-A17B-7F18DFA04C35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42617537-4E7C-4082-A17B-7F18DFA04C35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42617537-4E7C-4082-A17B-7F18DFA04C35}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42617537-4E7C-4082-A17B-7F18DFA04C35}.Release|Any CPU.Build.0 = Release|Any CPU + {7C740A47-99C6-44E1-BDEE-140086BCFE8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C740A47-99C6-44E1-BDEE-140086BCFE8B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C740A47-99C6-44E1-BDEE-140086BCFE8B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C740A47-99C6-44E1-BDEE-140086BCFE8B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {919F73B8-DE34-4992-9B05-E1FEC2D2F7C6} + EndGlobalSection +EndGlobal diff --git a/91_Train/csharp/Train/Train/TrainGame.cs b/91_Train/csharp/Train/Train/TrainGame.cs new file mode 100644 index 00000000..98e0db68 --- /dev/null +++ b/91_Train/csharp/Train/Train/TrainGame.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; + +namespace Train +{ + public class TrainGame + { + private Random Rnd { get; } = new Random(); + private readonly int ALLOWED_PERCENTAGE_DIFFERENCE = 5; + + static void Main() + { + TrainGame train = new TrainGame(); + train.GameLoop(); + } + + public void GameLoop() + { + DisplayIntroText(); + + do + { + PlayGame(); + } while (TryAgain()); + } + + private void PlayGame() + { + int carSpeed = (int)GenerateRandomNumber(40, 25); + int timeDifference = (int)GenerateRandomNumber(5, 15); + int trainSpeed = (int)GenerateRandomNumber(20, 19); + + Console.WriteLine($"A CAR TRAVELING {carSpeed} MPH CAN MAKE A CERTAIN TRIP IN"); + Console.WriteLine($"{timeDifference} HOURS LESS THAN A TRAIN TRAVELING AT {trainSpeed} MPH"); + Console.WriteLine("HOW LONG DOES THE TRIP TAKE BY CAR?"); + + double userInputCarJourneyDuration = double.Parse(Console.ReadLine()); + double actualCarJourneyDuration = CalculateCarJourneyDuration(carSpeed, timeDifference, trainSpeed); + int percentageDifference = CalculatePercentageDifference(userInputCarJourneyDuration, actualCarJourneyDuration); + + if (IsWithinAllowedDifference(percentageDifference, ALLOWED_PERCENTAGE_DIFFERENCE)) + { + Console.WriteLine($"GOOD! ANSWER WITHIN {percentageDifference} PERCENT."); + } + else + { + Console.WriteLine($"SORRY. YOU WERE OFF BY {percentageDifference} PERCENT."); + } + Console.WriteLine($"CORRECT ANSWER IS {actualCarJourneyDuration} HOURS."); + } + + public static bool IsWithinAllowedDifference(int percentageDifference, int allowedDifference) + { + return percentageDifference <= allowedDifference; + } + + private static int CalculatePercentageDifference(double userInputCarJourneyDuration, double carJourneyDuration) + { + return (int)(Math.Abs((carJourneyDuration - userInputCarJourneyDuration) * 100 / userInputCarJourneyDuration) + .5); + } + + public static double CalculateCarJourneyDuration(double carSpeed, double timeDifference, double trainSpeed) + { + return timeDifference * trainSpeed / (carSpeed - trainSpeed); + } + + public double GenerateRandomNumber(int baseSpeed, int multiplier) + { + return multiplier * Rnd.NextDouble() + baseSpeed; + } + + private bool TryAgain() + { + Console.WriteLine("ANOTHER PROBLEM (YES OR NO)? "); + return IsInputYes(Console.ReadLine()); + } + + public static bool IsInputYes(string consoleInput) + { + var options = new string[] { "Y", "YES" }; + return options.Any(o => o.Equals(consoleInput, StringComparison.CurrentCultureIgnoreCase)); + } + + private void DisplayIntroText() + { + Console.WriteLine("TRAIN"); + Console.WriteLine("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); + Console.WriteLine(); + Console.WriteLine("TIME - SPEED DISTANCE EXERCISE"); + Console.WriteLine(); + } + } +} diff --git a/91_Train/csharp/Train/Train/TrainGame.csproj b/91_Train/csharp/Train/Train/TrainGame.csproj new file mode 100644 index 00000000..c73e0d16 --- /dev/null +++ b/91_Train/csharp/Train/Train/TrainGame.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp3.1 + + + diff --git a/91_Train/csharp/TrainTests/TrainTests/TrainGameTests.cs b/91_Train/csharp/TrainTests/TrainTests/TrainGameTests.cs new file mode 100644 index 00000000..a9804283 --- /dev/null +++ b/91_Train/csharp/TrainTests/TrainTests/TrainGameTests.cs @@ -0,0 +1,53 @@ +using Train; +using Xunit; + +namespace TrainTests +{ + public class TrainGameTests + { + [Fact] + public void MiniumRandomNumber() + { + TrainGame game = new TrainGame(); + Assert.True(game.GenerateRandomNumber(10, 10) >= 10); + } + + [Fact] + public void MaximumRandomNumber() + { + TrainGame game = new TrainGame(); + Assert.True(game.GenerateRandomNumber(10, 10) <= 110); + } + + [Fact] + public void IsInputYesWhenY() + { + Assert.True(TrainGame.IsInputYes("y")); + } + + [Fact] + public void IsInputYesWhenNotY() + { + Assert.False(TrainGame.IsInputYes("a")); + } + + [Fact] + public void CarDurationTest() + { + Assert.Equal(1, TrainGame.CalculateCarJourneyDuration(30, 1, 15) ); + } + + [Fact] + public void IsWithinAllowedDifference() + { + Assert.True(TrainGame.IsWithinAllowedDifference(5,5)); + } + + + [Fact] + public void IsNotWithinAllowedDifference() + { + Assert.False(TrainGame.IsWithinAllowedDifference(6, 5)); + } + } +} diff --git a/91_Train/csharp/TrainTests/TrainTests/TrainTests.csproj b/91_Train/csharp/TrainTests/TrainTests/TrainTests.csproj new file mode 100644 index 00000000..a8de6dde --- /dev/null +++ b/91_Train/csharp/TrainTests/TrainTests/TrainTests.csproj @@ -0,0 +1,26 @@ + + + + netcoreapp3.1 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/96_Word/perl/word.pl b/96_Word/perl/word.pl new file mode 100755 index 00000000..5369cc71 --- /dev/null +++ b/96_Word/perl/word.pl @@ -0,0 +1,169 @@ +#!/usr/bin/env perl + +use 5.010; # To get 'state' and 'say' + +use strict; # Require explicit declaration of variables +use warnings; # Enable optional compiler warnings + +use English; # Use more friendly names for Perl's magic variables +use Term::ReadLine; # Prompt and return user input + +our $VERSION = '0.000_01'; + +print <<'EOD'; + WORD + Creative Computing Morristown, New Jersey + + + +I am thinking of a word -- you guess it. I will give you +clues to help you get it. Good luck!! + + +EOD + +# Read the content of __DATA__, remove the trailing newlines, and store +# each line into @words. Stop at __END__, since Perl does not see this +# as an end-of-file. +my @words; +while ( ) { + chomp; + last if $ARG eq '__END__'; + push @words, lc $ARG; # Normalize case to lower. +} + +# This loop represents an actual game. We execute it until the player +# does something that makes us explicitly break out. +while ( 1 ) { + print <<'EOD'; + + +You are starting a new game ... +EOD + + # Choose a random target word. The rand() function returns a number + # from 0 to its argument, and coerces its argument to a scalar. In + # scalar context, an array evaluates to the number of elements it + # contains. + my $target = $words[ rand @words ]; + + # We generalize the code by using the actual length of the target. + my $target_length = length $target; + + my $count = 0; # Number of guesses + + # Make an array of the individual letters in the target. We will + # iterate over this to determine matching letters. + my @target_array = split qr<>, $target; + + # Make a hash of those letters. We will use this to determine common + # letters. Any true value will do for the value of the hash. By + # making use of this hash we avoid the nested loops of the original + # BASIC program. + my %target_hash = map { $ARG => 1 } @target_array; + + # We keep prompting the player until we get a response that causes + # us to break out of the loop. + while ( 1 ) { + + # Create the readline object. The state keyword means the + # variable is only initialized once, no matter how many times + # execution passes this point. + state $term = Term::ReadLine->new( 'word' ); + + # Read the next guess. A return of undef means end-of-file. + my $guess = $term->readline( "Guess a $target_length letter word: " ); + exit unless defined $guess; + + last if $guess eq '?'; # A question mark means we give up + if ( length( $guess ) != $target_length ) { + # Wrong length. Ask again. + say "You must guess a $target_length letter word. Try again."; + redo; # Redo the innermost loop + } + + $guess = lc $guess; # Lower-case the guess + $count++; # Count another guess + + if ( $guess eq $target ) { + # We guessed the word. + say "You have guessed the word. It took $count guesses!"; + my $answer = $term->readline( 'Want to play again? [y/N]: '); + exit unless defined $guess; # End of file + exit unless $guess =~ m/ \A y /smxi; + last; # Exit the innermost loop. + } + + my @common_letters; # Letters common to guess and target + my $match = '-' x length $target; # Assume no matches + my $inx = 0; # Iterator + foreach my $letter ( split qr<>, $guess ) { + if ( $target_hash{$letter} ) { + # If the letter is in the hash, it occurs in the target + push @common_letters, $letter; + # If it is at the current position in the target, it is + # an actual match. + $target_array[$inx] eq $letter + and substr $match, $inx, 1, $letter; + } + $inx++; + } + + say 'There were ', scalar @common_letters, + ' matches and the common letters were... ', @common_letters; + say "From the exact letter matches, you know................ $match"; + say ''; + say q; + redo; + } + +} +__DATA__ +dinky +smoke +water +grass +train +might +first +candy +champ +would +clump +dopey +__END__ + +=head1 TITLE + +word.pl - Play the game 'word' from Basic Computer Games + +=head1 SYNOPSIS + + word.pl + +=head1 DETAILS + +This Perl script is a port of C, which is the 96th entry in Basic +Computer Games. + +=head1 PORTED BY + +Thomas R. Wyant, III F + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2022 by Thomas R. Wyant, III + +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl 5.10.0. For more details, see the Artistic +License 1.0 at +L, and/or the +Gnu GPL at L. + +This program is distributed in the hope that it will be useful, but +without any warranty; without even the implied warranty of +merchantability or fitness for a particular purpose. + +=cut + +# ex: set expandtab tabstop=4 textwidth=72 :