\ /
front end 

Mini gioco sulla dattilografia

A fine 2021 mi sono posto come buon proposito quello di migliorare nello scrivere codice, ed essendo uno sviluppatore ormai propenso sempre più verso lo sviluppo front-end ho deciso di rafforzare le mie conoscenze Javascript. Penso che prima ancora di imparare ad utilizzare qualsiasi libreria o framework moderno bisogna esser preparati nelle 3 cose fondamentali per uno sviluppatore front-end, cose che difficilmente diventeranno obsolete, ovvero HTML, CSS e Javascript. Il modo migliore per imparare/migliorare in qualcosa è facendo pratica, perciò mi sono trovato online qualche progettino carino da riprodurre nei weekend, ed uno di questi mi ha colpito particolarmente, ovvero una web app sulla dattilografia. Ho trovato alcuni spunti qua e là sul web ma volevo crearmene una tutta mia che permettesse non solo all’utente di esercitarsi e migliorare nella scrittura con 10 dita, ma anche di giocare e perché no, di divertirsi.

Il risultato finale è questo: https://lucapu88.github.io/speed-typer-game/

Il giochino è molto semplice: viene mostrata una parola e l’utente dovrà scriverla nell’apposito input, una volta inserita correttamente ne verrà mostrata un’altra a così via. Ad ogni parola scritta correttamente si guadagna un punto. La partita dura 30/60 secondi e lo scopo è quello di scrivere più parole possibili.

Ho deciso di creare un semplice progetto di 3 files, html, css e js, senza l’aiuto di nessuna libreria.

L’html è molto semplice, c’è un tag nav contenente 2 select, una per impostare il livello (facile, medio, difficile) ed una con il tempo (30/60 secondi). Poi c’è il tag main che contiene il cuore del gioco. Il main contiene una schermata iniziale dove un’immagine mostra come tenere le mani sulla tastiera, un contenitore delle parole che verranno mostrate, l’input dove dovranno essere riscritte, ed un contenitore con i punteggi migliori (questi verranno mostrati in base a ciò che clicchiamo). Infine, il footer contiene il pulsante che mostra la tabella dei punteggi, il pulsante che attiva o toglie l’audio (ho inserito musiche stile giochi arcade anni 90), un pulsante che riavvia il gioco (appare solo a gioco iniziato) ed un pulsante di impostazioni dove sono presenti le istruzioni e il cambio lingua.

Nel file js, per prima cosa, ho creato tutte le costanti che riprendono gli id dei vari elementi html, utilizzando il classico document.getElementById() per ciascuna costante. Le 2 select per settare il tempo e la difficoltà sono legate a degli event listener che richiamano le rispettive funzioni.

Quando si clicca su start prima di far partire il gioco ho impostato un conto alla rovescia per permettere all’utente di prepararsi. 3, 2, 1 parte il gioco. Le parole che vengono mostrate sono prese da degli array che ho creato. Non avendo un back-end ho deciso di creare un file separato contenente 3 array pieni di parole. Ho scelto di scriverle in inglese per permettere a chiunque di poterci giocare. Le parole sono lunghe in base alla difficoltà che si sceglie. Quindi al click su start viene chiamata una funzione shootWords alla quale viene passato l’array di parole che verranno mostrate (l’array viene settato in base alla difficoltà). shootWords racchiude in una costante un’altra funzione di nome randomWords. Questa funzione ritorna una parola casuale presa dall’array che gli passiamo:

let wordToDisplay = document.getElementById('word-to-display'); //parola che viene mostrata

function shootWords(array) {
    wordToPrint = randomWords(array);
    wordToDisplay.textContent = wordToPrint;
}

function randomWords(words) {
    return words[Math.floor(Math.random() * words.length)];
}

Contemporaneamente parte anche il timer:

timeInterval = setInterval(updateTime, 1000);

function updateTime() {
    +countdown--;

    time.innerHTML = countdown + 's';

    if (countdown === 0) {
        clearInterval(timeInterval);
        finalScore = initialScore;
        saveScoreAndTime(finalScore, timer, difficultyName);
        gameOver();
    }
}

La funzione updateTime non fa altro che diminuire la variabile countDown di uno ad ogni secondo. Quella variabile è stata impostata quando abbiamo scelto il tempo e mostra a schermo il tempo che scorre, e quando arriva a 0 ferma il timer, imposta il punteggio e lo passa alla funzione che salva il tutto nella tabella dei punteggi (saveScoreAndTime) insieme alla difficoltà. In fine chiama gameOver che andrà a resettare il tutto.

Per far si che le parole vengano cambiate, ho creato una funzione compareWords legata all’event listener keyup e touchend (per mobile), che va semplicemente a prendere il valore di ciò che abbiamo scritto in input e lo confronta con la parola presa dall’array.

function compareWords(e) {
    const writtenWord = e.target.value;
    const wordToPrintLowercase = wordToPrint.toLowerCase();
    const writtenWordLowercase = writtenWord.toLowerCase();

    if (wordToPrintLowercase === writtenWordLowercase) {
        shootWords(difficultySelect);
        inputText.value = '';
        initialScore++;
        score.textContent = initialScore;
        inputText.style.border = 'none';
    }
}

