feat: add score calculation with speed bonus and combo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-07 20:59:46 +07:00
co-authored by Claude Sonnet 4.6
parent 4c29f3838c
commit be033f93c4
2 changed files with 45 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
import { BASE_MATCH_SCORE, SPEED_BONUS_MAX, SPEED_BONUS_WINDOW_MS } from "./constants";
export function calculateMatchScore(
msSinceLastMatch: number,
combo: number
): number {
let speedBonus = 0;
if (msSinceLastMatch < SPEED_BONUS_WINDOW_MS) {
const ratio = 1 - msSinceLastMatch / SPEED_BONUS_WINDOW_MS;
speedBonus = Math.round(SPEED_BONUS_MAX * ratio);
}
return (BASE_MATCH_SCORE + speedBonus) * combo;
}
+31
View File
@@ -0,0 +1,31 @@
import { describe, it, expect } from "vitest";
import { calculateMatchScore } from "../../src/game/scoring";
describe("calculateMatchScore", () => {
it("returns base score with no speed bonus and combo 1", () => {
const score = calculateMatchScore(10000, 1);
expect(score).toBe(100);
});
it("adds speed bonus for fast match", () => {
const score = calculateMatchScore(1000, 1);
expect(score).toBeGreaterThan(100);
expect(score).toBeLessThanOrEqual(150);
});
it("applies combo multiplier", () => {
const base = calculateMatchScore(10000, 1);
const combo3 = calculateMatchScore(10000, 3);
expect(combo3).toBe(base * 3);
});
it("combines speed bonus and combo", () => {
const score = calculateMatchScore(0, 2);
expect(score).toBe((100 + 50) * 2);
});
it("no speed bonus after window expires", () => {
const score = calculateMatchScore(6000, 1);
expect(score).toBe(100);
});
});