001/*
002 * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
003 * Copyright (C) 2000-2010 The Gridarta Developers.
004 *
005 * This program is free software; you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation; either version 2 of the License, or
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License along
016 * with this program; if not, write to the Free Software Foundation, Inc.,
017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
018 */
019
020package net.sf.gridarta.model.mapcursor;
021
022import java.awt.Dimension;
023import java.awt.Point;
024import junit.framework.JUnit4TestAdapter;
025import net.sf.gridarta.model.archetype.TestArchetype;
026import net.sf.gridarta.model.direction.Direction;
027import net.sf.gridarta.model.gameobject.TestGameObject;
028import net.sf.gridarta.model.maparchobject.TestMapArchObject;
029import net.sf.gridarta.model.mapgrid.MapGrid;
030import net.sf.gridarta.model.mapgrid.SelectionMode;
031import net.sf.gridarta.model.mapmodel.MapModel;
032import net.sf.gridarta.model.mapmodel.MapSquare;
033import net.sf.gridarta.model.mapmodel.TestMapModelCreator;
034import net.sf.gridarta.utils.Size2D;
035import org.jetbrains.annotations.NotNull;
036import org.jetbrains.annotations.Nullable;
037import org.junit.Assert;
038import org.junit.Test;
039
040/**
041 * Unit test for {@link MapCursor}.
042 * @author <a href="mailto:cher@riedquat.de">Christian Hujer</a>
043 * @author <a href="mailto:dlviegas@gmail.com">Daniel Viegas</a>
044 * @author Andreas Kirschbaum
045 */
046public class MapCursorTest {
047
048    /**
049     * The enter x coordinate of newly created maps.
050     */
051    private static final int ENTER_X = 1;
052
053    /**
054     * The enter y coordinate of newly created maps.
055     */
056    private static final int ENTER_Y = 2;
057
058    /**
059     * The size of the map grid for the tested cursor.
060     */
061    @NotNull
062    private static final Size2D gridSize = new Size2D(6, 7);
063
064    /**
065     * A {@link MapCursorListener} that counts the number of callbacks.
066     */
067    @NotNull
068    private static final TestMapCursorListener listener = new TestMapCursorListener();
069
070    /**
071     * Creates a new {@link MapCursor} instance. No more than one instance may
072     * exist concurrently.
073     * @param grid the underlying map grid
074     * @return the new map cursor instance
075     */
076    @NotNull
077    private static MapCursor<TestGameObject, TestMapArchObject, TestArchetype> createCursor(@NotNull final MapGrid grid) {
078        final TestMapModelCreator mapModelCreator = new TestMapModelCreator(false);
079        final Size2D gridSize = grid.getSize();
080        final MapModel<TestGameObject, TestMapArchObject, TestArchetype> mapModel = mapModelCreator.newMapModel(gridSize.getWidth(), gridSize.getHeight());
081        mapModel.getMapArchObject().setEnterX(ENTER_X);
082        mapModel.getMapArchObject().setEnterY(ENTER_Y);
083        final MapCursor<TestGameObject, TestMapArchObject, TestArchetype> cursor = new MapCursor<TestGameObject, TestMapArchObject, TestArchetype>(grid, mapModel);
084        cursor.addMapCursorListener(listener);
085        listener.changedPosCounter = 0;
086        listener.changedModeCounter = 0;
087        return cursor;
088    }
089
090    /**
091     * Checks that settings the cursor outside of the grid behaves as expected.
092     */
093    @Test
094    public void setOutside() {
095        final MapGrid grid = new MapGrid(gridSize);
096        final MapCursor<TestGameObject, TestMapArchObject, TestArchetype> cursor = createCursor(grid);
097        final Point p = new Point();
098        final int width = gridSize.getWidth();
099        final int height = gridSize.getHeight();
100        for (int i = -2; i < width + 2; i++) {
101            p.setLocation(i, -1);
102            cursor.setLocation(p);
103            testEvents(1, 0);
104            Assert.assertEquals(new Point(Math.max(0, Math.min(width - 1, i)), 0), cursor.getLocation());
105            testEvents(0, 0);
106            p.setLocation(i, height);
107            cursor.setLocation(p);
108            testEvents(1, 0);
109            Assert.assertEquals(new Point(Math.max(0, Math.min(width - 1, i)), height - 1), cursor.getLocation());
110            testEvents(0, 0);
111        }
112        for (int i = -2; i < height + 2; i++) {
113            p.setLocation(-1, i);
114            cursor.setLocation(p);
115            testEvents(1, 0);
116            Assert.assertEquals(new Point(0, Math.max(0, Math.min(height - 1, i))), cursor.getLocation());
117            testEvents(0, 0);
118            p.setLocation(width, i);
119            cursor.setLocation(p);
120            testEvents(1, 0);
121            Assert.assertEquals(new Point(width - 1, Math.max(0, Math.min(height - 1, i))), cursor.getLocation());
122            testEvents(0, 0);
123        }
124    }
125
126    /**
127     * Checks that settings the cursor within the map grid behaves as expected.
128     */
129    @Test
130    public void setInside() {
131        final MapGrid grid = new MapGrid(gridSize);
132        final MapCursor<TestGameObject, TestMapArchObject, TestArchetype> cursor = createCursor(grid);
133        final Point p = new Point();
134        for (int j = 0; j < gridSize.getHeight(); j++) {
135            for (int i = 0; i < gridSize.getWidth(); i++) {
136                p.setLocation(i, j);
137                cursor.setLocation(p);
138                testEvents(1, 0);
139                final Point res = cursor.getLocation();
140                testEvents(0, 0);
141                Assert.assertEquals("getLocation()", p, res);
142            }
143        }
144    }
145
146    /**
147     * Checks that settings the cursor to the same location does not generate
148     * excess events.
149     */
150    @Test
151    public void setSameLocation() {
152        final MapGrid grid = new MapGrid(gridSize);
153        final MapCursor<TestGameObject, TestMapArchObject, TestArchetype> cursor = createCursor(grid);
154        cursor.setLocation(new Point(3, 4));
155        testEvents(1, 0);
156        cursor.setLocation(new Point(3, 4));
157        testEvents(0, 0);
158        cursor.setLocation(new Point(1, 0));
159        testEvents(1, 0);
160        cursor.setLocation(new Point(1, -1));
161        testEvents(0, 0);
162        cursor.setLocation(new Point(-3, -2));
163        testEvents(1, 0);
164        cursor.setLocation(new Point(-2, -1));
165        testEvents(0, 0);
166        cursor.setLocation(new Point(0, 0));
167        testEvents(0, 0);
168    }
169
170    /**
171     * Checks {@link MapCursor#setLocationSafe(Point)}.
172     */
173    @Test
174    public void setLocationSafe() {
175        final MapGrid grid = new MapGrid(gridSize);
176        final MapCursor<TestGameObject, TestMapArchObject, TestArchetype> cursor = createCursor(grid);
177        final Point p = new Point(-1, -1);
178        Assert.assertFalse("setLocationSafe(null) should return false", cursor.setLocationSafe(null));
179        testEvents(0, 0);
180        Assert.assertFalse("setLocationSafe(" + p + ") should return false", cursor.setLocationSafe(p));
181        testEvents(0, 0);
182        p.setLocation(3, 4);
183        Assert.assertTrue("setLocationSafe(" + p + ") should return true", cursor.setLocationSafe(p));
184        testEvents(1, 0);
185        Assert.assertFalse("setLocationSafe(" + p + ") should return false", cursor.setLocationSafe(p));
186        testEvents(0, 0);
187        p.setLocation(-1, -1);
188        Assert.assertFalse("setLocationSafe(" + p + ") should return false", cursor.setLocationSafe(p));
189        testEvents(0, 0);
190        Assert.assertFalse("setLocationSafe(null) should return false", cursor.setLocationSafe(null));
191        testEvents(0, 0);
192    }
193
194    /**
195     * Checks {@link MapCursor#isOnGrid(Point)}.
196     */
197    @Test
198    public void testIsOnGrid() {
199        final MapGrid grid = new MapGrid(gridSize);
200        final MapCursor<TestGameObject, TestMapArchObject, TestArchetype> cursor = createCursor(grid);
201        final Point p = new Point();
202        for (int j = -2; j < gridSize.getHeight() + 2; j++) {
203            for (int i = -2; i < gridSize.getWidth() + 2; i++) {
204                p.setLocation(i, j);
205                if (i >= 0 && i < gridSize.getWidth() && j >= 0 && j < gridSize.getHeight()) {
206                    Assert.assertTrue(p + " should be on the grid.", cursor.isOnGrid(p));
207                } else {
208                    Assert.assertFalse(p + " should not be on the grid.", cursor.isOnGrid(p));
209                }
210            }
211        }
212        Assert.assertFalse("Null should not be on the grid.", cursor.isOnGrid(null));
213    }
214
215    /**
216     * Checks {@link MapCursor#goTo(boolean, Direction)}.
217     */
218    @Test
219    public void testGoTo() {
220        final MapGrid grid = new MapGrid(gridSize);
221        final MapCursor<TestGameObject, TestMapArchObject, TestArchetype> cursor = createCursor(grid);
222        final Point pStart = new Point(2, 3);
223        final Point p = new Point(pStart);
224        cursor.setLocation(p);
225        testEvents(1, 0);
226        for (final Direction dir : Direction.values()) {
227            Assert.assertTrue("go(" + dir + ") should return true. (Maybe the grid was too small.)", cursor.goTo(true, dir));
228            testEvents(1, 0);
229            p.x += dir.getDx();
230            p.y += dir.getDy();
231            Assert.assertEquals("Moving cursor.", p, cursor.getLocation());
232        }
233        Assert.assertEquals("Moving in a circle.", pStart, cursor.getLocation());
234    }
235
236    /**
237     * Checks the dragging related functions of {@link MapCursor}.
238     */
239    @Test
240    public void dragging() {
241        final MapGrid grid = new MapGrid(gridSize);
242        final MapCursor<TestGameObject, TestMapArchObject, TestArchetype> cursor = createCursor(grid);
243        Assert.assertFalse("MapCursor should not drag while deactivated.", cursor.isDragging());
244        cursor.dragStart();
245        testEvents(0, 1);
246        Assert.assertEquals(new Dimension(0, 0), cursor.getDragOffset());
247        final Point dragStart = new Point(3, 4);
248        final Point p = new Point(dragStart);
249        final Dimension offset = new Dimension(0, 0);
250        cursor.setLocation(dragStart);
251        testEvents(1, 0);
252        cursor.dragStart();
253        testEvents(0, 0);
254        Assert.assertTrue("MapCursor should be in drag mode.", cursor.isDragging());
255        Assert.assertEquals("Wrong offset", offset, cursor.getDragOffset());
256        cursor.deactivate();
257        testEvents(0, 1);
258        Assert.assertFalse("MapCursor should not drag while deactivated.", cursor.isDragging());
259        Assert.assertNull("Drag offset should be null", cursor.getDragOffset());
260        cursor.setLocation(dragStart);
261        testEvents(0, 0);
262        cursor.dragStart();
263        testEvents(0, 1);
264        Assert.assertEquals("Wrong offset", offset, cursor.getDragOffset());
265        dragTo(cursor, grid, Direction.WEST, dragStart, p, offset);
266        dragTo(cursor, grid, Direction.EAST, dragStart, p, offset);
267        dragTo(cursor, grid, Direction.EAST, dragStart, p, offset);
268        dragTo(cursor, grid, Direction.NORTH_WEST, dragStart, p, offset);
269        dragTo(cursor, grid, Direction.SOUTH, dragStart, p, offset);
270        dragTo(cursor, grid, Direction.SOUTH, dragStart, p, offset);
271        dragTo(cursor, grid, Direction.WEST, dragStart, p, offset);
272        dragTo(cursor, grid, Direction.NORTH_EAST, dragStart, p, offset);
273    }
274
275    /**
276     * Calls {@link MapCursor#dragTo(Point)} and checks for expected results.
277     * @param cursor the map cursor to affect
278     * @param grid the associated map grid
279     * @param dir the direction to drag
280     * @param start the starting location
281     * @param p the destination location
282     * @param offset the expected dragging offset
283     */
284    private static void dragTo(@NotNull final MapCursor<TestGameObject, TestMapArchObject, TestArchetype> cursor, @NotNull final MapGrid grid, @NotNull final Direction dir, @NotNull final Point start, @NotNull final Point p, @NotNull final Dimension offset) {
285        final Point d = new Point(dir.getDx(), dir.getDy());
286        p.x += d.x;
287        p.y += d.y;
288        Assert.assertTrue("dragTo(" + p + ")", cursor.dragTo(p));
289        testEvents(1, 0);
290        Assert.assertTrue("MapCursor should be in drag mode.", cursor.isDragging());
291        Assert.assertEquals("Wrong position", p, cursor.getLocation());
292        offset.width = p.x - start.x;
293        offset.height = p.y - start.y;
294        Assert.assertEquals("Wrong offset", offset, cursor.getDragOffset());
295        assertPreSelection(grid, start, p);
296    }
297
298    /**
299     * Checks that a {@link MapGrid} includes a rectangle of {@link
300     * MapGrid#GRID_FLAG_SELECTING}.
301     * @param grid the map grid to check
302     * @param start one corner of the rectangle
303     * @param end the diagonally opposite corner of the rectangle
304     */
305    private static void assertPreSelection(@NotNull final MapGrid grid, @NotNull final Point start, @NotNull final Point end) {
306        final int minX = Math.min(start.x, end.x);
307        final int maxX = Math.max(start.x, end.x);
308        final int minY = Math.min(start.y, end.y);
309        final int maxY = Math.max(start.y, end.y);
310        final int height = gridSize.getHeight();
311        final int width = gridSize.getWidth();
312        for (int j = 0; j < height; j++) {
313            for (int i = 0; i < width; i++) {
314                if (i < minX || i > maxX || j < minY || j > maxY) {
315                    //Not preselected
316                    Assert.assertFalse("Pre-selection", (grid.getFlags(i, j) & MapGrid.GRID_FLAG_SELECTING) > 0);
317                } else {
318                    //Preselected
319                    Assert.assertTrue("Pre-selection", (grid.getFlags(i, j) & MapGrid.GRID_FLAG_SELECTING) > 0);
320                }
321            }
322
323        }
324    }
325
326    /**
327     * Checks that a {@link MapGrid} includes a rectangle of {@link
328     * MapGrid#GRID_FLAG_SELECTION}. Squares outside the rectangle are not
329     * checked.
330     * @param grid the map grid to check
331     * @param start one corner of the rectangle
332     * @param end the diagonally opposite corner of the rectangle
333     * @param flag the expected selection state
334     */
335    private static void assertSelection(@NotNull final MapGrid grid, @NotNull final Point start, @NotNull final Point end, final boolean flag) {
336        final int minX = Math.min(start.x, end.x);
337        final int maxX = Math.max(start.x, end.x);
338        final int minY = Math.min(start.y, end.y);
339        final int maxY = Math.max(start.y, end.y);
340        for (int j = minY; j <= maxY; j++) {
341            for (int i = minX; i <= maxX; i++) {
342                Assert.assertSame("Selection", flag, (grid.getFlags(i, j) & MapGrid.GRID_FLAG_SELECTION) > 0);
343            }
344        }
345    }
346
347    /**
348     * Checks for correct behavior of {@link MapGrid#GRID_FLAG_SELECTING} flags
349     * during selecting.
350     */
351    @Test
352    public void selecting() {
353        final MapGrid grid = new MapGrid(gridSize);
354        final MapCursor<TestGameObject, TestMapArchObject, TestArchetype> cursor = createCursor(grid);
355        final Point start = new Point(2, 3);
356        final Point end = new Point(4, 5);
357        final Point gridMaxIndex = new Point(gridSize.getWidth() - 1, gridSize.getHeight() - 1);
358        cursor.dragRelease();
359        testEvents(0, 0);
360        cursor.setLocation(start);
361        testEvents(1, 0);
362        cursor.dragStart();
363        testEvents(0, 1);
364        cursor.dragTo(end);
365        testEvents(1, 0);
366        Assert.assertTrue("MapCursor should be in drag mode.", cursor.isDragging());
367        cursor.dragRelease();
368        testEvents(0, 1);
369        Assert.assertFalse("MapCursor should not be in drag mode.", cursor.isDragging());
370        cursor.setLocation(start);
371        testEvents(1, 0);
372        cursor.dragStart();
373        testEvents(0, 1);
374        cursor.dragTo(end);
375        testEvents(1, 0);
376        Assert.assertTrue("MapCursor should be in drag mode.", cursor.isDragging());
377        cursor.dragRelease();
378        testEvents(0, 1);
379        Assert.assertFalse("MapCursor should not be in drag mode.", cursor.isDragging());
380        cursor.setLocation(start);
381        testEvents(1, 0);
382        cursor.dragStart();
383        testEvents(0, 1);
384        cursor.dragTo(end);
385        testEvents(1, 0);
386        Assert.assertTrue("MapCursor should be in drag mode.", cursor.isDragging());
387        assertSelection(grid, new Point(0, 0), gridMaxIndex, false);
388        cursor.dragSelect(SelectionMode.ADD);
389        testEvents(0, 1);
390        assertSelection(grid, start, end, true);
391        //Check if nothing is preselected
392        assertPreSelection(grid, new Point(-1, -1), new Point(-1, -1));
393        cursor.setLocation(start);
394        testEvents(1, 0);
395        cursor.dragStart();
396        testEvents(0, 1);
397        cursor.dragTo(end);
398        testEvents(1, 0);
399        cursor.dragSelect(SelectionMode.SUB);
400        testEvents(0, 1);
401        assertSelection(grid, new Point(0, 0), gridMaxIndex, false);
402        cursor.setLocation(start);
403        testEvents(1, 0);
404        cursor.dragStart();
405        testEvents(0, 1);
406        cursor.dragTo(end);
407        testEvents(1, 0);
408        cursor.dragSelect(SelectionMode.FLIP);
409        testEvents(0, 1);
410        assertSelection(grid, start, end, true);
411        start.setLocation(3, 4);
412        end.setLocation(5, 1);
413        cursor.setLocation(start);
414        testEvents(1, 0);
415        cursor.dragStart();
416        testEvents(0, 1);
417        cursor.dragTo(end);
418        testEvents(1, 0);
419        cursor.dragSelect(SelectionMode.FLIP);
420        testEvents(0, 1);
421        assertSelection(grid, start, new Point(4, 4), false);
422        assertSelection(grid, new Point(3, 2), end, true);
423        assertSelection(grid, new Point(5, 3), new Point(5, 4), true);
424        cursor.deactivate();
425        testEvents(0, 0);
426        assertSelection(grid, new Point(0, 0), gridMaxIndex, false);
427    }
428
429    /**
430     * Checks if the number of events fired is correct.
431     * @param nPos the expected number of position events
432     * @param nMode the expected number of mode events
433     */
434    private static void testEvents(final int nPos, final int nMode) {
435        Assert.assertEquals("Position change event", nPos, listener.changedPosCounter);
436        listener.changedPosCounter = 0;
437        Assert.assertEquals("Mode change event", nMode, listener.changedModeCounter);
438        listener.changedModeCounter = 0;
439    }
440
441    /**
442     * Returns a new test suite containing this test.
443     * @return the new test suite
444     */
445    @NotNull
446    public static junit.framework.Test suite() {
447        return new JUnit4TestAdapter(MapCursorTest.class);
448    }
449
450    /**
451     * A {@link MapCursorListener} that counts the number of event callbacks.
452     */
453    private static class TestMapCursorListener implements MapCursorListener<TestGameObject, TestMapArchObject, TestArchetype> {
454
455        /**
456         * The number of calls to {@link #mapCursorChangedPos(Point)}.
457         */
458        private int changedPosCounter;
459
460        /**
461         * The number of calls to {@link #mapCursorChangedMode()}.
462         */
463        private int changedModeCounter;
464
465        @Override
466        public void mapCursorChangedPos(@NotNull final Point location) {
467            changedPosCounter++;
468        }
469
470        @Override
471        public void mapCursorChangedMode() {
472            changedModeCounter++;
473        }
474
475        @Override
476        public void mapCursorChangedGameObject(@Nullable final MapSquare<TestGameObject, TestMapArchObject, TestArchetype> mapSquare, @Nullable final TestGameObject gameObject) {
477            // ignore
478        }
479
480        @Override
481        public void mapCursorChangedSize() {
482            // ignore
483        }
484
485    }
486
487}