Progetto individuale sviluppato per il corso Web Applications I (A.A. 2024/2025) al Politecnico di Torino. L'applicazione è ispirata al gioco "Stuff Happens", in cui il giocatore deve collocare eventi sfortunati in ordine crescente di gravità.
L’applicazione web implementa una versione single-player del gioco "Stuff Happens", con le seguenti funzionalità:
- Gioco completo per utenti registrati: Partite composte da più round. La partita è vinta se si collezionano 6 carte, mentre è persa se si sbagliano 3 carte.
- Modalità demo per visitatori: Una partita da un solo round accessibile senza login.
- Cronologia partite: Accessibile solo agli utenti registrati, con riepilogo di ogni carta, esito e round associato.
- Visualizzazione carte: Ogni carta mostra nome, immagine e indice di sfortuna (solo se conquistata).
- Riepilogo finale: Al termine di ogni partita, l’utente visualizza tutte le carte ottenute.
- Timer round: Ogni round ha un limite di tempo di 30 secondi per collocare correttamente la carta.
- Login/logout: Autenticazione tramite sessione con credenziali hashate (via Passport.js).
- Frontend: React + Vite, React Router DOM, CSS Modules
- Backend: Node.js (ES Modules) + Express
- Database: SQLite
- Autenticazione: Passport.js + cookie-session, password hash + salt
⚠️ Requisiti:
- Node.js 22.x LTS
- nodemon installato globalmente (
npm install -g nodemon)
git clone https://github.com/Tomm100/gioco-sfortuna.git
cd gioco-sfortunacd server
npm install
cd ../client
npm installcd server
nodemon index.mjscd client
npm run devL'app sarà visibile su: http://localhost:5173
User 1:
- username: tommaso@example.com
- password: password
User 2:
- username: bob@example.com
- password: password
⚠️ Le credenziali inserite sono solo a scopo di testing e non rappresentano utenti reali.
-
Route
/: Homepage pubblica dell’applicazione. Mostra una breve introduzione per utenti non loggati, un link al login e un bottone per iniziare una partita demo. Accessibile solo da utenti non autenticati. -
Route
/regole: Pagina con le istruzioni dettagliate del gioco, accessibile da chiunque. -
Route
/login: Pagina per effettuare l'autenticazione. Dopo il login, l’utente viene reindirizzato alla sua dashboard. -
Route
/user: Dashboard dell’utente registrato. Consente di iniziare una nuova partita o accedere alla cronologia delle partite completate. -
Route
/user/game/:gameId: Pagina di gioco principale per utenti registrati. Mostra le carte in possesso e la nuova carta da collocare. Il parametro:gameIdidentifica la partita in corso. -
Route
/user/game/:gameId/summary: Pagina accessibile solo agli utenti autenticati. Mostra il riepilogo della partita appena completata, includendo:- L’esito della partita (vinta o persa)
- L’elenco completo delle carte raccolte (nome, immagine e indice di sfortuna)
- Un pulsante per iniziare una nuova partita
- Un pulsante per tornare alla dashboard utente
Il parametro
:gameIdidentifica la partita in corso. -
Route
/user/storico: Pagina accessibile solo agli utenti autenticati. Mostra la cronologia delle partite completate, ordinate per data decrescente. Per ogni partita sono riportati:- L’esito finale (vinta o persa)
- Il numero totale di carte raccolte
- La lista delle carte coinvolte, con:
- Nome della situazione orribile
- Etichetta “Iniziale” se la carta era tra le 3 iniziali
- Etichetta “Round N” per le carte presentate nei vari round
- Stato “Vinta” se la carta è stata ottenuta, “Persa” se non è stata vinta
-
Route
/demo/game/:gameId: Pagina accessibile senza effettuare il login. Permette di giocare una partita demo composta da un solo round. Il giocatore riceve 3 carte iniziali e deve collocare correttamente una situazione aggiuntiva. Il parametro:gameIdidentifica la partita demo in corso. -
Route
/demo/game/:gameId/summary: Riepilogo della partita demo. Mostra le carte iniziali e, se il round è stato vinto, anche la carta ottenuta. Disponibile solo dopo aver completato la demo. Il parametro:gameIdidentifica la partita demo in appena completata. -
Route
*: Pagina per route non valide. Mostra un semplice messaggio di errore "404 Pagina non trovata" e un bottone per tornare alla home.
- URL:
/api/sessions - Metodo HTTP: POST
- Descrizione: Autentica un utente e crea una sessione.
- Corpo della richiesta:
{
"username": "user@example.com",
"password": "password123"
}- Risposta:
201 Created(successo) o401 Unauthorized(credenziali non valide). - Corpo della risposta:
{
"id": 1,
"username": "user@example.com"
}- URL:
/api/sessions/current - Metodo HTTP: GET
- Descrizione: Recupera le informazioni dell'utente attualmente autenticato.
- Risposta:
200 OK(successo) o401 Unauthorized(non autenticato). - Corpo della risposta:
{
"id": 1,
"username": "user@example.com"
}- URL:
/api/sessions/current - Metodo HTTP: DELETE
- Descrizione: Termina la sessione dell'utente corrente.
- Risposta:
200 OK(successo). - Corpo della risposta: Nessuno
- URL:
/api/user/game - Metodo HTTP: POST
- Descrizione: Crea una nuova partita per l'utente autenticato con 3 carte iniziali.
- Risposta:
201 Created(successo),401 Unauthorized(non autenticato) o500 Internal Server Error(errore generico). - Corpo della risposta:
{
"gameId": 123,
"initialCards": [
{
"id": 1,
"name": "Nome Carta",
"image": "card_image.jpg",
"badluck": 30
},
...
]
}- URL:
/api/user/game/:gameId/next - Metodo HTTP: GET
- Descrizione: Ottieni la prossima carta da indovinare per la partita specificata.
- Risposta:
200 OK(successo),404 Not Found(partita non trovata),400 Bad Request(partita già conclusa), o401 Unauthorized(non autenticato). - Corpo della risposta:
{
"id": 5,
"name": "Nuova Carta",
"image": "new_card.jpg"
}- URL:
/api/user/game/:gameId/guess - Metodo HTTP: POST
- Descrizione: Invia una proposta di posizione per una carta nella partita specificata
- Corpo della richiesta:
{
"cardId": 5,
"position": 2
}- Risposta:
200 OK(successo),404 Not Found(partita/carta non trovata),422 Unprocessable Entity(errore di validazione), o401 Unauthorized(non autenticato). - Corpo della risposta:
{
"result": "correct",
"card": {
"id": 5,
"name": "Nome Carta",
"image": "card_image.jpg",
"badluck": 45
},
"numPlayerCards": 4,
"gameStatus": "ongoing"
}oppure per tentativo sbagliato:
{
"result": "wrong",
"gameStatus": "ongoing",
"wrongGuesses": 1
}- URL:
/api/user/game/:gameId/timeout - Metodo HTTP: POST
- Descrizione: Gestisce il timeout per una carta, quando il giocatore non effettua una scelta entro 30 secondi.
- Corpo della richiesta:
{
"cardId": 5
}- Risposta:
200 OK(successo),404 Not Found(partita/round non trovato),400 Bad Request(tempo non scaduto), o401 Unauthorized(non autenticato). - Corpo della risposta:
{
"result": "wrong",
"gameStatus": "ongoing",
"wrongGuesses": 2
}- URL:
/api/user/game/:gameId - Metodo HTTP: GET
- Descrizione: Recupera lo stato attuale di una partita specifica.
- Risposta:
200 OK(successo),404 Not Found(partita non trovata), o401 Unauthorized(non autenticato). - Corpo della risposta:
{
"playerCards": [
{
"id": 1,
"name": "Nome Carta",
"image": "card_image.jpg",
"badluck": 30
},
...
],
"roundNumber": 5,
"wrongGuesses": 1,
"gameStatus": "ongoing"
}- URL:
/api/user/games - Metodo HTTP: GET
- Descrizione: Recupera tutte le partite completate dell'utente autenticato con cronologia dettagliata.
- Risposta:
200 OK(successo),401 Unauthorized(non autenticato), o500 Internal Server Error(errore generico). - Corpo della risposta:
[
{
"gameId": 123,
"status": "won",
"startedAt": "2025-06-19T10:30:00Z",
"totalCardsCollected": 6,
"cards": [
{
"id": 1,
"name": "Nome Carta",
"roundNumber": null,
"isWon": true,
"isInitial": true
},
{
"id": 3,
"name": "Altra Carta",
"roundNumber": null,
"isWon": true,
"isInitial": true
},
{
"id": 7,
"name": "Terza Carta",
"roundNumber": null,
"isWon": true,
"isInitial": true
},
{
"id": 5,
"name": "Carta Round 1",
"roundNumber": 1,
"isWon": true,
"isInitial": false
},
{
"id": 8,
"name": "Carta Round 2",
"roundNumber": 2,
"isWon": false,
"isInitial": false
}
]
}
]- URL:
/api/demo/game - Metodo HTTP: POST
- Descrizione: Crea una nuova partita demo (singolo round) con 3 carte iniziali selezionate casualmente dal server.
- Risposta:
201 Created(successo) o500 Internal Server Error(errore generico). - Corpo della risposta:
{
"gameId": 456,
"initialCards": [
{
"id": 1,
"name": "Nome Carta",
"image": "card_image.jpg",
"badluck": 30
},
{
"id": 4,
"name": "Carta Demo",
"image": "demo_card.jpg",
"badluck": 55
},
{
"id": 9,
"name": "Ultima Carta",
"image": "last_card.jpg",
"badluck": 75
}
]
}- URL:
/api/demo/game/:gameId/next - Metodo HTTP: GET
- Descrizione: Ottiene la prossima carta da indovinare nella partita demo. Il server seleziona automaticamente una carta casuale non utilizzata.
- Risposta:
200 OK(successo),404 Not Found(partita non trovata), o400 Bad Request(partita già conclusa). - Corpo della risposta:
{
"id": 5,
"name": "Carta Demo",
"image": "demo_card.jpg"
}- URL:
/api/demo/game/:gameId/guess - Metodo HTTP: POST
- Descrizione: Invia un tentativo di posizionamento per una carta nella partita demo.
- Corpo della richiesta:
{
"cardId": 5,
"position": 1
}- Risposta:
200 OK(successo),404 Not Found(partita/carta non trovata), o422 Unprocessable Entity(errore di validazione). - Corpo della risposta (tentativo corretto - partita vinta):
{
"result": "correct",
"card": {
"id": 5,
"name": "Nome Carta",
"image": "card_image.jpg",
"badluck": 25
},
"gameStatus": "won"
}- Corpo della risposta (tentativo sbagliato - partita persa):
{
"result": "wrong",
"gameStatus": "lost"
}- URL:
/api/demo/game/:gameId/timeout - Metodo HTTP: POST
- Descrizione: Gestisce il timeout per un tentativo di carta nella partita demo.
- Corpo della richiesta:
{
"cardId": 5
}- Risposta:
200 OK(successo),404 Not Found(partita/round non trovato), o400 Bad Request(tempo non scaduto). - Corpo della risposta:
{
"result": "wrong",
"gameStatus": "lost"
}- URL:
/api/demo/game/:gameId - Metodo HTTP: GET
- Descrizione: Recupera lo stato attuale di una partita demo.
- Risposta:
200 OK(successo) o404 Not Found(partita non trovata). - Corpo della risposta:
{
"playerCards": [
{
"id": 1,
"name": "Nome Carta",
"image": "card_image.jpg",
"badluck": 30
},
{
"id": 4,
"name": "Carta Demo",
"image": "demo_card.jpg",
"badluck": 55
},
{
"id": 9,
"name": "Ultima Carta",
"image": "last_card.jpg",
"badluck": 75
}
],
"gameStatus": "ongoing"
}-
users- Contiene le credenziali degli utenti registrati:id– Chiave primarianame– Nome completo dell'utenteemail– Email per il loginpassword– Password in formato hashatosalt– Valore salt per hashing sicuro
-
cards- Contiene tutte le carte disponibili nel gioco:id– Chiave primarianame– Descrizione della situazione sfortunataimg– Nome del file immagine associatobadluck– Valore numerico dell’indice di sfortuna (da 1.0 a 100.0)
-
games– Contiene tutte le partite giocate dagli utenti:id– Chiave primariauserId– Riferimento all’utentestartedAt– Data e ora di inizio della partitastatus– stato della partita (ongoing,won,lost)
-
initialCards– Contiene le 3 carte iniziali di ogni partita:id– Chiave primariagameId– Riferimento alla partitacardId– Riferimento alla carta iniziale
-
gameCards– Contiene per ogni partita le carte di ciascun round:id– Chiave primariagameId– Riferimento alla partitacardId– Carta mostrata nel roundroundNumber– Numero del roundguessed– Flag (0/1) per indicare se la carta è stata indovinata o menostartedAt– Timestamp di inizio del round
-
LoginForm(inAuthComponents.jsx): Gestisce l'autenticazione dell'utente con validazione del form e stato di caricamento. -
CardItem(inCardItem.jsx): visualizza una singola carta con immagine, nome e indice di sfortuna in un layout compatto. -
GameBoard(inGameBoard.jsx): Rappresenta l’interfaccia di gioco con le carte e i pulsanti per selezionare la posizione. -
RoundPrompt(inRoundPrompt.jsx): Componente principale per un singolo round, gestisce timer, visualizzazione della carta e interazione utente. -
Timer(inTimer.jsx): Mostra il conto alla rovescia per ogni round. -
CardsSummaryBox(inCardsSummaryBox.jsx): Mostra una griglia delle carte conquistate alla fine di una partita completata. -
NewGameModal(inNewGameModal.jsx): Finestra per avviare una nuova partita per utenti autenticati. -
NewDemoGameModal(inNewDemoGameModal.jsx): Finestra per iniziare una partita demo per utenti non loggati. -
ModalResultRound(inModalResultRound.jsx): Mostra i risultati del round appena completato oppure il messaggio introduttivo se si tratta del primo round. -
NavHeader(inNavHeader.jsx): Barra di navigazione principale con funzionalità di login/logout e gestione delle route. -
DefaultLayout(inDefaultLayout.jsx): Layout di base che fornisce una struttura coerente tra le varie pagine.
Screenshot durante una partita
Screenshot cronologia
Questo progetto è rilasciato sotto licenza MIT. Vedi il file LICENSE per i dettagli.

