diff --git a/src/nick/sweeper/main/Grid.java b/src/nick/sweeper/main/Grid.java index 91ba5b6..0e1d922 100644 --- a/src/nick/sweeper/main/Grid.java +++ b/src/nick/sweeper/main/Grid.java @@ -4,6 +4,7 @@ import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; +import java.awt.image.BufferStrategy; import nick.sweeper.ai.AILogic; import nick.sweeper.main.Tile.Type; @@ -39,6 +40,8 @@ public class Grid { private final MineSweeper game; private Tile highlight; + + private BufferStrategy bs; public Grid(final int xSize, final int ySize, final int mines, final MineSweeper gameObj) { @@ -51,6 +54,7 @@ public Grid(final int xSize, final int ySize, final int mines, final MineSweeper numMines = mines; game = gameObj; + grid = new Tile[sizeX][sizeY]; initGrid( ); } @@ -71,11 +75,27 @@ private final void clearEmptyNeighbors(final Tile t) { } } - public void draw(final Graphics g) { + public void addGraphics(BufferStrategy bs) { + this.bs = bs; + + // Needs to be called multiple times to fill the buffer + // Not quite perfect and doesn't work all the time + draw(); + draw(); + draw(); + } + + public void draw() { + Graphics g = bs.getDrawGraphics(); + setOffsets(game.getWidth( ) / 2, game.getHeight( ) / 2); + update( ); + + g.setColor(Color.BLACK); + g.fillRect(0, 0, game.getWidth( ), game.getHeight( )); drawTileGrid(g); - if (highlight != null) { + if (highlight != null && drawHighlight) { // Tile X and Y final int tX = highlight.getX( ) - 1; final int tY = highlight.getY( ) - 1; @@ -103,6 +123,11 @@ public void draw(final Graphics g) { g.setColor(Color.GREEN); g.drawString(win, rX, rY); + + g.dispose( ); + bs.show( ); +// game.stop(false); + game.restart(); } else if (hitMine) { g.setFont(new Font("Courier New", Font.BOLD, squareDrawSize)); final String lose = "Hit a Mine!"; @@ -116,7 +141,20 @@ public void draw(final Graphics g) { g.setColor(Color.RED); g.drawString(lose, rX, rY); + + + g.dispose( ); + bs.show( ); +// game.stop(true); + game.restart(); } + + final String basePrint = MineSweeper.name+" (" + sizeX( ) + ", " + sizeY( ) + ") | Flags Used: " + flagsUsed( ) + " | Mines: " + numMines( ) + " | " + String.format("%.2f", percentComplete( )) + "% Complete | AI Engaged: " + MineSweeper.getAI().isAlive( ); + + game.setTitle(basePrint); + + g.dispose( ); + bs.show( ); } private final void drawTileGrid(final Graphics g) { @@ -148,9 +186,13 @@ private void initGrid( ) { int minesToPlace = numMines; for (int x = 0; x < sizeX; x++) { for (int y = 0; y < sizeY; y++) { + if(x < 2 && y < 2) { + grid[x][y] = new Tile(this, x, y, false); + continue; + } final int spotsLeft = (totalSquares( ) - y) - (x * sizeY); - final double chanceOfMine = (double) (minesToPlace) / spotsLeft; + final double chanceOfMine = (double) (minesToPlace) / (spotsLeft - 4); if (Math.random( ) <= chanceOfMine) { grid[x][y] = new Tile(this, x, y, true); minesToPlace--; @@ -229,7 +271,6 @@ public void onClick(final int pX, final int pY, final boolean flag) { if (s != null) { onClick(s, flag); } - } public void onClick(final Tile s, final boolean flag) { @@ -264,6 +305,8 @@ public void onClick(final Tile s, final boolean flag) { } clearEmptyNeighbors(s); + + draw(); } public float percentComplete( ) { @@ -280,7 +323,6 @@ public float percentMines( ) { } public Dimension renderSize( ) { - return new Dimension(sizeX * squareDrawSize, sizeY * squareDrawSize); } @@ -357,14 +399,9 @@ public void update( ) { setHighLight(tileAt(tX, tY)); } } - if (numKnown( ) == totalSquares( )) { + if (numKnown( ) == totalSquares( ) || numKnown() - flagsUsed() == totalSquares() - numMines()) { completed = true; } - - if (hitMine) { - game.stop(true); - } - } } diff --git a/src/nick/sweeper/main/Input.java b/src/nick/sweeper/main/Input.java index dbf151b..6ef6084 100644 --- a/src/nick/sweeper/main/Input.java +++ b/src/nick/sweeper/main/Input.java @@ -6,6 +6,9 @@ import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; +import nick.sweeper.ai.AILogic; +import radar.sweeper.draw.MousePath; + public class Input implements MouseListener, MouseMotionListener, KeyListener { private static int mX, mY; @@ -27,18 +30,30 @@ public static int mouseY( ) { return mY; } - private final Grid g; - - public Input(final Grid g) { + private Grid g; + + private MousePath mousePath; + + public Input() { + } + public void setGrid(Grid g) { this.g = g; } - + + public void setMousePath(MousePath mp) { + this.mousePath = mp; + } + @Override public void keyPressed(final KeyEvent e) { if (e.getKeyCode( ) == KeyEvent.VK_A) { - MineSweeper.toggleAI( ); + if(!MineSweeper.getAI().isAlive()) { + MineSweeper.getAI().start(); + }else { + AILogic.halt(); + } } } @@ -55,7 +70,7 @@ public void keyTyped(final KeyEvent e) { @Override public void mouseClicked(final MouseEvent e) { - g.onClick(e.getX( ), e.getY( ), e.getButton( ) != MouseEvent.BUTTON1); +// g.onClick(e.getX( ), e.getY( ), e.getButton( ) != MouseEvent.BUTTON1); } @@ -81,11 +96,26 @@ public void mouseMoved(final MouseEvent e) { mX = e.getX( ); mY = e.getY( ); + + if(mousePath != null) { + mousePath.hoveredLocation(mX, mY); + } + + if(Grid.drawHighlight) { + g.draw(); + } } @Override - public void mousePressed(final MouseEvent arg0) { + public void mousePressed(final MouseEvent e) { + if(e.getButton() == MouseEvent.BUTTON1) { + g.onClick(e.getX( ), e.getY( ), false); + return; + } + if(e.getButton() == MouseEvent.BUTTON3) { + g.onClick(e.getX( ), e.getY( ), true); + } } @Override diff --git a/src/nick/sweeper/main/MineSweeper.java b/src/nick/sweeper/main/MineSweeper.java index 3627a80..eb2dec7 100644 --- a/src/nick/sweeper/main/MineSweeper.java +++ b/src/nick/sweeper/main/MineSweeper.java @@ -1,48 +1,77 @@ package nick.sweeper.main; import java.awt.Canvas; -import java.awt.Graphics; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; import java.awt.image.BufferStrategy; import javax.swing.JFrame; import nick.sweeper.ai.AILogic; +import radar.sweeper.draw.MousePath; public final class MineSweeper extends Canvas implements Runnable { private static final long serialVersionUID = 1L; - public static final short height = 25, width = 25, numMines = 90; +// public static final short height = 26, width = 50, numMines = 250; + public static short height = 15, width = 15, numMines = 20; private static JFrame frame; private static Grid grid; - private static final short maxFPS = 240; + private static final Input input = new Input(); + + private MineSweeper game; - private static boolean isRunning = true; + private Thread thread; - private static final MineSweeper game = new MineSweeper( ); - - private static final Thread thread = new Thread(game, "Main Thread"); - - public static final String name = "FreeSweeper v1.2c"; - - private static final Input input = new Input(grid); + public static final String name = "FreeSweeper v1.3a"; + + private BufferStrategy bs; public static final boolean debug = false; private static AILogic ai; - - private static boolean aiEngage = false; + + private MousePath mousePath; public static AILogic getAI( ) { - return ai; } + + /** + * @param args Valid arguments are + */ public static void main(final String[ ] args) { + MineSweeper game; + + if(args.length > 0) { + if(args.length < 3) { + System.out.println("Please specify the width and height of the board in the number of spaces and the number of mines to place"); + } + + width = (short) Integer.parseInt(args[0]); + height = (short) Integer.parseInt(args[1]); + numMines = (short) Integer.parseInt(args[2]); + + if(args.length > 3) { + game = new MineSweeper(args[3]); + }else { + game = new MineSweeper(); + } + }else { + game = new MineSweeper(); + } + + + Thread thread = new Thread(game, "Main Thread"); + + game.addThread(thread); + frame = new JFrame( ); frame.setResizable(true); @@ -52,9 +81,6 @@ public static void main(final String[ ] args) { frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setMinimumSize(grid.renderSize( )); - frame.addMouseListener(input); - frame.addMouseMotionListener(input); - game.addMouseListener(input); game.addMouseMotionListener(input); @@ -63,39 +89,36 @@ public static void main(final String[ ] args) { frame.add(game); frame.pack( ); + frame.addWindowListener(new WindowAdapter(){ + public void windowClosing(WindowEvent e) { + game.fastStop(); + frame.dispose(); + } + }); + frame.setVisible(true); thread.start( ); } - public static void toggleAI( ) { - - aiEngage = !AILogic.isRunning( ); + public void addThread(Thread thread) { + this.thread = thread; } - - public MineSweeper( ) { - + + public MineSweeper() { grid = new Grid(width, height, numMines, this); setPreferredSize(grid.renderSize( )); - + + input.setGrid(grid); + ai = new AILogic(grid); } + + public MineSweeper(String outputFile) { + this(); - private void render( ) { - - final BufferStrategy bs = getBufferStrategy( ); - - if (bs == null) { - createBufferStrategy(3); - return; - } - - final Graphics g = bs.getDrawGraphics( ); + mousePath = new MousePath(grid, outputFile); - g.clearRect(0, 0, getWidth( ), getHeight( )); - grid.draw(g); - - g.dispose( ); - bs.show( ); + input.setMousePath(mousePath); } public int renderHeight( ) { @@ -107,63 +130,50 @@ public int renderWidth( ) { return getWidth( ); } - - @SuppressWarnings("unused") + + public void setTitle(String title) { + frame.setTitle(title); + } + @Override - public void run( ) { - - System.out.printf("%.1f", grid.percentMines( )); - System.out.println("% of the map is mined."); - - final double delta = 1000.0 / 60, minFrameTime = 1000000000.0 / maxFPS; - - short fps = 0, ups = 0; - long lastUpdate = System.currentTimeMillis( ), lastPrint = System.currentTimeMillis( ), - lastFrameTime = System.nanoTime( ); - - while (isRunning) { - - if ((lastUpdate + delta) < System.currentTimeMillis( )) { - // System.out.println("Update"); - update( ); - lastUpdate += delta; - ++ups; - } - - while ((lastFrameTime + minFrameTime) < System.nanoTime( )) { - render( ); - lastFrameTime += minFrameTime; - ++fps; - } - - if ((lastPrint + 1000) < System.currentTimeMillis( )) { - - final String basePrint = name + " (" + grid.sizeX( ) + ", " + grid.sizeY( ) + ") | Flags Used: " + grid.flagsUsed( ) + " | Mines: " + grid.numMines( ) + " | " + String.format("%.2f", grid.percentComplete( )) + "% Complete | AI Engaged: " + ai.isAlive( ); - - if (debug) { - frame.setTitle(basePrint + " | UPS: " + ups + " | FPS: " + fps); - } else { - frame.setTitle(basePrint); - } - - fps = 0; - ups = 0; - lastPrint += 1000; - } + public void run() { + createBufferStrategy(2); + bs = getBufferStrategy( ); + + grid.addGraphics(bs); + } + public synchronized void fastStop() { + AILogic.halt(); + + if(mousePath != null) { + mousePath.saveImg(); + } + + try { + ai.join(1000); + thread.join(); + } catch(Exception e) { + System.out.println("Error occured while closing"); } } - + public synchronized void stop(final boolean lost) { - isRunning = false; AILogic.halt( ); + + if(mousePath != null) { + mousePath.saveImg(); + } if (lost) { System.out.println("Hit a mine!"); + }else { + System.out.println("Won game!"); } try { + wait(2000); if (debug) { wait( ); } @@ -175,16 +185,22 @@ public synchronized void stop(final boolean lost) { } } - private void update( ) { - - grid.setOffsets(getWidth( ) / 2, getHeight( ) / 2); - grid.update( ); - - if (aiEngage && !AILogic.isRunning( )) { - ai.start( ); - aiEngage = false; + public synchronized void restart() { + if(mousePath != null) { + mousePath.saveImg(); } - + + try { + wait(2000); + } catch(Exception e) { + System.out.println("Unable to wait to restart"); + } + grid = new Grid(width, height, numMines, this); + + input.setGrid(grid); + + ai = new AILogic(grid); + + grid.addGraphics(bs); } - } diff --git a/src/nick/sweeper/main/Tile.java b/src/nick/sweeper/main/Tile.java index a0c5918..ae9d5b9 100644 --- a/src/nick/sweeper/main/Tile.java +++ b/src/nick/sweeper/main/Tile.java @@ -116,20 +116,40 @@ public void render(final Graphics g, final int pX, final int pY, final short siz } } else if (getType( ) == Tile.Type.EMPTY) { - - g.setColor(Color.GRAY); + g.setColor(Color.LIGHT_GRAY); g.fillRect(pX, pY, size, size); } else if (getType( ) == Tile.Type.NUMBER) { g.setColor(Color.LIGHT_GRAY); g.fillRect(pX, pY, size, size); - g.setColor(Color.BLUE); + switch(numMineNeighbors) { + case 1: + g.setColor(Color.BLUE); + break; + case 2: + g.setColor(new Color(36, 142, 60)); + break; + case 3: + g.setColor(new Color(211, 47, 47)); + break; + case 4: + g.setColor(new Color(123, 31, 162)); + break; + case 5: + g.setColor(Color.ORANGE); + break; + default: + g.setColor(Color.CYAN); + } String txt = getDisplayNum( ); final int stringMidHigh = g.getFontMetrics( ).getHeight( ) / 2; final int stringMidWide = g.getFontMetrics( ).stringWidth(txt) / 2; final short squareMid = (short) (size / 2); g.drawString(txt, (pX - stringMidWide) + squareMid, pY + stringMidHigh + squareMid); + }else if(getType() == Tile.Type.MINE) { + g.setColor(Color.RED); + g.fillRect(pX, pY, size, size); } } diff --git a/src/radar/sweeper/draw/MousePath.java b/src/radar/sweeper/draw/MousePath.java new file mode 100644 index 0000000..0eaa202 --- /dev/null +++ b/src/radar/sweeper/draw/MousePath.java @@ -0,0 +1,55 @@ +package radar.sweeper.draw; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; + +import nick.sweeper.main.Grid; + +/** + * @author Radar + * A class to plot the path of your cursor as you play minesweeper + */ +public class MousePath { + private BufferedImage img; + private String outFile; + + /** + * Function to tell the canvas that the mouse has hovered a point + * @param x The x position of the cursor + * @param y The y position of the cursor + */ + public void hoveredLocation(int x, int y) { + if(x < 0 || y < 0 || x >= img.getWidth() || y >= img.getHeight()) { + return; + } + img.setRGB(x, y, Color.WHITE.getRGB()); + } + + /** + * Creates a new mouse path tracker + * @param grid The grid for the minesweeper game + */ + public MousePath(Grid grid, String outFile) { + img = new BufferedImage(grid.renderSize().width, grid.renderSize().height, BufferedImage.TYPE_INT_RGB); + this.outFile = outFile; + } + + /** + * Function to save the mouse path into an image file + * @param filename The name of the file to save the output to without extension (png is used) + */ + public void saveImg() { + File file = new File(outFile + ".png"); + + try { + ImageIO.write(img, "png", file); + } catch (IOException e) { + System.out.println("Unable to save image"); + } + System.out.println("Saved image as "+outFile+".png"); + } +}