Mines.java 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. package eu.tankernn.mines;
  2. import java.awt.EventQueue;
  3. import java.io.FileNotFoundException;
  4. import java.text.DecimalFormat;
  5. import java.util.ArrayList;
  6. import java.util.Arrays;
  7. import java.util.Collections;
  8. import java.util.List;
  9. import java.util.Map;
  10. import java.util.stream.Collectors;
  11. import java.util.stream.IntStream;
  12. import java.util.stream.Stream;
  13. import org.lwjgl.Sys;
  14. import org.lwjgl.input.Keyboard;
  15. import org.lwjgl.input.Mouse;
  16. import org.lwjgl.opengl.Display;
  17. import org.lwjgl.util.vector.Vector2f;
  18. import eu.tankernn.gameEngine.GameLauncher;
  19. import eu.tankernn.gameEngine.TankernnGame;
  20. import eu.tankernn.gameEngine.loader.font.Font;
  21. import eu.tankernn.gameEngine.loader.font.FontFamily;
  22. import eu.tankernn.gameEngine.loader.font.GUIText;
  23. import eu.tankernn.gameEngine.loader.textures.Texture;
  24. import eu.tankernn.gameEngine.renderEngine.gui.GuiRenderer;
  25. import eu.tankernn.gameEngine.renderEngine.gui.GuiTexture;
  26. import eu.tankernn.gameEngine.util.InternalFile;
  27. import eu.tankernn.mines.Tile.TileState;
  28. public class Mines extends TankernnGame {
  29. public static final String GAME_NAME = "Minesweeper";
  30. public static final Pos[] DEFAULT_PATTERN = { new Pos(0, 1), new Pos(0, -1), new Pos(1, 0), new Pos(1, 1),
  31. new Pos(1, -1), new Pos(-1, 0), new Pos(-1, -1), new Pos(-1, 1) };
  32. public static final Settings DEFAULT_SETTINGS = new Settings(Arrays.asList(DEFAULT_PATTERN), 9, 9, 10);
  33. public static final boolean DEBUG = false;
  34. // Utility
  35. private DecimalFormat format = new DecimalFormat("0.000 sec");
  36. private GuiRenderer renderer;
  37. private SettingsEditor editor;
  38. // Graphics
  39. private Texture hidden, exploded, flagged;
  40. private Texture[] checked;
  41. private GUIText timeText;
  42. private int tileWidth, tileHeight;
  43. /**
  44. * Next game settings.
  45. */
  46. private Settings nextSettings;
  47. /**
  48. * Current game settings.
  49. */
  50. private Settings settings;
  51. // Game state
  52. private long startTime;
  53. private Map<Pos, Tile> tiles;
  54. private int hiddenTiles;
  55. private boolean running = false;
  56. private boolean justClicked = false;
  57. public Mines(String name) {
  58. super(name);
  59. renderer = new GuiRenderer(loader);
  60. try {
  61. hidden = loader.loadTexture(new InternalFile("hidden.png"));
  62. checked = new Texture[10];
  63. for (int i = 0; i < checked.length; i++)
  64. checked[i] = loader.loadTexture(new InternalFile(i + ".png"));
  65. exploded = loader.loadTexture(new InternalFile("exploded.png"));
  66. flagged = loader.loadTexture(new InternalFile("flagged.png"));
  67. FontFamily fontFam = new FontFamily(loader.loadTextureAtlas(new InternalFile("arial.png")), new InternalFile("arial.fnt"));
  68. Font font = new Font(fontFam, 1);
  69. timeText = new GUIText(format.format(0), font, new Vector2f(0f, 0f), 100, false);
  70. GUIText helpText = new GUIText("Press R to reset board", font, new Vector2f(0.8f, 0f), 0.15f,
  71. false);
  72. textMaster.loadText(timeText);
  73. textMaster.loadText(helpText);
  74. } catch (FileNotFoundException e) {
  75. e.printStackTrace();
  76. }
  77. setSettings(DEFAULT_SETTINGS);
  78. EventQueue.invokeLater(() -> {
  79. editor = new SettingsEditor(DEFAULT_SETTINGS, this::setSettings);
  80. });
  81. startGame();
  82. }
  83. public void setSettings(Settings settings) {
  84. this.nextSettings = settings;
  85. }
  86. /**
  87. * Starts game with next settings.
  88. */
  89. private void startGame() {
  90. if (!nextSettings.equals(settings)) {
  91. this.settings = nextSettings;
  92. updateBoardSize();
  93. }
  94. startTime = Sys.getTime();
  95. hiddenTiles = settings.area;
  96. tiles = generateBoard(settings.boardWidth, settings.boardHeight, settings.mines);
  97. running = true;
  98. }
  99. private Map<Pos, Tile> generateBoard(int width, int height, int mines) {
  100. assert width * height < mines;
  101. List<Pos> minePositions = new ArrayList<Pos>(allPositions(width, height).collect(Collectors.toList()));
  102. Collections.shuffle(minePositions);
  103. minePositions.subList(0, minePositions.size() - mines).clear();
  104. return allPositions(width, height).map(p -> new Tile(minePositions.contains(p), p))
  105. .collect(Collectors.toMap(t -> t.pos, t -> t));
  106. }
  107. public void check(Tile tile) {
  108. if (running)
  109. if (tile.isMine)
  110. lose();
  111. else
  112. calculateMinesAround(tile, settings.pattern);
  113. }
  114. public void calculateMinesAround(Tile tile, List<Pos> pattern) {
  115. hiddenTiles--;
  116. List<Tile> testTiles = pattern.stream().map(p -> p.add(tile.pos))
  117. .map(tiles::get).filter(t -> t != null).collect(Collectors.toList());
  118. int minesAround = (int) testTiles.stream().filter(t -> t.isMine).count();
  119. tile.setMinesAround(minesAround);
  120. // Keep checking if there are no mines around
  121. if (minesAround == 0)
  122. testTiles.stream().filter(t -> t.getState().equals(Tile.TileState.HIDDEN))
  123. .forEach(t -> calculateMinesAround(t, pattern));
  124. }
  125. private void lose() {
  126. running = false;
  127. // Expose all mines
  128. for (Tile t : tiles.values())
  129. if (t.isMine)
  130. t.setState(TileState.EXPLODED);
  131. }
  132. private void win() {
  133. running = false;
  134. }
  135. @Override
  136. public void update() {
  137. if (Mouse.isButtonDown(1) || Mouse.isButtonDown(0)) {
  138. if (!justClicked && running) {
  139. justClicked = true;
  140. Pos p = new Pos(Mouse.getX() / tileWidth, Mouse.getY() / tileHeight);
  141. if (!tiles.containsKey(p)) {
  142. System.out.println(p);
  143. dumpMap();
  144. return;
  145. }
  146. if (Mouse.isButtonDown(0)) {
  147. check(tiles.get(p));
  148. } else {
  149. tiles.get(p).toggleFlag();
  150. }
  151. }
  152. } else
  153. justClicked = false;
  154. // Handle keyboard
  155. if (running)
  156. timeText.setText(format.format(((float) (Sys.getTime() - startTime)) / Sys.getTimerResolution()));
  157. else if (Keyboard.isKeyDown(Keyboard.KEY_R))
  158. startGame();
  159. if (hiddenTiles == settings.mines)
  160. win();
  161. super.update();
  162. }
  163. @Override
  164. public void render() {
  165. List<GuiTexture> toRender = new ArrayList<GuiTexture>();
  166. for (Tile t : tiles.values()) {
  167. // OpenGL output
  168. Texture tex;
  169. switch (t.getState()) {
  170. case CHECKED:
  171. tex = checked[t.getMinesAround()];
  172. break;
  173. case EXPLODED:
  174. tex = exploded;
  175. break;
  176. case FLAGGED:
  177. tex = flagged;
  178. break;
  179. case HIDDEN:
  180. default:
  181. tex = hidden;
  182. break;
  183. }
  184. Vector2f scale = new Vector2f(1f / settings.boardWidth, 1f / settings.boardHeight);
  185. toRender.add(new GuiTexture(tex,
  186. new Vector2f(2 * scale.x * t.pos.x + scale.x - 1, 2 * scale.y * t.pos.y + scale.y - 1), scale.x));
  187. }
  188. renderer.render(toRender);
  189. super.render();
  190. }
  191. @Override
  192. public void cleanUp() {
  193. super.cleanUp();
  194. if (editor != null)
  195. editor.dispose();
  196. }
  197. public static Stream<Pos> allPositions(int width, int height) {
  198. Stream<Integer> xs = IntStream.range(0, width).boxed();
  199. return xs.flatMap(x -> IntStream.range(0, height).mapToObj(y -> new Pos(x, y)));
  200. }
  201. private void dumpMap() {
  202. // Text output
  203. System.out.println("Size: " + tiles.size());
  204. for (Pos p : tiles.keySet()) {
  205. Tile t = tiles.get(p);
  206. System.out.println("Pos: " + p + " Tile: " + t);
  207. }
  208. }
  209. private void updateBoardSize() {
  210. tileWidth = Display.getWidth() / settings.boardWidth;
  211. tileHeight = Display.getHeight() / settings.boardHeight;
  212. }
  213. public static void main(String[] args) {
  214. GameLauncher.init(GAME_NAME, 800, 800);
  215. GameLauncher.launch(new Mines(GAME_NAME));
  216. }
  217. public Settings getSettings() {
  218. return settings;
  219. }
  220. }