Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

State refactor #7

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"bezier-js": "^2.2.1",
"color": "^0.11.3",
"d3": "^4.2.2",
"immutable": "^3.8.1",
"lodash": "^4.15.0",
"react": "^15.3.1",
"react-art": "^0.15.1",
Expand Down
63 changes: 38 additions & 25 deletions src/components/Aqueductulous.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,70 @@ import Game from './Game.js'
import TitleScreen from './TitleScreen.js'
import GameOver from './GameOver.js'

import { GAMEMODE } from '../game/core.js';
import { createInitialState } from '../state';
import { createInitialState as createGame } from '../state/game'

const VIEWS = {
Title: Symbol('Title'),
Playing: Symbol('Playing'),
GameOver: Symbol('GameOver'),
};

function newLevelSeed() {
return window.btoa((Math.random() * 10e2).toFixed(0))
}

export default class Aqueductulous extends Component {
constructor() {
super();

this.showTitle = this.updateGameMode.bind(this, GAMEMODE.Title);
this.newGame = () => {
window.history.replaceState({}, '', '#' + window.btoa((Math.random() * 10e2).toFixed(0)));
this.updateGameMode(GAMEMODE.Playing);
};
this.rematch = this.updateGameMode.bind(this, GAMEMODE.Playing);
this.showGameOver = result => {
this.updateGameMode(GAMEMODE.GameOver, result);
}

this.state = {
gameMode: GAMEMODE.Title,
gameResult: undefined, /*{
time: 0,
won: false,
}*/
...createInitialState(),
view: VIEWS.Title,
}

this.newGame = this.newGame.bind(this);
this.updateGame = (game, onComplete) => this.setState({ game }, onComplete);
this.showGameOver = this.setState.bind(this, { view: VIEWS.GameOver });
this.rematch = this.startGame.bind(this, null);
}

newGame() {
const newSeed = newLevelSeed();
window.history.replaceState({}, '', '#' + newSeed);
this.startGame(newSeed);
}

updateGameMode(gameMode, gameResult) {
this.setState({gameMode, gameResult});
startGame(seed) {
this.setState({
game: createGame(seed),
view: VIEWS.Playing,
});
}

render() {
const { gameMode, gameResult } = this.state;
const { view, game } = this.state;
const hasSeed = !!window.location.hash;

switch (gameMode) {
case GAMEMODE.Title:
switch (view) {
case VIEWS.Title:
return (
<TitleScreen
onStartGame={hasSeed ? this.rematch : this.newGame}
/>
)
case GAMEMODE.Playing:
case VIEWS.Playing:
return (
<Game
seed={window.location.hash}
state={game}
updateState={this.updateGame}
onGameOver={this.showGameOver}
/>
);
case GAMEMODE.GameOver:
case VIEWS.GameOver:
return (
<GameOver
result={gameResult}
game={game}
onRematch={this.rematch}
onNewGame={this.newGame}
/>
Expand Down
62 changes: 31 additions & 31 deletions src/components/Game.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import React, { Component } from 'react';

import { createInitialState,
isPlayerAtEndOfTrack,
isEnemyAtEndOfTrack,
updateInput,
updateTime } from '../game/core';

import { playerXOffset } from '../game/player'
import * as game from '../state/game';
import { playerXOffset } from '../state/game/player'

import FullWindowAspectFitSurface from './FullWindowAspectFitSurface';
import Player from './Player';
Expand All @@ -15,34 +10,16 @@ import Background from './Background';
import HazardFeedback from './HazardFeedback';

export default class Game extends Component {
constructor({seed, onGameOver}) {
constructor() {
super();

this.state = createInitialState(seed);

this.beginAcceleration = this.updateInput.bind(this, true);
this.endAcceleration = this.updateInput.bind(this, false);

// Start the time loop
const startTime = performance.now();
this.updateTime = currentTime => {
this.setState(updateTime(this.state, currentTime - startTime), () => {
//
// After the state updates, check to see if the game has ended.
// If it has not, enqueue another rAF update.
//
// A tie is a loss!
if (isPlayerAtEndOfTrack(this.state)) {
onGameOver({
time: this.state.elapsedTime,
won: isEnemyAtEndOfTrack(this.state)
? false
: true
})
} else {
this.raf = requestAnimationFrame(this.updateTime);
}
});
}
this.updateTime = this.updateTime.bind(this, startTime);
this.gameOverOrScheduleTimeUpdate = this.gameOverOrScheduleTimeUpdate.bind(this);
this.raf = requestAnimationFrame(this.updateTime);
}

Expand All @@ -51,11 +28,34 @@ export default class Game extends Component {
}

updateInput(accelerating) {
this.setState(updateInput(this.state, accelerating));
const { state, updateState } = this.props;
// ignore key repeats that don't change the state
if (accelerating === state.player.accelerating) return;

updateState(game.updateInput(state, accelerating));
}

updateTime(startTime, currentTime) {
const { state, updateState } = this.props;
updateState(
game.updateTime(state, currentTime - startTime),
this.gameOverOrScheduleTimeUpdate
);
}

gameOverOrScheduleTimeUpdate() {
const { state, onGameOver } = this.props;
// After the state updates, check to see if the game has ended.
// If it has not, enqueue another rAF update.
if (game.isPlayerAtEndOfTrack(state)) {
onGameOver();
} else {
this.raf = requestAnimationFrame(this.updateTime);
}
}

render() {
const { player, level, enemyPlayer, enemyLevel, elapsedTime } = this.state;
const { player, level, enemyPlayer, enemyLevel, elapsedTime } = this.props.state;

return (
<FullWindowAspectFitSurface
Expand Down
9 changes: 7 additions & 2 deletions src/components/GameOver.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { Group, Text } from 'react-art';
import FullWindowAspectFitSurface from './FullWindowAspectFitSurface';
import Button from './Button';

import { isEnemyAtEndOfTrack } from '../state/game';

const baseText = {
fill: '#fff',
font: {
Expand Down Expand Up @@ -57,10 +59,13 @@ function GameOverLayout ({
);
}

export default function GameOver (props) {
export default function GameOver ({game, ...rest}) {
return (
<FullWindowAspectFitSurface>
<GameOverLayout {...props} />
<GameOverLayout result={{
time: game.elapsedTime,
won: !isEnemyAtEndOfTrack(game), // a tie is a loss!
}} {...rest}/>
</FullWindowAspectFitSurface>
);
}
13 changes: 8 additions & 5 deletions src/components/HazardFeedback.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { Group } from 'react-art';
import { easeElastic } from 'd3';

import ShadowText from './ShadowText';
import { playerCenter } from '../game/player';
import { HAZARD_RESULTS } from '../game/core';
import { playerCenter } from '../state/game/player';
import { HAZARD_RESULTS } from '../state/game';
import { HAZARD_ZONE_COLOR } from './Level';

export default function HazardFeedback ({ player, level, elapsedTime, unitLength, yOffset, effectDuration }) {
Expand All @@ -20,14 +20,17 @@ export default function HazardFeedback ({ player, level, elapsedTime, unitLength
? 1 + (lastHazardEvent.result.velocity - 1) / 2
: 1;

const displayProps = lastHazardEvent && lastHazardEvent.result === HAZARD_RESULTS.FAIL
? {
const useFailureEffect = lastHazardEvent
&& lastHazardEvent.result.text === HAZARD_RESULTS.FAIL.text;

const displayProps = useFailureEffect
? { // shake
x: center.x + Math.sin(Math.PI * 10 * t) * unitLength / 8,
y: center.y + yOffset * 0.75 * unitLength,
shadowColor: HAZARD_ZONE_COLOR,
font: { fontSize: 22/45 * unitLength }
}
: {
: { // pop up
x: center.x,
y: center.y + (yOffset/3 - easeElastic(t, 0.5, 0.3) * 0.75) * unitLength,
font: { fontSize: 22/45 * unitLength * fontScale }
Expand Down
2 changes: 1 addition & 1 deletion src/components/Level.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { Group, Shape } from 'react-art';
import { playerXOffset as playerGameXGap } from '../game/player';
import { playerXOffset as playerGameXGap } from '../state/game/player';

const LIGHT_AQUADUCT_COLOR = 0xAAA;
const DARK_AQUADUCT_COLOR = 0x999;
Expand Down
4 changes: 2 additions & 2 deletions src/components/Player.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import Wedge from 'react-art/shapes/wedge';
import BubbleEffect from './BubbleEffect';
import SplashEffect from './SplashEffect';

import { trailingPathForX, tangentForX } from '../game/level';
import { playerCenter } from '../game/player';
import { trailingPathForX, tangentForX } from '../state/game/level';
import { playerCenter } from '../state/game/player';

function PlayerTrail ({
position,
Expand Down
File renamed without changes.
24 changes: 3 additions & 21 deletions src/game/core.js → src/state/game/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,13 @@

import { createEnemyProfile, isEnemyAcclerating } from './enemy.js'
import { createLevel, indexForX, segmentForIndex } from './level.js'
import { playerXOffset } from './player.js'

const VELOCITY = {
SLOW: 2.5,
FAST: 4
};
import { baseSpeed, slowerSpeedRatio, playerXOffset } from './player.js'

const CHARACTER = {
PLAYER: 1,
ENEMY: 2,
}

export const GAMEMODE = {
Title: 1,
Playing: 2,
GameOver: 3
};

export const HAZARD_RESULTS = {
FAIL: { text: 'TOO FAST', velocity: 0.5 },
GOOD: { text: 'GOOD', velocity: 1.2, window: 0.55 },
Expand Down Expand Up @@ -77,7 +66,7 @@ export function updateInput(state, accelerating) {
export function updateTime(state, elapsedTime) {
const dt = (elapsedTime - state.elapsedTime) / 1000;
const { position, accelerating } = state.player;
const velocity = accelerating ? VELOCITY.FAST : VELOCITY.SLOW;
const velocity = baseSpeed * (accelerating ? 1 : slowerSpeedRatio);

const newPlayerPosition = position + velocity * hazardVelocityModifier(state) * dt;

Expand All @@ -86,7 +75,7 @@ export function updateTime(state, elapsedTime) {
state.enemyPlayer,
state.enemyAI
);
const enemyVelocity = isEnemyAcceleratingNow ? VELOCITY.FAST : VELOCITY.SLOW;
const enemyVelocity = baseSpeed * (isEnemyAcceleratingNow ? 1 : slowerSpeedRatio);
const newEnemyPosition =
isEnemyAtEndOfTrack(state) ?
state.enemyLevel.curve[state.enemyLevel.curve.length - 1].endpoint.x :
Expand All @@ -110,13 +99,6 @@ export function updateTime(state, elapsedTime) {
return updateHazardEvent(updateHazardEvent(updatedMotion, CHARACTER.PLAYER), CHARACTER.ENEMY);
}

export function updateGameMode(state, gameMode) {
return {
...state,
gameMode
};
}

export function isPlayerAtEndOfTrack(state) {
return isAtEndOfTrack(state.player.position, state.level.curve);
}
Expand Down
File renamed without changes.
3 changes: 3 additions & 0 deletions src/game/player.js → src/state/game/player.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { yForX } from './level';

export const baseSpeed = 4;
export const slowerSpeedRatio = 0.5;

export const playerXOffset = 4; // Constant player position relative to edge of screen

export function playerCenter(position, level, unitLength=1, xOffset=playerXOffset) {
Expand Down
8 changes: 8 additions & 0 deletions src/state/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createDefaultSettings } from './settings';

export function createInitialState () {
return {
game: undefined,
settings: createDefaultSettings(),
}
}
15 changes: 15 additions & 0 deletions src/state/settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export function createDefaultSettings () {
return {
//
// This is an example of the kind of run-time tweakable settings that will be
// stored here in a future change.
//
// audio: {
// volume: 0,
// effects: { ... },
// },
// player: {
// baseSpeed: 4,
// },
}
}