The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

#!/usr/bin/perl
=begin metadata
Name: fish
Description: plays the children's game of Go Fish
Author: Clinton Pierce, clintp@geeksalad.org
License: perl
=end metadata
=cut
use strict;
use Getopt::Std qw(getopts);
use List::Util qw(shuffle);
$Getopt::Std::STANDARD_HELP_VERSION = 1;
my(@DECK, @PLAYERS_HAND, @COMPUTERS_HAND, %BOOKS, %opt);
my(@HIS_PAST_GUESSES, @MY_PAST_GUESSES);
my($asker, $opponent, $professional, $whoseturn, $status, $myb, $yourb);
my %so=( 'A'=>1, 'J'=>11, 'Q'=>12, 'K'=>13 ); # For sort ranking, below.
my %iscard = map { $_ => 1 } qw(A J Q K 2 3 4 5 6 7 8 9 10);
sub pickone { # Computer's card-picking routine. Dumb or smart.
my($myarr)=@_;
my %h = map { $_ => 1 } @$myarr;
return( (keys %h)[rand scalar keys %h] ) if (! $professional);
# Reasonably "smart" picker.
my(@saveguess,$guess)=();
while(@HIS_PAST_GUESSES) {
$guess=shift @HIS_PAST_GUESSES; # use his past guesses as a guide
next if (grep($guess eq $_, keys %{$BOOKS{'You'}}));
next if (grep($guess eq $_, @MY_PAST_GUESSES));
if ( ! grep($guess eq $_, @COMPUTERS_HAND) ) {
push(@saveguess, $guess); # come back to it later.
next;
}
push(@MY_PAST_GUESSES, $guess);
return($guess);
}
@HIS_PAST_GUESSES=@saveguess;
$guess=(keys %h)[rand scalar keys %h]; # degrade to blind guesses
push(@MY_PAST_GUESSES, $guess);
return($guess);
}
sub compguess { # Return 0 if hit, nonzero (the card) if miss
my($guess, $stat);
do {
print "\nI ask you for: ";
$guess=pickone(\@COMPUTERS_HAND);
print $guess, "\n";
} while(! defined ($stat=askfor(\@COMPUTERS_HAND, \@PLAYERS_HAND, $guess)));
havebook(\@COMPUTERS_HAND);
return($stat?0:$guess);
}
sub playguess {
my($guess, $stat);
do {
print "\nYou ask me for: ";
$guess=<STDIN>; chomp($guess);
} while(! defined ($stat=askfor(\@PLAYERS_HAND, \@COMPUTERS_HAND, $guess)));
# Professional mode....is watching you.
push(@HIS_PAST_GUESSES, $guess);
havebook(\@PLAYERS_HAND);
return($stat?0:$guess);
}
sub draw {
my($drawarr, $guess)=@_;
my $foo = pop @DECK;
die("No more cards: S.N.H.") if (!$foo);
if ($foo eq $guess) {
print "$asker drew the guess\n";
print "$asker get to go again\n";
} else {
print "$asker drew a $foo\n" if ($asker eq 'You');
# Professional mode...is watching you
@MY_PAST_GUESSES=();
}
push @$drawarr, $foo;
return($foo);
}
sub askfor {
my($askarr, $vicarr, $card)=@_; # The asker, the victim, and the card
$card = uc $card;
if ($card eq "") {
print "I have ", scalar(@COMPUTERS_HAND), " cards in my hand ";
printhand(\@COMPUTERS_HAND, 'I', 1);
print "There are ", scalar(@DECK), " cards remaining in the stock\n";
return;
}
exit if ($card eq 'QUIT');
if ($card eq 'P') {
$professional=!$professional;
print $professional?"Entering":"Leaving", " professional mode\n";
return;
}
unless (exists $iscard{$card}) {
print "I don't understand!\n";
return;
}
if (! grep($card eq $_, @$askarr)) {
print "$asker dont have any $card" . "s!\n";
return;
}
if (! grep($card eq $_, @$vicarr)) {
print "$opponent say \"GO FISH!\"\n";
return 0;
}
my @GOOD=grep($card eq $_, @$vicarr);
@$vicarr=(grep($card ne $_, @$vicarr));
print "$opponent have ", scalar(@GOOD), " $card", (scalar(@GOOD)>1)?"s":"", "\n";
print "$asker get another guess!\n";
push(@$askarr, @GOOD);
}
sub havebook {
my $array=shift;
my(%hash,$flag); $flag=0;
foreach(@$array){ $hash{$_}++;}
foreach my $value (keys %hash) {
if ($hash{$value}==4) {
@$array=grep( ($_ ne $value),@$array);
print "$asker made a book of ", $value, "s\n";
$BOOKS{$asker}{$value}=1;
$flag=1;
}
}
return($flag);
}
sub printhand {
unless ($_[2]) {
print "$_[1] hand is: " , join(' ', sort
{ (($a=~/^\d+$/)?$a:$so{$a}) <=> (($b=~/^\d+$/)?$b:$so{$b}); }
@{$_[0]});
}
my @bl=keys %{$BOOKS{$_[1]}};
if (@bl) {
print " + Book", scalar(@bl)>1?"s ":" ", join(' ', @bl);
}
print "\n";
}
#
# MAIN
#
getopts('p', \%opt) or die "Usage: $0 [-p]\n";
$professional = $opt{'p'};
print "Do you want to see instructions (y/n)?";
$status=<STDIN>;
if ($status=~/^y/i) {
print <DATA>;
print "Press <return>"; $status=<STDIN>;
}
@DECK = (keys %iscard) x 4;
@DECK = shuffle(@DECK);
foreach(1..7) {
push(@PLAYERS_HAND, pop @DECK);
push(@COMPUTERS_HAND, pop @DECK);
}
$whoseturn=int(rand(2)); # True for player
print $whoseturn?"You":"I", " get to start\n";
while(1) {
print "\n";
last if ( (!@PLAYERS_HAND) or (!@COMPUTERS_HAND) );
printhand(\@PLAYERS_HAND,"You");
$asker=$whoseturn?"You":"I";
$opponent=(!$whoseturn)?"You":"I";
my $hand;
if ($whoseturn) {
$status=playguess();
$hand = \@PLAYERS_HAND;
} else {
$status=compguess();
$hand = \@COMPUTERS_HAND;
}
if ($status) {
if (draw($hand, $status) eq $status) {
havebook($hand);
redo;
} else {
redo if (havebook($hand));
$whoseturn=!$whoseturn;
}
}
}
print @COMPUTERS_HAND?"You":"I", " ran out of cards\n";
$myb=scalar(keys %{$BOOKS{'I'}});
$yourb=scalar(keys %{$BOOKS{'You'}});
print "I had $myb Books and you had $yourb\n";
if ($myb==$yourb) {
print "It's a tie!\n";
} else {
print $myb>$yourb?"I":"You", " win!\n";
}
=pod
=head1 NAME
fish - plays the children's game of Go Fish
=head1 SYNOPSIS
fish [-p]
=head1 DESCRIPTION
I<fish> plays the children's game of Go Fish. The computer plays against
the player. The object of the game is to collect more "books" of cards
than your opponent by querying each other's hand.
This version implements the child-friendly rules outlined
in I<Hoyle's Rules of Games>.
=head2 OPTIONS
I<fish> accepts the following option:
=over 4
=item -p
Puts I<fish> into "professional mode".
=back
=head1 BUGS
Strategy employed by I<fish>'s "professional mode" could be
somewhat smarter.
User interaction resembles BSD's implementation which is rather...spartan.
=head1 AUTHOR
The Perl implementation of I<fish> was written by Clinton Pierce, I<clintp@geeksalad.org>.
=head1 COPYRIGHT and LICENSE
This program is Copyright 1999, by Clinton Pierce.
Freely redistributable under the Perl Artistic License.
=cut
__DATA__
This is the traditional children's card game "Go Fish". We each get seven
cards, and the rest of the deck is kept to be drawn from later. The
object of the game is to collect "books", or all of the cards of a single
value. For example, getting four 2's would give you a "book of 2's".
We take turns asking each other for cards, but you can't ask me for a card
value if you don't have one of them in your hand! If I have any cards of
the value you ask for, I have to give them to you. As long as I have one
of the cards you ask for, you get to keep asking. If you ask me for a
card of which I don't have any, then I'll tell you to "Go Fish!" This
means that you draw a card from the deck. If you draw the card you asked
me for, you get to keep asking me for cards. If not, it's my turn and I ask
you for a card.
Sometimes you get to ask first, sometimes I do. I'll tell you when it's
your turn to move, I'll draw cards from the deck for you, and I'll tell
you what you have in your hand. (Don't worry, I don't look at your hand
when I'm trying to decide what card to ask for, honest!)
Your input can be a card name ("A", "2", "3", "4", "5", "6", "7", "8",
"9", "10", "J", "Q" or "K") or the letter "p", or "quit". The letter "p"
makes my game much smarter, and the line "quit" stops the game. Just
hitting the carriage return key displays how many cards I have in my hand,
how many are left in the deck, and which books I've gotten.
Normally, the game stops when one of us runs out of cards, and the winner
is whoever has the most books!
Good luck!