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