├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── rlforj │ ├── IBoard.java │ ├── examples │ ├── ConeFovExample.java │ ├── Demo.java │ ├── ExampleBoard.java │ ├── FovExample.java │ └── LosExample.java │ ├── los │ ├── BresLos.java │ ├── BresOpportunisticLos.java │ ├── CLikeIterator.java │ ├── ConePrecisePremisive.java │ ├── FovType.java │ ├── GenericCalculateProjection.java │ ├── IConeFovAlgorithm.java │ ├── IFovAlgorithm.java │ ├── ILosAlgorithm.java │ ├── LosException.java │ ├── PrecisePermissive.java │ ├── RecordQuadrantVisitBoard.java │ └── ShadowCasting.java │ ├── math │ ├── Line2I.java │ └── Point.java │ ├── pathfinding │ ├── AStar.java │ └── IPathAlgorithm.java │ └── util │ ├── BresenhamLine.java │ ├── Directions.java │ ├── HeapNode.java │ ├── MathUtils.java │ └── SimpleHeap.java └── test └── java └── rlforj ├── los └── test │ ├── ConeFovTest.java │ ├── FovPrecisePermissiveTest.java │ ├── FovShadowCastingTest.java │ ├── FovSuite.java │ ├── FovTest.java │ ├── LosPrecisePermissiveTest.java │ ├── LosSuite.java │ ├── LosTest.java │ ├── ProjectionTest.java │ └── TestBoard.java ├── pathfinding └── test │ ├── AStarTest.java │ └── MockBoard.java └── util └── test ├── MathUtilsTest.java ├── MockBoard.java ├── MockBoardTest.java └── SimpleHeapTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .svn 2 | .classpath 3 | .project 4 | target/ 5 | .settings/ 6 | rlforj-alt.iml 7 | .idea/ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: java 4 | jdk: 5 | - oraclejdk8 6 | 7 | install: true 8 | 9 | script: 10 | - mvn clean verify 11 | 12 | cache: 13 | directories: 14 | - '$HOME/.m2/repository' 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 2 | Copyright (c) 2013, kba 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, this 12 | list of conditions and the following disclaimer in the documentation and/or 13 | other materials provided with the distribution. 14 | 15 | * Neither the name of the {organization} nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rlforj-alt 2 | 3 | [![Latest Github release](https://img.shields.io/github/release/fabio-t/rlforj-alt.svg)](https://github.com/fabio-t/rlforj-alt/releases/latest) 4 | [![Build Status](https://travis-ci.org/fabio-t/rlforj-alt.svg?branch=master)](https://travis-ci.org/fabio-t/rlforj-alt) 5 | 6 | Roguelike Library For Java (Alternative version). 7 | 8 | The main aim of this library is to provide a set of simple, yet efficient, algorithms for common problems in 9 | the development of roguelikes and other games: 10 | 11 | * **FoV**: the field of view is the area around a game entity that is visible. Commonly, things like walls and trees 12 | block vision. This is provided in both circular and conic format. 13 | 14 | * **LoS**: the line of sight is a linear path from a starting position to a target position, provided there 15 | no obstacles between them. It's useful for ranged attacks, for example. 16 | 17 | * **Pathfinding**: given a map, we want to find the optimal, nonlinear path between a start and end points. This 18 | may also take account of the terrain, to give preference to paths avoiding slow-moving terrains, for example. 19 | 20 | Some terrain generation utilities will also be available in the future. 21 | 22 | ## Install 23 | 24 | 1. First add Jitpack as a repository inside your pom.xml 25 | 26 | ```xml 27 | 28 | 29 | jitpack.io 30 | https://jitpack.io 31 | 32 | 33 | ``` 34 | 35 | 2. Then add `rlforj-alt` as a dependency (make sure to change `version` to one of the 36 | [releases](https://github.com/fabio-t/rlforj-alt/releases)): 37 | 38 | ```xml 39 | 40 | com.github.fabio-t 41 | rlforj-alt 42 | version 43 | 44 | ``` 45 | 46 | ## Use 47 | 48 | The main component of rlforj-alt is the so called `Board`. You must implement the [IBoard](src/main/java/rlforj/IBoard.java) 49 | interface before doing anything: 50 | 51 | ```java 52 | public interface IBoard 53 | { 54 | boolean contains(int x, int y); 55 | 56 | boolean blocksLight(int x, int y); 57 | 58 | boolean blocksStep(int x, int y); 59 | 60 | void visit(int x, int y); 61 | } 62 | ``` 63 | 64 | * `contains` should check boundaries of your map/level 65 | 66 | * `blocksLight` should return true for all positions that block vision (eg, walls). Used by Fov/Los algorithms 67 | 68 | * `blocksStep` should return true for all positions that block movement. Used by pathfinding algorithms 69 | 70 | * `visit` will be called by the Fov/Los algorithms on each visible cell. You may 71 | use it to store the visible cells or to manipulate the map, or whatever you wish. See below for details on how it's 72 | used. 73 | 74 | ### Field of View 75 | 76 | To explore all the visible cells (what is generally called `Field of View`) you must choose one of the available 77 | Fov algorithms implementing the [IFovAlgorithm](src/main/java/rlforj/los/IFovAlgorithm.java) interface: 78 | 79 | ```java 80 | public interface IFovAlgorithm 81 | { 82 | void visitFov(IBoard b, int x, int y, int distance); 83 | } 84 | ``` 85 | 86 | Then you can use it like this: 87 | 88 | ```java 89 | IBoard map = new MyMap(); 90 | 91 | // choose one of these 92 | IFovAlgorithm a = new ShadowCasting(); 93 | IFovAlgorithm a = new PrecisePermissive(); 94 | 95 | // calls IBoard#visit on all visible cells from the origin and up to the given radius 96 | a.visitFov(map, originX, originY, radius); 97 | ``` 98 | 99 | The method `IFovAlgorithm#visitFov` is guaranteed to call `IBoard#visit` **only once**, and always before 100 | `IBoard#blocksLight`. This last thing is important for things like a bomb or fireball: whatever blocking cell the 101 | Fov algorithm encounters, it's destroyed in the `FireballBoard#visit` - and thus the Fov will continue to visit surrounding 102 | cells that were previously shadowed by this cell. 103 | 104 | `IBoard#blocksLight` and `IBoard#blocksStep` can be called multiple times at each location. 105 | 106 | ### Conic Field of View 107 | 108 | In alternative to the above, if you want to only visit a **conic field of view** (eg, for breathing fire, or to simulate a 109 | directional light), you can choose one of the algorithms implementing the [IConeFovAlgorithm](src/main/java/rlforj/los/IConeFovAlgorithm.java) 110 | interface: 111 | 112 | ```java 113 | public interface IConeFovAlgorithm 114 | { 115 | void visitConeFov(IBoard b, int x, int y, int distance, int startAngle, int endAngle); 116 | } 117 | ``` 118 | 119 | Then you can use it like this: 120 | 121 | ```java 122 | IBoard map = new MyMap(); 123 | 124 | // choose one of these 125 | IConeFovAlgorithm a = new ShadowCasting(); 126 | IConeFovAlgorithm a = new ConePrecisePermissive(); 127 | 128 | // visit all visible cells from the origin and up the given radius, in a cone of 180 degrees 129 | a.visitConeFov(map, originX, originY, radius, 0, 180); 130 | ``` 131 | 132 | ### Line of Sight 133 | 134 | To find a straight, unobstructed line between a start and end point is a task called `Line of Sight`. This is very 135 | useful for ranged attacks. 136 | 137 | rlforj-alt supports many Los algorithms, all implementing the [ILosAlgorithm](src/main/java/rlforj/los/ILosAlgorithm.java) 138 | interface: 139 | 140 | ```java 141 | public interface ILosAlgorithm 142 | { 143 | boolean exists(IBoard b, int startX, int startY, int endX, int endY, boolean savePath); 144 | 145 | List getPath(); 146 | } 147 | 148 | ``` 149 | 150 | If you only need to **test** for line of sight, set the last argument, `savePath`, to `false`. 151 | 152 | More commonly, you will need the path (if one exists), so do this: 153 | 154 | ```java 155 | IBoard map = new MyMap(); 156 | 157 | // choose one of these 158 | ILosAlgorithm a = new BresLos(symmetric); // symmetric can be true or false 159 | ILosAlgorithm a = new BresOpportunisticLos(); 160 | ILosAlgorithm a = new ShadowCasting(); 161 | ILosAlgorithm a = new PrecisePermissive(); 162 | 163 | List path; 164 | if (a.exists(map, startX, startY, endX, endY, true)) 165 | path = a.getPath(); 166 | else 167 | // do something else? 168 | ``` 169 | 170 | Example of line of sight path: 171 | 172 | ``` 173 | ...##...........# 174 | ................. 175 | #...........#..*# 176 | ###...#......./.. 177 | ............./... 178 | ..........#/-.... 179 | #........./...... 180 | ........#/..#.... 181 | ....#...@..#..... 182 | ........#........ 183 | .......#...#..... 184 | ...#......#...... 185 | ...#..#......#... 186 | #.........#...... 187 | ...#.#........... 188 | ...#............. 189 | .............#... 190 | ``` 191 | 192 | If you are unsure which Los algorithm to use, consider this: 193 | 194 | - If you need absolute certainty that **if a point is in your Field of View, then is also in your Line of Sight**, 195 | then use the same algorithm, `ShadowCasting` or `PrecisePermissive`, for Los too 196 | 197 | - If you need **faster and prettier** Line of Sight, then choose one of the `Bresenham`. We advise you to use 198 | `BresLos(true)`, eg symmetric Bresenham, unless you have a reason not to 199 | 200 | - all algorithms return the same or very similar paths in "good" cases, but may vary wildly in more complicated ones 201 | 202 | A final note: if a line of sight cannot be established, the path will be `null`. If `path` is not `null`, it always 203 | includes the start and end point. 204 | 205 | ### Pathfinding 206 | 207 | Pathfinding is the task of finding an unobstructed path from a start to an end point. Contrarily to Los, 208 | the path does not need to be a line. 209 | 210 | Only one algorithm is supported for now, the king of pathfinding: AStar. Use it like this: 211 | 212 | ```java 213 | IBoard map = new MyMap(); 214 | 215 | // choose one of these 216 | diag = true; // true if you allow diagonal movement, false otherwise 217 | AStar a = new AStar(map, map.width(), map.height(), diag); 218 | 219 | radius = -1; // this will search the whole board - it can be very expensive to do so! 220 | radius = 10; // this will only search the cells in a radius of 10 cells from the starting point 221 | Point[] path = a.findPath(startX, startY, endX, endY, radius) 222 | ``` 223 | 224 | If `path` is not `null`, it always includes the start and end point. 225 | 226 | ## Examples 227 | 228 | Let's see some of the algorithms in action. If you wish to run them yourself, have a look at the 229 | [examples folder](src/main/java/rlforj/examples/). 230 | 231 | ### Fov ShadowCasting 232 | 233 | ``` 234 | .#... 235 | ...... . 236 | . ..... ... 237 | ...#...##.... 238 | ........... 239 | #......... 240 | #.@...... 241 | ............ 242 | ............. 243 | ..#.#....#.#. 244 | ...##...# . 245 | # ..... 246 | ..... 247 | ``` 248 | 249 | ### Fov PrecisePermissive 250 | 251 | ``` 252 | .#... 253 | ...... . 254 | . ..... ... 255 | ...#...##.... 256 | ............ 257 | #......... 258 | #.@...... 259 | ............. 260 | ............. 261 | ..#.#....#.#. 262 | ..##...# . 263 | # ..... 264 | ..... 265 | ``` 266 | 267 | ### Conic Fov ShadowCasting 268 | 269 | ``` 270 | @ 271 | .... 272 | .... 273 | ...... 274 | ......# 275 | ....... 276 | .....#.. 277 | ........ 278 | ....... 279 | #....#. 280 | ``` 281 | 282 | ### Conic Fov PrecisePermissive 283 | 284 | ``` 285 | @ 286 | ... 287 | ..... 288 | ........ 289 | ......# 290 | ....... 291 | .....# 292 | ....... 293 | ...... 294 | ##... 295 | # 296 | ``` 297 | 298 | Disclaimer 299 | --------- 300 | 301 | This was originally a fork from [kba/rlforj](https://github.com/kba/rlforj) (version 3.0), but has since diverged. 302 | See the LICENSE file for copyright information. 303 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 4.0.0 10 | 11 | com.github.fabio-t 12 | rlforj-alt 13 | 0.3.0 14 | jar 15 | 16 | Roguelike Library For Java (Alternative version) 17 | 18 | 19 | UTF-8 20 | 21 | 22 | 23 | 24 | junit 25 | junit 26 | [4,5) 27 | test 28 | 29 | 30 | 31 | 32 | 33 | 34 | maven-compiler-plugin 35 | 3.7.0 36 | 37 | 1.8 38 | 1.8 39 | 40 | 41 | 42 | 43 | org.apache.maven.plugins 44 | maven-release-plugin 45 | 2.5.3 46 | 47 | @{project.version} 48 | 49 | 50 | 51 | 52 | org.apache.maven.plugins 53 | maven-source-plugin 54 | 3.0.1 55 | 56 | 57 | attach-sources 58 | generate-resources 59 | 60 | jar-no-fork 61 | 62 | 63 | 64 | 65 | 66 | 67 | org.apache.maven.plugins 68 | maven-javadoc-plugin 69 | 3.0.0-M1 70 | 71 | 72 | attach-javadocs 73 | 74 | jar 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | scm:git:git@fabio-t/rlforj-alt.git 85 | scm:git:git@fabio-t/rlforj-alt.git 86 | scm:git:git@fabio-t/rlforj-alt.git 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/main/java/rlforj/IBoard.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj; 8 | 9 | import rlforj.los.IFovAlgorithm; 10 | import rlforj.los.ILosAlgorithm; 11 | import rlforj.pathfinding.AStar; 12 | 13 | /** 14 | * An interface board that allows visibility algorithms to 15 | * decide which points are in the board, which points are 16 | * obstacles to this form of visibility, and visit those points 17 | * on the board. 18 | * 19 | * @author sdatta 20 | */ 21 | public interface IBoard 22 | { 23 | /** 24 | * Is the location (x, y) inside the board ? 25 | * Note: If a point is outside, any radially 26 | * outward points are not checked, so the area must 27 | * be concave. 28 | * 29 | * @param x x coordinate 30 | * @param y y coordinate 31 | * @return true if point is inside board, false otherwise 32 | */ 33 | boolean contains(int x, int y); 34 | 35 | /** 36 | * Is the location (x, y) an obstacle? Generic obstacles may block 37 | * either light (for FOV/LoS) or movement. Pathfinding usually 38 | * looks for cells that are both visible and don't block movement so 39 | * it will probably use this method. LoS/FoV might just want to know 40 | * about light blocking. 41 | * 42 | * @param x x coordinate 43 | * @param y y coordinate 44 | * @return true if cell at position either blocks light, or movement, or both 45 | * @see AStar 46 | */ 47 | default boolean isObstacle(final int x, final int y) 48 | { 49 | return blocksLight(x, y) || blocksStep(x, y); 50 | } 51 | 52 | /** 53 | * Does the location (x, y) block light? This is mainly used 54 | * by the FOV/LoS algorithms, since they don't care if you can step 55 | * there or not: just if you see it. 56 | * 57 | * @param x x coordinate 58 | * @param y y coordinate 59 | * @return true if cell at position blocks light 60 | * @see ILosAlgorithm 61 | * @see IFovAlgorithm 62 | */ 63 | boolean blocksLight(int x, int y); 64 | 65 | /** 66 | * Does the location (x, y) block movement? Some implementations might 67 | * not care about light, for example if we have an "X-ray view" creature we'll 68 | * want to use special pathfinding/los/fov algorithms that only call blocksStep. 69 | * 70 | * @param x x coordinate 71 | * @param y y coordinate 72 | * @return true if cell at position blocks movement 73 | */ 74 | boolean blocksStep(int x, int y); 75 | 76 | /** 77 | * Location (x,y) is visible 78 | * Visit the location (x,y) 79 | *

80 | * This can involve saving the points in a collection, 81 | * setting flags on a 2D map etc.

82 | * 83 | * @param x x coordinate 84 | * @param y y coordinate 85 | */ 86 | void visit(int x, int y); 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/rlforj/examples/ConeFovExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.examples; 8 | 9 | import rlforj.los.ConePrecisePremisive; 10 | import rlforj.los.IConeFovAlgorithm; 11 | import rlforj.los.ShadowCasting; 12 | 13 | import java.util.Random; 14 | 15 | public class ConeFovExample 16 | { 17 | final static int width = 21; 18 | final static int height = 21; 19 | 20 | public static void main(final String[] args) 21 | { 22 | final ExampleBoard b = new ExampleBoard(width, height); 23 | final Random rand = new Random(); 24 | for (int i = 0; i < 30; i++) 25 | { 26 | b.setObstacle(rand.nextInt(width), rand.nextInt(height)); 27 | } 28 | // int startAngle=rand.nextInt(360), finishAngle=rand.nextInt(360); 29 | final int startAngle = 30; 30 | final int finishAngle = 70; 31 | System.out.println(startAngle + " degrees to " + finishAngle + " degrees"); 32 | System.out.println("ShadowCasting"); 33 | IConeFovAlgorithm a = new ShadowCasting(); 34 | a.visitConeFieldOfView(b, width / 2, height / 2, width / 3 + 1, startAngle, finishAngle); 35 | b.print(width / 2, height / 2); 36 | 37 | b.resetVisitedAndMarks(); 38 | System.out.println("Precise Permissive"); 39 | a = new ConePrecisePremisive(); 40 | a.visitConeFieldOfView(b, width / 2, height / 2, width / 3 + 1, startAngle, finishAngle); 41 | b.print(width / 2, height / 2); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/rlforj/examples/Demo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.examples; 8 | 9 | public class Demo 10 | { 11 | public static void main(final String[] args) 12 | { 13 | System.out.println("Field of Vision Example \n"); 14 | FovExample.main(new String[0]); 15 | System.out.println("Cone Field of Vision Example \n"); 16 | ConeFovExample.main(new String[0]); 17 | System.out.println("Line of Sight and Projection Example \n"); 18 | LosExample.main(new String[0]); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/rlforj/examples/ExampleBoard.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.examples; 8 | 9 | import rlforj.IBoard; 10 | import rlforj.math.Point; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | public class ExampleBoard implements IBoard 16 | { 17 | public char visibleFloor = '.', invisibleFloor = ' ', invisibleWall = ' '; 18 | int w, h; 19 | boolean[][] obstacles; 20 | boolean[][] visited; 21 | Map marks = new HashMap<>(); 22 | 23 | public ExampleBoard(final int w, final int h) 24 | { 25 | this.w = w; 26 | this.h = h; 27 | 28 | obstacles = new boolean[w][h]; 29 | visited = new boolean[w][h]; 30 | } 31 | 32 | public void resetVisitedAndMarks() 33 | { 34 | marks.clear(); 35 | visited = new boolean[w][h]; 36 | } 37 | 38 | public void mark(final int x, final int y, final char c) 39 | { 40 | marks.put(new Point(x, y), c); 41 | } 42 | 43 | public void setObstacle(final int x, final int y) 44 | { 45 | obstacles[x][y] = true; 46 | } 47 | 48 | public boolean contains(final int x, final int y) 49 | { 50 | return x >= 0 && y >= 0 && x < w && y < h; 51 | } 52 | 53 | public boolean isObstacle(final int x, final int y) 54 | { 55 | return obstacles[x][y]; 56 | } 57 | 58 | @Override 59 | public boolean blocksLight(final int x, final int y) 60 | { 61 | return isObstacle(x, y); 62 | } 63 | 64 | @Override 65 | public boolean blocksStep(final int x, final int y) 66 | { 67 | return isObstacle(x, y); 68 | } 69 | 70 | public void visit(final int x, final int y) 71 | { 72 | visited[x][y] = true; 73 | } 74 | 75 | public void print(final int ox, final int oy) 76 | { 77 | final Point p = new Point(0, 0); 78 | for (int j = 0; j < h; j++) 79 | { 80 | for (int i = 0; i < w; i++) 81 | { 82 | p.x = i; 83 | p.y = j; 84 | final Character c = marks.get(p); 85 | if (c != null) 86 | System.out.print(c); 87 | else if (i == ox && j == oy) 88 | System.out.print('@'); 89 | else 90 | System.out.print(visited[i][j] ? 91 | (obstacles[i][j] ? '#' : visibleFloor) : 92 | (obstacles[i][j] ? invisibleWall : invisibleFloor)); 93 | } 94 | System.out.println(); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/rlforj/examples/FovExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.examples; 8 | 9 | import rlforj.los.IFovAlgorithm; 10 | import rlforj.los.PrecisePermissive; 11 | import rlforj.los.ShadowCasting; 12 | 13 | import java.util.Random; 14 | 15 | public class FovExample 16 | { 17 | final static int width = 17; 18 | final static int height = 17; 19 | 20 | /* 21 | * Each time creates a 21x21 area with random obstacles and 22 | * runs ShadowCasting and Precise Permissive algorithms 23 | * on it, printing out the results in stdout. 24 | */ 25 | public static void main(final String[] args) 26 | { 27 | final ExampleBoard b = new ExampleBoard(width, height); 28 | final Random rand = new Random(); 29 | for (int i = 0; i < 30; i++) 30 | { 31 | b.setObstacle(rand.nextInt(width), rand.nextInt(height)); 32 | } 33 | 34 | System.out.println("ShadowCasting"); 35 | IFovAlgorithm a = new ShadowCasting(); 36 | a.visitFoV(b, width / 2, height / 2, width / 3 + 1); 37 | b.print(width / 2, height / 2); 38 | 39 | b.resetVisitedAndMarks(); 40 | System.out.println("Precise Permissive"); 41 | a = new PrecisePermissive(); 42 | a.visitFoV(b, width / 2, height / 2, width / 3 + 1); 43 | b.print(width / 2, height / 2); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/rlforj/examples/LosExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.examples; 8 | 9 | import rlforj.los.*; 10 | import rlforj.math.Point; 11 | 12 | import java.util.List; 13 | import java.util.Random; 14 | 15 | public class LosExample 16 | { 17 | final static int width = 17; 18 | final static int height = 17; 19 | 20 | public static void main(final String[] args) 21 | { 22 | final ExampleBoard b = new ExampleBoard(width, height); 23 | final Random rand = new Random(); 24 | for (int i = 0; i < 30; i++) 25 | { 26 | b.setObstacle(rand.nextInt(width), rand.nextInt(height)); 27 | } 28 | final int x1 = rand.nextInt(width); 29 | final int y1 = rand.nextInt(height); 30 | b.invisibleFloor = '.'; 31 | b.invisibleWall = '#'; 32 | 33 | displayLos(new ShadowCasting(), "Shadowcasting", b, x1, y1); 34 | displayLos(new PrecisePermissive(), "Precise Permissive", b, x1, y1); 35 | displayLos(new BresLos(false), "Bresenham", b, x1, y1); 36 | final BresLos bl = new BresLos(true); 37 | displayLos(bl, "Symmetric Bresenham", b, x1, y1); 38 | displayLos(new BresOpportunisticLos(), "Opportunistic Bresenham", b, x1, y1); 39 | } 40 | 41 | /** 42 | * @param a algorithm instance 43 | * @param algoName The name of the algorithm 44 | * @param b board 45 | * @param x1 x position 46 | * @param y1 y position 47 | */ 48 | private static void displayLos(final ILosAlgorithm a, final String algoName, final ExampleBoard b, final int x1, 49 | final int y1) 50 | { 51 | final boolean los; 52 | final List path; 53 | b.resetVisitedAndMarks(); 54 | System.out.println(algoName); 55 | los = a.exists(b, width / 2, height / 2, x1, y1, true); 56 | 57 | path = a.getPath(); 58 | markProjectPath(b, path); 59 | if (los) 60 | b.mark(x1, y1, '*'); 61 | else 62 | b.mark(x1, y1, '?'); 63 | 64 | System.out.println("Los " + (los ? "exists" : "does not exist")); 65 | b.print(width / 2, height / 2); 66 | } 67 | 68 | private static void markProjectPath(final ExampleBoard b, final List path) 69 | { 70 | if (path.size() < 1) 71 | return; 72 | 73 | int lastx = path.get(0).x, lasty = path.get(0).y; 74 | for (int i = 1; i < path.size(); i++) 75 | { 76 | final Point p = path.get(i); 77 | final int x = p.x; 78 | final int y = p.y; 79 | if (x != lastx) 80 | { 81 | if (y != lasty) 82 | { 83 | b.mark(x, y, ((x - lastx) * (y - lasty) > 0) ? '\\' : '/'); 84 | } 85 | else 86 | b.mark(x, y, '-'); 87 | } 88 | else 89 | b.mark(x, y, '|'); 90 | 91 | lastx = x; 92 | lasty = y; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/rlforj/los/BresLos.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los; 8 | 9 | import rlforj.IBoard; 10 | import rlforj.math.Point; 11 | import rlforj.util.BresenhamLine; 12 | 13 | import java.util.List; 14 | import java.util.Vector; 15 | 16 | /** 17 | * Bresenham LOS class. 18 | * Checks if a bresenham line can be drawn from 19 | * source to destination. If symmetric, also checks 20 | * the alternate Bresenham line from destination to 21 | * source. 22 | * 23 | * @author sdatta 24 | */ 25 | public class BresLos implements ILosAlgorithm 26 | { 27 | 28 | private boolean symmetricEnabled = false; 29 | 30 | private Vector path; 31 | 32 | public BresLos(final boolean symmetric) 33 | { 34 | symmetricEnabled = symmetric; 35 | } 36 | 37 | public boolean exists(final IBoard b, final int startX, final int startY, final int endX, final int endY, 38 | final boolean savePath) 39 | { 40 | final int dx = startX - endX; 41 | final int dy = startY - endY; 42 | final int adx = dx > 0 ? dx : -dx; 43 | final int ady = dy > 0 ? dy : -dy; 44 | final int len = (adx > ady ? adx : ady) + 1;//Max number of points on the path. 45 | 46 | if (savePath) 47 | path = new Vector<>(len); 48 | 49 | // array to store path. 50 | final int[] px = new int[len]; 51 | final int[] py = new int[len]; 52 | 53 | //Start to finish path 54 | BresenhamLine.plot(startX, startY, endX, endY, px, py); 55 | 56 | boolean los = false; 57 | for (int i = 0; i < len; i++) 58 | { 59 | if (savePath) 60 | { 61 | path.add(new Point(px[i], py[i])); 62 | } 63 | if (px[i] == endX && py[i] == endY) 64 | { 65 | los = true; 66 | break; 67 | } 68 | if (b.blocksLight(px[i], py[i])) 69 | break; 70 | } 71 | // Direct path couldn't find LOS so try alternate path 72 | if (!los && symmetricEnabled) 73 | { 74 | final int[] px1; 75 | final int[] py1; 76 | // allocate space for alternate path 77 | px1 = new int[len]; 78 | py1 = new int[len]; 79 | // finish to start path. 80 | BresenhamLine.plot(endX, endY, startX, startY, px1, py1); 81 | 82 | final Vector oldpath = path; 83 | path = new Vector<>(len); 84 | for (int i = len - 1; i > -1; i--) 85 | { 86 | if (savePath) 87 | { 88 | path.add(new Point(px1[i], py1[i])); 89 | } 90 | if (px1[i] == endX && py1[i] == endY) 91 | { 92 | los = true; 93 | break; 94 | } 95 | if (b.blocksLight(px1[i], py1[i])) 96 | break; 97 | } 98 | 99 | if (savePath) 100 | path = oldpath.size() > path.size() ? oldpath : path; 101 | } 102 | 103 | return los; 104 | } 105 | 106 | public List getPath() 107 | { 108 | return path; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/rlforj/los/BresOpportunisticLos.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los; 8 | 9 | import rlforj.IBoard; 10 | import rlforj.math.Point; 11 | import rlforj.util.BresenhamLine; 12 | 13 | import java.util.List; 14 | import java.util.Vector; 15 | 16 | /** 17 | * Bresenham LOS. 18 | * Tries to reach destination along first path. If 19 | * obstacled, shifts to alternate path. If that is blocked, 20 | * shift to first path again. Fails only if both are blocked 21 | * at a point. 22 | * 23 | * @author sdatta 24 | */ 25 | public class BresOpportunisticLos implements ILosAlgorithm 26 | { 27 | 28 | private Vector path; 29 | 30 | public boolean exists(final IBoard b, final int startX, final int startY, final int endX, final int endY, 31 | final boolean savePath) 32 | { 33 | final int dx = startX - endX; 34 | final int dy = startY - endY; 35 | final int adx = dx > 0 ? dx : -dx; 36 | final int ady = dy > 0 ? dy : -dy; 37 | final int len = (adx > ady ? adx : ady) + 1; 38 | 39 | if (savePath) 40 | path = new Vector<>(len); 41 | 42 | final int[] px = new int[len]; 43 | final int[] py = new int[len]; 44 | int[] px1, py1; 45 | px1 = new int[len]; 46 | py1 = new int[len]; 47 | 48 | //Compute both paths 49 | BresenhamLine.plot(startX, startY, endX, endY, px, py); 50 | BresenhamLine.plot(endX, endY, startX, startY, px1, py1); 51 | 52 | boolean los = false; 53 | boolean alternatePath = false; 54 | for (int i = 0; i < len; i++) 55 | { 56 | // Have we reached the end ? In that case quit 57 | if (px[i] == endX && py[i] == endY) 58 | { 59 | if (savePath) 60 | { 61 | path.add(new Point(px[i], py[i])); 62 | } 63 | los = true; 64 | break; 65 | } 66 | // if we are on alternate path, is the path clear ? 67 | if (alternatePath && !b.blocksLight(px1[len - i - 1], py1[len - i - 1])) 68 | { 69 | if (savePath) 70 | path.add(new Point(px1[len - i - 1], py1[len - i - 1])); 71 | continue; 72 | } 73 | else 74 | alternatePath = false;//come back to ordinary path 75 | 76 | //if on ordinary path, or alternate path was not clear 77 | if (!b.blocksLight(px[i], py[i])) 78 | { 79 | if (savePath) 80 | { 81 | path.add(new Point(px[i], py[i])); 82 | } 83 | continue; 84 | } 85 | //if ordinary path wasnt clear 86 | if (!b.blocksLight(px1[len - i - 1], py1[len - i - 1])) 87 | { 88 | if (savePath) 89 | path.add(new Point(px1[len - i - 1], py1[len - i - 1])); 90 | alternatePath = true;//go on alternate path 91 | continue; 92 | } 93 | if (savePath) 94 | path.add(new Point(px1[len - i - 1], py1[len - i - 1])); 95 | break; 96 | } 97 | 98 | return los; 99 | } 100 | 101 | public List getPath() 102 | { 103 | return path; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/rlforj/los/CLikeIterator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los; 8 | 9 | import java.util.ListIterator; 10 | 11 | /** 12 | * An iterator that behaves like C iterators 13 | * 14 | * @param class of elements in iterator 15 | * @author sdatta 16 | */ 17 | public class CLikeIterator 18 | { 19 | 20 | ListIterator it; 21 | T curr; 22 | boolean atEnd = false, atBegin = false; 23 | 24 | public CLikeIterator(final ListIterator it) 25 | { 26 | super(); 27 | this.it = it; 28 | if (it.hasNext()) 29 | curr = it.next(); 30 | else 31 | { 32 | curr = null; 33 | atEnd = true; 34 | } 35 | // checkPrevNext(); 36 | } 37 | 38 | // private final void checkPrevNext() 39 | // { 40 | //// if(it.hasNext()) 41 | //// atEnd=false; 42 | //// else 43 | //// atEnd=true; 44 | // 45 | // if(it.hasPrevious()) 46 | // atBegin=false; 47 | // else 48 | // atBegin=true; 49 | // } 50 | 51 | public final T getCurrent() 52 | { 53 | return curr; 54 | } 55 | 56 | public void gotoNext() 57 | { 58 | if (it.hasNext()) 59 | { 60 | curr = it.next(); 61 | } 62 | else 63 | { 64 | atEnd = true; 65 | curr = null; 66 | } 67 | // checkPrevNext(); 68 | } 69 | 70 | public void gotoPrevious() 71 | { 72 | if (it.hasPrevious()) 73 | { 74 | curr = it.previous(); 75 | } 76 | // else 77 | // { 78 | // curr=null; 79 | // } 80 | // checkPrevNext(); 81 | } 82 | 83 | public boolean isAtEnd() 84 | { 85 | return atEnd; 86 | } 87 | 88 | public void removeCurrent() 89 | { 90 | it.remove(); 91 | gotoNext(); 92 | } 93 | 94 | public void insertBeforeCurrent(final T t) 95 | { 96 | it.previous(); 97 | it.add(t); 98 | // checkPrevNext(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/rlforj/los/ConePrecisePremisive.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los; 8 | 9 | import rlforj.IBoard; 10 | import rlforj.math.Point; 11 | 12 | import java.util.LinkedList; 13 | 14 | /** 15 | * Precise Permissive class for computing cone 16 | * field of view. 17 | * 18 | * @author sdatta 19 | */ 20 | public class ConePrecisePremisive extends PrecisePermissive implements IConeFovAlgorithm 21 | { 22 | 23 | public void visitConeFieldOfView(final IBoard b, final int x, final int y, final int distance, int startAngle, 24 | int endAngle) 25 | { 26 | if (startAngle % 90 == 0 && startAngle % 360 != 0) 27 | startAngle--;//we dont like to start at 90, 180, 270 28 | // because it is screwed up by the "dont visit an axis twice" logic 29 | 30 | // normalize angled 31 | if (startAngle < 0) 32 | { 33 | startAngle %= 360; 34 | startAngle += 360; 35 | } 36 | if (endAngle < 0) 37 | { 38 | endAngle %= 360; 39 | endAngle += 360; 40 | } 41 | 42 | if (startAngle > 360) 43 | startAngle %= 360; 44 | if (endAngle > 360) 45 | endAngle %= 360; 46 | 47 | final permissiveMaskT mask = new permissiveMaskT(); 48 | mask.east = mask.north = mask.south = mask.west = distance; 49 | mask.mask = null; 50 | mask.fovType = FovType.CIRCLE; 51 | mask.distPlusOneSq = (distance + 1) * (distance + 1); 52 | mask.board = b; 53 | permissiveConeFov(x, y, mask, startAngle, endAngle); 54 | } 55 | 56 | /** 57 | * Process one quadrant. 58 | */ 59 | private void calculateConeFovQuadrant(final coneFovState state, final int startAngle, final int finishAngle) 60 | { 61 | // System.out.println("calcfovq called " + state.quadrantIndex + " " 62 | // + startAngle + " " + finishAngle); 63 | final LinkedList steepBumps = new LinkedList<>(); 64 | final LinkedList shallowBumps = new LinkedList<>(); 65 | // activeFields is sorted from shallow-to-steep. 66 | final LinkedList activeFields = new LinkedList<>(); 67 | activeFields.addLast(new fieldT()); 68 | 69 | // We decide the farthest cells that can be seen by the cone ( using 70 | // trigonometry.), then we set the active field to be in between them. 71 | if (startAngle == 0) 72 | { 73 | activeFields.getLast().shallow.near = new Point(0, 1); 74 | activeFields.getLast().shallow.far = new Point(state.extent.x, 0); 75 | } 76 | else 77 | { 78 | activeFields.getLast().shallow.near = new Point(0, 1); 79 | activeFields.getLast().shallow.far = new Point((int) Math.ceil( 80 | Math.cos(Math.toRadians(startAngle)) * state.extent.x), 81 | (int) Math.floor( 82 | Math.sin(Math.toRadians(startAngle)) * state.extent.y)); 83 | // System.out.println(activeFields.getLast().shallow.isAboveOrContains(new offsetT(0, 10))); 84 | } 85 | if (finishAngle == 90) 86 | { 87 | activeFields.getLast().steep.near = new Point(1, 0); 88 | activeFields.getLast().steep.far = new Point(0, state.extent.y); 89 | } 90 | else 91 | { 92 | activeFields.getLast().steep.near = new Point(1, 0); 93 | activeFields.getLast().steep.far = new Point((int) Math.floor( 94 | Math.cos(Math.toRadians(finishAngle)) * state.extent.x), 95 | (int) Math.ceil( 96 | Math.sin(Math.toRadians(finishAngle)) * state.extent.y)); 97 | } 98 | final Point dest = new Point(0, 0); 99 | 100 | // // Visit the source square exactly once (in quadrant 1). 101 | // if (state.quadrant.x == 1 && state.quadrant.y == 1) 102 | // { 103 | // actIsBlockedCone(state, dest); 104 | // } 105 | 106 | CLikeIterator currentField = new CLikeIterator<>(activeFields.listIterator()); 107 | int i; 108 | int j; 109 | final int maxI = state.extent.x + state.extent.y; 110 | // For each square outline 111 | for (i = 1; i <= maxI && !activeFields.isEmpty(); ++i) 112 | { 113 | final int startJ = max(0, i - state.extent.x); 114 | final int maxJ = min(i, state.extent.y); 115 | // System.out.println("Startj "+startJ+" maxj "+maxJ); 116 | // Visit the nodes in the outline 117 | for (j = startJ; j <= maxJ && !currentField.isAtEnd(); ++j) 118 | { 119 | // System.out.println("i j "+i+" "+j); 120 | dest.x = i - j; 121 | dest.y = j; 122 | visitConeSquare(state, dest, currentField, steepBumps, shallowBumps, activeFields); 123 | } 124 | // System.out.println("Activefields size "+activeFields.size()); 125 | currentField = new CLikeIterator<>(activeFields.listIterator()); 126 | } 127 | } 128 | 129 | private final int max(final int i, final int j) 130 | { 131 | return i > j ? i : j; 132 | } 133 | 134 | private final int min(final int i, final int j) 135 | { 136 | return i < j ? i : j; 137 | } 138 | 139 | private void permissiveConeFov(final int sourceX, final int sourceY, final permissiveMaskT mask, 140 | final int startAngle, final int finishAngle) 141 | { 142 | final coneFovState state = new coneFovState(); 143 | state.source = new Point(sourceX, sourceY); 144 | state.mask = mask; 145 | state.board = mask.board; 146 | // state.isBlocked = isBlocked; 147 | // state.visit = visit; 148 | // state.context = context; 149 | 150 | //visit origin once 151 | state.board.visit(sourceX, sourceY); 152 | 153 | final Point quadrants[] = { new Point(1, 1), new Point(-1, 1), new Point(-1, -1), new Point(1, -1) }; 154 | 155 | final Point extents[] = { new Point(mask.east, mask.north), new Point(mask.west, mask.north), 156 | new Point(mask.west, mask.south), new Point(mask.east, mask.south) }; 157 | 158 | final int[] angles = new int[12]; 159 | angles[0] = 0; 160 | angles[1] = 90; 161 | angles[2] = 180; 162 | angles[3] = 270; 163 | for (int i = 4; i < 12; i++) 164 | angles[i] = 720;//to keep them at the end 165 | int i; 166 | for (i = 0; i < 4; i++) 167 | { 168 | if (startAngle < angles[i]) 169 | { 170 | for (int j = 3; j >= i; j--) 171 | angles[j + 1] = angles[j]; 172 | break; 173 | } 174 | } 175 | angles[i] = startAngle; 176 | for (i = 0; i < 5; i++) 177 | { 178 | if (finishAngle < angles[i]) 179 | { 180 | for (int j = 4; j >= i; j--) 181 | angles[j + 1] = angles[j]; 182 | break; 183 | } 184 | } 185 | angles[i] = finishAngle; 186 | // Now angles[0..5] contains 0, 90, 180, 270, startAngle, finishAngle 187 | // in sorted order. 188 | int startIndex = 0; 189 | for (i = 0; i < 6; i++) 190 | { 191 | // System.out.println("sorted "+angles[i]); 192 | angles[i + 6] = angles[i]; 193 | if (angles[i] == startAngle) 194 | startIndex = i; 195 | } 196 | // Twice repeated, also foound out startAngle's index 197 | 198 | //effectively, what we do is: 199 | // traverse startAngle -> next axis(say 90), 90->180, 200 | // ...., some axis -> finishAngle. 201 | // Or startAngle -> endAngle if in same quadrant 202 | int stA = 0, endA = 0; 203 | for (i = startIndex; i < 12; i++) 204 | { 205 | if (angles[i] == finishAngle) 206 | break; 207 | final int quadrantIndex = angles[i] / 90; 208 | switch (quadrantIndex) 209 | { 210 | case 0: 211 | stA = angles[i]; 212 | endA = angles[i + 1]; 213 | break; 214 | case 1: 215 | stA = 180 - angles[i + 1]; 216 | endA = 180 - angles[i]; 217 | break; 218 | case 2: 219 | stA = angles[i] - 180; 220 | endA = angles[i + 1] - 180; 221 | break; 222 | case 3: 223 | stA = 360 - angles[i + 1]; 224 | endA = 360 - angles[i]; 225 | break; 226 | } 227 | state.quadrant = quadrants[quadrantIndex]; 228 | state.extent = extents[quadrantIndex]; 229 | state.quadrantIndex = quadrantIndex; 230 | 231 | calculateConeFovQuadrant(state, stA, endA); 232 | // System.out.println(quadrantIndex+" "+stA+" "+endA); 233 | 234 | if (stA == 0) 235 | state.axisDone[quadrantIndex] = true; 236 | if (endA == 90) 237 | state.axisDone[(quadrantIndex + 1) % 4] = true; 238 | // System.out.println(Arrays.toString(state.axisDone)); 239 | } 240 | } 241 | 242 | /** 243 | * It is here so that actisBlockedCone is called 244 | * instead of actIsBlocked. 245 | *

246 | * Note : I ( sdatta ) made the function name with Code added since 247 | * I wasnt sure inheritance was working properly when I was debugging this code. 248 | * Maybe this code can be simplified ? 249 | */ 250 | private void visitConeSquare(final coneFovState state, final Point dest, final CLikeIterator currentField, 251 | final LinkedList steepBumps, final LinkedList shallowBumps, 252 | final LinkedList activeFields) 253 | { 254 | // System.out.println("visitsq called "+dest); 255 | // The top-left and bottom-right corners of the destination square. 256 | final Point topLeft = new Point(dest.x, dest.y + 1); 257 | final Point bottomRight = new Point(dest.x + 1, dest.y); 258 | // System.out.println(dest); 259 | // fieldT currFld=null; 260 | 261 | while (!currentField.isAtEnd() && currentField.getCurrent().steep.isBelowOrContains(bottomRight)) 262 | { 263 | // System.out.println("currFld.steep.isBelowOrContains(bottomRight) " 264 | // + currentField.getCurrent().steep 265 | // .isBelowOrContains(bottomRight)); 266 | // case ABOVE 267 | // The square is in case 'above'. This means that it is ignored 268 | // for the currentField. But the steeper fields might need it. 269 | // ++currentField; 270 | // System.out.println("currFld.steep.isBelowOrContains(bottomRight) and shallow "+ 271 | // currentField.getCurrent().shallow.isAboveOrContains(bottomRight)+" "+ 272 | // currentField.getCurrent().steep.isBelowOrContains(topLeft)); 273 | 274 | if (currentField.getCurrent().shallow.isAboveOrContains(bottomRight) && 275 | currentField.getCurrent().steep.isBelowOrContains(topLeft)) 276 | { 277 | break; 278 | } 279 | 280 | currentField.gotoNext(); 281 | } 282 | if (currentField.isAtEnd()) 283 | { 284 | // System.out.println("currentField.isAtEnd()"); 285 | // The square was in case 'above' for all fields. This means that 286 | // we no longer care about it or any squares in its diagonal rank. 287 | return; 288 | } 289 | 290 | // Now we check for other cases. 291 | if (currentField.getCurrent().shallow.isAboveOrContains(topLeft)) 292 | { 293 | // case BELOW 294 | // The shallow line is above the extremity of the square, so that 295 | // square is ignored. 296 | 297 | // System.out.println("currFld.shallow.isAboveOrContains(topLeft) " 298 | // + currentField.getCurrent() + 299 | // currentField.getCurrent().shallow.isAboveOrContains(topLeft)+" "+ 300 | // currentField.getCurrent().shallow.isAboveOrContains(bottomRight)+" "+ 301 | // currentField.getCurrent().steep.isAboveOrContains(topLeft)+" "+ 302 | // currentField.getCurrent().steep.isAboveOrContains(bottomRight)); 303 | 304 | return; 305 | } 306 | // The square is between the lines in some way. This means that we 307 | // need to visit it and determine whether it is blocked. 308 | 309 | final boolean isBlocked = actIsBlockedCone(state, dest); 310 | if (!isBlocked) 311 | { 312 | // We don't care what case might be left, because this square does 313 | // not obstruct. 314 | return; 315 | } 316 | 317 | if (currentField.getCurrent().shallow.isAbove(bottomRight) && currentField.getCurrent().steep.isBelow(topLeft)) 318 | { 319 | // case BLOCKING 320 | // Both lines intersect the square. This current field has ended. 321 | currentField.removeCurrent(); 322 | } 323 | else if (currentField.getCurrent().shallow.isAbove(bottomRight)) 324 | { 325 | // case SHALLOW BUMP 326 | // The square intersects only the shallow line. 327 | addShallowBump(topLeft, currentField.getCurrent(), steepBumps, shallowBumps); 328 | checkField(currentField); 329 | } 330 | else if (currentField.getCurrent().steep.isBelow(topLeft)) 331 | { 332 | // case STEEP BUMP 333 | // The square intersects only the steep line. 334 | addSteepBump(bottomRight, currentField.getCurrent(), steepBumps, shallowBumps); 335 | checkField(currentField); 336 | } 337 | else 338 | { 339 | // case BETWEEN 340 | // The square intersects neither line. We need to split into two 341 | // fields. 342 | final fieldT steeperField = new fieldT(currentField.getCurrent()); 343 | final fieldT shallowerField = currentField.getCurrent(); 344 | currentField.insertBeforeCurrent(steeperField); 345 | // System.out.println("activeFields "+activeFields); 346 | addSteepBump(bottomRight, shallowerField, steepBumps, shallowBumps); 347 | currentField.gotoPrevious(); 348 | if (!checkField(currentField)) // did not remove 349 | currentField.gotoNext();// point to the original element 350 | // System.out.println("B4 addShallowBumps " 351 | // + currentField.getCurrent()); 352 | addShallowBump(topLeft, steeperField, steepBumps, shallowBumps); 353 | checkField(currentField); 354 | } 355 | } 356 | 357 | /** 358 | * Visit the square, also decide if it is blocked 359 | */ 360 | private boolean actIsBlockedCone(final coneFovState state, final Point pos) 361 | { 362 | final Point stateQuadrant = state.quadrant; 363 | final Point adjustedPos = new Point(pos.x * stateQuadrant.x + state.source.x, 364 | pos.y * stateQuadrant.y + state.source.y); 365 | 366 | //Keep track of which axes are done. 367 | if ((pos.x == 0 && stateQuadrant.y > 0 && !state.axisDone[1]) || 368 | (pos.x == 0 && stateQuadrant.y < 0 && !state.axisDone[3]) || 369 | (pos.y == 0 && stateQuadrant.x > 0 && !state.axisDone[0]) || 370 | (pos.y == 0 && stateQuadrant.x < 0 && !state.axisDone[2]) || (pos.x != 0 && pos.y != 0)) 371 | if (doesPermissiveVisit(state.mask, pos.x * stateQuadrant.x, pos.y * stateQuadrant.y) == 1) 372 | { 373 | state.board.visit(adjustedPos.x, adjustedPos.y); 374 | } 375 | return state.board.blocksLight(adjustedPos.x, adjustedPos.y); 376 | } 377 | 378 | public class coneFovState extends fovStateT 379 | { 380 | public boolean axisDone[] = { false, false, false, false }; 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /src/main/java/rlforj/los/FovType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los; 8 | 9 | /** 10 | * Different FOV types. Only Precise Permissive 11 | * can do square FOV right now. 12 | * 13 | * @author sdatta 14 | */ 15 | public enum FovType 16 | { 17 | SQUARE, 18 | CIRCLE 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/rlforj/los/GenericCalculateProjection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los; 8 | 9 | import rlforj.math.Point; 10 | 11 | import java.util.Vector; 12 | 13 | /** 14 | * Given a set of squares that we are allowed to visit, and two points 15 | * A and B, calculates a monotonic path from A to B, if it exists. 16 | * Else it stops after as far as it can go 17 | * It is useful to calculate a path along which sight runs from A to B 18 | * given B is visible from A. An arrow or bolt can fly along this path. 19 | * 20 | * @author sdatta 21 | */ 22 | public class GenericCalculateProjection 23 | { 24 | 25 | public static Vector calculateProjecton(final int startX, final int startY, final int x1, final int y1, 26 | final VisitedBoard fb) 27 | { 28 | final Vector path = new Vector<>(); 29 | 30 | // calculate usual Bresenham values required. 31 | final int dx = x1 - startX; 32 | final int dy = y1 - startY; 33 | final int signX; 34 | final int signY; 35 | int adx, ady; 36 | if (dx > 0) 37 | { 38 | adx = dx; 39 | signX = 1; 40 | } 41 | else 42 | { 43 | adx = -dx; 44 | signX = -1; 45 | } 46 | if (dy > 0) 47 | { 48 | ady = dy; 49 | signY = 1; 50 | } 51 | else 52 | { 53 | ady = -dy; 54 | signY = -1; 55 | } 56 | boolean axesSwapped = false; 57 | if (adx < ady) 58 | { 59 | axesSwapped = true; 60 | final int tmp = adx; 61 | adx = ady; 62 | ady = tmp; 63 | } 64 | 65 | // System.out.println("adx ady "+adx+" "+ady); 66 | //calculate the two error values. 67 | final int incE = 2 * ady; //error diff if x++ 68 | final int incNE = 2 * ady - 2 * adx; // error diff if x++ and y++ 69 | int d = 2 * ady - adx; // starting error 70 | final Point p = new Point(0, 0); 71 | int j; 72 | for (int i = 0; i <= adx; ) 73 | { 74 | if (axesSwapped) 75 | { 76 | i = p.y; 77 | j = p.x; 78 | } 79 | else 80 | { 81 | i = p.x; 82 | j = p.y; 83 | } 84 | // System.out.println("GCP loop "+i+" "+j+" d "+d); 85 | // if(d<-2*adx) System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); 86 | if (i > adx || j > ady) // searching outside range 87 | { 88 | // System.out.println("Outside range "+i+" "+j+" "+adx+" "+ady); 89 | break; 90 | } 91 | 92 | if (axesSwapped) 93 | { 94 | path.add(new Point((j * signX + startX), (i * signY + startY))); 95 | } 96 | else 97 | { 98 | path.add(new Point((i * signX + startX), (j * signY + startY))); 99 | } 100 | // System.out.println("Added to path "+path.lastElement()); 101 | if (i == adx && j == ady)//end reached and recorded 102 | { 103 | // System.out.println("End reached and recorded "); 104 | break; 105 | } 106 | 107 | boolean ippNotrecommended = false;//whether i++ is recommended 108 | if (d <= 0) 109 | { 110 | // try to just inc x 111 | if (axesSwapped) 112 | { 113 | p.y = i + 1; 114 | p.x = j; 115 | } 116 | else 117 | { 118 | p.x = i + 1; 119 | p.y = j; 120 | } 121 | if (fb.wasVisited(p.x, p.y) || /* end */ (i == adx && j == ady)) 122 | { 123 | d += incE; 124 | // i++; 125 | continue; 126 | } 127 | // System.out.println("cannot i++ "+p+" 128 | // "+fb.visitedNotObs.contains(p)); 129 | } 130 | else 131 | { 132 | // System.out.println("i++ not recommended "); 133 | ippNotrecommended = true; 134 | } 135 | 136 | // try to inc x and y 137 | if (axesSwapped) 138 | { 139 | p.y = i + 1; 140 | p.x = j + 1; 141 | } 142 | else 143 | { 144 | p.x = i + 1; 145 | p.y = j + 1; 146 | } 147 | if (fb.wasVisited(p.x, p.y) || /* end */ (i == adx && j == ady)) 148 | { 149 | d += incNE; 150 | // j++; 151 | // i++; 152 | continue; 153 | } 154 | // System.out.println("cannot i++ j++ "+p+" 155 | // "+fb.visitedNotObs.contains(p)); 156 | if (ippNotrecommended) 157 | { // try it even if not recommended 158 | if (axesSwapped) 159 | { 160 | p.y = i + 1; 161 | p.x = j; 162 | } 163 | else 164 | { 165 | p.x = i + 1; 166 | p.y = j; 167 | } 168 | if (fb.wasVisited(p.x, p.y) || /* end */ (i == adx && j == ady)) 169 | { 170 | d += incE; 171 | // i++; 172 | continue; 173 | } 174 | // System.out.println("cannot i++ "+p+" 175 | // "+fb.visitedNotObs.contains(p)); 176 | } 177 | // last resort 178 | // try to inc just y 179 | if (axesSwapped) 180 | { 181 | p.y = i; 182 | p.x = j + 1; 183 | } 184 | else 185 | { 186 | p.x = i; 187 | p.y = j + 1; 188 | } 189 | if (fb.wasVisited(p.x, p.y) || /* end */ (i == adx && j == ady)) 190 | { 191 | // System.out.println("GCP y++ "+i+" "+j+" last "+lasti+" "+lastj); 192 | // if (lasti == i - 1 && lastj == j)// last step was 1 to the 193 | // right 194 | // System.out.println("<<- GenericCalculateProj check code"); 195 | // this step is 1 step to up, 196 | // together 1 diagonal 197 | // => we dont need last point 198 | 199 | d += -incE + incNE;// as if we went 1 step left then took 1 200 | // step up right 201 | // j++; 202 | continue; 203 | } 204 | // System.out.println("cannot j++ "+p+" 205 | // "+fb.visitedNotObs.contains(p)); 206 | // no path, end here, after adding last point. 207 | if (axesSwapped) 208 | { 209 | path.add(new Point((j * signX + startX), (i * signY + startY))); 210 | } 211 | else 212 | { 213 | path.add(new Point((i * signX + startX), (j * signY + startY))); 214 | } 215 | break; 216 | } 217 | 218 | return path; 219 | } 220 | 221 | public interface VisitedBoard 222 | { 223 | boolean wasVisited(int x, int y); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/main/java/rlforj/los/IConeFovAlgorithm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los; 8 | 9 | import rlforj.IBoard; 10 | 11 | /** 12 | * FOV along a cone. Give starting and finish angle. 13 | * Note: Positive Y axis is down. 14 | * 15 | * @author sdatta 16 | */ 17 | public interface IConeFovAlgorithm extends IFovAlgorithm 18 | { 19 | /** 20 | * Compute cone FOV on board b, starting from (x,y), from startAngle to 21 | * finishAngle. 22 | * Positive Y axis is downwards. 23 | * 24 | * @param b board 25 | * @param x x position 26 | * @param y y position 27 | * @param distance maximum distance of cone of view 28 | * @param startAngle start angle 29 | * @param endAngle end angle 30 | */ 31 | void visitConeFieldOfView(IBoard b, int x, int y, int distance, int startAngle, int endAngle); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/rlforj/los/IFovAlgorithm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los; 8 | 9 | import rlforj.IBoard; 10 | 11 | /** 12 | * An interface for FOV algorithms. 13 | * 14 | * @author sdatta 15 | */ 16 | public interface IFovAlgorithm 17 | { 18 | /** 19 | * All locations of Board b that are visible 20 | * from (x, y) will be visited, ie b.visit(x, y) 21 | * will be called on them. 22 | *

23 | * Algorithms must call visit on the same location only once. 24 | * Algorithms should try to visit points closer to the 25 | * starting point before farther points. 26 | * Algorithms should try to visit a location before calling isObstacle 27 | * on it, allowing effects like an explosion destroying a wall and affecting 28 | * areas beyond it. 29 | * 30 | * @param b The target board 31 | * @param x Starting location:x 32 | * @param y Starting location:y 33 | * @param distance How far can this Field of View go 34 | */ 35 | void visitFoV(IBoard b, int x, int y, int distance); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/rlforj/los/ILosAlgorithm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los; 8 | 9 | import rlforj.IBoard; 10 | import rlforj.math.Point; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * An interface for for LOS and projection 16 | * 17 | * @author sdatta 18 | */ 19 | public interface ILosAlgorithm 20 | { 21 | /** 22 | * Determines whether line of sight exists between point (startX, startY) and 23 | * (endX, endY). Optionally calculates the path of projection (retrievable via call to 24 | * {@link ILosAlgorithm#getPath}). 25 | * 26 | * @param b The board to be visited. 27 | * @param startX Starting position:x 28 | * @param startY Starting position:y 29 | * @param endX Target location:x 30 | * @param endY Target location:y 31 | * @param savePath Whether to also calculate and store the path from the source to the target. 32 | * @return true if a line of sight could be established 33 | */ 34 | boolean exists(IBoard b, int startX, int startY, int endX, int endY, boolean savePath); 35 | 36 | /** 37 | * Obtain the path of the projection calculated during the last call 38 | * to {@link ILosAlgorithm#exists}. 39 | * 40 | * @return null if no los was established so far, or a list of points if a los found 41 | */ 42 | List getPath(); 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/rlforj/los/LosException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los; 8 | 9 | /** 10 | * Exception thrown by LOS algorithms. 11 | * 12 | * @author sdatta 13 | */ 14 | public class LosException extends RuntimeException 15 | { 16 | private static final long serialVersionUID = 8210411767466028759L; 17 | 18 | public LosException(final String msg) 19 | { 20 | super(msg); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/rlforj/los/PrecisePermissive.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los; 8 | 9 | import rlforj.IBoard; 10 | import rlforj.math.Line2I; 11 | import rlforj.math.Point; 12 | 13 | import java.util.LinkedList; 14 | import java.util.List; 15 | import java.util.Vector; 16 | 17 | public class PrecisePermissive implements IFovAlgorithm, ILosAlgorithm 18 | { 19 | private final ILosAlgorithm fallBackLos = new BresLos(true); 20 | private Vector path; 21 | 22 | void calculateFovQuadrant(final fovStateT state) 23 | { 24 | // System.out.println("calcfovq called"); 25 | final LinkedList steepBumps = new LinkedList<>(); 26 | final LinkedList shallowBumps = new LinkedList<>(); 27 | // activeFields is sorted from shallow-to-steep. 28 | final LinkedList activeFields = new LinkedList<>(); 29 | activeFields.addLast(new fieldT()); 30 | activeFields.getLast().shallow.near = new Point(0, 1); 31 | activeFields.getLast().shallow.far = new Point(state.extent.x, 0); 32 | activeFields.getLast().steep.near = new Point(1, 0); 33 | activeFields.getLast().steep.far = new Point(0, state.extent.y); 34 | 35 | final Point dest = new Point(0, 0); 36 | 37 | // Visit the source square exactly once (in quadrant 1). 38 | if (state.quadrant.x == 1 && state.quadrant.y == 1) 39 | { 40 | actIsBlocked(state, dest); 41 | } 42 | 43 | CLikeIterator currentField = new CLikeIterator<>(activeFields.listIterator()); 44 | int i; 45 | int j; 46 | final int maxI = state.extent.x + state.extent.y; 47 | // For each square outline 48 | for (i = 1; i <= maxI && !activeFields.isEmpty(); ++i) 49 | { 50 | final int startJ = max(0, i - state.extent.x); 51 | final int maxJ = min(i, state.extent.y); 52 | // System.out.println("Startj "+startJ+" maxj "+maxJ); 53 | // Visit the nodes in the outline 54 | for (j = startJ; j <= maxJ && !currentField.isAtEnd(); ++j) 55 | { 56 | // System.out.println("i j "+i+" "+j); 57 | dest.x = i - j; 58 | dest.y = j; 59 | visitSquare(state, dest, currentField, steepBumps, shallowBumps, activeFields); 60 | } 61 | // System.out.println("Activefields size "+activeFields.size()); 62 | currentField = new CLikeIterator<>(activeFields.listIterator()); 63 | } 64 | } 65 | 66 | private final int max(final int i, final int j) 67 | { 68 | return i > j ? i : j; 69 | } 70 | 71 | private final int min(final int i, final int j) 72 | { 73 | return i < j ? i : j; 74 | } 75 | 76 | void visitSquare(final fovStateT state, final Point dest, final CLikeIterator currentField, 77 | final LinkedList steepBumps, final LinkedList shallowBumps, 78 | final LinkedList activeFields) 79 | { 80 | // System.out.println("-> "+steepBumps+" - "+shallowBumps); 81 | // System.out.println("visitsq called "+dest); 82 | // The top-left and bottom-right corners of the destination square. 83 | final Point topLeft = new Point(dest.x, dest.y + 1); 84 | final Point bottomRight = new Point(dest.x + 1, dest.y); 85 | 86 | // fieldT currFld=null; 87 | 88 | while (!currentField.isAtEnd() && currentField.getCurrent().steep.isBelowOrContains(bottomRight)) 89 | { 90 | // System.out.println("currFld.steep.isBelowOrContains(bottomRight) " 91 | // + currentField.getCurrent().steep 92 | // .isBelowOrContains(bottomRight)); 93 | // case ABOVE 94 | // The square is in case 'above'. This means that it is ignored 95 | // for the currentField. But the steeper fields might need it. 96 | // ++currentField; 97 | currentField.gotoNext(); 98 | } 99 | if (currentField.isAtEnd()) 100 | { 101 | // System.out.println("currentField.isAtEnd()"); 102 | // The square was in case 'above' for all fields. This means that 103 | // we no longer care about it or any squares in its diagonal rank. 104 | return; 105 | } 106 | 107 | // Now we check for other cases. 108 | if (currentField.getCurrent().shallow.isAboveOrContains(topLeft)) 109 | { 110 | // case BELOW 111 | // The shallow line is above the extremity of the square, so that 112 | // square is ignored. 113 | // System.out.println("currFld.shallow.isAboveOrContains(topLeft) " 114 | // + currentField.getCurrent().shallow); 115 | return; 116 | } 117 | // The square is between the lines in some way. This means that we 118 | // need to visit it and determine whether it is blocked. 119 | final boolean isBlocked = actIsBlocked(state, dest); 120 | if (!isBlocked) 121 | { 122 | // We don't care what case might be left, because this square does 123 | // not obstruct. 124 | return; 125 | } 126 | 127 | if (currentField.getCurrent().shallow.isAbove(bottomRight) && currentField.getCurrent().steep.isBelow(topLeft)) 128 | { 129 | // case BLOCKING 130 | // Both lines intersect the square. This current field has ended. 131 | currentField.removeCurrent(); 132 | } 133 | else if (currentField.getCurrent().shallow.isAbove(bottomRight)) 134 | { 135 | // case SHALLOW BUMP 136 | // The square intersects only the shallow line. 137 | addShallowBump(topLeft, currentField.getCurrent(), steepBumps, shallowBumps); 138 | checkField(currentField); 139 | } 140 | else if (currentField.getCurrent().steep.isBelow(topLeft)) 141 | { 142 | // case STEEP BUMP 143 | // The square intersects only the steep line. 144 | addSteepBump(bottomRight, currentField.getCurrent(), steepBumps, shallowBumps); 145 | checkField(currentField); 146 | } 147 | else 148 | { 149 | // case BETWEEN 150 | // The square intersects neither line. We need to split into two 151 | // fields. 152 | final fieldT steeperField = currentField.getCurrent(); 153 | final fieldT shallowerField = new fieldT(currentField.getCurrent()); 154 | currentField.insertBeforeCurrent(shallowerField); 155 | addSteepBump(bottomRight, shallowerField, steepBumps, shallowBumps); 156 | currentField.gotoPrevious(); 157 | if (!checkField(currentField)) // did not remove 158 | currentField.gotoNext();// point to the original element 159 | addShallowBump(topLeft, steeperField, steepBumps, shallowBumps); 160 | checkField(currentField); 161 | } 162 | } 163 | 164 | boolean checkField(final CLikeIterator currentField) 165 | { 166 | // If the two slopes are colinear, and if they pass through either 167 | // extremity, remove the field of view. 168 | final fieldT currFld = currentField.getCurrent(); 169 | boolean ret = false; 170 | 171 | if (currFld.shallow.doesContain(currFld.steep.near) && currFld.shallow.doesContain(currFld.steep.far) && 172 | (currFld.shallow.doesContain(new Point(0, 1)) || currFld.shallow.doesContain(new Point(1, 0)))) 173 | { 174 | // System.out.println("removing "+currentField.getCurrent()); 175 | currentField.removeCurrent(); 176 | ret = true; 177 | } 178 | // System.out.println("CheckField "+ret); 179 | return ret; 180 | } 181 | 182 | void addShallowBump(final Point point, final fieldT currFld, final LinkedList steepBumps, 183 | final LinkedList shallowBumps) 184 | { 185 | // System.out.println("Adding shallow "+point); 186 | // First, the far point of shallow is set to the new point. 187 | currFld.shallow.far = point; 188 | // Second, we need to add the new bump to the shallow bump list for 189 | // future steep bump handling. 190 | shallowBumps.addLast(new bumpT()); 191 | shallowBumps.getLast().location = point; 192 | shallowBumps.getLast().parent = currFld.shallowBump; 193 | currFld.shallowBump = shallowBumps.getLast(); 194 | // Now we have too look through the list of steep bumps and see if 195 | // any of them are below the line. 196 | // If there are, we need to replace near point too. 197 | bumpT currentBump = currFld.steepBump; 198 | while (currentBump != null) 199 | { 200 | if (currFld.shallow.isAbove(currentBump.location)) 201 | { 202 | currFld.shallow.near = currentBump.location; 203 | } 204 | currentBump = currentBump.parent; 205 | } 206 | } 207 | 208 | void addSteepBump(final Point point, final fieldT currFld, final LinkedList steepBumps, 209 | final LinkedList shallowBumps) 210 | { 211 | // System.out.println("Adding steep "+point); 212 | currFld.steep.far = point; 213 | steepBumps.addLast(new bumpT()); 214 | steepBumps.getLast().location = point; 215 | steepBumps.getLast().parent = currFld.steepBump; 216 | currFld.steepBump = steepBumps.getLast(); 217 | // Now look through the list of shallow bumps and see if any of them 218 | // are below the line. 219 | bumpT currentBump = currFld.shallowBump; 220 | while (currentBump != null) 221 | { 222 | if (currFld.steep.isBelow(currentBump.location)) 223 | { 224 | currFld.steep.near = currentBump.location; 225 | } 226 | currentBump = currentBump.parent; 227 | } 228 | } 229 | 230 | boolean actIsBlocked(final fovStateT state, final Point pos) 231 | { 232 | final Point adjustedPos = new Point(pos.x * state.quadrant.x + state.source.x, 233 | pos.y * state.quadrant.y + state.source.y); 234 | 235 | if (!state.board.contains(adjustedPos.x, adjustedPos.y)) 236 | return false;//we are getting outside the board 237 | 238 | // System.out.println("actIsBlocked "+adjustedPos.x+" "+adjustedPos.y); 239 | 240 | // if ((state.quadrant.x * state.quadrant.y == 1 241 | // && pos.x == 0 && pos.y != 0) 242 | // || (state.quadrant.x * state.quadrant.y == -1 243 | // && pos.y == 0 && pos.x != 0) 244 | // || doesPermissiveVisit(state.mask, pos.x*state.quadrant.x, 245 | // pos.y*state.quadrant.y) == 0) 246 | // { 247 | // // return result; 248 | // } 249 | // else 250 | // { 251 | // board.visit(adjustedPos.x, adjustedPos.y); 252 | // // return result; 253 | // } 254 | /* 255 | * ^ | 2 | <-3-+-1-> | 4 | v 256 | * 257 | * To ensure all squares are visited before checked ( so that we can 258 | * decide obstacling at visit time, eg walls destroyed by explosion) , 259 | * visit axes 1,2 only in Q1, 3 in Q2, 4 in Q3 260 | */ 261 | if (state.isLos // In LOS calculation all visits allowed 262 | || state.quadrantIndex == 0 // can visit anything from Q1 263 | || (state.quadrantIndex == 1 && pos.x != 0) // Q2 : no Y axis 264 | || (state.quadrantIndex == 2 && pos.y != 0) // Q3 : no X axis 265 | || (state.quadrantIndex == 3 && pos.x != 0 && pos.y != 0)) // Q4 266 | // no X 267 | // or Y 268 | // axis 269 | if (doesPermissiveVisit(state.mask, pos.x * state.quadrant.x, pos.y * state.quadrant.y) == 1) 270 | { 271 | state.board.visit(adjustedPos.x, adjustedPos.y); 272 | } 273 | return state.board.blocksLight(adjustedPos.x, adjustedPos.y); 274 | } 275 | 276 | void permissiveFov(final int sourceX, final int sourceY, final permissiveMaskT mask) 277 | { 278 | final fovStateT state = new fovStateT(); 279 | state.source = new Point(sourceX, sourceY); 280 | state.mask = mask; 281 | state.board = mask.board; 282 | // state.isBlocked = isBlocked; 283 | // state.visit = visit; 284 | // state.context = context; 285 | 286 | final int quadrantCount = 4; 287 | final Point quadrants[] = { new Point(1, 1), new Point(-1, 1), new Point(-1, -1), new Point(1, -1) }; 288 | 289 | final Point extents[] = { new Point(mask.east, mask.north), new Point(mask.west, mask.north), 290 | new Point(mask.west, mask.south), new Point(mask.east, mask.south) }; 291 | int quadrantIndex = 0; 292 | for (; quadrantIndex < quadrantCount; ++quadrantIndex) 293 | { 294 | state.quadrant = quadrants[quadrantIndex]; 295 | state.extent = extents[quadrantIndex]; 296 | state.quadrantIndex = quadrantIndex; 297 | calculateFovQuadrant(state); 298 | } 299 | } 300 | 301 | int doesPermissiveVisit(final permissiveMaskT mask, final int x, final int y) 302 | { 303 | if (mask.fovType == FovType.SQUARE) 304 | return 1; 305 | else if (mask.fovType == FovType.CIRCLE) 306 | { 307 | if (x * x + y * y < mask.distPlusOneSq) 308 | return 1; 309 | else 310 | return 0; 311 | } 312 | return 1; 313 | } 314 | 315 | public void visitFoV(final IBoard b, final int x, final int y, final int distance) 316 | { 317 | final permissiveMaskT mask = new permissiveMaskT(); 318 | mask.east = mask.north = mask.south = mask.west = distance; 319 | mask.mask = null; 320 | mask.fovType = FovType.CIRCLE; 321 | mask.distPlusOneSq = (distance + 1) * (distance + 1); 322 | mask.board = b; 323 | permissiveFov(x, y, mask); 324 | } 325 | 326 | /** 327 | * Algorithm inspired by 328 | * http://groups.google.com/group/rec.games.roguelike.development/browse_thread/thread/f3506215be9d9f9a/2e543127f705a278#2e543127f705a278 329 | * 330 | * @see rlforj.los.ILosAlgorithm#exists(IBoard, int, int, int, int, boolean) 331 | */ 332 | public boolean exists(final IBoard b, final int startX, final int startY, final int endX, final int endY, 333 | final boolean savePath) 334 | { 335 | final permissiveMaskT mask = new permissiveMaskT(); 336 | final int dx = endX - startX; 337 | final int adx = dx > 0 ? dx : -dx; 338 | final int dy = endY - startY; 339 | final int ady = dy > 0 ? dy : -dy; 340 | final RecordQuadrantVisitBoard fb = new RecordQuadrantVisitBoard(b, startX, startY, endX, endY, savePath); 341 | mask.east = mask.west = adx; 342 | mask.north = mask.south = ady; 343 | mask.mask = null; 344 | mask.fovType = FovType.SQUARE; 345 | mask.distPlusOneSq = 0; 346 | mask.board = fb; 347 | 348 | final fovStateT state = new fovStateT(); 349 | state.source = new Point(startX, startY); 350 | state.mask = mask; 351 | state.board = fb; 352 | state.isLos = true; 353 | state.quadrant = new Point(dx < 0 ? -1 : 1, dy < 0 ? -1 : 1); 354 | state.quadrantIndex = 0; 355 | 356 | final LinkedList steepBumps = new LinkedList<>(); 357 | final LinkedList shallowBumps = new LinkedList<>(); 358 | // activeFields is sorted from shallow-to-steep. 359 | final LinkedList activeFields = new LinkedList<>(); 360 | activeFields.addLast(new fieldT()); 361 | activeFields.getLast().shallow.near = new Point(0, 1); 362 | activeFields.getLast().shallow.far = new Point(adx + 1, 0); 363 | activeFields.getLast().steep.near = new Point(1, 0); 364 | activeFields.getLast().steep.far = new Point(0, ady + 1); 365 | 366 | final Point dest = new Point(0, 0); 367 | 368 | final Line2I stopLine = new Line2I(new Point(0, 1), 369 | new Point(adx, ady + 1)), startLine = new Line2I(new Point(1, 0), 370 | new Point(adx + 1, ady)); 371 | 372 | // Visit the source square exactly once (in quadrant 1). 373 | actIsBlocked(state, dest); 374 | 375 | CLikeIterator currentField = new CLikeIterator<>(activeFields.listIterator()); 376 | final int maxI = adx + ady; 377 | // For each square outline 378 | int lastStartJ = -1; 379 | final Point topLeft = new Point(0, 0), bottomRight = new Point(0, 0); 380 | for (int i = 1; i <= maxI && !activeFields.isEmpty(); ++i) 381 | { 382 | // System.out.println("i "+i); 383 | int startJ = max(0, i - adx); 384 | startJ = max(startJ, lastStartJ - 1); 385 | final int maxJ = min(i, ady); 386 | 387 | // System.out.println("Startj "+startJ+" maxj "+maxJ); 388 | // Visit the nodes in the outline 389 | int thisStartJ = -1; 390 | // System.out.println("startJ "+startJ+" maxJ "+maxJ); 391 | for (int j = startJ; j <= maxJ && !currentField.isAtEnd(); ++j) 392 | { 393 | // System.out.println("i j "+i+" "+j); 394 | dest.x = i - j; 395 | dest.y = j; 396 | topLeft.x = dest.x; 397 | topLeft.y = dest.y + 1; 398 | bottomRight.x = dest.x + 1; 399 | bottomRight.y = dest.y; 400 | // System.out.println(startLine+" "+topLeft+" "+stopLine+" 401 | // "+bottomRight); 402 | // System.out.println("isbelow "+startLine.isBelow(topLeft)+" 403 | // isabove "+stopLine.isAbove(bottomRight)); 404 | if (startLine.isAboveOrContains(topLeft)) 405 | { 406 | // not in range, continue 407 | // System.out.println("below start"); 408 | continue; 409 | } 410 | if (stopLine.isBelowOrContains(bottomRight)) 411 | { 412 | // done 413 | // System.out.println("Above stop "); 414 | break; 415 | } 416 | // in range 417 | if (thisStartJ == -1) 418 | thisStartJ = j; 419 | visitSquare(state, dest, currentField, steepBumps, shallowBumps, activeFields); 420 | } 421 | lastStartJ = thisStartJ; 422 | // System.out.println("Activefields size "+activeFields.size()); 423 | currentField = new CLikeIterator<>(activeFields.listIterator()); 424 | } 425 | 426 | if (savePath) 427 | { 428 | if (fb.endVisited) 429 | path = GenericCalculateProjection.calculateProjecton(startX, startY, endX, endY, fb); 430 | else 431 | { 432 | fallBackLos.exists(b, startX, startY, endX, endY, true); 433 | path = (Vector) fallBackLos.getPath(); 434 | } 435 | // calculateProjecton(startX, startY, adx, ady, fb, state); 436 | } 437 | return fb.endVisited; 438 | } 439 | 440 | /* 441 | * (non-Javadoc) 442 | * 443 | * @see sid.los.ILosAlgorithm1#getPath() 444 | */ 445 | public List getPath() 446 | { 447 | return path; 448 | } 449 | 450 | class permissiveMaskT 451 | { 452 | public FovType fovType; 453 | public int distPlusOneSq; 454 | /* 455 | * Do not interact with the members directly. Use the provided 456 | * functions. 457 | */ int north; 458 | int south; 459 | int east; 460 | int west; 461 | // int width; 462 | // int height; 463 | int[] mask; 464 | IBoard board; 465 | } 466 | 467 | class fovStateT 468 | { 469 | public int quadrantIndex; 470 | public boolean isLos = false; 471 | Point source; 472 | permissiveMaskT mask; 473 | Object context; 474 | Point quadrant; 475 | Point extent; 476 | IBoard board; 477 | } 478 | 479 | class bumpT 480 | { 481 | Point location; 482 | bumpT parent = null; 483 | 484 | public bumpT() 485 | { 486 | } 487 | 488 | public String toString() 489 | { 490 | return location.toString() + " p( " + parent + " ) "; 491 | } 492 | } 493 | 494 | class fieldT 495 | { 496 | Line2I steep = new Line2I(new Point(0, 0), new Point(0, 0)); 497 | Line2I shallow = new Line2I(new Point(0, 0), new Point(0, 0)); 498 | bumpT steepBump; 499 | bumpT shallowBump; 500 | 501 | public fieldT(final fieldT f) 502 | { 503 | steep = new Line2I(new Point(f.steep.near.x, f.steep.near.y), new Point(f.steep.far.x, f.steep.far.y)); 504 | shallow = new Line2I(new Point(f.shallow.near.x, f.shallow.near.y), 505 | new Point(f.shallow.far.x, f.shallow.far.y)); 506 | steepBump = f.steepBump; 507 | shallowBump = f.shallowBump; 508 | } 509 | 510 | public fieldT() 511 | { 512 | // TODO Auto-generated constructor stub 513 | } 514 | 515 | public String toString() 516 | { 517 | return "[ steep " + steep + ", shallow " + shallow + "]"; 518 | } 519 | } 520 | } 521 | -------------------------------------------------------------------------------- /src/main/java/rlforj/los/RecordQuadrantVisitBoard.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los; 8 | 9 | import rlforj.IBoard; 10 | import rlforj.math.Point; 11 | 12 | import java.util.HashSet; 13 | import java.util.Set; 14 | 15 | /** 16 | * A LOS board that records points that were visited, while using another 17 | * board to decide obstacles. 18 | * 19 | * @author sdatta 20 | */ 21 | public class RecordQuadrantVisitBoard implements IBoard, GenericCalculateProjection.VisitedBoard 22 | { 23 | private final Point visitedCheck = new Point(0, 0); 24 | IBoard b; 25 | int sx, sy, sxy; 26 | int targetX, targetY; 27 | // int manhattanDist; 28 | Set visitedNotObs = new HashSet<>(); 29 | boolean endVisited = false; 30 | boolean calculateProject; 31 | 32 | public RecordQuadrantVisitBoard(final IBoard b, final int sx, final int sy, final int dx, final int dy, 33 | final boolean calculateProject) 34 | { 35 | super(); 36 | this.b = b; 37 | this.sx = sx; 38 | this.sy = sy; 39 | sxy = sx + sy; 40 | this.targetX = dx; 41 | this.targetY = dy; 42 | 43 | this.calculateProject = calculateProject; 44 | } 45 | 46 | public boolean contains(final int x, final int y) 47 | { 48 | return b.contains(x, y); 49 | } 50 | 51 | public boolean isObstacle(final int x, final int y) 52 | { 53 | return b.isObstacle(x, y); 54 | } 55 | 56 | @Override 57 | public boolean blocksLight(final int x, final int y) 58 | { 59 | return b.blocksLight(x, y); 60 | } 61 | 62 | @Override 63 | public boolean blocksStep(final int x, final int y) 64 | { 65 | return b.blocksStep(x, y); 66 | } 67 | 68 | public void visit(final int x, final int y) 69 | { 70 | // System.out.println("visited "+x+" "+y); 71 | if (x == targetX && y == targetY) 72 | endVisited = true; 73 | if (calculateProject && !b.blocksLight(x, y)) 74 | { 75 | int dx = x - sx; 76 | dx = dx > 0 ? dx : -dx; 77 | int dy = y - sy; 78 | dy = dy > 0 ? dy : -dy; 79 | visitedNotObs.add(new Point(dx, dy)); 80 | } 81 | //DEBUG 82 | // b.visit(x, y); 83 | } 84 | 85 | public boolean wasVisited(final int x, final int y) 86 | { 87 | visitedCheck.x = x; 88 | visitedCheck.y = y; 89 | return visitedNotObs.contains(visitedCheck); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/rlforj/los/ShadowCasting.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los; 8 | 9 | import rlforj.IBoard; 10 | import rlforj.math.Point; 11 | 12 | import java.util.*; 13 | 14 | /** 15 | * Code adapted from NG roguelike engine http://roguelike-eng.sourceforge.net/ 16 | *

17 | * Recursive line-of-sight class implementing a spiraling shadow-casting 18 | * algorithm. This algorithm chosen because it can establish line-of-sight by 19 | * visiting each grid at most once, and is (for me) much simpler to implement 20 | * than octant oriented or non-recursive approaches. -TSS 21 | * 22 | * @author TSS 23 | */ 24 | public class ShadowCasting implements IConeFovAlgorithm, ILosAlgorithm 25 | { 26 | public static final int MAX_CACHED_RADIUS = 40; 27 | 28 | static HashMap> circles = new HashMap<>(); 29 | 30 | static 31 | { 32 | final Point origin = new Point(0, 0); 33 | 34 | final int radius = MAX_CACHED_RADIUS; 35 | 36 | for (int i = -radius; i <= radius; i++) 37 | { 38 | for (int j = -radius; j <= radius; j++) 39 | { 40 | // final int distance = (int) floor(origin.distance(i, j)); 41 | final int distance = origin.distance2(i, j); 42 | 43 | // If filled, add anything where floor(distance) <= radius 44 | // If not filled, require that floor(distance) == radius 45 | if (distance <= radius) 46 | { 47 | final ArrayList circ = circles.computeIfAbsent(distance, k -> new ArrayList<>()); 48 | circ.add(new ArcPoint(i, j)); 49 | } 50 | } 51 | } 52 | 53 | for (final ArrayList list : circles.values()) 54 | { 55 | Collections.sort(list); 56 | // System.out.println("r: "+r+" "+list); 57 | } 58 | } 59 | 60 | /** 61 | * When LOS not found, use Bresenham to find failed path 62 | */ 63 | BresLos fallBackLos = new BresLos(true); 64 | private Vector path; 65 | 66 | static void go(final IBoard board, final Point ctr, final int r, final int maxDistance, double th1, 67 | final double th2) 68 | { 69 | if (r > maxDistance) 70 | throw new IllegalArgumentException(); 71 | if (r <= 0) 72 | throw new IllegalArgumentException(); 73 | final ArrayList circle = circles.get(r); 74 | final int circSize = circle.size(); 75 | boolean wasObstacle = false; 76 | boolean foundClear = false; 77 | for (int i = 0; i < circSize; i++) 78 | { 79 | final ArcPoint arcPoint = circle.get(i); 80 | final int px = ctr.x + arcPoint.x; 81 | final int py = ctr.y + arcPoint.y; 82 | // Point point = new Point(px, py); 83 | 84 | // if outside the board, ignore it and move to the next one 85 | if (!board.contains(px, py)) 86 | { 87 | wasObstacle = true; 88 | continue; 89 | } 90 | 91 | if (arcPoint.lagging < th1 && arcPoint.theta != th1 && arcPoint.theta != th2) 92 | { 93 | // System.out.println("< than " + arcPoint); 94 | continue; 95 | } 96 | if (arcPoint.leading > th2 && arcPoint.theta != th1 && arcPoint.theta != th2) 97 | { 98 | // System.out.println("> than " + arcPoint); 99 | continue; 100 | } 101 | 102 | // Accept this point 103 | // pointSet.add(point); 104 | board.visit(px, py); 105 | 106 | // Check to see if we have an obstacle here 107 | final boolean isObstacle = board.blocksLight(px, py); 108 | 109 | // If obstacle is encountered, we start a new run from our start 110 | // theta 111 | // to the rightTheta of the current point at radius+1 112 | // We then proceed to the next non-obstacle, whose leftTheta 113 | // becomes 114 | // our new start theta 115 | // If the last point is an obstacle, we do not start a new Run 116 | // at the 117 | // end. 118 | if (isObstacle) 119 | { 120 | // keep going 121 | if (wasObstacle) 122 | { 123 | continue; 124 | } 125 | 126 | // start a new run from our start to this point's right side 127 | else if (foundClear) 128 | { 129 | 130 | if (r < maxDistance) 131 | go(board, ctr, r + 1, maxDistance, th1, arcPoint.leading); 132 | } 133 | else 134 | { 135 | if (arcPoint.theta == 0.0) 136 | { 137 | th1 = 0.0; 138 | } 139 | else 140 | { 141 | th1 = arcPoint.leading; 142 | } 143 | // System.out.println("Adjusting start for obstacle 144 | // "+th1+" at " + arcPoint); 145 | } 146 | } 147 | else 148 | { 149 | foundClear = true; 150 | // we're clear of obstacle; any runs propogated from this 151 | // run starts at this 152 | // point's leftTheta 153 | if (wasObstacle) 154 | { 155 | final ArcPoint last = circle.get(i - 1); 156 | // if (last.theta == 0.0) { 157 | // th1 = 0.0; 158 | // } 159 | // else { 160 | th1 = last.lagging; 161 | // } 162 | 163 | // System.out.println("Adjusting start for clear of 164 | // obstacle "+th1+" at " + arcPoint); 165 | 166 | wasObstacle = false; 167 | } 168 | else 169 | { 170 | wasObstacle = false; 171 | continue; 172 | } 173 | } 174 | wasObstacle = isObstacle; 175 | } 176 | 177 | if (!wasObstacle && r < maxDistance) 178 | { 179 | go(board, ctr, r + 1, maxDistance, th1, th2); 180 | } 181 | } 182 | 183 | /** 184 | * Compute and return the list of RLPoints in line-of-sight to the given 185 | * region. In general, this method should be very fast. 186 | */ 187 | public void visitFoV(final IBoard b, final int x, final int y, final int distance) 188 | { 189 | if (b == null) 190 | throw new IllegalArgumentException(); 191 | if (distance < 1) 192 | throw new IllegalArgumentException(); 193 | 194 | // HashSet points = new HashSet(31); 195 | // RLRectangle r = locator.bounds(); 196 | // Board b = locator.board(); 197 | 198 | // Note: it would be slightly more efficient to just check around 199 | // the perimeter, but only for observers of size 3+, so for now I'm 200 | // too lazy 201 | // for (int i = 0; i < r.width; i++) { 202 | // for (int j = 0; j < r.height; j++) { 203 | // RLPoint p = RLPoint.point(r.x + i, r.y + j); 204 | // points.add(p); 205 | final Point p = new Point(x, y); 206 | b.visit(x, y); 207 | go(b, p, 1, distance, 0.0, 359.9); 208 | // } 209 | // } 210 | 211 | // return points; 212 | } 213 | 214 | public boolean exists(final IBoard b, final int startX, final int startY, final int endX, final int endY, 215 | final boolean savePath) 216 | { 217 | final int dx = endX - startX; 218 | final int dy = endY - startY; 219 | final int signX, signY; 220 | final int adx, ady; 221 | 222 | if (dx > 0) 223 | { 224 | adx = dx; 225 | signX = 1; 226 | } 227 | else 228 | { 229 | adx = -dx; 230 | signX = -1; 231 | } 232 | if (dy > 0) 233 | { 234 | ady = dy; 235 | signY = 1; 236 | } 237 | else 238 | { 239 | ady = -dy; 240 | signY = -1; 241 | } 242 | final RecordQuadrantVisitBoard fb = new RecordQuadrantVisitBoard(b, startX, startY, endX, endY, savePath); 243 | 244 | final Point p = new Point(startX, startY); 245 | 246 | if (startY == endY && endX > startX) 247 | { 248 | final int distance = dx + 1; 249 | final double deg1 = Math.toDegrees(Math.atan2(.25, dx));//very thin angle 250 | go(fb, p, 1, distance, -deg1, 0); 251 | go(fb, p, 1, distance, 0, deg1); 252 | } 253 | else 254 | { 255 | final int distance = (int) Math.sqrt(adx * adx + ady * ady) + 1; 256 | double deg1 = Math.toDegrees(Math.atan2(-dy, (adx - .5) * signX)); 257 | if (deg1 < 0) 258 | deg1 += 360; 259 | double deg2 = Math.toDegrees(Math.atan2(-(ady - .5) * signY, dx)); 260 | if (deg2 < 0) 261 | deg2 += 360; 262 | if (deg1 > deg2) 263 | { 264 | final double temp = deg1; 265 | deg1 = deg2; 266 | deg2 = temp; 267 | } 268 | 269 | // System.out.println("Locations "+(adx-1)*signX+" "+dy); 270 | // System.out.println("Locations "+dx+" "+(ady-1)*signY); 271 | // System.out.println("Degrees "+deg1+" "+deg2); 272 | 273 | go(fb, p, 1, distance, deg1, deg2); 274 | } 275 | 276 | if (savePath) 277 | { 278 | if (fb.endVisited) 279 | path = GenericCalculateProjection.calculateProjecton(startX, startY, endX, endY, fb); 280 | else 281 | { 282 | fallBackLos.exists(b, startX, startY, endX, endY, true); 283 | path = (Vector) fallBackLos.getPath(); 284 | } 285 | // calculateProjecton(startX, startY, adx, ady, fb, state); 286 | } 287 | return fb.endVisited; 288 | } 289 | 290 | public List getPath() 291 | { 292 | return path; 293 | } 294 | 295 | public void visitConeFieldOfView(final IBoard b, final int x, final int y, final int distance, int startAngle, 296 | int endAngle) 297 | { 298 | // Making Positive Y downwards 299 | final int tmp = startAngle; 300 | startAngle = -endAngle; 301 | endAngle = -tmp; 302 | 303 | if (startAngle < 0) 304 | { 305 | startAngle %= 360; 306 | startAngle += 360; 307 | } 308 | if (endAngle < 0) 309 | { 310 | endAngle %= 360; 311 | endAngle += 360; 312 | } 313 | 314 | if (startAngle > 360) 315 | startAngle %= 360; 316 | if (endAngle > 360) 317 | endAngle %= 360; 318 | // System.out.println(startAngle+" "+finishAngle); 319 | 320 | if (b == null) 321 | throw new IllegalArgumentException(); 322 | if (distance < 1) 323 | throw new IllegalArgumentException(); 324 | 325 | final Point p = new Point(x, y); 326 | b.visit(x, y); 327 | if (startAngle > endAngle) 328 | { 329 | go(b, p, 1, distance, startAngle, 359.999); 330 | go(b, p, 1, distance, 0.0, endAngle); 331 | } 332 | else 333 | go(b, p, 1, distance, startAngle, endAngle); 334 | } 335 | 336 | static class ArcPoint implements Comparable 337 | { 338 | int x, y; 339 | 340 | double theta; 341 | 342 | double leading; 343 | 344 | double lagging; 345 | 346 | ArcPoint(final int dx, final int dy) 347 | { 348 | this.x = dx; 349 | this.y = dy; 350 | theta = angle(y, x); 351 | // System.out.println(x + "," + y + ", theta=" + theta); 352 | // top left 353 | if (x < 0 && y < 0) 354 | { 355 | leading = angle(y - 0.5, x + 0.5); 356 | lagging = angle(y + 0.5, x - 0.5); 357 | } 358 | // bottom left 359 | else if (x < 0) 360 | { 361 | leading = angle(y - 0.5, x - 0.5); 362 | lagging = angle(y + 0.5, x + 0.5); 363 | } 364 | // bottom right 365 | else if (y > 0) 366 | { 367 | leading = angle(y + 0.5, x - 0.5); 368 | lagging = angle(y - 0.5, x + 0.5); 369 | } 370 | // top right 371 | else 372 | { 373 | leading = angle(y + 0.5, x + 0.5); 374 | lagging = angle(y - 0.5, x - 0.5); 375 | } 376 | 377 | } 378 | 379 | public String toString() 380 | { 381 | return "[" + x + "," + y + "=" + (int) (theta) + "/" + (int) (leading) + "/" + (int) (lagging); 382 | } 383 | 384 | double angle(final double y, final double x) 385 | { 386 | double a = Math.atan2(y, x); 387 | a = Math.toDegrees(a); 388 | a = 360.0 - a; 389 | a %= 360; 390 | if (a < 0) 391 | a += 360; 392 | return a; 393 | } 394 | 395 | public int compareTo(final Object o) 396 | { 397 | return theta > ((ArcPoint) o).theta ? 1 : -1; 398 | } 399 | 400 | @Override 401 | public boolean equals(final Object o) 402 | { 403 | if (this == o) 404 | return true; 405 | if (o == null || getClass() != o.getClass()) 406 | return false; 407 | 408 | final ArcPoint arcPoint = (ArcPoint) o; 409 | 410 | return theta == arcPoint.theta; 411 | } 412 | } 413 | 414 | } 415 | -------------------------------------------------------------------------------- /src/main/java/rlforj/math/Line2I.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.math; 8 | 9 | /** 10 | * A Euclidean 2D line class represented by integers. 11 | * 12 | * @author Jonathan Duerig 13 | */ 14 | public class Line2I 15 | { 16 | public Point near; 17 | 18 | public Point far; 19 | 20 | public Line2I(final Point newNear, final Point newFar) 21 | { 22 | near = newNear; 23 | far = newFar; 24 | } 25 | 26 | public Line2I(final int x1, final int y1, final int x2, final int y2) 27 | { 28 | near = new Point(x1, y1); 29 | far = new Point(x2, y2); 30 | } 31 | 32 | public boolean isBelow(final Point point) 33 | { 34 | return relativeSlope(point) > 0; 35 | } 36 | 37 | public boolean isBelowOrContains(final Point point) 38 | { 39 | return relativeSlope(point) >= 0; 40 | } 41 | 42 | public boolean isAbove(final Point point) 43 | { 44 | return relativeSlope(point) < 0; 45 | } 46 | 47 | public boolean isAboveOrContains(final Point point) 48 | { 49 | return relativeSlope(point) <= 0; 50 | } 51 | 52 | public boolean doesContain(final Point point) 53 | { 54 | return relativeSlope(point) == 0; 55 | } 56 | 57 | // negative if the line is above the point. 58 | // positive if the line is below the point. 59 | // 0 if the line is on the point. 60 | public int relativeSlope(final Point point) 61 | { 62 | return (far.y - near.y) * (far.x - point.x) - (far.y - point.y) * (far.x - near.x); 63 | } 64 | 65 | @Override 66 | public String toString() 67 | { 68 | return "( " + near + " -> " + far + " )"; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/rlforj/math/Point.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.math; 8 | 9 | /** 10 | * A class encapsulating a 2D point, as integers. 11 | * 12 | * @author sdatta 13 | */ 14 | public class Point 15 | { 16 | public int x; 17 | public int y; 18 | 19 | public Point(final int x, final int y) 20 | { 21 | this.x = x; 22 | this.y = y; 23 | } 24 | 25 | public String toString() 26 | { 27 | return "(" + x + "," + y + ")"; 28 | } 29 | 30 | public int distance(final Point p) 31 | { 32 | return distance(p.x, p.y); 33 | } 34 | 35 | public int distance(final int x, final int y) 36 | { 37 | return Math.max(Math.abs(this.x - x), Math.abs(this.y - y)); 38 | } 39 | 40 | public int distance2(final Point p) 41 | { 42 | return distance2(p.x, p.y); 43 | } 44 | 45 | public int distance2(final int x, final int y) 46 | { 47 | final float dx = this.x - x; 48 | final float dy = this.y - y; 49 | 50 | return (int) Math.floor(Math.sqrt(dx * dx + dy * dy)); 51 | } 52 | 53 | @Override 54 | public boolean equals(final Object o) 55 | { 56 | if (this == o) 57 | return true; 58 | if (o == null || getClass() != o.getClass()) 59 | return false; 60 | 61 | final Point point = (Point) o; 62 | 63 | return x == point.x && y == point.y; 64 | } 65 | 66 | @Override 67 | public int hashCode() 68 | { 69 | return x << 7 - x + y;//x*prime+y 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/rlforj/pathfinding/AStar.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.pathfinding; 8 | 9 | import rlforj.IBoard; 10 | import rlforj.math.Point; 11 | import rlforj.util.HeapNode; 12 | import rlforj.util.SimpleHeap; 13 | 14 | import java.util.ArrayList; 15 | 16 | public class AStar implements IPathAlgorithm 17 | { 18 | private final IBoard map; 19 | private final int boardWidth; 20 | private final int boardHeight; 21 | private final boolean allowDiagonal; 22 | 23 | public AStar(final IBoard map, final int boardWidth, final int boardHeight) 24 | { 25 | this(map, boardWidth, boardHeight, true); 26 | } 27 | 28 | public AStar(final IBoard map, final int boardWidth, final int boardHeight, final boolean allowDiagonal) 29 | { 30 | this.map = map; 31 | this.boardWidth = boardWidth; 32 | this.boardHeight = boardHeight; 33 | this.allowDiagonal = allowDiagonal; 34 | } 35 | 36 | public Point[] findPath(final int startX, final int startY, final int endX, final int endY) 37 | { 38 | return findPath(startX, startY, endX, endY, -1); 39 | } 40 | 41 | public Point[] findPath(final int startX, final int startY, final int endX, final int endY, final int radius) 42 | { 43 | if (!this.map.contains(startX, startY) || !this.map.contains(endX, endY)) 44 | { 45 | return null; 46 | } 47 | 48 | final int width; 49 | final int height; 50 | final int minX, minY, maxX, maxY; 51 | 52 | if (radius == 0) 53 | { 54 | return new Point[] { new Point(startX, startY) }; 55 | } 56 | else if (radius < 0) 57 | { 58 | width = boardWidth; 59 | height = boardHeight; 60 | 61 | minX = 0; 62 | minY = 0; 63 | maxX = boardWidth - 1; 64 | maxY = boardHeight - 1; 65 | } 66 | else 67 | { 68 | minX = Math.max(0, startX - radius); 69 | minY = Math.max(0, startY - radius); 70 | maxX = Math.min(boardWidth - 1, startX + radius); 71 | maxY = Math.min(boardHeight - 1, startY + radius); 72 | 73 | width = maxX - minX + 1; 74 | height = maxY - minY + 1; 75 | } 76 | 77 | final PathNode[][] nodeHash = new PathNode[width][height]; 78 | final SimpleHeap open = new SimpleHeap<>(1000); 79 | final PathNode startNode = new PathNode(startX, startY, 0.0); 80 | startNode.h = this.computeHeuristics(startNode, endX, endY, startX, startY); 81 | startNode.calcCost(); 82 | open.add(startNode); 83 | nodeHash[startX - minX][startY - minY] = startNode; 84 | while (open.size() > 0) 85 | { 86 | final PathNode step = (PathNode) open.poll(); 87 | if (step.x == endX && step.y == endY) 88 | { 89 | return this.createPath(step); 90 | } 91 | 92 | for (int dx = -1; dx <= 1; dx++) 93 | { 94 | for (int dy = -1; dy <= 1; dy++) 95 | { 96 | // exclude the current point, as well as diagonals if not allowed 97 | if (!((dx == 0 && dy == 0) || (dx != 0 && dy != 0 && !this.allowDiagonal))) 98 | { 99 | final int cx = step.x + dx; 100 | final int cy = step.y + dy; 101 | if (cx >= minX && cy >= minY && cx <= maxX && cy <= maxY && this.map.contains(cx, cy)) 102 | { 103 | // the only allowed obstacle is the end point 104 | if ((cx != endX || cy != endY) && this.map.isObstacle(cx, cy)) 105 | continue; 106 | 107 | final PathNode n1; 108 | final double this_cost = dx != 0 && dy != 0 ? 1.1 : 1.0; 109 | if (nodeHash[cx - minX][cy - minY] == null) 110 | { 111 | n1 = new PathNode(cx, cy, step.g + this_cost); 112 | n1.prev = step; 113 | n1.h = this.computeHeuristics(n1, endX, endY, startX, startY); 114 | n1.calcCost(); 115 | open.add(n1); 116 | nodeHash[cx - minX][cy - minY] = n1; 117 | } 118 | else 119 | { 120 | n1 = nodeHash[cx - minX][cy - minY]; 121 | if (n1.g > step.g + this_cost) 122 | { 123 | n1.g = step.g + this_cost; 124 | n1.calcCost(); 125 | n1.prev = step; 126 | if (open.contains(n1)) 127 | { 128 | open.adjust(n1); 129 | } 130 | else 131 | { 132 | open.add(n1); 133 | } 134 | } 135 | } 136 | } 137 | } 138 | } 139 | } 140 | } 141 | return null; 142 | } 143 | 144 | private double computeHeuristics(final PathNode node, final int x1, final int y1, final int startx, 145 | final int starty) 146 | { 147 | final int dx = Math.abs(node.x - x1); 148 | final int dy = Math.abs(node.y - y1); 149 | final int diagsteps = Math.min(dx, dy); 150 | return (double) diagsteps * 1.0 + (double) ((Math.max(dx, dy) - diagsteps)) + 151 | (double) Math.abs((node.x - x1) * (starty - y1) - (node.y - y1) * (startx - x1)) * 0.01; 152 | } 153 | 154 | private Point[] createPath(PathNode end) 155 | { 156 | if (end == null) 157 | return null; 158 | 159 | final ArrayList v = new ArrayList<>(); 160 | while (end != null) 161 | { 162 | v.add(new Point(end.x, end.y)); 163 | end = end.prev; 164 | } 165 | final int sz = v.size(); 166 | final Point[] ret = new Point[sz]; 167 | int i = 0; 168 | while (i < sz) 169 | { 170 | ret[i] = v.get(sz - i - 1); 171 | ++i; 172 | } 173 | return ret; 174 | } 175 | 176 | public static class PathNode implements HeapNode 177 | { 178 | public double g; 179 | public double h; 180 | int x; 181 | int y; 182 | double cost; 183 | PathNode prev; 184 | int heapIndex; 185 | 186 | public PathNode(final int x, final int y, final double g) 187 | { 188 | this.x = x; 189 | this.y = y; 190 | this.g = g; 191 | } 192 | 193 | public PathNode(final int x, final int y) 194 | { 195 | this.x = x; 196 | this.y = y; 197 | } 198 | 199 | public void calcCost() 200 | { 201 | this.cost = this.h + this.g; 202 | } 203 | 204 | public int compareTo(final Object o) 205 | { 206 | return (int) Math.signum(this.cost - ((PathNode) o).cost); 207 | } 208 | 209 | public int getHeapIndex() 210 | { 211 | return this.heapIndex; 212 | } 213 | 214 | public void setHeapIndex(final int heapIndex) 215 | { 216 | this.heapIndex = heapIndex; 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/main/java/rlforj/pathfinding/IPathAlgorithm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.pathfinding; 8 | 9 | import rlforj.math.Point; 10 | 11 | /** 12 | * Author: Fabio Ticconi 13 | * Date: 07/11/17 14 | */ 15 | public interface IPathAlgorithm 16 | { 17 | Point[] findPath(final int startX, final int starty, final int endX, final int endY, final int radius); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/rlforj/util/BresenhamLine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | package rlforj.util; 7 | 8 | /** 9 | * Bresenham's famous line drawing algorithm. Works for 2D. 10 | */ 11 | public final class BresenhamLine 12 | { 13 | /** 14 | * General case algorithm 15 | */ 16 | private static final BresenhamLine bresenham = new BresenhamLine(); 17 | 18 | /** 19 | * Used for calculation 20 | */ 21 | private int dx, dy, error, x_inc, y_inc, xx, yy, length, count; 22 | 23 | /** 24 | * Construct a Bresenham algorithm. 25 | */ 26 | public BresenhamLine() 27 | { 28 | } 29 | 30 | /** 31 | * Plot a line between (x1,y1) and (x2,y2). The results are placed in x[] and y[], which must be large enough. 32 | * 33 | * @param x1 x start position 34 | * @param y1 y start position 35 | * @param x2 x end position 36 | * @param y2 y end position 37 | * @param x array where output x values are put 38 | * @param y array where output y values are put 39 | * @return the length of the line or the length of x[]/y[], whichever is smaller 40 | */ 41 | public static final int plot(final int x1, final int y1, final int x2, final int y2, final int x[], final int y[]) 42 | { 43 | 44 | final int length = Math.min(x.length, Math.min(y.length, bresenham.plot(x1, y1, x2, y2))); 45 | for (int i = 0; i < length; i++) 46 | { 47 | x[i] = bresenham.getX(); 48 | y[i] = bresenham.getY(); 49 | bresenham.next(); 50 | } 51 | 52 | return length; 53 | } 54 | 55 | /** 56 | * Plot a line between (x1,y1) and (x2,y2). To step through the line use next(). 57 | */ 58 | private int plot(final int x1, final int y1, final int x2, final int y2) 59 | { 60 | // compute horizontal and vertical deltas 61 | dx = x2 - x1; 62 | dy = y2 - y1; 63 | 64 | // test which direction the line is going in i.e. slope angle 65 | if (dx >= 0) 66 | { 67 | x_inc = 1; 68 | } 69 | else 70 | { 71 | x_inc = -1; 72 | dx = -dx; // need absolute value 73 | } 74 | 75 | // test y component of slope 76 | 77 | if (dy >= 0) 78 | { 79 | y_inc = 1; 80 | } 81 | else 82 | { 83 | y_inc = -1; 84 | dy = -dy; // need absolute value 85 | } 86 | 87 | xx = x1; 88 | yy = y1; 89 | 90 | if (dx > dy) 91 | error = dx >> 1; 92 | else 93 | error = dy >> 1; 94 | 95 | count = 0; 96 | length = Math.max(dx, dy) + 1; 97 | return length; 98 | } 99 | 100 | /** 101 | * Get the next point in the line. You must not call next() if the 102 | * previous invocation of next() returned false. 103 | *

104 | * Retrieve the X and Y coordinates of the line with getX() and getY(). 105 | */ 106 | private void next() 107 | { 108 | // now based on which delta is greater we can draw the line 109 | if (dx > dy) 110 | { 111 | // adjust the error term 112 | error += dy; 113 | 114 | // test if error has overflowed 115 | if (error >= dx) 116 | { 117 | error -= dx; 118 | 119 | // move to next line 120 | yy += y_inc; 121 | } 122 | 123 | // move to the next pixel 124 | xx += x_inc; 125 | } 126 | else 127 | { 128 | // adjust the error term 129 | error += dx; 130 | 131 | // test if error overflowed 132 | if (error >= dy) 133 | { 134 | error -= dy; 135 | 136 | // move to next line 137 | xx += x_inc; 138 | } 139 | 140 | // move to the next pixel 141 | yy += y_inc; 142 | } 143 | 144 | count++; 145 | } 146 | 147 | /** 148 | * @return the current X coordinate 149 | */ 150 | private int getX() 151 | { 152 | return xx; 153 | } 154 | 155 | /** 156 | * @return the current Y coordinate 157 | */ 158 | private int getY() 159 | { 160 | return yy; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/main/java/rlforj/util/Directions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.util; 8 | 9 | /** 10 | * A class for various directions, their offsets. 11 | * 12 | * @author sdatta 13 | */ 14 | public enum Directions 15 | { 16 | NORTH, 17 | WEST, 18 | SOUTH, 19 | EAST, 20 | NE, 21 | NW, 22 | SE, 23 | SW; 24 | public static final int[] dx = { 0, -1, 0, 1, 1, -1, 1, -1 }, dy = { 1, 0, -1, 0, 1, 1, -1, -1 }; 25 | 26 | /** 27 | * The N4 neighbourhood, in clockwise order 28 | * NORTH, WEST, SOUTH, EAST 29 | */ 30 | public static final Directions[] N4 = { NORTH, WEST, SOUTH, EAST }; 31 | 32 | /** 33 | * The N8 neighbourhood, in clockwise order 34 | * NORTH, NW, WEST, SW, SOUTH, SE, EAST, NE 35 | */ 36 | public static final Directions[] N8 = { NORTH, NW, WEST, SW, SOUTH, SE, EAST, NE }; 37 | 38 | /** 39 | * The x offset 40 | * 41 | * @return x offset of this direction 42 | */ 43 | public int dx() 44 | { 45 | return dx[this.ordinal()]; 46 | } 47 | 48 | /** 49 | * The y offset 50 | * 51 | * @return y offset of this direction 52 | */ 53 | public int dy() 54 | { 55 | return dy[this.ordinal()]; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/rlforj/util/HeapNode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.util; 8 | 9 | /** 10 | * A heapnode interface that will allow the index to be stored and retrieved 11 | * from the object directly. 12 | *

13 | * This is for maximum efficiency, so that when the heap is notified that a 14 | * object has changed, instead of searching for the object in O(N) time, it 15 | * will directly jump to the object and put it in the right place in O(log n) 16 | * time. 17 | *

18 | * Implementing class SHOULD NOT modify the int index. Ideally it should be a 19 | * SimpleHeapContext instead of int but I am saving an unnecessary new() call. 20 | *

21 | * Downside: An object can be stored in only one heap. Hopefully this is the 22 | * typical case. 23 | * 24 | * @author sidatta 25 | */ 26 | public interface HeapNode extends Comparable 27 | { 28 | int getHeapIndex(); 29 | 30 | void setHeapIndex(int heapIndex); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/rlforj/util/MathUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.util; 8 | 9 | public class MathUtils 10 | { 11 | public static final int isqrt(final int x) 12 | { 13 | int op, res, one; 14 | 15 | op = x; 16 | res = 0; 17 | 18 | /* "one" starts at the highest power of four <= than the argument. */ 19 | one = 1 << 30; /* second-to-top bit set */ 20 | while (one > op) 21 | one >>= 2; 22 | 23 | while (one != 0) 24 | { 25 | if (op >= res + one) 26 | { 27 | op = op - (res + one); 28 | res = res + 2 * one; 29 | } 30 | res >>= 1; 31 | one >>= 2; 32 | } 33 | return (res); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/rlforj/util/SimpleHeap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.util; 8 | 9 | /** 10 | * A Simple heap. Behaves pretty much like priority queue. 11 | *

12 | * Differences: Objects are allowed to change, but the heap must be notified 13 | * immediately by calling adjust(Object). 14 | * Each object MUST implement HeapNode interface. This allows for storing 15 | * and retrieving the heap index directly and hence speeding up the adjust 16 | * process greatly. Objects should not modify the index stored. 17 | *

18 | * Downside: Each object can only be stored in ONE SimpleHeap. Hopefully this 19 | * is the typical case. 20 | * 21 | * @param The type 22 | */ 23 | public class SimpleHeap 24 | { 25 | Object[] queue; // package-level for testing purpose 26 | private int size = 0; 27 | 28 | public SimpleHeap(final int initialCapacity) 29 | { 30 | this.queue = new Object[initialCapacity]; 31 | } 32 | 33 | /** 34 | * Add a new value. Null cannot be added. 35 | * 36 | * @param e value to add 37 | * @return true if could add, false if failed (can never happen) 38 | */ 39 | public boolean add(final T e) 40 | { 41 | if (e == null) 42 | throw new NullPointerException(); 43 | final int i = size; 44 | if (i >= queue.length) 45 | grow(i + 1); 46 | size = i + 1; 47 | if (i == 0) 48 | { 49 | queue[0] = e; 50 | e.setHeapIndex(0); 51 | } 52 | else 53 | { 54 | siftUp(i, e); 55 | } 56 | return true; 57 | } 58 | 59 | private void siftUp(int k, final T x) 60 | { 61 | final Comparable key = (Comparable) x; 62 | while (k > 0) 63 | { 64 | final int parent = (k - 1) >>> 1; 65 | final T e = (T) queue[parent]; 66 | if (key.compareTo(e) >= 0) 67 | break; 68 | queue[k] = e; 69 | e.setHeapIndex(k); 70 | k = parent; 71 | } 72 | queue[k] = x; 73 | x.setHeapIndex(k); 74 | } 75 | 76 | private void siftDown(int k, final T x) 77 | { 78 | final Comparable key = (Comparable) x; 79 | final int half = size >>> 1; // loop while a non-leaf 80 | while (k < half) 81 | { 82 | int child = (k << 1) + 1; // assume left child is least 83 | T c = (T) queue[child]; 84 | final int right = child + 1; 85 | if (right < size && c.compareTo(queue[right]) > 0) 86 | c = (T) queue[child = right]; 87 | if (key.compareTo(c) <= 0) 88 | break; 89 | queue[k] = c; 90 | c.setHeapIndex(k); 91 | k = child; 92 | } 93 | queue[k] = x; 94 | x.setHeapIndex(k); 95 | } 96 | 97 | /** 98 | * Get the top element from the heap, removing it from the heap. 99 | * Returns null if none are left. 100 | * 101 | * @return element removed from heap, or null if empty 102 | */ 103 | public T poll() 104 | { 105 | if (size == 0) 106 | return null; 107 | final int s = --size; 108 | final T result = (T) queue[0]; 109 | final T x = (T) queue[s]; 110 | queue[s] = null; 111 | if (s != 0) 112 | siftDown(0, x); 113 | result.setHeapIndex(-1); // Mark it as not in heap 114 | return result; 115 | } 116 | 117 | public void adjust(final T x) 118 | { 119 | if (x.getHeapIndex() < 0 || x.getHeapIndex() >= size) 120 | return; 121 | siftUp(x.getHeapIndex(), x); 122 | siftDown(x.getHeapIndex(), x); 123 | } 124 | 125 | public boolean contains(final T x) 126 | { 127 | return x.getHeapIndex() >= 0 && x.getHeapIndex() < size && queue[x.getHeapIndex()] == x; 128 | } 129 | 130 | // private void heapify() { 131 | // for (int i = (size >>> 1) - 1; i >= 0; i--) 132 | // siftDown(i, (T) queue[i]); 133 | // } 134 | // 135 | // private T removeAt(int i) { 136 | // assert i >= 0 && i < size; 137 | // int s = --size; 138 | // if (s == i) // removed last element 139 | // queue[i] = null; 140 | // else { 141 | // T moved = (T) queue[s]; 142 | // queue[s] = null; 143 | // siftDown(i, moved); 144 | // if (queue[i] == moved) { 145 | // siftUp(i, moved); 146 | // if (queue[i] != moved) 147 | // return moved; 148 | // } 149 | // } 150 | // return null; 151 | // } 152 | 153 | private void grow(final int minCapacity) 154 | { 155 | if (minCapacity < 0) // overflow 156 | throw new OutOfMemoryError(); 157 | final int oldCapacity = queue.length; 158 | // Double size if small; else grow by 50% 159 | int newCapacity = ((oldCapacity < 64) ? ((oldCapacity + 1) * 2) : ((oldCapacity / 2) * 3)); 160 | if (newCapacity < 0) // overflow 161 | newCapacity = Integer.MAX_VALUE; 162 | if (newCapacity < minCapacity) 163 | newCapacity = minCapacity; 164 | final Object[] oldQueue = queue; 165 | queue = new Object[newCapacity]; 166 | System.arraycopy(oldQueue, 0, queue, 0, oldQueue.length); 167 | } 168 | 169 | public int size() 170 | { 171 | return size; 172 | } 173 | 174 | public void clear() 175 | { 176 | for (int i = 0; i < size; i++) 177 | queue[i] = null; 178 | size = 0; 179 | } 180 | 181 | /** 182 | * Meant for testing rather than actual use. 183 | * 184 | * @param index index in internal queue 185 | * @return element at index, or null if invalid 186 | */ 187 | public T getElementAt(final int index) 188 | { 189 | if (index >= 0 && index < queue.length) 190 | return (T) queue[index]; 191 | else 192 | return null; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/test/java/rlforj/los/test/ConeFovTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los.test; 8 | 9 | import rlforj.los.ConePrecisePremisive; 10 | import rlforj.los.IConeFovAlgorithm; 11 | import rlforj.los.ShadowCasting; 12 | import rlforj.math.Point; 13 | 14 | public class ConeFovTest 15 | { 16 | /* 17 | * TODO : convert to TestCase 18 | */ 19 | public static void main(final String[] args) 20 | { 21 | final int startAngle = -10; 22 | final int finishAngle = 100; 23 | // if(startAngle<0) {startAngle%=360; startAngle+=360; } 24 | // if(finishAngle<0) {finishAngle%=360; finishAngle+=360; } 25 | // 26 | // if(startAngle>360) startAngle%=360; 27 | // if(finishAngle>360) finishAngle%=360; 28 | // System.out.println(startAngle+" "+finishAngle); 29 | 30 | final TestBoard b = new TestBoard(false); 31 | 32 | b.exception.add(new Point(15, 15)); 33 | b.exception.add(new Point(15, 16)); 34 | b.exception.add(new Point(16, 15)); 35 | 36 | IConeFovAlgorithm a = new ConePrecisePremisive(); 37 | 38 | a.visitConeFieldOfView(b, 10, 10, 10, startAngle, finishAngle); 39 | 40 | b.mark(10, 10, '@'); 41 | b.print(-1, 21, -1, 21); 42 | 43 | System.out.println("visitederr " + b.visiterr); 44 | 45 | a = new ShadowCasting(); 46 | b.visited.clear(); 47 | 48 | a.visitConeFieldOfView(b, 10, 10, 10, startAngle, finishAngle); 49 | 50 | b.mark(10, 10, '@'); 51 | b.print(-1, 21, -1, 21); 52 | 53 | // IFovAlgorithm a1= new ShadowCasting(); 54 | // b.visited.clear(); 55 | // 56 | // a1.visitFoV(b, 10, 10, 10); 57 | // 58 | // b.mark(10, 10, '@'); 59 | // b.print(-1, 21, -1, 21); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/rlforj/los/test/FovPrecisePermissiveTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los.test; 8 | 9 | import rlforj.los.PrecisePermissive; 10 | 11 | public class FovPrecisePermissiveTest extends FovTest 12 | { 13 | public FovPrecisePermissiveTest() 14 | { 15 | super(); 16 | a = new PrecisePermissive(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/rlforj/los/test/FovShadowCastingTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los.test; 8 | 9 | import rlforj.los.ShadowCasting; 10 | 11 | public class FovShadowCastingTest extends FovTest 12 | { 13 | public FovShadowCastingTest() 14 | { 15 | a = new ShadowCasting(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/rlforj/los/test/FovSuite.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los.test; 8 | 9 | import junit.framework.Test; 10 | import junit.framework.TestSuite; 11 | 12 | /** 13 | * Currently not working 14 | * 15 | * @author sdatta 16 | */ 17 | public class FovSuite extends TestSuite 18 | { 19 | public FovSuite() 20 | { 21 | addTestSuite(FovPrecisePermissiveTest.class); 22 | } 23 | 24 | public static Test suite() 25 | { 26 | final TestSuite s = new TestSuite(); 27 | s.addTestSuite(FovPrecisePermissiveTest.class); 28 | s.addTestSuite(FovShadowCastingTest.class); 29 | return s; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/rlforj/los/test/FovTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los.test; 8 | 9 | import junit.framework.TestCase; 10 | import rlforj.los.IFovAlgorithm; 11 | import rlforj.math.Point; 12 | 13 | import java.util.Random; 14 | 15 | /** 16 | * Testing FOV algorithms 17 | * 18 | * @author sdatta 19 | */ 20 | public abstract class FovTest extends TestCase 21 | { 22 | IFovAlgorithm a; 23 | 24 | public void testEmpty() 25 | { 26 | final TestBoard b = new TestBoard(false); 27 | 28 | a.visitFoV(b, 10, 10, 5); 29 | // b.print(5, 15, 5, 15); 30 | // System.out.println(); 31 | 32 | assertTrue(b.visited.contains(new Point(11, 11))); 33 | assertTrue(b.visited.contains(new Point(10, 11))); 34 | assertTrue(b.visited.contains(new Point(11, 10))); 35 | assertTrue(b.visited.contains(new Point(10, 15))); 36 | assertTrue(b.visited.contains(new Point(15, 10))); 37 | } 38 | 39 | public void testFull() 40 | { 41 | final TestBoard b = new TestBoard(true); 42 | 43 | a.visitFoV(b, 10, 10, 5); 44 | // b.print(5, 15, 5, 15); 45 | // System.out.println(); 46 | 47 | assertTrue(b.visited.contains(new Point(11, 11))); 48 | assertTrue(b.visited.contains(new Point(10, 11))); 49 | assertTrue(b.visited.contains(new Point(11, 10))); 50 | assertFalse(b.visited.contains(new Point(10, 15))); 51 | assertFalse(b.visited.contains(new Point(15, 10))); 52 | } 53 | 54 | public void testLine() 55 | { 56 | final TestBoard b = new TestBoard(true); 57 | 58 | for (int i = 5; i < 11; i++) 59 | { 60 | b.exception.add(new Point(i, 10)); 61 | } 62 | 63 | a.visitFoV(b, 10, 10, 5); 64 | // b.print(5, 15, 5, 15); 65 | // System.out.println(); 66 | 67 | assertTrue(b.visited.contains(new Point(11, 11))); 68 | assertTrue(b.visited.contains(new Point(10, 11))); 69 | assertTrue(b.visited.contains(new Point(11, 10))); 70 | assertTrue(b.visited.contains(new Point(5, 10))); 71 | assertFalse(b.visited.contains(new Point(15, 10))); 72 | } 73 | 74 | public void testAcrossPillar() 75 | { 76 | final TestBoard b = new TestBoard(false); 77 | 78 | b.exception.add(new Point(10, 10)); 79 | 80 | a.visitFoV(b, 9, 9, 5); 81 | // b.print(4, 14, 4, 14); 82 | // System.out.println(); 83 | 84 | assertTrue(b.visited.contains(new Point(10, 11))); 85 | assertFalse(b.visited.contains(new Point(11, 11))); 86 | } 87 | 88 | public void testDiagonalWall() 89 | { 90 | final TestBoard b = new TestBoard(false); 91 | 92 | b.exception.add(new Point(11, 11)); 93 | b.exception.add(new Point(10, 10)); 94 | 95 | a.visitFoV(b, 10, 11, 5); 96 | // b.print(5, 15, 6, 16); 97 | // System.out.println(); 98 | 99 | assertTrue(b.visited.contains(new Point(11, 10))); 100 | } 101 | 102 | public void testLarge() 103 | { 104 | final TestBoard b = new TestBoard(false); 105 | 106 | final Random rand = new Random(); 107 | for (int i = 0; i < 100; i++) 108 | { 109 | b.exception.add(new Point(rand.nextInt(81) + 60, rand.nextInt(81) + 60)); 110 | } 111 | 112 | final long t1 = System.currentTimeMillis(); 113 | a.visitFoV(b, 100, 100, 40); 114 | final long t2 = System.currentTimeMillis(); 115 | 116 | System.out.println("Large Test took " + (t2 - t1)); 117 | System.out.println("Chk b4 visit " + b.chkb4visit.size()); 118 | System.out.println("Chk b4 visit fails for circular fov in PrecisePermissive"); 119 | // System.out.println(b.chkb4visit); 120 | System.out.println("Error visit " + b.visiterr.size()); 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/test/java/rlforj/los/test/LosPrecisePermissiveTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los.test; 8 | 9 | import rlforj.los.PrecisePermissive; 10 | 11 | public class LosPrecisePermissiveTest extends LosTest 12 | { 13 | public LosPrecisePermissiveTest() 14 | { 15 | a = new PrecisePermissive(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/rlforj/los/test/LosSuite.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los.test; 8 | 9 | import junit.framework.Test; 10 | import junit.framework.TestSuite; 11 | 12 | /** 13 | * Author: Fabio Ticconi 14 | * Date: 24/10/17 15 | */ 16 | public class LosSuite extends TestSuite 17 | { 18 | public LosSuite() 19 | { 20 | addTestSuite(LosPrecisePermissiveTest.class); 21 | } 22 | 23 | public static Test suite() 24 | { 25 | final TestSuite s = new TestSuite(); 26 | s.addTestSuite(LosPrecisePermissiveTest.class); 27 | // s.addTestSuite(LosShadowCastingTest.class); 28 | return s; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/rlforj/los/test/LosTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los.test; 8 | 9 | import junit.framework.TestCase; 10 | import rlforj.los.ILosAlgorithm; 11 | import rlforj.math.Point; 12 | 13 | /** 14 | * Testing Los algorithms 15 | */ 16 | public abstract class LosTest extends TestCase 17 | { 18 | ILosAlgorithm a; 19 | 20 | public void testEmpty() 21 | { 22 | final TestBoard b = new TestBoard(false); 23 | 24 | // b.print(5, 15, 5, 15); 25 | // System.out.println(); 26 | 27 | assertTrue(a.exists(b, 10, 10, 11, 11, false)); 28 | assertTrue(a.exists(b, 10, 10, 10, 11, false)); 29 | assertTrue(a.exists(b, 10, 10, 11, 10, false)); 30 | assertTrue(a.exists(b, 10, 10, 10, 15, false)); 31 | assertTrue(a.exists(b, 10, 10, 15, 10, false)); 32 | } 33 | 34 | public void testFull() 35 | { 36 | final TestBoard b = new TestBoard(true); 37 | 38 | // b.print(5, 15, 5, 15); 39 | // System.out.println(); 40 | 41 | assertTrue(a.exists(b, 10, 10, 11, 11, false)); 42 | assertTrue(a.exists(b, 10, 10, 10, 11, false)); 43 | assertTrue(a.exists(b, 10, 10, 11, 10, false)); 44 | assertFalse(a.exists(b, 10, 10, 10, 15, false)); 45 | assertFalse(a.exists(b, 10, 10, 15, 10, false)); 46 | } 47 | 48 | public void testLine() 49 | { 50 | final TestBoard b = new TestBoard(true); 51 | 52 | for (int i = 5; i < 11; i++) 53 | { 54 | b.exception.add(new Point(i, 10)); 55 | } 56 | 57 | // b.print(5, 15, 5, 15); 58 | // System.out.println(); 59 | 60 | assertTrue(a.exists(b, 10, 10, 11, 11, false)); 61 | assertTrue(a.exists(b, 10, 10, 10, 11, false)); 62 | assertTrue(a.exists(b, 10, 10, 11, 10, false)); 63 | assertTrue(a.exists(b, 10, 10, 5, 10, false)); 64 | assertFalse(a.exists(b, 10, 10, 15, 10, false)); 65 | } 66 | 67 | public void testAcrossPillar() 68 | { 69 | final TestBoard b = new TestBoard(false); 70 | 71 | b.exception.add(new Point(10, 10)); 72 | 73 | // b.print(4, 14, 4, 14); 74 | // System.out.println(); 75 | 76 | assertTrue(a.exists(b, 9, 9, 10, 11, false)); 77 | assertFalse(a.exists(b, 9, 9, 11, 11, false)); 78 | } 79 | 80 | public void testDiagonalWall() 81 | { 82 | final TestBoard b = new TestBoard(false); 83 | 84 | b.exception.add(new Point(11, 11)); 85 | b.exception.add(new Point(10, 10)); 86 | 87 | // b.print(5, 15, 6, 16); 88 | // System.out.println(); 89 | assertTrue(a.exists(b, 10, 11, 11, 10, false)); 90 | 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/rlforj/los/test/ProjectionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los.test; 8 | 9 | import rlforj.los.ILosAlgorithm; 10 | import rlforj.los.ShadowCasting; 11 | import rlforj.math.Point; 12 | 13 | import java.util.List; 14 | import java.util.Random; 15 | 16 | public class ProjectionTest 17 | { 18 | 19 | public static void main(final String[] args) 20 | { 21 | final Random rand = new Random(); 22 | final TestBoard tb = new TestBoard(false); 23 | 24 | for (int i = 0; i < 50; i++) 25 | { 26 | tb.exception.add(new Point(rand.nextInt(21), rand.nextInt(21))); 27 | } 28 | 29 | final int x1 = rand.nextInt(21); 30 | final int y1 = rand.nextInt(21); 31 | 32 | // ILosAlgorithm alg = new PrecisePermissive(); 33 | final ILosAlgorithm alg = new ShadowCasting(); 34 | 35 | final boolean losExists = alg.exists(tb, 10, 10, x1, y1, true); 36 | final List path = alg.getPath(); 37 | 38 | for (final Point p : path) 39 | { 40 | final int xx = p.x; 41 | final int yy = p.y; 42 | tb.mark(xx, yy, '-'); 43 | } 44 | 45 | tb.mark(10, 10, '@'); 46 | tb.mark(x1, y1, '*'); 47 | 48 | tb.print(-1, 46, -1, 22); 49 | System.out.println("LosExists " + losExists); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/rlforj/los/test/TestBoard.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.los.test; 8 | 9 | import rlforj.IBoard; 10 | import rlforj.math.Point; 11 | 12 | import java.util.HashMap; 13 | import java.util.HashSet; 14 | import java.util.Map; 15 | import java.util.Set; 16 | 17 | public class TestBoard implements IBoard 18 | { 19 | 20 | public boolean def; // true => obstacle 21 | 22 | public Set exception = new HashSet<>(); 23 | 24 | public Set visited = new HashSet<>(); 25 | 26 | public Set chkb4visit = new HashSet<>(); 27 | 28 | public Set visiterr = new HashSet<>(); 29 | 30 | public Set prjPath = new HashSet<>(); 31 | 32 | public Map marks = new HashMap<>(); 33 | 34 | public TestBoard(final boolean defaultObscured) 35 | { 36 | this.def = defaultObscured; 37 | } 38 | 39 | public void mark(final int x, final int y, final char c) 40 | { 41 | marks.put(new Point(x, y), c); 42 | } 43 | 44 | public boolean contains(final int x, final int y) 45 | { 46 | return true; 47 | } 48 | 49 | public boolean isObstacle(final int x, final int y) 50 | { 51 | final Point p = new Point(x, y); 52 | if (!visited.contains(p)) 53 | chkb4visit.add(p); 54 | return def ^ exception.contains(new Point(x, y)); 55 | } 56 | 57 | @Override 58 | public boolean blocksLight(final int x, final int y) 59 | { 60 | return isObstacle(x, y); 61 | } 62 | 63 | @Override 64 | public boolean blocksStep(final int x, final int y) 65 | { 66 | return isObstacle(x, y); 67 | } 68 | 69 | public void visit(final int x, final int y) 70 | { 71 | final Point p = new Point(x, y); 72 | if (visited.contains(p)) 73 | visiterr.add(p); 74 | visited.add(new Point(x, y)); 75 | } 76 | 77 | public void print(final int fromx, final int tox, final int fromy, final int toy) 78 | { 79 | for (int y = fromy; y <= toy; y++) 80 | { 81 | for (int x = fromx; x <= tox; x++) 82 | { 83 | final Point point = new Point(x, y); 84 | Character c = marks.get(point); 85 | if (c == null) 86 | { 87 | if (blocksLight(x, y)) 88 | c = (visited.contains(point) ? '#' : 'x'); 89 | else 90 | { 91 | c = (visited.contains(point) ? 'o' : '.'); 92 | } 93 | } 94 | System.out.print(c); 95 | } 96 | System.out.println(); 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/test/java/rlforj/pathfinding/test/AStarTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.pathfinding.test; 8 | 9 | import org.junit.Test; 10 | import rlforj.math.Point; 11 | import rlforj.pathfinding.AStar; 12 | import rlforj.pathfinding.IPathAlgorithm; 13 | import rlforj.util.Directions; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Random; 17 | 18 | import static org.junit.Assert.*; 19 | 20 | public class AStarTest 21 | { 22 | 23 | /** 24 | * 1000 times 25 | * Build a random board. Pick 2 random points. Find a path. If a path is 26 | * returned, check that: 27 | * 1. It is a valid path (all points are adjacent to each other) 28 | * 2. No point on the path is an obstacle. 29 | * 3. If pathfindfind fails, floodfill the map startinf from the start point. 30 | * If endpoint is not the same color, path does not exist. Hence check 31 | * pathfinding failure. 32 | *

33 | * Not tested: 34 | * 1. It is the shortest path. 35 | */ 36 | @Test 37 | public void testAStarBasic() 38 | { 39 | final Random rand = new Random(); 40 | for (int i = 0; i < 1000; i++) 41 | { 42 | final int w = rand.nextInt(80) + 20; //20 - 100 43 | final int h = rand.nextInt(80) + 20; //20 - 100 44 | 45 | final StringBuilder sb = new StringBuilder(); 46 | // Create mockboard 47 | for (int k = 0; k < h; k++) 48 | { 49 | for (int j = 0; j < w; j++) 50 | if (rand.nextInt(100) < 30)// 30% coverage 51 | sb.append('#'); 52 | else 53 | sb.append(' '); 54 | sb.append('\n'); 55 | } 56 | final MockBoard m = new MockBoard(sb.toString()); 57 | 58 | int startx, starty, endx, endy; 59 | 60 | // we want to check all possible cases of start and end point being, or not, obstacles 61 | final boolean startNoObstacle = rand.nextBoolean(); 62 | final boolean endNoObstacle = rand.nextBoolean(); 63 | while (true) 64 | { 65 | startx = rand.nextInt(w); 66 | starty = rand.nextInt(h); 67 | endx = rand.nextInt(w); 68 | endy = rand.nextInt(h); 69 | 70 | if ((startNoObstacle && m.isObstacle(startx, starty)) || 71 | (!startNoObstacle && !m.isObstacle(startx, starty))) 72 | break; 73 | 74 | if ((endNoObstacle && m.isObstacle(endx, endy)) || (!endNoObstacle && !m.isObstacle(endx, endy))) 75 | break; 76 | } 77 | 78 | final IPathAlgorithm algo = new AStar(m, w, h); 79 | 80 | final Point pStart = new Point(startx, starty); 81 | final Point pEnd = new Point(endx, endy); 82 | 83 | final int radius = rand.nextInt(80) + 20; // 20-100 84 | final Point[] path = algo.findPath(startx, starty, endx, endy, radius); 85 | if (path != null) 86 | { 87 | // Check path 88 | for (int pi = 0; pi < path.length; pi++) 89 | { 90 | final Point step = path[pi]; 91 | 92 | if (pi == 0) 93 | assertEquals("Path did not start with the starting point", step, pStart); 94 | else if (pi == path.length - 1) 95 | assertEquals("Path did not end with the ending point", step, pEnd); 96 | else 97 | assertFalse("A point on A* path was an obstacle", m.isObstacle(step.x, step.y)); 98 | } 99 | 100 | // Check continuity 101 | Point lastStep = null; 102 | for (final Point step : path) 103 | { 104 | if (lastStep == null) 105 | { 106 | lastStep = step; 107 | continue; 108 | } 109 | 110 | assertTrue("Discontinuous path in A*", 111 | step.x - lastStep.x <= 1 && step.x - lastStep.x >= -1 && step.y - lastStep.y <= 1 && 112 | step.y - lastStep.y >= -1); 113 | 114 | lastStep = step; 115 | } 116 | } 117 | else 118 | { 119 | assertFalse("Path existed but A* failed", floodFillTest(m, pStart, pEnd, radius)); 120 | } 121 | } 122 | } 123 | 124 | /** 125 | * FloodFill the board from point 1 and see if point2 is same color. If not, 126 | * points are not reachable from each other. 127 | * 128 | * @param mb board 129 | * @param start start point (can be a obstacle) 130 | * @param end end point (can be a obstacle) 131 | * @param radius radius of search 132 | * @return true if there exists a path between start and end point, false otherwise 133 | */ 134 | private boolean floodFillTest(final MockBoard mb, final Point start, final Point end, final int radius) 135 | { 136 | final int EMPTY = 0, FULL = 1, COLOR = 2; 137 | 138 | final int width = mb.getWidth(); 139 | final int height = mb.getHeight(); 140 | 141 | final int[][] board = new int[width][]; 142 | for (int i = 0; i < width; i++) 143 | { 144 | board[i] = new int[height]; 145 | for (int j = 0; j < height; j++) 146 | { 147 | // Special handling for start and end point: start it's always coloured, 148 | // while end is always empty, even if they are both obstacles 149 | if (start.x == i && start.y == j) 150 | board[i][j] = COLOR; 151 | else if (end.x == i && end.y == j) 152 | board[i][j] = EMPTY; 153 | else if (mb.isObstacle(i, j)) 154 | board[i][j] = FULL; 155 | else 156 | board[i][j] = EMPTY; 157 | } 158 | } 159 | 160 | final ArrayList l = new ArrayList<>(width * height); 161 | l.add(start); 162 | while (!l.isEmpty()) 163 | { 164 | final Point p1 = l.remove(l.size() - 1); 165 | for (final Directions d : Directions.N8) 166 | { 167 | final Point p2 = new Point(p1.x + d.dx(), p1.y + d.dy()); 168 | if (start.distance(p2) >= radius || !mb.contains(p2.x, p2.y) || board[p2.x][p2.y] != EMPTY) 169 | continue; 170 | 171 | board[p2.x][p2.y] = COLOR; 172 | l.add(p2); 173 | } 174 | } 175 | 176 | // if the end point is coloured, then there's a path between the start and end point 177 | return board[end.x][end.y] == COLOR; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/test/java/rlforj/pathfinding/test/MockBoard.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.pathfinding.test; 8 | 9 | import rlforj.IBoard; 10 | 11 | /** 12 | * A simple board for testing LOS, Pathfinding, etc 13 | * 14 | * @author vic 15 | */ 16 | class MockBoard implements IBoard 17 | { 18 | 19 | private final boolean[][] obstacle; 20 | 21 | public MockBoard(final String map) 22 | { 23 | final String[] mapText = map.split("\n"); 24 | obstacle = new boolean[mapText.length][]; 25 | int lineNo = 0; 26 | for (final String line : mapText) 27 | { 28 | final boolean[] lineTiles = new boolean[line.length()]; 29 | for (int i = 0; i < line.length(); i++) 30 | { 31 | lineTiles[i] = line.charAt(i) == '#'; 32 | } 33 | obstacle[lineNo++] = lineTiles; 34 | } 35 | } 36 | 37 | public boolean contains(final int x, final int y) 38 | { 39 | return x >= 0 && x < obstacle[0].length && y >= 0 && y < obstacle.length; 40 | } 41 | 42 | public boolean isObstacle(final int x, final int y) 43 | { 44 | return obstacle[y][x]; 45 | } 46 | 47 | @Override 48 | public boolean blocksLight(final int x, final int y) 49 | { 50 | return isObstacle(x, y); 51 | } 52 | 53 | @Override 54 | public boolean blocksStep(final int x, final int y) 55 | { 56 | return isObstacle(x, y); 57 | } 58 | 59 | public void visit(final int x, final int y) 60 | { 61 | } 62 | 63 | public int getWidth() 64 | { 65 | return obstacle[0].length; 66 | } 67 | 68 | public int getHeight() 69 | { 70 | return obstacle.length; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/rlforj/util/test/MathUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.util.test; 8 | 9 | import junit.framework.TestCase; 10 | import rlforj.util.MathUtils; 11 | 12 | public class MathUtilsTest extends TestCase 13 | { 14 | 15 | public void testISqrt() 16 | { 17 | final int LIM = 1000000; 18 | 19 | final long start = System.currentTimeMillis(); 20 | for (int i = 0; i < LIM; i++) 21 | { 22 | final int j = MathUtils.isqrt(i); 23 | final int k = (int) Math.floor(Math.sqrt(i)); 24 | 25 | assertTrue("Sqrt of " + i + " supposed to be " + k + " but is " + j, j == k); 26 | } 27 | final long end = System.currentTimeMillis(); 28 | 29 | System.out.println("Time taken " + (end - start)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/rlforj/util/test/MockBoard.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.util.test; 8 | 9 | import rlforj.IBoard; 10 | 11 | /** 12 | * A simple board for testing LOS, Pathfinding, etc 13 | * 14 | * @author vic 15 | */ 16 | class MockBoard implements IBoard 17 | { 18 | 19 | private final boolean[][] obstacle; 20 | 21 | public MockBoard(final String map) 22 | { 23 | final String[] mapText = map.split("\n"); 24 | obstacle = new boolean[mapText.length][]; 25 | int lineNo = 0; 26 | for (final String line : mapText) 27 | { 28 | final boolean[] lineTiles = new boolean[line.length()]; 29 | for (int i = 0; i < line.length(); i++) 30 | { 31 | lineTiles[i] = line.charAt(i) == '#'; 32 | } 33 | obstacle[lineNo++] = lineTiles; 34 | } 35 | } 36 | 37 | public boolean contains(final int x, final int y) 38 | { 39 | return x >= 0 && x < obstacle[0].length && y >= 0 && y < obstacle.length; 40 | } 41 | 42 | public boolean isObstacle(final int x, final int y) 43 | { 44 | return obstacle[y][x]; 45 | } 46 | 47 | @Override 48 | public boolean blocksLight(final int x, final int y) 49 | { 50 | return isObstacle(x, y); 51 | } 52 | 53 | @Override 54 | public boolean blocksStep(final int x, final int y) 55 | { 56 | return isObstacle(x, y); 57 | } 58 | 59 | public void visit(final int x, final int y) 60 | { 61 | } 62 | 63 | public int getWidth() 64 | { 65 | return obstacle[0].length; 66 | } 67 | 68 | public int getHeight() 69 | { 70 | return obstacle.length; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/rlforj/util/test/MockBoardTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.util.test; 8 | 9 | import junit.framework.TestCase; 10 | 11 | /** 12 | * Test the MockBoard class 13 | * 14 | * @author vic 15 | */ 16 | public class MockBoardTest extends TestCase 17 | { 18 | 19 | public void testConstructor_empty() 20 | { 21 | final MockBoard board = new MockBoard(" "); 22 | 23 | assertEquals(3, board.getWidth()); 24 | assertEquals(1, board.getHeight()); 25 | assertFalse(board.isObstacle(0, 0)); 26 | assertFalse(board.isObstacle(1, 0)); 27 | assertFalse(board.isObstacle(2, 0)); 28 | } 29 | 30 | public void testConstructor_N() 31 | { 32 | final MockBoard board = new MockBoard("#########\n" + "# #\n" + "####### #\n" + "# #\n" + 33 | "#########"); 34 | 35 | assertEquals(9, board.getWidth()); 36 | assertEquals(5, board.getHeight()); 37 | assertTrue(board.isObstacle(0, 0)); 38 | assertTrue(board.isObstacle(0, 1)); 39 | assertTrue(board.isObstacle(1, 0)); 40 | assertFalse(board.isObstacle(1, 1)); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/rlforj/util/test/SimpleHeapTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Fabio Ticconi, fabio.ticconi@gmail.com 3 | * Copyright (c) 2013, kba 4 | * All rights reserved. 5 | */ 6 | 7 | package rlforj.util.test; 8 | 9 | import org.junit.Test; 10 | import rlforj.util.HeapNode; 11 | import rlforj.util.SimpleHeap; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Collections; 15 | import java.util.Random; 16 | 17 | import static org.junit.Assert.assertEquals; 18 | 19 | /** 20 | * SimpleHeap Test 21 | * 22 | * @author vic 23 | */ 24 | public class SimpleHeapTest 25 | { 26 | // NOTE: JUnit 4 guideline is no longer to use TestCase but use annotations. 27 | // assert* can be static imported from org.junit.Assert. 28 | 29 | @Test 30 | public void testIndex() 31 | { 32 | final int[] arr = { 4, 5, 3, 7, 8, 1, 2, 20, 14, 100, -1 }; 33 | 34 | final SimpleHeap h = new SimpleHeap<>(20); 35 | 36 | for (final int i : arr) 37 | { 38 | h.add(new A(i)); 39 | assertIndexes(h); 40 | } 41 | 42 | while (h.size() != 0) 43 | { 44 | System.out.println(h.poll().a); 45 | assertIndexes(h); 46 | } 47 | 48 | h.clear(); 49 | assertIndexes(h); 50 | 51 | final A a1 = new A(12); 52 | final A a2 = new A(5); 53 | final A a3 = new A(10); 54 | final A a4 = new A(1); 55 | 56 | h.add(a1); 57 | h.add(a2); 58 | h.add(a3); 59 | h.add(a4); 60 | assertIndexes(h); 61 | 62 | a2.a = 1000; 63 | h.adjust(a2); 64 | assertIndexes(h); 65 | 66 | h.poll(); 67 | h.poll(); 68 | h.poll(); 69 | assertEquals(1000, h.poll().a); 70 | 71 | h.add(a1); 72 | h.add(a2); 73 | h.add(a3); 74 | h.add(a4); 75 | assertIndexes(h); 76 | 77 | a2.a = -1000; 78 | h.adjust(a2); 79 | assertIndexes(h); 80 | 81 | assertEquals(-1000, h.poll().a); 82 | } 83 | 84 | /** 85 | * Test that SimleHeap behaves like a heap. The top of the heap is always 86 | * the same as the first element in a sorted list. 87 | */ 88 | @Test 89 | public void testHeapFunctionality() throws Exception 90 | { 91 | final Random rand = new Random(); 92 | final SimpleHeap h = new SimpleHeap<>(50); 93 | final ArrayList arr = new ArrayList<>(1000); 94 | for (int i = 0; i < 1000; i++) 95 | { 96 | final A a = new A(rand.nextInt()); 97 | h.add(a); 98 | arr.add(a); 99 | } 100 | 101 | Collections.sort(arr); 102 | 103 | for (int i = 0; i < 1000; i++) 104 | { 105 | final A a1 = h.poll(); 106 | final A a2 = arr.remove(0); 107 | assertEquals("SimpleHeap does not match Array at " + i, a1.a, a2.a); 108 | 109 | if (rand.nextInt(100) < 30) 110 | { 111 | // Make sure SimpleHeap works in the face of random insertions. 112 | final A a = new A(rand.nextInt()); 113 | h.add(a); 114 | arr.add(a); 115 | 116 | Collections.sort(arr); 117 | } 118 | 119 | } 120 | } 121 | 122 | /** 123 | * Test that heap properties are maintained in face of property changes and 124 | * adjustments. 125 | */ 126 | @Test 127 | public void testHeapAdjust() throws Exception 128 | { 129 | final Random rand = new Random(); 130 | final SimpleHeap h = new SimpleHeap<>(50); 131 | final ArrayList arr = new ArrayList<>(1000); 132 | for (int i = 0; i < 1000; i++) 133 | { 134 | final A a = new A(rand.nextInt()); 135 | h.add(a); 136 | arr.add(a); 137 | } 138 | 139 | Collections.sort(arr); 140 | 141 | for (int i = 0; i < 2000; i++) 142 | { 143 | final A a1 = h.poll(); 144 | final A a2 = arr.remove(0); 145 | 146 | assertEquals("SimpleHeap does not match Array at " + i, a1.a, a2.a); 147 | 148 | if (h.size() == 0) 149 | { 150 | break; 151 | } 152 | 153 | if (rand.nextInt(100) < 70) 154 | { 155 | // Make sure SimpleHeap works in the face of random adjusts. 156 | final int idx = rand.nextInt(h.size()); 157 | final A a = h.getElementAt(idx); 158 | a.a = rand.nextInt(); 159 | 160 | h.adjust(a); 161 | Collections.sort(arr); 162 | } 163 | 164 | } 165 | } 166 | 167 | private void assertIndexes(final SimpleHeap h) 168 | { 169 | for (int i = 0; i < h.size(); i++) 170 | { 171 | assertEquals(i, (h.getElementAt(i)).idx); 172 | } 173 | } 174 | 175 | private static class A implements HeapNode 176 | { 177 | int a; 178 | 179 | int idx; 180 | 181 | public A(final int i) 182 | { 183 | a = i; 184 | } 185 | 186 | public int compareTo(final Object o) 187 | { 188 | if (this == o) 189 | return 0; 190 | final A a2 = (A) o; 191 | 192 | return Integer.compare(a, a2.a); 193 | } 194 | 195 | public int getHeapIndex() 196 | { 197 | return idx; 198 | } 199 | 200 | public void setHeapIndex(final int heapIndex) 201 | { 202 | idx = heapIndex; 203 | } 204 | } 205 | 206 | } 207 | --------------------------------------------------------------------------------