Skip to content

Instantly share code, notes, and snippets.

@remexre
Created October 9, 2013 18:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save remexre/6906120 to your computer and use it in GitHub Desktop.
Save remexre/6906120 to your computer and use it in GitHub Desktop.
Minesweeper
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
/* TODO
*
* Flags, in mouseClicked(MouseEvent);
* Autofilling for zeroes. Maybe make a new discover(int, int); function that can recurse?
* Timing as part of score. This likely will involve effort. ☠
*/
@SuppressWarnings("serial")
public class Minesweeper extends JPanel implements MouseListener {
public static int MAP_SIZE = 10; // The default is 10 in case of error in input.
public static final int TILE_SIZE = 64; // Texture size, in pixels. e.g. 64x64 pixels.
public static Boolean mapDiscovered[][] = new Boolean[MAP_SIZE][MAP_SIZE]; // Make a list of discovered locations
public static Coordinate mines[] = new Coordinate[MAP_SIZE]; // Make a list of mine locations
public static int totalMines = 0; // To block segfaults. Trust me.
public static Random prng = new Random(); // Create a prng for making mines.
public static Boolean gameOn = false; // Whether the player can click.
public static Integer opened = 0; // For competition and win-tracking.
public Minesweeper(Dimension size) throws Exception {
this.setSize(size); // Set current size
this.setMinimumSize(size); // Set min size
this.setMaximumSize(size); // Set max size
this.addMouseListener(this); // Make clickevents go to self.
}
public void mouseClicked(MouseEvent mev) {
if(!gameOn) return; // Don't let people click before the game begins or after it ends.
if(mev.getButton() == MouseEvent.BUTTON1) { // Left-click
try {
int x = mev.getX() / TILE_SIZE, y = mev.getY() / TILE_SIZE; // Calculate which tile was clicked
System.out.println("Click at (" + x + ", " + y + ")."); // Log the click
discover(x, y); // "Discover" the tile.
this.repaint(); // Redraw everything, since it changed.
if(isBomb(x, y)) this.gameOver(false); // Ker-blam!
if(opened == (MAP_SIZE * MAP_SIZE) - MAP_SIZE) this.gameOver(true); // Winning!
} catch(Exception ex) {} // Incase click is somewhere weird
} else if(mev.getButton() == MouseEvent.BUTTON2) { // Right-click
// TODO Place flag
} else { // Middle/side/3-finger/demonic click
System.err.println("No weird mouse buttons.");
}
}
public void paintComponent(Graphics g) {
for(int x = 0; x < MAP_SIZE; x++) { // For each
for(int y = 0; y < MAP_SIZE; y++) { // tile
TileImage image = TileImage.BLANK; // default to blank
if(mapDiscovered[x][y]) image = getImage(x, y); // if it's discovered, get the "real" picture
g.drawImage(image.getBufferedImage(), x * TILE_SIZE, y * TILE_SIZE, null); // paint the picture
//g.finalize(); // Causes errors if window > 100,000 pixels to a side or so.
}
}
}
public void discover(int x, int y) { // It's *actually* recursive-ish.
if(mapDiscovered[x][y]) return; // Only call if non-discovered.
opened++; // Increase the opened count.
mapDiscovered[x][y] = true; // Set the tile to discovered.
if(borderingBombs(x, y) == 0) { // Start trying to fill nearbys.
for(int xOffset = -1; xOffset <= 1; xOffset++) {
for(int yOffset = -1; yOffset <= 1; yOffset++) {
if(borderingBombs(x + xOffset, y + yOffset) == 0) {
discover(x + xOffset, y + yOffset); // Recursing!
}
}
}
}
}
public void gameOver(boolean win) {
gameOn = false; // Stop the game
Integer score = opened; // TODO More complicated formula.
for(int x = 0; x < MAP_SIZE; x++) for(int y = 0; y < MAP_SIZE; y++) mapDiscovered[x][y] = true; // Display the grid
JOptionPane.showMessageDialog(this, (win ? "Victory!" : "GAME OVER") + "\nScore: " + score, (win ? "Victory!" : "GAME OVER"), JOptionPane.PLAIN_MESSAGE, new ImageIcon((win ? TileImage.FLAG : TileImage.BOMB).getBufferedImage())); // Give score
}
public static void main(String ... args) throws Exception {
try {
MAP_SIZE = Integer.parseInt(JOptionPane.showInputDialog(null, "How many squares should be on each side?", "MineSweeper", JOptionPane.PLAIN_MESSAGE)); // Get the squares per side.
} catch(Exception ex) {
JOptionPane.showMessageDialog(null, "You said 10, right? You totally said 10.", "10 is a perfectly good number", JOptionPane.ERROR_MESSAGE, new ImageIcon(TileImage.FLAG.getBufferedImage())); // Tell them that you're using the default.
}
for(int x = 0; x < MAP_SIZE; x++) for(int y = 0; y < MAP_SIZE; y++) mapDiscovered[x][y] = false; // "zero out" the grid
generateMines(); // This shouldn't need a comment...
JFrame window = new JFrame("MineSweeper"); // Create the window
Dimension size = new Dimension(MAP_SIZE * TILE_SIZE, MAP_SIZE * TILE_SIZE); // get an appropriate size
Minesweeper panel = new Minesweeper(size); // Make the game panel itself
window.getContentPane().add(panel, BorderLayout.CENTER); // and drop it in
window.pack(); // <i>Should</i> resize. Doesn't
//Dimension size = window.getSize();
//size = panel.getSize();
window.setSize(size); // This code {
window.setMinimumSize(size);
window.setMaximumSize(size); // } does instead.
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // No need for running after frame dies
window.setVisible(true); // Show the window
}
public static void generateMines() {
for(int i = 0; i < MAP_SIZE; i++) { // For every mine that needs placement
int x = prng.nextInt(MAP_SIZE); // Gen a x.
int y = prng.nextInt(MAP_SIZE); // Gen a y.
while(isBomb(x, y)) { // Until it's on a new space.
x = prng.nextInt(MAP_SIZE); // Gen a new x.
y = prng.nextInt(MAP_SIZE); // Gen a new y.
}
mines[i] = new Coordinate(x, y); // Generate a mine.
totalMines++; // Increase the mine count
System.out.println("Mine placed at (" + x + ", " + y + ")."); // Log the mine
}
gameOn = true; // Allow gameplay
}
public static boolean isBomb(int x, int y) {
for(int i = 0; i < totalMines; i++) { // Looks through each mine
if(mines[i].x == x && mines[i].y == y) return true; // And says if it matches
}
return false; // If none do, it's mine-free.
}
public static int borderingBombs(int x, int y) {
int count = 0; // Number of bombs bordering
for(int xOffset = -1; xOffset <= 1; xOffset++) {
for(int yOffset = -1; yOffset <= 1; yOffset++) {
if(isBomb(x + xOffset, y + yOffset)) {
count++; // Record a bomb
}
}
}
return count; // Give the number of bordering bombs/mines/whatever
}
public static TileImage getImage(int x, int y) {
if(isBomb(x, y)) return TileImage.BOMB; // Give a bomb if it is
return TileImage.fromNumber(borderingBombs(x, y)); // Otherwise, get the "right" number.
}
public enum TileImage {
UNKNOWN,
BLANK,
BOMB,
FLAG,
ZERO,
ONE,
TWO,
THREE,
FOUR,
FIVE,
SIX,
SEVEN,
EIGHT;
private static final String prefix = "src/";
public String getFileName() { return prefix + this.name().toLowerCase() + ".png"; } // Get full file name; enum types are names of sprites
public File getFile() { return new File(this.getFileName()); } // Simple constructor
public BufferedImage getBufferedImage() {
try { return ImageIO.read(this.getFile()); } // Load the image
catch(Exception ex) {
System.err.println("Was trying to open \"" + this.getFileName() + "\"."); // Log the error
ex.printStackTrace(); // Probably a FnF.
return null; // No image is better than a segfault.
}
}
public static TileImage fromNumber(int i) { // This doesn't need comments for the literate.
switch(i) { // Returns the TileImage which corresponds to a number.
case 0:
return TileImage.ZERO;
case 1:
return TileImage.ONE;
case 2:
return TileImage.TWO;
case 3:
return TileImage.THREE;
case 4:
return TileImage.FOUR;
case 5:
return TileImage.FIVE;
case 6:
return TileImage.SIX;
case 7:
return TileImage.SEVEN;
case 8:
return TileImage.EIGHT;
default:
return TileImage.UNKNOWN;
}
}
}
public void mouseEntered(MouseEvent mev) {} // Needed, not used.
public void mouseExited(MouseEvent mev) {} // Needed, not used.
public void mousePressed(MouseEvent mev) {} // Needed, not used.
public void mouseReleased(MouseEvent mev) {} // Needed, not used.
public static class Coordinate {
public int x, y;
public Coordinate(int x, int y) {
this.x = x;
this.y = y;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment