File Coverage

blib/lib/Games/2048/Board.pm
Criterion Covered Total %
statement 24 124 19.3
branch 0 74 0.0
condition 0 18 0.0
subroutine 8 22 36.3
pod 0 15 0.0
total 32 253 12.6


line stmt bran cond sub pod time code
1             package Games::2048::Board;
2 4     4   86 use 5.012;
  4         66  
  4         415  
3 4     4   21 use Moo;
  4         6  
  4         20  
4              
5 4     4   8741 use Text::Wrap;
  4         19473  
  4         535  
6 4     4   5823 use Term::ANSIColor;
  4         54753  
  4         763  
7 4     4   54 use POSIX qw/floor ceil/;
  4         7  
  4         66  
8 4     4   827 use List::Util qw/max min/;
  4         9  
  4         443  
9 4     4   11704 use Color::ANSI::Util qw/ansifg ansibg/;
  4         28850  
  4         14788  
10              
11             extends 'Games::2048::Grid';
12              
13             has score => is => 'rw', default => 0;
14             has best_score => is => 'rw', default => 0;
15             has needs_redraw => is => 'rw', default => 1;
16             has win => is => 'rw', default => 0;
17             has lose => is => 'rw', default => 0;
18              
19             has moving => is => 'rw';
20             has moving_vec => is => 'rw';
21              
22             has border_width => is => 'rw', default => 2;
23             has border_height => is => 'rw', default => 1;
24             has cell_width => is => 'rw', default => 7;
25             has cell_height => is => 'rw', default => 3;
26             has score_width => is => 'rw', default => 7;
27              
28             sub insert_tile {
29 474     474 0 5185 my ($self, $cell, $value) = @_;
30 474         11749 my $tile = Games::2048::Tile->new(
31             value => $value,
32             appear => Games::2048::Animation->new(
33             duration => 0.2,
34             first_value => -1 / max($self->cell_width, $self->cell_height),
35             last_value => 1,
36             ),
37             );
38 474         38746 $self->set_tile($cell, $tile);
39             }
40              
41             sub draw {
42 0     0 0   my ($self, $redraw) = @_;
43              
44 0 0 0       return if $redraw and !$self->needs_redraw;
45              
46 0           $self->hide_cursor;
47 0 0         $self->restore_cursor if $redraw;
48 0           $self->needs_redraw(0);
49              
50 0           $self->draw_score;
51 0           $self->draw_border_horizontal;
52              
53 0           for my $y (0..$self->size-1) {
54 0           for my $line (0..$self->cell_height-1) {
55 0           $self->draw_border_vertical;
56              
57 0           for my $x (0..$self->size-1) {
58 0           my $tile = $self->tile([$x, $y]);
59              
60 0           my $string;
61 0 0         my $value = $tile ? $tile->value : undef;
62 0           my $color = $self->tile_color($value);
63 0           my $bgcolor = $self->tile_color(undef);
64              
65 0   0       my $lines = min(ceil(length($value // '') / $self->cell_width), $self->cell_height);
66 0           my $first_line = floor(($self->cell_height - $lines) / 2);
67 0           my $this_line = $line - $first_line;
68              
69 0 0 0       if ($this_line >= 0 and $this_line < $lines) {
70 0           my $cols = min(ceil(length($value) / $lines), $self->cell_width);
71 0           my $string_offset = $this_line * $cols;
72 0           my $string_length = min($cols, length($value) - $string_offset, $self->cell_width);
73 0           my $cell_offset = floor(($self->cell_width - $string_length) / 2);
74              
75 0           $string = " " x $cell_offset;
76              
77 0           $string .= substr($value, $string_offset, $string_length);
78              
79 0           $string .= " " x ($self->cell_width - $cell_offset - $string_length);
80             }
81             else {
82 0           $string = " " x $self->cell_width;
83             }
84              
85 0 0 0       if ($tile and $tile->appear) {
86             # if any animation is going we need to keep redrawing
87 0           $self->needs_redraw(1);
88              
89 0           my $value = $tile->appear->value;
90 0 0         if ($line == $self->cell_height-1) {
91 0 0         $tile->appear(undef) if !$tile->appear->update;
92             }
93              
94 0           my $x_center = ($self->cell_width - 1) / 2;
95 0           my $y_center = ($self->cell_height - 1) / 2;
96              
97 0           my $on = 0;
98 0           my $extra = 0;
99 0           for my $col (0..$self->cell_width-1) {
100 0           my $x_distance = $col / $x_center - 1;
101 0           my $y_distance = $line / $y_center - 1;
102 0           my $distance = $x_distance**2 + $y_distance**2;
103              
104 0           my $within = $distance <= 2 * $value**2;
105              
106 0 0 0       if ($within xor $on) {
107 0           $on = $within;
108              
109 0 0         my $insert = $on
110             ? $color
111             : $bgcolor;
112              
113 0           substr($string, $col + $extra, 0) = $insert;
114 0           $extra += length($insert);
115             }
116             }
117 0 0         if ($on) {
118 0           $string .= $bgcolor;
119             }
120             }
121             else {
122 0           $string = $color . $string . $bgcolor;
123             }
124              
125 0           print $string;
126             }
127              
128 0           $self->draw_border_vertical;
129 0           say color("reset");
130             }
131             }
132              
133 0           $self->draw_border_horizontal;
134 0 0         $self->show_cursor if !$self->needs_redraw;
135             }
136              
137             sub draw_win {
138 0     0 0   my $self = shift;
139 0 0 0       return if !$self->win and !$self->lose;
140 0 0         my $message =
141             $self->win ? "You win!"
142             : "Game over!";
143 0           my $offset = ceil(($self->board_width - length($message)) / 2);
144              
145 0           say " " x $offset, colored(uc $message, "bold"), "\n";
146             }
147              
148             sub draw_score {
149 0     0 0   my ($self) = @_;
150              
151 0           my $score = "Score:";
152 0           my $best_score = "Best:";
153              
154 0           my $blank_width = $self->board_width - length($score) - length($best_score);
155 0           my $score_width = min(floor(($blank_width - 1) / 2), $self->score_width);
156 0           my $inner_padding = $blank_width - $score_width * 2;
157              
158 0           $self->draw_sub_score($score, $score_width, $self->score);
159              
160 0           print " " x $inner_padding;
161              
162 0           $self->draw_sub_score($best_score, $score_width, $self->best_score);
163              
164 0           say "";
165             }
166              
167             sub draw_sub_score {
168 0     0 0   my ($self, $string, $score_width, $score) = @_;
169 0           printf "%s%*d", colored($string, "bold"), $score_width, $score;
170             }
171              
172             sub tile_color {
173 0     0 0   my ($self, $value) = @_;
174 0 0         if ($ENV{KONSOLE_DBUS_SERVICE}) {
175             return
176 0 0         !defined $value ? ansifg("BBADA0") . ansibg("CCC0B3")
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
177             : $value < 4 ? ansifg("776E65") . ansibg("EEE4DA")
178             : $value < 8 ? ansifg("776E65") . ansibg("EDE0C8")
179             : $value < 16 ? ansifg("F9F6F2") . ansibg("F2B179")
180             : $value < 32 ? ansifg("F9F6F2") . ansibg("F59563")
181             : $value < 64 ? ansifg("F9F6F2") . ansibg("F67C5F")
182             : $value < 128 ? ansifg("F9F6F2") . ansibg("F65E3B")
183             : $value < 256 ? ansifg("F9F6F2") . ansibg("EDCF72") . color("bold")
184             : $value < 512 ? ansifg("F9F6F2") . ansibg("EDCC61") . color("bold")
185             : $value < 1024 ? ansifg("F9F6F2") . ansibg("EDC850") . color("bold")
186             : $value < 2048 ? ansifg("F9F6F2") . ansibg("EDC53F") . color("bold")
187             : $value < 4096 ? ansifg("F9F6F2") . ansibg("EDC22E") . color("bold")
188             : ansifg("F9F6F2") . ansibg("3C3A32") . color("bold");
189             }
190 0 0         my $bright = $^O eq "MSWin32" ? "underline " : "bright_";
191 0 0         my $bold = $^O eq "MSWin32" ? "underline" : "bold";
192 0 0         return color (
    0          
    0          
    0          
    0          
    0          
    0          
    0          
193             !defined $value ? "reset"
194             : $value < 4 ? "reverse cyan"
195             : $value < 8 ? "reverse ${bright}blue"
196             : $value < 16 ? "reverse blue"
197             : $value < 32 ? "reverse green"
198             : $value < 64 ? "reverse magenta"
199             : $value < 128 ? "reverse red"
200             : $value < 4096 ? "reverse yellow"
201             : "reverse $bold"
202             );
203             }
204              
205             sub border_color {
206 0 0   0 0   $ENV{KONSOLE_DBUS_SERVICE}
207             ? ansifg("CCC0B3") . ansibg("BBADA0")
208             : color("reverse");
209             }
210              
211             sub board_width {
212 0     0 0   my $self = shift;
213 0           return $self->size * $self->cell_width + $self->border_width * 2;
214             }
215              
216             sub board_height {
217 0     0 0   my $self = shift;
218 0           return $self->size * $self->cell_height + $self->border_height * 2;
219             }
220              
221             sub draw_border_horizontal {
222 0     0 0   my $self = shift;
223 0           say $self->border_color, " " x $self->board_width, color("reset") for 1..$self->border_height;
224             }
225             sub draw_border_vertical {
226 0     0 0   my $self = shift;
227 0           print $self->border_color, " " x $self->border_width, $self->tile_color(undef);
228             }
229              
230             sub restore_cursor {
231 0     0 0   my $self = shift;
232 0           printf "\e[%dA", $self->board_height + 1;
233             }
234              
235             sub draw_welcome {
236 0     0 0   local $Text::Wrap::columns = Games::2048::Input::window_size;
237              
238 0           my $message = <
239             2048 - Join the numbers and get to the 2048 tile!
240              
241             How to play: Use your arrow keys to move the tiles. When two tiles with the same number touch, they merge into one!
242             Quit: Q
243             New Game: R
244              
245             MESSAGE
246              
247 0           $message = wrap "", "", $message;
248              
249 0           $message =~ s/(^2048|How to play:|arrow keys|merge into one!|Quit:|New Game:)/colored $1, "bold"/ge;
  0            
250              
251 0           say $message;
252             }
253              
254             sub hide_cursor {
255 0     0 0   my $self = shift;
256 0           state $once = eval 'END { $self->show_cursor }';
257 0           print "\e[?25l";
258             }
259             sub show_cursor {
260 0     0 0   my $self = shift;
261 0           print "\e[?25h";
262             }
263              
264             1;