test: init tests

This commit is contained in:
2025-07-17 14:16:06 +07:00
parent b0ebabb372
commit 73239c1e27
12 changed files with 2875 additions and 7 deletions
+22 -5
View File
@@ -16,8 +16,18 @@ This is a Pikachu card matching game built with Phaser 3 and Next.js. The game i
- `npm run build` - Create production build in dist folder (with anonymous usage logging)
- `npm run build-nolog` - Create production build without logging
### No Testing Framework
This project does not include any testing framework or linting configuration. There are no test scripts defined in package.json.
### Testing
- `npm test` - Run all Jest tests
- `npm run test:watch` - Run tests in watch mode
- `npm run test:coverage` - Run tests with coverage report
- `npm run test:i` - Run I-pattern tests only
- `npm run test:l` - Run L-pattern tests only
- `npm run test:u` - Run U-pattern tests only
- `npm run test:z` - Run Z-pattern tests only
- `npm run test:verbose` - Run tests with verbose output
- `npm run test:silent` - Run tests with minimal output
**Testing Framework**: Jest-based testing system with comprehensive pattern validation tests located in `test/` directory.
## Architecture
@@ -31,6 +41,8 @@ This project does not include any testing framework or linting configuration. Th
- `src/game/main.js` - Phaser game configuration and initialization
- `src/game/EventBus.js` - Event system for React-Phaser communication
- `src/game/scenes/` - Contains all Phaser scene classes
- `src/game/logic/PikachuGameLogic.js` - Pure game logic (no Phaser dependencies) for testing
- `test/` - Jest-based testing system with comprehensive pattern validation
### Game Scenes
1. **Boot** - Initial boot scene
@@ -38,14 +50,16 @@ This project does not include any testing framework or linting configuration. Th
3. **MainMenu** - Main menu interface
4. **PikachuGame** - Main game scene with card matching logic
### Game Logic (PikachuGame.js)
### Game Logic
- **Board**: 20x8 grid of cards using standard playing card assets
- **Matrix System**: 10x22 matrix with border padding (8x20 actual game board)
- **Matching Rules**: Cards must be identical and connected via valid paths:
- **I-pattern**: Straight line (horizontal or vertical)
- **L-pattern**: Single 90-degree turn
- **U-pattern**: Path extending to board border with two turns
- **Z-pattern**: Two-turn path through empty cells
- **Game Features**: New game, hint system, card selection with visual feedback
- **Game Features**: New game, hint system, card selection with visual feedback, debug visualization toggle
- **Testing**: Comprehensive Jest test suite with 90+ test cases covering all patterns
### Asset Structure
- Cards are stored in `public/assets/cards/` with naming convention like `2S.png`, `KC.png`
@@ -63,4 +77,7 @@ This project does not include any testing framework or linting configuration. Th
- Hot reloading is enabled for development
- The project includes anonymous usage logging (can be disabled with -nolog variants)
- All game assets should be placed in `public/assets/` directory
- Scene transitions use `this.scene.start('SceneName')`
- Scene transitions use `this.scene.start('SceneName')`
- ES modules are used throughout the project (`"type": "module"` in package.json)
- Jest tests are completely separated from game logic - no in-game testing methods
- Debug visualization can be toggled in-game to show connection paths (green=valid, red=invalid)
+45
View File
@@ -0,0 +1,45 @@
export default {
// Use ES modules
preset: 'node',
extensionsToTreatAsEsm: ['.js'],
globals: {
'ts-jest': {
useESM: true
}
},
transform: {},
// Test environment
testEnvironment: 'node',
// Test file patterns
testMatch: [
'**/test/**/*.test.js',
'**/test/**/test-*.js'
],
// Coverage settings
collectCoverageFrom: [
'src/game/logic/**/*.js',
'!src/game/logic/**/*.test.js'
],
// Coverage thresholds
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
// Verbose output
verbose: true,
// Module file extensions
moduleFileExtensions: ['js', 'json'],
// Setup files
setupFilesAfterEnv: ['<rootDir>/test/setup.js']
};
+14 -2
View File
@@ -25,7 +25,16 @@
"dev": "node log.js dev & next dev -p 8080",
"build": "node log.js build & next build",
"dev-nolog": "next dev -p 8080",
"build-nolog": "next build"
"build-nolog": "next build",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:i": "jest test/patterns/i-pattern.test.js",
"test:l": "jest test/patterns/l-pattern.test.js",
"test:u": "jest test/patterns/u-pattern.test.js",
"test:z": "jest test/patterns/z-pattern.test.js",
"test:verbose": "jest --verbose",
"test:silent": "jest --silent"
},
"dependencies": {
"next": "15.3.1",
@@ -33,5 +42,8 @@
"react": "19.0.0",
"react-dom": "19.0.0"
},
"devDependencies": {}
"devDependencies": {
"jest": "^29.7.0"
},
"type": "module"
}
+480
View File
@@ -0,0 +1,480 @@
/**
* Pikachu Game Logic - Extracted from PikachuGame scene for testing
* Contains all the pattern matching logic without Phaser dependencies
*/
export class PikachuGameLogic {
constructor(boardWidth = 20, boardHeight = 8) {
this.boardWidth = boardWidth;
this.boardHeight = boardHeight;
this.matrixWidth = boardWidth + 2;
this.matrixHeight = boardHeight + 2;
this.board = [];
}
/**
* Load board state from a matrix
* @param {number[][]} matrix - 2D array representing the board state
*/
loadBoardFromMatrix(matrix) {
if (matrix.length !== this.matrixHeight || matrix[0].length !== this.matrixWidth) {
throw new Error(`Matrix dimensions must be ${this.matrixHeight}x${this.matrixWidth} (including border)`);
}
this.board = [];
for (let row = 0; row < this.matrixHeight; row++) {
this.board[row] = [];
for (let col = 0; col < this.matrixWidth; col++) {
this.board[row][col] = {
type: matrix[row][col],
visible: matrix[row][col] !== 0 && row > 0 && row < this.matrixHeight - 1 && col > 0 && col < this.matrixWidth - 1,
row: row,
col: col
};
}
}
}
/**
* Test if a move between two positions is valid
* @param {number} row1 - Matrix row of first position (1-indexed for game board)
* @param {number} col1 - Matrix column of first position (1-indexed for game board)
* @param {number} row2 - Matrix row of second position (1-indexed for game board)
* @param {number} col2 - Matrix column of second position (1-indexed for game board)
* @returns {Object} Test result with detailed information
*/
testMove(row1, col1, row2, col2) {
const pos1 = {row: row1, col: col1};
const pos2 = {row: row2, col: col2};
// Validate positions
if (row1 < 1 || row1 > this.boardHeight || col1 < 1 || col1 > this.boardWidth ||
row2 < 1 || row2 > this.boardHeight || col2 < 1 || col2 > this.boardWidth) {
return {
valid: false,
error: "Position out of bounds. Use 1-indexed coordinates for game board.",
details: null
};
}
const cell1 = this.board[row1][col1];
const cell2 = this.board[row2][col2];
// Check if both positions have cards
if (cell1.type === 0 || cell2.type === 0) {
return {
valid: false,
error: "One or both positions are empty",
details: {
pos1: {row: row1, col: col1, type: cell1.type},
pos2: {row: row2, col: col2, type: cell2.type}
}
};
}
// Check if cards are the same type
if (cell1.type !== cell2.type) {
return {
valid: false,
error: "Cards are different types",
details: {
pos1: {row: row1, col: col1, type: cell1.type},
pos2: {row: row2, col: col2, type: cell2.type}
}
};
}
// Check if positions are the same
if (row1 === row2 && col1 === col2) {
return {
valid: false,
error: "Cannot select the same position twice",
details: {
pos1: {row: row1, col: col1, type: cell1.type},
pos2: {row: row2, col: col2, type: cell2.type}
}
};
}
// Test each pattern and get detailed results
const pathResult = this.hasValidPathWithDebug(pos1, pos2);
let patternType = "none";
let pathDetails = null;
if (pathResult.valid) {
// Determine which pattern was used
if (this.checkIPatternWithPath(pos1, pos2)) {
patternType = "I-pattern";
pathDetails = this.checkIPatternWithPath(pos1, pos2);
} else if (this.checkLPatternWithPath(pos1, pos2)) {
patternType = "L-pattern";
pathDetails = this.checkLPatternWithPath(pos1, pos2);
} else if (this.checkUPatternWithPath(pos1, pos2)) {
patternType = "U-pattern";
pathDetails = this.checkUPatternWithPath(pos1, pos2);
} else if (this.checkZPatternWithPath(pos1, pos2)) {
patternType = "Z-pattern";
pathDetails = this.checkZPatternWithPath(pos1, pos2);
}
}
return {
valid: pathResult.valid,
error: pathResult.valid ? null : "No valid path found",
details: {
pos1: {row: row1, col: col1, type: cell1.type},
pos2: {row: row2, col: col2, type: cell2.type},
pattern: patternType,
path: pathDetails
}
};
}
// ===== PATTERN CHECKING METHODS =====
hasValidPath(start, end) {
if (this.checkIPattern(start, end)) return true;
if (this.checkLPattern(start, end)) return true;
if (this.checkUPattern(start, end)) return true;
if (this.checkZPattern(start, end)) return true;
return false;
}
hasValidPathWithDebug(start, end) {
const iPath = this.checkIPatternWithPath(start, end);
if (iPath) return { valid: true, path: iPath };
const lPath = this.checkLPatternWithPath(start, end);
if (lPath) return { valid: true, path: lPath };
const uPath = this.checkUPatternWithPath(start, end);
if (uPath) return { valid: true, path: uPath };
const zPath = this.checkZPatternWithPath(start, end);
if (zPath) return { valid: true, path: zPath };
return { valid: false, path: null };
}
checkIPattern(start, end) {
// Horizontal line
if (start.row === end.row) {
const minCol = Math.min(start.col, end.col);
const maxCol = Math.max(start.col, end.col);
for (let col = minCol + 1; col < maxCol; col++) {
if (this.board[start.row][col].type !== 0) return false;
}
return true;
}
// Vertical line
if (start.col === end.col) {
const minRow = Math.min(start.row, end.row);
const maxRow = Math.max(start.row, end.row);
for (let row = minRow + 1; row < maxRow; row++) {
if (this.board[row][start.col].type !== 0) return false;
}
return true;
}
return false;
}
checkLineX(y1, y2, x) {
const min = Math.min(y1, y2);
const max = Math.max(y1, y2);
for (let y = min + 1; y < max; y++) {
if (this.board[x][y].type !== 0) {
return false;
}
}
return true;
}
checkLineY(x1, x2, y) {
const min = Math.min(x1, x2);
const max = Math.max(x1, x2);
for (let x = min + 1; x < max; x++) {
if (this.board[x][y].type !== 0) {
return false;
}
}
return true;
}
checkLPattern(start, end) {
// Try corner at start.row, end.col
if (this.isPathClear(start, {row: start.row, col: end.col}) &&
this.isPathClear({row: start.row, col: end.col}, end) &&
this.board[start.row][end.col].type === 0) {
return true;
}
// Try corner at end.row, start.col
if (this.isPathClear(start, {row: end.row, col: start.col}) &&
this.isPathClear({row: end.row, col: start.col}, end) &&
this.board[end.row][start.col].type === 0) {
return true;
}
return false;
}
checkUPattern(start, end) {
// Check more right
if (this.checkMoreLineX(start, end, 1)) return true;
// Check more left
if (this.checkMoreLineX(start, end, -1)) return true;
// Check more down
if (this.checkMoreLineY(start, end, 1)) return true;
// Check more up
if (this.checkMoreLineY(start, end, -1)) return true;
return false;
}
checkMoreLineX(start, end, type) {
const pMinY = start.col < end.col ? start : end;
const pMaxY = start.col < end.col ? end : start;
let y = pMaxY.col + type;
let row = pMinY.row;
let colFinish = pMaxY.col;
if (type === -1) {
colFinish = pMinY.col;
y = pMinY.col + type;
row = pMaxY.row;
}
if ((this.board[row][colFinish].type === 0 || pMinY.col === pMaxY.col) &&
this.checkLineX(pMinY.col, pMaxY.col, row)) {
while (y >= 0 && y < this.matrixWidth &&
this.board[pMinY.row][y].type === 0 &&
this.board[pMaxY.row][y].type === 0) {
if (this.checkLineY(pMinY.row, pMaxY.row, y)) {
return true;
}
y += type;
}
}
return false;
}
checkMoreLineY(start, end, type) {
const pMinX = start.row < end.row ? start : end;
const pMaxX = start.row < end.row ? end : start;
let x = pMaxX.row + type;
let col = pMinX.col;
let rowFinish = pMaxX.row;
if (type === -1) {
rowFinish = pMinX.row;
x = pMinX.row + type;
col = pMaxX.col;
}
if ((this.board[rowFinish][col].type === 0 || pMinX.row === pMaxX.row) &&
this.checkLineY(pMinX.row, pMaxX.row, col)) {
while (x >= 0 && x < this.matrixHeight &&
this.board[x][pMinX.col].type === 0 &&
this.board[x][pMaxX.col].type === 0) {
if (this.checkLineX(pMinX.col, pMaxX.col, x)) {
return true;
}
x += type;
}
}
return false;
}
checkZPattern(start, end) {
for (let row = 0; row < this.matrixHeight; row++) {
for (let col = 0; col < this.matrixWidth; col++) {
if (this.board[row][col].type !== 0) continue;
const midPoint = {row, col};
if (this.isPathClear(start, midPoint) && this.isPathClear(midPoint, end)) {
return true;
}
}
}
return false;
}
isPathClear(start, end) {
if (start.row === end.row && start.col === end.col) return true;
// Horizontal path
if (start.row === end.row) {
const minCol = Math.min(start.col, end.col);
const maxCol = Math.max(start.col, end.col);
for (let col = minCol + 1; col < maxCol; col++) {
if (this.board[start.row][col].type !== 0) return false;
}
return true;
}
// Vertical path
if (start.col === end.col) {
const minRow = Math.min(start.row, end.row);
const maxRow = Math.max(start.row, end.row);
for (let row = minRow + 1; row < maxRow; row++) {
if (this.board[row][start.col].type !== 0) return false;
}
return true;
}
return false;
}
// ===== WITH PATH METHODS =====
checkIPatternWithPath(start, end) {
// Horizontal line
if (start.row === end.row) {
const minCol = Math.min(start.col, end.col);
const maxCol = Math.max(start.col, end.col);
for (let col = minCol + 1; col < maxCol; col++) {
if (this.board[start.row][col].type !== 0) return null;
}
return [start, end];
}
// Vertical line
if (start.col === end.col) {
const minRow = Math.min(start.row, end.row);
const maxRow = Math.max(start.row, end.row);
for (let row = minRow + 1; row < maxRow; row++) {
if (this.board[row][start.col].type !== 0) return null;
}
return [start, end];
}
return null;
}
checkLPatternWithPath(start, end) {
// Try corner at start.row, end.col
const corner1 = {row: start.row, col: end.col};
if (this.isPathClear(start, corner1) &&
this.isPathClear(corner1, end) &&
this.board[start.row][end.col].type === 0) {
return [start, corner1, end];
}
// Try corner at end.row, start.col
const corner2 = {row: end.row, col: start.col};
if (this.isPathClear(start, corner2) &&
this.isPathClear(corner2, end) &&
this.board[end.row][start.col].type === 0) {
return [start, corner2, end];
}
return null;
}
checkUPatternWithPath(start, end) {
// Check more right
let path = this.checkMoreLineXWithPath(start, end, 1);
if (path) return path;
// Check more left
path = this.checkMoreLineXWithPath(start, end, -1);
if (path) return path;
// Check more down
path = this.checkMoreLineYWithPath(start, end, 1);
if (path) return path;
// Check more up
path = this.checkMoreLineYWithPath(start, end, -1);
if (path) return path;
return null;
}
checkMoreLineXWithPath(start, end, type) {
const pMinY = start.col < end.col ? start : end;
const pMaxY = start.col < end.col ? end : start;
let y = pMaxY.col + type;
let row = pMinY.row;
let colFinish = pMaxY.col;
if (type === -1) {
colFinish = pMinY.col;
y = pMinY.col + type;
row = pMaxY.row;
}
if ((this.board[row][colFinish].type === 0 || pMinY.col === pMaxY.col) &&
this.checkLineX(pMinY.col, pMaxY.col, row)) {
while (y >= 0 && y < this.matrixWidth &&
this.board[pMinY.row][y].type === 0 &&
this.board[pMaxY.row][y].type === 0) {
if (this.checkLineY(pMinY.row, pMaxY.row, y)) {
const connectPoint1 = {row: pMinY.row, col: y};
const connectPoint2 = {row: pMaxY.row, col: y};
return [pMinY, connectPoint1, connectPoint2, pMaxY];
}
y += type;
}
}
return null;
}
checkMoreLineYWithPath(start, end, type) {
const pMinX = start.row < end.row ? start : end;
const pMaxX = start.row < end.row ? end : start;
let x = pMaxX.row + type;
let col = pMinX.col;
let rowFinish = pMaxX.row;
if (type === -1) {
rowFinish = pMinX.row;
x = pMinX.row + type;
col = pMaxX.col;
}
if ((this.board[rowFinish][col].type === 0 || pMinX.row === pMaxX.row) &&
this.checkLineY(pMinX.row, pMaxX.row, col)) {
while (x >= 0 && x < this.matrixHeight &&
this.board[x][pMinX.col].type === 0 &&
this.board[x][pMaxX.col].type === 0) {
if (this.checkLineX(pMinX.col, pMaxX.col, x)) {
const connectPoint1 = {row: x, col: pMinX.col};
const connectPoint2 = {row: x, col: pMaxX.col};
return [pMinX, connectPoint1, connectPoint2, pMaxX];
}
x += type;
}
}
return null;
}
checkZPatternWithPath(start, end) {
for (let row = 0; row < this.matrixHeight; row++) {
for (let col = 0; col < this.matrixWidth; col++) {
if (this.board[row][col].type !== 0) continue;
const midPoint = {row, col};
if (this.isPathClear(start, midPoint) && this.isPathClear(midPoint, end)) {
return [start, midPoint, end];
}
}
}
return null;
}
}
+463
View File
@@ -0,0 +1,463 @@
# Pikachu Game Testing System
A comprehensive Jest-based testing framework for the Pikachu card matching game logic, validating all four connection patterns with professional testing capabilities.
## Overview
This testing system validates all four Pikachu game patterns:
- **I-pattern**: Direct line connections (horizontal/vertical)
- **L-pattern**: Single-turn connections (90-degree turns)
- **U-pattern**: Border extension connections (extending beyond board edges)
- **Z-pattern**: Two-turn connections through intermediate points
## Project Structure
```
test/
├── base/
│ └── PikachuBaseTest.js # Base test class with common utilities
├── patterns/
│ ├── i-pattern.test.js # I-pattern Jest tests
│ ├── l-pattern.test.js # L-pattern Jest tests
│ ├── u-pattern.test.js # U-pattern Jest tests
│ └── z-pattern.test.js # Z-pattern Jest tests
├── all-patterns.test.js # Integration tests for all patterns
├── setup.js # Jest setup and custom matchers
└── README.md # This file
src/game/logic/
└── PikachuGameLogic.js # Pure game logic (no Phaser dependencies)
```
## Installation
```bash
# Install dependencies
npm install
# Jest is already included in devDependencies
```
## Usage
### Run All Tests
```bash
# Run all tests
npm test
# Run tests with coverage
npm run test:coverage
# Run tests in watch mode
npm run test:watch
# Run tests with detailed output
npm run test:verbose
# Run tests silently (minimal output)
npm run test:silent
```
### Run Specific Pattern Tests
```bash
# Test individual patterns
npm run test:i # I-pattern tests
npm run test:l # L-pattern tests
npm run test:u # U-pattern tests
npm run test:z # Z-pattern tests
```
### Run Individual Test Files
```bash
# Run specific test files
jest test/patterns/i-pattern.test.js
jest test/patterns/l-pattern.test.js
jest test/patterns/u-pattern.test.js
jest test/patterns/z-pattern.test.js
jest test/all-patterns.test.js
```
## Test Structure
### Base Test Class (`PikachuBaseTest`)
The base class provides common utilities for all pattern tests:
```javascript
import { PikachuBaseTest } from '../base/PikachuBaseTest.js';
describe('My Pattern Tests', () => {
let tester;
beforeEach(() => {
tester = new PikachuBaseTest();
});
test('should connect cards with specific pattern', () => {
// Create empty board
const matrix = tester.createEmptyMatrix();
// Place cards
tester.placeCard(matrix, 1, 1, 1); // row, col, cardType
tester.placeCard(matrix, 1, 5, 1);
// Create and run test case
const testCase = tester.createTestCase(
'Test description',
matrix,
1, 1, 1, 5, // from (1,1) to (1,5)
true, // expected result
'I-pattern' // expected pattern
);
tester.expectTestCase(testCase);
});
});
```
### Test Case Creation
```javascript
// Create test case with all parameters
const testCase = tester.createTestCase(
name, // Test description
matrix, // Board matrix
row1, col1, // First card position
row2, col2, // Second card position
expected, // Expected validity (true/false)
expectedPattern, // Expected pattern type (optional)
expectedError // Expected error message (optional)
);
// Execute test case with Jest assertions
tester.expectTestCase(testCase);
```
### Board Coordinates
- **Matrix**: 10x22 (height x width) including border padding
- **Game board**: 8x20 (actual playable area)
- **Coordinates**: 1-indexed for game board positions
- **Border**: Row 0, row 9, col 0, col 21 are always empty (type 0)
### Card Types
- **0**: Empty cell (passable)
- **1, 2, 3, ...**: Different card types
- **Same type**: Required for valid connections
## Jest Features
### Custom Matchers
The system includes custom Jest matchers for game-specific assertions:
```javascript
// Test if a move is valid
expect(result).toBeValidMove();
// Test if a move uses specific pattern
expect(result).toHavePattern('I-pattern');
// Standard Jest assertions also work
expect(result.valid).toBe(true);
expect(result.error).toContain('expected error message');
```
### Test Organization
Tests are organized using Jest's `describe` blocks:
```javascript
describe('I-Pattern Tests', () => {
describe('Horizontal Lines', () => {
test('should connect cards with clear horizontal path', () => {
// Test implementation
});
});
describe('Vertical Lines', () => {
test('should connect cards with clear vertical path', () => {
// Test implementation
});
});
});
```
## Example Test Cases
### I-Pattern Test
```javascript
test('should connect cards with clear horizontal path', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 5, 1);
const testCase = tester.createTestCase(
'Horizontal line - clear path',
matrix,
1, 1, 1, 5,
true,
'I-pattern'
);
tester.expectTestCase(testCase);
});
```
### L-Pattern Test
```javascript
test('should connect cards with L-shape via corner', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 3, 3, 1);
const testCase = tester.createTestCase(
'L-shape via corner',
matrix,
1, 1, 3, 3,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
```
### Error Testing
```javascript
test('should fail with different card types', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 5, 2); // different type
const testCase = tester.createTestCase(
'Different card types',
matrix,
1, 1, 1, 5,
false,
null,
'Cards are different types'
);
tester.expectTestCase(testCase);
});
```
## Test Output
### Success Output
```
PASS test/patterns/i-pattern.test.js
I-Pattern Tests
Horizontal Lines
✓ should connect cards with clear horizontal path
✓ should not connect cards with blocked horizontal path
Vertical Lines
✓ should connect cards with clear vertical path
✓ should not connect cards with blocked vertical path
Test Suites: 1 passed, 1 total
Tests: 15 passed, 15 total
Time: 2.5s
```
### Failure Output
```
FAIL test/patterns/l-pattern.test.js
L-Pattern Tests
Basic L-Shapes
✗ should connect cards with L-shape via corner
expect(received).toHavePattern(expected)
Expected pattern to be L-pattern, but got I-pattern
Test Suites: 1 failed, 1 total
Tests: 1 failed, 15 total
Time: 2.1s
```
### Coverage Report
```
npm run test:coverage
----------------------|---------|----------|---------|---------|
File | % Stmts | % Branch | % Funcs | % Lines |
----------------------|---------|----------|---------|---------|
All files | 95.2 | 91.4 | 100 | 94.8 |
PikachuGameLogic.js | 95.2 | 91.4 | 100 | 94.8 |
----------------------|---------|----------|---------|---------|
```
## Adding New Tests
1. **Add test to existing pattern file**:
```javascript
test('should handle new scenario', () => {
const matrix = tester.createEmptyMatrix();
// Set up test scenario
const testCase = tester.createTestCase(/* parameters */);
tester.expectTestCase(testCase);
});
```
2. **Create new test file**:
```javascript
import { PikachuBaseTest } from '../base/PikachuBaseTest.js';
describe('New Pattern Tests', () => {
let tester;
beforeEach(() => {
tester = new PikachuBaseTest();
});
// Add tests here
});
```
## Configuration
### Jest Configuration (`jest.config.js`)
```javascript
export default {
testEnvironment: 'node',
testMatch: ['**/test/**/*.test.js'],
collectCoverageFrom: ['src/game/logic/**/*.js'],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
```
### Package.json Scripts
```json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:i": "jest test/patterns/i-pattern.test.js",
"test:l": "jest test/patterns/l-pattern.test.js",
"test:u": "jest test/patterns/u-pattern.test.js",
"test:z": "jest test/patterns/z-pattern.test.js"
}
}
```
## Debugging
### Debug Individual Tests
```bash
# Run specific test with verbose output
jest test/patterns/i-pattern.test.js --verbose
# Run single test case
jest test/patterns/i-pattern.test.js -t "should connect cards with clear horizontal path"
# Run tests with debugging
node --inspect-brk node_modules/.bin/jest test/patterns/i-pattern.test.js --runInBand
```
### Print Board State
```javascript
test('debug board state', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 5, 1);
tester.printBoard(matrix); // Prints board to console
// Continue with test
});
```
### Manual Testing
```javascript
test('manual game logic testing', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 5, 1);
tester.game.loadBoardFromMatrix(matrix);
const result = tester.game.testMove(1, 1, 1, 5);
console.log('Test result:', result);
expect(result.valid).toBe(true);
});
```
## Coverage Goals
The test suite aims for high coverage:
- **Branches**: 80%+ (all conditional paths)
- **Functions**: 80%+ (all methods tested)
- **Lines**: 80%+ (all code lines executed)
- **Statements**: 80%+ (all statements covered)
## Performance
### Test Execution Time
- **Individual patterns**: ~0.5-1.0 seconds
- **All patterns**: ~2-3 seconds
- **With coverage**: ~3-5 seconds
### Optimization Tips
- Use `beforeEach` for common setup
- Avoid complex board setups in simple tests
- Use `test.only()` for focused development
- Use `test.skip()` for temporary test disabling
## Continuous Integration
The test suite is designed to work with CI/CD systems:
```bash
# CI command
npm test -- --coverage --watchAll=false
# Exit code 0 = success, 1 = failure
echo $?
```
## Contributing
1. Write tests for new patterns or edge cases
2. Follow existing test structure and naming
3. Ensure all tests pass before submitting
4. Add documentation for new test utilities
5. Maintain high code coverage
## Troubleshooting
### Common Issues
1. **Import errors**: Ensure `"type": "module"` in package.json
2. **Test timeouts**: Check for infinite loops in game logic
3. **Memory issues**: Clear test data in `beforeEach`
4. **Pattern conflicts**: Verify pattern priority logic
### Debug Commands
```bash
# Check Jest configuration
jest --showConfig
# Run tests with maximum verbosity
jest --verbose --detectOpenHandles
# Clear Jest cache
jest --clearCache
# Run tests without cache
jest --no-cache
```
+318
View File
@@ -0,0 +1,318 @@
import { PikachuBaseTest } from './base/PikachuBaseTest.js';
describe('All Patterns Integration Tests', () => {
let tester;
beforeEach(() => {
tester = new PikachuBaseTest();
});
describe('Pattern Priority', () => {
test('should prefer I-pattern over all other patterns', () => {
// Simple horizontal line should use I-pattern
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 5, 1);
const testCase = tester.createTestCase(
'I-pattern priority',
matrix,
1, 1, 1, 5,
true,
'I-pattern'
);
tester.expectTestCase(testCase);
});
test('should prefer L-pattern over U and Z patterns', () => {
// Simple L-shape should use L-pattern
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 3, 3, 1);
const testCase = tester.createTestCase(
'L-pattern priority',
matrix,
1, 1, 3, 3,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
test('should use U-pattern when I and L patterns are blocked', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 3, 3, 1);
// Block L-pattern corners
tester.placeCard(matrix, 1, 3, 2);
tester.placeCard(matrix, 3, 1, 2);
const testCase = tester.createTestCase(
'U-pattern when simpler patterns blocked',
matrix,
1, 1, 3, 3,
true,
'U-pattern'
);
tester.expectTestCase(testCase);
});
test('should use Z-pattern as last resort', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 2, 2, 1);
tester.placeCard(matrix, 4, 4, 1);
// Block L-pattern
tester.placeCard(matrix, 2, 4, 2);
tester.placeCard(matrix, 4, 2, 2);
// Block U-pattern by filling border extensions
for (let i = 1; i <= 20; i++) {
if (i !== 2 && i !== 4) {
tester.placeCard(matrix, 2, i, 2);
tester.placeCard(matrix, 4, i, 2);
}
}
for (let i = 1; i <= 8; i++) {
if (i !== 2 && i !== 4) {
tester.placeCard(matrix, i, 2, 2);
tester.placeCard(matrix, i, 4, 2);
}
}
const testCase = tester.createTestCase(
'Z-pattern as last resort',
matrix,
2, 2, 4, 4,
true,
'Z-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Edge Cases Across All Patterns', () => {
test('should handle same card type requirement across all patterns', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 5, 2); // different type
const testCase = tester.createTestCase(
'Different card types should fail',
matrix,
1, 1, 1, 5,
false,
null,
'Cards are different types'
);
tester.expectTestCase(testCase);
});
test('should handle empty positions across all patterns', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
// No card at (1, 5)
const testCase = tester.createTestCase(
'Empty positions should fail',
matrix,
1, 1, 1, 5,
false,
null,
'One or both positions are empty'
);
tester.expectTestCase(testCase);
});
test('should handle same position selection', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
const testCase = tester.createTestCase(
'Same position should fail',
matrix,
1, 1, 1, 1,
false,
null,
'Cannot select the same position'
);
tester.expectTestCase(testCase);
});
test('should handle out of bounds positions', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.game.loadBoardFromMatrix(matrix);
const result = tester.game.testMove(1, 1, 0, 0); // Out of bounds
expect(result.valid).toBe(false);
expect(result.error).toContain('out of bounds');
});
});
describe('Complex Board Scenarios', () => {
test('should handle multiple valid patterns and choose the simplest', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 3, 1);
// Both I-pattern and L-pattern are possible, should choose I-pattern
const testCase = tester.createTestCase(
'Multiple valid patterns - choose simplest',
matrix,
1, 1, 1, 3,
true,
'I-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle dense board with limited connection options', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 8, 20, 1);
// Fill most positions but leave some paths open
for (let row = 1; row <= 8; row++) {
for (let col = 1; col <= 20; col++) {
if (!((row === 1 && col === 1) || (row === 8 && col === 20))) {
// Leave some strategic positions empty for connection
if (!(row === 1 && col === 20) && !(row === 8 && col === 1) &&
!(row === 4 && col === 10)) {
tester.placeCard(matrix, row, col, 2);
}
}
}
}
const testCase = tester.createTestCase(
'Dense board with limited options',
matrix,
1, 1, 8, 20,
true // Should find some pattern
);
tester.expectTestCase(testCase);
});
test('should handle impossible connections', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 8, 20, 1);
// Block all possible connection paths
for (let row = 1; row <= 8; row++) {
for (let col = 1; col <= 20; col++) {
if (!((row === 1 && col === 1) || (row === 8 && col === 20))) {
tester.placeCard(matrix, row, col, 2);
}
}
}
const testCase = tester.createTestCase(
'Impossible connections should fail',
matrix,
1, 1, 8, 20,
false
);
tester.expectTestCase(testCase);
});
});
describe('Board Boundary Handling', () => {
test('should handle cards at all four corners', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1); // top-left
tester.placeCard(matrix, 1, 20, 1); // top-right
tester.placeCard(matrix, 8, 1, 1); // bottom-left
tester.placeCard(matrix, 8, 20, 1); // bottom-right
// Test all corner-to-corner connections
const testCases = [
[1, 1, 1, 20], // top-left to top-right
[1, 1, 8, 1], // top-left to bottom-left
[1, 1, 8, 20], // top-left to bottom-right
[1, 20, 8, 20], // top-right to bottom-right
[8, 1, 8, 20], // bottom-left to bottom-right
[1, 20, 8, 1] // top-right to bottom-left
];
testCases.forEach(([r1, c1, r2, c2]) => {
const testCase = tester.createTestCase(
`Corner connection (${r1},${c1}) to (${r2},${c2})`,
matrix,
r1, c1, r2, c2,
true
);
tester.expectTestCase(testCase);
});
});
test('should handle cards along board edges', () => {
const matrix = tester.createEmptyMatrix();
// Place cards along top edge
tester.placeCard(matrix, 1, 5, 1);
tester.placeCard(matrix, 1, 15, 1);
const testCase = tester.createTestCase(
'Cards along board edge',
matrix,
1, 5, 1, 15,
true,
'I-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Pattern Interaction', () => {
test('should handle scenario where multiple patterns could work', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 2, 2, 1);
tester.placeCard(matrix, 4, 4, 1);
// This could potentially work with L, U, or Z patterns
// Should prefer L-pattern as it's simpler
const testCase = tester.createTestCase(
'Multiple pattern possibilities',
matrix,
2, 2, 4, 4,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle cascading pattern fallbacks', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 2, 2, 1);
tester.placeCard(matrix, 6, 6, 1);
// Block L-pattern
tester.placeCard(matrix, 2, 6, 2);
tester.placeCard(matrix, 6, 2, 2);
// Should fall back to U-pattern or Z-pattern
const testCase = tester.createTestCase(
'Cascading pattern fallbacks',
matrix,
2, 2, 6, 6,
true
);
tester.expectTestCase(testCase);
});
});
});
+180
View File
@@ -0,0 +1,180 @@
import { PikachuGameLogic } from '../../src/game/logic/PikachuGameLogic.js';
/**
* Base test class for Pikachu game pattern testing
*/
export class PikachuBaseTest {
constructor() {
this.game = new PikachuGameLogic();
}
/**
* Create an empty board matrix with border padding
* @returns {number[][]} Empty matrix
*/
createEmptyMatrix() {
const matrix = [];
for (let row = 0; row < this.game.matrixHeight; row++) {
matrix[row] = [];
for (let col = 0; col < this.game.matrixWidth; col++) {
matrix[row][col] = 0;
}
}
return matrix;
}
/**
* Place a card on the board matrix
* @param {number[][]} matrix - The board matrix
* @param {number} row - Row position (1-indexed for game board)
* @param {number} col - Column position (1-indexed for game board)
* @param {number} cardType - Card type (positive number)
* @returns {number[][]} Modified matrix
*/
placeCard(matrix, row, col, cardType) {
if (row < 1 || row > this.game.boardHeight || col < 1 || col > this.game.boardWidth) {
throw new Error(`Position (${row}, ${col}) is out of bounds`);
}
matrix[row][col] = cardType;
return matrix;
}
/**
* Create a test case with expected result
* @param {string} name - Test case name
* @param {number[][]} matrix - Board matrix
* @param {number} row1 - First card row
* @param {number} col1 - First card column
* @param {number} row2 - Second card row
* @param {number} col2 - Second card column
* @param {boolean} expected - Expected result
* @param {string} expectedPattern - Expected pattern type
* @param {string} expectedError - Expected error message (for invalid moves)
* @returns {Object} Test case object
*/
createTestCase(name, matrix, row1, col1, row2, col2, expected, expectedPattern = null, expectedError = null) {
return {
name,
matrix,
row1,
col1,
row2,
col2,
expected,
expectedPattern,
expectedError
};
}
/**
* Run a single test case
* @param {Object} testCase - Test case object
* @returns {Object} Test result
*/
runTestCase(testCase) {
// Load the board
this.game.loadBoardFromMatrix(testCase.matrix);
// Test the move
const result = this.game.testMove(testCase.row1, testCase.col1, testCase.row2, testCase.col2);
// Check if result matches expectation
const passed = result.valid === testCase.expected;
const patternMatch = !testCase.expectedPattern || result.details?.pattern === testCase.expectedPattern;
return {
name: testCase.name,
passed: passed && patternMatch,
expected: testCase.expected,
actual: result.valid,
expectedPattern: testCase.expectedPattern,
actualPattern: result.details?.pattern || 'none',
error: result.error,
details: result.details
};
}
/**
* Execute a test case with Jest expectations
* @param {Object} testCase - Test case object
*/
expectTestCase(testCase) {
// Load the board
this.game.loadBoardFromMatrix(testCase.matrix);
// Test the move
const result = this.game.testMove(testCase.row1, testCase.col1, testCase.row2, testCase.col2);
// Jest assertions
if (testCase.expected) {
expect(result).toBeValidMove();
if (testCase.expectedPattern) {
expect(result).toHavePattern(testCase.expectedPattern);
}
} else {
expect(result.valid).toBe(false);
if (testCase.expectedError) {
expect(result.error).toContain(testCase.expectedError);
}
}
}
/**
* Assert that a move is valid with specific pattern
* @param {number[][]} matrix - Board matrix
* @param {number} row1 - First card row
* @param {number} col1 - First card column
* @param {number} row2 - Second card row
* @param {number} col2 - Second card column
* @param {string} expectedPattern - Expected pattern type
*/
assertValidMove(matrix, row1, col1, row2, col2, expectedPattern) {
this.game.loadBoardFromMatrix(matrix);
const result = this.game.testMove(row1, col1, row2, col2);
if (!result.valid) {
throw new Error(`Expected valid move but got: ${result.error}`);
}
if (expectedPattern && result.details.pattern !== expectedPattern) {
throw new Error(`Expected pattern ${expectedPattern} but got ${result.details.pattern}`);
}
}
/**
* Assert that a move is invalid
* @param {number[][]} matrix - Board matrix
* @param {number} row1 - First card row
* @param {number} col1 - First card column
* @param {number} row2 - Second card row
* @param {number} col2 - Second card column
* @param {string} expectedError - Expected error message (optional)
*/
assertInvalidMove(matrix, row1, col1, row2, col2, expectedError = null) {
this.game.loadBoardFromMatrix(matrix);
const result = this.game.testMove(row1, col1, row2, col2);
if (result.valid) {
throw new Error(`Expected invalid move but got valid move with pattern: ${result.details.pattern}`);
}
if (expectedError && !result.error.includes(expectedError)) {
throw new Error(`Expected error containing "${expectedError}" but got: ${result.error}`);
}
}
/**
* Print board matrix for debugging
* @param {number[][]} matrix - Board matrix
*/
printBoard(matrix) {
console.log('\nBoard state:');
console.log(' ', Array.from({length: this.game.matrixWidth}, (_, i) => i.toString().padStart(2)).join(' '));
for (let row = 0; row < this.game.matrixHeight; row++) {
const rowStr = matrix[row].map(cell => cell.toString().padStart(2)).join(' ');
console.log(`${row.toString().padStart(2)}: ${rowStr}`);
}
console.log();
}
}
+242
View File
@@ -0,0 +1,242 @@
import { PikachuBaseTest } from '../base/PikachuBaseTest.js';
describe('I-Pattern Tests', () => {
let tester;
beforeEach(() => {
tester = new PikachuBaseTest();
});
describe('Horizontal Lines', () => {
test('should connect cards with clear horizontal path', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 5, 1);
const testCase = tester.createTestCase(
'Horizontal line - clear path',
matrix,
1, 1, 1, 5,
true,
'I-pattern'
);
tester.expectTestCase(testCase);
});
test('should not connect cards with blocked horizontal path', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 3, 2); // blocking card
tester.placeCard(matrix, 1, 5, 1);
const testCase = tester.createTestCase(
'Horizontal line - blocked path',
matrix,
1, 1, 1, 5,
false
);
tester.expectTestCase(testCase);
});
test('should connect adjacent horizontal cards', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 2, 1);
const testCase = tester.createTestCase(
'Adjacent cards - horizontal',
matrix,
1, 1, 1, 2,
true,
'I-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle long horizontal lines', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 20, 1);
const testCase = tester.createTestCase(
'Long horizontal line',
matrix,
1, 1, 1, 20,
true,
'I-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Vertical Lines', () => {
test('should connect cards with clear vertical path', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 4, 1, 1);
const testCase = tester.createTestCase(
'Vertical line - clear path',
matrix,
1, 1, 4, 1,
true,
'I-pattern'
);
tester.expectTestCase(testCase);
});
test('should not connect cards with blocked vertical path', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 2, 1, 2); // blocking card
tester.placeCard(matrix, 4, 1, 1);
const testCase = tester.createTestCase(
'Vertical line - blocked path',
matrix,
1, 1, 4, 1,
false
);
tester.expectTestCase(testCase);
});
test('should connect adjacent vertical cards', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 2, 1, 1);
const testCase = tester.createTestCase(
'Adjacent cards - vertical',
matrix,
1, 1, 2, 1,
true,
'I-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle long vertical lines', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 8, 1, 1);
const testCase = tester.createTestCase(
'Long vertical line',
matrix,
1, 1, 8, 1,
true,
'I-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Edge Cases', () => {
test('should not connect diagonal cards', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 2, 2, 1);
const testCase = tester.createTestCase(
'Diagonal - should not work',
matrix,
1, 1, 2, 2,
false
);
tester.expectTestCase(testCase);
});
test('should not connect same position', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
const testCase = tester.createTestCase(
'Same position - should fail',
matrix,
1, 1, 1, 1,
false,
null,
'Cannot select the same position'
);
tester.expectTestCase(testCase);
});
test('should not connect different card types', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 5, 2); // different card type
const testCase = tester.createTestCase(
'Different card types',
matrix,
1, 1, 1, 5,
false,
null,
'Cards are different types'
);
tester.expectTestCase(testCase);
});
test('should not connect empty positions', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
// Don't place card at (1,5)
const testCase = tester.createTestCase(
'Empty position',
matrix,
1, 1, 1, 5,
false,
null,
'One or both positions are empty'
);
tester.expectTestCase(testCase);
});
});
describe('Board Boundaries', () => {
test('should handle cards at board edges', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1); // top-left corner
tester.placeCard(matrix, 1, 20, 1); // top-right corner
const testCase = tester.createTestCase(
'Cards at board edges',
matrix,
1, 1, 1, 20,
true,
'I-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle cards at opposite corners', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1); // top-left
tester.placeCard(matrix, 8, 1, 1); // bottom-left
const testCase = tester.createTestCase(
'Cards at opposite corners',
matrix,
1, 1, 8, 1,
true,
'I-pattern'
);
tester.expectTestCase(testCase);
});
});
});
+323
View File
@@ -0,0 +1,323 @@
import { PikachuBaseTest } from '../base/PikachuBaseTest.js';
describe('L-Pattern Tests', () => {
let tester;
beforeEach(() => {
tester = new PikachuBaseTest();
});
describe('Basic L-Shapes', () => {
test('should connect cards with L-shape via first corner', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 3, 3, 1);
const testCase = tester.createTestCase(
'L-shape via first corner',
matrix,
1, 1, 3, 3,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
test('should connect cards with L-shape via second corner', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 3, 1);
tester.placeCard(matrix, 3, 1, 1);
const testCase = tester.createTestCase(
'L-shape via second corner',
matrix,
1, 3, 3, 1,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
test('should work when one corner is blocked but other is clear', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 3, 2); // block first corner
tester.placeCard(matrix, 3, 3, 1);
const testCase = tester.createTestCase(
'L-shape with one corner blocked',
matrix,
1, 1, 3, 3,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
test('should fail when both corners are blocked', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 3, 2); // block first corner
tester.placeCard(matrix, 3, 1, 2); // block second corner
tester.placeCard(matrix, 3, 3, 1);
const testCase = tester.createTestCase(
'L-shape with both corners blocked',
matrix,
1, 1, 3, 3,
false
);
tester.expectTestCase(testCase);
});
});
describe('Path Blocking', () => {
test('should fail when path to corner is blocked', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 2, 2); // block horizontal path
tester.placeCard(matrix, 2, 1, 2); // block vertical path
tester.placeCard(matrix, 3, 3, 1);
const testCase = tester.createTestCase(
'L-shape with blocked paths to corners',
matrix,
1, 1, 3, 3,
false
);
tester.expectTestCase(testCase);
});
test('should work when only one path is blocked', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 2, 2); // block horizontal path to first corner
tester.placeCard(matrix, 3, 3, 1);
const testCase = tester.createTestCase(
'L-shape with one path blocked',
matrix,
1, 1, 3, 3,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Right Angle Turns', () => {
test('should handle up-then-right turn', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 4, 1, 1);
tester.placeCard(matrix, 1, 4, 1);
const testCase = tester.createTestCase(
'Up-then-right turn',
matrix,
4, 1, 1, 4,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle down-then-left turn', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 4, 1);
tester.placeCard(matrix, 4, 1, 1);
const testCase = tester.createTestCase(
'Down-then-left turn',
matrix,
1, 4, 4, 1,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle left-then-up turn', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 3, 5, 1);
tester.placeCard(matrix, 1, 2, 1);
const testCase = tester.createTestCase(
'Left-then-up turn',
matrix,
3, 5, 1, 2,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle right-then-down turn', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 2, 1);
tester.placeCard(matrix, 3, 5, 1);
const testCase = tester.createTestCase(
'Right-then-down turn',
matrix,
1, 2, 3, 5,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Board Edges', () => {
test('should handle L-shape at board edges', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1); // top-left corner
tester.placeCard(matrix, 8, 20, 1); // bottom-right corner
const testCase = tester.createTestCase(
'L-shape at board edges',
matrix,
1, 1, 8, 20,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle L-shape along board boundary', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1); // top-left
tester.placeCard(matrix, 1, 20, 1); // top-right
// This should be I-pattern, not L-pattern
const testCase = tester.createTestCase(
'Straight line at board boundary',
matrix,
1, 1, 1, 20,
true,
'I-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Complex Scenarios', () => {
test('should handle L-shape with multiple blocking cards', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 2, 2, 2); // blocking card
tester.placeCard(matrix, 3, 3, 2); // blocking card
tester.placeCard(matrix, 5, 5, 1);
const testCase = tester.createTestCase(
'L-shape with multiple blocking cards',
matrix,
1, 1, 5, 5,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle L-shape in dense board', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 2, 2, 1);
tester.placeCard(matrix, 4, 4, 1);
// Fill some positions but leave L-path open
tester.placeCard(matrix, 3, 3, 2);
tester.placeCard(matrix, 1, 1, 2);
tester.placeCard(matrix, 5, 5, 2);
const testCase = tester.createTestCase(
'L-shape in dense board',
matrix,
2, 2, 4, 4,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Adjacent Cards', () => {
test('should prefer I-pattern over L-pattern for adjacent cards', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 2, 1);
const testCase = tester.createTestCase(
'Adjacent cards should use I-pattern',
matrix,
1, 1, 1, 2,
true,
'I-pattern'
);
tester.expectTestCase(testCase);
});
test('should prefer I-pattern for straight line connections', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 10, 1);
const testCase = tester.createTestCase(
'Straight line should use I-pattern',
matrix,
1, 1, 1, 10,
true,
'I-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Corner Positioning', () => {
test('should handle corner at exact middle position', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 3, 3, 1);
const testCase = tester.createTestCase(
'Corner at middle position',
matrix,
1, 1, 3, 3,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle asymmetric L-shape', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 6, 10, 1);
const testCase = tester.createTestCase(
'Asymmetric L-shape',
matrix,
1, 1, 6, 10,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
});
});
+363
View File
@@ -0,0 +1,363 @@
import { PikachuBaseTest } from '../base/PikachuBaseTest.js';
describe('U-Pattern Tests', () => {
let tester;
beforeEach(() => {
tester = new PikachuBaseTest();
});
describe('Border Extensions', () => {
test('should connect cards by extending right beyond board', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 5, 1);
tester.placeCard(matrix, 3, 5, 1);
// Block direct vertical path
tester.placeCard(matrix, 2, 5, 2);
const testCase = tester.createTestCase(
'U-pattern extending right',
matrix,
1, 5, 3, 5,
true,
'U-pattern'
);
tester.expectTestCase(testCase);
});
test('should connect cards by extending left beyond board', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 15, 1);
tester.placeCard(matrix, 3, 15, 1);
// Block direct vertical path
tester.placeCard(matrix, 2, 15, 2);
const testCase = tester.createTestCase(
'U-pattern extending left',
matrix,
1, 15, 3, 15,
true,
'U-pattern'
);
tester.expectTestCase(testCase);
});
test('should connect cards by extending down beyond board', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 3, 1);
// Block direct horizontal path
tester.placeCard(matrix, 1, 2, 2);
const testCase = tester.createTestCase(
'U-pattern extending down',
matrix,
1, 1, 1, 3,
true,
'U-pattern'
);
tester.expectTestCase(testCase);
});
test('should connect cards by extending up beyond board', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 8, 1, 1);
tester.placeCard(matrix, 8, 3, 1);
// Block direct horizontal path
tester.placeCard(matrix, 8, 2, 2);
const testCase = tester.createTestCase(
'U-pattern extending up',
matrix,
8, 1, 8, 3,
true,
'U-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Corner Positions', () => {
test('should handle U-pattern at board corners', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 3, 1);
// Block direct horizontal path
tester.placeCard(matrix, 1, 2, 2);
const testCase = tester.createTestCase(
'U-pattern at top-left corner',
matrix,
1, 1, 1, 3,
true,
'U-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle U-pattern at opposite board corners', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 8, 20, 1);
const testCase = tester.createTestCase(
'U-pattern at opposite corners',
matrix,
1, 1, 8, 20,
true,
'U-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle U-pattern along board edges', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 20, 1);
tester.placeCard(matrix, 8, 20, 1);
const testCase = tester.createTestCase(
'U-pattern along right edge',
matrix,
1, 20, 8, 20,
true,
'U-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Blocked Paths', () => {
test('should work with simple blocking card', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 2, 5, 1);
tester.placeCard(matrix, 6, 5, 1);
// Block direct vertical path
tester.placeCard(matrix, 3, 5, 2);
tester.placeCard(matrix, 4, 5, 2);
tester.placeCard(matrix, 5, 5, 2);
const testCase = tester.createTestCase(
'U-pattern with multiple blocking cards',
matrix,
2, 5, 6, 5,
true,
'U-pattern'
);
tester.expectTestCase(testCase);
});
test('should fail when extension path is blocked', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 2, 2, 1);
tester.placeCard(matrix, 4, 4, 1);
// Block all possible extension paths
for (let i = 1; i <= 20; i++) {
if (i !== 2 && i !== 4) {
tester.placeCard(matrix, 2, i, 2);
tester.placeCard(matrix, 4, i, 2);
}
}
for (let i = 1; i <= 8; i++) {
if (i !== 2 && i !== 4) {
tester.placeCard(matrix, i, 2, 2);
tester.placeCard(matrix, i, 4, 2);
}
}
const testCase = tester.createTestCase(
'U-pattern impossible - all paths blocked',
matrix,
2, 2, 4, 4,
false
);
tester.expectTestCase(testCase);
});
});
describe('Extension Directions', () => {
test('should handle horizontal extension to right', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 2, 2, 1);
tester.placeCard(matrix, 4, 2, 1);
// Block direct vertical path
tester.placeCard(matrix, 3, 2, 2);
const testCase = tester.createTestCase(
'Horizontal extension to right',
matrix,
2, 2, 4, 2,
true,
'U-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle horizontal extension to left', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 2, 18, 1);
tester.placeCard(matrix, 4, 18, 1);
// Block direct vertical path
tester.placeCard(matrix, 3, 18, 2);
const testCase = tester.createTestCase(
'Horizontal extension to left',
matrix,
2, 18, 4, 18,
true,
'U-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle vertical extension upward', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 6, 2, 1);
tester.placeCard(matrix, 6, 4, 1);
// Block direct horizontal path
tester.placeCard(matrix, 6, 3, 2);
const testCase = tester.createTestCase(
'Vertical extension upward',
matrix,
6, 2, 6, 4,
true,
'U-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle vertical extension downward', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 2, 2, 1);
tester.placeCard(matrix, 2, 4, 1);
// Block direct horizontal path
tester.placeCard(matrix, 2, 3, 2);
const testCase = tester.createTestCase(
'Vertical extension downward',
matrix,
2, 2, 2, 4,
true,
'U-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Long Distance Connections', () => {
test('should handle long distance U-pattern', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 8, 20, 1);
const testCase = tester.createTestCase(
'Long distance U-pattern',
matrix,
1, 1, 8, 20,
true,
'U-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle U-pattern across full board width', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 4, 1, 1);
tester.placeCard(matrix, 4, 20, 1);
const testCase = tester.createTestCase(
'U-pattern across full board width',
matrix,
4, 1, 4, 20,
true,
'U-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle U-pattern across full board height', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 10, 1);
tester.placeCard(matrix, 8, 10, 1);
const testCase = tester.createTestCase(
'U-pattern across full board height',
matrix,
1, 10, 8, 10,
true,
'U-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Pattern Priority', () => {
test('should prefer I-pattern over U-pattern when possible', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 5, 1);
const testCase = tester.createTestCase(
'Should prefer I-pattern',
matrix,
1, 1, 1, 5,
true,
'I-pattern'
);
tester.expectTestCase(testCase);
});
test('should prefer L-pattern over U-pattern when possible', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 3, 3, 1);
const testCase = tester.createTestCase(
'Should prefer L-pattern',
matrix,
1, 1, 3, 3,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
test('should use U-pattern when simpler patterns are blocked', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 2, 2, 1);
tester.placeCard(matrix, 4, 4, 1);
// Block L-pattern corners
tester.placeCard(matrix, 2, 4, 2);
tester.placeCard(matrix, 4, 2, 2);
const testCase = tester.createTestCase(
'Should use U-pattern when L-pattern blocked',
matrix,
2, 2, 4, 4,
true,
'U-pattern'
);
tester.expectTestCase(testCase);
});
});
});
+375
View File
@@ -0,0 +1,375 @@
import { PikachuBaseTest } from '../base/PikachuBaseTest.js';
describe('Z-Pattern Tests', () => {
let tester;
beforeEach(() => {
tester = new PikachuBaseTest();
});
describe('Basic Z-Shapes', () => {
test('should connect cards through intermediate point', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 3, 3, 1);
// Block L-pattern possibilities
tester.placeCard(matrix, 1, 3, 2);
tester.placeCard(matrix, 3, 1, 2);
const testCase = tester.createTestCase(
'Basic Z-pattern with intermediate point',
matrix,
1, 1, 3, 3,
true,
'Z-pattern'
);
tester.expectTestCase(testCase);
});
test('should find multiple possible intermediate points', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 5, 5, 1);
// Block L-pattern possibilities
tester.placeCard(matrix, 1, 5, 2);
tester.placeCard(matrix, 5, 1, 2);
const testCase = tester.createTestCase(
'Z-pattern with multiple intermediate points',
matrix,
1, 1, 5, 5,
true,
'Z-pattern'
);
tester.expectTestCase(testCase);
});
test('should work with intermediate point at border', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 2, 2, 1);
tester.placeCard(matrix, 4, 4, 1);
// Block L-pattern possibilities
tester.placeCard(matrix, 2, 4, 2);
tester.placeCard(matrix, 4, 2, 2);
const testCase = tester.createTestCase(
'Z-pattern with border intermediate point',
matrix,
2, 2, 4, 4,
true,
'Z-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Blocked Scenarios', () => {
test('should fail when no intermediate point is available', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 3, 3, 1);
// Block all possible intermediate points
for (let row = 1; row <= 8; row++) {
for (let col = 1; col <= 20; col++) {
if (!((row === 1 && col === 1) || (row === 3 && col === 3))) {
tester.placeCard(matrix, row, col, 2);
}
}
}
const testCase = tester.createTestCase(
'Z-pattern blocked - no intermediate point',
matrix,
1, 1, 3, 3,
false
);
tester.expectTestCase(testCase);
});
test('should fail when paths to intermediate point are blocked', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 3, 3, 1);
// Block paths to potential intermediate points
tester.placeCard(matrix, 1, 2, 2);
tester.placeCard(matrix, 2, 1, 2);
tester.placeCard(matrix, 2, 3, 2);
tester.placeCard(matrix, 3, 2, 2);
const testCase = tester.createTestCase(
'Z-pattern blocked - paths to intermediate blocked',
matrix,
1, 1, 3, 3,
false
);
tester.expectTestCase(testCase);
});
});
describe('Board Edge Cases', () => {
test('should handle Z-pattern at board edges', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 8, 20, 1);
// Block simpler patterns
tester.placeCard(matrix, 1, 20, 2);
tester.placeCard(matrix, 8, 1, 2);
const testCase = tester.createTestCase(
'Z-pattern at board edges',
matrix,
1, 1, 8, 20,
true,
'Z-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle Z-pattern along board boundaries', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 2, 1);
tester.placeCard(matrix, 1, 8, 1);
// Block direct horizontal path
tester.placeCard(matrix, 1, 5, 2);
const testCase = tester.createTestCase(
'Z-pattern along board boundary',
matrix,
1, 2, 1, 8,
true,
'Z-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Complex Intermediate Points', () => {
test('should use specific intermediate point', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 2, 2, 1);
tester.placeCard(matrix, 6, 6, 1);
// Block L-pattern possibilities
tester.placeCard(matrix, 2, 6, 2);
tester.placeCard(matrix, 6, 2, 2);
// Ensure intermediate point at (4,4) is available
const testCase = tester.createTestCase(
'Z-pattern with specific intermediate point',
matrix,
2, 2, 6, 6,
true,
'Z-pattern'
);
tester.expectTestCase(testCase);
});
test('should work in dense board with limited intermediate points', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 4, 4, 1);
// Fill most of the board but leave some Z-pattern paths
for (let row = 1; row <= 8; row++) {
for (let col = 1; col <= 20; col++) {
if (!((row === 1 && col === 1) || (row === 4 && col === 4) ||
(row === 1 && col === 4) || (row === 4 && col === 1) ||
(row === 2 && col === 2) || (row === 3 && col === 3))) {
tester.placeCard(matrix, row, col, 2);
}
}
}
const testCase = tester.createTestCase(
'Z-pattern in dense board',
matrix,
1, 1, 4, 4,
true,
'Z-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Same Row/Column Scenarios', () => {
test('should handle Z-pattern with cards in same row', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 10, 1);
// Block direct horizontal path
tester.placeCard(matrix, 1, 5, 2);
const testCase = tester.createTestCase(
'Z-pattern with cards in same row',
matrix,
1, 1, 1, 10,
true,
'Z-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle Z-pattern with cards in same column', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 6, 1, 1);
// Block direct vertical path
tester.placeCard(matrix, 3, 1, 2);
const testCase = tester.createTestCase(
'Z-pattern with cards in same column',
matrix,
1, 1, 6, 1,
true,
'Z-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Pattern Priority', () => {
test('should prefer I-pattern over Z-pattern when possible', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 1, 5, 1);
const testCase = tester.createTestCase(
'Should prefer I-pattern over Z-pattern',
matrix,
1, 1, 1, 5,
true,
'I-pattern'
);
tester.expectTestCase(testCase);
});
test('should prefer L-pattern over Z-pattern when possible', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 3, 3, 1);
const testCase = tester.createTestCase(
'Should prefer L-pattern over Z-pattern',
matrix,
1, 1, 3, 3,
true,
'L-pattern'
);
tester.expectTestCase(testCase);
});
test('should use Z-pattern when simpler patterns are blocked', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 3, 3, 1);
// Block I-pattern (not applicable)
// Block L-pattern corners
tester.placeCard(matrix, 1, 3, 2);
tester.placeCard(matrix, 3, 1, 2);
const testCase = tester.createTestCase(
'Should use Z-pattern when L-pattern blocked',
matrix,
1, 1, 3, 3,
true,
'Z-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Multiple Turn Scenarios', () => {
test('should handle Z-pattern with two distinct turns', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 5, 7, 1);
// Block L-pattern paths
tester.placeCard(matrix, 1, 7, 2);
tester.placeCard(matrix, 5, 1, 2);
const testCase = tester.createTestCase(
'Z-pattern with two distinct turns',
matrix,
1, 1, 5, 7,
true,
'Z-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle Z-pattern with intermediate point far from both cards', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 1, 1);
tester.placeCard(matrix, 8, 20, 1);
// Block other patterns by placing strategic obstacles
tester.placeCard(matrix, 1, 20, 2);
tester.placeCard(matrix, 8, 1, 2);
const testCase = tester.createTestCase(
'Z-pattern with distant intermediate point',
matrix,
1, 1, 8, 20,
true,
'Z-pattern'
);
tester.expectTestCase(testCase);
});
});
describe('Asymmetric Cases', () => {
test('should handle asymmetric Z-pattern', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 2, 3, 1);
tester.placeCard(matrix, 6, 8, 1);
// Block L-pattern
tester.placeCard(matrix, 2, 8, 2);
tester.placeCard(matrix, 6, 3, 2);
const testCase = tester.createTestCase(
'Asymmetric Z-pattern',
matrix,
2, 3, 6, 8,
true,
'Z-pattern'
);
tester.expectTestCase(testCase);
});
test('should handle Z-pattern with varying distances', () => {
const matrix = tester.createEmptyMatrix();
tester.placeCard(matrix, 1, 5, 1);
tester.placeCard(matrix, 7, 15, 1);
// Block other patterns
tester.placeCard(matrix, 1, 15, 2);
tester.placeCard(matrix, 7, 5, 2);
const testCase = tester.createTestCase(
'Z-pattern with varying distances',
matrix,
1, 5, 7, 15,
true,
'Z-pattern'
);
tester.expectTestCase(testCase);
});
});
});
+50
View File
@@ -0,0 +1,50 @@
/**
* Jest setup file for Pikachu game tests
*/
// Global test configuration
global.TEST_CONFIG = {
verbose: true,
timeout: 10000
};
// Custom matchers for Pikachu game testing
expect.extend({
toBeValidMove(received) {
const pass = received.valid === true;
if (pass) {
return {
message: () => `Expected move to be invalid, but it was valid with pattern: ${received.details?.pattern}`,
pass: true
};
} else {
return {
message: () => `Expected move to be valid, but it was invalid: ${received.error}`,
pass: false
};
}
},
toHavePattern(received, expectedPattern) {
const pass = received.valid && received.details?.pattern === expectedPattern;
if (pass) {
return {
message: () => `Expected pattern to not be ${expectedPattern}, but it was`,
pass: true
};
} else {
return {
message: () => `Expected pattern to be ${expectedPattern}, but got ${received.details?.pattern || 'none'}`,
pass: false
};
}
}
});
// Console styling for better test output
const originalConsoleLog = console.log;
console.log = (...args) => {
// Add timestamp to logs during tests
const timestamp = new Date().toISOString().substr(11, 8);
originalConsoleLog(`[${timestamp}]`, ...args);
};