Go Fish/Python: Difference between revisions

From Rosetta Code
Content added Content deleted
m (Categorization now in master page)
No edit summary
Line 1: Line 1:
{{collection|Go Fish}}
{{collection|Go Fish}}


===OOP===
{{works with|Python|2.5}}
{{works with|Python|2.5}}
<lang Python>import random
<lang Python>import random
Line 143: Line 144:
game = PlayGoFish()
game = PlayGoFish()
game.play()</lang>
game.play()</lang>

===Procedural===
{{works with|Python|3.6}}
<lang Python>"""
Plays a Go Fish game between a user and computer
following the rules specified at
http://www.rosettacode.org/wiki/Go_Fish
"""
import itertools
import random
from copy import deepcopy
from operator import attrgetter
from typing import (Counter,
Iterable,
Iterator,
List,
NamedTuple,
NewType,
Sequence,
Set,
Tuple,
TypeVar)

Card = NewType('Card', str)
Hand = Counter[Card]
Deck = Tuple[Card, ...]
Watchlist = Set[Card]
T = TypeVar('T')

SUITS_COUNT = 4
RANKS = ('2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A')
DECK = RANKS * SUITS_COUNT

EMPTY_HAND_MESSAGE = "{} is without cards. They go fishing."
CARD_REQUEST_MESSAGE = "{player_name} asks for {card}"
NO_CARD_MESSAGE = "{player_name} doesn't have {card}"
BOOK_COMPLETED_MESSAGE = "{player_name} completed the book of {card}'s."
FISHING_RESULT_MESSAGE = "{player_name} fished a {card}"


class Player(NamedTuple):
name: str
hand: Hand
is_human: bool
score: int = 0
is_lucky: bool = True # if False, turn goes to another player
watchlist: Watchlist = set() # used by AI to track enemy's cards


def play(deck: Deck = DECK,
*,
first_hand_count: int = 9,
lucky_fishing: bool = False) -> None:
"""
Plays the Go Fish game according to
http://www.rosettacode.org/wiki/Go_Fish
:param deck: deck from where the cards are drawn
:param first_hand_count: starting amount of cards for a player
:param lucky_fishing: if True, continue playing
after fishing the card from the deck
that was asked from an opponent
"""
deck = shuffle(deck)
deck, (hand, other_hand) = deal_first_hands(deck, count=first_hand_count)
human = Player(name=input('Name yourself: '),
hand=hand,
is_human=True)
ai = Player(name='Computer',
hand=other_hand,
is_human=False)
human = check_hand_for_books(human)
ai = check_hand_for_books(ai)

player, opponent = ai, human
for turn in itertools.count(1):
player, opponent = opponent, player
print_turn_stats(turn=turn,
players=(player, opponent),
cards_left=len(deck))
while cards_in_game(deck, hands=(player.hand, opponent.hand)):
player, opponent, deck = play_turn(player,
opponent,
deck,
lucky_fishing=lucky_fishing)
if not player.is_lucky:
break
else:
print_final_stats((player, opponent))
return


def shuffle(deck: Deck) -> Deck:
"""Returns a shuffled deck"""
deck = list(deck)
random.shuffle(deck)
return tuple(deck)


def deal_first_hands(deck: Deck,
count: int) -> Tuple[Deck, Tuple[Hand, Hand]]:
"""Gives count cards to each player"""
hands = (Hand(), Hand()) # type: Tuple[Hand, Hand]
for hand in ncycles(hands, count):
card, deck = fish(deck)
hand[card] += 1
return deck, hands


def fish(deck: Deck) -> Tuple[Card, Deck]:
"""Returns a fished card from a deck, and a new deck"""
return deck[-1], deck[:-1]


def ncycles(iterable: Iterable[T], n: int) -> Iterator[T]:
"""Returns the sequence elements n times"""
repeat = itertools.repeat(tuple(iterable), n)
return itertools.chain.from_iterable(repeat)


def check_hand_for_books(player: Player) -> Player:
"""
Walks through cards in the hand,
removes books and adds corresponding score
"""

def is_book(card_and_count: Tuple[Card, int]) -> bool:
"""
For a pair of card-count
checks if count equals the number of suits
"""
return card_and_count[1] == SUITS_COUNT

player = deepcopy(player)
cards_counts = player.hand.items()
books = list(filter(is_book, cards_counts)) # type: List[Tuple[Card, int]]
for card, _ in books:
player.hand.pop(card)
return player._replace(score=player.score + len(books))


