Added Glitch Room Source code to Gitty
This commit is contained in:
commit
1ac9eb5644
505 files changed
+28504
No files matched your search
@@ -0,0 +1,180 @@
|
||||
extends SceneTree
|
||||
const Model = preload("res://games/pacman/pacman_model.gd")
|
||||
const Maze = preload("res://games/pacman/pacman_maze.gd")
|
||||
const Rules = preload("res://games/pacman/pacman_rules.gd")
|
||||
var checks := 0
|
||||
var failures := 0
|
||||
|
||||
func check(condition: bool, label: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
push_error("FAIL: " + label)
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func run() -> void:
|
||||
for row in Maze.ROWS: check(row.length() == 28, "28-column maze")
|
||||
check(Maze.pellets().size() == 244, "244 pellets")
|
||||
var powers := 0
|
||||
for value in Maze.pellets().values():
|
||||
if value: powers += 1
|
||||
check(powers == 4, "four energizers")
|
||||
var seen := {Maze.PLAYER_START: true}
|
||||
var queue := [Maze.PLAYER_START]
|
||||
while not queue.is_empty():
|
||||
var tile: Vector2i = queue.pop_front()
|
||||
for direction in Maze.DIRS:
|
||||
var next: Vector2i = Maze.neighbor(tile, direction)
|
||||
if Maze.walkable(next) and not seen.has(next):
|
||||
seen[next] = true
|
||||
queue.append(next)
|
||||
for tile in Maze.pellets(): check(seen.has(tile), "reachable pellet %s" % tile)
|
||||
check(not Maze.walkable(Vector2i(13, 12)), "player cannot enter ghost door")
|
||||
check(Maze.neighbor(Vector2i(0,14), Vector2i.LEFT) == Vector2i(27,14), "tunnel wrap")
|
||||
check(Rules.target(0, Vector2i(5,5), Vector2i.UP, Vector2i.ZERO, Vector2i.ZERO) == Vector2i(5,5), "Blinky target")
|
||||
check(Rules.target(1, Vector2i(5,5), Vector2i.UP, Vector2i.ZERO, Vector2i.ZERO) == Vector2i(1,1), "Pinky upward quirk")
|
||||
check(Rules.target(2, Vector2i(5,5), Vector2i.RIGHT, Vector2i(3,5), Vector2i.ZERO) == Vector2i(11,5), "Inky target")
|
||||
check(Rules.target(3, Vector2i(5,5), Vector2i.RIGHT, Vector2i.ZERO, Vector2i(6,5)) == Rules.SCATTER[3], "Clyde retreats")
|
||||
var m = Model.new()
|
||||
m.start()
|
||||
m.update(5.0)
|
||||
check(m.state == "playing", "ready becomes playing")
|
||||
# Movement stops at the corridor wall, despite a held direction.
|
||||
for i in range(180):
|
||||
m._move_player(0.1)
|
||||
m.pellet_pause = 0.0
|
||||
check(Maze.walkable(m.player.tile), "movement stays in corridor")
|
||||
# Energizer changes active ghosts; capture returns eyes and adds score.
|
||||
m.player = Maze.actor(Vector2i(1,3), Vector2i.DOWN)
|
||||
m._consume()
|
||||
check(m.fright_time > 0.0, "energizer starts frightened timer")
|
||||
var before: int = m.score
|
||||
m.ghosts[0].tile = m.player.tile
|
||||
m.ghosts[0].next = m.player.tile
|
||||
m.ghosts[0].progress = 0.0
|
||||
m._check_collisions()
|
||||
check(m.ghosts[0].status == "eyes" and m.score == before + 200, "ghost capture score and eyes")
|
||||
m.reset()
|
||||
check(m.state == "idle" and m.score == 0 and m.dots.size() == 244, "reset restores board")
|
||||
m.start()
|
||||
m.state = "playing"
|
||||
m.dots = {m.player.tile: false}
|
||||
m._consume()
|
||||
check(m.state == "clear", "last pellet clears level")
|
||||
m.update(3.0)
|
||||
check(m.state == "won", "default room objective wins")
|
||||
m.boards_to_win = 0
|
||||
m.start()
|
||||
m.state = "clear"
|
||||
m.phase_time = 0.0
|
||||
m.update(0.1)
|
||||
check(m.level == 2 and m.state == "ready" and m.dots.size() == 244, "arcade progression")
|
||||
m.state = "clear"
|
||||
m.phase_time = 0.0
|
||||
m.update(0.1)
|
||||
check(m.state == "intermission", "second board starts intermission")
|
||||
m.update(6.0)
|
||||
check(m.state == "ready" and m.level == 3, "intermission continues to next level")
|
||||
m.lives = 1
|
||||
m.state = "dying"
|
||||
m.phase_time = 0.0
|
||||
m.update(0.1)
|
||||
assert(m.state == "death_pop", "death pop before final loss")
|
||||
m.phase_time = 0.0
|
||||
m.update(0.1)
|
||||
check(m.state == "lost", "final life loses")
|
||||
m.start()
|
||||
m.state = "playing"
|
||||
m.score_to_win = 100
|
||||
m._add_score(100)
|
||||
check(m.state == "won", "score objective")
|
||||
# Traverse the entire board through the actual movement and consumption code.
|
||||
m.score_to_win = 0
|
||||
m.boards_to_win = 1
|
||||
m.start()
|
||||
m.state = "playing"
|
||||
var moves := 0
|
||||
while not m.dots.is_empty() and moves < 3000:
|
||||
var frontier: Array = [m.player.tile]
|
||||
var parent := {m.player.tile: null}
|
||||
var destination: Vector2i = m.player.tile
|
||||
while not frontier.is_empty():
|
||||
var current: Vector2i = frontier.pop_front()
|
||||
if m.dots.has(current):
|
||||
destination = current
|
||||
break
|
||||
for direction in Maze.DIRS:
|
||||
var next: Vector2i = Maze.neighbor(current, direction)
|
||||
if Maze.walkable(next) and not parent.has(next):
|
||||
parent[next] = current
|
||||
frontier.append(next)
|
||||
var path: Array[Vector2i] = []
|
||||
while destination != m.player.tile:
|
||||
path.push_front(destination)
|
||||
destination = parent[destination]
|
||||
if path.is_empty():
|
||||
m._consume()
|
||||
for tile in path:
|
||||
var direction: Vector2i = tile - m.player.tile
|
||||
if abs(direction.x) > 1: direction.x = -signi(direction.x)
|
||||
m.requested = direction
|
||||
for step in range(50):
|
||||
m.pellet_pause = 0.0
|
||||
m._move_player(0.1)
|
||||
if m.player.tile == tile: break
|
||||
check(m.player.tile == tile, "buffered turn reaches next corridor tile")
|
||||
moves += 1
|
||||
check(m.dots.is_empty() and m.state == "clear", "full maze cleared through movement")
|
||||
# Every returning ghost must be able to reach the house and emerge again.
|
||||
m.start()
|
||||
m.state = "playing"
|
||||
for tile in seen:
|
||||
var g: Dictionary = m.ghosts[0]
|
||||
g.tile = tile
|
||||
g.next = tile
|
||||
g.progress = 0.0
|
||||
g.dir = Vector2i.LEFT
|
||||
g.status = "eyes"
|
||||
g.reverse = false
|
||||
for step in range(1600):
|
||||
m._move_ghost(0, 0.5)
|
||||
if g.status == "active": break
|
||||
check(g.status == "active", "eyes return from %s" % tile)
|
||||
# Headless random walk checks ghost movement stays in legal corridors.
|
||||
m.score_to_win = 0
|
||||
m.start()
|
||||
m.state = "playing"
|
||||
for frame in range(12000):
|
||||
m.requested = Maze.DIRS[(frame / 30) % 4]
|
||||
m.update(1.0 / 60.0)
|
||||
if m.state == "lost": m.start()
|
||||
for g in m.ghosts:
|
||||
if g.status == "active":
|
||||
check(Maze.walkable(g.tile), "ghost stays on map")
|
||||
var game = load("res://games/pacman/Pacman.tscn").instantiate()
|
||||
root.add_child(game)
|
||||
var counts := {"start": 0, "won": 0, "lost": 0, "reset": 0}
|
||||
game.game_started.connect(func(): counts.start += 1)
|
||||
game.game_won.connect(func(): counts.won += 1)
|
||||
game.game_lost.connect(func(): counts.lost += 1)
|
||||
game.game_reset.connect(func(): counts.reset += 1)
|
||||
check(game.start_game(), "ArcadeGame starts")
|
||||
check(not game.start_game(), "duplicate start ignored")
|
||||
game.win_game()
|
||||
game.win_game()
|
||||
check(counts.won == 1 and counts.start == 1, "win event exactly once")
|
||||
game.reset_game()
|
||||
game.start_game()
|
||||
game.lose_game()
|
||||
game.lose_game()
|
||||
check(counts.lost == 1 and counts.reset == 1, "loss and reset events")
|
||||
var loop = game.audio._loop_stream("siren0", game.audio.stream("siren0_firstloop"))
|
||||
check(loop.loop_begin > 0 and loop.loop_end > loop.loop_begin, "intro plays once before PCM loop")
|
||||
game.audio.set_background("fright")
|
||||
game.reset_game()
|
||||
check(game.audio.background == "" and not game.audio.ambience.playing, "reset stops ambience")
|
||||
game.queue_free()
|
||||
print("PACMAN: %d checks, %d failures" % [checks, failures])
|
||||
quit(1 if failures else 0)
|
||||
Reference in new issue
Block a user