JSweeper

Adding to my collection of clones of popular, well-known games, I created back in November of 2016 a Java-implementation of the all-time Windows classic game, Minesweeper.

Minesweeper was pre-installed on every installation of Windows up to and including Windows 7 and has been ported to a variety of different systems. Because of this, nearly everyone has at least once in their life played Minesweeper or at least heard of it.
In Minesweeper you are presented with a square grid of covered tiles containing either numbers or mines. Your task is it to uncover all tiles which are not mines in the least amount of time. When you uncover a mine, it explodes and the game is lost. To aid in figuring out which tiles are mines and which are not, every tile that is not a mine tells you how many mines are in the neighbouring eight tiles. Tiles which have no neighbouring mines are drawn gray and uncover neighbouring non-mine tiles once uncovered.
More on Minesweeper can be found in this Wikipedia article — I am linking to the German version, as the current English version has major flaws and lacks crucial information. If you are so inclined, feel free to fix the English Minesweeper Wikipedia article.

In my clone, there are three pre-defined difficulty levels, directly ported from the original Minesweeper game, and an option to freely adjust the board’s width and height as well as the number of bombs which will be placed. Gameplay is nearly identical to the original, as my clone also uses a square grid and the tile’s numbers correspond to the number of bombs in the eight tiles surrounding that tile.
The game has a purposefully chosen pixel-look using a self-made font to go along with the pixel-style.

Controls

  • Arrow keys and enter to navigate the main menu
  • Arrow keys or mouse movement to select tiles
  • Space, enter or left-click to expose a tile
  • ‘f’ or right-click to flag a tile
  • ‘r’ to restart game when game is either won or lost
  • Escape to return to the main menu when game is either won or lost
  • F11 toggles fullscreen

To play the game, you can either download the .jar file or compile the source code for yourself. The source code is listed below and can be downloaded as a .java file.

Level select screen Successfully played an easy game A failed attempt at solving a hard game


// Java 1.6 / 1.8 code
// Jonathan Frech  5th of November, 2016
//         edited  7th of November, 2016
//         edited 11th of November, 2016
//         edited 13th of November, 2016
//         edited 14th of November, 2016
//         edited 15th of November, 2016
//         edited 17th of November, 2016
//         edited 19th of November, 2016
//         edited 19th of May     , 2017
//         edited 22nd of May     , 2017
//          * fixed max mine cap when
//            using custom settings

// import
import javax.swing.*;
import javax.swing.Timer;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.*;
import java.util.List;

// J-Utilities
class jutil {
    // characters with pixel size 5x5
    private static Map<String, String> characters = new HashMap<String, String>(){{
        put("0", "-+++--+-+--+-+--+-+--+++-");
        put("1", "---+----+----+----+----+-");
        put("2", "-+++----+--+++--+----+++-");
        put("3", "-+++----+--+++----+--+++-");
        put("4", "-+-+--+-+--+++----+----+-");
        put("5", "-+++--+----+++----+--+++-");
        put("6", "-+++--+----+++--+-+--+++-");
        put("7", "-+++----+----+----+----+-");
        put("8", "-+++--+-+--+++--+-+--+++-");
        put("9", "-+++--+-+--+++----+--+++-");

        put("!", "--+----+----+---------+--");
        put("x", "------+-+---+---+-+------");

        put("A", "-+++--+-+--+++--+-+--+-+-");
        put("B", "-++---+-+--++---+-+--++--");
        put("C", "--++--+----+----+-----++-");
        put("D", "-++---+-+--+-+--+-+--++--");
        put("E", "-+++--+----++---+----+++-");
        put("F", "-+++--+----++---+----+---");
        put("G", "-+++--+----+-+--+-+--+++-");
        put("H", "-+-+--+-+--+++--+-+--+-+-");
        put("I", "--+----+----+----+----+--");
        put("J", "-+++----+----+----+--++--");
        put("K", "-+-+--+-+--++---+-+--+-+-");
        put("L", "-+----+----+----+----+++-");
        put("M", "+---+++-+++-+-++---++---+");
        put("N", "+---+++--++-+-++--+++---+");
        put("O", "-+++--+-+--+-+--+-+--+++-");
        put("P", "-+++--+-+--+++--+----+---");
        put("Q", "-+++--+-+--+-+--+-+--++++");
        put("R", "-+++--+-+--+++--++---+-+-");
        put("S", "-+++--+-----+-----+--+++-");
        put("T", "-+++---+----+----+----+--");
        put("U", "-+-+--+-+--+-+--+-+--+++-");
        put("V", "-+-+--+-+--+-+--+-+---+--");
        put("W", "+---++---++-+-++-+-+-+-+-");
        put("X", "-+-+--+-+---+---+-+--+-+-");
        put("Y", "-+-+--+-+--+++---+----+--");
        put("Z", "-+++----+---+---+----+++-");

        put(":", "-------+---------+-------");

        // clock for elapsed time
        put("@", "-+++-+-+-++-++++---+-+++-");

        // cursor for menu
        put(">", "-------+-----+---+-------");

        // right and up arrow for menu
        put("r", "--+-----+-+++++---+---+--");
        put("u", "--+---+++-+-+-+--+----+--");
    }};

