Minor bugs resolution - improved compatibility with mobile.
This commit is contained in:
parent
1ffeeab8c8
commit
2576ffe4f7
7 changed files with 68 additions and 23 deletions
|
|
@ -104,6 +104,11 @@ const server = http.createServer(async (req, res) => {
|
|||
}
|
||||
|
||||
// GET /api/played
|
||||
// NOTE: the endpoint simply remembers a hashed IP for the day. this is
|
||||
// trivially circumventable by using a different IP/proxy or by clearing
|
||||
// cookies. no server‑side score validation is performed either; the leaderboard
|
||||
// is for fun only. if this service ever becomes competitive it should be
|
||||
// hardened (rate‑limit, score sanity checks, CAPTCHAs, etc.).
|
||||
if (path === '/api/played' && req.method === 'GET') {
|
||||
const date = todayUTC();
|
||||
const ipHash = hashIP(getIP(req));
|
||||
|
|
|
|||
|
|
@ -12802,7 +12802,7 @@
|
|||
{
|
||||
"Rang": 158,
|
||||
"Pays": "Nagorno-Karabakh",
|
||||
"Code ISO2": "N\/A",
|
||||
"Code ISO2": "",
|
||||
"Indice GAI": 2.54
|
||||
},
|
||||
{
|
||||
|
|
@ -12886,7 +12886,7 @@
|
|||
{
|
||||
"Rang": 172,
|
||||
"Pays": "Somaliland",
|
||||
"Code ISO2": "N\/A",
|
||||
"Code ISO2": "",
|
||||
"Indice GAI": 1.59
|
||||
},
|
||||
{
|
||||
|
|
|
|||
26
js/daily.js
26
js/daily.js
|
|
@ -149,23 +149,15 @@ function _shuffle(arr, rng) {
|
|||
// ── Fin de partie Daily : intercepte showEndPanel ─────────────────────────────
|
||||
var _origShowEndPanel = null;
|
||||
|
||||
function _dailyShowEndPanel() {
|
||||
// Appeler l'original
|
||||
_origShowEndPanel();
|
||||
|
||||
if (!isDailyMode) return;
|
||||
|
||||
// Cacher la combinaison optimale et la seed (anti-triche daily)
|
||||
var optBlock = document.getElementById('opt-block');
|
||||
if (optBlock) optBlock.style.display = 'none';
|
||||
var seedBox = document.getElementById('seed-display');
|
||||
if (seedBox) seedBox.style.display = 'none';
|
||||
var copyBtns = document.querySelector('[id="btn-copy-seed"]');
|
||||
if (copyBtns) copyBtns.parentElement.style.display = 'none';
|
||||
|
||||
// Sauvegarder le score et ouvrir le modal submit
|
||||
dailyScore = totalScore;
|
||||
setTimeout(function() { openSubmitModal(); }, 600);
|
||||
function skipDailySubmit() {
|
||||
var overlay = document.getElementById('daily-submit-overlay');
|
||||
if (overlay) overlay.style.display = 'none';
|
||||
_setDailyPlayed();
|
||||
// leave isDailyMode true until after the leaderboard opens, just in case
|
||||
setTimeout(function() {
|
||||
isDailyMode = false;
|
||||
openLeaderboard();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// ── Modal submit ──────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -227,13 +227,25 @@ function animateCounter(el, from, to, duration, onStep) {
|
|||
|
||||
function showEndPanel() {
|
||||
document.getElementById('panel-end').classList.remove('hidden');
|
||||
// Recompute from gameLog to be bulletproof against any NaN accumulation
|
||||
// Recompute from gameLog to be bulletproof against any NaN accumulation.
|
||||
//
|
||||
// WARNING / INVARIANT: entries logged for hint penalties carry a positive
|
||||
// `points` value and are added to totalScore during play. we therefore
|
||||
// _also_ include them in this recomputation; the computed score must match
|
||||
// the running total exactly. if the hint logic ever changes (e.g. storing
|
||||
// negative penalties, separating the entries, or removing the recompute
|
||||
// entirely) this loop needs to be updated accordingly. leaving the code as
|
||||
// it is protects us from the classic "score doubled after refactor" bug.
|
||||
var computedScore = 0;
|
||||
gameLog.forEach(function(e) {
|
||||
var v = Number(e.points);
|
||||
if (!isNaN(v)) computedScore += v;
|
||||
});
|
||||
if (computedScore !== totalScore) {
|
||||
console.warn('score mismatch in showEndPanel', computedScore, totalScore);
|
||||
}
|
||||
var safeScore = computedScore;
|
||||
// overwrite global in case some consumer reads it later (daily, replay, ...)
|
||||
totalScore = safeScore;
|
||||
document.getElementById('final-score').textContent = safeScore;
|
||||
var tl = t('taglines');
|
||||
|
|
|
|||
31
js/game.js
31
js/game.js
|
|
@ -574,6 +574,12 @@ function showTurn() {
|
|||
|
||||
// ─── REVERSE MODE TURN ────────────────────────────────────────────────────────
|
||||
function showTurnReverse() {
|
||||
// guard against malformed seed: we must check against the *categories*
|
||||
// array rather than gameCountries (which is what showTurn checks). the
|
||||
// two lengths normally match, but diverging lengths used to allow the game
|
||||
// to continue one turn too many when there was a generation bug.
|
||||
if (currentStep >= gameCategories.length) { endGame(); return; }
|
||||
|
||||
// make sure any open cat-tooltip from normal mode is shut, removing ghost flashes
|
||||
hideTooltip();
|
||||
var cat = gameCategories[currentStep];
|
||||
|
|
@ -699,7 +705,9 @@ function handleCountryChoice(countryIdx) {
|
|||
function autoPick_reverse() {
|
||||
var firstFree = -1;
|
||||
for (var i = 0; i < gameCountries.length; i++) {
|
||||
if (!reverseAssignments[i]) { firstFree = i; break; }
|
||||
// only treat a slot as free if it hasn't been assigned at all; falsy
|
||||
// category ids (0, "", etc.) should not trigger the fallback.
|
||||
if (reverseAssignments[i] === undefined) { firstFree = i; break; }
|
||||
}
|
||||
if (firstFree === -1) { currentStep++; showTurn(); return; }
|
||||
|
||||
|
|
@ -783,19 +791,35 @@ function startTimer() {
|
|||
}
|
||||
|
||||
function updateTimerUI(remaining) {
|
||||
// TIME_PER_TURN may be 0 when the timer is "infinite"; bail out early
|
||||
// to avoid dividing by zero or producing NaN widths. startTimer already
|
||||
// handles this case, but other callers (tests, manual tweaks) might
|
||||
// invoke the helper directly so we guard defensively here.
|
||||
if (!TIME_PER_TURN) return;
|
||||
|
||||
var numEl = document.getElementById('timer-num');
|
||||
var fillEl = document.getElementById('progress-fill');
|
||||
|
||||
var pct = (remaining !== undefined)
|
||||
? (remaining / TIME_PER_TURN * 100)
|
||||
: 100;
|
||||
var displayNum = (remaining !== undefined) ? Math.ceil(remaining) : TIME_PER_TURN;
|
||||
numEl.textContent = displayNum;
|
||||
fillEl.style.width = pct + '%';
|
||||
var urgent = displayNum <= (isHardcoreActive() ? 4 : 8);
|
||||
|
||||
// urgency threshold is now relative to the total turn length instead of a
|
||||
// hard‑coded 4/8 seconds. this makes custom timers (30s, 60s, …) feel
|
||||
// sensible: the bar turns amber when ~25% time remains, regardless of mode.
|
||||
var ratio = (remaining !== undefined) ? (remaining / TIME_PER_TURN) : 1;
|
||||
var urgent = ratio < 0.25;
|
||||
numEl.classList.toggle('urgent', urgent);
|
||||
fillEl.classList.toggle('urgent', urgent);
|
||||
|
||||
// In hardcore mode we also add a red "danger" state; keep it tied to the
|
||||
// same relative threshold so that longer custom hardcore games still go
|
||||
// red when only a small fraction of turns remains.
|
||||
if (isHardcoreActive()) {
|
||||
numEl.classList.toggle('danger', displayNum <= 4);
|
||||
numEl.classList.toggle('danger', ratio < 0.25);
|
||||
} else {
|
||||
numEl.classList.remove('danger');
|
||||
}
|
||||
|
|
@ -810,6 +834,7 @@ function autoPick() {
|
|||
disableAllCatButtons();
|
||||
var PENALTY = 200;
|
||||
totalScore += PENALTY;
|
||||
document.getElementById('total-score').textContent = totalScore; // keep UI in sync immediately
|
||||
|
||||
var country = gameCountries[currentStep];
|
||||
var cName = getCountryName(country);
|
||||
|
|
|
|||
|
|
@ -90,12 +90,18 @@ function generateSeed() {
|
|||
function applyGameFromSeed(str) {
|
||||
var decoded = decodeSeed(str);
|
||||
if (!decoded) { alert(t('invalidSeed')); return false; }
|
||||
|
||||
// extract indices early so we can validate before touching any global state
|
||||
var ci = decoded.countryIndices, ki = decoded.catIndices;
|
||||
if (ci.some(function(i){return i<0||i>=countriesDB.length;})) { alert(t('invalidSeedIdx')); return false; }
|
||||
if (ki.some(function(i){return i<0||i>=ALL_CATEGORIES.length;})) { alert(t('invalidSeedIdx')); return false; }
|
||||
|
||||
// At this point the seed is syntactically correct *and* the indices refer to
|
||||
// existing entries; safe to mutate globals.
|
||||
gameCountries = ci.map(function(i){return countriesDB[i];});
|
||||
gameCategories = ki.map(function(i){return ALL_CATEGORIES[i];});
|
||||
currentSeed = str;
|
||||
|
||||
if (!decoded.legacy) {
|
||||
var m = decoded.mode;
|
||||
document.body.classList.remove('hardcore','reverse-mode');
|
||||
|
|
|
|||
|
|
@ -70,6 +70,11 @@ function encodeSeed(countryIdxs, catIdxs, mode, timeSecs) {
|
|||
}
|
||||
|
||||
function decodeSeed(str) {
|
||||
// Remember: this helper only converts the opaque base62 string back into
|
||||
// numeric fields. it does **not** know anything about the current
|
||||
// countriesDB/ALL_CATEGORIES arrays, so it cannot verify that the indices it
|
||||
// returns are in-bounds. callers (e.g. applyGameFromSeed) must perform a
|
||||
// separate sanity check before using the results.
|
||||
if (!str || typeof str !== 'string') return null;
|
||||
str = str.trim();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue