Per realizzare l’ambiente del mondo del wumpus in cui l’agente si deve muovere, si può utilizzare il simulatore contenuto nel file wumpus_world (notare che questo richiede anche il file utils).
Il simulatore fornisce un predicato
execute(Action, Percept)che esegue l’azione Action e restituisce la percezione Percept.
L’azione è una fra:
goforward: muoviti di un quadrato nella direzione corrente
turnleft: gira di 90 gradi a sinistra
turnright: gira di 90 gradi a destra
grab: prendi l’oro
shoot: tira una freccia nella direzione corrente (c’è solo una freccia)
climb: se nella posizione (1,1), esci dalla caverna
La percezione è una lista di 5 elementi:
[Stench (Fetore), Breeze (Brezza), Glitter (Luccichio), Bump (Botta), Scream (Grido)]Il simulatore restituisce le percezioni sotto forma di yes o no.
Per creare un nuovo mondo occorre usare il predicato
initialize(World, Percept)dove World è o l’atomo fig62 (la Figura 6.2 del Russell e Norvig) oppure l’atomo random, per generare un mondo random.
I mondi creati hanno per default dimensione 4x4, ma è facile modificare questa e altri parametri, come le probabilità usate per generare i mondi random, modificando i relativi fatti nel file wumpus_world.pl.
Per comodità, il modulo wumpus fornisce anche un predicato:
display_world che stampa lo stato del mondo
AGENTE
Il simulatore conosce lo stato complessivo del mondo, mentre l’agente conosce solo ciò che sente man mano attraverso le percezioni. Inizialmente l’agente conosce solo le percezioni nella posizione (1,1) e per acquisire altre informazioni deve spostarsi in altre celle della griglia.
Stato dell’agente
L’agente deve mantenere un proprio stato, contenente tutte le
informazioni che via via vengono acquisite. Lo stato dovrà essere
aggiornato con le nuove percezioni ogni volta che l’agente esegue un’azione.
L’informazione deve essere rappresentata in modo da consentire di fare
agevolmente ragionamenti del tipo di quelli descritti nel Russell e Norvig.
Per rappresentare lo stato e per ragionare si suggeriscono due strade possibili:
Azioni primitive
Si suggerisce di definire un predicato
perform(Action, ...)che esegue le azioni primitive elencate sopra. Questo predicato dovrà, oltre a chiamare la execute del simulatore, aggiornare lo stato interno dell’agente in base all’azione eseguita (ad esempio se l’agente esegue una goforward deve modificare la propria posizione) ed alle percezioni ricevute dal simulatore. Gli altri parametri della perform, oltre ad Action, dipendono da come si decide di rappresentare lo stato. Ad esempio, se lo stato è rappresentato con dei fatti, non servono parametri, perché la modifica dello stato viene fatta con assert e retract.
Strategie (azioni composte)
Usando le azioni primitive, si possono definire delle azioni composte (procedure) che specificano delle strategie per raggiungere l’obiettivo. Una strategia molto semplice è quella di esplorare le celle sicure (OK nella terminologia del libro) fino a quando si trova l’oro o non ci sono più celle sicure visitabili. In quest’ultimo caso si deve rischiare una mossa che può essere fatale oppure cercare di utilizzare la freccia per uccidere il wumpus. Se si trova l’oro, si deve tornare indietro con un percorso minimo. In questa fase può essere utile usare un algoritmo di ricerca, in particolare quello di iterative deepening, che consente di trovare il cammino minimo. Questo algoritmo può essere utile anche nella fase di esplorazione, per spostarsi nella cella sicura più vicina a quella in cui ci si trova.
NOTA. E’ consigliabile fare delle ipotesi semplificative, in
particolare assumendo che l’agente conosca la dimensione del mondo. In
questo caso, se l’agente non sbaglia a ragionare, non serve la percezione
di Bump. Analogamente in una prima fase si può trascurare l’azione
di shoot.
Per realizzare lo stato basato su vincoli booleani, si può utilizzare il file vincoli. Il file fornisce un predicato
init_constraints(S,B,W,P,N)che restituisce 4 matrici S, B, W, P di variabili booleane, di dimensione NxN, che rappresentano rispettivamente i valori di Stench, Breeze, Wumpus e Pit in tutte le celle della griglia. Il predicato crea anche tutti i seguenti vincoli fra le variabili
init_constraints(S,B,W,P,4),se successivamente si esegue
elem(S,1,1,0),
elem(B,1,1,0),
elem(S,2,1,0),
elem(B,2,1,1),
elem(S,1,2,1),
elem(B,1,2,0),
elem(W,1,3,W13),si ottiene che W13 vale 1 (ossia che il Wumpus è in (1,3)) e che P31 vale 1 (ossia che in (3,1) c'è una buca).
elem(P,3,1,P31)