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