Skip to content

Commit 4ea943e

Browse files
committed
Merge branch 'main' into stage
2 parents 1920506 + 34dcc3a commit 4ea943e

File tree

20 files changed

+334
-491
lines changed

20 files changed

+334
-491
lines changed

public/assets/thumbnail1-2.png

381 KB
Loading

public/assets/thumbnail1-3.png

381 KB
Loading

routes/+page.svelte

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,62 @@
1-
<!-- <h1>Welcome!</h1>
2-
3-
<section>
4-
<h2>World 1</h2>
5-
<a href="/game?stage=1">Stage 1</a>
6-
<a href="/game?stage=2">Stage 2</a>
7-
<a href="/game?stage=3">Stage 3</a>
8-
<a href="/game?stage=4">Stage 4</a>
9-
<a href="/game?stage=5">Stage 5</a>
10-
<a href="/game?stage=6">Stage 6</a>
11-
<a href="/game?stage=7">Stage 7</a>
12-
<a href="/game?stage=8">Stage 8</a>
13-
</section>
14-
15-
<section>
16-
<h2>World 3</h2>
17-
<a href="/game?stage=3-1">Stage 1</a>
18-
<a href="/game?stage=3-2">Stage 2</a>
19-
<a href="/game?stage=3-3">Stage 3</a>
20-
<a href="/game?stage=3-4">Stage 4</a>
21-
</section>
22-
23-
<section>
24-
<h2>World 4</h2>
25-
<a href="/game?stage=4-1">Stage 1</a>
26-
<a href="/game?stage=4-2">Stage 2</a>
27-
</section>
28-
29-
<p>
30-
<a href="/stage-select">Stage-Select</a>
31-
</p> -->
32-
331
<script lang="ts">
34-
import { onDestroy } from "svelte";
2+
import { onDestroy, onMount } from "svelte";
3+
import "@/ui-components/menu/menu.css";
354
5+
onMount(() => {
6+
const userAgent = navigator.userAgent.toLowerCase();
7+
if (
8+
userAgent.includes("android") ||
9+
userAgent.match(/iphone|ipad|ipod/) ||
10+
(userAgent.includes("macintosh") && navigator.maxTouchPoints >= 1) /* recent iPad */
11+
) {
12+
window.alert("このゲームはPC用です。キーボードが接続されていない端末ではプレイできません。");
13+
}
14+
});
3615
onDestroy(() => {
3716
// document.body.style.backgroundColor = "black";
3817
});
18+
19+
let btn1: HTMLElement;
20+
let btn2: HTMLElement;
21+
function handleKeyDown(e: KeyboardEvent) {
22+
const buttons = [btn1, btn2];
23+
const currentIndex = buttons.indexOf(document.activeElement as HTMLElement);
24+
if (e.key === "ArrowUp") {
25+
if (currentIndex > 0) {
26+
buttons[currentIndex - 1].focus();
27+
} else {
28+
buttons[buttons.length - 1].focus();
29+
}
30+
e.preventDefault();
31+
} else if (e.key === "ArrowDown") {
32+
if (currentIndex < buttons.length - 1) {
33+
buttons[currentIndex + 1].focus();
34+
} else {
35+
buttons[0].focus();
36+
}
37+
e.preventDefault();
38+
} else if (e.key === " ") {
39+
(document.activeElement as HTMLElement)?.click?.();
40+
e.preventDefault();
41+
}
42+
}
43+
$effect(() => {
44+
document.addEventListener("keydown", handleKeyDown);
45+
return () => {
46+
document.removeEventListener("keydown", handleKeyDown);
47+
};
48+
});
3949
</script>
4050