Se ciò che abbiamo scritto è uguale a ciò che ci è stato mostrato richiamo la funzione shootWords descritta in precedenza, azzero l’input, e incremento il punteggio (successivamente poi ho aggiunto una funzione reportWrongWord che se la parola inserita è errata, segnala con il contorno del bordo rosso, ma solo per alcune modalità di gioco). Con il tempo mi sono venute altre idee come l’aggiunta di altre modalità di gioco.

Ho aggiunto prima la difficoltà Hero che ha solo 60 secondi (per evitare esaurimenti nervosi :P), dove le parole sono molto difficili (vanno dagli 11 caratteri in su) e se la parola è errata, il punteggio verrà diminuito di 1. Un po' diabolica.

Ho aggiunto, inoltre, una modalità più semplice, ovvero Exercise dove non c’è né tempo né punteggio e quindi l’utente può esercitarsi liberamente senza ansia ;-). Per questa modalità però ho preferito non utilizzare gli array da me creati, poiché a lungo andare le parole comincerebbero a ripetersi! Allora ho optato per l’utilizzo di una API (anche per aumentare un po' l’apprendimento di Javascript). Ho cercato veramente tanto sul web, e parecchie non funzionavano o non facevano al caso mio. Dopo tanta ricerca e tante prove andate male, ho trovato un utente che si è imbattuto nel mio stesso problema e si è creato una API apposita. Si può creare facilmente con Node ed il framework Express (il vero problema è andare a cercare tante parole diverse).

Quindi per la modalità Exercise ho creato una funzione (prepareWordsForPractice) che fa una fetch della mia API, tornando un JSON che passo ad un’altra funzione (deleteShortAndDuplicateWords) che crea un array eliminando parole troppo corte (ho scelto minori di 4 lettere) e parole duplicate. I risultati della API vengono passati alla funzione shotWords (descritta precedentemente) che mostrerà parole casuali.

async function prepareWordsForPractice(url) {
    return fetch(url)
        .then((results) => {
            return results.json();
    })
    .then((json) => {
        const finalWordsArray = deleteShortAndDuplicateWords(json);
        return finalWordsArray;
    })
    .catch((err) => {
        ifThereIsAnError(err);
    });
}
…
let randomWords = prepareWordsForPractice(loremIpsumAPI);
…
randomWords.then((res) => {
    return shootWords(res);
});

Per il salvataggio dei punteggi non avendo un back-end ho optato per l’utilizzo di localStorage che permette di archiviare i dati localmente all’interno del browser.

Lo scopo di questo esercizio non era quello di creare un database, ma una semplice applicazione hostata su github pages, senza persistenza dei dati.

Quindi ho creato una funzione (saveScoreAndTime) che riceve come parametri il punteggio, il tempo e la difficoltà, e salva il punteggio tramite localStorage.setItem. Se il punteggio ricevuto è più alto dei punteggi precedenti, viene salvato in base al tempo e alla difficoltà scelti. Le variabili salvate nel localStorage vengono recuperate con localStorage.getItem quando clicco sul pulsante che mostra la tabella dei punteggi. Riguardo l’audio, utilizzo un tag HTML apposito:

<audio loop id="audio">
    <source src="sounds/Pokemon-Center.mp3" type="audio/mpeg" />
    Your browser does not support the audio element.
</audio>

Succcessivamente al tag è presente un pulsante che consente di modificare una variabile booleana (audioPlay = false) che attiverà o disattiverà l’audio audioPlay = !audioPlay; Se audioPlay è uguale a true, utilizzando il metodo play() partirà l’audio (audio.play()), altrimenti verrà fermato tramite audio.pause().

Infine, vi parlo del pulsante che mostra/nasconde le istruzioni. La cosa carina di questo pulsante è stata fatta con il css, dove ho creato una comparsa a scorrimento dal basso verso l’alto. Al click va semplicemente a togliere/aggiungere una classe (slideUp/Down) al riquadro. Nel css utilizzando i keyframes vado a muovere dal basso verso l’alto o dall’alto verso il basso il contenitore:

.helper-description.slideUp {
    display: flex;
    animation-name: slideUp;
    animation-duration: 1s;
    animation-fill-mode: forwards;
}
@keyframes slideUp {
    from {
        top: 200%;
    }
    to {
        top: 0;
    }
}
.helper-description.slideDown {
    display: flex;
    animation-name: slideDown;
    animation-duration: 0.8s;
    animation-fill-mode: none;
}
@keyframes slideDown {
    from {
        top: 55px;
    }
    to {
        top: 200%;
    }
}

Nel riquadro delle istruzioni ho aggiunto anche una select che permette di cambiare lingua (solamente per quanto riguarda le istruzioni). Al click su un’opzione, in base alla lingua scelta, utilizza una variabile creata nel file fake-db.js, contenente il testo delle istruzioni nelle diverse lingue.

Ho descritto solo le parti del codice che, secondo me, sono più importanti, omettendo il superfluo. Se vi interessa dare un’occhiata a tutto il codice nel dettaglio lo trovate nel mio profilo github: https://github.com/lucapu88/speed-typer-game

Un altro aspetto interessante è stato il supporto da vari “colleghi”. Essendo un esercizio per fare pratica ho chiesto pareri sui vari forum, ed ho ricevuto alcuni consigli su come migliorare. Trovo che sia molto utile sentire diversi pareri, anche se la domanda può sembrare stupida, perché se chiedi puoi sembrare stupido quella volta, se non chiedi resti stupido sempre ;-)

comments powered by Disqus