diff --git a/app/client/arcade/RowingGames.js b/app/client/arcade/RowingGames.js index e8e88d2..763b157 100644 --- a/app/client/arcade/RowingGames.js +++ b/app/client/arcade/RowingGames.js @@ -24,7 +24,7 @@ export function createRowingGames (canvasElement, clientWidth, clientHeight) { height: clientHeight }) // for now show debug infos all the time - k.debug.inspect = true + // k.debug.inspect = true // todo: once there are multiple games, asset loadingshould be moved to the individual games const assets = '/assets' diff --git a/app/client/arcade/SpaceBackground.js b/app/client/arcade/SpaceBackground.js index 5498340..ef8f637 100644 --- a/app/client/arcade/SpaceBackground.js +++ b/app/client/arcade/SpaceBackground.js @@ -17,7 +17,7 @@ export default function addSpaceBackground (k) { k.add([ k.rect(k.width() + 50, k.height() + 50), k.pos(-25, -25), - k.color(0, 0, 0), + k.color(0, 9, 28), k.layer('background') ]) @@ -45,10 +45,10 @@ export default function addSpaceBackground (k) { ]) } - k.onUpdate('star', (component) => { - component.move(0, component.speed) - if (component.pos.y - component.height > k.height()) { - k.destroy(component) + k.onUpdate('star', (star) => { + star.move(0, star.speed) + if (star.pos.y - star.height > k.height()) { + k.destroy(star) addStar(true) } }) diff --git a/app/client/arcade/StrokeFighterBattleScene.js b/app/client/arcade/StrokeFighterBattleScene.js index 329f565..5ce03e8 100644 --- a/app/client/arcade/StrokeFighterBattleScene.js +++ b/app/client/arcade/StrokeFighterBattleScene.js @@ -12,6 +12,14 @@ import addSpaceBackground from './SpaceBackground.js' * @param {import('kaboom').KaboomCtx} k Kaboom Context */ export default function StrokeFighterBattleScene (k) { + // how much stroke power is needed to fire high power lasers + const THRESHOLD_POWER = 180 + // training duration in seconds + const TARGET_TIME = 5 * 60 + // strokes per minute at start of training + const SPM_START = 14 + // strokes per minute at end of training + const SPM_END = 28 const BULLET_SPEED = 1200 const ENEMY_SPEED = 60 const PLAYER_SPEED = 480 @@ -28,6 +36,8 @@ export default function StrokeFighterBattleScene (k) { { sprite: 'spaceShips_009', health: 2 } ] + let trainingTime = 0 + k.layers([ 'background', 'game', @@ -67,8 +77,8 @@ export default function StrokeFighterBattleScene (k) { } } - player.onCollide('enemy', (e) => { - k.destroy(e) + player.onCollide('enemy', (enemy) => { + k.destroy(enemy) k.shake(4) k.play('hit', { detune: -1200, @@ -89,13 +99,13 @@ export default function StrokeFighterBattleScene (k) { } }) - function addLaserHit (p, n) { + function addLaserHit (pos, n) { for (let i = 0; i < n; i++) { k.wait(k.rand(n * 0.1), () => { for (let i = 0; i < 2; i++) { k.add([ k.sprite('laserRed09'), - k.pos(p.sub(0, 10)), + k.pos(pos.sub(0, 10)), k.scale(k.vec2(0.5)), k.lifespan(0.1), grow(k.rand(0.5, 2)), @@ -106,11 +116,11 @@ export default function StrokeFighterBattleScene (k) { } } - function spawnBullet (p) { + function spawnBullet (pos) { k.add([ k.sprite('laserRed01'), k.area(), - k.pos(p), + k.pos(pos), k.origin('center'), k.move(k.UP, BULLET_SPEED), k.cleanup(), @@ -141,8 +151,7 @@ export default function StrokeFighterBattleScene (k) { }) } - function spawnEnemy () { - const enemy = k.choose(ENEMIES) + function spawnEnemy (enemy) { k.add([ k.sprite(enemy.sprite), k.area(), @@ -152,16 +161,18 @@ export default function StrokeFighterBattleScene (k) { 'enemy', { speed: k.rand(ENEMY_SPEED * 0.5, ENEMY_SPEED * 1.5) } ]) - k.wait(3, spawnEnemy) } - k.on('death', 'enemy', (e) => { - k.destroy(e) - k.every('bullet', (bullet) => { k.destroy(bullet) }) + k.on('death', 'enemy', (enemy) => { + k.destroy(enemy) + k.every('bullet', (bullet) => { + addLaserHit(bullet.pos, 1) + k.destroy(bullet) + }) k.shake(2) }) - k.on('hurt', 'enemy', (e) => { + k.on('hurt', 'enemy', (enemy) => { k.shake(1) k.play('hit', { detune: k.rand(-1200, 1200), @@ -174,25 +185,24 @@ export default function StrokeFighterBattleScene (k) { k.text('0'), k.pos(12, 32), k.fixed(), - k.layer('ui'), - { time: 0 } + k.layer('ui') ]) timer.onUpdate(() => { - timer.time += k.dt() - timer.text = timer.time.toFixed(2) + trainingTime += k.dt() + timer.text = trainingTime.toFixed(2) }) - k.onCollide('bullet', 'enemy', (b, e) => { - k.destroy(b) - e.hurt(1) - addLaserHit(b.pos, 1) + k.onCollide('bullet', 'enemy', (bullet, enemy) => { + k.destroy(bullet) + enemy.hurt(1) + addLaserHit(bullet.pos, 1) }) - k.onUpdate('enemy', (sprite) => { - sprite.move(0, sprite.speed) - if (sprite.pos.y - sprite.height > k.height()) { - k.destroy(sprite) + k.onUpdate('enemy', (enemy) => { + enemy.move(0, enemy.speed) + if (enemy.pos.y - enemy.height > k.height()) { + k.destroy(enemy) } }) @@ -208,16 +218,31 @@ export default function StrokeFighterBattleScene (k) { } function driveFinished (metrics) { - if (metrics.power < 120) { + if (metrics.power < THRESHOLD_POWER * 0.75) { fireWeapons(1) - } else if (metrics.power < 180) { + } else if (metrics.power < THRESHOLD_POWER) { fireWeapons(2) } else { fireWeapons(3) } } - spawnEnemy() + function scheduleNextEnemy () { + const percentTrainingFinished = trainingTime / TARGET_TIME + const currentSPM = SPM_START + (SPM_END - SPM_START) * percentTrainingFinished + let maxEnemyHealth = 1 + if (percentTrainingFinished < 0.4) { + maxEnemyHealth = 1 + } else if (percentTrainingFinished < 0.8) { + maxEnemyHealth = 2 + } else { + maxEnemyHealth = 3 + } + spawnEnemy(k.choose(ENEMIES.filter((enemy) => enemy.health <= maxEnemyHealth))) + k.wait(60 / currentSPM, scheduleNextEnemy) + } + + scheduleNextEnemy() return { appState diff --git a/app/client/components/GameComponent.js b/app/client/components/GameComponent.js index f62f8c7..8a29659 100644 --- a/app/client/components/GameComponent.js +++ b/app/client/components/GameComponent.js @@ -7,23 +7,77 @@ import { customElement } from 'lit/decorators.js' import { createRowingGames } from '../arcade/RowingGames.js' +import { icon_exit } from '../lib/icons.js' import { AppElement, css, html } from './AppElement.js' - @customElement('game-component') export class GameComponent extends AppElement { static styles = css` :host { width: 100%; height: 100%; - display: block; + display: flex; } #arcade { + width: 100vh; + height: 100vh; + } + @media (orientation: portrait) { + :host { + flex-direction: column + } + #arcade { + width: 100vw; + height: 100vw; + } + } + + button { + outline:none; + background-color: var(--theme-button-color); + border: 0; + border-radius: var(--theme-border-radius); + color: var(--theme-font-color); + margin: 0.2em 0; + font-size: 60%; + text-decoration: none; + display: inline-flex; + width: 3.5em; + height: 2.5em; + justify-content: center; + align-items: center; + } + button:hover { + filter: brightness(150%); + } + .icon { + height: 1.7em; + } + #controls { width: 100%; height: 100%; + text-align: center; + position: relative; + } + #buttons { + padding: 0.5em 0 0.5em 0; + margin: 1vw; + background: var(--theme-widget-color); + border-radius: var(--theme-border-radius); } ` render () { - return html`` + return html` + +
+
+ +
+
+ ` + } + + openDashboard () { + this.sendEvent('triggerAction', { command: 'openDashboard' }) } firstUpdated () { diff --git a/app/client/lib/app.js b/app/client/lib/app.js index 1bfd483..105f78f 100644 --- a/app/client/lib/app.js +++ b/app/client/lib/app.js @@ -135,6 +135,10 @@ export function createApp (app) { app.updateState({ ...app.getState(), activeRoute: 'ROWINGGAMES' }) break } + case 'openDashboard': { + app.updateState({ ...app.getState(), activeRoute: 'DASHBOARD' }) + break + } case 'reset': { resetFields() if (socket)socket.send(JSON.stringify({ command: 'reset' })) diff --git a/app/client/lib/icons.js b/app/client/lib/icons.js index 463f10c..ba258c0 100644 --- a/app/client/lib/icons.js +++ b/app/client/lib/icons.js @@ -26,3 +26,4 @@ export const icon_compress = svg`` export const icon_upload = svg`` export const icon_gamepad = svg`` +export const icon_exit = svg``