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, WON, LOST;
47 @Model(className = "Row", properties = {
48 @Property(name = "columns", type = Square.class, array = true)
50 static class RowModel {
53 @Model(className = "Square", properties = {
54 @Property(name = "state", type = SquareType.class),
55 @Property(name = "mine", type = boolean.class)
57 static class SquareModel {
58 @ComputedProperty static String html(SquareType state) {
59 if (state == null) return " ";
61 case EXPLOSION: return "✗";
62 case UNKNOWN: return " ";
63 case DISCOVERED: return "✔";
64 case N_0: return " ";
66 return "ɸ" + (state.ordinal() - 1);
69 @ComputedProperty static String style(SquareType state) {
70 return state == null ? null : state.toString();
75 N_0, N_1, N_2, N_3, N_4, N_5, N_6, N_7, N_8,
76 UNKNOWN, EXPLOSION, DISCOVERED;
78 final boolean isVisible() {
79 return name().startsWith("N_");
82 final SquareType moreBombsAround() {
90 return values()[ordinal() + 1];
94 @ComputedProperty static boolean fieldShowing(GameState state) {
98 @Function static void showHelp(Mines model) {
102 @Function static void smallGame(Mines model) {
105 @Function static void normalGame(Mines model) {
106 model.init(10, 10, 10);
109 @Function static void giveUp(Mines model) {
110 showAllBombs(model, SquareType.EXPLOSION);
111 model.setState(GameState.LOST);
114 @ModelOperation static void init(Mines model, int width, int height, int mines) {
115 List<Row> rows = model.getRows();
116 if (rows.size() != height || rows.get(0).getColumns().size() != width) {
117 rows = new ArrayList<Row>(height);
118 for (int y = 0; y < height; y++) {
119 Square[] columns = new Square[width];
120 for (int x = 0; x < width; x++) {
121 columns[x] = new Square(SquareType.UNKNOWN, false);
123 rows.add(new Row(columns));
126 for (Row row : rows) {
127 for (Square sq : row.getColumns()) {
128 sq.setState(SquareType.UNKNOWN);
134 Random r = new Random();
136 int x = r.nextInt(width);
137 int y = r.nextInt(height);
138 final Square s = rows.get(y).getColumns().get(x);
146 model.setState(GameState.IN_PROGRESS);
147 if (rows != model.getRows()) {
148 model.getRows().clear();
149 model.getRows().addAll(rows);
153 @ModelOperation static void computeMines(Mines model) {
154 List<Integer> xBombs = new ArrayList<Integer>();
155 List<Integer> yBombs = new ArrayList<Integer>();
156 final List<Row> rows = model.getRows();
157 boolean emptyHidden = false;
158 SquareType[][] arr = new SquareType[rows.size()][];
159 for (int y = 0; y < rows.size(); y++) {
160 final List<Square> columns = rows.get(y).getColumns();
161 arr[y] = new SquareType[columns.size()];
162 for (int x = 0; x < columns.size(); x++) {
163 Square sq = columns.get(x);
168 if (sq.getState().isVisible()) {
169 arr[y][x] = SquareType.N_0;
177 for (int i = 0; i < xBombs.size(); i++) {
178 int x = xBombs.get(i);
179 int y = yBombs.get(i);
181 incrementAround(arr, x, y);
183 for (int y = 0; y < rows.size(); y++) {
184 final List<Square> columns = rows.get(y).getColumns();
185 for (int x = 0; x < columns.size(); x++) {
186 Square sq = columns.get(x);
187 final SquareType newState = arr[y][x];
188 if (newState != null && newState != sq.getState()) {
189 sq.setState(newState);
195 model.setState(GameState.WON);
196 showAllBombs(model, SquareType.DISCOVERED);
197 AudioClip applause = AudioClip.create("applause.wav");
202 private static void incrementAround(SquareType[][] arr, int x, int y) {
203 incrementAt(arr, x - 1, y - 1);
204 incrementAt(arr, x - 1, y);
205 incrementAt(arr, x - 1, y + 1);
207 incrementAt(arr, x + 1, y - 1);
208 incrementAt(arr, x + 1, y);
209 incrementAt(arr, x + 1, y + 1);
211 incrementAt(arr, x, y - 1);
212 incrementAt(arr, x, y + 1);
215 private static void incrementAt(SquareType[][] arr, int x, int y) {
216 if (y >= 0 && y < arr.length) {
217 SquareType[] r = arr[y];
218 if (x >= 0 && x < r.length) {
219 SquareType sq = r[x];
221 r[x] = sq.moreBombsAround();
227 static void showAllBombs(Mines model, SquareType state) {
228 for (Row row : model.getRows()) {
229 for (Square square : row.getColumns()) {
230 if (square.isMine()) {
231 square.setState(state);
237 @Function static void click(Mines model, Square data) {
238 if (model.getState() != GameState.IN_PROGRESS) {
241 if (data.getState() != SquareType.UNKNOWN) {
245 Square fair = atLeastOnePlaceWhereBombCantBe(model);
247 if (placeBombElseWhere(model, data)) {
248 cleanedUp(model, data);
254 Square takeFrom = tryStealBomb(model, data);
255 if (takeFrom != null) {
256 final Square fair = atLeastOnePlaceWhereBombCantBe(model);
258 takeFrom.setMine(false);
264 cleanedUp(model, data);
268 private static void cleanedUp(Mines model, Square data) {
269 AudioClip touch = AudioClip.create("move.mp3");
271 expandKnown(model, data);
272 model.computeMines();
275 private static void explosion(Mines model) {
276 showAllBombs(model, SquareType.EXPLOSION);
277 model.setState(GameState.LOST);
278 AudioClip oops = AudioClip.create("oops.wav");
282 private static Square tryStealBomb(Mines model, Square data) {
284 final List<Row> rows = model.getRows();
285 for (int y = 0; y < rows.size(); y++) {
286 final List<Square> columns = rows.get(y).getColumns();
287 for (int x = 0; x < columns.size(); x++) {
288 Square sq = columns.get(x);
294 final boolean ok = isConsistent(model);
307 private static Square atLeastOnePlaceWhereBombCantBe(Mines model) {
308 final List<Row> rows = model.getRows();
309 Square cantBe = null;
311 for (int y = 0; y < rows.size(); y++) {
312 final List<Square> columns = rows.get(y).getColumns();
313 for (int x = 0; x < columns.size(); x++) {
314 Square sq = columns.get(x);
315 if (sq.getState() == SquareType.UNKNOWN) {
317 if (tryStealBomb(model, sq) == null) {
327 if (discovered > 5) {
334 private static boolean placeBombElseWhere(Mines model, Square moveBomb) {
335 List<Square> ok = new ArrayList<Square>();
336 moveBomb.setMine(false);
337 final List<Row> rows = model.getRows();
338 for (int y = 0; y < rows.size(); y++) {
339 final List<Square> columns = rows.get(y).getColumns();
340 for (int x = 0; x < columns.size(); x++) {
341 Square sq = columns.get(x);
342 if (sq == moveBomb || sq.isMine() || sq.getState().isVisible()) {
346 if (isConsistent(model)) {
353 moveBomb.setMine(true);
356 int r = new Random().nextInt(ok.size());
357 ok.get(r).setMine(true);
362 private static void expandKnown(Mines model, Square data) {
363 final List<Row> rows = model.getRows();
364 for (int y = 0; y < rows.size(); y++) {
365 final List<Square> columns = rows.get(y).getColumns();
366 for (int x = 0; x < columns.size(); x++) {
367 Square sq = columns.get(x);
369 expandKnown(model, x, y);
375 private static void expandKnown(Mines model, int x , int y) {
376 if (y < 0 || y >= model.getRows().size()) {
379 final List<Square> columns = model.getRows().get(y).getColumns();
380 if (x < 0 || x >= columns.size()) {
383 final Square sq = columns.get(x);
384 if (sq.getState() == SquareType.UNKNOWN) {
385 int around = around(model, x, y);
386 final SquareType t = SquareType.valueOf("N_" + around);
388 if (t == SquareType.N_0) {
389 expandKnown(model, x - 1, y - 1);
390 expandKnown(model, x - 1, y);
391 expandKnown(model, x - 1, y + 1);
392 expandKnown(model, x , y - 1);
393 expandKnown(model, x, y + 1);
394 expandKnown(model, x + 1, y - 1);
395 expandKnown(model, x + 1, y);
396 expandKnown(model, x + 1, y + 1);
401 private static int around(Mines model, int x, int y) {
403 minesAt(model, x - 1, y - 1) +
404 minesAt(model, x - 1, y) +
405 minesAt(model, x - 1, y + 1) +
406 minesAt(model, x , y - 1) +
407 minesAt(model, x, y + 1) +
408 minesAt(model, x + 1, y - 1) +
409 minesAt(model, x + 1, y) +
410 minesAt(model, x + 1, y + 1);
413 private static int minesAt(Mines model, int x, int y) {
414 if (y < 0 || y >= model.getRows().size()) {
417 final List<Square> columns = model.getRows().get(y).getColumns();
418 if (x < 0 || x >= columns.size()) {
421 Square sq = columns.get(x);
422 return sq.isMine() ? 1 : 0;
425 private static boolean isConsistent(Mines m) {
426 for (int row = 0; row < m.getRows().size(); row++) {
427 Row r = m.getRows().get(row);
428 for (int col = 0; col < r.getColumns().size(); col++) {
429 Square sq = r.getColumns().get(col);
430 if (sq.getState().isVisible()) {
431 int around = around(m, col, row);
432 if (around != sq.getState().ordinal()) {
442 * Called when page is ready
444 public static void main(String... args) throws Exception {
445 Mines m = new Mines();