Go Fish: Difference between revisions
Underscore (talk | contribs) (Realphabetized. Removed attribution from the Python implementation.) |
(added ocaml) |
||
Line 13: | Line 13: | ||
You may want to use code from [[Playing Cards]]. |
You may want to use code from [[Playing Cards]]. |
||
=={{header|OCaml}}== |
|||
<lang ocaml>type pip = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | |
|||
Jack | Queen | King | Ace |
|||
let pips = [Two; Three; Four; Five; Six; Seven; Eight; Nine; Ten; |
|||
Jack; Queen; King; Ace] |
|||
type suit = Diamonds | Spades | Hearts | Clubs |
|||
let suits = [Diamonds; Spades; Hearts; Clubs] |
|||
type card = pip * suit |
|||
let string_of_pip = function |
|||
| Two -> "Two" |
|||
| Three -> "Three" |
|||
| Four -> "Four" |
|||
| Five -> "Five" |
|||
| Six -> "Six" |
|||
| Seven -> "Seven" |
|||
| Eight -> "Eight" |
|||
| Nine -> "Nine" |
|||
| Ten -> "Ten" |
|||
| Jack -> "Jack" |
|||
| Queen -> "Queen" |
|||
| King -> "King" |
|||
| Ace -> "Ace" |
|||
let string_of_suit = function |
|||
| Diamonds -> "Diamonds" |
|||
| Spades -> "Spades" |
|||
| Hearts -> "Hearts" |
|||
| Clubs -> "Clubs" |
|||
let string_of_card (pip, suit) = |
|||
(Printf.sprintf "(%s-%s)" (string_of_pip pip) (string_of_suit suit)) |
|||
let pip_of_card (pip, _) = (pip) |
|||
let deck = List.concat (List.map (fun pip -> List.map (fun suit -> (pip, suit)) suits) pips) |
|||
type rank_state = |
|||
| Unknown (* Don't know if the opponent has any cards in that rank. *) |
|||
| No_cards (* Opponent has no cards there; I took them away, or I asked yet. *) |
|||
| Has_cards (* Opponent has cards there; they tried to get them off me and haven't booked them yet. *) |
|||
| Booked (* Someone has booked the rank. *) |
|||
let state_score = function |
|||
| Booked -> 0 |
|||
| No_cards -> 1 |
|||
| Unknown -> 2 |
|||
| Has_cards -> 3 |
|||
let string_of_state = function |
|||
| Booked -> "Booked" |
|||
| No_cards -> "No_cards" |
|||
| Unknown -> "Unknown" |
|||
| Has_cards -> "Has_cards" |
|||
let replace ((rank,_) as state) opp = |
|||
let rec aux acc = function |
|||
| (_rank,_)::tl when _rank = rank -> List.rev_append acc (state::tl) |
|||
| hd::tl -> aux (hd::acc) tl |
|||
| [] -> assert(false) |
|||
in |
|||
aux [] opp ;; |
|||
class virtual abstract_player = |
|||
object (s) |
|||
val mutable virtual cards : card list |
|||
val mutable virtual books : pip list |
|||
method virtual ask_rank : unit -> pip |
|||
method virtual give_rank : pip -> card list |
|||
method virtual notify_booked : pip -> unit |
|||
method virtual request_failed : pip -> unit |
|||
method private cards_given rank = |
|||
let matched, rest = List.partition (fun (pip,suit) -> pip = rank) cards in |
|||
if List.length matched = 4 then begin |
|||
cards <- rest; |
|||
books <- rank :: books; |
|||
s#notify_booked rank; |
|||
(Some rank) |
|||
end |
|||
else (None) |
|||
method give_card (card : card) = |
|||
let rank = pip_of_card card in |
|||
cards <- card :: cards; |
|||
s#cards_given rank |
|||
method give_cards (_cards : card list) = |
|||
let rank = |
|||
match _cards with |
|||
| [] -> invalid_arg "empty list" |
|||
| hd::tl -> |
|||
List.fold_left |
|||
(fun rank1 (rank2,_) -> |
|||
if rank1 <> rank2 |
|||
then invalid_arg "!= ranks" |
|||
else (rank1) |
|||
) (pip_of_card hd) tl |
|||
in |
|||
cards <- _cards @ cards; |
|||
s#cards_given rank |
|||
method give_rank rank = |
|||
let give, _cards = List.partition (fun (pip, suit) -> pip = rank) cards in |
|||
cards <- _cards; |
|||
(give) |
|||
method books_length = |
|||
(List.length books) |
|||
method empty_hand = |
|||
(List.length cards) = 0 |
|||
method private dump_cards() = |
|||
print_endline(String.concat ", " (List.map string_of_card cards)); |
|||
end |
|||
class human_player = |
|||
object (s) inherit abstract_player |
|||
val mutable cards = [] |
|||
val mutable books = [] |
|||
method ask_rank() = |
|||
let ranks = |
|||
List.fold_left (fun acc card -> |
|||
let rank = pip_of_card card in |
|||
if List.exists (fun _rank -> _rank = rank) acc |
|||
then (acc) |
|||
else (rank::acc) |
|||
) |
|||
[] cards |
|||
in |
|||
s#dump_cards(); |
|||
Printf.printf "Ranks: %s\n%!" (String.concat ", " (List.map string_of_pip ranks)); |
|||
let n = List.length ranks in |
|||
Printf.printf "choose from 1 to %d\n%!" n; |
|||
let get_int() = |
|||
try int_of_string(read_line()) |
|||
with Failure "int_of_string" -> raise Exit |
|||
in |
|||
let rec aux() = |
|||
let d = get_int() in |
|||
if d <= 0 || d > n then aux() else (pred d) |
|||
in |
|||
let d = aux() in |
|||
(List.nth ranks d) |
|||
method notify_booked rank = |
|||
Printf.printf "Rank [%s] is now booked\n%!" (string_of_pip rank); |
|||
method request_failed rank = () |
|||
end |
|||
class ai_player = |
|||
object (s) inherit abstract_player as parent |
|||
val mutable cards = [] |
|||
val mutable books = [] |
|||
val mutable opponent = List.map (fun rank -> (rank, Unknown)) pips |
|||
method private dump_state() = |
|||
let f (pip, state) = |
|||
Printf.sprintf "{%s:%s}" (string_of_pip pip) (string_of_state state) |
|||
in |
|||
print_endline(String.concat ", " (List.map f opponent)); |
|||
method ask_rank() = |
|||
let ranks = |
|||
List.fold_left (fun acc card -> |
|||
let rank = pip_of_card card in |
|||
try |
|||
let _,n = List.find (fun (_rank,_) -> _rank = rank) acc in |
|||
(replace (rank, n+1) acc) |
|||
with Not_found -> |
|||
((rank,1)::acc) |
|||
) |
|||
[] cards |
|||
in |
|||
let f (rank,_) = |
|||
(state_score(List.assoc rank opponent)) |
|||
in |
|||
let ranks = List.sort (fun a b -> (f b) - (f a)) ranks in |
|||
(* DEBUG |
|||
Printf.printf "Ranks: %s\n%!" (String.concat ", " (List.map string_of_pip ranks)); |
|||
s#dump_state(); |
|||
s#dump_cards(); |
|||
*) |
|||
opponent <- List.sort (fun _ _ -> Random.int 9 - Random.int 9) opponent; |
|||
if ranks <> [] |
|||
then fst(List.hd ranks) |
|||
else Jack |
|||
method give_cards (_cards : card list) = |
|||
let rank = pip_of_card(List.hd _cards) in |
|||
opponent <- replace (rank, No_cards) opponent; |
|||
(parent#give_cards _cards) |
|||
method give_rank rank = |
|||
opponent <- replace (rank, Has_cards) opponent; |
|||
(parent#give_rank rank) |
|||
method notify_booked rank = |
|||
opponent <- replace (rank, Booked) opponent |
|||
method request_failed rank = |
|||
opponent <- replace (rank, No_cards) opponent |
|||
end |
|||
class random_player = |
|||
object (s) inherit ai_player |
|||
method ask_rank() = |
|||
let ranks = |
|||
List.fold_left (fun acc card -> |
|||
let rank = pip_of_card card in |
|||
if List.exists (fun _rank -> _rank = rank) acc |
|||
then (acc) |
|||
else (rank::acc) |
|||
) |
|||
[] cards |
|||
in |
|||
let n = List.length ranks in |
|||
let d = Random.int n in |
|||
(List.nth ranks d) |
|||
end |
|||
exception Empty_deck |
|||
let card_to_player deck player op = |
|||
match deck with |
|||
| card::deck -> |
|||
begin match player#give_card card with |
|||
| None -> () |
|||
| Some rank -> op#notify_booked rank |
|||
end; |
|||
(deck) |
|||
| _ -> raise Empty_deck |
|||
let n_cards_to_player n deck player op = |
|||
let rec aux i deck = |
|||
if i >= n then (deck) else |
|||
let deck = card_to_player deck player op in |
|||
aux (succ i) deck |
|||
in |
|||
aux 0 deck ;; |
|||
let () = |
|||
Random.self_init(); |
|||
let deck = List.sort (fun _ _ -> Random.int 9 - Random.int 9) deck in |
|||
let player_a = new human_player |
|||
and player_b = new ai_player in |
|||
let deck = n_cards_to_player 9 deck player_a player_b in |
|||
let deck = n_cards_to_player 9 deck player_b player_a in |
|||
let deck = ref deck in |
|||
let empty_hand player1 player2 = |
|||
if player1#empty_hand |
|||
then deck := card_to_player !deck player1 player2 |
|||
in |
|||
let rec make_turn id1 id2 player1 player2 = |
|||
print_newline(); |
|||
(try |
|||
empty_hand player1 player2; |
|||
empty_hand player2 player1; |
|||
with Empty_deck -> ()); |
|||
if player1#books_length + player2#books_length = 13 |
|||
then () |
|||
else begin |
|||
let rank = player1#ask_rank() in |
|||
Printf.printf "player %s asked for %ss\n%!" id1 (string_of_pip rank); |
|||
let cards = player2#give_rank rank in |
|||
match cards with |
|||
| [] -> |
|||
Printf.printf "player %s has no %ss\n%!" id2 (string_of_pip rank); |
|||
player1#request_failed rank; |
|||
(try |
|||
deck := card_to_player !deck player1 player2; |
|||
make_turn id2 id1 player2 player1 |
|||
with Empty_deck -> ()) |
|||
| cards -> |
|||
let given = String.concat ", " (List.map string_of_card cards) in |
|||
Printf.printf "player %s gives %s\n%!" id2 given; |
|||
begin match player1#give_cards cards with |
|||
| None -> () |
|||
| Some rank -> |
|||
Printf.printf "player %s booked [%s]\n%!" id1 (string_of_pip rank); |
|||
player2#notify_booked rank; |
|||
end; |
|||
make_turn id1 id2 player1 player2 |
|||
end |
|||
in |
|||
(try |
|||
if Random.int 2 = 0 |
|||
then make_turn "a" "b" player_a player_b |
|||
else make_turn "b" "a" player_b player_a; |
|||
with Exit -> ()); |
|||
Printf.printf "player a has %d books\n" (player_a#books_length); |
|||
Printf.printf "player b has %d books\n" (player_b#books_length); |
|||
;; |
|||
</lang> |
|||
=={{header|Perl 6}}== |
=={{header|Perl 6}}== |
Revision as of 22:19, 8 December 2009
You are encouraged to solve this task according to the task description, using any language you may know.
Write a program to let the user play Go Fish against a computer opponent. Use the following rules:
- Each player is dealt nine cards to start with.
- On their turn, a player asks their opponent for a given rank (like threes or kings). A player must already have at least one card of a given rank to ask for more.
- If the opponent has any cards of the named rank, they must hand over all such cards, and the requester can ask again.
- If the opponent has no cards of the named rank, the requester draws a card and ends their turn.
- A book is a collection of every card of a given rank. Whenever a player completes a book, they may remove it from their hand.
- If at any time a player's hand is empty, they may immediately draw a new card, so long as any new cards remain in the deck.
- The game ends when every book is complete. The player with more books wins.
The game's AI need not be terribly smart, but it should use at least some strategy. That is, it shouldn't choose legal moves entirely at random.
You may want to use code from Playing Cards.
OCaml
<lang ocaml>type pip = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten |
Jack | Queen | King | Ace
let pips = [Two; Three; Four; Five; Six; Seven; Eight; Nine; Ten;
Jack; Queen; King; Ace]
type suit = Diamonds | Spades | Hearts | Clubs let suits = [Diamonds; Spades; Hearts; Clubs]
type card = pip * suit
let string_of_pip = function
| Two -> "Two" | Three -> "Three" | Four -> "Four" | Five -> "Five" | Six -> "Six" | Seven -> "Seven" | Eight -> "Eight" | Nine -> "Nine" | Ten -> "Ten" | Jack -> "Jack" | Queen -> "Queen" | King -> "King" | Ace -> "Ace"
let string_of_suit = function
| Diamonds -> "Diamonds" | Spades -> "Spades" | Hearts -> "Hearts" | Clubs -> "Clubs"
let string_of_card (pip, suit) =
(Printf.sprintf "(%s-%s)" (string_of_pip pip) (string_of_suit suit))
let pip_of_card (pip, _) = (pip)
let deck = List.concat (List.map (fun pip -> List.map (fun suit -> (pip, suit)) suits) pips)
type rank_state =
| Unknown (* Don't know if the opponent has any cards in that rank. *) | No_cards (* Opponent has no cards there; I took them away, or I asked yet. *) | Has_cards (* Opponent has cards there; they tried to get them off me and haven't booked them yet. *) | Booked (* Someone has booked the rank. *)
let state_score = function
| Booked -> 0 | No_cards -> 1 | Unknown -> 2 | Has_cards -> 3
let string_of_state = function
| Booked -> "Booked" | No_cards -> "No_cards" | Unknown -> "Unknown" | Has_cards -> "Has_cards"
let replace ((rank,_) as state) opp =
let rec aux acc = function | (_rank,_)::tl when _rank = rank -> List.rev_append acc (state::tl) | hd::tl -> aux (hd::acc) tl | [] -> assert(false) in aux [] opp ;;
class virtual abstract_player =
object (s) val mutable virtual cards : card list val mutable virtual books : pip list method virtual ask_rank : unit -> pip method virtual give_rank : pip -> card list method virtual notify_booked : pip -> unit method virtual request_failed : pip -> unit
method private cards_given rank = let matched, rest = List.partition (fun (pip,suit) -> pip = rank) cards in if List.length matched = 4 then begin cards <- rest; books <- rank :: books; s#notify_booked rank; (Some rank) end else (None)
method give_card (card : card) = let rank = pip_of_card card in cards <- card :: cards; s#cards_given rank
method give_cards (_cards : card list) = let rank = match _cards with | [] -> invalid_arg "empty list" | hd::tl -> List.fold_left (fun rank1 (rank2,_) -> if rank1 <> rank2 then invalid_arg "!= ranks" else (rank1) ) (pip_of_card hd) tl in cards <- _cards @ cards; s#cards_given rank
method give_rank rank = let give, _cards = List.partition (fun (pip, suit) -> pip = rank) cards in cards <- _cards; (give)
method books_length = (List.length books)
method empty_hand = (List.length cards) = 0
method private dump_cards() = print_endline(String.concat ", " (List.map string_of_card cards));
end
class human_player =
object (s) inherit abstract_player
val mutable cards = [] val mutable books = []
method ask_rank() = let ranks = List.fold_left (fun acc card -> let rank = pip_of_card card in if List.exists (fun _rank -> _rank = rank) acc then (acc) else (rank::acc) ) [] cards in s#dump_cards(); Printf.printf "Ranks: %s\n%!" (String.concat ", " (List.map string_of_pip ranks)); let n = List.length ranks in Printf.printf "choose from 1 to %d\n%!" n; let get_int() = try int_of_string(read_line()) with Failure "int_of_string" -> raise Exit in let rec aux() = let d = get_int() in if d <= 0 || d > n then aux() else (pred d) in let d = aux() in (List.nth ranks d)
method notify_booked rank = Printf.printf "Rank [%s] is now booked\n%!" (string_of_pip rank);
method request_failed rank = () end
class ai_player =
object (s) inherit abstract_player as parent
val mutable cards = [] val mutable books = [] val mutable opponent = List.map (fun rank -> (rank, Unknown)) pips
method private dump_state() = let f (pip, state) = Printf.sprintf "{%s:%s}" (string_of_pip pip) (string_of_state state) in print_endline(String.concat ", " (List.map f opponent));
method ask_rank() = let ranks = List.fold_left (fun acc card -> let rank = pip_of_card card in try let _,n = List.find (fun (_rank,_) -> _rank = rank) acc in (replace (rank, n+1) acc) with Not_found -> ((rank,1)::acc) ) [] cards in let f (rank,_) = (state_score(List.assoc rank opponent)) in let ranks = List.sort (fun a b -> (f b) - (f a)) ranks in (* DEBUG Printf.printf "Ranks: %s\n%!" (String.concat ", " (List.map string_of_pip ranks)); s#dump_state(); s#dump_cards(); *) opponent <- List.sort (fun _ _ -> Random.int 9 - Random.int 9) opponent; if ranks <> [] then fst(List.hd ranks) else Jack
method give_cards (_cards : card list) = let rank = pip_of_card(List.hd _cards) in opponent <- replace (rank, No_cards) opponent; (parent#give_cards _cards)
method give_rank rank = opponent <- replace (rank, Has_cards) opponent; (parent#give_rank rank)
method notify_booked rank = opponent <- replace (rank, Booked) opponent
method request_failed rank = opponent <- replace (rank, No_cards) opponent end
class random_player =
object (s) inherit ai_player
method ask_rank() = let ranks = List.fold_left (fun acc card -> let rank = pip_of_card card in if List.exists (fun _rank -> _rank = rank) acc then (acc) else (rank::acc) ) [] cards in let n = List.length ranks in let d = Random.int n in (List.nth ranks d)
end
exception Empty_deck let card_to_player deck player op =
match deck with | card::deck -> begin match player#give_card card with | None -> () | Some rank -> op#notify_booked rank end; (deck) | _ -> raise Empty_deck
let n_cards_to_player n deck player op =
let rec aux i deck = if i >= n then (deck) else let deck = card_to_player deck player op in aux (succ i) deck in aux 0 deck ;;
let () =
Random.self_init(); let deck = List.sort (fun _ _ -> Random.int 9 - Random.int 9) deck in let player_a = new human_player and player_b = new ai_player in let deck = n_cards_to_player 9 deck player_a player_b in let deck = n_cards_to_player 9 deck player_b player_a in let deck = ref deck in let empty_hand player1 player2 = if player1#empty_hand then deck := card_to_player !deck player1 player2 in let rec make_turn id1 id2 player1 player2 = print_newline(); (try empty_hand player1 player2; empty_hand player2 player1; with Empty_deck -> ()); if player1#books_length + player2#books_length = 13 then () else begin let rank = player1#ask_rank() in Printf.printf "player %s asked for %ss\n%!" id1 (string_of_pip rank); let cards = player2#give_rank rank in match cards with | [] -> Printf.printf "player %s has no %ss\n%!" id2 (string_of_pip rank); player1#request_failed rank; (try deck := card_to_player !deck player1 player2; make_turn id2 id1 player2 player1 with Empty_deck -> ())
| cards -> let given = String.concat ", " (List.map string_of_card cards) in Printf.printf "player %s gives %s\n%!" id2 given; begin match player1#give_cards cards with | None -> () | Some rank -> Printf.printf "player %s booked [%s]\n%!" id1 (string_of_pip rank); player2#notify_booked rank; end; make_turn id1 id2 player1 player2 end in (try if Random.int 2 = 0 then make_turn "a" "b" player_a player_b else make_turn "b" "a" player_b player_a; with Exit -> ());
Printf.printf "player a has %d books\n" (player_a#books_length); Printf.printf "player b has %d books\n" (player_b#books_length);
</lang>
Perl 6
<lang perl6>constant BOOKSIZE = 4; constant HANDSIZE = 9; constant Str @pips = <two three four five six seven eight nine ten jack queen king ace>;
# The elements of @pips are only names. Pips are represented internally # as indices of this array.
constant Str @piparticles = <a a a a a a an a a a a a an>; constant Str @ppips = <deuces threes fours fives sixes sevens eights nines tens jacks queens kings aces>; constant Str @shortpips = <2 3 4 5 6 7 8 9 T J Q K A>; constant $foe_nominative_pronoun = pick 1, <he she it e xe>;
sub count ($x, *@a) {
my $n = 0; $_ eqv $x and ++$n for @a; return $n;
}
sub find ($x, *@a) {
for @a.kv -> $k, $v { $v eqv $x and return $k; } fail 'Not found';
}
sub maxes (&f, *@a) {
my $x = [max] map &f, @a; return grep { f($^e) eqv $x }, @a;
}
sub ncard ($n, $pip) {
$n > 1 ?? "$n {@ppips[$pip]}" !! "{@piparticles[$pip]} {@pips[$pip]}"
}
sub readpip (@user_hand) {
my @choices = grep { @user_hand[$^p] }, ^@pips; if (@choices == 1) { say "You're obliged to ask for { @ppips[@choices[0]] }."; return @choices[0]; } loop { print 'For what do you ask? (', join(', ', @shortpips[@choices]), '): '; my $in = substr uc($*IN.get or next), 0, 1; my $pip = find $in, @shortpips; if defined $pip { @user_hand[$pip] and return $pip; say "You don't have any { @ppips[$pip] }."; } else { say 'No such rank.'; } }
}
enum Maybe <No Yes Dunno>;
class Knowledge {
- The computer player has an instance of this class for each pip.
- Each instance tracks whether the computer thinks the user has at
- least one card of the corresponding pip.
has Maybe $.maybe = Dunno; # Yes if the user definitely has this pip, No if they didn't # have it the last time we checked, Dunno if we haven't yet # checked. has Int $.n = 0; # If $.maybe is No, $.n counts how many cards the user # has drawn since we last checked.
method set (Maybe $!maybe) { $!n = 0 }
method incr { $.maybe == No and ++$!n }
}
class Player {
has Int @.h; # @h[$n] is number of cards of pip $n in this player's hand. has $.deck; # A reference to whatever deck the player's playing with. has Int $.books = 0; has Bool $.cpu; has Knowledge @.know;
method new ($cpu, @deck is rw) { my Int @h = 0 xx @pips; ++@h[$_] for @deck[^HANDSIZE]; @deck = @deck[HANDSIZE ..^ @deck]; Player.bless(*, h => @h, cpu => $cpu, deck => \@deck, know => ($cpu ?? map { Knowledge.new() }, @pips !! ()) ); }
method showhand { say ($.cpu ?? 'The dealer has ' !! 'You have '), join(' ', map { join ' ', @shortpips[.key] xx .value }, grep { .value }, pairs @.h), '.'; }
method draw () { my $new = shift $.deck; $.cpu or print "You got { ncard 1, $new }. "; say "({ $.deck.elems or 'No' } card{ $.deck.elems == 1 ?? !! 's' } left.)"; self.getcards(1, $new); }
method getcards (Int $quantity, Int $pip) { @!h[$pip] += $quantity; @.h[$pip] == BOOKSIZE or return; ++$!books; say ($.cpu ?? "The dealer puts down a book of { @ppips[$pip] }" !! "That's a book"), " (for a total of $.books book{ $.books == 1 ?? !! 's' })."; self.losecards($pip); }
method losecards (Int $pip) { @.h[$pip] = 0; while none @.h and $.deck.elems { say do $.cpu ?? "The dealer's hand is empty, so $foe_nominative_pronoun draws a new card." !! "Your hand's empty, so you draw a new card."; self.draw; } }
method learn (Int $pip, Maybe $m) { @.know[$pip].set($m) }
method notice_draw () { .incr for @.know }
method choose_request () returns Int { #self.showhand; #say 'Know: ', join ', ', map # { .maybe ~~ Yes ?? 'Yes' !! .maybe ~~ Dunno ?? 'Dunno' !! .n }, # @.know; my @ps = map { .key }, grep { .value }, pairs @.h; return pick 1, maxes { @.h[$^p] }, do # Most of all we should ask for cards we know the # user has. grep { @.know[$^p].maybe ~~ Yes }, @ps or # Then try asking for one we haven't requested # before. grep { @.know[$^p].maybe ~~ Dunno }, @ps or # Then try asking for one we least recently # asked about. maxes { @.know[$^p].n }, @ps; }
}
sub play () {
my Int @deck; # Shuffle the deck until the first two hands contain no books. # (If BOOKSIZE is greater than 2 and HANDSIZE is reasonably # small, this'll probably take only one shuffle.) repeat { @deck = pick *, ^@pips xx BOOKSIZE } until none(map { count $^x, @deck[^HANDSIZE] }, ^@pips) >= BOOKSIZE and none(map { count $^x, @deck[HANDSIZE ..^ 2*HANDSIZE] }, ^@pips) >= BOOKSIZE;
my Player $user .= new(False, @deck); my Player $foe .= new(True, @deck); while any |$user.h or any |$foe.h { # The user goes first. while any |$user.h { say ; $user.showhand; my $request = readpip $user.h; $foe.learn($request, Yes); if $foe.h[$request] -> $quantity is copy { say 'The dealer reluctantly hands over ', ncard($quantity, $request), '.'; $foe.losecards($request); $user.getcards($quantity, $request); } else { say '"Go fish!"'; $user.draw; $foe.notice_draw; last; } }
while any |$foe.h { my $request = $foe.choose_request; say "\n\"Got any ", @ppips[$request], '?"'; $foe.learn($request, No); if $user.h[$request] -> $quantity is copy { say '"Thanks!"'; $foe.getcards($quantity, $request); $user.losecards($request); } else { say 'The dealer goes fishing.'; $foe.draw; last; } }
}
say "\nGame over!"; say 'Your books: ', $user.books; say "The dealer's books: ", $foe.books; say do $user.books > $foe.books ?? 'A winner is you!' !! $user.books < $foe.books ?? 'Alas, you have lost.' # A draw is possible if @pips !% 2. !! "It's a draw.";
}
sub MAIN () { play }</lang>
Python
<lang Python>import random import sys class HumanPlayer(object):
def __init__(self,deck): self.hand = {} 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] = self.hand.get(cardDrawn,0)+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 in self.hand.keys(): if self.hand[key] == 4: #completed a book self.book.append(key) print '%s completed the book of %s\'s.' % (self.name,key) self.score += 1 self.hand.pop(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 in self.hand.keys() for i in range(self.hand[key])]) #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 not self.hand.has_key(chooseCard): print 'You don\'t have that card. Try again! (or enter quit to exit)' chooseCard = self.makeTurn() return chooseCard def fishFor(self,card): if self.hand.has_key(card): # if card in hand, returns count and removes the card from hand val = self.hand.pop(card) self.emptyCheck() return val 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 = {} 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] = self.hand.get(cardDrawn,0)+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.intersection(set(self.hand.keys()))) #checks for cards in hand that computer knows you have if not any(candidates): candidates = self.hand.keys() #if no intersection between those two, random guess rnd = random.randrange(len(candidates)) print '%s fishes for %s.' % (self.name,candidates[rnd]) return candidates[rnd] def fishFor(self,card): #Same as for humans players, but adds the card fished for to opponentHas list. self.opponentHas.add(card) if self.hand.has_key(card): # if card in hand, returns count and removes the card from hand val = self.hand.pop(card) self.emptyCheck() return val return False def gotCard(self,card,amount): self.hand[card] += amount if card in self.opponentHas: self.opponentHas.remove(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 any(self.deck) or any(self.player[0].hand) or any(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>
Tcl
<lang tcl>package require Tcl 8.6
- How to sort ranks
proc suitorder {a b} {
set a1 [lsearch -exact {2 3 4 5 6 7 8 9 10 J Q K A} $a] set b1 [lsearch -exact {2 3 4 5 6 7 8 9 10 J Q K A} $b] expr {$a1 - $b1}
}
- Class to manage the deck of cards
oo::class create Deck {
variable deck
constructor Template:Packs 1 {
set deck [list] for {set p 0} {$p < $packs} {incr p} { foreach suit {C D H S} { foreach pip {2 3 4 5 6 7 8 9 10 J Q K A} { lappend deck [list $pip $suit] } } }
}
method shuffle {} {
# Shuffle in-place for {set i [llength $deck]} {[incr i -1] > 0} {} { set n [expr {int($i * rand())}] set card [lindex $deck $n] lset deck $n [lindex $deck $i] lset deck $i $card }
}
method deal {num} {
incr num -1 set hand [lrange $deck 0 $num] set deck [lreplace $deck 0 $num] return $hand
}
method renderCard {card} {
string map {C \u2663 D \u2662 H \u2661 S \u2660 " " {}} $card
} method print {hand} {
set prev {} foreach card [my sortHand $hand] { if {[lindex $card 0] ne $prev} { if {$prev ne ""} {puts ""} puts -nonewline \t[my renderCard $card] } else { puts -nonewline " [my renderCard $card]" } set prev [lindex $card 0] } puts ""
}
method sortHand {hand} {
lsort -index 0 -command suitorder [lsort -index 1 $hand]
}
proc empty {} {
return [expr {[llength $deck] == 0}]
}
}
- "Abstract" class of all players; implements core game mechanics
- from a player's perspective
oo::class create GoFishPlayer {
variable theDeck hand opponent constructor {deck otherPlayer} {
set theDeck $deck set hand [$deck deal 9] set opponent $otherPlayer
}
method ask {rank} {
set response {} set new {} foreach card $hand { if {[lindex $card 0] eq $rank} { lappend response $card } else { lappend new $card } } set hand [expr {[llength $new] ? $new : [$theDeck deal 1]}] return $response
} method AskFor {rank} {
set withoutOne 1 foreach card $hand { if {[lindex $card 0] eq $rank} { set withoutOne 0 break } } if {$withoutOne} { error "do not have any $rank cards" }
set response [$opponent ask $rank] if {[llength $response]} { lappend hand {*}$response } else { my GoFish lappend hand {*}[$theDeck deal 1] }
return [llength $response]
}
method MakeBooks {} {
foreach rank {2 3 4 5 6 7 8 9 10 J Q K A} { set n {} set idx -1 foreach card $hand { incr idx if {[lindex $card 0] eq $rank} { lappend n $idx } } if {[llength $n] == 4} { announceBook $rank [self] foreach idx [lreverse $n] { set hand [lreplace $hand $idx $idx] } } } if {[llength $hand] == 0} { set hand [$theDeck deal 1] }
}
method makeAPlay {} {
set msg "" while {$::books(total) < 13} { set rank [my SelectRank $msg] try { if {![my AskFor $rank]} { my YieldToOpponent break } } on error msg { # Back round the loop with an error message } on ok {} { my MakeBooks set msg "" } } my MakeBooks
}
method GoFish {} {
# Do nothing with this notification by default
} method madeBook {rank who} {
# Do nothing with this notification by default
} method YieldToOpponent {} {
# Do nothing with this notification by default
} method SelectRank {msg} {
error "not implemented"
}
}
- A player that works by communicating with a human
oo::class create HumanPlayer {
superclass GoFishPlayer variable theDeck hand opponent method madeBook {rank who} {
if {$who eq [self]} {set who "You"} puts "$who made a book of $rank"
} method YieldToOpponent {} {
puts "Now your opponent's turn"
} method AskFor {rank} {
set count [next $rank] puts "You asked for ${rank}s and received $count cards" if {$count > 0} { puts "You may ask again!" } return $count
} method ask {rank} {
set cards [next $rank] puts "[namespace tail $opponent] asked for $rank cards, and got [llength $cards] of them" return $cards
} method GoFish {} {
puts "You were told to \"Go Fish!\""
}
method SelectRank {msg} {
if {$msg ne ""} { puts "ERROR: $msg" } set I [namespace tail [self]] puts "You are ${I}: Your cards are:" $theDeck print $hand while 1 { puts -nonewline "What rank to ask for? " flush stdout set rank [string toupper [gets stdin]] if {$rank in {2 3 4 5 6 7 8 9 10 J Q K A}} { return $rank } puts "Rank must be 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, or A" puts "You must also have at least one of them already" }
}
}
- A computer player that tracks what it's opponent must have
oo::class create ThinkingPlayer {
superclass GoFishPlayer variable state hand constructor args {
next {*}$args foreach rank {2 3 4 5 6 7 8 9 10 J Q K A} { set state($rank) unknown }
}
method madeBook {rank who} {
set state($rank) booked
} method AskFor {rank} {
set count [next $rank] set state($rank) none if {$count == 0} { foreach rank {2 3 4 5 6 7 8 9 10 J Q K A} { if {$state($rank) eq "none"} { set state($rank) unknown } } } return $count
} method ask {rank} {
set cards [next $rank] set state($rank) some return $cards
}
method GoFish {} {
puts "You told your opponent to \"Go Fish!\""
}
method SelectRank {ignored} {
# If we know they have the cards and we can grab them, do so! # It's a safe move since we get to go again. foreach {rank s} [array get state] { if {$s eq "some" && [lsearch -exact -index 0 $hand $rank] >= 0} { return $rank } } # Only unsafe moves remain; pick a random non-stupid one foreach c $hand { set rank [lindex $c 0] if {$state($rank) ne "none"} { set r([lindex $c 0]) . } } if {[array size r]} { return [lindex [array names r] [expr {int([array size r]*rand())}]] } # No good choices; oh well... return [lindex $hand [expr {int([llength $hand]*rand())}] 0]
}
}
- How announcements of a book being made are done
proc announceBook {rank who} {
global books
A madeBook $rank $who B madeBook $rank $who lappend books($who) $rank incr books(total)
}
- Stitch things together to make a whole game.
Deck create deck deck shuffle array set books {total 0 ::A {} ::B {}} HumanPlayer create A deck B ThinkingPlayer create B deck A while {$books(total) < 13} {
A makeAPlay if {$books(total) < 13} {
B makeAPlay
}
} puts "You have [llength $books(::A)]: [lsort -command suitorder $books(::A)]" puts "The computer has [llength $books(::B)]: [lsort -command suitorder $books(::B)]" if {[llength $books(::A)] > [llength $books(::B)]} {
puts "You win!"
} else {
puts "The computer won!"
}</lang>
Notes on the Mechanical Player
The computer player (implemented as a subclass of the generic player class) has four states for each rank (aside from basic overall state like what cards it is holding, which every player has to have):
- unknown
- Don't know if the opponent has any cards in that rank.
- none
- Opponent has no cards there; I took them away.
- some
- Opponent has cards there; they tried to get them off me and haven't booked them yet.
- booked
- Someone has booked the rank.
It prefers to take cards away from the opponent if it can and tries hard to avoid moves it knows will fail. It never makes illegal moves. It does not bother to look at the number of booked suits, though as that is global state it could. No player or the deck has any method to reveal (within the game world, not the display) what hand of cards it actually has.