243 lines
6.9 KiB
Java
243 lines
6.9 KiB
Java
package dev.kske.minesweeper;
|
|
|
|
import java.awt.BasicStroke;
|
|
import java.awt.Color;
|
|
import java.awt.Dimension;
|
|
import java.awt.Font;
|
|
import java.awt.FontMetrics;
|
|
import java.awt.Graphics;
|
|
import java.awt.Graphics2D;
|
|
import java.awt.Image;
|
|
import java.awt.event.MouseAdapter;
|
|
import java.awt.event.MouseEvent;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Random;
|
|
|
|
import javax.swing.JPanel;
|
|
|
|
/**
|
|
* Project: <strong>Minesweeper</strong><br>
|
|
* File: <strong>Board.java</strong><br>
|
|
* Created: <strong>22.03.2019</strong><br>
|
|
* Author: <strong>Kai S. K. Engelbart</strong>
|
|
*/
|
|
public class Board extends JPanel {
|
|
|
|
private static final long serialVersionUID = -279269871397851420L;
|
|
private static final int tileSize = 32;
|
|
|
|
private static Map<String, Image> icons;
|
|
|
|
private int boardWidth, boardHeight;
|
|
private GameState gameState;
|
|
private int mines, activeTiles, flaggedTiles;
|
|
private Tile[][] board;
|
|
private BoardConfig initialConfig;
|
|
|
|
private List<GameListener> listeners;
|
|
|
|
static {
|
|
icons = new HashMap<>();
|
|
final String[] names = { "mine2", "mine4", "tile", "tile3" };
|
|
for (String name : names) {
|
|
icons.put(name, TextureLoader.loadScaledImage(name, tileSize));
|
|
}
|
|
}
|
|
|
|
public Board() {
|
|
// Not using a layout manager
|
|
super(null);
|
|
listeners = new ArrayList<>();
|
|
addMouseListener(new MouseAdapter() {
|
|
|
|
@Override
|
|
public void mousePressed(MouseEvent evt) {
|
|
int n = evt.getX() / tileSize, m = evt.getY() / tileSize;
|
|
Tile tile = board[n][m];
|
|
|
|
if (tile.isTouched() || gameState != GameState.ACTIVE) return;
|
|
switch (evt.getButton()) {
|
|
case MouseEvent.BUTTON1:
|
|
touchTile(n, m);
|
|
break;
|
|
case MouseEvent.BUTTON3:
|
|
flagTile(n, m);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
public void init(BoardConfig config) {
|
|
initialConfig = config;
|
|
|
|
boardWidth = config.width;
|
|
boardHeight = config.height;
|
|
|
|
setPreferredSize(new Dimension(config.width * tileSize, config.height * tileSize));
|
|
|
|
gameState = GameState.ACTIVE;
|
|
mines = config.mines;
|
|
activeTiles = boardWidth * boardHeight;
|
|
flaggedTiles = 0;
|
|
|
|
// Initialize board
|
|
board = new Tile[boardWidth][boardHeight];
|
|
for (int i = 0; i < boardWidth; i++)
|
|
for (int j = 0; j < boardHeight; j++)
|
|
board[i][j] = new Tile();
|
|
initMines();
|
|
repaint();
|
|
}
|
|
|
|
public void reset() {
|
|
init(initialConfig);
|
|
}
|
|
|
|
@Override
|
|
public void paintComponent(Graphics g) {
|
|
super.paintComponent(g);
|
|
for (int i = 0; i < boardWidth; i++)
|
|
for (int j = 0; j < boardHeight; j++) {
|
|
Tile tile = board[i][j];
|
|
int x = i * tileSize, y = j * tileSize;
|
|
|
|
// Draw background
|
|
g.setColor(Color.gray);
|
|
g.fillRect(x, y, x + tileSize, y + tileSize);
|
|
|
|
// Draw tile with normal mine
|
|
if (gameState == GameState.LOST && tile.isMine()) g.drawImage(icons.get("mine2"), x, y, this);
|
|
// Draw tile with diffused mine
|
|
else if (gameState == GameState.WON && tile.isMine()) g.drawImage(icons.get("mine4"), x, y, this);
|
|
else if (tile.isTouched()) {
|
|
|
|
// Draw tile with mine
|
|
if (tile.isMine()) g.drawImage(icons.get("mine2"), x, y, this);
|
|
|
|
// Draw flagged tile
|
|
else if (tile.isDrawSurroundingMines() && tile.getSurroundingMines() > 0) {
|
|
// Draw number of surrounding mines
|
|
String numStr = String.valueOf(tile.getSurroundingMines());
|
|
g.setFont(new Font("Arial", Font.BOLD, 18));
|
|
g.setColor(Color.red);
|
|
FontMetrics fm = g.getFontMetrics();
|
|
int w = fm.stringWidth(numStr), h = fm.getHeight();
|
|
g.drawString(numStr, x + (tileSize - w) / 2, y + (tileSize - h) / 2 + fm.getAscent());
|
|
}
|
|
}
|
|
|
|
// Draw flagged tile
|
|
else if (tile.isFlagged()) g.drawImage(icons.get("tile3"), x, y, this);
|
|
|
|
// Draw normal tile
|
|
else g.drawImage(icons.get("tile"), x, y, this);
|
|
|
|
// Draw grid
|
|
((Graphics2D) g).setStroke(new BasicStroke(2.0f));
|
|
g.setColor(Color.black);
|
|
g.drawRect(x, y, x + tileSize, y + tileSize);
|
|
}
|
|
}
|
|
|
|
public void registerGameListener(GameListener listener) {
|
|
listeners.add(listener);
|
|
}
|
|
|
|
private void notifyGameStateEvent(GameStateEvent evt) {
|
|
listeners.forEach(listener -> listener.onGameStateEvent(evt));
|
|
}
|
|
|
|
private void notifyFlaggedTilesEvent(FlaggedTilesEvent evt) {
|
|
listeners.forEach(listener -> listener.onFlaggedTilesEvent(evt));
|
|
}
|
|
|
|
private void repaintTile(int n, int m) {
|
|
repaint(n * tileSize, m * tileSize, (n + 1) * tileSize, (n + 1) * tileSize);
|
|
}
|
|
|
|
private void initMines() {
|
|
int remaining = mines;
|
|
Random random = new Random();
|
|
while (remaining > 0) {
|
|
// Randomly select a tile
|
|
int n = random.nextInt(boardWidth);
|
|
int m = random.nextInt(boardHeight);
|
|
|
|
// Check if the selected tile already is a mine and is not touched
|
|
if (!board[n][m].isMine()) {
|
|
// Decrement the counter
|
|
remaining--;
|
|
|
|
// Place the mine
|
|
board[n][m].setMine(true);
|
|
|
|
// Adjust surrounding mine counters
|
|
for (int i = Math.max(0, n - 1); i < Math.min(n + 2, board.length); i++)
|
|
for (int j = Math.max(0, m - 1); j < Math.min(m + 2, board[i].length); j++)
|
|
board[i][j].setSurroundingMines(board[i][j].getSurroundingMines() + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void touchTile(int n, int m) {
|
|
Tile tile = board[n][m];
|
|
if (!tile.isTouched()) {
|
|
tile.setTouched(true);
|
|
activeTiles--;
|
|
tile.setDrawSurroundingMines(true);
|
|
|
|
// Adjust the number of flagged tiles if the tile was flagged
|
|
if (tile.isFlagged()) {
|
|
tile.setFlagged(false);
|
|
flaggedTiles--;
|
|
notifyFlaggedTilesEvent(new FlaggedTilesEvent(this, flaggedTiles));
|
|
}
|
|
|
|
// Test if the game is won or lost
|
|
if (tile.isMine()) {
|
|
gameState = GameState.LOST;
|
|
repaint();
|
|
GameStateEvent evt = new GameStateEvent(this, gameState);
|
|
notifyGameStateEvent(evt);
|
|
} else if (mines == activeTiles) {
|
|
gameState = GameState.WON;
|
|
repaint();
|
|
GameStateEvent evt = new GameStateEvent(this, gameState);
|
|
notifyGameStateEvent(evt);
|
|
}
|
|
|
|
// Touch surrounding tiles when there are zero surrounding mines
|
|
else if (tile.getSurroundingMines() == 0)
|
|
for (int i = Math.max(0, n - 1); i < Math.min(n + 2, board.length); i++)
|
|
for (int j = Math.max(0, m - 1); j < Math.min(m + 2, board[i].length); j++)
|
|
if (i != n || j != m && board[i][j].getSurroundingMines() == 0) touchTile(i, j);
|
|
|
|
repaintTile(n, m);
|
|
}
|
|
}
|
|
|
|
private void flagTile(int n, int m) {
|
|
Tile tile = board[n][m];
|
|
if (!tile.isTouched()) {
|
|
if (tile.isFlagged()) {
|
|
tile.setFlagged(false);
|
|
flaggedTiles--;
|
|
} else {
|
|
tile.setFlagged(true);
|
|
flaggedTiles++;
|
|
}
|
|
notifyFlaggedTilesEvent(new FlaggedTilesEvent(this, flaggedTiles));
|
|
repaintTile(n, m);
|
|
}
|
|
}
|
|
|
|
public int getMines() { return mines; }
|
|
|
|
public int getActiveTiles() { return activeTiles; }
|
|
|
|
public int getFlaggedTiles() { return flaggedTiles; }
|
|
}
|