416 lines
13 KiB
Java
416 lines
13 KiB
Java
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<DirectionPosition> solvedPath = null;
|
|
LinkedList<DirectionPosition> drawingPath = null;
|
|
final Object lockChange = new Object();
|
|
// current position of human resolving
|
|
Position human = new Position(0,0);
|
|
|
|
// goal exit
|
|
Position goal = new Position(-1,-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<Point> 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(), path);
|
|
} else {
|
|
map.setDirection(last.getPosition(), 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 (human.equals(goal)) {
|
|
statusListener.addStatus("Exit found by human !");
|
|
}
|
|
}
|
|
|
|
private void proceed() {
|
|
// should redraw ...
|
|
invalidate();
|
|
repaint();
|
|
checkExit();
|
|
}
|
|
|
|
void goTo(short direction)
|
|
{
|
|
resetResolution();
|
|
if (map.moveInDirection(human, direction)) {
|
|
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;
|
|
goal = new Position(map.getWidth() - 1,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;
|
|
human = new Position(0,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);
|
|
|
|
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 (human.equals(goal)) {
|
|
g.setColor(colorMap.goal);
|
|
} else {
|
|
g.setColor(colorMap.resolved_path);
|
|
cp.drawDot(g, goal, min, max);
|
|
g.setColor(colorMap.path);
|
|
}
|
|
cp.drawDot(g, human, 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<DirectionPosition> solvedPath) {
|
|
LinkedList<DirectionPosition> 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;
|
|
}
|
|
}
|