Init form Local
- Puzzle8 already implemented - Tris started - Empty Main
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -396,3 +396,6 @@ FodyWeavers.xsd
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
|
||||
# CUSTOM
|
||||
target
|
||||
BIN
ProgettoImplementativo_A_22-23.pdf
Normal file
BIN
ProgettoImplementativo_A_22-23.pdf
Normal file
Binary file not shown.
16
pom.xml
Normal file
16
pom.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>net.berack.upo</groupId>
|
||||
<artifactId>upo-ai</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>1.17</maven.compiler.source>
|
||||
<maven.compiler.target>1.17</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
8
src/main/java/net/berack/upo/ai/Main.java
Normal file
8
src/main/java/net/berack/upo/ai/Main.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package net.berack.upo.ai;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
}
|
||||
}
|
||||
159
src/main/java/net/berack/upo/ai/problem1/AStar.java
Normal file
159
src/main/java/net/berack/upo/ai/problem1/AStar.java
Normal file
@@ -0,0 +1,159 @@
|
||||
package net.berack.upo.ai.problem1;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Una classe che risolve problemi utilizzando l'algoritmo A*.
|
||||
* @author Berack96
|
||||
* @param State la classe degli stati in cui si trova il problema da risolvere
|
||||
* @param Action la classe di azioni che si possono compiere da uno stato e l'altro
|
||||
*/
|
||||
public class AStar<State, Action> {
|
||||
|
||||
private Function<State, Action[]> actions;
|
||||
private BiFunction<State, Action, State> transition;
|
||||
|
||||
private BiFunction<State, State, Integer> heuristic;
|
||||
private TriFunction<State, State, Action, Integer> cost;
|
||||
|
||||
/**
|
||||
* Crea una istanza dell'algoritmo A* con una funzione di azioni possibili da compiere dato uno stato
|
||||
* e una funzione di transizione che dato uno stato permette di raggiungerne un'altro tramite un'azione.
|
||||
*
|
||||
* @param actions la funzione che da uno stato permette di vedere le possibili azioni da compiere
|
||||
* @param transition la funzione di tansizione che ci permette di raggiungere uno stato da un'altro tramite un'azione
|
||||
*/
|
||||
public AStar(Function<State, Action[]> actions, BiFunction<State, Action, State> transition) {
|
||||
this.actions = Objects.requireNonNull(actions);
|
||||
this.transition = Objects.requireNonNull(transition);
|
||||
|
||||
this.cost = (s1,s2, a) -> 1;
|
||||
this.heuristic = (s1,s2) -> 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permette di impostare la funzione di costo che indica, facendo un'azione su uno stato e raggiungendone un'altro, quanto devo spendere, ovvero il costo.
|
||||
* Di default la funzione di costo equivale sempre ad 1
|
||||
* @param cost la funzione di costo
|
||||
* @return se stesso in modo da poter concatenare i vari set (un pò come una factory)
|
||||
*/
|
||||
public AStar<State, Action> setCost(TriFunction<State, State, Action, Integer> cost) {
|
||||
this.cost = Objects.requireNonNull(cost);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permette di impostare una funzione di transizione che indica, facendo un'azione su uno stato, a quale stato possiamo arrivare.
|
||||
* @param transition la funzione di transizione
|
||||
* @return se stesso in modo da poter concatenare i vari set (un pò come una factory)
|
||||
*/
|
||||
public AStar<State, Action> setTransition(BiFunction<State, Action, State> transition) {
|
||||
this.transition = Objects.requireNonNull(transition);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permette di impostare una funzione euristica che indica, prendendo uno stato e uno stato goal, la distanza di costo approssimata per arrivare al goal
|
||||
* @param heuristic la funzione euristica
|
||||
* @return se stesso in modo da poter concatenare i vari set (un pò come una factory)
|
||||
*/
|
||||
public AStar<State, Action> setHeuristic(BiFunction<State, State, Integer> heuristic) {
|
||||
this.heuristic = Objects.requireNonNull(heuristic);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permette di impostare una funzione che indica, a partire da uno stato, quali sono le azioni che posso svolgere
|
||||
* @param actions la funzione di azioni
|
||||
* @return se stesso in modo da poter concatenare i vari set (un pò come una factory)
|
||||
*/
|
||||
public AStar<State, Action> setActions(Function<State, Action[]> actions) {
|
||||
this.actions = Objects.requireNonNull(actions);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permette di risolvere il problema dopo aver indicato le varie funzioni.
|
||||
* In input viene richiesto uno stato iniziale e uno stato goal da raggiungere dallo stato corrente.
|
||||
* La funzione utilizzata per il raggiungimento dello stato goal è equals(), quindi è richiesto che
|
||||
* la classe State abbia implementato correttamente la funzione equals().
|
||||
*
|
||||
* @param initial lo stato corrente
|
||||
* @param goal lo stato da raggiungere
|
||||
* @return una sequenza di azioni per poter raggiungere lo stato goal oppure null se non è possibile
|
||||
*/
|
||||
public List<Action> solve(State initial, State goal) {
|
||||
Objects.requireNonNull(initial);
|
||||
Objects.requireNonNull(goal);
|
||||
|
||||
NodeState found = null;
|
||||
var list = new PriorityQueue<NodeState>();
|
||||
list.add(new NodeState(null, initial, null, 0));
|
||||
|
||||
while(list.size() > 0) {
|
||||
var current = list.poll();
|
||||
if(current.state.equals(goal)) {
|
||||
found = current;
|
||||
break;
|
||||
}
|
||||
|
||||
for(var action : this.actions.apply(current.state)) try {
|
||||
|
||||
var next = this.transition.apply(current.state, action);
|
||||
|
||||
var cost = this.cost.apply(current.state, next, action);
|
||||
var dist = this.heuristic.apply(next, goal);
|
||||
|
||||
list.add(new NodeState(current, next, action, current.cost + cost + dist));
|
||||
|
||||
} catch (Exception ignore) {}
|
||||
}
|
||||
|
||||
if(found == null) return null;
|
||||
|
||||
var path = new ArrayList<Action>();
|
||||
while(found != null) {
|
||||
path.add(found.action);
|
||||
found = found.parent;
|
||||
}
|
||||
|
||||
Collections.reverse(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Classe privata per mantenere i dati all'interno della PriorityQueue.
|
||||
*/
|
||||
private class NodeState implements Comparable<NodeState> {
|
||||
NodeState parent;
|
||||
State state;
|
||||
Action action;
|
||||
int cost;
|
||||
|
||||
NodeState(NodeState parent, State state, Action action, int cost) {
|
||||
this.parent = parent;
|
||||
this.state = state;
|
||||
this.action = action;
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(NodeState other) {
|
||||
return this.cost - other.cost;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interfaccia privata per poter indicare una TriFunction, ovvero una funzione che accetta in input 3 parametri
|
||||
*/
|
||||
@FunctionalInterface private interface TriFunction<T, U, V, R> {
|
||||
R apply(T t, U u, V v);
|
||||
}
|
||||
}
|
||||
231
src/main/java/net/berack/upo/ai/problem1/Puzzle8.java
Normal file
231
src/main/java/net/berack/upo/ai/problem1/Puzzle8.java
Normal file
@@ -0,0 +1,231 @@
|
||||
package net.berack.upo.ai.problem1;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Classe utilizzata per la rappresentazione del problema delle 8 tessere.
|
||||
* In essa ci sono dei metodi base per il modello del problema tra cui getter e setter.
|
||||
* Oltre a questi ci sono dei metodi per la risoluzione del problema sia manuale,
|
||||
* che automatico tramite l'algoritmo A*
|
||||
*
|
||||
* @author Berack96
|
||||
*/
|
||||
public class Puzzle8 implements Iterable<Integer> {
|
||||
public static final int LENGTH = 3;
|
||||
|
||||
/**
|
||||
* Possibili movimenti della tessera "vuota"
|
||||
*/
|
||||
public enum Move {
|
||||
UP,
|
||||
DOWN,
|
||||
LEFT,
|
||||
RIGHT
|
||||
}
|
||||
|
||||
private int[] puzzle = new int[LENGTH * LENGTH];
|
||||
private int blank = 0;
|
||||
|
||||
/**
|
||||
* Genera una nuova istanza del problema con le tessere posizionate in modo casuale.
|
||||
*/
|
||||
public Puzzle8() {
|
||||
var values = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8);
|
||||
Collections.shuffle(values);
|
||||
|
||||
for(var i = 0; i < this.puzzle.length; i++) {
|
||||
this.puzzle[i] = values.get(i).intValue();
|
||||
if(this.puzzle[i] == 0) this.blank = i;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera una nuova istanza del problema con le tessere posizionate come il
|
||||
* problema passato in input e dopo aver eseguito la mossa indicata.
|
||||
*
|
||||
* @param original il puzzle di partenza che si vuole copiare
|
||||
* @param move la mossa da eseguire
|
||||
*
|
||||
* @throws UnsupportedOperationException nel caso la mossa non sia disponibile.
|
||||
*/
|
||||
public Puzzle8(Puzzle8 original, Move move) {
|
||||
Arrays.copyOf(original.puzzle, this.puzzle.length);
|
||||
this.blank = original.blank;
|
||||
|
||||
this.move(move);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inizializza il problema con gli interi passati.
|
||||
* L'array inserito deve essere di 9 valori dato che la matrice è di 3x3.
|
||||
* I valori saranno posizionati a tre a tre nella matrice, quindi i primi 3 saranno nella
|
||||
* prima riga, i secondi tre nella seconda e i terzi saranno nella terza riga.
|
||||
* Nel caso in cui vengano passati meno valori una eccezione verrà sollevata.
|
||||
* Gli elementi inseriti devono essere UNICI e compresi tra 0 e 8 estremi inclusi.
|
||||
*
|
||||
* @param values i valori a cui inizializzare il problema.
|
||||
* @throws IllegalArgumentException nel caso in cui
|
||||
*/
|
||||
public Puzzle8(int...values) {
|
||||
if(values == null || values.length != LENGTH*LENGTH)
|
||||
throw new IllegalArgumentException("The size of the array must be " + LENGTH*LENGTH);
|
||||
|
||||
var check = new int[LENGTH * LENGTH];
|
||||
Arrays.copyOf(values, values.length);
|
||||
|
||||
for(var i = 0; i < this.puzzle.length; i++) {
|
||||
var curr = this.puzzle[i];
|
||||
var ok = true;
|
||||
|
||||
ok = (curr >= 0 && curr < check.length);
|
||||
if(ok) {
|
||||
if(curr == 0) this.blank = i;
|
||||
|
||||
check[curr] += 1;
|
||||
ok = (check[curr] == 1);
|
||||
}
|
||||
|
||||
if(!ok) throw new IllegalArgumentException("The input values must be UNIQUE integers between 0 and " + (LENGTH*LENGTH -1) + " inclusive");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Indica la grandezza della tavoletta che contiene le tessere.
|
||||
* Essa è una matrice 3x3.
|
||||
*
|
||||
* @see #LENGTH
|
||||
* @return la grandezza del lato della matrice
|
||||
*/
|
||||
public int size() {
|
||||
return LENGTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permette di ricevere il valore della tessera posizionata nella zona richiesta.
|
||||
* La zona è indicata come una matrice 3x3 dove la prima zona si trova nelle coordinate
|
||||
* 0x0 e l'ultima nella zona 2x2.
|
||||
* Il valore varia da 1 a 8 ed il valore della tessera "vuota" equivale a 0.
|
||||
*
|
||||
* @param x la prima coordinata, i possibili valori sono 0, 1, 2
|
||||
* @param y la seconda coordinata, i possibili valori sono 0, 1, 2
|
||||
* @return il valore della tessera nelle coordinate selezionate
|
||||
*/
|
||||
public int get(int x, int y) {
|
||||
return puzzle[x * LENGTH + y];
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera un array di mosse disponibili a partire dalla configurazione corrente.
|
||||
* Questo metodo esiste dato che non tutte le mosse sono disponibili in tutti i casi.
|
||||
* per esempio se mi trovo nell'angolo in basso a dx potrò fare solo
|
||||
* le mosse UP e LEFT.
|
||||
*
|
||||
* @return un array di mosse disponibili
|
||||
*/
|
||||
public Move[] availableMoves() {
|
||||
return switch(this.blank) {
|
||||
case 0 -> new Move[] { Move.DOWN, Move.RIGHT };
|
||||
case 1 -> new Move[] { Move.DOWN, Move.LEFT, Move.RIGHT };
|
||||
case 2 -> new Move[] { Move.DOWN, Move.LEFT };
|
||||
case 3 -> new Move[] { Move.UP, Move.DOWN, Move.RIGHT };
|
||||
case 4 -> new Move[] { Move.UP, Move.DOWN, Move.LEFT, Move.RIGHT };
|
||||
case 5 -> new Move[] { Move.UP, Move.DOWN, Move.LEFT };
|
||||
case 6 -> new Move[] { Move.UP, Move.RIGHT };
|
||||
case 7 -> new Move[] { Move.UP, Move.LEFT, Move.RIGHT };
|
||||
case 8 -> new Move[] { Move.UP, Move.LEFT };
|
||||
default -> new Move[] {};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Indica se la mossa inserita sia una mossa valida, dato che non tutte le mosse sono disponibili in tutti i casi.
|
||||
* per esempio se mi trovo nell'angolo in basso a dx potrò fare solo le mosse UP e LEFT.
|
||||
* @param move la mossa da testare
|
||||
* @return VERO se la mossa è valida altrimenti falso
|
||||
*/
|
||||
public boolean isValidMove(Move move) {
|
||||
return switch(move) {
|
||||
case UP -> this.blank >= LENGTH;
|
||||
case DOWN -> this.blank <= 2*LENGTH - 1;
|
||||
case LEFT -> this.blank % LENGTH != 0;
|
||||
case RIGHT -> this.blank % LENGTH != LENGTH - 1;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Muove la tessera "vuota" in una delle direzioni indicate.
|
||||
* Essa viene scambiata con una delle tessere adiacenti.
|
||||
*
|
||||
* Nota: non tutte le mosse sono disponibili in tutti i casi.
|
||||
* per esempio se mi trovo nell'angolo in basso a dx potrò fare solo
|
||||
* le mosse UP e LEFT.
|
||||
*
|
||||
* @see #availableMoves()
|
||||
* @see #isValidMove(Move)
|
||||
* @see #solve()
|
||||
*
|
||||
* @param move
|
||||
* @throws UnsupportedOperationException nel caso in cui la mossa inserita non sia valida
|
||||
*/
|
||||
public void move(Move move) {
|
||||
if(!this.isValidMove(move))
|
||||
throw new UnsupportedOperationException("Move not available");
|
||||
|
||||
var other = switch(move) {
|
||||
case UP -> this.blank - LENGTH;
|
||||
case DOWN -> this.blank + LENGTH;
|
||||
case LEFT -> this.blank - 1;
|
||||
case RIGHT -> this.blank + 1;
|
||||
};
|
||||
|
||||
this.puzzle[this.blank] = this.puzzle[other];
|
||||
this.puzzle[other] = 0;
|
||||
this.blank = other;
|
||||
}
|
||||
|
||||
/**
|
||||
* Usa l'algoritmo A* per la soluzione del problema
|
||||
* @param goal lo stato goal in cui arrivare
|
||||
* @return una sequenza di mosse per poter risolvere il problema o null se non risolvibile
|
||||
*/
|
||||
public List<Move> solve(Puzzle8 goal) {
|
||||
var aStar = new AStar<Puzzle8, Move>(Puzzle8::availableMoves, (p, m) -> new Puzzle8(p, m))
|
||||
.setHeuristic((p1, p2) -> {
|
||||
var sum = 0;
|
||||
var n = p1.puzzle.length;
|
||||
var index = new int[n];
|
||||
|
||||
for(var i = 0; i < n; i++) index[p1.puzzle[i]] = i;
|
||||
for(var i = 0; i < n; i++) {
|
||||
var curr = p2.puzzle[i];
|
||||
var i2 = index[curr];
|
||||
|
||||
sum += Math.abs((i / LENGTH) - (i2 / LENGTH)) + Math.abs((i % LENGTH) - (i2 % LENGTH));
|
||||
}
|
||||
return sum;
|
||||
});
|
||||
|
||||
return aStar.solve(this, Objects.requireNonNull(goal));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if(!obj.getClass().isInstance(this)) return false;
|
||||
return Arrays.equals(((Puzzle8) obj).puzzle, this.puzzle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Integer> iterator() {
|
||||
return new Iterator<Integer>() {
|
||||
int current = 0;
|
||||
@Override public boolean hasNext() { return current < puzzle.length; }
|
||||
@Override public Integer next() { return puzzle[current++]; }
|
||||
};
|
||||
}
|
||||
}
|
||||
29
src/main/java/net/berack/upo/ai/problem2/Tris.java
Normal file
29
src/main/java/net/berack/upo/ai/problem2/Tris.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package net.berack.upo.ai.problem2;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Tris {
|
||||
public static final int LENGTH = 3;
|
||||
|
||||
/**
|
||||
* Possibili stati delle zone
|
||||
*/
|
||||
public enum State {
|
||||
EMPTY,
|
||||
VALUE_X,
|
||||
VALUE_O
|
||||
}
|
||||
|
||||
private State[] tris = new State[LENGTH * LENGTH];
|
||||
|
||||
/**
|
||||
* Crea una nuova istanza del gioco con tutti gli spazi vuoti
|
||||
*/
|
||||
public Tris() {
|
||||
Arrays.fill(tris, State.EMPTY);
|
||||
}
|
||||
|
||||
public Tris(Tris current, State state, int x, int y) {
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user