Indicate the mine selection mode by blinking the danger sign on all undiscovered squares
2 * The MIT License (MIT)
4 * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 package org.apidesign.demo.minesweeper;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.Random;
29 import net.java.html.json.ComputedProperty;
30 import net.java.html.json.Function;
31 import net.java.html.json.Model;
32 import net.java.html.json.ModelOperation;
33 import net.java.html.json.Property;
34 import net.java.html.sound.AudioClip;
36 /** Model of the mine field.
38 @Model(className = "Mines", properties = {
39 @Property(name = "state", type = MinesModel.GameState.class),
40 @Property(name = "rows", type = Row.class, array = true),
42 public final class MinesModel {
44 IN_PROGRESS, MARKING_MINE, WON, LOST;
47 @ComputedProperty static String gameStyle(GameState state) {
48 return state == GameState.MARKING_MINE ? "MARKING" : null;
51 @Model(className = "Row", properties = {
52 @Property(name = "columns", type = Square.class, array = true)
54 static class RowModel {
57 @Model(className = "Square", properties = {
58 @Property(name = "state", type = SquareType.class),
59 @Property(name = "mine", type = boolean.class)
61 static class SquareModel {
62 @ComputedProperty static String style(SquareType state) {
63 return state == null ? null : state.toString();
68 N_0, N_1, N_2, N_3, N_4, N_5, N_6, N_7, N_8,
69 UNKNOWN, EXPLOSION, DISCOVERED, MARKED;
71 final boolean isVisible() {
72 return name().startsWith("N_");
75 final SquareType moreBombsAround() {
83 return values()[ordinal() + 1];
87 @ComputedProperty static boolean fieldShowing(GameState state) {
91 @Function static void showHelp(Mines model) {
95 @Function static void smallGame(Mines model) {
98 @Function static void normalGame(Mines model) {
99 model.init(10, 10, 10);
102 @Function static void giveUp(Mines model) {
103 showAllBombs(model, SquareType.EXPLOSION);
104 model.setState(GameState.LOST);
107 @Function static void markMine(Mines model) {
108 if (model.getState() == GameState.IN_PROGRESS) {
109 model.setState(GameState.MARKING_MINE);
113 @ModelOperation static void init(Mines model, int width, int height, int mines) {
114 List<Row> rows = model.getRows();
115 if (rows.size() != height || rows.get(0).getColumns().size() != width) {
116 rows = new ArrayList<Row>(height);
117 for (int y = 0; y < height; y++) {
118 Square[] columns = new Square[width];
119 for (int x = 0; x < width; x++) {
120 columns[x] = new Square(SquareType.UNKNOWN, false);
122 rows.add(new Row(columns));
125 for (Row row : rows) {
126 for (Square sq : row.getColumns()) {
127 sq.setState(SquareType.UNKNOWN);
133 Random r = new Random();
135 int x = r.nextInt(width);
136 int y = r.nextInt(height);
137 final Square s = rows.get(y).getColumns().get(x);
145 model.setState(GameState.IN_PROGRESS);
146 if (rows != model.getRows()) {
147 model.getRows().clear();
148 model.getRows().addAll(rows);
152 @ModelOperation static void computeMines(Mines model) {
153 List<Integer> xBombs = new ArrayList<Integer>();
154 List<Integer> yBombs = new ArrayList<Integer>();
155 final List<Row> rows = model.getRows();
156 boolean emptyHidden = false;
157 SquareType[][] arr = new SquareType[rows.size()][];
158 for (int y = 0; y < rows.size(); y++) {
159 final List<Square> columns = rows.get(y).getColumns();
160 arr[y] = new SquareType[columns.size()];
161 for (int x = 0; x < columns.size(); x++) {
162 Square sq = columns.get(x);
167 if (sq.getState().isVisible()) {
168 arr[y][x] = SquareType.N_0;
176 for (int i = 0; i < xBombs.size(); i++) {
177 int x = xBombs.get(i);
178 int y = yBombs.get(i);
180 incrementAround(arr, x, y);
182 for (int y = 0; y < rows.size(); y++) {
183 final List<Square> columns = rows.get(y).getColumns();
184 for (int x = 0; x < columns.size(); x++) {
185 Square sq = columns.get(x);
186 final SquareType newState = arr[y][x];
187 if (newState != null && newState != sq.getState()) {
188 sq.setState(newState);
194 model.setState(GameState.WON);
195 showAllBombs(model, SquareType.DISCOVERED);
196 AudioClip applause = AudioClip.create("applause.mp3");
201 private static void incrementAround(SquareType[][] arr, int x, int y) {
202 incrementAt(arr, x - 1, y - 1);
203 incrementAt(arr, x - 1, y);
204 incrementAt(arr, x - 1, y + 1);
206 incrementAt(arr, x + 1, y - 1);
207 incrementAt(arr, x + 1, y);
208 incrementAt(arr, x + 1, y + 1);
210 incrementAt(arr, x, y - 1);
211 incrementAt(arr, x, y + 1);
214 private static void incrementAt(SquareType[][] arr, int x, int y) {
215 if (y >= 0 && y < arr.length) {
216 SquareType[] r = arr[y];
217 if (x >= 0 && x < r.length) {
218 SquareType sq = r[x];
220 r[x] = sq.moreBombsAround();
226 static void showAllBombs(Mines model, SquareType state) {
227 for (Row row : model.getRows()) {
228 for (Square square : row.getColumns()) {
229 if (square.isMine()) {
230 square.setState(state);
236 @Function static void click(Mines model, Square data) {
237 if (model.getState() == GameState.MARKING_MINE) {
238 if (data.getState() == SquareType.UNKNOWN) {
239 data.setState(SquareType.MARKED);
240 if (allMarked(model)) {
241 model.setState(GameState.WON);
245 model.setState(GameState.IN_PROGRESS);
248 if (model.getState() != GameState.IN_PROGRESS) {
251 if (data.getState() == SquareType.MARKED) {
252 data.setState(SquareType.UNKNOWN);
255 if (data.getState() != SquareType.UNKNOWN) {
259 Square fair = atLeastOnePlaceWhereBombCantBe(model);
261 if (placeBombElseWhere(model, data)) {
262 cleanedUp(model, data);
268 Square takeFrom = tryStealBomb(model, data);
269 if (takeFrom != null) {
270 final Square fair = atLeastOnePlaceWhereBombCantBe(model);
272 takeFrom.setMine(false);
278 cleanedUp(model, data);
282 private static void cleanedUp(Mines model, Square data) {
283 AudioClip touch = AudioClip.create("move.mp3");
285 expandKnown(model, data);
286 model.computeMines();
289 private static void explosion(Mines model) {
290 showAllBombs(model, SquareType.EXPLOSION);
291 model.setState(GameState.LOST);
292 AudioClip oops = AudioClip.create("oops.mp3");
296 private static Square tryStealBomb(Mines model, Square data) {
298 final List<Row> rows = model.getRows();
299 for (int y = 0; y < rows.size(); y++) {
300 final List<Square> columns = rows.get(y).getColumns();
301 for (int x = 0; x < columns.size(); x++) {
302 Square sq = columns.get(x);
308 final boolean ok = isConsistent(model);
321 private static Square atLeastOnePlaceWhereBombCantBe(Mines model) {
322 final List<Row> rows = model.getRows();
323 Square cantBe = null;
325 for (int y = 0; y < rows.size(); y++) {
326 final List<Square> columns = rows.get(y).getColumns();
327 for (int x = 0; x < columns.size(); x++) {
328 Square sq = columns.get(x);
329 if (sq.getState() == SquareType.UNKNOWN) {
331 if (tryStealBomb(model, sq) == null) {
341 if (discovered > 5) {
348 private static boolean placeBombElseWhere(Mines model, Square moveBomb) {
349 List<Square> ok = new ArrayList<Square>();
350 moveBomb.setMine(false);
351 final List<Row> rows = model.getRows();
352 for (int y = 0; y < rows.size(); y++) {
353 final List<Square> columns = rows.get(y).getColumns();
354 for (int x = 0; x < columns.size(); x++) {
355 Square sq = columns.get(x);
356 if (sq == moveBomb || sq.isMine() || sq.getState().isVisible()) {
360 if (isConsistent(model)) {
367 moveBomb.setMine(true);
370 int r = new Random().nextInt(ok.size());
371 ok.get(r).setMine(true);
376 private static void expandKnown(Mines model, Square data) {
377 final List<Row> rows = model.getRows();
378 for (int y = 0; y < rows.size(); y++) {
379 final List<Square> columns = rows.get(y).getColumns();
380 for (int x = 0; x < columns.size(); x++) {
381 Square sq = columns.get(x);
383 expandKnown(model, x, y);
389 private static void expandKnown(Mines model, int x , int y) {
390 if (y < 0 || y >= model.getRows().size()) {
393 final List<Square> columns = model.getRows().get(y).getColumns();
394 if (x < 0 || x >= columns.size()) {
397 final Square sq = columns.get(x);
398 if (sq.getState() == SquareType.UNKNOWN) {
399 int around = around(model, x, y);
400 final SquareType t = SquareType.valueOf("N_" + around);
402 if (t == SquareType.N_0) {
403 expandKnown(model, x - 1, y - 1);
404 expandKnown(model, x - 1, y);
405 expandKnown(model, x - 1, y + 1);
406 expandKnown(model, x , y - 1);
407 expandKnown(model, x, y + 1);
408 expandKnown(model, x + 1, y - 1);
409 expandKnown(model, x + 1, y);
410 expandKnown(model, x + 1, y + 1);
415 private static int around(Mines model, int x, int y) {
417 minesAt(model, x - 1, y - 1) +
418 minesAt(model, x - 1, y) +
419 minesAt(model, x - 1, y + 1) +
420 minesAt(model, x , y - 1) +
421 minesAt(model, x, y + 1) +
422 minesAt(model, x + 1, y - 1) +
423 minesAt(model, x + 1, y) +
424 minesAt(model, x + 1, y + 1);
427 private static int minesAt(Mines model, int x, int y) {
428 if (y < 0 || y >= model.getRows().size()) {
431 final List<Square> columns = model.getRows().get(y).getColumns();
432 if (x < 0 || x >= columns.size()) {
435 Square sq = columns.get(x);
436 return sq.isMine() ? 1 : 0;
439 private static boolean isConsistent(Mines m) {
440 for (int row = 0; row < m.getRows().size(); row++) {
441 Row r = m.getRows().get(row);
442 for (int col = 0; col < r.getColumns().size(); col++) {
443 Square sq = r.getColumns().get(col);
444 if (sq.getState().isVisible()) {
445 int around = around(m, col, row);
446 if (around != sq.getState().ordinal()) {
455 private static boolean allMarked(Mines m) {
456 for (Row r : m.getRows()) {
457 for (Square sq : r.getColumns()) {
458 if (sq.isMine() && sq.getState() != SquareType.MARKED) {
463 for (Row r : m.getRows()) {
464 for (Square sq : r.getColumns()) {
466 sq.setState(SquareType.DISCOVERED);
468 sq.setState(SquareType.N_0);
477 * Called when page is ready
479 public static void main(String... args) throws Exception {
480 Mines m = new Mines();