minesweeper/src/main/java/org/apidesign/demo/minesweeper/MinesModel.java
changeset 243 64b15a1fb456
parent 237 83302b8a5cdf
child 244 cea2063fd0f9
     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 -}