def print_turn_stats(*,
turn: int,
players: Sequence[Player],
cards_left: int) -> None:
"""Prints stats of the current turn"""
name_score = name_score_pairs(players, sep=' ')
print(f'\nTurn {turn} ({name_score}) {cards_left} cards remaining.')


def name_score_pairs(players: Sequence[Player],
*,
sep: str) -> str:
"""Returns a string of pairs of 'name: score'; human goes first"""
players = sorted(players,
key=attrgetter('is_human'),
reverse=True)
name_score_generator = map('{0.name}: {0.score}'.format, players)
return sep.join(name_score_generator)


def cards_in_game(deck: Deck,
hands: Iterable[Hand]) -> bool:
"""Checks if hands or the deck have any cards"""
return deck or any(hands)


def play_turn(player: Player,
opponent: Player,
deck: Deck,
*,
lucky_fishing: bool) -> Tuple[Player, Player, Deck]:
"""Plays one turn"""
player, deck = replenish_card(player, deck=deck)
opponent, deck = replenish_card(opponent, deck=deck)

if player.is_human:
requested_card, watchlist = human_asks_card(
hand=player.hand,
watchlist=opponent.watchlist)
opponent = opponent._replace(watchlist=watchlist)
else:
requested_card = ai_asks_card(player)

if requested_card in opponent.hand:
player, opponent = correct_guess_actions(player=player,
opponent=opponent,
card=requested_card)
else:
player, deck = wrong_guess_actions(
player=player,
opponent=opponent,
requested_card=requested_card,
deck=deck,
lucky_fishing=lucky_fishing)
return player, opponent, deck


def print_final_stats(players: Sequence[Player]) -> None:
"""Prints final stats"""
name_score = name_score_pairs(players, sep='\n')
print(f'\nScores: \n{name_score}\n')
scores = list(map(attrgetter('score'), players))
if all(score == scores[0] for score in scores):
print('Draw!')
else:
winning_player = max(players, key=attrgetter('score'))
print(winning_player.name, 'won!')


def replenish_card(player: Player,
*,
deck: Deck) -> Tuple[Player, Deck]:
"""Returns a player with a card drawn from a deck"""
player = deepcopy(player)
if not player.hand:
print(EMPTY_HAND_MESSAGE.format(player.name))
card, deck = fish(deck)
player.hand[card] += 1
print_fishing_result(player=player,
card=card)
return player, deck


def human_asks_card(*,
hand: Hand,
watchlist: Watchlist) -> Tuple[Card, Watchlist]:
"""Set of actions for when human asks a card from computer"""
watchlist = watchlist.copy()
print_hand(hand)
requested_card = ask_for_card(hand=hand)
watchlist.add(requested_card)
return requested_card, watchlist


def ai_asks_card(player: Player) -> Card:
"""Set of actions for when computer asks a card from human"""
requested_card = request_card(player.hand,
watchlist=player.watchlist)
print_message(CARD_REQUEST_MESSAGE,
player_name=player.name,
card=requested_card)
return requested_card


def correct_guess_actions(player: Player,
opponent: Player,
card: Card):
"""
Set of actions for when player guesses correctly card
in a hand of an opponent
"""
player = deepcopy(player)
opponent = deepcopy(opponent)
card_count = opponent.hand.pop(card)
print(f'{player.name} got {card_count} more {card} '
f'from {opponent.name}.')
player.hand[card] += card_count
player = check_book(player, card)
if not player.is_human:
player.watchlist.discard(card)
player = player._replace(is_lucky=True)
return player, opponent


def wrong_guess_actions(*,
player: Player,
opponent: Player,
requested_card: Card,
deck: Deck,
lucky_fishing: bool) -> Tuple[Player, Deck]:
"""
Set of actions for when player guesses incorrectly card
in a hand of an opponent.
`lucky_fishing` determines if player can continue playing
after fishing the wanted card
"""
player = deepcopy(player)
print_message(NO_CARD_MESSAGE,
player_name=opponent.name,
card=requested_card)
card, deck = fish(deck)
player.hand[card] += 1
print_fishing_result(player=player,
card=card)
player = check_book(player, card)
if lucky_fishing:
player = player._replace(is_lucky=card == requested_card)
else:
player = player._replace(is_lucky=False)
if player.is_lucky:
print("What a luck! "
"The fished card was the same as requested!")
return player, deck


