Skip to content

Commit 62a9696

Browse files
Mobile and dynamic screen sizes (#33)
* feat: add touch support * support touch and mobile devices (Closes #25 #26) * changes in rollup, action, state and stf for the same
1 parent b9da1f3 commit 62a9696

23 files changed

+163
-318
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
**/*.env
33
node_modules/
44
*.wasm
5+
.vscode

game/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@ yarn-error.log*
3434
# typescript
3535
*.tsbuildinfo
3636
next-env.d.ts
37+
.vscode/*

game/.vscode/settings.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"typescript.tsdk": "node_modules/typescript/lib"
3+
}

game/src/app/page.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ export default function Main() {
1818
const setupGame = async () => {
1919
setLoading(true);
2020
await Promise.all([fetchMruInfo(), fetchLeaderboard()]);
21+
setLoading(false);
2122
const connector = connectors[0];
2223
await connector.getProvider();
23-
setLoading(false);
2424
};
2525
setupGame();
2626
}, [connectors]);
@@ -43,15 +43,18 @@ export default function Main() {
4343
};
4444

4545
const renderContinueButton = () => {
46+
const text = isConnecting ? "Connecting Wallet..." : "Connect Wallet";
4647
return (
4748
<div className="flex flex-col justify-center">
48-
<Button onClick={connectWallet}>Connect Wallet</Button>
49+
<Button isDisabled={isConnecting} onClick={connectWallet}>
50+
{text}
51+
</Button>
4952
<div className="text-center mt-4">To play game</div>
5053
</div>
5154
);
5255
};
5356

54-
if (isLoading || isConnecting) {
57+
if (isLoading) {
5558
return <div className="text-3xl w-full text-center">Loading Game...</div>;
5659
}
5760

game/src/app/useAction.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const useAction = () => {
88
const { signTypedDataAsync } = useSignTypedData();
99

1010
if (!address) {
11-
throw new Error("No address found");
11+
return { submit: () => Promise.resolve() };
1212
}
1313

1414
const mruInfo = getFromStore(StorageKey.MRU_INFO);

game/src/components/button.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
interface Props {
22
children: React.ReactNode;
33
onClick: () => void;
4+
isDisabled?: boolean;
45
}
56

6-
export const Button: React.FC<Props> = ({ children, onClick }) => {
7+
export const Button: React.FC<Props> = ({ children, onClick, isDisabled }) => {
78
return (
89
<button
9-
className="select-none p-2 px-4 border-2 cursor-pointer shadow-sm hover:bg-gray-800 hover:border-gray-400"
10+
className="select-none p-2 sm:px-2 md:px-3 lg:px-4 border-2 cursor-pointer shadow-sm hover:bg-gray-800 hover:border-gray-400"
11+
disabled={isDisabled}
1012
onClick={onClick}
1113
>
1214
{children}

game/src/components/game.tsx

+16-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22
import { useAction } from "@/app/useAction";
33
import { Comets } from "@/core/comets";
4+
import { HEIGHT, updateScreenDimensions, WIDTH } from "@/core/constants";
45
import { KeyManager } from "@/core/keys";
56
import { init } from "@/core/loop";
67
import { TickRecorder } from "@/core/tickRecorder";
@@ -12,6 +13,7 @@ import {
1213
} from "@/rpc/storage";
1314
import { useEffect, useRef } from "react";
1415

16+
const NAV_WIDTH = 80;
1517
const LOOP_INTERVAL = 1000;
1618
export const Game = () => {
1719
const { submit } = useAction();
@@ -37,6 +39,8 @@ export const Game = () => {
3739
console.error("Error sending ticks", (e as Error).message);
3840
} finally {
3941
removeFromStore(StorageKey.GAME_ID);
42+
// manual delay
43+
await new Promise((resolve) => setTimeout(resolve, 2000));
4044
game.current?.switchToMainPage();
4145
isEnding.current = false;
4246
}
@@ -46,15 +50,18 @@ export const Game = () => {
4650
if (isStarting.current) {
4751
return;
4852
}
49-
5053
isStarting.current = true;
5154
const res = await submit("startGame", {
5255
timestamp: Date.now(),
56+
width: WIDTH,
57+
height: HEIGHT,
5358
});
54-
const gameId = res.logs[0].value;
55-
console.debug("Game started", gameId);
56-
addToStore(StorageKey.GAME_ID, gameId);
57-
isStarting.current = false;
59+
const gameId = res.logs?.[0]?.value;
60+
if (gameId) {
61+
console.debug("Game started", gameId);
62+
addToStore(StorageKey.GAME_ID, gameId);
63+
isStarting.current = false;
64+
}
5865
return gameId;
5966
};
6067

@@ -68,6 +75,10 @@ export const Game = () => {
6875
};
6976

7077
useEffect(() => {
78+
const width = Math.floor(0.9 * screen.width);
79+
const height = Math.floor(0.8 * (screen.height - NAV_WIDTH));
80+
updateScreenDimensions(width, height);
81+
7182
createGame();
7283

7384
const tick = setTimeout(() => {

game/src/components/navbar.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ export const Navbar = () => {
1616
}, [chainId]);
1717

1818
return (
19-
<nav className="px-12 py-4">
19+
<nav className="sm:px-2 lg:px-10 md:px-8 py-4 ">
2020
<div className="flex justify-between items-center">
21-
<div className="text-2xl p-2 px-4 select-none">Comets</div>
21+
<div className="text-2xl p-2 lg:px-4 select-none">Comets</div>
2222
{!!address && (
23-
<div className="flex gap-4 text-center items-center">
23+
<div className="flex flex-wrap gap-4 text-center items-center">
2424
<div>{formatAddress(address)}</div>
2525
<Button onClick={() => disconnect()}>Disconnect</Button>
2626
</div>

game/src/core/comets.ts

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export class Comets {
4545
this.gameMode.on("done", (_, world) => {
4646
Sound.off();
4747
this.gameEndHandler(world.score);
48+
this.keyManager.clear();
4849
});
4950
};
5051

game/src/core/constants.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { Rect } from "../comets";
2+
export let WIDTH = 960;
3+
export let HEIGHT = 720;
4+
5+
export const updateScreenDimensions = (width: number, height: number) => {
6+
WIDTH = width;
7+
HEIGHT = height;
8+
};
29

3-
export const WIDTH = 960;
4-
export const HEIGHT = 720;
510
export const OBJECT_SCALE = 0.75;
611
export const OFF_RECT = 120 * OBJECT_SCALE;
712
export const SHIP_RECT: Rect = {

game/src/core/draw.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,18 @@ export class Draw {
378378

379379
signPrompt() {
380380
this.screen.draw.text3(
381-
"You'll be prompted to sign Proof of Gameplay",
381+
"You'll be prompted",
382+
this.screen.font.large,
383+
(width) => {
384+
return {
385+
x: this.screen.width2 - width / 2,
386+
y: screen.height / 3.5,
387+
};
388+
}
389+
);
390+
391+
this.screen.draw.text3(
392+
`to sign "Proof of Gameplay"`,
382393
this.screen.font.large,
383394
(width) => {
384395
return {

game/src/core/gameMode.ts

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { IGameState, Rect, VirtualInput } from "../comets";
22
import { Collisions } from "./collisions";
3-
import { HEIGHT, WIDTH } from "./constants";
3+
import { HEIGHT, updateScreenDimensions, WIDTH } from "./constants";
44
import { EventSource } from "./events";
55
import Global from "./global";
66
import { Object2D } from "./object2d";
@@ -26,6 +26,7 @@ type Options = {
2626
tickRecorder?: {
2727
recordInputs: (ticks: VirtualInput) => void;
2828
};
29+
screenDimensions?: { width: number; height: number };
2930
};
3031
export class GameMode extends EventSource implements IGameState {
3132
bounds: Object2D[] = [];
@@ -41,13 +42,20 @@ export class GameMode extends EventSource implements IGameState {
4142
recordInputs: () => {},
4243
},
4344
gameId: options?.gameId,
45+
screenDimensions: { width: WIDTH, height: HEIGHT },
4446
};
4547

46-
if (options?.tickRecorder) {
47-
this.options.tickRecorder = options.tickRecorder;
48+
const { tickRecorder, gameId, screenDimensions } = options || {};
49+
50+
if (tickRecorder) {
51+
this.options.tickRecorder = tickRecorder;
52+
}
53+
if (gameId) {
54+
updateSeed(gameId);
4855
}
49-
if (this.options.gameId) {
50-
updateSeed(this.options.gameId);
56+
if (screenDimensions) {
57+
const { width, height } = screenDimensions;
58+
updateScreenDimensions(width, height);
5159
}
5260
}
5361

game/src/core/keys.ts

+55-44
Original file line numberDiff line numberDiff line change
@@ -35,50 +35,57 @@ export class KeyManager {
3535
this.keys[e.keyCode] = false;
3636
};
3737

38-
// this.mc = new Hammer.Manager(stage);
39-
40-
// const pan = new Hammer.Pan();
41-
// const tap = new Hammer.Tap();
42-
// const pinch = new Hammer.Pinch({
43-
// enable: true,
44-
// });
45-
46-
// this.mc.add(pan);
47-
// this.mc.add(tap, {
48-
// interval: 50,
49-
// });
50-
// this.mc.add(pinch);
51-
52-
// this.mc.on("panup", () => {
53-
// this.thrust(true);
54-
// });
55-
56-
// this.mc.on("panleft", () => {
57-
// this.rotateLeft(true);
58-
// });
59-
60-
// this.mc.on("panright", () => {
61-
// this.rotateRight(true);
62-
// });
63-
64-
// this.mc.on("panend", () => {
65-
// this.thrust(false);
66-
// this.rotateLeft(false);
67-
// this.rotateRight(false);
68-
// });
69-
70-
// this.mc.on("tap", () => {
71-
// this.fire(true);
72-
// this.touched = true;
73-
// });
74-
75-
// this.mc.on("pinchout", () => {
76-
// this.hyperspace(true);
77-
// });
78-
79-
// this.mc.on("pinchend", () => {
80-
// this.hyperspace(false);
81-
// });
38+
const isTouchDevice = "ontouchstart" in window;
39+
if (isTouchDevice) {
40+
console.log("Touch device detected");
41+
const Hammer = require("hammerjs");
42+
43+
this.mc = new Hammer.Manager(stage);
44+
45+
const pan = new Hammer.Pan();
46+
const tap = new Hammer.Tap();
47+
const pinch = new Hammer.Pinch({
48+
enable: true,
49+
});
50+
51+
this.mc.add(pan);
52+
this.mc.add(tap, {
53+
interval: 50,
54+
});
55+
this.mc.add(pinch);
56+
57+
this.mc.on("panup", () => {
58+
this.thrust(true);
59+
});
60+
61+
this.mc.on("panleft", () => {
62+
this.rotateLeft(true);
63+
});
64+
65+
this.mc.on("panright", () => {
66+
this.rotateRight(true);
67+
});
68+
69+
this.mc.on("panend", () => {
70+
this.thrust(false);
71+
this.rotateLeft(false);
72+
this.rotateRight(false);
73+
});
74+
75+
this.mc.on("tap", () => {
76+
this.fire(true);
77+
this.enter(true);
78+
this.touched = true;
79+
});
80+
81+
this.mc.on("pinchout", () => {
82+
this.hyperspace(true);
83+
});
84+
85+
this.mc.on("pinchend", () => {
86+
this.hyperspace(false);
87+
});
88+
}
8289
}
8390

8491
update() {
@@ -168,6 +175,10 @@ export class KeyManager {
168175
this.keys[Keys.FIRE] = active;
169176
};
170177

178+
private enter = (active: boolean) => {
179+
this.keys[Keys.START] = active;
180+
};
181+
171182
private hyperspace = (active: boolean) => {
172183
this.keys[Keys.HYPERSPACE] = active;
173184
};

game/src/core/screen.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,11 @@ export class Screen implements Rect {
4242
this.width2 = this.width / 2;
4343
this.height2 = this.height / 2;
4444

45-
this._fontXL = 48;
46-
this._fontL = 24;
47-
this._fontM = 18;
48-
this._fontS = 10;
45+
const factor = this.width < 800 ? 0.5 : 1;
46+
this._fontXL = 48 * factor;
47+
this._fontL = 24 * factor;
48+
this._fontM = 18 * factor;
49+
this._fontS = 10 * factor;
4950
this._objectScale = OBJECT_SCALE;
5051

5152
this._pointSize = 4 * this._objectScale;

game/src/rpc/storage.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export enum StorageKey {
33
GAME_ID = "game_id",
44
LEADERBOARD = "leaderboard",
55
LAST_SCORE = "lastScore",
6+
SCREEN_DIMENSIONS = "screenDimensions",
67
}
78

89
export const addToStore = (key: StorageKey, value: any) => {

rollup/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ stackr_build/
66
dist/
77
.DS_Store
88
*.sqlite
9+
.vscode/*

0 commit comments

Comments
 (0)