quoridor/src/main/java/cz/xelfi/quoridor/Board.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Tue, 14 Sep 2010 08:56:13 +0200
changeset 264 d60370059c3c
parent 253 ee02205edf13
permissions -rw-r--r--
Changing headers to GPLv3
jtulach@0
     1
/*
jaroslav@264
     2
 * Quoridor server and related libraries
jaroslav@264
     3
 * Copyright (C) 2009-2010 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
jtulach@8
     4
 *
jaroslav@264
     5
 * This program is free software: you can redistribute it and/or modify
jaroslav@264
     6
 * it under the terms of the GNU General Public License as published by
jaroslav@264
     7
 * the Free Software Foundation, either version 3 of the License.
jtulach@8
     8
 *
jaroslav@264
     9
 * This program is distributed in the hope that it will be useful,
jaroslav@264
    10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
jaroslav@264
    11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
jaroslav@264
    12
 * GNU General Public License for more details.
jtulach@8
    13
 *
jaroslav@264
    14
 * You should have received a copy of the GNU General Public License
jaroslav@264
    15
 * along with this program. Look for COPYING file in the top folder.
jaroslav@264
    16
 * If not, see http://www.gnu.org/licenses/.
jtulach@0
    17
 */
jtulach@0
    18
jtulach@0
    19
package cz.xelfi.quoridor;
jtulach@0
    20
jtulach@15
    21
import cz.xelfi.quoridor.Player.Direction;
jtulach@10
    22
import java.io.BufferedReader;
jtulach@10
    23
import java.io.EOFException;
jtulach@9
    24
import java.io.IOException;
jtulach@9
    25
import java.io.Reader;
jtulach@9
    26
import java.io.Writer;
jtulach@10
    27
import java.util.ArrayList;
jtulach@3
    28
import java.util.Arrays;
jtulach@3
    29
import java.util.BitSet;
jtulach@3
    30
import java.util.Collection;
jtulach@3
    31
import java.util.Collections;
jtulach@3
    32
import java.util.HashSet;
jtulach@3
    33
import java.util.List;
jtulach@3
    34
import java.util.Set;
jaroslav@178
    35
import java.util.TreeSet;
jaroslav@253
    36
import java.util.logging.Level;
jaroslav@253
    37
import java.util.logging.Logger;
jtulach@10
    38
import java.util.regex.Matcher;
jtulach@10
    39
import java.util.regex.Pattern;
jtulach@3
    40
jtulach@0
    41
/**
jtulach@16
    42
 * Represents a snapshot of the game position,
jtulach@16
    43
 * including all the placed and not-yet placed fences and player positions.
jtulach@16
    44
 * It it can print itself to stream in
jtulach@16
    45
 * <a href="http://www.gamerz.net/pbmserv/quoridor.html">ascii art format<a/>
jtulach@16
    46
 * and can it read back. The class is immutable
jtulach@16
    47
 * but it contains {@link #apply(cz.xelfi.quoridor.Move)}
jtulach@16
    48
 * that produce new {@link Board} with position created after
jtulach@16
    49
 * applying some {@link Move}. Use:
jtulach@16
    50
 * <pre>
jtulach@16
    51
 * Board whiteOnTurn = Board.empty();
jtulach@16
    52
 * Board blackOnTurn = whiteOnTurn.apply(Move.NORTH);
jtulach@16
    53
 * Board whiteAgain = blackOnTurn.apply(Move.SOUTH);
jtulach@16
    54
 * Board withOneFence = whiteAgain.apply(Move.fence('D', 7));
jtulach@16
    55
 * </pre>
jtulach@0
    56
 * 
jtulach@0
    57
 * @author Jaroslav Tulach
jtulach@0
    58
 */
jtulach@0
    59
