Spaccamuro

Volevo fare un'app da usare durante le uscite con gli amici, quando si sta attorno a un tavolo in una pizzeria o al bancone del bar.

Dopo aver scoperto che è possibile accedere tramite l'evento devicemotion all'accelerazione del telefono lungo i tre assi ho pensato di sfruttarlo cercando di misurare la vibrazione del tavolo dove sta appoggiato il telefono.

Il sensore è molto preciso e sensibile e l'aggiornamento del dato è molto frequente, quindi risponde immediatamente ad ogni cambiamento. Qui, accedendo con uno smartphone, puoi vedere i valori grezzi dell'accelerazione lungo i tre assi, oltre all'orientamento nello spazio del telefono, che sarà invece oggetto di un'altra app.

Spaccamuro

Url
https://spaccamuro.it
Github
https://github.com/polesello/spaccamuro
Data di uscita
1 febbraio 2024
Caratteristiche
devicemotion, CSS transition
Dispositivi
Android, iOS, Desktop
Browser
Chrome, Safari

Analizziamo ora in dettaglio le varie caratteristiche e particolarità che compongono questa applicazione.

Per farlo è certamente più semplice partire dalla prima versione, che trovate disponibile al link spaccamuro.it/old

Il mattone

Il mattone è realizzato interamente in CSS, la parte interessante è l'animazione che fa cadere il mattone, basata su una transizione CSS che combina una caduta verso il basso e una leggera rotazione, oltre a un cambio di colore.

Le transizioni CSS sono molto usate nella versione online dell'applicazione, ad esempio per il cambio di pagina, la comparsa dell'indicazione del numero di mattoni da buttare giù e il passaggio da schermata verticale a orizzontale.

.mattone {
    height: 80px;
    width: 120px;
    background-color: brown;
    border: 1px solid #fff;
    transition: all 2s;
}
.mattone.caduto {
    transform: translateY(1000px) rotate(30deg);
    background-color: black;
}

Nella versione attualmente online è stato aggiunto un effetto tridimensionale ottenuto dichiarando multipli box-shadow senza sfocatura (il terzo parametro è sempre zero).

.mattone {
    height: 14vw;
    width: 19vw;
    background-color: var(--mattone);
    border: 1px solid #fff;
    /* 3d effect */
    box-shadow: 1px -1px 0 var(--mattone-chiaro), 2px -2px 0 var(--mattone-chiaro), 3px -3px 0 var(--mattone-chiaro), 4px -4px 0 var(--mattone-chiaro), 5px -5px 0 var(--mattone-chiaro), 6px -6px 0 var(--mattone-chiaro), 7px -7px 0 var(--mattone-chiaro), 8px -8px 0 var(--mattone-chiaro), 9px -9px 0 var(--mattone-chiaro), 10px -10px 0 var(--mattone-chiaro), 11px -11px 0 var(--mattone-chiaro), 12px -12px 0 var(--mattone-chiaro);
    transition: all 2s;
}

Transizioni

Ci sono molte transizioni CSS, che servono a dare un aspetto più fluido e meno scattoso all'app. La più evidente è quella che fa cadere il mattone, ma sono usate anche per dare l'effetto di movimento al numero dei mattoni da demolire, che entra da destra, o al fade che si vede quando comincia o finisce il gioco e vengono cambiate le schermate.

L’evento “colpo sul tavolo”

Per riuscire a determinare quando viene colpito il tavolo e con che intensità è necessario restare in ascolto dell'evento devicemotion.

La funzione che se ne occupa è la seguente: viene ascoltato continuamente l'evento devicemotion e se l'accelerazione lungo lungo l'asse z, cioè quello perpendicolare al tavolo, è maggiore di 11 allora viene buttato giù un mattone. L'accelerazione normale, intesa come il momento in cui nessuno batte sul tavolo, è quella che abbiamo imparato dalla fisica delle superiori, cioè 9,81 m/s².

Il valore 11 è stato scelto in modo arbitrario sulla base di alcune prove; se si mette un valore troppo alto si rischia di dover distruggere il tavolo per far cadere un mattone, e se il numero fosse ad esempio 10 si avrebbe un'eccessiva sensibilità.

È necessario considerare il valore assoluto del numero perché è positivo nei telefoni Android ed è negativo nei sistemi iOS.

Documentazione evento devicemotion

function handleMotionEvent(event) {
        const z = event.accelerationIncludingGravity.z
        if (Math.abs(z) > 11) { // abs perché iOs è negativa, Android è positiva
            buttaGiu()
        }
    }

window.addEventListener("devicemotion", handleMotionEvent, true)

Toc toc, permesso!

L'utilizzo del sensore di accelerazione sarebbe semplice ma c'è un aspetto fondamentale da considerare se vogliamo che la nostra app sia compatibile anche con i sistemi iOS.

