Platform Jumper Game

By adamlubek

This recipe demonstrates RxJS implementation of Platform Jumper game.

Ultimate RxJS

Example Code

( StackBlitz )

Platform Jumper

index.ts

// RxJS v6+
import { interval, of, fromEvent, combineLatest } from 'rxjs';
import { scan, tap, startWith, pluck, switchMap, takeWhile } from 'rxjs/operators';
import { gameSize } from './constants';
import { render } from './html-renderer';
import { Player, Platform } from './interfaces';
import { updatePlayer, updatePlatforms, initialPlatforms, initialPlayer, handleCollisions, handleKeypresses } from './game';
const gameSpeed = 500;
const platforms$ = interval(gameSpeed)
.pipe(
scan<number, Platform[]>(updatePlatforms, initialPlatforms)
);
const keys$ = (initialPlayer: Player) => fromEvent(document, 'keydown')
.pipe(
startWith({ key: '' }),
pluck('key'),
scan<string, Player>(
(plyr: Player, key: string) => handleKeypresses(plyr, key),
initialPlayer
)
);
const player$ = of(initialPlayer())
.pipe(
switchMap(p => combineLatest(interval(gameSpeed / 4), keys$(p))
.pipe(
scan<[number, Player], Player>((_, [__, player]) => updatePlayer(player))
)),
);
combineLatest(player$, platforms$)
.pipe(
scan<[Player, Platform[]], [Player, Platform[]]>(
(_, [player, platforms]) => handleCollisions([player, platforms])),
tap(render),
takeWhile(([player, platforms]) => player.lives > 0)
)
.subscribe()

game.ts

import { Player, Platform } from './interfaces';
import { gameSize } from './constants';
const newPlatform = (x, y): Platform => ({ x, y, scored: false });
const newPlayer = (x, y, jumpValue, score, lives): Player => ({
x,
y,
jumpValue,
canJump: false,
score: score,
lives: lives
});
const startingY = 4;
export const initialPlayer = (): Player => newPlayer(0, startingY, 0, 0, 3);
export const initialPlatforms = [newPlatform(gameSize / 2, startingY)];
const random = y => {
let min = Math.ceil(y - 4);
let max = Math.floor(y + 4);
min = min < 0 ? 0 : min;
max = max > gameSize - 1 ? gameSize - 1 : max;
return Math.floor(Math.random() * (max - min + 1)) + min;
};
export const updatePlatforms = (platforms: Platform[]): Platform[] => (
platforms[platforms.length - 1].x > gameSize / 5
? platforms.push(newPlatform(1, random(platforms[platforms.length - 1].y)))
: () => {},
platforms.filter(e => e.x < gameSize - 1).map(e => newPlatform(e.x + 1, e.y))
);
export const handleKeypresses = (player: Player, key: string) =>
key === 'ArrowRight'
? newPlayer(
player.x,
player.y + (player.y < gameSize - 1 ? 1 : 0),
player.jumpValue,
player.score,
player.lives
)
: key === 'ArrowLeft'
? newPlayer(
player.x,
player.y - (player.y > 0 ? 1 : 0),
player.jumpValue,
player.score,
player.lives
)
: key === 'ArrowUp'
? newPlayer(
player.x,
player.y,
player.x === gameSize - 1 || player.canJump ? 6 : 0,
player.score,
player.lives
)
: player;
export const updatePlayer = (player: Player): Player => (
(player.jumpValue -= player.jumpValue > 0 ? 1 : 0),
(player.x -= player.x - 3 > 0 ? player.jumpValue : 0),
(player.x += player.x < gameSize - 1 ? 1 : 0),
player.x === gameSize - 1 ? ((player.lives -= 1), (player.x = 1)) : () => {},
player
);
const handleCollidingPlatform = (
collidingPlatform: Platform,
player: Player
) => {
if (player.canJump) {
return;
}
if (!collidingPlatform) {
player.canJump = false;
return;
}
if (!collidingPlatform.scored) {
player.score += 1;
}
collidingPlatform.scored = true;
player.canJump = true;
};
export const handleCollisions = ([player, platforms]: [Player, Platform[]]): [
Player,
Platform[]
] => (
handleCollidingPlatform(
platforms.find(p => p.x - 1 === player.x && p.y === player.y),
player
),
(player.x = player.canJump
? (collidingPlatforms =>
collidingPlatforms.length
? (platform => platform.x - 1)(
collidingPlatforms[collidingPlatforms.length - 1]
)
: player.x)(
platforms.filter(p => p.y === player.y && p.x >= player.x)
)
: player.x),
[player, platforms]
);

interfaces.ts

export interface Player {
x: number;
y: number;
jumpValue: number;
canJump: boolean;
score: number;
lives: number;
}
export interface Platform {
x: number;
y: number;
scored: boolean;
}

constants.ts

export const gameSize = 20;

html-renderer.ts

import { gameSize } from './constants';
import { Player, Platform } from './interfaces';
export const render = ([player, platforms]: [Player, Platform[]]) => {
document.body.innerHTML = `Lives: ${player.lives} Score: ${player.score} </br>`;
const game = Array(gameSize)
.fill(0)
.map(_ => Array(gameSize).fill(0));
game[player.x][player.y] = '*';
platforms.forEach(p => (game[p.x][p.y] = '_'));
game.forEach(r => {
r.forEach(c => (document.body.innerHTML += c === 0 ? '...' : c));
document.body.innerHTML += '<br/>';
});
};

Operators Used