def print_hand(hand: Hand) -> None:
"""Prints a hand in the following form: 'Q Q 7 7 7 3'"""
ranks = itertools.starmap(itertools.repeat, hand.items())
print(*itertools.chain.from_iterable(ranks))


def ask_for_card(hand: Hand) -> Card:
"""Asks user for a card, and checks if it is in their hand"""
while True:
asked_card = Card(input('What card do you ask for? '))
if asked_card in hand:
return asked_card
print("You don't have that card. "
"Try again! (or enter quit to exit)")


def request_card(hand: Hand, watchlist: Watchlist) -> Card:
"""AI-like choice for a card that will be asked from a human"""
candidates = list(watchlist & set(hand.keys()))
if not candidates:
candidates = list(hand.keys())
return random.choice(candidates)


def print_message(message: str,
*,
player_name: str,
card: str) -> None:
"""Prints a template message"""
print(message.format(player_name=player_name,
card=card))


def check_book(player: Player,
card: Card) -> Player:
"""
Checks if player has all cards of specified type,
removes them if true, and adds +1 to score
"""
player = deepcopy(player)
if player.hand[card] == SUITS_COUNT:
print_message(BOOK_COMPLETED_MESSAGE,
player_name=player.name,
card=card)
player.hand.pop(card)
player = player._replace(score=player.score + 1)
return player


def print_fishing_result(*,
player: Player,
card: Card) -> None:
"""
Prints result of fishing.
Showing card or not depends on if a player is a human.
"""
card_stub = str(card) if player.is_human else 'card'
print_message(FISHING_RESULT_MESSAGE,
player_name=player.name,
card=card_stub)


if __name__ == "__main__":
play()
</lang>

Revision as of 14:39, 11 March 2019

Go Fish/Python is part of Go Fish. You may find other members of Go Fish at Category:Go Fish.

OOP

Works with: Python version 2.5

<lang Python>import random import sys from collections import defaultdict

class HumanPlayer(object):

   def __init__(self,deck):
       self.hand = defaultdict(int)
       self.book = []
       self.deck = deck #making a copy of deck, all changes within
                       #this class should affect the global deck
       self.score = 0
       self.name = raw_input('Name yourself: ')
       
   def Draw(self): #assuming that deck is a global
       cardDrawn = self.deck.pop() #removes the last card from deck
       self.hand[cardDrawn] += 1 #adds card to hand
       print '%s drew %s.' % (self.name,cardDrawn)
       self.checkForBooks()
       
   def checkForBooks(self):
  1. Removes all items of which are 4.
       for key,val in self.hand.items(): #can't use iteritems() because we are modifying hand in loop
           if val == 4: #completed a book
               self.book.append(key)
               print '%s completed the book of %s\'s.' % (self.name,key)
               self.score += 1
               del self.hand[key]
       self.emptyCheck()
   def emptyCheck(self):
       if len(self.deck)!=0 and len(self.hand)==0: #checks if deck/hand is empty
           self.Draw()
   def displayHand(self): #Displays current hand, cards separated by spaces
       return ' '.join(key for key,val in self.hand.iteritems()
                       for i in range(val)) #meh, make it prettier
   def makeTurn(self):
       print '%s\'s hand: %s' % (self.name,self.displayHand())
       chooseCard = raw_input('What card do you ask for? ').strip()
       if chooseCard == 'quit':
           sys.exit(0)
       if chooseCard not in self.hand:
           print 'You don\'t have that card. Try again! (or enter quit to exit)'
           chooseCard = self.makeTurn()
       return chooseCard
   
   def fishFor(self,card):
       if card in self.hand: # if card in hand, returns count and removes the card from hand
           val = self.hand.pop(card)
           self.emptyCheck()
           return val
       else:
           return False
   def gotCard(self,card,amount):
       self.hand[card] += amount
       self.checkForBooks()
       
       
       

