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 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;
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 @ModelOperation static void init(Mines model, int width, int height, int mines) {
104 List<Row> rows = model.getRows();
105 if (rows.size() != height || rows.get(0).getColumns().size() != width) {
106 rows = new ArrayList<Row>(height);
107 for (int y = 0; y < height; y++) {
108 Square[] columns = new Square[width];
109 for (int x = 0; x < width; x++) {
110 columns[x] = new Square(SquareType.UNKNOWN, false);
112 rows.add(new Row(columns));
115 for (Row row : rows) {
116 for (Square sq : row.getColumns()) {
117 sq.setState(SquareType.UNKNOWN);
123 Random r = new Random();
125 int x = r.nextInt(width);
126 int y = r.nextInt(height);
127 final Square s = rows.get(y).getColumns().get(x);
135 model.setState(GameState.IN_PROGRESS);
136 if (rows != model.getRows()) {
137 model.getRows().clear();
138 model.getRows().addAll(rows);
142 @ModelOperation static void computeMines(Mines model) {
143 List<Integer> xBombs = new ArrayList<Integer>();
144 List<Integer> yBombs = new ArrayList<Integer>();
145 final List<Row> rows = model.getRows();
146 boolean emptyHidden = false;
147 SquareType[][] arr = new SquareType[rows.size()][];
148 for (int y = 0; y < rows.size(); y++) {
149 final List<Square> columns = rows.get(y).getColumns();
150 arr[y] = new SquareType[columns.size()];
151 for (int x = 0; x < columns.size(); x++) {
152 Square sq = columns.get(x);
157 if (sq.getState().isVisible()) {
158 arr[y][x] = SquareType.N_0;
166 for (int i = 0; i < xBombs.size(); i++) {
167 int x = xBombs.get(i);
168 int y = yBombs.get(i);
170 incrementAround(arr, x, y);
172 for (int y = 0; y < rows.size(); y++) {
173 final List<Square> columns = rows.get(y).getColumns();
174 for (int x = 0; x < columns.size(); x++) {
175 Square sq = columns.get(x);
176 final SquareType newState = arr[y][x];
177 if (newState != null && newState != sq.getState()) {
178 sq.setState(newState);
184 model.setState(GameState.WON);
185 showAllBombs(model, SquareType.DISCOVERED);
186 AudioClip applause = AudioClip.create("applause.mp3");
191 private static void incrementAround(SquareType[][] arr, int x, int y) {
192 incrementAt(arr, x - 1, y - 1);
193 incrementAt(arr, x - 1, y);
194 incrementAt(arr, x - 1, y + 1);
196 incrementAt(arr, x + 1, y - 1);
197 incrementAt(arr, x + 1, y);
198 incrementAt(arr, x + 1, y + 1);
200 incrementAt(arr, x, y - 1);
201 incrementAt(arr, x, y + 1);
204 private static void incrementAt(SquareType[][] arr, int x, int y) {
205 if (y >= 0 && y < arr.length) {
206 SquareType[] r = arr[y];
207 if (x >= 0 && x < r.length) {
208 SquareType sq = r[x];
210 r[x] = sq.moreBombsAround();
216 static void showAllBombs(Mines model, SquareType state) {
217 for (Row row : model.getRows()) {
218 for (Square square : row.getColumns()) {
219 if (square.isMine()) {
220 square.setState(state);
226 @Function static void click(Mines model, Square data) {
227 if (model.getState() != GameState.IN_PROGRESS) {
230 if (data.getState() != SquareType.UNKNOWN) {
234 Square fair = atLeastOnePlaceWhereBombCantBe(model);
236 if (placeBombElseWhere(model, data)) {
237 cleanedUp(model, data);
243 Square takeFrom = tryStealBomb(model, data);
244 if (takeFrom != null) {
245 final Square fair = atLeastOnePlaceWhereBombCantBe(model);
247 takeFrom.setMine(false);
253 cleanedUp(model, data);
257 private static void cleanedUp(Mines model, Square data) {
258 AudioClip touch = AudioClip.create("move.mp3");
260 expandKnown(model, data);
261 model.computeMines();
264 private static void explosion(Mines model) {
265 showAllBombs(model, SquareType.EXPLOSION);
266 model.setState(GameState.LOST);
267 AudioClip oops = AudioClip.create("oops.mp3");
271 private static Square tryStealBomb(Mines model, Square data) {
273 final List<Row> rows = model.getRows();
274 for (int y = 0; y < rows.size(); y++) {
275 final List<Square> columns = rows.get(y).getColumns();
276 for (int x = 0; x < columns.size(); x++) {
277 Square sq = columns.get(x);
283 final boolean ok = isConsistent(model);
296 private static Square atLeastOnePlaceWhereBombCantBe(Mines model) {
297 final List<Row> rows = model.getRows();
298 Square cantBe = null;
300 for (int y = 0; y < rows.size(); y++) {
301 final List<Square> columns = rows.get(y).getColumns();
302 for (int x = 0; x < columns.size(); x++) {
303 Square sq = columns.get(x);
304 if (sq.getState() == SquareType.UNKNOWN) {
306 if (tryStealBomb(model, sq) == null) {
316 if (discovered > 5) {
323 private static boolean placeBombElseWhere(Mines model, Square moveBomb) {
324 List<Square> ok = new ArrayList<Square>();
325 moveBomb.setMine(false);
326 final List<Row> rows = model.getRows();
327 for (int y = 0; y < rows.size(); y++) {
328 final List<Square> columns = rows.get(y).getColumns();
329 for (int x = 0; x < columns.size(); x++) {
330 Square sq = columns.get(x);
331 if (sq == moveBomb || sq.isMine() || sq.getState().isVisible()) {
335 if (isConsistent(model)) {
342 moveBomb.setMine(true);
345 int r = new Random().nextInt(ok.size());
346 ok.get(r).setMine(true);
351 private static void expandKnown(Mines model, Square data) {
352 final List<Row> rows = model.getRows();
353 for (int y = 0; y < rows.size(); y++) {
354 final List<Square> columns = rows.get(y).getColumns();
355 for (int x = 0; x < columns.size(); x++) {
356 Square sq = columns.get(x);
358 expandKnown(model, x, y);
364 private static void expandKnown(Mines model, int x , int y) {
365 if (y < 0 || y >= model.getRows().size()) {
368 final List<Square> columns = model.getRows().get(y).getColumns();
369 if (x < 0 || x >= columns.size()) {
372 final Square sq = columns.get(x);
373 if (sq.getState() == SquareType.UNKNOWN) {
374 int around = around(model, x, y);
375 final SquareType t = SquareType.valueOf("N_" + around);
377 if (t == SquareType.N_0) {
378 expandKnown(model, x - 1, y - 1);
379 expandKnown(model, x - 1, y);
380 expandKnown(model, x - 1, y + 1);
381 expandKnown(model, x , y - 1);
382 expandKnown(model, x, y + 1);
383 expandKnown(model, x + 1, y - 1);
384 expandKnown(model, x + 1, y);
385 expandKnown(model, x + 1, y + 1);
390 private static int around(Mines model, int x, int y) {
392 minesAt(model, x - 1, y - 1) +
393 minesAt(model, x - 1, y) +
394 minesAt(model, x - 1, y + 1) +
395 minesAt(model, x , y - 1) +
396 minesAt(model, x, y + 1) +
397 minesAt(model, x + 1, y - 1) +
398 minesAt(model, x + 1, y) +
399 minesAt(model, x + 1, y + 1);
402 private static int minesAt(Mines model, int x, int y) {
403 if (y < 0 || y >= model.getRows().size()) {
406 final List<Square> columns = model.getRows().get(y).getColumns();
407 if (x < 0 || x >= columns.size()) {
410 Square sq = columns.get(x);
411 return sq.isMine() ? 1 : 0;
414 private static boolean isConsistent(Mines m) {
415 for (int row = 0; row < m.getRows().size(); row++) {
416 Row r = m.getRows().get(row);
417 for (int col = 0; col < r.getColumns().size(); col++) {
418 Square sq = r.getColumns().get(col);
419 if (sq.getState().isVisible()) {
420 int around = around(m, col, row);
421 if (around != sq.getState().ordinal()) {
431 * Called when page is ready
433 public static void main(String... args) throws Exception {
434 Mines m = new Mines();