Skip to content

Commit 0cc2a8c

Browse files
committed
add gamepad icons + refactor + clean up
1 parent 9492293 commit 0cc2a8c

File tree

11 files changed

+345
-112
lines changed

11 files changed

+345
-112
lines changed

src/main/services/preact-canvas/components/board/index.tsx

Lines changed: 40 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ import { Animator } from "src/main/rendering/animator";
1515
import { Renderer } from "src/main/rendering/renderer";
1616
import { putCanvas } from "src/main/utils/canvas-pool";
1717
import { cellFocusMode } from "src/main/utils/constants";
18+
import { gamepad } from "src/main/utils/gamepad";
1819
import { isFeaturePhone } from "src/main/utils/static-display";
1920
import { bind } from "src/utils/bind";
2021
import { StateChange } from "src/worker/gamelogic";
21-
import { Cell } from "src/worker/gamelogic/types";
22+
import { Cell, PlayMode } from "src/worker/gamelogic/types";
2223
import { GameChangeCallback } from "../../index";
2324
import {
2425
board,
@@ -45,24 +46,17 @@ export interface Props {
4546
renderer: Renderer;
4647
animator: Animator;
4748
dangerMode: boolean;
49+
interactive: boolean;
4850
gameChangeSubscribe: (f: GameChangeCallback) => void;
4951
gameChangeUnsubscribe: (f: GameChangeCallback) => void;
5052
onDangerModeChange: (v: boolean) => void;
5153
}
5254

53-
interface State {
54-
keyNavigation: boolean;
55-
}
56-
5755
interface SetFocusOptions {
5856
preventScroll?: boolean;
5957
}
6058

61-
export default class Board extends Component<Props, State> {
62-
state: State = {
63-
keyNavigation: false
64-
};
65-
59+
export default class Board extends Component<Props, {}> {
6660
private _canvas?: HTMLCanvasElement;
6761
private _table?: HTMLTableElement;
6862
private _buttons: HTMLButtonElement[] = [];
@@ -74,10 +68,6 @@ export default class Board extends Component<Props, State> {
7468
private _currentFocusableBtn?: HTMLButtonElement;
7569
private _tableContainer?: HTMLDivElement;
7670

77-
private _gamepadState: boolean[][] = [];
78-
private _gamepadIsTicking = false;
79-
private _gamepadStopTicking = false;
80-
8171
componentDidMount() {
8272
document.documentElement.classList.add("in-game");
8373
this._createTable(this.props.width, this.props.height);
@@ -105,23 +95,16 @@ export default class Board extends Component<Props, State> {
10595

10696
window.addEventListener("resize", this._onWindowResize);
10797
window.addEventListener("keyup", this._onGlobalKeyUp);
108-
// @ts-ignore This version of TS does not know about gamepadconnected
109-
window.addEventListener("gamepadconnected", this._onGamepadsUpdate);
110-
// @ts-ignore This version of TS does not know about gamepaddisconnected
111-
window.addEventListener("gamepaddisconnected", this._onGamepadsUpdate);
112-
this._gamepadStopTicking = false;
113-
this._initGamepads();
98+
gamepad.addButtonDownCallback(this._onGamepadButtonDown);
99+
gamepad.addButtonPressCallback(this._onGamepadButtonPress);
114100
}
115101

116102
componentWillUnmount() {
117103
document.documentElement.classList.remove("in-game");
118104
window.removeEventListener("resize", this._onWindowResize);
119105
window.removeEventListener("keyup", this._onGlobalKeyUp);
120-
// @ts-ignore This version of TS does not know about gamepadconnected
121-
window.removeEventListener("gamepadconnected", this._onGamepadsUpdate);
122-
// @ts-ignore This version of TS does not know about gamepaddisconnected
123-
window.removeEventListener("gamepaddisconnected", this._onGamepadsUpdate);
124-
this._gamepadStopTicking = true;
106+
gamepad.removeButtonDownCallback(this._onGamepadButtonDown);
107+
gamepad.removeButtonPressCallback(this._onGamepadButtonPress);
125108
this.props.gameChangeUnsubscribe(this._doManualDomHandling);
126109
this.props.renderer.stop();
127110
this.props.animator.stop();
@@ -159,6 +142,9 @@ export default class Board extends Component<Props, State> {
159142

160143
@bind
161144
private _onGlobalKeyUp(event: KeyboardEvent) {
145+
if (!this.props.interactive) {
146+
return;
147+
}
162148
// This returns the focus to the board when one of these keys is pressed (on feature phones
163149
// only). This means the user doesn't have to manually refocus the board.
164150
if (
@@ -177,86 +163,52 @@ export default class Board extends Component<Props, State> {
177163
}
178164

179165
@bind
180-
private _onGamepadsUpdate() {
181-
this._initGamepads();
182-
}
183-
184-
private _initGamepads() {
185-
this._gamepadState = [];
186-
if (!this._gamepadIsTicking) {
187-
this._gamepadIsTicking = true;
188-
requestAnimationFrame(this._gamepadTick);
189-
}
190-
}
191-
192-
@bind
193-
private _gamepadTick() {
194-
const gamepads = navigator.getGamepads();
195-
196-
const oldState = this._gamepadState;
197-
this._gamepadState = [];
198-
199-
let connectedGamepads = 0;
200-
for (let i = 0; i < gamepads.length; i++) {
201-
const gamepad = gamepads[i];
202-
this._gamepadState[i] = [];
203-
if (gamepad !== null) {
204-
connectedGamepads++;
205-
for (let j = 0; j < gamepad.buttons.length; j++) {
206-
const button = gamepad.buttons[j];
207-
this._gamepadState[i][j] = button.pressed;
208-
}
209-
}
166+
private _onGamepadButtonDown(buttonId: number) {
167+
if (!this.props.interactive) {
168+
return;
210169
}
211-
212-
for (let i = 0; i < this._gamepadState.length; i++) {
213-
for (let j = 0; j < this._gamepadState[i].length; j++) {
214-
const oldValue = oldState[i] && oldState[i][j];
215-
if (this._gamepadState[i][j] && !oldValue) {
216-
this._onGamepadPress(j);
217-
}
218-
if (!this._gamepadState[i][j] && oldValue) {
219-
console.log("release", i, j);
170+
switch (buttonId) {
171+
case 0: // A
172+
case 4: /* Left Shoulder Button */ {
173+
const button = document.activeElement as HTMLButtonElement;
174+
if (!button) {
175+
return;
220176
}
177+
this.simulateClick(button);
178+
break;
221179
}
222-
}
223-
224-
// Continue ticking if there are still gamepads connected, else stop ticking
225-
// to avoid unnecessary CPU usage.
226-
if (connectedGamepads > 0 && !this._gamepadStopTicking) {
227-
requestAnimationFrame(this._gamepadTick);
228-
} else {
229-
this._gamepadIsTicking = false;
230-
}
231-
}
232-
233-
private _onGamepadPress(id: number) {
234-
switch (id) {
235-
case 0: {
236-
// A
180+
case 2: // X
181+
case 5: /* Right Shoulder Button */ {
237182
const button = document.activeElement as HTMLButtonElement;
238183
if (!button) {
239184
return;
240185
}
241-
this.simulateClick(button);
186+
this.simulateClick(button, true);
242187
break;
243188
}
244189
case 1: // B
245190
this._toggleDangerMode();
246191
break;
192+
}
193+
}
194+
195+
@bind
196+
private _onGamepadButtonPress(buttonId: number) {
197+
if (!this.props.interactive) {
198+
return;
199+
}
200+
switch (buttonId) {
247201
case 15: // Right
248-
this.moveFocusByKey(new KeyboardEvent("press"), 1, 0);
202+
this.moveFocusByKey(new Event(""), 1, 0);
249203
break;
250204
case 14: // Left
251-
this.moveFocusByKey(new KeyboardEvent("press"), -1, 0);
205+
this.moveFocusByKey(new Event(""), -1, 0);
252206
break;
253207
case 12: // Up
254-
this.moveFocusByKey(new KeyboardEvent("press"), 0, -1);
208+
this.moveFocusByKey(new Event(""), 0, -1);
255209
break;
256210
case 13: // Down
257-
this.moveFocusByKey(new KeyboardEvent("press"), 0, 1);
258-
break;
259-
default:
211+
this.moveFocusByKey(new Event(""), 0, 1);
260212
break;
261213
}
262214
}
@@ -346,7 +298,7 @@ export default class Board extends Component<Props, State> {
346298
button.classList.contains("focus-visible") ||
347299
isFeaturePhone ||
348300
cellFocusMode ||
349-
this._gamepadIsTicking;
301+
gamepad.isGamepadConnected;
350302

351303
if (!showFocusStyle) {
352304
this.props.renderer.setFocus(-1, -1);
@@ -419,7 +371,7 @@ export default class Board extends Component<Props, State> {
419371
}
420372

421373
@bind
422-
private moveFocusByKey(event: KeyboardEvent, h: number, v: number) {
374+
private moveFocusByKey(event: Event, h: number, v: number) {
423375
event.stopPropagation();
424376
event.preventDefault();
425377

src/main/services/preact-canvas/components/game/index.tsx

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { GameChangeCallback } from "src/main/services/preact-canvas";
1818
import { submitTime } from "src/main/services/state/best-times";
1919
import { supportsVibration } from "src/main/services/state/vibration-preference";
2020
import { vibrationLength } from "src/main/utils/constants";
21+
import { gamepad } from "src/main/utils/gamepad";
2122
import { isFeaturePhone } from "src/main/utils/static-display";
2223
import { bind } from "src/utils/bind";
2324
import { StateChange } from "src/worker/gamelogic";
@@ -33,6 +34,9 @@ import {
3334
exitRow,
3435
exitRowInner,
3536
game as gameClass,
37+
gamepadButton,
38+
gamepadButtonA,
39+
gamepadButtonB,
3640
mainButton,
3741
shortcutKey
3842
} from "./style.css";
@@ -50,6 +54,7 @@ export interface Props {
5054
useMotion: boolean;
5155
bestTime?: number;
5256
useVibration: boolean;
57+
isGamepadConnected: boolean;
5358
}
5459

5560
interface State {
@@ -96,7 +101,8 @@ export default class Game extends Component<Props, State> {
96101
gameChangeUnsubscribe,
97102
toRevealTotal,
98103
useMotion,
99-
bestTime: previousBestTime
104+
bestTime: previousBestTime,
105+
isGamepadConnected
100106
}: Props,
101107
{ playMode, toReveal, animator, renderer, completeTime, bestTime }: State
102108
) {
@@ -123,6 +129,7 @@ export default class Game extends Component<Props, State> {
123129
height={height}
124130
mines={mines}
125131
useMotion={this.props.useMotion}
132+
isGamepadConnected={isGamepadConnected}
126133
/>
127134
) : renderer && animator ? (
128135
[
@@ -132,6 +139,9 @@ export default class Game extends Component<Props, State> {
132139
dangerMode={dangerMode}
133140
animator={animator}
134141
renderer={renderer}
142+
interactive={
143+
playMode === PlayMode.Pending || playMode === PlayMode.Playing
144+
}
135145
gameChangeSubscribe={gameChangeSubscribe}
136146
gameChangeUnsubscribe={gameChangeUnsubscribe}
137147
onCellClick={this.onCellClick}
@@ -150,10 +160,24 @@ export default class Game extends Component<Props, State> {
150160
#
151161
</span>
152162
)}{" "}
163+
{isGamepadConnected ? (
164+
<span class={[gamepadButton, gamepadButtonA].join(" ")}>
165+
A
166+
</span>
167+
) : (
168+
""
169+
)}{" "}
153170
Try again
154171
</button>
155172
<button class={mainButton} onClick={this.onReset}>
156173
{isFeaturePhone ? <span class={shortcutKey}>*</span> : ""}{" "}
174+
{isGamepadConnected ? (
175+
<span class={[gamepadButton, gamepadButtonB].join(" ")}>
176+
B
177+
</span>
178+
) : (
179+
""
180+
)}{" "}
157181
Main menu
158182
</button>
159183
</div>
@@ -175,11 +199,13 @@ export default class Game extends Component<Props, State> {
175199
this.props.onDangerModeChange(true);
176200
}
177201
window.addEventListener("keyup", this.onKeyUp);
202+
gamepad.addButtonDownCallback(this.onGamepadButtonDown);
178203
}
179204

180205
componentWillUnmount() {
181206
this.props.gameChangeUnsubscribe(this.onGameChange);
182207
window.removeEventListener("keyup", this.onKeyUp);
208+
gamepad.removeButtonDownCallback(this.onGamepadButtonDown);
183209
}
184210

185211
componentDidUpdate(_: Props, previousState: State) {
@@ -230,12 +256,31 @@ export default class Game extends Component<Props, State> {
230256

231257
@bind
232258
private onKeyUp(event: KeyboardEvent) {
233-
if (event.key === "#") {
234-
if (this.state.playMode === PlayMode.Lost) {
259+
if (
260+
this.state.playMode === PlayMode.Won ||
261+
this.state.playMode === PlayMode.Lost
262+
) {
263+
if (event.key === "#") {
264+
this.onRestart();
265+
} else if (event.key === "*") {
266+
this.onReset();
267+
}
268+
}
269+
}
270+
271+
@bind
272+
private onGamepadButtonDown(buttonId: number) {
273+
if (
274+
this.state.playMode === PlayMode.Won ||
275+
this.state.playMode === PlayMode.Lost
276+
) {
277+
if (buttonId === 0) {
278+
// A
235279
this.onRestart();
280+
} else if (buttonId === 1) {
281+
// B
282+
this.onReset();
236283
}
237-
} else if (event.key === "*") {
238-
this.onReset();
239284
}
240285
}
241286

src/main/services/preact-canvas/components/game/style.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,15 @@
7878
/** PostCSS adding classes in wrong order :( */
7979
border-color: #000 !important;
8080
}
81+
82+
.gamepad-button {
83+
composes: gamepadbutton from "../../utils.css";
84+
}
85+
86+
.gamepad-button-a {
87+
color: var(--color-gamepad-a);
88+
}
89+
90+
.gamepad-button-b {
91+
color: var(--color-gamepad-b);
92+
}

0 commit comments

Comments
 (0)