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