minesweeper/src/main/java/org/apidesign/demo/minesweeper/MinesModel.java
author Jaroslav Tulach <jtulach@netbeans.org>
Mon, 08 Sep 2014 17:15:18 +0200
branchnbrwsr
changeset 208 9073c01ba497
parent 178 87d475e1cf75
permissions -rw-r--r--
When there is HTML/Java project, clicking on 'Develop' should open the project wizard
     1 /**
     2  * The MIT License (MIT)
     3  *
     4  * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     5  *
     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:
    12  *
    13  * The above copyright notice and this permission notice shall be included in
    14  * all copies or substantial portions of the Software.
    15  *
    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
    22  * THE SOFTWARE.
    23  */
    24 package org.apidesign.demo.minesweeper;
    25 
    26 import java.awt.event.ActionEvent;
    27 import java.net.MalformedURLException;
    28 import java.net.URL;
    29 import java.util.ArrayList;
    30 import java.util.List;
    31 import java.util.Random;
    32 import java.util.logging.Level;
    33 import java.util.logging.Logger;
    34 import javax.swing.Action;
    35 import net.java.html.json.ComputedProperty;
    36 import net.java.html.json.Function;
    37 import net.java.html.json.Model;
    38 import net.java.html.json.ModelOperation;
    39 import net.java.html.json.Property;
    40 import net.java.html.sound.AudioClip;
    41 import org.openide.awt.HtmlBrowser;
    42 import org.openide.filesystems.FileObject;
    43 import org.openide.filesystems.FileUtil;
    44 import org.openide.util.ContextAwareAction;
    45 import org.openide.util.Exceptions;
    46 import org.openide.util.Lookup;
    47 import org.openide.util.Utilities;
    48 
    49 /** Model of the mine field.
    50  */
    51 @Model(className = "Mines", properties = {
    52     @Property(name = "state", type = MinesModel.GameState.class),
    53     @Property(name = "rows", type = Row.class, array = true),
    54 })
    55 public final class MinesModel {
    56     enum GameState {
    57         IN_PROGRESS, MARKING_MINE, WON, LOST;
    58     }
    59     
    60     @ComputedProperty static String gameStyle(GameState state) {
    61         return state == GameState.MARKING_MINE ? "MARKING" : "PLAYING";
    62     }
    63     
    64     @Model(className = "Row", properties = {
    65         @Property(name = "columns", type = Square.class, array = true)
    66     })
    67     static class RowModel {
    68     }
    69 
    70     @Model(className = "Square", properties = {
    71         @Property(name = "state", type = SquareType.class),
    72         @Property(name = "mine", type = boolean.class)
    73     })
    74     static class SquareModel {
    75         @ComputedProperty static String style(SquareType state) {
    76             return state == null ? null : state.toString();
    77         }
    78     }
    79     
    80     enum SquareType {
    81         N_0, N_1, N_2, N_3, N_4, N_5, N_6, N_7, N_8,
    82         UNKNOWN, EXPLOSION, DISCOVERED, MARKED;
    83         
    84         final boolean isVisible() {
    85             return name().startsWith("N_");
    86         }
    87 
    88         final SquareType moreBombsAround() {
    89             switch (this) {
    90                 case EXPLOSION:
    91                 case UNKNOWN:
    92                 case DISCOVERED:
    93                 case N_8:
    94                     return this;
    95             }
    96             return values()[ordinal() + 1];
    97         }
    98     }
    99     
   100     @ComputedProperty static boolean fieldShowing(GameState state) {
   101         return state != null;
   102     }
   103     
   104     @ComputedProperty static boolean gameInProgress(GameState state) {
   105         return state == GameState.IN_PROGRESS;
   106     }
   107     
   108     @Function static void showHelp(Mines model) {
   109         model.setState(null);
   110     }
   111     
   112     @Function static void smallGame(Mines model) {
   113         model.init(5, 5, 5);
   114     }
   115     @Function static void normalGame(Mines model) {
   116         model.init(10, 10, 10);
   117     }
   118     
   119     @Function static void giveUp(Mines model) {
   120         showAllBombs(model, SquareType.EXPLOSION);
   121         model.setState(GameState.LOST);
   122     }
   123     
   124     @Function static void markMine(Mines model) {
   125         if (model.getState() == GameState.IN_PROGRESS) {
   126             model.setState(GameState.MARKING_MINE);
   127         }
   128     }
   129     
   130     private static final Logger LOG = Logger.getLogger(MinesModel.class.getName());
   131     @Function static void develop(Mines model) {
   132         Action a = null;
   133         try {
   134             ClassLoader l = Lookup.getDefault().lookup(ClassLoader.class);
   135             if (l == null) {
   136                 l = Thread.currentThread().getContextClassLoader();
   137             }
   138             if (l == null) {
   139                 l = MinesModel.class.getClassLoader();
   140             }
   141             Class<?> newPrj = Class.forName("org.netbeans.spi.project.ui.support.CommonProjectActions", true, l); // NOI18N
   142             a = (Action) newPrj.getMethod("newProjectAction").invoke(null); // NOI18N
   143         } catch (Exception ex) {
   144             LOG.log(Level.FINE, "Cannot find New project action!", ex);
   145         }
   146         if (a != null) {
   147             FileObject fo = FileUtil.getConfigFile("Templates/Project/ClientSide"); // NOI18N
   148             if (fo != null) {
   149                 a.putValue("PRESELECT_CATEGORY", "ClientSide"); // NOI18N
   150                 for (FileObject template : fo.getChildren()) {
   151                     if (template.getName().contains("htmljava")) { // NOI18N
   152                         a.putValue("PRESELECT_TEMPLATE", template.getName()); // NOI18N
   153                         a.actionPerformed(new ActionEvent(model, 0, null));
   154                         return;
   155                     }
   156                 }
   157             }
   158         } 
   159         try {
   160             HtmlBrowser.URLDisplayer.getDefault().showURL(new URL("http://wiki.apidesign.org/wiki/DukeScriptInNetBeans")); // NO18N
   161         } catch (MalformedURLException ex) {
   162             Exceptions.printStackTrace(ex);
   163         }
   164     }
   165     
   166     @ModelOperation static void init(Mines model, int width, int height, int mines) {
   167         List<Row> rows = model.getRows();
   168         if (rows.size() != height || rows.get(0).getColumns().size() != width) {
   169             rows = new ArrayList<Row>(height);
   170             for (int y = 0; y < height; y++) {
   171                 Square[] columns = new Square[width];
   172                 for (int x = 0; x < width; x++) {
   173                     columns[x] = new Square(SquareType.UNKNOWN, false);
   174                 }
   175                 rows.add(new Row(columns));
   176             }
   177         } else {
   178             for (Row row : rows) {
   179                 for (Square sq : row.getColumns()) {
   180                     sq.setState(SquareType.UNKNOWN);
   181                     sq.setMine(false);
   182                 }
   183             }
   184         }
   185         
   186         Random r = new Random();
   187         while (mines > 0) {
   188             int x = r.nextInt(width);
   189             int y = r.nextInt(height);
   190             final Square s = rows.get(y).getColumns().get(x);
   191             if (s.isMine()) {
   192                 continue;
   193             }
   194             s.setMine(true);
   195             mines--;
   196         }
   197 
   198         model.setState(GameState.IN_PROGRESS);
   199         if (rows != model.getRows()) {
   200             model.getRows().clear();
   201             model.getRows().addAll(rows);
   202         }
   203     }
   204     
   205     @ModelOperation static void computeMines(Mines model) {
   206         List<Integer> xBombs = new ArrayList<Integer>();
   207         List<Integer> yBombs = new ArrayList<Integer>();
   208         final List<Row> rows = model.getRows();
   209         boolean emptyHidden = false;
   210         SquareType[][] arr = new SquareType[rows.size()][];
   211         for (int y = 0; y < rows.size(); y++) {
   212             final List<Square> columns = rows.get(y).getColumns();
   213             arr[y] = new SquareType[columns.size()];
   214             for (int x = 0; x < columns.size(); x++) {
   215                 Square sq = columns.get(x);
   216                 if (sq.isMine()) {
   217                     xBombs.add(x);
   218                     yBombs.add(y);
   219                 }
   220                 if (sq.getState().isVisible()) {
   221                     arr[y][x] = SquareType.N_0;
   222                 } else {
   223                     if (!sq.isMine()) {
   224                         emptyHidden = true;
   225                     }
   226                 }
   227             }
   228         }
   229         for (int i = 0; i < xBombs.size(); i++) {
   230             int x = xBombs.get(i);
   231             int y = yBombs.get(i);
   232             
   233             incrementAround(arr, x, y);
   234         }
   235         for (int y = 0; y < rows.size(); y++) {
   236             final List<Square> columns = rows.get(y).getColumns();
   237             for (int x = 0; x < columns.size(); x++) {
   238                 Square sq = columns.get(x);
   239                 final SquareType newState = arr[y][x];
   240                 if (newState != null && newState != sq.getState()) {
   241                     sq.setState(newState);
   242                 }
   243             }
   244         }
   245         
   246         if (!emptyHidden) {
   247             model.setState(GameState.WON);
   248             showAllBombs(model, SquareType.DISCOVERED);
   249             AudioClip applause = AudioClip.create("applause.mp3");
   250             applause.play();
   251         }
   252     }
   253     
   254     private static void incrementAround(SquareType[][] arr, int x, int y) {
   255         incrementAt(arr, x - 1, y - 1);
   256         incrementAt(arr, x - 1, y);
   257         incrementAt(arr, x - 1, y + 1);
   258 
   259         incrementAt(arr, x + 1, y - 1);
   260         incrementAt(arr, x + 1, y);
   261         incrementAt(arr, x + 1, y + 1);
   262         
   263         incrementAt(arr, x, y - 1);
   264         incrementAt(arr, x, y + 1);
   265     }
   266     
   267     private static void incrementAt(SquareType[][] arr, int x, int y) {
   268         if (y >= 0 && y < arr.length) {
   269             SquareType[] r = arr[y];
   270             if (x >= 0 && x < r.length) {
   271                 SquareType sq = r[x];
   272                 if (sq != null) {
   273                     r[x] = sq.moreBombsAround();
   274                 }
   275             }
   276         }
   277     }
   278     
   279     static void showAllBombs(Mines model, SquareType state) {
   280         for (Row row : model.getRows()) {
   281             for (Square square : row.getColumns()) {
   282                 if (square.isMine()) {
   283                     square.setState(state);
   284                 }
   285             }
   286         }
   287     }
   288     
   289     @Function static void click(Mines model, Square data) {
   290         if (model.getState() == GameState.MARKING_MINE) {
   291             if (data.getState() == SquareType.UNKNOWN) {
   292                 data.setState(SquareType.MARKED);
   293                 if (allMarked(model)) {
   294                     model.setState(GameState.WON);
   295                     return;
   296                 }
   297             }
   298             model.setState(GameState.IN_PROGRESS);
   299             return;
   300         }
   301         if (model.getState() != GameState.IN_PROGRESS) {
   302             return;
   303         }
   304         if (data.getState() == SquareType.MARKED) {
   305             data.setState(SquareType.UNKNOWN);
   306             if (allMarked(model)) {
   307                 model.setState(GameState.WON);
   308             }
   309             return;
   310         }
   311         if (data.getState() != SquareType.UNKNOWN) {
   312             return;
   313         }
   314         if (data.isMine()) {
   315             Square fair = atLeastOnePlaceWhereBombCantBe(model);
   316             if (fair == null) {
   317                 if (placeBombElseWhere(model, data)) {
   318                     cleanedUp(model, data);
   319                     return;
   320                 }
   321             }
   322             explosion(model);
   323         } else {
   324             Square takeFrom = tryStealBomb(model, data);
   325             if (takeFrom != null) {
   326                 final Square fair = atLeastOnePlaceWhereBombCantBe(model);
   327                 if (fair != null) {
   328                     takeFrom.setMine(false);
   329                     data.setMine(true);
   330                     explosion(model);
   331                     return;
   332                 }
   333             }
   334             cleanedUp(model, data);
   335         }
   336     }
   337 
   338     private static void cleanedUp(Mines model, Square data) {
   339         AudioClip touch = AudioClip.create("move.mp3");
   340         touch.play();
   341         expandKnown(model, data);
   342         model.computeMines();
   343     }
   344 
   345     private static void explosion(Mines model) {
   346         showAllBombs(model, SquareType.EXPLOSION);
   347         model.setState(GameState.LOST);
   348         AudioClip oops = AudioClip.create("oops.mp3");
   349         oops.play();
   350     }
   351     
   352     private static Square tryStealBomb(Mines model, Square data) {
   353         data.setMine(true);
   354         final List<Row> rows = model.getRows();
   355         for (int y = 0; y < rows.size(); y++) {
   356             final List<Square> columns = rows.get(y).getColumns();
   357             for (int x = 0; x < columns.size(); x++) {
   358                 Square sq = columns.get(x);
   359                 if (sq == data) {
   360                     continue;
   361                 }
   362                 if (sq.isMine()) {
   363                     sq.setMine(false);
   364                     final boolean ok = isConsistent(model);
   365                     sq.setMine(true);
   366                     if (ok) {
   367                         data.setMine(false);
   368                         return sq;
   369                     }
   370                 }
   371             }
   372         }
   373         data.setMine(false);        
   374         return null;
   375     }
   376     
   377     private static Square atLeastOnePlaceWhereBombCantBe(Mines model) {
   378         final List<Row> rows = model.getRows();
   379         Square cantBe = null;
   380         int discovered = 0;
   381         for (int y = 0; y < rows.size(); y++) {
   382             final List<Square> columns = rows.get(y).getColumns();
   383             for (int x = 0; x < columns.size(); x++) {
   384                 Square sq = columns.get(x);
   385                 if (sq.getState() == SquareType.UNKNOWN) {
   386                     if (!sq.isMine()) {
   387                         if (tryStealBomb(model, sq) == null) {
   388                             cantBe = sq;
   389                         }
   390                     }
   391                 } else {
   392                     discovered++;
   393                 }
   394             }
   395         }
   396         
   397         if (discovered > 5) {
   398             return cantBe;
   399         }
   400         
   401         return null;
   402     }
   403     
   404     private static boolean placeBombElseWhere(Mines model, Square moveBomb) {
   405         List<Square> ok = new ArrayList<Square>();
   406         moveBomb.setMine(false);
   407         final List<Row> rows = model.getRows();
   408         for (int y = 0; y < rows.size(); y++) {
   409             final List<Square> columns = rows.get(y).getColumns();
   410             for (int x = 0; x < columns.size(); x++) {
   411                 Square sq = columns.get(x);
   412                 if (sq == moveBomb || sq.isMine() || sq.getState().isVisible()) {
   413                     continue;
   414                 }
   415                 sq.setMine(true);
   416                 if (isConsistent(model)) {
   417                     ok.add(sq);
   418                 }
   419                 sq.setMine(false);
   420             }
   421         }
   422         if (ok.isEmpty()) {
   423             moveBomb.setMine(true);
   424             return false;
   425         } else {
   426             int r = new Random().nextInt(ok.size());
   427             ok.get(r).setMine(true);
   428             return true;
   429         }
   430     }
   431     
   432     private static void expandKnown(Mines model, Square data) {
   433         final List<Row> rows = model.getRows();
   434         for (int y = 0; y < rows.size(); y++) {
   435             final List<Square> columns = rows.get(y).getColumns();
   436             for (int x = 0; x < columns.size(); x++) {
   437                 Square sq = columns.get(x);
   438                 if (sq == data) {
   439                     expandKnown(model, x, y);
   440                     return;
   441                 }
   442             }
   443         }
   444     }
   445     private static void expandKnown(Mines model, int x , int y) {
   446         if (y < 0 || y >= model.getRows().size()) {
   447             return;
   448         }
   449         final List<Square> columns = model.getRows().get(y).getColumns();
   450         if (x < 0 || x >= columns.size()) {
   451             return;
   452         }
   453         final Square sq = columns.get(x);
   454         if (sq.getState() == SquareType.UNKNOWN) {
   455             int around = around(model, x, y);
   456             final SquareType t = SquareType.valueOf("N_" + around);
   457             sq.setState(t);
   458             if (t == SquareType.N_0) {
   459                 expandKnown(model, x - 1, y - 1);
   460                 expandKnown(model, x - 1, y);
   461                 expandKnown(model, x - 1, y + 1);
   462                 expandKnown(model, x , y - 1);
   463                 expandKnown(model, x, y + 1);
   464                 expandKnown(model, x + 1, y - 1);
   465                 expandKnown(model, x + 1, y);
   466                 expandKnown(model, x + 1, y + 1);
   467             }
   468         }
   469     }
   470 
   471     private static int around(Mines model, int x, int y) {
   472         return 
   473             minesAt(model, x - 1, y - 1) +
   474             minesAt(model, x - 1, y) +
   475             minesAt(model, x - 1, y + 1) +
   476             minesAt(model, x , y - 1) +
   477             minesAt(model, x, y + 1) +
   478             minesAt(model, x + 1, y - 1) +
   479             minesAt(model, x + 1, y) +
   480             minesAt(model, x + 1, y + 1);
   481     }
   482     
   483     private static int minesAt(Mines model, int x, int y) {
   484         if (y < 0 || y >= model.getRows().size()) {
   485             return 0;
   486         }
   487         final List<Square> columns = model.getRows().get(y).getColumns();
   488         if (x < 0 || x >= columns.size()) {
   489             return 0;
   490         }
   491         Square sq = columns.get(x);
   492         return sq.isMine() ? 1 : 0;
   493     }
   494     
   495     private static boolean isConsistent(Mines m) {
   496         for (int row = 0; row < m.getRows().size(); row++) {
   497             Row r = m.getRows().get(row);
   498             for (int col = 0; col < r.getColumns().size(); col++) {
   499                 Square sq = r.getColumns().get(col);
   500                 if (sq.getState().isVisible()) {
   501                     int around = around(m, col, row);
   502                     if (around != sq.getState().ordinal()) {
   503                         return false;
   504                     }
   505                 }
   506             }
   507         }
   508         return true;
   509     }
   510     
   511     private static boolean allMarked(Mines m) {
   512         for (Row r : m.getRows()) {
   513             for (Square sq : r.getColumns()) {
   514                 if (sq.isMine() == (sq.getState() != SquareType.MARKED)) {
   515                     return false;
   516                 }
   517             }
   518         }
   519         for (Row r : m.getRows()) {
   520             for (Square sq : r.getColumns()) {
   521                 if (sq.isMine()) {
   522                     sq.setState(SquareType.DISCOVERED);
   523                 } else {
   524                     sq.setState(SquareType.N_0);
   525                 }
   526             }
   527         }
   528         computeMines(m);
   529         return true;
   530     }
   531 
   532     /**
   533      * Called when page is ready
   534      */
   535     public static void main(String... args) throws Exception {
   536         Mines m = new Mines();
   537         m.applyBindings();
   538     }
   539 }