File Coverage

blib/lib/Game/Marad.pm
Criterion Covered Total %
statement 91 94 96.8
branch 40 40 100.0
condition 33 33 100.0
subroutine 12 13 92.3
pod 7 7 100.0
total 183 187 97.8


line stmt bran cond sub pod time code
1             # -*- Perl -*-
2              
3             package Game::Marad 0.05;
4 2     2   229779 use 5.26.0;
  2         11  
5 2     2   1213 use Object::Pad 0.52;
  2         22080  
  2         11  
6             class Game::Marad :strict(params);
7              
8             use constant {
9 2         4812 BOARD_SIZE => 9,
10             MIDDLE => 4, # the middle square is used for scoring
11              
12             MAX_MOVES => 4,
13              
14             TYPE_MASK => 0b0000_1111, # piece type
15             PLAYER_BIT => 6, # owned by player 1 or 2?
16             MOVED_MASK => 0b1000_0000, # has the piece moved?
17              
18             MOVE_NOPE => 0,
19             MOVE_SQUARE => 1, # NOTE also Rook
20             MOVE_DIAGONAL => 2, # NOTE also Bishop
21              
22             PIECE_EMPTY => 0,
23             PIECE_ROOK => 1,
24             PIECE_BISHOP => 2,
25             PIECE_KING => 3, # NOTE Rook and Bishop bits are set
26              
27             XX => 0,
28             YY => 1,
29 2     2   875 };
  2         4  
30              
31             # NOTE the client is expected to be well behaved and to not modify the
32             # contents of board nor the score (hiding these references would only
33             # make such impolite behavior slightly more difficult)
34 1     1 1 4 has $board :reader;
  1         11  
35 2     2 1 7 has $move_count :reader;
  2         8  
36 3     3 1 10 has $player :reader;
  3         14  
37 5     5 1 406 has $score :reader;
  5         34  
38              
39             ADJUST {
40             $board = [
41             [qw/0 0 0 0 0 0 0 0 0/], # starting game state
42             [qw/0 2 0 0 0 0 0 66 0/],
43             [qw/0 1 0 0 0 0 0 65 0/],
44             [qw/0 1 0 0 0 0 0 65 0/],
45             [qw/0 3 0 0 0 0 0 67 0/],
46             [qw/0 1 0 0 0 0 0 65 0/],
47             [qw/0 1 0 0 0 0 0 65 0/],
48             [qw/0 2 0 0 0 0 0 66 0/],
49             [qw/0 0 0 0 0 0 0 0 0/],
50             ];
51             $move_count = _move_count();
52             $player = 0;
53             $score = [ 0, 0 ];
54             }
55              
56             ########################################################################
57             #
58             # METHODS
59              
60 7     7 1 25 method is_owner( $x, $y ) {
  7         10  
  7         10  
  7         10  
  7         8  
61 7 100 100     61 return 0 if $x < 0 or $x >= BOARD_SIZE or $y < 0 or $y >= BOARD_SIZE;
      100        
      100        
62 3         5 my $piece = $board->[$y][$x];
63 3 100       11 return 0 if $piece == PIECE_EMPTY;
64 2 100       10 return 0 unless ( $piece >> PLAYER_BIT & 1 ) == $player;
65 1         7 return 1;
66             }
67              
68             # try to carry out a game move involving two points, generally from a
69             # player selecting a piece to move and a direction (via the destination
70             # point) to move in. the move may not be possible for various reasons.
71             # if possible a move may cause a bunch of changes to the board and other
72             # game state
73 13     13 1 161 method move( $srcx, $srcy, $dstx, $dsty ) {
  13         21  
  13         17  
  13         17  
  13         19  
  13         14  
  13         16  
74 13 100 100     109 return 0, "out of bounds"
      100        
      100        
75             if $srcx < 0
76             or $srcx >= BOARD_SIZE
77             or $srcy < 0
78             or $srcy >= BOARD_SIZE;
79              
80 9         17 my $piece = $board->[$srcy][$srcx];
81 9 100       25 return 0, "not a piece" if $piece == PIECE_EMPTY;
82              
83 8 100       29 return 0, "not owner" unless ( $piece >> PLAYER_BIT & 1 ) == $player;
84              
85 7         21 my ( $move_type, $stepx, $stepy ) = _move_type( $srcx, $srcy, $dstx, $dsty );
86 7 100       26 return 0, "invalid move" if $move_type == MOVE_NOPE;
87              
88             # this is probably too clever: the king by virtue of being number 3
89             # has both the square and diagonal move bits set, while rooks and
90             # bishops have only one of them set
91 6         9 my $piece_type = $piece & TYPE_MASK;
92 6 100       20 return 0, "invalid move type" unless ( $move_type & $piece_type ) > 0;
93              
94 5         16 _move_stack( $board, $move_count, $srcx, $srcy, $stepx, $stepy );
95              
96             # score points for motion in the middle
97 5         9 my $center = $board->[MIDDLE][MIDDLE];
98 5 100 100     17 if ( $center > 0 and ( $center & MOVED_MASK ) == MOVED_MASK ) {
99 2         5 $score->[ $center >> PLAYER_BIT & 1 ]++;
100             }
101             # clear any moved bits (keeps the map clean, only the middle moved
102             # bit really needs to be cleared, provided things that iterate over
103             # the $board extract the type and owner)
104 5         9 for my $row ( $board->@* ) {
105 45         68 for my $col ( $row->@* ) {
106 405         505 $col &= ~MOVED_MASK;
107             }
108             }
109              
110 5         7 $player ^= 1;
111 5 100       14 $move_count = _move_count() if $player == 0;
112              
113 5         54 return 1, "ok";
114             }
115              
116             # boards of different sizes might be supported in which case clients may
117             # need something like the following to obtain that information
118 1     1 1 15 method size() { return BOARD_SIZE }
  1         5  
  1         2  
  1         3  