In tale sistema operativo è necessario chiedere il permesso l'utente anche per l'utilizzo del sensore di accelerazione. Tale permesso invece non deve essere richiesto nei sistemi Android. Si utilizza, come accade sovente, un test basato sulla presenza di alcune funzioni o proprietà, in questo caso DeviceMotionEvent. La richiesta di permesso deve essere scatenata da un'azione esplicita dell'utente, ad esempio un clic, ciò significa che non si può avviare la richiesta automaticamente al momento del caricamento della pagina.

Ecco perché tale richiesta è stata collegata ai bottoni che permettono di cominciare la partita con uno o due giocatori.

// in iOS va richiesto il permesso
    let permissionGranted = false
    
    function getPermission () {
        if (!permissionGranted) {
            DeviceMotionEvent.requestPermission()
                .then(response => {
                    if (response == "granted") {
                        window.addEventListener("devicemotion", handleMotionEvent, true)
                    }
            })
        }
    }
    
    // iPhone
    if (typeof(DeviceMotionEvent) !== "undefined" && typeof(DeviceMotionEvent.requestPermission) === "function") {
        document.getElementById('player1').addEventListener('click', getPermission)
        document.getElementById('player2').addEventListener('click', getPermission)
    } else {
        // Android
        window.addEventListener("devicemotion", handleMotionEvent, true)
    }

Altre particolarità

Un'altra cosa a cui ho dovuto fare attenzione è di impedire che l'utente faccia scroll. Sembra semplice come dichiarare overflow: hidden sul body, ma in realtà nel browser predefinito Samsung con il telefono in orizzontale e in Safari Mobile ciò non ha effetto.

Si rischia quindi che l'utente facendo scroll veda dove sono andati a finire i mattoni demoliti.

È sufficiente dichiarare la proprietà overflow in un wrapper interno, quindi non direttamente nel body.

html, body, #wrapper {
    overflow: hidden;
    height: 100vh;
    width: 100%;
}

/* altrimenti non funziona overflow hidden sul body */
#wrapper {
    position: relative;
}

E se mi addormento?

Essendo un'applicazione non basata sul touch, c'è la possibilità che in alcuni telefoni che hanno impostato la modalità di stand-by dopo un breve periodo di inattività, non si riesca più a vedere lo schermo e quindi bisogna bisognerebbe toccarlo con un dito per uscire dalla modalità di stand-by.

È possibile impedire questo usando l'API WakeLock, che permette di evitare che il telefono si addormenti. Ho definito quindi la funzione noStandby che viene chiamata al momento in cui si avvia il gioco con i due bottoni principali.

let wakeLockObj = null

function noStandby() {
    if ("wakeLock" in navigator && wakeLockObj === null) {
        navigator.wakeLock.request("screen").then(function(wakeLock) {
            wakeLockObj = wakeLock;
        }).catch(function(err) {
            console.error(`${err.name}, ${err.message}`);
        });
    }
}

Io non sono muratore!

Se non vi piacciono i lavori pesanti potete sempre darvi all'agricoltura spostando barattoli di pomodori e olive. O meglio, spostando il telefono in orizzontale, così da apprezzare una volta di più la magia delle transizioni CSS e delle media query.

Con delle semplici dichiarazioni di proprietà CSS il mattone è stato trasformato in un cilindro ed è stato aggiunta un'immagine di sfondo. Già che c'ero ho anche riorganizzato la disposizione delle istruzioni e dei pulsanti di avvio in modo che non si sovrapponessero.

@media (orientation:landscape) {
    body {
        background-color: #000;
    }
    #page2 {
        transform: rotate(-3deg);
    }
    .mattone {
        height: 8vh;
        width: 8vh;
        border-radius: 50%;
        background: #333 url(img/pomodoro.png) no-repeat center center;
        background-size: 75%;
    }
    .player2 .mattone {
        background: #ccc url(img/olive.png) no-repeat center center;
        background-size: 75%;
        transition-duration: 4s;
    }
    .mattone.caduto {
        transform: translate(-1000px, 100px) rotate(500deg);
    }
    .prato {
        padding-left: 30px;
        padding-right: 30px;
    }
    .prato .istruzioni {
        margin-top: 0;
    }
    .buttons {
        margin: 30px;
        display: flex;
        justify-content: space-between;
    }
    #page2 .muro.winner {
        background-size: auto 150%;
    }
}

«La voglio installare»

Un po' di dichiarazioni di proprietà nel file manifest.json e la lista dei file da mettere in cache nel service worker e l'app è pronta per fare bella mostra di sé nella schermata home del vostro telefono.

«E ora che faccio?»

  • se qualcosa non funziona, segnalamelo con il form qui sotto. Grazie alla trasmissione del pensiero, sarò in grado di capire che browser stai usando.
  • se il gioco funziona, dimmelo lo stesso.
  • se ti piace tanto, clicca l'icona con il simbolo dell'euro che compare nella prima schermata.
  • se il gioco non ti piace, non dirmelo, aspetta il prossimo mese sperando che ti vada meglio.
  • per ogni altra richiesta puoi scrivermi a polesello@infofactory.it