summaryrefslogtreecommitdiffstats
path: root/core/src/ch/asynk/gdx/boardgame
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/ch/asynk/gdx/boardgame')
-rw-r--r--core/src/ch/asynk/gdx/boardgame/Assets.java30
-rw-r--r--core/src/ch/asynk/gdx/boardgame/Board.java9
-rw-r--r--core/src/ch/asynk/gdx/boardgame/Camera.java223
-rw-r--r--core/src/ch/asynk/gdx/boardgame/Drawable.java19
-rw-r--r--core/src/ch/asynk/gdx/boardgame/Touchable.java6
-rw-r--r--core/src/ch/asynk/gdx/boardgame/board/BoardFactory.java42
-rw-r--r--core/src/ch/asynk/gdx/boardgame/board/HexBoard.java136
-rw-r--r--core/src/ch/asynk/gdx/boardgame/board/SquareBoard.java39
-rw-r--r--core/src/ch/asynk/gdx/boardgame/board/TriangleBoard.java108
-rw-r--r--core/src/ch/asynk/gdx/boardgame/ui/Alignment.java168
-rw-r--r--core/src/ch/asynk/gdx/boardgame/ui/Assembly.java63
-rw-r--r--core/src/ch/asynk/gdx/boardgame/ui/Button.java67
-rw-r--r--core/src/ch/asynk/gdx/boardgame/ui/Element.java116
-rw-r--r--core/src/ch/asynk/gdx/boardgame/ui/Label.java61
-rw-r--r--core/src/ch/asynk/gdx/boardgame/ui/Menu.java126
-rw-r--r--core/src/ch/asynk/gdx/boardgame/ui/Patch.java23
-rw-r--r--core/src/ch/asynk/gdx/boardgame/ui/Root.java28
-rw-r--r--core/src/ch/asynk/gdx/boardgame/util/Collection.java28
-rw-r--r--core/src/ch/asynk/gdx/boardgame/util/IterableArray.java143
-rw-r--r--core/src/ch/asynk/gdx/boardgame/util/IterableQueue.java19
-rw-r--r--core/src/ch/asynk/gdx/boardgame/util/IterableSet.java16
-rw-r--r--core/src/ch/asynk/gdx/boardgame/util/IterableStack.java26
22 files changed, 1496 insertions, 0 deletions
diff --git a/core/src/ch/asynk/gdx/boardgame/Assets.java b/core/src/ch/asynk/gdx/boardgame/Assets.java
new file mode 100644
index 0000000..3402430
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/Assets.java
@@ -0,0 +1,30 @@
+package ch.asynk.gdx.boardgame;
+
+import com.badlogic.gdx.assets.AssetManager;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.NinePatch;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+
+public class Assets extends AssetManager
+{
+ public Texture getTexture(String assetName)
+ {
+ return get(assetName, Texture.class);
+ }
+
+ public NinePatch getNinePatch(String assetName, int left, int right, int top, int bottom)
+ {
+ return new NinePatch(get(assetName, Texture.class), left, right, top, bottom);
+ }
+
+ public TextureAtlas getAtlas(String assetName)
+ {
+ return get(assetName, TextureAtlas.class);
+ }
+
+ public BitmapFont getFont(String assetName)
+ {
+ return get(assetName, BitmapFont.class);
+ }
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/Board.java b/core/src/ch/asynk/gdx/boardgame/Board.java
new file mode 100644
index 0000000..b1d82d0
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/Board.java
@@ -0,0 +1,9 @@
+package ch.asynk.gdx.boardgame;
+
+import com.badlogic.gdx.math.Vector2;
+
+public interface Board
+{
+ public void centerOf(int x, int y, Vector2 v);
+ public void toBoard(float x, float y, Vector2 v);
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/Camera.java b/core/src/ch/asynk/gdx/boardgame/Camera.java
new file mode 100644
index 0000000..5140538
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/Camera.java
@@ -0,0 +1,223 @@
+package ch.asynk.gdx.boardgame;
+
+import com.badlogic.gdx.graphics.glutils.HdpiUtils;
+import com.badlogic.gdx.graphics.OrthographicCamera;
+
+import com.badlogic.gdx.math.Rectangle;
+import com.badlogic.gdx.math.Vector3;
+import com.badlogic.gdx.math.MathUtils;
+import com.badlogic.gdx.math.Matrix4;
+
+public class Camera extends OrthographicCamera
+{
+ private static final float ZEROF = 0.01f;
+
+ private int padding;
+ private float boardWidth;
+ private float boardHeight;
+ private float boardAspectRatio;
+ private float zoomMax;
+ private float zoomMin;
+ private int screenWidth;
+ private int screenHeight;
+ private float widthFactor;
+ private float heightFactor;
+ private Rectangle viewport;
+
+ private Rectangle hud;
+ private Matrix4 hudMatrix;
+ private Matrix4 hudInvProjMatrix;
+ private int hudLeft;
+ private int hudBottom;
+ private boolean hudInBoard;
+
+ public Camera(int padding, float boardWidth, float boardHeight, float zoomMax, float zoomMin, boolean hudInBoard)
+ {
+ super(boardWidth, boardHeight);
+ this.boardWidth = boardWidth;
+ this.boardHeight = boardHeight;
+ this.padding = padding;
+ this.boardAspectRatio = (boardWidth / boardHeight);
+ this.zoomMax = zoomMax;
+ this.zoomMin = zoomMin;
+ this.viewport = new Rectangle();
+
+ this.hudInBoard = hudInBoard;
+ this.hud = new Rectangle();
+ this.hudMatrix = new Matrix4();
+ this.hudInvProjMatrix = new Matrix4();
+ }
+
+ public void setDimension(float boardWidth, float boardHeight)
+ {
+ setToOrtho(false, boardWidth, boardHeight);
+ this.boardWidth = boardWidth;
+ this.boardHeight = boardHeight;
+ this.boardAspectRatio = (boardWidth / boardHeight);
+ }
+
+ public void updateViewport(int screenWidth, int screenHeight)
+ {
+ this.screenWidth = screenWidth;
+ this.screenHeight = screenHeight;
+
+ float screenAvailableWidth = screenWidth - (2 * padding);
+ float screenAvailableHeight = screenHeight - (2 * padding);
+ float screenAspectRatio = (screenWidth / (float) screenHeight);
+ float diff = (boardAspectRatio - screenAspectRatio);
+
+ if (diff <= -ZEROF) {
+ // screen wider than board : use max height, width grows up until max available on zooming
+ viewport.height = screenAvailableHeight;
+ viewport.width = java.lang.Math.min((screenAvailableHeight * boardAspectRatio / zoom), screenAvailableWidth);
+ viewport.x = padding + ((screenAvailableWidth - viewport.width) / 2f);
+ viewport.y = padding;
+ // camera aspect ratio must follow viewport aspect ration
+ viewportHeight = boardHeight;
+ viewportWidth = (viewportHeight * (viewport.width / viewport.height));
+ // hud
+ hud.y = 0;
+ hud.x = (hud.y * viewportWidth / viewportHeight);
+ } else if (diff > ZEROF) {
+ // word is wider than screen : use max width, height grows up until max available on zooming
+ viewport.width = screenAvailableWidth;
+ viewport.height = java.lang.Math.min((screenAvailableWidth / boardAspectRatio / zoom), screenAvailableHeight);
+ viewport.y = padding + ((screenAvailableHeight - viewport.height) / 2f);
+ viewport.x = padding;
+ // camera aspect ratio must follow viewport aspect ration
+ viewportWidth = boardWidth;
+ viewportHeight = (viewportWidth * (viewport.height / viewport.width));
+ // hud
+ hud.x = 0;
+ hud.y = (hud.x * viewportHeight / viewportWidth);
+ }
+
+ if (hudInBoard) {
+ hud.width = (viewport.width - (2 * hud.x));
+ hud.height = (viewport.height - (2 * hud.y));
+ } else {
+ hud.x = 0;
+ hud.y = 0;
+ hud.width = screenWidth;
+ hud.height = screenHeight;
+ }
+
+ // ratio viewport -> camera
+ widthFactor = (viewportWidth / viewport.width);
+ heightFactor = (viewportHeight / viewport.height);
+
+ clampPosition();
+ update(true);
+
+ hudMatrix.setToOrtho2D(hud.x, hud.y, hud.width, hud.height);
+ hudInvProjMatrix.set(hudMatrix);
+ Matrix4.inv(hudInvProjMatrix.val);
+ }
+
+ public void applyScreenViewport()
+ {
+ HdpiUtils.glViewport(0, 0, screenWidth, screenHeight);
+ }
+
+ public void applyBoardViewport()
+ {
+ HdpiUtils.glViewport((int)viewport.x, (int)viewport.y, (int)viewport.width, (int)viewport.height);
+ }
+
+ public void applyHudViewport()
+ {
+ if (hudInBoard)
+ applyBoardViewport();
+ else
+ applyScreenViewport();
+ }
+
+ public void centerOnWorld()
+ {
+ position.set((boardWidth / 2f), (boardHeight / 2f), 0f);
+ }
+
+ public void zoom(float dz)
+ {
+ zoom += dz;
+ clampZoom();
+ }
+
+ public void clampZoom()
+ {
+ zoom = MathUtils.clamp(zoom, zoomMin, zoomMax);
+ }
+
+ public void translate(float dx, float dy)
+ {
+ float deltaX = (dx * zoom * widthFactor);
+ float deltaY = (dy * zoom * heightFactor);
+ translate(deltaX, -deltaY, 0);
+ clampPosition();
+ update(true);
+ }
+
+ public void clampPosition()
+ {
+ float cameraWidth = (viewportWidth * zoom);
+ float cameraHeight = (viewportHeight * zoom);
+
+ // on each axis, clamp on [ cameraDim/2 ; boardDim - cameraDim/2 ]
+
+ if ((boardWidth - cameraWidth) > ZEROF) {
+ cameraWidth /= 2f;
+ position.x = MathUtils.clamp(position.x, cameraWidth, (boardWidth - cameraWidth));
+ } else {
+ position.x = (boardWidth / 2f);
+ }
+
+ if ((boardHeight - cameraHeight) > ZEROF) {
+ cameraHeight /= 2f;
+ position.y = MathUtils.clamp(position.y, cameraHeight, (boardHeight - cameraHeight));
+ } else {
+ position.y = (boardHeight / 2f);
+ }
+ }
+
+ public void unproject(int x, int y, Vector3 v)
+ {
+ unproject(v.set(x, y, 0), viewport.x, viewport.y, viewport.width, viewport.height);
+ }
+
+ public void unprojectHud(float x, float y, Vector3 v)
+ {
+ Rectangle r = (hudInBoard ? viewport : hud);
+ x = x - r.x;
+ y = screenHeight - y - 1;
+ y = y - r.y;
+ v.x = (2 * x) / r.width - 1;
+ v.y = (2 * y) / r.height - 1;
+ v.z = 2 * v.z - 1;
+ v.prj(hudInvProjMatrix);
+ }
+
+ public int getScreenWidth()
+ {
+ return screenWidth;
+ }
+
+ public int getScreenHeight()
+ {
+ return screenHeight;
+ }
+
+ public Matrix4 getHudMatrix()
+ {
+ return hudMatrix;
+ }
+
+ public Rectangle getViewport()
+ {
+ return viewport;
+ }
+
+ public Rectangle getHud()
+ {
+ return hud;
+ }
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/Drawable.java b/core/src/ch/asynk/gdx/boardgame/Drawable.java
new file mode 100644
index 0000000..618d6a2
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/Drawable.java
@@ -0,0 +1,19 @@
+package ch.asynk.gdx.boardgame;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+public interface Drawable
+{
+ public float getX();
+ public float getY();
+ public float getWidth();
+ public float getHeight();
+ public float getInnerX();
+ public float getInnerY();
+ public float getInnerWidth();
+ public float getInnerHeight();
+ public void draw(Batch batch);
+ default public void drawDebug(ShapeRenderer debugShapes) { }
+ public void setPosition(float x, float y, float w, float h);
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/Touchable.java b/core/src/ch/asynk/gdx/boardgame/Touchable.java
new file mode 100644
index 0000000..0fd99d0
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/Touchable.java
@@ -0,0 +1,6 @@
+package ch.asynk.gdx.boardgame;
+
+public interface Touchable
+{
+ public boolean touch(float x, float y);
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/board/BoardFactory.java b/core/src/ch/asynk/gdx/boardgame/board/BoardFactory.java
new file mode 100644
index 0000000..8dc73b6
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/board/BoardFactory.java
@@ -0,0 +1,42 @@
+package ch.asynk.gdx.boardgame.board;
+
+import ch.asynk.gdx.boardgame.Board;
+
+public class BoardFactory
+{
+ public enum BoardType
+ {
+ HEX, SQUARE, TRIANGLE
+ }
+
+ public enum BoardOrientation
+ {
+ VERTICAL,
+ HORIZONTAL,
+ }
+
+ public static Board getBoard(BoardType boardType, float side)
+ {
+ return getBoard(boardType, side, 0f, 0f, BoardOrientation.VERTICAL);
+ }
+
+ public static Board getBoard(BoardType boardType, float side, float x0, float y0)
+ {
+ return getBoard(boardType, side, x0, y0, BoardOrientation.VERTICAL);
+ }
+
+ public static Board getBoard(BoardType boardType, float side, float x0, float y0, BoardOrientation boardOrientation)
+ {
+ switch(boardType)
+ {
+ case HEX:
+ return new HexBoard(side, x0, y0, boardOrientation);
+ case SQUARE:
+ return new SquareBoard(side, x0, y0);
+ case TRIANGLE:
+ return new TriangleBoard(side, x0, y0, boardOrientation);
+ default:
+ throw new RuntimeException( String.format("%s board type is not implemented yet.", boardType) );
+ }
+ }
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/board/HexBoard.java b/core/src/ch/asynk/gdx/boardgame/board/HexBoard.java
new file mode 100644
index 0000000..d5b2a26
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/board/HexBoard.java
@@ -0,0 +1,136 @@
+package ch.asynk.gdx.boardgame.board;
+
+import com.badlogic.gdx.math.Vector2;
+
+import ch.asynk.gdx.boardgame.Board;
+
+public class HexBoard implements Board
+{
+ private final float side; // length of the side of the hex
+ private final float x0; // bottom left x offset
+ private final float y0; // bottom left y offset
+ private final BoardFactory.BoardOrientation orientation;
+
+ private final float w; // side to side orthogonal distance
+ private final float dw; // half hex : w/2
+ private final float dh; // hex top : s/2
+ private final float h; // square height : s + dh
+ private final float slope; // dh / dw
+
+ // BoardOrientation.VERTICAL : 2 vertical sides : 2 vertices pointing up and down
+ // coordinates
+ // \
+ // \___
+ // cols are horizontal
+ // rows are at -120°
+ // bottom left is the bottom vertice of the most bottom-left vertical hex side of the map
+ //
+ // BoardOrientation.HORIZONTAL : 2 horizontal sides : 2 vertices pointing left and right
+ // coordinates
+ // |
+ // |
+ // \
+ // \
+ // cols are at +120°
+ // rows are vertical°
+ // bottom left is the left vertice of the most bottom-left horizontal hex side of the map
+
+ public HexBoard(float side, float x0, float y0, BoardFactory.BoardOrientation boardOrientation)
+ {
+ this.side = side;
+ this.x0 = x0;
+ this.y0 = y0;
+ this.orientation = boardOrientation;
+
+ this.w = side * 1.73205f;
+ this.dw = w / 2.0f;
+ this.dh = side / 2.0f;
+ this.h = side + dh;
+ this.slope = dh / dw;
+ }
+
+ @Override public void centerOf(int x, int y, Vector2 v)
+ {
+ float cx = this.x0;
+ float cy = this.y0;
+
+ if (this.orientation == BoardFactory.BoardOrientation.VERTICAL) {
+ cx += (this.dw + (x * this.w) - (y * this.dw));
+ cy += (this.dh + (y * this.h));
+ } else {
+ cx += (this.dh + (x * this.h));
+ cy += (this.dw + (y * this.w) - (x * this.dw));
+ }
+
+ v.set(cx, cy);
+ }
+
+ @Override public void toBoard(float x, float y, Vector2 v)
+ {
+ int col = -1;
+ int row = -1;
+
+ if (this.orientation == BoardFactory.BoardOrientation.VERTICAL) {
+ // compute row
+ float dy = y - this.y0;
+ row = (int) (dy / this.h);
+ if (dy < 0f) row -= 1;
+
+ // compute col
+ float dx = x - this.x0 + (row * this.dw);
+ col = (int) (dx / this.w);
+ if (dx < 0f) col -= 1;
+
+ // upper rectangle or hex body
+ if (dy > ((row * this.h) + this.side)) {
+ dy -= ((row * this.h) + this.side);
+ dx -= (col * this.w);
+ // upper left or right rectangle
+ if (dx < this.dw) {
+ if (dy > (dx * this.slope)) {
+ // upper left hex
+ row += 1;
+ }
+ } else {
+ // if (dy > ((2 * this.dh) - (dx * this.slope))) {
+ if (dy > ((this.w - dx) * this.slope)) {
+ // upper right hex
+ row += 1;
+ col += 1;
+ }
+ }
+ }
+ } else {
+ // compute col
+ float dx = x - this.x0;
+ col = (int) (dx / this.h);
+ if (dx < 0f) col -= 1;
+
+ // compute row
+ float dy = y - this.y0 + (col * this.dw);
+ row = (int) (dy / this.w);
+ if (dy < 0f) row -= 1;
+
+ // right rectangle or hex body
+ if (dx > ((col * this.h) + this.side)) {
+ dx -= ((col * this.h) + this.side);
+ dy -= (row * this.w);
+ // upper or lower rectangle
+ if (dy > ((this.dw - dx) / this.slope)) {
+ if (dy > ((2 * this.dw) - (dx / this.slope))) {
+ // upper right hex
+ col += 1;
+ row += 1;
+ }
+ } else {
+ if (dy < (dx / this.slope)) {
+ // lower right hex
+ col += 1;
+ }
+ }
+ }
+ }
+
+ v.set(col, row);
+ }
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/board/SquareBoard.java b/core/src/ch/asynk/gdx/boardgame/board/SquareBoard.java
new file mode 100644
index 0000000..de2ab4c
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/board/SquareBoard.java
@@ -0,0 +1,39 @@
+package ch.asynk.gdx.boardgame.board;
+
+import com.badlogic.gdx.math.Vector2;
+
+import ch.asynk.gdx.boardgame.Board;
+
+public class SquareBoard implements Board
+{
+ private final float side; // length of the side of a square
+ private final float x0; // bottom left x offset
+ private final float y0; // bottom left y offset
+
+ public SquareBoard(float side, float x0, float y0)
+ {
+ this.side = side;
+ this.x0 = x0;
+ this.y0 = y0;
+ }
+
+ @Override public void centerOf(int x, int y, Vector2 v)
+ {
+ float cx = this.x0 + (this.side / 2) + (this.side * x);
+ float cy = this.y0 + (this.side / 2) + (this.side * y);
+
+ v.set(cx, cy);
+ }
+
+ @Override public void toBoard(float x, float y, Vector2 v)
+ {
+ float dx = x - this.x0;
+ float dy = y - this.y0;
+ int col = (int) (dx / this.side);
+ int row = (int) (dy / this.side);
+ if (dx < 0) col -=1;
+ if (dy < 0) row -=1;
+
+ v.set(col, row);
+ }
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/board/TriangleBoard.java b/core/src/ch/asynk/gdx/boardgame/board/TriangleBoard.java
new file mode 100644
index 0000000..e3c9434
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/board/TriangleBoard.java
@@ -0,0 +1,108 @@
+package ch.asynk.gdx.boardgame.board;
+
+import com.badlogic.gdx.math.Vector2;
+
+import ch.asynk.gdx.boardgame.Board;
+
+public class TriangleBoard implements Board
+{
+ private final float side; // length of the side of the equilateral triangle
+ private final float x0; // bottom left x offset
+ private final float y0; // bottom left y offset
+ private final BoardFactory.BoardOrientation orientation;
+
+ private final float d; // side / 2
+ private final float h; // height of the triangle
+ private final float m; // h / d
+ private final float h13; // 1/3 height of the triangle
+ private final float h23; // 2/3 height of the triangle
+ private final float h43; // 4/3 height of the triangle
+
+ public TriangleBoard(float side, float x0, float y0, BoardFactory.BoardOrientation boardOrientation)
+ {
+ this.side = side;
+ this.x0 = x0;
+ this.y0 = y0;
+ this.orientation = boardOrientation;
+
+ this.d = side / 2f;
+ this.h = side * 0.866f;
+ this.m = this.h / this.d;
+ this.h13 = this.h * 0.33333f;
+ this.h23 = this.h * 0.66666f;
+ this.h43 = this.h * 1.33333f;
+ }
+
+ @Override public void centerOf(int x, int y, Vector2 v)
+ {
+ float cx = this.x0;
+ float cy = this.y0;
+
+ if (this.orientation == BoardFactory.BoardOrientation.VERTICAL) {
+ cy += (y * this.d);
+ cx += ((x * this.h) + (((x + y) % 2 == 0) ? this.h23 : this.h13));
+ } else {
+ cx += (this.d + (x * this.d));
+ cy += ((y * this.h) + (((x + y) % 2 == 0) ? this.h13 : this.h23));
+ }
+
+ v.set(cx, cy);
+ }
+
+ @Override public void toBoard(float x, float y, Vector2 v)
+ {
+ boolean vert = (this.orientation == BoardFactory.BoardOrientation.VERTICAL);
+
+ float dx = x - this.x0;
+ float dy = y - this.y0;
+ float cx = (vert ? this.h : this.d);
+ float cy = (vert ? this.d : this.h);
+
+ int col = (int) (dx / cx);
+ int row = (int) (dy / cy);
+ if (dx < 0) col -=1;
+ if (dy < 0) row -=1;
+ dx -= (col * cx);
+ dy -= (row * cy);
+
+ if (vert) {
+ if (col % 2 == 0) {
+ if (row % 2 == 0) {
+ if (dy > (dx / this.m))
+ row += 1;
+ } else {
+ if (dy + (dx / this.m) > d )
+ row += 1;
+ }
+ } else {
+ if (row % 2 == 0) {
+ if (dy + (dx / this.m) > d )
+ row += 1;
+ } else {
+ if (dy > (dx / this.m))
+ row += 1;
+ }
+ }
+ } else {
+ if (row % 2 == 0) {
+ if (col % 2 == 0) {
+ if (dy > (dx * this.m))
+ col -= 1;
+ } else {
+ if (dy + (dx * this.m) < h )
+ col -= 1;
+ }
+ } else {
+ if (col % 2 == 0) {
+ if (dy + (dx * this.m) < h )
+ col -= 1;
+ } else {
+ if (dy > (dx * this.m))
+ col -= 1;
+ }
+ }
+ }
+
+ v.set(col, row);
+ }
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/ui/Alignment.java b/core/src/ch/asynk/gdx/boardgame/ui/Alignment.java
new file mode 100644
index 0000000..67da089
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/ui/Alignment.java
@@ -0,0 +1,168 @@
+package ch.asynk.gdx.boardgame.ui;
+
+public enum Alignment
+{
+ ABSOLUTE, // Root
+ RELATIVE, // Default
+ TOP_LEFT,
+ TOP_RIGHT,
+ TOP_CENTER,
+ MIDDLE_LEFT,
+ MIDDLE_RIGHT,
+ MIDDLE_CENTER,
+ BOTTOM_LEFT,
+ BOTTOM_RIGHT,
+ BOTTOM_CENTER;
+
+ public Alignment verticalMirror()
+ {
+ switch(this) {
+ case TOP_LEFT:
+ return TOP_RIGHT;
+ case MIDDLE_LEFT:
+ return MIDDLE_RIGHT;
+ case BOTTOM_LEFT:
+ return BOTTOM_RIGHT;
+ case TOP_RIGHT:
+ return TOP_LEFT;
+ case MIDDLE_RIGHT:
+ return MIDDLE_LEFT;
+ case BOTTOM_RIGHT:
+ return BOTTOM_LEFT;
+ }
+ return this;
+ }
+
+ public Alignment horizontalMirror()
+ {
+ switch(this) {
+ case TOP_LEFT:
+ return BOTTOM_LEFT;
+ case TOP_CENTER:
+ return BOTTOM_CENTER;
+ case TOP_RIGHT:
+ return BOTTOM_RIGHT;
+ case BOTTOM_LEFT:
+ return TOP_LEFT;
+ case BOTTOM_CENTER:
+ return TOP_CENTER;
+ case BOTTOM_RIGHT:
+ return TOP_RIGHT;
+ }
+ return this;
+ }
+
+ public boolean isTop()
+ {
+ switch(this) {
+ case TOP_LEFT:
+ case TOP_CENTER:
+ case TOP_RIGHT:
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isMiddle()
+ {
+ boolean r = false;
+ switch(this) {
+ case MIDDLE_LEFT:
+ case MIDDLE_CENTER:
+ case MIDDLE_RIGHT:
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isBottom()
+ {
+ boolean r = false;
+ switch(this) {
+ case BOTTOM_LEFT:
+ case BOTTOM_CENTER:
+ case BOTTOM_RIGHT:
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isLeft()
+ {
+ boolean r = false;
+ switch(this) {
+ case TOP_LEFT:
+ case MIDDLE_LEFT:
+ case BOTTOM_LEFT:
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isRight()
+ {
+ boolean r = false;
+ switch(this) {
+ case TOP_RIGHT:
+ case MIDDLE_RIGHT:
+ case BOTTOM_RIGHT:
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isCenter()
+ {
+ switch(this) {
+ case TOP_CENTER:
+ case MIDDLE_CENTER:
+ case BOTTOM_CENTER:
+ return true;
+ }
+ return false;
+ }
+
+ public float getX(Element element, float width)
+ {
+ float x = element.getInnerX();
+ switch(this) {
+ case TOP_LEFT:
+ case MIDDLE_LEFT:
+ case BOTTOM_LEFT:
+ break;
+ case TOP_CENTER:
+ case MIDDLE_CENTER:
+ case BOTTOM_CENTER:
+ x += ((element.getInnerWidth() - width) / 2);
+ break;
+ case TOP_RIGHT:
+ case MIDDLE_RIGHT:
+ case BOTTOM_RIGHT:
+ x += (element.getInnerWidth() - width);
+ break;
+ }
+ return x;
+ }
+
+ public float getY(Element element, float height)
+ {
+ float y = element.getInnerY();
+ switch(this) {
+ case TOP_LEFT:
+ case TOP_CENTER:
+ case TOP_RIGHT:
+ y += (element.getInnerHeight() - height);
+ break;
+ case MIDDLE_LEFT:
+ case MIDDLE_CENTER:
+ case MIDDLE_RIGHT:
+ y += ((element.getInnerHeight() - height) / 2);
+ break;
+ case BOTTOM_LEFT:
+ case BOTTOM_CENTER:
+ case BOTTOM_RIGHT:
+ break;
+ }
+ return y;
+ }
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/ui/Assembly.java b/core/src/ch/asynk/gdx/boardgame/ui/Assembly.java
new file mode 100644
index 0000000..c603383
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/ui/Assembly.java
@@ -0,0 +1,63 @@
+package ch.asynk.gdx.boardgame.ui;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.gdx.boardgame.util.IterableSet;
+
+public abstract class Assembly extends Element
+{
+ private IterableSet<Element> children;
+ private Element touched;
+
+ public Assembly(int c)
+ {
+ this.children = new IterableSet<Element>(c);
+ }
+
+ public void add(Element e)
+ {
+ if (children.add(e)) {
+ e.setParent(this);
+ }
+ }
+
+ public void remove(Element e)
+ {
+ if (children.remove(e)) {
+ e.setParent(null);
+ }
+ }
+
+ public Element touched()
+ {
+ return touched;
+ }
+
+ @Override public boolean touch(float x, float y)
+ {
+ for (Element e : children)
+ if (e.touch(x, y)) {
+ touched = e;
+ return true;
+ }
+ touched = null;
+ return false;
+ }
+
+ @Override public void taint()
+ {
+ children.forEach( c -> c.taint() );
+ }
+
+ @Override public void draw(Batch batch)
+ {
+ if (tainted) computeGeometry();
+ children.forEach( c -> c.draw(batch) );
+ }
+
+ @Override public void drawDebug(ShapeRenderer debugShapes)
+ {
+ children.forEach( c -> c.drawDebug(debugShapes) );
+ }
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/ui/Button.java b/core/src/ch/asynk/gdx/boardgame/ui/Button.java
new file mode 100644
index 0000000..9bc1149
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/ui/Button.java
@@ -0,0 +1,67 @@
+package ch.asynk.gdx.boardgame.ui;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.NinePatch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+public class Button extends Patch
+{
+ private Label label;
+ private float spacing; // for label alignment;
+
+ public Button(BitmapFont font, NinePatch patch)
+ {
+ this(font, patch, 0);
+ }
+
+ public Button(BitmapFont font, NinePatch patch, float padding)
+ {
+ this(font, patch, padding, 0);
+ }
+
+ public Button(BitmapFont font, NinePatch patch, float padding, float spacing)
+ {
+ super(patch);
+ this.padding = padding;
+ this.spacing = spacing;
+ label = new Label(font);
+ label.setParent(this);
+ label.setAlignment(Alignment.MIDDLE_CENTER);
+ }
+
+ public void write(String text)
+ {
+ label.write(text);
+ this.tainted = true; // might impact Button's geometry
+ }
+
+ public void setLabelAlignment(Alignment alignment)
+ {
+ label.setAlignment(alignment);
+ }
+
+ @Override public void computeGeometry()
+ {
+ float dd = 2 * (padding + spacing);
+ label.computeGeometry(); // update dimensions
+ rect.width = label.getWidth() + dd;
+ rect.height = label.getHeight() + dd;
+ super.computeGeometry();
+ label.computeGeometry(); // update position
+ }
+
+ @Override public void draw(Batch batch)
+ {
+ if (!visible) return;
+ if (tainted) computeGeometry();
+ super.draw(batch);
+ label.draw(batch);
+ }
+
+ @Override public void drawDebug(ShapeRenderer debugShapes)
+ {
+ super.drawDebug(debugShapes);
+ label.drawDebug(debugShapes);
+ }
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/ui/Element.java b/core/src/ch/asynk/gdx/boardgame/ui/Element.java
new file mode 100644
index 0000000..25f38eb
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/ui/Element.java
@@ -0,0 +1,116 @@
+package ch.asynk.gdx.boardgame.ui;
+
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+import com.badlogic.gdx.math.Rectangle;
+
+import ch.asynk.gdx.boardgame.Drawable;
+import ch.asynk.gdx.boardgame.Touchable;
+
+public abstract class Element implements Drawable, Touchable
+{
+ public boolean blocked;
+ public boolean visible;
+ protected float padding;
+ protected Element parent;
+ protected Alignment alignment;
+ protected Rectangle rect; // outer drawing coordinates
+ protected float x, y; // given position
+ protected boolean tainted; // geometry must be computed
+
+ protected Element()
+ {
+ this.blocked = false;
+ this.visible = true;
+ this.padding = 0;
+ this.parent = null;
+ this.alignment = alignment.RELATIVE;
+ this.rect = new Rectangle(0, 0, 0, 0);
+ this.x = this.y = 0;
+ this.tainted = true;
+ }
+
+ @Override public final float getX() { return rect.x; }
+ @Override public final float getY() { return rect.y; }
+ @Override public final float getWidth() { return rect.width; }
+ @Override public final float getHeight() { return rect.height; }
+
+ @Override public final float getInnerX() { return rect.x + padding; }
+ @Override public final float getInnerY() { return rect.y + padding; }
+ @Override public final float getInnerWidth() { return rect.width - 2 * padding; }
+ @Override public final float getInnerHeight() { return rect.height - 2 * padding; }
+
+ @Override public void drawDebug(ShapeRenderer debugShapes)
+ {
+ debugShapes.rect(getX(), getY(), getWidth(), getHeight());
+ }
+
+ @Override public boolean touch(float x, float y)
+ {
+ if (blocked || !visible) return false;
+ return rect.contains(x, y);
+ }
+
+ public void taint()
+ {
+ this.tainted = true;
+ }
+
+ @Override public void setPosition(float x, float y, float w, float h)
+ {
+ this.x = x;
+ this.y = y;
+ this.rect.width = w;
+ this.rect.height = h;
+ this.tainted = true;
+ // rect.(x,y) will be set in computeGeometry
+ }
+
+ public void setParent(Element parent)
+ {
+ this.parent = parent;
+ this.tainted = true;
+ }
+
+ public void setPadding(float padding)
+ {
+ this.padding = padding;
+ this.tainted = true;
+ }
+
+ public void setAlignment(Alignment alignment)
+ {
+ this.alignment = alignment;
+ this.tainted = true;
+ }
+
+ public final void translate(float dx, float dy)
+ {
+ setPosition(x + dx, y + dy);
+ }
+
+ public final void setPosition(Rectangle r)
+ {
+ setPosition(r.x, r.x, r.width, r.height);
+ }
+
+ public final void setPosition(float x, float y)
+ {
+ setPosition(x, y, rect.width, rect.height);
+ }
+
+ protected void computeGeometry()
+ {
+ if (alignment == Alignment.ABSOLUTE || parent == null) {
+ rect.x = x;
+ rect.y = y;
+ } else if (alignment == Alignment.RELATIVE) {
+ rect.x = x + parent.getInnerX();
+ rect.y = y + parent.getInnerX();
+ } else {
+ rect.x = x + alignment.getX(parent, rect.width);
+ rect.y = y + alignment.getY(parent, rect.height);
+ }
+ this.tainted = false;
+ // System.err.println(String.format("%s : %s", this, rect));
+ }
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/ui/Label.java b/core/src/ch/asynk/gdx/boardgame/ui/Label.java
new file mode 100644
index 0000000..32ae814
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/ui/Label.java
@@ -0,0 +1,61 @@
+package ch.asynk.gdx.boardgame.ui;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.GlyphLayout;
+
+public class Label extends Element
+{
+ private BitmapFont font;
+ private GlyphLayout layout;
+ private float fx;
+ private float fy;
+ private String text;
+
+ public Label(BitmapFont font)
+ {
+ this(font, 0);
+ }
+
+ public Label(BitmapFont font, float padding)
+ {
+ this(font, padding, Alignment.RELATIVE);
+ }
+
+ public Label(BitmapFont font, float padding, Alignment alignment)
+ {
+ super();
+ this.font = font;
+ this.padding = padding;
+ this.alignment = alignment;
+ this.layout = new GlyphLayout();
+ }
+
+ public void write(String text)
+ {
+ write(text, getX(), getY());
+ }
+
+ public void write(String text, float x, float y)
+ {
+ this.text = text;
+ this.layout.setText(font, (text == null) ? "" : text);
+ this.tainted = true;
+ }
+
+ @Override protected void computeGeometry()
+ {
+ this.rect.width = (layout.width + (2 * padding));
+ this.rect.height = (layout.height + (2 * padding));
+ super.computeGeometry();
+ fx = getInnerX();
+ fy = getInnerY() + layout.height;
+ }
+
+ @Override public void draw(Batch batch)
+ {
+ if (!visible) return;
+ if (tainted) computeGeometry();
+ font.draw(batch, layout, fx, fy);
+ }
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/ui/Menu.java b/core/src/ch/asynk/gdx/boardgame/ui/Menu.java
new file mode 100644
index 0000000..aa62483
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/ui/Menu.java
@@ -0,0 +1,126 @@
+package ch.asynk.gdx.boardgame.ui;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.NinePatch;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+
+public class Menu extends Patch
+{
+ private Label title;
+ private Label[] entries;
+ private Integer touchedItem;
+
+ private int entriesOffset; // horizontal offset
+ private int titleSpacing; // between title and entries
+ private int entriesSpacing; // between entries
+
+ public Menu(BitmapFont font, NinePatch patch, String title, String[] entries)
+ {
+ super(patch);
+ this.touchedItem = null;
+ setTitle(font, title);
+ setEntries(font, entries);
+ }
+
+ public void setTitle(BitmapFont font, String title)
+ {
+ this.title = new Label(font);
+ this.title.write(title);
+ this.tainted = true;
+ }
+
+ public void setEntries(BitmapFont font, String[] entries)
+ {
+ this.entries = new Label[entries.length];
+ for (int i = 0; i < entries.length; i++) {
+ Label l = new Label(font);
+ l.write(entries[i]);
+ this.entries[i] = l;
+ }
+ this.tainted = true;
+ }
+
+ public void setLabelsOffset(int entriesOffset)
+ {
+ this.entriesOffset = entriesOffset;
+ this.tainted = true;
+ }
+
+ public void setPaddings(int titlePadding, int labelPadding)
+ {
+ this.title.setPadding(titlePadding);
+ for (Label label : entries) {
+ label.setPadding(labelPadding);
+ }
+ this.tainted = true;
+ }
+
+ public void setSpacings(int titleSpacing, int entriesSpacing)
+ {
+ this.titleSpacing = titleSpacing;
+ this.entriesSpacing = entriesSpacing;
+ this.tainted = true;
+ }
+
+ @Override public void computeGeometry()
+ {
+ title.computeGeometry();
+ float h = title.getHeight();
+ float w = title.getWidth();
+ for (Label label : entries) {
+ label.computeGeometry();
+ h += label.getHeight();
+ float t = label.getWidth() + entriesOffset;
+ if (t > w)
+ w = t;
+ }
+ h += (titleSpacing + (entriesSpacing * (entries.length - 1)) + (2 * padding));
+ w += (2 * padding);
+
+ rect.width = w;
+ rect.height = h;
+ super.computeGeometry();
+
+ float x = getInnerX();
+ float y = getInnerY();
+
+ // setPosition() will trigger computeGeometry() from within draw()
+ for (int i = entries.length - 1; i >= 0; i--) {
+ entries[i].setPosition(x + entriesOffset, y);
+ y += entries[i].getHeight() + entriesSpacing;
+ }
+ y -= entriesSpacing;
+ y += titleSpacing;
+ title.setPosition(x, y);
+ }
+
+ public Integer touched()
+ {
+ return touchedItem;
+ }
+
+ @Override public boolean touch(float x, float y)
+ {
+ touchedItem = null;
+ if (super.touch(x, y)) {
+ for (int i = 0; i < entries.length; i++) {
+ if (entries[i].touch(x, y)) {
+ touchedItem = i;
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override public void draw(Batch batch)
+ {
+ if (!visible) return;
+ if (tainted) computeGeometry();
+ super.draw(batch);
+ title.draw(batch);
+ for (Label label : entries) {
+ label.draw(batch);
+ }
+ }
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/ui/Patch.java b/core/src/ch/asynk/gdx/boardgame/ui/Patch.java
new file mode 100644
index 0000000..df2cc06
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/ui/Patch.java
@@ -0,0 +1,23 @@
+package ch.asynk.gdx.boardgame.ui;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.NinePatch;
+
+public class Patch extends Element
+{
+ private NinePatch patch;
+
+ public Patch(NinePatch patch)
+ {
+ super();
+ this.patch = patch;
+ setPosition(0, 0, patch.getTotalWidth(), patch.getTotalHeight());
+ }
+
+ @Override public void draw(Batch batch)
+ {
+ if (!visible) return;
+ if (tainted) computeGeometry();
+ patch.draw(batch, getX(), getY(), getWidth(), getHeight());
+ }
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/ui/Root.java b/core/src/ch/asynk/gdx/boardgame/ui/Root.java
new file mode 100644
index 0000000..2353c41
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/ui/Root.java
@@ -0,0 +1,28 @@
+package ch.asynk.gdx.boardgame.ui;
+
+import com.badlogic.gdx.math.Rectangle;
+
+public class Root extends Assembly
+{
+ public Root(int c)
+ {
+ super(c);
+ this.alignment = Alignment.ABSOLUTE;
+ }
+
+ public void resize(Rectangle r)
+ {
+ resize(r.x, r.y, r.width, r.height);
+ }
+
+ public void resize(float width, float height)
+ {
+ resize(getX(), getY(), width, height);
+ }
+
+ public void resize(float x, float y, float width, float height)
+ {
+ setPosition(x, y, width, height);
+ taint();
+ }
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/util/Collection.java b/core/src/ch/asynk/gdx/boardgame/util/Collection.java
new file mode 100644
index 0000000..1d7d5e4
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/util/Collection.java
@@ -0,0 +1,28 @@
+package ch.asynk.gdx.boardgame.util;
+
+import java.util.Iterator;
+
+public interface Collection<E> extends Iterator, Iterable<E>
+{
+ public int size();
+
+ public boolean isEmpty();
+
+ public void clear();
+
+ public void ensureCapacity(int c);
+
+ public boolean contains(E e);
+
+ public E get(int idx);
+
+ public int indexOf(E e);
+
+ public boolean add(E e);
+
+ public boolean insert(E e, int idx);
+
+ public E remove(int idx);
+
+ public boolean remove(E e);
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/util/IterableArray.java b/core/src/ch/asynk/gdx/boardgame/util/IterableArray.java
new file mode 100644
index 0000000..ba57de1
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/util/IterableArray.java
@@ -0,0 +1,143 @@
+package ch.asynk.gdx.boardgame.util;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+public class IterableArray<E> implements Collection<E>
+{
+ private int idx;
+ private int s;
+ private int c;
+ transient E[] data;
+
+ @SuppressWarnings("unchecked")
+ public IterableArray(int capacity)
+ {
+ this.s = 0;
+ this.c = capacity;
+ this.data = (E[]) new Object[c];
+ }
+
+ @Override public int size()
+ {
+ return s;
+ }
+
+ @Override public boolean isEmpty()
+ {
+ return (s == 0);
+ }
+
+ @Override public void clear()
+ {
+ for (int i = 0; i < s; i++)
+ data[i] = null;
+ s = 0;
+ }
+
+ @Override public void ensureCapacity(int min)
+ {
+ if (c > min) return;
+ c += (c >> 1);
+ if (c < min)
+ c = min;
+ data = Arrays.copyOf(data, c);
+ }
+
+ @Override public boolean contains(E e)
+ {
+ if (e == null) {
+ for (int i = 0; i < s; i++) {
+ if (data[i] == null)
+ return true;
+ }
+ } else {
+ for (int i = 0; i < s; i++) {
+ if (e.equals(data[i]))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override public E get(int i)
+ {
+ return data[i];
+ }
+
+ @Override public int indexOf(E e)
+ {
+ for (int i = 0; i < data.length; i++) {
+ if (data[i] != null && data[i].equals(e))
+ return i;
+ }
+ return -1;
+ }
+
+ @Override public boolean add(E e)
+ {
+ ensureCapacity(s + 1);
+ data[s] = e;
+ s += 1;
+ return true;
+ }
+
+ @Override public boolean insert(E e, int idx)
+ {
+ ensureCapacity(s + 1);
+ System.arraycopy(data, idx, data, idx+1, (s - idx));
+ data[idx] = e;
+ s += 1;
+ return true;
+ }
+
+ @Override public E remove(int i)
+ {
+ E e = data[i];
+ int m = (s - i - 1);
+ if (m > 0)
+ System.arraycopy(data, i+1, data, i, m);
+ data[--s] = null;
+
+ return e;
+ }
+
+ @Override public boolean remove(E e)
+ {
+ for (int i = 0; i < s; i++) {
+ if (e.equals(data[i])) {
+ int m = (s - i - 1);
+ if (m > 0)
+ System.arraycopy(data, i+1, data, i, m);
+ data[--s] = null;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override public Iterator<E> iterator()
+ {
+ this.idx = 0;
+ return (Iterator<E>) this;
+ }
+
+ @Override public boolean hasNext()
+ {
+ return (idx < s);
+ }
+
+ @Override public E next()
+ {
+ E e = get(idx);
+ idx += 1;
+ return e;
+ }
+
+ @Override public void remove()
+ {
+ idx -= 1;
+ remove(idx);
+ }
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/util/IterableQueue.java b/core/src/ch/asynk/gdx/boardgame/util/IterableQueue.java
new file mode 100644
index 0000000..16ab442
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/util/IterableQueue.java
@@ -0,0 +1,19 @@
+package ch.asynk.gdx.boardgame.util;
+
+public class IterableQueue<E> extends IterableArray<E>
+{
+ public IterableQueue(int n)
+ {
+ super(n);
+ }
+
+ public void enqueue(E e)
+ {
+ add(e);
+ }
+
+ public E dequeue()
+ {
+ return remove(0);
+ }
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/util/IterableSet.java b/core/src/ch/asynk/gdx/boardgame/util/IterableSet.java
new file mode 100644
index 0000000..4132771
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/util/IterableSet.java
@@ -0,0 +1,16 @@
+package ch.asynk.gdx.boardgame.util;
+
+public class IterableSet<E> extends IterableArray<E>
+{
+ public IterableSet(int n)
+ {
+ super(n);
+ }
+
+ @Override public boolean add(E e)
+ {
+ if (contains(e)) return false;
+ super.add(e);
+ return true;
+ }
+}
diff --git a/core/src/ch/asynk/gdx/boardgame/util/IterableStack.java b/core/src/ch/asynk/gdx/boardgame/util/IterableStack.java
new file mode 100644
index 0000000..7400718
--- /dev/null
+++ b/core/src/ch/asynk/gdx/boardgame/util/IterableStack.java
@@ -0,0 +1,26 @@
+package ch.asynk.gdx.boardgame.util;
+
+public class IterableStack<E> extends IterableArray<E>
+{
+ public IterableStack(int n)
+ {
+ super(n);
+ }
+
+ public void push(E e)
+ {
+ add(e);
+ }
+
+ public E pop()
+ {
+ if (size() <= 0) return null;
+ return remove(size() - 1);
+ }
+
+ public E getTop()
+ {
+ if (size() <= 0) return null;
+ return get(size() - 1);
+ }
+}