Files
artloglaby/java/org/artisanlogiciel/games/Display.java
philippe lhardy fa0c705fd1 uses default locale
only french and english supported
2017-11-18 22:00:26 +01:00

918 lines
24 KiB
Java

package org.artisanlogiciel.games;
import java.awt.BorderLayout;
import java.awt.image.BufferedImage;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Scanner;
import java.util.ResourceBundle;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.artisanlogiciel.games.stl.Wall3d;
import org.artisanlogiciel.util.UTF8Control;
/**
* Display is Main JFrame for this tool
**/
public class Display extends JFrame
{
// to please eclipse, not supposed to be serialized
private static final long serialVersionUID = 8500214871372184418L;
final static ResourceBundle labels = ResourceBundle.getBundle("LabelsBundle", Locale.getDefault(), Display.class.getClassLoader(), new UTF8Control());
MazeComponent maze;
MazeControler controler;
LabyModel model;
JPanel controlPanel;
boolean autoSize;
MazeParams params = null;
Display(LabyModel model,int W, int H, MazeParams params)
{
super("Maze display");
if (params != null)
{
// fixedParams = new MazeParamsFixed(params.getSaveDir(),params.getWidth(),params.getHeight(),params.getMaxDepth());
this.params = params;
}
this.model = model;
maze = createMazeComponent(model,W,H);
Container con = this.getContentPane();
con.add(new JScrollPane(maze), BorderLayout.CENTER);
controler = new MazeControler(params);
con.add(controler.getMoveControl(), BorderLayout.NORTH);
con.add(controler.getGenerationControl(), BorderLayout.SOUTH);
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
if ( autoSize )
{
maze.getAutoSize();
}
}
});
model.setMazeListener(maze);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(W, H, W, H);
setVisible(true);
}
private static MazeComponent createMazeComponent(LabyModel model, int W, int H)
{
MazeCellParameters cp = new MazeCellParameters(model.getWidth(),model.getHeight(), W, H, 3, 3);
MazeComponent comp = new MazeComponent(model, cp);
return comp;
}
void recreateModel()
{
// recreate labyrinth
if (params != null)
{
params = controler.getSettings().getParams();
model = new LabyModel(params, new java.util.Random());
maze.resetWallsProvider(model);
model.setMazeListener(maze);
// we are in GUI event thread, need to release it and do it outside.
new Thread() {
public void run() {
model.generateWithEntry(0, 0);
model.addEntryOrExit(-1, 0);
model.addEntryOrExit(params.getWidth(), params.getHeight() - 1);
}
}.start();
}
}
void resolve()
{
model.resolve(model.getWidth() - 1, model.getHeight() - 1, maze);
}
void goNorth()
{
maze.goNorth();
}
void goSouth()
{
maze.goSouth();
}
void goEast()
{
maze.goEast();
}
void goWest()
{
maze.goWest();
}
void setWallSize(int size)
{
maze.setWallSize(size);
}
void setAutoSize(boolean autoSize)
{
this.autoSize = autoSize;
if ( autoSize )
{
maze.getAutoSize();
}
}
void savePng()
{
File file = new File("snapshot.png");
// BufferedImage bi = new BufferedImage(this.getSize().width, this.getSize().height, BufferedImage.TYPE_INT_ARGB);
BufferedImage bi = new BufferedImage(maze.getSize().width, maze.getSize().height, BufferedImage.TYPE_INT_ARGB);
Graphics g = bi.createGraphics();
// this.paint(g);
maze.paint(g);
g.dispose();
try{
ImageIO.write(bi,"png",file);
}catch (Exception e) {
e.printStackTrace();
}
}
private class MazeSettings extends JPanel
{
MazeParams params;
JTextField textWidth = null;
JTextField textHeight = null;
JTextField textDepth = null;
// TODO set width and height and depth of maze with gui
public MazeSettings(MazeParams params)
{
super();
this.params = params;
createSettingsGui();
}
void createSettingsGui()
{
if ( params != null )
{
JButton buttonCreate = new JButton(labels.getString("create"));
buttonCreate.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent evt)
{
recreateModel();
}
});
add(buttonCreate);
}
if ( params != null )
{
JLabel widthLabel = new JLabel(labels.getString("width"));
textWidth = new JTextField("0" + params.getWidth());
add(widthLabel);
add(textWidth);
JLabel heightLabel = new JLabel(labels.getString("height"));
textHeight = new JTextField("0" + params.getHeight());
add(heightLabel);
add(textHeight);
JLabel depthLabel = new JLabel(labels.getString("depth"));
textDepth = new JTextField("0" + params.getMaxDepth());
add(depthLabel);
add(textDepth);
}
}
public MazeParams getParams()
{
return new MazeParamsFixed(params.getSaveDir(),
Integer.parseInt(textWidth.getText()),
Integer.parseInt(textHeight.getText()),
Integer.parseInt(textDepth.getText()));
}
}
private static class MazeParamsFixed implements MazeParams
{
int width;
int height;
int maxdepth;
File labdir;
String name;
public MazeParamsFixed()
{
}
public MazeParamsFixed(MazeParams params)
{
setParams(params.getSaveDir(),params.getWidth(),params.getHeight(),params.getMaxDepth());
}
public void setParams(File saveDir, int W, int H, int MD)
{
labdir = saveDir;
width=W;
height=H;
maxdepth=MD;
}
public MazeParamsFixed(File saveDir, int W, int H, int MD)
{
name = null;
setParams(saveDir,W,H,MD);
}
public int getWidth()
{
return width;
}
public int getHeight()
{
return height;
}
public int getMaxDepth()
{
return maxdepth;
}
public String getName()
{
if (name == null)
{
name = "lab" + width + "x" + height;
}
return name;
}
public File getSaveDir()
{
return labdir;
}
}
private class MazeControler extends JPanel
{
/**
*
*/
private static final long serialVersionUID = 1L;
private MazeSettings settings = null;
private JPanel resizecontrol = null;
public MazeControler(MazeParams params)
{
super(new BorderLayout());
controlPanel = new JPanel(new BorderLayout());
settings = new MazeSettings(params);
JButton button = new JButton(labels.getString("resolve"));
button.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent evt)
{
//
System.out.println("Resolving");
resolve();
}
});
@SuppressWarnings("serial")
Action goNorth = new AbstractAction()
{
public void actionPerformed(ActionEvent evt)
{
//
System.out.println("go North");
goNorth();
}
};
JButton north = addDirection(this, labels.getString("north"), "UP", goNorth);
@SuppressWarnings("serial")
Action goEast = new AbstractAction()
{
public void actionPerformed(ActionEvent evt)
{
//
System.out.println("go East");
goEast();
}
};
JButton east = addDirection(this, labels.getString("east"), "RIGHT", goEast);
@SuppressWarnings("serial")
Action goWest = new AbstractAction()
{
public void actionPerformed(ActionEvent evt)
{
//
System.out.println("go West");
goWest();
}
};
JButton west = addDirection(this, labels.getString("west"), "LEFT", goWest);
@SuppressWarnings("serial")
Action goSouth = new AbstractAction()
{
public void actionPerformed(ActionEvent evt)
{
//
System.out.println("go South");
goSouth();
}
};
JButton south = addDirection(this, labels.getString("south"), "DOWN", goSouth);
controlPanel.add(button, BorderLayout.CENTER);
controlPanel.add(north, BorderLayout.NORTH);
controlPanel.add(west, BorderLayout.WEST);
controlPanel.add(east, BorderLayout.EAST);
controlPanel.add(south, BorderLayout.SOUTH);
add(controlPanel, BorderLayout.NORTH);
// control display panel contains controls for display
// zoom , autozoom.
final JPanel controlDisplayPanel = new JPanel(new BorderLayout());
final JSlider slider = new JSlider(2, 40);
slider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
setWallSize(slider.getValue());
}
});
final JCheckBox autoSlide = new JCheckBox("autoslide");
autoSlide.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
setAutoSize( autoSlide.isSelected());
}
});
final JButton savePngButton = new JButton(labels.getString("save") +" png");
Action savePngAction = new AbstractAction() {
public void actionPerformed(ActionEvent evt)
{
//
System.out.println("save png");
savePng();
}
};
savePngButton.addActionListener(savePngAction);
final JButton quitButton = new JButton(labels.getString("quit"));
Action quitAction = new AbstractAction() {
public void actionPerformed(ActionEvent evt)
{
//
System.out.println("quit");
System.exit(0);
}
};
quitButton.addActionListener(quitAction);
resizecontrol = new JPanel(new FlowLayout());
resizecontrol.add(slider);
resizecontrol.add(autoSlide);
resizecontrol.add(savePngButton);
resizecontrol.add(quitButton);
add(controlDisplayPanel, BorderLayout.SOUTH);
add(resizecontrol,BorderLayout.WEST);
add(settings,BorderLayout.EAST);
}
private JPanel getMoveControl()
{
return this;
}
private JPanel getResizeControl()
{
return resizecontrol;
}
private JPanel getGenerationControl()
{
return settings;
}
public MazeSettings getSettings()
{
return settings;
}
}
private JButton addDirection(final JPanel panel, final String direction, String key, Action goAction)
{
String actionName = "go" + direction;
JButton button = new JButton(direction);
button.addActionListener(goAction);
KeyStroke keystroke = KeyStroke.getKeyStroke(key);
panel.getInputMap().put(keystroke, actionName);
panel.getActionMap().put(actionName, goAction);
controlPanel.getInputMap().put(keystroke, actionName);
controlPanel.getActionMap().put(actionName, goAction);
return button;
}
private static class MazeCellParameters
{
double width = 10; // width of one cell
double height = 10; // height of one cell
int offsetX = 5; // x offset of upper corner left
int offsetY = 5; // y offset of upper corner left
int mapWidth = 0;
int mapHeight = 0;
public MazeCellParameters(int mapw, int maph, int W, int H, int x, int y)
{
double w = ( W - x )/ mapw;
double h = ( H - y )/ maph;
mapWidth = mapw;
mapHeight = maph;
if (w < 5)
w = 5;
if (h < 5)
h = 5;
setCellSize(w,h);
offsetX = x;
offsetY = y;
}
public void adaptTo(double W, double H)
{
double w = ( W - offsetX ) / mapWidth;
double h = ( H - offsetY ) / mapHeight;
mapWidth = mapWidth;
mapHeight = mapHeight;
if (w < 5)
w = 5;
if (h < 5)
h = 5;
setCellSize(w,h);
}
public void setCellSize(double w, double h)
{
width = w;
height = h;
}
public double getWidth()
{
return width;
}
public double getHeight()
{
return height;
}
public int getOffsetX()
{
return offsetX;
}
public int getOffsetY()
{
return offsetY;
}
public Dimension getDimension()
{
return new Dimension(offsetX + (int) (mapWidth * width),
offsetY + (int) (mapHeight * height));
}
public void drawWalls(Graphics g, int pX, int pY, short walls)
{
int x = offsetX + (int) (pX * width);
int y = offsetY + (int) (pY * height);
if ( (pY == 0) && ((walls & Brick.UP) == Brick.UP))
g.drawLine(x, y, x + (int) width, y);
if ((walls & Brick.DOWN) == Brick.DOWN)
g.drawLine(x, y + (int) height, x + (int) width, y + (int) height);
if ((walls & Brick.RIGHT) == Brick.RIGHT)
g.drawLine(x + (int) width, y, x + (int) width, y + (int) height);
if ( (pX == 0 ) && ((walls & Brick.LEFT) == Brick.LEFT))
g.drawLine(x, y, x, y + (int) height);
}
public void drawDot(Graphics g, Position dot, int pX, int pY, int mX, int mY)
{
double radius = (height > width) ? width : height;
int a = (int) ( radius / 4 );
if ((dot.getX() >= pX) && (dot.getY() >= pY) && (dot.getX() < mX) && (dot.getY() < mY))
{
int x = offsetX + (int) (dot.getX() * width);
int y = offsetY + (int) (dot.getY() * height);
int r2 = (int) ( (radius * 3)/4 );
g.drawOval(x + 1, y + 1, r2, r2);
// g.drawLine(x+a,y+a,x+width-a,y+height-a);
// g.drawLine(x+a,y+height-a,x+width-a,y+a);
}
else
{
int x = offsetX + (int) (pX * width);
int y = offsetY + (int) (pY * height);
g.drawLine(x + 1, y + 1, x + (int) width - 1, y + (int) height - 1);
}
}
}
private static class MazeComponent extends JComponent implements MazeCreationListener, MazeResolutionListener
{
private static final long serialVersionUID = 3163272907991176390L;
WallsProvider map;
final MazeCellParameters cp;
Position current = null;
LinkedList<Position> solvedPath = 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;
void checkExit()
{
if ((sX == gX) && (sY == gY))
{
System.out.println("Exit found by human !");
}
}
void goNorth()
{
resetResolution();
if ((map.getWalls(sX, sY) & Brick.UP) == 0)
{
sY = sY - 1;
// should redraw ...
invalidate();
repaint();
checkExit();
}
}
void goSouth()
{
resetResolution();
if ((map.getWalls(sX, sY) & Brick.DOWN) == 0)
{
sY = sY + 1;
// should redraw ...
invalidate();
repaint();
checkExit();
}
}
void goEast()
{
resetResolution();
if ((map.getWalls(sX, sY) & Brick.RIGHT) == 0)
{
sX = sX + 1;
// should redraw ...
invalidate();
repaint();
checkExit();
}
}
void goWest()
{
resetResolution();
if ((map.getWalls(sX, sY) & Brick.LEFT) == 0)
{
sX = sX - 1;
// should redraw ...
invalidate();
repaint();
checkExit();
}
}
/**
width, height of one cell,
offsetX, offsetY of upper left corner
**/
public MazeComponent(WallsProvider map, MazeCellParameters cp)
{
super();
this.cp = cp;
this.map = map;
setPreferredSize(cp.getDimension());
gX = map.getWidth() - 1;
gY = map.getHeight() - 1;
}
/**
warning works only if new map has same dimensions than previous
*/
public void resetWallsProvider(WallsProvider map)
{
this.map = map;
}
public void setWallSize(int size)
{
cp.setCellSize((double) size,(double) size);
// should redraw ...
invalidate();
repaint();
}
public int getAutoSize()
{
Rectangle r = getBounds();
cp.adaptTo((int) r.getWidth(),(int) r.getHeight());
// should redraw ...
invalidate();
repaint();
return 50;
}
public void changed(Position cl, Position cr, WallsProvider map)
{
// should redraw ...
invalidate();
repaint();
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;
// 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();
}
if ((sX == gX) && (sY == gY))
{
g.setColor(Color.red);
}
else
{
g.setColor(Color.green);
cp.drawDot(g, new Position(gX, gY), pX, pY, mX, mY);
g.setColor(Color.blue);
}
cp.drawDot(g, new Position(sX, sY), pX, pY, mX, mY);
synchronized (lockChange)
{
g.setColor(Color.red);
if (current != null)
{
cp.drawDot(g, current, pX, pY, mX, mY);
}
if (solvedPath != null)
{
for (Position resolved : solvedPath)
{
cp.drawDot(g, resolved, pX, pY, mX, mY);
}
}
}
g.setColor(Color.black);
for (; pY < mY; pY++)
{
for (pX = 0; pX < mX; pX++)
{
walls = map.getWalls(pX, pY);
cp.drawWalls(g, pX, pY, walls);
}
}
}
public boolean notifySearch(Position pPosition)
{
synchronized (lockChange)
{
current = pPosition;
}
// should redraw ...
invalidate();
repaint();
return true;
}
public void notifySearchError(String error)
{
System.err.println(error);
}
public void notifyCompletion(LinkedList<Position> solvedPath)
{
LinkedList<Position> newPath = new LinkedList<>(solvedPath);
System.out.println("resolution completed");
synchronized (lockChange)
{
this.solvedPath = newPath;
}
// should redraw ...
invalidate();
repaint();
}
public void resetResolution()
{
solvedPath = null;
}
}
private static void setupDisplay(LabyModel model, int W, int H, MazeParams params)
{
Display display = new Display(model, W,H,params);
}
public static void main(String pArgs[])
{
LabyModel model = null;
int W = 600;
int H = 400;
System.out.println("Default Locale " + Locale.getDefault());
if ( (pArgs.length > 0) && (pArgs[0].length() > 0))
{
try
{
model = new LabyModel("raw", new FileInputStream(pArgs[0]));
}
catch (IOException io)
{
io.printStackTrace(System.err);
System.exit(1);
}
setupDisplay(model, W,H,null);
}
else
{
MazeParamsFixed params = new MazeParamsFixed(new File("lab"),60,60,15);
model = new LabyModel(params, new java.util.Random());
setupDisplay(model,W,H,params);
model.generateWithEntry(0, 0);
model.addEntryOrExit(-1, 0);
model.addEntryOrExit(params.getWidth(), params.getHeight() - 1);
System.out.println("Generation completed");
File outfile = new File(params.getSaveDir(), params.getName() + ".raw");
if (!outfile.exists())
{
System.out.println("Saving to " + outfile + " ...");
try
{
FileOutputStream out = new FileOutputStream(outfile);
model.streamOut("raw", out);
out.flush();
out.close();
System.out.println("... Done.");
}
catch (IOException io)
{
io.printStackTrace(System.err);
}
}
else
{
System.out.println("" + outfile + " already exists");
}
outfile = new File(params.getSaveDir(), params.getName() + ".stl");
if (!outfile.exists())
{
System.out.println("Saving to " + outfile + " ...");
try
{
FileOutputStream out = new FileOutputStream(outfile);
Wall3d.streamWallsOut(params.getName(), (WallsProvider) model, out);
out.flush();
out.close();
System.out.println("... Done.");
}
catch (IOException io)
{
io.printStackTrace(System.err);
}
}
else
{
System.out.println("" + outfile + " already exists");
}
}
}
}