File Coverage

blib/lib/Game/Lottery.pm
Criterion Covered Total %
statement 52 52 100.0
branch 2 2 100.0
condition 2 3 66.6
subroutine 10 10 100.0
pod n/a
total 66 67 98.5


line stmt bran cond sub pod time code
1             # PODNAME: Game::Lottery
2             # ABSTRACT: Generate and Check Big Draw Lottery Tickets
3              
4             =pod
5              
6             =head1 NAME
7              
8             Game::Lottery
9              
10             =head1 VERSION
11              
12             version 1.04
13              
14             =head1 DESCRIPTION
15              
16             Pick and check numbers for popular Lottery Draw Games using a Cryptographically Secure randomizer.
17              
18             General Draw Games and Big Draw Games are supported, but ticket valuation functions are only available for PowerBall and MegaMillions.
19              
20             =head1 SYNOPSIS
21              
22             Use the bundled lottery-big-draw.pl script to pick your numbers:
23              
24             lottery-big-draw.pl -h
25              
26             To use in your code:
27              
28             use Game::Lottery;
29             my $lottery = Game::Lottery->new( game => 'PowerBall');
30              
31             =head1 DRAW GAMES
32              
33             Lottery Draw games have a pool of balls. Players bet on as many numbers as the number of balls that will be drawn.
34              
35             =head1 BIG DRAW LOTTERY GAMES
36              
37             Have a pool of balls (white) players must match, and a second pool (red) from which (usually) a single ball is drawn, many of the prizes require matching the red ball.
38              
39             The odds of winning the big prize in the two main games in the United States: PowerBall and MegaMillions, are about 300 Million to 1. When the prize isn't won, the jackpot increases. The JackPots frequently get quite large. When the JackPots get large enough, the expected value of a ticket exceeds the cost. The smaller prizes in both US games return less than 20% of the ticket value.
40              
41             There are several options to increase bet sizes, both major games have a multiplier ball option; these are not currently supported.
42              
43             California has a different payout on non-jackpot prizes. For California and any other jurisdictions not using the standard payout table, the expected value generated by this software does not apply.
44              
45             =cut
46              
47 6     6   1564651 use 5.038;
  6         25  
48 6     6   3216 use experimental qw/class/;
  6         25986  
  6         37  
49              
50             class Game::Lottery;
51             our $VERSION="1.04";
52 6     6   4557 use Math::Random::Secure;
  6         1257983  
  6         403  
53 6     6   5323 use Path::Tiny;
  6         92001  
  6         604  
54             # use Data::Dumper;
55             # use Data::Printer;
56 6     6   3977 use English;
  6         19171  
  6         40  