4151
<div id="container" class="fixed inset-0">
4252
<div class="fixed inset-0 backdrop-blur-xs">
4353
<div class="flex flex-col gap-5 mt-30 m-10 p-5 bg-yellow-400/85 rounded-lg">
44-
<h1 class="text-center mt-6 mb-2">Shortcut Game</h1> <!--TODO: 題名-->
45-
<a class="btn modal-btn text-2xl" href="/game?stage=1"> Start </a>
46-
<a class="btn modal-btn text-2xl" href="/stage-select"> Stage Select </a>
54+
<h1 class="text-center mt-6 mb-2">Shortcut Game</h1>
55+
<!--TODO: 題名-->
56+
<a class="btn modal-btn text-2xl" href="/game?stage=1-1" bind:this={btn1} tabindex="1"> Start </a>
57+
<a class="btn modal-btn text-2xl" href="/stage-select" bind:this={btn2} tabindex="2"> Stage Select </a>
4758
<p class="bg-white/90 p-2 rounded-lg">
48-
Move: &#x21E6; &#x21E8; or A D<br />
59+
Move: &#x21E6; &#x21E8; or A D<br />
4960
Jump: &#x21E7; or W or Space<br />
5061
Ability: Ctrl + ?
5162
</p>
@@ -56,10 +67,13 @@ onDestroy(() => {
5667
<style>
5768
#container {
5869
background-color: black;
59-
background-image: url('/assets/home.png'), url('/assets/home-block.png');
70+
background-image: url("/assets/home.png"), url("/assets/home-block.png");
6071
background-size: 100%, 100%;
6172
background-repeat: no-repeat, repeat-y;
62-
background-position: left top, left, top;
73+
background-position:
74+
left top,
75+
left,
76+
top;
6377
backdrop-filter: blur(10px);
6478
}
6579
</style>

routes/stage-select/+page.svelte

Lines changed: 120 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,154 @@
11
<script lang="ts">
22
import Key from "@/ui-components/Key.svelte";
33
import { onMount } from "svelte";
4+
import "@/ui-components/menu/menu.css";
5+
import { replaceState } from "$app/navigation";
46
5-
let w = "1";
7+
let world: string | null = null;
8+
let selected: number | null = null;
69
710
onMount(() => {
811
const params = new URLSearchParams(window.location.search);
9-
w = params.get("w") ?? "1";
12+
world = params.get("w") ?? "1";
13+
selected = Number(params.get("s") ?? "1") - 1;
1014
});
1115
1216
$: blocks = [
13-
{ label: "1", link: `/game?stage=${w}-1` },
14-
{ label: "2", link: `/game?stage=${w}-2` },
15-
{ label: "3", link: `/game?stage=${w}-3` },
16-
{ label: "4", link: `/game?stage=${w}-4` },
17+
{ label: "1", link: `/game?stage=${world}-1` },
18+
{ label: "2", link: `/game?stage=${world}-2` },
19+
{ label: "3", link: `/game?stage=${world}-3` },
20+
{ label: "4", link: `/game?stage=${world}-4` },
1721
];
1822
19-
let selected = 0;
2023
let lastKeyTime = 0;
24+
let lastKey: string | null = null;
2125
const KEY_REPEAT_DELAY = 180; // ms
2226
27+
function prevWorld() {
28+
// wを数値として1減らす(1未満にはしない)
29+
world = String(Math.max(1, Number(world) - 1));
30+
const url = new URL(window.location.href);
31+
url.searchParams.set("w", world);
32+
replaceState(url.toString(), {});
33+
}
34+
function nextWorld() {
35+
// wを数値として1増やす(4より大きくしない)
36+
world = String(Math.min(4, Number(world) + 1));
37+
const url = new URL(window.location.href);
38+
url.searchParams.set("w", world);
39+
replaceState(url.toString(), {});
40+
}
41+
function select(index: number): void {
42+
selected = index;
43+
const url = new URL(window.location.href);
44+
url.searchParams.set("s", String(index + 1));
45+
replaceState(url.toString(), {});
46+
}
47+
2348
function handleKey(e: KeyboardEvent): void {
2449
const now = Date.now();
2550
if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
26-
if (now - lastKeyTime < KEY_REPEAT_DELAY) {
51+
if (lastKey === e.key && now - lastKeyTime < KEY_REPEAT_DELAY) {
2752
return;
2853
}
2954
lastKeyTime = now;
55+
lastKey = e.key;
56+
}
57+
58+
if (selected === null) {
59+
return;
3060
}
3161
3262
if (e.key === "ArrowRight") {
33-
selected = (selected + 1) % blocks.length;
63+
if (selected === blocks.length - 1) {
64+
if (world !== "4") {
65+
nextWorld();
66+
select(0);
67+
}
68+
} else {
69+
select(selected + 1);
70+
}
3471
} else if (e.key === "ArrowLeft") {
35-
selected = (selected - 1 + blocks.length) % blocks.length;
72+
if (selected === 0) {
73+
if (world !== "1") {
74+
prevWorld();
75+
select(blocks.length - 1);
76+
}
77+
} else {
78+
select(selected - 1);
79+
}
3680
} else if (e.key === "Enter" || e.key === " ") {
3781
window.location.href = blocks[selected].link;
82+
} else if (e.key === "Escape") {
83+
window.location.href = "/";
84+
e.preventDefault();
3885
}
3986
}
40-
41-
function handleClick(index: number): void {
42-
selected = index;
87+
function handleKeyUp() {
88+
lastKeyTime = 0;
89+
lastKey = null;
4390
}
4491
4592
let container: HTMLDivElement | null = null;
4693
4794
onMount(() => {
48-
container?.focus();
95+
document.addEventListener("keydown", handleKey);
96+
document.addEventListener("keyup", handleKeyUp);
97+
return () => {
98+
document.removeEventListener("keydown", handleKey);
99+
document.removeEventListener("keyup", handleKeyUp);
100+
};
49101
});
50-
51-
// Todo?: ワールド選択の矢印をクリックすると、矢印キーを押してもステージが移動できない(=ブロックをクリックする必要あり)
52-
// TODO?: Enterキーを押すと、カーソルがある場所のブロックの色が変わる(正しいステージには飛ぶから実害はない)
53-
// Todo: 画像をステージごとに変える
54-
// TODO: ゲームメニューからstage-selectに飛ばす
55102
</script>
56103

57104
<div id="container" class="fixed inset-0">
58-
<div class="fixed inset-0 backdrop-blur-xs">
59-
60-
<div class="p-10 text-8xl text-center flex items-center justify-center gap-8">
105+
<div class="w-full h-full py-8 backdrop-blur-xs flex flex-col">
106+
<div class="">
107+
<button class="btn modal-btn text-xl ml-8 mb-6 w-max! px-4!">
108+
<a href="/">
109+
&lt;
110+
<Key key="Esc" enabled />
111+
Back to Main Menu
112+
</a>
113+
</button>
114+
</div>
115+
<div class="text-7xl text-center flex items-center justify-center gap-8">
61116
<!-- 左矢印ボタン -->
62117
<button
63-
class="px-4 select-none cursor-pointer"
118+
class="appearance-none focus:outline-none px-4 select-none cursor-pointer {Number(world) <= 1
119+
? 'invisible'
120+
: ''} hover:-translate-y-1 hover:text-gray-700 active:translate-y-0 active:text-black"
64121
aria-label="前のワールド"
65-
on:click={() => {
66-
// wを数値として1減らす(1未満にはしない)
67-
const next = Math.max(1, Number(w) - 1);
68-
const url = new URL(window.location.href);
69-
url.searchParams.set("w", String(next));
70-
window.location.href = url.toString();
71-
}}
72-
disabled={Number(w) <= 1}
122+
on:click={prevWorld}
123+
disabled={Number(world) <= 1}
73124
>
74125
&lt;
75126
</button>
76-
<span>W{w}</span>
127+
<span>World {world}</span>
77128
<!-- 右矢印ボタン -->
78129
<button
79-
class="px-4 select-none cursor-pointer"
130+
class="appearance-none focus:outline-none px-4 select-none cursor-pointer {Number(world) >= 4
131+
? 'invisible'
132+
: ''} hover:-translate-y-1 hover:text-gray-700 active:translate-y-0 active:text-black"
80133
aria-label="次のワールド"
81-
on:click={() => {
82-
// wを数値として1増やす(4より大きくしない)
83-
const next = Math.min(4, Number(w) + 1);
84-
const url = new URL(window.location.href);
85-
url.searchParams.set("w", String(next));
86-
window.location.href = url.toString();
87-
}}
88-
disabled={Number(w) >= 4}
134+
on:click={nextWorld}
135+
disabled={Number(world) >= 4}
89136
>
90137
&gt;
91138
</button>
92139
</div>
93140