class Computer(HumanPlayer):

   def __init__(self,deck):
       self.name = 'Computer'
       self.hand = defaultdict(int)
       self.book = []
       self.deck = deck
       self.opponentHas = set()
       self.score = 0
   def Draw(self): #assuming that deck is a global
       cardDrawn = self.deck.pop() #removes the last card from deck
       self.hand[cardDrawn] += 1 #adds card to hand
       print '%s drew a card.' % (self.name)
       self.checkForBooks()
   ##AI: guesses cards that knows you have, then tries cards he has at random.
   ##Improvements: remember if the card was rejected before, guess probabilities
   def makeTurn(self):
  1. print self.displayHand(),self.opponentHas
       candidates = list(self.opponentHas & set(self.hand.keys())) #checks for cards in hand that computer knows you have
       if not candidates:
           candidates = self.hand.keys() #if no intersection between those two, random guess
       move = random.choice(candidates)
       print '%s fishes for %s.' % (self.name,move)
       return move
   
   def fishFor(self,card): #Same as for humans players, but adds the card fished for to opponentHas list.
       self.opponentHas.add(card)
       if card in self.hand: # if card in hand, returns count and removes the card from hand
           val = self.hand.pop(card)
           self.emptyCheck()
           return val
       else:
           return False
   
   def gotCard(self,card,amount):
       self.hand[card] += amount
       self.opponentHas.discard(card)
       self.checkForBooks()
       

class PlayGoFish(object):

   def __init__(self):
       self.deck = ('2 3 4 5 6 7 8 9 10 J Q K A '*4).split(' ')
       self.deck.remove()
       self.player = [HumanPlayer(self.deck),Computer(self.deck)] #makes counting turns easier
   def endOfPlayCheck(self):#checks if hands/decks are empty using the any method
           return self.deck or self.player[0].hand or self.player[1].hand
       
   def play(self):
       random.shuffle(self.deck)
       for i in xrange(9): # Deal the first cards
           self.player[0].Draw()
           self.player[1].Draw()
       turn = 0
       while self.endOfPlayCheck():
           print '\nTurn %d (%s:%d %s:%d) %d cards remaining.' % (turn,self.player[0].name,
                   self.player[0].score,self.player[1].name,self.player[1].score,len(self.deck))
           whoseTurn = turn%2
           otherPlayer = (turn+1)%2
           while True: #loop until player finishes turn
               cardFished = self.player[whoseTurn].makeTurn()
               result = self.player[otherPlayer].fishFor(cardFished)
               if not result: #Draws and ends turn
                   self.player[whoseTurn].Draw()
                   break
               print '%s got %d more %s.' % (self.player[whoseTurn].name,result, cardFished)
               self.player[whoseTurn].gotCard(cardFished,result)
               if not self.endOfPlayCheck(): break
           turn+=1
       print '\nScores: \n%s: %d\n%s: %d\n' % (self.player[0].name,self.player[0].score,
                                         self.player[1].name,self.player[1].score)
       if self.player[0].score>self.player[1].score:
           print self.player[0].name,'won!'
       elif self.player[0].score==self.player[1].score:
           print 'Draw!'
       else:
           print self.player[1].name,'won!'

if __name__=="__main__":

   game = PlayGoFish()
   game.play()</lang>

Procedural

Works with: Python version 3.6

<lang Python>""" Plays a Go Fish game between a user and computer following the rules specified at http://www.rosettacode.org/wiki/Go_Fish """ import itertools import random from copy import deepcopy from operator import attrgetter from typing import (Counter,

                   Iterable,
                   Iterator,
                   List,
                   NamedTuple,
                   NewType,
                   Sequence,
                   Set,
                   Tuple,
                   TypeVar)

Card = NewType('Card', str) Hand = Counter[Card] Deck = Tuple[Card, ...] Watchlist = Set[Card] T = TypeVar('T')

SUITS_COUNT = 4 RANKS = ('2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A') DECK = RANKS * SUITS_COUNT

EMPTY_HAND_MESSAGE = "{} is without cards. They go fishing." CARD_REQUEST_MESSAGE = "{player_name} asks for {card}" NO_CARD_MESSAGE = "{player_name} doesn't have {card}" BOOK_COMPLETED_MESSAGE = "{player_name} completed the book of {card}'s." FISHING_RESULT_MESSAGE = "{player_name} fished a {card}"


class Player(NamedTuple):

   name: str
   hand: Hand
   is_human: bool
   score: int = 0
   is_lucky: bool = True  # if False, turn goes to another player
   watchlist: Watchlist = set()  # used by AI to track enemy's cards


