/* * Copyright (c) 2014 tabletoptool.com team. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * rptools.com team - initial implementation * tabletoptool.com team - further development */ package com.t3.client.walker.astar; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import com.t3.client.walker.AbstractZoneWalker; import com.t3.model.CellPoint; import com.t3.model.Zone; public abstract class AbstractAStarWalker extends AbstractZoneWalker { public AbstractAStarWalker(Zone zone) { super(zone); } private int distance = -1; /** * Returns the list of neighbor cells that are valid for being movement-checked. This is an array of (x,y) offsets * (see the constants in this class) named as compass points. * <p> * It should be possible to query the current (x,y) CellPoint passed in to determine which directions are feasible * to move into. But it would require information about visibility (which token is moving, does it have sight, and * so on). Currently that information is not available here, but perhaps an option Token parameter could be * specified to the constructor? Or maybe as the tree was scanned, since I believe all Grids share a common * ZoneWalker. */ protected abstract int[][] getNeighborMap(int x, int y); @Override protected List<CellPoint> calculatePath(CellPoint start, CellPoint end) { List<AStarCellPoint> openList = new ArrayList<AStarCellPoint>(); Map<AStarCellPoint, AStarCellPoint> openSet = new HashMap<AStarCellPoint, AStarCellPoint>(); // For faster lookups Set<AStarCellPoint> closedSet = new HashSet<AStarCellPoint>(); openList.add(new AStarCellPoint(start)); openSet.put(openList.get(0), openList.get(0)); AStarCellPoint node = null; while (!openList.isEmpty()) { node = openList.remove(0); openSet.remove(node); if (node.equals(end)) { break; } int[][] neighborMap = getNeighborMap(node.x, node.y); for (int i = 0; i < neighborMap.length; i++) { int x = node.x + neighborMap[i][0]; int y = node.y + neighborMap[i][1]; AStarCellPoint neighborNode = new AStarCellPoint(x, y); if (closedSet.contains(neighborNode)) { continue; } neighborNode.parent = node; neighborNode.gScore = gScore(start, neighborNode); neighborNode.hScore = hScore(neighborNode, end); if (openSet.containsKey(neighborNode)) { AStarCellPoint oldNode = openSet.get(neighborNode); // check if it is cheaper to get here the way that we just // came, versus the previous path if (neighborNode.gScore < oldNode.gScore) { oldNode.gScore = neighborNode.gScore; neighborNode = oldNode; neighborNode.parent = node; } continue; } pushNode(openList, neighborNode); openSet.put(neighborNode, neighborNode); } closedSet.add(node); node = null; } List<CellPoint> ret = new LinkedList<CellPoint>(); while (node != null) { ret.add(node); node = node.parent; } distance = -1; Collections.reverse(ret); return ret; } private void pushNode(List<AStarCellPoint> list, AStarCellPoint node) { if (list.isEmpty()) { list.add(node); return; } if (node.cost() < list.get(0).cost()) { list.add(0, node); return; } if (node.cost() > list.get(list.size() - 1).cost()) { list.add(node); return; } for (ListIterator<AStarCellPoint> iter = list.listIterator(); iter.hasNext();) { AStarCellPoint listNode = iter.next(); if (listNode.cost() > node.cost()) { iter.previous(); iter.add(node); return; } } } protected abstract int calculateDistance(List<CellPoint> path, int feetPerCell); protected abstract double gScore(CellPoint p1, CellPoint p2); protected abstract double hScore(CellPoint p1, CellPoint p2); @Override public int getDistance() { if (distance == -1) { distance = calculateDistance(getPath().getCellPath(), getZone().getUnitsPerCell()); } return distance; } }