2PlayerGame/detective-game/casegen/rng.js

77 lines
4.2 KiB
JavaScript
Raw Permalink Normal View History

// casegen/rng.js — детермінована випадковість, шифри, паролі
const crypto = require('crypto');
const N = require('./names');
// ---------- RNG ----------
function hashSeed(str) {
let h = 2166136261 >>> 0;
for (const c of String(str)) { h ^= c.codePointAt(0); h = Math.imul(h, 16777619); }
return h >>> 0;
}
function mulberry32(a) {
return function () {
a |= 0; a = (a + 0x6D2B79F5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
const ri = (r, a, b) => a + Math.floor(r() * (b - a + 1));
const estRange = (r, actual, lo = 3, hi = 5) => `${actual - ri(r, lo, hi)}${actual + ri(r, lo, hi)}`;
const pick = (r, arr) => arr[Math.floor(r() * arr.length)];
const chance = (r, p) => r() < p;
function shuffle(r, arr) {
const a = arr.slice();
for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(r() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; }
return a;
}
// ---------- Шифри ----------
const UKR = 'абвгґдеєжзиіїйклмнопрстуфхцчшщьюя';
function caesar(text, k) {
return [...text].map(ch => {
const lower = ch.toLowerCase(), i = UKR.indexOf(lower);
if (i < 0) return ch;
const out = UKR[(i + k + UKR.length) % UKR.length];
return ch === lower ? out : out.toUpperCase();
}).join('');
}
const b64 = t => Buffer.from(t, 'utf8').toString('base64');
const md5 = t => crypto.createHash('md5').update(t, 'utf8').digest('hex');
// ---------- Паролі ----------
const TRANSLIT = { а:'a',б:'b',в:'v',г:'h',ґ:'g',д:'d',е:'e',є:'ie',ж:'zh',з:'z',и:'y',і:'i',ї:'i',й:'i',к:'k',л:'l',м:'m',н:'n',о:'o',п:'p',р:'r',с:'s',т:'t',у:'u',ф:'f',х:'kh',ц:'ts',ч:'ch',ш:'sh',щ:'shch',ь:'',ю:'iu',я:'ia' };
const translit = s => [...String(s).toLowerCase()].map(c => TRANSLIT[c] ?? c).join('').replace(/[^a-z0-9]/g, '');
const COMMON_PASSWORDS = ['sunshine', 'dragon', 'monkey', 'princess', 'football', 'iloveyou',
'superman', 'batman', 'master', 'hunter', 'freedom', 'trustno1', 'whatever', 'ginger',
'flower', 'summer', 'winter', 'orange', 'purple', 'silver', 'letmein', 'qwerty', 'pepper',
'cookie', 'tigger', 'jasmine', 'soccer', 'maggie', 'hannah', 'ashley', 'chelsea', 'butterfly',
'password', 'shadow', 'michael', 'jessica', 'charlie', 'daniel', 'jordan', 'matrix', 'falcon',
'phoenix', 'rainbow', 'diamond', 'banana', 'cheese', 'coffee', 'chocolate', 'midnight',
'october', 'november', 'chester', 'oliver', 'ranger', 'harley', 'buster', 'george', 'sammy',
'pumpkin', 'muffin', 'peanut', 'smokey', 'bandit', 'lucky', 'angel', 'lovely', 'sunflower',
'babygirl', 'bubbles', 'snickers', 'chicken', 'turtle', 'rabbit', 'dolphin', 'junior',
'dexter', 'guitar', 'hammer', 'winner', 'copper', 'marina', 'sierra', 'tucker', 'willow',
'zombie', 'viking', 'thunder', 'topgun', 'maverick', 'eclipse', 'scorpion', 'panther'];
function genPassword(r, victim) {
const yr = String(victim.birthYear), yr2 = yr.slice(2);
const personal = [];
if (victim.dog) personal.push(
{ answer: translit(victim.dog), hint: 'кличка собаки латиницею (див. досьє)' },
{ answer: translit(victim.dog) + yr, hint: 'кличка собаки латиницею + повний рік народження' });
personal.push(
{ answer: translit(victim.first) + yr, hint: `ім'я латиницею + повний рік народження` },
{ answer: translit(victim.last) + yr2, hint: 'прізвище латиницею + дві цифри року народження' },
{ answer: translit(victim.first) + translit(victim.last), hint: `ім'я та прізвище латиницею разом` });
if (chance(r, 0.5)) {
const p = pick(r, personal);
return { ...p, style: 'personal', personal: true };
}
return { answer: pick(r, COMMON_PASSWORDS), style: 'random', personal: false,
hint: 'жодного зв\'язку з жертвою — тільки перебір за словником (rockyou.txt)' };
}
module.exports = { hashSeed, mulberry32, ri, estRange, pick, chance, shuffle, UKR, caesar, b64, md5, translit, COMMON_PASSWORDS, genPassword };