57              
58             field $game : param;
59             field $wb; # White Balls
60             field $rb; # Red Balls
61             field $wbd; # White Balls to Draw
62             field $rbd; # Red Balls to Draw
63             field %coll; # collision prevention
64              
65             ADJUST {
66             if ( $game =~ /^power/i ) {
67             $game = 'PowerBall';
68             $wb = 69;
69             $wbd = 5;
70             $rb = 26;
71             $rbd = 1;
72             }
73             elsif ( $game =~ /^mega/i ) {
74             $game = 'MegaMillions';
75             $wb = 70;
76             $wbd = 5;
77             $rb = 25;
78             $rbd = 1;
79             }
80             elsif ( $game =~ /^draw/i ) {
81             $game = 'Draw';
82             }
83             elsif ( $game =~ /^custom/i ) {
84             $game = 'CustomBigDraw';
85             }
86             else {
87             die "Unknown GAME $game.\n";
88             }
89             }
90              
91 5040     5040   1027089 sub _PickBall ($BallLastNum) {
  5040         8799  
  5040         8210  
92             # rand starts at 0 -- offset the pick.
93 5040         11383 my $pick = 1 + int Math::Random::Secure::rand($BallLastNum);
94             }
95              
96 12     12   3793 sub _DrawBalls ( $BallLastNum, $BallsDrawn ) {
  12         28  
  12         43  
  12         20  
97 12 100 66     74 unless( $BallLastNum and $BallsDrawn ) {
98 1         6 die "Balls are not defined. If this is a BigDraw did you use CustomBigDrawSetup?\n";
99             }
100 11         26 my %balls = ();
101 11         39 until ( scalar( keys %balls ) == $BallsDrawn ) {
102 40         94 my $newball = sprintf( "%02d", _PickBall($BallLastNum) );
103 40         56298 $balls{$newball} = $newball;
104             }
105 11         96 return [ sort { $a <=> $b } ( keys(%balls) ) ];
  52         147  
106             }
107              
108             =head1 METHODS
109              
110             =head2 new
111              
112             Required parameter: game
113              
114             Games currently supported are PowerBall, MegaMillions, Draw (1 ball pool) and CustomBigDraw(custom 2 ball pools). Game names may be abbreviated as mega, power, draw and custom.
115              
116             my $game = Game::Lottery->new( game => 'PowerBall');
117             my $game = Game::Lottery->new( game => 'power');
118              
119             =head2 Game
120              
121             Returns the Game.
122              
123             say $game->Game();
124              
125             =cut
126              
127             method _NoCollision ( $balls1, $balls2=[] ) {
128             my $key = join '', ($balls1->@*, $balls2->@*);
129             if ( defined $coll{$key }) { return 0 }
130             $coll{$key } = 1 ;
131             return 1;
132             }
133              
134             method Game {
135             return $game;
136             }
137              
138             =head2 BasicDraw
139              
140             Returns an Array Reference of balls picked. Applicable to a large number of Draw games.
141              
142             my $drawn = $game->BasicDraw( $BallLastNum, $BallsDrawn );
143              
144             =cut
145              
146             method BasicDraw ( $BallLastNum, $BallsDrawn ) {
147             my $balls = undef;
148             until (defined $balls) {
149             $balls = _DrawBalls( $BallLastNum, $BallsDrawn );
150             $balls = undef unless $self->_NoCollision( $balls );
151             }
152             $balls;
153             }
154              
155             =head2 BigDraw
156              
157             Draws White and Red Balls for Big Draw Games.
158              
159             Returns a Hash Reference.
160              
161             my $drawn = $game->BigDraw();
162              
163             {
164             game => $game,
165             whiteballs => ArrayRef,
166             redballs => ArrayRef
167             }
168              
169             =head2 CustomBigDrawSetup
170              
171             For CustomBigDraw games, the pool ranges and number of balls to draw must be set before using the BigDraw method. For the directly supported Big Draw Games this is done at new. The game name is set to CustomBigDraw
172              
173             $game->CustomBigDrawSetup(
174             game => 'Some other big draw game', # optionally set a custom game name.
175             white => 99,
176             whitecount => 1,
177             red => 20,
178             redcount => 5
179             );
180              
181             =cut
182              
183             method CustomBigDrawSetup (%balls) {
184             $wb = $balls{'white'};
185             $wbd = $balls{'whitecount'};
186             $rb = $balls{'red'};
187             $rbd = $balls{'redcount'} || 1;
188             $game = $balls{'game'} if $balls{'game'};
189             }
190              
191             method BigDraw {
192             while (1) {
193             my $draw = {
194             'game' => $game,
195             'whiteballs' => _DrawBalls( $wb, $wbd ),
196             'redballs' => _DrawBalls( $rb, $rbd )
197             };
198             if ( $self->_NoCollision( $draw->{whiteballs}, $draw->{redballs} ) ) {
199             return $draw;
200             }
201             }
202             }
203              
204 8     8   12 sub _round_val ($val) { sprintf( "%.2f", $val ) }
  8         11  
  8         9  
  8         118  