    // resize a BufferedImage
    static BufferedImage resize(BufferedImage img, int width, int height) {
        BufferedImage Img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = Img.createGraphics();
        g.drawImage(img, 0, 0, width, height, null);
        g.dispose();

        return Img;
    }

    // fill a buffered image
    static void fill(BufferedImage img, Color color) {
        Graphics2D g = img.createGraphics();
        g.setColor(color);
        g.fillRect(0, 0, img.getWidth(), img.getHeight());
        g.dispose();
    }

    // blit one buffered image onto another one
    static void blit(BufferedImage img, BufferedImage IMG, int x, int y) {
        Graphics2D g = IMG.createGraphics();
        g.drawImage(img, null, x, y);
        g.dispose();
    }

    // blit one buffered image onto another one at (0, 0)
    static void blit(BufferedImage img, BufferedImage IMG) {
        blit(img, IMG, 0, 0);
    }

    // random number between 0 and a (inclusive!)
    static int rand(int a) {
        return (int) (Math.random()*(a+1));
    }

    // random number between a and b (inclusive!)
    static int rand(int a, int b) {
        return a + rand(b-a);
    }

    // random color
    static Color randcolor() {
        return new Color(rand(255), rand(255), rand(255));
    }

    // random object from array
    static Object rand(Object[] obj) {
        return obj[rand(obj.length-1)];
    }

    // darken a given color
    static Color darken(Color color, int k) {
        return new Color(
                Math.min(Math.max(color.getRed  ()-k, 0), 255),
                Math.min(Math.max(color.getGreen()-k, 0), 255),
                Math.min(Math.max(color.getBlue ()-k, 0), 255)
        );
    }

    // 7x7 image of character with background color
    static BufferedImage character(char chr, Color backcolor) {
        return character(Character.toString(chr), backcolor, new Color(255, 255, 255));
    }
    static BufferedImage character(String chr, Color backcolor) {
        return character(chr, backcolor, new Color(255, 255, 255));
    }
    static BufferedImage character(char chr, Color backcolor, Color textcolor) {
        return character(Character.toString(chr), backcolor, textcolor);
    }
    private static BufferedImage character(String chr, Color backcolor, Color textcolor) {
        BufferedImage img = new BufferedImage(7, 7, BufferedImage.TYPE_INT_RGB);
        fill(img, backcolor);

        if (characters.containsKey(chr)) {
            int c = (textcolor).getRGB();
            for (int i = 0; i < 25; i++) {
                if (characters.get(chr).charAt(i) == '+') {
                    img.setRGB(i%5+1, i/5+1, c);
                }
            }
        }

        return img;
    }

    // concatenate a string n times
    private static String stringmultiply(String str, int n) {
        String STR = "";
        for (int i = 0; i < n; i++) {
            STR += str;
        }
        return STR;
    }