94-
<div class="flex justify-center items-center h-64">
95-
<div
96-
bind:this={container}
97-
role="button"
98-
tabindex="0"
99-
on:keydown={handleKey}
100-
class="flex outline-none items-center"
101-
>
141+
<div class="flex justify-center items-center grow-1">
142+
<div role="button" tabindex="0" class="flex outline-none items-center">
102143
{#each blocks as block, i}
103144
<button
104145
type="button"
105-
class={`bg-white border-6 pt-8 pb-6 pl-8 pr-6 transition-colors duration-200 text-7xl cursor-pointer ${
106-
selected === i ? 'border-red-500 ring ring-red-500' : 'border-base'
146+
class={`appearance-none focus:outline-none bg-white border-6 pt-8 pb-6 pl-8 pr-6 transition-colors duration-200 text-7xl cursor-pointer ${
147+
selected === i
148+
? "border-red-500 ring ring-red-500 bg-amber-100!"
149+
: "border-base"
107150
}`}
108-
on:click={() => handleClick(i)}
151+
on:click={() => select(i)}
109152
>
110153
{block.label}
111154
</button>
@@ -116,11 +159,27 @@ onMount(() => {
116159
{/each}
117160
</div>
118161
</div>
119-
<div class="relative flex justify-center items-center h-96 mb-10">
162+
<div
163+
class="flex justify-center items-center basis-2/5 min-h-0 shrink grow-2"
164+
>
120165
<!-- 画像を中央に配置 -->
121-
<img src="/assets/thumbnaildev.png" alt="" class="h-80 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
166+
<div class="h-full">
167+
{#if selected !== null}
168+
{#key world}
169+
{#each { length: 4 } as _, idx}
170+
<img
171+
src="/assets/thumbnail{world}-{idx + 1}.png"
172+
alt=""
173+
class={["h-full skeleton", idx !== selected && "hidden"]}
174+
/>
175+
{/each}
176+
{/key}
177+
{/if}
178+
</div>
122179
<!-- テキストを画像の右側に配置 -->
123-
<div class="absolute left-1/2 top-1/2 -translate-y-1/2 ml-80 flex flex-col items-start bg-white/90 p-4 rounded-lg border-2">
180+
<div
181+
class="flex-none w-max max-h-full flex flex-col items-start bg-white/90 p-4 m-4 rounded-lg border-2"
182+
>
124183
Press <Key key="Enter" enabled /> or <Key key="Space" enabled /> to start
125184
</div>
126185
</div>
@@ -130,10 +189,13 @@ onMount(() => {
130189
<style>
131190
#container {
132191
background-color: black;
133-
background-image: url('/assets/home.png'), url('/assets/home-block.png');
192+
background-image: url("/assets/home.png"), url("/assets/home-block.png");
134193
background-size: 100%, 100%;
135194
background-repeat: no-repeat, repeat-y;
136-
background-position: left top, left, top;
195+
background-position:
196+
left top,
197+
left,
198+
top;
137199
backdrop-filter: blur(10px);
138200
}
139-
</style>
201+
</style>

src/stages.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
11
import { stagePreprocess } from "./stage-preprocessor.ts";
2-
import { stage1 } from "./stages/1.ts";
3-
import { stage2 } from "./stages/2.ts";
4-
import { stage3 } from "./stages/3.ts";
5-
import { stage4 } from "./stages/4.ts";
6-
import { stage5 } from "./stages/5.ts";
7-
import { stage6 } from "./stages/6.ts";
8-
import { stage7 } from "./stages/7.ts";
9-
import { stage8 } from "./stages/8.ts";
102
import type { StageDefinition } from "./stages/type.ts";
113
import { world1 } from "./stages/world1.ts";
124
import { world2 } from "./stages/world2.ts";
@@ -15,14 +7,6 @@ import { world4 } from "./stages/world4.ts";
157
import { world5 } from "./stages/world5.ts";
168

179
export const stages = new Map<string, StageDefinition>([
18-
["1", stage1],
19-
["2", stage2],
20-
["3", stage3],
21-
["4", stage4],
22-
["5", stage5],
23-
["6", stage6],
24-
["7", stagePreprocess(stage7)],
25-
["8", stage8],
2610
["1-1", world1.stage1],
2711
["1-2", world1.stage2],
2812
["1-3", world1.stage3],

0 commit comments

Comments
 (0)