Learn RxJS
Search…
Breakout Game
By adamlubek
This recipe demonstrates an RxJS implementation of Breakout game.

Example Code

Breakout Game

index.ts

1
// RxJS v6+
2
import { fromEvent, of, interval, combineLatest, generate, noop } from 'rxjs';
3
import { map, mergeMap, pluck, startWith, scan, toArray, takeWhile, tap } from 'rxjs/operators';
4
import { gameSize } from './constants';
5
import { Player, Ball, GameObject } from './interfaces';
6
import { render } from './html-renderer';
7
8
const createGameObject = (x, y) => ({ x, y });
9
10
const player$ = combineLatest(
11
of({ ...createGameObject(gameSize - 2, (gameSize / 2) - 1), score: 0, lives: 3 }),
12
fromEvent(document, 'keyup').pipe(startWith({ code: '' }), pluck('code'))
13
).pipe(
14
map(([player, key]) => (
15
key === 'ArrowLeft'
16
? player.y -= 1
17
: key === 'ArrowRight'
18
? player.y += 1
19
: noop
20
, player)
21
)
22
)
23
24
const ball$ = combineLatest(
25
of({ ...createGameObject(gameSize / 2, (gameSize - 3)), dirX: 1, dirY: 1 }),
26
interval(150)
27
).pipe(
28
map(([ball, _]: [Ball, number]) => (
29
ball.dirX *= ball.x > 0 ? 1 : -1,
30
ball.dirY *= (ball.y > 0 && ball.y < gameSize - 1) ? 1 : -1,
31
ball.x += 1 * ball.dirX,
32
ball.y -= 1 * ball.dirY,
33
ball)
34
)
35
)
36
37
const bricks$ = generate(1, x => x < 8, x => x + 1)
38
.pipe(
39
mergeMap(r => generate(r % 2 === 0 ? 1 : 0, x => x < gameSize, x => x + 2)
40
.pipe(map(c => createGameObject(r, c)))
41
),
42
toArray()
43
)
44
45
const processGameCollisions = (_, [player, ball, bricks]: [Player, Ball, GameObject[]])
46
: [Player, Ball, GameObject[]] => (
47
(collidingBrickIndex => collidingBrickIndex > -1
48
? (bricks.splice(collidingBrickIndex, 1), ball.dirX *= -1, player.score++)
49
: noop
50
)(bricks.findIndex(e => e.x === ball.x && e.y === ball.y)),
51
ball.dirX *= player.x === ball.x && player.y === ball.y ? -1 : 1,
52
ball.x > player.x ? (player.lives-- , ball.x = (gameSize / 2) - 3) : noop,
53
[player, ball, bricks]
54
)
55
56
combineLatest(player$, ball$, bricks$)
57
.pipe(
58
scan<[Player, Ball, GameObject[]], [Player, Ball, GameObject[]]>(processGameCollisions),
59
tap(render),
60
takeWhile(([player]: [Player, Ball, GameObject[]]) => player.lives > 0)
61
).subscribe()
Copied!

interfaces.ts

1
export interface GameObject {
2
x: number;
3
y: number;
4
}
5
export interface Player extends GameObject {
6
score: number;
7
lives: number;
8
}
9
export interface Ball extends GameObject {
10
dirX: number;
11
dirY: number;
12
}
Copied!

constants.ts

1
export const gameSize = 20;
Copied!

html-renderer.ts

1
import { gameSize } from './constants';
2
import { Player, Ball, GameObject } from './interfaces';
3
4
const empty = 0;
5
const plyer = 1;
6
const bll = 2;
7
const brick = 3;
8
9
const createElem = col => {
10
const elem = document.createElement('div');
11
elem.classList.add('board');
12
elem.style.display = 'inline-block';
13
elem.style.marginLeft = '10px';
14
elem.style.height = '6px';
15
elem.style.width = '6px';
16
elem.style['background-color'] =
17
col === empty
18
? 'white'
19
: col === plyer
20
? 'cornflowerblue'
21
: col === bll
22
? 'gray'
23
: 'silver';
24
elem.style['border-radius'] = col === bll ? '100%' : '0%';
25
return elem;
26
};
27
28
export const render = ([player, ball, bricks]: [
29
Player,
30
Ball,
31
GameObject[]
32
]) => {
33
const game = Array(gameSize)
34
.fill(0)
35
.map(e => Array(gameSize).fill(0));
36
game[player.x][player.y] = plyer;
37
game[ball.x][ball.y] = bll;
38
bricks.forEach(b => (game[b.x][b.y] = brick));
39
40
document.body.innerHTML = `Score: ${player.score} Lives: ${player.lives} <br/>`;
41
game.forEach(r => {
42
const rowContainer = document.createElement('div');
43
r.forEach(c => rowContainer.appendChild(createElem(c)));
44
document.body.appendChild(rowContainer);
45
});
46
};
Copied!

Operators Used

Last modified 1yr ago