turn-based-mcp-shared 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.
Files changed (111) hide show
  1. package/dist/constants/game-constants.d.ts +77 -0
  2. package/dist/constants/game-constants.d.ts.map +1 -0
  3. package/dist/constants/game-constants.js +68 -0
  4. package/dist/constants/game-constants.js.map +1 -0
  5. package/dist/constants/index.d.ts +5 -0
  6. package/dist/constants/index.d.ts.map +1 -0
  7. package/dist/constants/index.js +5 -0
  8. package/dist/constants/index.js.map +1 -0
  9. package/dist/games/index.d.ts +3 -0
  10. package/dist/games/index.d.ts.map +1 -0
  11. package/dist/games/index.js +3 -0
  12. package/dist/games/index.js.map +1 -0
  13. package/dist/games/rock-paper-scissors.d.ts +117 -0
  14. package/dist/games/rock-paper-scissors.d.ts.map +1 -0
  15. package/dist/games/rock-paper-scissors.js +247 -0
  16. package/dist/games/rock-paper-scissors.js.map +1 -0
  17. package/dist/games/tic-tac-toe.d.ts +121 -0
  18. package/dist/games/tic-tac-toe.d.ts.map +1 -0
  19. package/dist/games/tic-tac-toe.js +240 -0
  20. package/dist/games/tic-tac-toe.js.map +1 -0
  21. package/dist/index.d.ts +7 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +5 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/storage/game-storage.d.ts +11 -0
  26. package/dist/storage/game-storage.d.ts.map +1 -0
  27. package/dist/storage/game-storage.js +28 -0
  28. package/dist/storage/game-storage.js.map +1 -0
  29. package/dist/storage/index.d.ts +3 -0
  30. package/dist/storage/index.d.ts.map +1 -0
  31. package/dist/storage/index.js +3 -0
  32. package/dist/storage/index.js.map +1 -0
  33. package/dist/storage/mcp-api-client.d.ts +8 -0
  34. package/dist/storage/mcp-api-client.d.ts.map +1 -0
  35. package/dist/storage/mcp-api-client.js +52 -0
  36. package/dist/storage/mcp-api-client.js.map +1 -0
  37. package/dist/storage/memory-storage.d.ts +12 -0
  38. package/dist/storage/memory-storage.d.ts.map +1 -0
  39. package/dist/storage/memory-storage.js +34 -0
  40. package/dist/storage/memory-storage.js.map +1 -0
  41. package/dist/storage/sqlite-storage.d.ts +2 -0
  42. package/dist/storage/sqlite-storage.d.ts.map +1 -0
  43. package/dist/storage/sqlite-storage.js +3 -0
  44. package/dist/storage/sqlite-storage.js.map +1 -0
  45. package/dist/testing/api-test-utils.d.ts +56 -0
  46. package/dist/testing/api-test-utils.d.ts.map +1 -0
  47. package/dist/testing/api-test-utils.js +125 -0
  48. package/dist/testing/api-test-utils.js.map +1 -0
  49. package/dist/testing/index.d.ts +10 -0
  50. package/dist/testing/index.d.ts.map +1 -0
  51. package/dist/testing/index.js +10 -0
  52. package/dist/testing/index.js.map +1 -0
  53. package/dist/testing/test-database.d.ts +24 -0
  54. package/dist/testing/test-database.d.ts.map +1 -0
  55. package/dist/testing/test-database.js +39 -0
  56. package/dist/testing/test-database.js.map +1 -0
  57. package/dist/testing/vitest-setup.d.ts +12 -0
  58. package/dist/testing/vitest-setup.d.ts.map +1 -0
  59. package/dist/testing/vitest-setup.js +35 -0
  60. package/dist/testing/vitest-setup.js.map +1 -0
  61. package/dist/types/game.d.ts +39 -0
  62. package/dist/types/game.d.ts.map +1 -0
  63. package/dist/types/game.js +2 -0
  64. package/dist/types/game.js.map +1 -0
  65. package/dist/types/games.d.ts +26 -0
  66. package/dist/types/games.d.ts.map +1 -0
  67. package/dist/types/games.js +2 -0
  68. package/dist/types/games.js.map +1 -0
  69. package/dist/utils/http-client.d.ts +25 -0
  70. package/dist/utils/http-client.d.ts.map +1 -0
  71. package/dist/utils/http-client.js +42 -0
  72. package/dist/utils/http-client.js.map +1 -0
  73. package/dist/utils/index.d.ts +15 -0
  74. package/dist/utils/index.d.ts.map +1 -0
  75. package/dist/utils/index.js +34 -0
  76. package/dist/utils/index.js.map +1 -0
  77. package/eslint.config.js +41 -0
  78. package/package.json +1 -0
  79. package/src/constants/game-constants.test.ts +102 -0
  80. package/src/constants/game-constants.ts +86 -0
  81. package/src/constants/index.ts +5 -0
  82. package/src/games/index.test.ts +22 -0
  83. package/src/games/index.ts +2 -0
  84. package/src/games/rock-paper-scissors.test.ts +313 -0
  85. package/src/games/rock-paper-scissors.ts +276 -0
  86. package/src/games/tic-tac-toe.test.ts +258 -0
  87. package/src/games/tic-tac-toe.ts +267 -0
  88. package/src/index.test.ts +64 -0
  89. package/src/index.ts +6 -0
  90. package/src/storage/game-storage.test.ts +204 -0
  91. package/src/storage/game-storage.ts +38 -0
  92. package/src/storage/index.test.ts +48 -0
  93. package/src/storage/index.ts +2 -0
  94. package/src/storage/mcp-api-client.test.ts +339 -0
  95. package/src/storage/mcp-api-client.ts +53 -0
  96. package/src/storage/memory-storage.ts +46 -0
  97. package/src/storage/sqlite-storage.test.ts +139 -0
  98. package/src/storage/sqlite-storage.ts +12 -0
  99. package/src/testing/api-test-utils.ts +144 -0
  100. package/src/testing/index.ts +29 -0
  101. package/src/testing/test-database.ts +46 -0
  102. package/src/testing/vitest-setup.ts +35 -0
  103. package/src/types/game.ts +48 -0
  104. package/src/types/games.ts +33 -0
  105. package/src/utils/http-client.ts +44 -0
  106. package/src/utils/index.test.ts +133 -0
  107. package/src/utils/index.ts +40 -0
  108. package/tsconfig-paths.json +12 -0
  109. package/tsconfig.json +19 -0
  110. package/vitest.config.ts +21 -0
  111. package/vitest.setup.ts +10 -0
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Shared game constants
3
+ * Single source of truth for game types, difficulties, player IDs, and default values
4
+ */
5
+ /**
6
+ * Supported game types
7
+ */
8
+ export declare const GAME_TYPES: readonly ["tic-tac-toe", "rock-paper-scissors"];
9
+ /**
10
+ * Available AI difficulty levels
11
+ */
12
+ export declare const DIFFICULTIES: readonly ["easy", "medium", "hard"];
13
+ /**
14
+ * Standard player IDs used across the system
15
+ */
16
+ export declare const PLAYER_IDS: {
17
+ readonly HUMAN: "player1";
18
+ readonly PLAYER2: "player2";
19
+ readonly AI: "ai";
20
+ };
21
+ /**
22
+ * Game status values
23
+ */
24
+ export declare const GAME_STATUSES: readonly ["waiting", "playing", "finished"];
25
+ /**
26
+ * Derive types from constants
27
+ */
28
+ export type GameType = typeof GAME_TYPES[number];
29
+ export type Difficulty = typeof DIFFICULTIES[number];
30
+ export type PlayerId = typeof PLAYER_IDS[keyof typeof PLAYER_IDS];
31
+ export type GameStatus = typeof GAME_STATUSES[number];
32
+ /**
33
+ * Default player configurations
34
+ */
35
+ export declare const DEFAULT_PLAYER_NAME = "Player";
36
+ export declare const DEFAULT_AI_DIFFICULTY: Difficulty;
37
+ /**
38
+ * Difficulty display configuration
39
+ */
40
+ export declare const DIFFICULTY_DISPLAY: {
41
+ readonly easy: {
42
+ readonly emoji: "😌";
43
+ readonly label: "Easy";
44
+ };
45
+ readonly medium: {
46
+ readonly emoji: "🎯";
47
+ readonly label: "Medium";
48
+ };
49
+ readonly hard: {
50
+ readonly emoji: "🔥";
51
+ readonly label: "Hard";
52
+ };
53
+ };
54
+ /**
55
+ * Type guard to check if a string is a supported game type
56
+ */
57
+ export declare function isSupportedGameType(gameType: string): gameType is GameType;
58
+ /**
59
+ * Type guard to check if a string is a valid difficulty level
60
+ */
61
+ export declare function isValidDifficulty(difficulty: string): difficulty is Difficulty;
62
+ /**
63
+ * Type guard to check if a string is a valid player ID
64
+ */
65
+ export declare function isValidPlayerId(playerId: string): playerId is PlayerId;
66
+ /**
67
+ * Type guard to check if a string is a valid game status
68
+ */
69
+ export declare function isValidGameStatus(status: string): status is GameStatus;
70
+ /**
71
+ * Get difficulty display configuration
72
+ */
73
+ export declare function getDifficultyDisplay(difficulty: Difficulty): {
74
+ emoji: string;
75
+ label: string;
76
+ };
77
+ //# sourceMappingURL=game-constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"game-constants.d.ts","sourceRoot":"","sources":["../../src/constants/game-constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,eAAO,MAAM,UAAU,iDAAkD,CAAA;AAEzE;;GAEG;AACH,eAAO,MAAM,YAAY,qCAAsC,CAAA;AAE/D;;GAEG;AACH,eAAO,MAAM,UAAU;;;;CAIb,CAAA;AAEV;;GAEG;AACH,eAAO,MAAM,aAAa,6CAA8C,CAAA;AAExE;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,UAAU,CAAC,MAAM,CAAC,CAAA;AAChD,MAAM,MAAM,UAAU,GAAG,OAAO,YAAY,CAAC,MAAM,CAAC,CAAA;AACpD,MAAM,MAAM,QAAQ,GAAG,OAAO,UAAU,CAAC,MAAM,OAAO,UAAU,CAAC,CAAA;AACjE,MAAM,MAAM,UAAU,GAAG,OAAO,aAAa,CAAC,MAAM,CAAC,CAAA;AAErD;;GAEG;AACH,eAAO,MAAM,mBAAmB,WAAW,CAAA;AAC3C,eAAO,MAAM,qBAAqB,EAAE,UAAqB,CAAA;AAEzD;;GAEG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;CAIrB,CAAA;AAEV;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,IAAI,QAAQ,CAE1E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,IAAI,UAAU,CAE9E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,IAAI,QAAQ,CAEtE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,UAAU,CAEtE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAE7F"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Shared game constants
3
+ * Single source of truth for game types, difficulties, player IDs, and default values
4
+ */
5
+ /**
6
+ * Supported game types
7
+ */
8
+ export const GAME_TYPES = ['tic-tac-toe', 'rock-paper-scissors'];
9
+ /**
10
+ * Available AI difficulty levels
11
+ */
12
+ export const DIFFICULTIES = ['easy', 'medium', 'hard'];
13
+ /**
14
+ * Standard player IDs used across the system
15
+ */
16
+ export const PLAYER_IDS = {
17
+ HUMAN: 'player1',
18
+ PLAYER2: 'player2',
19
+ AI: 'ai'
20
+ };
21
+ /**
22
+ * Game status values
23
+ */
24
+ export const GAME_STATUSES = ['waiting', 'playing', 'finished'];
25
+ /**
26
+ * Default player configurations
27
+ */
28
+ export const DEFAULT_PLAYER_NAME = 'Player';
29
+ export const DEFAULT_AI_DIFFICULTY = 'medium';
30
+ /**
31
+ * Difficulty display configuration
32
+ */
33
+ export const DIFFICULTY_DISPLAY = {
34
+ easy: { emoji: '😌', label: 'Easy' },
35
+ medium: { emoji: '🎯', label: 'Medium' },
36
+ hard: { emoji: '🔥', label: 'Hard' }
37
+ };
38
+ /**
39
+ * Type guard to check if a string is a supported game type
40
+ */
41
+ export function isSupportedGameType(gameType) {
42
+ return GAME_TYPES.includes(gameType);
43
+ }
44
+ /**
45
+ * Type guard to check if a string is a valid difficulty level
46
+ */
47
+ export function isValidDifficulty(difficulty) {
48
+ return DIFFICULTIES.includes(difficulty);
49
+ }
50
+ /**
51
+ * Type guard to check if a string is a valid player ID
52
+ */
53
+ export function isValidPlayerId(playerId) {
54
+ return Object.values(PLAYER_IDS).includes(playerId);
55
+ }
56
+ /**
57
+ * Type guard to check if a string is a valid game status
58
+ */
59
+ export function isValidGameStatus(status) {
60
+ return GAME_STATUSES.includes(status);
61
+ }
62
+ /**
63
+ * Get difficulty display configuration
64
+ */
65
+ export function getDifficultyDisplay(difficulty) {
66
+ return DIFFICULTY_DISPLAY[difficulty];
67
+ }
68
+ //# sourceMappingURL=game-constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"game-constants.js","sourceRoot":"","sources":["../../src/constants/game-constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,aAAa,EAAE,qBAAqB,CAAU,CAAA;AAEzE;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAA;AAE/D;;GAEG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,KAAK,EAAE,SAAS;IAChB,OAAO,EAAE,SAAS;IAClB,EAAE,EAAE,IAAI;CACA,CAAA;AAEV;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,UAAU,CAAU,CAAA;AAUxE;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAQ,CAAA;AAC3C,MAAM,CAAC,MAAM,qBAAqB,GAAe,QAAQ,CAAA;AAEzD;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE;IACpC,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE;IACxC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE;CAC5B,CAAA;AAEV;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAgB;IAClD,OAAO,UAAU,CAAC,QAAQ,CAAC,QAAoB,CAAC,CAAA;AAClD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAkB;IAClD,OAAO,YAAY,CAAC,QAAQ,CAAC,UAAwB,CAAC,CAAA;AACxD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,OAAO,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,QAAoB,CAAC,CAAA;AACjE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,OAAO,aAAa,CAAC,QAAQ,CAAC,MAAoB,CAAC,CAAA;AACrD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAsB;IACzD,OAAO,kBAAkB,CAAC,UAAU,CAAC,CAAA;AACvC,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Constants index - exports all game constants
3
+ */
4
+ export * from './game-constants.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/constants/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,kBAAkB,CAAA"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Constants index - exports all game constants
3
+ */
4
+ export * from './game-constants.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/constants/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,kBAAkB,CAAA"}
@@ -0,0 +1,3 @@
1
+ export { TicTacToeGame } from './tic-tac-toe.js';
2
+ export { RockPaperScissorsGame } from './rock-paper-scissors.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/games/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { TicTacToeGame } from './tic-tac-toe.js';
2
+ export { RockPaperScissorsGame } from './rock-paper-scissors.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/games/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,117 @@
1
+ import { Game, Player, PlayerId, GameResult } from '../types/game.js';
2
+ import { RPSGameState, RPSMove } from '../types/games.js';
3
+ /**
4
+ * Implementation of the classic Rock Paper Scissors game
5
+ *
6
+ * A best-of-N rounds game where players simultaneously choose rock, paper, or scissors.
7
+ * Rock beats scissors, scissors beats paper, paper beats rock.
8
+ * The first player to win the majority of rounds wins the match.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const game = new RockPaperScissorsGame();
13
+ * const players = [
14
+ * { id: 'player1', name: 'Human', isAI: false },
15
+ * { id: 'ai', name: 'Computer', isAI: true }
16
+ * ];
17
+ * const initialState = game.getInitialState(players); // Creates best-of-3 game
18
+ * ```
19
+ */
20
+ export declare class RockPaperScissorsGame implements Game<RPSGameState, RPSMove> {
21
+ /**
22
+ * Validates whether a move is legal in the current game state
23
+ *
24
+ * @param gameState - The current state of the rock-paper-scissors game
25
+ * @param move - The move to validate (choice of rock, paper, or scissors)
26
+ * @param playerId - The ID of the player attempting the move
27
+ * @returns true if the move is valid, false otherwise
28
+ *
29
+ * @description
30
+ * A move is valid if:
31
+ * - The choice is one of: 'rock', 'paper', 'scissors'
32
+ * - The game status is 'playing'
33
+ * - The current round hasn't exceeded max rounds
34
+ * - The player hasn't already made a choice in the current round
35
+ */
36
+ validateMove(gameState: RPSGameState, move: RPSMove, playerId: PlayerId): boolean;
37
+ /**
38
+ * Applies a validated move to the game state and returns the new state
39
+ *
40
+ * @param gameState - The current state of the rock-paper-scissors game
41
+ * @param move - The move to apply (must be valid)
42
+ * @param playerId - The ID of the player making the move
43
+ * @returns A new game state with the move applied
44
+ * @throws Error if the move is invalid
45
+ *
46
+ * @description
47
+ * This method handles both partial and complete rounds:
48
+ * - If first player choice: records choice and switches to other player
49
+ * - If second player choice: records choice, determines round winner,
50
+ * updates scores, and advances to next round or ends game
51
+ */
52
+ applyMove(gameState: RPSGameState, move: RPSMove, playerId: PlayerId): RPSGameState;
53
+ /**
54
+ * Checks if the game has ended and determines the winner
55
+ *
56
+ * @param gameState - The current state of the rock-paper-scissors game
57
+ * @returns GameResult with winner and reason if game ended, null if game continues
58
+ *
59
+ * @description
60
+ * The game ends when all rounds are complete (currentRound >= maxRounds).
61
+ * Winner is determined by comparing final scores:
62
+ * - Player with higher score wins
63
+ * - Equal scores result in a draw
64
+ * - Reason includes the final score (e.g., "Won 2-1")
65
+ */
66
+ checkGameEnd(gameState: RPSGameState): GameResult | null;
67
+ /**
68
+ * Gets all valid moves for a player in the current game state
69
+ *
70
+ * @param gameState - The current state of the rock-paper-scissors game
71
+ * @param playerId - The ID of the player to get moves for
72
+ * @returns Array of valid RPSMove objects (all choices if player can move)
73
+ *
74
+ * @description
75
+ * Returns all three choices [rock, paper, scissors] if:
76
+ * - Game is still playing
77
+ * - Current round is within max rounds
78
+ * - Player hasn't made a choice in the current round
79
+ * Otherwise returns an empty array.
80
+ */
81
+ getValidMoves(gameState: RPSGameState, playerId: PlayerId): RPSMove[];
82
+ /**
83
+ * Creates the initial game state for a new rock-paper-scissors game
84
+ *
85
+ * @param players - Array of exactly 2 players
86
+ * @param options - Optional configuration including maxRounds
87
+ * @returns Initial RPSGameState set up for a configurable number of rounds
88
+ *
89
+ * @description
90
+ * Sets up a new game with:
91
+ * - Configurable number of rounds (default: 3 for best-of-3 format)
92
+ * - All scores initialized to 0
93
+ * - First player goes first
94
+ * - Game status set to 'playing'
95
+ * - Current round set to 0
96
+ */
97
+ getInitialState(players: Player[], options?: {
98
+ maxRounds?: number;
99
+ }): RPSGameState;
100
+ /**
101
+ * Determines the winner of a single round based on the classic RPS rules
102
+ *
103
+ * @param choice1 - First player's choice
104
+ * @param choice2 - Second player's choice
105
+ * @returns 'player1' if choice1 wins, 'player2' if choice2 wins, 'draw' if same
106
+ *
107
+ * @private
108
+ * @description
109
+ * Implements the classic rules:
110
+ * - Rock beats Scissors
111
+ * - Paper beats Rock
112
+ * - Scissors beats Paper
113
+ * - Same choices result in a draw
114
+ */
115
+ private determineRoundWinner;
116
+ }
117
+ //# sourceMappingURL=rock-paper-scissors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rock-paper-scissors.d.ts","sourceRoot":"","sources":["../../src/games/rock-paper-scissors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAa,MAAM,gBAAgB,CAAC;AAElE;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,qBAAsB,YAAW,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC;IACvE;;;;;;;;;;;;;;OAcG;IACH,YAAY,CAAC,SAAS,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO;IA4BjF;;;;;;;;;;;;;;OAcG;IACH,SAAS,CAAC,SAAS,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,YAAY;IAsDnF;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,SAAS,EAAE,YAAY,GAAG,UAAU,GAAG,IAAI;IA2BxD;;;;;;;;;;;;;OAaG;IACH,aAAa,CAAC,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,EAAE;IAsBrE;;;;;;;;;;;;;;OAcG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,YAAY;IAuBlF;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,oBAAoB;CAa7B"}
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Implementation of the classic Rock Paper Scissors game
3
+ *
4
+ * A best-of-N rounds game where players simultaneously choose rock, paper, or scissors.
5
+ * Rock beats scissors, scissors beats paper, paper beats rock.
6
+ * The first player to win the majority of rounds wins the match.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const game = new RockPaperScissorsGame();
11
+ * const players = [
12
+ * { id: 'player1', name: 'Human', isAI: false },
13
+ * { id: 'ai', name: 'Computer', isAI: true }
14
+ * ];
15
+ * const initialState = game.getInitialState(players); // Creates best-of-3 game
16
+ * ```
17
+ */
18
+ export class RockPaperScissorsGame {
19
+ /**
20
+ * Validates whether a move is legal in the current game state
21
+ *
22
+ * @param gameState - The current state of the rock-paper-scissors game
23
+ * @param move - The move to validate (choice of rock, paper, or scissors)
24
+ * @param playerId - The ID of the player attempting the move
25
+ * @returns true if the move is valid, false otherwise
26
+ *
27
+ * @description
28
+ * A move is valid if:
29
+ * - The choice is one of: 'rock', 'paper', 'scissors'
30
+ * - The game status is 'playing'
31
+ * - The current round hasn't exceeded max rounds
32
+ * - The player hasn't already made a choice in the current round
33
+ */
34
+ validateMove(gameState, move, playerId) {
35
+ const { choice } = move;
36
+ // Check if it's a valid choice
37
+ if (!['rock', 'paper', 'scissors'].includes(choice)) {
38
+ return false;
39
+ }
40
+ // Check if game is still ongoing
41
+ if (gameState.status !== 'playing') {
42
+ return false;
43
+ }
44
+ // Check if current round is valid
45
+ if (gameState.currentRound >= gameState.maxRounds) {
46
+ return false;
47
+ }
48
+ const currentRound = gameState.rounds[gameState.currentRound];
49
+ // Check if player hasn't made a choice yet in this round
50
+ if (playerId === 'player1' || playerId === gameState.players[0].id) {
51
+ return !currentRound.player1Choice;
52
+ }
53
+ else {
54
+ return !currentRound.player2Choice;
55
+ }
56
+ }
57
+ /**
58
+ * Applies a validated move to the game state and returns the new state
59
+ *
60
+ * @param gameState - The current state of the rock-paper-scissors game
61
+ * @param move - The move to apply (must be valid)
62
+ * @param playerId - The ID of the player making the move
63
+ * @returns A new game state with the move applied
64
+ * @throws Error if the move is invalid
65
+ *
66
+ * @description
67
+ * This method handles both partial and complete rounds:
68
+ * - If first player choice: records choice and switches to other player
69
+ * - If second player choice: records choice, determines round winner,
70
+ * updates scores, and advances to next round or ends game
71
+ */
72
+ applyMove(gameState, move, playerId) {
73
+ if (!this.validateMove(gameState, move, playerId)) {
74
+ throw new Error('Invalid move');
75
+ }
76
+ const newRounds = [...gameState.rounds];
77
+ const currentRound = { ...newRounds[gameState.currentRound] };
78
+ // Apply the move
79
+ if (playerId === gameState.players[0].id) {
80
+ currentRound.player1Choice = move.choice;
81
+ }
82
+ else {
83
+ currentRound.player2Choice = move.choice;
84
+ }
85
+ newRounds[gameState.currentRound] = currentRound;
86
+ let newCurrentRound = gameState.currentRound;
87
+ const newScores = { ...gameState.scores };
88
+ let newCurrentPlayerId = gameState.currentPlayerId;
89
+ // If both players have made their choices, resolve the round
90
+ if (currentRound.player1Choice && currentRound.player2Choice) {
91
+ const roundWinner = this.determineRoundWinner(currentRound.player1Choice, currentRound.player2Choice);
92
+ currentRound.winner = roundWinner;
93
+ // Update scores
94
+ if (roundWinner !== 'draw') {
95
+ const winnerId = roundWinner === 'player1' ? gameState.players[0].id : gameState.players[1].id;
96
+ newScores[winnerId] = (newScores[winnerId] || 0) + 1;
97
+ }
98
+ // Move to next round
99
+ newCurrentRound++;
100
+ newCurrentPlayerId = gameState.players[0].id; // Reset to first player
101
+ }
102
+ else {
103
+ // Switch to other player
104
+ newCurrentPlayerId = gameState.players.find(p => p.id !== playerId)?.id || gameState.players[0].id;
105
+ }
106
+ return {
107
+ ...gameState,
108
+ rounds: newRounds,
109
+ currentRound: newCurrentRound,
110
+ scores: newScores,
111
+ currentPlayerId: newCurrentPlayerId,
112
+ updatedAt: new Date(),
113
+ };
114
+ }
115
+ /**
116
+ * Checks if the game has ended and determines the winner
117
+ *
118
+ * @param gameState - The current state of the rock-paper-scissors game
119
+ * @returns GameResult with winner and reason if game ended, null if game continues
120
+ *
121
+ * @description
122
+ * The game ends when all rounds are complete (currentRound >= maxRounds).
123
+ * Winner is determined by comparing final scores:
124
+ * - Player with higher score wins
125
+ * - Equal scores result in a draw
126
+ * - Reason includes the final score (e.g., "Won 2-1")
127
+ */
128
+ checkGameEnd(gameState) {
129
+ // Game ends when all rounds are complete
130
+ if (gameState.currentRound >= gameState.maxRounds) {
131
+ const player1Score = gameState.scores[gameState.players[0].id] || 0;
132
+ const player2Score = gameState.scores[gameState.players[1].id] || 0;
133
+ if (player1Score > player2Score) {
134
+ return {
135
+ winner: gameState.players[0].id,
136
+ reason: `Won ${player1Score}-${player2Score}`
137
+ };
138
+ }
139
+ else if (player2Score > player1Score) {
140
+ return {
141
+ winner: gameState.players[1].id,
142
+ reason: `Won ${player2Score}-${player1Score}`
143
+ };
144
+ }
145
+ else {
146
+ return {
147
+ winner: 'draw',
148
+ reason: `Tied ${player1Score}-${player2Score}`
149
+ };
150
+ }
151
+ }
152
+ return null; // Game continues
153
+ }
154
+ /**
155
+ * Gets all valid moves for a player in the current game state
156
+ *
157
+ * @param gameState - The current state of the rock-paper-scissors game
158
+ * @param playerId - The ID of the player to get moves for
159
+ * @returns Array of valid RPSMove objects (all choices if player can move)
160
+ *
161
+ * @description
162
+ * Returns all three choices [rock, paper, scissors] if:
163
+ * - Game is still playing
164
+ * - Current round is within max rounds
165
+ * - Player hasn't made a choice in the current round
166
+ * Otherwise returns an empty array.
167
+ */
168
+ getValidMoves(gameState, playerId) {
169
+ if (gameState.status !== 'playing' || gameState.currentRound >= gameState.maxRounds) {
170
+ return [];
171
+ }
172
+ const currentRound = gameState.rounds[gameState.currentRound];
173
+ // Check if player can make a move in current round
174
+ const canMove = (playerId === gameState.players[0].id && !currentRound.player1Choice) ||
175
+ (playerId === gameState.players[1].id && !currentRound.player2Choice);
176
+ if (!canMove) {
177
+ return [];
178
+ }
179
+ return [
180
+ { choice: 'rock' },
181
+ { choice: 'paper' },
182
+ { choice: 'scissors' }
183
+ ];
184
+ }
185
+ /**
186
+ * Creates the initial game state for a new rock-paper-scissors game
187
+ *
188
+ * @param players - Array of exactly 2 players
189
+ * @param options - Optional configuration including maxRounds
190
+ * @returns Initial RPSGameState set up for a configurable number of rounds
191
+ *
192
+ * @description
193
+ * Sets up a new game with:
194
+ * - Configurable number of rounds (default: 3 for best-of-3 format)
195
+ * - All scores initialized to 0
196
+ * - First player goes first
197
+ * - Game status set to 'playing'
198
+ * - Current round set to 0
199
+ */
200
+ getInitialState(players, options) {
201
+ const maxRounds = options?.maxRounds || 3; // Default to best of 3
202
+ const rounds = Array.from({ length: maxRounds }, () => ({}));
203
+ const scores = {};
204
+ players.forEach(player => {
205
+ scores[player.id] = 0;
206
+ });
207
+ return {
208
+ id: crypto.randomUUID(),
209
+ players,
210
+ currentPlayerId: players[0].id,
211
+ status: 'playing',
212
+ createdAt: new Date(),
213
+ updatedAt: new Date(),
214
+ rounds,
215
+ currentRound: 0,
216
+ maxRounds,
217
+ scores,
218
+ };
219
+ }
220
+ /**
221
+ * Determines the winner of a single round based on the classic RPS rules
222
+ *
223
+ * @param choice1 - First player's choice
224
+ * @param choice2 - Second player's choice
225
+ * @returns 'player1' if choice1 wins, 'player2' if choice2 wins, 'draw' if same
226
+ *
227
+ * @private
228
+ * @description
229
+ * Implements the classic rules:
230
+ * - Rock beats Scissors
231
+ * - Paper beats Rock
232
+ * - Scissors beats Paper
233
+ * - Same choices result in a draw
234
+ */
235
+ determineRoundWinner(choice1, choice2) {
236
+ if (choice1 === choice2) {
237
+ return 'draw';
238
+ }
239
+ const winConditions = {
240
+ rock: 'scissors',
241
+ paper: 'rock',
242
+ scissors: 'paper'
243
+ };
244
+ return winConditions[choice1] === choice2 ? 'player1' : 'player2';
245
+ }
246
+ }
247
+ //# sourceMappingURL=rock-paper-scissors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rock-paper-scissors.js","sourceRoot":"","sources":["../../src/games/rock-paper-scissors.ts"],"names":[],"mappings":"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"}