Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .windsurf/rules/code-style-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
trigger: model_decision
---

Indentation: Use 4 spaces per indentation level. Avoid tabs.

Line Length: Limit lines to a maximum of 79 characters. For docstrings and comments, limit to 72 characters.

Blank Lines: Separate top-level function and class definitions with two blank lines. Use a single blank line between method definitions inside a class and to separate logical sections within functions.

Naming Conventions:
Use lowercase_with_underscores for functions and variables.
Use CamelCase for classes.
Use ALL_CAPS for constants.
Prefix internal attributes with a single underscore (_).

Comments: Write clear, concise comments to explain code functionality. Update comments when code changes.

Spaces: Use spaces around operators and after commas.
Trailing Commas: Use trailing commas in lists, tuples, and dictionaries.

Avoid Extraneous Whitespace: Do not add unnecessary spaces at the beginning or end of lines.

Imports: Group imports in the following order: standard library imports, related third-party imports, local application/library specific imports. Put a blank line between each group of imports.

Avoid shadowing names: Do not use names of built-in functions, constants, types, and exceptions.
4 changes: 4 additions & 0 deletions .windsurf/workflows/debug-issues-generic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
description:
---

106 changes: 106 additions & 0 deletions .windsurf/workflows/pr-analysis-remediation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
description: Perform PR analysis, remediate errors, and push changes using GitHub CLI
---

# PR Analysis and Remediation Workflow

This workflow helps you analyze a pull request, identify and fix issues, and push the remediated changes back to the repository.

## Prerequisites
- GitHub CLI (`gh`) installed and authenticated
- Repository cloned locally
- Appropriate permissions to push to the branch

## Steps

1. **Fetch the latest PR information**
```bash
gh pr list --state open
```

2. **Checkout the PR branch**
```bash
gh pr checkout <PR_NUMBER>
```

3. **Analyze PR changes and get diff**
```bash
gh pr diff <PR_NUMBER>
```

4. **Check PR status and reviews**
```bash
gh pr view <PR_NUMBER> --json reviews,statusCheckRollup
```

5. **Run automated checks locally**
// turbo
```bash
npm test
```

6. **Run linting and formatting**
// turbo
```bash
npm run lint
```

7. **Fix any linting issues automatically**
// turbo
```bash
npm run lint:fix
```

8. **Run type checking (if applicable)**
// turbo
```bash
npm run type-check
```

9. **Stage and commit any fixes**
```bash
git add .
git commit -m "fix: remediate PR analysis findings"
```

10. **Push the remediated changes**
```bash
git push origin HEAD
```

11. **Add a comment to the PR about the remediation**
```bash
gh pr comment <PR_NUMBER> --body "🔧 Automated remediation applied:
- Fixed linting issues
- Resolved test failures
- Applied code formatting
Ready for re-review!"
```

12. **Request re-review if needed**
```bash
gh pr review <PR_NUMBER> --request-changes --body "Please review the automated fixes applied"
```

## Optional: Advanced Analysis

13. **Check for security vulnerabilities**
```bash
npm audit
```

14. **Run performance tests**
```bash
npm run test:performance
```

15. **Generate coverage report**
```bash
npm run test:coverage
```

## Notes
- Replace `<PR_NUMBER>` with the actual PR number
- Ensure you have the necessary permissions before pushing changes
- Review automated fixes before committing to avoid introducing new issues
- Consider running the full test suite after remediation
112 changes: 112 additions & 0 deletions static/js/__tests__/ai-movement.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { updateAI } from '../entities.js';
import { gameState } from '../gameState.js';
import { WORLD_SIZE } from '../config.js';

// Mock gameState
jest.mock('../gameState.js', () => ({
gameState: {
aiPlayers: []
}
}));

describe('updateAI', () => {
beforeEach(() => {
// Reset gameState before each test
gameState.aiPlayers = [];

// Mock Math.random to make tests deterministic
jest.spyOn(Math, 'random').mockImplementation(() => 0.5);
});

afterEach(() => {
// Restore Math.random
jest.restoreAllMocks();
});

test('updates AI position based on direction', () => {
const ai = {
x: 100,
y: 100,
score: 100,
direction: Math.PI / 4 // 45 degrees
};

gameState.aiPlayers = [ai];

updateAI();

// AI should have moved in the direction of its angle
expect(gameState.aiPlayers[0].x).toBeGreaterThan(100);
expect(gameState.aiPlayers[0].y).toBeGreaterThan(100);
});

test('AI changes direction randomly', () => {
const ai = {
x: 100,
y: 100,
score: 100,
direction: 0
};

gameState.aiPlayers = [ai];

// Mock Math.random to return a value that will trigger direction change
jest.spyOn(Math, 'random')
.mockImplementationOnce(() => 0.01) // Less than 0.02 to trigger direction change
.mockImplementationOnce(() => 0.5); // For the new direction calculation

updateAI();

// Direction should have changed
expect(gameState.aiPlayers[0].direction).toBeCloseTo(Math.PI);
});

test('AI speed is inversely proportional to size', () => {
const smallAI = {
x: 100,
y: 100,
score: 100,
direction: 0 // Moving right
};

const largeAI = {
x: 100,
y: 100,
score: 400,
direction: 0 // Moving right
};

// Test small AI first
gameState.aiPlayers = [smallAI];
updateAI();
const smallAISpeed = gameState.aiPlayers[0].x - 100;

// Test large AI
gameState.aiPlayers = [largeAI];
updateAI();
const largeAISpeed = gameState.aiPlayers[0].x - 100;

// Small AI should move faster than large AI
expect(smallAISpeed).toBeGreaterThan(largeAISpeed);
});

test('AI stays within world boundaries', () => {
// Test AI at edge of world
const edgeAI = {
x: WORLD_SIZE - 1,
y: WORLD_SIZE - 1,
score: 100,
direction: Math.PI / 4 // 45 degrees, moving toward edge
};

gameState.aiPlayers = [edgeAI];

updateAI();

// AI should be constrained to world boundaries
expect(gameState.aiPlayers[0].x).toBeLessThanOrEqual(WORLD_SIZE);
expect(gameState.aiPlayers[0].y).toBeLessThanOrEqual(WORLD_SIZE);
expect(gameState.aiPlayers[0].x).toBeGreaterThanOrEqual(0);
expect(gameState.aiPlayers[0].y).toBeGreaterThanOrEqual(0);
});
});
129 changes: 129 additions & 0 deletions static/js/__tests__/cell-merging.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { updateCellMerging } from '../entities.js';
import { gameState } from '../gameState.js';
import { MERGE_COOLDOWN } from '../config.js';

