Learn RxJS
Search…
Tank Battle Game
By adamlubek
This recipe demonstrates RxJS implementation of Tank Battle like game.

Example Code

Could not load image
Tank Battle

index.ts

1
// RxJS v6+
2
import { fromEvent, combineLatest, interval } from 'rxjs';
3
import { scan, tap, startWith } from 'rxjs/operators';
4
import { gameSize, down, up, right, left, p1Color, p2Color } from './constants';
5
import { State } from './interfaces';
6
import {
7
updatePlayer,
8
addShots,
9
updateShots,
10
checkCollisions,
11
initialState
12
} from './game';
13
import { paint } from './html-renderer';
14
15
combineLatest(
16
interval(100),
17
fromEvent(document, 'keydown').pipe(startWith({ key: '' }))
18
)
19
.pipe(
20
scan < [number, KeyboardEvent],
21
State >
22
((state, [_, event]) => (
23
updatePlayer(state.players[0], event.key, 'w', 's', 'a', 'd'),
24
updatePlayer(state.players[1], event.key, 'i', 'k', 'j', 'l'),
25
addShots(state, event.key),
26
(state.shots = updateShots(state.shots)),
27
checkCollisions(state),
28
state
29
),
30
initialState),
31
tap(paint)
32
)
33
.subscribe();
Copied!

game.ts

1
import {
2
gameSize,
3
down,
4
up,
5
right,
6
left,
7
p1Color,
8
p2Color,
9
p1Shot,
10
p2Shot
11
} from './constants';
12
import { GameObject, State } from './interfaces';
13
14
const gameObject = (x, y, g, c): GameObject => ({ x, y, g, s: 0, c: c });
15
const noop = (): void => {};
16
17
export const updatePlayer = (
18
p: GameObject,
19
key: string,
20
u,
21
d,
22
l,
23
r
24
): GameObject => (
25
key === d
26
? ((p.x += p.x < gameSize - 1 ? 1 : 0), (p.g = down))
27
: key === u
28
? ((p.x += p.x > 0 ? -1 : 0), (p.g = up))
29
: noop,
30
key === r
31
? ((p.y += p.y < gameSize - 1 ? 1 : 0), (p.g = right))
32
: key === l
33
? ((p.y += p.y > 0 ? -1 : 0), (p.g = left))
34
: noop,
35
p
36
);
37
38
export const addShot = (player: GameObject): GameObject => ({
39
x: player.x,
40
y: player.y,
41
g: player.g
42
});
43
44
export const addShots = (state: State, key: string): void =>
45
state.shots.push(
46
key === p1Shot
47
? addShot(state.players[0])
48
: key === p2Shot
49
? addShot(state.players[1])
50
: []
51
);
52
53
export const updateShots = (shots: GameObject[]): GameObject[] =>
54
shots
55
.filter(s => s.x > 0 && s.x < gameSize - 1 && s.y > 0 && s.y < gameSize)
56
.map(
57
s => (
58
s.g === down
59
? (s.x += 1)
60
: s.g === up
61
? (s.x += -1)
62
: s.g === right
63
? (s.y += 1)
64
: s.g === left
65
? (s.y += -1)
66
: () => {},
67
s
68
)
69
);
70
71
export const initialState: State = {
72
players: [
73
gameObject(1, 1, right, p1Color),
74
gameObject(gameSize - 2, gameSize - 2, left, p2Color)
75
],
76
shots: []
77
};
78
79
export const checkCollisions = (state: State): void =>
80
state.players.forEach((p, i) => {
81
const collidingShotIndex = state.shots.findIndex(
82
s => s.x === p.x && s.y === p.y
83
);
84
if (collidingShotIndex > -1) {
85
if (i === 0) {
86
state.players[1].s += 1;
87
} else {
88
state.players[0].s += 1;
89
}
90
state.shots.splice(collidingShotIndex, 1);
91
}
92
});
Copied!

interfaces.ts

1
export interface GameObject {
2
x: number;
3
y: number;
4
g: string;
5
s: number;
6
c: string;
7
}
8
9
export interface State {
10
players: GameObject[];
11
shots: GameObject[];
12
}
Copied!

constants.ts

1
export const gameSize = 20;
2
export const up = '^';
3
export const down = 'v';
4
export const left = '<';
5
export const right = '>';
6
export const empty = 0;
7
export const p1Color = 'DarkViolet';
8
export const p2Color = 'CornflowerBlue';
9
export const p1Shot = 'c';
10
export const p2Shot = 'n';
Copied!

html-renderer.ts

1
import { gameSize, empty, p1Color, p2Color } from './constants';
2
import { State, GameObject } from './interfaces';
3
4
const createElem = (gameObject: GameObject) => {
5
const elem = document.createElement('div');
6
elem.style.display = 'inline-block';
7
elem.style.marginLeft = '10px';
8
elem.style.height = '6px';
9
elem.style.width = '6px';
10
elem.style.color = gameObject.c;
11
elem.innerText = gameObject === empty ? ' ' : gameObject.g;
12
13
return elem;
14
};
15
16
const paintPlayerScore = (score: number, color: string) => {
17
const scoreElem = document.createElement('span');
18
scoreElem.innerHTML = `P1: ${score} `;
19
scoreElem.style.color = color;
20
document.body.appendChild(scoreElem);
21
};
22
23
const paintScores = (state: State) => {
24
document.body.innerHTML = 'Scores: ';
25
paintPlayerScore(state.players[0].s, p1Color);
26
paintPlayerScore(state.players[1].s, p2Color);
27
};
28
29
const painInfo = () => {
30
document.body.innerHTML += 'This game requires 2 players :)';
31
document.body.innerHTML += '<br/>';
32
document.body.innerHTML += 'Player 1 controls: wsad, fire: c';
33
document.body.innerHTML += '<br/>';
34
document.body.innerHTML += 'Player 2 controls: ikjl, fire: n';
35
};
36
37
const emptyGame = () =>
38
Array(gameSize)
39
.fill(empty)
40
.map(_ => Array(gameSize).fill(empty));
41
const paintGame = (state: State) => {
42
const game = emptyGame();
43
state.players.forEach(p => (game[p.x][p.y] = { g: p.g, c: p.c }));
44
state.shots.forEach(s => (game[s.x][s.y] = { g: '*', c: 'black' }));
45
46
game.forEach(row => {
47
const rowContainer = document.createElement('div');
48
row.forEach(col => rowContainer.appendChild(createElem(col)));
49
document.body.appendChild(rowContainer);
50
});
51
};
52
53
export const paint = (state: State) => {
54
paintScores(state);
55
document.body.innerHTML += '<br/>';
56
paintGame(state);
57
painInfo();
58
};
Copied!

Operators Used

Last modified 1yr ago