1.2 Hello World!
4.4.1 Codice
1.
far sì che Tavoloimplementi l’interfaccia Iterable<Card>Implementare l’interfaccia
Iterable<Card>
vuol dire essenzialmente fornire un metodoIterator<Card>
iterator(), come del resto suggerito da IDEA con Implement methods. public class Tavolo implements Iterable<Card> {private List<Card> scoperte = new ArrayList<>();
@Override
public Iterator<Card> iterator() {
return scoperte.iterator();
} // ...
}
2. completare Giocatore.turnoin maniera che ogni giocatore giochi una carta in maniera conforme alle regole del gioco
Attualmente
Giocatore.turno
sceglie una carta (aCard
, chiamandostrategia.get
⌋Cardche sceglie fra le carte che il giocatore ha in
mano
), ma non la gioca.public void turno() { assert strategia != null;
final Card aCard = strategia.getCard(mano);
//TODO GIOCARE LA CARTA rispettando le regole
}
Per giocarla secondo le regole bisogna seguire il procedimento indicato nel testo dell’eser-cizio:
se tale carta ha lo stesso valore di una sul tavolo, il giocatore pone le due carte con lo stesso valore in un mazzetto davanti a sé in modo che tutti possano vedere l’ultima “presa”. Se non c’è una carta dello stesso valore sul tavolo e invece uno dei mazzetti degli altri giocatori ha in cima una carta dello stesso valore di quella giocata, il giocatore può “rubare” il mazzetto all’avversario, sottraendolo all’avversario e ponendolo insieme alla carta giocata in cima al proprio. Altrimenti la carta viene posta insieme alle altre scoperta sul tavolo.
Cioè:
la carta è sul tavolo bisogna “prendere”: il mazzetto del giocatore aumenta di 2, in cima c’è il
Rank
giocato e la carta viene tolta dalTavolo
;la carta non è sul tavolo, ma c’è un mazzetto bisogna “rubare”: il mazzetto del gioca-tore aumenta del punteggio del
Giocatore
derubato +1 e in cima c’è ilRank
giocato;il punteggio del derubato torna a zero e il suo mazzetto vuoto;
la carta non è sul tavolo e non c’è un mazzetto la carta viene messa sul tavolo.
In Java:
public void turno() { assert strategia != null;
final Card aCard = strategia.getCard(mano);
mano.remove(aCard);
if (Partita.TAVOLO.inMostra(aCard)){
this.prendiDalTavolo(aCard);
return;
}for (Giocatore giocatore : altri) {
if (sePuoiRuba(giocatore, aCard)) return;
}
4.4 Soluzioni
Partita.TAVOLO.metti(aCard);
}
private boolean sePuoiRuba(Giocatore altro, Card aCard) { if (altro.getMazzettoTop() == aCard.getRank()){
punti += altro.cediMazzetto() + 1;
mazzettoTop = aCard.getRank();
return true;
}
return false;
}
private int cediMazzetto() { int ris = getPunti();
punti = 0;
mazzettoTop = null;
return ris;
}
private void prendiDalTavolo(Card aCard) { Partita.TAVOLO.prendi(aCard);
punti += 2;
mazzettoTop = aCard.getRank();
}
Vale la pena di notare una peculiarità della programmazione orientata agli oggetti: i due metodi (privati)
prendiDalTavolo
ecediMazzetto
sono definiti nella stessa clas-se (Giocatore
): sono due computazioni che tutti gli oggetti di tipoGiocatore
sanno svolgere. In ciascuna effettiva esecuzione del metodoturno
però, i due metodi saranno presumibilmente eseguiti su oggetti diversi (cioè due istanze della classe, con due identità distinte) perché un giocatore non ruba mai il proprio mazzetto. Quindi: in un’esecuzione diturno
in cui viene chiamatoprendiDalTavolo
,turno
eprendiDalTavolo
operano sul medesimo oggetto (come risulta evidente dall’uso esplicito di this per la chiamata diprendiDalTavolo
); in un’esecuzione diturno
in cui viene chiamato (indirettamente, tramitesePuoiRuba
)cediMazzetto
, invece,turno
ecediMazzetto
operano su oggetti distinti (nell’ipotesi che il parametro attualegiocatore
passato asePuoiRuba
si riferisca effettivamente a un altro giocatore).3. usare il pattern “Chain of Responsibility” per creare almeno 4 strategie di gioco
Per prima cosa definiamo opportunamente l’interfaccia
Strategia
. Non c’è molto da inventare, visto che è un tipo già citato inGiocatore
: dovrà come minimo esporre un metodogetCard
compatibile con l’uso inGiocatore.turno
. La “strategia” del resto serve a scegliere una carta fra quelle che si hanno in mano.public interface SelettoreCarta { Card getCard(List<Card> mano);
}
A questo punto, fra l’altro, non abbiamo più problemi in compilazione, quindi è più facile fare prove.
Possiamo ora implementare le varie strategie “atomiche” che potranno poi essere concatena-te per comporre (via “Chain of Responsibility”) la straconcatena-tegia di ogni giocatore, impostandola
con
Giocatore.setStrategia(SelettoreCarta strategia)
. Ogni strategia, meno l’ultima, avrà quindi un riferimento alla prossima, che potrà usare quando l’approccio seguito fallisce: si noti che non occorre sapere di quale strategia effettivamente si tratti (l’oggetto viene usato, ma non costruito), l’importante è che sia di tipoSelettoreCarta
e fornisca quindi un metodogetCard
.Iniziamo a implementare l’ultima, che è quella che non può fallire: tutte le “catene strategiche” dovranno terminare con questa.
public class StrategiaCartaCasuale implements SelettoreCarta {
@Override
public Card getCard(List<Card> mano) { Random random = new Random();
int i = random.nextInt(mano.size());
return mano.get(i);
} }
Poi implementiamo la strategia che, se c’è, sceglie una carta per prendere una carta sul tavolo. Questa strategia può fallire: quando non c’è una carta adatta alla presa. Bisogna quindi prevedere la conoscenza di una “prossima” strategia.
public class StrategiaCartaTavolo implements SelettoreCarta { private final SelettoreCarta next;
public StrategiaCartaTavolo(SelettoreCarta next) { this.next = next;
}
@Override
public Card getCard(List<Card> mano) { for (Card card : mano) {
if (Partita.TAVOLO.inMostra(card)) return card;
}return next.getCard(mano);
} }
Ora implementiamo la strategia che, se c’è, prende una carta per rubare un mazzetto.
Anche questa strategia può fallire. Inoltre per scegliere
getCard
non può basarsi solo sulle carte in mano (né, come prima, sulle informazioni “globali” delle carte in tavola).Bisogna quindi fornire informazioni ulteriori (per esempio un riferimento al
Giocatore
) in fase di costruzione della strategia.public class StrategiaMazzetto implements SelettoreCarta { private final Giocatore giocatore;
private final SelettoreCarta next;
public StrategiaMazzetto(Giocatore g, SelettoreCarta next) { giocatore = g;
this.next = next;
}
@Override
4.4 Soluzioni
public Card getCard(List<Card> mano) { for (Card card : mano) {
Iterator<Giocatore> altri = giocatore.getAltri();
while (altri.hasNext()) { Giocatore g = altri.next();
if (g.getMazzettoTop() == card.getRank()) { return card;
} }
}return next.getCard(mano);
}
L’ultima strategia è molto simile alla precedente (il che dovrebbe suggerire l’opportunità di un refactoring, quando avremo preso confidenza anche con l’ereditarietà).
public class StrategiaProtezione implements SelettoreCarta { private final Giocatore giocatore;
private final SelettoreCarta next;
public StrategiaProtezione(Giocatore g, SelettoreCarta next) { giocatore = g;
this.next = next;
}
@Override
public Card getCard(List<Card> mano) { for (Card card : mano) {
if (card.getRank() == giocatore.getMazzettoTop()){
return card;
} }
return next.getCard(mano);
} }
4. In Rubamazzetto creare i giocatori componendo diverse catene di strategie Giocatore carlo = new Giocatore("Carlo");
Giocatore mattia = new Giocatore("Mattia");
Giocatore violetta = new Giocatore("Violetta");
partecipanti.add(carlo);
partecipanti.add(mattia);
partecipanti.add(violetta);
carlo.setStrategia(new StrategiaProtezione(carlo, new StrategiaCartaCasuale()));
mattia.setStrategia(new StrategiaMazzetto(mattia, new StrategiaCartaCasuale()));
violetta.setStrategia(new StrategiaMazzetto(violetta, new StrategiaProtezione(violetta,
new StrategiaCartaTavolo(new StrategiaCartaCasuale()))));
Ora manca solo il TODO in Partita
.distribuisciMano. L’implementazione deve
rendere vere le post-condizioni indicate.
public void distribuisciMano(int num){
// PRE CONDIZIONI assert num <= 3;
for (int i=0; i<num && deckSize() >= giocatori.size(); i++) { distribuisciCarta();
}// POST CONDIZIONI
for (Giocatore giocatore : giocatori) {
assert giocatore.numCards() <= 3 : "non si possono avere più di tre carte in mano";
assert giocatori.get(0).numCards() == giocatore.numCards() : "non è stato dato stesso numero di carte a tutti";
⤶
⤷
assert giocatore.numCards() == 3 || deckSize() < giocatori.size() : "si possono avere meno di tre carte solo se nel mazzo non ce ne sono abbastanza per fare un altro giro";
⤶
⤷ ⤶
⤷ } }
5 Lab05: BlackJack
Il punto di partenza di questo laboratorio è disponibile all’indirizzo:
https://gitlab.com/programmazione2/lab05