    // add whitespace to the left and right
    static String center(String str, int n) {
        int N = n-str.length();
        String space = stringmultiply(" ", N/2);
        if (N%2 == 0) {
            return space + str + space;
        }
        else {
            return space + str + space + " ";
        }
    }

    // generate a string of an integer which has at least n digits
    static String ndigitint(int n, int d) {
        String str = Integer.toString(n);
        return stringmultiply("0", d-str.length()) + str;}
    static String twodigitint(int n) {
        return ndigitint(n, 2);
    }

    // logger
    static void log(Object o) {
        System.out.println(o);
    }

    // calculate n mod m with only positive results
    static int mod(int n, int m) {
        if (n < 0) {
            return m+(n%m);
        }
        return n%m;
    }

    // wrap a number between two bounds (shifted modulo calculation)
    static int wrap(int n, int min, int max) {
        return mod(n-min, max-min+1)+min;
    }
}

// tile class
class Tile {
    private boolean visible, bomb, flagged;
    private int value;

    Tile() {
        visible = false;
        bomb = false;
        flagged = false;
        value = 0;
    }

    BufferedImage render(boolean selected, boolean cheat) {
        String character;
        Color color;

        if (flagged) {
            character = "!";
            color = new Color(255, 150, 0);
        }
        else if (!visible) {
            character = "";
            color = new Color(100, 150, 200);

            // cheat!
            if (cheat && selected && bomb) {
                color = new Color(150, 150, 200);
            }
        }
        else {
            if (bomb) {
                character = "x";
                color = new Color(255, 0, 0);
            }
            else {
                if (value == 0) {
                    character = "";
                    color = new Color(100, 100, 100);
                }
                else {
                    character = Integer.toString(value);
                    color = new Color(100, 150, 200);
                }
            }
        }


        if (selected) {
            color = jutil.darken(color, 50);
        }

        return jutil.character(character, color);
    }

    void flag() {
        if (!visible) {
            flagged = !flagged;
        }
    }

    void expose() {
        visible = true;
    }

    void setbomb(boolean b) {
        bomb = b;
        if (bomb) {
            value = -1;
        }
    }
    boolean isbomb() {
        return bomb;
    }

    void setvisible(boolean v) {
        visible = v;
    }
    boolean isvisible() {
        return visible;
    }

    boolean isflagged() {
        return flagged;
    }
    void setflagged(boolean f) {
        flagged = f;
    }

    void setvalue(int v) {
        value = v;
    }
    int getvalue() {
        return value;
    }
    boolean isempty() {
        return value == 0;
    }
}

