added game over mechanics

This commit is contained in:
helori_ollivier
2026-03-13 15:30:37 +01:00
parent f3118ddf96
commit a9512b4af8
5 changed files with 392 additions and 336 deletions

View File

@@ -0,0 +1,120 @@
package bzh.risotto;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.ScreenUtils;
import com.badlogic.gdx.utils.viewport.FitViewport;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Game implements ApplicationListener {
private static final Logger log = LogManager.getLogger(Game.class);
private FitViewport viewport;
private SpriteBatch spriteBatch;
private OrthographicCamera camera;
private Vector2 zoom;
private Minesweeper minesweeper;
private Timer clickDelay;
private Logger logger = LogManager.getLogger();
@Override
public void create() {
int width = 16*10;
int height = 16*10;
viewport = new FitViewport(width,height);
spriteBatch = new SpriteBatch();
zoom = new Vector2(Gdx.graphics.getHeight() / height, Gdx.graphics.getWidth() / width);
minesweeper = new Minesweeper(new Vector2(10, 10), 20);
clickDelay = new Timer(0.25);
}
@Override
public void resize(int width, int height) {
viewport.update(width, height, true);
}
@Override
public void render() {
logic();
move();
draw();
}
private void logic() {
if (Gdx.input.isButtonPressed(Input.Buttons.LEFT) && clickDelay.isFinished()) {
Vector2 mousePos = getMouseWorldPos();
minesweeper.dig(mousePos);
clickDelay.start();
}
if (Gdx.input.isButtonPressed(Input.Buttons.RIGHT) && clickDelay.isFinished()) {
Vector2 mousePos = getMouseWorldPos();
minesweeper.mark(mousePos);
clickDelay.start();
}
if (minesweeper.isEndgame()) {
minesweeper.endGame();
}
}
private void move() {
}
private void draw() {
ScreenUtils.clear(Color.RED);
spriteBatch.setProjectionMatrix(viewport.getCamera().combined);
spriteBatch.begin();
minesweeper.render(spriteBatch);
spriteBatch.end();
}
@Override
public void pause() {
}
@Override
public void resume() {
}
@Override
public void dispose() {
}
private void gameOver(Vector2 pause) {
}
private Vector2 getMouseWorldPos() {
float mouseX = Gdx.input.getX();
float mouseY = Gdx.input.getY();
Vector2 mouseWorldCoord = new Vector2(mouseX, mouseY);
mouseWorldCoord = viewport.unproject(mouseWorldCoord);
return mouseWorldCoord;
}
}

View File

