Od Wordla do 64rdla: Kako zgraditi svojo različico legendarne igre

03.07.2026

Ta tehnični React Wordle vodič prikazuje razvoj osnovne različice priljubljene igre ugibanja besed, ki služi kot optimiziran temelj za kasnejšo zahtevnejšo nadgradnjo. Avtor s poudarkom na optimizaciji delovanja komponent pojasnjuje uporabo podatkovne strukture »Set« za bliskovito preverjanje veljavnosti besed ter implementacijo determinističnega dnevnega izziva s pomočjo generatorja naključnih števil s »semenom« (seed). Zaključek ponuja praktičen pregled upravljanja stanja igre prek shranjevanja napredka v localStorage ter napredno logiko za dinamičen izris igralnega polja in barvanje črk s pomočjo CSS Grid arhitekture.

Ste kdaj občutili, da je klasični Wordle preveč enostaven? Sam se s prijatelji vsak dan pomerim v ugibanju besed, a včasih si človek zaželi večjega izziva. Tako sem na spletu naletel na Sexaginta-quattuordle – različico, kjer rešuješ kar 64 besed hkrati v 70-ih poskusih. Ideja je fantastična, a izvedba na spletu pogosto šepa. Stran postane počasna, ker ne uporablja virtualizacije seznama; vsakič, ko vnesete črko, osveži vseh 64 polj, tudi tista, ki niso na zaslonu.

Ker bi bila celotna optimizacija za en članek preobsežna, bomo projekt razdelili. Danes bomo postavili temelje - delujoč klasičen Wordle v Reactu. V naslednji številki pa ga bomo nadgradili v zmogljiv 64rdle.

1. Priprava besedišča in optimizacija iskanja

Ko postavimo React aplikacijo, najprej potrebujemo podatke. Besede razdelimo na dva seznama:
1. Seznam možnih rešitev: Besede, ki so primerne za končni odgovor.
2. Seznam vseh besed: Širši nabor besed, ki jih igra sprejme kot veljaven poskus.
Triki za hitrost: Seznam možnih rešitev shranimo v običajno polje (Array), seznam vseh besed pa v Set, ker je preverjanje, ali beseda obstaja v Set, bistveno hitrejše kot iskanje po dolgem seznamu.

2. Logika za dnevno besedo (Daily Seed)

Da bi vsi igralci isti dan ugibali isto besedo, potrebujemo deterministično izbiro. Ne želimo, da se beseda spremeni ob vsaki osvežitvi strani. Uporabili bomo preprosto funkcijo s "semenom" (seed), ki za določeno številko vedno vrne enak rezultat.

const DATE_SEED = 123;

const getDailySeed = () => {
    let date = new Date();
    let startDate = new Date('03/24/2022');
    let offset = (date.getTimezoneOffset() - startDate.getTimezoneOffset()) * 60000;
    let seed = (DATE_SEED + ((date.getTime() - offset - startDate.getTime()) / (1000 * 3600 * 24))) >> 0;
    return seed < 10 ? 10 : seed;
};

function RandomInt(seed: number) {
    return function () {
        seed = (seed * 1664525 + 1013904223) % 4294967296;
        return seed;
    };
}

3. Stanje igre in shranjevanje napredka

Znotraj React komponente določimo stanja. Uporabimo localStorage, da igralec ne izgubi napredka, če po nesreči zapre zavihek.
const seed = getDailySeed();

const [word, setWord] = useState<string>('');
const [todaysWord] = useState<string>(() => {
    let rnd = RandomInt(seed);
    return answerWords[rnd() % answerWords.length];
});

const [history, setHistory] = useState<string[]>(() => 
    localStorage.getItem(`daily_guesses_${seed}`)?.split(',').filter(w => w.length === 5) || []
);

useEffect(() => {
    localStorage.setItem(`daily_guesses_${seed}`, history.join(','));
}, [history]);

const gameFinished = useMemo(() => 
    history.length >= 6 || (history.length > 0 && history[history.length - 1] === todaysWord), 
[history, todaysWord]);

4. Upravljanje vnosov

Potrebujemo funkcijo, ki preveri veljavnost vnesene besede, in poslušalca dogodkov (Event Listener) za tipkovnico.
const submitWord = () => {
    if (word.length !== 5) return;
    if (!allWords.has(word)) {
        setWord(""); // Počistimo vnos, če beseda ne obstaja
        return;
    }
    setHistory([...history, word]);
    setWord("");
};