def play(deck: Deck = DECK,

        *,
        first_hand_count: int = 9,
        lucky_fishing: bool = False) -> None:
   """
   Plays the Go Fish game according to
   http://www.rosettacode.org/wiki/Go_Fish
   :param deck: deck from where the cards are drawn
   :param first_hand_count: starting amount of cards for a player
   :param lucky_fishing: if True, continue playing
   after fishing the card from the deck
   that was asked from an opponent
   """
   deck = shuffle(deck)
   deck, (hand, other_hand) = deal_first_hands(deck, count=first_hand_count)
   human = Player(name=input('Name yourself: '),
                  hand=hand,
                  is_human=True)
   ai = Player(name='Computer',
               hand=other_hand,
               is_human=False)
   human = check_hand_for_books(human)
   ai = check_hand_for_books(ai)
   player, opponent = ai, human
   for turn in itertools.count(1):
       player, opponent = opponent, player
       print_turn_stats(turn=turn,
                        players=(player, opponent),
                        cards_left=len(deck))
       while cards_in_game(deck, hands=(player.hand, opponent.hand)):
           player, opponent, deck = play_turn(player,
                                              opponent,
                                              deck,
                                              lucky_fishing=lucky_fishing)
           if not player.is_lucky:
               break
       else:
           print_final_stats((player, opponent))
           return


def shuffle(deck: Deck) -> Deck:

   """Returns a shuffled deck"""
   deck = list(deck)
   random.shuffle(deck)
   return tuple(deck)


def deal_first_hands(deck: Deck,

                    count: int) -> Tuple[Deck, Tuple[Hand, Hand]]:
   """Gives count cards to each player"""
   hands = (Hand(), Hand())  # type: Tuple[Hand, Hand]
   for hand in ncycles(hands, count):
       card, deck = fish(deck)
       hand[card] += 1
   return deck, hands


def fish(deck: Deck) -> Tuple[Card, Deck]:

   """Returns a fished card from a deck, and a new deck"""
   return deck[-1], deck[:-1]


def ncycles(iterable: Iterable[T], n: int) -> Iterator[T]:

   """Returns the sequence elements n times"""
   repeat = itertools.repeat(tuple(iterable), n)
   return itertools.chain.from_iterable(repeat)


def check_hand_for_books(player: Player) -> Player:

   """
   Walks through cards in the hand,
   removes books and adds corresponding score
   """
   def is_book(card_and_count: Tuple[Card, int]) -> bool:
       """
       For a pair of card-count
       checks if count equals the number of suits
       """
       return card_and_count[1] == SUITS_COUNT
   player = deepcopy(player)
   cards_counts = player.hand.items()
   books = list(filter(is_book, cards_counts))  # type: List[Tuple[Card, int]]
   for card, _ in books:
       player.hand.pop(card)
   return player._replace(score=player.score + len(books))


def print_turn_stats(*,

                    turn: int,
                    players: Sequence[Player],
                    cards_left: int) -> None:
   """Prints stats of the current turn"""
   name_score = name_score_pairs(players, sep=' ')
   print(f'\nTurn {turn} ({name_score}) {cards_left} cards remaining.')


def name_score_pairs(players: Sequence[Player],

                    *,
                    sep: str) -> str:
   """Returns a string of pairs of 'name: score'; human goes first"""
   players = sorted(players,
                    key=attrgetter('is_human'),
                    reverse=True)
   name_score_generator = map('{0.name}: {0.score}'.format, players)
   return sep.join(name_score_generator)


def cards_in_game(deck: Deck,

                 hands: Iterable[Hand]) -> bool:
   """Checks if hands or the deck have any cards"""
   return deck or any(hands)


def play_turn(player: Player,

             opponent: Player,
             deck: Deck,
             *,
             lucky_fishing: bool) -> Tuple[Player, Player, Deck]:
   """Plays one turn"""
   player, deck = replenish_card(player, deck=deck)
   opponent, deck = replenish_card(opponent, deck=deck)
   if player.is_human:
       requested_card, watchlist = human_asks_card(
           hand=player.hand,
           watchlist=opponent.watchlist)
       opponent = opponent._replace(watchlist=watchlist)
   else:
       requested_card = ai_asks_card(player)
   if requested_card in opponent.hand:
       player, opponent = correct_guess_actions(player=player,
                                                opponent=opponent,
                                                card=requested_card)
   else:
       player, deck = wrong_guess_actions(
           player=player,
           opponent=opponent,
           requested_card=requested_card,
           deck=deck,
           lucky_fishing=lucky_fishing)
   return player, opponent, deck


def print_final_stats(players: Sequence[Player]) -> None:

   """Prints final stats"""
   name_score = name_score_pairs(players, sep='\n')
   print(f'\nScores: \n{name_score}\n')
   scores = list(map(attrgetter('score'), players))
   if all(score == scores[0] for score in scores):
       print('Draw!')
   else:
       winning_player = max(players, key=attrgetter('score'))
       print(winning_player.name, 'won!')


