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);
253 if (allMarked(model)) {
254 model.setState(GameState.WON);
258 if (data.getState() != SquareType.UNKNOWN) {
262 Square fair = atLeastOnePlaceWhereBombCantBe(model);
264 if (placeBombElseWhere(model, data)) {
265 cleanedUp(model, data);
271 Square takeFrom = tryStealBomb(model, data);
272 if (takeFrom != null) {
273 final Square fair = atLeastOnePlaceWhereBombCantBe(model);
275 takeFrom.setMine(false);
281 cleanedUp(model, data);
285 private static void cleanedUp(Mines model, Square data) {
286 AudioClip touch = AudioClip.create("move.mp3");
288 expandKnown(model, data);
289 model.computeMines();
292 private static void explosion(Mines model) {
293 showAllBombs(model, SquareType.EXPLOSION);
294 model.setState(GameState.LOST);
295 AudioClip oops = AudioClip.create("oops.mp3");
299 private static Square tryStealBomb(Mines model, Square data) {
301 final List<Row> rows = model.getRows();
302 for (int y = 0; y < rows.size(); y++) {
303 final List<Square> columns = rows.get(y).getColumns();
304 for (int x = 0; x < columns.size(); x++) {
305 Square sq = columns.get(x);
311 final boolean ok = isConsistent(model);
324 private static Square atLeastOnePlaceWhereBombCantBe(Mines model) {
325 final List<Row> rows = model.getRows();
326 Square cantBe = null;
328 for (int y = 0; y < rows.size(); y++) {
329 final List<Square> columns = rows.get(y).getColumns();
330 for (int x = 0; x < columns.size(); x++) {
331 Square sq = columns.get(x);
332 if (sq.getState() == SquareType.UNKNOWN) {
334 if (tryStealBomb(model, sq) == null) {
344 if (discovered > 5) {
351 private static boolean placeBombElseWhere(Mines model, Square moveBomb) {
352 List<Square> ok = new ArrayList<Square>();
353 moveBomb.setMine(false);
354 final List<Row> rows = model.getRows();
355 for (int y = 0; y < rows.size(); y++) {
356 final List<Square> columns = rows.get(y).getColumns();
357 for (int x = 0; x < columns.size(); x++) {
358 Square sq = columns.get(x);
359 if (sq == moveBomb || sq.isMine() || sq.getState().isVisible()) {
363 if (isConsistent(model)) {
370 moveBomb.setMine(true);
373 int r = new Random().nextInt(ok.size());
374 ok.get(r).setMine(true);
379 private static void expandKnown(Mines model, Square data) {
380 final List<Row> rows = model.getRows();
381 for (int y = 0; y < rows.size(); y++) {
382 final List<Square> columns = rows.get(y).getColumns();
383 for (int x = 0; x < columns.size(); x++) {
384 Square sq = columns.get(x);
386 expandKnown(model, x, y);
392 private static void expandKnown(Mines model, int x , int y) {
393 if (y < 0 || y >= model.getRows().size()) {
396 final List<Square> columns = model.getRows().get(y).getColumns();
397 if (x < 0 || x >= columns.size()) {
400 final Square sq = columns.get(x);
401 if (sq.getState() == SquareType.UNKNOWN) {
402 int around = around(model, x, y);
403 final SquareType t = SquareType.valueOf("N_" + around);
405 if (t == SquareType.N_0) {
406 expandKnown(model, x - 1, y - 1);
407 expandKnown(model, x - 1, y);
408 expandKnown(model, x - 1, y + 1);
409 expandKnown(model, x , y - 1);
410 expandKnown(model, x, y + 1);
411 expandKnown(model, x + 1, y - 1);
412 expandKnown(model, x + 1, y);
413 expandKnown(model, x + 1, y + 1);
418 private static int around(Mines model, int x, int y) {
420 minesAt(model, x - 1, y - 1) +
421 minesAt(model, x - 1, y) +
422 minesAt(model, x - 1, y + 1) +
423 minesAt(model, x , y - 1) +
424 minesAt(model, x, y + 1) +
425 minesAt(model, x + 1, y - 1) +
426 minesAt(model, x + 1, y) +
427 minesAt(model, x + 1, y + 1);
430 private static int minesAt(Mines model, int x, int y) {
431 if (y < 0 || y >= model.getRows().size()) {
434 final List<Square> columns = model.getRows().get(y).getColumns();
435 if (x < 0 || x >= columns.size()) {
438 Square sq = columns.get(x);
439 return sq.isMine() ? 1 : 0;
442 private static boolean isConsistent(Mines m) {
443 for (int row = 0; row < m.getRows().size(); row++) {
444 Row r = m.getRows().get(row);
445 for (int col = 0; col < r.getColumns().size(); col++) {
446 Square sq = r.getColumns().get(col);
447 if (sq.getState().isVisible()) {
448 int around = around(m, col, row);
449 if (around != sq.getState().ordinal()) {
458 private static boolean allMarked(Mines m) {
459 for (Row r : m.getRows()) {
460 for (Square sq : r.getColumns()) {
461 if (sq.isMine() == (sq.getState() != SquareType.MARKED)) {
466 for (Row r : m.getRows()) {
467 for (Square sq : r.getColumns()) {
469 sq.setState(SquareType.DISCOVERED);
471 sq.setState(SquareType.N_0);
480 * Called when page is ready
482 public static void main(String... args) throws Exception {
483 Mines m = new Mines();