Skip to content

Commit 4cf5397

Browse files
eubashEubash
and
Eubash
authored
feature: add practice code (#126)
Co-authored-by: Eubash <[email protected]>
1 parent 312aff7 commit 4cf5397

14 files changed

+591
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ dist
44
.vscode
55
.eslintcache
66
tmp
7+
.idea
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"responsive-preview": {
3+
"Mobile": [
4+
320,
5+
675
6+
],
7+
"Tablet": [
8+
1024,
9+
765
10+
],
11+
"Desktop": [
12+
1400,
13+
800
14+
],
15+
"Desktop HD": [
16+
1920,
17+
1080
18+
]
19+
}
20+
}

lessons/lesson21/code/index.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Parcel Sandbox</title>
5+
<meta charset="UTF-8" />
6+
</head>
7+
8+
<body>
9+
<h1>Game of Life</h1>
10+
<div id="app"></div>
11+
12+
<script src="src/index.ts"></script>
13+
</body>
14+
</html>

lessons/lesson21/code/package.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "game-of-life-ts-oopblank",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.html",
6+
"scripts": {
7+
"test": "jest",
8+
"start": "parcel index.html --open",
9+
"build": "parcel build index.html"
10+
},
11+
"dependencies": {
12+
"parcel-bundler": "^1.6.1"
13+
},
14+
"devDependencies": {
15+
"@babel/core": "7.2.0"
16+
},
17+
"resolutions": {
18+
"@babel/preset-env": "7.13.8"
19+
},
20+
"keywords": []
21+
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { Game } from "./Game";
2+
import { IGameField } from "./GameField";
3+
import { IGameView } from "./GameView";
4+
import { Cell } from "./types/Cell";
5+
6+
const sleep = (x: number) => new Promise((resolve) => setTimeout(resolve, x));
7+
8+
describe("Game", () => {
9+
let stepDurationMs = 10;
10+
let state: Cell[][];
11+
let gameField: IGameField;
12+
let gameView: IGameView;
13+
let onGameStateChange = jest.fn();
14+
let onFieldSizeChange = jest.fn();
15+
let onCellClick = jest.fn();
16+
17+
const getGameField = (): IGameField => ({
18+
getState: jest.fn(() => state),
19+
toggleCellState: jest.fn(),
20+
nextGeneration: jest.fn(),
21+
setSize: jest.fn()
22+
});
23+
24+
const getGameView = (): IGameView => ({
25+
updateGameField: jest.fn(),
26+
updateGameState: jest.fn(),
27+
onCellClick: jest.fn((cb) => {
28+
onCellClick = jest.fn(cb);
29+
}),
30+
onGameStateChange: jest.fn((cb) => {
31+
onGameStateChange = jest.fn(cb);
32+
}),
33+
onFieldSizeChange: jest.fn((cb) => {
34+
onFieldSizeChange = jest.fn(cb);
35+
})
36+
});
37+
38+
beforeEach(() => {
39+
state = [
40+
[Math.random(), Math.random()],
41+
[Math.random(), Math.random()],
42+
[Math.random(), Math.random()]
43+
];
44+
gameView = getGameView();
45+
gameField = getGameField();
46+
});
47+
48+
it("is a class", () => {
49+
expect(Game).toBeInstanceOf(Function);
50+
expect(new Game(gameField, gameView)).toBeInstanceOf(Game);
51+
});
52+
53+
describe("functionality", () => {
54+
let game: Game;
55+
beforeEach(() => {
56+
game = new Game(gameField, gameView, stepDurationMs);
57+
});
58+
59+
it("renders initial state on instantiating", () => {
60+
expect(gameField.getState).toHaveBeenCalled();
61+
expect(gameView.updateGameField).toHaveBeenCalledWith(state);
62+
expect(gameView.updateGameState).toHaveBeenCalledWith({
63+
isRunning: false,
64+
width: state[0].length,
65+
height: state.length
66+
});
67+
});
68+
69+
it("calls field.toggleCellState on view.onCellClick and renders with updated state", () => {
70+
state = [[1, 2, 3]];
71+
onCellClick(0, 1);
72+
expect(gameField.toggleCellState).toHaveBeenCalledWith(0, 1);
73+
expect(gameView.updateGameField).toHaveBeenCalledWith(state);
74+
});
75+
76+
it("calls field.setSize on view.onFieldSizeChange and renders with updated state", () => {
77+
state = [
78+
[1, 2, 3],
79+
[1, 2, 3],
80+
[1, 2, 3],
81+
[1, 2, 3]
82+
];
83+
const width = state[0].length;
84+
const height = state.length;
85+
onFieldSizeChange(width, height);
86+
expect(gameField.setSize).toHaveBeenCalledWith(width, height);
87+
expect(gameView.updateGameField).toHaveBeenCalledWith(state);
88+
expect(gameView.updateGameState).toHaveBeenCalledWith(
89+
expect.objectContaining({
90+
width,
91+
height
92+
})
93+
);
94+
});
95+
96+
it("is able to start/stop game with onGameStateChange", async () => {
97+
// https://github.com/codesandbox/codesandbox-client/issues/513
98+
expect(gameView.updateGameState).toHaveBeenCalledTimes(1);
99+
expect(gameField.getState).toHaveBeenCalledTimes(1);
100+
expect(gameView.updateGameField).toHaveBeenCalledTimes(1);
101+
expect(gameField.nextGeneration).toHaveBeenCalledTimes(0);
102+
await sleep(stepDurationMs);
103+
expect(gameField.getState).toHaveBeenCalledTimes(1);
104+
expect(gameView.updateGameField).toHaveBeenCalledTimes(1);
105+
await sleep(stepDurationMs);
106+
expect(gameField.getState).toHaveBeenCalledTimes(1);
107+
expect(gameView.updateGameField).toHaveBeenCalledTimes(1);
108+
expect(gameView.updateGameState).toHaveBeenCalledTimes(1);
109+
expect(gameField.nextGeneration).toHaveBeenCalledTimes(0);
110+
111+
onGameStateChange(true);
112+
113+
expect(gameField.nextGeneration).toHaveBeenCalledTimes(1);
114+
expect(gameView.updateGameState).toHaveBeenCalledTimes(2);
115+
await sleep(stepDurationMs);
116+
expect(gameField.nextGeneration).toHaveBeenCalledTimes(2);
117+
expect(gameField.getState).toHaveBeenCalledTimes(3);
118+
expect(gameView.updateGameField).toHaveBeenCalledTimes(3);
119+
await sleep(stepDurationMs);
120+
expect(gameField.nextGeneration).toHaveBeenCalledTimes(3);
121+
expect(gameField.getState).toHaveBeenCalledTimes(4);
122+
expect(gameView.updateGameField).toHaveBeenCalledTimes(4);
123+
// expect(gameView.updateGameState).toHaveBeenCalledTimes(4);
124+
125+
onGameStateChange(false);
126+
127+
expect(gameField.nextGeneration).toHaveBeenCalledTimes(3);
128+
expect(gameView.updateGameState).toHaveBeenCalledTimes(3);
129+
await sleep(stepDurationMs);
130+
expect(gameField.nextGeneration).toHaveBeenCalledTimes(3);
131+
expect(gameView.updateGameState).toHaveBeenCalledTimes(3);
132+
expect(gameField.getState).toHaveBeenCalledTimes(5);
133+
expect(gameView.updateGameField).toHaveBeenCalledTimes(5);
134+
await sleep(stepDurationMs);
135+
expect(gameField.nextGeneration).toHaveBeenCalledTimes(3);
136+
// expect(gameView.updateGameState).toHaveBeenCalledTimes(5);
137+
expect(gameField.getState).toHaveBeenCalledTimes(5);
138+
expect(gameView.updateGameField).toHaveBeenCalledTimes(5);
139+
await sleep(stepDurationMs);
140+
// expect(gameView.updateGameState).toHaveBeenCalledTimes(5);
141+
expect(gameField.getState).toHaveBeenCalledTimes(5);
142+
expect(gameView.updateGameField).toHaveBeenCalledTimes(5);
143+
expect(gameField.nextGeneration).toHaveBeenCalledTimes(3);
144+
145+
onGameStateChange(true);
146+
147+
expect(gameField.nextGeneration).toHaveBeenCalledTimes(4);
148+
expect(gameView.updateGameState).toHaveBeenCalledTimes(4);
149+
expect(gameField.getState).toHaveBeenCalledTimes(6);
150+
expect(gameView.updateGameField).toHaveBeenCalledTimes(6);
151+
await sleep(stepDurationMs);
152+
expect(gameField.nextGeneration).toHaveBeenCalledTimes(5);
153+
expect(gameView.updateGameState).toHaveBeenCalledTimes(4);
154+
expect(gameField.getState).toHaveBeenCalledTimes(7);
155+
expect(gameView.updateGameField).toHaveBeenCalledTimes(7);
156+
await sleep(stepDurationMs);
157+
expect(gameField.nextGeneration).toHaveBeenCalledTimes(6);
158+
expect(gameField.getState).toHaveBeenCalledTimes(8);
159+
expect(gameView.updateGameField).toHaveBeenCalledTimes(8);
160+
expect(gameView.updateGameState).toHaveBeenCalledTimes(4);
161+
162+
onGameStateChange(false);
163+
164+
expect(gameField.nextGeneration).toHaveBeenCalledTimes(6);
165+
expect(gameView.updateGameState).toHaveBeenCalledTimes(5);
166+
await sleep(stepDurationMs);
167+
expect(gameField.getState).toHaveBeenCalledTimes(9);
168+
expect(gameView.updateGameField).toHaveBeenCalledTimes(9);
169+
expect(gameField.nextGeneration).toHaveBeenCalledTimes(6);
170+
await sleep(stepDurationMs);
171+
expect(gameField.getState).toHaveBeenCalledTimes(9);
172+
expect(gameView.updateGameField).toHaveBeenCalledTimes(9);
173+
expect(gameView.updateGameState).toHaveBeenCalledTimes(5);
174+
expect(gameField.nextGeneration).toHaveBeenCalledTimes(6);
175+
});
176+
});
177+
});