// Mock gameState
jest.mock('../gameState.js', () => ({
gameState: {
playerCells: []
}
}));

describe('updateCellMerging', () => {
beforeEach(() => {
// Reset gameState before each test
gameState.playerCells = [];
// Mock Date.now to control time
jest.spyOn(Date, 'now').mockImplementation(() => 1000);
});

afterEach(() => {
// Restore Date.now
jest.restoreAllMocks();
});

test('cells do not merge when cooldown has not passed', () => {
// Create two cells that are close enough to merge but with recent split time
const cell1 = {
x: 100, y: 100, score: 100,
velocityX: 0, velocityY: 0,
splitTime: Date.now() - (MERGE_COOLDOWN / 2)
};
const cell2 = {
x: 101, y: 101, score: 100,
velocityX: 0, velocityY: 0,
splitTime: Date.now() - (MERGE_COOLDOWN / 2)
};

gameState.playerCells = [cell1, cell2];

updateCellMerging();

// Cells should not merge, still have two cells
expect(gameState.playerCells.length).toBe(2);
});

test('cells merge when cooldown has passed and they are close enough', () => {
// Mock Date.now to return a time after the cooldown
jest.spyOn(Date, 'now').mockImplementation(() => MERGE_COOLDOWN + 2000);

// Create two cells that are close enough to merge with old split time
const cell1 = {
x: 100, y: 100, score: 100,
velocityX: 0, velocityY: 0,
splitTime: 0 // Old split time
};
const cell2 = {
x: 101, y: 101, score: 100,
velocityX: 0, velocityY: 0,
splitTime: 0 // Old split time
};

gameState.playerCells = [cell1, cell2];

updateCellMerging();

// Cells should merge into one
expect(gameState.playerCells.length).toBe(1);
// The merged cell should have the combined score
expect(gameState.playerCells[0].score).toBe(200);
});

test('cells attract each other when close but not close enough to merge', () => {
// Mock Date.now to return a time after the cooldown
jest.spyOn(Date, 'now').mockImplementation(() => MERGE_COOLDOWN + 2000);

// Create two cells that are not close enough to merge immediately
const cell1 = {
x: 100, y: 100, score: 100,
velocityX: 0, velocityY: 0,
splitTime: 0 // Old split time
};
const cell2 = {
x: 200, y: 200, score: 100, // Increased distance to ensure no merging
velocityX: 0, velocityY: 0,
splitTime: 0 // Old split time
};

gameState.playerCells = [cell1, cell2];

updateCellMerging();

// Cells should not merge yet, still have two cells
expect(gameState.playerCells.length).toBe(2);
// But they should have velocities towards each other
expect(gameState.playerCells[0].velocityX).toBeGreaterThan(0);
expect(gameState.playerCells[0].velocityY).toBeGreaterThan(0);
expect(gameState.playerCells[1].velocityX).toBeLessThan(0);
expect(gameState.playerCells[1].velocityY).toBeLessThan(0);
});

test('cells repel each other when too close', () => {
// Create two cells that are too close and should repel
const cell1 = {
x: 100, y: 100, score: 400, // Larger size
velocityX: 0, velocityY: 0,
splitTime: 0
};
const cell2 = {
x: 100.1, y: 100.1, score: 400, // Larger size
velocityX: 0, velocityY: 0,
splitTime: 0
};

gameState.playerCells = [cell1, cell2];

// Mock Date.now to return a time before the cooldown
jest.spyOn(Date, 'now').mockImplementation(() => 1000);

updateCellMerging();

// Cells should not merge, still have two cells
expect(gameState.playerCells.length).toBe(2);
// They should have velocities pushing away from each other
expect(gameState.playerCells[0].velocityX).toBeLessThan(0);
expect(gameState.playerCells[0].velocityY).toBeLessThan(0);
expect(gameState.playerCells[1].velocityX).toBeGreaterThan(0);
expect(gameState.playerCells[1].velocityY).toBeGreaterThan(0);
});
});
Loading