mirror of
https://github.com/tiennm99/pikachu.git
synced 2026-08-21 08:26:48 +00:00
test: init tests
This commit is contained in:
@@ -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)
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
```
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
};
|
||||
Reference in New Issue
Block a user