Go Fish/Nim
Appearance
< Go Fish
We use the module "playing_cards" from task https://rosettacode.org/wiki/Playing_cards.
import strutils, strformat
import playing_cards
const
Human = 0
Computer = 1
type
Player = range[Human..Computer]
SortedHand = array[Rank, set[Suit]] # Map card ranks to sets of suits in hand.
Game = object
deck: Deck # Current content of deck.
hands: array[Player, SortedHand] # Player hands.
bookCounts: array[Player, Natural] # Number of books.
prevChoice: Rank # Previous choice done by computer.
const Quit = -1 # Special value used to indicate to quit.
#---------------------------------------------------------------------------------------------------
template other(player: Player): Player = 1 - player
#---------------------------------------------------------------------------------------------------
proc toSortedHand(hand: Hand): SortedHand =
## Convert a hand (which is as a sequence of cards) to a "SortedHand".
for card in hand:
result[card.rank].incl(card.suit)
#---------------------------------------------------------------------------------------------------
proc `$`(hand: SortedHand): string =
## Return the representation of a "SortedHand".
for rank, suits in hand:
for suit in suits:
result.addSep(" ")
result.add $(rank: rank, suit: suit)
#---------------------------------------------------------------------------------------------------
proc askQuestion(question: string; answers: openArray[string]): int =
## Ask a question and return the index in the possible answers.
## Return the value "Quit" if an end of file is encountered.
try:
while true:
stdout.write question, ' '
let input = stdin.readLine()
result = answers.find(input)
if result >= 0: return
echo "I don’t understand your answer"
except EOFError:
result = Quit
#---------------------------------------------------------------------------------------------------
proc pronoun(player: Player): string {.inline.} =
## Return the pronoun to use according to the player.
if player == Human: "You" else: "I"
#---------------------------------------------------------------------------------------------------
proc checkBookCompleted(game: var Game; player: Player; rank: Rank) =
## Check if a book is completed.
if game.hands[player][rank].len == 4:
game.hands[player][rank] = {}
inc game.bookCounts[player]
echo &"{player.pronoun} completed the book for rank: {rank}"
#---------------------------------------------------------------------------------------------------
proc findChoices(hand: SortedHand): seq[string] =
## Find the possible choices for ranks according to the "SortedHand".
for rank, suits in hand:
if suits.len != 0: result.add $rank
#---------------------------------------------------------------------------------------------------
proc updateHands(game: var Game; player: Player; rank: Rank): Player =
## Update the hands after a player has asked for a rank.
let otherPlayer = player.other
# Search in other player hand.
if game.hands[otherPlayer][rank].len != 0:
# Transfer cards to player.
var cards: seq[Card]
for suit in game.hands[otherPlayer][rank]: cards.add (rank, suit)
echo &"{player.pronoun} get: {cards}"
game.hands[player][rank] = game.hands[player][rank] + game.hands[otherPlayer][rank]
game.hands[otherPlayer][rank] = {}
# Check if a book is completed.
game.checkBookCompleted(player, rank)
result = player
else:
# No cards available. Draw a card from deck.
echo "No luck."
let card = game.deck.draw()
if player == Human: echo "You draw the card: ", card
else: echo "I draw a card"
game.hands[player][card.rank].incl(card.suit)
game.checkBookCompleted(player, card.rank)
result = otherPlayer
#---------------------------------------------------------------------------------------------------
proc humanPlay(game: var Game): Player =
## Process human turn.
echo "Your hand: ", game.hands[Human]
var choices = game.hands[Human].findChoices()
if choices.len == 0:
# Special case if we have no cards in our hand.
if game.deck.len == 0: return Computer # Nothing to do.
let card = game.deck.draw()
echo "You draw the card: ", card
game.hands[Human][card.rank].incl(card.suit)
choices = game.hands[Human].findChoices()
let choiceString = choices.join(", ")
let answer = askQuestion(&"What is your choice ({choiceString})?", choices)
if answer == Quit:
echo ""
quit("Quitting")
let choice = parseEnum[Rank](choices[answer])
result = game.updateHands(Human, choice)
#---------------------------------------------------------------------------------------------------
proc computerPlay(game: var Game): Player =
## Process computer turn.
# The simple (but not so bad) strategy used consists to choose the rank
# immediately succeeding to the previous used rank (wrapping around if needed).
var choice = game.prevChoice
while true:
choice = if choice == King: Ace else: succ(choice)
if game.hands[Computer][choice].len != 0 or choice == game.prevChoice: break
if game.hands[Computer][choice].len == 0:
# Special case if the computer has no cards in hand.
if game.deck.len == 0: return Human # Nothing to do.
let card = game.deck.draw()
echo "I draw a card"
game.hands[Computer][card.rank].incl(card.suit)
choice = card.rank
game.prevChoice = choice
echo "I want cards of rank: ", choice
result = game.updateHands(Computer, choice)
#---------------------------------------------------------------------------------------------------
proc playGame(firstPlayer: Player) =
## Play a game.
var game: Game
game.deck = initDeck()
game.deck.shuffle()
let handSeqs = game.deck.deal(2, 9)
game.hands = [handSeqs[Human].toSortedHand(), handSeqs[Computer].toSortedHand()]
game.prevChoice = Ace
echo &"{firstPlayer.pronoun} play first.\n"
# Search for books before starting.
for rank in Rank:
game.checkBookCompleted(Human, rank)
game.checkBookCompleted(Computer, rank)
# Play.
var player = firstPlayer
while game.bookCounts[Human] + game.bookCounts[Computer] != 13:
echo &"Your books: {game.bookCounts[Human]} My books: {game.bookCounts[Computer]}"
if player == Human:
player = game.humanPlay()
else:
player = game.computerPlay()
echo ""
if game.bookCounts[Human] > game.bookCounts[Computer]:
echo &"You win with {game.bookCounts[Human]} books against {game.bookCounts[Computer]}"
else:
echo &"I win with {game.bookCounts[Computer]} books against {game.bookCounts[Human]}"
#———————————————————————————————————————————————————————————————————————————————————————————————————
var firstPlayer: Player
let answer = askQuestion("Who will play first ([h]uman/[c]omputer)?", ["h", "c"])
if answer == Quit:
echo ""
quit("Quitting")
firstPlayer = Player(answer)
while true:
playGame(firstPlayer)
let answer = askQuestion("Do you want to play another game ([y]es/[n]o)? ", ["y", "n"])
if answer == 1: break
firstPlayer = firstPlayer.other()