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);
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);
199 private static void incrementAround(SquareType[][] arr, int x, int y) {
200 incrementAt(arr, x - 1, y - 1);
201 incrementAt(arr, x - 1, y);
202 incrementAt(arr, x - 1, y + 1);
204 incrementAt(arr, x + 1, y - 1);
205 incrementAt(arr, x + 1, y);
206 incrementAt(arr, x + 1, y + 1);
208 incrementAt(arr, x, y - 1);
209 incrementAt(arr, x, y + 1);
212 private static void incrementAt(SquareType[][] arr, int x, int y) {
213 if (y >= 0 && y < arr.length) {
214 SquareType[] r = arr[y];
215 if (x >= 0 && x < r.length) {
216 SquareType sq = r[x];
218 r[x] = sq.moreBombsAround();
224 static void showAllBombs(Mines model, SquareType state) {
225 for (Row row : model.getRows()) {
226 for (Square square : row.getColumns()) {
227 if (square.isMine()) {
228 square.setState(state);
234 private static AudioClip TOUCH;
235 @Function static void click(Mines model, Square data) {
236 if (model.getState() != GameState.IN_PROGRESS) {
240 switch (data.getState()) {
243 showAllBombs(model, SquareType.EXPLOSION);
244 model.setState(GameState.LOST);
247 TOUCH = AudioClip.create("move.mp3");
250 expandKnown(model, data);
251 model.computeMines();
256 private static void expandKnown(Mines model, Square data) {
257 final List<Row> rows = model.getRows();
258 for (int y = 0; y < rows.size(); y++) {
259 final List<Square> columns = rows.get(y).getColumns();
260 for (int x = 0; x < columns.size(); x++) {
261 Square sq = columns.get(x);
263 expandKnown(model, x, y);
269 private static void expandKnown(Mines model, int x , int y) {
270 if (y < 0 || y >= model.getRows().size()) {
273 final List<Square> columns = model.getRows().get(y).getColumns();
274 if (x < 0 || x >= columns.size()) {
277 final Square sq = columns.get(x);
278 if (sq.getState() == SquareType.UNKNOWN) {
280 minesAt(model, x - 1, y - 1) +
281 minesAt(model, x - 1, y) +
282 minesAt(model, x - 1, y + 1) +
283 minesAt(model, x , y - 1) +
284 minesAt(model, x, y + 1) +
285 minesAt(model, x + 1, y - 1) +
286 minesAt(model, x + 1, y) +
287 minesAt(model, x + 1, y + 1);
288 final SquareType t = SquareType.valueOf("N_" + around);
290 if (t == SquareType.N_0) {
291 expandKnown(model, x - 1, y - 1);
292 expandKnown(model, x - 1, y);
293 expandKnown(model, x - 1, y + 1);
294 expandKnown(model, x , y - 1);
295 expandKnown(model, x, y + 1);
296 expandKnown(model, x + 1, y - 1);
297 expandKnown(model, x + 1, y);
298 expandKnown(model, x + 1, y + 1);
303 private static int minesAt(Mines model, int x, int y) {
304 if (y < 0 || y >= model.getRows().size()) {
307 final List<Square> columns = model.getRows().get(y).getColumns();
308 if (x < 0 || x >= columns.size()) {
311 Square sq = columns.get(x);
312 return sq.isMine() ? 1 : 0;
316 * Called when page is ready
318 public static void main(String... args) throws Exception {
319 Mines m = new Mines();