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", targetId = "", 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" : "PLAYING";
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 @ComputedProperty static boolean gameInProgress(GameState state) {
92 return state == GameState.IN_PROGRESS;
95 @Function static void showHelp(Mines model) {
99 @Function static void smallGame(Mines model) {
102 @Function static void normalGame(Mines model) {
103 model.init(10, 10, 10);
106 @Function static void giveUp(Mines model) {
107 showAllBombs(model, SquareType.EXPLOSION);
108 model.setState(GameState.LOST);
111 @Function static void markMine(Mines model) {
112 if (model.getState() == GameState.IN_PROGRESS) {
113 model.setState(GameState.MARKING_MINE);
117 @ModelOperation static void init(Mines model, int width, int height, int mines) {
118 List<Row> rows = model.getRows();
119 if (rows.size() != height || rows.get(0).getColumns().size() != width) {
120 rows = new ArrayList<Row>(height);
121 for (int y = 0; y < height; y++) {
122 Square[] columns = new Square[width];
123 for (int x = 0; x < width; x++) {
124 columns[x] = new Square(SquareType.UNKNOWN, false);
126 rows.add(new Row(columns));
129 for (Row row : rows) {
130 for (Square sq : row.getColumns()) {
131 sq.setState(SquareType.UNKNOWN);
137 Random r = new Random();
139 int x = r.nextInt(width);
140 int y = r.nextInt(height);
141 final Square s = rows.get(y).getColumns().get(x);
149 model.setState(GameState.IN_PROGRESS);
150 if (rows != model.getRows()) {
151 model.getRows().clear();
152 model.getRows().addAll(rows);
156 @ModelOperation static void computeMines(Mines model) {
157 List<Integer> xBombs = new ArrayList<Integer>();
158 List<Integer> yBombs = new ArrayList<Integer>();
159 final List<Row> rows = model.getRows();
160 boolean emptyHidden = false;
161 SquareType[][] arr = new SquareType[rows.size()][];
162 for (int y = 0; y < rows.size(); y++) {
163 final List<Square> columns = rows.get(y).getColumns();
164 arr[y] = new SquareType[columns.size()];
165 for (int x = 0; x < columns.size(); x++) {
166 Square sq = columns.get(x);
171 if (sq.getState().isVisible()) {
172 arr[y][x] = SquareType.N_0;
180 for (int i = 0; i < xBombs.size(); i++) {
181 int x = xBombs.get(i);
182 int y = yBombs.get(i);
184 incrementAround(arr, x, y);
186 for (int y = 0; y < rows.size(); y++) {
187 final List<Square> columns = rows.get(y).getColumns();
188 for (int x = 0; x < columns.size(); x++) {
189 Square sq = columns.get(x);
190 final SquareType newState = arr[y][x];
191 if (newState != null && newState != sq.getState()) {
192 sq.setState(newState);
198 model.setState(GameState.WON);
199 showAllBombs(model, SquareType.DISCOVERED);
200 AudioClip applause = AudioClip.create("applause.mp3");
205 private static void incrementAround(SquareType[][] arr, int x, int y) {
206 incrementAt(arr, x - 1, y - 1);
207 incrementAt(arr, x - 1, y);
208 incrementAt(arr, x - 1, y + 1);
210 incrementAt(arr, x + 1, y - 1);
211 incrementAt(arr, x + 1, y);
212 incrementAt(arr, x + 1, y + 1);
214 incrementAt(arr, x, y - 1);
215 incrementAt(arr, x, y + 1);
218 private static void incrementAt(SquareType[][] arr, int x, int y) {
219 if (y >= 0 && y < arr.length) {
220 SquareType[] r = arr[y];
221 if (x >= 0 && x < r.length) {
222 SquareType sq = r[x];
224 r[x] = sq.moreBombsAround();
230 static void showAllBombs(Mines model, SquareType state) {
231 for (Row row : model.getRows()) {
232 for (Square square : row.getColumns()) {
233 if (square.isMine()) {
234 square.setState(state);
240 @Function static void click(Mines model, Square data) {
241 if (model.getState() == GameState.MARKING_MINE) {
242 if (data.getState() == SquareType.UNKNOWN) {
243 data.setState(SquareType.MARKED);
244 if (allMarked(model)) {
245 model.setState(GameState.WON);
249 model.setState(GameState.IN_PROGRESS);
252 if (model.getState() != GameState.IN_PROGRESS) {
255 if (data.getState() == SquareType.MARKED) {
256 data.setState(SquareType.UNKNOWN);
257 if (allMarked(model)) {
258 model.setState(GameState.WON);
262 if (data.getState() != SquareType.UNKNOWN) {
266 Square fair = atLeastOnePlaceWhereBombCantBe(model);
268 if (placeBombElseWhere(model, data)) {
269 cleanedUp(model, data);
275 Square takeFrom = tryStealBomb(model, data);
276 if (takeFrom != null) {
277 final Square fair = atLeastOnePlaceWhereBombCantBe(model);
279 takeFrom.setMine(false);
285 cleanedUp(model, data);
289 private static void cleanedUp(Mines model, Square data) {
290 AudioClip touch = AudioClip.create("move.mp3");
292 expandKnown(model, data);
293 model.computeMines();
296 private static void explosion(Mines model) {
297 showAllBombs(model, SquareType.EXPLOSION);
298 model.setState(GameState.LOST);
299 AudioClip oops = AudioClip.create("oops.mp3");
303 private static Square tryStealBomb(Mines model, Square data) {
305 final List<Row> rows = model.getRows();
306 for (int y = 0; y < rows.size(); y++) {
307 final List<Square> columns = rows.get(y).getColumns();
308 for (int x = 0; x < columns.size(); x++) {
309 Square sq = columns.get(x);
315 final boolean ok = isConsistent(model);
328 private static Square atLeastOnePlaceWhereBombCantBe(Mines model) {
329 final List<Row> rows = model.getRows();
330 Square cantBe = null;
332 for (int y = 0; y < rows.size(); y++) {
333 final List<Square> columns = rows.get(y).getColumns();
334 for (int x = 0; x < columns.size(); x++) {
335 Square sq = columns.get(x);
336 if (sq.getState() == SquareType.UNKNOWN) {
338 if (tryStealBomb(model, sq) == null) {
348 if (discovered > 5) {
355 private static boolean placeBombElseWhere(Mines model, Square moveBomb) {
356 List<Square> ok = new ArrayList<Square>();
357 moveBomb.setMine(false);
358 final List<Row> rows = model.getRows();
359 for (int y = 0; y < rows.size(); y++) {
360 final List<Square> columns = rows.get(y).getColumns();
361 for (int x = 0; x < columns.size(); x++) {
362 Square sq = columns.get(x);
363 if (sq == moveBomb || sq.isMine() || sq.getState().isVisible()) {
367 if (isConsistent(model)) {
374 moveBomb.setMine(true);
377 int r = new Random().nextInt(ok.size());
378 ok.get(r).setMine(true);
383 private static void expandKnown(Mines model, Square data) {
384 final List<Row> rows = model.getRows();
385 for (int y = 0; y < rows.size(); y++) {
386 final List<Square> columns = rows.get(y).getColumns();
387 for (int x = 0; x < columns.size(); x++) {
388 Square sq = columns.get(x);
390 expandKnown(model, x, y);
396 private static void expandKnown(Mines model, int x , int y) {
397 if (y < 0 || y >= model.getRows().size()) {
400 final List<Square> columns = model.getRows().get(y).getColumns();
401 if (x < 0 || x >= columns.size()) {
404 final Square sq = columns.get(x);
405 if (sq.getState() == SquareType.UNKNOWN) {
406 int around = around(model, x, y);
407 final SquareType t = SquareType.valueOf("N_" + around);
409 if (t == SquareType.N_0) {
410 expandKnown(model, x - 1, y - 1);
411 expandKnown(model, x - 1, y);
412 expandKnown(model, x - 1, y + 1);
413 expandKnown(model, x , y - 1);
414 expandKnown(model, x, y + 1);
415 expandKnown(model, x + 1, y - 1);
416 expandKnown(model, x + 1, y);
417 expandKnown(model, x + 1, y + 1);
422 private static int around(Mines model, int x, int y) {
424 minesAt(model, x - 1, y - 1) +
425 minesAt(model, x - 1, y) +
426 minesAt(model, x - 1, y + 1) +
427 minesAt(model, x , y - 1) +
428 minesAt(model, x, y + 1) +
429 minesAt(model, x + 1, y - 1) +
430 minesAt(model, x + 1, y) +
431 minesAt(model, x + 1, y + 1);
434 private static int minesAt(Mines model, int x, int y) {
435 if (y < 0 || y >= model.getRows().size()) {
438 final List<Square> columns = model.getRows().get(y).getColumns();
439 if (x < 0 || x >= columns.size()) {
442 Square sq = columns.get(x);
443 return sq.isMine() ? 1 : 0;
446 private static boolean isConsistent(Mines m) {
447 for (int row = 0; row < m.getRows().size(); row++) {
448 Row r = m.getRows().get(row);
449 for (int col = 0; col < r.getColumns().size(); col++) {
450 Square sq = r.getColumns().get(col);
451 if (sq.getState().isVisible()) {
452 int around = around(m, col, row);
453 if (around != sq.getState().ordinal()) {
462 private static boolean allMarked(Mines m) {
463 for (Row r : m.getRows()) {
464 for (Square sq : r.getColumns()) {
465 if (sq.isMine() == (sq.getState() != SquareType.MARKED)) {
470 for (Row r : m.getRows()) {
471 for (Square sq : r.getColumns()) {
473 sq.setState(SquareType.DISCOVERED);
475 sq.setState(SquareType.N_0);
483 private static Mines ui;
484 public static void main(String... args) throws Exception {