File Coverage

blib/lib/Games/Euchre/AI.pm
Criterion Covered Total %
statement 6 76 7.8
branch 0 26 0.0
condition 0 11 0.0
subroutine 2 15 13.3
pod 13 13 100.0
total 21 141 14.8


line stmt bran cond sub pod time code
1             package Games::Euchre::AI;
2              
3             =head1 NAME
4              
5             Games::Euchre::AI - Player API for Euchre card game
6              
7             =head1 DESCRIPTION
8              
9             This class implements a skeletal Euchre player programming interface.
10             Subclasses can be created quite easily as interactive interfaces or AI
11             computer players.
12              
13             If you wish to write your own computer player, I recommend you start
14             with Games::Euchre::AI::Simple. If you wish to write your own human
15             interface, I recommend you start with Games::Euchre::AI::Human.
16              
17             =cut
18              
19 1     1   1160 use strict;
  1         2  
  1         31  
20 1     1   4 use warnings;
  1         2  
  1         813  
21              
22             =head1 CLASS METHODS
23              
24             =over 4
25              
26             =item new
27              
28             Create and initialize a new Euchre AI. This object is implemented as
29             an empty hash. Subclasses may wish to use this hash for state
30             storage.
31              
32             =cut
33              
34             sub new {
35 0     0 1   my $pkg = shift;
36 0           return bless({
37             # subclasses can store anything they want in here
38             # because $self is unused by this superclass
39             }, $pkg);
40             }
41              
42             =back
43              
44             =head1 INSTANCE METHODS
45              
46             =head2 Actions
47              
48             The following methods are called in the course of the game where the AI (or human) has to make a decision. The state of the game is always passed in a hashreference. The following fields are always available:
49              
50             'name' is the name of the current player. This is useful for output
51             messages.
52              
53             'names' is a hash relating player number to player name for all four
54             players.
55              
56             'debug' is a boolean indicating if we are debugging game or the AIs.
57             Your AI may wish to provide verbose output if debugging is going on.
58              
59             =over 4
60              
61             =item bid STATEHASH
62              
63             Choose trump or pass. The relevent details of the current state of
64             the game are provided in a hash reference. Here is an example of that
65             data structure:
66              
67             {
68             name => 'Player 1',
69             names => {1 => 'Player 1', 2 => 'P2', 3 => 'P3', 4 => 'Fred'},
70             number => 1,
71             turnedUp => 'KH',
72             passes => 1,
73             ourScore => 2,
74             theirScore => 4,
75             winScore => 10,
76             hangdealer => false,
77             notrump => false,
78             hand => ['JS', 'QH', '9S', 'KC', 'AD'],
79             debug => false,
80             }
81              
82             'turnedUp' is the suit and value of the card on the top of the blind.
83             This will be undef on the second round of bidding.
84              
85             'passes' says how many people have passed so far
86              
87             'hangdealer' is a boolean saying whether the 'hang-the-dealer'
88             optional rule is in effect
89              
90             'notrump' is a boolean saying whether the 'no trump' optional rule is in effect
91              
92             This function must return one of: H, D, C, S, N, HA, DA, CA, SA, NA, or undef
93              
94             'N' means 'no trump', 'A' means 'alone', undef means 'pass'. Not all
95             of these are legal at any given round! Use the isLegalBid() method
96             below if you are unsure.
97              
98             =cut
99              
100             sub bid {
101 0     0 1   my $self = shift;
102 0           my $state = shift;
103              
104 0           return undef; # pass (may not be legal)
105             }
106              
107             =item pickItUp STATEHASH
108              
109             If this is called, you are the dealer and someone called trump.
110             Choose which card from your hand to discard in exchange for the top
111             card of the blind. The relevent details of the current state of the
112             game are provided in a hash reference. Here is an example of that
113             data structure:
114              
115             {
116             name => 'Player 1',
117             names => {1 => 'Player 1', 2 => 'P2', 3 => 'P3', 4 => 'Fred'},
118             number => 1,
119             turnedUp => 'KH',
120             trump => 'H',
121             bidder => 2,
122             weBid => false,
123             usAlone => false,
124             themAlone => false,
125             hand => ['JS', 'QH', '9S', 'KC', 'AD'],
126             debug => false,
127             }
128              
129             This method should return the 0-based index of the card to trade for
130             the turnedUp card. Namely, this in the index of the 'hand' array for
131             the card that you choose.
132              
133             =cut
134              
135             sub pickItUp {
136 0     0 1   my $self = shift;
137 0           my $state = shift;
138              
139 0           return 0; # first card
140             }
141              
142             =item playCard STATEHASH
143              
144             Choose which card from your hand to play on this trick. The relevent
145             details of the current state of the game are provided in a hash
146             reference. Here is an example of that data structure:
147              
148             {
149             name => 'Player 1',
150             names => {1 => 'Player 1', 2 => 'P2', 3 => 'P3', 4 => 'Fred'},
151             number => 1,
152             trump => 'H',
153             bidder => 2,
154             weBid => true,
155             usAlone => false,
156             themAlone => false,
157             trick => 2,
158             ourTricks => 0,
159             theirTricks => 1,
160             ourScore => 2,
161             theirScore => 4,
162             winScore => 10,
163             played => ['10H', '9H', 'QC'],
164             playedBy => [2, 3, 4, 1],
165             hand => ['JH', 'AH', 'AS', 'KS'],
166             debug => false,
167             }
168              
169             'playedBy' is an arrayref of numbers of the players in the order they
170             will play. Without this, the alone possibility makes it hard to tell
171             who played what.
172              
173             Any needed information not stored here (like who was the dealer, what
174             was the turn-up card, what happened in the first trick) is YOUR
175             responsibility to collect and store in your instance.
176              
177             This method should return the 0-based index of the card to play.
178             Namely, this in the index of the 'hand' array for the card that you
179             choose.
180              
181             =cut
182              
183             sub playCard {
184 0     0 1   my $self = shift;
185 0           my $state = shift;
186              
187 0           return 0; # first card (may not be legal)
188             }
189              
190             =back
191              
192             =head2 Event handlers
193              
194             These methods are called when certain events happen in the game. They
195             are for informational purposes only, and no values should be returned.
196             Unlike the methods above, these really only need to be overridden for
197             human players, or debugging output, or ubersmart robots.
198              
199             =over 4
200              
201             =item endOfBidding STATEHASH
202              
203             {
204             name => 'Player 1',
205             names => {1 => 'Player 1', 2 => 'P2', 3 => 'P3', 4 => 'Fred'},
206             number => 1,
207             trump => 'H',
208             dealer => 1,
209             bidder => 2,
210             weBid => false,
211             usAlone => false,
212             themAlone => false,
213             debug => false,
214             }
215              
216             'trump' may be undef if everybody passed (which means a new turn is
217             about to start).
218              
219             =cut
220              
221             sub endOfBidding {
222 0     0 1   my $self = shift;
223 0           my $state = shift;
224             # do nothing
225             }
226              
227              
228             =item endOfTrick STATEHASH
229              
230             {
231             name => 'Player 1',
232             names => {1 => 'Player 1', 2 => 'P2', 3 => 'P3', 4 => 'Fred'},
233             number => 1,
234             played => ['KH', '9H', 'JC', '9S'],
235             playedBy => [2, 3, 4, 1],
236             myCard => 3,
237             winCard => 0,
238             winner => 2,
239             debug => false,
240             }
241              
242             'playedBy' is an arrayref of numbers of the players in the order they
243             will play. Without this, the alone possibility makes it hard to tell
244             who played what.
245              
246             'myCard' is 0-based index in the 'played' array for the card played by this
247             player. It is undef if the player's teammate went alone.
248              
249             'winCard' is the 0-based index of the winning card. The mapping of
250             winning card index to winning player number may not be obvious due to
251             the possibility of players going alone, so both numbers are explicitly
252             provided.
253              
254             'winner' is the 1-based number of the winning player.
255              
256             =cut
257              
258             sub endOfTrick {
259 0     0 1   my $self = shift;
260 0           my $state = shift;
261             # do nothing
262             }
263              
264             =item endOfHand STATEHASH
265              
266             {
267             name => 'Player 1',
268             names => {1 => 'Player 1', 2 => 'P2', 3 => 'P3', 4 => 'Fred'},
269             number => 1,
270             ourTricks => 3,
271             theirTricks => 2,
272             winType => 'win',
273             ourScore => 8,
274             theirScore => 6,
275             winScore => 10,
276             debug => false,
277             }
278              
279             'winType' is one of: 'win', 'all', 'alone', 'euchre', which is 1, 2,
280             4, and 2 points respectively.
281              
282             =cut
283              
284             sub endOfHand {
285 0     0 1   my $self = shift;
286 0           my $state = shift;
287             # do nothing
288             }
289              
290             =item endOfGame STATEHASH
291              
292             {
293             name => 'Player 1',
294             names => {1 => 'Player 1', 2 => 'P2', 3 => 'P3', 4 => 'Fred'},
295             number => 1,
296             ourScore => 10,
297             theirScore => 6,
298             debug => false,
299             }
300              
301             =cut
302              
303             sub endOfGame {
304 0     0 1   my $self = shift;
305 0           my $state = shift;
306             # do nothing
307             }
308              
309             =back
310              
311             =head2 Miscellaneous AI Information
312              
313             These methods are used to specify the programmatic behavior of your AI
314             across mutliple games. Many AIs won't need this.
315              
316             =over 4
317              
318             =item persist
319              
320             This method should return a boolean indicating whether the AI wants to
321             live longer than one game. When a new game starts, this method is
322             called. If it returns true, the instance is retained and the reset()
323             method is called. If it returns false, the game will just call new()
324             again creating a new instance. The default is falsee. If you
325             override this to return true, you should override the reset() method
326             too.
327              
328             =cut
329              
330             sub persist {
331 0     0 1   my $self = shift;
332              
333 0           return undef;
334             }
335              
336             =item reset
337              
338             Called to refresh the instance between games.
339              
340             =cut
341              
342             sub reset {
343 0     0 1   my $self = shift;
344             }
345              
346             =back
347              
348             =head2 Utility Methods
349              
350             These are routines of general convenience. They are never called from
351             external objects. They can be called as class methods or instance
352             methods.
353              
354             =over 4
355              
356             =item isLegalBid STATEHASH BID
357              
358             Given a state hash and bid of the form described in the bid() method
359             above, return a boolean indicating whether that bid is valid.
360              
361             =cut
362              
363             sub isLegalBid {
364 0     0 1   my $me = shift;
365 0           my $state = shift;
366 0           my $bid = shift;
367            
368             # Is it a pass?
369 0 0         if (!defined $bid) {
370             # Can't pass on the last bid if hang-the-dealer is in effect
371 0 0 0       if ($state->{hangdealer} && $state->{passes} == 7) {
372 0           return undef;
373             } else {
374 0           return $me;
375             }
376             }
377              
378             # Is is a valid bid?
379 0 0         return undef if ($bid !~ /^([HSDCN])(|A)$/i);
380              
381 0           my $suit = uc($1);
382 0           my $alone = $2;
383              
384             # Is it no trump?
385 0 0         if ($suit eq "N") {
386             # NT must be enable to call it
387 0 0         return undef if (!$state->{notrump});
388             }
389              
390             # Must call THE suit in the first round
391 0 0         if ($state->{passes} < 4) {
392 0 0         unless ($state->{turnedUp} =~ /(.)$/) {
393 0           die "Missing suit on turned up card!?!?!?";
394             }
395 0           my $topsuit = $1;
396 0 0         return undef if ($suit ne $topsuit);
397             }
398              
399 0           return $me;
400             }
401              
402             =item isLegalPlay STATEHASH CHOICE
403              
404             Given a state hash and a hand index of the form described in the
405             playCard() method above, return a boolean indicating whether that
406             choice of plays is valid.
407              
408             =cut
409              
410             sub isLegalPlay {
411 0     0 1   my $me = shift;
412 0           my $state = shift;
413 0           my $choice = shift;
414              
415 0           my $card = $state->{hand}->[$choice];
416 0 0         return undef if (!$card);
417              
418             # Is it the first card led?
419 0           my $cards = $state->{played};
420 0           my $leadcard = $cards->[0];
421 0 0         return $me if (!$leadcard); # lead card can be anything
422              
423             # Is it following suit?
424 0           my $cardsuit = $me->getCardSuit($state, $card);
425 0           my $leadsuit = $me->getCardSuit($state, $leadcard);
426 0 0         return $me if ($cardsuit eq $leadsuit);
427              
428             # Is the player out of the lead suit?
429 0           my $hasLeadSuit = undef;
430 0           foreach my $card (@{$state->{hand}}) {
  0            
431 0           my $cardsuit = $me->getCardSuit($state, $card);
432 0 0         if ($cardsuit eq $leadsuit) {
433 0           $hasLeadSuit = $me;
434 0           last;
435             }
436             }
437 0           return !$hasLeadSuit;
438             }
439              
440             =item getCardSuit STATEHASH CARD
441              
442             Given a state hash from either any of the above action methods
443             [e.g. bid(), pickItUp() or playCard()], return the suit of a card,
444             properly accounting for suit of the left jack if trump has been
445             called. The card argument should be in the form of '10C' or 'KD'. The return value will be one of 'H', 'C', 'S' or 'D'.
446              
447             =cut
448              
449             sub getCardSuit {
450             # report suit, or trumpsuit for left jack
451 0     0 1   my $me = shift;
452 0           my $state = shift;
453 0           my $card = shift;
454              
455 0           $card =~ /^(.*)(.)$/;
456 0           my $cardvalue = $1;
457 0           my $cardsuit = $2;
458 0           my %othertrump = ("H" => "D", "D" => "H", "S" => "C", "C" => "S");
459 0   0       my $othertrump = $othertrump{$state->{trump} || ""}; # undef is notrump
460 0 0 0       if ($othertrump && $cardsuit eq $othertrump && $cardvalue eq "J") {
      0        
461 0           $cardsuit = $state->{trump};
462             }
463 0           return $cardsuit;
464             }
465              
466             1;
467             __END__