Skip to content

Commit b33ed95

Browse files
Merge pull request #5 from chrisreddington/copilot/fix-4
Consolidate duplicate code patterns and remove dead code
2 parents 41b8c01 + fc8b73c commit b33ed95

31 files changed

+1115
-189
lines changed

.github/instructions/mcp-server.instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ Implement consistent difficulty levels across all games:
145145
```typescript
146146
export function calculateTicTacToeMove(
147147
gameState: TicTacToeGameState,
148-
difficulty: 'easy' | 'medium' | 'hard' = 'medium'
148+
difficulty: Difficulty = 'medium'
149149
): TicTacToeMove {
150150
const validMoves = getValidMoves(gameState)
151151

.github/instructions/shared-library.instructions.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,89 @@ export function getGameDisplayName(type: GameType): string {
140140
}
141141
```
142142

143+
## Constants and Common Values
144+
145+
### Centralized Constants and Derived Types
146+
**Types are derived from constants using `as const` assertions - constants are the single source of truth:**
147+
148+
```typescript
149+
// ✅ Constants define the source of truth
150+
export const DIFFICULTIES = ['easy', 'medium', 'hard'] as const
151+
export const GAME_TYPES = ['tic-tac-toe', 'rock-paper-scissors'] as const
152+
export const PLAYER_IDS = { HUMAN: 'player1', PLAYER2: 'player2', AI: 'ai' } as const
153+
154+
// ✅ Types are derived from constants
155+
export type Difficulty = typeof DIFFICULTIES[number]
156+
export type GameType = typeof GAME_TYPES[number]
157+
export type PlayerId = typeof PLAYER_IDS[keyof typeof PLAYER_IDS]
158+
159+
// ✅ Import the derived types
160+
import type { Difficulty, GameType } from '@turn-based-mcp/shared'
161+
162+
// ❌ Don't define duplicate union types
163+
export type Difficulty = 'easy' | 'medium' | 'hard' // This duplicates the constants!
164+
```
165+
166+
### Available Constants
167+
Key constants provided by the shared library:
168+
169+
```typescript
170+
// Constants with derived types
171+
export const GAME_TYPES = ['tic-tac-toe', 'rock-paper-scissors'] as const
172+
export const DIFFICULTIES = ['easy', 'medium', 'hard'] as const
173+
export const PLAYER_IDS = { HUMAN: 'player1', PLAYER2: 'player2', AI: 'ai' } as const
174+
export const GAME_STATUSES = ['waiting', 'playing', 'finished'] as const
175+
176+
// Derived types (auto-generated from constants)
177+
export type GameType = typeof GAME_TYPES[number]
178+
export type Difficulty = typeof DIFFICULTIES[number]
179+
export type PlayerId = typeof PLAYER_IDS[keyof typeof PLAYER_IDS]
180+
export type GameStatus = typeof GAME_STATUSES[number]
181+
182+
// Default values
183+
export const DEFAULT_PLAYER_NAME = 'Player'
184+
export const DEFAULT_AI_DIFFICULTY: Difficulty = 'medium'
185+
186+
// UI display configuration
187+
export const DIFFICULTY_DISPLAY = {
188+
easy: { emoji: '😌', label: 'Easy' },
189+
medium: { emoji: '🎯', label: 'Medium' },
190+
hard: { emoji: '🔥', label: 'Hard' }
191+
} as const
192+
```
193+
194+
### Type Guards and Utilities
195+
Use provided validation functions that work with the constants:
196+
197+
```typescript
198+
// Type guards (check against the constant arrays)
199+
export function isSupportedGameType(gameType: string): gameType is GameType
200+
export function isValidDifficulty(difficulty: string): difficulty is Difficulty
201+
export function isValidPlayerId(playerId: string): playerId is PlayerId
202+
203+
// Display helpers
204+
export function getDifficultyDisplay(difficulty: Difficulty)
205+
```
206+
207+
### Architecture Benefits
208+
This approach ensures:
209+
- **Single source of truth**: Constants define what values are valid
210+
- **Type safety**: TypeScript derives exact types from the constant values
211+
- **Runtime validation**: Type guards check against the same arrays used to derive types
212+
- **Maintainability**: Add a new difficulty by updating one constant array
213+
214+
### Testing Constants
215+
For mocking and test data, use shared testing utilities:
216+
217+
```typescript
218+
// Test data from shared/src/testing/
219+
import {
220+
mockTicTacToeGameState,
221+
mockRPSGameState,
222+
createMockGameSession
223+
} from '@turn-based-mcp/shared/testing'
224+
```
225+
143226
## Testing Infrastructure
144227

145228
### Test Database Utilities

