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 super T> key = (Comparable super T>) 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 super T> key = (Comparable super T>) 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 |
--------------------------------------------------------------------------------