Repository navigation
Expand file tree
/
Copy pathplayer.lua
More file actions
332 lines (288 loc) · 11.2 KB
/
Copy pathplayer.lua
File metadata and controls
332 lines (288 loc) · 11.2 KB
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
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
Player = Object.extend(Object)
-- I can probably use this as a generic character class for all ghosts, pacman, etc.
-- TODO: subclass this for each char instead of all this per-char specific stuff
-- or at least a special one for pacman.
-- TODO: Ghost house logic:
-- staggered exiting the ghosthouse (higher priority)
-- scripted leaving the ghosthouse (polish stuff, could make them just pop in for now)
-- TODO: scatter modes (note clyde kind of has that already)
--
-- only difference is pathfinding:
-- ai-ghosts: simple ecludian distance descision making on turns
-- pacman: eating pellets, avoiding ghosts, power pellets + ghost attack, etc
-- player: controlled by user
function Player.new(self, x, y, sprite, name)
self.name = name
self.x = x
self.y = y
self.tile_x = 0
self.tile_y = 0
self.width = 32
self.height = 32
self.velocity = 2
-- 0 = left, 1 = right, 2 = up, 3 = down
self.direction = 0
self.queued_direction = nil
self.new_tile = false
self.target_x = 0
self.target_y = 0
-- TODO: Animate and change sprite based on direction etc
self.img = sprite
math.randomseed(os.time())
end
function Player.update(self, dt)
updateTilePos(self)
if self.name == "pacman" then
if self.new_tile then
local tv = maze.tilemap[self.tile_y][self.tile_x]
if tv == 39 or tv == 40 then
maze.tilemap[self.tile_y][self.tile_x] = 0
end
end
if self.new_tile and self.queued_direction == nil then
randomMovement(self)
self.new_tile = false
end
if self.queued_direction and self:changeDirection(self.queued_direction) then
-- print("Move executed", self.queued_direction)
self.queued_direction = nil
end
end
if self.name == "pinky" or self.name == "inky" or self.name == "clyde" then
if self.new_tile and self.queued_direction == nil then
local nt = nextTile(self)
local possible = possibleMovements(self, nt)
target = findTargetTile(self)
self.queued_direction = decideDirectionEcludian(self, target, possible)
self.new_tile = false
end
if self.queued_direction and self:changeDirection(self.queued_direction) then
self.queued_direction = nil
-- print("Move executed")
end
end
move(self)
end
function findTargetTile(self)
local target = {}
if self.name == "pinky" then
if pacman.direction == LEFT then
target.x = pacman.tile_x - 4
target.y = pacman.tile_y
elseif pacman.direction == RIGHT then
target.x = pacman.tile_x + 4
target.y = pacman.tile_y
elseif pacman.direction == UP then
-- x modification mimics the original's bug
target.x = pacman.tile_x - 4
target.y = pacman.tile_y - 4
elseif pacman.direction == DOWN then
target.x = pacman.tile_x
target.y = pacman.tile_y + 4
end
elseif self.name == "inky" then
-- first offset 2 tiles from pacman
-- then draw a line from Blinky's tile to that offset.
-- Now double the line length by extending the line out just as far again
if pacman.direction == LEFT then
target.x = pacman.tile_x - 2
target.y = pacman.tile_y
elseif pacman.direction == RIGHT then
target.x = pacman.tile_x + 2
target.y = pacman.tile_y
elseif pacman.direction == UP then
-- x modification mimics the original's bug
target.x = pacman.tile_x - 2
target.y = pacman.tile_y - 2
elseif pacman.direction == DOWN then
target.x = pacman.tile_x
target.y = pacman.tile_y + 2
end
local xdiff = target.x - blinky.tile_x
local ydiff = target.y - blinky.tile_y
target.x = target.x + xdiff
target.y = target.y + ydiff
elseif self.name == "clyde" then
if distance(self.tile_x, self.tile_y, pacman.tile_x, pacman.tile_y) > 8 then
target.x = pacman.tile_x
target.y = pacman.tile_y
else
target.x = 1
target.y = 36
end
end
self.target_x = target.x
self.target_y = target.y
return target
end
function decideDirectionEcludian(self, target, possible)
-- ecludian distance, same for all ghosts
local min = nil
local dir = nil
for _, _v in ipairs(possible) do
local d,ty,tx = unpack(_v)
local dis = distance(tx, ty, target.x, target.y)
--print("option", d, dis)
if min == nil or dis < min then
min = dis
dir = d
end
end
--print("best option", dir)
return dir
end
function distance ( x1, y1, x2, y2 )
local dx = x1 - x2
local dy = y1 - y2
return math.sqrt ( dx * dx + dy * dy )
end
function nextTile(self)
local target = {}
target.x = self.tile_x
target.y = self.tile_y
if self.direction == 0 and emptyTiles[maze.tilemap[target.y][target.x -1]] then
target.x = target.x -1
elseif self.direction == 1 and emptyTiles[maze.tilemap[target.y][target.x +1]] then
target.x = target.x +1
elseif self.direction == 2 and emptyTiles[maze.tilemap[target.y -1][target.x]] then
target.y = target.y -1
elseif self.direction == 3 and emptyTiles[maze.tilemap[target.y +1][target.x]] then
target.y = target.y +1
end
return target
end
function possibleMovements(self, target)
-- TODO: remove the 'going backwards' restriction
-- it's up to the movement algo to not go backwards
-- change to possible[0] = coordinates ?
-- ipairs seems strange when you do that
-- might just change direction to a string entirely, then index possible with "up" etc
-- then for checking direction I check the keys of the table
-- alternatively do 1= left 2 = right, 3=up, 4 = down etc.
-- annoying to renumber them all but 0 is just problematic many times so far
-- ghosts not allowed to go UP to [y,x] 13,26 + 16,26 and 13,14 + 16,14
-- unless in scatter mode? TODO if I want to do that.
-- undecided if I want player to be constrained here if I do ghosts
local possible = {}
if emptyTiles[maze.tilemap[target.y][target.x -1]] and self.direction ~= 1 then
table.insert(possible, {0, target.y, target.x -1})
end
if emptyTiles[maze.tilemap[target.y][target.x +1]] and self.direction ~= 0 then
table.insert(possible, {1, target.y, target.x +1})
end
if emptyTiles[maze.tilemap[target.y -1][target.x]] and self.direction ~= 3 then
table.insert(possible, {2, target.y -1,target.x})
end
if emptyTiles[maze.tilemap[target.y +1][target.x]] and self.direction ~=2 then
table.insert(possible, {3, target.y +1, target.x})
end
return possible
end
function randomMovement(self)
-- TODO: make actual pacman movement routine
local target = nextTile(self)
local possible = possibleMovements(self, target)
local r = possible[math.random( #possible )]
if r == nil then
newdir = self.direction
else
newdir = unpack(r)
end
-- this is a hack to deal with the wraparound, the possible directions will be nil
if newdir == nil then
newdir = self.direction
end
self.queued_direction = newdir
-- print("Queued ", self.queued_direction, "on ", target.x, target.y)
end
function move(self)
--pacman chars move 'on rails' in an 8px bound box
-- so their x/y is constrained to stay centered in the maze.
-- since I doubled everything it's 16px bounding in this case
-- I can probably greatly simplify this movement system.
if self.direction == 0 then
self.x = self.x - self.velocity
tileX = math.floor(((self.x + 8) / 16))
--wraparound special
if self.tile_y == 18 then
if self.x + 32 == 16 then
self.x = (29 * 16)
return
end
if tileX < 1 or tileX > 28 then
return
end
end
if not emptyTiles[maze.tilemap[self.tile_y][tileX]] then
self.x = (tileX * 16) + 8
end
elseif self.direction == 1 then
self.x = self.x + self.velocity
tileX = math.floor(((self.x + 24) / 16))
--wraparound special
if self.tile_y == 18 then
if self.x == (29 * 16) then
self.x = -16
return
end
if tileX > 28 or tileX < 1 then
return
end
end
if not emptyTiles[maze.tilemap[self.tile_y][tileX]] then
self.x = (tileX * 16) - 24
end
elseif self.direction == 2 then
self.y = self.y - self.velocity
tileY = math.floor(((self.y + 8) / 16))
if not emptyTiles[maze.tilemap[tileY][self.tile_x]] then
self.y = (tileY * 16) + 8
end
elseif self.direction == 3 then
self.y = self.y + self.velocity
tileY = math.floor(((self.y + 24) / 16))
if not emptyTiles[maze.tilemap[tileY][self.tile_x]] then
self.y = (tileY * 16) - 24
end
end
end
function Player.changeDirection(self, dir)
-- TODO tile value of pellets might not be 0
-- if I gate this properly I don't need to check empty here
horiz_check = math.abs((self.y+8) - self.tile_y * 16) < 3
vert_check = math.abs((self.x+8) - self.tile_x * 16) < 3
if dir == 0 and emptyTiles[maze.tilemap[self.tile_y][self.tile_x-1]] and horiz_check then
self.direction = 0
self.y = (self.tile_y * 16) - 8
return true
elseif dir == 1 and emptyTiles[maze.tilemap[self.tile_y][self.tile_x+1]] and horiz_check then
self.direction = 1
self.y = (self.tile_y * 16) - 8
return true
elseif dir == 2 and emptyTiles[maze.tilemap[self.tile_y-1][self.tile_x]] and vert_check then
self.direction = 2
self.x = (self.tile_x * 16) - 8
return true
elseif dir == 3 and emptyTiles[maze.tilemap[self.tile_y+1][self.tile_x]] and vert_check then
self.direction = 3
self.x = (self.tile_x * 16) - 8
return true
end
return false
end
function updateTilePos(self)
-- tile is determined by the point in the exact 'center' of the sprite.
-- since it's 32x32 there is no actual center
-- so it ends up on the top-left pixel of the center 4
new_y = math.floor(((self.y + 16) / 16))
new_x = math.floor(((self.x + 16) / 16))
if new_y ~= self.tile_y or new_x ~= self.tile_x then
self.new_tile = true
self.tile_y = new_y
self.tile_x = new_x
-- print("Now on", new_x, new_y)
end
end
function Player.draw(self)
love.graphics.draw(self.img, self.x, self.y)
end