package org.artisanlogiciel.games.maze.gui; import org.artisanlogiciel.games.maze.*; import org.artisanlogiciel.games.maze.model.LabyModelProvider; import org.artisanlogiciel.games.maze.model.WidthHeightProvider; import org.artisanlogiciel.games.maze.solve.DirectionPosition; import org.artisanlogiciel.games.maze.solve.MazeResolutionListener; import org.artisanlogiciel.graphics.Drawing; import org.artisanlogiciel.graphics.DrawingLine; import org.artisanlogiciel.xpm.Xpm; import javax.swing.*; import java.awt.*; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.util.ArrayList; import java.util.Date; import java.util.LinkedList; public class MazeComponent extends CellGridComponent implements MazeCreationListener, MazeResolutionListener, MouseMotionListener, Scrollable { private static final long serialVersionUID = 3163272907991176390L; private boolean viewBackground = true; LabyModelProvider map; Position current = null; LinkedList solvedPath = null; LinkedList drawingPath = null; final Object lockChange = new Object(); // current postion of human resolving int sX = 0; int sY = 0; // goal exit int gX = -1; int gY = -1; // use a background Xpm xpm = null; Date lastDrag = null; // delay after which a draging to draw a continuous line is ended, a new line will then start. long dragTimeout = MazeDefault.dragTimeout; // not set by default for debug, will show any path boolean showAll = false; StatusListener statusListener; MazeColorMap colorMap = new MazeColorMap(Color.white, Color.black, Color.blue, Color.green, Color.red); public void setXpm(Xpm xpm) { this.xpm = xpm; } // for a given (x,y) pixel return cell position. Position getPosition(int x, int y) { // if rightmost position of this view not (0,0) this is not working. // that's why MousListener should be attache to this MazeComponent and not to Scrollable one... return cp.toMazeCoordinates(x,y); } @Override public void mouseDragged(MouseEvent e) { boolean add = ((e.getModifiersEx() & InputEvent.BUTTON1_DOWN_MASK) != 0); // where pixel -> maze coordinate is done Position newPosition = getPosition(e.getX(), e.getY()); Date now = new Date(System.currentTimeMillis()); if (lastDrag == null) { resetDrawingPath(); } else { if (now.getTime() - lastDrag.getTime() > dragTimeout) { resetDrawingPath(); statusListener.addStatus("move timeout"); } } lastDrag = now; addPosition(newPosition, add); changed(null, null, map); } public void resetDrawingPath() { drawingPath = null; } public void addDrawing(Drawing drawing, boolean add) { for (DrawingLine line : drawing.getInternLines()) { addDrawingLine(line, add); } changed(null, null, map); } public void addDrawingLine(DrawingLine line, boolean add) { resetDrawingPath(); ArrayList points = line.getPoints(); for (Point p : points) { addPosition(new Position(p.x, p.y), add); } } // button 1 : add direction; button 2 : replace with direction. // Position is Already in maze coordinates (ie not pixels) public void addPosition(Position newPosition, boolean add) { // should find the cell ... DirectionPosition last = null; short path = 0; if (drawingPath == null) { drawingPath = new LinkedList<>(); last = new DirectionPosition((short) 0, newPosition); statusListener.addStatus("Mouse dragged Cell " + newPosition + " Button " + add); drawingPath.addLast(last); } else { // setShowAll(true); DirectionPosition first = drawingPath.getLast(); last = first; // construct as many move form last to new position as needed. while (!last.getPosition().equals(newPosition)) { path = LabyModel.getDirection(last.getPosition(), newPosition); last.setDirection(path); if (add) { map.addDirection(last.getPosition().getX(), last.getPosition().getY(), path); } else { map.setDirection(last.getPosition().getX(), last.getPosition().getY(), path); } last = last.moveToAdjacentDirection(); drawingPath.addLast(last); } statusListener.addStatus("Mouse dragged from Cell " + first.getPosition() + "To" + newPosition + " Button " + add); } } @Override public void mouseMoved(MouseEvent e) { // addStatus("Mouse moved (" + e.getX() + ',' + e.getY() + ')'); Position newPosition = getPosition(e.getX(), e.getY()); if ((newPosition.getX() >= 0) && (newPosition.getY() >= 0)) { requestFocus(); } } public void setShowAll(boolean showall) { this.showAll = showall; statusListener.addStatus(showAll ? "show all" : "X"); } void checkExit() { if ((sX == gX) && (sY == gY)) { statusListener.addStatus("Exit found by human !"); } } private void proceed() { // should redraw ... invalidate(); repaint(); checkExit(); } void goNorth() { resetResolution(); if (map.canMoveInDirection(sX, sY, Brick.UP)) { sY = sY - 1; proceed(); } } void goSouth() { resetResolution(); if (map.canMoveInDirection(sX, sY, Brick.DOWN)) { sY = sY + 1; proceed(); } } void goEast() { resetResolution(); if (map.canMoveInDirection(sX, sY, Brick.RIGHT)) { sX = sX + 1; proceed(); } } void goWest() { resetResolution(); if (map.canMoveInDirection(sX, sY, Brick.LEFT)) { sX = sX - 1; proceed(); } } /** * width, height of one cell, * offsetX, offsetY of upper left corner **/ // public MazeComponent(WallsProvider map, MazeCellParameters cp) public MazeComponent(LabyModel map, WidthHeightProvider frame, StatusListener statusListener) { super(); this.map = map; gX = map.getWidth() - 1; gY = map.getHeight() - 1; resetCellRenderer(false,frame); this.statusListener = statusListener; addMouseMotionListener(this); } public void resetCellRenderer(boolean hexagon, WidthHeightProvider frame) { cp = createCellRenderer(hexagon,map,frame); setPreferredSize(cp.getDimension()); } // public void resetWallsProvider(WallsProvider map) public void resetWallsProvider(LabyModel map) { this.map = map; solvedPath = null; // could be kept drawingPath = null; sX = 0; sY = 0; cp.resetMazeWidthHeight(map); setPreferredSize(cp.getDimension()); } public void setWallSize(int size) { cp.setCellSize((double) size, (double) size); // should redraw ... invalidate(); repaint(); } public int getAutoSize() { Rectangle r = getBounds(); // cp.resetMazeWidthHeight( (int)r.getWidth(), (int) r.getHeight()); cp.adaptTo(r.getWidth(), r.getHeight()); // should redraw ... invalidate(); repaint(); return 50; } public void changed(Position cl, Position cr, WallsProvider map) { // should redraw ... invalidate(); repaint(); /* what was this ? Object waiter = new Object(); synchronized (waiter) { try { waiter.wait(10); } catch (InterruptedException e) { System.err.println("Interrupted !"); } } */ } public void setViewBackground(boolean viewBackground) { this.viewBackground = viewBackground; } private boolean hasBackground() { return viewBackground && (xpm != null); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); int x = 0; int y = 0; short walls = 0; short path = 0; // try to display only visible part... // should compute pX, pY, mX, mY based on clip... // Shape s=g.getClip(); Rectangle r = g.getClipBounds(); // visible part of grid : // (min)------------- // | | // _______________(max) Position min = new Position( (int) ((double) (r.getX() - cp.getOffsetX()) / cp.getWidth()), (int) ((double) (r.getY() - cp.getOffsetY()) / cp.getHeight()) ); min.limitToMin(0,0); if ( (min.getX() >= map.getWidth()) || (min.getY() >= map.getHeight()) ) return; Position extent = new Position( (int) ((double) r.getWidth() / cp.getWidth()), (int) ((double) r.getHeight() / cp.getHeight()) ); Position max = min.translate(extent); max.limitToMax(map.getWidth(), map.getHeight()); int mX = max.getX(); int mY = max.getY(); // draw background XYGridIterator iterator = new XYGridIterator(min,max); Position cell = null; while ( ( cell = iterator.next()) != null ) { walls = map.getWalls(cell); if ( hasBackground() ) { Position colored = new Position(cell); colored.limitToMax(xpm.getWidth() - 1, xpm.getHeight() -1 ); g.setColor(xpm.getColor(colored.getX(), colored.getY())); } else { g.setColor(colorMap.background); } cp.drawBackground(g, cell, walls); } if ((sX == gX) && (sY == gY)) { g.setColor(colorMap.goal); } else { g.setColor(colorMap.resolved_path); cp.drawDot(g, new Position(gX, gY), min, max); g.setColor(colorMap.path); } cp.drawDot(g, new Position(sX, sY), min, max); // draw all walls within clip bounds horiz first then lines g.setColor(colorMap.wall); iterator.reset(); cell = null; while ( ( cell = iterator.next()) != null ) { cp.drawWalls(g, cell, map.getWalls(cell)); } if (this.showAll) { statusListener.addStatus("*"); // draw all path within clip bounds horiz first then lines iterator.reset(); cell = null; while ( ( cell = iterator.next()) != null ) { path = map.getPath(cell.getX(), cell.getY()); g.setColor(colorMap.path); if (path != 0) { DirectionPosition dp = new DirectionPosition(path, new Position(cell)); cp.drawPath(g, dp, cell.getX(), cell.getY(), cell.getX() + 1, cell.getY() + 1); } } } synchronized (lockChange) { g.setColor(colorMap.goal); if (current != null) { cp.drawDot(g, current, min, max); } g.setColor(colorMap.resolved_path); if (solvedPath != null) { for (DirectionPosition resolved : solvedPath) { cp.drawDot(g, resolved.getPosition(), min, max); cp.drawPath(g, resolved, min.getX(), min.getY(), mX, mY); } } } } public boolean notifySearch(DirectionPosition pPosition) { synchronized (lockChange) { current = pPosition.getPosition(); } // should redraw ... invalidate(); repaint(); return true; } public void notifySearchError(String error) { notifySearchInfoLevel(0,error); } public void notifySearchInfoLevel(int infoLevel, String pInfo) { statusListener.addStatus(pInfo); } public void notifyCompletion(LinkedList solvedPath) { LinkedList newPath = new LinkedList<>(solvedPath); statusListener.addStatus("resolution completed"); synchronized (lockChange) { this.solvedPath = newPath; } // should redraw ... invalidate(); repaint(); } public void resetResolution() { solvedPath = null; } @Override public Dimension getPreferredScrollableViewportSize() { return cp.getDimension(); } @Override public int getScrollableUnitIncrement(Rectangle rectangle, int i, int i1) { return ( i == SwingConstants.VERTICAL ) ? (int) cp.getHeight() : (int) cp.getWidth(); } @Override public int getScrollableBlockIncrement(Rectangle rectangle, int i, int i1) { return ( i == SwingConstants.VERTICAL ) ? (int) cp.getHeight() : (int) cp.getWidth(); } @Override public boolean getScrollableTracksViewportWidth() { return false; } @Override public boolean getScrollableTracksViewportHeight() { return false; } }