119              
120             ########################################################################
121             #
122             # SUBROUTINES
123              
124             # this many moves happen in each turnpair
125 0     0   0 sub _move_count () { 1 + int rand(MAX_MOVES) }
  0         0  
  0         0  
126              
127             # moves stuff with less recursion than in prior versions (before 0.05)
128 10     10   9352 sub _move_stack ( $grid, $moves, $srcx, $srcy, $stepx, $stepy ) {
  10         14  
  10         13  
  10         15  
  10         13  
  10         13  
  10         12  
  10         12  
129 10         17 my $point = [];
130 10         26 $point->@[XX,YY] = ($srcx, $srcy);
131 10         19 my @stack = $point;
132 10         26 while ( $moves > 0 ) {
133 37         52 my $point = [];
134 37         73 $point->@[ XX, YY ] = ( $stack[-1][XX] + $stepx, $stack[-1][YY] + $stepy );
135             # edge: ran out of space for pushing
136             last
137 37 100 100     194 if $point->[XX] < 0
      100        
      100        
138             or $point->[XX] >= BOARD_SIZE
139             or $point->[YY] < 0
140             or $point->[YY] >= BOARD_SIZE;
141 30         45 push @stack, $point;
142             # empty cell: swap along the stack to advance the pieces
143 30 100       80 if ( $grid->[ $stack[-1][YY] ][ $stack[-1][XX] ] == PIECE_EMPTY ) {
144 27         54 for my $i ( reverse 0 .. ( $#stack - 1 ) ) {
145             # downside: this may happen more than it needs to
146 37         59 $grid->[ $stack[$i][YY] ][ $stack[$i][XX] ] |= MOVED_MASK;
147 37         48 my $j = $i + 1;
148 37         105 ( $grid->[ $stack[$i][YY] ][ $stack[$i][XX] ],
149             $grid->[ $stack[$j][YY] ][ $stack[$j][XX] ]
150             )
151             = (
152             $grid->[ $stack[$j][YY] ][ $stack[$j][XX] ],
153             $grid->[ $stack[$i][YY] ][ $stack[$i][XX] ]
154             );
155             # in theory one could collect a list of moves for use by
156             # an animation routine, or have a callback for that. but
157             # that would use more CPU and memory
158             }
159 27         40 shift @stack;
160 27         53 $moves--;
161             }
162             # non-empty cell: put it onto the stack next time around
163             }
164             }
165              
166             # given two points, what sort of movement is it? (may not be valid)
167 17     17   13139 sub _move_type ( $x1, $y1, $x2, $y2 ) {
  17         23  
  17         24  
  17         25  
  17         21  
  17         20  
168 17 100 100     64 return MOVE_NOPE, undef, undef if $x1 == $x2 and $y1 == $y2;
169              
170 15         35 my ( $dy, $plus_x ) = ( $y2 - $y1, $x2 > $x1 );
171 15 100       59 return MOVE_SQUARE, ( $plus_x ? 1 : -1 ), 0 if $dy == 0;
    100          
172              
173 11         20 my ( $dx, $plus_y ) = ( $x2 - $x1, $y2 > $y1 );
174 11 100       32 return MOVE_SQUARE, 0, ( $plus_y ? 1 : -1 ) if $dx == 0;
    100          
175              
176 9 100       59 return MOVE_DIAGONAL, ( $plus_x ? 1 : -1 ), ( $plus_y ? 1 : -1 )
    100          
    100          
177             if abs($dx) == abs($dy);
178              
179 1         6 return MOVE_NOPE, undef, undef;
180             }
181              
182             1;
183             __END__