Skip to content
jacopo1010Public

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Tank Game

Tank Game è un piccolo gioco 2D sviluppato in Java, nato principalmente come progetto didattico per esercitarsi sui concetti di Analisi e Progettazione del Software.

L'obiettivo principale del progetto non era realizzare un videogioco completo, ma partire da un dominio relativamente semplice e far evolvere progressivamente il software applicando concetti come:

  • modellazione del dominio;
  • programmazione orientata agli oggetti;
  • assegnazione delle responsabilità;
  • principi e pattern GRASP;
  • Information Expert;
  • Controller;
  • polimorfismo ed ereditarietà;
  • composizione tra oggetti;
  • refactoring;
  • Command Pattern;
  • gestione dello stato;
  • game loop;
  • persistenza su file.

Il gioco è stato quindi costruito per iterazioni successive, evitando di progettare fin dall'inizio un'architettura completa.

Ogni nuova funzionalità ha introdotto un nuovo problema da risolvere e, quando necessario, una modifica del modello o delle responsabilità delle classi.


Approccio al progetto

Lo sviluppo è iniziato dal modello di dominio, prima ancora di realizzare l'interfaccia grafica.

L'idea è stata quella di descrivere inizialmente gli elementi fondamentali del gioco e le loro relazioni:

  • una Partita;
  • un CampoDiBattaglia;
  • diversi Tank;
  • un TankPlayer;
  • dei Tank controllati dall'AI;
  • Proiettile;
  • Ostacolo;
  • Posizione;
  • Direzione.

Solo successivamente è stata aggiunta la GUI.

Questo permette di mantenere separati:

Dominio del gioco
        ↓
logica e regole

Interfaccia Swing
        ↓
visualizzazione e input

La GUI non rappresenta quindi il gioco stesso: visualizza lo stato degli oggetti appartenenti al dominio.


Modello di dominio

Il modello di dominio utilizzato nel progetto è disponibile nella cartella docs.

Modello di dominio

Il modello è stato utilizzato come punto di partenza, ma non è rimasto necessariamente identico durante tutto lo sviluppo.

Alcune responsabilità e relazioni sono infatti cambiate quando sono emersi nuovi requisiti.

Questo è stato intenzionale: uno degli obiettivi dell'esercizio era osservare come un modello possa evolvere durante lo sviluppo.


Responsabilità e GRASP

Durante lo sviluppo si è cercato di evitare di concentrare tutta la logica in una singola classe.

Le responsabilità sono state assegnate cercando di seguire soprattutto il principio Information Expert:

una responsabilità dovrebbe essere assegnata all'oggetto che possiede le informazioni necessarie per svolgerla.

Ad esempio, le operazioni relative allo stato del campo di battaglia utilizzano le informazioni possedute dal CampoDiBattaglia, mentre i comportamenti specifici dei Tank sono mantenuti all'interno della gerarchia Tank.

La classe Partita svolge invece un ruolo di coordinamento delle principali operazioni del gioco.

Durante lo sviluppo sono stati quindi affrontati concretamente concetti come:

Chi deve conoscere questa informazione?

Quale classe dovrebbe eseguire questa operazione?

Questa responsabilità appartiene davvero a questa classe?

Sto introducendo una dipendenza necessaria oppure evitabile?

L'obiettivo non era applicare pattern in maniera forzata, ma introdurli quando un problema concreto ne rendeva evidente l'utilità.


Sviluppo incrementale

Il progetto è stato sviluppato attraverso piccoli milestone.

Iterazione 1 — Dominio

La prima fase è stata dedicata alla definizione degli oggetti principali del gioco e delle loro relazioni.

Sono stati introdotti concetti come:

Partita
CampoDiBattaglia
Tank
TankPlayer
Tank controllati dall'AI
Posizione
Direzione
Ostacolo
Proiettile

In questa fase il gioco non aveva ancora una vera interfaccia grafica.

Lo scopo era far funzionare prima il modello.


Iterazione 2 — Movimento

È stato introdotto il movimento dei Tank.

Una direzione è rappresentata attraverso uno spostamento lungo gli assi X e Y.

Questo ha permesso di gestire sia movimenti cardinali sia diagonali.

Sono stati inoltre introdotti i limiti del campo di battaglia.


Iterazione 3 — Ostacoli e collisioni

Sono stati aggiunti ostacoli generati all'interno del campo.

Successivamente è stato necessario impedire ai Tank di:

  • uscire dai limiti;
  • attraversare gli ostacoli;
  • attraversare altri Tank.

Le prime verifiche basate esclusivamente sulle coordinate sono state progressivamente sostituite da collisioni tra aree rettangolari.

