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; |