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 @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 style(SquareType state) {
59 return state == null ? null : state.toString();
64 N_0, N_1, N_2, N_3, N_4, N_5, N_6, N_7, N_8,
65 UNKNOWN, EXPLOSION, DISCOVERED, MARKED;
67 final boolean isVisible() {
68 return name().startsWith("N_");
71 final SquareType moreBombsAround() {
79 return values()[ordinal() + 1];
83 @ComputedProperty static boolean fieldShowing(GameState state) {
87 @Function static void showHelp(Mines model) {
91 @Function static void smallGame(Mines model) {
94 @Function static void normalGame(Mines model) {
95 model.init(10, 10, 10);
98 @Function static void giveUp(Mines model) {
99 showAllBombs(model, SquareType.EXPLOSION);
100 model.setState(GameState.LOST);
103 @Function static void markMine(Mines model) {
104 if (model.getState() == GameState.IN_PROGRESS) {
105 model.setState(GameState.MARKING_MINE);
109 @ModelOperation static void init(Mines model, int width, int height, int mines) {
110 List<Row> rows = model.getRows();
111 if (rows.size() != height || rows.get(0).getColumns().size() != width) {
112 rows = new ArrayList<Row>(height);
113 for (int y = 0; y < height; y++) {
114 Square[] columns = new Square[width];
115 for (int x = 0; x < width; x++) {
116 columns[x] = new Square(SquareType.UNKNOWN, false);
118 rows.add(new Row(columns));
121 for (Row row : rows) {
122 for (Square sq : row.getColumns()) {
123 sq.setState(SquareType.UNKNOWN);
129 Random r = new Random();
131 int x = r.nextInt(width);
132 int y = r.nextInt(height);
133 final Square s = rows.get(y).getColumns().get(x);
141 model.setState(GameState.IN_PROGRESS);
142 if (rows != model.getRows()) {
143 model.getRows().clear();
144 model.getRows().addAll(rows);
148 @ModelOperation static void computeMines(Mines model) {
149 List<Integer> xBombs = new ArrayList<Integer>();
150 List<Integer> yBombs = new ArrayList<Integer>();
151 final List<Row> rows = model.getRows();
152 boolean emptyHidden = false;
153 SquareType[][] arr = new SquareType[rows.size()][];
154 for (int y = 0; y < rows.size(); y++) {
155 final List<Square> columns = rows.get(y).getColumns();
156 arr[y] = new SquareType[columns.size()];
157 for (int x = 0; x < columns.size(); x++) {
158 Square sq = columns.get(x);
163 if (sq.getState().isVisible()) {
164 arr[y][x] = SquareType.N_0;
172 for (int i = 0; i < xBombs.size(); i++) {
173 int x = xBombs.get(i);
174 int y = yBombs.get(i);
176 incrementAround(arr, x, y);
178 for (int y = 0; y < rows.size(); y++) {
179 final List<Square> columns = rows.get(y).getColumns();
180 for (int x = 0; x < columns.size(); x++) {
181 Square sq = columns.get(x);
182 final SquareType newState = arr[y][x];
183 if (newState != null && newState != sq.getState()) {
184 sq.setState(newState);
190 model.setState(GameState.WON);
191 showAllBombs(model, SquareType.DISCOVERED);
192 AudioClip applause = AudioClip.create("applause.mp3");
197 private static void incrementAround(SquareType[][] arr, int x, int y) {
198 incrementAt(arr, x - 1, y - 1);
199 incrementAt(arr, x - 1, y);
200 incrementAt(arr, x - 1, y + 1);
202 incrementAt(arr, x + 1, y - 1);
203 incrementAt(arr, x + 1, y);
204 incrementAt(arr, x + 1, y + 1);
206 incrementAt(arr, x, y - 1);
207 incrementAt(arr, x, y + 1);
210 private static void incrementAt(SquareType[][] arr, int x, int y) {
211 if (y >= 0 && y < arr.length) {
212 SquareType[] r = arr[y];
213 if (x >= 0 && x < r.length) {
214 SquareType sq = r[x];
216 r[x] = sq.moreBombsAround();
222 static void showAllBombs(Mines model, SquareType state) {
223 for (Row row : model.getRows()) {
224 for (Square square : row.getColumns()) {
225 if (square.isMine()) {
226 square.setState(state);
232 @Function static void click(Mines model, Square data) {
233 if (model.getState() == GameState.MARKING_MINE) {
234 if (data.getState() == SquareType.UNKNOWN) {
235 data.setState(SquareType.MARKED);
237 model.setState(GameState.IN_PROGRESS);
240 if (model.getState() != GameState.IN_PROGRESS) {
243 if (data.getState() == SquareType.MARKED) {
244 data.setState(SquareType.UNKNOWN);
247 if (data.getState() != SquareType.UNKNOWN) {
251 Square fair = atLeastOnePlaceWhereBombCantBe(model);
253 if (placeBombElseWhere(model, data)) {
254 cleanedUp(model, data);
260 Square takeFrom = tryStealBomb(model, data);
261 if (takeFrom != null) {
262 final Square fair = atLeastOnePlaceWhereBombCantBe(model);
264 takeFrom.setMine(false);
270 cleanedUp(model, data);
274 private static void cleanedUp(Mines model, Square data) {
275 AudioClip touch = AudioClip.create("move.mp3");
277 expandKnown(model, data);
278 model.computeMines();
281 private static void explosion(Mines model) {
282 showAllBombs(model, SquareType.EXPLOSION);
283 model.setState(GameState.LOST);
284 AudioClip oops = AudioClip.create("oops.mp3");
288 private static Square tryStealBomb(Mines model, Square data) {
290 final List<Row> rows = model.getRows();
291 for (int y = 0; y < rows.size(); y++) {
292 final List<Square> columns = rows.get(y).getColumns();
293 for (int x = 0; x < columns.size(); x++) {
294 Square sq = columns.get(x);
300 final boolean ok = isConsistent(model);
313 private static Square atLeastOnePlaceWhereBombCantBe(Mines model) {
314 final List<Row> rows = model.getRows();
315 Square cantBe = null;
317 for (int y = 0; y < rows.size(); y++) {
318 final List<Square> columns = rows.get(y).getColumns();
319 for (int x = 0; x < columns.size(); x++) {
320 Square sq = columns.get(x);
321 if (sq.getState() == SquareType.UNKNOWN) {
323 if (tryStealBomb(model, sq) == null) {
333 if (discovered > 5) {
340 private static boolean placeBombElseWhere(Mines model, Square moveBomb) {
341 List<Square> ok = new ArrayList<Square>();
342 moveBomb.setMine(false);
343 final List<Row> rows = model.getRows();
344 for (int y = 0; y < rows.size(); y++) {
345 final List<Square> columns = rows.get(y).getColumns();
346 for (int x = 0; x < columns.size(); x++) {
347 Square sq = columns.get(x);
348 if (sq == moveBomb || sq.isMine() || sq.getState().isVisible()) {
352 if (isConsistent(model)) {
359 moveBomb.setMine(true);
362 int r = new Random().nextInt(ok.size());
363 ok.get(r).setMine(true);
368 private static void expandKnown(Mines model, Square data) {
369 final List<Row> rows = model.getRows();
370 for (int y = 0; y < rows.size(); y++) {
371 final List<Square> columns = rows.get(y).getColumns();
372 for (int x = 0; x < columns.size(); x++) {
373 Square sq = columns.get(x);
375 expandKnown(model, x, y);
381 private static void expandKnown(Mines model, int x , int y) {
382 if (y < 0 || y >= model.getRows().size()) {
385 final List<Square> columns = model.getRows().get(y).getColumns();
386 if (x < 0 || x >= columns.size()) {
389 final Square sq = columns.get(x);
390 if (sq.getState() == SquareType.UNKNOWN) {
391 int around = around(model, x, y);
392 final SquareType t = SquareType.valueOf("N_" + around);
394 if (t == SquareType.N_0) {
395 expandKnown(model, x - 1, y - 1);
396 expandKnown(model, x - 1, y);
397 expandKnown(model, x - 1, y + 1);
398 expandKnown(model, x , y - 1);
399 expandKnown(model, x, y + 1);
400 expandKnown(model, x + 1, y - 1);
401 expandKnown(model, x + 1, y);
402 expandKnown(model, x + 1, y + 1);
407 private static int around(Mines model, int x, int y) {
409 minesAt(model, x - 1, y - 1) +
410 minesAt(model, x - 1, y) +
411 minesAt(model, x - 1, y + 1) +
412 minesAt(model, x , y - 1) +
413 minesAt(model, x, y + 1) +
414 minesAt(model, x + 1, y - 1) +
415 minesAt(model, x + 1, y) +
416 minesAt(model, x + 1, y + 1);
419 private static int minesAt(Mines model, int x, int y) {
420 if (y < 0 || y >= model.getRows().size()) {
423 final List<Square> columns = model.getRows().get(y).getColumns();
424 if (x < 0 || x >= columns.size()) {
427 Square sq = columns.get(x);
428 return sq.isMine() ? 1 : 0;
431 private static boolean isConsistent(Mines m) {
432 for (int row = 0; row < m.getRows().size(); row++) {
433 Row r = m.getRows().get(row);
434 for (int col = 0; col < r.getColumns().size(); col++) {
435 Square sq = r.getColumns().get(col);
436 if (sq.getState().isVisible()) {
437 int around = around(m, col, row);
438 if (around != sq.getState().ordinal()) {
448 * Called when page is ready
450 public static void main(String... args) throws Exception {
451 Mines m = new Mines();