Una esecuzione di un generico gioco del bowling è formato da una serie di frames, ognuno dei quali è composto da un certo numero di lanci. Ogni lancio è caratterizzato dal numero di birilli abbattuti.
Dunque secondo la regola che un frame generico è composto di due lanci, la sequenza di lanci {5,5}, che significa aver abbattuto 5 birilli al primo lancio e 5 al secondo, è un frame valido, mentre la sequenza {10,1} non lo è per due ragioni: una perché viene superato il totale di 10, e l'altra perché se il primo lancio è strike, il frame non può essere composto di un secondo lancio.
Quindi ogni potenziale frame è dotato di una composizione di vincoli che consentono di distinguere se il frame è valido o meno.
Come giò detto, una regola di validità è che il totale della sequenza di lanci per frame non è superiore a 10. ed un'altra è che un frame che abbia 10 al primo lancio, non ha un secondo lancio.
Tali regole vengono indicate nel codice nel seguente modo, attraverso la tecnica dei delegates. Ho definito come Constraint una funzione: che dato un frame restituisce un boolean.
Constraint sumOfAllRollMustbeLessThanTen = (x => x.Rolls.Sum() <= 10);
Constraint ifFirstRollIsTenThanTheFrameIsOver = (x => (!(x.Rolls[0] == 10) || x.Rolls.Count == 1));
Constraint ifFirstRollIsLessThanTenThenThereIsAnotherRollInTheFrame =
(x => (!(x.Rolls[0] < 10) || x.Rolls.Count == 2));
La factory, che costruisce ogni particolare bowling iniettandovi le relative regole, è il punto di partenza del kata del bowling, e compone la variante terrestre del bowling iniettando le regole di validità attraverso la chiamata: SetConstraintForFrame() cui viene passato il Constraint (dotato di una descrizione) e dell'indice di un frame.
Ciò significa che è possibile avere constraint diversi per ogni frame.
Infatti nel bowling "terrestre" i frames che vanno da 0 a 9 rispettano gli stessi constraint descritti in alto, mentre il decimo frame ha dei vincoli diversi: può essere composto fino a tre lanci, e solo se il punteggio di ognuno di essi è pari a 10.
Ciò si descrive con i seguenti constraints:
Constraint sumRollsNoHigherThanThirty = x => x.Rolls.Sum() <= 30;
Constraint ifFirstRollIsTenThanThereIsAtLeastAnotherRoll = x => !(x.Rolls[0]==10)||x.Rolls.Count > 1;
Constraint ifSecondRollIsTenThenThereIsAnotherRoll =
x => (!(x.Rolls.Count > 1 && x.Rolls[1] == 10) || x.Rolls.Count == 3);
(vedere nella factory)
Notare che viene usata la regola logica:
if A then B equivalente a !A || B
Oltre a determinare la validità di ogni frame, è necessario anche un modo per determinare le
regole che servono per calcolare il punteggio ed il bonus.
Solitamente il bonus è calcolato in funzione degli altri frames.
Per esempio la regola dello Spare restituisce come bonus il valore del lancio successivo al frame attuale, mentre la regola dello Strike restituisce come bonus il valore dei due lanci successivi.
Le diverse regole per ogni frame vengono valutate in ordine, e la condizione di Break indicata nella regola indica se è necessario continuare a valutare le regole successive.
Questo per evitare, per esempio, che uno Strike venga contato anche come Spare, cosa che potrebbe essere, visto che la definizione di Strike (primi 10 birilli abbattuti), è compatibile con la definizione di Spare (la somma dei due lanci abbatte 10 birilli).
Infine vi è la regola per calcolare il punteggio per frame, al quale sarà sommato il bonus, per determinare l'effettivo punteggio totale.
Il punteggio per frame è il semplice totale dei birilli abbattuti per ogni lancio all'interno del frame.
Un dilemma ancora da sciogliere è se i test unitari debbano allocare i frames o i singoli lanci.
Nel dubbio sono stati lasciati per il momento i test in entrambe le modalità.
Per esempio c'è il seguente test che, allocando esplicitamente i frames, testa il caso di giocate tutte a punteggio zero:
[Test]
public void TestAllZeroes()
{
Frame frame = new Frame(0,0);
for (int i = 0; i < 10; i++)
{
terrestrialGame.AddFrame(frame);
}
Assert.AreEqual(0, terrestrialGame.Score());
}
mentre il seguente è l'equivalente in termini di singoli lanci:
[Test]
public void TestAllZeroesByRolling()
{
for (int i=0;i<20;i++)
{
terrestrialGame.Roll(0);
}
Assert.AreEqual(0,terrestrialGame.Score());
}
La possibilità di allocare il frame è essenziale per testare i vincoli, ma una volta esposta la funzionalità e testata, è plausibile pensare di esporre al client solo il metodo "roll" e nascondere quello che si basa sull'esporre il frame, e dunque rendere il metodo che alloca il frame da pubblico a privato.
Questo significherebbe eliminare i test che si basano sui frames (accogliendo il punto di vista che i metodi privati non debbano essere testati direttamente).
Nella variante del "bowling marziano", che consiste in tre frames, ognuno costituito fino a tre lanci, ogni strike viene premiato con il risultato dell'ultimo frame.
Nessun commento:
Posta un commento