// basic vector class
class Vec {
    int x, y;
    Vec(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

// main menu
class Menu {
    // characters (7x7 pixels each)
    private int width, height;

    // all options, selected option
    private String[] options;
    private int selected;

    // option values
    private int opt_width, opt_height, opt_bombs;
    private int opt_mode;

    // constructor
    Menu(int w, int h, int b) {
        opt_width = w;
        opt_height = h;
        opt_bombs = b;

        opt_mode = 0;
        settomode();

        width = 7*10;
        height = 7*10;
        options = new String[] {"NEW GAME", "", " ", " r: ", " u: ", " x: ", "", "EXIT"};
        selected = 0;
    }

    // return the individual option values
    int option_width() {
        return opt_width;
    }
    int option_height() {
        return opt_height;
    }
    int option_bombs() {
        return opt_bombs;
    }

    // render menu on graphics object
    void render(Graphics g, int WIDTH, int HEIGHT) {
        // image (gets centered on graphics object)
        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        jutil.fill(img, new Color(20, 40, 60));

        // modify options to show changing values
        String[] opt = options.clone();
        if (opt_mode == 0) {
            opt[2] += "EASY";
        }
        else if (opt_mode == 1) {
            opt[2] += "NORMAL";
        }
        else if (opt_mode == 2) {
            opt[2] += "HARD";
        }
        else if (opt_mode == 3) {
            opt[2] += "CUSTOM";
        }
        opt[3] += jutil.ndigitint(opt_width , 3);
        opt[4] += jutil.ndigitint(opt_height, 3);
        opt[5] += jutil.ndigitint(opt_bombs , 3);

        // display options
        String str;
        Color c;
        for (int y = 0; y < opt.length && y < height; y++) {
            // cursor
            if (selected == y) {
                str = ">" + opt[y];
            }
            else {
                str = " " + opt[y];
            }

            // gray out options that cannot be changed
            c = new Color(255, 255, 255);
            if (opt_mode != 3 && (y == 3 || y == 4 || y == 5)) {
                c = new Color(150, 150, 150);
            }

            // display string char by char
            for (int x = 0; x < str.length() && x < width; x++) {
                jutil.blit(jutil.character(str.charAt(x), new Color(20, 40, 60), c), img, x*7, y*7+((height-opt.length*7)/2));
            }
        }

        // maximize whilst keeping the correct ratio
        float k = ((float) width) / ((float) height);
        int width = WIDTH;
        int height = (int) (WIDTH/k);
        if (height > HEIGHT) {
            height = HEIGHT;
            width = (int) (HEIGHT*k);
        }

        // draw onto graphics object
        g.drawImage(jutil.resize(img, width, height), (WIDTH-width)/2, (HEIGHT-height)/2, null);
    }

    // set width, height and bombs according to mode
    private void settomode() {
        if (opt_mode == 0) {
            opt_width = 8;
            opt_height = 8;
            opt_bombs = 10;
        }
        else if (opt_mode == 1) {
            opt_width = 16;
            opt_height = 16;
            opt_bombs = 40;
        }
        else if (opt_mode == 2) {
            opt_width = 30;
            opt_height = 16;
            opt_bombs = 99;
        }
    }

    // handle keys
    String keyPressed(KeyEvent e) {
        // move through menu
        if (e.getKeyCode() == KeyEvent.VK_UP) {
            do {
                selected = jutil.mod(selected-1, options.length);
            } while (options[selected].equals(""));
        }
        else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
            do {
                selected = jutil.mod(selected+1, options.length);
            } while (options[selected].equals(""));
        }

        // edit options
        else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
            if (selected == 2) {
                opt_mode = jutil.wrap(opt_mode+1, 0, 3);
            }
            else if (selected == 3) {
                opt_width = jutil.wrap(opt_width+1, 8, 99);
            }
            else if (selected == 4) {
                opt_height = jutil.wrap(opt_height+1, 8, 99);
            }
            else if (selected == 5) {
                // FIX: changed 'opt_height*opt_height' to 'opt_width*opt_height'
                opt_bombs = jutil.wrap(opt_bombs+1, 1, Math.min(999, opt_height*opt_width-1));
            }

            // upper bound on bonds
            // FIX: changed 'opt_height*opt_height' to 'opt_width*opt_height' in the following two lines
            if (opt_bombs >= opt_width*opt_height) {
                opt_bombs = opt_width*opt_height-1;
            }
        }
        else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
            if (selected == 2) {
                opt_mode = jutil.wrap(opt_mode-1, 0, 3);
            }
            else if (selected == 3) {
                opt_width = jutil.wrap(opt_width-1, 8, 99);
            }
            else if (selected == 4) {
                opt_height = jutil.wrap(opt_height-1, 8, 99);
            }
            else if (selected == 5) {
                // FIX: changed 'opt_height*opt_height' to 'opt_width*opt_height'
                opt_bombs = jutil.wrap(opt_bombs-1, 1, Math.min(999, opt_height*opt_width-1));
            }

            // FIX: added this cap code
            // upper bound on bonds
            if (opt_bombs >= opt_width*opt_height) {
                opt_bombs = opt_width*opt_height-1;
            }
        }

        // enter option
        else if (e.getKeyCode() == KeyEvent.VK_SPACE || e.getKeyCode() == KeyEvent.VK_ENTER) {
            return options[selected];
        }

        // fix
        settomode();

