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 = new ArrayList<Row>(height);
115 for (int y = 0; y < height; y++) {
116 Square[] columns = new Square[width];
117 for (int x = 0; x < width; x++) {
118 columns[x] = new Square(SquareType.UNKNOWN, false);
120 rows.add(new Row(columns));
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 model.getRows().clear();
137 model.getRows().addAll(rows);
140 @ModelOperation static void computeMines(Mines model) {
141 List<Integer> xBombs = new ArrayList<Integer>();
142 List<Integer> yBombs = new ArrayList<Integer>();
143 final List<Row> rows = model.getRows();
144 boolean emptyHidden = false;
145 SquareType[][] arr = new SquareType[rows.size()][];
146 for (int y = 0; y < rows.size(); y++) {
147 final List<Square> columns = rows.get(y).getColumns();
148 arr[y] = new SquareType[columns.size()];
149 for (int x = 0; x < columns.size(); x++) {
150 Square sq = columns.get(x);
155 if (sq.getState().isVisible()) {
156 arr[y][x] = SquareType.N_0;
164 for (int i = 0; i < xBombs.size(); i++) {
165 int x = xBombs.get(i);
166 int y = yBombs.get(i);
168 incrementAround(arr, x, y);
170 for (int y = 0; y < rows.size(); y++) {
171 final List<Square> columns = rows.get(y).getColumns();
172 for (int x = 0; x < columns.size(); x++) {
173 Square sq = columns.get(x);
174 final SquareType newState = arr[y][x];
175 if (newState != null && newState != sq.getState()) {
176 sq.setState(newState);
182 model.setState(GameState.WON);
183 showAllBombs(model, SquareType.DISCOVERED);
187 private static void incrementAround(SquareType[][] arr, int x, int y) {
188 incrementAt(arr, x - 1, y - 1);
189 incrementAt(arr, x - 1, y);
190 incrementAt(arr, x - 1, y + 1);
192 incrementAt(arr, x + 1, y - 1);
193 incrementAt(arr, x + 1, y);
194 incrementAt(arr, x + 1, y + 1);
196 incrementAt(arr, x, y - 1);
197 incrementAt(arr, x, y + 1);
200 private static void incrementAt(SquareType[][] arr, int x, int y) {
201 if (y >= 0 && y < arr.length) {
202 SquareType[] r = arr[y];
203 if (x >= 0 && x < r.length) {
204 SquareType sq = r[x];
206 r[x] = sq.moreBombsAround();
212 static void showAllBombs(Mines model, SquareType state) {
213 for (Row row : model.getRows()) {
214 for (Square square : row.getColumns()) {
215 if (square.isMine()) {
216 square.setState(state);
222 private static final AudioClip TOUCH = AudioClip.create("move.mp3");
223 @Function static void click(Mines model, Square data) {
224 if (model.getState() != GameState.IN_PROGRESS) {
228 switch (data.getState()) {
231 showAllBombs(model, SquareType.EXPLOSION);
232 model.setState(GameState.LOST);
235 expandKnown(model, data);
240 private static void expandKnown(Mines model, Square data) {
241 final List<Row> rows = model.getRows();
242 for (int y = 0; y < rows.size(); y++) {
243 final List<Square> columns = rows.get(y).getColumns();
244 for (int x = 0; x < columns.size(); x++) {
245 Square sq = columns.get(x);
247 expandKnown(model, x, y);
253 private static void expandKnown(Mines model, int x , int y) {
254 if (y < 0 || y >= model.getRows().size()) {
257 final List<Square> columns = model.getRows().get(y).getColumns();
258 if (x < 0 || x >= columns.size()) {
261 final Square sq = columns.get(x);
262 if (sq.getState() == SquareType.UNKNOWN) {
263 sq.setState(SquareType.N_0);
264 model.computeMines();
265 if (sq.getState() == SquareType.N_0) {
266 expandKnown(model, x - 1, y - 1);
267 expandKnown(model, x - 1, y);
268 expandKnown(model, x - 1, y + 1);
269 expandKnown(model, x , y - 1);
270 expandKnown(model, x, y + 1);
271 expandKnown(model, x + 1, y - 1);
272 expandKnown(model, x + 1, y);
273 expandKnown(model, x + 1, y + 1);
279 * Called when page is ready
281 public static void main(String... args) throws Exception {
282 Mines m = new Mines();