@@ -1,255 +0,0 @@
package bzh.risotto;
import bzh.risotto.tilemap.Tile;
import bzh.risotto.tilemap.TileMap;
import bzh.risotto.tilemap.TileSet;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector2;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
import static java.util.Collections.emptyMap;
/**
* class that load, store and update the visual map of the game,
* according to the game actions
*/
public class GameMap {
/** tilemap used by the gamemap */
private final TileMap tileMap;
/** overlay tilemap */
private final TileMap overlayMap;
/** list of tile that are not digged */
private List<Integer> dirtTiles;
private final Logger logger = LogManager.getLogger();
/**
* Constructor of the gamemap
*
* @param size size of the map
*/
public GameMap(Vector2 size) {
TileSet tileSet = new TileSet("tileset.png", new Vector2(16,16), new Vector2(6,10));
List<List<Integer>> map = loadMap(size);
this.tileMap = new TileMap(tileSet, map);
this.tileMap.setTile(5,0,0);
tileSet = new TileSet("ui.png", new Vector2(16,16), new Vector2(5,3));
this.overlayMap = new TileMap(tileSet, Utils.emptyMap(size));
loadConstArrays();
}
/**
* generate the gameMap
*
* @param size size of the map
* @return the generated map
*/
private List<List<Integer>> loadMap(Vector2 size) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> row;
for (int i = 0; i < size.y; i++) {
row = new ArrayList<>();
for (int j = 0; j < size.x; j++) {
int tileId = (i+j)%3;
row.add(tileId);
}
res.add(row);
}
return res;
}
/**
* Dig the gamemap at the coord
* convert grass tiles to dirt tiles, and make them visualy connect
*
* @param worldCoord the world coordinat to dig
*/
public void dig(Vector2 worldCoord, int nbMines) {
// convert world coord to tilemap coord
Vector2 coord = tileMap.toTileMapCoord(worldCoord);
logger.debug("pos: " + coord);
logger.debug("tileType: " + getTileType((int) coord.x, (int) coord.y));
// prevent diging if spot is marked
int overlayTileId = this.overlayMap.getTileId((int) coord.x, (int) coord.y);
if (overlayTileId == 1) {
return;
}
// dig the tile
tileMap.setTile(13, (int) coord.x, (int) coord.y);
// update the surrounding tiles
for (int i = -1; i < 2; i ++) {
for (int j = -1; j < 2; j ++) {
Tile tile = null;
// prevent out of bound error while checking border tiles
try {
tile = tileMap.getTile((int) coord.x + j, (int) coord.y + i);
} catch (IndexOutOfBoundsException e) {
logger.info("Out of world check", e);
}
// set the right tile to make them visually connect
if ( tile != null && !dirtTiles.contains(tile.getId())) {
int tileType = getTileType((int) coord.x + j, (int) coord.y + i);
int tileId = tileTypeToId(tileType);
tileMap.setTile(tileId, (int) coord.x + j, (int) coord.y + i);
}
}
}
this.overlayMap.setTile((nbMines+5), (int) coord.x, (int) coord.y);
}
public void mark(Vector2 worldCoord) {
Vector2 coord = tileMap.toTileMapCoord(worldCoord);
int tileId = this.overlayMap.getTileId((int) coord.x, (int) coord.y);
if (tileId == 0) {
this.overlayMap.setTile((1), (int) coord.x, (int) coord.y);
}
else if (tileId == 1) {
this.overlayMap.setTile((0), (int) coord.x, (int) coord.y);
}
}
/**
* draw the map to the screen
*
* @param spriteBatch the spritebatch to use
*/
public void render(SpriteBatch spriteBatch) {
this.tileMap.render(spriteBatch);
this.overlayMap.render(spriteBatch);
}
/**
* calculate the type of the tile, by checking the surrounding tiles
*
* @param coordX x coordinate of the tile to check
* @param coordY y coordinate of the tile to check
* @return the tile type
*/
private int getTileType(int coordX, int coordY) {
// count tiles around
int p = 0;
int tileType = 0;
for (int i = -1; i < 2; i ++) {
for (int j = -1; j < 2; j ++) {
Tile tile = null;
// prevent out of bound error when checking border tiles
try {
tile = tileMap.getTile(coordX + j, coordY + i);
} catch (IndexOutOfBoundsException e) {
logger.info("Out of world check", e);
}
// compute tile type
if (tile != null && !dirtTiles.contains(tile.getId())) {
tileType += (int) Math.pow(2, p);
}
p++;
}
}
tileType -= 16;
return tileType;
}
/**
* convert tile type to tile set coordinate
* (could be generalized too fit every tileset)
*coordinate
* @param tileType the tile type we want to convert
* @return the corresponding coordinate in the tileset
*/
private int tileTypeToId(int tileType) {
return switch (tileType) {
case 2, 3, 6, 7, 71, 323, 327, 66, 322, 67, 258, 259, 263, 262, 326, 70 -> 10; // down
case 128, 192, 448, 384, 385, 449, 453, 193, 132, 196, 452, 133, 197, 388, 389, 129 -> 22; // up
case 8, 73, 72, 9, 329, 333, 328, 332, 77, 264, 268, 269, 12, 13, 76 -> 17; // right
case 32, 288, 292, 36, 356, 357, 33, 97, 353, 96, 100, 101, 37, 293, 352, 289 -> 15; // left
case 160, 161, 165, 229, 224, 228, 164, 225 -> 21; // inner bot-left corner
case 10, 74, 330, 334, 266, 270, 78, 14 -> 11; // inner top-right corner
case 34, 35, 99, 355, 290, 354, 291, 98 -> 9; // inner top-left corner
case 136, 137, 393, 397, 140, 141, 392, 396 -> 23; // inner bot-right corner
case 40, 44, 45, 109, 365, 105, 361, 296, 360, 104, 364, 41, 297, 301, 108, 300 -> 45; // left-right corridor
case 130, 134, 135, 199, 455, 391, 194, 195, 451, 198, 131, 387, 450, 454, 386, 390 -> 39; // top-down corridor
case 168, 169, 173, 172 -> 34; // T up
case 42, 106, 362, 298 -> 29; // T down
case 138, 394, 398, 142 -> 35; // T right
case 227, 162, 226, 163 -> 28; // T left
case 38, 102, 358, 359, 294, 295, 39, 103 -> 6; // top-left corner
case 204, 205, 461, 456, 457, 200, 201, 460 -> 20; // bot-left corner
case 416, 417, 481, 485, 420, 484, 421, 480 -> 18; // bot-right corner
case 11, 75, 79, 335, 267, 271, 351, 15, 331 -> 8; // top-right corner
case 47, 111, 367, 303 -> 7; // up-straight
case 488, 496, 493, 492, 489 -> 19; // bot-straight
case 203, 459, 463, 207 -> 14; // left-straight
case 422, 486, 487, 423 -> 12; // right-straight
case 232, 233, 237, 236 -> 30;
case 418, 482, 483, 419 -> 24;
case 143, 399, 139, 395 -> 31;
case 46, 110, 366, 302 -> 25;
case 166, 167, 230, 231 -> 46;
case 43, 299, 363, 107 -> 40;
case 202, 206, 462, 458 -> 41;
case 424, 428, 429, 425 -> 47;
case 235 -> 37; // right gateway
case 430 -> 38; // left gateway
case 175 -> 42; // top gateway
case 490 -> 36; // bo gateway
case 239 -> 32; // inner top-right
case 431 -> 33; // inner top-left
case 491 -> 26; // inner bot-left
case 494 -> 27; // inner bot-right
case 427 -> 44;
case 238 -> 43;
case 171 -> 54;
case 174 -> 55;
case 234 -> 48;
case 426 -> 49;
case 495 -> 13; // center full
case 170 -> 16; // center cross
default -> 5;
};
}
public TileMap getTileMap() {
return this.tileMap;
}
private void loadConstArrays() {
this.dirtTiles = new ArrayList<>();
this.dirtTiles.add(0);
this.dirtTiles.add(1);
this.dirtTiles.add(2);
this.dirtTiles.add(4);
}
}

