extends ArcadeGame const Model = preload("res://games/pacman/pacman_model.gd") const View = preload("res://games/pacman/pacman_view.gd") const Sound = preload("res://games/pacman/pacman_audio.gd") @export_group("Room Objective") ## Zero means unlimited boards. Set a score target or use arcade mode. @export_range(0, 100, 1) var boards_to_win: int = 1 @export_range(0, 1000000, 100) var score_to_win: int = 0 @export_range(1, 9, 1) var starting_lives: int = 3 @export var extra_life_score: int = 10000 var model = Model.new() var view var audio var accumulator := 0.0 var credits := 0 var rfid_seen: Dictionary = {} var rfid_initialized := false func receive_device_feedback(feedback: Dictionary) -> void: var events: Array = feedback.get("events", []) if not rfid_initialized: if not bool(feedback.get("fresh", false)): return for entry in events: rfid_seen[str(entry.get("id", ""))] = true rfid_initialized = true return if not bool(feedback.get("fresh", false)): return var device_id := str(run_config.get("rfid_credit_device", "")) for entry in events: var ident := str(entry.get("id", "")) if ident.is_empty() or rfid_seen.has(ident): continue rfid_seen[ident] = true if device_id.is_empty() or is_running or credits > 0 or model.state != "idle": continue if entry.get("device") != device_id or entry.get("kind") != "rfid" or not str(entry.get("error", "")).is_empty(): continue var uid := str(entry.get("value", "")).to_upper() if uid.is_empty(): continue var allowed: Array = run_config.get("rfid_allowed_uids", []) if not allowed.is_empty() and not allowed.has(uid): continue for sample in feedback.get("devices", []): if sample.get("id") == device_id and int(sample.get("age_ms", 999999)) < 2000 and sample.get("value") == entry.get("value") and str(sample.get("error", "")).is_empty(): credits = 1 view.credits = credits audio.play_effect("credit") break # The bridge retains only its last 64 events. Keep deduplication bounded too. if rfid_seen.size() > 256: rfid_seen.clear() for entry in events: rfid_seen[str(entry.get("id", ""))] = true func _ready() -> void: view = View.new() view.model = model add_child(view) audio = Sound.new() add_child(audio) model.sound_requested.connect(audio.play_effect) model.finished.connect(_on_finished) _apply_config() func configure(config: Dictionary = {}) -> void: super.configure(config) credits = 0 rfid_seen.clear() rfid_initialized = false _apply_config() func _apply_config() -> void: if view != null: view.credit_required = not str(run_config.get("rfid_credit_device", "")).is_empty() view.credits = credits model.boards_to_win = maxi(0, int(run_config.get("boards_to_win", boards_to_win))) model.score_to_win = maxi(0, int(run_config.get("score_to_win", score_to_win))) model.initial_lives = clampi(int(run_config.get("starting_lives", starting_lives)), 1, 9) model.extra_life_score = maxi(0, int(run_config.get("extra_life_score", extra_life_score))) func start_game() -> bool: if is_running: return false if not str(run_config.get("rfid_credit_device", "")).is_empty(): if credits < 1: return false credits -= 1 _apply_config() audio.stop_all() model.start() accumulator = 0.0 return super.start_game() func reset_game() -> void: credits = 0 if view != null: view.credits = 0 audio.stop_all() model.reset() accumulator = 0.0 super.reset_game() func win_game() -> void: if not is_running: return model.state = "won" audio.stop_all() super.win_game() func lose_game() -> void: if not is_running: return model.state = "lost" audio.stop_all() super.lose_game() func _on_finished(won: bool) -> void: if won: win_game() else: lose_game() func _unhandled_input(event: InputEvent) -> void: if event.is_action_pressed("arcade_start"): start_game() get_viewport().set_input_as_handled() for pair in [["arcade_up", Vector2i.UP], ["arcade_left", Vector2i.LEFT], ["arcade_down", Vector2i.DOWN], ["arcade_right", Vector2i.RIGHT]]: if event.is_action_pressed(pair[0]): model.requested = pair[1] func _physics_process(delta: float) -> void: if not is_running: model.clock += delta if is_running: accumulator += minf(delta, 0.25) while accumulator >= 1.0 / 60.0 and is_running: model.update(1.0 / 60.0) accumulator -= 1.0 / 60.0 if audio != null: audio.set_background(model.background_sound() if is_running else "")