package org.artisanlogiciel.games.maze.gui; import org.artisanlogiciel.games.maze.*; 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 JComponent implements MazeCreationListener, MazeResolutionListener, MouseMotionListener, Scrollable { private static final long serialVersionUID = 3163272907991176390L; // WallsProvider map; LabyModel map; final MazeCellParameters cp; 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 class MazeColorMap { public Color background; public Color wall; public Color path; public Color resolved_path; public Color goal; public MazeColorMap(Color background, Color wall, Color path, Color resolved_path, Color goal) { this.background = background; this.wall = wall; this.path = path; this.resolved_path = resolved_path; this.goal = goal; } } 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, MazeCellParameters cp, StatusListener statusListener) { super(); this.cp = cp; this.map = map; setPreferredSize(cp.getDimension()); gX = map.getWidth() - 1; gY = map.getHeight() - 1; this.statusListener = statusListener; addMouseMotionListener(this); } // 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.getWidth(), map.getHeight()); 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 !"); } } */ } @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(); int mX = (int) ((double) r.getWidth() / cp.getWidth()); int mY = (int) ((double) r.getHeight() / cp.getHeight()); int pX = (int) ((double) (r.getX() - cp.getOffsetX()) / cp.getWidth()); int pY = (int) ((double) (r.getY() - cp.getOffsetY()) / cp.getHeight()); if (pX < 0) pX = 0; if (pY < 0) pY = 0; if (pX >= map.getWidth()) return; if (pY >= map.getHeight()) return; mX = mX + pX; mY = mY + pY; if (mX > map.getWidth()) { mX = map.getWidth(); } if (mY > map.getHeight()) { mY = map.getHeight(); } int aX = pX; int aY = pY; // draw background for (; pY < mY; pY++) { for (pX = 0; pX < mX; pX++) { walls = map.getWalls(pX, pY); if ( xpm != null ) { int cX = ( pX < xpm.getWidth()) ? pX : xpm.getWidth() - 1; int cY = ( pY < xpm.getHeight()) ? pY : xpm.getHeight() - 1; g.setColor(xpm.getColor(cX,cY)); } else { g.setColor(colorMap.background); } cp.drawBackground(g, pX, pY, walls); } } if ((sX == gX) && (sY == gY)) { g.setColor(colorMap.goal); } else { g.setColor(colorMap.resolved_path); cp.drawDot(g, new Position(gX, gY), pX, pY, mX, mY); g.setColor(colorMap.path); } cp.drawDot(g, new Position(sX, sY), pX, pY, mX, mY); pX = aX; pY = aY; // draw all walls within clip bounds horiz first then lines g.setColor(colorMap.wall); for (; pY < mY; pY++) { for (pX = 0; pX < mX; pX++) { walls = map.getWalls(pX, pY); cp.drawWalls(g, pX, pY, walls); } } pX = aX; pY = aY; synchronized (lockChange) { g.setColor(colorMap.goal); if (current != null) { cp.drawDot(g, current, pX, pY, mX, mY); } if (solvedPath != null) { for (DirectionPosition resolved : solvedPath) { // cp.drawDot(g, resolved.getPosition(), pX, pY, mX, mY); cp.drawPath(g, resolved, pX, pY, mX, mY); } } } if (this.showAll) { statusListener.addStatus("*"); pX = aX; pY = aY; // draw all path within clip bounds horiz first then lines for (; pY < mY; pY++) { for (pX = 0; pX < mX; pX++) { path = map.getPath(pX, pY); if ((path & LabyModel.SOLVED) == LabyModel.SOLVED) { g.setColor(colorMap.resolved_path); } else { g.setColor(colorMap.path); } if (path != 0) { DirectionPosition dp = new DirectionPosition(path, new Position(pX, pY)); cp.drawPath(g, dp, pX, pY, pX + 1, pY + 1); } } } } } public boolean notifySearch(DirectionPosition pPosition) { synchronized (lockChange) { current = pPosition.getPosition(); } // should redraw ... invalidate(); repaint(); return true; } public void notifySearchError(String error) { System.err.println(error); } 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; } }