public final class Board {
jtulach@75
    60
    /** winner, if any */
jtulach@75
    61
    private final Player winner;
jtulach@0
    62
    /** players */
jtulach@3
    63
    private final List<Player> players;
jtulach@0
    64
    /** fences placed on board */
jtulach@3
    65
    private final Set<Fence> fences;
jtulach@0
    66
    /** occurpied bits (coordinates encoded by toIndex methods) 
jtulach@0
    67
                         [N]
jtulach@0
    68
               
jtulach@0
    69
        +-----------------------------------+
jtulach@0
    70
     6  |                                   |          
jtulach@0
    71
     5  |   +   +   +   +   +   +   +   +   |  
jtulach@0
    72
     4  |                                   |          
jtulach@0
    73
     3  |   +   +   +   +   +   +   +   +   |  
jtulach@0
    74
     2  |                                   |          
jtulach@0
    75
     1  |   +   +   +   +   +   +   +   +   |  
jtulach@0
    76
     0  |                                   |
jtulach@0
    77
     9  |   +   +   +   +   +   +   +   +   |
jtulach@0
    78
[W]  8  |                                   |     [E]
jtulach@0
    79
     7  |   +   +   +   +   +   +   +   +   |
jtulach@0
    80
     6  |                                   |
jtulach@0
    81
     5  |   +   +   +   +   +   +   +   +   |
jtulach@0
    82
     4  |                                   |
jtulach@0
    83
     3  |   +   +   +   +   +   +   +   +   |
jtulach@0
    84
     2  |                                   |
jtulach@0
    85
     1  |   +   +   +   +   +   +   +   +   |
jtulach@0
    86
     0  |                                   |
jtulach@0
    87
        +-----------------------------------+
jtulach@0
    88
          0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
jtulach@0
    89
                         [S]
jtulach@0
    90
     
jtulach@0
    91
     * even indexes == position of the pawns
jtulach@0
    92
     * odd indexes  == centers of fences
jtulach@0
    93
     * even x odd   == side of a fence
jtulach@0
    94
     * odd x even   == side of a fence
jtulach@0
    95
     */
jtulach@3
    96
    private final BitSet occupied;
jtulach@3
    97
    /** which player's turn is next? */
jtulach@3
    98
    private final int turn;
jtulach@0
    99
    
jtulach@0
   100
    /**
jtulach@0
   101
     * Creates a new instance of Board 
jtulach@0
   102
     */
jtulach@0
   103
    private Board (int x1, int y1, int f1, int x2, int y2, int f2) {
jtulach@3
   104
        this.players = Collections.unmodifiableList (Arrays.asList (new Player[] {
jtulach@0
   105
            new Player (x1, y1, f1, Player.Direction.NORTH),
jtulach@0
   106
            new Player (x2, y2, f2, Player.Direction.SOUTH),
jtulach@0
   107
        }));
jtulach@3
   108
        this.fences = Collections.emptySet ();
jtulach@0
   109
        try {
jtulach@0
   110
            this.occupied = computeOccupied (players, fences);
jtulach@0
   111
        } catch (IllegalPositionException ex) {
jtulach@0
   112
            throw new IllegalStateException (ex.getMessage ());
jtulach@0
   113
        }
jtulach@3
   114
        this.turn = 0;
jtulach@75
   115
        this.winner = null;
jtulach@0
   116
    }
jtulach@0
   117
    
jtulach@0
   118
    /** Copy constructor that provides players and fences.
jtulach@0
   119
     */
jtulach@3
   120
    private Board (int turn, List<Player> players, Set<Fence> fences)
jtulach@0
   121
    throws IllegalPositionException {
jtulach@3
   122
        this.players = Collections.unmodifiableList (players);
jtulach@3
   123
        this.fences = Collections.unmodifiableSet (fences);
jtulach@0
   124
        this.occupied = computeOccupied (players, fences);
jaroslav@92
   125
jtulach@0
   126
        for (Player p : players) {
jaroslav@92
   127
            BitSet bs = new BitSet(17 * 17);
jaroslav@92
   128
            if (!accessibleFinalLine (p, p.endDirection, occupied, bs)) {
jtulach@0
   129
                throw new IllegalPositionException ("Player " + p + " cannot reach " + p.endDirection + " side"); // NOI18N
jtulach@0
   130
            }
jtulach@0
   131
        }
jtulach@3
   132
        this.turn = turn % players.size();
jtulach@75
   133
        this.winner = null;
jtulach@75
   134
    }
jtulach@75
   135
jtulach@75
   136
    /** Copy constructor for resigning the game */
jtulach@75
   137
    private Board(Board previous, int winner) {
jtulach@75
   138
        this.players = previous.players;
jtulach@75
   139
        this.turn = winner % players.size();
jtulach@75
   140
        this.fences = previous.fences;
jtulach@75
   141
        this.occupied = previous.occupied;
jtulach@75
   142
        this.winner = players.get(this.turn);
jtulach@0
   143
    }
jtulach@0
   144
    
jtulach@16
   145
    /** Returns empty board with default starting position.
jtulach@16
   146
     * @return board with two pawns.
jtulach@0
   147
     */
jtulach@16
   148
    public static Board empty () {
jtulach@0
   149
        return new Board (8, 0, 10, 8, 16, 10);
jtulach@0
   150
    }
jtulach@0
   151
    
jtulach@0
   152
    /** Returns players in the game.
jtulach@0
   153
     * @return player object
jtulach@0
   154
     */
jtulach@3
   155
    public List<Player> getPlayers () {
jtulach@0
   156
        return players;
jtulach@0
   157
    }
jtulach@0
   158
    
jtulach@0
   159
    /** The fences currently on board. 
jtulach@0
   160
     * 
jtulach@0
   161
     * @return immutable set of Fences
jtulach@0
   162
     */
jtulach@3
   163
    public Set<Fence> getFences () {
jtulach@0
   164
        return fences;
jtulach@0
   165
    }
jtulach@3
   166
jtulach@3
   167
    /** The player that is supposed to play now.
jtulach@75
   168
     * @return the player to do next move, null if the game is over
jtulach@3
   169
     */
jtulach@3
   170
    public Player getCurrentPlayer() {
jtulach@75
   171
        if (getWinner() != null) {
jtulach@75
   172
            return null;
jtulach@75
   173
        }
jtulach@3
   174
        return players.get(turn);
jtulach@3
   175
    }
jtulach@14
   176
jtulach@14
   177
    /** The play who wins on current board.
jtulach@14
   178
     *
jtulach@14
   179
     * @return the winning player or <code>null</code>
jtulach@14
   180
     */
jtulach@14
   181
    public Player getWinner() {
jtulach@75
   182
        if (winner != null) {
jtulach@75
   183
            return winner;
jtulach@75
   184
        }
jtulach@14
   185
        for (Player p : players) {
jtulach@14
   186
            if (p.endDirection.reached(p)) {
jtulach@14
   187
                return p;
jtulach@14
   188
            }
jtulach@14
   189
        }
jtulach@14
   190
        return null;
jtulach@14
   191
    }
jtulach@16
   192
jtulach@16
   193
    /** Applies given move to current board.
jtulach@16
   194
     *
jtulach@16
   195
     * @param move move creates by one of the factory methods or fields of the {@link Move} class
jtulach@16
   196
     * @return new board derived from this one
jtulach@16
   197
     *
jtulach@16
   198
     * @throws cz.xelfi.quoridor.IllegalPositionException throws exception if the move is illegal
jtulach@16
   199
     */
jtulach@3
   200
    public Board apply(Move move) throws IllegalPositionException {
jtulach@14
   201
        if (getWinner() != null) {
jtulach@14
   202
            throw new IllegalPositionException("Game finished!"); // NOI18N
jtulach@14
   203
        }
jaroslav@253
   204
        return apply(move, getCurrentPlayer());
jaroslav@253
   205
    }
jtulach@14
   206
jaroslav@253
   207
    private Board apply(Move move, Player p) throws IllegalPositionException {
jtulach@3
   208
        if (move.direction != null) {
jaroslav@253
   209
            return move(p, move.direction);
jtulach@3
   210
        } else {
jtulach@75
   211
            if (move.fence != null) {
jaroslav@253
   212
                return fence(p, move.fence);
jtulach@75
   213
            } else {
jtulach@75
   214
                return new Board(this, turn + 1);
jtulach@75
   215
            }
jtulach@3
   216
        }
jtulach@3
   217
    }
jaroslav@253
   218
    
jaroslav@253
   219
    /** Is there a move that would take the player onto given position while
jaroslav@253
   220
     * respecting situation on board?
jaroslav@253
   221
     * 
jaroslav@253
   222
     * @param p the player to try to move
jaroslav@253
   223
     * @param column desired x-coordinate. Between 0-8. See {@link Player#getColumn()}.
jaroslav@253
   224
     * @param row desired y-coordinate. Between 0-8. See {@link Player#getRow()}.
jaroslav@253
   225
     * @return the move that can be {@link #apply applied} to the position and
jaroslav@253
   226
     *   that would move the player p into desired position or null, if such 
jaroslav@253
   227
     *   move does not exist
jaroslav@253
   228
     * @since 1.4
jaroslav@253
   229
     */
jaroslav@253
   230
    public Move findMove(Player p, int column, int row) {
jaroslav@253
   231
        int idx = getPlayers().indexOf(p);
jaroslav@253
   232
        if (idx < 0) {
jaroslav@253
   233
            return null;
jaroslav@253
   234
        }
jaroslav@253
   235
        for (Move m : Move.allMovements()) {
jaroslav@253
   236
            Board b;
jaroslav@253
   237
            try {
jaroslav@253
   238
                b = apply(m, p);
jaroslav@253
   239
            } catch (IllegalPositionException ex) {
jaroslav@253
   240
                continue;
jaroslav@253
   241
            }
jaroslav@253
   242
            if (
jaroslav@253
   243
                b.getPlayers().get(idx).getColumn() == column &&
jaroslav@253
   244
                b.getPlayers().get(idx).getRow() == row
jaroslav@253
   245
            ) {
jaroslav@253
   246
                return m;
jaroslav@253
   247
            }
jaroslav@253
   248
        }
jaroslav@253
   249
        return null;
jaroslav@253
   250
    }
jtulach@0
   251
jtulach@17
   252
    /** Can the move be applied to current board position?
jtulach@17
   253
     * 
jtulach@17
   254
     * @param move the move to apply
jtulach@17
   255
     * @return true if one can call {@link #apply} method without getting 
jtulach@17
   256
     *   an exception
jtulach@17
   257
     */
jtulach@17
   258
    public boolean isApplicable(Move move) {
jtulach@17
   259
        try {
jtulach@17
   260
            // trivial implementation is enough for now
jtulach@17
   261
            apply(move);
jtulach@17
   262
            return true;
jtulach@17
   263
        } catch (IllegalPositionException ex) {
jtulach@17
   264
            return false;
jtulach@17
   265
        }
jtulach@17
   266
    }
jtulach@17
   267
    
jtulach@0
   268
    /** Moves the player in given direction. The direction usually
jtulach@0
   269
     * is one of Player.Direction constants, but in case the move
jtulach@0
   270
     * is a jump over another players pawn it can be followed by
jtulach@0
   271
     * another (usually, if there is no fence the same) direction.
jtulach@0
   272
     *
jtulach@0
   273
     * @param player the player to move
jtulach@0
   274
     * @param where one or two directions saying where
jtulach@0
   275
     * @return the new board
jtulach@0
   276
     * @exception IllegalPositionException if the move is not possible
jtulach@0
   277
     */
jtulach@3
   278
    final Board move (Player player, Player.Direction... where) throws IllegalPositionException {
jtulach@0
   279
        if (where.length != 1 && where.length != 2) {
jtulach@0
   280
            throw new IllegalPositionException ("Move over one or two Directions"); // NOI18N
jtulach@0
   281
        }
jtulach@0
   282
        
jtulach@0
   283
        int index = players.indexOf (player);
jtulach@0
   284
        Player[] arr = players.toArray (new Player[0]);
jtulach@0
   285
jtulach@0
   286
        Player oneStep = newPosition (player, where[0]);
jtulach@0
   287
        
jtulach@0
   288
        if (where.length == 1) {
jtulach@0
   289
            arr[index] = oneStep;
jtulach@3
   290
            return new Board(turn + 1, Arrays.asList (arr), fences);
jtulach@0
   291
        }
jtulach@0
   292
jtulach@0
   293
        // straight jump over
jtulach@0
   294
        for (Player p : players) {
jtulach@26
   295
            if (p.getXInternal () == oneStep.getXInternal () && p.getYInternal() == oneStep.getYInternal ()) {
jtulach@0
   296
                // ok, we are jumping over this one
jtulach@0
   297
                GO_ON: if (where[0] != where[1]) {
jtulach@0
   298
                    // first of all ensure that we cannot go straight
jtulach@0
   299
                    try {
jtulach@0
   300
                        newPosition (oneStep, where[0]);
jtulach@0
   301
                    } catch (IllegalPositionException ex) {
jtulach@0
   302
                        // ok
jtulach@0
   303
                        break GO_ON;
jtulach@0
   304
                    }
jtulach@0
   305
                    throw new IllegalPositionException ("You have to jump straight if there is no wall"); // NOI18N
jtulach@0
   306
                }
jtulach@0
   307
                arr[index] = newPosition (oneStep, where[1]);
jaroslav@237
   308
                if (arr[index].equals(player)) {
jaroslav@237
   309
                    throw new IllegalPositionException("You cannot jump forth and back"); // NOI18N
jaroslav@237
   310
                }
jtulach@3
   311
                return new Board (turn + 1, Arrays.asList (arr), fences);
jtulach@0
   312
            }
jtulach@0
   313
        }
jtulach@0
   314
        throw new IllegalPositionException ("Cannot use multi direction when there is not oponent pawn"); // NOI18N
jtulach@0
   315
    }
jtulach@0
   316
    
jtulach@3
   317
    final Board fence (Player player, char x, int y, Fence.Orientation orientation) throws IllegalPositionException {
jtulach@3
   318
        return fence(player, new Fence ((x - 'A') * 2 + 1, y * 2 - 1, orientation));
jtulach@3
   319
    }
jtulach@3
   320
jtulach@40
   321
    private void columnLine(int width, int spaceX, Writer w) throws IOException {
jtulach@40
   322
        char ch = 'A';
jtulach@40
   323
        for (int x = 0; x < width - 1; x++) {
jtulach@40
   324
            if (x % (spaceX + 1) == 0 && x > 0) {
jtulach@40
   325
                w.write(ch);
jtulach@40
   326
                ch = (char) (ch + 1);
jtulach@40
   327
            } else {
jtulach@40
   328
                w.write(' ');
jtulach@40
   329
            }
jtulach@40
   330
        }
jtulach@40
   331
    }
jtulach@40
   332
jtulach@3
   333
    private Board fence(Player player, Fence fence) throws IllegalPositionException {
jtulach@0
   334
        if (player.getFences () == 0) {
jtulach@0
   335
            throw new IllegalPositionException ("Not enough fences: " + player); // NOI18N
jtulach@0
   336
        }
jtulach@107
   337
jtulach@0
   338
        int index = players.indexOf (player);
jtulach@0
   339
        Player[] arr = players.toArray (new Player[0]);
jtulach@26
   340
        arr[index] = new Player (arr[index].getXInternal(), arr[index].getYInternal(), arr[index].getFences() - 1, arr[index].endDirection);
jtulach@0
   341
        
jtulach@3
   342
        HashSet<Fence> fen = new HashSet<Fence> (this.fences);
jtulach@107
   343
        if (!fen.add (fence)) {
jtulach@107
   344
            throw new IllegalPositionException ("Fence already prsent: " + fence); // NOI18N
jtulach@107
   345
        }
jtulach@0
   346
        
jtulach@3
   347
        return new Board (turn + 1, Arrays.asList (arr), fen);
jtulach@0
   348
    }
jtulach@9
   349
jtulach@9
   350
    //
jtulach@9
   351
    // Serialization
jtulach@9
   352
    //
jtulach@9
   353
jtulach@13
   354
    private static final Pattern northSouthPattern = Pattern.compile("(\\+(\\|*)(\\*)?-+\\+)");
jaroslav@48
   355
jaroslav@48
   356
    /** Reads the board from a reader. Opposite operation to {@link #write(java.io.Writer)}.
jaroslav@48
   357
     *
jaroslav@48
   358
     * @param r the reader
jaroslav@48
   359
     * @return the read board
jaroslav@48
   360
     * @throws IOException if I/O error occurs
jaroslav@48
   361
     * @throws IllegalPositionException if the reader does not contain description
jaroslav@48
   362
     *   of the board
jaroslav@48
   363
     */
jtulach@10
   364
    public static Board read(Reader r) throws IOException, IllegalPositionException {
jtulach@10
   365
        BufferedReader b = new BufferedReader(r);
jtulach@10
   366
        for (;;) {
jtulach@10
   367
            String s = b.readLine();
jtulach@10
   368
            if (s == null) {
jtulach@10
   369
                throw new IOException("No board found!");
jtulach@10
   370
            }
jtulach@11
   371
            Matcher m = northSouthPattern.matcher(s);
jtulach@10
   372
            if (m.find()) {
jtulach@13
   373
                return readFromBetween(b, m);
jtulach@10
   374
            }
jtulach@10
   375
        }
jtulach@10
   376
    }
jtulach@10
   377
jaroslav@48
   378
    /** Translates the string into board, if possible. String created by
jaroslav@48
   379
     * use of {@link #toString()} is accepted, more information about the
jaroslav@48
   380
     * format is avaliable in the description of {@link #write(java.io.Writer)}
jaroslav@48
   381
     * method.
jaroslav@48
   382
     *
jaroslav@48
   383
     * @param board string to analyze
jaroslav@48
   384
     * @return board object, if the string can be read
jaroslav@48
   385
     * @throws IllegalPositionException if the string does not represent the board
jaroslav@48
   386
     */
jaroslav@178
   387
    public static Board valueOf(String board) {
jaroslav@178
   388
        return new Board(board);
jaroslav@178
   389
    }
jaroslav@178
   390
jtulach@12
   391
    private static int assertChar(String s, int pos, char... ch) throws IOException {
jtulach@12
   392
        if (s.length() >= pos) {
jtulach@12
   393
            for (int i = 0; i < ch.length; i++) {
jtulach@12
   394
                if (ch[i] == s.charAt(pos)) {
jtulach@12
   395
                    return i;
jtulach@12
   396
                }
jtulach@12
   397
            }
jtulach@10
   398
        }
jtulach@12
   399
        throw new IOException("Not found " + ch[0] + " at " + pos + " in" + s);
jtulach@10
   400
    }
jtulach@10
   401
jtulach@13
   402
    private static Player findPlayer(
jtulach@13
   403
        Player previous, String line, int y, int spaceX, Player.Direction dir, int fences
jtulach@10
   404
    ) {
jtulach@13
   405
        int index = line.indexOf(dir.player);
jtulach@10
   406
        if (index == -1) {
jtulach@13
   407
            return previous;
jtulach@10
   408
        }
jtulach@10
   409
        int x = (index - 1) / (spaceX + 1) * 2;
jtulach@13
   410
        return new Player(x, y - 1, fences, dir);
jtulach@10
   411
    }
jtulach@10
   412
jtulach@13
   413
    private static Board readFromBetween(BufferedReader b, Matcher firstMatcher)
jtulach@10
   414
    throws IOException, IllegalPositionException {
jtulach@13
   415
        final int from = firstMatcher.start(1);
jtulach@13
   416
        final int to = firstMatcher.end(1);
jtulach@13
   417
        final int northFences = firstMatcher.end(2) - firstMatcher.start(2);
jtulach@10
   418
        final int spaceX = (to - from - 1) / 9 - 1;
jtulach@10
   419
        final int spaceY = 1;
jtulach@10
   420
jtulach@13
   421
        Player p = null;
jtulach@13
   422
        Player q = null;
jtulach@12
   423
        Set<Fence> fences = new HashSet<Fence>();
jtulach@10
   424
jtulach@9
   425
        StringBuffer sb = new StringBuffer();
jtulach@40
   426
        int row = 7;
jtulach@40
   427
        for (int y = (spaceY + 1) * 9 - 1; y > 0; y--) {
jtulach@10
   428
            String s = b.readLine();
jtulach@10
   429
            if (s == null) {
jtulach@10
   430
                throw new EOFException();
jtulach@9
   431
            }
jtulach@10
   432
            sb.append(s);
jtulach@10
   433
            sb.append('\n');
jtulach@10
   434
            if (s.length() < to) {
jtulach@10
   435
                throw new IOException("Too short line: " + s); // NOI18N
jtulach@10
   436
            }
jtulach@10
   437
            assertChar(s, from, '|');
jtulach@10
   438
            assertChar(s, to - 1, '|');
jtulach@10
   439
jtulach@10
   440
            if (y % (spaceY + 1) == 0) {
jtulach@10
   441
                for (int x = 1; x < 9; x++) {
jtulach@12
   442
                    switch (assertChar(s, from + (spaceX + 1) * x, '+', '-', '|')) {
jtulach@12
   443
                        case 1:
jtulach@12
   444
                            fences.add(new Fence(x * 2 - 1, row * 2 + 1, Fence.Orientation.HORIZONTAL));
jtulach@12
   445
                            break;
jtulach@12
   446
                        case 2:
jtulach@12
   447
                            fences.add(new Fence(x * 2 - 1, row * 2 + 1, Fence.Orientation.VERTICAL));
jtulach@12
   448
                            break;
jtulach@12
   449
                        case 0:
jtulach@12
   450
                            break;
jtulach@12
   451
                        default:
jtulach@12
   452
                            assert false;
jtulach@12
   453
                    }
jtulach@10
   454
                }
jtulach@40
   455
                row--;
jtulach@10
   456
            } else {
jtulach@10
   457
                String line = s.substring(from, to);
jtulach@13
   458
                p = findPlayer(p, line, y, spaceX, Player.Direction.NORTH, -1);
jtulach@13
   459
                q = findPlayer(q, line, y, spaceX, Player.Direction.SOUTH, northFences);
jtulach@10
   460
            }
jtulach@9
   461
        }
jtulach@10
   462
jtulach@11
   463
        String last = b.readLine();
jtulach@11
   464
        if (last == null) {
jtulach@11
   465
            throw new EOFException();
jtulach@11
   466
        }
jtulach@13
   467
        Matcher lastMatcher = northSouthPattern.matcher(last);
jtulach@13
   468
        if (!lastMatcher.find()) {
jtulach@11
   469
            throw new IOException("Unrecognized last line: " + last);
jtulach@11
   470
        }
jtulach@11
   471
jtulach@13
   472
        List<Player> arr = new ArrayList<Player>(2);
jtulach@13
   473
        assert p != null;
jtulach@13
   474
        int southFences = lastMatcher.end(2) - lastMatcher.start(2);
jtulach@26
   475
        arr.add(new Player(p.getXInternal(), p.getYInternal(), southFences, p.endDirection));
jtulach@13
   476
        arr.add(q);
jtulach@13
   477
        int turn = "*".equals(lastMatcher.group(3)) ? 0 : 1; // NOI18N
jtulach@13
   478
        return new Board(turn, arr, fences);
jtulach@9
   479
    }
jtulach@9
   480
jtulach@9
   481
jtulach@0
   482
    
jtulach@40
   483
    /** Writes the board to the provided writer. <b>P</b> denotes position
jtulach@40
   484
     * of the first player and <b>Q</b> of the second. Number of remaining
jtulach@40
   485
     * fences of each player are shown in left bottom and left top corner
jtulach@40
   486
     * as appropriate number of <b>|||</b> - one <b>|</b> per fence. The
jtulach@40
   487
     * player that is supposed to move in this turn has <b>*</b> right after
jtulach@40
   488
     * the set of its fences.
jtulach@0
   489
     *
jtulach@9
   490
     * <pre>
jtulach@40
   491
                         [N]
jtulach@40
   492
jtulach@40
   493
            A   B   C   D   E   F   G   H
jtulach@0
   494
            |   |   |   |   |   |   |   |
jtulach@11
   495
        +|||||------------------------------+
jtulach@40
   496
        |                 Q                 |
jtulach@40
   497
     8--|   +   +   +   +   +   +   +   +   |--8
jtulach@11
   498
        |       |                           |           
jtulach@40
   499
     7--|   +   |   +   +-------+-------+   |--7
jtulach@11
   500
        |       |       |                   |           
jtulach@40
   501
     6--|-------+-------|-------+-------+   |--6
jtulach@0
   502
        |               |                   |          
jtulach@40
   503
     5--|   +   +   +   +   +   +   +   +   |--5
jtulach@40
   504
[W]     |               |                   |     [E]
jtulach@40
   505
     4--|   +   +   +   |   +   +   +-------|--4
jtulach@40
   506
        |               |                   |
jtulach@40
   507
     3--|   +   +   +   +-------+   +   +   |--3
jtulach@40
   508
        |                                   |
jtulach@40
   509
     2--|   +   +   +   +   +   +   +   +   |--2
jtulach@40
   510
        |                       |           |
jtulach@40
   511
     1--|   +   +   +   +   +   |   +   +   |--1
jtulach@40
   512
        |                 P     |           |
jtulach@40
   513
        +|||*-------------------------------+
jtulach@0
   514
            |   |   |   |   |   |   |   |
jtulach@40
   515
            A   B   C   D   E   F   G   H
jtulach@40
   516
jtulach@40
   517
                         [S]
jtulach@9
   518
     </pre>
jtulach@0
   519
     * @param w writer to write the board to
jtulach@0
   520
     * @exception IOException if communiction with writer fails
jtulach@0
   521
     */
jtulach@9
   522
    public void write (Writer w) throws IOException {
jtulach@9
   523
        write(w, 3, 1);
jtulach@0
   524
    }
jtulach@40
   525
jtulach@40
   526
    private void northSouthSign(int width, char sign, Writer w) throws IOException {
jtulach@40
   527
        int middle = width / 2;
jtulach@40
   528
        for (int x = 0; x < width; x++) {
jtulach@40
   529
            char ch = ' ';
jtulach@40
   530
            if (x == middle - 1) {
jtulach@40
   531
                ch = '[';
jtulach@40
   532
            }
jtulach@40
   533
            if (x == middle) {
jtulach@40
   534
                ch = sign;
jtulach@40
   535
            }
jtulach@40
   536
            if (x == middle + 1) {
jtulach@40
   537
                ch = ']';
jtulach@40
   538
            }
jtulach@40
   539
            w.write(ch);
jtulach@40
   540
        }
jtulach@40
   541
        w.write(System.getProperty("line.separator")); // NOI18N
jtulach@40
   542
    }
jtulach@40
   543
jtulach@40
   544
    private void subColumnLine(int width, int spaceX, Writer w) throws IOException {
jtulach@40
   545
        for (int x = 0; x < width - 1; x++) {
jtulach@40
   546
            if (x % (spaceX + 1) == 0 && x > 0) {
jtulach@40
   547
                w.write('|');
jtulach@40
   548
            } else {
jtulach@40
   549
                w.write(' ');
jtulach@40
   550
            }
jtulach@40
   551
        }
jtulach@40
   552
    }
jtulach@0
   553
    
jtulach@0
   554
    /** This will print the board with provided spacing.
jtulach@0
   555
     * This is example of 3:1 spacing: 
jtulach@0
   556
     * <pre>
jtulach@0
   557
     *  +---+
jtulach@0
   558
     *  |sss|
jtulach@0
   559
     *  +---+
jtulach@0
   560
     * </pre>
jtulach@0
   561
     * and this 4:2 spacing:
jtulach@0
   562
     * <pre>
jtulach@0
   563
     *  +----+
jtulach@0
   564
     *  |ssss|
jtulach@0
   565
     *  |ssss|
jtulach@0
   566
     *  +----+
jtulach@0
   567
     * </pre>
jtulach@0
   568
     */
jtulach@9
   569
    private void write (Writer w, int spaceX, int spaceY) throws IOException {
jtulach@9
   570
        int width = spaceX * 9 + 10;
jtulach@9
   571
        int height = spaceY * 9 + 10;
jtulach@9
   572
        char[][] desk = new char[height][];
jtulach@9
   573
        for (int i = 0; i < height; i++) {
jtulach@9
   574
            desk[i] = new char[width];
jtulach@9
   575
            for (int j = 0; j < width; j++) {
jtulach@9
   576
                if (i % (spaceY + 1) == 0 && j % (spaceX + 1) == 0) {
jtulach@9
   577
                    desk[i][j] = '+';
jtulach@9
   578
                } else {
jtulach@9
   579
                    desk[i][j] = ' ';
jtulach@9
   580
                }
jtulach@0
   581
            }
jtulach@9
   582
            desk[i][0] = '|';
jtulach@9
   583
            desk[i][width - 1] = '|';
jtulach@0
   584
        }
jtulach@9
   585
        for (int i = 1; i < width - 1; i++) {
jtulach@9
   586
            desk[0][i] = '-';
jtulach@9
   587
            desk[height -1][i] = '-';
jtulach@9
   588
        }
jtulach@9
   589
        desk[0][0] = '+';
jtulach@9
   590
        desk[0][width - 1] = '+';
jtulach@9
   591
        desk[height - 1][0] = '+';
jtulach@9
   592
        desk[height - 1][width - 1] = '+';
jtulach@9
   593
jtulach@9
   594
        {
jtulach@9
   595
            for (Player p : players) {
jtulach@26
   596
                int px = p.getXInternal() / 2;
jtulach@26
   597
                int py = p.getYInternal() / 2;
jtulach@9
   598
                desk
jtulach@9
   599
                    [1 + py * (spaceY + 1) + spaceY / 2]
jtulach@13
   600
                    [1 + px * (spaceX + 1) + spaceX / 2] = p.endDirection.player;
jtulach@13
   601
                paintLeftFences(desk, p.endDirection, p.getFences(), p.equals(getCurrentPlayer()));
jtulach@9
   602
            }
jtulach@0
   603
        }
jtulach@0
   604
        
jtulach@0
   605
        for (Fence f : fences) {
jtulach@12
   606
            int fx = (f.getX() / 2 + 1) * (spaceX + 1);
jtulach@12
   607
            int fy = (f.getY() / 2 + 1) * (spaceY + 1);
jtulach@0
   608
            switch (f.getOrientation()) {
jtulach@0
   609
                case HORIZONTAL:
jtulach@12
   610
                    for (int i = -spaceX; i <= spaceX; i++) {
jtulach@12
   611
                        desk[fy][fx + i] = '-';
jtulach@0
   612
                    }
jtulach@0
   613
                    break;
jtulach@0
   614
                case VERTICAL:
jtulach@12
   615
                    for (int i = -spaceY; i <= spaceY; i++) {
jtulach@12
   616
                        desk[fy + i][fx] = '|';
jtulach@0
   617
                    }
jtulach@0
   618
                    break;
jtulach@0
   619
                default: 
jtulach@11
   620
                    throw new IllegalStateException ("Unknown orientation: " + f.getOrientation ()); // NOI18N
jtulach@0
   621
            }
jtulach@0
   622
        }
jtulach@40
   623
        w.write("        ");
jtulach@40
   624
        northSouthSign(width, 'N', w);
jtulach@40
   625
        w.write(System.getProperty("line.separator")); // NOI18N
jtulach@40
   626
        w.write("        ");
jtulach@40
   627
        columnLine(width, spaceX, w);
jtulach@40
   628
        w.write(System.getProperty("line.separator")); // NOI18N
jtulach@40
   629
        w.write("        ");
jtulach@40
   630
        subColumnLine(width, spaceX, w);
jtulach@40
   631
        w.write(System.getProperty("line.separator")); // NOI18N
jtulach@40
   632
        int cnt = 9;
jtulach@40
   633
        for (int y = height - 1; y >= 0; y--) {
jtulach@40
   634
            if (y == height / 2) {
jtulach@40
   635
                w.write("[W]  ");
jtulach@40
   636
            } else {
jtulach@40
   637
                w.write("     ");
jtulach@40
   638
            }
jtulach@40
   639
            boolean number = y % (spaceY + 1) == 0 && y > 0 && y < height - 1;
jtulach@40
   640
            if (number) {
jtulach@40
   641
                cnt--;
jtulach@40
   642
                w.write(Integer.toString(cnt));
jtulach@40
   643
                w.write("--");
jtulach@40
   644
            } else {
jtulach@40
   645
                w.write("   ");
jtulach@40
   646
            }
jtulach@9
   647
            for (int x = 0; x < width; x++) {
jtulach@9
   648
                w.write(desk[y][x]);
jtulach@9
   649
            }
jtulach@40
   650
            if (number) {
jtulach@40
   651
                w.write("--");
jtulach@40
   652
                w.write(Integer.toString(cnt));
jtulach@40
   653
            } else {
jtulach@40
   654
                w.write("   ");
jtulach@40
   655
            }
jtulach@40
   656
            if (y == height / 2) {
jtulach@40
   657
                w.write("  [E]");
jtulach@40
   658
            }
jtulach@9
   659
            w.write(System.getProperty("line.separator")); // NOI18N
jtulach@9
   660
        }
jtulach@40
   661
        w.write("        ");
jtulach@40
   662
        subColumnLine(width, spaceX, w);
jtulach@40
   663
        w.write(System.getProperty("line.separator")); // NOI18N
jtulach@40
   664
        w.write("        ");
jtulach@40
   665
        columnLine(width, spaceX, w);
jtulach@40
   666
        w.write(System.getProperty("line.separator")); // NOI18N
jtulach@40
   667
        w.write(System.getProperty("line.separator")); // NOI18N
jtulach@40
   668
        w.write("        ");
jtulach@40
   669
        northSouthSign(width, 'S', w);
jtulach@0
   670
    }
jtulach@11
   671
jtulach@11
   672
jtulach@13
   673
    private void paintLeftFences(char[][] desk, Direction endDirection, int fences, boolean currentTurn) {
jtulach@13
   674
        assert fences >= 0 && fences <= 10 : "Players: " + players;
jtulach@11
   675
        switch (endDirection) {
jtulach@40
   676
            case SOUTH: {
jtulach@11
   677
                for (int i = 0; i < fences; i++) {
jtulach@11
   678
                    desk[desk.length - 1][i + 1] = '|';
jtulach@11
   679
                }
jtulach@13
   680
                if (currentTurn) {
jtulach@13
   681
                    desk[desk.length - 1][fences + 1] = '*';
jtulach@13
   682
                }
jtulach@11
   683
                break;
jtulach@11
   684
            }
jtulach@40
   685
            case NORTH: {
jtulach@11
   686
                for (int i = 0; i < fences; i++) {
jtulach@11
   687
                    desk[0][i + 1] = '|';
jtulach@11
   688
                }
jtulach@13
   689
                if (currentTurn) {
jtulach@13
   690
                    desk[0][fences + 1] = '*';
jtulach@13
   691
                }
jtulach@11
   692
                break;
jtulach@11
   693
            }
jtulach@11
   694
            default:
jtulach@11
   695
                assert false;
jtulach@11
   696
        }
jtulach@11
   697
    }
jtulach@11
   698
jtulach@11
   699
jtulach@0
   700
    //
jtulach@0
   701
    // Standard Methods
jtulach@0
   702
    // 
jtulach@0
   703
jtulach@0
   704
    
jtulach@2
   705
    @Override
jtulach@0
   706
    public int hashCode () {
jtulach@0
   707
        return occupied.hashCode ();
jtulach@0
   708
    }
jtulach@0
   709
    
jtulach@2
   710
    @Override
jtulach@0
   711
    public boolean equals (Object o) {
jtulach@0
   712
        if (o instanceof Board) {
jtulach@0
   713
            Board b = (Board)o;
jtulach@0
   714
            return occupied.equals (b.occupied) && players.equals (b.players);
jtulach@0
   715
        }
jtulach@0
   716
        return false;
jtulach@0
   717
    }
jtulach@12
   718
jaroslav@48
   719
    /** Converts the board into string representation. For more information
jaroslav@48
   720
     * about the format see {@link #write(java.io.Writer)}. To read the
jaroslav@48
   721
     * string back use {@link #valueOf(java.lang.String)}.
jaroslav@48
   722
     *
jaroslav@48
   723
     * @return string representing the board
jaroslav@48
   724
     */
jtulach@12
   725
    @Override
jtulach@12
   726
    public String toString() {
jaroslav@179
   727
        return getCode();
jaroslav@178
   728
    }
jaroslav@178
   729
jtulach@0
   730
    //
jtulach@0
   731
    // Validation methods
jtulach@0
   732
    //
jtulach@0
   733
jtulach@0
   734
    /** Computes new position of a player and checks whether there is no
jtulach@0
   735
     * fence between the old and new.
jtulach@0
   736
     */
jtulach@0
   737
    private Player newPosition (Player old, Player.Direction direction) throws IllegalPositionException {
jtulach@0
   738
        return newPosition (old, direction, occupied);
jtulach@0
   739
    }
jtulach@0
   740
    
jtulach@0
   741
    
jtulach@3
   742
    private static Player newPosition (Player old, Player.Direction direction, BitSet occupied) throws IllegalPositionException {
jtulach@0
   743
        int nx = old.x;
jtulach@0
   744
        int ny = old.y;
jtulach@0
   745
        int fx = old.x;
jtulach@0
   746
        int fy = old.y;
jtulach@0
   747
        
jtulach@0
   748
        switch (direction) {
jtulach@0
   749
            case NORTH: 
jtulach@0
   750
                ny = old.y + 2;
jtulach@0
   751
                fy = old.y + 1;
jtulach@0
   752
                break;
jtulach@0
   753
            case SOUTH: 
jtulach@0
   754
                ny = old.y - 2; 
jtulach@0
   755
                fy = old.y - 1;
jtulach@0
   756
                break;
jtulach@0
   757
            case EAST: 
jtulach@0
   758
                nx = old.x + 2; 
jtulach@0
   759
                fx = old.x + 1;
jtulach@0
   760
                break;
jtulach@0
   761
            case WEST: 
jtulach@0
   762
                nx = old.x - 2; 
jtulach@0
   763
                fx = old.x - 1;
jtulach@0
   764
                break;
jtulach@0
   765
            default: throw new IllegalStateException ("Unknown direction: " + direction); // NOI18N
jtulach@0
   766
        }
jtulach@0
   767
        
jtulach@0
   768
        if (nx < 0 || nx > 16) throw new IllegalPositionException ("Wrong player position: " + nx + ":" + ny); // NOI18N
jtulach@0
   769
        if (ny < 0 || ny > 16) throw new IllegalPositionException ("Wrong player position: " + nx + ":" + ny); // NOI18N
jtulach@0
   770
        
jtulach@0
   771
        int fenceIndex = toIndex (fx, fy);
jtulach@0
   772
        if (occupied.get (fenceIndex)) {
jtulach@0
   773
            throw new IllegalPositionException ("You cannot go over fences"); // NOI18N
jtulach@0
   774
        }
jtulach@0
   775
        
jtulach@0
   776
        return new Player (nx, ny, old.getFences (), old.endDirection);
jtulach@0
   777
    }
jtulach@0
   778
    
jtulach@0
   779
    /** @param position the current position of the player
jtulach@0
   780
     * @param endDir the side the player wants to reach
jtulach@0
   781
     * @param fences bits set to 1 when fences are placed
jtulach@0
   782
     * @param reached bits on squares that were already reached (modified during run)
jtulach@0
   783
     * @return true if the end line can be reached
jtulach@0
   784
     */
jtulach@3
   785
    private static boolean accessibleFinalLine (Player position, Player.Direction endDir, BitSet fences, BitSet reached) {
jtulach@0
   786
        int index = toIndex (position);
jtulach@0
   787
        if (reached.get (index)) {
jtulach@0
   788
            // already visited without success
jtulach@0
   789
            return false;
jtulach@0
   790
        }
jaroslav@92
   791
jaroslav@92
   792
        if (endDir.reached(position)) {
jaroslav@92
   793
            return true;
jtulach@0
   794
        }
jtulach@0
   795
        
jtulach@0
   796
        reached.set (index);
jtulach@0
   797
        
jtulach@0
   798
        for (Player.Direction oneDirection : Player.Direction.values ()) {
jtulach@0
   799
            try {
jtulach@0
   800
                if (accessibleFinalLine (newPosition (position, oneDirection, fences), endDir, fences, reached)) {
jtulach@0
   801
                    return true;
jtulach@0
   802
                }
jtulach@0
   803
            } catch (IllegalPositionException ex) {
jtulach@0
   804
                // ok, try once more
jtulach@0
   805
            }
jtulach@0
   806
        }
jtulach@0
   807
        
jtulach@0
   808
        return false;
jtulach@0
   809
    }
jtulach@0
   810
    
jtulach@0
   811
    /** Computes mask of the occupried bits.
jtulach@0
   812
     */
jtulach@3
   813
    private static BitSet computeOccupied (
jtulach@3
   814
        Collection<Player> players, Collection<Fence> fences
jtulach@0
   815
    ) throws IllegalPositionException {
jaroslav@92
   816
        BitSet occupied = new BitSet (17 * 17);
jtulach@0
   817
        
jtulach@0
   818
        for (Player p : players) {
jtulach@26
   819
            if (p.getXInternal() % 2 == 1) throw new IllegalPositionException ("Wrong player position: " + p); // NOI18N
jtulach@26
   820
            if (p.getYInternal() % 2 == 1) throw new IllegalPositionException ("Wrong player position: " + p); // NOI18N
jtulach@0
   821
            
jtulach@0
   822
            int index = toIndex (p);
jtulach@0
   823
            if (occupied.get (index)) {
jtulach@0
   824
                throw new IllegalPositionException ("There cannot be two players at " + p); // NOI18N
jtulach@0
   825
            }
jtulach@0
   826
            occupied.set (index);
jtulach@0
   827
        }
jtulach@0
   828
        
jtulach@0
   829
        for (Fence f : fences) {
jtulach@0
   830
            
jtulach@0
   831
            for (int i = -1; i <= 1; i++) {
jtulach@0
   832
                int index = toIndex (f, i);
jtulach@0
   833
                if (index < 0 || index > occupied.size ()) {
jtulach@0
   834
                    throw new IllegalPositionException ("Wrong fence position: " + f); // NOI18N
jtulach@0
   835
                }
jtulach@0
   836
                if (occupied.get (index)) {
jtulach@0
   837
                    throw new IllegalPositionException ("Fence collition: " + f); // NOI18N
jtulach@0
   838
                }
jtulach@0
   839
                
jtulach@0
   840
                occupied.set (index);
jtulach@0
   841
            }
jtulach@0
   842
            
jtulach@0
   843
        }
jtulach@0
   844
        
jtulach@0
   845
        return occupied;
jtulach@0
   846
    }
jtulach@0
   847
jtulach@0
   848
    /** Converts twodimensional coordinates of player to one dimensional index.
jtulach@0
   849
     */
jtulach@0
   850
    private static int toIndex (Player p) {
jtulach@26
   851
        return toIndex (p.getXInternal(), p.getYInternal());
jtulach@0
   852
    }
jtulach@0
   853
    
jtulach@0
   854
    /** Converts twodimensional coordinates of fence to one dimensional index.
jtulach@0
   855
     * @param f the fence
jtulach@0
   856
     * @param whichPart (-1, 0, 1) 
jtulach@0
   857
     */
jtulach@0
   858
    private static int toIndex (Fence f, int whichPart) {
jtulach@0
   859
        int x = f.getX ();
jtulach@0
   860
        int y = f.getY ();
jtulach@0
   861
        
jtulach@0
   862
        switch (f.getOrientation ()) {
jtulach@0
   863
            case HORIZONTAL: x += whichPart; break;
jtulach@0
   864
            case VERTICAL: y += whichPart; break;
jtulach@0
   865
            default: throw new IllegalStateException ("Wrong orientation: " + f.getOrientation ()); // NOI18N
jtulach@0
   866
        }
jtulach@0
   867
        
jtulach@0
   868
        return toIndex (x, y);
jtulach@0
   869
    }
jtulach@0
   870
    
jtulach@0
   871
    /** Diagonal conversion of two positive integers to one integer.
jtulach@0
   872
     * <pre>
jtulach@0
   873
     * y3 9
jtulach@0
   874
     * y2 5  8
jtulach@0
   875
     * y1 2  4  7
jtulach@0
   876
     * y0 0  1  3  6
jtulach@0
   877
     *   x0 x1 x2 x3
jtulach@0
   878
     * </pre>
jtulach@0
   879
     */
jtulach@0
   880
    private static int toIndex (int x, int y) {
jaroslav@92
   881
        assert x >= 0 && x < 17;
jaroslav@92
   882
        assert y >= 0 && y < 17;
jaroslav@92
   883
        return 17 * y + x;
jtulach@0
   884
    }
jtulach@17
   885
jaroslav@179
   886
    Board(String hashCode) throws IllegalStateException{
jaroslav@178
   887
        this.fences = new HashSet<Fence>();
jaroslav@178
   888
        if((hashCode != null) && (hashCode.length() > 6)){
jaroslav@178
   889
            char[]c = hashCode.toCharArray();
jaroslav@178
   890
            this.players = Collections.unmodifiableList (Arrays.asList (new Player[] {
jaroslav@178
   891
                new Player ((c[0]-'A')*2, (c[1]-'0')*2, c[2]-'a', Player.Direction.NORTH),
jaroslav@178
   892
                new Player ((c[3]-'A')*2, (c[4]-'0')*2, c[5]-'a', Player.Direction.SOUTH),
jaroslav@178
   893
            }));
jaroslav@178
   894
            if(c[6]=='w'){
jaroslav@178
   895
                this.turn = 0;
jaroslav@178
   896
                this.winner = null;
jaroslav@178
   897
            }else if(c[6]=='b'){
jaroslav@178
   898
                this.turn = 1;
jaroslav@178
   899
                this.winner = null;
jaroslav@178
   900
            }else if(c[6]=='W'){
jaroslav@178
   901
                this.turn = 0;
jaroslav@178
   902
                this.winner = this.players.get(0);
jaroslav@178
   903
            }else if(c[6]=='B'){
jaroslav@178
   904
                this.turn = 1;
jaroslav@178
   905
                this.winner = this.players.get(1);
jaroslav@178
   906
            }else{
jaroslav@178
   907
                this.turn = 0;
jaroslav@178
   908
                this.winner = null;
jaroslav@178
   909
            }
jaroslav@178
   910
            for(int i=7; i<c.length;i+=2){
jaroslav@178
   911
                int f = Integer.parseInt(hashCode.substring(i, i+2),16);
jaroslav@178
   912
                Fence.Orientation o = Fence.Orientation.HORIZONTAL;
jaroslav@219
   913
                if(f >= 64){
jaroslav@178
   914
                    o = Fence.Orientation.VERTICAL;
jaroslav@178
   915
                    f -= 64;
jaroslav@178
   916
                }
jaroslav@178
   917
                fences.add(new Fence((f/8)*2+1, (f%8)*2+1,o));
jaroslav@178
   918
            }
jaroslav@178
   919
        }else{
jaroslav@178
   920
            this.players = Collections.unmodifiableList (Arrays.asList (new Player[] {
jaroslav@178
   921
                new Player (8,0,10,Player.Direction.NORTH),
jaroslav@178
   922
                new Player (8,16,10,Player.Direction.SOUTH),
jaroslav@178
   923
            }));
jaroslav@178
   924
            this.winner = null;
jaroslav@178
   925
            this.turn = 0;
jaroslav@178
   926
        }
jaroslav@178
   927
        try {
jaroslav@178
   928
            this.occupied = computeOccupied (players, fences);
jaroslav@178
   929
        } catch (IllegalPositionException ex) {
jaroslav@178
   930
            throw new IllegalStateException (ex.getMessage ());
jaroslav@178
   931
        }
jaroslav@178
   932
    }
jaroslav@178
   933
jaroslav@179
   934
    public final String getCode() {
jaroslav@179
   935
        Board b = this;
jaroslav@178
   936
        StringBuilder sb = new StringBuilder();
jaroslav@178
   937
        for(Player p: b.getPlayers()){
jaroslav@178
   938
            sb.append((char)(p.getColumn() + 'A'));
jaroslav@178
   939
            sb.append((char)(p.getRow() + '0'));
jaroslav@178
   940
            sb.append((char)(p.getFences() + 'a'));
jaroslav@178
   941
        }
jaroslav@178
   942
        Player winner = b.getWinner();
jaroslav@178
   943
        if(winner == null){
jaroslav@178
   944
            if(b.players.indexOf(b.getCurrentPlayer())==0)
jaroslav@178
   945
                sb.append('w');
jaroslav@178
   946
            else if(b.players.indexOf(b.getCurrentPlayer())==1)
jaroslav@178
   947
                sb.append('b');
jaroslav@178
   948
            else
jaroslav@178
   949
                sb.append('n');
jaroslav@178
   950
        }else{
jaroslav@178
   951
            if(b.players.indexOf(winner)==0)
jaroslav@178
   952
                sb.append('W');
jaroslav@178
   953
            else if(b.players.indexOf(winner)==1)
jaroslav@178
   954
                sb.append('B');
jaroslav@178
   955
            else
jaroslav@178
   956
                sb.append('N');
jaroslav@178
   957
        }
jaroslav@178
   958
jaroslav@178
   959
        TreeSet<Integer> fences = new TreeSet<Integer>();
jaroslav@178
   960
        for(Fence f: b.getFences()){
jaroslav@178
   961
            int a = (f.getColumn() - 'A')*8 + (f.getRow()-1);
jaroslav@178
   962
            if(f.getOrientation().equals(Fence.Orientation.VERTICAL))
jaroslav@178
   963
                a+=64;
jaroslav@178
   964
            fences.add(a);
jaroslav@178
   965
        }
jaroslav@178
   966
        for(int f: fences){
jaroslav@178
   967
            if(f<16)
jaroslav@178
   968
                sb.append('0');
jaroslav@178
   969
            sb.append(Integer.toHexString(f));
jaroslav@178
   970
        }
jaroslav@178
   971
        return sb.toString();
jaroslav@178
   972
    }
jaroslav@178
   973
jtulach@0
   974
}