Tris
- implemented Tris class - added test cases
This commit is contained in:
@@ -1,21 +1,46 @@
|
||||
package net.berack.upo.ai.problem2;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class Tris {
|
||||
/**
|
||||
* Classe che rappresenta il classico gioco del tris, dove per vincere bisogna
|
||||
* mettere il carattere che si gioca in fila di tre prima dell'avversario.
|
||||
*
|
||||
* @author Berack96
|
||||
*/
|
||||
public class Tris implements Iterable<Tris.State> {
|
||||
public static final int LENGTH = 3;
|
||||
|
||||
/**
|
||||
* Classe di appoggio per la creazione di una lista di possibili coordinate
|
||||
* da utilizzare per giocare una mossa.
|
||||
*/
|
||||
public static class Coordinate {
|
||||
public final int x;
|
||||
public final int y;
|
||||
private Coordinate(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
@Override public boolean equals(Object obj) {
|
||||
if(!(obj instanceof Coordinate)) return false;
|
||||
var curr = (Coordinate) obj;
|
||||
return this.x == curr.x && this.y == curr.y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Possibili stati delle zone
|
||||
*/
|
||||
public enum State {
|
||||
public static enum State {
|
||||
EMPTY,
|
||||
VALUE_X,
|
||||
VALUE_O
|
||||
}
|
||||
|
||||
private State[] tris;
|
||||
private State currentTurn = State.VALUE_X;
|
||||
|
||||
/**
|
||||
* Crea una nuova istanza del gioco con tutti gli spazi vuoti
|
||||
@@ -28,13 +53,12 @@ public class Tris {
|
||||
/**
|
||||
* Crea una nuova istanza del gioco a partire da quella passata in input
|
||||
* @param current l'istanza correte
|
||||
* @param state lo stato che si vuole mettere ad una cella
|
||||
* @param x la coordinata x in cui mettere lo stato
|
||||
* @param y la coordinata y in cui mettere lo stato
|
||||
* @param coord le coordinate in cui il giocatore vuole giocare la sua mossa
|
||||
*/
|
||||
public Tris(Tris current, State state, int x, int y) {
|
||||
public Tris(Tris current, Coordinate coord) {
|
||||
Arrays.copyOf(current.tris, current.tris.length);
|
||||
this.set(state, x, y);
|
||||
this.currentTurn = current.currentTurn;
|
||||
this.play(coord.x, coord.y);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,24 +68,119 @@ public class Tris {
|
||||
* @return il valore della cella
|
||||
*/
|
||||
public State get(int x, int y) {
|
||||
if(x >= LENGTH) throw new IndexOutOfBoundsException();
|
||||
return this.tris[y * LENGTH + x];
|
||||
return this.tris[this.index(x, y)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Permette di mettere lo stato scelto nella cella specificata dalle coordinate.
|
||||
* Nel caso in cui lo stato scelto dalle coordinate non risulti EMPTY
|
||||
* il metodo lancerà una eccezione.
|
||||
* @param state lo stato da impostare
|
||||
* Permette di far avanzare il gioco, facendo giocare il turno al giocatore corrente nella cella specificata dalle coordinate.
|
||||
* Nel caso in cui lo stato scelto dalle coordinate non risulti EMPTY il metodo lancerà una eccezione.
|
||||
* Una volta che si ha un vincitore questo metodo non potrà essere piu chiamato e lancerà una eccezione.
|
||||
*
|
||||
* @param x la coordinata x in cui mettere lo stato
|
||||
* @param y la coordinata y in cui mettere lo stato
|
||||
* @throws UnsupportedOperationException nel caso in cui si ha già avuto un vincitore
|
||||
*/
|
||||
public void set(State state, int x, int y) {
|
||||
if(x >= LENGTH) throw new IndexOutOfBoundsException();
|
||||
var index = y * LENGTH + x;
|
||||
public void play(int x, int y) {
|
||||
if(this.haveWinner() != State.EMPTY) throw new UnsupportedOperationException("The game has already finished!");
|
||||
if(!isPlayAvailable(x, y)) throw new IllegalArgumentException("The state to modify must be Empty!");
|
||||
|
||||
if(this.tris[index] != State.EMPTY)
|
||||
throw new IllegalArgumentException();
|
||||
this.tris[index] = Objects.requireNonNull(state);
|
||||
this.tris[this.index(x, y)] = this.currentTurn;
|
||||
this.currentTurn = switch(this.currentTurn) {
|
||||
case VALUE_X -> State.VALUE_O;
|
||||
case VALUE_O -> State.VALUE_X;
|
||||
default -> State.EMPTY;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Indica se è possibile giocare nella cella indicata.
|
||||
* Questo metodo non controlla che il gioco sia finito, ma controlla solamente se la mossa
|
||||
* indicata nei parametri è legale.
|
||||
*
|
||||
* @param x la coordinata X
|
||||
* @param y la coordinata Y
|
||||
* @return vero se la mossa può essere giocata, altrimenti falso
|
||||
*/
|
||||
public boolean isPlayAvailable(int x, int y) {
|
||||
return this.tris[this.index(x, y)] == State.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permette di avere una lista di tutte le coordinate per le mosse possibili.
|
||||
* Le coordinate sono incapsulate dentro un oggetto Coordintate in cui l'elemento
|
||||
* X corrsiponde alla coordinata X, mentre l'elemento Y corrisponde alla coordianta Y.
|
||||
* Nel caso in cui il gioco sia completato, la lista risultante sarà vuota.
|
||||
*
|
||||
* @return un array di coordinate disponibili per giocare.
|
||||
*/
|
||||
public Coordinate[] availablePlays() {
|
||||
var count = 0;
|
||||
for(var i = 0; i < this.tris.length; i++)
|
||||
if(this.tris[i] == State.EMPTY)
|
||||
count += 1;
|
||||
|
||||
var res = new Coordinate[count];
|
||||
count = 0;
|
||||
|
||||
for(var y = 0; y < LENGTH; y++)
|
||||
for(var x = 0; x < LENGTH; x++)
|
||||
if(isPlayAvailable(x, y))
|
||||
res[count++] = new Coordinate(x, y);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indica se si ha un vincitore e restituisce chi ha vinto.
|
||||
* @return EMPTY se non c'è ancora un vincitore, altrimenti restituisci il vincitore
|
||||
*/
|
||||
public State haveWinner() {
|
||||
// top left corner -> horizontal and vertical
|
||||
var state = this.tris[0];
|
||||
if(state != State.EMPTY) {
|
||||
if(this.tris[1] == state && this.tris[2] == state) return state;
|
||||
if(this.tris[3] == state && this.tris[6] == state) return state;
|
||||
}
|
||||
|
||||
// bottom right corner -> horizontal and vertical
|
||||
state = this.tris[8];
|
||||
if(state != State.EMPTY) {
|
||||
if(this.tris[7] == state && this.tris[6] == state) return state;
|
||||
if(this.tris[5] == state && this.tris[2] == state) return state;
|
||||
}
|
||||
|
||||
// central -> diagonals, horizontal and vertical
|
||||
state = this.tris[4];
|
||||
if(state != State.EMPTY) {
|
||||
if(this.tris[0] == state && this.tris[8] == state) return state;
|
||||
if(this.tris[6] == state && this.tris[2] == state) return state;
|
||||
if(this.tris[3] == state && this.tris[5] == state) return state;
|
||||
if(this.tris[1] == state && this.tris[7] == state) return state;
|
||||
}
|
||||
|
||||
return State.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcola l'indice trasformato da 2D a 1D.
|
||||
* Questo metodo serve internamente per la classe.
|
||||
* Nel caso in cui si inseriscano delle coordinate non valide lancia una eccezione.
|
||||
*
|
||||
* @param x il valore della coordinata X
|
||||
* @param y il valore della coordinata Y
|
||||
* @return il valore risultante
|
||||
*/
|
||||
private int index(int x, int y) {
|
||||
if(x >= LENGTH || y >= LENGTH) throw new IndexOutOfBoundsException();
|
||||
return x + y * LENGTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<State> iterator() {
|
||||
return new Iterator<State>() {
|
||||
int current = 0;
|
||||
@Override public boolean hasNext() { return current < tris.length; }
|
||||
@Override public State next() { return tris[current++]; }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
160
src/test/java/net/berack/upo/ai/problem2/TestTris.java
Normal file
160
src/test/java/net/berack/upo/ai/problem2/TestTris.java
Normal file
@@ -0,0 +1,160 @@
|
||||
package net.berack.upo.ai.problem2;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static net.berack.upo.ai.problem2.Tris.State.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import net.berack.upo.ai.problem2.Tris.State;
|
||||
|
||||
public class TestTris {
|
||||
|
||||
@Test
|
||||
public void testConstructor() {
|
||||
var tris = new Tris();
|
||||
for(var state : tris) assertEquals(EMPTY, state);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlay() {
|
||||
var tris = new Tris();
|
||||
tris.play(0, 0);
|
||||
|
||||
var i = 0;
|
||||
var states = new Tris.State[] {VALUE_X, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY};
|
||||
for(var state : tris) assertEquals(states[i++], state);
|
||||
|
||||
tris.play(2, 2);
|
||||
states[8] = VALUE_O;
|
||||
i = 0;
|
||||
for(var state : tris) assertEquals(states[i++], state);
|
||||
|
||||
tris.play(1, 1);
|
||||
states[4] = VALUE_X;
|
||||
i = 0;
|
||||
for(var state : tris) assertEquals(states[i++], state);
|
||||
|
||||
tris.play(0, 1);
|
||||
states[3] = VALUE_O;
|
||||
i = 0;
|
||||
for(var state : tris) assertEquals(states[i++], state);
|
||||
|
||||
tris.play(1, 0);
|
||||
states[1] = VALUE_X;
|
||||
i = 0;
|
||||
for(var state : tris) assertEquals(states[i++], state);
|
||||
|
||||
tris.play(0, 2);
|
||||
states[6] = VALUE_O;
|
||||
i = 0;
|
||||
for(var state : tris) assertEquals(states[i++], state);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAvailableActions() {
|
||||
var tris = new Tris();
|
||||
var actions = tris.availablePlays();
|
||||
|
||||
assertEquals(9, actions.length);
|
||||
for(var i = 0; i < actions.length -2; i++) {
|
||||
|
||||
for(var j = 0; j < actions.length; j++) {
|
||||
var curr = actions[j];
|
||||
assertEquals(j >= i, tris.isPlayAvailable(curr.x, curr.y));
|
||||
}
|
||||
|
||||
var array = Arrays.copyOfRange(actions, i, actions.length);
|
||||
var avail = tris.availablePlays();
|
||||
assertArrayEquals(array, avail, "Array differ at iteration " + i);
|
||||
|
||||
var curr = actions[i];
|
||||
tris.play(curr.x, curr.y);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlayException() {
|
||||
var tris = new Tris();
|
||||
tris.play(0, 0);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> tris.play(0, 0));
|
||||
|
||||
tris.play(1, 1);
|
||||
assertThrows(IllegalArgumentException.class, () -> tris.play(0, 0));
|
||||
assertThrows(IllegalArgumentException.class, () -> tris.play(1, 1));
|
||||
|
||||
tris.play(0, 2);
|
||||
assertThrows(IllegalArgumentException.class, () -> tris.play(0, 0));
|
||||
assertThrows(IllegalArgumentException.class, () -> tris.play(1, 1));
|
||||
assertThrows(IllegalArgumentException.class, () -> tris.play(0, 2));
|
||||
|
||||
tris.play(2, 1);
|
||||
assertThrows(IllegalArgumentException.class, () -> tris.play(0, 0));
|
||||
assertThrows(IllegalArgumentException.class, () -> tris.play(1, 1));
|
||||
assertThrows(IllegalArgumentException.class, () -> tris.play(0, 2));
|
||||
assertThrows(IllegalArgumentException.class, () -> tris.play(2, 1));
|
||||
|
||||
tris.play(0, 1);
|
||||
for(var i = 0; i < Tris.LENGTH; i++)
|
||||
for(var j = 0; j < Tris.LENGTH; j++) {
|
||||
final var x = i;
|
||||
final var y = j;
|
||||
assertThrows(UnsupportedOperationException.class, () -> tris.play(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWinner() {
|
||||
|
||||
// horizontal 1 line X
|
||||
var tris = new Tris();
|
||||
assertTrue(tris.haveWinner() == State.EMPTY);
|
||||
tris.play(1,0);
|
||||
assertTrue(tris.haveWinner() == State.EMPTY);
|
||||
tris.play(1,1);
|
||||
assertTrue(tris.haveWinner() == State.EMPTY);
|
||||
tris.play(0,0);
|
||||
assertTrue(tris.haveWinner() == State.EMPTY);
|
||||
tris.play(1,2);
|
||||
assertTrue(tris.haveWinner() == State.EMPTY);
|
||||
tris.play(2,0);
|
||||
assertTrue(tris.haveWinner() == State.VALUE_X);
|
||||
|
||||
// diagonal \ O
|
||||
tris = new Tris();
|
||||
assertTrue(tris.haveWinner() == State.EMPTY);
|
||||
tris.play(2,1);
|
||||
assertTrue(tris.haveWinner() == State.EMPTY);
|
||||
tris.play(1,1);
|
||||
assertTrue(tris.haveWinner() == State.EMPTY);
|
||||
tris.play(2,0);
|
||||
assertTrue(tris.haveWinner() == State.EMPTY);
|
||||
tris.play(2,2);
|
||||
assertTrue(tris.haveWinner() == State.EMPTY);
|
||||
tris.play(1,2);
|
||||
assertTrue(tris.haveWinner() == State.EMPTY);
|
||||
tris.play(0,0);
|
||||
assertTrue(tris.haveWinner() == State.VALUE_O);
|
||||
|
||||
// vertical 2 column X
|
||||
tris = new Tris();
|
||||
assertTrue(tris.haveWinner() == State.EMPTY);
|
||||
tris.play(1,0);
|
||||
assertTrue(tris.haveWinner() == State.EMPTY);
|
||||
tris.play(0,2);
|
||||
assertTrue(tris.haveWinner() == State.EMPTY);
|
||||
tris.play(1,1);
|
||||
assertTrue(tris.haveWinner() == State.EMPTY);
|
||||
tris.play(0,1);
|
||||
assertTrue(tris.haveWinner() == State.EMPTY);
|
||||
tris.play(1,2);
|
||||
assertTrue(tris.haveWinner() == State.VALUE_X);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user