chess/src/main/java/org/apidesign/html/demo/chess/BoardModel.java
author Jaroslav Tulach <jtulach@netbeans.org>
Wed, 25 Sep 2013 04:18:47 +0200
branchchess
changeset 53 bc0094a5f88c
parent 52 6bb4070d2c20
child 244 cea2063fd0f9
permissions -rw-r--r--
Can now play game against ownself
     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.html.demo.chess;
    25 
    26 import java.util.Arrays;
    27 import java.util.Collections;
    28 import java.util.List;
    29 import java.util.Timer;
    30 import java.util.TimerTask;
    31 import net.java.html.json.ComputedProperty;
    32 import net.java.html.json.Function;
    33 import net.java.html.json.Model;
    34 import net.java.html.json.ModelOperation;
    35 import net.java.html.json.OnPropertyChange;
    36 import net.java.html.json.Property;
    37 import net.java.html.sound.AudioClip;
    38 
    39 @Model(className="Board", properties={
    40     @Property(name = "player", type = String.class),
    41     @Property(name = "gameId", type = String.class),
    42     @Property(name = "whitePlayer", type = String.class),
    43     @Property(name = "blackPlayer", type = String.class),
    44     @Property(name = "alertMessage", type = String.class),
    45     @Property(name = "status", type = String.class),
    46     @Property(name = "rows", type = Row.class, array = true),
    47     @Property(name = "turn", type = Color.class),
    48     @Property(name = "moves", type = Move.class, array = true),
    49     @Property(name = "pendingMove", type = Move.class),
    50     @Property(name = "active", type = boolean.class),
    51 })
    52 public class BoardModel {
    53     @ComputedProperty static String title(String status, String gameId) {
    54         String t = status != null ? status : gameId;
    55         if (t != null && t.length() > 10) {
    56             return t.substring(0, 10);
    57         }
    58         return t;
    59     }
    60 
    61     @ModelOperation static void updateSummary(Board b, String summary) {
    62         if (summary != null) {
    63             b.setStatus(summary);
    64         }
    65     }
    66     
    67     private static final AudioClip MOVE = AudioClip.create("sounds/move.mp3");
    68     @OnPropertyChange("moves") static void playMove() {
    69         MOVE.play();
    70     }
    71 
    72     private static final AudioClip CHECK = AudioClip.create("sounds/check.mp3");
    73     private static final AudioClip CHECKMATE = AudioClip.create("sounds/checkmate.mp3");
    74     
    75     @Function static void selected(Board b, Square data) {
    76         Square previoslySelected = findSelectedSquare(b);
    77         if (previoslySelected == null) {
    78             if (data.getPiece() != null && data.getPieceColor() == b.getTurn()) {
    79                 data.setSelected(true);
    80                 Rules.computeAccessible(b, data);
    81             }
    82         } else {
    83             if (previoslySelected == data) {
    84                 data.setSelected(false);
    85                 Rules.computeAccessible(b, null);
    86                 return;
    87             }
    88             if (data.getPiece() != null && data.getPieceColor() == previoslySelected.getPieceColor()) {
    89                 previoslySelected.setSelected(false);
    90                 data.setSelected(true);
    91                 Rules.computeAccessible(b, data);
    92                 return;
    93             }
    94             if (data.isAccessible()) {
    95                 previoslySelected.setSelected(false);
    96 
    97                 Move newMove = new Move();
    98                 newMove.setFrom(previoslySelected.getPosition());
    99                 newMove.setTo(data.getPosition());
   100                 newMove.setRound(b.getMoves().size() / 2 + 1);
   101                 newMove.setPiece(previoslySelected.getPiece());
   102                 newMove.setTurn(previoslySelected.getPieceColor());
   103                 newMove.setTakes(data.getPiece() != null);
   104                 b.getMoves().add(newMove);
   105                 b.setPendingMove(newMove);
   106 
   107                 data.setPieceColor(previoslySelected.getPieceColor());
   108                 data.setPiece(previoslySelected.getPiece());
   109                 previoslySelected.setPiece(null);
   110                 previoslySelected.setPieceColor(null);
   111                 b.setTurn(b.getTurn() == Color.W ? Color.B : Color.W);
   112                 Rules.computeAccessible(b, null);
   113             }
   114         }
   115     }
   116     
   117     static class NextMove extends TimerTask {
   118         private static final Timer T = new Timer("Animate moves");
   119         private final Board b;
   120         private final Move m;
   121 
   122         public NextMove(Board b, Move m) {
   123             this.b = b;
   124             this.m = m;
   125             T.schedule(this, 1000);
   126         }
   127         
   128         
   129         @Override
   130         public void run() {
   131             b.showPosition(m);
   132         }
   133     }
   134     
   135     @ModelOperation @Function static void showPosition(Board b, Move data) {
   136         Rules.initBoard(b);
   137         boolean found = false;
   138         for (Move m : b.getMoves()) {
   139             if (found) {
   140                 b.setTurn(null);
   141                 new NextMove(b, m);
   142                 return;
   143             }
   144             Square from = findSquare(b, (char)m.getFrom().getX(), m.getFrom().getY());
   145             Square to = findSquare(b, (char)m.getTo().getX(), m.getTo().getY());
   146             to.setPiece(from.getPiece());
   147             to.setPieceColor(from.getPieceColor());
   148             from.setPiece(null);
   149             from.setPieceColor(null);
   150             if (m == data) {
   151                 found = true;
   152             }
   153         }
   154         b.setTurn(b.getMoves().size() % 2 == 0 ? Color.W : Color.B);
   155     }
   156     
   157     
   158 
   159     @Function static void rotateBoard(Board b) {
   160         Collections.reverse(b.getRows());
   161         for (Row r : b.getRows()) {
   162             Collections.reverse(r.getColumns());
   163         }
   164         Square sq = findSelectedSquare(b);
   165         if (sq != null) {
   166             sq.setSelected(false);
   167             Rules.computeAccessible(b, null);
   168         }
   169     }
   170     
   171     @ComputedProperty static boolean whiteTurn(Color turn) {
   172         return turn == Color.W;
   173     }
   174 
   175     @ComputedProperty static boolean blackTurn(Color turn) {
   176         return turn == Color.B;
   177     }
   178     
   179     @ComputedProperty static List<String> columnNames(List<Row> rows) {
   180         boolean whiteDown = rows.isEmpty() || rows.get(0).getY() == 8;
   181         String[] arr = new String[8];
   182         for (int i = 0; i < 8; i++) {
   183             String s;
   184             if (whiteDown) {
   185                 s = "" + (char)('A' + i);
   186             } else {
   187                 s = "" + (char)('H' - i);
   188             }
   189             arr[i] = s;
   190         }
   191         return Arrays.asList(arr);
   192     }
   193     
   194     static Square findSquare(Board b, char column, int row) {
   195         for (Row r : b.getRows()) {
   196             for (Square square : r.getColumns()) {
   197                 if (square.getPosition().getX() == column && square.getPosition().getY() == row) {
   198                     return square;
   199                 }
   200             }
   201         }
   202         return null;
   203     }
   204 
   205     private static Square findSquare(Board b, Position to) {
   206         return findSquare(b, (char)to.getX(), to.getY());
   207     }
   208     
   209     static Square findSelectedSquare(Board b) {
   210         for (Row row : b.getRows()) {
   211             for (Square square : row.getColumns()) {
   212                 if (square.isSelected()) {
   213                     return square;
   214                 }
   215             }
   216         }
   217         return null;
   218     }
   219 
   220     static void moveUpdate(
   221         final Board b, final Move move, 
   222         final List<String> whites, final List<String> blacks, 
   223         final Color turn, Object alert
   224     ) {
   225         final Square from = BoardModel.findSquare(b, move.getFrom());
   226         final Square to = BoardModel.findSquare(b, move.getTo());
   227         move.setPiece(from.getPiece());
   228         move.setTurn(from.getPieceColor());
   229         if (to.getPiece() != null) {
   230             move.setTakes(true);
   231         }
   232         move.setRound(b.getMoves().size() / 2 + 1);
   233         b.getMoves().add(move);
   234         Rules.initBoard(b, whites, blacks, turn);
   235     }
   236 
   237     @Model(className="Row", properties = {
   238         @Property(name = "columns", type = Square.class, array = true)
   239     })
   240     static class RowsImpl {
   241         @ComputedProperty static int y(List<Square> columns) {
   242             return columns.isEmpty() ? 0 : columns.get(0).getY();
   243         }
   244     }
   245     
   246     enum PieceType {
   247         PAWN(5), ROCK(2), KNIGHT(4), BISHOP(3), QUEEN(1), KING(0);
   248 
   249         final int entityIndex;
   250         
   251         PieceType(int ei) {
   252             this.entityIndex = ei;
   253         }
   254 
   255         static PieceType fromNotation(char notation) {
   256             switch (notation) {
   257                 case 'R': return ROCK;
   258                 case 'N': return KNIGHT;
   259                 case 'B': return BISHOP;
   260                 case 'Q': return QUEEN;
   261                 case 'K': return KING;
   262                 case 'P': return PAWN;
   263             }
   264             throw new IllegalStateException("Unexpected: " + notation);
   265         }
   266         
   267         String computeEntity(Color color) {
   268             if (color == null) {
   269                 color = Color.W;
   270             }
   271             int base;
   272             switch (color) {
   273                 case W: base = 12; break;
   274                 case B: base = 18; break;
   275                 default:
   276                     throw new AssertionError();
   277             }
   278             return "&#98" + String.valueOf(base + entityIndex) + ";";
   279         }
   280     }
   281     
   282     @Model(className="Position", properties = {
   283         @Property(name = "x", type = char.class),
   284         @Property(name = "y", type = int.class),
   285     })
   286     static class PositionImpl {
   287         @ComputedProperty static String location(int x, int y) {
   288             return "" + (char)(x - 'A' + 'a') + y;
   289         }
   290     }
   291     
   292     @Model(className="Square", properties = {
   293         @Property(name = "position", type = Position.class),
   294         @Property(name = "color", type = Color.class),
   295         @Property(name = "piece", type = PieceType.class),
   296         @Property(name = "pieceColor", type = Color.class),
   297         @Property(name = "selected", type = boolean.class),
   298         @Property(name = "accessible", type = boolean.class),
   299     })
   300     static class SquareModel {
   301         @ComputedProperty static String pieceEntity(
   302             PieceType piece, Color pieceColor
   303         ) {
   304             if (piece == null) {
   305                 return "";
   306             }
   307             return piece.computeEntity(pieceColor);
   308         }
   309         
   310         @ComputedProperty static String squareColor(
   311             Color color, boolean selected, boolean accessible
   312         ) {
   313             if (selected) {
   314                 return "selected";
   315             }
   316             if (accessible) {
   317                 return "accessible";
   318             }
   319             
   320             if (color == null) {
   321                 return "";
   322             } else {
   323                 if (color == Color.W) {
   324                     return "white";
   325                 } else {
   326                     return "black";
   327                 }
   328             }
   329         }
   330         
   331         @ComputedProperty static char x(Position position) {
   332             return position == null ? 'A' : position.getX();
   333         }
   334 
   335         @ComputedProperty static int y(Position position) {
   336             return position == null ? 1 : position.getY();
   337         }
   338     }
   339     
   340     @Model(className = "Move", properties = {
   341         @Property(name = "round", type = int.class),
   342         @Property(name = "turn", type = Color.class),
   343         @Property(name = "piece", type = PieceType.class),
   344         @Property(name = "from", type = Position.class),
   345         @Property(name = "to", type = Position.class),
   346         @Property(name = "promoted", type = PieceType.class),
   347         @Property(name = "takes", type = boolean.class),
   348         @Property(name = "check", type = boolean.class),
   349     })
   350     static class MoveImpl {
   351         @ComputedProperty static boolean whiteMove(Color turn) {
   352             return turn == Color.W;
   353         }
   354         
   355         @ComputedProperty static String html(
   356             Position from, Position to, boolean takes, PieceType piece, Color turn
   357         ) {
   358             if (from == null || to == null) {
   359                 return "";
   360             }
   361             StringBuilder sb = new StringBuilder();
   362             if (piece != null && piece != PieceType.PAWN) {
   363                 sb.append(piece.computeEntity(turn));
   364             }
   365             
   366             sb.append(from.getLocation());
   367             if (takes) {
   368                 sb.append("x");
   369             }
   370             sb.append(to.getLocation());
   371             return sb.toString();
   372         }
   373 
   374         static Move valueOf(String move) {
   375             move = move.toUpperCase();
   376             Move m = new Move();
   377             {
   378                 Position p = new Position();
   379                 p.setX(move.charAt(0));
   380                 p.setY(move.charAt(1) - '0');
   381                 m.setFrom(p);
   382             }
   383             {
   384                 Position p = new Position();
   385                 p.setX(move.charAt(2));
   386                 p.setY(move.charAt(3) - '0');
   387                 m.setTo(p);
   388             }
   389             return m;
   390         }
   391     }
   392 }