/**
* Defines the board for the game
*
* @author Shashi Mittal
* @version 1.5(08-09-2002)
*/
package de.axxeed.animosy.ai;
import java.io.*;
import java.util.*;
import java.awt.*;
import javax.swing.*;
import org.apache.log4j.Logger;
import de.axxeed.animosy.model.BoardModel;
import de.axxeed.animosy.model.Constants;
import de.axxeed.animosy.model.Detective;
import de.axxeed.animosy.model.Fugitive;
import de.axxeed.animosy.model.Game;
import de.axxeed.animosy.model.GameBoard;
import de.axxeed.animosy.model.Link;
import de.axxeed.animosy.model.Manager;
import de.axxeed.animosy.model.Move;
import de.axxeed.animosy.model.Node;
public class VirtualBoard implements Comparator,Comparable, Constants
{
private static Logger log = Logger.getLogger(VirtualBoard.class);
static private final int INF=100;
static private int DEPTH=3;
static private final int WIN=200;
static private final int LOSE=-200;
static private final int NBEST=25;
static private Point pos[] = null;
private int alphabeta[];
private int currentMoves;
private static int [][]shortestDistance = null;
private int [] checkPoints;
private Detective []detectives;
private Fugitive MrX;
/**This constructor initializes the board
*/
public VirtualBoard(GameBoard board)
{
int nrOfcheckPoints = 5;
int nrOfDetectives = Manager.getOptions().getNumberOfDetectives();
currentMoves=0;
checkPoints=new int[nrOfcheckPoints];
for(int i=0;i<nrOfcheckPoints;i++)
checkPoints[i]=3+(3+Manager.getOptions().getLevel())*i;
detectives=new Detective[nrOfDetectives];
for(int i=0;i<nrOfDetectives;i++)
{
detectives[i]=new Detective(board.getDetectives()[i].getPosition());
}
MrX=new Fugitive(board.getMrX().getPosition());
int noOfNodes = BoardModel.nodesCount();
if(shortestDistance == null) {
shortestDistance=new int[noOfNodes][noOfNodes];
for(int i=0;i<noOfNodes;i++)
for(int j=0;j<noOfNodes;j++)
shortestDistance[i][j]=weight(BoardModel.getNode(i),BoardModel.getNode(j));
shortestDistance=getShortestDistanceMatrix(shortestDistance,1);
// test();
}
alphabeta=new int[DEPTH+1];
}
/**This constructor make a copy of the VirtualBoard board
*@param board the VirtualBoard whose copy has to be made
*/
private VirtualBoard(VirtualBoard board)
{
// log.debug("New virtual board ("+hashCode()+") with "+board.detectives.length+" detectives...");
this.currentMoves=board.currentMoves;
this.checkPoints=board.checkPoints;
detectives=new Detective[board.detectives.length];
for(int i=0;i<detectives.length;i++)
{
Node n=board.detectives[i].getPosition();
if(n==null) {
log.warn("No position node found for detective "+i);
}
detectives[i]=new Detective(n);
}
Node n=board.MrX.getPosition();
MrX=new Fugitive(n);
}
/**This method changes the difficulty level for the game
*@param d the required dificulty level
*/
public void setDepth(int d)
{
DEPTH=d;
}
/**This method returns the distance between the two nodes
* @param x,y the two nodes given
* @return 0 if x and y are the same nodes
* 100 if they are not adjacent nodes
* weight of the link if the two nodes are connected
*/
private int weight(Node x,Node y)
{
if(x.equals(y)) return 0;
int weight=INF;
{
Link []lk=x.getLinks();
for(int i=0;i<lk.length;i++)
{
Node n=lk[i].getToNode();
if(n.equals(y)) weight=lk[i].getType();
}
}
return weight;
}
/**This method evaluates the shortest distance between all the possible nodes
* It uses Floyd Warshalls Algorithm
*/
private int[][] getShortestDistanceMatrix(int [][]mat,int k)
{
int noOfNodes = BoardModel.nodesCount();
if (k==noOfNodes-1) return mat;
int newMat[][]=new int[noOfNodes][noOfNodes];
{
for(int i=0;i<noOfNodes;i++)
for(int j=0;j<noOfNodes;j++) {
newMat[i][j]=Math.min(mat[i][j],mat[i][k]+mat[k][j]);
}
}
k=k+1;
return getShortestDistanceMatrix(newMat,k);
}
/**Prints the initial matrix
*/
public void test()
{
int noOfNodes = BoardModel.nodesCount();
for(int i=0;i<noOfNodes;i++) {
StringBuilder buffer = new StringBuilder();
buffer.append("Node"+i).append(";");
for(int j=0;j<noOfNodes;j++) {
buffer.append(shortestDistance[i][j]).append(";");
}
System.out.println(buffer);
}
}
/**Display the map*/
public void displayMap()
{
JFrame frame=new JFrame();
ImageIcon map=new ImageIcon("map.jpg");
JLabel label=new JLabel(map);
frame.getContentPane().add(label);
frame.show();
}
/**Checks if the move to node n is legal or not
*@param n the node n to be tested
*@return true if the move is legal otherwise it returns false
*/
private boolean isLegalMove(Node to)
{
boolean canMove=true;
for(int i=0;i<detectives.length;i++)
{
Node n=detectives[i].getPosition();
if(n.getPosition()==to.getPosition()) canMove=false;
}
return canMove;
}
/**This method returns all the possible moves for a given detective
*@param i the detective index of the detective whose possible moves we want
*@return the possible moves of this detective in a TreeSet
*/
public TreeSet getDetectivePossibleMoves(int i)
{
if(!canMove(this, i))
{
detectives[i].setStaticState();
throw new IllegalArgumentException();
}
return getPossibleMoves(this, i);
}
/**This methos is used to change the position of a detective
*@param i the index of the detective whose position we want to change
*@param move the new move for this detective
*/
public void changeDetectivePosition(int i,Move move)
{
detectives[i].changePosition(BoardModel.getNode(move.getNode()),move.getType());
}
/**Checks whether the machine has won
* @return true if machine has won,otherwise false
*/
public boolean isMachineWin()
{
boolean noneCanMove=true;
for(int i=0;i<Manager.getOptions().getNumberOfDetectives();i++)
if(canMove(this, i)) noneCanMove=false;
return((currentMoves==Manager.getOptions().getNumberOfMoves())||noneCanMove);
}
/**Checks if the user has won
* @return true if the user has won,otherwise false
*/
public boolean isUserWin()
{
Link []xLinks=MrX.getPosition().getLinks();
boolean isBlocked=true;
for(int i=0;i<xLinks.length;i++)
{
Node xNode=xLinks[i].getToNode();
boolean thisIsOccupied=false;
for(int j=0;j<Manager.getOptions().getNumberOfDetectives();j++)
{
Node dNode=detectives[j].getPosition();
if(dNode.equals(xNode)) thisIsOccupied=true;
}
if(!thisIsOccupied) isBlocked=false;
}
boolean isCaptured=false;
for(int i=0;i<Manager.getOptions().getNumberOfDetectives();i++)
{
Node detNode=detectives[i].getPosition();
if(detNode.equals(MrX.getPosition())) isCaptured=true;
}
return(isBlocked||isCaptured);
}
/**This is the most important method of this class
* It returns the best possible move by calling the evaluate() method
* @return the best node posiion of MrX
*/
private Node bestMove()
{
Node n=MrX.getPosition();
Link[] lk=n.getLinks();
int score[]=new int[20];
Node possibleNodes[]=new Node[20];
int legalMoves=0;
int noOfNodes=lk.length;
for(int i=0;i<lk.length;i++)
{
if(isLegalMove(lk[i].getToNode()))
{
VirtualBoard board=new VirtualBoard(this);
board.MrX.change(lk[i].getToNode());
possibleNodes[legalMoves]=lk[i].getToNode();
score[legalMoves]=evaluateMove(board,false,0);
legalMoves++;
}
}
Node toNode=possibleNodes[0];
int max=score[0];
for(int i=0;i<legalMoves;i++)
{
if(max<score[i])
{
toNode=possibleNodes[i];
max=score[i];
}
}
return toNode;
}
/**This method evaluates the position of the Node using depth first shallow search algorithm
*@param b the initial VirtualBoard passed by the user
*@param depth the current depth of the recursion tree
*@param isMachineMove true if the next move is of the machine,else returns false
*/
private int evaluateMove(VirtualBoard b,boolean isMachineMove,int depth)
{
VirtualBoard board=new VirtualBoard(b);
if(isMachineMove)
{
Node dPos=board.MrX.getPosition();
Link []lk=dPos.getLinks();
int score[]=new int[lk.length];
int legalMoves=0;
for(int i=0;i<lk.length;i++)
{
Node newPos=lk[i].getToNode();
if (!board.isLegalMove(newPos)) continue;
board.MrX.change(newPos);
if(board.isUserWin()) score[legalMoves]=LOSE;
else if(board.isUserWin()) score[legalMoves]=WIN;
else if(depth==DEPTH) score[legalMoves]=scoreBoard(board);
else score[legalMoves]=evaluateMove(board,false,depth+1);
if(i==0) alphabeta[depth]=score[legalMoves];
if(score[legalMoves]>alphabeta[depth]) alphabeta[depth]=score[legalMoves];
if((depth>0)&&(score[legalMoves]>alphabeta[depth-1]))
return score[legalMoves];
legalMoves++;
}
int max=score[0];
for(int i=0;i<legalMoves;i++)
if(score[i]>max) max=score[i];
return max;
}
else
{
int score[]=new int[NBEST];
int legalMoves=0;
TreeSet possibleMoves=generateUserMoves(board);
for(int i=0;(i<NBEST)&&(!possibleMoves.isEmpty());i++)
{
board=(VirtualBoard)possibleMoves.first();
if(board.isUserWin()) score[legalMoves]=LOSE;
else if(board.isMachineWin()) score[legalMoves]=WIN;
else if(depth==DEPTH) score[legalMoves]=scoreBoard(board);
else score[legalMoves]=evaluateMove(board,true,depth+1);
possibleMoves.remove(board);
if(legalMoves==0) alphabeta[depth]=score[legalMoves];
if(score[legalMoves]<alphabeta[depth]) alphabeta[depth]=score[legalMoves];
if((depth>0)&&(score[legalMoves]<alphabeta[depth-1]))
return score[legalMoves];
legalMoves++;
}
int min=score[0];
for(int i=0;i<legalMoves;i++)
if(score[i]<min) min=score[i];
return min;
}
}
/**This method generates all the possible user moves for the given board.The generated
*possible moves are stored in a TreeSet and only the first n(ie the n-best boards ) are
*used for continuing the minimax tree.Thus this is done for the forward prunning
*of the branch.
*@param b the board for which the moves have to be generated
*@return all possible board positions in a TreeSet
*/
private TreeSet generateUserMoves(VirtualBoard b) {
TreeSet possibleMoves=new TreeSet();
int nrOfDetectives = Manager.getOptions().getNumberOfDetectives();
Node n[]=new Node[nrOfDetectives];
Node detNode[]=new Node[nrOfDetectives];
for(int i=0;i<nrOfDetectives;i++)
detNode[i]=b.detectives[i].getPosition();
for(int detLnk=0;detLnk<detNode[0].getLinks().length;detLnk++) {
VirtualBoard board=new VirtualBoard(b);
Link []lnk=detNode[0].getLinks();
if(!canMove(board, 0)) {
n[0]=detNode[0];
}
else if(!board.isLegalMove(lnk[detLnk].getToNode())) {
continue;
}
else {
n[0]=lnk[detLnk].getToNode();
}
generateDetectiveMove(1, possibleMoves, n, detNode, board);
}
// This is the old version
/*
for(int d1=0;d1<detNode[0].getLinks().length;d1++)
{
board=new VirtualBoard(b);
Link []l1=detNode[0].getLinks();
if(!canMove(board, 0)) n[0]=detNode[0];
else if(!board.isLegalMove(l1[d1].getToNode())) continue;
else n[0]=l1[d1].getToNode();
for(int d2=0;d2<detNode[1].getLinks().length;d2++)
{
Link []l2=detNode[1].getLinks();
n[1]=l2[d2].getToNode();
if(!canMove(board, 1)) n[1]=detNode[1];
else if(!board.isLegalMove(l2[d2].getToNode())) continue;
for(int d3=0;d3<detNode[2].getLinks().length;d3++)
{
Link []l3=detNode[2].getLinks();
n[2]=l3[d3].getToNode();
if(!canMove(board, 2)) n[2]=detNode[2];
else if(!board.isLegalMove(l3[d3].getToNode())) continue;
for(int d4=0;d4<detNode[3].getLinks().length;d4++)
{
Link []l4=detNode[3].getLinks();
n[3]=l4[d4].getToNode();
if(!canMove(board, 3)) n[3]=detNode[3];
else if(!board.isLegalMove(l3[d3].getToNode())) continue;
for(int d5=0;d5<detNode[4].getLinks().length;d5++)
{
Link []l5=detNode[4].getLinks();
n[4]=l5[d5].getToNode();
if(!canMove(board, 4)) n[4]=detNode[4];
else if(!board.isLegalMove(l5[d5].getToNode())) continue;
for(int count=0;count<nrOfDetectives;count++)
board.detectives[count].change(n[count]);
possibleMoves.add((Object)board);
}
}
}
}
}
*/
return possibleMoves;
}
private void generateDetectiveMove(int detNr, TreeSet possibleMoves, Node[] n, Node[] detNode, VirtualBoard board) {
int nrOfDetectives = Manager.getOptions().getNumberOfDetectives();
for(int det=0;det<detNode[detNr].getLinks().length;det++) {
Link []lnk=detNode[detNr].getLinks();
n[detNr]=lnk[det].getToNode();
if(!canMove(board, detNr)) n[detNr]=detNode[detNr];
else if(!board.isLegalMove(lnk[det].getToNode())) continue;
if(detNr==nrOfDetectives-1) {
for(int count=0;count<nrOfDetectives;count++)
board.detectives[count].change(n[count]);
possibleMoves.add((Object)board);
}
else {
generateDetectiveMove(detNr+1, possibleMoves, n, detNode, board);
}
}
}
/**This method evaluates the given board
*@param board the given board
*@return the score for this board
*/
private static int scoreBoard(VirtualBoard board)
{
Detective []detectives=board.detectives;
Fugitive mrx=board.MrX;
int totalDistance=0;
int totalMobility=0;
for(int count=0;count<Manager.getOptions().getNumberOfDetectives();count++)
{
totalDistance+=shortestDistance[detectives[count].getPosition().getPosition()][mrx.getPosition().getPosition()];
totalMobility+=detectives[count].mobility();
}
int score=3*totalDistance-totalMobility/5;
return score;
}
/**This method compares two objects of this class depending on the score of the boards
*@param b1,b2 the two boards which are to be compared
*@return negative if score of b1 less then score of b2
*positive otherwise
*/
public int compare(Object b1,Object b2)
{
int s1=scoreBoard((VirtualBoard)b1);
int s2=scoreBoard((VirtualBoard)b2);
if(s1<s2) return -1;
else return 1;
}
/**This method checks whether two boards are equal
*@param b1.b2 the two boards
*@return true if the boards are equal,false otherwise
*/
public boolean equal(VirtualBoard b1,VirtualBoard b2)
{
return (scoreBoard(b1)==scoreBoard(b2));
}
/**This method comapres this board to another board o
*@param o the board with which this is to be compared
*@return similar to the compare() method
*/
public int compareTo(Object o)
{
VirtualBoard b=(VirtualBoard)o;
return compare(this,b);
}
public Move moveMrX()
{
Node bestNode=bestMove();
int type=MrX.changePosition(bestNode);
int pos=MrX.getPosition().getPosition();
currentMoves++;
return (new Move(pos,type));
}
/**This method is used to get the detectives of this board
*@return the array containing the detectives of the current game
*/
public Detective[] getDetectives()
{
return detectives;
}
/**This method is used to get the MrX of this object
*@return the MrX of this object
*/
public Fugitive getMrX()
{
return MrX;
}
/**This methos returns the currentMoves of this object
*@return the currentMoves of this object
*/
public int getCurrentMoves()
{
return currentMoves;
}
/**String representation of this board
*@return the score of this board in String form
*/
public String toString()
{
return ""+scoreBoard(this);
}
public static Point getPos(int i) {
return pos[i];
}
/**Checks if the detective can make a move or not
* @return true if the detective can move,false if the detective is stranded
*/
private boolean canMove(VirtualBoard board, int detNo)
{
Node n=board.getDetectives()[detNo].getPosition();
// log.debug("Detective #"+detNo+", Node: "+n+" on board ("+board.hashCode()+")");
Link []lk=n.getLinks();
boolean canMove=false;
for(int i=0;i<lk.length;i++)
{
boolean canGoToThisNode=true;
Node toNode=lk[i].getToNode();
Detective[] det=Manager.getGame().getBoard().getDetectives();
for(int j=0;j<det.length;j++)
if (toNode.equals(det[j].getPosition())) canGoToThisNode=false;
int t=lk[i].getType();
switch(t)
{
case TAXI:if(getDetectives()[detNo].getTaxiTickets()<=0) if(canGoToThisNode) canGoToThisNode=false;
break;
case BUS:if(getDetectives()[detNo].getBusTickets()<=0) if(canGoToThisNode) canGoToThisNode=false;
break;
case UG:if(getDetectives()[detNo].getUndergroundTickets()<=0) if(canGoToThisNode) canGoToThisNode=false;
break;
case BLACK:canGoToThisNode=false;
break;
}
if(canGoToThisNode) canMove=true;
}
return canMove;
}
/**This method returns the possible moves of the detective in a TreeSet
* @param board the board of which this detective is a part of
* @return all the possible moves in TreeSet
*/
private TreeSet getPossibleMoves(VirtualBoard board, int detNo)
{
if(!canMove(board, detNo)) return null;
boolean added=false;
Node n=board.getDetectives()[detNo].getPosition();
Link []lk=n.getLinks();
TreeSet possibleMoves=new TreeSet();
for(int i=0;i<lk.length;i++)
{
boolean canGoToThisNode=true;
Node toNode=lk[i].getToNode();
Detective[] det=Manager.getGame().getBoard().getDetectives();
for(int j=0;j<det.length;j++)
if(toNode.equals(det[j].getPosition())) canGoToThisNode=false;
int t=lk[i].getType();
switch(t)
{
case TAXI:if(getDetectives()[detNo].getTaxiTickets()<=0) if(canGoToThisNode) canGoToThisNode=false;
break;
case BUS:if(getDetectives()[detNo].getBusTickets()<=0) if(canGoToThisNode) canGoToThisNode=false;
break;
case UG:if(getDetectives()[detNo].getUndergroundTickets()<=0) if(canGoToThisNode) canGoToThisNode=false;
break;
case BLACK:canGoToThisNode=false;
break;
}
if(canGoToThisNode) possibleMoves.add(new Move(toNode.getPosition(),t));
}
return possibleMoves;
}
}