File Coverage

blib/lib/Poker/Robot.pm
Criterion Covered Total %
statement 30 320 9.3
branch 0 44 0.0
condition 0 11 0.0
subroutine 10 146 6.8
pod 0 62 0.0
total 40 583 6.8


line stmt bran cond sub pod time code
1             package Poker::Robot;
2 1     1   14018 use Moo;
  1         9397  
  1         4  
3 1     1   1447 use Mojo::JSON qw(j);
  1         53704  
  1         52  
4 1     1   527 use Mojo::Log;
  1         2044  
  1         6  
5 1     1   482 use Mojo::UserAgent;
  1         127301  
  1         7  
6 1     1   411 use Poker::Robot::Login;
  1         4  
  1         30  
7 1     1   510 use Poker::Robot::Ring;
  1         2  
  1         28  
8 1     1   340 use Poker::Robot::Chair;
  1         11  
  1         25  
9 1     1   1520 use DBI;
  1         11672  
  1         57  
10 1     1   691 use DBD::SQLite;
  1         5446  
  1         24  
11 1     1   6 use EV;
  1         1  
  1         3235  
12              
13             =encoding utf8
14              
15             =head1 NAME
16              
17             Poker::Robot - base class for building custom poker robots
18              
19             =head1 VERSION
20              
21             Version 0.01
22              
23             =cut
24              
25             our $VERSION = '0.01';
26              
27             =head1 SYNOPSIS
28              
29             package Poker::Robot::Mybot;
30             use Moo;
31            
32             # Poker::Robot::Random shows a working example
33             extends 'Poker::Robot::Random';
34              
35             # override default method
36             sub move {
37             # move selection logic goes here
38             }
39              
40             # and elsewhere in a script ...
41             use Poker::Robot::Mybot;
42              
43             # Note: you must pick a unique username!
44             $robot = Poker::Robot::Mybot->new(
45             websocket => 'wss://aitestbed.com:443/websocket',
46             username => 'Mybot',
47             ring_ids => [ 1 ],
48             );
49              
50             $robot->connect;
51              
52             =head1 INTRODUCTION
53              
54             Handlers are automatically executed at appropriate stages of the game, allowing your bot to run on autopilot. By default, these handlers return legal but essentially random values. Your job is to override them in your subclass with something that makes more sense. Poker::Robot::Random shows a working example.
55              
56             =head1 SERVERS
57              
58             https://aitestbed.com is the default test server. This is where you can deploy your bot once it is ready and have it compete against other bots and humans in real-time.
59              
60             =head1 LOGGING
61              
62             To see what your bot is doing, do a tail -f on robot.log
63              
64             =head1 ATTRIBUTES
65              
66             =head2 websocket
67              
68             Websocket address of the test server. Default is wss://aitestbed:443/websocket
69              
70             =cut
71              
72             has 'websocket' => (
73             is => 'rw',
74             builder => '_build_websocket',
75             );
76              
77             sub _build_websocket {
78 0     0     return 'wss://aitestbed.com:443/websocket';
79             }
80              
81             =head2 ring_ids
82              
83             Required. Ids of ring games to join. Before setting this attribute, bring up the test site on your browser to see which tables have open seats.
84              
85             =cut
86              
87             has 'ring_ids' => (
88             is => 'rw',
89             isa => sub { die "Not an array!" unless ref $_[0] eq 'ARRAY' },
90             builder => '_build_ring_ids',
91             );
92              
93             sub _build_ring_ids {
94 0     0     return [];
95             }
96              
97             has 'log' => (
98             is => 'rw',
99             isa => sub { die "Not a Mojo::Log!" unless $_[0]->isa('Mojo::Log') },
100             default =>
101             sub { return Mojo::Log->new( path => 'robot.log' ) },
102             );
103              
104             has 'login_id' => ( is => 'rw', );
105              
106             has 'username' => (
107             is => 'rw',
108             required => 1,
109             );
110              
111             has 'user_id' => ( is => 'rw', );
112              
113             has 'password' => ( is => 'rw', );
114              
115             has 'bookmark' => ( is => 'rw', );
116              
117             has 'login_list' => (
118             is => 'rw',
119             isa => sub { die "Not a hash.\n" unless ref( $_[0] ) eq 'HASH' },
120             builder => '_build_login_list',
121             );
122              
123             sub _build_login_list {
124 0     0     return {};
125             }
126              
127             sub fetch_login {
128 0     0 0   my ( $self, $id ) = @_;
129 0           return $self->login_list->{$id};
130             }
131              
132             has 'table_list' => (
133             is => 'rw',
134             isa => sub { die "Not a hash.\n" unless ref( $_[0] ) eq 'HASH' },
135             builder => '_build_table_list',
136             );
137              
138             sub _build_table_list {
139 0     0     return {};
140             }
141              
142             sub fetch_ring {
143 0     0 0   my ( $self, $id ) = @_;
144 0           return $self->table_list->{$id};
145             }
146              
147             sub response_handler {
148 0     0 0   my ( $self, $aref ) = @_;
149              
150 0 0         if ( ref $aref ne 'ARRAY' ) {
151 0           $self->log->info('invalid_format');
152 0           return;
153             }
154              
155 0           my ( $cmd, $opts ) = @$aref;
156              
157 0 0 0       if ( ref $cmd || !exists $self->client_update->{$cmd} ) {
158 0           $self->log->info("invalid_client_update: $cmd");
159 0           return;
160             }
161              
162 0           $self->client_update->{$cmd}( $self, $opts );
163             }
164              
165             has 'client_update' => (
166             is => 'rw',
167             isa => sub { die "Not a hash.\n" unless ref( $_[0] ) eq 'HASH' },
168             builder => '_build_client_update',
169             );
170              
171             sub _build_client_update {
172             return {
173              
174             # SERVER CODES
175 0     0     guest_login => sub { shift->guest_login(shift) },
176 0     0     login_snap => sub { shift->login_snap(shift) },
177 0     0     ring_snap => sub { shift->ring_snap(shift) },
178 0     0     tour_snap => sub { shift->tour_snap(shift) },
179 0     0     player_snap => sub { shift->player_snap(shift) },
180 0     0     table_snap => sub { shift->table_snap(shift) },
181       0     message_snap => sub { },
182 0     0     table_update => sub { shift->table_update(shift) },
183 0     0     player_update => sub { shift->player_update(shift) },
184 0     0     login_update => sub { shift->login_update(shift) },
185 0     0     new_game => sub { shift->new_game(shift) },
186 0     0     end_game => sub { shift->end_game(shift) },
187 0     0     deal_hole => sub { shift->deal_hole(shift) },
188 0     0     begin_new_round => sub { shift->begin_new_round(shift) },
189 0     0     begin_new_action => sub { shift->begin_new_action(shift) },
190 0     0     deal_community => sub { shift->deal_community(shift) },
191 0     0     showdown => sub { shift->showdown(shift) },
192 0     0     high_winner => sub { shift->high_winner(shift) },
193 0     0     low_winner => sub { shift->low_winner(shift) },
194 0     0     move_button => sub { shift->move_button(shift) },
195 0     0     forced_logout => sub { shift->forced_logout(shift) },
196              
197             # NOTIFICATION CODES
198 0     0     notify_login => sub { shift->notify_login(shift) },
199 0     0     notify_update_login => sub { shift->notify_update_login(shift) },
200 0     0     notify_logout => sub { shift->notify_logout(shift) },
201 0     0     notify_create_ring => sub { shift->notify_create_ring(shift) },
202 0     0     notify_join_table => sub { shift->notify_join_table(shift) },
203 0     0     notify_unjoin_table => sub { shift->notify_unjoin_ring(shift) },
204 0     0     notify_post => sub { shift->notify_bet(shift) },
205 0     0     notify_bet => sub { shift->notify_bet(shift) },
206 0     0     notify_check => sub { shift->notify_check(shift) },
207 0     0     notify_fold => sub { shift->notify_fold(shift) },
208 0     0     notify_discard => sub { shift->notify_discard(shift) },
209 0     0     notify_draw => sub { shift->notify_draw(shift) },
210 0     0     notify_credit_chips => sub { shift->notify_credit_chips(shift) },
211 0     0     notify_table_chips => sub { shift->notify_table_chips(shift) },
212       0     notify_lobby_update => sub { },
213       0     notify_message => sub { },
214       0     notify_pick_game => sub { },
215       0     notify_lr_update => sub { },
216              
217             # RESPONSE CODES
218 0     0     join_ring_res => sub { shift->join_ring_res(shift) },
219 0     0     unjoin_ring_res => sub { shift->unjoin_ring_res(shift) },
220 0     0     watch_table_res => sub { shift->watch_table_res(shift) },
221 0     0     unwatch_table_res => sub { shift->unwatch_table_res(shift) },
222 0     0     login_res => sub { shift->login_res(shift) },
223 0     0     logout_res => sub { shift->logout_res(shift) },
224 0     0     register_res => sub { shift->register_res(shift) },
225 0     0     bet_res => sub { shift->bet_res(shift) },
226 0     0     check_res => sub { shift->check_res(shift) },
227 0     0     fold_res => sub { shift->fold_res(shift) },
228 0     0     discard_res => sub { shift->discard_res(shift) },
229 0     0     draw_res => sub { shift->draw_res(shift) },
230 0     0     credit_chips_res => sub { shift->add_chips_res(shift) },
231       0     pick_game_res => sub { },
232 0     0     };
233             }
234              
235             =head1 HANDLERS
236              
237             The following handlers can be overriden in your subclass with custom code for you robot. At some point I'll get around to documenting this better, but this will have to do for now.
238              
239             =head2 SERVER CODES
240              
241             guest_login
242             login_snap
243             ring_snap
244             tour_snap
245             player_snap
246             table_snap
247             message_snap
248             table_update
249             player_update
250             login_update
251             new_game
252             end_game
253             deal_hole
254             begin_new_round
255             begin_new_action
256             deal_community
257             showdown
258             high_winner
259             low_winner
260             move_button
261             forced_logout
262              
263             =head2 NOTIFICATION CODES
264              
265             notify_login
266             notify_update_login
267             notify_logout
268             notify_create_ring
269             notify_join_table
270             notify_unjoin_table
271             notify_post
272             notify_bet
273             notify_check
274             notify_fold
275             notify_discard
276             notify_draw
277             notify_credit_chips
278             notify_table_chips
279             notify_lobby_update
280             notify_message
281             notify_pick_game
282             notify_lr_update
283              
284             =head2 RESPONSE CODES
285              
286             join_ring_res
287             unjoin_ring_res
288             watch_table_res
289             unwatch_table_res
290             login_res
291             logout_res
292             register_res
293             bet_res
294             check_res
295             fold_res
296             discard_res
297             draw_res
298             pick_game_res
299             reload_res
300              
301             =head2 REQUEST CODES
302              
303             join_ring
304             unjoin_ring
305             watch_table
306             unwatch_table
307             login
308             logout
309             register
310             bet
311             check
312             fold
313             discard
314             draw
315             pick_game
316             reload
317              
318             =cut
319              
320             sub forced_logout {
321 0     0 0   my ( $self, $opts ) = @_;
322             }
323              
324             sub add_ring {
325 0     0 0   my ( $self, $opts ) = @_;
326 0           delete $self->table_list->{ $opts->{table_id} };
327 0           my $ring = Poker::Robot::Ring->new($opts);
328 0           $self->table_list->{ $opts->{table_id} } = $ring;
329 0 0         if ( exists $self->ring_hash->{ $opts->{table_id} } ) {
330 0           $self->respond( [ 'watch_table', { table_id => $opts->{table_id} } ] );
331             }
332             }
333              
334             sub add_login {
335 0     0 0   my ( $self, $opts ) = @_;
336 0           $self->login_list->{ $opts->{login_id} } = Poker::Robot::Login->new($opts);
337             }
338              
339             # SERVER CODES
340              
341             sub guest_login {
342 0     0 0   my ( $self, $opts ) = @_;
343 0           $self->login_id( $opts->{login_id} );
344 0 0         if ( defined $self->bookmark ) {
345 0           $self->respond( [ 'login_book', { bookmark => $self->bookmark } ] );
346             }
347             else {
348 0           my $reg = [ 'register', { username => $self->username } ];
349 0 0         $reg->[1]->{password} = $self->password if $self->password;
350 0 0         $reg->[1]->{email} = $self->email if $self->email;
351 0 0         $reg->[1]->{birthday} = $self->birthday if $self->birthday;
352 0 0         $reg->[1]->{handle} = $self->handle if $self->handle;
353 0           $self->respond($reg);
354             }
355             }
356              
357             sub login_snap {
358 0     0 0   my ( $self, $opts ) = @_;
359 0           $self->add_login($_) for (@$opts);
360             }
361              
362             sub ring_snap {
363 0     0 0   my ( $self, $opts ) = @_;
364 0           $self->add_ring($_) for (@$opts);
365             }
366              
367             sub tour_snap {
368 0     0 0   my ( $self, $opts ) = @_;
369             }
370              
371             sub table_update {
372 0     0 0   my ( $self, $opts ) = @_;
373 0           $self->table_snap($opts);
374             }
375              
376             sub table_snap {
377 0     0 0   my ( $self, $opts ) = @_;
378 0           my $ring = $self->table_list->{ $opts->{table_id} };
379 0           %$ring = ( %$ring, %$opts );
380             }
381              
382             sub player_snap {
383 0     0 0   my ( $self, $opts ) = @_;
384 0           for my $r (@$opts) {
385 0           $self->_join_table($r);
386             }
387             }
388              
389             sub player_update {
390 0     0 0   my ( $self, $opts ) = @_;
391 0           my $ring = $self->table_list->{ $opts->{table_id} };
392 0           my $chair = $ring->chairs->[ $opts->{chair} ];
393 0 0         %$chair = ( %$chair, %$opts ) if $chair;
394             }
395              
396             sub new_game {
397 0     0 0   my ( $self, $opts ) = @_;
398 0           my $ring = $self->table_list->{ $opts->{table_id} };
399 0           $ring->reset;
400 0           %$ring = ( %$ring, %$opts );
401             }
402              
403             sub end_game {
404 0     0 0   my ( $self, $opts ) = @_;
405 0           my $ring = $self->table_list->{ $opts->{table_id} };
406 0           $ring->game_over(1);
407             }
408              
409             sub deal_hole {
410 0     0 0   my ( $self, $opts ) = @_;
411 0           my $ring = $self->table_list->{ $opts->{table_id} };
412 0           $ring->chairs->[ $opts->{chair} ]->cards( $opts->{cards} );
413             }
414              
415             sub begin_new_round {
416 0     0 0   my ( $self, $opts ) = @_;
417 0           my $ring = $self->table_list->{ $opts->{table_id} };
418 0           for my $chair ( grep { defined } @{ $ring->chairs } ) {
  0            
  0            
419 0           $chair->in_pot_this_round(0);
420             }
421 0           %$ring = ( %$ring, %$opts );
422             }
423              
424             sub deal_community {
425 0     0 0   my ( $self, $opts ) = @_;
426 0           my $ring = $self->table_list->{ $opts->{table_id} };
427 0           %$ring = ( %$ring, %$opts );
428             }
429              
430             sub showdown {
431 0     0 0   my ( $self, $opts ) = @_;
432 0           my $ring = $self->table_list->{ $opts->{table_id} };
433 0           %$ring = ( %$ring, %$opts );
434             }
435              
436             sub high_winner {
437 0     0 0   my ( $self, $opts ) = @_;
438 0           my $ring = $self->table_list->{ $opts->{table_id} };
439 0           %$ring = ( %$ring, %$opts );
440             }
441              
442             sub low_winner {
443 0     0 0   my ( $self, $opts ) = @_;
444 0           my $ring = $self->table_list->{ $opts->{table_id} };
445 0           %$ring = ( %$ring, %$opts );
446             }
447              
448             sub move_button {
449 0     0 0   my ( $self, $opts ) = @_;
450 0           my $ring = $self->table_list->{ $opts->{table_id} };
451 0           %$ring = ( %$ring, %$opts );
452             }
453              
454             # NOTIFICATION CODES
455              
456             sub notify_login {
457 0     0 0   my ( $self, $opts ) = @_;
458             $self->login_list->{ $opts->{login_id} } = Poker::Robot::Login->new($opts)
459 0 0         unless $opts->{login_id} == $self->login_id;
460             }
461              
462             sub notify_update_login {
463 0     0 0   my ( $self, $opts ) = @_;
464 0           my $login = $self->login_list->{ $opts->{login_id} };
465 0           %$login = %$opts;
466             }
467              
468             sub notify_logout {
469 0     0 0   my ( $self, $opts ) = @_;
470 0           delete $self->login_list->{ $opts->{login_id} };
471             }
472              
473             sub notify_create_ring {
474 0     0 0   my ( $self, $opts ) = @_;
475 0           $self->add_ring($opts);
476             }
477              
478             sub notify_join_table {
479 0     0 0   my ( $self, $opts ) = @_;
480 0           $self->_join_table($opts);
481             }
482              
483             sub _join_table {
484 0     0     my ( $self, $opts ) = @_;
485 0           my $ring = $self->table_list->{ $opts->{table_id} };
486 0           $ring->chairs->[ $opts->{chair} ] = Poker::Robot::Chair->new($opts);
487             }
488              
489             sub notify_unjoin_ring {
490 0     0 0   my ( $self, $opts ) = @_;
491 0           my $ring = $self->table_list->{ $opts->{table_id} };
492 0           $ring->chairs->[ $opts->{chair} ] = undef;
493             }
494              
495             sub notify_fold {
496 0     0 0   my ( $self, $opts ) = @_;
497 0           my $ring = $self->table_list->{ $opts->{table_id} };
498 0           my $chair = $ring->chairs->[ $opts->{chair} ];
499 0           $chair->is_in_hand(0);
500 0           $chair->cards( [] );
501             }
502              
503             sub notify_bet {
504 0     0 0   my ( $self, $opts ) = @_;
505 0           my $ring = $self->table_list->{ $opts->{table_id} };
506 0           my $chair = $ring->chairs->[ $opts->{chair} ];
507 0           $chair->in_pot_this_round( $chair->in_pot_this_round + $opts->{chips} );
508 0           $chair->in_pot( $chair->in_pot + $opts->{chips} );
509             }
510              
511             sub notify_check {
512 0     0 0   my ( $self, $opts ) = @_;
513             }
514              
515             sub notify_discard {
516 0     0 0   my ( $self, $opts ) = @_;
517 0           my $ring = $self->table_list->{ $opts->{table_id} };
518 0           my $chair = $ring->chairs->[ $opts->{chair} ];
519 0 0         unless ( $chair->login_id == $self->login_id ) {
520 0           for my $id ( @{ $opts->{card_idx} } ) {
  0            
521 0           splice( @{ $chair->cards }, $id, 1 );
  0            
522             }
523             }
524             }
525              
526             sub notify_draw {
527 0     0 0   my ( $self, $opts ) = @_;
528             }
529              
530             sub notify_credit_chips {
531 0     0 0   my ( $self, $opts ) = @_;
532 0           my $login = $self->login_list->{ $opts->{login_id} };
533 0           $login->chips->{ $opts->{director_id} } = $opts->{chips};
534             }
535              
536             sub notify_table_chips {
537 0     0 0   my ( $self, $opts ) = @_;
538 0           my $ring = $self->table_list->{ $opts->{table_id} };
539 0           $ring->chair->[ $opts->{chair} ]->chips( $opts->{chips} );
540             }
541              
542             # RESPONSE CODES
543              
544             sub join_ring_res {
545 0     0 0   my ( $self, $opts ) = @_;
546             }
547              
548             sub unjoin_ring_res {
549 0     0 0   my ( $self, $opts ) = @_;
550             }
551              
552             sub watch_table_res {
553 0     0 0   my ( $self, $opts ) = @_;
554 0           $self->table_snap($opts);
555 0           my $login = $self->login_list->{ $self->login_id };
556 0           my $chips = $login->chips->{ $opts->{director_id} };
557 0           my $table = $self->table_list->{ $opts->{table_id} };
558 0 0 0       return unless $chips && $table;
559 0 0 0       $chips = $table->table_max if $table->table_max && $chips > $table->table_max;
560             $self->respond(
561             [
562             'join_ring',
563 0           { table_id => $opts->{table_id}, chips => $chips, auto_rebuy => $chips }
564             ]
565             );
566             }
567              
568             sub unwatch_table_res {
569 0     0 0   my ( $self, $opts ) = @_;
570             }
571              
572             sub login_res {
573 0     0 0   my ( $self, $opts ) = @_;
574 0 0         if ( $opts->{success} ) {
575 0           $self->login_id( $opts->{login_id} );
576 0           my $login = $self->login_list->{ $opts->{login_id} };
577 0           %$login = %$opts;
578             }
579             }
580              
581             sub login_update {
582 0     0 0   my ( $self, $opts ) = @_;
583 0           my $login = $self->login_list->{ $self->{login_id} };
584 0           %$login = %$opts;
585             }
586              
587             sub logout_res {
588 0     0 0   my ( $self, $opts ) = @_;
589 0           $self->tx->finish;
590             }
591              
592             sub register_res {
593 0     0 0   my ( $self, $opts ) = @_;
594 0 0         if ( $opts->{success} ) {
595 0           $self->login_id( $opts->{login_id} );
596 0           my $login = $self->login_list->{ $opts->{login_id} };
597 0           %$login = %$opts;
598 0           $self->_replace_bot($opts);
599             }
600             else {
601 0           $self->respond( ['logout'] );
602             }
603             }
604              
605             sub bet_res {
606 0     0 0   my ( $self, $opts ) = @_;
607             }
608              
609             sub check_res {
610 0     0 0   my ( $self, $opts ) = @_;
611             }
612              
613             sub fold_res {
614 0     0 0   my ( $self, $opts ) = @_;
615             }
616              
617             sub discard_res {
618 0     0 0   my ( $self, $opts ) = @_;
619             }
620              
621             sub draw_res {
622 0     0 0   my ( $self, $opts ) = @_;
623             }
624              
625             sub credit_chips_res {
626 0     0 0   my ( $self, $opts ) = @_;
627             }
628              
629             1;
630              
631             has 'db' => ( is => 'rw', );
632              
633             sub _build_db {
634 0     0     my $self = shift;
635 0           return DBI->connect( "dbi:SQLite:dbname=robots.db", "", "" );
636             }
637              
638             has 'ring_hash' => (
639             is => 'rw',
640             isa => sub { die "Not a hash!" unless ref $_[0] eq 'HASH' },
641             );
642              
643             sub _build_ring_hash {
644 0     0     my $self = shift;
645 0           return { map { $_ => 1 } @{ $self->ring_ids } } ;
  0            
  0            
646             }
647              
648             has 'move_timer' => (
649             is => 'rw',
650             isa => sub { die "Not a hash!" unless ref $_[0] eq 'HASH' },
651             default => sub { {} },
652             );
653              
654             has 'tx' => ( is => 'rw', );
655              
656             has 'ua' => (
657             is => 'rw',
658             builder => '_build_ua',
659             );
660              
661             sub _build_ua {
662 0     0     return Mojo::UserAgent->new( inactivity_timeout => 0 );
663             }
664              
665             has 'valid_actions' => (
666             is => 'rw',
667             builder => '_build_valid_actions',
668             );
669              
670             sub _build_valid_actions {
671             return {
672 0     0     bet => sub { shift->bet(shift) },
673 0     0     check => sub { shift->check(shift) },
674 0     0     fold => sub { shift->fold(shift) },
675 0     0     draw => sub { shift->draw(shift) },
676 0     0     discard => sub { shift->discard(shift) },
677 0     0     choice => sub { shift->choice(shift) },
678 0     0     bring => sub { shift->bet(shift) },
679 0     0     };
680             }
681              
682             has 'email' => ( is => 'rw', );
683              
684             has 'birthday' => ( is => 'rw', );
685              
686             has 'handle' => ( is => 'rw', );
687              
688             sub respond {
689 0     0 0   my ( $self, $data ) = @_;
690              
691 0           my $json = j($data);
692 0           $self->tx->send( $json );
693 0           $self->log->info("robot: $json");
694             }
695              
696             sub begin_new_action {
697 0     0 0   my ( $self, $opts ) = @_;
698 0           my $ring = $self->table_list->{ $opts->{table_id} };
699 0           %$ring = ( %$ring, %$opts );
700 0           my $table = $self->table_list->{ $opts->{table_id} };
701              
702 0           my $login_id = $table->chairs->[ $opts->{action} ]->login_id;
703 0 0         $self->move($table) if $login_id == $self->login_id;
704             }
705              
706             sub move {
707 0     0 0   my ( $self, $table ) = @_;
708             }
709              
710             sub size_bring {
711 0     0 0   my ( $self, $table ) = @_;
712 0           my @bets = ( $table->bring, $table->max_bet );
713 0           $table->bet_size( $bets[ int( rand( scalar @bets ) ) ] );
714             }
715              
716             sub bet {
717 0     0 0   my ( $self, $table ) = @_;
718 0           $self->respond(
719             [ 'bet', { table_id => $table->table_id, chips => $table->bet_size } ] );
720             }
721              
722             sub check {
723 0     0 0   my ( $self, $table ) = @_;
724 0           $self->respond( [ 'check', { table_id => $table->table_id } ] );
725             }
726              
727             sub fold {
728 0     0 0   my ( $self, $table ) = @_;
729 0           $self->respond( [ 'fold', { table_id => $table->table_id } ] );
730             }
731              
732             sub choice {
733 0     0 0   my ( $self, $table ) = @_;
734 0           $self->respond(
735             [
736             'pick_game', { table_id => $table->table_id, game => $table->game_choice }
737             ]
738             );
739             }
740              
741             sub discard {
742 0     0 0   my ( $self, $table ) = @_;
743 0           $self->respond(
744             [
745             'discard',
746             { table_id => $table->table_id, card_idx => $table->card_select }
747             ]
748             );
749             }
750              
751             sub draw {
752 0     0 0   my ( $self, $table ) = @_;
753 0           $self->respond(
754             [
755             'draw', { table_id => $table->table_id, card_idx => $table->card_select }
756             ]
757             );
758             }
759              
760             sub connect {
761 0     0 0   my $self = shift;
762              
763             $self->ua->websocket(
764             $self->websocket => sub {
765 0     0     my ( $ua, $tx ) = @_;
766 0 0         $self->log->error($tx->error->{message}) if $tx->error;
767              
768             # Check if WebSocket handshake was successful
769 0 0 0       $self->log->error('WebSocket handshake failed!') and return unless $tx->is_websocket;
770 0           $self->tx($tx);
771              
772             # Wait for WebSocket to be closed
773             $tx->on(
774             finish => sub {
775 0           my ( $tx, $code ) = @_;
776 0 0         $self->log->error( $tx->error->{message}) if $tx->error;
777 0           $self->log->info("WebSocket closed with code $code.");
778             }
779 0           );
780              
781             $tx->on(
782             json => sub {
783 0           my ( $tx, $js ) = @_;
784 0 0         if ($js) {
785 0           $self->log->info('server: ' . j($js));
786 0           $self->response_handler($js);
787             }
788             }
789 0           );
790 0           $tx->send('["guest_login"]');
791             }
792 0           );
793 0           EV::run;
794             }
795              
796             sub _select_bot {
797 0     0     my $self = shift;
798 0           my $sql = 'SELECT * FROM bots WHERE username = ?';
799 0           my $sth = $self->db->prepare($sql);
800 0           $sth->execute( $self->username );
801 0           my $opts = $sth->fetchrow_hashref;
802 0 0         if ( ref $opts eq 'HASH' ) {
803 0           $self->bookmark( $opts->{bookmark} );
804             }
805             }
806              
807             sub _replace_bot {
808 0     0     my ( $self, $opts ) = @_;
809 0           my $sql = <
810             REPLACE INTO bots (username, password, bookmark, modified)
811             VALUES (?,?,?,?)
812             SQL
813 0           my $sth = $self->db->prepare($sql);
814             $sth->execute( $opts->{username}, $opts->{password}, $opts->{bookmark},
815 0           time );
816             }
817              
818             sub _create_bots {
819 0     0     my $self = shift;
820 0           my $sql = <
821             CREATE TABLE bots (
822             id INTEGER PRIMARY KEY NOT NULL,
823             bookmark varchar(40) NOT NULL,
824             username varchar(255) NOT NULL,
825             password varchar(40),
826             modified datetime
827             );
828             SQL
829              
830 0           $self->db->do($sql);
831 0           $self->db->do('CREATE UNIQUE INDEX bots_idx1 ON bots (username)');
832 0           $self->db->do('CREATE UNIQUE INDEX bots_idx2 ON bots (bookmark)');
833             }
834              
835             sub BUILD {
836 0     0 0   my $self = shift;
837 0           $self->ring_hash( $self->_build_ring_hash );
838 0           $self->db( $self->_build_db );
839 0 0         eval { $self->db->prepare("SELECT 1 FROM bots") } or $self->_create_bots;
  0            
840 0           $self->_select_bot;
841             }
842              
843             =head1 AUTHOR
844              
845             Nathaniel Graham, C
846              
847             =head1 BUGS
848              
849             Please report any bugs or feature requests directly to C
850              
851             =head1 LICENSE AND COPYRIGHT
852              
853             Copyright 2016 Nathaniel Graham.
854              
855             This program is free software; you can redistribute it and/or modify it
856             under the terms of the MIT license.
857              
858             =cut
859              
860             1;