line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# -*- Perl -*- |
2
|
|
|
|
|
|
|
# |
3
|
|
|
|
|
|
|
# Game::PlatformsOfPeril - this is a terminal-based game, run the |
4
|
|
|
|
|
|
|
# `pperil` command that should be installed with this module to begin |
5
|
|
|
|
|
|
|
# |
6
|
|
|
|
|
|
|
# some details for the unwary, or brave, regarding the code: |
7
|
|
|
|
|
|
|
# |
8
|
|
|
|
|
|
|
# this implementation uses arrays heavily so instead of a more typical |
9
|
|
|
|
|
|
|
# Player object there is an array with various slots that are used for |
10
|
|
|
|
|
|
|
# various purposes. these slots are indexed using constant subs, and |
11
|
|
|
|
|
|
|
# there is some overlap of these slots for animates, items, and terrain. |
12
|
|
|
|
|
|
|
# the @Animates array (where the player, monsters, and items reside) and |
13
|
|
|
|
|
|
|
# $LMap (level map, which has every ROW and COL and then an array (LMC) |
14
|
|
|
|
|
|
|
# for what is in that cell) is where most of the game data resides. |
15
|
|
|
|
|
|
|
# there can be only one terrain (GROUND), one ITEM, and one animate |
16
|
|
|
|
|
|
|
# (ANI) per level map cell; any new interactions will need to support |
17
|
|
|
|
|
|
|
# this. there are also four graphs per level map; these graphs dictate |
18
|
|
|
|
|
|
|
# what moves are possible for animates (double benefit of providing both |
19
|
|
|
|
|
|
|
# legal next moves and for pathfinding across the map). gravity pulls |
20
|
|
|
|
|
|
|
# things down at the beginning of a turn (bottom up), and the player |
21
|
|
|
|
|
|
|
# always moves first in the turn (low id to high), see the game_loop. |
22
|
|
|
|
|
|
|
# level maps are ASCII text, and only one thing can be present in a cell |
23
|
|
|
|
|
|
|
# in the map (with FLOOR being assumed present below any item or |
24
|
|
|
|
|
|
|
# animate). there are some complications around killing things off; dead |
25
|
|
|
|
|
|
|
# things must not interact with anything, but may still be looped to |
26
|
|
|
|
|
|
|
# after their death in the apply_gravity or game_loop UPDATE calls. |
27
|
|
|
|
|
|
|
# hence the BLACK_SPOT |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
package Game::PlatformsOfPeril; |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
our $VERSION = '0.10'; |
32
|
|
|
|
|
|
|
|
33
|
1
|
|
|
1
|
|
70391
|
use 5.24.0; |
|
1
|
|
|
|
|
4
|
|
34
|
1
|
|
|
1
|
|
16
|
use warnings; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
44
|
|
35
|
1
|
|
|
1
|
|
540
|
use File::Spec::Functions qw(catfile); |
|
1
|
|
|
|
|
842
|
|
|
1
|
|
|
|
|
59
|
|
36
|
1
|
|
|
1
|
|
545
|
use List::PriorityQueue (); |
|
1
|
|
|
|
|
877
|
|
|
1
|
|
|
|
|
25
|
|
37
|
1
|
|
|
1
|
|
7
|
use List::Util qw(first); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
119
|
|
38
|
1
|
|
|
1
|
|
525
|
use List::UtilsBy 0.06 qw(nsort_by rev_nsort_by); |
|
1
|
|
|
|
|
1974
|
|
|
1
|
|
|
|
|
70
|
|
39
|
1
|
|
|
1
|
|
521
|
use Term::ReadKey qw(GetTerminalSize ReadKey ReadMode); |
|
1
|
|
|
|
|
2125
|
|
|
1
|
|
|
|
|
71
|
|
40
|
1
|
|
|
1
|
|
578
|
use Time::HiRes qw(sleep); |
|
1
|
|
|
|
|
1369
|
|
|
1
|
|
|
|
|
5
|
|
41
|
1
|
|
|
1
|
|
768
|
use POSIX qw(STDIN_FILENO TCIFLUSH tcflush); |
|
1
|
|
|
|
|
6476
|
|
|
1
|
|
|
|
|
5
|
|
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
# ANSI or XTerm control sequences |
44
|
0
|
|
|
0
|
0
|
0
|
sub at { "\e[" . $_[1] . ';' . $_[0] . 'H' } |
45
|
0
|
|
|
0
|
0
|
0
|
sub at_col { "\e[" . $_[0] . 'G' } |
46
|
|
|
|
|
|
|
sub alt_screen () { "\e[?1049h" } |
47
|
|
|
|
|
|
|
sub clear_line () { "\e[2K" } |
48
|
|
|
|
|
|
|
sub clear_right () { "\e[K" } |
49
|
|
|
|
|
|
|
sub clear_screen () { "\e[1;1H\e[2J" } |
50
|
|
|
|
|
|
|
sub hide_cursor () { "\e[?25l" } |
51
|
|
|
|
|
|
|
sub hide_pointer () { "\e[>3p" } |
52
|
|
|
|
|
|
|
sub show_cursor () { "\e[?25h" } |
53
|
|
|
|
|
|
|
sub term_norm () { "\e[m" } |
54
|
|
|
|
|
|
|
sub unalt_screen () { "\e[?1049l" } |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
# WHAT Animates and such can be |
57
|
|
|
|
|
|
|
sub HERO () { 0 } |
58
|
|
|
|
|
|
|
sub MONST () { 1 } |
59
|
|
|
|
|
|
|
sub BOMB () { 2 } |
60
|
|
|
|
|
|
|
sub GEM () { 3 } |
61
|
|
|
|
|
|
|
sub FLOOR () { 4 } |
62
|
|
|
|
|
|
|
sub WALL () { 5 } |
63
|
|
|
|
|
|
|
sub LADDER () { 6 } |
64
|
|
|
|
|
|
|
sub STAIR () { 7 } |
65
|
|
|
|
|
|
|
sub STATUE () { 8 } |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
sub BOMB_COST () { 2 } |
68
|
|
|
|
|
|
|
sub GEM_VALUE () { 1 } |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
# for the Level Map Cell (LMC) |
71
|
|
|
|
|
|
|
sub WHERE () { 0 } |
72
|
|
|
|
|
|
|
sub GROUND () { 1 } |
73
|
|
|
|
|
|
|
sub ITEM () { 2 } |
74
|
|
|
|
|
|
|
sub ANI () { 3 } |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
sub MOVE_FAILED () { 0 } |
77
|
|
|
|
|
|
|
sub MOVE_OK () { 1 } |
78
|
|
|
|
|
|
|
sub MOVE_NEWLVL () { 2 } |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
# for the level map |
81
|
|
|
|
|
|
|
sub COLS () { 23 } |
82
|
|
|
|
|
|
|
sub ROWS () { 23 } |
83
|
|
|
|
|
|
|
sub MAP_DISP_OFF () { 1 } |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
# level map is row, col while points are [ col, row ] |
86
|
|
|
|
|
|
|
sub PROW () { 1 } |
87
|
|
|
|
|
|
|
sub PCOL () { 0 } |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
sub MSG_ROW () { 1 } |
90
|
|
|
|
|
|
|
sub MSG_COL () { 25 } |
91
|
|
|
|
|
|
|
# these also used to determine the minimum size for the terminal |
92
|
|
|
|
|
|
|
sub MSG_MAX () { 24 } |
93
|
|
|
|
|
|
|
sub MSG_COLS_MAX () { 70 } |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
# for Animates (and also some Things for the first few slots) |
96
|
|
|
|
|
|
|
sub WHAT () { 0 } |
97
|
|
|
|
|
|
|
sub DISP () { 1 } |
98
|
|
|
|
|
|
|
# NOTE that GROUND use TYPE to distinguish between different types of |
99
|
|
|
|
|
|
|
# those (FLOOR, STAIR, STATUE) which makes the graph code simpler as |
100
|
|
|
|
|
|
|
# that only needs to look at WHAT for whether motion is possible in that |
101
|
|
|
|
|
|
|
# cell; ANI and ITEM instead use TYPE to tell ANI apart from ITEM |
102
|
|
|
|
|
|
|
sub TYPE () { 2 } |
103
|
|
|
|
|
|
|
sub STASH () { 3 } |
104
|
|
|
|
|
|
|
sub UPDATE () { 4 } |
105
|
|
|
|
|
|
|
sub LMC () { 5 } |
106
|
|
|
|
|
|
|
sub BLACK_SPOT () { 6 } |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
sub GEM_STASH () { 0 } |
109
|
|
|
|
|
|
|
sub BOMB_STASH () { 1 } |
110
|
|
|
|
|
|
|
sub GEM_ODDS () { 1 } |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
sub GEM_ODDS_ADJUST () { 0.05 } |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
sub START_GEMS () { 0 } |
115
|
|
|
|
|
|
|
sub START_BOMBS () { 1 } |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
sub GRAPH_NODE () { 0 } |
118
|
|
|
|
|
|
|
sub GRAPH_WEIGHT () { 1 } |
119
|
|
|
|
|
|
|
sub GRAPH_POINT () { 2 } |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
our %CharMap = ( |
122
|
|
|
|
|
|
|
'o' => BOMB, |
123
|
|
|
|
|
|
|
'.' => FLOOR, |
124
|
|
|
|
|
|
|
'*' => GEM, |
125
|
|
|
|
|
|
|
'@' => HERO, |
126
|
|
|
|
|
|
|
'=' => LADDER, |
127
|
|
|
|
|
|
|
'P' => MONST, |
128
|
|
|
|
|
|
|
'%' => STAIR, |
129
|
|
|
|
|
|
|
'&' => STATUE, |
130
|
|
|
|
|
|
|
'#' => WALL, |
131
|
|
|
|
|
|
|
); |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
our ( |
134
|
|
|
|
|
|
|
@Animates, @Graphs, $LMap, $Monst_Name, @RedrawA, |
135
|
|
|
|
|
|
|
@RedrawB, $Hero, $TCols, $TRows |
136
|
|
|
|
|
|
|
); |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
our %Examine_Offsets = ( |
139
|
|
|
|
|
|
|
'h' => [ -1, +0 ], # left |
140
|
|
|
|
|
|
|
'j' => [ +0, +1 ], # down |
141
|
|
|
|
|
|
|
'k' => [ +0, -1 ], # up |
142
|
|
|
|
|
|
|
'l' => [ +1, +0 ], # right |
143
|
|
|
|
|
|
|
'y' => [ -1, -1 ], |
144
|
|
|
|
|
|
|
'u' => [ +1, -1 ], |
145
|
|
|
|
|
|
|
'b' => [ -1, +1 ], |
146
|
|
|
|
|
|
|
'n' => [ +1, +1 ], |
147
|
|
|
|
|
|
|
); |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
our $Level = 0; |
150
|
|
|
|
|
|
|
our $Level_Path; |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
# plosive practice. these must pluralize properly |
153
|
|
|
|
|
|
|
our @Menagerie = ( |
154
|
|
|
|
|
|
|
'Palace Peacock', |
155
|
|
|
|
|
|
|
'Peckish Packrat', |
156
|
|
|
|
|
|
|
'Peevish Penguin', |
157
|
|
|
|
|
|
|
'Piratical Parakeet', |
158
|
|
|
|
|
|
|
'Placid Piranha', |
159
|
|
|
|
|
|
|
'Pleasant Porcupine', |
160
|
|
|
|
|
|
|
'Priggish Python', |
161
|
|
|
|
|
|
|
'Prurient Pachyderm', |
162
|
|
|
|
|
|
|
'Purposeful Plant', |
163
|
|
|
|
|
|
|
# and some not-plosives for reasons lost in the mists of time |
164
|
|
|
|
|
|
|
'Gruesome Goose', |
165
|
|
|
|
|
|
|
'Sinister Swan', |
166
|
|
|
|
|
|
|
'Xenophobic Xarci', |
167
|
|
|
|
|
|
|
); |
168
|
|
|
|
|
|
|
$Monst_Name = $Menagerie[ rand @Menagerie ]; |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
our $Redraw_Delay = 0.05; |
171
|
|
|
|
|
|
|
our $Rotate_Delay = 0.20; |
172
|
|
|
|
|
|
|
our $Rotation = 0; |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
our @Scientists = qw(Eigen Maxwell Newton); |
175
|
|
|
|
|
|
|
our $Scientist = $Scientists[ rand @Scientists ]; |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
our $Seed; |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
our @Styles = |
180
|
|
|
|
|
|
|
qw(Abstract Art-Deco Brutalist Egyptian Greek Impressionist Post-Modern Roman Romantic); |
181
|
|
|
|
|
|
|
our $Style = $Styles[ rand @Styles ]; |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
our %Things = ( |
184
|
|
|
|
|
|
|
BOMB, [ BOMB, "\e[31mo\e[0m", ITEM ], |
185
|
|
|
|
|
|
|
FLOOR, [ FLOOR, "\e[33m.\e[0m", FLOOR ], |
186
|
|
|
|
|
|
|
GEM, [ GEM, "\e[32m*\e[0m", ITEM ], |
187
|
|
|
|
|
|
|
LADDER, [ LADDER, "\e[37m=\e[0m", LADDER ], |
188
|
|
|
|
|
|
|
STAIR, [ FLOOR, "\e[37m%\e[0m", STAIR ], |
189
|
|
|
|
|
|
|
STATUE, [ FLOOR, "\e[1;33m&\e[0m", STATUE ], |
190
|
|
|
|
|
|
|
WALL, [ WALL, "\e[35m#\e[0m", WALL ], |
191
|
|
|
|
|
|
|
); |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
our %Descriptions = ( |
194
|
|
|
|
|
|
|
BOMB, 'Bomb. Avoid.', |
195
|
|
|
|
|
|
|
FLOOR, 'Empty cell.', |
196
|
|
|
|
|
|
|
GEM, 'A gem. Get these.', |
197
|
|
|
|
|
|
|
HERO, 'The much suffering hero.', |
198
|
|
|
|
|
|
|
LADDER, 'A ladder.', |
199
|
|
|
|
|
|
|
MONST, $Monst_Name . '. Wants to kill you.', |
200
|
|
|
|
|
|
|
STAIR, 'A way out of this mess.', |
201
|
|
|
|
|
|
|
STATUE, 'Empty cell with decorative statue.', |
202
|
|
|
|
|
|
|
WALL, 'A wall.', |
203
|
|
|
|
|
|
|
); |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
$Animates[HERO]->@[ WHAT, DISP, TYPE, STASH, UPDATE ] = |
206
|
|
|
|
|
|
|
(HERO, "\e[1;33m\@\e[0m", ANI, [ START_GEMS, START_BOMBS ], \&update_hero); |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
our %Interact_With = ( |
209
|
|
|
|
|
|
|
HERO, # the target of the mover |
210
|
|
|
|
|
|
|
sub { |
211
|
|
|
|
|
|
|
my ($mover, $target) = @_; |
212
|
|
|
|
|
|
|
game_over_monster() if $mover->[WHAT] == MONST; |
213
|
|
|
|
|
|
|
game_over_bomb() if $mover->[WHAT] == BOMB; |
214
|
|
|
|
|
|
|
grab_gem($target, $mover); |
215
|
|
|
|
|
|
|
}, |
216
|
|
|
|
|
|
|
MONST, |
217
|
|
|
|
|
|
|
sub { |
218
|
|
|
|
|
|
|
my ($mover, $target) = @_; |
219
|
|
|
|
|
|
|
game_over_monster() if $mover->[WHAT] == HERO; |
220
|
|
|
|
|
|
|
if ($mover->[WHAT] == BOMB) { |
221
|
|
|
|
|
|
|
my @cells = map { kill_animate($_, 1); $_->[LMC][WHERE] } $mover, $target; |
222
|
|
|
|
|
|
|
redraw_ref(\@cells); |
223
|
|
|
|
|
|
|
explode($target); |
224
|
|
|
|
|
|
|
} elsif ($mover->[WHAT] == GEM) { |
225
|
|
|
|
|
|
|
grab_gem($target, $mover); |
226
|
|
|
|
|
|
|
} |
227
|
|
|
|
|
|
|
}, |
228
|
|
|
|
|
|
|
BOMB, |
229
|
|
|
|
|
|
|
sub { |
230
|
|
|
|
|
|
|
my ($mover, $target) = @_; |
231
|
|
|
|
|
|
|
game_over_bomb() if $mover->[WHAT] == HERO; |
232
|
|
|
|
|
|
|
if ($mover->[WHAT] == MONST) { |
233
|
|
|
|
|
|
|
my @cells = map { kill_animate($_, 1); $_->[LMC][WHERE] } $mover, $target; |
234
|
|
|
|
|
|
|
redraw_ref(\@cells); |
235
|
|
|
|
|
|
|
explode($target); |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
}, |
238
|
|
|
|
|
|
|
GEM, |
239
|
|
|
|
|
|
|
sub { |
240
|
|
|
|
|
|
|
my ($mover, $target) = @_; |
241
|
|
|
|
|
|
|
if ($mover->[TYPE] == ANI) { |
242
|
|
|
|
|
|
|
relocate($mover, $target->[LMC][WHERE]); |
243
|
|
|
|
|
|
|
grab_gem($mover, $target); |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
}, |
246
|
|
|
|
|
|
|
); |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
our %Key_Commands = ( |
249
|
|
|
|
|
|
|
'h' => move_player(-1, +0), # left |
250
|
|
|
|
|
|
|
'j' => move_player(+0, +1), # down |
251
|
|
|
|
|
|
|
'k' => move_player(+0, -1), # up |
252
|
|
|
|
|
|
|
'l' => move_player(+1, +0), # right |
253
|
|
|
|
|
|
|
'.' => \&move_nop, # rest |
254
|
|
|
|
|
|
|
' ' => \&move_nop, # also rest |
255
|
|
|
|
|
|
|
'v' => sub { post_message('Version ' . $VERSION); return MOVE_FAILED }, |
256
|
|
|
|
|
|
|
'x' => \&move_examine, |
257
|
|
|
|
|
|
|
'<' => sub { |
258
|
|
|
|
|
|
|
post_message($Scientist . q{'s magic wonder left boot, activate!}); |
259
|
|
|
|
|
|
|
rotate_left(); |
260
|
|
|
|
|
|
|
print draw_level(); |
261
|
|
|
|
|
|
|
sleep($Rotate_Delay); |
262
|
|
|
|
|
|
|
return MOVE_OK; |
263
|
|
|
|
|
|
|
}, |
264
|
|
|
|
|
|
|
'>' => sub { |
265
|
|
|
|
|
|
|
post_message($Scientist . q{'s magic wonder right boot, activate!}); |
266
|
|
|
|
|
|
|
rotate_right(); |
267
|
|
|
|
|
|
|
print draw_level(); |
268
|
|
|
|
|
|
|
sleep($Rotate_Delay); |
269
|
|
|
|
|
|
|
return MOVE_OK; |
270
|
|
|
|
|
|
|
}, |
271
|
|
|
|
|
|
|
'?' => sub { |
272
|
|
|
|
|
|
|
post_help(); |
273
|
|
|
|
|
|
|
return MOVE_FAILED; |
274
|
|
|
|
|
|
|
}, |
275
|
|
|
|
|
|
|
# for debugging, probably shouldn't be included as it shows exactly |
276
|
|
|
|
|
|
|
# where the monsters are trying to move to which may or may not be |
277
|
|
|
|
|
|
|
# where the player is |
278
|
|
|
|
|
|
|
'T' => sub { |
279
|
|
|
|
|
|
|
local $" = ','; |
280
|
|
|
|
|
|
|
post_message("T $Hero->@* R $Rotation"); |
281
|
|
|
|
|
|
|
return MOVE_FAILED; |
282
|
|
|
|
|
|
|
}, |
283
|
|
|
|
|
|
|
'@' => sub { |
284
|
|
|
|
|
|
|
local $" = ','; |
285
|
|
|
|
|
|
|
post_message("\@ $Animates[HERO][LMC][WHERE]->@* R $Rotation"); |
286
|
|
|
|
|
|
|
return MOVE_FAILED; |
287
|
|
|
|
|
|
|
}, |
288
|
|
|
|
|
|
|
'$' => sub { |
289
|
|
|
|
|
|
|
post_message('You have ' |
290
|
|
|
|
|
|
|
. $Animates[HERO][STASH][BOMB_STASH] |
291
|
|
|
|
|
|
|
. ' bombs and ' |
292
|
|
|
|
|
|
|
. $Animates[HERO][STASH][GEM_STASH] |
293
|
|
|
|
|
|
|
. ' gems.'); |
294
|
|
|
|
|
|
|
return MOVE_FAILED; |
295
|
|
|
|
|
|
|
}, |
296
|
|
|
|
|
|
|
# by way of history '%' is what rogue (version 3.6) uses for stairs, |
297
|
|
|
|
|
|
|
# except the '>' (or very rarely '<') keys are used to interact with |
298
|
|
|
|
|
|
|
# that symbol |
299
|
|
|
|
|
|
|
'%' => sub { |
300
|
|
|
|
|
|
|
if ($Animates[HERO][LMC][GROUND][TYPE] == STAIR) { |
301
|
|
|
|
|
|
|
load_level(); |
302
|
|
|
|
|
|
|
print clear_screen(), draw_level(); |
303
|
|
|
|
|
|
|
post_message('Level ' |
304
|
|
|
|
|
|
|
. $Level |
305
|
|
|
|
|
|
|
. ' (You have ' |
306
|
|
|
|
|
|
|
. $Animates[HERO][STASH][BOMB_STASH] |
307
|
|
|
|
|
|
|
. ' bombs and ' |
308
|
|
|
|
|
|
|
. $Animates[HERO][STASH][GEM_STASH] |
309
|
|
|
|
|
|
|
. ' gems.)'); |
310
|
|
|
|
|
|
|
return MOVE_NEWLVL; |
311
|
|
|
|
|
|
|
} else { |
312
|
|
|
|
|
|
|
post_message('There are no stairs here?'); |
313
|
|
|
|
|
|
|
return MOVE_FAILED; |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
}, |
316
|
|
|
|
|
|
|
'B' => sub { |
317
|
|
|
|
|
|
|
my $lmc = $Animates[HERO][LMC]; |
318
|
|
|
|
|
|
|
return MOVE_FAILED, 'You have no bombs (make them from gems).' |
319
|
|
|
|
|
|
|
if $Animates[HERO][STASH][BOMB_STASH] < 1; |
320
|
|
|
|
|
|
|
return MOVE_FAILED, 'There is already an item in this cell.' |
321
|
|
|
|
|
|
|
if defined $lmc->[ITEM]; |
322
|
|
|
|
|
|
|
$Animates[HERO][STASH][BOMB_STASH]--; |
323
|
|
|
|
|
|
|
make_item($lmc->[WHERE], BOMB, 0); |
324
|
|
|
|
|
|
|
return MOVE_OK; |
325
|
|
|
|
|
|
|
}, |
326
|
|
|
|
|
|
|
'M' => sub { |
327
|
|
|
|
|
|
|
return MOVE_FAILED, 'You need more gems.' |
328
|
|
|
|
|
|
|
if $Animates[HERO][STASH][GEM_STASH] < BOMB_COST; |
329
|
|
|
|
|
|
|
$Animates[HERO][STASH][GEM_STASH] -= BOMB_COST; |
330
|
|
|
|
|
|
|
post_message('You now have ' . ++$Animates[HERO][STASH][BOMB_STASH] . ' bombs'); |
331
|
|
|
|
|
|
|
return MOVE_OK; |
332
|
|
|
|
|
|
|
}, |
333
|
|
|
|
|
|
|
'q' => sub { game_over('Be seeing you...') }, |
334
|
|
|
|
|
|
|
"\003" => sub { # |
335
|
|
|
|
|
|
|
post_message('Enough with these silly interruptions!'); |
336
|
|
|
|
|
|
|
return MOVE_FAILED; |
337
|
|
|
|
|
|
|
}, |
338
|
|
|
|
|
|
|
"\014" => sub { # |
339
|
|
|
|
|
|
|
redraw_level(); |
340
|
|
|
|
|
|
|
return MOVE_FAILED; |
341
|
|
|
|
|
|
|
}, |
342
|
|
|
|
|
|
|
"\032" => sub { # |
343
|
|
|
|
|
|
|
post_message('You hear a strange noise in the background.'); |
344
|
|
|
|
|
|
|
return MOVE_FAILED; |
345
|
|
|
|
|
|
|
}, |
346
|
|
|
|
|
|
|
"\033" => sub { |
347
|
|
|
|
|
|
|
post_message('You cannot escape quite so easily.'); |
348
|
|
|
|
|
|
|
return MOVE_FAILED; |
349
|
|
|
|
|
|
|
}, |
350
|
|
|
|
|
|
|
); |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
sub apply_gravity { |
353
|
0
|
|
|
0
|
0
|
0
|
for my $ent (rev_nsort_by { $_->[LMC][WHERE][PROW] } @Animates) { |
|
0
|
|
|
0
|
|
0
|
|
354
|
0
|
0
|
|
|
|
0
|
next if $ent->[BLACK_SPOT]; |
355
|
0
|
|
|
|
|
0
|
my $here = $ent->[LMC][WHERE]; |
356
|
|
|
|
|
|
|
next |
357
|
0
|
0
|
0
|
|
|
0
|
if $here->[PROW] == ROWS - 1 |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
358
|
|
|
|
|
|
|
or ( $ent->[TYPE] == ANI |
359
|
|
|
|
|
|
|
and $LMap->[ $here->[PROW] ][ $here->[PCOL] ][GROUND][WHAT] == LADDER) |
360
|
|
|
|
|
|
|
or $LMap->[ $here->[PROW] + 1 ][ $here->[PCOL] ][GROUND][WHAT] == WALL; |
361
|
0
|
|
|
|
|
0
|
my $dest = [ $here->[PCOL], $here->[PROW] + 1 ]; |
362
|
0
|
0
|
|
|
|
0
|
relocate($ent, $dest) unless interact($ent, $dest); |
363
|
0
|
0
|
|
|
|
0
|
if ($ent->[WHAT] == HERO) { |
364
|
0
|
0
|
|
|
|
0
|
if ($ent->[LMC][GROUND][WHAT] == LADDER) { |
365
|
0
|
|
|
|
|
0
|
post_message('You fall, but grab onto a ladder.'); |
366
|
|
|
|
|
|
|
} else { |
367
|
0
|
|
|
|
|
0
|
post_message('You fall!'); |
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
} |
370
|
|
|
|
|
|
|
} |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
sub bad_terminal { |
374
|
0
|
|
|
0
|
0
|
0
|
($TCols, $TRows) = (GetTerminalSize(*STDOUT))[ 0, 1 ]; |
375
|
0
|
|
0
|
|
|
0
|
return (not defined $TCols or $TCols < MSG_COLS_MAX or $TRows < MSG_MAX); |
376
|
|
|
|
|
|
|
} |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
sub bail_out { |
379
|
0
|
|
|
0
|
0
|
0
|
restore_term(); |
380
|
0
|
|
|
|
|
0
|
print "\n", at_col(0), clear_line; |
381
|
0
|
0
|
|
|
|
0
|
warn $_[0] if @_; |
382
|
0
|
|
|
|
|
0
|
game_over("Suddenly, the platforms collapse about you."); |
383
|
|
|
|
|
|
|
} |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
sub between { |
386
|
0
|
|
|
0
|
0
|
0
|
my ($min, $max, $value) = @_; |
387
|
0
|
0
|
|
|
|
0
|
if ($value < $min) { |
|
|
0
|
|
|
|
|
|
388
|
0
|
|
|
|
|
0
|
$value = $min; |
389
|
|
|
|
|
|
|
} elsif ($value > $max) { |
390
|
0
|
|
|
|
|
0
|
$value = $max; |
391
|
|
|
|
|
|
|
} |
392
|
0
|
|
|
|
|
0
|
return $value; |
393
|
|
|
|
|
|
|
} |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
sub draw_level { |
396
|
0
|
|
|
0
|
0
|
0
|
my $s = ''; |
397
|
0
|
|
|
|
|
0
|
for my $rownum (0 .. ROWS - 1) { |
398
|
0
|
|
|
|
|
0
|
$s .= at(MAP_DISP_OFF, MAP_DISP_OFF + $rownum); |
399
|
0
|
|
|
|
|
0
|
for my $lmc ($LMap->[$rownum]->@*) { |
400
|
0
|
0
|
|
|
|
0
|
if (defined $lmc->[ANI]) { |
|
|
0
|
|
|
|
|
|
401
|
0
|
|
|
|
|
0
|
$s .= $lmc->[ANI][DISP]; |
402
|
|
|
|
|
|
|
} elsif (defined $lmc->[ITEM]) { |
403
|
0
|
|
|
|
|
0
|
$s .= $lmc->[ITEM][DISP]; |
404
|
|
|
|
|
|
|
} else { |
405
|
0
|
|
|
|
|
0
|
$s .= $lmc->[GROUND][DISP]; |
406
|
|
|
|
|
|
|
} |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
} |
409
|
0
|
|
|
|
|
0
|
$s .= at(1, ROWS + 1) . $Things{ WALL, }[DISP] x COLS; |
410
|
0
|
|
|
|
|
0
|
return $s; |
411
|
|
|
|
|
|
|
} |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
sub explode { |
414
|
0
|
|
|
0
|
0
|
0
|
my ($something) = @_; |
415
|
0
|
|
|
|
|
0
|
my $lmc = $something->[LMC]; |
416
|
0
|
|
|
|
|
0
|
my $pos = $lmc->[WHERE]; |
417
|
0
|
|
|
|
|
0
|
my @colors = ("\e[31m", "\e[33m"); |
418
|
0
|
|
|
|
|
0
|
for (1 .. 7) { |
419
|
0
|
|
|
|
|
0
|
print at(map { MAP_DISP_OFF + $_ } $pos->@*), $colors[ rand @colors ], '*', |
|
0
|
|
|
|
|
0
|
|
420
|
|
|
|
|
|
|
term_norm; |
421
|
0
|
|
|
|
|
0
|
sleep($Redraw_Delay); |
422
|
|
|
|
|
|
|
} |
423
|
0
|
|
|
|
|
0
|
post_message('ka-boom!'); |
424
|
|
|
|
|
|
|
# HEROIC DESTRUCTION |
425
|
0
|
0
|
|
|
|
0
|
$lmc->[GROUND] = $Things{ FLOOR, } if $lmc->[GROUND][TYPE] == STATUE; |
426
|
0
|
|
|
|
|
0
|
push @RedrawA, $pos; |
427
|
|
|
|
|
|
|
} |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
# cribbed from some A* article on https://www.redblobgames.com/ |
430
|
|
|
|
|
|
|
sub find_hero { |
431
|
0
|
|
|
0
|
0
|
0
|
my ($ent, $mcol, $mrow) = @_; |
432
|
|
|
|
|
|
|
|
433
|
0
|
|
|
|
|
0
|
my $start = $mcol . ',' . $mrow; |
434
|
0
|
|
|
|
|
0
|
my $pcol = $Hero->[PCOL]; |
435
|
0
|
|
|
|
|
0
|
my $prow = $Hero->[PROW]; |
436
|
0
|
|
|
|
|
0
|
my $end = $pcol . ',' . $prow; |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
# already waiting where the player is going to fall to |
439
|
0
|
0
|
|
|
|
0
|
return if $start eq $end; |
440
|
|
|
|
|
|
|
|
441
|
0
|
|
|
|
|
0
|
my %costs = ($start => 0); |
442
|
0
|
|
|
|
|
0
|
my %seen = ($start => undef); |
443
|
0
|
|
|
|
|
0
|
my $q = List::PriorityQueue->new; |
444
|
0
|
|
|
|
|
0
|
$q->insert($start, 0); |
445
|
|
|
|
|
|
|
|
446
|
0
|
|
|
|
|
0
|
my $linked = 0; |
447
|
0
|
|
|
|
|
0
|
while (my $node = $q->pop) { |
448
|
0
|
0
|
|
|
|
0
|
if ($node eq $end) { |
449
|
0
|
|
|
|
|
0
|
$linked = 1; |
450
|
0
|
|
|
|
|
0
|
last; |
451
|
|
|
|
|
|
|
} |
452
|
0
|
|
|
|
|
0
|
for my $peer ($Graphs[$Rotation]{$node}->@*) { |
453
|
0
|
|
|
|
|
0
|
my $new = $peer->[GRAPH_NODE]; |
454
|
0
|
|
|
|
|
0
|
my $cost = $costs{$node} + $peer->[GRAPH_WEIGHT]; |
455
|
0
|
0
|
0
|
|
|
0
|
if (not exists $seen{$new} or $cost < $costs{$new}) { |
456
|
0
|
|
|
|
|
0
|
$costs{$new} = $cost; |
457
|
|
|
|
|
|
|
# perhaps they drove taxicabs in Manhattan in a former life? |
458
|
0
|
|
|
|
|
0
|
my $priority = |
459
|
|
|
|
|
|
|
$cost + |
460
|
|
|
|
|
|
|
abs($pcol - $peer->[GRAPH_POINT][PCOL]) + |
461
|
|
|
|
|
|
|
abs($prow - $peer->[GRAPH_POINT][PROW]); |
462
|
0
|
|
|
|
|
0
|
$q->insert($new, $priority); |
463
|
0
|
|
|
|
|
0
|
$seen{$new} = $node; |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
} |
466
|
|
|
|
|
|
|
} |
467
|
0
|
0
|
|
|
|
0
|
return unless $linked; |
468
|
|
|
|
|
|
|
|
469
|
0
|
|
|
|
|
0
|
my @path; |
470
|
0
|
|
|
|
|
0
|
my $node = $end; |
471
|
0
|
|
|
|
|
0
|
while ($node ne $start) { |
472
|
0
|
|
|
|
|
0
|
unshift @path, $node; |
473
|
0
|
|
|
|
|
0
|
$node = $seen{$node}; |
474
|
|
|
|
|
|
|
} |
475
|
0
|
|
|
|
|
0
|
return [ split ',', $path[0] ]; |
476
|
|
|
|
|
|
|
} |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
sub game_loop { |
479
|
0
|
0
|
|
0
|
0
|
0
|
game_over('Terminal must be at least ' . MSG_COLS_MAX . 'x' . MSG_MAX) |
480
|
|
|
|
|
|
|
if bad_terminal(); |
481
|
0
|
|
|
|
|
0
|
($Level_Path, $Level, $Seed) = @_; |
482
|
0
|
|
|
|
|
0
|
$SIG{$_} = \&bail_out for qw(INT HUP TERM PIPE QUIT USR1 USR2 __DIE__); |
483
|
0
|
|
|
|
|
0
|
STDOUT->autoflush(1); |
484
|
0
|
|
|
|
|
0
|
load_level(); |
485
|
0
|
|
|
|
|
0
|
ReadMode 'raw'; |
486
|
0
|
|
|
|
|
0
|
print term_norm, alt_screen, hide_cursor, hide_pointer, clear_screen, |
487
|
|
|
|
|
|
|
draw_level; |
488
|
0
|
|
|
|
|
0
|
post_message('The Platforms of Peril'); |
489
|
0
|
|
|
|
|
0
|
post_message(''); |
490
|
0
|
|
|
|
|
0
|
post_message('Your constant foes, the ' . properly_plural($Monst_Name)); |
491
|
0
|
|
|
|
|
0
|
post_message('seek to destroy your way of life!'); |
492
|
0
|
|
|
|
|
0
|
post_help(); |
493
|
0
|
|
|
|
|
0
|
post_message(''); |
494
|
0
|
|
|
|
|
0
|
post_message('Seed ' . $Seed . ' of version ' . $VERSION); |
495
|
0
|
|
|
|
|
0
|
$SIG{CONT} = \&redraw_level; |
496
|
|
|
|
|
|
|
$SIG{WINCH} = sub { |
497
|
0
|
0
|
|
0
|
|
0
|
post_message('The terminal is too small!') if bad_terminal(); |
498
|
0
|
|
|
|
|
0
|
redraw_level(); |
499
|
0
|
|
|
|
|
0
|
}; |
500
|
|
|
|
|
|
|
|
501
|
0
|
|
|
|
|
0
|
while (1) { |
502
|
0
|
|
|
|
|
0
|
apply_gravity(); |
503
|
0
|
|
|
|
|
0
|
@Animates = grep { !$_->[BLACK_SPOT] } @Animates; |
|
0
|
|
|
|
|
0
|
|
504
|
0
|
0
|
|
|
|
0
|
redraw_movers() if @RedrawA; |
505
|
0
|
0
|
|
|
|
0
|
next if $Animates[HERO][UPDATE]->() == MOVE_NEWLVL; |
506
|
0
|
|
|
|
|
0
|
track_hero(); |
507
|
0
|
|
|
|
|
0
|
for my $ent (@Animates[ 1 .. $#Animates ]) { |
508
|
0
|
0
|
0
|
|
|
0
|
$ent->[UPDATE]->($ent) if !$ent->[BLACK_SPOT] and defined $ent->[UPDATE]; |
509
|
|
|
|
|
|
|
} |
510
|
0
|
|
|
|
|
0
|
@Animates = grep { !$_->[BLACK_SPOT] } @Animates; |
|
0
|
|
|
|
|
0
|
|
511
|
0
|
|
|
|
|
0
|
redraw_movers(); |
512
|
|
|
|
|
|
|
} |
513
|
|
|
|
|
|
|
} |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
sub game_over { |
516
|
0
|
|
|
0
|
0
|
0
|
my ($msg, $code) = @_; |
517
|
0
|
|
0
|
|
|
0
|
$code //= 1; |
518
|
0
|
|
|
|
|
0
|
restore_term(); |
519
|
0
|
|
|
|
|
0
|
print clear_right, $msg, ' (', $Animates[HERO][STASH][GEM_STASH], |
520
|
|
|
|
|
|
|
" gems)\n", clear_right; |
521
|
0
|
|
|
|
|
0
|
exit $code; |
522
|
|
|
|
|
|
|
} |
523
|
|
|
|
|
|
|
|
524
|
0
|
|
|
0
|
0
|
0
|
sub game_over_bomb { game_over('You gone done blowed yourself up.') } |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
sub game_over_monster { |
527
|
0
|
|
|
0
|
0
|
0
|
game_over('The ' . $Monst_Name . ' polished you off.'); |
528
|
|
|
|
|
|
|
} |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
sub grab_gem { |
531
|
0
|
|
|
0
|
0
|
0
|
my ($ent, $gem) = @_; |
532
|
0
|
|
|
|
|
0
|
$ent->[STASH][GEM_STASH] += $gem->[STASH]; |
533
|
0
|
|
|
|
|
0
|
kill_animate($gem); |
534
|
0
|
0
|
|
|
|
0
|
if ($ent->[WHAT] == MONST) { |
535
|
0
|
|
|
|
|
0
|
post_message('The ' . $Monst_Name . ' grabs a gem.'); |
536
|
|
|
|
|
|
|
} else { |
537
|
0
|
|
|
|
|
0
|
post_message('You now have ' . $ent->[STASH][GEM_STASH] . ' gems.'); |
538
|
|
|
|
|
|
|
} |
539
|
|
|
|
|
|
|
} |
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
sub graph_bilink { |
542
|
0
|
|
|
0
|
0
|
0
|
my ($g, $c1, $r1, $c2, $r2) = @_; |
543
|
0
|
|
|
|
|
0
|
my $from = $c1 . ',' . $r1; |
544
|
0
|
|
|
|
|
0
|
my $to = $c2 . ',' . $r2; |
545
|
0
|
|
|
|
|
0
|
push $g->{$from}->@*, [ $to, 1, [ $c2, $r2 ] ]; |
546
|
0
|
|
|
|
|
0
|
push $g->{$to}->@*, [ $from, 1, [ $c1, $r1 ] ]; |
547
|
|
|
|
|
|
|
} |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
sub graph_setup { |
550
|
0
|
|
|
0
|
0
|
0
|
my $g = {}; |
551
|
0
|
|
|
|
|
0
|
for my $r (0 .. ROWS - 2) { |
552
|
0
|
|
|
|
|
0
|
for my $c (0 .. COLS - 1) { |
553
|
0
|
0
|
|
|
|
0
|
next if $LMap->[$r][$c][GROUND][WHAT] == WALL; |
554
|
|
|
|
|
|
|
# allow left/right, if ladder or wall below permits it |
555
|
0
|
0
|
0
|
|
|
0
|
if ($c != COLS - 1 |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
556
|
|
|
|
|
|
|
and ( $LMap->[$r][$c][GROUND][WHAT] == LADDER |
557
|
|
|
|
|
|
|
or $LMap->[ $r + 1 ][$c][GROUND][WHAT] == WALL) |
558
|
|
|
|
|
|
|
and ( |
559
|
|
|
|
|
|
|
$LMap->[$r][ $c + 1 ][GROUND][WHAT] == LADDER |
560
|
|
|
|
|
|
|
or ( $LMap->[$r][ $c + 1 ][GROUND][WHAT] != WALL |
561
|
|
|
|
|
|
|
and $LMap->[ $r + 1 ][ $c + 1 ][GROUND][WHAT] == WALL) |
562
|
|
|
|
|
|
|
) |
563
|
|
|
|
|
|
|
) { |
564
|
0
|
|
|
|
|
0
|
graph_bilink($g, $c, $r, $c + 1, $r); |
565
|
|
|
|
|
|
|
} |
566
|
0
|
0
|
|
|
|
0
|
if ($r > 0) { |
567
|
|
|
|
|
|
|
# allow motion up/down ladders |
568
|
0
|
0
|
0
|
|
|
0
|
if ( $LMap->[$r][$c][GROUND][WHAT] == LADDER |
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
569
|
|
|
|
|
|
|
and $LMap->[ $r - 1 ][$c][GROUND][WHAT] == LADDER) { |
570
|
0
|
|
|
|
|
0
|
graph_bilink($g, $c, $r, $c, $r - 1); |
571
|
|
|
|
|
|
|
} elsif ( |
572
|
|
|
|
|
|
|
$LMap->[$r][$c][GROUND][WHAT] == LADDER |
573
|
|
|
|
|
|
|
or ( $LMap->[$r][$c][GROUND][WHAT] == FLOOR |
574
|
|
|
|
|
|
|
and $LMap->[ $r + 1 ][$c][GROUND][WHAT] == WALL) |
575
|
|
|
|
|
|
|
) { |
576
|
|
|
|
|
|
|
# can we fall into this cell from above? |
577
|
0
|
|
|
|
|
0
|
graph_shaft($g, $c, $r); |
578
|
|
|
|
|
|
|
} |
579
|
|
|
|
|
|
|
} |
580
|
|
|
|
|
|
|
} |
581
|
|
|
|
|
|
|
} |
582
|
0
|
|
|
|
|
0
|
for my $c (0 .. COLS - 1) { |
583
|
0
|
0
|
|
|
|
0
|
next if $LMap->[ ROWS - 1 ][$c][GROUND][WHAT] == WALL; |
584
|
0
|
0
|
0
|
|
|
0
|
if ( $LMap->[ ROWS - 1 ][$c][GROUND][WHAT] == LADDER |
585
|
|
|
|
|
|
|
and $LMap->[ ROWS - 2 ][$c][GROUND][WHAT] == LADDER) { |
586
|
0
|
|
|
|
|
0
|
graph_bilink($g, $c, ROWS - 1, $c, ROWS - 2); |
587
|
|
|
|
|
|
|
} else { |
588
|
0
|
|
|
|
|
0
|
graph_shaft($g, $c, ROWS - 1); |
589
|
|
|
|
|
|
|
} |
590
|
0
|
0
|
|
|
|
0
|
if ($c != COLS - 1) { |
591
|
0
|
|
|
|
|
0
|
graph_bilink($g, $c, ROWS - 1, $c + 1, ROWS - 1); |
592
|
|
|
|
|
|
|
} |
593
|
|
|
|
|
|
|
} |
594
|
0
|
|
|
|
|
0
|
return $g; |
595
|
|
|
|
|
|
|
} |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
sub graph_shaft { |
598
|
0
|
|
|
0
|
0
|
0
|
my ($g, $c, $r) = @_; |
599
|
0
|
|
|
|
|
0
|
for my $x (reverse 0 .. $r - 1) { |
600
|
0
|
0
|
|
|
|
0
|
last if $LMap->[$x][$c][GROUND][WHAT] == WALL; |
601
|
0
|
|
|
|
|
0
|
my $weight = $r - $x; |
602
|
0
|
0
|
|
|
|
0
|
if ($LMap->[$x][$c][GROUND][WHAT] == LADDER) { |
603
|
0
|
0
|
|
|
|
0
|
if ($weight == 1) { |
604
|
0
|
|
|
|
|
0
|
graph_udlink($g, $c, $x, $c, $r, 1, [ $c, $x ]); |
605
|
|
|
|
|
|
|
} else { |
606
|
0
|
|
|
|
|
0
|
graph_udlink($g, $c, $x, $c, $x + 1, 1, [ $c, $x ]); |
607
|
0
|
|
|
|
|
0
|
graph_udlink($g, $c, $x + 1, $c, $r, $weight - 2, [ $c, $r ]); |
608
|
|
|
|
|
|
|
} |
609
|
0
|
|
|
|
|
0
|
last; |
610
|
|
|
|
|
|
|
} |
611
|
|
|
|
|
|
|
# can fall into this shaft from the left or right? |
612
|
0
|
0
|
0
|
|
|
0
|
if ($c != 0 |
|
|
|
0
|
|
|
|
|
613
|
|
|
|
|
|
|
and ( |
614
|
|
|
|
|
|
|
$LMap->[$x][ $c - 1 ][GROUND][WHAT] == LADDER |
615
|
|
|
|
|
|
|
or ( $LMap->[$x][ $c - 1 ][GROUND][WHAT] == FLOOR |
616
|
|
|
|
|
|
|
and $LMap->[ $x + 1 ][ $c - 1 ][GROUND][WHAT] == WALL) |
617
|
|
|
|
|
|
|
) |
618
|
|
|
|
|
|
|
) { |
619
|
0
|
|
|
|
|
0
|
graph_udlink($g, $c - 1, $x, $c, $x, 1, [ $c, $x ]); |
620
|
0
|
|
|
|
|
0
|
graph_udlink($g, $c, $x, $c, $r, $weight - 1, [ $c, $r ]); |
621
|
|
|
|
|
|
|
} |
622
|
0
|
0
|
0
|
|
|
0
|
if ($c != COLS - 1 |
|
|
|
0
|
|
|
|
|
623
|
|
|
|
|
|
|
and ( |
624
|
|
|
|
|
|
|
$LMap->[$x][ $c + 1 ][GROUND][WHAT] == LADDER |
625
|
|
|
|
|
|
|
or ( $LMap->[$x][ $c + 1 ][GROUND][WHAT] == FLOOR |
626
|
|
|
|
|
|
|
and $LMap->[ $x + 1 ][ $c + 1 ][GROUND][WHAT] == WALL) |
627
|
|
|
|
|
|
|
) |
628
|
|
|
|
|
|
|
) { |
629
|
0
|
|
|
|
|
0
|
graph_udlink($g, $c + 1, $x, $c, $x, $weight, [ $c, $x ]); |
630
|
0
|
|
|
|
|
0
|
graph_udlink($g, $c, $x, $c, $r, $weight - 1, [ $c, $r ]); |
631
|
|
|
|
|
|
|
} |
632
|
|
|
|
|
|
|
} |
633
|
|
|
|
|
|
|
} |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
sub graph_udlink { |
636
|
0
|
|
|
0
|
0
|
0
|
my ($g, $c1, $r1, $c2, $r2, $weight, $point) = @_; |
637
|
0
|
|
|
|
|
0
|
my $from = $c1 . ',' . $r1; |
638
|
0
|
|
|
|
|
0
|
my $to = $c2 . ',' . $r2; |
639
|
0
|
|
|
|
|
0
|
push $g->{$from}->@*, [ $to, $weight, $point ]; |
640
|
|
|
|
|
|
|
} |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
sub interact { |
643
|
0
|
|
|
0
|
0
|
0
|
my ($mover, $dest) = @_; |
644
|
0
|
|
|
|
|
0
|
for my $i (ANI, ITEM) { |
645
|
0
|
|
|
|
|
0
|
my $target = $LMap->[ $dest->[PROW] ][ $dest->[PCOL] ][$i]; |
646
|
0
|
0
|
|
|
|
0
|
if (defined $target) { |
647
|
|
|
|
|
|
|
# this code is assumed to take care of everything and be the |
648
|
|
|
|
|
|
|
# final say on the interaction |
649
|
0
|
|
|
|
|
0
|
$Interact_With{ $target->[WHAT] }->($mover, $target); |
650
|
0
|
|
|
|
|
0
|
return 1; |
651
|
|
|
|
|
|
|
} |
652
|
|
|
|
|
|
|
} |
653
|
0
|
|
|
|
|
0
|
return 0; |
654
|
|
|
|
|
|
|
} |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
sub kill_animate { |
657
|
0
|
|
|
0
|
0
|
0
|
my ($ent, $no_draw) = @_; |
658
|
0
|
0
|
|
|
|
0
|
push @RedrawA, $ent->[LMC][WHERE] unless defined $no_draw; |
659
|
0
|
|
|
|
|
0
|
$ent->[BLACK_SPOT] = 1; |
660
|
|
|
|
|
|
|
# NOTE this only works for TYPE of ANI or ITEM, may need to rethink |
661
|
|
|
|
|
|
|
# how STATUE and STAIRS are handled if there are GROUND checks on |
662
|
|
|
|
|
|
|
# TYPE as those abuse the TYPE field for other things (see %Things) |
663
|
0
|
|
|
|
|
0
|
undef $ent->[LMC][ $ent->[TYPE] ]; |
664
|
|
|
|
|
|
|
} |
665
|
|
|
|
|
|
|
|
666
|
|
|
|
|
|
|
sub load_level { |
667
|
0
|
|
|
0
|
0
|
0
|
my $file = catfile($Level_Path, 'level' . $Level++); |
668
|
0
|
0
|
|
|
|
0
|
game_over('You have completed all the levels.', 0) unless -e $file; |
669
|
|
|
|
|
|
|
|
670
|
0
|
0
|
|
|
|
0
|
open(my $fh, '<', $file) or game_over("Failed to open '$file': $!"); |
671
|
|
|
|
|
|
|
|
672
|
0
|
|
|
|
|
0
|
splice @Animates, 1; |
673
|
0
|
|
|
|
|
0
|
undef $Animates[HERO][LMC]; |
674
|
0
|
|
|
|
|
0
|
$LMap = []; |
675
|
|
|
|
|
|
|
|
676
|
0
|
|
|
|
|
0
|
my $rownum = 0; |
677
|
0
|
|
|
|
|
0
|
while (my $line = readline $fh) { |
678
|
0
|
|
|
|
|
0
|
chomp $line; |
679
|
0
|
0
|
|
|
|
0
|
game_over("Wrong number of columns at $file:$.") if length $line != COLS; |
680
|
0
|
|
|
|
|
0
|
my $colnum = 0; |
681
|
0
|
|
|
|
|
0
|
for my $v (split //, $line) { |
682
|
0
|
|
0
|
|
|
0
|
my $c = $CharMap{$v} // game_over("Unknown character $v at $file:$."); |
683
|
0
|
|
|
|
|
0
|
my $point = [ $colnum++, $rownum ]; # PCOL, PROW (x, y) |
684
|
0
|
0
|
|
|
|
0
|
if (exists $Things{$c}) { |
685
|
0
|
0
|
|
|
|
0
|
if ($c eq BOMB) { |
|
|
0
|
|
|
|
|
|
686
|
0
|
|
|
|
|
0
|
push $LMap->[$rownum]->@*, [ $point, $Things{ FLOOR, } ]; |
687
|
0
|
|
|
|
|
0
|
make_item($point, BOMB, 0); |
688
|
|
|
|
|
|
|
} elsif ($c eq GEM) { |
689
|
0
|
|
|
|
|
0
|
push $LMap->[$rownum]->@*, [ $point, $Things{ FLOOR, } ]; |
690
|
0
|
|
|
|
|
0
|
make_item($point, GEM, GEM_VALUE); |
691
|
|
|
|
|
|
|
} else { |
692
|
0
|
|
|
|
|
0
|
push $LMap->[$rownum]->@*, [ $point, $Things{$c} ]; |
693
|
|
|
|
|
|
|
} |
694
|
|
|
|
|
|
|
} else { |
695
|
0
|
0
|
|
|
|
0
|
if ($c eq HERO) { |
|
|
0
|
|
|
|
|
|
696
|
0
|
0
|
|
|
|
0
|
game_over("Player placed twice in $file") |
697
|
|
|
|
|
|
|
if defined $Animates[HERO][LMC]; |
698
|
|
|
|
|
|
|
push $LMap->[$rownum]->@*, |
699
|
0
|
|
|
|
|
0
|
[ $point, $Things{ FLOOR, }, undef, $Animates[HERO] ]; |
700
|
0
|
|
|
|
|
0
|
$Animates[HERO][LMC] = $LMap->[$rownum][-1]; |
701
|
0
|
|
|
|
|
0
|
$Hero = $point; |
702
|
|
|
|
|
|
|
} elsif ($c eq MONST) { |
703
|
0
|
|
|
|
|
0
|
push $LMap->[$rownum]->@*, [ $point, $Things{ FLOOR, } ]; |
704
|
0
|
|
|
|
|
0
|
make_monster($point); |
705
|
|
|
|
|
|
|
} else { |
706
|
0
|
|
|
|
|
0
|
game_over("Unknown object '$v' at $file:$."); |
707
|
|
|
|
|
|
|
} |
708
|
|
|
|
|
|
|
} |
709
|
|
|
|
|
|
|
} |
710
|
0
|
0
|
|
|
|
0
|
last if ++$rownum == ROWS; |
711
|
|
|
|
|
|
|
} |
712
|
0
|
0
|
|
|
|
0
|
game_over("Too few rows in $file") if $rownum < ROWS; |
713
|
0
|
0
|
|
|
|
0
|
game_over("No player in $file") unless defined $Animates[HERO][LMC]; |
714
|
|
|
|
|
|
|
|
715
|
0
|
|
|
|
|
0
|
$Rotation = 0; |
716
|
0
|
|
|
|
|
0
|
for my $rot (1 .. 4) { |
717
|
0
|
|
|
|
|
0
|
$Graphs[$Rotation] = graph_setup(); |
718
|
0
|
|
|
|
|
0
|
rotate_left(); |
719
|
|
|
|
|
|
|
} |
720
|
|
|
|
|
|
|
} |
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
sub make_item { |
723
|
0
|
|
|
0
|
0
|
0
|
my ($point, $thingy, $stash, $update) = @_; |
724
|
0
|
|
|
|
|
0
|
my $item; |
725
|
|
|
|
|
|
|
$item->@[ WHAT, DISP, TYPE, STASH, UPDATE, LMC ] = ( |
726
|
0
|
|
|
|
|
0
|
$Things{$thingy}->@*, |
727
|
|
|
|
|
|
|
$stash, $update, $LMap->[ $point->[PROW] ][ $point->[PCOL] ] |
728
|
|
|
|
|
|
|
); |
729
|
0
|
|
|
|
|
0
|
push @Animates, $item; |
730
|
0
|
|
|
|
|
0
|
$LMap->[ $point->[PROW] ][ $point->[PCOL] ][ITEM] = $item; |
731
|
|
|
|
|
|
|
} |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
sub make_monster { |
734
|
0
|
|
|
0
|
0
|
0
|
my ($point) = @_; |
735
|
0
|
|
|
|
|
0
|
my $monst; |
736
|
0
|
|
|
|
|
0
|
my $ch = substr $Monst_Name, 0, 1; |
737
|
|
|
|
|
|
|
# STASH replicates that of the HERO for simpler GEM handling code |
738
|
|
|
|
|
|
|
# though the BOMB_STASH is instead used for GEM_ODDS |
739
|
0
|
|
|
|
|
0
|
$monst->@[ WHAT, DISP, TYPE, STASH, UPDATE, LMC ] = ( |
740
|
|
|
|
|
|
|
MONST, "\e[1;33m$ch\e[0m", ANI, [ 0, 0.0 ], |
741
|
|
|
|
|
|
|
\&update_monst, $LMap->[ $point->[PROW] ][ $point->[PCOL] ] |
742
|
|
|
|
|
|
|
); |
743
|
0
|
|
|
|
|
0
|
push @Animates, $monst; |
744
|
0
|
|
|
|
|
0
|
$LMap->[ $point->[PROW] ][ $point->[PCOL] ][ANI] = $monst; |
745
|
|
|
|
|
|
|
} |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
sub move_animate { |
748
|
0
|
|
|
0
|
0
|
0
|
my ($ent, $cols, $rows) = @_; |
749
|
0
|
|
|
|
|
0
|
my $lmc = $ent->[LMC]; |
750
|
|
|
|
|
|
|
|
751
|
0
|
|
|
|
|
0
|
my $from = $lmc->[WHERE][PCOL] . ',' . $lmc->[WHERE][PROW]; |
752
|
0
|
|
|
|
|
0
|
my $to = |
753
|
|
|
|
|
|
|
($lmc->[WHERE][PCOL] + $cols) . ',' . ($lmc->[WHERE][PROW] + $rows); |
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
return MOVE_FAILED |
756
|
0
|
0
|
|
0
|
|
0
|
unless first { $_->[GRAPH_NODE] eq $to } $Graphs[$Rotation]{$from}->@*; |
|
0
|
|
|
|
|
0
|
|
757
|
|
|
|
|
|
|
|
758
|
0
|
|
|
|
|
0
|
my $dest = [ $lmc->[WHERE][PCOL] + $cols, $lmc->[WHERE][PROW] + $rows ]; |
759
|
|
|
|
|
|
|
|
760
|
0
|
0
|
|
|
|
0
|
relocate($ent, $dest) unless interact($ent, $dest); |
761
|
0
|
|
|
|
|
0
|
return MOVE_OK; |
762
|
|
|
|
|
|
|
} |
763
|
|
|
|
|
|
|
|
764
|
|
|
|
|
|
|
# so the player can see if there is a ladder under something; this is an |
765
|
|
|
|
|
|
|
# important consideration on some levels |
766
|
|
|
|
|
|
|
sub move_examine { |
767
|
0
|
|
|
0
|
0
|
0
|
my $key; |
768
|
0
|
|
|
|
|
0
|
my $row = $Animates[HERO][LMC][WHERE][PROW]; |
769
|
0
|
|
|
|
|
0
|
my $col = $Animates[HERO][LMC][WHERE][PCOL]; |
770
|
0
|
|
|
|
|
0
|
print at(MSG_COL, MSG_ROW + $_), clear_right for 1 .. MSG_MAX; |
771
|
0
|
|
|
|
|
0
|
print at(MSG_COL, MSG_ROW), clear_right, |
772
|
|
|
|
|
|
|
'Move cursor to view a cell. Esc exits', show_cursor; |
773
|
0
|
|
|
|
|
0
|
while (1) { |
774
|
0
|
|
|
|
|
0
|
print at(MSG_COL, MSG_ROW + $_), clear_right for 3 .. 5; |
775
|
0
|
|
|
|
|
0
|
my $disp_row = 2; |
776
|
0
|
|
|
|
|
0
|
for my $i (ANI, ITEM) { |
777
|
0
|
|
|
|
|
0
|
my $x = $LMap->[$row][$col][$i]; |
778
|
0
|
0
|
|
|
|
0
|
if (defined $x) { |
779
|
|
|
|
|
|
|
print at(MSG_COL, MSG_ROW + $disp_row++), clear_right, $x->[DISP], |
780
|
0
|
|
|
|
|
0
|
' - ', $Descriptions{ $x->[WHAT] }; |
781
|
|
|
|
|
|
|
} |
782
|
|
|
|
|
|
|
} |
783
|
0
|
|
|
|
|
0
|
my $g = $LMap->[$row][$col][GROUND]; |
784
|
|
|
|
|
|
|
print at(MSG_COL, MSG_ROW + $disp_row), clear_right, $g->[DISP], |
785
|
0
|
|
|
|
|
0
|
' - ', $Descriptions{ $g->[TYPE] }, |
786
|
|
|
|
|
|
|
at(MAP_DISP_OFF + $col, MAP_DISP_OFF + $row); |
787
|
0
|
|
|
|
|
0
|
$key = ReadKey(0); |
788
|
0
|
0
|
|
|
|
0
|
last if $key eq "\033"; |
789
|
0
|
|
|
|
|
0
|
my $distance = 1; |
790
|
0
|
0
|
|
|
|
0
|
if (ord $key < 97) { # SHIFT moves faster |
791
|
0
|
|
|
|
|
0
|
$key = lc $key; |
792
|
0
|
|
|
|
|
0
|
$distance = 5; |
793
|
|
|
|
|
|
|
} |
794
|
0
|
|
0
|
|
|
0
|
my $dir = $Examine_Offsets{$key} // next; |
795
|
0
|
|
|
|
|
0
|
$row = between(0, ROWS - 1, $row + $dir->[PROW] * $distance); |
796
|
0
|
|
|
|
|
0
|
$col = between(0, COLS - 1, $col + $dir->[PCOL] * $distance); |
797
|
|
|
|
|
|
|
} |
798
|
0
|
|
|
|
|
0
|
print hide_cursor; |
799
|
0
|
|
|
|
|
0
|
show_messages(); |
800
|
0
|
|
|
|
|
0
|
return MOVE_FAILED; |
801
|
|
|
|
|
|
|
} |
802
|
|
|
|
|
|
|
|
803
|
0
|
|
|
0
|
0
|
0
|
sub move_nop { return MOVE_OK } |
804
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
sub move_player { |
806
|
4
|
|
|
4
|
0
|
8
|
my ($cols, $rows) = @_; |
807
|
0
|
|
|
0
|
|
|
sub { move_animate($Animates[HERO], $cols, $rows) } |
808
|
4
|
|
|
|
|
50
|
} |
809
|
|
|
|
|
|
|
|
810
|
|
|
|
|
|
|
sub post_help { |
811
|
0
|
|
|
0
|
0
|
|
my $ch = substr $Monst_Name, 0, 1; |
812
|
0
|
|
|
|
|
|
post_message(''); |
813
|
0
|
|
|
|
|
|
post_message( |
814
|
|
|
|
|
|
|
' ' . $Animates[HERO][DISP] . ' - You ' . $ch . ' - a ' . $Monst_Name); |
815
|
|
|
|
|
|
|
post_message( |
816
|
0
|
|
|
|
|
|
' ' . $Things{ STATUE, }[DISP] . ' - a large granite statue done in the'); |
817
|
0
|
|
|
|
|
|
post_message(' ' . $Style . ' style'); |
818
|
|
|
|
|
|
|
post_message(' ' |
819
|
|
|
|
|
|
|
. $Things{ BOMB, }[DISP] |
820
|
|
|
|
|
|
|
. ' - Bomb ' |
821
|
0
|
|
|
|
|
|
. $Things{ GEM, }[DISP] |
822
|
|
|
|
|
|
|
. ' - Gem (get these)'); |
823
|
0
|
|
|
|
|
|
post_message(''); |
824
|
0
|
|
|
|
|
|
post_message(' h j k l - move'); |
825
|
0
|
|
|
|
|
|
post_message(' < > - activate left or right boot'); |
826
|
0
|
|
|
|
|
|
post_message(' B - drop a Bomb'); |
827
|
0
|
|
|
|
|
|
post_message(' M - make a Bomb (consumes ' . BOMB_COST . ' Gems)'); |
828
|
|
|
|
|
|
|
post_message( |
829
|
0
|
|
|
|
|
|
' % - when on ' . $Things{ STAIR, }[DISP] . ' goes to the next level'); |
830
|
0
|
|
|
|
|
|
post_message(' . space - pass a turn (handy when falling)'); |
831
|
0
|
|
|
|
|
|
post_message(''); |
832
|
0
|
|
|
|
|
|
post_message(' q - quit the game (no save)'); |
833
|
0
|
|
|
|
|
|
post_message(' $ - display Bomb and Gem counts'); |
834
|
0
|
|
|
|
|
|
post_message(' ? - post these help messages'); |
835
|
0
|
|
|
|
|
|
post_message(''); |
836
|
0
|
|
|
|
|
|
post_message('You have ' |
837
|
|
|
|
|
|
|
. $Animates[HERO][STASH][BOMB_STASH] |
838
|
|
|
|
|
|
|
. ' bombs and ' |
839
|
|
|
|
|
|
|
. $Animates[HERO][STASH][GEM_STASH] |
840
|
|
|
|
|
|
|
. ' gems.'); |
841
|
|
|
|
|
|
|
} |
842
|
|
|
|
|
|
|
|
843
|
|
|
|
|
|
|
{ |
844
|
|
|
|
|
|
|
my @log; |
845
|
|
|
|
|
|
|
|
846
|
|
|
|
|
|
|
sub post_message { |
847
|
0
|
|
|
0
|
0
|
|
my ($msg) = @_; |
848
|
0
|
|
|
|
|
|
while (@log >= MSG_MAX) { shift @log } |
|
0
|
|
|
|
|
|
|
849
|
0
|
|
|
|
|
|
push @log, $msg; |
850
|
0
|
|
|
|
|
|
show_messages(); |
851
|
|
|
|
|
|
|
} |
852
|
0
|
|
|
0
|
0
|
|
sub clear_messages { @log = () } |
853
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
sub show_messages { |
855
|
0
|
|
|
0
|
0
|
|
for my $i (0 .. $#log) { |
856
|
0
|
|
|
|
|
|
print at(MSG_COL, MSG_ROW + $i), clear_right, $log[$i]; |
857
|
|
|
|
|
|
|
} |
858
|
|
|
|
|
|
|
} |
859
|
|
|
|
|
|
|
} |
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
# fsvo properly... damnit Jim I'm a sysadmin not a linguist |
862
|
|
|
|
|
|
|
sub properly_plural { |
863
|
0
|
|
|
0
|
0
|
|
my ($name) = @_; |
864
|
0
|
0
|
|
|
|
|
$name =~ s/oo/ee/ ? $name : $name . 's'; |
865
|
|
|
|
|
|
|
} |
866
|
|
|
|
|
|
|
|
867
|
0
|
|
|
0
|
0
|
|
sub redraw_level { print clear_screen, draw_level; show_messages() } |
|
0
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
|
869
|
|
|
|
|
|
|
sub redraw_movers { |
870
|
0
|
|
|
0
|
0
|
|
redraw_ref(\@RedrawA); |
871
|
0
|
|
|
|
|
|
sleep($Redraw_Delay); |
872
|
0
|
|
|
|
|
|
redraw_ref(\@RedrawB); |
873
|
0
|
|
|
|
|
|
@RedrawA = (); |
874
|
0
|
|
|
|
|
|
@RedrawB = (); |
875
|
|
|
|
|
|
|
} |
876
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
sub redraw_ref { |
878
|
0
|
|
|
0
|
0
|
|
CELL: for my $point ($_[0]->@*) { |
879
|
0
|
|
|
|
|
|
for my $i (ANI, ITEM) { |
880
|
0
|
|
|
|
|
|
my $ent = $LMap->[ $point->[PROW] ][ $point->[PCOL] ][$i]; |
881
|
0
|
0
|
0
|
|
|
|
if (defined $ent and !$ent->[BLACK_SPOT]) { |
882
|
0
|
|
|
|
|
|
print at(map { MAP_DISP_OFF + $_ } $point->@*), $ent->[DISP]; |
|
0
|
|
|
|
|
|
|
883
|
0
|
|
|
|
|
|
next CELL; |
884
|
|
|
|
|
|
|
} |
885
|
|
|
|
|
|
|
} |
886
|
0
|
|
|
|
|
|
print at(map { MAP_DISP_OFF + $_ } $point->@*), |
|
0
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
$LMap->[ $point->[PROW] ][ $point->[PCOL] ][GROUND][DISP]; |
888
|
|
|
|
|
|
|
} |
889
|
|
|
|
|
|
|
} |
890
|
|
|
|
|
|
|
|
891
|
|
|
|
|
|
|
sub relocate { |
892
|
0
|
|
|
0
|
0
|
|
my ($ent, $dest) = @_; |
893
|
0
|
|
|
|
|
|
my $src = $ent->[LMC][WHERE]; |
894
|
0
|
|
|
|
|
|
push @RedrawA, $src; |
895
|
0
|
|
|
|
|
|
push @RedrawB, $dest; |
896
|
0
|
|
|
|
|
|
my $lmc = $LMap->[ $dest->[PROW] ][ $dest->[PCOL] ]; |
897
|
0
|
|
|
|
|
|
$lmc->[ $ent->[TYPE] ] = $ent; |
898
|
0
|
|
|
|
|
|
undef $LMap->[ $src->[PROW] ][ $src->[PCOL] ][ $ent->[TYPE] ]; |
899
|
0
|
|
|
|
|
|
$ent->[LMC] = $lmc; |
900
|
|
|
|
|
|
|
} |
901
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
sub restore_term { |
903
|
0
|
|
|
0
|
0
|
|
ReadMode 'restore'; |
904
|
0
|
|
|
|
|
|
print term_norm, show_cursor, unalt_screen; |
905
|
|
|
|
|
|
|
} |
906
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
sub rotate_left { |
908
|
0
|
|
|
0
|
0
|
|
my $lm; |
909
|
0
|
|
|
|
|
|
for my $r (0 .. ROWS - 1) { |
910
|
0
|
|
|
|
|
|
for my $c (0 .. COLS - 1) { |
911
|
0
|
|
|
|
|
|
my $newr = COLS - 1 - $c; |
912
|
0
|
|
|
|
|
|
$lm->[$newr][$r] = $LMap->[$r][$c]; |
913
|
0
|
|
|
|
|
|
$lm->[$newr][$r][WHERE] = [ $r, $newr ]; |
914
|
|
|
|
|
|
|
} |
915
|
|
|
|
|
|
|
} |
916
|
0
|
|
|
|
|
|
$LMap = $lm; |
917
|
0
|
|
|
|
|
|
$Rotation = ($Rotation + 1) % 4; |
918
|
|
|
|
|
|
|
} |
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
sub rotate_right { |
921
|
0
|
|
|
0
|
0
|
|
my $lm; |
922
|
0
|
|
|
|
|
|
for my $r (0 .. ROWS - 1) { |
923
|
0
|
|
|
|
|
|
for my $c (0 .. COLS - 1) { |
924
|
0
|
|
|
|
|
|
my $newc = ROWS - 1 - $r; |
925
|
0
|
|
|
|
|
|
$lm->[$c][$newc] = $LMap->[$r][$c]; |
926
|
0
|
|
|
|
|
|
$lm->[$c][$newc][WHERE] = [ $newc, $c ]; |
927
|
|
|
|
|
|
|
} |
928
|
|
|
|
|
|
|
} |
929
|
0
|
|
|
|
|
|
$LMap = $lm; |
930
|
0
|
|
|
|
|
|
$Rotation = ($Rotation - 1) % 4; |
931
|
|
|
|
|
|
|
} |
932
|
|
|
|
|
|
|
|
933
|
|
|
|
|
|
|
sub track_hero { |
934
|
0
|
|
|
0
|
0
|
|
$Hero = $Animates[HERO][LMC][WHERE]; |
935
|
|
|
|
|
|
|
|
936
|
|
|
|
|
|
|
# route monsters to where the player will fall to as otherwise they |
937
|
|
|
|
|
|
|
# tend to freeze or head in the wrong direction |
938
|
0
|
|
|
|
|
|
my $row = $Hero->[PROW]; |
939
|
0
|
|
|
|
|
|
my $col = $Hero->[PCOL]; |
940
|
0
|
0
|
0
|
|
|
|
return if $row == ROWS - 1 or $LMap->[$row][$col][GROUND][WHAT] == LADDER; |
941
|
|
|
|
|
|
|
|
942
|
0
|
|
|
|
|
|
my $goal = $row; |
943
|
0
|
|
|
|
|
|
for my $r ($row + 1 .. ROWS - 1) { |
944
|
0
|
0
|
|
|
|
|
last if $LMap->[$r][$col][GROUND][WHAT] == WALL; |
945
|
0
|
0
|
0
|
|
|
|
if ($LMap->[$r][$col][GROUND][WHAT] == LADDER |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
946
|
|
|
|
|
|
|
or ( $r < ROWS - 2 |
947
|
|
|
|
|
|
|
and $LMap->[$r][$col][GROUND][WHAT] == FLOOR |
948
|
|
|
|
|
|
|
and $LMap->[ $r + 1 ][$col][GROUND][WHAT] == WALL) |
949
|
|
|
|
|
|
|
or ( $r == ROWS - 1 |
950
|
|
|
|
|
|
|
and $LMap->[$r][$col][GROUND][WHAT] == FLOOR) |
951
|
|
|
|
|
|
|
) { |
952
|
0
|
|
|
|
|
|
$goal = $r; |
953
|
0
|
|
|
|
|
|
last; |
954
|
|
|
|
|
|
|
} |
955
|
|
|
|
|
|
|
} |
956
|
0
|
|
|
|
|
|
$Hero = [ $col, $goal ]; |
957
|
|
|
|
|
|
|
} |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
sub update_hero { |
960
|
0
|
|
|
0
|
0
|
|
my ($key, $ret); |
961
|
0
|
|
|
|
|
|
tcflush(STDIN_FILENO, TCIFLUSH); |
962
|
0
|
|
|
|
|
|
while (1) { |
963
|
0
|
|
|
|
|
|
while (1) { |
964
|
0
|
|
|
|
|
|
$key = ReadKey(0); |
965
|
0
|
0
|
|
|
|
|
last if exists $Key_Commands{$key}; |
966
|
|
|
|
|
|
|
#post_message(sprintf "Illegal command \\%03o", ord $key); |
967
|
|
|
|
|
|
|
} |
968
|
0
|
|
|
|
|
|
$ret = $Key_Commands{$key}->(); |
969
|
0
|
0
|
|
|
|
|
last if $ret != MOVE_FAILED; |
970
|
|
|
|
|
|
|
} |
971
|
0
|
|
|
|
|
|
return $ret; |
972
|
|
|
|
|
|
|
} |
973
|
|
|
|
|
|
|
|
974
|
|
|
|
|
|
|
sub update_monst { |
975
|
0
|
|
|
0
|
0
|
|
my ($ent) = @_; |
976
|
0
|
|
|
|
|
|
my $mcol = $ent->[LMC][WHERE][PCOL]; |
977
|
0
|
|
|
|
|
|
my $mrow = $ent->[LMC][WHERE][PROW]; |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
# prevent monster move where only gravity should apply |
980
|
|
|
|
|
|
|
# NOTE one may have the clever idea that monsters can run across the |
981
|
|
|
|
|
|
|
# heads of other monsters though that would require changes to how |
982
|
|
|
|
|
|
|
# the graph is setup to permit such moves, and additional checks to |
983
|
|
|
|
|
|
|
# see if something to tread upon is available (and then to let the |
984
|
|
|
|
|
|
|
# hero do that (like in Lode Runner) or to prevent them from such |
985
|
|
|
|
|
|
|
# head-running...) |
986
|
0
|
0
|
0
|
|
|
|
if ( $mrow != ROWS - 1 |
|
|
|
0
|
|
|
|
|
987
|
|
|
|
|
|
|
and $ent->[LMC][GROUND][WHAT] == FLOOR |
988
|
|
|
|
|
|
|
and $LMap->[ $mrow + 1 ][$mcol][GROUND][WHAT] != WALL) { |
989
|
0
|
|
|
|
|
|
return; |
990
|
|
|
|
|
|
|
} |
991
|
|
|
|
|
|
|
|
992
|
0
|
|
|
|
|
|
my $dest = find_hero($ent, $mcol, $mrow); |
993
|
0
|
0
|
|
|
|
|
return unless defined $dest; |
994
|
|
|
|
|
|
|
|
995
|
0
|
0
|
|
|
|
|
relocate($ent, $dest) unless interact($ent, $dest); |
996
|
|
|
|
|
|
|
|
997
|
0
|
0
|
0
|
|
|
|
if ($ent->[STASH][GEM_STASH] > 0 |
998
|
|
|
|
|
|
|
and !defined $ent->[LMC][ITEM]) { |
999
|
0
|
0
|
|
|
|
|
if (rand() < $ent->[STASH][GEM_ODDS]) { |
1000
|
0
|
|
|
|
|
|
post_message('The ' . $Monst_Name . ' drops a gem!'); |
1001
|
0
|
|
|
|
|
|
$ent->[STASH][GEM_STASH]--; |
1002
|
0
|
|
|
|
|
|
make_item($ent->[LMC][WHERE], GEM, GEM_VALUE); |
1003
|
0
|
|
|
|
|
|
$ent->[STASH][GEM_ODDS] = 0.0 - GEM_ODDS_ADJUST; |
1004
|
|
|
|
|
|
|
} |
1005
|
0
|
|
|
|
|
|
$ent->[STASH][GEM_ODDS] += GEM_ODDS_ADJUST; |
1006
|
|
|
|
|
|
|
} |
1007
|
|
|
|
|
|
|
} |
1008
|
|
|
|
|
|
|
|
1009
|
|
|
|
|
|
|
1; |
1010
|
|
|
|
|
|
|
__END__ |