        // nothing pressed
        return "";
    }
}

// game class
class Game extends JPanel implements ActionListener, KeyListener, MouseListener, MouseMotionListener, ComponentListener {
    private int WIDTH, HEIGHT;
    private int width, height, footer;
    private int bombs;
    private Tile[][] field;

    private int cx, cy;
    private long timestarted;
    private boolean gameover, gamewon;
    private int finaltime;

    private int fieldwidth, fieldheight;
    private int fieldx, fieldy;

    private boolean menuactive = true;
    private Menu menu;

    private boolean cheat = false;

    private Main parent;
    private boolean fullscreen = false;

    // constructor
    Game(Main parent) {
        this.parent = parent;

        // footer
        footer = 1;

        // pixel size
        setsize(640, 480);

        // menu
        menu = new Menu(8, 8, 15);

        // timer every 100 ms
        Timer timer = new Timer(100, this);
        timer.start();

        // listeners
        addKeyListener(this);
        addMouseListener(this);
        addMouseMotionListener(this);
        addComponentListener(this);

        // focus
        setFocusable(true);
        setBackground(new Color(20, 40, 60));

        // create new field
        newfield();

        // start into menu
        menuactive = true;

        // repaint
        repaint();
    }

    // set size, calculate field position
    private void setsize() {
        float k = ((float) width) / ((float) (height+footer));
        int width = WIDTH;
        int height = (int) (WIDTH/k);

        if (height > HEIGHT) {
            height = HEIGHT;
            width = (int) (HEIGHT*k);
        }
        fieldwidth = width;
        fieldheight = height;
        fieldx = (WIDTH-fieldwidth)/2;
        fieldy = (HEIGHT-fieldheight)/2;
    }
    private void setsize(int width, int height) {
        WIDTH = width;
        HEIGHT = height;
        setsize();
        repaint();
    }

    // change from windowed to windowed borderless maximized
    private void togglefullscreen() {
        // used to achieve real fullscreen
        GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()[0];

        parent.dispose();
        if (fullscreen) {
            parent.setSize(new Dimension(640, 480));
            setsize(640, 480);
            parent.setUndecorated(false);
            parent.setLocationByPlatform(true);
            device.setFullScreenWindow(null);
            fullscreen = false;
        }
        else {
            Dimension s = Toolkit.getDefaultToolkit().getScreenSize();
            parent.setSize(s);
            setsize((int) s.getWidth(), (int) s.getHeight());
            parent.setUndecorated(true);
            parent.setLocation(0, 0);
            device.setFullScreenWindow(parent);
            fullscreen = true;
        }
        parent.setVisible(true);
    }