const onWinKeyUp = useCallback((ev: KeyboardEvent) => {
    let key = ev.key.toLowerCase();

    if (key === "enter") {
        setWord(current => { submitWord(); return current; });
        return;
    }
    if (key === "backspace") {
        setWord(prev => prev.slice(0, -1));
        return;
    }
    if (key.length === 1 && key >= 'a' && key <= 'z') {
        setWord(prev => (prev.length >= 5 ? prev : prev + key));
    }
}, [history, word]);
useEffect(() => {
        if (!gameFinished)
            window.addEventListener('keyup', onWinKeyUp);
        return () => window.removeEventListener('keyup', onWinKeyUp);
    }, [onWinKeyUp]);

    useEffect(() => {
        if (gameFinished) {
            window.removeEventListener('keyup', onWinKeyUp);
        }
    }, [gameFinished]);
let todaysCC: { [key: string]: number } = {}; // todays character counter
    for (let char of todaysWord) {
        todaysCC[char] = todaysCC[char] && todaysCC[char] + 1 || 1;
    }

5. Izris igralne površine in CSS magija

Pri izrisu je ključno pravilno barvanje črk. Upoštevati moramo, kolikokrat se določena črka pojavi v besedi, da ne pobarvamo preveč črk oranžno (če se npr. črka v rešitvi pojavi enkrat, mi pa smo jo uganili dvakrat na napačnih mestih).

<div className='gametable'>
    {
        [0, 1, 2, 3, 4, 5].map((i) => {
            let tmpWord;
            if (i < history.length) {
                tmpWord = history[i];
// Tukaj v tmpTodaysCC nam pove katere črke še niso bile uganjene in tudi, kolikokrat so še v besedi 
                let tmpTodaysCC = { ...todaysCC };
                for (let j = 0; j < 5; j++) {
                    let char = tmpWord[j];
                    if (todaysWord[j] === char) {
                        let num = (tmpTodaysCC[char] || 0) - 1;
                        tmpTodaysCC[char] = num;
                    }
                }

                return [...tmpWord].map((char, j) => {
                    let className = '';
               // Izbere ozadje, ki bo izrisano za črko     
if (char === todaysWord[j]) className = 'correct';
                    else if (todaysWord.indexOf(char) >= 0 && tmpTodaysCC[char] > 0) className = 'missplaced'

                    return <div className={className}>{char}</div>;
                });
            }
            else if (i == history.length) {
                tmpWord = word;
// Tukaj naredi vrstico, kjer se zdaj piše, ker ni nujno, da je trenutna beseda 5 črk dolga, dodamo v listo še dodatnih 5 null, in nato s pogojnimi stavki vrnemo le 5 veljavnih elementov za react
                return [...tmpWord, null, null, null, null, null].map((char, j) => {
                    if (j >= 5) return;
                    if (!char) return <div></div>;

                    return <div>{char}</div>;
                })
            }

            return <>
                <div></div>
                <div></div>
                <div></div>
                <div></div>
                <div></div>
            </>;
        })
    }
</div>
Css:
.gametable {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
    gap: 5px;

    max-width: 340px;
    margin: 0px auto;

    padding-top: 25px;

    > div {
        box-sizing: border-box;

        display: inline-flex;
        align-items: center;
        justify-content: center;

        width: 64px;
        height: 64px;

        font-size: 2rem;
        text-transform: uppercase;

        text-align: center;

        border: 2px solid lightgrey;

        &:empty {
            border: 2px solid grey;
        }
       /* Izberemo le tiste elemete, ki so pred elementi div, ki so prazni in jim damo animacijo */
        &:not(:empty):has(~ div:empty) {
            animation: bounce 0.25s;
        }

        &.correct {
            background-color: green;
        }

        &.missplaced {
            background-color: orange;
        }
    }
}

@keyframes bounce {
    0% {
        transform: scale(1);
    }
    50% {
        transform: scale(1.075);
    }
    100% {
        transform: scale(1);
    }
}
S tem smo postavili temelje za stabilno in odzivno igro. V naslednjem delu pa se bomo poglobili v virtualizacijo seznama, ki nam bo omogočila, da brez izgube hitrosti poganjamo 64 takšnih tabel hkrati!

Če želite probati že postavljeno kodo, jo lahko najdete pod: 

https://github.com/LenartSvetek/Xnet

V commit Wordle za članek:

LenartSvetek/Xnet at 2f05bd65441f77aaa53e56e03a3d8e73c4f4c9a0

Lenart Svetek
Lenart Svetek
Front - end programer, študent
lenart.svetek@kompas-xnet.si

Do you have any additional questions?

For more information, we are always happy to assist you. Feel free to contact us at info@kompas-xnet.si or call us at 01 5136 990.

Contact us

Novice

Naročite se na Xnet novice in ostanite na tekočem glede novih tečajev, seminarjev, možnosti pridobitve novih certificiranj in akcijskih cen.

What area of ​​news are you interested in?

Need assistance? bot icon
Need assistance?