t3core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -0
- package/dist/eslint.config.d.mts +2 -0
- package/dist/eslint.config.mjs +86 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/src/app.d.ts +1 -0
- package/dist/src/app.js +9 -0
- package/dist/src/core/Board.d.ts +34 -0
- package/dist/src/core/Board.js +51 -0
- package/dist/src/core/Game.d.ts +42 -0
- package/dist/src/core/Game.js +83 -0
- package/dist/src/core/constants/index.d.ts +2 -0
- package/dist/src/core/constants/index.js +14 -0
- package/dist/src/core/index.d.ts +30 -0
- package/dist/src/core/index.js +14 -0
- package/dist/src/core/tests/gameCore.test.d.ts +1 -0
- package/dist/src/core/tests/gameCore.test.js +50 -0
- package/dist/src/core/tests/getWinnerFromFields.test.d.ts +1 -0
- package/dist/src/core/tests/getWinnerFromFields.test.js +24 -0
- package/dist/src/core/types/Board.d.ts +9 -0
- package/dist/src/core/types/Board.js +2 -0
- package/dist/src/core/types/Game.d.ts +21 -0
- package/dist/src/core/types/Game.js +2 -0
- package/dist/src/core/types/Symbol.d.ts +3 -0
- package/dist/src/core/types/Symbol.js +2 -0
- package/dist/src/core/utils/getWinnerFromFields.d.ts +2 -0
- package/dist/src/core/utils/getWinnerFromFields.js +16 -0
- package/dist/src/ui/components/Header/Header.d.ts +1 -0
- package/dist/src/ui/components/Header/Header.js +20 -0
- package/dist/src/ui/components/Header/index.d.ts +1 -0
- package/dist/src/ui/components/Header/index.js +17 -0
- package/dist/src/ui/components/UserInput/UserInput.d.ts +2 -0
- package/dist/src/ui/components/UserInput/UserInput.js +20 -0
- package/dist/src/ui/components/UserInput/index.d.ts +1 -0
- package/dist/src/ui/components/UserInput/index.js +17 -0
- package/dist/src/ui/features/game/components/Board/Board.d.ts +1 -0
- package/dist/src/ui/features/game/components/Board/Board.js +21 -0
- package/dist/src/ui/features/game/components/Board/index.d.ts +1 -0
- package/dist/src/ui/features/game/components/Board/index.js +17 -0
- package/dist/src/ui/features/game/components/GameEntryMessage/GameEntryMessage.d.ts +1 -0
- package/dist/src/ui/features/game/components/GameEntryMessage/GameEntryMessage.js +22 -0
- package/dist/src/ui/features/game/components/GameEntryMessage/index.d.ts +1 -0
- package/dist/src/ui/features/game/components/GameEntryMessage/index.js +17 -0
- package/dist/src/ui/features/game/components/GameHeader/GameHeader.d.ts +1 -0
- package/dist/src/ui/features/game/components/GameHeader/GameHeader.js +17 -0
- package/dist/src/ui/features/game/components/GameHeader/index.d.ts +1 -0
- package/dist/src/ui/features/game/components/GameHeader/index.js +17 -0
- package/dist/src/ui/features/game/components/GameStatusMessage/GameStatusMessage.d.ts +1 -0
- package/dist/src/ui/features/game/components/GameStatusMessage/GameStatusMessage.js +28 -0
- package/dist/src/ui/features/game/components/GameStatusMessage/index.d.ts +1 -0
- package/dist/src/ui/features/game/components/GameStatusMessage/index.js +17 -0
- package/dist/src/ui/features/game/components/PlayerEntry/PlayerEntry.d.ts +1 -0
- package/dist/src/ui/features/game/components/PlayerEntry/PlayerEntry.js +37 -0
- package/dist/src/ui/features/game/components/PlayerEntry/index.d.ts +1 -0
- package/dist/src/ui/features/game/components/PlayerEntry/index.js +17 -0
- package/dist/src/ui/features/game/components/PlayerEntry/utils/getPlayerAnswer.d.ts +1 -0
- package/dist/src/ui/features/game/components/PlayerEntry/utils/getPlayerAnswer.js +34 -0
- package/dist/src/ui/features/game/components/PlayerEntry/utils/playAgain.d.ts +1 -0
- package/dist/src/ui/features/game/components/PlayerEntry/utils/playAgain.js +20 -0
- package/dist/src/ui/features/game/components/PlayerEntry/utils/validatePlayerEntry.d.ts +1 -0
- package/dist/src/ui/features/game/components/PlayerEntry/utils/validatePlayerEntry.js +18 -0
- package/dist/src/ui/features/game/services/gameSession.d.ts +3 -0
- package/dist/src/ui/features/game/services/gameSession.js +19 -0
- package/dist/src/ui/features/game/util/colorLabelSymbol.d.ts +2 -0
- package/dist/src/ui/features/game/util/colorLabelSymbol.js +15 -0
- package/dist/src/ui/features/menu/components/MenuEntry/MenuEntry.d.ts +1 -0
- package/dist/src/ui/features/menu/components/MenuEntry/MenuEntry.js +36 -0
- package/dist/src/ui/features/menu/components/MenuEntry/index.d.ts +1 -0
- package/dist/src/ui/features/menu/components/MenuEntry/index.js +17 -0
- package/dist/src/ui/features/menu/components/MenuHeader/MenuHeader.d.ts +1 -0
- package/dist/src/ui/features/menu/components/MenuHeader/MenuHeader.js +18 -0
- package/dist/src/ui/features/menu/components/MenuHeader/index.d.ts +1 -0
- package/dist/src/ui/features/menu/components/MenuHeader/index.js +17 -0
- package/dist/src/ui/features/menu/components/MenuOptions/MenuOptions.d.ts +1 -0
- package/dist/src/ui/features/menu/components/MenuOptions/MenuOptions.js +18 -0
- package/dist/src/ui/features/menu/components/MenuOptions/index.d.ts +1 -0
- package/dist/src/ui/features/menu/components/MenuOptions/index.js +17 -0
- package/dist/src/ui/features/menu/constants/menuItems.d.ts +4 -0
- package/dist/src/ui/features/menu/constants/menuItems.js +8 -0
- package/dist/src/ui/features/settings/components/SettingsEntry/SettingsEntry.d.ts +1 -0
- package/dist/src/ui/features/settings/components/SettingsEntry/SettingsEntry.js +42 -0
- package/dist/src/ui/features/settings/components/SettingsEntry/index.d.ts +1 -0
- package/dist/src/ui/features/settings/components/SettingsEntry/index.js +17 -0
- package/dist/src/ui/features/settings/components/SettingsHeader/SettingsHeader.d.ts +1 -0
- package/dist/src/ui/features/settings/components/SettingsHeader/SettingsHeader.js +18 -0
- package/dist/src/ui/features/settings/components/SettingsHeader/index.d.ts +1 -0
- package/dist/src/ui/features/settings/components/SettingsHeader/index.js +17 -0
- package/dist/src/ui/features/settings/components/SettingsOptions/SettingsOptions.d.ts +1 -0
- package/dist/src/ui/features/settings/components/SettingsOptions/SettingsOptions.js +34 -0
- package/dist/src/ui/features/settings/components/SettingsOptions/index.d.ts +1 -0
- package/dist/src/ui/features/settings/components/SettingsOptions/index.js +17 -0
- package/dist/src/ui/features/settings/constants/settingsOptions.d.ts +8 -0
- package/dist/src/ui/features/settings/constants/settingsOptions.js +7 -0
- package/dist/src/ui/global/settings.global.d.ts +12 -0
- package/dist/src/ui/global/settings.global.js +24 -0
- package/dist/src/ui/navigation/index.d.ts +3 -0
- package/dist/src/ui/navigation/index.js +27 -0
- package/dist/src/ui/navigation/routes.d.ts +6 -0
- package/dist/src/ui/navigation/routes.js +8 -0
- package/dist/src/ui/screens/Game.screen.d.ts +1 -0
- package/dist/src/ui/screens/Game.screen.js +20 -0
- package/dist/src/ui/screens/Menu.screen.d.ts +1 -0
- package/dist/src/ui/screens/Menu.screen.js +25 -0
- package/dist/src/ui/screens/Settings.screen.d.ts +1 -0
- package/dist/src/ui/screens/Settings.screen.js +16 -0
- package/dist/src/ui/utils/beepAndClear.d.ts +1 -0
- package/dist/src/ui/utils/beepAndClear.js +9 -0
- package/dist/src/ui/utils/beepSound.d.ts +1 -0
- package/dist/src/ui/utils/beepSound.js +10 -0
- package/dist/src/ui/utils/styledLabel.d.ts +9 -0
- package/dist/src/ui/utils/styledLabel.js +76 -0
- package/dist/vitest.config.d.mts +2 -0
- package/dist/vitest.config.mjs +6 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# TIC-TAC-TOE-CORE (t3c)
|
|
2
|
+
|
|
3
|
+
A reusable TypeScript core library for Tic Tac Toe games with a built-in CLI.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install t3c
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage as a Library
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Game } from 't3c';
|
|
15
|
+
|
|
16
|
+
// Create a game with default symbols 'O' and 'X'
|
|
17
|
+
const game = new Game();
|
|
18
|
+
|
|
19
|
+
// Make a move (field 1-9)
|
|
20
|
+
game.savePlayerSelection(5);
|
|
21
|
+
|
|
22
|
+
// Check game status
|
|
23
|
+
console.log(game.gameStatus); // { status: 'running' } | { status: 'win', winner: 'O' } | { status: 'draw' }
|
|
24
|
+
console.log(game.currentPlayer); // 'O' or 'X'
|
|
25
|
+
|
|
26
|
+
// Check if field is already selected
|
|
27
|
+
console.log(game.isFieldSelected(5)); // true
|
|
28
|
+
|
|
29
|
+
// Access the board
|
|
30
|
+
console.log(game.getBoard()); // [1, 2, 3, 4, 'O', 6, 7, 8, 9]
|
|
31
|
+
|
|
32
|
+
// Reset the game
|
|
33
|
+
game.reset();
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### API
|
|
37
|
+
|
|
38
|
+
#### `Game`
|
|
39
|
+
|
|
40
|
+
| Property/Method | Description |
|
|
41
|
+
| --------------- | ------------------------------ |
|
|
42
|
+
| `constructor()` | Create a new game with default symbols `['O', 'X']` |
|
|
43
|
+
| `currentPlayer` | Get the current player's symbol |
|
|
44
|
+
| `gameStatus` | Get current game status |
|
|
45
|
+
| `getBoard()` | Returns the current board state as `(number \| PlayerSymbol)[]` |
|
|
46
|
+
| `savePlayerSelection(field: number)` | Place current player's symbol on field 1-9 |
|
|
47
|
+
| `isFieldSelected(field: number)` | Check if a field is already occupied |
|
|
48
|
+
| `reset()` | Reset the game to initial state |
|
|
49
|
+
|
|
50
|
+
## CLI Usage
|
|
51
|
+
|
|
52
|
+
Run the interactive console game:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx tic-tac-toe-core
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Exports
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// Core class
|
|
62
|
+
export { Game } from 't3c';
|
|
63
|
+
|
|
64
|
+
// Constants
|
|
65
|
+
export { DEFAULT_GAME_SYMBOLS } from 't3c';
|
|
66
|
+
|
|
67
|
+
// Types
|
|
68
|
+
export type { IGame, GameStatus, PlayerSymbol, PlayerSymbols } from 't3c';
|
|
69
|
+
```
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import { importX } from "eslint-plugin-import-x";
|
|
3
|
+
import perfectionist from "eslint-plugin-perfectionist";
|
|
4
|
+
import { defineConfig } from "eslint/config";
|
|
5
|
+
import globals from "globals";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import tseslint from "typescript-eslint";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
export default defineConfig([
|
|
12
|
+
{
|
|
13
|
+
ignores: ["dist/**", "node_modules/**"],
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
languageOptions: {
|
|
17
|
+
parserOptions: {
|
|
18
|
+
tsconfigRootDir: __dirname,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
|
|
24
|
+
plugins: { js },
|
|
25
|
+
extends: ["js/recommended"],
|
|
26
|
+
languageOptions: { globals: globals.browser },
|
|
27
|
+
},
|
|
28
|
+
tseslint.configs.recommended,
|
|
29
|
+
{
|
|
30
|
+
plugins: {
|
|
31
|
+
"import-x": importX,
|
|
32
|
+
perfectionist,
|
|
33
|
+
},
|
|
34
|
+
settings: {
|
|
35
|
+
"import-x/internal-regex": "^@/",
|
|
36
|
+
"import-x/resolver": {
|
|
37
|
+
typescript: true,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
rules: {
|
|
41
|
+
"no-case-declarations": "off",
|
|
42
|
+
"@typescript-eslint/consistent-type-imports": [
|
|
43
|
+
"error",
|
|
44
|
+
{
|
|
45
|
+
prefer: "type-imports",
|
|
46
|
+
fixStyle: "separate-type-imports",
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
"@typescript-eslint/no-explicit-any": "warn",
|
|
50
|
+
"@typescript-eslint/no-unused-vars": [
|
|
51
|
+
"error",
|
|
52
|
+
{
|
|
53
|
+
argsIgnorePattern: "^_",
|
|
54
|
+
varsIgnorePattern: "^_",
|
|
55
|
+
caughtErrorsIgnorePattern: "^_",
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
"no-console": "off",
|
|
59
|
+
"perfectionist/sort-imports": [
|
|
60
|
+
"error",
|
|
61
|
+
{
|
|
62
|
+
type: "alphabetical",
|
|
63
|
+
order: "asc",
|
|
64
|
+
ignoreCase: true,
|
|
65
|
+
newlinesBetween: 1,
|
|
66
|
+
tsconfig: { rootDir: "." },
|
|
67
|
+
internalPattern: ["^@/"],
|
|
68
|
+
groups: [
|
|
69
|
+
"type-import",
|
|
70
|
+
["value-builtin", "value-external"],
|
|
71
|
+
"value-internal",
|
|
72
|
+
["value-parent", "value-sibling", "value-index"],
|
|
73
|
+
"side-effect",
|
|
74
|
+
"unknown",
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
"import-x/no-cycle": [
|
|
79
|
+
"error",
|
|
80
|
+
{
|
|
81
|
+
ignoreExternal: true,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
]);
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const app: () => void;
|
package/dist/src/app.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.app = void 0;
|
|
4
|
+
const navigation_1 = require("./ui/navigation");
|
|
5
|
+
const routes_1 = require("./ui/navigation/routes");
|
|
6
|
+
const app = () => {
|
|
7
|
+
(0, navigation_1.navigateTo)(routes_1.ROUTES.MENU);
|
|
8
|
+
};
|
|
9
|
+
exports.app = app;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { IBoard } from "./types/Board";
|
|
2
|
+
import type { PlayerSymbol } from "./types/Symbol";
|
|
3
|
+
export declare const BOARD_SIZE = 9;
|
|
4
|
+
export declare class Board implements IBoard {
|
|
5
|
+
private fields;
|
|
6
|
+
/**
|
|
7
|
+
* Returns the current board state.
|
|
8
|
+
* @returns The current board state.
|
|
9
|
+
* @type {number[] | PlayerSymbol[]}
|
|
10
|
+
*/
|
|
11
|
+
getFields(): (number | "O" | "X")[];
|
|
12
|
+
/**
|
|
13
|
+
* Returns the value of a field by its number.
|
|
14
|
+
* @param fieldNumber The field number (1-9) to get.
|
|
15
|
+
* @returns The value of the field.
|
|
16
|
+
* @type {number | TSymbol}
|
|
17
|
+
*/
|
|
18
|
+
getFieldByNumber(fieldNumber: number): number | "O" | "X";
|
|
19
|
+
/**
|
|
20
|
+
* Checks if the board is full.
|
|
21
|
+
* @returns `true` if the board is full, `false` otherwise.
|
|
22
|
+
*/
|
|
23
|
+
isFull(): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Sets a field's value by its number.
|
|
26
|
+
* @param fieldNumber The field number (1-9) to set.
|
|
27
|
+
* @param symbol The symbol to set.
|
|
28
|
+
*/
|
|
29
|
+
setFieldByNumber(fieldNumber: number, symbol: PlayerSymbol): void;
|
|
30
|
+
/**
|
|
31
|
+
* Resets the board to its initial state.
|
|
32
|
+
*/
|
|
33
|
+
reset(): void;
|
|
34
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Board = exports.BOARD_SIZE = void 0;
|
|
4
|
+
const fillFields = (_, idx) => idx + 1;
|
|
5
|
+
exports.BOARD_SIZE = 9;
|
|
6
|
+
class Board {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.fields = new Array(exports.BOARD_SIZE)
|
|
9
|
+
.fill(0)
|
|
10
|
+
.map(fillFields);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Returns the current board state.
|
|
14
|
+
* @returns The current board state.
|
|
15
|
+
* @type {number[] | PlayerSymbol[]}
|
|
16
|
+
*/
|
|
17
|
+
getFields() {
|
|
18
|
+
return this.fields;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Returns the value of a field by its number.
|
|
22
|
+
* @param fieldNumber The field number (1-9) to get.
|
|
23
|
+
* @returns The value of the field.
|
|
24
|
+
* @type {number | TSymbol}
|
|
25
|
+
*/
|
|
26
|
+
getFieldByNumber(fieldNumber) {
|
|
27
|
+
return this.fields[fieldNumber - 1];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Checks if the board is full.
|
|
31
|
+
* @returns `true` if the board is full, `false` otherwise.
|
|
32
|
+
*/
|
|
33
|
+
isFull() {
|
|
34
|
+
return this.fields.every((field) => typeof field === "string");
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Sets a field's value by its number.
|
|
38
|
+
* @param fieldNumber The field number (1-9) to set.
|
|
39
|
+
* @param symbol The symbol to set.
|
|
40
|
+
*/
|
|
41
|
+
setFieldByNumber(fieldNumber, symbol) {
|
|
42
|
+
this.fields[fieldNumber - 1] = symbol;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resets the board to its initial state.
|
|
46
|
+
*/
|
|
47
|
+
reset() {
|
|
48
|
+
this.fields = new Array(exports.BOARD_SIZE).fill(0).map(fillFields);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.Board = Board;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { GameStatus, IGame } from "./types/Game";
|
|
2
|
+
import type { PlayerSymbol } from "./types/Symbol";
|
|
3
|
+
export declare class Game implements IGame {
|
|
4
|
+
private _currentPlayer;
|
|
5
|
+
private _gameStatus;
|
|
6
|
+
private _symbols;
|
|
7
|
+
private _board;
|
|
8
|
+
private _togglePlayer;
|
|
9
|
+
private _updateGameStatus;
|
|
10
|
+
/**
|
|
11
|
+
* Returns the current player.
|
|
12
|
+
* @returns The current player.
|
|
13
|
+
*/
|
|
14
|
+
get currentPlayer(): PlayerSymbol;
|
|
15
|
+
/**
|
|
16
|
+
* Returns the current game status.
|
|
17
|
+
* @returns The current game status.
|
|
18
|
+
* @type {GameStatus<PlayerSymbol>}
|
|
19
|
+
*/
|
|
20
|
+
get gameStatus(): GameStatus;
|
|
21
|
+
/**
|
|
22
|
+
* Returns the current board state.
|
|
23
|
+
* @returns The current board state.
|
|
24
|
+
* @type {(number | PlayerSymbol)[]}
|
|
25
|
+
*/
|
|
26
|
+
getBoard(): (number | "O" | "X")[];
|
|
27
|
+
/**
|
|
28
|
+
* Checks if a field is already selected by a player.
|
|
29
|
+
* @param field The field number to check.
|
|
30
|
+
* @returns `true` if the field is selected, `false` otherwise.
|
|
31
|
+
*/
|
|
32
|
+
isFieldSelected(field: number): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Saves a player's selection on the board.
|
|
35
|
+
* @param field The field number to mark.
|
|
36
|
+
*/
|
|
37
|
+
savePlayerSelection(field: number): void;
|
|
38
|
+
/**
|
|
39
|
+
* Resets the game to its initial state.
|
|
40
|
+
*/
|
|
41
|
+
reset(): void;
|
|
42
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Game = void 0;
|
|
4
|
+
const Board_1 = require("./Board");
|
|
5
|
+
const constants_1 = require("./constants");
|
|
6
|
+
const getWinnerFromFields_1 = require("./utils/getWinnerFromFields");
|
|
7
|
+
class Game {
|
|
8
|
+
constructor() {
|
|
9
|
+
this._currentPlayer = constants_1.DEFAULT_GAME_SYMBOLS[0];
|
|
10
|
+
this._gameStatus = { status: "running" };
|
|
11
|
+
this._symbols = constants_1.DEFAULT_GAME_SYMBOLS;
|
|
12
|
+
this._board = new Board_1.Board();
|
|
13
|
+
}
|
|
14
|
+
_togglePlayer() {
|
|
15
|
+
this._currentPlayer =
|
|
16
|
+
this._currentPlayer === this._symbols[0]
|
|
17
|
+
? this._symbols[1]
|
|
18
|
+
: this._symbols[0];
|
|
19
|
+
}
|
|
20
|
+
_updateGameStatus() {
|
|
21
|
+
const board = this._board;
|
|
22
|
+
const winner = (0, getWinnerFromFields_1.getWinnerFromFields)(board.getFields());
|
|
23
|
+
const isDraw = board.isFull() && !winner;
|
|
24
|
+
if (winner) {
|
|
25
|
+
this._gameStatus = { status: "win", winner };
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (isDraw) {
|
|
29
|
+
this._gameStatus = { status: "draw" };
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
this._gameStatus = { status: "running" };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Returns the current player.
|
|
36
|
+
* @returns The current player.
|
|
37
|
+
*/
|
|
38
|
+
get currentPlayer() {
|
|
39
|
+
return this._currentPlayer;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Returns the current game status.
|
|
43
|
+
* @returns The current game status.
|
|
44
|
+
* @type {GameStatus<PlayerSymbol>}
|
|
45
|
+
*/
|
|
46
|
+
get gameStatus() {
|
|
47
|
+
return this._gameStatus;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Returns the current board state.
|
|
51
|
+
* @returns The current board state.
|
|
52
|
+
* @type {(number | PlayerSymbol)[]}
|
|
53
|
+
*/
|
|
54
|
+
getBoard() {
|
|
55
|
+
return this._board.getFields();
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Checks if a field is already selected by a player.
|
|
59
|
+
* @param field The field number to check.
|
|
60
|
+
* @returns `true` if the field is selected, `false` otherwise.
|
|
61
|
+
*/
|
|
62
|
+
isFieldSelected(field) {
|
|
63
|
+
return typeof this._board.getFieldByNumber(field) === "string";
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Saves a player's selection on the board.
|
|
67
|
+
* @param field The field number to mark.
|
|
68
|
+
*/
|
|
69
|
+
savePlayerSelection(field) {
|
|
70
|
+
this._board.setFieldByNumber(field, this._currentPlayer);
|
|
71
|
+
this._togglePlayer();
|
|
72
|
+
this._updateGameStatus();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Resets the game to its initial state.
|
|
76
|
+
*/
|
|
77
|
+
reset() {
|
|
78
|
+
this._gameStatus = { status: "running" };
|
|
79
|
+
this._currentPlayer = this._symbols[0];
|
|
80
|
+
this._board.reset();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.Game = Game;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_GAME_SYMBOLS = exports.WINNING_COMBINATIONS_INDEXES = void 0;
|
|
4
|
+
exports.WINNING_COMBINATIONS_INDEXES = [
|
|
5
|
+
[0, 1, 2],
|
|
6
|
+
[3, 4, 5],
|
|
7
|
+
[6, 7, 8],
|
|
8
|
+
[0, 3, 6],
|
|
9
|
+
[1, 4, 7],
|
|
10
|
+
[2, 5, 8],
|
|
11
|
+
[0, 4, 8],
|
|
12
|
+
[2, 4, 6],
|
|
13
|
+
];
|
|
14
|
+
exports.DEFAULT_GAME_SYMBOLS = ["O", "X"];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core game logic for Tic Tac Toe.
|
|
3
|
+
* Manages player turns, board state, win/draw detection, and game lifecycle.
|
|
4
|
+
*/
|
|
5
|
+
export { Game } from "./Game";
|
|
6
|
+
/**
|
|
7
|
+
* Default player symbols ['O', 'X'] for convenience.
|
|
8
|
+
*/
|
|
9
|
+
export { DEFAULT_GAME_SYMBOLS } from "./constants";
|
|
10
|
+
/**
|
|
11
|
+
* Interface describing the Game class contract.
|
|
12
|
+
*/
|
|
13
|
+
export type { IGame } from "./types/Game";
|
|
14
|
+
/**
|
|
15
|
+
* Union type representing possible game states:
|
|
16
|
+
* - `{ status: 'running' }` - Game in progress
|
|
17
|
+
* - `{ status: 'win', winner: TSymbol }` - A player won
|
|
18
|
+
* - `{ status: 'draw' }` - Board full, no winner
|
|
19
|
+
*/
|
|
20
|
+
export type { GameStatus } from "./types/Game";
|
|
21
|
+
/**
|
|
22
|
+
* Tuple type for player symbols: `[symbol1, symbol2]`.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* type Symbols = ['O', 'X']; // PlayerSymbols
|
|
27
|
+
* type Symbol = 'O' | 'X'; // PlayerSymbol
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export type { PlayerSymbols, PlayerSymbol } from "./types/Symbol";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_GAME_SYMBOLS = exports.Game = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Core game logic for Tic Tac Toe.
|
|
6
|
+
* Manages player turns, board state, win/draw detection, and game lifecycle.
|
|
7
|
+
*/
|
|
8
|
+
var Game_1 = require("./Game");
|
|
9
|
+
Object.defineProperty(exports, "Game", { enumerable: true, get: function () { return Game_1.Game; } });
|
|
10
|
+
/**
|
|
11
|
+
* Default player symbols ['O', 'X'] for convenience.
|
|
12
|
+
*/
|
|
13
|
+
var constants_1 = require("./constants");
|
|
14
|
+
Object.defineProperty(exports, "DEFAULT_GAME_SYMBOLS", { enumerable: true, get: function () { return constants_1.DEFAULT_GAME_SYMBOLS; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const Game_1 = require("../Game");
|
|
5
|
+
/*
|
|
6
|
+
O X O
|
|
7
|
+
O X X
|
|
8
|
+
X O O
|
|
9
|
+
*/
|
|
10
|
+
(0, vitest_1.test)("Check if the game ends in a draw", () => {
|
|
11
|
+
const game = new Game_1.Game();
|
|
12
|
+
game.savePlayerSelection(1);
|
|
13
|
+
game.savePlayerSelection(2);
|
|
14
|
+
game.savePlayerSelection(3);
|
|
15
|
+
game.savePlayerSelection(5);
|
|
16
|
+
game.savePlayerSelection(4);
|
|
17
|
+
game.savePlayerSelection(6);
|
|
18
|
+
game.savePlayerSelection(8);
|
|
19
|
+
game.savePlayerSelection(7);
|
|
20
|
+
game.savePlayerSelection(9);
|
|
21
|
+
(0, vitest_1.expect)(game.gameStatus).toEqual({ status: "draw" });
|
|
22
|
+
});
|
|
23
|
+
(0, vitest_1.test)("Reset the game", () => {
|
|
24
|
+
const game = new Game_1.Game();
|
|
25
|
+
game.savePlayerSelection(5);
|
|
26
|
+
game.savePlayerSelection(1);
|
|
27
|
+
game.savePlayerSelection(2);
|
|
28
|
+
game.savePlayerSelection(3);
|
|
29
|
+
game.savePlayerSelection(8);
|
|
30
|
+
game.reset();
|
|
31
|
+
(0, vitest_1.expect)(game.getBoard()).toEqual(new Array(9).fill(0).map((_, idx) => idx + 1));
|
|
32
|
+
(0, vitest_1.expect)(game.gameStatus.status).toBe("running");
|
|
33
|
+
(0, vitest_1.expect)(game.currentPlayer).toBe("O");
|
|
34
|
+
});
|
|
35
|
+
/*
|
|
36
|
+
X O X
|
|
37
|
+
4 O 6
|
|
38
|
+
7 O 9
|
|
39
|
+
*/
|
|
40
|
+
(0, vitest_1.test)("Check if the game is won", () => {
|
|
41
|
+
const game = new Game_1.Game();
|
|
42
|
+
game.savePlayerSelection(5);
|
|
43
|
+
game.savePlayerSelection(1);
|
|
44
|
+
game.savePlayerSelection(2);
|
|
45
|
+
game.savePlayerSelection(3);
|
|
46
|
+
game.savePlayerSelection(8);
|
|
47
|
+
(0, vitest_1.expect)(game.gameStatus).toEqual({ status: "win", winner: "O" });
|
|
48
|
+
(0, vitest_1.expect)(game.isFieldSelected(5)).toBe(true);
|
|
49
|
+
(0, vitest_1.expect)(game.isFieldSelected(9)).toBe(false);
|
|
50
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const getWinnerFromFields_1 = require("../utils/getWinnerFromFields");
|
|
5
|
+
(0, vitest_1.test)("getWinnerFromFields: top row all X wins", () => {
|
|
6
|
+
const fields = ["X", "X", "X", 4, 5, 6, 7, 8, 9];
|
|
7
|
+
(0, vitest_1.expect)((0, getWinnerFromFields_1.getWinnerFromFields)(fields)).toBe("X");
|
|
8
|
+
});
|
|
9
|
+
(0, vitest_1.test)("getWinnerFromFields: middle column all O wins", () => {
|
|
10
|
+
const fields = [1, "O", 3, 4, "O", 6, 7, "O", 9];
|
|
11
|
+
(0, vitest_1.expect)((0, getWinnerFromFields_1.getWinnerFromFields)(fields)).toBe("O");
|
|
12
|
+
});
|
|
13
|
+
(0, vitest_1.test)("getWinnerFromFields: diagonal 0-4-8 wins", () => {
|
|
14
|
+
const fields = ["X", 2, 3, 4, "X", 6, 7, 8, "X"];
|
|
15
|
+
(0, vitest_1.expect)((0, getWinnerFromFields_1.getWinnerFromFields)(fields)).toBe("X");
|
|
16
|
+
});
|
|
17
|
+
(0, vitest_1.test)("getWinnerFromFields: fresh labels — no winner", () => {
|
|
18
|
+
const fields = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
19
|
+
(0, vitest_1.expect)((0, getWinnerFromFields_1.getWinnerFromFields)(fields)).toBe(null);
|
|
20
|
+
});
|
|
21
|
+
(0, vitest_1.test)("getWinnerFromFields: mixed line — no winner", () => {
|
|
22
|
+
const fields = ["X", "O", "X", 4, 5, 6, 7, 8, 9];
|
|
23
|
+
(0, vitest_1.expect)((0, getWinnerFromFields_1.getWinnerFromFields)(fields)).toBe(null);
|
|
24
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PlayerSymbol } from "./Symbol";
|
|
2
|
+
export interface IBoard {
|
|
3
|
+
/** Cell value for field **1–9** (grid label shown to the player). */
|
|
4
|
+
getFieldByNumber: (fieldNumber: number) => number | PlayerSymbol;
|
|
5
|
+
setFieldByNumber: (fieldNumber: number, symbol: PlayerSymbol) => void;
|
|
6
|
+
getFields: () => (number | PlayerSymbol)[];
|
|
7
|
+
isFull: () => boolean;
|
|
8
|
+
reset: () => void;
|
|
9
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PlayerSymbol } from "./Symbol";
|
|
2
|
+
type GameStatusWin = {
|
|
3
|
+
status: "win";
|
|
4
|
+
winner: PlayerSymbol;
|
|
5
|
+
};
|
|
6
|
+
type GameStatusDraw = {
|
|
7
|
+
status: "draw";
|
|
8
|
+
};
|
|
9
|
+
type GameStatusRunning = {
|
|
10
|
+
status: "running";
|
|
11
|
+
};
|
|
12
|
+
export type GameStatus = GameStatusWin | GameStatusDraw | GameStatusRunning;
|
|
13
|
+
export interface IGame {
|
|
14
|
+
readonly gameStatus: GameStatus;
|
|
15
|
+
readonly currentPlayer: PlayerSymbol;
|
|
16
|
+
savePlayerSelection: (field: number) => void;
|
|
17
|
+
reset: () => void;
|
|
18
|
+
isFieldSelected: (field: number) => boolean;
|
|
19
|
+
getBoard: () => (number | PlayerSymbol)[];
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getWinnerFromFields = getWinnerFromFields;
|
|
4
|
+
const constants_1 = require("../constants");
|
|
5
|
+
/** Winner from a 9-cell row-major slice matching {@link Board} (`number` = empty slot label). */
|
|
6
|
+
function getWinnerFromFields(fields) {
|
|
7
|
+
for (const combination of constants_1.WINNING_COMBINATIONS_INDEXES) {
|
|
8
|
+
const firstIdx = combination[0];
|
|
9
|
+
const field = fields[firstIdx];
|
|
10
|
+
const isWinningCombination = combination.every((i) => fields[i] === field);
|
|
11
|
+
if (typeof field === "string" && isWinningCombination) {
|
|
12
|
+
return field;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const Header: () => void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Header = void 0;
|
|
4
|
+
const styledLabel_1 = require("../../../ui/utils/styledLabel");
|
|
5
|
+
const borderStyle = { color: "red" };
|
|
6
|
+
const Header = () => {
|
|
7
|
+
console.log(`\t`);
|
|
8
|
+
const gameName = (0, styledLabel_1.styledLabel)("Tic Tac Toe Game", {
|
|
9
|
+
color: "yellow",
|
|
10
|
+
textStyle: "bold",
|
|
11
|
+
});
|
|
12
|
+
const border = (0, styledLabel_1.styledLabel)("============================================", borderStyle);
|
|
13
|
+
const sideBorder = (0, styledLabel_1.styledLabel)("|", borderStyle);
|
|
14
|
+
const headerLabel = (0, styledLabel_1.styledLabel)(`${border}
|
|
15
|
+
${sideBorder} ${gameName} ${sideBorder}
|
|
16
|
+
${border}`);
|
|
17
|
+
console.log(headerLabel);
|
|
18
|
+
console.log("\t");
|
|
19
|
+
};
|
|
20
|
+
exports.Header = Header;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Header";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./Header"), exports);
|