    private void newfield() {
        // get parameters from menu
        width  = menu.option_width();
        height = menu.option_height();
        bombs  = menu.option_bombs();

        // reset
        timestarted = System.currentTimeMillis();
        gameover = false;
        gamewon = false;
        finaltime = 0;

        // tile array
        field = new Tile[width][height];

        // add bombs
        // Diamond operator '<>' not used, so that source code runs in Java 1.6
        List<Vec> P = new ArrayList<Vec>();
        Vec p;
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                field[x][y] = new Tile();
                P.add(new Vec(x, y));
            }
        }
        for (int i = 0; i < bombs; i++) {
            p = P.remove(jutil.rand(P.size()-1));
            field[p.x][p.y].setbomb(true);
        }

        // calculate value
        int v;
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                if (!field[x][y].isbomb()) {
                    v = 0;

                    if (x > 0                       && field[x-1][y  ].isbomb()) {v++;}
                    if (x < width-1                 && field[x+1][y  ].isbomb()) {v++;}
                    if (               y > 0        && field[x  ][y-1].isbomb()) {v++;}
                    if (               y < height-1 && field[x  ][y+1].isbomb()) {v++;}
                    if (x < width-1 && y > 0        && field[x+1][y-1].isbomb()) {v++;}
                    if (x < width-1 && y < height-1 && field[x+1][y+1].isbomb()) {v++;}
                    if (x > 0       && y > 0        && field[x-1][y-1].isbomb()) {v++;}
                    if (x > 0       && y < height-1 && field[x-1][y+1].isbomb()) {v++;}

                    field[x][y].setvalue(v);
                }
            }
        }

        // calculate pixel pos and size
        setsize();

        // go to game
        menuactive = false;

        // repaint
        repaint();
    }

    // expose current tile
    private void expose() {
        if (!gamewon && !gameover){
            List<Vec> V = new ArrayList<Vec>() {{
                add(new Vec(cx, cy));
            }};

            Vec v;
            while (V.size() > 0) {
                v = V.remove(0);

                if (!field[v.x][v.y].isvisible() && !field[v.x][v.y].isflagged()) {
                    field[v.x][v.y].expose();
                    if (field[v.x][v.y].getvalue() == 0) {
                        if (v.x > 0                        ) {V.add(new Vec(v.x-1, v.y  ));}
                        if (v.x < width-1                  ) {V.add(new Vec(v.x+1, v.y  ));}
                        if (                 v.y > 0       ) {V.add(new Vec(v.x  , v.y-1));}
                        if (                 v.y < height-1) {V.add(new Vec(v.x  , v.y+1));}
                        if (v.x < width-1 && v.y > 0       ) {V.add(new Vec(v.x+1, v.y-1));}
                        if (v.x < width-1 && v.y < height-1) {V.add(new Vec(v.x+1, v.y+1));}
                        if (v.x > 0       && v.y > 0       ) {V.add(new Vec(v.x-1, v.y-1));}
                        if (v.x > 0       && v.y < height-1) {V.add(new Vec(v.x-1, v.y+1));}
                    }
                }
            }

            // check gameover / gamewon
            if (!gamewon && !gameover) {
                boolean go = false;
                boolean gw = true;

                // loop through field
                for (int x = 0; x < width; x++) {
                    for (int y = 0; y < height; y++) {

                        // bomb
                        if (field[x][y].isbomb()) {
                            if (field[x][y].isvisible()) {
                                go = true;
                                break;
                            }
                        }

                        // not a bomb
                        else {
                            if (!field[x][y].isvisible()) {
                                gw = false;
                            }
                        }
                    }
                }

                // gameover / gamewon occurred, stop time
                if (go) {
                    gameover = true;

                    // show all bombs
                    for (int x = 0; x < width; x++) {
                        for (int y = 0; y < height; y++) {
                            if (field[x][y].isbomb()) {
                                field[x][y].setflagged(false);
                                field[x][y].setvisible(true);
                            }
                        }
                    }
                    stoptime();
                }
                else if (gw) {
                    gamewon = true;
                    stoptime();
                }
            }
        }
    }

    // flag current tile
    private void flag() {
        if (!gamewon && !gameover) {
            field[cx][cy].flag();
        }
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(WIDTH, HEIGHT);
    }

    // timer
    @Override
    public void actionPerformed(ActionEvent e) {
        repaint();
    }

    // keys
    @Override
    public void keyPressed(KeyEvent e) {
        if (menuactive) {
            if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                menuactive = false;
            }
            else {
                String str = menu.keyPressed(e);
                if (str.equals("NEW GAME")) {
                    newfield();
                }
                else if (str.equals("EXIT")) {
                    System.exit(0);
                }
            }
        }
        else {
            // switch to menu
            if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                if (gamewon || gameover) {
                    menuactive = true;
                }
            }

            // move cursor
            else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
                if (cx > 0) {
                    cx--;
                }
            }
            else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
                if (cx < width-1) {
                    cx++;
                }
            }
            else if (e.getKeyCode() == KeyEvent.VK_UP) {
                if (cy > 0) {
                    cy--;
                }
            }
            else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
                if (cy < height-1) {
                    cy++;
                }
            }

            // expose and flag
            else if (e.getKeyCode() == KeyEvent.VK_SPACE || e.getKeyCode() == KeyEvent.VK_ENTER) {
                expose();
            }
            else if (e.getKeyCode() == KeyEvent.VK_F) {
                flag();
            }

            // cheat (?)
            else if (e.getKeyCode() == KeyEvent.VK_C) {
                cheat = !cheat;
            }

            // reset
            else if (e.getKeyCode() == KeyEvent.VK_R) {
                newfield();
            }
        }

        // toggle fullscreen
        if (e.getKeyCode() == KeyEvent.VK_F11) {
            togglefullscreen();
        }

        // repaint
        repaint();
    }
    @Override
    public void keyReleased(KeyEvent e) {}
    @Override
    public void keyTyped(KeyEvent e) {}

    // calculate which tile the mouse currently hits
    private void mouse(MouseEvent e) {
        cx = Math.max(0, Math.min(width -1, (e.getX()-fieldx)* width         /fieldwidth ));
        cy = Math.max(0, Math.min(height-1, (e.getY()-fieldy)*(height+footer)/fieldheight));
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        if (!menuactive) {
            mouse(e);
            repaint();
        }

    }
    @Override
    public void mouseEntered(MouseEvent e) {}
    @Override
    public void mouseExited(MouseEvent e) {}
    @Override
    public void mousePressed(MouseEvent e) {
        if (!menuactive) {
            mouse(e);

            if (SwingUtilities.isLeftMouseButton(e)) {
                expose();
            }
            else if (SwingUtilities.isRightMouseButton(e)) {
                flag();
            }

            repaint();
        }
    }
    @Override
    public void mouseReleased(MouseEvent e) {}
    @Override
    public void mouseDragged(MouseEvent e) {
        if (!menuactive) {
            mouse(e);
            repaint();
        }
    }
    @Override
    public void mouseMoved(MouseEvent e) {
        if (!menuactive) {
            mouse(e);
            repaint();
        }
    }

    // stop timer
    private void stoptime() {
        finaltime = (int) (System.currentTimeMillis()-timestarted)/1000;
    }

    // calculate elapsed time
    private String timeelapsed() {
        // calculate elapsed time
        int t = (int) (System.currentTimeMillis()-timestarted)/1000;

        if (gamewon || gameover) {
            t = finaltime;
        }

        // time shorter an hour
        if (t < 3600) {
            return "@" + (t/60) + ":" + jutil.twodigitint(t%60);
        }
        // you took too long
        else {
            return "@LONG";
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        // menu
        if (menuactive) {
            menu.render(g, WIDTH, HEIGHT);
        }

        // game
        else {
            BufferedImage img = new BufferedImage(width*7, (height+footer)*7, BufferedImage.TYPE_INT_RGB);
            // field
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++) {
                    jutil.blit(field[x][y].render(cx == x && cy == y, cheat), img, x*7, y*7);
                }
            }

            // text color
            Color c = new Color(255, 255, 255);
            if (gameover) {
                c = new Color(255, 0, 0);
            }
            else if (gamewon) {
                c = new Color(0, 255, 0);
            }

            // display time
            String str = jutil.center(timeelapsed(), width);
            for (int i = 0; i < str.length(); i++) {
                jutil.blit(jutil.character(str.charAt(i), new Color(20, 40, 60), c), img, i*7, height*7);
            }

            // put image onto graphics object
            g.drawImage(jutil.resize(img, fieldwidth, fieldheight), fieldx, fieldy, null);
        }

        // dispose
        g.dispose();
    }

    @Override
    public void componentMoved(ComponentEvent e) {}
    @Override
    public void componentHidden(ComponentEvent e) {}
    @Override
    public void componentShown(ComponentEvent e) {}
    @Override
    public void componentResized(ComponentEvent e) {
        setsize(e.getComponent().getWidth(), e.getComponent().getHeight());
    }
}

// Main class
class Main extends JFrame {
    private Main() {
        setTitle("JSweeper");
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setLocationByPlatform(true);
        add(new Game(this));
        pack();
        setVisible(true);
    }

    public static void main(String[] args) {
        new Main();
    }
}

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s