-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathMAPS.PAS
260 lines (216 loc) · 7.84 KB
/
MAPS.PAS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
{$A+,B-,E+,F-,G+,I-,N+,P-,Q-,R-,S-,T-,V-,X+}
unit Maps;
interface
uses Entities;
const
SCREEN_MAP_LAYER = 2;
SCREEN_MAP_WIDTH = 20;
SCREEN_MAP_HEIGHT = 11;
SCREEN_MAP_SIZE = SCREEN_MAP_WIDTH*SCREEN_MAP_HEIGHT;
MAP_RIGHT = SCREEN_MAP_WIDTH - 1;
MAP_BOTTOM = SCREEN_MAP_HEIGHT - 1;
{ every tile beginning with this index should be
considered 'solid' for collision-purposes }
SOLID_TILE_START = 70;
{ inclusive start/end tile indices marking the range in which all
possible "dirt" tiles are found within }
DIRT_TILES_START = 15;
DIRT_TILES_END = 54;
type
DirtTile = record
fruit : Fruit;
hasFruit : bytebool;
x, y : word;
mapIndex : word;
end;
PDirtTile = ^DirtTile;
MapArray = array[0..(SCREEN_MAP_SIZE-1)] of byte;
MapHeader = record
name : string[32]; { display name }
time : word; { match time in seconds }
initialFruit : word; { initial amount of fruit plants to spawn }
maxFruit : word; { max number of fruit/plants that can be
active. once reached, no more will spawn
until some of the existing ones are
removed }
player1x : word; { player 1 starting tile coordinates }
player1y : word;
player2x : word; { player 2 starting tile coordinates }
player2y : word;
end;
MapFile = record
header : MapHeader;
map : MapArray;
end;
MapToDirtTileArray = array[0..(SCREEN_MAP_SIZE-1)] of PDirtTile;
{ even though this is sized identically to the map itself, the actual
number of indices used will be less. AND these indices DO NOT
correspond to the same indices in the map itself! }
DirtTileArray = array[0..(SCREEN_MAP_SIZE-1)] of DirtTile;
const
{ if true, the map should be re-rendered to SCREEN_MAP_LAYER }
isMapDirty : boolean = false;
var
map : MapFile;
{ a mapping of map x,y coordinates to DirtTile instances. any index
where the value in this array is nil means that that x,y coordinate
is not for a dirt tile }
dirtTileMapping : MapToDirtTileArray;
{ contains all of the dirt tiles. the indices in this array DO NOT
correspond to map x,y coordinates. use the above dirtTileMapping
array to find a dirt tile located in this array given a set of x,y
coordinates. }
{ TODO: perhaps this should be implemented as a linked-list? }
dirtTiles : DirtTileArray;
numDirtTiles : word;
numActiveDirtTiles : word;
function IsMapCollision(x, y : integer) : boolean;
function DoesEntityOverlapMapTile(const entity : Entity;
xt, yt : integer) : boolean;
procedure InitDirtTiles;
function GetUnusedDirtTileIndex : integer;
function GetRandomUnusedDirtTileIndex : integer;
implementation
uses FixedP, Toolbox, Shared;
function IsMapCollision(x, y : integer) : boolean;
{ returns true if an entity-sized object located at the given x,y
coordinates (which indicate the top-left of the entity) will collide
with any 'solid' tiles on the map. }
const
EDGE = 2;
var
left, right, top, bottom : integer;
cx, cy : integer;
index : word;
begin
IsMapCollision := false;
{ TODO: something to make collision feel less "sticky" and a bit more
forgiving ... }
left := (x+EDGE) div TILE_SIZE;
right := ((x-EDGE) + ENTITY_SIZE-1) div TILE_SIZE;
top := (y+EDGE) div TILE_SIZE;
bottom := ((y-EDGE) + ENTITY_SIZE-1) div TILE_SIZE;
if left < 0 then left := 0;
if right > MAP_RIGHT then right := MAP_RIGHT;
if top < 0 then top := 0;
if bottom > MAP_BOTTOM then bottom := MAP_BOTTOM;
with map do begin
for cy := top to bottom do begin
for cx := left to right do begin
index := (cy * SCREEN_MAP_WIDTH) + cx;
if map[index] >= SOLID_TILE_START then begin
IsMapCollision := true;
exit;
end else if (dirtTileMapping[index] <> nil)
and (dirtTileMapping[index]^.hasFruit) then begin
IsMapCollision := true;
exit;
end;
end;
end;
end;
end;
function DoesEntityOverlapMapTile(const entity : Entity;
xt, yt : integer) : boolean;
{ returns true if the given entity fully or partially overlaps the boundaries
of the given map tile coordinates. the x and y coordinates given should be
tile coordinates, not pixel coordinates. }
const
EDGE = 2;
var
ex1, ey1, ex2, ey2 : integer;
x2, y2 : integer;
begin
DoesEntityOverlapMapTile := false;
with entity.position do begin
ex1 := FixToInt(x)+EDGE;
ey1 := FixToInt(y)+EDGE;
ex2 := ex1 + (ENTITY_SIZE-1)-EDGE;
ey2 := ey1 + (ENTITY_SIZE-1)-EDGE;
end;
xt := xt * TILE_SIZE;
yt := yt * TILE_SIZE;
x2 := xt + (TILE_SIZE-1);
y2 := yt + (TILE_SIZE-1);
if (ey1 < yt) and (ey2 < yt) then
exit;
if (ey1 > y2) and (ey2 > y2) then
exit;
if (ex1 < xt) and (ex2 < xt) then
exit;
if (ex1 > x2) and (ex2 > x2) then
exit;
DoesEntityOverlapMapTile := true;
end;
procedure InitDirtTiles;
{ after a map has been freshly loaded, call this to scan the map for
all its dirt tiles. x,y coord to DirtTile instance mapping information
will be prepared as well as initializing the dirt tiles array itself }
var
mapIdx, dirtIdx, x, y : word;
tile : byte;
begin
MemFill(@dirtTileMapping, 0, SizeOf(dirtTileMapping));
MemFill(@dirtTiles, 0, SizeOf(dirtTiles));
dirtIdx := 0;
for y := 0 to SCREEN_MAP_HEIGHT-1 do begin
for x := 0 to SCREEN_MAP_WIDTH-1 do begin
mapIdx := (y * SCREEN_MAP_WIDTH) + x;
tile := map.map[mapIdx];
{ if this map location contains a dirt tile ... }
if (tile >= DIRT_TILES_START) and (tile <= DIRT_TILES_END) then begin
{ set up this next DirtTile instance with coordinate info about
this map location }
dirtTiles[dirtIdx].x := x;
dirtTiles[dirtIdx].y := y;
dirtTiles[dirtIdx].mapIndex := mapIdx;
{ and add a pointer for this map coordinate index to the table }
dirtTileMapping[mapIdx] := @dirtTiles[dirtIdx];
inc(dirtIdx);
end;
end;
end;
numDirtTiles := dirtIdx;
numActiveDirtTiles := 0;
end;
function GetUnusedDirtTileIndex : integer;
{ returns the index of the next unused/inactive dirt tile from dirtTiles.
returns -1 if there is no free index }
var
idx : integer;
begin
GetUnusedDirtTileIndex := -1;
for idx := 0 to numDirtTiles-1 do begin
if dirtTiles[idx].hasFruit then begin
GetUnusedDirtTileIndex := idx;
exit;
end;
end;
end;
function GetRandomUnusedDirtTileIndex : integer;
{ returns the index of a random unused/inactive dirt tile from dirtTiles.
returns -1 if there is no free index }
const
MAX_TRIES = 10; { TODO: LOL, this is a bad way to do this }
var
try, idx : integer;
begin
GetRandomUnusedDirtTileIndex := -1;
for try := 0 to MAX_TRIES do begin
idx := random(numDirtTiles);
{ make sure there is no fruit in this tile ... }
if not dirtTiles[idx].hasFruit then begin
with dirtTiles[idx] do begin
{ and also make sure that neither player is currently anywhere
within this tile either }
if (not DoesEntityOverlapMapTile(player1.entity, x, y))
and (not DoesEntityOverlapMapTile(player2.entity, x, y)) then begin
{ now we know for sure that this tile is clear }
GetRandomUnusedDirtTileIndex := idx;
exit;
end;
end;
end;
end;
end;
end.