Questo ha portato alla creazione di una logica comune per verificare l'intersezione tra due entità.


Iterazione 4 — Proiettili

È stata introdotta la classe Proiettile.

Ogni proiettile possiede informazioni come:

  • posizione;
  • direzione;
  • Tank che lo ha sparato;
  • eventuale Tank colpito.

Il proiettile viene aggiornato progressivamente durante il game loop fino a quando:

  • colpisce un Tank;
  • colpisce un ostacolo;
  • raggiunge i limiti del campo.

Iterazione 5 — Vite e fine della partita

Ai Tank sono state associate delle vite.

Quando un proiettile colpisce un Tank viene applicato il danno e, quando le vite raggiungono zero, il Tank viene eliminato.

Sono quindi state introdotte le condizioni di:

  • vittoria;
  • sconfitta;
  • fine partita.

Iterazione 6 — Intelligenza artificiale

I Tank nemici sono stati dotati di un comportamento autonomo.

Inizialmente ogni aggiornamento poteva provocare direttamente una nuova azione.

Questo rendeva però il comportamento poco naturale.

La logica è stata quindi separata tra:

decisione dell'AI
        ↓
eseguita periodicamente

movimento corrente
        ↓
eseguito ad ogni tick

In questo modo un Tank può scegliere una direzione e continuare a muoversi fino alla decisione successiva.


Iterazione 7 — Game Loop

È stato introdotto un ciclo di aggiornamento continuo della partita.

Ad ogni tick vengono aggiornati elementi come:

  • movimento dei Tank nemici;
  • movimento dei proiettili;
  • collisioni;
  • stato della partita.

La frequenza delle decisioni dell'AI è invece indipendente dalla frequenza generale degli aggiornamenti.


Iterazione 8 — Interfaccia Swing

Solo dopo aver costruito gran parte della logica è stata aggiunta una GUI utilizzando Java Swing.

La GUI legge lo stato del dominio e lo rappresenta graficamente.

Sono stati aggiunti:

  • sprite dei Tank;
  • ostacoli;
  • proiettili;
  • vite del player;
  • numero di nemici;
  • schermata di vittoria;
  • schermata di sconfitta.

Gli sprite vengono inoltre ruotati in base alla Direzione dell'entità.


Iterazione 9 — Input e Command Pattern

Per evitare di legare direttamente gli eventi della tastiera alle operazioni della partita è stato introdotto il Command Pattern.

È presente un'interfaccia Comando, dalla quale derivano operazioni come:

  • movimento;
  • sparo.

Una FabbricaDiComandi interpreta i tasti premuti e genera i comandi appropriati.

La gestione di un insieme di tasti premuti permette inoltre di combinare più input contemporaneamente, ad esempio:

W + D

per il movimento diagonale, oppure:

W + SPACE

per muoversi e sparare.

Durante il refactoring, invece di mantenere una classe comando per ogni possibile direzione, è stato introdotto un unico comando di movimento parametrizzato da una Direzione.


Iterazione 10 — Storico degli oggetti

Quando un oggetto viene rimosso dal campo di battaglia, alcune informazioni utili per analizzare la partita andrebbero perse.

Per questo sono state introdotte collezioni storiche.

Ad esempio:

proiettili attivi
+
storico dei proiettili

Tank attivi
+
storico dei Tank

Lo storico permette di mantenere informazioni anche sugli oggetti che non fanno più parte dello stato corrente del gioco.

Questo introduce una distinzione importante tra:

stato corrente

e:

storia della partita

Iterazione 11 — Statistiche

Utilizzando lo storico vengono calcolate alcune statistiche della partita.

Sono state utilizzate anche strutture dati come Map per rappresentare informazioni quali:

Tank → numero di proiettili sparati

e:

Proiettile → Tank colpito

Le statistiche possono essere mostrate in console sotto forma di report.


Iterazione 12 — Persistenza

Come ultimo passo è stata introdotta una forma molto semplice di persistenza.

Il report della partita può essere scritto su un file di testo:

statistiche.txt

utilizzando le API Java NIO:

Path
Files

Questo passaggio è volutamente semplice e rappresenta un possibile punto di partenza per sistemi di persistenza più complessi.


Controlli

Tasto Azione
W Movimento verso l'alto
A Movimento verso sinistra
S Movimento verso il basso
D Movimento verso destra
SPACE Sparo

È possibile premere più tasti contemporaneamente.


Tecnologie utilizzate

  • Java
  • Java Swing
  • Java Collections Framework
  • Java NIO
  • JUnit
  • Git

