-
Notifications
You must be signed in to change notification settings - Fork 64
Score decay feature #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,4 +39,8 @@ instance/ | |
| # Virtual Environment | ||
| venv/ | ||
| env/ | ||
| ENV/ | ||
| ENV/ | ||
| .windsurf/rules/do-not-make-new-test-files.md | ||
| .windsurf/workflows/test-new-changes.md | ||
| .gitignore | ||
| .windsurf/rules/minimal-rules.md | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| import { BASE_DECAY_RATE, DECAY_SCALE_FACTOR, MIN_SPLIT_SCORE } from '../config.js'; | ||
| import { getSize } from '../utils.js'; | ||
|
|
||
| describe('Score Decay', () => { | ||
| // Test the score decay calculation directly | ||
| test('score decays correctly based on size and time', () => { | ||
| // Initial score and time values | ||
| const initialScore = 100; | ||
| const deltaTime = 1; // 1 second | ||
|
|
||
| // Calculate size based on score | ||
| const size = getSize(initialScore); | ||
|
|
||
| // Calculate decay rate (replicating the logic from applyScoreDecay) | ||
| const decayRate = BASE_DECAY_RATE * (1 + size * DECAY_SCALE_FACTOR); | ||
|
|
||
| // Calculate expected decay amount | ||
| const decay = decayRate * deltaTime; | ||
|
|
||
| // Calculate expected score after decay | ||
| const expectedScore = Math.max(MIN_SPLIT_SCORE / 2, initialScore - decay); | ||
|
|
||
| // Verify decay amount is positive | ||
| expect(decay).toBeGreaterThan(0); | ||
|
|
||
| // Verify score decreases by the expected amount | ||
| expect(expectedScore).toBeLessThan(initialScore); | ||
| expect(initialScore - expectedScore).toBeCloseTo(decay, 5); | ||
| }); | ||
|
|
||
| test('larger entities decay faster than smaller ones', () => { | ||
| // Test with two different sized entities | ||
| const smallEntityScore = 50; | ||
| const largeEntityScore = 500; | ||
| const deltaTime = 1; // 1 second | ||
|
|
||
| // Calculate sizes | ||
| const smallSize = getSize(smallEntityScore); | ||
| const largeSize = getSize(largeEntityScore); | ||
|
|
||
| // Calculate decay rates | ||
| const smallDecayRate = BASE_DECAY_RATE * (1 + smallSize * DECAY_SCALE_FACTOR); | ||
| const largeDecayRate = BASE_DECAY_RATE * (1 + largeSize * DECAY_SCALE_FACTOR); | ||
|
|
||
| // Calculate decay amounts | ||
| const smallDecay = smallDecayRate * deltaTime; | ||
| const largeDecay = largeDecayRate * deltaTime; | ||
|
|
||
| // Verify larger entities decay faster | ||
| expect(largeDecayRate).toBeGreaterThan(smallDecayRate); | ||
| expect(largeDecay).toBeGreaterThan(smallDecay); | ||
| }); | ||
|
|
||
| test('score never decays below minimum threshold', () => { | ||
| // Test with a score close to the minimum threshold | ||
| const lowScore = MIN_SPLIT_SCORE / 2 + 0.1; // Just above minimum | ||
| const deltaTime = 10; // Long time period to ensure decay would go below minimum | ||
|
|
||
| // Calculate size and decay | ||
| const size = getSize(lowScore); | ||
| const decayRate = BASE_DECAY_RATE * (1 + size * DECAY_SCALE_FACTOR); | ||
| const decay = decayRate * deltaTime; | ||
|
|
||
| // Calculate expected score after decay | ||
| const expectedScore = Math.max(MIN_SPLIT_SCORE / 2, lowScore - decay); | ||
|
|
||
| // Verify the decay would have gone below minimum without the limit | ||
| expect(lowScore - decay).toBeLessThan(MIN_SPLIT_SCORE / 2); | ||
|
|
||
| // Verify score is clamped to minimum | ||
| expect(expectedScore).toEqual(MIN_SPLIT_SCORE / 2); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,9 @@ import { | |
| MERGE_COOLDOWN, | ||
| MERGE_DISTANCE, | ||
| MERGE_FORCE, | ||
| MERGE_START_FORCE | ||
| MERGE_START_FORCE, | ||
| BASE_DECAY_RATE, | ||
| DECAY_SCALE_FACTOR | ||
| } from './config.js'; | ||
|
|
||
| const AI_NAMES = [ | ||
|
|
@@ -164,7 +166,13 @@ function updateCellMerging() { | |
| } | ||
| } | ||
|
|
||
| let lastUpdateTime = Date.now(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
|
||
| export function updatePlayer() { | ||
| const currentTime = Date.now(); | ||
| const deltaTime = (currentTime - lastUpdateTime) / 1000; // Convert to seconds | ||
| lastUpdateTime = currentTime; | ||
|
|
||
| const dx = mouse.x - window.innerWidth / 2; | ||
| const dy = mouse.y - window.innerHeight / 2; | ||
| const distance = Math.sqrt(dx * dx + dy * dy); | ||
|
|
@@ -187,6 +195,9 @@ export function updatePlayer() { | |
| // Update position | ||
| cell.x = Math.max(0, Math.min(WORLD_SIZE, cell.x + cell.velocityX)); | ||
| cell.y = Math.max(0, Math.min(WORLD_SIZE, cell.y + cell.velocityY)); | ||
|
|
||
| // Apply score decay | ||
| applyScoreDecay(cell, deltaTime); | ||
| }); | ||
| } | ||
|
|
||
|
|
@@ -244,7 +255,20 @@ export function handlePlayerSplit() { | |
| cellsToSplit.forEach(cell => splitPlayerCell(cell)); | ||
| } | ||
|
|
||
| function applyScoreDecay(entity, deltaTime) { | ||
| // Calculate decay based on size (larger entities decay faster) | ||
| const size = getSize(entity.score); | ||
| const decayRate = BASE_DECAY_RATE * (1 + size * DECAY_SCALE_FACTOR); | ||
|
|
||
| // Apply decay based on time elapsed | ||
| const decay = decayRate * deltaTime; | ||
| entity.score = Math.max(MIN_SPLIT_SCORE / 2, entity.score - decay); | ||
| } | ||
|
|
||
| export function updateAI() { | ||
| const currentTime = Date.now(); | ||
| const deltaTime = (currentTime - lastUpdateTime) / 1000; // Convert to seconds | ||
|
Comment on lines
+269
to
+270
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's an issue with the
Comment on lines
+269
to
+270
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
|
||
| gameState.aiPlayers.forEach(ai => { | ||
| if (Math.random() < 0.02) { | ||
| ai.direction = Math.random() * Math.PI * 2; | ||
|
|
@@ -256,6 +280,9 @@ export function updateAI() { | |
|
|
||
| ai.x = Math.max(0, Math.min(WORLD_SIZE, ai.x)); | ||
| ai.y = Math.max(0, Math.min(WORLD_SIZE, ai.y)); | ||
|
|
||
| // Apply score decay | ||
| applyScoreDecay(ai, deltaTime); | ||
| }); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,20 @@ | ||
| import { gameState } from './gameState.js'; | ||
| import { getSize, calculateCenterOfMass } from './utils.js'; | ||
| import { WORLD_SIZE, COLORS, FOOD_SIZE } from './config.js'; | ||
| import { WORLD_SIZE, COLORS, FOOD_SIZE, MIN_SPLIT_SCORE } from './config.js'; | ||
|
|
||
| // Helper function to convert hex color to RGB | ||
| function hexToRgb(hex) { | ||
| // Remove the hash if present | ||
| hex = hex.replace(/^#/, ''); | ||
|
|
||
| // Parse the hex values | ||
| const bigint = parseInt(hex, 16); | ||
| const r = (bigint >> 16) & 255; | ||
| const g = (bigint >> 8) & 255; | ||
| const b = bigint & 255; | ||
|
|
||
| return `${r}, ${g}, ${b}`; | ||
| } | ||
|
|
||
| let canvas, ctx, minimapCanvas, minimapCtx, scoreElement, leaderboardContent; | ||
|
|
||
|
|
@@ -32,10 +46,19 @@ function drawCircle(x, y, value, color, isFood) { | |
| function drawCellWithName(x, y, score, color, name) { | ||
| const size = getSize(score); | ||
|
|
||
| // Draw cell | ||
| // Draw cell with decay effect | ||
| ctx.beginPath(); | ||
| ctx.arc(x, y, size, 0, Math.PI * 2); | ||
| ctx.fillStyle = color; | ||
|
|
||
| // Add pulsing effect when score is decaying | ||
| if (score < MIN_SPLIT_SCORE) { | ||
| const pulseIntensity = 0.2 * Math.sin(Date.now() / 200); // Pulsing every 200ms | ||
| const alpha = Math.max(0.6, 1 - pulseIntensity); | ||
| ctx.fillStyle = color.startsWith('rgba') ? color : `rgba(${hexToRgb(color)}, ${alpha})`; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no validation before converting colors with hexToRgb. If a color is already in rgba format or is invalid, this could cause errors. Consider adding a check to verify the color is a valid hex format before conversion. |
||
| } else { | ||
| ctx.fillStyle = color; | ||
| } | ||
|
|
||
| ctx.fill(); | ||
|
|
||
| // Draw name | ||
|
|
@@ -105,8 +128,12 @@ export function drawGame() { | |
| } | ||
| }); | ||
|
|
||
| // Update score display | ||
| scoreElement.textContent = `Score: ${Math.floor(gameState.playerCells.reduce((sum, cell) => sum + cell.score, 0))}`; | ||
| // Update score display with 1 decimal place when below split threshold | ||
| const totalScore = gameState.playerCells.reduce((sum, cell) => sum + cell.score, 0); | ||
| const formattedScore = totalScore < MIN_SPLIT_SCORE * 2 ? | ||
| totalScore.toFixed(1) : | ||
| Math.floor(totalScore); | ||
| scoreElement.textContent = `Score: ${formattedScore}`; | ||
| } | ||
|
|
||
| export function drawMinimap() { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You've added
.gitignoreto the.gitignorefile on line 46. This will cause git to ignore the.gitignorefile itself, which means future changes to this file won't be tracked. This is generally not recommended as it would make it difficult to maintain consistent ignore rules across the repository.