View File

@@ -16,6 +16,6 @@ public class Main {
// Optional: Set window title
config.setTitle("MineSweeper");
new Lwjgl3Application(new Minesweeper(), config);
new Lwjgl3Application(new Game(), config);
}
}

View File

@@ -1,7 +1,6 @@
package bzh.risotto;
import bzh.risotto.tilemap.Tile;
import com.badlogic.gdx.math.Vector2;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -14,7 +13,7 @@ import java.util.Random;
/**
* Class that load, store and update the mines map
*/
public class MinesweeperMap {
public class MinesMap {
private Vector2 size;
private List<List<Integer>> map;
@@ -23,13 +22,18 @@ public class MinesweeperMap {
private final Logger logger = LogManager.getLogger();
public MinesweeperMap(Vector2 size, int nbMines) {
public MinesMap(Vector2 size, int nbMines) {
this.size = size;
this.map = loadMap(size, nbMines);
}
public int getCell(Vector2 coord) {
return this.map.get((int) coord.y).get((int) coord.x);
if (coord != null) {
return this.map.get((int) coord.y).get((int) coord.x);
}
else {
return -2;
}
}
private List<List<Integer>> loadMap(Vector2 size, int nbMines) {

View File

@@ -1,124 +1,311 @@
package bzh.risotto;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.OrthographicCamera;
import bzh.risotto.tilemap.Tile;
import bzh.risotto.tilemap.TileMap;
import bzh.risotto.tilemap.TileSet;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.ScreenUtils;
import com.badlogic.gdx.utils.viewport.FitViewport;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
public class Minesweeper implements ApplicationListener {
/**
* class that load, store and update the visual map of the game,
* according to the game actions
*/
public class Minesweeper {
private static final Logger log = LogManager.getLogger(Minesweeper.class);
private FitViewport viewport;
private SpriteBatch spriteBatch;
private OrthographicCamera camera;
private Vector2 zoom;
/** size of the map */
private final Vector2 size;
private GameMap gameMap;
private MinesweeperMap minesweeperMap;
private Timer clickDelay;
/** map of the mines */
private final MinesMap minesMap;
private Logger logger = LogManager.getLogger();
/** tilemap used by the gamemap */
private final TileMap tileMap;
/** overlay tilemap */
private final TileMap overlayMap;
/** list of tile that are not digged */
private List<Integer> dirtTiles;
private Vector2 lastTileDigged;
@Override
public void create() {
private final int nbMines;
private int nbLeftOverMines;
private int markedMines;
int width = 16*10;
int height = 16*10;
private final Logger logger = LogManager.getLogger();
viewport = new FitViewport(width,height);
spriteBatch = new SpriteBatch();
/**
* Constructor of the gamemap
*
* @param size size of the map
*/
public Minesweeper(Vector2 size, int nbMines) {
zoom = new Vector2(Gdx.graphics.getHeight() / height, Gdx.graphics.getWidth() / width);
this.size = size;
gameMap = new GameMap(new Vector2(10, 10));
minesweeperMap = new MinesweeperMap(new Vector2(10, 10), 10);
TileSet tileSet = new TileSet("tileset.png", new Vector2(16,16), new Vector2(6,10));
clickDelay = new Timer(0.25);
List<List<Integer>> map = loadMap(size);
this.tileMap = new TileMap(tileSet, map);
this.tileMap.setTile(5,0,0);
tileSet = new TileSet("ui.png", new Vector2(16,16), new Vector2(5,3));
this.overlayMap = new TileMap(tileSet, Utils.emptyMap(size));
this.minesMap = new MinesMap(new Vector2(10, 10), nbMines);
this.nbMines = nbMines;
this.nbLeftOverMines = nbMines;
loadConstArrays();
}
@Override
public void resize(int width, int height) {
viewport.update(width, height, true);
}
/**
* generate the gameMap
*
* @param size size of the map
* @return the generated map
*/
private List<List<Integer>> loadMap(Vector2 size) {
@Override
public void render() {
logic();
move();
draw();
}
List<List<Integer>> res = new ArrayList<>();
private void logic() {
List<Integer> row;
for (int i = 0; i < size.y; i++) {
row = new ArrayList<>();
for (int j = 0; j < size.x; j++) {
if (Gdx.input.isButtonPressed(Input.Buttons.LEFT) && clickDelay.isFinished()) {
Vector2 mousePos = getMouseWorldPos();
int cell = minesweeperMap.getCell(this.gameMap.getTileMap().toTileMapCoord(mousePos));
int tileId = (i+j)%3;
gameMap.dig(mousePos, cell);
if (cell == -1) {
logger.info("Perdu");
row.add(tileId);
}
else {
logger.info(cell);
}
clickDelay.start();
res.add(row);
}
if (Gdx.input.isButtonPressed(Input.Buttons.RIGHT) && clickDelay.isFinished()) {
Vector2 mousePos = getMouseWorldPos();
gameMap.mark(mousePos);
return res;
}
clickDelay.start();
/**
* Dig the gamemap at the coord
* convert grass tiles to dirt tiles, and make them visualy connect
*
* @param worldCoord the world coordinat to dig
*/
public void dig(Vector2 worldCoord) {
// convert world coord to tilemap coord
Vector2 coord = tileMap.toTileMapCoord(worldCoord);
logger.debug("pos: " + coord);
logger.debug("tileType: " + getTileType((int) coord.x, (int) coord.y));
// prevent diging if spot is marked
int overlayTileId = this.overlayMap.getTileId((int) coord.x, (int) coord.y);
if (overlayTileId == 1) {
return;
}
// dig the tile
tileMap.setTile(13, (int) coord.x, (int) coord.y);
lastTileDigged = coord;
// update the surrounding tiles
for (int i = -1; i < 2; i ++) {
for (int j = -1; j < 2; j ++) {
Tile tile = null;
// prevent out of bound error while checking border tiles
try {
tile = tileMap.getTile((int) coord.x + j, (int) coord.y + i);
} catch (IndexOutOfBoundsException e) {
logger.info("Out of world check", e);
}
// set the right tile to make them visually connect
if ( tile != null && !dirtTiles.contains(tile.getId())) {
int tileType = getTileType((int) coord.x + j, (int) coord.y + i);
int tileId = tileTypeToId(tileType);
tileMap.setTile(tileId, (int) coord.x + j, (int) coord.y + i);
}
}
}
int nbMines = minesMap.getCell(coord);
this.overlayMap.setTile((nbMines+5), (int) coord.x, (int) coord.y);
}
public void mark(Vector2 worldCoord) {
Vector2 coord = tileMap.toTileMapCoord(worldCoord);
int tileId = this.overlayMap.getTileId((int) coord.x, (int) coord.y);
if (tileId == 0) {
this.overlayMap.setTile((1), (int) coord.x, (int) coord.y);
if (minesMap.getCell(coord) == -1) {
nbLeftOverMines -= 1;
}
this.markedMines -= 1;
}
else if (tileId == 1) {
this.overlayMap.setTile((0), (int) coord.x, (int) coord.y);
if (minesMap.getCell(coord) == -1) {
nbLeftOverMines += 1;
}
this.markedMines += 1;
}
}
private void move() {
public boolean isEndgame() {
return (nbLeftOverMines == 0 && markedMines == nbMines) || minesMap.getCell(lastTileDigged) == -1;
}
private void draw() {
ScreenUtils.clear(Color.RED);
spriteBatch.setProjectionMatrix(viewport.getCamera().combined);
spriteBatch.begin();
gameMap.render(spriteBatch);
spriteBatch.end();
}
public void endGame() {
@Override
public void pause() {
if (minesMap.getCell(lastTileDigged) == -1) {
revealMines();
}
}
@Override
public void resume() {
public void revealMines() {
for (int i = 0; i < size.y; i++ ) {
for (int j = 0; j < size.y; j++ ) {
if (
minesMap.getCell(new Vector2(j,i)) == -1
&& overlayMap.getTileId(j, i) != 1
) {
overlayMap.setTile(4, j, i);
}
}
}
}
@Override
public void dispose() {
/**
* draw the map to the screen
*
* @param spriteBatch the spritebatch to use
*/
public void render(SpriteBatch spriteBatch) {
this.tileMap.render(spriteBatch);
this.overlayMap.render(spriteBatch);
}
private Vector2 getMouseWorldPos() {
float mouseX = Gdx.input.getX();
float mouseY = Gdx.input.getY();
Vector2 mouseWorldCoord = new Vector2(mouseX, mouseY);
/**
* calculate the type of the tile, by checking the surrounding tiles
*
* @param coordX x coordinate of the tile to check
* @param coordY y coordinate of the tile to check
* @return the tile type
*/
private int getTileType(int coordX, int coordY) {
// count tiles around
int p = 0;
int tileType = 0;
for (int i = -1; i < 2; i ++) {
for (int j = -1; j < 2; j ++) {
Tile tile = null;
mouseWorldCoord = viewport.unproject(mouseWorldCoord);
// prevent out of bound error when checking border tiles
try {
tile = tileMap.getTile(coordX + j, coordY + i);
} catch (IndexOutOfBoundsException e) {
logger.info("Out of world check", e);
}
return mouseWorldCoord;
// compute tile type
if (tile != null && !dirtTiles.contains(tile.getId())) {
tileType += (int) Math.pow(2, p);
}
p++;
}
}
tileType -= 16;
return tileType;
}
/**
* convert tile type to tile set coordinate
* (could be generalized too fit every tileset)
*coordinate
* @param tileType the tile type we want to convert
* @return the corresponding coordinate in the tileset
*/
private int tileTypeToId(int tileType) {
return switch (tileType) {
case 2, 3, 6, 7, 71, 323, 327, 66, 322, 67, 258, 259, 263, 262, 326, 70 -> 10; // down
case 128, 192, 448, 384, 385, 449, 453, 193, 132, 196, 452, 133, 197, 388, 389, 129 -> 22; // up
case 8, 73, 72, 9, 329, 333, 328, 332, 77, 264, 268, 269, 12, 13, 76 -> 17; // right
case 32, 288, 292, 36, 356, 357, 33, 97, 353, 96, 100, 101, 37, 293, 352, 289 -> 15; // left
case 160, 161, 165, 229, 224, 228, 164, 225 -> 21; // inner bot-left corner
case 10, 74, 330, 334, 266, 270, 78, 14 -> 11; // inner top-right corner
case 34, 35, 99, 355, 290, 354, 291, 98 -> 9; // inner top-left corner
case 136, 137, 393, 397, 140, 141, 392, 396 -> 23; // inner bot-right corner
case 40, 44, 45, 109, 365, 105, 361, 296, 360, 104, 364, 41, 297, 301, 108, 300 -> 45; // left-right corridor
case 130, 134, 135, 199, 455, 391, 194, 195, 451, 198, 131, 387, 450, 454, 386, 390 -> 39; // top-down corridor
case 168, 169, 173, 172 -> 34; // T up
case 42, 106, 362, 298 -> 29; // T down
case 138, 394, 398, 142 -> 35; // T right
case 227, 162, 226, 163 -> 28; // T left
case 38, 102, 358, 359, 294, 295, 39, 103 -> 6; // top-left corner
case 204, 205, 461, 456, 457, 200, 201, 460 -> 20; // bot-left corner
case 416, 417, 481, 485, 420, 484, 421, 480 -> 18; // bot-right corner
case 11, 75, 79, 335, 267, 271, 351, 15, 331 -> 8; // top-right corner
case 47, 111, 367, 303 -> 7; // up-straight
case 488, 496, 493, 492, 489 -> 19; // bot-straight
case 203, 459, 463, 207 -> 14; // left-straight
case 422, 486, 487, 423 -> 12; // right-straight
case 232, 233, 237, 236 -> 30;
case 418, 482, 483, 419 -> 24;
case 143, 399, 139, 395 -> 31;
case 46, 110, 366, 302 -> 25;
case 166, 167, 230, 231 -> 46;
case 43, 299, 363, 107 -> 40;
case 202, 206, 462, 458 -> 41;
case 424, 428, 429, 425 -> 47;
case 235 -> 37; // right gateway
case 430 -> 38; // left gateway
case 175 -> 42; // top gateway
case 490 -> 36; // bo gateway
case 239 -> 32; // inner top-right
case 431 -> 33; // inner top-left
case 491 -> 26; // inner bot-left
case 494 -> 27; // inner bot-right
case 427 -> 44;
case 238 -> 43;
case 171 -> 54;
case 174 -> 55;
case 234 -> 48;
case 426 -> 49;
case 495 -> 13; // center full
case 170 -> 16; // center cross
default -> 5;
};
}
public TileMap getTileMap() {
return this.tileMap;
}
private void loadConstArrays() {
this.dirtTiles = new ArrayList<>();
this.dirtTiles.add(0);
this.dirtTiles.add(1);
this.dirtTiles.add(2);
this.dirtTiles.add(4);
}
}