package ch.asynk.tankontank.engine; import java.util.Set; import java.util.List; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashSet; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.utils.Pool; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.math.GridPoint2; import com.badlogic.gdx.math.Matrix4; import ch.asynk.tankontank.engine.gfx.Image; import ch.asynk.tankontank.engine.gfx.Animation; import ch.asynk.tankontank.engine.gfx.animations.AnimationSequence; import ch.asynk.tankontank.engine.gfx.animations.RunnableAnimation; public abstract class Board implements Disposable { private final Tile neighbours[] = new Tile[6]; protected List> paths; public interface TileBuilder { public Tile getNewTile(float x, float y, int col, int row); } public static class Config { public int cols; public int rows; public int x0; // bottom left x offset public int y0; // bottom left y offset public int w; // hex width public int dw; // half hex : w/2 public int s; // hex side public float dh; // hex top : s/2 public float h; // square height : s + dh public float slope; // north-west side slope : (dh / (float) dw) } private final Pool gridPoint2Pool = new Pool() { @Override protected GridPoint2 newObject() { return new GridPoint2(); } }; private final Pool vector3Pool = new Pool() { @Override protected Vector3 newObject() { return new Vector3(); } }; private Config cfg; private Tile[] tiles; private SearchBoard searchBoard; private Image image; private boolean transform; private Matrix4 prevTransform; private Matrix4 nextTransform; private int tileCount = 0; private int pawnCount = 0; private int animationCount = 0; private final ArrayList animations = new ArrayList(2); private final ArrayList nextAnimations = new ArrayList(2); private final LinkedHashSet tilesToDraw = new LinkedHashSet(); protected Board(int cols, int rows) { searchBoard = new SearchBoard(this, cols, rows); } public Board(TileBuilder tileBuilder, Config cfg, Texture texture) { image = new Image(texture); this.cfg = cfg; this.tiles = new Tile[cfg.cols * cfg.rows]; searchBoard = new SearchBoard(this, cfg.cols, cfg.rows); int idx = 0; boolean evenRow = true; float y = cfg.y0 - cfg.dh + cfg.s; for (int i = 0; i < cfg.rows; i++) { float x = cfg.x0 + cfg.dw; if (!evenRow) x += cfg.dw; for ( int j = 0; j < cfg.cols; j ++) { this.tiles[idx] = tileBuilder.getNewTile(x, y, (j + ((i + 1) / 2)), i); idx += 1; x += cfg.w; } y += cfg.h; evenRow = !evenRow; } } @Override public void dispose() { image.dispose(); for (int i = 0; i < (cfg.cols * cfg.rows); i++) tiles[i].dispose(); tilesToDraw.clear(); for (int i = 0, n = nextAnimations.size(); i < n; i++) nextAnimations.get(i).dispose(); animations.clear(); for (int i = 0, n = animations.size(); i < n; i++) animations.get(i).dispose(); animations.clear(); } public float getWidth() { return image.getWidth(); } public float getHeight() { return image.getHeight(); } public void setPosition(float x, float y) { image.setPosition(x, y); if ((x != 0.0f) || (y != 0.0f)) { transform = true; prevTransform = new Matrix4(); nextTransform = new Matrix4(); nextTransform.translate(x, y, 0); } else transform = false; } public Tile getTile(GridPoint2 coords) { return getTile(coords.x, coords.y); } public Tile getTile(int col, int row) { int idx = ((col - ((row + 1) / 2))) + (row * cfg.cols); // Gdx.app.debug("Board", " getTile: " + col + " ; " + row + " -> " + idx); return tiles[idx]; } public Tile getTileSafe(GridPoint2 coords) { return getTileSafe(coords.x, coords.y); } public Tile getTileSafe(int col, int row) { int colOffset = ((row + 1) / 2); if ((col < colOffset) || (row < 0) || (row >= cfg.rows) || ((col - colOffset) >= cfg.cols)) return null; return tiles[((col - colOffset)) + (row * cfg.cols)]; } private void getAdjacentTiles(GridPoint2 coords, Tile tiles[]) { tiles[0] = getTileSafe((coords.x - 1), (coords.y)); tiles[1] = getTileSafe((coords.x), (coords.y + 1)); tiles[2] = getTileSafe((coords.x + 1), (coords.y + 1)); tiles[3] = getTileSafe((coords.x + 1), (coords.y)); tiles[4] = getTileSafe((coords.x), (coords.y - 1)); tiles[5] = getTileSafe((coords.x - 1), (coords.y - 1)); } public void addAnimation(Animation a) { nextAnimations.add(a); } private void stats() { boolean print = false; if (tileCount != tilesToDraw.size()) { tileCount = tilesToDraw.size(); print = true; } if (animationCount != animations.size()) { animationCount = animations.size(); print = true; } if (print) Gdx.app.debug("Board", " tiles:" + tileCount + " pawns:" + pawnCount + " animations:" + animationCount); } public void animate(float delta) { Iterator iter = animations.iterator(); while (iter.hasNext()) { Animation a = iter.next(); if (a.animate(delta)) iter.remove(); } for (int i = 0, n = nextAnimations.size(); i < n; i++) animations.add(nextAnimations.get(i)); nextAnimations.clear(); } public void draw(Batch batch) { image.draw(batch); if (transform) { prevTransform.set(batch.getTransformMatrix()); batch.setTransformMatrix(nextTransform); } Iterator tileIter = tilesToDraw.iterator(); while (tileIter.hasNext()) tileIter.next().draw(batch); Iterator animationIter = animations.iterator(); while (animationIter.hasNext()) animationIter.next().draw(batch); if (transform) batch.setTransformMatrix(prevTransform); } public void drawDebug(ShapeRenderer debugShapes) { stats(); if (transform) { prevTransform.set(debugShapes.getTransformMatrix()); debugShapes.setTransformMatrix(nextTransform); } Iterator iter = tilesToDraw.iterator(); while (iter.hasNext()) iter.next().drawDebug(debugShapes); Iterator animationIter = animations.iterator(); while (animationIter.hasNext()) animationIter.next().drawDebug(debugShapes); if (transform) debugShapes.setTransformMatrix(prevTransform); } protected void clearPointVector(ArrayList points) { for (GridPoint2 point : points) gridPoint2Pool.free(point); points.clear(); } private void nodesToPoints(List nodes, ArrayList points) { int ns = nodes.size(); int ps = points.size(); if (ps > ns) { for (int i = (ps - 1); i >= ns; i--) gridPoint2Pool.free(points.remove(i)); } else points.ensureCapacity(ns); int i = 0; for (SearchBoard.Node node : nodes) { if (i < ps) { points.get(i).set(node.col, node.row); } else { GridPoint2 point = gridPoint2Pool.obtain(); point.set(node.col, node.row); points.add(point); } i += 1; } } protected int buildPossibleMovesFrom(Pawn pawn, GridPoint2 coords, ArrayList moves) { List nodes = searchBoard.possibleMovesFrom(pawn, coords.x, coords.y); nodesToPoints(nodes, moves); return moves.size(); } protected int buildPossibleTargetsFrom(Pawn pawn, GridPoint2 coords, ArrayList targets) { List nodes = searchBoard.possibleTargetsFrom(pawn, coords.x, coords.y); nodesToPoints(nodes, targets); return targets.size(); } protected int buildPossibleTargetsFrom(Pawn pawn, GridPoint2 coords, Iterator units, ArrayList targets) { clearPointVector(targets); GridPoint2 to = gridPoint2Pool.obtain(); while (units.hasNext()) { Pawn target = units.next(); getHexUnder(target, to); if (searchBoard.buildAttack(pawn, true, target, coords.x, coords.y, to.x, to.y)) { targets.add(to); to = gridPoint2Pool.obtain(); } } int s = targets.size(); if ((s > 0) && (to != targets.get(s - 1))) gridPoint2Pool.free(to); return s; } protected int buildMoveAssists(Pawn pawn, GridPoint2 coords, List assists) { assists.clear(); getAdjacentTiles(coords, neighbours); for (int i = 0; i < 6; i++) { Tile t = neighbours[i]; if (t != null) { // FIXME should support may pawns per tile Pawn p = t.getTopPawn(); if ((p != null) && p.canMove() && !pawn.isEnemy(p)) { GridPoint2 assist = gridPoint2Pool.obtain(); assist.set(t.getCol(), t.getRow()); assists.add(assist); } } } return assists.size(); } protected int buildAttackAssists(Pawn pawn, Pawn target, GridPoint2 coords, Iterator units, ArrayList assists) { clearPointVector(assists); GridPoint2 from = gridPoint2Pool.obtain(); while (units.hasNext()) { Pawn p = units.next(); if ((p == pawn) || !p.canAttack()) continue; getHexUnder(p, from); if (searchBoard.buildAttack(p, !p.canAssistAttackWithoutLos(), target, from.x, from.y, coords.x, coords.y)) { if (p != pawn) { assists.add(from); from = gridPoint2Pool.obtain(); } } } int s = assists.size(); if ((s > 0) && (from != assists.get(s- 1))) gridPoint2Pool.free(from); return s; } protected void clearPointSet(Set points) { for (GridPoint2 point : points) gridPoint2Pool.free(point); points.clear(); } private int nodesToSet(List> nodes, Set points) { for (GridPoint2 point : points) gridPoint2Pool.free(point); points.clear(); for (ArrayList path : nodes) { for (int i = 1, n = (path.size() - 1); i < n; i++) { SearchBoard.Node node = path.get(i); GridPoint2 point = gridPoint2Pool.obtain(); point.set(node.col, node.row); if (!points.add(point)) gridPoint2Pool.free(point); } } return nodes.size(); } protected int buildPossiblePaths(Pawn pawn, GridPoint2 from, GridPoint2 to, Set points) { paths = searchBoard.possiblePaths(pawn, from.x, from.y, to.x, to.y); return nodesToSet(paths, points); } protected int possiblePathsFilterToggle(GridPoint2 coords, Set points) { paths = searchBoard.possiblePathsFilterToggle(coords.x, coords.y); return nodesToSet(paths, points); } protected void clearCoordinateVector(ArrayList points) { for (Vector3 point : points) vector3Pool.free(point); points.clear(); } protected int getPathCost(Pawn pawn, int i) { return searchBoard.pathCost(pawn, paths.get(i)); } protected int getCoordinatePath(Pawn pawn, int idx, ArrayList path, Orientation finalOrientation) { clearCoordinateVector(path); Vector2 tmpCoords = new Vector2(); GridPoint2 tmpHex = gridPoint2Pool.obtain(); Vector3 p = pawn.getPosition(); Vector3 v = vector3Pool.obtain(); v.set(p.x, p.y, 0f); Orientation prevOrientation = pawn.getOrientation(); ArrayList nodes = paths.get(idx); SearchBoard.Node prevNode = nodes.get(0); // Gdx.app.debug("Board", "getCoordinatePath()"); // Gdx.app.debug("Board", " " + prevNode); for (int i = 1, n = nodes.size(); i < n; i++) { SearchBoard.Node node = nodes.get(i); // Gdx.app.debug("Board", " " + node); Orientation o = Orientation.fromMove(prevNode.col, prevNode.row, node.col, node.row); if ((o != Orientation.KEEP) && (o != prevOrientation)) { v.z = o.r(); path.add(v); v = vector3Pool.obtain(); } tmpHex.set(node.col, node.row); getPawnPosAt(pawn, tmpHex, tmpCoords); v.set(tmpCoords.x, tmpCoords.y, o.r()); path.add(v); prevOrientation = o; v = vector3Pool.obtain(); v.set(tmpCoords.x, tmpCoords.y, 0f); prevNode = node; } if (finalOrientation != prevOrientation) { v.z = finalOrientation.r(); path.add(v); } else { vector3Pool.free(v); } gridPoint2Pool.free(tmpHex); // Gdx.app.debug("Board", " =>"); // for (Vector3 vector :path) // Gdx.app.debug("Board", " " + vector); return path.size(); } protected boolean hasUnits(GridPoint2 coords) { return getTile(coords).hasUnits(); } public boolean isOffMap(GridPoint2 coords) { return getTile(coords).isOffMap(); } protected boolean isOverlayEnabledOn(GridPoint2 coords, int i) { return getTile(coords).isOverlayEnabled(i); } public void enableOverlayOn(GridPoint2 coords, int i, boolean enable) { Tile tile = getTile(coords); if(tile.enableOverlay(i, enable)) tilesToDraw.add(tile); else tilesToDraw.remove(tile); } public void enableOverlayOn(GridPoint2 coords, int i, boolean enable, Orientation o) { Tile tile = getTile(coords); if(tile.enableOverlay(i, enable, o.r())) tilesToDraw.add(tile); else tilesToDraw.remove(tile); } public Pawn getTopPawnAt(GridPoint2 coords) { return getTile(coords).getTopPawn(); } private int pushPawnAt(Pawn pawn, GridPoint2 coords) { Tile tile = getTile(coords); tilesToDraw.add(tile); return tile.push(pawn); } public int removePawn(Pawn pawn) { return removePawnFrom(pawn, getHexUnder(pawn)); } public int removePawnFrom(Pawn pawn, GridPoint2 coords) { Tile tile = getTile(coords); int n = tile.remove(pawn); if (!tile.mustBeDrawn()) tilesToDraw.remove(tile); return n; } protected Vector2 getPawnPosAt(Pawn pawn, GridPoint2 coords, Vector2 pos) { return pawn.getPosAt(getTile(coords.x, coords.y), pos); } public Pawn setPawnAt(Pawn pawn, GridPoint2 coords, Orientation o) { Vector2 pos = getPawnPosAt(pawn, coords, null); pawn.setPosition(pos.x, pos.y, o.r()); pushPawnAt(pawn, coords); return pawn; } protected void movePawn(final Pawn pawn, int cost, ArrayList path, RunnableAnimation whenDone) { removePawnFrom(pawn, getHexUnder(pawn)); AnimationSequence seq = pawn.getMoveAnimation(path); seq.addAnimation(RunnableAnimation.get(pawn, new Runnable() { @Override public void run() { pushPawnAt(pawn, getHexUnder(pawn)); } })); seq.addAnimation(whenDone); addAnimation(seq); pawn.move(cost); } protected void rotatePawn(final Pawn pawn, Orientation o, RunnableAnimation whenDone) { Vector3 p = pawn.getPosition(); Vector3 v = vector3Pool.obtain(); v.set(p.x, p.y, o.r()); AnimationSequence seq = pawn.getRotateAnimation(v); seq.addAnimation(whenDone); addAnimation(seq); vector3Pool.free(v); pawn.rotate(o); } protected void revertLastPawnMove(final Pawn pawn, RunnableAnimation whenDone) { removePawnFrom(pawn, getHexUnder(pawn)); AnimationSequence seq = pawn.getRevertLastMoveAnimation(); seq.addAnimation(RunnableAnimation.get(pawn, new Runnable() { @Override public void run() { pushPawnAt(pawn, getHexUnder(pawn)); } })); seq.addAnimation(whenDone); addAnimation(seq); pawn.revertLastMove(); } public GridPoint2 getHexUnder(Pawn pawn) { return getHexAt(pawn.getCenter(), null); } public GridPoint2 getHexUnder(Pawn pawn, GridPoint2 hex) { return getHexAt(pawn.getCenter(), hex); } public GridPoint2 getHexAt(Vector2 v, GridPoint2 hex) { return getHexAt(hex, v.x, v.y); } public GridPoint2 getHexAt(GridPoint2 hex, float mx, float my) { if (hex == null) hex = new GridPoint2(); // compute row float y = (my - cfg.y0); int row = (int) (y / cfg.h); boolean oddRow = ((row % 2) == 1); if (y < 0.f) { row = -1; oddRow = true; } // compute col float x = (mx - cfg.x0); if (oddRow) x -= cfg.dw; int col = (int) (x / cfg.w); if (x < 0.f) col = -1; int colOffset = ((row +1) / 2); // check upper boundaries float dy = (y - (row * cfg.h)); if (dy > cfg.s) { dy -= cfg.s; float dx = (x - (col * cfg.w)); col += colOffset; if (dx < cfg.dw) { if ((dx * cfg.slope) < dy) { // upper left corner row += 1; colOffset = ((row +1) / 2); } } else { if (((cfg.w - dx) * cfg.slope) < dy) { // upper right corner row += 1; col += 1; colOffset = ((row +1) / 2); } } } else col += colOffset; // validate hex if ((col < colOffset) || (row < 0) || (row >= cfg.rows) || ((col - colOffset) >= cfg.cols)) hex.set(-1, -1); else hex.set(col, row); // Gdx.app.debug("Board", " hex: " + hex.x + " ; " + hex.y); return hex; } }