File Coverage

blib/lib/Games/Poker/OPP.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Games::Poker::OPP;
2 1     1   8176 use IO::Socket::INET;
  1         24288  
  1         7  
3 1     1   2356 use Games::Poker::TexasHold'em; #'
  0            
  0            
4             use Carp;
5              
6             use 5.006;
7             use strict;
8             use warnings;
9              
10             our $VERSION = '1.0';
11              
12             use constant FOLD => 0;
13             use constant CALL => 1;
14             #use constant CHECK => 1; # Synonym (but sadly also a Perl keyword)
15             use constant RAISE => 2;
16             use constant BLIND => 3;
17             use constant GOODBYE => 11; # Undocumented.
18             use constant JOIN_GAME => 20;
19             use constant GOODPASS => 21;
20             use constant BADPASS => 22;
21             use constant BADNICK => 24;
22             use constant ACTION => 30;
23             use constant CHAT => 32;
24             use constant QUIT_GAME => 33;
25             use constant GET_GRAPH => 42;
26             use constant INFORMATION => 43;
27             use constant SET_FACE => 45;
28             use constant GET_FACE => 46;
29             use constant CHANGE_FACE => 47;
30             use constant START_NEW_GAME => 50;
31             use constant HOLE_CARDS => 51;
32             use constant NEW_STAGE => 52;
33             use constant WINNERS => 53;
34             use constant CHATTER => 54;
35             use constant NEXT_TO_ACT => 57;
36             use constant PING => 60;
37             use constant PONG => 61;
38              
39             use Exporter;
40              
41             our @ISA = qw(Exporter);
42             our %EXPORT_TAGS = ( 'actions' => [ qw( RAISE FOLD CHECK CALL ) ],
43             'server_notices' => [ qw(
44             START_NEW_GAME HOLE_CARDS NEW_STAGE NEXT_TO_ACT
45             FOLD CALL RAISE BLIND WINNERS CHATTER INFORMATION
46             ) ]
47             );
48              
49             our @EXPORT = (@{$EXPORT_TAGS{actions}}, @{$EXPORT_TAGS{server_notices}});
50             our @protocol;
51             my @handlers;
52             map {$protocol[$_->[0]] = $_->[1];
53             $handlers[$_->[0]] = $_->[2] if $_->[2];
54             } (
55             [ START_NEW_GAME , "N5(Z*NN)*", \&new_game_handler ],
56             [ HOLE_CARDS , "NZ*", \&hole_card_handler ],
57             [ NEW_STAGE , "NZ*", \&next_stage_handler ],
58             [ NEXT_TO_ACT , "N4", \&next_turn_handler ],
59             [ FOLD , "NN", \&fold_handler ],
60             [ CALL , "NN", \&call_handler ],
61             [ RAISE , "NN", \&raise_handler ],
62             [ BLIND , "NN", \&blinds_handler ],
63             [ WINNERS , "N(NN)*" ],
64              
65             # Stuff we send
66             [ JOIN_GAME , "Z*Z*NZ*" ],
67             [ ACTION , "N" ],
68             [ GET_GRAPH , "Z*" ],
69             [ SET_FACE , "Z*" ],
70             [ GET_FACE , "Z*" ],
71             [ CHANGE_FACE , "N" ],
72             [ CHAT , "Z*" ],
73             [ QUIT_GAME , "" ],
74              
75             # Status messages
76             [ GOODPASS , "" ],
77             [ BADPASS , "" ],
78             [ BADNICK , "" ],
79              
80             # Handled internally by playgame
81             [ PING , "" ],
82             [ PONG , "" ],
83             [ CHATTER , "Z*" ],
84             [ INFORMATION , "Z*" ],
85             );
86              
87             sub send_packet {
88             my ($self, $message_id, @data) = @_;
89             croak sprintf "Protocol error: command %d not recognised", $message_id
90             unless exists $protocol[$message_id];
91             my $packed_data = "";
92             if ($protocol[$message_id]) {
93             eval { $packed_data = pack($protocol[$message_id], @data); };
94             croak sprintf "Problem packing data for %d command", $message_id if $@;
95             }
96             my $packet = pack "NN", $message_id, length $packed_data;
97             $packet .= $packed_data;
98             $self->put($packet);
99             return $packet;
100             }
101              
102             sub get_packet {
103             my $self = shift;
104             # You got the message?
105             return unless my $data = $self->get(8);
106             # I just got it!
107             my ($code, $length) = unpack("NN", $data);
108             # And give?
109             croak sprintf "Protocol error: command %d not recognised", $code
110             unless exists $protocol[$code];
111             # You've never been with it - I mean, with us.
112             if (!$length) {
113             # I'm gone, gone away.
114             return $code
115             # But you were here, then you went and gone.
116             }
117             # Got the word?
118             $data = $self->get($length);
119             my @args;
120             # The message.
121             eval { @args = unpack($protocol[$code], $data) };
122             croak sprintf "Didn't get the arguments to the 0x%x command we expected",
123             $code if $@;
124             # Give, all you want's give, that's it!
125             return ($code, @args);
126             # Give it to me baby!
127             confess;
128             }
129              
130             =head1 NAME
131              
132             Games::Poker::OPP - Implements the Online Poker Protocol
133              
134             =head1 SYNOPSIS
135              
136             use Games::Poker::OPP;
137             my $poker = Games::Poker::OPP->new(
138             username => "Perlkibot",
139             password => "sekrit",
140             server => "chinook6.cs.ualberta.ca",
141             port => 55006
142             );
143             $poker->connect or die $@;
144              
145             =head1 DESCRIPTION
146              
147             This class implements the Online Poker Protocol as specified at
148             L. This
149             implementation uses C to do all the communication, but
150             is designed to be subclassable for, e.g. POE.
151              
152             =head1 METHODS
153              
154             =head2 new
155              
156             my $poker = Games::Poker::OPP->new(
157             username => "Perlkibot",
158             password => "sekrit",
159             server => "chinook6.cs.ualberta.ca",
160             port => 55006,
161             status => \&handle_update,
162             callback => \&decide_strategy
163             );
164              
165             Prepares a new connection to a poker server. This doesn't actually make
166             the connection yet; use C to do that.
167              
168             You B supply a C which will be called when it is your
169             turn to act; you may supply a C callback which will be called
170             during a game when something happens.
171              
172             =cut
173              
174             sub new {
175             my $class = shift;
176             my %args = (
177             server => "chinook6.cs.ualberta.ca",
178             port => 55006,
179             status => sub {},
180             @_
181             );
182             defined $args{$_} or croak "No $_ specified"
183             for qw(username password callback);
184             return bless \%args, $class;
185             }
186              
187             =head2 connect
188              
189             Initiates a connection to the specified server. This is something you'll
190             want to override if you're subclassing this module.
191              
192             =cut
193              
194             sub connect {
195             my $self = shift;
196             $self->{socket} = IO::Socket::INET->new(
197             PeerHost => $self->{server},
198             PeerPort => $self->{port},
199             );
200             }
201              
202             =head2 put ($data)
203              
204             Sends C<$data> to the server.
205              
206             =head2 get ($len)
207              
208             Tries to retrieve C<$len> bytes of data from the server.
209              
210             Again, things you'll override when inheriting.
211              
212             =cut
213              
214             sub put { my ($self, $what) = @_; $self->{socket}->write($what, length $what); }
215             sub get {
216             my ($self, $len) = @_;
217             my $buf = " "x$len;
218             my $newlen = $self->{socket}->read($buf, $len);
219             return substr($buf,0,$newlen);
220             }
221              
222             =head2 joingame
223              
224             Sends username/password credentials and joins the game. Returns 0 if
225             the username/password was not accepted.
226              
227             =cut
228              
229             sub joingame {
230             my $self = shift;
231             $self->send_packet(JOIN_GAME,
232             $self->{username},
233             $self->{password},
234             1, # Protocol version
235             ref $self # Class. ;)
236             );
237             my ($status) = $self->get_packet();
238             if ($status == GOODPASS) {
239             return 1;
240             } elsif ($status == BADPASS) {
241             return 0;
242             } else {
243             croak sprintf "Protocol error: got %i from server", $status;
244             }
245             }
246              
247             =head2 playgame
248              
249             $self->playgame( )
250              
251             Once you've signed into the server, the C loop will receive
252             status events from the server, update the internal game status object
253             and call your callbacks.
254              
255             =cut
256              
257             sub playgame {
258             my $self = shift;
259             $self->{game} = undef;
260              
261             while (my ($cmd, @data) = $self->get_packet()) {
262             if ($cmd == PING) { $self->send_packet(PONG); next; }
263             if ($cmd == GOODBYE) { last }
264             if ($cmd == CHATTER ||
265             $cmd == INFORMATION) {
266             $self->{status}->($self, $cmd, @data); next;
267             }
268            
269             # Discard things which don't concern us.
270             next unless $self->{game} or $cmd == START_NEW_GAME;
271              
272             if (exists $handlers[$cmd]) {
273             $handlers[$cmd]->($self, $cmd, @data);
274             }
275             $self->{status}->($self, $cmd, @data);
276              
277             }
278             }
279              
280             =head2 state
281              
282             Returns a C object representing the current
283             state of play - the players involved, the pot, and so on. See
284             L for more information about how to use this.
285              
286             =cut
287              
288             sub state { $_[0]->{game} }
289              
290             sub new_game_handler { my ($self, $cmd, @data) = @_;
291             my ($bet, $nplayers, $button, $position, $gid) = splice @data,0,5;
292             return unless $position > -1;
293             my @players;
294             for (1..$nplayers) {
295             croak "Protocol error: Expected $nplayers, only saw ".@players
296             unless @data;
297             my ($name, $bankroll, $icon) = splice @data,0,3;
298             push @players, { name => $name, bankroll => $bankroll };
299             }
300             $self->{game} = Games::Poker::TexasHold'em->new( #'
301             players => \@players,
302             bet => $bet,
303             button => $players[$button]->{name},
304             );
305              
306             # Sadly, different people have different ideas about how the
307             # button works.
308             $self->{game}->_advance;
309             $self->{game}->_advance;
310             $self->{game}->_advance;
311             }
312              
313             sub hole_card_handler {
314             my ($self, $msg, $who, $cards) = @_;
315             if ($who == $self->{game}->{seats}->{$self->{username}}) {
316             $self->{game}->hole($cards)
317             }
318             }
319              
320             sub blinds_handler {
321             my $self = shift;
322             return if !$self->{game} || $self->{game}{blinded}++;
323             $self->{game}->blinds;
324             }
325              
326             sub fold_handler { shift->{game}->fold() }
327             sub call_handler { shift->{game}->check_call(); }
328             sub raise_handler { my ($self, $amount) = @_[0,2];
329             $self->{game}->raise($amount); }
330             sub next_turn_handler {
331             my ($self, $cmd, $who, $to_call, $min_bet, $max_bet) = @_;
332             my $game = $self->{game};
333              
334             # If it's me, make the callback
335             if ($who == $game->{seats}->{$self->{username}}) {
336             my $action = $self->{callback}->($self, $to_call, $min_bet, $max_bet);
337             return $self->send_packet(ACTION, $action);
338             }
339             # If it's not me, see if it's who we think it is.
340             return if $who == $game->{next};
341             # If it's not who we think it is, we need to advance until it is;
342             # this may happen when we hit the next stage.
343             return unless $game->{blinded};
344             $game->{next} = $who;
345             }
346              
347             sub next_stage_handler {
348             my ($self, $msg, $stage, $cards) = @_;
349             $self->{game}->next_stage() if $self->{game}->{blinded};
350             if ($cards) { $self->{game}->{board} = [$cards]; }
351             }
352              
353             =head1 EXAMPLES
354              
355             See the included F as an example of how to use this
356             module.
357              
358             =head1 AUTHOR
359              
360             Simon Cozens, Esimon@dsl.easynet.co.ukE
361              
362             =head1 COPYRIGHT AND LICENSE
363              
364             Copyright 2003 by Simon Cozens
365              
366             This library is free software; you can redistribute it and/or modify
367             it under the same terms as Perl itself.
368              
369             =cut
370              
371             1;