refactor(lib): split game.js into scoring and username modules

Extract username generation into a dedicated module, simplify game.js to
focus on round scoring logic, and update consumers accordingly.
This commit is contained in:
tiennm99 committed 2026-08-30 20:52:15 +07:00
1 parent 3177e70e67
commit 9133a3efc2
4 files changed
+43 -37

No files matched your search

+14 -1
View File
@@ -3,7 +3,20 @@
import { Medal } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { Skeleton } from '@/components/ui/skeleton';
import { formatDistance, getDistanceColor } from '../../lib/game';
import { formatDistance, calculateScore } from '../../lib/game';
// Keyed by the round score the distance would earn, so the tint tracks the
// scoring ladder instead of re-typing its thresholds.
const DISTANCE_COLORS = {
5: 'text-green-700 dark:text-green-300',
4: 'text-blue-700 dark:text-blue-300',
3: 'text-yellow-700 dark:text-yellow-300',
2: 'text-orange-700 dark:text-orange-300',
};
function getDistanceColor(distance) {
return DISTANCE_COLORS[calculateScore(distance)] ?? 'text-red-700 dark:text-red-300';
}
function getScoreColor(score) {
if (score >= 50) return 'text-purple-700 dark:text-purple-300';
+13 -35
View File
@@ -17,14 +17,21 @@ export function calculateDistance(lat1, lon1, lat2, lon2) {
}
}
// Distance ceiling in meters -> points. The single scoring ladder; anything
// that bands a result (colors, labels, the scoring table on the home page)
// should derive from calculateScore rather than re-typing these thresholds.
export const SCORE_BANDS = [
{ maxMeters: 50, points: 5 },
{ maxMeters: 100, points: 4 },
{ maxMeters: 200, points: 3 },
{ maxMeters: 500, points: 2 },
{ maxMeters: 1000, points: 1 },
];
// Calculate score based on distance (0-5 points scale)
export function calculateScore(distance) {
if (distance <= 50) return 5;
if (distance <= 100) return 4;
if (distance <= 200) return 3;
if (distance <= 500) return 2;
if (distance <= 1000) return 1;
return 0;
const band = SCORE_BANDS.find((entry) => distance <= entry.maxMeters);
return band ? band.points : 0;
}
// Format distance for display
@@ -35,32 +42,3 @@ export function formatDistance(distance) {
return `${(distance / 1000).toFixed(2)}km`;
}
}
// Get or set username from localStorage
export function getUsername() {
if (typeof window === 'undefined') return null;
return localStorage.getItem('vngeoguessr_username');
}
export function setUsername(username) {
if (typeof window === 'undefined') return;
localStorage.setItem('vngeoguessr_username', username);
}
// Get result message based on score
export function getResultMessage(score, distance) {
if (score > 4) return "Excellent! Outstanding guess!";
if (score > 2) return "Good job! Nice work!";
if (score > 0) return "Not bad! Keep trying!";
return "Nice try! Better luck next time!";
}
// Get distance color for leaderboard display
export function getDistanceColor(distance) {
if (distance <= 50) return 'text-green-700 dark:text-green-300';
if (distance <= 100) return 'text-blue-700 dark:text-blue-300';
if (distance <= 200) return 'text-yellow-700 dark:text-yellow-300';
if (distance <= 500) return 'text-orange-700 dark:text-orange-300';
return 'text-red-700 dark:text-red-300';
}
+1 -1
View File
@@ -6,7 +6,7 @@
// replaces asking the Graph API what is near a random point, which fails
// outright in dense districts and takes seconds where it works.
//
// SERVER-SIDE ONLY. These files are ~25MB of exact answers; importing them from
// SERVER-SIDE ONLY. These files are ~28MB of exact answers; importing them from
// anywhere the browser can reach hands every round's solution to the player.
// src/lib/regions.js is the client-safe view of the same tree and deliberately
// imports nothing from here -- tests/regions.test.js enforces that boundary.
+15
View File
@@ -0,0 +1,15 @@
// Player name in localStorage, mirroring theme.js: one module owns one
// browser-storage concern, so server code importing lib/game.js for scoring
// never touches window.
export const USERNAME_STORAGE_KEY = 'vngeoguessr_username';
export function getUsername() {
if (typeof window === 'undefined') return null;
return localStorage.getItem(USERNAME_STORAGE_KEY);
}
export function setUsername(username) {
if (typeof window === 'undefined') return;
localStorage.setItem(USERNAME_STORAGE_KEY, username);
}