1.1 --- a/minesweeper/src/main/java/org/apidesign/demo/minesweeper/MinesModel.java Sun Jun 28 07:14:21 2015 +0200
1.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1.3 @@ -1,488 +0,0 @@
1.4 -/**
1.5 - * The MIT License (MIT)
1.6 - *
1.7 - * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
1.8 - *
1.9 - * Permission is hereby granted, free of charge, to any person obtaining a copy
1.10 - * of this software and associated documentation files (the "Software"), to deal
1.11 - * in the Software without restriction, including without limitation the rights
1.12 - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1.13 - * copies of the Software, and to permit persons to whom the Software is
1.14 - * furnished to do so, subject to the following conditions:
1.15 - *
1.16 - * The above copyright notice and this permission notice shall be included in
1.17 - * all copies or substantial portions of the Software.
1.18 - *
1.19 - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1.20 - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1.21 - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1.22 - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1.23 - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1.24 - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1.25 - * THE SOFTWARE.
1.26 - */
1.27 -package org.apidesign.demo.minesweeper;
1.28 -
1.29 -import java.util.ArrayList;
1.30 -import java.util.List;
1.31 -import java.util.Random;
1.32 -import net.java.html.json.ComputedProperty;
1.33 -import net.java.html.json.Function;
1.34 -import net.java.html.json.Model;
1.35 -import net.java.html.json.ModelOperation;
1.36 -import net.java.html.json.Property;
1.37 -import net.java.html.sound.AudioClip;
1.38 -
1.39 -/** Model of the mine field.
1.40 - */
1.41 -@Model(className = "Mines", targetId = "", properties = {
1.42 - @Property(name = "state", type = MinesModel.GameState.class),
1.43 - @Property(name = "rows", type = Row.class, array = true),
1.44 -})
1.45 -public final class MinesModel {
1.46 - enum GameState {
1.47 - IN_PROGRESS, MARKING_MINE, WON, LOST;
1.48 - }
1.49 -
1.50 - @ComputedProperty static String gameStyle(GameState state) {
1.51 - return state == GameState.MARKING_MINE ? "MARKING" : "PLAYING";
1.52 - }
1.53 -
1.54 - @Model(className = "Row", properties = {
1.55 - @Property(name = "columns", type = Square.class, array = true)
1.56 - })
1.57 - static class RowModel {
1.58 - }
1.59 -
1.60 - @Model(className = "Square", properties = {
1.61 - @Property(name = "state", type = SquareType.class),
1.62 - @Property(name = "mine", type = boolean.class)
1.63 - })
1.64 - static class SquareModel {
1.65 - @ComputedProperty static String style(SquareType state) {
1.66 - return state == null ? null : state.toString();
1.67 - }
1.68 - }
1.69 -
1.70 - enum SquareType {
1.71 - N_0, N_1, N_2, N_3, N_4, N_5, N_6, N_7, N_8,
1.72 - UNKNOWN, EXPLOSION, DISCOVERED, MARKED;
1.73 -
1.74 - final boolean isVisible() {
1.75 - return name().startsWith("N_");
1.76 - }
1.77 -
1.78 - final SquareType moreBombsAround() {
1.79 - switch (this) {
1.80 - case EXPLOSION:
1.81 - case UNKNOWN:
1.82 - case DISCOVERED:
1.83 - case N_8:
1.84 - return this;
1.85 - }
1.86 - return values()[ordinal() + 1];
1.87 - }
1.88 - }
1.89 -
1.90 - @ComputedProperty static boolean fieldShowing(GameState state) {
1.91 - return state != null;
1.92 - }
1.93 -
1.94 - @ComputedProperty static boolean gameInProgress(GameState state) {
1.95 - return state == GameState.IN_PROGRESS;
1.96 - }
1.97 -
1.98 - @Function static void showHelp(Mines model) {
1.99 - model.setState(null);
1.100 - }
1.101 -
1.102 - @Function static void smallGame(Mines model) {
1.103 - model.init(5, 5, 5);
1.104 - }
1.105 - @Function static void normalGame(Mines model) {
1.106 - model.init(10, 10, 10);
1.107 - }
1.108 -
1.109 - @Function static void giveUp(Mines model) {
1.110 - showAllBombs(model, SquareType.EXPLOSION);
1.111 - model.setState(GameState.LOST);
1.112 - }
1.113 -
1.114 - @Function static void markMine(Mines model) {
1.115 - if (model.getState() == GameState.IN_PROGRESS) {
1.116 - model.setState(GameState.MARKING_MINE);
1.117 - }
1.118 - }
1.119 -
1.120 - @ModelOperation static void init(Mines model, int width, int height, int mines) {
1.121 - List<Row> rows = model.getRows();
1.122 - if (rows.size() != height || rows.get(0).getColumns().size() != width) {
1.123 - rows = new ArrayList<Row>(height);
1.124 - for (int y = 0; y < height; y++) {
1.125 - Square[] columns = new Square[width];
1.126 - for (int x = 0; x < width; x++) {
1.127 - columns[x] = new Square(SquareType.UNKNOWN, false);
1.128 - }
1.129 - rows.add(new Row(columns));
1.130 - }
1.131 - } else {
1.132 - for (Row row : rows) {
1.133 - for (Square sq : row.getColumns()) {
1.134 - sq.setState(SquareType.UNKNOWN);
1.135 - sq.setMine(false);
1.136 - }
1.137 - }
1.138 - }
1.139 -
1.140 - Random r = new Random();
1.141 - while (mines > 0) {
1.142 - int x = r.nextInt(width);
1.143 - int y = r.nextInt(height);
1.144 - final Square s = rows.get(y).getColumns().get(x);
1.145 - if (s.isMine()) {
1.146 - continue;
1.147 - }
1.148 - s.setMine(true);
1.149 - mines--;
1.150 - }
1.151 -
1.152 - model.setState(GameState.IN_PROGRESS);
1.153 - if (rows != model.getRows()) {
1.154 - model.getRows().clear();
1.155 - model.getRows().addAll(rows);
1.156 - }
1.157 - }
1.158 -
1.159 - @ModelOperation static void computeMines(Mines model) {
1.160 - List<Integer> xBombs = new ArrayList<Integer>();
1.161 - List<Integer> yBombs = new ArrayList<Integer>();
1.162 - final List<Row> rows = model.getRows();
1.163 - boolean emptyHidden = false;
1.164 - SquareType[][] arr = new SquareType[rows.size()][];
1.165 - for (int y = 0; y < rows.size(); y++) {
1.166 - final List<Square> columns = rows.get(y).getColumns();
1.167 - arr[y] = new SquareType[columns.size()];
1.168 - for (int x = 0; x < columns.size(); x++) {
1.169 - Square sq = columns.get(x);
1.170 - if (sq.isMine()) {
1.171 - xBombs.add(x);
1.172 - yBombs.add(y);
1.173 - }
1.174 - if (sq.getState().isVisible()) {
1.175 - arr[y][x] = SquareType.N_0;
1.176 - } else {
1.177 - if (!sq.isMine()) {
1.178 - emptyHidden = true;
1.179 - }
1.180 - }
1.181 - }
1.182 - }
1.183 - for (int i = 0; i < xBombs.size(); i++) {
1.184 - int x = xBombs.get(i);
1.185 - int y = yBombs.get(i);
1.186 -
1.187 - incrementAround(arr, x, y);
1.188 - }
1.189 - for (int y = 0; y < rows.size(); y++) {
1.190 - final List<Square> columns = rows.get(y).getColumns();
1.191 - for (int x = 0; x < columns.size(); x++) {
1.192 - Square sq = columns.get(x);
1.193 - final SquareType newState = arr[y][x];
1.194 - if (newState != null && newState != sq.getState()) {
1.195 - sq.setState(newState);
1.196 - }
1.197 - }
1.198 - }
1.199 -
1.200 - if (!emptyHidden) {
1.201 - model.setState(GameState.WON);
1.202 - showAllBombs(model, SquareType.DISCOVERED);
1.203 - AudioClip applause = AudioClip.create("applause.mp3");
1.204 - applause.play();
1.205 - }
1.206 - }
1.207 -
1.208 - private static void incrementAround(SquareType[][] arr, int x, int y) {
1.209 - incrementAt(arr, x - 1, y - 1);
1.210 - incrementAt(arr, x - 1, y);
1.211 - incrementAt(arr, x - 1, y + 1);
1.212 -
1.213 - incrementAt(arr, x + 1, y - 1);
1.214 - incrementAt(arr, x + 1, y);
1.215 - incrementAt(arr, x + 1, y + 1);
1.216 -
1.217 - incrementAt(arr, x, y - 1);
1.218 - incrementAt(arr, x, y + 1);
1.219 - }
1.220 -
1.221 - private static void incrementAt(SquareType[][] arr, int x, int y) {
1.222 - if (y >= 0 && y < arr.length) {
1.223 - SquareType[] r = arr[y];
1.224 - if (x >= 0 && x < r.length) {
1.225 - SquareType sq = r[x];
1.226 - if (sq != null) {
1.227 - r[x] = sq.moreBombsAround();
1.228 - }
1.229 - }
1.230 - }
1.231 - }
1.232 -
1.233 - static void showAllBombs(Mines model, SquareType state) {
1.234 - for (Row row : model.getRows()) {
1.235 - for (Square square : row.getColumns()) {
1.236 - if (square.isMine()) {
1.237 - square.setState(state);
1.238 - }
1.239 - }
1.240 - }
1.241 - }
1.242 -
1.243 - @Function static void click(Mines model, Square data) {
1.244 - if (model.getState() == GameState.MARKING_MINE) {
1.245 - if (data.getState() == SquareType.UNKNOWN) {
1.246 - data.setState(SquareType.MARKED);
1.247 - if (allMarked(model)) {
1.248 - model.setState(GameState.WON);
1.249 - return;
1.250 - }
1.251 - }
1.252 - model.setState(GameState.IN_PROGRESS);
1.253 - return;
1.254 - }
1.255 - if (model.getState() != GameState.IN_PROGRESS) {
1.256 - return;
1.257 - }
1.258 - if (data.getState() == SquareType.MARKED) {
1.259 - data.setState(SquareType.UNKNOWN);
1.260 - if (allMarked(model)) {
1.261 - model.setState(GameState.WON);
1.262 - }
1.263 - return;
1.264 - }
1.265 - if (data.getState() != SquareType.UNKNOWN) {
1.266 - return;
1.267 - }
1.268 - if (data.isMine()) {
1.269 - Square fair = atLeastOnePlaceWhereBombCantBe(model);
1.270 - if (fair == null) {
1.271 - if (placeBombElseWhere(model, data)) {
1.272 - cleanedUp(model, data);
1.273 - return;
1.274 - }
1.275 - }
1.276 - explosion(model);
1.277 - } else {
1.278 - Square takeFrom = tryStealBomb(model, data);
1.279 - if (takeFrom != null) {
1.280 - final Square fair = atLeastOnePlaceWhereBombCantBe(model);
1.281 - if (fair != null) {
1.282 - takeFrom.setMine(false);
1.283 - data.setMine(true);
1.284 - explosion(model);
1.285 - return;
1.286 - }
1.287 - }
1.288 - cleanedUp(model, data);
1.289 - }
1.290 - }
1.291 -
1.292 - private static void cleanedUp(Mines model, Square data) {
1.293 - AudioClip touch = AudioClip.create("move.mp3");
1.294 - touch.play();
1.295 - expandKnown(model, data);
1.296 - model.computeMines();
1.297 - }
1.298 -
1.299 - private static void explosion(Mines model) {
1.300 - showAllBombs(model, SquareType.EXPLOSION);
1.301 - model.setState(GameState.LOST);
1.302 - AudioClip oops = AudioClip.create("oops.mp3");
1.303 - oops.play();
1.304 - }
1.305 -
1.306 - private static Square tryStealBomb(Mines model, Square data) {
1.307 - data.setMine(true);
1.308 - final List<Row> rows = model.getRows();
1.309 - for (int y = 0; y < rows.size(); y++) {
1.310 - final List<Square> columns = rows.get(y).getColumns();
1.311 - for (int x = 0; x < columns.size(); x++) {
1.312 - Square sq = columns.get(x);
1.313 - if (sq == data) {
1.314 - continue;
1.315 - }
1.316 - if (sq.isMine()) {
1.317 - sq.setMine(false);
1.318 - final boolean ok = isConsistent(model);
1.319 - sq.setMine(true);
1.320 - if (ok) {
1.321 - data.setMine(false);
1.322 - return sq;
1.323 - }
1.324 - }
1.325 - }
1.326 - }
1.327 - data.setMine(false);
1.328 - return null;
1.329 - }
1.330 -
1.331 - private static Square atLeastOnePlaceWhereBombCantBe(Mines model) {
1.332 - final List<Row> rows = model.getRows();
1.333 - Square cantBe = null;
1.334 - int discovered = 0;
1.335 - for (int y = 0; y < rows.size(); y++) {
1.336 - final List<Square> columns = rows.get(y).getColumns();
1.337 - for (int x = 0; x < columns.size(); x++) {
1.338 - Square sq = columns.get(x);
1.339 - if (sq.getState() == SquareType.UNKNOWN) {
1.340 - if (!sq.isMine()) {
1.341 - if (tryStealBomb(model, sq) == null) {
1.342 - cantBe = sq;
1.343 - }
1.344 - }
1.345 - } else {
1.346 - discovered++;
1.347 - }
1.348 - }
1.349 - }
1.350 -
1.351 - if (discovered > 5) {
1.352 - return cantBe;
1.353 - }
1.354 -
1.355 - return null;
1.356 - }
1.357 -
1.358 - private static boolean placeBombElseWhere(Mines model, Square moveBomb) {
1.359 - List<Square> ok = new ArrayList<Square>();
1.360 - moveBomb.setMine(false);
1.361 - final List<Row> rows = model.getRows();
1.362 - for (int y = 0; y < rows.size(); y++) {
1.363 - final List<Square> columns = rows.get(y).getColumns();
1.364 - for (int x = 0; x < columns.size(); x++) {
1.365 - Square sq = columns.get(x);
1.366 - if (sq == moveBomb || sq.isMine() || sq.getState().isVisible()) {
1.367 - continue;
1.368 - }
1.369 - sq.setMine(true);
1.370 - if (isConsistent(model)) {
1.371 - ok.add(sq);
1.372 - }
1.373 - sq.setMine(false);
1.374 - }
1.375 - }
1.376 - if (ok.isEmpty()) {
1.377 - moveBomb.setMine(true);
1.378 - return false;
1.379 - } else {
1.380 - int r = new Random().nextInt(ok.size());
1.381 - ok.get(r).setMine(true);
1.382 - return true;
1.383 - }
1.384 - }
1.385 -
1.386 - private static void expandKnown(Mines model, Square data) {
1.387 - final List<Row> rows = model.getRows();
1.388 - for (int y = 0; y < rows.size(); y++) {
1.389 - final List<Square> columns = rows.get(y).getColumns();
1.390 - for (int x = 0; x < columns.size(); x++) {
1.391 - Square sq = columns.get(x);
1.392 - if (sq == data) {
1.393 - expandKnown(model, x, y);
1.394 - return;
1.395 - }
1.396 - }
1.397 - }
1.398 - }
1.399 - private static void expandKnown(Mines model, int x , int y) {
1.400 - if (y < 0 || y >= model.getRows().size()) {
1.401 - return;
1.402 - }
1.403 - final List<Square> columns = model.getRows().get(y).getColumns();
1.404 - if (x < 0 || x >= columns.size()) {
1.405 - return;
1.406 - }
1.407 - final Square sq = columns.get(x);
1.408 - if (sq.getState() == SquareType.UNKNOWN) {
1.409 - int around = around(model, x, y);
1.410 - final SquareType t = SquareType.valueOf("N_" + around);
1.411 - sq.setState(t);
1.412 - if (t == SquareType.N_0) {
1.413 - expandKnown(model, x - 1, y - 1);
1.414 - expandKnown(model, x - 1, y);
1.415 - expandKnown(model, x - 1, y + 1);
1.416 - expandKnown(model, x , y - 1);
1.417 - expandKnown(model, x, y + 1);
1.418 - expandKnown(model, x + 1, y - 1);
1.419 - expandKnown(model, x + 1, y);
1.420 - expandKnown(model, x + 1, y + 1);
1.421 - }
1.422 - }
1.423 - }
1.424 -
1.425 - private static int around(Mines model, int x, int y) {
1.426 - return
1.427 - minesAt(model, x - 1, y - 1) +
1.428 - minesAt(model, x - 1, y) +
1.429 - minesAt(model, x - 1, y + 1) +
1.430 - minesAt(model, x , y - 1) +
1.431 - minesAt(model, x, y + 1) +
1.432 - minesAt(model, x + 1, y - 1) +
1.433 - minesAt(model, x + 1, y) +
1.434 - minesAt(model, x + 1, y + 1);
1.435 - }
1.436 -
1.437 - private static int minesAt(Mines model, int x, int y) {
1.438 - if (y < 0 || y >= model.getRows().size()) {
1.439 - return 0;
1.440 - }
1.441 - final List<Square> columns = model.getRows().get(y).getColumns();
1.442 - if (x < 0 || x >= columns.size()) {
1.443 - return 0;
1.444 - }
1.445 - Square sq = columns.get(x);
1.446 - return sq.isMine() ? 1 : 0;
1.447 - }
1.448 -
1.449 - private static boolean isConsistent(Mines m) {
1.450 - for (int row = 0; row < m.getRows().size(); row++) {
1.451 - Row r = m.getRows().get(row);
1.452 - for (int col = 0; col < r.getColumns().size(); col++) {
1.453 - Square sq = r.getColumns().get(col);
1.454 - if (sq.getState().isVisible()) {
1.455 - int around = around(m, col, row);
1.456 - if (around != sq.getState().ordinal()) {
1.457 - return false;
1.458 - }
1.459 - }
1.460 - }
1.461 - }
1.462 - return true;
1.463 - }
1.464 -
1.465 - private static boolean allMarked(Mines m) {
1.466 - for (Row r : m.getRows()) {
1.467 - for (Square sq : r.getColumns()) {
1.468 - if (sq.isMine() == (sq.getState() != SquareType.MARKED)) {
1.469 - return false;
1.470 - }
1.471 - }
1.472 - }
1.473 - for (Row r : m.getRows()) {
1.474 - for (Square sq : r.getColumns()) {
1.475 - if (sq.isMine()) {
1.476 - sq.setState(SquareType.DISCOVERED);
1.477 - } else {
1.478 - sq.setState(SquareType.N_0);
1.479 - }
1.480 - }
1.481 - }
1.482 - computeMines(m);
1.483 - return true;
1.484 - }
1.485 -
1.486 - private static Mines ui;
1.487 - public static void main(String... args) throws Exception {
1.488 - ui = new Mines();
1.489 - ui.applyBindings();
1.490 - }
1.491 -}