205              
206             # Calculate the
207             sub _BaseMMVal {
208 3     3   8 my $val = 2 * ( 1 / 37 ); # Red
209 3         4 $val += 4 * ( 1 / 89 ); # Red + 1 White
210 3         3 $val += 10 * ( 1 / 693 ); # Red + 2 White
211 3         4 $val += 10 * ( 1 / 606 ); # 3 White
212 3         5 $val += 200 * ( 1 / 14547 ); # Red + 3 White
213 3         4 $val += 500 * ( 1 / 38792 ); # 4 White
214 3         5 $val += 10000 * ( 1 / 931001 ); # Red + 4 White
215 3         4 $val += 10**6 * ( 1 / 12607306 ); # 5 White
216 3         34 return $val;
217             }
218              
219             sub _BasePBVal {
220 3     3   4 my $val = 4 * ( 1 / 38.32 ); # Red
221 3         7 $val += 4 * ( 1 / 91.98 ); # Red + 1 White
222 3         2 $val += 7 * ( 1 / 701.33 ); # Red + 2 White
223 3         5 $val += 7 * ( 1 / 579.76 ); # 3 White
224 3         3 $val += 100 * ( 1 / 1494.11 ); # Red + 3 White
225 3         4 $val += 100 * ( 1 / 36525.17 ); # 4 White
226 3         4 $val += 50000 * ( 1 / 913129.18 ); # Red + 4 White
227 3         5 $val += 10**6 * ( 1 / 11688053.52 ); # 5 White
228 3         9 return $val;
229             }
230              
231             =head2 TicketValue
232              
233             Returns the expected value of a ticket given a JackPot. The Cash Value should be used, not the advertised annuity value. The value passed may be either in dollars or millions of dollars. If no value (or 0) is based the result will be the return of all other prizes than the jackpot.
234              
235             my $TicketValue = $game->TicketValue( 600 );
236             my $BaseTicketValue = $game->TicketValue( 0 );
237              
238             =head2 TicketJackPotValue
239              
240             Returns just value of the JackPot divided by the number of possible combinations.
241              
242             my $JackPot = $game->TicketJackPotValue( 600 );
243              
244             =cut
245              
246             method TicketValue ( $jackpot = 0 ) {
247             # allows jackpot in millions or in dollars.
248             $jackpot = $jackpot * 10**6 if $jackpot < 10**6;
249             if ( 'PowerBall' eq $game ) {
250             return _round_val( _BasePBVal() + $jackpot / 292201338 );
251             }
252             elsif ( 'MegaMillions' eq $game ) {
253             return _round_val( _BaseMMVal() + $jackpot / 302575350 );
254             }
255             else {
256             die "Payout data not available for ${game}!";
257             }
258             }
259              
260             method TicketJackPotValue ($jackpot) {
261             # allows jackpot in millions or in dollars.
262             $jackpot = $jackpot * 10**6 if $jackpot < 10**6;
263             if ( 'PowerBall' eq $game ) {
264             return _round_val( $jackpot / 292201338 );
265             }
266             elsif ( 'MegaMillions' eq $game ) {
267             return _round_val( $jackpot / 302575350 );
268             }
269             else {
270             die "Payout data not available for ${game}!";
271             }
272             }
273              
274             =head2 SavePicks
275              
276             Save an arrayref of picks from BasicDraw or BigDraw to a file.
277              
278             $game->SavePicks( $file, $picks );
279              
280             =head2 ReadPicks
281              
282             Read saved picks from a file, returns an array.
283              
284             my @picks = $game->ReadPicks( $file );
285              
286             =cut
287              
288             method SavePicks ( $file, $picks ) {
289             open( my $out, '>', $file ) || die "unable to open output $file, $?";
290             for my $pick ( $picks->@* ) {
291             print $out do { join ' ', $pick->{whiteballs}->@* };
292             print $out ' ';
293             for my $red ( $pick->{redballs}->@* ) {
294             print $out "[${red}]";
295             }
296             print $out "\n";
297             }
298             }
299              
300             method ReadPicks ($file) {
301             my @picks = ();
302             for my $line ( path($file)->lines() ) {
303             next if $line =~ /^#/; # skip comment lines
304             next unless $line =~ /\d/; # skip lines missing data
305             my $pick = {};
306             chomp $line;
307             ( $pick->{memo}, $line ) = split /::\s+|::/, $line if ( $line =~ /::/ );
308             $pick->{balls} = [ split /\s+/, $line ];
309             push @picks, $pick;
310             }
311             @picks;
312             }
313              
314             =head1 Checking Tickets
315              
316             =head2 CheckPicks
317              
318             say join "\n", $game->CheckPicks( $winnersfile, $ticketsfile );
319              
320             =head2 The Saved Tickets File Format
321              
322             This library pads single digit numbers with a leading 0. It uses [] to differentiate red balls. The balls are checked as strings so 01 and 1 are not matching numbers just as 02 and [02] are not. The entries are simply the numbers on a line, separated by spaces, with the red balls in [brackets].
323              
324             A file generated by SavePicks looks like this:
325              
326             05 40 50 57 60 [15]
327             02 33 36 44 68 [19]
328              
329             The format allows for a memo at the beginning of a line, separated from the data by a double colon ::. When included in a winners file, the memo is included in the output. Usually the memo is the date of the draw, but it can be any string. Lines beginning with # are considered comments and not read.
330              
331             Winners files need to be created by hand.
332              
333             # example winners file
334             2023-09-16:: 22 30 37 44 45 [18]
335             # the memo is just a string, it doesn't have to be the date
336             Wednesday 2023-10-11::22 24 40 52 64 [10]
337             # the memo isn't required but is helpful when you have
338             # multiple lines in your winners file.
339             16 34 46 57 59 [23]
340              
341             =cut
342              
343             method CheckPicks ( $winnersfile, $ticketsfile ) {
344             local $LIST_SEPARATOR = " ";
345             my @winners = $self->ReadPicks($winnersfile);
346             my @tickets = $self->ReadPicks($ticketsfile);
347             my @result = ();
348             for my $drawing (@winners) {
349             my $ctr = 1;
350             my $linehead = '* ';
351             my %winballs = map { $_ => $_ } ( $drawing->{balls}->@* );
352             if ( defined $drawing->{memo} ) {
353             $linehead .= $drawing->{memo} . ' * ';
354             }
355             $linehead .= "@{ $drawing->{balls} }";
356             push @result, $linehead;
357             for my $ticket (@tickets) {
358             my @matched = ();
359             my @checkballs = $ticket->{balls}->@*;
360             for my $ball (@checkballs) {
361             push @matched, ($ball) if defined $winballs{$ball};
362             }
363             my $out = " ${\ $ctr++ } -- @checkballs ";
364             if (@matched) {
365             $out .= "matched (${\ scalar(@matched) }): @matched";
366             }
367             push @result, $out;
368             }
369             }
370             return @result;
371             }
372              
373             1;
374              
375             =pod
376              
377             =head1 SEE ALSO
378              
379             You can allow the Lottery to pick numbers for you, which will save you from having to put numbers you picked onto the slips for the lottery machines. If you search the web and the mobile app stores, you'll find plenty of sites and apps that do similar things to this module.
380              
381             =head1 AUTHOR
382              
383             John Karr L
384              
385             =head2 CONTRIBUTORS
386              
387             =head1 BUGS
388              
389             Please report any bugs or feature requests through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
390              
391             =head1 TODO
392              
393             The most interesting thing todo would be to use an API to get winning ticket data. There are several providers that require registering for a key. The author would prefer to use a keyless api, preferably provided by the major games or one of the state lotteries. The major games do not provide such an API.
394              
395             =head1 LICENSE AND COPYRIGHT
396              
397             Copyright 2023 John Karr.
398              
399             This program is free software: you can redistribute it and/or modify
400             it under the terms of the GNU General Public License as published by
401             the Free Software Foundation, either version 3 of the License, or
402             (at your option) any later version.
403              
404             This program is distributed in the hope that it will be useful,
405             but WITHOUT ANY WARRANTY; without even the implied warranty of
406             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
407             GNU General Public License for more details.
408              
409             You should have received a copy of the GNU General Public License
410             along with this program. If not, see L.
411              
412             =cut