Skip to content

Commit

Permalink
Capture more than one key at a time, and use the gameloop to incremen…
Browse files Browse the repository at this point in the history
…t each input
  • Loading branch information
ndorfin committed Feb 19, 2024
1 parent d7b1fb5 commit aaa8318
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 166 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

I'm fascinated by the prospect of a little lander working its way to the surface of an alien world.

This project is a Web-based (and overly) simulation of a craft trying to successfully softly land on the surface of a moon or planet.
This project is a Web-based (and overly) simple simulation of a craft trying to successfully softly land on the surface of a moon or planet.
I wanted to use this project as a vehicle for learning more about Web Components, ES Modules, Event-based communication, and using PWA technologies.

[Check out the work in progress at `ndorfin.github.io/moon-lander`](https://ndorfin.github.io/moon-lander/)
Expand Down
6 changes: 3 additions & 3 deletions assets/css/wc/lander-vehicle.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ lander-vehicle {
block-size: calc(4 * var(--unit_root));
transform-origin: 50% 50%;
transition-property: transform, inset-block-end, inset-inline-start;
transition-duration: 0.35s;
transition-duration: 0.05s;
transition-timing-function: linear;
transform:
translate(-50%, -50%)
Expand All @@ -18,8 +18,8 @@ lander-vehicle {

lander-vehicle .leg {
transition-property: transform;
transition-duration: 0.5s;
transition-timing-function: linear;
transition-duration: 2s;
transition-timing-function: ease-in-out;
transform: rotate(0deg);
}

Expand Down
45 changes: 31 additions & 14 deletions assets/mjs/model.mjs
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
export const MODEL = {
position_y: {
name: 'Y Position',
position_x: {
name: 'X Position',
type: 'integer',
formElement: 'range',
initial: 100,
initial: 50,
min: 0,
max: 120,
max: 100,
affects: 'lander',
eventName: 'LanderStateChanged',
},
position_x: {
name: 'X Position',
position_y: {
name: 'Y Position',
type: 'integer',
formElement: 'range',
initial: 50,
initial: 60,
min: 0,
max: 100,
max: 120,
affects: 'lander',
eventName: 'LanderStateChanged',
},
rotation: {
name: 'Rotation',
Expand All @@ -27,7 +25,6 @@ export const MODEL = {
min: -100,
max: 100,
affects: 'lander',
eventName: 'LanderStateChanged',
},
running: {
name: 'Running',
Expand All @@ -37,7 +34,6 @@ export const MODEL = {
labelTrue: 'Running',
labelFalse: 'Stopped',
affects: 'game',
eventName: 'GameStateChanged',
},
speed: {
name: 'Speed',
Expand All @@ -47,7 +43,6 @@ export const MODEL = {
min: 0,
max: 100,
affects: 'lander',
eventName: 'LanderStateChanged',
},
thruster: {
name: 'Thruster',
Expand All @@ -57,6 +52,28 @@ export const MODEL = {
min: 0,
max: 100,
affects: 'lander',
eventName: 'LanderStateChanged',
},
};

export const KEYMAP = {
'ArrowUp': {
affects: 'thruster',
change: 1,
active: false,
},
'ArrowDown': {
affects: 'thruster',
change: -1,
active: false,
},
'ArrowLeft': {
affects: 'rotation',
change: -1,
active: false,
},
'ArrowRight': {
affects: 'rotation',
change: 1,
active: false,
},
};
67 changes: 0 additions & 67 deletions assets/mjs/wc/game-controls.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ import { dispatchEventWithDetails } from './../events.mjs';
export default class GameControls extends GameElement {
#formElements = MODEL;

constructor() {
super();
this.registerHandlers();
}

createRangeControl(keyName, modelValue) {
let controlId = `rng_${keyName}`;

Expand Down Expand Up @@ -63,68 +58,6 @@ export default class GameControls extends GameElement {
this.dispatchEvent(new Event('FormElementsAdded', { bubbles: true }));
}

gameStateChangedEventHandler(event) {
if (event.detail) {
this.querySelector(`[name="running"][value="${event.detail.running}"]`).click();
}
}

landerStateChangedEventHandler(event) {
if (event.detail) {
Object.keys(event.detail).forEach((keyName) => {
let currentValue = parseInt(this.querySelector(`[name="${keyName}"]`).value);
this.querySelector(`[name="${keyName}"]`).value = currentValue + event.detail[keyName];
});
}
}

keyboardHandler(event) {
let landerChange = null;
let gameChange = null;

switch (event.key) {
case 'ArrowUp':
landerChange = {
thruster: 10
};
break;
case 'ArrowDown':
landerChange = {
thruster: -10
};
break;
case 'ArrowRight':
landerChange = {
rotation: 10
};
break;
case 'ArrowLeft':
landerChange = {
rotation: -10
};
break;
case 'Escape':
gameChange = {
running: false
};
break;
case 'Enter':
gameChange = {
running: true
};
break;
}

if (landerChange) dispatchEventWithDetails('LanderStateChanged', landerChange);
if (gameChange) dispatchEventWithDetails('GameStateChanged', gameChange);
}

registerHandlers() {
document.addEventListener('GameStateChanged', this.gameStateChangedEventHandler);
document.addEventListener('LanderStateChanged', this.landerStateChangedEventHandler);
document.addEventListener('keyup', this.keyboardHandler);
}

connectedCallback() {
super.connectedCallback();
this.createFormControls();
Expand Down
157 changes: 95 additions & 62 deletions assets/mjs/wc/game-engine.mjs
Original file line number Diff line number Diff line change
@@ -1,107 +1,140 @@
import GameElement from './game-element.mjs';
import { MODEL } from './../model.mjs';
import { dispatchEventWithDetails } from './../events.mjs';
import { MODEL, KEYMAP } from './../model.mjs';

const keyboardEventsToWatch = ['keydown', 'keyup'];

export default class GameEngine extends GameElement {

modelLander = {};

#gameRunning;
#gameStarted;
#gameEnded;
#gameEventFrequency = 100; // Milliseconds
#gameEventFrequency = 50; // Milliseconds
#gameDuration = 0; // Counts the time elapsed based on multiples of `this.#gameEventFrequency`
#gameInterval; // Placeholder for window.setInterval so that it can be cleared later.
#keyMap = KEYMAP;

#gameRunningStateChanged = (runningState) => {
this.#gameRunning = runningState;
dispatchEventWithDetails('GameStateChange', {running: runningState});
}

#landerStateChanged = (values) => {
dispatchEventWithDetails('LanderStateChanged', values);
}

constructor() {
super();

this.handleKeyboardInterrupts = this.handleKeyboardInterrupts.bind(this);
this.setInitialValuesAndStart = this.setInitialValuesAndStart.bind(this);
this.#gameLoop = this.#gameLoop.bind(this);
}

#gameLoop = function () {
this.#gameDuration = this.#gameDuration + this.#gameEventFrequency;
let newPositionYAdjustment = -1;

/*
If thruster = 0
newSpeed = currentSpeed + gravity
If thruster > 0
newSpeed = currentSpeed - (thruster - gravity)
*/

this.modelLander.position_y = this.modelLander.position_y + newPositionYAdjustment;
this.#landerStateChanged({position_y: newPositionYAdjustment});
}

startGame() {
#startGame() {
this.#gameStarted = true;
this.#gameRunningStateChanged(true);
this.#addLanderKeyboardHandlers();
this.#gameDuration = 0;
this.#gameInterval = window.setInterval(this.#gameLoop, this.#gameEventFrequency);
}

pauseGame() {
this.#gameRunning = false;
this.#gameRunningStateChanged(false);
window.clearInterval(this.#gameInterval);
this.#gameInterval = window.setInterval(this.gameLoop, this.#gameEventFrequency);
}

unpauseGame() {
#unpauseGame() {
if (!this.#gameRunning) {
this.#gameRunning = true;
this.#addLanderKeyboardHandlers();
this.#gameRunningStateChanged(true);
this.#gameInterval = window.setInterval(this.#gameLoop, this.#gameEventFrequency);
this.#gameInterval = window.setInterval(this.gameLoop, this.#gameEventFrequency);
}
}

stopGame() {
#pauseGame() {
this.#gameRunningStateChanged(false);
this.#removeLanderKeyboardHandlers();
window.clearInterval(this.#gameInterval);
}

#stopGame() {
this.#gameEnded = true;
console.log('Game ended after duration', this.#gameDuration);
document.removeEventListener('keyup', this.handleKeyboardInterrupts);
this.#removeLanderKeyboardHandlers();
window.clearInterval(this.#gameInterval);
}

#addLanderKeyboardHandlers() {
keyboardEventsToWatch.forEach(eventName => {
document.addEventListener(eventName, this.handleLanderKeyboardInupts);
});
}

#removeLanderKeyboardHandlers() {
keyboardEventsToWatch.forEach(eventName => {
document.removeEventListener(eventName, this.handleLanderKeyboardInupts);
});
}

#updateCustomProperties() {
Object.keys(this.modelLander).forEach(landerProperty => {
let value = this.modelLander[landerProperty];
this.style.setProperty(`--lander_${landerProperty}`, value);
})
}

constructor() {
super();

this.handleGameStateKeyboardInupts = this.handleGameStateKeyboardInupts.bind(this);
this.handleLanderKeyboardInupts = this.handleLanderKeyboardInupts.bind(this);
this.setInitialValuesAndStart = this.setInitialValuesAndStart.bind(this);
this.gameLoop = this.gameLoop.bind(this);
}

setInitialValuesAndStart() {
Object.keys(MODEL).forEach((keyName) => {
let currentItem = MODEL[keyName];
if (currentItem.affects === 'lander') this.modelLander[keyName] = currentItem.initial;
this.style.setProperty(`--lander_${keyName}`, currentItem.initial);
if (currentItem.affects === 'lander') {
this.modelLander[keyName] = currentItem.initial;
}
});
this.startGame();
this.#updateCustomProperties();
this.#startGame();
}

handleKeyboardInterrupts(event) {
switch (event.key) {
case 'Enter':
if (this.#gameStarted && !this.#gameRunning) {
this.unpauseGame();
} else if (!this.#gameStarted) {
this.startGame();
}
break;
case 'Escape':
if (this.#gameStarted) {
(this.#gameRunning) ? this.pauseGame() : this.stopGame();
}
break;
handleGameStateKeyboardInupts(event) {
let keyName = event.key;

if (keyName == 'Enter') {
if (this.#gameStarted && !this.#gameRunning) {
this.#unpauseGame();
} else if (!this.#gameStarted) {
this.#startGame();
}
} else if (keyName == 'Escape' && this.#gameStarted) {
this.#gameRunning ? this.#pauseGame() : this.#stopGame();
}
}

handleLanderKeyboardInupts(event) {
let keyName = event.key;
let eventType = event.type;

if (this.#keyMap[keyName]) {
this.#keyMap[keyName].active = (eventType === 'keydown') ? true : false;
}
}

gameLoop() {
// Increment game duration counter
this.#gameDuration = this.#gameDuration + this.#gameEventFrequency;

// Go through the batched inputs and change the lander's position
Object.keys(this.#keyMap).forEach(keyName => {
let keyItem = this.#keyMap[keyName];
let landerProperty = this.#keyMap[keyName].affects;
if (keyItem.active) {
this.modelLander[landerProperty] = this.modelLander[landerProperty] + keyItem.change;
}
});

this.#updateCustomProperties();
}

connectedCallback() {
super.connectedCallback();
this.addEventListener('FormElementsAdded', this.setInitialValuesAndStart, {once: true});
document.addEventListener('keyup', this.handleKeyboardInterrupts);
document.addEventListener('keydown', this.handleGameStateKeyboardInupts);
}

disconnectedCallback() {
super.disconnectedCallback();
document.removeEventListener('keydown', this.handleGameStateKeyboardInupts);
}


Expand Down
Loading

0 comments on commit aaa8318

Please sign in to comment.