Scopo didattico

Tank Game può essere utilizzato anche come esercizio da ricostruire da zero.

Il consiglio è di non partire immediatamente dalla GUI.

Una possibile strategia è:

1. definire il dominio;
2. disegnare il modello;
3. identificare responsabilità e relazioni;
4. implementare una funzionalità alla volta;
5. testare il dominio;
6. introdurre il game loop;
7. aggiungere la GUI solamente dopo;
8. rifattorizzare quando emergono nuovi problemi.

Partire dal modello permette di evitare che tutta la logica del gioco finisca direttamente negli eventi della GUI.

L'obiettivo dell'esercizio non dovrebbe essere semplicemente ottenere il gioco funzionante, ma chiedersi continuamente:

Perché questa responsabilità appartiene a questa classe?

Quale oggetto possiede le informazioni necessarie?

Questa relazione è davvero necessaria?

Posso estendere questa parte senza modificare tutto il resto?

Idee per esercitarsi ed estendere il progetto

Una volta completata la versione base, il progetto può essere utilizzato come punto di partenza per ulteriori esercizi di progettazione.

Nuovi tipi di proiettile

Attualmente esiste un tipo principale di proiettile.

Un possibile esercizio consiste nel progettare una gerarchia con più tipi, ad esempio:

Proiettile
├── ProiettileNormale
├── ProiettilePesante
└── ProiettileVeloce

con caratteristiche differenti come:

  • danno;
  • velocità;
  • dimensione;
  • comportamento.

Questo permette di esercitarsi con:

  • ereditarietà;
  • polimorfismo;
  • overriding.

Nuovi tipi di Tank

È possibile introdurre differenti categorie di nemici:

Tank
├── TankPlayer
├── TankNemicoBase
├── TankNemicoVeloce
└── TankNemicoPesante

Ogni tipo potrebbe implementare comportamenti differenti.

Questo può diventare un esercizio per ragionare sulla separazione tra:

tipo di Tank

e:

strategia utilizzata dall'AI

Strategie di AI differenti

Un'estensione interessante consiste nel sostituire il comportamento casuale con strategie differenti.

Ad esempio:

  • movimento casuale;
  • inseguimento del player;
  • fuga;
  • mantenimento della distanza;
  • pattugliamento;
  • comportamento aggressivo.

Questo può diventare un buon punto di partenza per sperimentare ulteriori pattern di progettazione.


Database

La persistenza su file può essere sostituita con un database.

Un possibile esercizio consiste nel salvare:

  • data della partita;
  • durata;
  • esito;
  • Tank partecipanti;
  • proiettili sparati;
  • colpi a segno;
  • statistiche finali.

In questo modo il progetto può essere esteso introducendo:

  • JDBC;
  • SQL;
  • progettazione dello schema relazionale;
  • repository;
  • persistenza delle statistiche.

Il database potrebbe inoltre permettere di visualizzare statistiche aggregate su più partite.


Statistiche più avanzate

Utilizzando le informazioni già raccolte è possibile aggiungere:

  • precisione di ogni Tank;
  • numero di colpi ricevuti;
  • danni inflitti;
  • danni subiti;
  • numero di eliminazioni;
  • durata media delle partite;
  • storico delle vittorie e delle sconfitte.

Livelli e mappe

Il CampoDiBattaglia può essere evoluto per supportare:

  • livelli differenti;
  • mappe caricate da file;
  • quantità differenti di nemici;
  • ostacoli predefiniti;
  • difficoltà crescente.

Separazione ulteriore tra dominio e interfaccia

Un altro possibile esercizio consiste nel sostituire Swing mantenendo invariato il più possibile il dominio.

Ad esempio potrebbe essere realizzata:

  • una nuova GUI;
  • una versione console;
  • una diversa tecnologia grafica.

Questo permette di verificare quanto il dominio sia realmente indipendente dalla rappresentazione grafica.


Filosofia del progetto

Questo repository non vuole rappresentare un motore di gioco completo né un esempio di architettura definitiva.

È principalmente una palestra di progettazione software.

L'idea è costruire una prima soluzione semplice, far emergere i problemi attraverso nuovi requisiti e successivamente rifattorizzare il modello.

Il valore del progetto non è quindi solamente nel risultato finale, ma soprattutto nel percorso:

problema
   ↓
prima soluzione
   ↓
nuovo requisito
   ↓
limite della soluzione
   ↓
refactoring
   ↓
nuova progettazione

È proprio questo processo che permette di esercitarsi concretamente nell'analisi, nella progettazione orientata agli oggetti e nell'assegnazione delle responsabilità.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages