⚠️ Warning: This is a draft ⚠️

This means it might contain formatting issues, incorrect code, conceptual problems, or other severe issues.

If you want to help to improve and eventually enable this page, please fork RosettaGit's repository and open a merge request on GitHub.

{{collection|Go Fish}}

OOP

{{works with|Python|2.5}}

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):
#       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):
#        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()

Procedural

{{works with|Python|3.6}}

"""
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!")


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()