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