.github/instructions/testing.instructions.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,33 @@ Follow these testing patterns for the turn-based games platform:
3131
})
3232
```
3333

34+
## Shared Testing Utilities
35+
36+
**Use centralized mock data and test utilities from the shared package:**
37+
38+
```typescript
39+
// ✅ Import shared testing utilities
40+
import {
41+
mockTicTacToeGameState,
42+
mockRPSGameState,
43+
createMockGameSession,
44+
setupTestDatabase,
45+
clearTestDatabase
46+
} from '@turn-based-mcp/shared/testing'
47+
48+
// ✅ Use shared constants in tests
49+
import { DIFFICULTIES, GAME_TYPES } from '@turn-based-mcp/shared'
50+
51+
// ❌ Don't recreate mock data locally
52+
const localMockGameState = { /* duplicated data */ } // Use shared mocks instead!
53+
```
54+
55+
**Available shared testing utilities:**
56+
- Mock game states: `mockTicTacToeGameState`, `mockRPSGameState`
57+
- Factory functions: `createMockGameSession`, `createMockPlayer`
58+
- Database utilities: `setupTestDatabase`, `clearTestDatabase`, `teardownTestDatabase`
59+
- Type assertions and validation helpers
60+
3461
## Component Testing
3562

3663
- Always render components with realistic props

.github/instructions/typescript.instructions.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,37 @@ Follow these TypeScript patterns for consistent, type-safe code:
1515
- Use barrel exports (`index.ts`) for clean imports
1616
- Re-export shared types from `@turn-based-mcp/shared`
1717

18+
### Shared Types and Constants
19+
**Always import types derived from shared constants - don't duplicate union types:**
20+
21+
```typescript
22+
// ✅ Import types derived from constants
23+
import type { Difficulty, GameType, PlayerId } from '@turn-based-mcp/shared'
24+
import { DIFFICULTIES, DEFAULT_AI_DIFFICULTY, GAME_TYPES, PLAYER_IDS } from '@turn-based-mcp/shared'
25+
26+
// ✅ Use the imported types
27+
const [aiDifficulty, setAiDifficulty] = useState<Difficulty>('medium')
28+
const playerIds: PlayerId[] = Object.values(PLAYER_IDS)
29+
30+
// ❌ Don't define duplicate union types
31+
type Difficulty = 'easy' | 'medium' | 'hard' // This duplicates shared constants!
32+
type PlayerId = 'player1' | 'player2' | 'ai' // Use the derived type instead!
33+
```
34+
35+
**Key principle: Types are derived from constants using `as const` assertions:**
36+
```typescript
37+
// In shared/src/constants/game-constants.ts
38+
export const DIFFICULTIES = ['easy', 'medium', 'hard'] as const
39+
export type Difficulty = typeof DIFFICULTIES[number] // 'easy' | 'medium' | 'hard'
40+
```
41+
42+
**Common types available from shared package:**
43+
- `Difficulty` - AI difficulty levels (derived from `DIFFICULTIES`)
44+
- `GameType` - Supported game types (derived from `GAME_TYPES`)
45+
- `PlayerId` - Player identifiers (derived from `PLAYER_IDS`)
46+
- `GameStatus` - Game state values (derived from `GAME_STATUSES`)
47+
- Game-specific interfaces: `TicTacToeGameState`, `RPSGameState`, etc.
48+
1849
### Interface Design
1950
- Use interfaces for object shapes and component props
2051
- Include JSDoc comments for complex properties

mcp-server/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
"name": "@turn-based-mcp/mcp-server",
33
"version": "1.0.0",
44
"description": "MCP server for turn-based games AI opponent",
5-
"main": "dist/index.js",
5+
"main": "dist/server.js",
66
"type": "module",
77
"scripts": {
88
"build": "tsc",
99
"dev": "tsc --watch",
10-
"start": "node dist/index.js",
10+
"start": "node dist/server.js",
1111
"clean": "rm -rf dist",
1212
"type-check": "tsc --noEmit",
1313
"test": "vitest run",

mcp-server/src/ai/rock-paper-scissors-ai.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import type { RPSGameState, RPSMove, RPSChoice } from '@turn-based-mcp/shared'
1+
import type { RPSGameState, RPSMove, RPSChoice, Difficulty } from '@turn-based-mcp/shared'
22
import { RockPaperScissorsGame } from '@turn-based-mcp/shared'
33

44
export type Strategy = 'random' | 'adaptive' | 'pattern'
5-
export type Difficulty = 'easy' | 'medium' | 'hard'
65

76
/**
87
* AI opponent for Rock Paper Scissors with multiple strategic approaches

mcp-server/src/ai/tic-tac-toe-ai.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import type { TicTacToeGameState, TicTacToeMove } from '@turn-based-mcp/shared'
1+
import type { TicTacToeGameState, TicTacToeMove, Difficulty } from '@turn-based-mcp/shared'
22
import { TicTacToeGame } from '@turn-based-mcp/shared'
33

4-
export type Difficulty = 'easy' | 'medium' | 'hard'
5-
64
/**
75
* AI opponent for Tic-Tac-Toe with configurable difficulty levels
86
*

mcp-server/src/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

mcp-server/src/utils/http-client.ts

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,11 @@
11
/**
22
* HTTP client utilities for communicating with the web API
3+
*
4+
* This module provides higher-level API functions for the MCP server
5+
* while using shared HTTP utilities to eliminate code duplication.
36
*/
47

5-
const WEB_API_BASE = process.env.WEB_API_BASE || 'http://localhost:3000'
6-
7-
export async function httpGet(url: string): Promise<any> {
8-
const response = await fetch(url)
9-
if (!response.ok) {
10-
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
11-
}
12-
return response.json()
13-
}
14-
15-
export async function httpPost(url: string, data: any): Promise<any> {
16-
const response = await fetch(url, {
17-
method: 'POST',
18-
headers: { 'Content-Type': 'application/json' },
19-
body: JSON.stringify(data)
20-
})
21-
if (!response.ok) {
22-
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
23-
}
24-
return response.json()
25-
}
8+
import { httpGet, httpPost, WEB_API_BASE } from '@turn-based-mcp/shared'
269

2710
/**
2811
* Generic game state fetcher for resources

mcp-server/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@
1515
"allowSyntheticDefaultImports": true
1616
},
1717
"include": ["src/**/*"],
18-
"exclude": ["dist", "node_modules"]
18+
"exclude": ["dist", "node_modules", "**/*.test.ts", "**/*.test.tsx"]
1919
}

0 commit comments

Comments
 (0)