From a0d5d375b844a097dc7c7b26a3973daf62310b05 Mon Sep 17 00:00:00 2001
From: Nico Hagelberg <16757571+nicou@users.noreply.github.com>
Date: Sun, 23 Jun 2024 15:54:31 +0300
Subject: [PATCH] Fix game refresh rates (#11)
* Implement a generic game tester component
* Hide manual login when NFC is enabled
* Add difficulty picker to GameTester
* Add FPS counters to Snake and FlappyDrone
* Add max FPS to FlappyDrone
* Add max FPS and touch controls to Snake
* Only use touch/keyboard controls in Snake
* Enable game debug mode for GMs
* Adjust difficulty to be consistent between devices
Old HANSCA phones have 60Hz refresh rate. By setting the max FPS to 58
we ensure that the game difficulty is consistent between older and newer
devices.
---
src/components/Carousel.vue | 12 +-
src/components/GameScanner.vue | 1 -
src/components/GameTester.vue | 251 ++++++++++++++++++
src/components/Greeter.vue | 28 +-
.../{GameTest.vue => PhaseSyncGameTest.vue} | 0
src/components/game-test-configs/flappy.js | 41 +++
src/components/game-test-configs/snake.js | 26 ++
src/components/games/FlappyDrone.js | 88 ++++--
src/components/games/FlappyDrone.vue | 9 +-
src/components/games/Snake.vue | 169 +++++++++---
10 files changed, 556 insertions(+), 69 deletions(-)
create mode 100644 src/components/GameTester.vue
rename src/components/{GameTest.vue => PhaseSyncGameTest.vue} (100%)
create mode 100644 src/components/game-test-configs/flappy.js
create mode 100644 src/components/game-test-configs/snake.js
diff --git a/src/components/Carousel.vue b/src/components/Carousel.vue
index 554a175..d3565c9 100644
--- a/src/components/Carousel.vue
+++ b/src/components/Carousel.vue
@@ -27,7 +27,8 @@
import Scanner from './Scanner.vue'
import GameScanner from './GameScanner'
import RadiationDetector from './RadiationDetector.vue'
-import GameTest from './GameTest.vue'
+import GameTester from './GameTester.vue'
+import PhaseSyncGameTest from './PhaseSyncGameTest.vue'
import MedicalRecords from './MedicalRecords.vue'
import MedicalDiagnosis from './MedicalDiagnosis.vue'
import MedicalSample from './MedicalSample.vue'
@@ -114,7 +115,7 @@ const PAGES = [
},
{
id: 'admin',
- title: 'Off-game admin',
+ title: 'HANSCA / GM / ADMIN / DEV',
role: 'role:admin',
color: '#9c4e4e',
tools: [
@@ -126,7 +127,12 @@ const PAGES = [
{
icon: 'atom',
title: 'Phase sync game test',
- page: GameTest,
+ page: PhaseSyncGameTest,
+ },
+ {
+ icon: 'atom',
+ title: 'Game tester',
+ page: GameTester,
},
{
icon: 'atom',
diff --git a/src/components/GameScanner.vue b/src/components/GameScanner.vue
index b27450a..489ef79 100644
--- a/src/components/GameScanner.vue
+++ b/src/components/GameScanner.vue
@@ -157,7 +157,6 @@ export default {
return
}
- console.log('getting game component');
if (config.game in GAMES) {
cancelWatch()
this.gameLoader = () => {
diff --git a/src/components/GameTester.vue b/src/components/GameTester.vue
new file mode 100644
index 0000000..b9a69ea
--- /dev/null
+++ b/src/components/GameTester.vue
@@ -0,0 +1,251 @@
+
+ Select game
+
+ {{config.title}}
+
+ {{config.title}}
+
+ {{config.title}}
+
+ {{config.title}}
+
+ {{config.title}}
+
+
Bio ID:
-Scan or enter your Bio ID:
+Scan your Bio ID to proceed
+Version {{version}}
Manual hull repairs required using a drone.
\n\t\tYou must fly the drone to the hull area. Tap HANSCA to fly upwards.
", + preFailDescription: "No drones available!", + }, + "skill:expert": { + gap: 150, + game: "flappy", + score: 7, + gravity: 1.3, + interval: 250, + preCondition: "/data/misc/flappy_drone", + endDescription: "Drone successfully arrived at hull. Repairs started.", + failDescription: "Drone destroyed!", + initDescription: + "Manual hull repairs required using a drone.
\n\t\tYou must fly the drone to the hull area. Tap HANSCA to fly upwards.
", + preFailDescription: "No drones available!", + }, + "skill:master": { + gap: 150, + game: "flappy", + score: 10, + gravity: 1.6, + interval: 250, + preCondition: "/data/misc/flappy_drone", + endDescription: "Drone successfully arrived at hull. Repairs started.", + failDescription: "Drone destroyed!", + initDescription: + "Manual hull repairs required using a drone.
\n\t\tYou must fly the drone to the hull area. Tap HANSCA to fly upwards.
", + preFailDescription: "No drones available!", + }, +}; diff --git a/src/components/game-test-configs/snake.js b/src/components/game-test-configs/snake.js new file mode 100644 index 0000000..c562fec --- /dev/null +++ b/src/components/game-test-configs/snake.js @@ -0,0 +1,26 @@ +export const snakeConfig = { + default: { + game: "snake", + speed: 8, + targetSnakeLength: 16, + endDescription: "All shield segments fixed!", + initDescription: + "Front shield generator shield segments are dirty.
\n\t\tClean up all faulty segments by collecting (red) 'apples' with the remote-controlled snake cleaner. After enough dirt has been cleaned the system will become operational.
", + }, + "skill:master": { + game: "snake", + speed: 12, + targetSnakeLength: 12, + endDescription: "All shield segments fixed!", + initDescription: + "Front shield generator shield segments are dirty.
\n\t\tClean up all faulty segments by collecting (red) 'apples' with the remote-controlled snake cleaner. After enough dirt has been cleaned the system will become operational.
", + }, + "skill:expert": { + game: "snake", + speed: 16, + targetSnakeLength: 8, + endDescription: "All shield segments fixed!", + initDescription: + "Front shield generator shield segments are dirty.
\n\t\tClean up all faulty segments by collecting (red) 'apples' with the remote-controlled snake cleaner. After enough dirt has been cleaned the system will become operational.
", + }, +}; diff --git a/src/components/games/FlappyDrone.js b/src/components/games/FlappyDrone.js index 50caeb5..6a02d27 100644 --- a/src/components/games/FlappyDrone.js +++ b/src/components/games/FlappyDrone.js @@ -1,6 +1,7 @@ -import { getBlob, patchBlob } from '../../blob' +import { patchBlob } from '../../blob' + class FlappyDrone { - constructor(context, canvas, config, game) { + constructor(context, canvas, config, game, debug = false) { this.ctx = context this.cvs = canvas this.game = game @@ -17,7 +18,7 @@ class FlappyDrone { this.pipeNorth.src = 'images/flappy/pipeNorth.png' this.pipeSouth.src = 'images/flappy/pipeSouth.png' - this.scor = 0 + this.currentScore = 0 this.gap = config.gap this.interval = config.interval @@ -26,33 +27,61 @@ class FlappyDrone { this.gravity = config.gravity this.collision = false this.requiredScore = config.score - console.log( this.requiredScore) + console.log("FlappyDrone required score:", this.requiredScore) this.pipes = [{ x: canvas.width, y: 0 }] + + this.maxFps = 58; // Old HANSCA phones have 60Hz refresh rate, so we can't go higher than this + this.lastFrameRenderedAt = Date.now(); + this.debug = debug; + this.showFps = this.debug; + this.frameRateCounter = 0; + this.frameRateCountStartedAt = Date.now(); + this.fps = ""; + } + + calculateFPS() { + this.frameRateCounter++ + if (Date.now() - this.frameRateCountStartedAt >= 1000) { + this.fps = this.frameRateCounter + this.frameRateCounter = 0 + this.frameRateCountStartedAt = Date.now() + } } getGap() { return this.pipeNorth.height + this.gap } - getHeigth() { + getHeight() { return this.cvs.height } - getWidth() { - return this.cvs.width - } - fly() { this.bY -= 25 } - draw() { + shouldRender() { + const now = Date.now(); + const timeSinceLastFrame = now - this.lastFrameRenderedAt; + const timeBetweenFrames = 1000 / this.maxFps; + if (timeSinceLastFrame > timeBetweenFrames) { + this.lastFrameRenderedAt = now - (timeSinceLastFrame % timeBetweenFrames); + return true; + } + return false; + } + draw() { const go = () => { + if (!this.shouldRender()) { + requestAnimationFrame(go) + return + } + this.drawBg() this.drawPipes() @@ -65,6 +94,8 @@ class FlappyDrone { this.drawScore() + this.drawFps() + if (this.checkComplete()) { this.game.$emit('gameSuccess') } else { @@ -75,14 +106,23 @@ class FlappyDrone { this.game.$emit('gameFail') } } + this.calculateFPS() } go() - } checkComplete() { - return this.scor >= this.requiredScore + return this.currentScore >= this.requiredScore + } + + drawFps() { + if (!this.showFps) { + return; + } + this.ctx.fillStyle = '#fff' + this.ctx.font = "20px verdana" + this.ctx.fillText(`FPS: ${this.fps}`, 10, 40) } drawPipes() { @@ -101,12 +141,20 @@ class FlappyDrone { this.detectCollision(this.pipes[i].x, this.pipes[i].y) - this.score(this.pipes[i].x) + this.incrementScore(this.pipes[i].x) } } drawDrone(x = 0, y = 0) { this.ctx.drawImage(this.drone, x, y) + if (this.debug) { + this.drawDroneBorders() + } + } + + drawDroneBorders() { + this.ctx.strokeStyle = 'red' + this.ctx.strokeRect(this.bX, this.bY, this.drone.width, this.drone.height) } drawBg() { @@ -114,7 +162,7 @@ class FlappyDrone { } drawFg() { - this.ctx.drawImage(this.fg, 0, this.getHeigth() - this.fg.height) + this.ctx.drawImage(this.fg, 0, this.getHeight() - this.fg.height) } drawPipeNorth(x = 0, y = 0) { @@ -125,10 +173,10 @@ class FlappyDrone { this.ctx.drawImage(this.pipeSouth, x, y) } - drawScore(x, y) { - this.ctx.fillStyle = '#000' + drawScore() { + this.ctx.fillStyle = '#fff' this.ctx.font = "20px verdana" - this.ctx.fillText(`Score: ${this.scor}`, 40, this.cvs.height - 50) + this.ctx.fillText(`Score: ${this.currentScore}`, 10, 20); } detectCollision(x, y) { @@ -139,9 +187,9 @@ class FlappyDrone { } } - score(x) { - if (x == 5) { - this.scor++ + incrementScore(x) { + if (x === 5) { + this.currentScore++ } } } diff --git a/src/components/games/FlappyDrone.vue b/src/components/games/FlappyDrone.vue index 03609dc..3cc1e12 100644 --- a/src/components/games/FlappyDrone.vue +++ b/src/components/games/FlappyDrone.vue @@ -7,7 +7,7 @@ @@ -183,6 +275,7 @@ body { canvas { border: 1px solid white; + height: 95%; } .info { @@ -190,12 +283,8 @@ canvas { margin-bottom: 10px; } -.arrow-btn{ - width: 50px; - height: 50px; - margin-left: 4px; - margin-right: 4px; - margin-top: 8px; - padding-top: 8px; +.snake-app { + text-align: center; + height: 95%; }