Created
October 9, 2013 18:48
-
-
Save remexre/6906120 to your computer and use it in GitHub Desktop.
Minesweeper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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