├── src
├── main.ts
└── main.spec.ts
├── public
├── style.css
├── index.html
└── normilize.css
├── .nvmrc
├── .editorconfig
├── jest.config.js
├── tsconfig.json
├── README.md
├── package.json
├── webpack.config.js
├── .gitignore
└── .eslintrc.js
/src/main.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/style.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v16.16.0
2 |
--------------------------------------------------------------------------------
/src/main.spec.ts:
--------------------------------------------------------------------------------
1 | describe('>>> Fake test', () => {
2 | it('should be true', () => {
3 | expect(true).toBe(true)
4 | })
5 | })
6 |
7 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | The Game
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testEnvironment: 'jsdom',
3 | moduleNameMapper: {
4 | '^@/(.*)$': '/src/$1'
5 | },
6 | moduleFileExtensions: ['js', 'ts'],
7 | transform: {
8 | '^.+\\.ts$': 'ts-jest'
9 | },
10 | setupFiles: [],
11 | transformIgnorePatterns: [
12 | '/node_modules/'
13 | ],
14 | testPathIgnorePatterns: [
15 | '__testUtils__'
16 | ],
17 | clearMocks: true,
18 | restoreMocks: true,
19 | testMatch: [
20 | '**/*.spec.(js|ts)'
21 | ],
22 | roots: [
23 | '/src'
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "es6",
5 | "outDir": "./dist/",
6 | "moduleResolution": "node",
7 | "experimentalDecorators": true,
8 | "lib": [
9 | "esnext",
10 | "esnext.asynciterable",
11 | "dom"
12 | ],
13 | "importHelpers": true,
14 | "esModuleInterop": true,
15 | "strictPropertyInitialization": false,
16 | "allowJs": true,
17 | "sourceMap": true,
18 | "strict": true,
19 | "baseUrl": "src",
20 | "paths": {
21 | "@/*": [
22 | "./*"
23 | ]
24 | },
25 | "types": [
26 | "jest"
27 | ],
28 | },
29 | "exclude": [
30 | "node_modules"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Building a Game With TypeScript
2 | > In this [series of tutorials](https://medium.com/@gregsolo/gamedev-patterns-and-algorithms-in-action-with-typescript-d29b913858e) we are discussing how to build a simple turn-based game from scratch using TypeScript, no external libraries, SOLID architecture, incremental approach and unit testing.
3 |
4 | ## Project setup
5 | - install Node.js (the version is specified in package.json)
6 | - if you have nvm installed:
7 | ```
8 | nvm use
9 | ```
10 | - Install dependencies
11 | ```
12 | npm install
13 | ```
14 |
15 | ## Compiles and hot-reloads for development
16 | ```
17 | npm start
18 | ```
19 |
20 | ## Run your unit tests
21 | ```
22 | npm t
23 | ```
24 | ## Lints and fixes files
25 | ```
26 | npm run lint
27 | ```
28 |
29 | ## Tech stack
30 | - TypeScript
31 | - Jest / ESLint
32 |
33 | ## Links
34 | [Complete tutorial](https://medium.com/@gregsolo/gamedev-patterns-and-algorithms-in-action-with-typescript-d29b913858e)
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tic-tac-toe-ts",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "webpack-dev-server --config webpack.config.js",
8 | "test": "jest",
9 | "lint": "eslint src/**"
10 | },
11 | "engines": {
12 | "node": "16.16.0",
13 | "npm": "8.11.0"
14 | },
15 | "author": "",
16 | "license": "ISC",
17 | "devDependencies": {
18 | "@babel/cli": "^7.18.6",
19 | "@babel/core": "^7.18.6",
20 | "@types/jest": "^28.1.4",
21 | "@typescript-eslint/eslint-plugin": "^5.30.5",
22 | "@typescript-eslint/parser": "^5.0.0",
23 | "babel-jest": "^28.1.2",
24 | "babel-loader": "^8.2.5",
25 | "babel-preset-env": "^1.7.0",
26 | "canvas": "^2.9.3",
27 | "clean-webpack-plugin": "^4.0.0",
28 | "eslint": "^8.19.0",
29 | "html-webpack-plugin": "^5.5.0",
30 | "jest": "^28.1.2",
31 | "jest-environment-jsdom": "^28.1.2",
32 | "pre-commit": "^1.2.2",
33 | "ts-jest": "^28.0.5",
34 | "ts-loader": "^9.3.1",
35 | "typescript": "^4.3.0",
36 | "webpack": "^5.73.0",
37 | "webpack-cli": "^4.10.0",
38 | "webpack-dev-server": "^4.9.3"
39 | },
40 | "pre-commit": [
41 | "lint",
42 | "test"
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const path = require('path')
4 | const webpack = require('webpack')
5 | const { CleanWebpackPlugin } = require('clean-webpack-plugin')
6 | const HtmlWebpackPlugin = require('html-webpack-plugin')
7 |
8 | module.exports = {
9 | entry: {
10 | app: `${path.join(__dirname, 'src')}/main.ts`
11 | },
12 | output: {
13 | filename: '[name].[hash].js',
14 | path: path.resolve(__dirname, 'dist'),
15 | publicPath: '/'
16 | },
17 | module: {
18 | rules: [
19 | {
20 | test: /\.ts/,
21 | use: 'ts-loader',
22 | exclude: /node_modules/,
23 | }, {
24 | test: /\.js$/,
25 | exclude: /(node_modules)/,
26 | use: {
27 | loader: 'babel-loader'
28 | }
29 | }
30 | ]
31 | },
32 | mode: 'development',
33 | devtool: 'source-map',
34 | devServer: {
35 | hot: true,
36 | historyApiFallback: true
37 | },
38 | resolve: {
39 | extensions: ['.ts', '.js'],
40 | alias: {
41 | '@': path.join(__dirname, 'src')
42 | }
43 | },
44 | plugins: [
45 | new webpack.HotModuleReplacementPlugin(),
46 | new CleanWebpackPlugin(),
47 | new HtmlWebpackPlugin({
48 | template: `${path.join(__dirname, 'public')}/index.html`,
49 | scriptLoading: 'defer'
50 | }),
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Node template
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 |
10 | # Runtime data
11 | pids
12 | *.pid
13 | *.seed
14 | *.pid.lock
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # nyc test coverage
23 | .nyc_output
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # Bower dependency directory (https://bower.io/)
29 | bower_components
30 |
31 | # node-waf configuration
32 | .lock-wscript
33 |
34 | # Compiled binary addons (https://nodejs.org/api/addons.html)
35 | build/Release
36 |
37 | # Dependency directories
38 | node_modules/
39 | jspm_packages/
40 |
41 | # TypeScript v1 declaration files
42 | typings/
43 |
44 | # Optional npm cache directory
45 | .npm
46 |
47 | # Optional eslint cache
48 | .eslintcache
49 |
50 | # Optional REPL history
51 | .node_repl_history
52 |
53 | # Output of 'npm pack'
54 | *.tgz
55 |
56 | # Yarn Integrity file
57 | .yarn-integrity
58 |
59 | # dotenv environment variables file
60 | .env
61 |
62 | # parcel-bundler cache (https://parceljs.org/)
63 | .cache
64 |
65 | # next.js build output
66 | .next
67 |
68 | # nuxt.js build output
69 | .nuxt
70 |
71 | # Nuxt generate
72 | dist
73 |
74 | # vuepress build output
75 | .vuepress/dist
76 |
77 | # Serverless directories
78 | .serverless
79 |
80 | # IDE / Editor
81 | .idea
82 |
83 | # Service worker
84 | sw.*
85 |
86 | # Mac OSX
87 | .DS_Store
88 |
89 | # Vim swap files
90 | *.swp
91 |
92 | // VS Code setting
93 | .vscode
94 |
95 | // Cypress
96 | cypress/screenshots
97 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | jest: true,
6 | es6: true
7 | },
8 | parserOptions: {
9 | parser: '@typescript-eslint/parser'
10 | },
11 | extends: [
12 | 'eslint:recommended',
13 | 'plugin:@typescript-eslint/recommended'
14 | ],
15 | // add your custom rules here
16 | rules: {
17 | 'semi': [
18 | 'error',
19 | 'never'
20 | ],
21 | 'quotes': [
22 | 'error',
23 | 'single'
24 | ],
25 | '@typescript-eslint/explicit-function-return-type': [
26 | 'error',
27 | {
28 | allowExpressions: false,
29 | allowTypedFunctionExpressions: true,
30 | allowHigherOrderFunctions: true,
31 | }
32 | ],
33 | '@typescript-eslint/naming-convention': [
34 | 'error',
35 | {
36 | 'selector': 'interface',
37 | 'format': ['PascalCase'],
38 | 'custom': {
39 | 'regex': '^I[A-Z]',
40 | 'match': true
41 | }
42 | }
43 | ],
44 | 'no-unused-vars': 'off', // note you must disable the base rule as it can report incorrect errors
45 | '@typescript-eslint/no-unused-vars': [
46 | 'error',
47 | {
48 | 'vars': 'all',
49 | 'args': 'none',
50 | 'ignoreRestSiblings': false
51 | }
52 | ],
53 | 'no-useless-constructor': 'off',
54 | '@typescript-eslint/no-useless-constructor': 'error',
55 | '@typescript-eslint/member-delimiter-style': [
56 | 'error',
57 | {
58 | 'multiline': {
59 | 'delimiter': 'none',
60 | 'requireLast': false
61 | },
62 | 'singleline': {
63 | 'delimiter': 'semi',
64 | 'requireLast': false
65 | }
66 | }
67 | ]
68 | },
69 | overrides: [
70 | {
71 | 'files': ['*.js'],
72 | rules: {
73 | '@typescript-eslint/explicit-function-return-type': 'off',
74 | }
75 | }
76 | ]
77 | }
78 |
--------------------------------------------------------------------------------
/public/normilize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15; /* 1 */
13 | -webkit-text-size-adjust: 100%; /* 2 */
14 | }
15 |
16 | /* Sections
17 | ========================================================================== */
18 |
19 | /**
20 | * Remove the margin in all browsers.
21 | */
22 |
23 | body {
24 | margin: 0;
25 | }
26 |
27 | /**
28 | * Render the `main` element consistently in IE.
29 | */
30 |
31 | main {
32 | display: block;
33 | }
34 |
35 | /**
36 | * Correct the font size and margin on `h1` elements within `section` and
37 | * `article` contexts in Chrome, Firefox, and Safari.
38 | */
39 |
40 | h1 {
41 | font-size: 2em;
42 | margin: 0.67em 0;
43 | }
44 |
45 | /* Grouping content
46 | ========================================================================== */
47 |
48 | /**
49 | * 1. Add the correct box sizing in Firefox.
50 | * 2. Show the overflow in Edge and IE.
51 | */
52 |
53 | hr {
54 | box-sizing: content-box; /* 1 */
55 | height: 0; /* 1 */
56 | overflow: visible; /* 2 */
57 | }
58 |
59 | /**
60 | * 1. Correct the inheritance and scaling of font size in all browsers.
61 | * 2. Correct the odd `em` font sizing in all browsers.
62 | */
63 |
64 | pre {
65 | font-family: monospace, monospace; /* 1 */
66 | font-size: 1em; /* 2 */
67 | }
68 |
69 | /* Text-level semantics
70 | ========================================================================== */
71 |
72 | /**
73 | * Remove the gray background on active links in IE 10.
74 | */
75 |
76 | a {
77 | background-color: transparent;
78 | }
79 |
80 | /**
81 | * 1. Remove the bottom border in Chrome 57-
82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
83 | */
84 |
85 | abbr[title] {
86 | border-bottom: none; /* 1 */
87 | text-decoration: underline; /* 2 */
88 | text-decoration: underline dotted; /* 2 */
89 | }
90 |
91 | /**
92 | * Add the correct font weight in Chrome, Edge, and Safari.
93 | */
94 |
95 | b,
96 | strong {
97 | font-weight: bolder;
98 | }
99 |
100 | /**
101 | * 1. Correct the inheritance and scaling of font size in all browsers.
102 | * 2. Correct the odd `em` font sizing in all browsers.
103 | */
104 |
105 | code,
106 | kbd,
107 | samp {
108 | font-family: monospace, monospace; /* 1 */
109 | font-size: 1em; /* 2 */
110 | }
111 |
112 | /**
113 | * Add the correct font size in all browsers.
114 | */
115 |
116 | small {
117 | font-size: 80%;
118 | }
119 |
120 | /**
121 | * Prevent `sub` and `sup` elements from affecting the line height in
122 | * all browsers.
123 | */
124 |
125 | sub,
126 | sup {
127 | font-size: 75%;
128 | line-height: 0;
129 | position: relative;
130 | vertical-align: baseline;
131 | }
132 |
133 | sub {
134 | bottom: -0.25em;
135 | }
136 |
137 | sup {
138 | top: -0.5em;
139 | }
140 |
141 | /* Embedded content
142 | ========================================================================== */
143 |
144 | /**
145 | * Remove the border on images inside links in IE 10.
146 | */
147 |
148 | img {
149 | border-style: none;
150 | }
151 |
152 | /* Forms
153 | ========================================================================== */
154 |
155 | /**
156 | * 1. Change the font styles in all browsers.
157 | * 2. Remove the margin in Firefox and Safari.
158 | */
159 |
160 | button,
161 | input,
162 | optgroup,
163 | select,
164 | textarea {
165 | font-family: inherit; /* 1 */
166 | font-size: 100%; /* 1 */
167 | line-height: 1.15; /* 1 */
168 | margin: 0; /* 2 */
169 | }
170 |
171 | /**
172 | * Show the overflow in IE.
173 | * 1. Show the overflow in Edge.
174 | */
175 |
176 | button,
177 | input { /* 1 */
178 | overflow: visible;
179 | }
180 |
181 | /**
182 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
183 | * 1. Remove the inheritance of text transform in Firefox.
184 | */
185 |
186 | button,
187 | select { /* 1 */
188 | text-transform: none;
189 | }
190 |
191 | /**
192 | * Correct the inability to style clickable types in iOS and Safari.
193 | */
194 |
195 | button,
196 | [type="button"],
197 | [type="reset"],
198 | [type="submit"] {
199 | -webkit-appearance: button;
200 | }
201 |
202 | /**
203 | * Remove the inner border and padding in Firefox.
204 | */
205 |
206 | button::-moz-focus-inner,
207 | [type="button"]::-moz-focus-inner,
208 | [type="reset"]::-moz-focus-inner,
209 | [type="submit"]::-moz-focus-inner {
210 | border-style: none;
211 | padding: 0;
212 | }
213 |
214 | /**
215 | * Restore the focus styles unset by the previous rule.
216 | */
217 |
218 | button:-moz-focusring,
219 | [type="button"]:-moz-focusring,
220 | [type="reset"]:-moz-focusring,
221 | [type="submit"]:-moz-focusring {
222 | outline: 1px dotted ButtonText;
223 | }
224 |
225 | /**
226 | * Correct the padding in Firefox.
227 | */
228 |
229 | fieldset {
230 | padding: 0.35em 0.75em 0.625em;
231 | }
232 |
233 | /**
234 | * 1. Correct the text wrapping in Edge and IE.
235 | * 2. Correct the color inheritance from `fieldset` elements in IE.
236 | * 3. Remove the padding so developers are not caught out when they zero out
237 | * `fieldset` elements in all browsers.
238 | */
239 |
240 | legend {
241 | box-sizing: border-box; /* 1 */
242 | color: inherit; /* 2 */
243 | display: table; /* 1 */
244 | max-width: 100%; /* 1 */
245 | padding: 0; /* 3 */
246 | white-space: normal; /* 1 */
247 | }
248 |
249 | /**
250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
251 | */
252 |
253 | progress {
254 | vertical-align: baseline;
255 | }
256 |
257 | /**
258 | * Remove the default vertical scrollbar in IE 10+.
259 | */
260 |
261 | textarea {
262 | overflow: auto;
263 | }
264 |
265 | /**
266 | * 1. Add the correct box sizing in IE 10.
267 | * 2. Remove the padding in IE 10.
268 | */
269 |
270 | [type="checkbox"],
271 | [type="radio"] {
272 | box-sizing: border-box; /* 1 */
273 | padding: 0; /* 2 */
274 | }
275 |
276 | /**
277 | * Correct the cursor style of increment and decrement buttons in Chrome.
278 | */
279 |
280 | [type="number"]::-webkit-inner-spin-button,
281 | [type="number"]::-webkit-outer-spin-button {
282 | height: auto;
283 | }
284 |
285 | /**
286 | * 1. Correct the odd appearance in Chrome and Safari.
287 | * 2. Correct the outline style in Safari.
288 | */
289 |
290 | [type="search"] {
291 | -webkit-appearance: textfield; /* 1 */
292 | outline-offset: -2px; /* 2 */
293 | }
294 |
295 | /**
296 | * Remove the inner padding in Chrome and Safari on macOS.
297 | */
298 |
299 | [type="search"]::-webkit-search-decoration {
300 | -webkit-appearance: none;
301 | }
302 |
303 | /**
304 | * 1. Correct the inability to style clickable types in iOS and Safari.
305 | * 2. Change font properties to `inherit` in Safari.
306 | */
307 |
308 | ::-webkit-file-upload-button {
309 | -webkit-appearance: button; /* 1 */
310 | font: inherit; /* 2 */
311 | }
312 |
313 | /* Interactive
314 | ========================================================================== */
315 |
316 | /*
317 | * Add the correct display in Edge, IE 10+, and Firefox.
318 | */
319 |
320 | details {
321 | display: block;
322 | }
323 |
324 | /*
325 | * Add the correct display in all browsers.
326 | */
327 |
328 | summary {
329 | display: list-item;
330 | }
331 |
332 | /* Misc
333 | ========================================================================== */
334 |
335 | /**
336 | * Add the correct display in IE 10+.
337 | */
338 |
339 | template {
340 | display: none;
341 | }
342 |
343 | /**
344 | * Add the correct display in IE 10.
345 | */
346 |
347 | [hidden] {
348 | display: none;
349 | }
350 |
--------------------------------------------------------------------------------