diff --git a/src/main/java/Minesweeper/Cell.java b/src/main/java/Minesweeper/Cell.java new file mode 100644 index 0000000..aa1a65f --- /dev/null +++ b/src/main/java/Minesweeper/Cell.java @@ -0,0 +1,143 @@ +package Minesweeper; + +import java.awt.Color; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +import javax.swing.JButton; +import javax.swing.JOptionPane; + +enum CellType { + Number, Bomb +} + +public class Cell extends JButton { + + private static final Color FLAGCOLOR = Color.RED; + private static final Color FLOODEDCOLOR = Color.LIGHT_GRAY; + private static final Color HIDDENCOLOR = Color.WHITE; + private static final Color MINECOLOR = Color.BLACK; + private static final long serialVersionUID = 1L; + private Playfield playfield; + + public CellType type; + public Point cord; + public boolean flagged = false; + public int value = 0; + + public Cell(CellType _type, Playfield _playfield, Point _cord) { + type = _type; + cord = _cord; + playfield = _playfield; + + setBackground(HIDDENCOLOR); + addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + OnMouseClick(); + } + }); + + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + // TODO Auto-generated method stub + super.mousePressed(e); + if (e.getButton() == 3) { + OnMouseRightClick(); + } + } + }); + } + + protected void OnMouseClick() { + if (!flagged) { + reveal(); + if (type != CellType.Bomb) { + flood(); + } else { + playfield.revealAllBombs(); + JOptionPane.showMessageDialog(getParent(),"KABOOM! Try again!"); + playfield.reset(); + } + + } + } + + protected void OnMouseRightClick() { + if (isEnabled()) { + if (flagged) { + flagged = false; + + setBackground(HIDDENCOLOR); + if (type == CellType.Number) { + playfield.cellDried(); + } + + } else { + flagged = true; + setBackground(FLAGCOLOR); + if (type == CellType.Number) { + playfield.cellFlooded(); + } + } + } + } + + public void reveal() { + if (type == CellType.Number) { + if(value > 0) { + setText(String.valueOf(value)); + } + } else { + setBackground(MINECOLOR); + } + } + + public void flood() { + if (type == CellType.Bomb || flagged) { + return; + } + + setBackground(FLOODEDCOLOR); + setEnabled(false); + reveal(); + playfield.cellFlooded(); + + if (value == 0) { + if (cord.y > 0) { + if (playfield.cells[cord.y - 1][cord.x].type == CellType.Number + && playfield.cells[cord.y - 1][cord.x].isEnabled()) { + playfield.cells[cord.y - 1][cord.x].flood(); + } + } + + if (cord.x < playfield.Size - 1) { + if (playfield.cells[cord.y][cord.x + 1].type == CellType.Number + && playfield.cells[cord.y][cord.x + 1].isEnabled()) { + playfield.cells[cord.y][cord.x + 1].flood(); + } + } + + if (cord.y < playfield.Size - 1) { + if (playfield.cells[cord.y + 1][cord.x].type == CellType.Number + && playfield.cells[cord.y + 1][cord.x].isEnabled()) { + playfield.cells[cord.y + 1][cord.x].flood(); + } + } + + if (cord.x > 0) { + if (playfield.cells[cord.y][cord.x - 1].type == CellType.Number + && playfield.cells[cord.y][cord.x - 1].isEnabled()) { + playfield.cells[cord.y][cord.x - 1].flood(); + } + } + + } + + } + +} diff --git a/src/main/java/Minesweeper/MinesweeperGame.java b/src/main/java/Minesweeper/MinesweeperGame.java new file mode 100644 index 0000000..cf07347 --- /dev/null +++ b/src/main/java/Minesweeper/MinesweeperGame.java @@ -0,0 +1,33 @@ +package Minesweeper; + +import javax.swing.JFrame; +import javax.swing.JPanel; + +public class MinesweeperGame extends JPanel { + + private static final long serialVersionUID = 1L; + public static final int WIDTH = 600, HEIGTH = 600; + public Playfield playfield; + public TimerLable tl; + + public MinesweeperGame(int _playfieldSize, int _bombAmount) { + this.setSize(WIDTH, HEIGTH); + setLayout(null); + playfield = new Playfield(this, _playfieldSize, _bombAmount); + + tl = new TimerLable(); + tl.setBounds((WIDTH / 2 - 5), HEIGTH / 2 - 240, 20, 20); + add(tl); + tl.start(); + } + + public static void main(String[] args) { + JFrame f = new JFrame(); + MinesweeperGame MsG = new MinesweeperGame(8, 10); + + f.add(MsG); + f.setSize(WIDTH, HEIGTH); + f.setLayout(null); + f.setVisible(true); + } +} \ No newline at end of file diff --git a/src/main/java/Minesweeper/Playfield.java b/src/main/java/Minesweeper/Playfield.java new file mode 100644 index 0000000..29dde34 --- /dev/null +++ b/src/main/java/Minesweeper/Playfield.java @@ -0,0 +1,151 @@ +package Minesweeper; + +import java.awt.Point; + +import javax.swing.JOptionPane; + +public class Playfield { + + private static final int CELLSIZE = 50; + public int Size; + private MinesweeperGame MsG; + public Cell[][] cells; + private int bombAmount; + private int cellsFlooded = 0; + + public Playfield(MinesweeperGame _MsG, int _Size, int _bombAmount) { + MsG = _MsG; + Size = _Size; + bombAmount = _bombAmount; + generatePlayfield(); + } + + public void generatePlayfield() { + + cells = new Cell[Size][Size]; + + int[] bPlacement = new int[bombAmount]; + for (int i = 0; i < bPlacement.length; i++) { + bPlacement[i] = (int) (Math.random() * Size * Size); + + for (int j = 0; j < i; j++) { + if (bPlacement[i] == bPlacement[j]) { + i--; + break; + } + } + } + + for (int i = 0; i < Size; i++) { + for (int j = 0; j < Size; j++) { + cells[i][j] = new Cell(CellType.Number, this, new Point(j, i)); + + cells[i][j].setBounds(j * CELLSIZE + (MsG.WIDTH / 2 - Size * CELLSIZE / 2), + i * CELLSIZE + (MsG.HEIGTH / 2 - Size * CELLSIZE / 2), CELLSIZE, CELLSIZE); + MsG.add(cells[i][j]); + for (int k = 0; k < bPlacement.length; k++) { + if (bPlacement[k] == i * Size + j) { + cells[i][j].type = CellType.Bomb; + break; + } + } + } + } + + for (int i = 0; i < Size; i++) { + for (int j = 0; j < Size; j++) { + if (cells[i][j].type == CellType.Number) { + calculateBombProximity(i, j); + } + } + } + MsG.repaint(); + } + + public void reset() { + cellsFlooded = 0; + for (int i = 0; i < Size; i++) { + for (int j = 0; j < Size; j++) { + MsG.remove(cells[i][j]); + } + } + MsG.tl.reset(); + generatePlayfield(); + } + + public void calculateBombProximity(int row, int column) { + + if (row > 0) { + if (column > 0) { + if (cells[row - 1][column - 1].type == CellType.Bomb) { + cells[row][column].value++; + } + } + + if (cells[row - 1][column].type == CellType.Bomb) { + cells[row][column].value++; + } + + if (column < cells.length - 1) { + if (cells[row - 1][column + 1].type == CellType.Bomb) { + cells[row][column].value++; + } + } + } + + if (row < cells.length - 1) { + if (column > 0) { + if (cells[row + 1][column - 1].type == CellType.Bomb) { + cells[row][column].value++; + } + } + + if (cells[row + 1][column].type == CellType.Bomb) { + cells[row][column].value++; + } + + if (column < cells.length - 1) { + if (cells[row + 1][column + 1].type == CellType.Bomb) { + cells[row][column].value++; + } + } + } + + if (column > 0) { + if (cells[row][column - 1].type == CellType.Bomb) { + cells[row][column].value++; + } + } + if (column < cells.length - 1) { + if (cells[row][column + 1].type == CellType.Bomb) { + cells[row][column].value++; + } + } + + } + + public void cellFlooded() { + cellsFlooded++; + if (cellsFlooded >= Size * Size - bombAmount) { + revealAllBombs(); + JOptionPane.showMessageDialog(MsG, "You won, congratulations!"); + reset(); + } + } + + public void revealAllBombs() { + for (int i = 0; i < Size; i++) { + for (int j = 0; j < Size; j++) { + if(cells[i][j].type == CellType.Bomb) { + cells[i][j].reveal(); + } + } + } + MsG.tl.stop(); + MsG.repaint(); + } + + public void cellDried() { + cellsFlooded--; + } +} \ No newline at end of file diff --git a/src/main/java/Minesweeper/TimerLable.java b/src/main/java/Minesweeper/TimerLable.java new file mode 100644 index 0000000..68a1ea9 --- /dev/null +++ b/src/main/java/Minesweeper/TimerLable.java @@ -0,0 +1,60 @@ +package Minesweeper; + +import java.util.Timer; +import java.util.TimerTask; + +import javax.swing.JLabel; + +public class TimerLable extends JLabel { + + private static final long serialVersionUID = 1L; + protected int counter = 0; + private Helper task; + + public void start() { + Timer timer = new Timer(); + task = new Helper(this); + + timer.schedule(task, 0, 1000); + } + + public void update() { + setText(String.valueOf(++counter)); + } + + public void reset() { + task.reset = true; + counter = 0; + setText(String.valueOf(counter)); + repaint(); + task.stop = false; + } + + public void stop() { + task.stop = true; + } +} + + +class Helper extends TimerTask +{ + public boolean reset; + public boolean stop; + public static int i = 0; + private TimerLable timerLable; + + public Helper(TimerLable _timerLable) { + timerLable = _timerLable; + } + public void run() + { + if(stop) { + return; + } + if(reset) { + reset = false; + timerLable.counter = 0; + } + timerLable.update(); + } +} \ No newline at end of file diff --git a/src/test/java/Minesweeper/MinesweeperGameTest.java b/src/test/java/Minesweeper/MinesweeperGameTest.java new file mode 100644 index 0000000..4b61967 --- /dev/null +++ b/src/test/java/Minesweeper/MinesweeperGameTest.java @@ -0,0 +1,53 @@ +package Minesweeper; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class MinesweeperGameTest { + + @ParameterizedTest + @MethodSource("testBombs") + void testBombPlacement(int _playfieldSize, int _bombAmount) { + MinesweeperGame m = new MinesweeperGame(_playfieldSize, _bombAmount); + + int bombCounter = 0; + + for(Cell[] row : m.playfield.cells){ + for (Cell c : row) { + if(c.type == CellType.Bomb) { + bombCounter++; + } + } + } + assertEquals(_bombAmount, bombCounter); + } + + @Test + void testProximityPoints() { + MinesweeperGame m = new MinesweeperGame(3, 0); + m.playfield.cells[0][0].type = CellType.Bomb; + m.playfield.calculateBombProximity(1, 1); + + assertEquals(1, m.playfield.cells[1][1].value); + } + + + private static Stream testBombs(){ + return Stream.of( + Arguments.of(8, 10), + Arguments.of(8, 0), + Arguments.of(4, 12), + Arguments.of(10, 100), + Arguments.of(5, 1) + ); + } + + + +}