File Coverage

blib/lib/Games/Score.pm
Criterion Covered Total %
statement 109 111 98.2
branch 47 54 87.0
condition 10 11 90.9
subroutine 22 22 100.0
pod 19 19 100.0
total 207 217 95.3


line stmt bran cond sub pod time code
1             package Games::Score;
2              
3 4     4   139560 use 5.006;
  4         15  
  4         175  
4 4     4   25 use strict;
  4         8  
  4         162  
5 4     4   39 use warnings;
  4         14  
  4         8191  
6              
7             =head1 NAME
8              
9             Games::Score - Keep track of score in games
10              
11             =cut
12              
13             our $VERSION = '0.02';
14              
15             =head1 SYNOPSIS
16              
17             use Games::Score;
18              
19             # these three values are the default ones, by the way
20             Games::Score->default_score(0);
21             Games::Score->default_step(1);
22             Games::Score->step_method('inc');
23              
24             # start two players
25             my $player1 = Games::Score->new();
26             my $player2 = Games::Score->new();
27              
28             # set a winning condition
29             Games::Score->victory_is( sub { $_[0] >= 20 } );
30              
31             # and something to do if it is achieved
32             Games::Score->on_victory_do( sub { print "Won!" } );
33              
34             # give points to the players
35             $player1->add(2);
36             $player2->step();
37              
38             # look at section FUNCTIONS for more functionalities, such as
39             Games::Score->invalidate_if( sub { $_[0] > 20 } );
40              
41             =head1 DESCRIPTION
42              
43             Games::Score can be use to keep track of several players' points in a game,
44             regardless of the starting amount of points, winning and/or losing conditions,
45             etc.
46              
47             It provides several useful methods so that the user doesn't have to keep
48             testing values to see if they're valid or if the player condition has changed.
49              
50             =head1 FUNCTIONS
51              
52             =head2 BASIC METHODS
53              
54             =head3 new
55              
56             new();
57             new(PLAYERNAME);
58             new(PLAYER_NAME,PLAYER_SCORE);
59              
60             Creates a new Games::Score object. Default name is "Player" and default score
61             is 0.
62              
63             # start a player with the default points
64             my $player1 = Games::Score->new();
65              
66             # start a player named "BANZAI"
67             my $player2 = Games::Score->new("BANZAI");
68              
69             # start a player named "BANZAI" with 20 points
70             my $player2 = Games::Score->new("BANZAI",20);
71              
72             =cut
73              
74             sub new {
75 8     8 1 57 my ($self, $name, $score) = @_;
76 8   66     41 $name ||= default_name();
77 8   100     39 $score ||= default_score();
78 8         36 my %player = (NAME => $name, SCORE => $score);
79 8         36 bless \%player => $self;
80             }
81              
82             =head2 VICTORY AND DEFEAT METHODS
83              
84             =head3 victory_is
85              
86             victory_is(CODE)
87              
88             Allows for defining a funtion which, receiving the player's score, will return
89             true if the player has won the game.
90              
91             This function is used by has_won().
92              
93             In addition, if both victory_is() and on_victory_do() are defined, as soon as
94             the player's score changes and the victory condition (defined with
95             victory_is()) is verified, the function defined with on_victory_do() is
96             executed.
97              
98             The function defined with victory_is() receives the score as a parameter.
99              
100             # set the winning condition to be "score is greater or equal than 20"
101             Games::Score->victory_is( sub { $_[0] >= 20 } )
102              
103             =cut
104              
105             our $victory_is = sub { undef };
106              
107             sub victory_is {
108 9     9 1 33 shift;
109 9 100       50 ref($_[0]) eq 'CODE' || return undef;
110              
111 6         45 $victory_is = shift;
112             }
113              
114             =head3 defeat_is
115              
116             defeat_is(CODE);
117              
118             Allows for defining a funtion which, receiving the player's score, will return
119             true if the player has lost the game.
120              
121             This function is used by has_lost().
122              
123             In addition, if both defeat_is() and on_defeat_do() are defined, as soon as the
124             player's score changes and the defeat condition (defined with defeat_is()) is
125             verified, the function defined with on_defeat_do() is executed.
126              
127             The function defined with defeat_is() receives the score as a parameter.
128              
129             # set the winning condition to be "score is negative"
130             Games::Score->defeat_is( sub { $_[0] < 0 } )
131              
132             =cut
133              
134             our $defeat_is = sub { undef };
135              
136             sub defeat_is {
137 8     8 1 17 shift;
138 8 100       43 ref($_[0]) eq 'CODE' || return undef;
139              
140 5         24 $defeat_is = shift;
141             }
142              
143             =head3 on_victory_do
144              
145             on_victory_do(CODE);
146              
147             This method lets you define a function that will be called as soon as has_won()
148             starts returning a true value. In other words, when the score changes and the
149             function defined with is_victory() returns true, the function defined with
150             on_victory_do() is called.
151              
152             The function receives as parameters the score of the player and its name.
153              
154             # set a new condition for on_victory_do()
155             our $game_ended;
156             Games::Score->on_victory_do( sub { $game_ended = 1 } );
157              
158             # assuming this:
159             my $player1 = Games::Score->new();
160             Games::Score->victory_is( sub { $_[0] == 1 } );
161              
162             # the following line will trigger sub { $game_ended = 1 }
163             $player1->score(1);
164              
165             =cut
166              
167             our $on_victory_do = sub { undef };
168              
169             sub on_victory_do {
170 7     7 1 29 shift;
171 7 100       35 ref($_[0]) eq 'CODE' || return undef;
172              
173 5         25 $on_victory_do = shift;
174             }
175              
176             =head3 on_defeat_do
177              
178             on_defeat_do(CODE);
179              
180             This method lets you define a function that will be called as soon as has_lost()
181             starts returning a true value. In other words, when the score changes and the
182             function defined with is_defeat() returns true, the function defined with
183             on_defeat_do() is called.
184              
185             The function receives as parameters the score of the player and its name.
186              
187             # set a new condition for on_defeat_do()
188             our $game_ended;
189             Games::Score->on_defeat_do( sub { $game_ended = 1 } );
190              
191             # assuming this:
192             my $player1 = Games::Score->new();
193             Games::Score->defeat_is( sub { $_[0] == -1 } );
194              
195             # the following line will trigger sub { $game_ended = 1 }
196             $player1->score(-1);
197              
198             =cut
199              
200             our $on_defeat_do = sub { undef };
201              
202             sub on_defeat_do {
203 6     6 1 12 shift;
204 6 100       26 ref($_[0]) eq 'CODE' || return undef;
205              
206 4         20 $on_defeat_do = shift;
207             }
208              
209             =head3 has_won
210              
211             has_won();
212              
213             Returns true if the function defined with victory_is() returns true;
214              
215             if ($player1->has_won()) {
216             print "$player1->name() has won";
217             }
218              
219             =cut
220              
221             sub has_won {
222 147     147 1 693 my $self = shift;
223 147         294 &{$victory_is}($self->score(), $self->name());
  147         342  
224             }
225              
226             =head3 has_lost
227              
228             has_lost();
229              
230             Returns true if the function defined with defeat_is() returns true;
231              
232             if ($player1->has_lost()) {
233             print "$player1->name() has lost";
234             }
235              
236             =cut
237              
238             sub has_lost {
239 107     107 1 183 my $self = shift;
240 107         230 &{$defeat_is}($self->score(), $self->name());
  107         253  
241             }
242              
243             =head3 is_ok
244              
245             is_ok();
246              
247             Returns true if the player hasn't won or lost.
248              
249             # keep playing until player either wins or loses
250             while ($player1->is_ok()) {
251             # your game code here
252             }
253              
254             =cut
255              
256             sub is_ok {
257 8     8 1 3663 my $self = shift;
258 8 100 100     23 return 0 if $self->has_won() or $self->has_lost();
259 5         38 1;
260             }
261              
262             =head2 SCORE METHODS
263              
264             =head3 add
265              
266             add(NUMBER);
267             add(NUMBER, NUMBER, ...);
268              
269             Give X points to the player (this always increases, regardless of
270             step_method()).
271              
272             # player1 gets 5 more points
273             $player1->add(5);
274              
275             # player1 gets 2, 3 and 4 more points
276             $player1->add(2,3,4);
277              
278             =cut
279              
280             sub add {
281 6     6 1 21 my $self = shift;
282              
283 6         20 for (@_) {
284 8 50       22 $_ || next;
285 8 100       21 $self->score() == $self->score($self->score + $_) && last;
286             }
287              
288 6         23 $self->score();
289             }
290              
291             =head3 subtract
292              
293             subtract(NUMBER);
294             subtract(NUMBER, NUMBER, ...);
295              
296             Take X points from the player (this always decreases, regardless of
297             step_method()).
298              
299             # player1 loses 5 points
300             $player1->subtract(5);
301              
302             # player1 loses 2, 3 and 4 more points
303             $player1->subtract(2,3,4);
304              
305             =cut
306              
307             sub subtract {
308 6     6 1 21 my $self = shift;
309              
310 6         20 for (@_) {
311 8 50       19 $_ || next;
312 8 100       21 $self->score() == $self->score($self->score - $_) && last;
313             }
314              
315 6         18 $self->score();
316             }
317              
318             =head3 invalidate_if
319              
320             invalidate_if(CODE);
321              
322             When the score is about to change, score doesn't change if the function defined
323             with invalidate_if() returns true.
324              
325             # sets the condition so that negative values are not allowed
326             Games::Score->invalidate_if( sub { $_[0] < 0 } );
327              
328             # here's an example of how this works:
329             Games::Score->invalidate_if( sub { $_[0] < 0 } );
330             my $player1 = Games::Score->new();
331             $player1->score(3);
332             # this line subtracts the player's score by 2
333             $player1->subtract(2);
334             # this one doesn't, as his score is already 1 and the result would be
335             # invalid
336             $player1->subtract(2);
337              
338             If one is, for instance, adding several numbers to the score, no more numbers
339             are added as soon as the score can be invalidated.
340              
341             # assuming the same configuration as before:
342             Games::Score->invalidate_if( sub { $_[0] > 20 } );
343             my $player1 = Games::Score->new();
344             $player1->score(18);
345              
346             # the following line adds 1 point to the score, doesn't add 2 more as
347             # that would take the score up do 21, and skips the rest of the
348             # instruction, even though it wouldn't invalidate anything by itself
349             $player1->add(1,2,1);
350              
351             To remove the condition, assign it an empty function
352              
353             # assign an empty function to victory_if
354             Games::Score->invalidate_if( sub { } );
355              
356             =cut
357              
358             our $invalidate_if = sub { undef };
359              
360             sub invalidate_if {
361 12     12 1 21 shift;
362 12 100       54 ref($_[0]) eq 'CODE' || return undef;
363              
364 8         42 $invalidate_if = shift;
365             }
366              
367             =head3 step_method
368              
369             step_method();
370             step_method('inc');
371             step_method('dec');
372              
373             Defines whether the set() method increases or decreases score; possible values
374             are 'inc' (increase) and 'dec' (decrease). Assigning multiple values stops at
375             the first valid one. Default value is 'inc', increase.
376              
377             # step method is inc (increase)
378             Games::Score->step_method('inc');
379              
380             # step method is dec (decrease)
381             Games::Score->step_method('dec');
382              
383             # check the step method
384             my $step_method = Games::Score->step_method();
385              
386             =cut
387              
388             our $step_method = 'inc';
389              
390             sub step_method {
391 60     60 1 130 for (@_) {
392 19 100       79 if (/^(?:inc|dec)$/) {
393 6         10 $step_method = $_;
394 6         13 last;
395             }
396             }
397              
398 60         149 $step_method;
399             }
400              
401             =head3 default_step
402              
403             default_step();
404             default_step(NUMBER);
405              
406             Set or check the default number of points the step() method uses. Assigning
407             multiple values makes the last of them to be it. Default value is 1.
408              
409             # step() function now increases (or decreases, see step_method()) in
410             # 2 points
411             Games::Score->default_step(2);
412              
413             # check the default_step
414             my $default_step = Games::Score->default_step();
415              
416             =cut
417              
418             our $default_step = 1;
419              
420             sub default_step {
421 55     55 1 66 shift;
422              
423 55         87 for (@_) {
424 4         7 $default_step = $_;
425             }
426              
427 55         143 return $default_step;
428             }
429              
430             =head3 step
431              
432             step();
433             step(NUMBER);
434              
435             The basic operation to change score. Default is "add one point"; that can be
436             changed with default_step() and method().
437              
438             # the score from player1 steps once
439             $player1->step;
440              
441             # the score from player1 steps twice
442             $player1->step(2);
443              
444             =cut
445              
446             sub step {
447 47     47 1 411 my $self = shift;
448              
449 47   100     1411 for (1 .. ($_[0] || 1)) {
450 50         93 for (step_method()) {
451 50 50       100 $self->score( $self->score() + (/^inc$/ ? default_step() :
452             - default_step()));
453             }
454             }
455              
456 47         98 $self->score();
457             }
458              
459             =head3 default_score
460              
461             default_score();
462             default_score(NUMBER);
463              
464             Set or check the default score with which new players start. Default is 0.
465              
466             # all players start with 301 points
467             Games::Score->default_score(301);
468              
469             # check the default_score
470             my $default_score = Games::Score->default_score();
471              
472             =cut
473              
474             our $default_score = 0;
475              
476             sub default_score {
477 10     10 1 40 for (@_) {
478 5 100       44 /^\d+$/ || next;
479 2         8 $default_score = $_;
480             }
481              
482 10         38 $default_score;
483             }
484              
485             =head3 priority_is
486              
487             priority_is();
488             priority_is('win');
489             priority_is('lose');
490             priority_is('win_lose');
491             priority_is('lose_win');
492              
493             Get or set the priority for actions involving winning or losing.
494              
495             Possible values are:
496              
497             =over 6
498              
499             =item lose
500              
501             If the player wins and loses at the same time, only the action for defeat is
502             run.
503              
504             =item win
505              
506             If the player wins and loses at the same time, only the action for victory is
507             run.
508              
509             =item win_lose
510              
511             If the player wins and loses at the same time, the action for victory is run
512             first and than the action for defeat is run too.
513              
514             =item lose_win
515              
516             If the player wins and loses at the same time, the action for defeat is run
517             first and than the action for victory is run too.
518              
519             =back
520              
521             Default_value is 'lose'.
522              
523             # Assuming this configuration
524             Games::Score->on_victory_do( sub { "You won!" } );
525             Games::Score->on_defeat( sub { "You lost!" } );
526              
527             # The following line states that if the player wins and loses at the
528             # same time, he loses
529             Games::Score->priority_is('lose');
530              
531             =cut
532              
533             our $priority = "lose";
534              
535             sub priority_is {
536 14     14 1 27 shift;
537 14         76 for (@_) {
538 14 100       81 /^(?:win|lose|win_lose|lose_win)$/ || return $priority;
539 11         17 $priority = $_;
540 11         24 last;
541             }
542              
543 11         45 $priority;
544             }
545              
546             =head3 score
547              
548             score();
549             score(NUMBER);
550             score(NUMBER, NUMBER, ...);
551              
552             Get or set the score of the player. Assigning multiple values goes through all
553             of them, skipping when invalidate_if() returns true, and stopping at the last
554             one. Default score is 0.
555              
556             # get the score of the player
557             my $score = $player->score();
558              
559             # player now has 10 points
560             $player->score(10);
561              
562             =cut
563              
564             sub score { # this should change, but it will have to do for now
565 523     523 1 698 my $self = shift;
566              
567 523         1011 for (@_) {
568 99 100       126 &{$invalidate_if}($_) && last;
  99         221  
569 97         235 $$self{SCORE} = $_;
570 97         156 for ($priority) {
571 97 100       433 if (/^win$/) {
    100          
    100          
    50          
572 1 50       4 if ($self->has_won()) {
    0          
573 1         8 &{$on_victory_do}($self->score(), $self->name());
  1         4  
574             }
575             elsif ($self->has_lost()) {
576 0         0 &{$on_defeat_do}($self->score(), $self->name());
  0         0  
577             }
578             }
579             elsif (/^lose$/) {
580 89 100       186 if ($self->has_lost()) {
    100          
581 4         26 &{$on_defeat_do}($self->score(), $self->name());
  4         14  
582             }
583             elsif ($self->has_won()) {
584 5         34 &{$on_victory_do}($self->score(), $self->name());
  5         19  
585             }
586             }
587             elsif (/^win_lose$/) {
588 2 100       24 if ($self->has_won()) {
589 1         10 &{$on_victory_do}($self->score(), $self->name());
  1         5  
590             }
591 2 100       20 if ($self->has_lost()) {
592 1         10 &{$on_defeat_do}($self->score(), $self->name());
  1         4  
593             }
594             }
595             elsif (/^lose_win$/) {
596 5 100       14 if ($self->has_lost()) {
597 3         20 &{$on_defeat_do}($self->score(), $self->name());
  3         9  
598             }
599 5 100       29 if ($self->has_won()) {
600 3         20 &{$on_victory_do}($self->score(), $self->name());
  3         10  
601             }
602             }
603             }
604             }
605              
606 523         2358 $$self{SCORE};
607             }
608              
609             =head2 OTHER METHODS
610              
611             =head3 default_name
612              
613             default_name();
614             default_name(DEFAULT_NAME);
615              
616             Set or check the default name with which new players start. Default name is
617             'Player'.
618              
619             # all players are by default named "PLAYER"
620             Games::Score->default_name("PLAYER");
621              
622             # check the default_name
623             my $default_name = Games::Score->default_name();
624              
625             =cut
626              
627             our $default_name = 'Player';
628              
629             sub default_name {
630 7     7 1 15 shift;
631              
632 7         17 for (@_) {
633 1         2 $default_name = $_;
634 1         3 last;
635             }
636              
637 7         182 $default_name;
638             }
639              
640             =head3 name
641              
642             name();
643             name(NEW_NAME);
644              
645             Get or set the name of the player. Default name is 'Player', which can be
646             changed with default_name().
647              
648             # get the name of the player
649             my $name = $player->name();
650              
651             # player is now named "WARRIOR"
652             $player->name("WARRIOR");
653              
654             =cut
655              
656             sub name {
657 282     282 1 362 my $self = shift;
658              
659 282         432 for (@_) {
660 1         5 $$self{NAME} = $_;
661             }
662              
663 282         686 return $$self{NAME};
664             }
665              
666             =head1 REGARDING DRAWS
667              
668             Please note the following: if you happen to have two players, change the score
669             for both of them and both of them get in the same situation (victory, for
670             instance), one of them is going to have his on_victory_do() function (if
671             defined) run before the other one.
672              
673             Always consider the possibility of draws in your game with disregard to
674             Games::Score (at least for now).
675              
676             =head1 EXAMPLES
677              
678             =head2 START AT 0, WIN AT 20 OR MORE
679              
680             Example of a game where users start with 0 points and win as soon as they get
681             more than 20 points. There is no way of losing.
682              
683             # These two lines aren't actually needed, as these are the default
684             # values
685             Games::Score->default_score(0);
686             Games::Score->step_method('inc');
687              
688             # Set the victory condition
689             Games::Score->victory_is( sub { $_[0] > 20; } );
690              
691             # Set what to do on victory
692             our $game_ended = 0;
693             our $message = '';
694             Games::Score->on_victory_do( sub {
695             $game_ended = 1;
696             $message = "$_[1] has won!\n";
697             } );
698              
699             # Start two players, "Shiribi" and "Zuncucu"
700             my $player1 = Games::Score->new("Shiribi");
701             my $player2 = Games::Score->new("Zuncucu");
702             my @players = ($player1, $player2);
703              
704             # And have a random game
705             until ($game_ended) {
706             for (@players) {
707             if (rand(1)) {
708             $_->step();
709             print "Player $_->name() scored ",
710             "and now has $_->score() point(s).\n";
711             last if $_->has_won();
712             }
713             else {
714             print "Player $_->name() didn't score.\n"
715             }
716             }
717             }
718              
719             =head2 START AT 301, GO DOWN AND WIN ON 0, PRECISELY
720              
721             Example of a game where users start with 301 points, always lose points instead
722             of gaining them, and win when they reach 0 points. Getting less than 0 points
723             invalidates the score update.
724              
725             # default score is 301, points decrease, winning on 0, negative score
726             # disallowed
727             Games::Score->default_score(301);
728             Games::Score->step_method('dec');
729             Games::Score->invalidate_if( sub { $_[0] < 0 } );
730             Games::Score->victory_is( sub { $_[0] == 0 } );
731              
732             =head1 AUTHOR
733              
734             Jose Castro, C<< >>
735              
736             =head1 COPYRIGHT & LICENSE
737              
738             Copyright 2004 Jose Castro, All Rights Reserved.
739              
740             This program is free software; you can redistribute it and/or modify it
741             under the same terms as Perl itself.
742              
743             =cut
744              
745             1;