Files
artloglaby/java/org/artisanlogiciel/games/maze/gui/MazeComponent.java
philippe lhardy 0bab1d51b1 minor chnages / renaming / path painting
- draw resolved path as showAll correctly
it was displaying all direction of same cell as solution while only one direction is ok
- Renderer renaming for class rendering cells
- draw directions as arrows
2020-12-28 13:16:35 +01:00

452 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.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;
LabyModelProvider map;
final MazeCellRenderer cp;
Position current = null;
LinkedList<DirectionPosition> solvedPath = null;
LinkedList<DirectionPosition> 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<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().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, MazeCellRenderer 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);
}
}
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);
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);
}
}
}
}
pX = aX;
pY = aY;
synchronized (lockChange) {
g.setColor(colorMap.goal);
if (current != null) {
cp.drawDot(g, current, pX, pY, mX, mY);
}
g.setColor(colorMap.resolved_path);
if (solvedPath != null) {
for (DirectionPosition resolved : solvedPath) {
cp.drawDot(g, resolved.getPosition(), pX, pY, mX, mY);
cp.drawPath(g, resolved, pX, pY, 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;
}
}