-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhistory.ts
More file actions
141 lines (128 loc) · 3.97 KB
/
history.ts
File metadata and controls
141 lines (128 loc) · 3.97 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
import { get } from "svelte/store";
import { printCells } from "./grid.ts";
import { assert } from "./lib.ts";
import type { Context, StateSnapshot } from "./public-types.ts";
export function init(cx: Context) {
// there is no such thing as undo / redo event
document.addEventListener("keydown", (e) => {
if (!e.ctrlKey && !e.metaKey) return;
switch (e.key) {
case "z":
e.preventDefault();
undo(cx);
break;
case "y":
e.preventDefault();
redo(cx);
break;
}
});
record(cx);
}
// History については、 Discord の中川さんの画像を参照のこと
export function record(cx: Context) {
const h = createSnapshot(cx);
cx.history.update((prev) => {
if (prev.tree.length > prev.index + 1) {
// undo した後に record をする場合、履歴を切り詰める
prev.tree.length = prev.index + 1;
}
prev.tree.push(h);
prev.index = prev.tree.length - 1;
return prev;
});
}
export function undo(cx: Context) {
const history = get(cx.history);
// 最新に戻るため、記録しておく
if (history.index === history.tree.length - 1 && !get(cx.state).gameover) {
console.log("stashing...");
stash(cx);
}
// 0. 一般
// ... *記録*}-> 移動 -> {*記録* -> Action -> *記録*}
// ^ Snapshot はここのを使いたい
// ^ index は整合性のためここに置く ^ 1 個前のindex はここ
//
// 1. 開始位置まで戻る
// {*記録*} -> 移動 -> {*記録* -> Action -> *記録*}
// ^このsnapshotを使う ^1 個前の Snapshot はここ
// ^ 1 個前の index はここ
assert(history.index % 2 === 0, "history index looks very wrong");
const snapshotIndex = Math.max(0, history.index - 1);
const nextIndex = Math.max(0, history.index - 2);
const snapshot = history.tree[snapshotIndex];
history.index = nextIndex;
// 状態を巻き戻す
restore(cx, snapshot);
cx.history.set(history);
return {
x: snapshot.playerX,
y: snapshot.playerY,
};
}
export function redo(cx: Context) {
const history = get(cx.history);
// 0. 一般
// {*記録* -> Action -> *記録*} -> 移動 -> {*記録* -> Action -> *記録*} -> 移動 (optional)
// ^ Snapshot はここのを使いたい
// ^ 現在の index はここ ^ 次の index はここ
//
// 1. 最新に戻る -> stash に残っている snapshot に戻る
// 2. すでに最新に戻ったことがある -> 操作なし
const snapshot = history.tree[history.index + 2];
if (!snapshot) {
const stash = popStash(cx);
if (!stash) {
// case 2. すでに最新に戻ったことがある
return;
}
// case 1. 最新に戻る
restore(cx, stash);
return;
}
history.index += 2;
restore(cx, snapshot);
cx.history.set(history);
return;
}
// 状態を巻き戻す
function restore(cx: Context, ss: StateSnapshot) {
cx.state.set(structuredClone(ss.game));
cx.dynamic.player.x = ss.playerX;
cx.dynamic.player.y = ss.playerY;
cx.dynamic.player.facing = ss.playerFacing;
cx.grid.diffAndUpdateTo(cx, ss.game.cells);
cx.grid.clearFallableSprites(cx);
printCells(ss.game.cells, "restore");
}
function stash(cx: Context) {
cx.history.update((prev) => {
prev.stash = createSnapshot(cx);
return prev;
});
}
function popStash(cx: Context): StateSnapshot | undefined {
const history = get(cx.history);
if (history.stash) {
const stash = history.stash;
cx.history.update((prev) => {
prev.stash = undefined;
return prev;
});
return stash;
}
return undefined;
}
export function createSnapshot(cx: Context): StateSnapshot {
const game = get(cx.state);
const playerX = cx.dynamic.player.x;
const playerY = cx.dynamic.player.y;
const playerFacing = cx.dynamic.player.facing;
return {
game: structuredClone(game),
playerX,
playerY,
playerFacing,
};
}