Выделить event-driven PuzzleSystem
This commit is contained in:
@@ -21,6 +21,7 @@ import android.view.accessibility.AccessibilityEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@@ -228,9 +229,11 @@ public final class GameView extends View implements Runnable {
|
||||
private final CorpseManager corpseManager = new CorpseManager(MAX_CORPSES);
|
||||
private final List<Body> corpses = corpseManager.bodies();
|
||||
private final ArrayList<PlatformState> platformStates = new ArrayList<>();
|
||||
private final Map<String, Boolean> toggleSignals = new HashMap<>();
|
||||
private final Map<String, Float> timedSignals = new HashMap<>();
|
||||
private final Map<String, Boolean> previousSwitchContact = new HashMap<>();
|
||||
private final Map<LevelDefinition.Door, DoorController> doorControllers =
|
||||
new IdentityHashMap<>();
|
||||
private final Map<String, LevelDefinition.Door> doorsById = new HashMap<>();
|
||||
private PuzzleSystem puzzleSystem = new PuzzleSystem();
|
||||
private final AccessibilityStateCache accessibilityStateCache =
|
||||
new AccessibilityStateCache();
|
||||
private final VolunteerCanvasRenderer volunteerRenderer = new VolunteerCanvasRenderer();
|
||||
@@ -266,7 +269,6 @@ public final class GameView extends View implements Runnable {
|
||||
private AccessibilitySettings accessibilitySettings;
|
||||
private AudioSettings audioSettings;
|
||||
private float lastFootstepAt = -10f;
|
||||
private boolean previousAllDoorsOpen;
|
||||
private boolean deterministicAutomationClock;
|
||||
private double deterministicAccumulatorSeconds;
|
||||
private float lastDrawDurationMs;
|
||||
@@ -385,7 +387,6 @@ public final class GameView extends View implements Runnable {
|
||||
cameraShakeY = 0f;
|
||||
cameraController.reset();
|
||||
lastFootstepAt = -10f;
|
||||
previousAllDoorsOpen = false;
|
||||
performanceMonitor.reset();
|
||||
performanceFrameCounter = 0;
|
||||
userPaused = false;
|
||||
@@ -393,9 +394,8 @@ public final class GameView extends View implements Runnable {
|
||||
deriveControls();
|
||||
corpseManager.reset();
|
||||
platformStates.clear();
|
||||
toggleSignals.clear();
|
||||
timedSignals.clear();
|
||||
previousSwitchContact.clear();
|
||||
configurePuzzleSystem();
|
||||
completionResult = null;
|
||||
configureAccessibilitySignals();
|
||||
invalidateHudTextCache();
|
||||
@@ -451,17 +451,6 @@ public final class GameView extends View implements Runnable {
|
||||
lastFootstepAt = levelElapsed;
|
||||
audioEngine.play(AudioCue.FOOTSTEP);
|
||||
}
|
||||
boolean allDoorsOpen = allDoorsOpenNow();
|
||||
if (allDoorsOpen && !previousAllDoorsOpen) {
|
||||
audioEngine.play(AudioCue.DOOR);
|
||||
if (!level.doors.isEmpty()) {
|
||||
LevelDefinition.Door door = level.doors.get(0);
|
||||
cameraController.requestFocus(door.box.x + door.box.width * 0.5f,
|
||||
door.box.y + door.box.height * 0.5f, 0.35f);
|
||||
}
|
||||
}
|
||||
previousAllDoorsOpen = allDoorsOpen;
|
||||
|
||||
refreshAccessibilityDescriptionIfStateChanged();
|
||||
|
||||
CameraController.Frame camera = cameraController.update(dt,
|
||||
@@ -473,30 +462,85 @@ public final class GameView extends View implements Runnable {
|
||||
cameraShakeY = camera.shakeY();
|
||||
}
|
||||
|
||||
private void updateSignals(float dt) {
|
||||
for (Map.Entry<String, Float> item : new ArrayList<>(timedSignals.entrySet())) {
|
||||
timedSignals.put(item.getKey(), Math.max(0f, item.getValue() - dt));
|
||||
private void configurePuzzleSystem() {
|
||||
doorControllers.clear();
|
||||
doorsById.clear();
|
||||
ArrayList<DoorController> controllers = new ArrayList<>(level.doors.size());
|
||||
for (int index = 0; index < level.doors.size(); index++) {
|
||||
LevelDefinition.Door door = level.doors.get(index);
|
||||
String id = "door-" + index;
|
||||
DoorController controller = new DoorController(id, door.requiredSignals);
|
||||
controllers.add(controller);
|
||||
doorControllers.put(door, controller);
|
||||
doorsById.put(id, door);
|
||||
}
|
||||
puzzleSystem = new PuzzleSystem(controllers);
|
||||
}
|
||||
|
||||
private void updateSignals(float dt) {
|
||||
updateSignals(dt, true);
|
||||
}
|
||||
|
||||
private void updateSignals(float dt, boolean feedback) {
|
||||
ArrayList<PuzzleSystem.Input> inputs = new ArrayList<>(
|
||||
level.switches.size() + level.hazards.size());
|
||||
for (LevelDefinition.Switch item : level.switches) {
|
||||
boolean contact = switchContact(item);
|
||||
String contactKey = item.signalId + "@" + item.box.x;
|
||||
boolean previous = Boolean.TRUE.equals(previousSwitchContact.get(contactKey));
|
||||
if (item.type == LevelDefinition.SwitchType.TOGGLE && contact && !previous) {
|
||||
toggleSignals.put(item.signalId, !Boolean.TRUE.equals(toggleSignals.get(item.signalId)));
|
||||
switch (item.type) {
|
||||
case PRESSURE:
|
||||
inputs.add(PuzzleSystem.Input.pressure(
|
||||
contactKey, item.signalId, contact));
|
||||
break;
|
||||
case TOGGLE:
|
||||
inputs.add(PuzzleSystem.Input.toggle(
|
||||
contactKey, item.signalId, contact));
|
||||
break;
|
||||
case TIMED:
|
||||
inputs.add(PuzzleSystem.Input.timed(
|
||||
contactKey, item.signalId, contact, item.durationSeconds));
|
||||
break;
|
||||
}
|
||||
if (feedback && contact && !previous
|
||||
&& item.type != LevelDefinition.SwitchType.PRESSURE) {
|
||||
emitHaptic(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||
}
|
||||
if (item.type == LevelDefinition.SwitchType.TIMED && contact && !previous) {
|
||||
timedSignals.put(item.signalId, item.durationSeconds);
|
||||
emitHaptic(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||
}
|
||||
if (contact && !previous) audioEngine.play(AudioCue.SWITCH);
|
||||
if (contact && !previous && player.alive
|
||||
if (feedback && contact && !previous) audioEngine.play(AudioCue.SWITCH);
|
||||
if (feedback && contact && !previous && player.alive
|
||||
&& intersects(player.x, player.y, PLAYER_WIDTH, PLAYER_HEIGHT, item.box)) {
|
||||
characterStateMachine.beginInteraction();
|
||||
}
|
||||
previousSwitchContact.put(contactKey, contact);
|
||||
}
|
||||
|
||||
for (int index = 0; index < level.hazards.size(); index++) {
|
||||
LevelDefinition.Hazard hazard = level.hazards.get(index);
|
||||
if (hazard.relayId == null || hazard.relayId.isEmpty()) continue;
|
||||
boolean active = false;
|
||||
for (Body corpse : corpses) {
|
||||
if (corpse.conductive
|
||||
&& intersects(corpse.x, corpse.y, corpse.width, corpse.height,
|
||||
hazard.box)) {
|
||||
active = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
inputs.add(PuzzleSystem.Input.relay(
|
||||
"relay-" + index, hazard.relayId, active));
|
||||
}
|
||||
|
||||
puzzleSystem.update(dt, inputs);
|
||||
if (!feedback) return;
|
||||
for (PuzzleSystem.Event event : puzzleSystem.events()) {
|
||||
if (event.kind() != PuzzleSystem.Event.Kind.DOOR_OPENED) continue;
|
||||
audioEngine.play(AudioCue.DOOR);
|
||||
LevelDefinition.Door door = doorsById.get(event.targetId());
|
||||
if (door != null) {
|
||||
cameraController.requestFocus(door.box.x + door.box.width * 0.5f,
|
||||
door.box.y + door.box.height * 0.5f, 0.35f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean allDoorsOpenNow() {
|
||||
@@ -515,28 +559,12 @@ public final class GameView extends View implements Runnable {
|
||||
}
|
||||
|
||||
private boolean signalActive(String id) {
|
||||
if (id == null || id.isEmpty()) return true;
|
||||
if (Boolean.TRUE.equals(toggleSignals.get(id))) return true;
|
||||
Float time = timedSignals.get(id);
|
||||
if (time != null && time > 0f) return true;
|
||||
for (LevelDefinition.Switch item : level.switches) {
|
||||
if (item.signalId.equals(id) && item.type == LevelDefinition.SwitchType.PRESSURE && switchContact(item)) return true;
|
||||
}
|
||||
for (LevelDefinition.Hazard hazard : level.hazards) {
|
||||
if (id.equals(hazard.relayId)) {
|
||||
for (Body corpse : corpses) {
|
||||
if (corpse.conductive && intersects(corpse.x, corpse.y, corpse.width, corpse.height, hazard.box)) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return puzzleSystem.signalActive(id);
|
||||
}
|
||||
|
||||
private boolean doorOpen(LevelDefinition.Door door) {
|
||||
for (String signal : door.requiredSignals) {
|
||||
if (!signalActive(signal)) return false;
|
||||
}
|
||||
return true;
|
||||
DoorController controller = doorControllers.get(door);
|
||||
return controller != null && controller.open();
|
||||
}
|
||||
|
||||
private void updatePlatforms(float dt) {
|
||||
@@ -1003,8 +1031,9 @@ public final class GameView extends View implements Runnable {
|
||||
stroke.setStrokeWidth(7f);
|
||||
canvas.drawLine(box.centerX(), box.centerY(), box.centerX() + (active ? 20 : -20), box.top - 28, stroke);
|
||||
if (item.type == LevelDefinition.SwitchType.TIMED && active) {
|
||||
Float remaining = timedSignals.get(item.signalId);
|
||||
drawWorldLabel(canvas, String.format(Locale.US, "%.1f", remaining == null ? 0f : remaining), box.centerX(), box.top - 52);
|
||||
float remaining = puzzleSystem.remainingSeconds(item.signalId);
|
||||
drawWorldLabel(canvas, String.format(Locale.US, "%.1f", remaining),
|
||||
box.centerX(), box.top - 52);
|
||||
}
|
||||
drawWorldLabel(canvas, active ? "ON" : "OFF", box.centerX(), box.bottom + 32);
|
||||
}
|
||||
@@ -2282,10 +2311,11 @@ public final class GameView extends View implements Runnable {
|
||||
ArrayList<Float> motionTimes = new ArrayList<>(platformStates.size());
|
||||
for (PlatformState state : platformStates) motionTimes.add(state.motionTime);
|
||||
builder.platformMotionTimes(motionTimes);
|
||||
for (Map.Entry<String, Boolean> entry : toggleSignals.entrySet()) {
|
||||
PuzzleSystem.State puzzleState = puzzleSystem.snapshot();
|
||||
for (Map.Entry<String, Boolean> entry : puzzleState.toggleSignals().entrySet()) {
|
||||
builder.toggleSignal(entry.getKey(), Boolean.TRUE.equals(entry.getValue()));
|
||||
}
|
||||
for (Map.Entry<String, Float> entry : timedSignals.entrySet()) {
|
||||
for (Map.Entry<String, Float> entry : puzzleState.timedSignals().entrySet()) {
|
||||
builder.timedSignal(entry.getKey(), Math.max(0f, entry.getValue()));
|
||||
}
|
||||
for (Map.Entry<String, Boolean> entry : previousSwitchContact.entrySet()) {
|
||||
@@ -2316,12 +2346,10 @@ public final class GameView extends View implements Runnable {
|
||||
levelElapsed = snapshot.elapsedSeconds();
|
||||
deaths = snapshot.deaths();
|
||||
respawnTimer = snapshot.respawnSeconds();
|
||||
toggleSignals.clear();
|
||||
toggleSignals.putAll(snapshot.toggleSignals());
|
||||
timedSignals.clear();
|
||||
timedSignals.putAll(snapshot.timedSignals());
|
||||
previousSwitchContact.clear();
|
||||
previousSwitchContact.putAll(snapshot.previousSwitchContacts());
|
||||
puzzleSystem.restore(snapshot.toggleSignals(), snapshot.timedSignals(),
|
||||
snapshot.previousSwitchContacts());
|
||||
for (int i = 0; i < platformStates.size(); i++) {
|
||||
PlatformState state = platformStates.get(i);
|
||||
state.motionTime = snapshot.platformMotionTimes().get(i);
|
||||
@@ -2362,6 +2390,7 @@ public final class GameView extends View implements Runnable {
|
||||
corpseManager.transform(corpse, CorpseManager.Transformation.PINNED);
|
||||
}
|
||||
}
|
||||
updateSignals(0f, false);
|
||||
|
||||
leftPressed = false;
|
||||
rightPressed = false;
|
||||
@@ -2370,7 +2399,6 @@ public final class GameView extends View implements Runnable {
|
||||
userPaused = true;
|
||||
paused = false;
|
||||
lastFootstepAt = levelElapsed;
|
||||
previousAllDoorsOpen = allDoorsOpenNow();
|
||||
cameraController.reset();
|
||||
cameraX = cameraY = cameraShakeX = cameraShakeY = 0f;
|
||||
lastFrameNanos = System.nanoTime();
|
||||
@@ -2448,4 +2476,8 @@ public final class GameView extends View implements Runnable {
|
||||
public CorpseManager.Diagnostics corpseDiagnosticsForAutomation() {
|
||||
return corpseManager.diagnostics();
|
||||
}
|
||||
|
||||
public PuzzleSystem.Diagnostics puzzleDiagnosticsForAutomation() {
|
||||
return puzzleSystem.diagnostics();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user