def replenish_card(player: Player,

                  *,
                  deck: Deck) -> Tuple[Player, Deck]:
   """Returns a player with a card drawn from a deck"""
   player = deepcopy(player)
   if not player.hand:
       print(EMPTY_HAND_MESSAGE.format(player.name))
       card, deck = fish(deck)
       player.hand[card] += 1
       print_fishing_result(player=player,
                            card=card)
   return player, deck


def human_asks_card(*,

                   hand: Hand,
                   watchlist: Watchlist) -> Tuple[Card, Watchlist]:
   """Set of actions for when human asks a card from computer"""
   watchlist = watchlist.copy()
   print_hand(hand)
   requested_card = ask_for_card(hand=hand)
   watchlist.add(requested_card)
   return requested_card, watchlist


def ai_asks_card(player: Player) -> Card:

   """Set of actions for when computer asks a card from human"""
   requested_card = request_card(player.hand,
                                 watchlist=player.watchlist)
   print_message(CARD_REQUEST_MESSAGE,
                 player_name=player.name,
                 card=requested_card)
   return requested_card


def correct_guess_actions(player: Player,

                         opponent: Player,
                         card: Card):
   """
   Set of actions for when player guesses correctly card
   in a hand of an opponent
   """
   player = deepcopy(player)
   opponent = deepcopy(opponent)
   card_count = opponent.hand.pop(card)
   print(f'{player.name} got {card_count} more {card} '
         f'from {opponent.name}.')
   player.hand[card] += card_count
   player = check_book(player, card)
   if not player.is_human:
       player.watchlist.discard(card)
   player = player._replace(is_lucky=True)
   return player, opponent


def wrong_guess_actions(*,

                       player: Player,
                       opponent: Player,
                       requested_card: Card,
                       deck: Deck,
                       lucky_fishing: bool) -> Tuple[Player, Deck]:
   """
   Set of actions for when player guesses incorrectly card
   in a hand of an opponent.
   `lucky_fishing` determines if player can continue playing
   after fishing the wanted card
   """
   player = deepcopy(player)
   print_message(NO_CARD_MESSAGE,
                 player_name=opponent.name,
                 card=requested_card)
   card, deck = fish(deck)
   player.hand[card] += 1
   print_fishing_result(player=player,
                        card=card)
   player = check_book(player, card)
   if lucky_fishing:
       player = player._replace(is_lucky=card == requested_card)
   else:
       player = player._replace(is_lucky=False)
   if player.is_lucky:
       print("What a luck! "
             "The fished card was the same as requested!")
   return player, deck


def print_hand(hand: Hand) -> None:

   """Prints a hand in the following form: 'Q Q 7 7 7 3'"""
   ranks = itertools.starmap(itertools.repeat, hand.items())
   print(*itertools.chain.from_iterable(ranks))


def ask_for_card(hand: Hand) -> Card:

   """Asks user for a card, and checks if it is in their hand"""
   while True:
       asked_card = Card(input('What card do you ask for? '))
       if asked_card in hand:
           return asked_card
       print("You don't have that card. "
             "Try again! (or enter quit to exit)")


def request_card(hand: Hand, watchlist: Watchlist) -> Card:

   """AI-like choice for a card that will be asked from a human"""
   candidates = list(watchlist & set(hand.keys()))
   if not candidates:
       candidates = list(hand.keys())
   return random.choice(candidates)


def print_message(message: str,

                 *,
                 player_name: str,
                 card: str) -> None:
   """Prints a template message"""
   print(message.format(player_name=player_name,
                        card=card))


def check_book(player: Player,

              card: Card) -> Player:
   """
   Checks if player has all cards of specified type,
   removes them if true, and adds +1 to score
   """
   player = deepcopy(player)
   if player.hand[card] == SUITS_COUNT:
       print_message(BOOK_COMPLETED_MESSAGE,
                     player_name=player.name,
                     card=card)
       player.hand.pop(card)
       player = player._replace(score=player.score + 1)
   return player


def print_fishing_result(*,

                        player: Player,
                        card: Card) -> None:
   """
   Prints result of fishing.
   Showing card or not depends on if a player is a human.
   """
   card_stub = str(card) if player.is_human else 'card'
   print_message(FISHING_RESULT_MESSAGE,
                 player_name=player.name,
                 card=card_stub)


if __name__ == "__main__":

   play()

</lang>