Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,14 @@ Node版との差分(意図的な改善):
- あくまで表示を先出しするだけで、**実際に引けるかは常にサーバーが決める**
(端末の時計は書き換えられる)。残り時間は8時間(最長のクールダウン)で頭打ち。
別端末では保存が無いので、従来どおり peek の応答から復元される。
- **問い合わせが終わるまで「引く」は押せない**(`statusChecked`)。保存済みの状態で
先に画面を出すため、確認前でもボタンが見えている。ここで押せると、演出の最中に
peek の応答が返って `state` を上書きし、**演出が畳まれて引く前の画面に戻る**
(リロードされたように見える)。併せて、**その問い合わせより後に演出が始まっていたら
応答を捨てる**(`sceneCount` の世代で判定)。「今 animating か」で判定してはいけない
— 演出が結果より先に終わったときの取り直し(`onLanded`)はまだ `animating` のまま
呼ぶため、それを捨てると演出が閉じずに固まる。演出中にクールダウンが明けたときの
タイマーも `state` を触らない。

### フロント

Expand Down
36 changes: 35 additions & 1 deletion web/pages/omikuji.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,19 @@
<!-- 引ける -->
<div v-else-if="state === 'available'" class="my-5">
<div class="omikuji-box mx-auto mb-4"><i class="fas fa-torii-gate"></i></div>
<button class="btn btn-lg btn-accent px-5" @click="startScene">
<!-- 状態の問い合わせが終わるまで押せない。保存済みの状態で先に画面を
出しているため(#236)、確認前でもボタンが見えている。ここで押せると
演出の最中に問い合わせの応答が返り、状態が上書きされて演出が消える。 -->
<button
class="btn btn-lg btn-accent px-5"
:disabled="!statusChecked"
@click="startScene"
>
おみくじを引く
</button>
<div v-if="!statusChecked" class="mt-2 text-muted small">
<i class="fas fa-fw fa-spinner fa-spin"></i> お伺いを立てています…
</div>
<div v-if="result" class="mt-3 text-muted small">
(前回の結果は下に表示されています)
</div>
Expand Down Expand Up @@ -108,6 +118,11 @@ export default {
data() {
return {
state: "loading", // loading | available | animating | cooldown | empty(物理乱数枯渇) | error
// サーバーへの状態問い合わせが1度でも完了したか。保存済みの状態で先に
// 画面を出すので、これが false の間は「引く」を押せないようにする。
statusChecked: false,
// 演出を始めた回数。飛行中の問い合わせの応答が古いかどうかの判定に使う。
sceneCount: 0,
result: null,
pendingResult: null, // 演出中に保持(着地まで表示しない)
remaining: 0, // 次に引けるまでの秒
Expand Down Expand Up @@ -157,6 +172,8 @@ export default {
if (this.state !== "cooldown" && this.state !== "available") {
this.state = "loading";
}
// 応答が返るまでに演出が始まったかを見分けるための世代。
const sceneAtRequest = this.sceneCount;
const token = await this.getToken();
if (!token) return;
try {
Expand All @@ -165,14 +182,29 @@ export default {
{ github_id: this.user.github_id, peek: true },
{ headers: { Authorization: `Bearer ${token}` } }
);
// この問い合わせを投げた後に演出が始まっていたら、応答はもう古い。
// 抽選側が状態を持っているので触らない(触ると演出が畳まれて引く前の
// 画面に戻ってしまう)。
//
// 「今 animating か」で判定してはいけない。演出が結果より先に終わった
// ときの取り直し(onLanded)はまだ animating のまま呼ぶため、応答を
// 捨てると演出が閉じずに固まる。
if (this.sceneCount !== sceneAtRequest) return;
this.applyResponse(res.data);
} catch (e) {
if (this.sceneCount !== sceneAtRequest) return;
this.state = "error";
} finally {
this.statusChecked = true;
}
},
// 「引く」→ 演出(鈴の緒儀式)を開始。実際の抽選は鈴が鳴った時(onRang)。
startScene() {
if (this.state === "animating") return;
// 確認前は引かせない(ボタンも disabled だが、念のため入口でも止める)。
if (!this.statusChecked) return;
// 飛行中の問い合わせがあれば、その応答は古いものとして捨てさせる。
this.sceneCount++;
this.pendingResult = null;
this._pendingRemaining = 0;
this.state = "animating";
Expand Down Expand Up @@ -272,6 +304,8 @@ export default {
clearInterval(this.timerId);
this.timerId = null;
this.remaining = 0;
// 演出中にクールダウンが明けても状態を触らない(演出が消えてしまう)。
if (this.state === "animating") return;
// 引ける状態へ。前回結果は残しつつボタンを出す。
this.state = "available";
saveOmikujiState(
Expand Down
Loading