lessons/lesson21/code/src/Game.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { GameField, IGameField } from "./GameField";
2+
import { GameView, IGameView } from "./GameView";
3+
import { Cell } from "./types/Cell";
4+
5+
export interface IGame {}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { GameField } from "./GameField";
2+
3+
describe.skip("GameField", () => {
4+
describe("public interface", () => {
5+
it("is a class", () => {
6+
expect(GameField).toBeInstanceOf(Function);
7+
expect(new GameField()).toBeInstanceOf(GameField);
8+
});
9+
10+
it("has a function getState", () => {
11+
const gameField = new GameField();
12+
expect(gameField.getState).toBeInstanceOf(Function);
13+
expect(gameField.getState()).toEqual([[]]);
14+
});
15+
});
16+
17+
describe("functional tests", () => {
18+
const width = 2;
19+
const height = 3;
20+
let gameField;
21+
beforeEach(() => {
22+
gameField = new GameField(width, height);
23+
});
24+
25+
it("supports settings side from constructor", () => {
26+
expect(gameField.getState()).toEqual([
27+
[0, 0],
28+
[0, 0],
29+
[0, 0]
30+
]);
31+
});
32+
33+
it("has .toggleCellState method", () => {
34+
expect(gameField.toggleCellState).toBeInstanceOf(Function);
35+
const [x1, y1] = [0, 0];
36+
const [x2, y2] = [1, 2];
37+
gameField.toggleCellState(x1, y1);
38+
gameField.toggleCellState(x2, y2);
39+
expect(gameField.getState()).toEqual([
40+
[1, 0],
41+
[0, 0],
42+
[0, 1]
43+
]);
44+
gameField.toggleCellState(x2, y2);
45+
expect(gameField.getState()).toEqual([
46+
[1, 0],
47+
[0, 0],
48+
[0, 0]
49+
]);
50+
});
51+
52+
it("has method .nextGeneration", () => {
53+
expect(gameField.nextGeneration).toBeInstanceOf(Function);
54+
const [x1, y1] = [0, 0];
55+
const [x2, y2] = [1, 2];
56+
gameField.toggleCellState(x1, y1);
57+
gameField.toggleCellState(x2, y2);
58+
expect(gameField.getState()).toEqual([
59+
[1, 0],
60+
[0, 0],
61+
[0, 1]
62+
]);
63+
gameField.nextGeneration();
64+
expect(gameField.getState()).toEqual([
65+
[0, 0],
66+
[0, 0],
67+
[0, 0]
68+
]);
69+
gameField.toggleCellState(0, 0);
70+
gameField.toggleCellState(1, 0);
71+
gameField.toggleCellState(0, 1);
72+
expect(gameField.getState()).toEqual([
73+
[1, 1],
74+
[1, 0],
75+
[0, 0]
76+
]);
77+
gameField.nextGeneration();
78+
expect(gameField.getState()).toEqual([
79+
[1, 1],
80+
[1, 1],
81+
[0, 0]
82+
]);
83+
});
84+
85+
it("has method .setSize(newWidth, newHeight)", () => {
86+
gameField.toggleCellState(0, 0);
87+
gameField.toggleCellState(1, 1);
88+
gameField.toggleCellState(0, 2);
89+
expect(gameField.getState()).toEqual([
90+
[1, 0],
91+
[0, 1],
92+
[1, 0]
93+
]);
94+
gameField.setSize(3, 4);
95+
expect(gameField.getState()).toEqual([
96+
[1, 0, 0],
97+
[0, 1, 0],
98+
[1, 0, 0],
99+
[0, 0, 0]
100+
]);
101+
gameField.setSize(2, 2);
102+
expect(gameField.getState()).toEqual([
103+
[1, 0],
104+
[0, 1]
105+
]);
106+
});
107+
});
108+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Cell } from "./types/Cell";
2+
3+
export interface IGameField {
4+
getState(): Cell[][];
5+
toggleCellState(x: number, y: number);
6+
nextGeneration();
7+
setSize(width: number, height: number);
8+
}

0 commit comments

Comments
 (0)