Learn RxJS
Search…
Platform Jumper Game
By adamlubek
This recipe demonstrates RxJS implementation of Platform Jumper game.

Example Code

Could not load image
Platform Jumper

index.ts

1
// RxJS v6+
2
import { interval, of, fromEvent, combineLatest } from 'rxjs';
3
import { scan, tap, startWith, pluck, switchMap, takeWhile } from 'rxjs/operators';
4
import { gameSize } from './constants';
5
import { render } from './html-renderer';
6
import { Player, Platform } from './interfaces';
7
import { updatePlayer, updatePlatforms, initialPlatforms, initialPlayer, handleCollisions, handleKeypresses } from './game';
8
9
const gameSpeed = 500;
10
11
const platforms$ = interval(gameSpeed)
12
.pipe(
13
scan<number, Platform[]>(updatePlatforms, initialPlatforms)
14
);
15
16
const keys$ = (initialPlayer: Player) => fromEvent(document, 'keydown')
17
.pipe(
18
startWith({ key: '' }),
19
pluck('key'),
20
scan<string, Player>(
21
(plyr: Player, key: string) => handleKeypresses(plyr, key),
22
initialPlayer
23
)
24
);
25
26
const player$ = of(initialPlayer())
27
.pipe(
28
switchMap(p => combineLatest(interval(gameSpeed / 4), keys$(p))
29
.pipe(
30
scan<[number, Player], Player>((_, [__, player]) => updatePlayer(player))
31
)),
32
);
33
34
combineLatest(player$, platforms$)
35
.pipe(
36
scan<[Player, Platform[]], [Player, Platform[]]>(
37
(_, [player, platforms]) => handleCollisions([player, platforms])),
38
tap(render),
39
takeWhile(([player, platforms]) => player.lives > 0)
40
)
41
.subscribe()
Copied!

game.ts

1
import { Player, Platform } from './interfaces';
2
import { gameSize } from './constants';
3
4
const newPlatform = (x, y): Platform => ({ x, y, scored: false });
5
const newPlayer = (x, y, jumpValue, score, lives): Player => ({
6
x,
7
y,
8
jumpValue,
9
canJump: false,
10
score: score,
11
lives: lives
12
});
13
const startingY = 4;
14
export const initialPlayer = (): Player => newPlayer(0, startingY, 0, 0, 3);
15
export const initialPlatforms = [newPlatform(gameSize / 2, startingY)];
16
17
const random = y => {
18
let min = Math.ceil(y - 4);
19
let max = Math.floor(y + 4);
20
min = min < 0 ? 0 : min;
21
max = max > gameSize - 1 ? gameSize - 1 : max;
22
23
return Math.floor(Math.random() * (max - min + 1)) + min;
24
};
25
26
export const updatePlatforms = (platforms: Platform[]): Platform[] => (
27
platforms[platforms.length - 1].x > gameSize / 5
28
? platforms.push(newPlatform(1, random(platforms[platforms.length - 1].y)))
29
: () => {},
30
platforms.filter(e => e.x < gameSize - 1).map(e => newPlatform(e.x + 1, e.y))
31
);
32
33
export const handleKeypresses = (player: Player, key: string) =>
34
key === 'ArrowRight'
35
? newPlayer(
36
player.x,
37
player.y + (player.y < gameSize - 1 ? 1 : 0),
38
player.jumpValue,
39
player.score,
40
player.lives
41
)
42
: key === 'ArrowLeft'
43
? newPlayer(
44
player.x,
45
player.y - (player.y > 0 ? 1 : 0),
46
player.jumpValue,
47
player.score,
48
player.lives
49
)
50
: key === 'ArrowUp'
51
? newPlayer(
52
player.x,
53
player.y,
54
player.x === gameSize - 1 || player.canJump ? 6 : 0,
55
player.score,
56
player.lives
57
)
58
: player;
59
60
export const updatePlayer = (player: Player): Player => (
61
(player.jumpValue -= player.jumpValue > 0 ? 1 : 0),
62
(player.x -= player.x - 3 > 0 ? player.jumpValue : 0),
63
(player.x += player.x < gameSize - 1 ? 1 : 0),
64
player.x === gameSize - 1 ? ((player.lives -= 1), (player.x = 1)) : () => {},
65
player
66
);
67
68
const handleCollidingPlatform = (
69
collidingPlatform: Platform,
70
player: Player
71
) => {
72
if (player.canJump) {
73
return;
74
}
75
76
if (!collidingPlatform) {
77
player.canJump = false;
78
return;
79
}
80
81
if (!collidingPlatform.scored) {
82
player.score += 1;
83
}
84
collidingPlatform.scored = true;
85
86
player.canJump = true;
87
};
88
89
export const handleCollisions = ([player, platforms]: [Player, Platform[]]): [
90
Player,
91
Platform[]
92
] => (
93
handleCollidingPlatform(
94
platforms.find(p => p.x - 1 === player.x && p.y === player.y),
95
player
96
),
97
(player.x = player.canJump
98
? (collidingPlatforms =>
99
collidingPlatforms.length
100
? (platform => platform.x - 1)(
101
collidingPlatforms[collidingPlatforms.length - 1]
102
)
103
: player.x)(
104
platforms.filter(p => p.y === player.y && p.x >= player.x)
105
)
106
: player.x),
107
[player, platforms]
108
);
Copied!

interfaces.ts

1
export interface Player {
2
x: number;
3
y: number;
4
jumpValue: number;
5
canJump: boolean;
6
score: number;
7
lives: number;
8
}
9
10
export interface Platform {
11
x: number;
12
y: number;
13
scored: boolean;
14
}
Copied!

constants.ts

1
export const gameSize = 20;
Copied!

html-renderer.ts

1
import { gameSize } from './constants';
2
import { Player, Platform } from './interfaces';
3
4
export const render = ([player, platforms]: [Player, Platform[]]) => {
5
document.body.innerHTML = `Lives: ${player.lives} Score: ${player.score} </br>`;
6
7
const game = Array(gameSize)
8
.fill(0)
9
.map(_ => Array(gameSize).fill(0));
10
game[player.x][player.y] = '*';
11
platforms.forEach(p => (game[p.x][p.y] = '_'));
12
13
game.forEach(r => {
14
r.forEach(c => (document.body.innerHTML += c === 0 ? '...' : c));
15
document.body.innerHTML += '<br/>';
16
});
17
};
Copied!

Operators Used

Last modified 1yr ago