quoridor/src/main/java/cz/xelfi/quoridor/Board.java
author Jaroslav Tulach <jtulach@netbeans.org>
Wed, 29 Jul 2009 21:35:04 +0200
changeset 40 e45bc8ad2eaf
parent 26 549ff1a27eff
child 48 69e897fe8140
permissions -rw-r--r--
Rotating the display of the board - north is on top. Enhancing the display with row ids and column numbers and compass directions.
jtulach@0
     1
/*
jtulach@8
     2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
jtulach@8
     3
 *
jtulach@8
     4
 * The contents of this file are subject to the terms of either the GNU
jtulach@8
     5
 * General Public License Version 2 only ("GPL") or the Common
jtulach@8
     6
 * Development and Distribution License("CDDL") (collectively, the
jtulach@8
     7
 * "License"). You may not use this file except in compliance with the
jtulach@8
     8
 * License. You can obtain a copy of the License at
jtulach@8
     9
 * http://www.netbeans.org/cddl-gplv2.html
jtulach@8
    10
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
jtulach@8
    11
 * specific language governing permissions and limitations under the
jtulach@8
    12
 * License.  When distributing the software, include this License Header
jtulach@8
    13
 * Notice in each file and include the License file at
jtulach@8
    14
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
jtulach@8
    15
 * particular file as subject to the "Classpath" exception as provided
jtulach@8
    16
 * by Sun in the GPL Version 2 section of the License file that
jtulach@8
    17
 * accompanied this code. If applicable, add the following below the
jtulach@8
    18
 * License Header, with the fields enclosed by brackets [] replaced by
jtulach@8
    19
 * your own identifying information:
jtulach@8
    20
 * "Portions Copyrighted [year] [name of copyright owner]"
jtulach@8
    21
 *
jtulach@8
    22
 * Contributor(s):
jtulach@8
    23
 *
jtulach@8
    24
 * Portions Copyrighted 2009 Jaroslav Tulach
jtulach@0
    25
 */
jtulach@0
    26
jtulach@0
    27
package cz.xelfi.quoridor;
jtulach@0
    28
jtulach@15
    29
import cz.xelfi.quoridor.Player.Direction;
jtulach@10
    30
import java.io.BufferedReader;
jtulach@10
    31
import java.io.EOFException;
jtulach@9
    32
import java.io.IOException;
jtulach@9
    33
import java.io.Reader;
jtulach@12
    34
import java.io.StringWriter;
jtulach@9
    35
import java.io.Writer;
jtulach@10
    36
import java.util.ArrayList;
jtulach@3
    37
import java.util.Arrays;
jtulach@3
    38
import java.util.BitSet;
jtulach@3
    39
import java.util.Collection;
jtulach@3
    40
import java.util.Collections;
jtulach@3
    41
import java.util.HashSet;
jtulach@3
    42
import java.util.List;
jtulach@3
    43
import java.util.Set;
jtulach@10
    44
import java.util.regex.Matcher;
jtulach@10
    45
import java.util.regex.Pattern;
jtulach@3
    46
jtulach@0
    47
/**
jtulach@16
    48
 * Represents a snapshot of the game position,
jtulach@16
    49
 * including all the placed and not-yet placed fences and player positions.
jtulach@16
    50
 * It it can print itself to stream in
jtulach@16
    51
 * <a href="http://www.gamerz.net/pbmserv/quoridor.html">ascii art format<a/>
jtulach@16
    52
 * and can it read back. The class is immutable
jtulach@16
    53
 * but it contains {@link #apply(cz.xelfi.quoridor.Move)}
jtulach@16
    54
 * that produce new {@link Board} with position created after
jtulach@16
    55
 * applying some {@link Move}. Use:
jtulach@16
    56
 * <pre>
jtulach@16
    57
 * Board whiteOnTurn = Board.empty();
jtulach@16
    58
 * Board blackOnTurn = whiteOnTurn.apply(Move.NORTH);
jtulach@16
    59
 * Board whiteAgain = blackOnTurn.apply(Move.SOUTH);
jtulach@16
    60
 * Board withOneFence = whiteAgain.apply(Move.fence('D', 7));
jtulach@16
    61
 * </pre>
jtulach@0
    62
 * 
jtulach@0
    63
 * @author Jaroslav Tulach
jtulach@0
    64
 */
jtulach@0
    65
public final class Board {
jtulach@0
    66
    /** players */
jtulach@3
    67
    private final List<Player> players;
jtulach@0
    68
    /** fences placed on board */
jtulach@3
    69
    private final Set<Fence> fences;
jtulach@0
    70
    /** occurpied bits (coordinates encoded by toIndex methods) 
jtulach@0
    71
                         [N]
jtulach@0
    72
               
jtulach@0
    73
        +-----------------------------------+
jtulach@0
    74
     6  |                                   |          
jtulach@0
    75
     5  |   +   +   +   +   +   +   +   +   |  
jtulach@0
    76
     4  |                                   |          
jtulach@0
    77
     3  |   +   +   +   +   +   +   +   +   |  
jtulach@0
    78
     2  |                                   |          
jtulach@0
    79
     1  |   +   +   +   +   +   +   +   +   |  
jtulach@0
    80
     0  |                                   |
jtulach@0
    81
     9  |   +   +   +   +   +   +   +   +   |
jtulach@0
    82
[W]  8  |                                   |     [E]
jtulach@0
    83
     7  |   +   +   +   +   +   +   +   +   |
jtulach@0
    84
     6  |                                   |
jtulach@0
    85
     5  |   +   +   +   +   +   +   +   +   |
jtulach@0
    86
     4  |                                   |
jtulach@0
    87
     3  |   +   +   +   +   +   +   +   +   |
jtulach@0
    88
     2  |                                   |
jtulach@0
    89
     1  |   +   +   +   +   +   +   +   +   |
jtulach@0
    90
     0  |                                   |
jtulach@0
    91
        +-----------------------------------+
jtulach@0
    92
          0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
jtulach@0
    93
                         [S]
jtulach@0
    94
     
jtulach@0
    95
     * even indexes == position of the pawns
jtulach@0
    96
     * odd indexes  == centers of fences
jtulach@0
    97
     * even x odd   == side of a fence
jtulach@0
    98
     * odd x even   == side of a fence
jtulach@0
    99
     */
jtulach@3
   100
    private final BitSet occupied;
jtulach@3
   101
    /** which player's turn is next? */
jtulach@3
   102
    private final int turn;
jtulach@0
   103
    
jtulach@0
   104
    /**
jtulach@0
   105
     * Creates a new instance of Board 
jtulach@0
   106
     */
jtulach@0
   107
    private Board (int x1, int y1, int f1, int x2, int y2, int f2) {
jtulach@3
   108
        this.players = Collections.unmodifiableList (Arrays.asList (new Player[] {
jtulach@0
   109
            new Player (x1, y1, f1, Player.Direction.NORTH),
jtulach@0
   110
            new Player (x2, y2, f2, Player.Direction.SOUTH),
jtulach@0
   111
        }));
jtulach@3
   112
        this.fences = Collections.emptySet ();
jtulach@0
   113
        try {
jtulach@0
   114
            this.occupied = computeOccupied (players, fences);
jtulach@0
   115
        } catch (IllegalPositionException ex) {
jtulach@0
   116
            throw new IllegalStateException (ex.getMessage ());
jtulach@0
   117
        }
jtulach@3
   118
        this.turn = 0;
jtulach@0
   119
    }
jtulach@0
   120
    
jtulach@0
   121
    /** Copy constructor that provides players and fences.
jtulach@0
   122
     */
jtulach@3
   123
    private Board (int turn, List<Player> players, Set<Fence> fences)
jtulach@0
   124
    throws IllegalPositionException {
jtulach@3
   125
        this.players = Collections.unmodifiableList (players);
jtulach@3
   126
        this.fences = Collections.unmodifiableSet (fences);
jtulach@0
   127
        this.occupied = computeOccupied (players, fences);
jtulach@0
   128
        
jtulach@0
   129
        for (Player p : players) {
jtulach@3
   130
            if (!accessibleFinalLine (p, p.endDirection, occupied, new BitSet ())) {
jtulach@0
   131
                throw new IllegalPositionException ("Player " + p + " cannot reach " + p.endDirection + " side"); // NOI18N
jtulach@0
   132
            }
jtulach@0
   133
        }
jtulach@3
   134
        this.turn = turn % players.size();
jtulach@0
   135
    }
jtulach@0
   136
    
jtulach@16
   137
    /** Returns empty board with default starting position.
jtulach@16
   138
     * @return board with two pawns.
jtulach@0
   139
     */
jtulach@16
   140
    public static Board empty () {
jtulach@0
   141
        return new Board (8, 0, 10, 8, 16, 10);
jtulach@0
   142
    }
jtulach@0
   143
    
jtulach@0
   144
    /** Returns players in the game.
jtulach@0
   145
     * @return player object
jtulach@0
   146
     */
jtulach@3
   147
    public List<Player> getPlayers () {
jtulach@0
   148
        return players;
jtulach@0
   149
    }
jtulach@0
   150
    
jtulach@0
   151
    /** The fences currently on board. 
jtulach@0
   152
     * 
jtulach@0
   153
     * @return immutable set of Fences
jtulach@0
   154
     */
jtulach@3
   155
    public Set<Fence> getFences () {
jtulach@0
   156
        return fences;
jtulach@0
   157
    }
jtulach@3
   158
jtulach@3
   159
    /** The player that is supposed to play now.
jtulach@3
   160
     * @return the player to do next move
jtulach@3
   161
     */
jtulach@3
   162
    public Player getCurrentPlayer() {
jtulach@3
   163
        return players.get(turn);
jtulach@3
   164
    }
jtulach@14
   165
jtulach@14
   166
    /** The play who wins on current board.
jtulach@14
   167
     *
jtulach@14
   168
     * @return the winning player or <code>null</code>
jtulach@14
   169
     */
jtulach@14
   170
    public Player getWinner() {
jtulach@14
   171
        for (Player p : players) {
jtulach@14
   172
            if (p.endDirection.reached(p)) {
jtulach@14
   173
                return p;
jtulach@14
   174
            }
jtulach@14
   175
        }
jtulach@14
   176
        return null;
jtulach@14
   177
    }
jtulach@16
   178
jtulach@16
   179
    /** Applies given move to current board.
jtulach@16
   180
     *
jtulach@16
   181
     * @param move move creates by one of the factory methods or fields of the {@link Move} class
jtulach@16
   182
     * @return new board derived from this one
jtulach@16
   183
     *
jtulach@16
   184
     * @throws cz.xelfi.quoridor.IllegalPositionException throws exception if the move is illegal
jtulach@16
   185
     */
jtulach@3
   186
    public Board apply(Move move) throws IllegalPositionException {
jtulach@14
   187
        if (getWinner() != null) {
jtulach@14
   188
            throw new IllegalPositionException("Game finished!"); // NOI18N
jtulach@14
   189
        }
jtulach@14
   190
jtulach@3
   191
        if (move.direction != null) {
jtulach@3
   192
            return move(getCurrentPlayer(), move.direction);
jtulach@3
   193
        } else {
jtulach@3
   194
            return fence(getCurrentPlayer(), move.fence);
jtulach@3
   195
        }
jtulach@3
   196
    }
jtulach@0
   197
jtulach@17
   198
    /** Can the move be applied to current board position?
jtulach@17
   199
     * 
jtulach@17
   200
     * @param move the move to apply
jtulach@17
   201
     * @return true if one can call {@link #apply} method without getting 
jtulach@17
   202
     *   an exception
jtulach@17
   203
     */
jtulach@17
   204
    public boolean isApplicable(Move move) {
jtulach@17
   205
        try {
jtulach@17
   206
            // trivial implementation is enough for now
jtulach@17
   207
            apply(move);
jtulach@17
   208
            return true;
jtulach@17
   209
        } catch (IllegalPositionException ex) {
jtulach@17
   210
            return false;
jtulach@17
   211
        }
jtulach@17
   212
    }
jtulach@17
   213
    
jtulach@0
   214
    /** Moves the player in given direction. The direction usually
jtulach@0
   215
     * is one of Player.Direction constants, but in case the move
jtulach@0
   216
     * is a jump over another players pawn it can be followed by
jtulach@0
   217
     * another (usually, if there is no fence the same) direction.
jtulach@0
   218
     *
jtulach@0
   219
     * @param player the player to move
jtulach@0
   220
     * @param where one or two directions saying where
jtulach@0
   221
     * @return the new board
jtulach@0
   222
     * @exception IllegalPositionException if the move is not possible
jtulach@0
   223
     */
jtulach@3
   224
    final Board move (Player player, Player.Direction... where) throws IllegalPositionException {
jtulach@0
   225
        if (where.length != 1 && where.length != 2) {
jtulach@0
   226
            throw new IllegalPositionException ("Move over one or two Directions"); // NOI18N
jtulach@0
   227
        }
jtulach@0
   228
        
jtulach@0
   229
        int index = players.indexOf (player);
jtulach@0
   230
        Player[] arr = players.toArray (new Player[0]);
jtulach@0
   231
jtulach@0
   232
        Player oneStep = newPosition (player, where[0]);
jtulach@0
   233
        
jtulach@0
   234
        if (where.length == 1) {
jtulach@0
   235
            arr[index] = oneStep;
jtulach@3
   236
            return new Board(turn + 1, Arrays.asList (arr), fences);
jtulach@0
   237
        }
jtulach@0
   238
jtulach@0
   239
        // straight jump over
jtulach@0
   240
        for (Player p : players) {
jtulach@26
   241
            if (p.getXInternal () == oneStep.getXInternal () && p.getYInternal() == oneStep.getYInternal ()) {
jtulach@0
   242
                // ok, we are jumping over this one
jtulach@0
   243
                GO_ON: if (where[0] != where[1]) {
jtulach@0
   244
                    // first of all ensure that we cannot go straight
jtulach@0
   245
                    try {
jtulach@0
   246
                        newPosition (oneStep, where[0]);
jtulach@0
   247
                    } catch (IllegalPositionException ex) {
jtulach@0
   248
                        // ok
jtulach@0
   249
                        break GO_ON;
jtulach@0
   250
                    }
jtulach@0
   251
                    throw new IllegalPositionException ("You have to jump straight if there is no wall"); // NOI18N
jtulach@0
   252
                }
jtulach@0
   253
                arr[index] = newPosition (oneStep, where[1]);
jtulach@3
   254
                return new Board (turn + 1, Arrays.asList (arr), fences);
jtulach@0
   255
            }
jtulach@0
   256
        }
jtulach@0
   257
        throw new IllegalPositionException ("Cannot use multi direction when there is not oponent pawn"); // NOI18N
jtulach@0
   258
    }
jtulach@0
   259
    
jtulach@3
   260
    final Board fence (Player player, char x, int y, Fence.Orientation orientation) throws IllegalPositionException {
jtulach@3
   261
        return fence(player, new Fence ((x - 'A') * 2 + 1, y * 2 - 1, orientation));
jtulach@3
   262
    }
jtulach@3
   263
jtulach@40
   264
    private void columnLine(int width, int spaceX, Writer w) throws IOException {
jtulach@40
   265
        char ch = 'A';
jtulach@40
   266
        for (int x = 0; x < width - 1; x++) {
jtulach@40
   267
            if (x % (spaceX + 1) == 0 && x > 0) {
jtulach@40
   268
                w.write(ch);
jtulach@40
   269
                ch = (char) (ch + 1);
jtulach@40
   270
            } else {
jtulach@40
   271
                w.write(' ');
jtulach@40
   272
            }
jtulach@40
   273
        }
jtulach@40
   274
    }
jtulach@40
   275
jtulach@3
   276
    private Board fence(Player player, Fence fence) throws IllegalPositionException {
jtulach@0
   277
        if (player.getFences () == 0) {
jtulach@0
   278
            throw new IllegalPositionException ("Not enough fences: " + player); // NOI18N
jtulach@0
   279
        }
jtulach@0
   280
        
jtulach@0
   281
        int index = players.indexOf (player);
jtulach@0
   282
        Player[] arr = players.toArray (new Player[0]);
jtulach@26
   283
        arr[index] = new Player (arr[index].getXInternal(), arr[index].getYInternal(), arr[index].getFences() - 1, arr[index].endDirection);
jtulach@0
   284
        
jtulach@3
   285
        HashSet<Fence> fen = new HashSet<Fence> (this.fences);
jtulach@3
   286
        fen.add (fence);
jtulach@0
   287
        
jtulach@3
   288
        return new Board (turn + 1, Arrays.asList (arr), fen);
jtulach@0
   289
    }
jtulach@9
   290
jtulach@9
   291
    //
jtulach@9
   292
    // Serialization
jtulach@9
   293
    //
jtulach@9
   294
jtulach@13
   295
    private static final Pattern northSouthPattern = Pattern.compile("(\\+(\\|*)(\\*)?-+\\+)");
jtulach@10
   296
    public static Board read(Reader r) throws IOException, IllegalPositionException {
jtulach@10
   297
        BufferedReader b = new BufferedReader(r);
jtulach@10
   298
        for (;;) {
jtulach@10
   299
            String s = b.readLine();
jtulach@10
   300
            if (s == null) {
jtulach@10
   301
                throw new IOException("No board found!");
jtulach@10
   302
            }
jtulach@11
   303
            Matcher m = northSouthPattern.matcher(s);
jtulach@10
   304
            if (m.find()) {
jtulach@13
   305
                return readFromBetween(b, m);
jtulach@10
   306
            }
jtulach@10
   307
        }
jtulach@10
   308
    }
jtulach@10
   309
jtulach@12
   310
    private static int assertChar(String s, int pos, char... ch) throws IOException {
jtulach@12
   311
        if (s.length() >= pos) {
jtulach@12
   312
            for (int i = 0; i < ch.length; i++) {
jtulach@12
   313
                if (ch[i] == s.charAt(pos)) {
jtulach@12
   314
                    return i;
jtulach@12
   315
                }
jtulach@12
   316
            }
jtulach@10
   317
        }
jtulach@12
   318
        throw new IOException("Not found " + ch[0] + " at " + pos + " in" + s);
jtulach@10
   319
    }
jtulach@10
   320
jtulach@13
   321
    private static Player findPlayer(
jtulach@13
   322
        Player previous, String line, int y, int spaceX, Player.Direction dir, int fences
jtulach@10
   323
    ) {
jtulach@13
   324
        int index = line.indexOf(dir.player);
jtulach@10
   325
        if (index == -1) {
jtulach@13
   326
            return previous;
jtulach@10
   327
        }
jtulach@10
   328
        int x = (index - 1) / (spaceX + 1) * 2;
jtulach@13
   329
        return new Player(x, y - 1, fences, dir);
jtulach@10
   330
    }
jtulach@10
   331
jtulach@13
   332
    private static Board readFromBetween(BufferedReader b, Matcher firstMatcher)
jtulach@10
   333
    throws IOException, IllegalPositionException {
jtulach@13
   334
        final int from = firstMatcher.start(1);
jtulach@13
   335
        final int to = firstMatcher.end(1);
jtulach@13
   336
        final int northFences = firstMatcher.end(2) - firstMatcher.start(2);
jtulach@10
   337
        final int spaceX = (to - from - 1) / 9 - 1;
jtulach@10
   338
        final int spaceY = 1;
jtulach@10
   339
jtulach@13
   340
        Player p = null;
jtulach@13
   341
        Player q = null;
jtulach@12
   342
        Set<Fence> fences = new HashSet<Fence>();
jtulach@10
   343
jtulach@9
   344
        StringBuffer sb = new StringBuffer();
jtulach@40
   345
        int row = 7;
jtulach@40
   346
        for (int y = (spaceY + 1) * 9 - 1; y > 0; y--) {
jtulach@10
   347
            String s = b.readLine();
jtulach@10
   348
            if (s == null) {
jtulach@10
   349
                throw new EOFException();
jtulach@9
   350
            }
jtulach@10
   351
            sb.append(s);
jtulach@10
   352
            sb.append('\n');
jtulach@10
   353
            if (s.length() < to) {
jtulach@10
   354
                throw new IOException("Too short line: " + s); // NOI18N
jtulach@10
   355
            }
jtulach@10
   356
            assertChar(s, from, '|');
jtulach@10
   357
            assertChar(s, to - 1, '|');
jtulach@10
   358
jtulach@10
   359
            if (y % (spaceY + 1) == 0) {
jtulach@10
   360
                for (int x = 1; x < 9; x++) {
jtulach@12
   361
                    switch (assertChar(s, from + (spaceX + 1) * x, '+', '-', '|')) {
jtulach@12
   362
                        case 1:
jtulach@12
   363
                            fences.add(new Fence(x * 2 - 1, row * 2 + 1, Fence.Orientation.HORIZONTAL));
jtulach@12
   364
                            break;
jtulach@12
   365
                        case 2:
jtulach@12
   366
                            fences.add(new Fence(x * 2 - 1, row * 2 + 1, Fence.Orientation.VERTICAL));
jtulach@12
   367
                            break;
jtulach@12
   368
                        case 0:
jtulach@12
   369
                            break;
jtulach@12
   370
                        default:
jtulach@12
   371
                            assert false;
jtulach@12
   372
                    }
jtulach@10
   373
                }
jtulach@40
   374
                row--;
jtulach@10
   375
            } else {
jtulach@10
   376
                String line = s.substring(from, to);
jtulach@13
   377
                p = findPlayer(p, line, y, spaceX, Player.Direction.NORTH, -1);
jtulach@13
   378
                q = findPlayer(q, line, y, spaceX, Player.Direction.SOUTH, northFences);
jtulach@10
   379
            }
jtulach@9
   380
        }
jtulach@10
   381
jtulach@11
   382
        String last = b.readLine();
jtulach@11
   383
        if (last == null) {
jtulach@11
   384
            throw new EOFException();
jtulach@11
   385
        }
jtulach@13
   386
        Matcher lastMatcher = northSouthPattern.matcher(last);
jtulach@13
   387
        if (!lastMatcher.find()) {
jtulach@11
   388
            throw new IOException("Unrecognized last line: " + last);
jtulach@11
   389
        }
jtulach@11
   390
jtulach@13
   391
        List<Player> arr = new ArrayList<Player>(2);
jtulach@13
   392
        assert p != null;
jtulach@13
   393
        int southFences = lastMatcher.end(2) - lastMatcher.start(2);
jtulach@26
   394
        arr.add(new Player(p.getXInternal(), p.getYInternal(), southFences, p.endDirection));
jtulach@13
   395
        arr.add(q);
jtulach@13
   396
        int turn = "*".equals(lastMatcher.group(3)) ? 0 : 1; // NOI18N
jtulach@13
   397
        return new Board(turn, arr, fences);
jtulach@9
   398
    }
jtulach@9
   399
jtulach@9
   400
jtulach@0
   401
    
jtulach@40
   402
    /** Writes the board to the provided writer. <b>P</b> denotes position
jtulach@40
   403
     * of the first player and <b>Q</b> of the second. Number of remaining
jtulach@40
   404
     * fences of each player are shown in left bottom and left top corner
jtulach@40
   405
     * as appropriate number of <b>|||</b> - one <b>|</b> per fence. The
jtulach@40
   406
     * player that is supposed to move in this turn has <b>*</b> right after
jtulach@40
   407
     * the set of its fences.
jtulach@0
   408
     *
jtulach@9
   409
     * <pre>
jtulach@40
   410
                         [N]
jtulach@40
   411
jtulach@40
   412
            A   B   C   D   E   F   G   H
jtulach@0
   413
            |   |   |   |   |   |   |   |
jtulach@11
   414
        +|||||------------------------------+
jtulach@40
   415
        |                 Q                 |
jtulach@40
   416
     8--|   +   +   +   +   +   +   +   +   |--8
jtulach@11
   417
        |       |                           |           
jtulach@40
   418
     7--|   +   |   +   +-------+-------+   |--7
jtulach@11
   419
        |       |       |                   |           
jtulach@40
   420
     6--|-------+-------|-------+-------+   |--6
jtulach@0
   421
        |               |                   |          
jtulach@40
   422
     5--|   +   +   +   +   +   +   +   +   |--5
jtulach@40
   423
[W]     |               |                   |     [E]
jtulach@40
   424
     4--|   +   +   +   |   +   +   +-------|--4
jtulach@40
   425
        |               |                   |
jtulach@40
   426
     3--|   +   +   +   +-------+   +   +   |--3
jtulach@40
   427
        |                                   |
jtulach@40
   428
     2--|   +   +   +   +   +   +   +   +   |--2
jtulach@40
   429
        |                       |           |
jtulach@40
   430
     1--|   +   +   +   +   +   |   +   +   |--1
jtulach@40
   431
        |                 P     |           |
jtulach@40
   432
        +|||*-------------------------------+
jtulach@0
   433
            |   |   |   |   |   |   |   |
jtulach@40
   434
            A   B   C   D   E   F   G   H
jtulach@40
   435
jtulach@40
   436
                         [S]
jtulach@9
   437
     </pre>
jtulach@0
   438
     * @param w writer to write the board to
jtulach@0
   439
     * @exception IOException if communiction with writer fails
jtulach@0
   440
     */
jtulach@9
   441
    public void write (Writer w) throws IOException {
jtulach@9
   442
        write(w, 3, 1);
jtulach@0
   443
    }
jtulach@40
   444
jtulach@40
   445
    private void northSouthSign(int width, char sign, Writer w) throws IOException {
jtulach@40
   446
        int middle = width / 2;
jtulach@40
   447
        for (int x = 0; x < width; x++) {
jtulach@40
   448
            char ch = ' ';
jtulach@40
   449
            if (x == middle - 1) {
jtulach@40
   450
                ch = '[';
jtulach@40
   451
            }
jtulach@40
   452
            if (x == middle) {
jtulach@40
   453
                ch = sign;
jtulach@40
   454
            }
jtulach@40
   455
            if (x == middle + 1) {
jtulach@40
   456
                ch = ']';
jtulach@40
   457
            }
jtulach@40
   458
            w.write(ch);
jtulach@40
   459
        }
jtulach@40
   460
        w.write(System.getProperty("line.separator")); // NOI18N
jtulach@40
   461
    }
jtulach@40
   462
jtulach@40
   463
    private void subColumnLine(int width, int spaceX, Writer w) throws IOException {
jtulach@40
   464
        for (int x = 0; x < width - 1; x++) {
jtulach@40
   465
            if (x % (spaceX + 1) == 0 && x > 0) {
jtulach@40
   466
                w.write('|');
jtulach@40
   467
            } else {
jtulach@40
   468
                w.write(' ');
jtulach@40
   469
            }
jtulach@40
   470
        }
jtulach@40
   471
    }
jtulach@0
   472
    
jtulach@0
   473
    /** This will print the board with provided spacing.
jtulach@0
   474
     * This is example of 3:1 spacing: 
jtulach@0
   475
     * <pre>
jtulach@0
   476
     *  +---+
jtulach@0
   477
     *  |sss|
jtulach@0
   478
     *  +---+
jtulach@0
   479
     * </pre>
jtulach@0
   480
     * and this 4:2 spacing:
jtulach@0
   481
     * <pre>
jtulach@0
   482
     *  +----+
jtulach@0
   483
     *  |ssss|
jtulach@0
   484
     *  |ssss|
jtulach@0
   485
     *  +----+
jtulach@0
   486
     * </pre>
jtulach@0
   487
     */
jtulach@9
   488
    private void write (Writer w, int spaceX, int spaceY) throws IOException {
jtulach@9
   489
        int width = spaceX * 9 + 10;
jtulach@9
   490
        int height = spaceY * 9 + 10;
jtulach@9
   491
        char[][] desk = new char[height][];
jtulach@9
   492
        for (int i = 0; i < height; i++) {
jtulach@9
   493
            desk[i] = new char[width];
jtulach@9
   494
            for (int j = 0; j < width; j++) {
jtulach@9
   495
                if (i % (spaceY + 1) == 0 && j % (spaceX + 1) == 0) {
jtulach@9
   496
                    desk[i][j] = '+';
jtulach@9
   497
                } else {
jtulach@9
   498
                    desk[i][j] = ' ';
jtulach@9
   499
                }
jtulach@0
   500
            }
jtulach@9
   501
            desk[i][0] = '|';
jtulach@9
   502
            desk[i][width - 1] = '|';
jtulach@0
   503
        }
jtulach@9
   504
        for (int i = 1; i < width - 1; i++) {
jtulach@9
   505
            desk[0][i] = '-';
jtulach@9
   506
            desk[height -1][i] = '-';
jtulach@9
   507
        }
jtulach@9
   508
        desk[0][0] = '+';
jtulach@9
   509
        desk[0][width - 1] = '+';
jtulach@9
   510
        desk[height - 1][0] = '+';
jtulach@9
   511
        desk[height - 1][width - 1] = '+';
jtulach@9
   512
jtulach@9
   513
        {
jtulach@9
   514
            for (Player p : players) {
jtulach@26
   515
                int px = p.getXInternal() / 2;
jtulach@26
   516
                int py = p.getYInternal() / 2;
jtulach@9
   517
                desk
jtulach@9
   518
                    [1 + py * (spaceY + 1) + spaceY / 2]
jtulach@13
   519
                    [1 + px * (spaceX + 1) + spaceX / 2] = p.endDirection.player;
jtulach@13
   520
                paintLeftFences(desk, p.endDirection, p.getFences(), p.equals(getCurrentPlayer()));
jtulach@9
   521
            }
jtulach@0
   522
        }
jtulach@0
   523
        
jtulach@0
   524
        for (Fence f : fences) {
jtulach@12
   525
            int fx = (f.getX() / 2 + 1) * (spaceX + 1);
jtulach@12
   526
            int fy = (f.getY() / 2 + 1) * (spaceY + 1);
jtulach@0
   527
            switch (f.getOrientation()) {
jtulach@0
   528
                case HORIZONTAL:
jtulach@12
   529
                    for (int i = -spaceX; i <= spaceX; i++) {
jtulach@12
   530
                        desk[fy][fx + i] = '-';
jtulach@0
   531
                    }
jtulach@0
   532
                    break;
jtulach@0
   533
                case VERTICAL:
jtulach@12
   534
                    for (int i = -spaceY; i <= spaceY; i++) {
jtulach@12
   535
                        desk[fy + i][fx] = '|';
jtulach@0
   536
                    }
jtulach@0
   537
                    break;
jtulach@0
   538
                default: 
jtulach@11
   539
                    throw new IllegalStateException ("Unknown orientation: " + f.getOrientation ()); // NOI18N
jtulach@0
   540
            }
jtulach@0
   541
        }
jtulach@40
   542
        w.write("        ");
jtulach@40
   543
        northSouthSign(width, 'N', w);
jtulach@40
   544
        w.write(System.getProperty("line.separator")); // NOI18N
jtulach@40
   545
        w.write("        ");
jtulach@40
   546
        columnLine(width, spaceX, w);
jtulach@40
   547
        w.write(System.getProperty("line.separator")); // NOI18N
jtulach@40
   548
        w.write("        ");
jtulach@40
   549
        subColumnLine(width, spaceX, w);
jtulach@40
   550
        w.write(System.getProperty("line.separator")); // NOI18N
jtulach@40
   551
        int cnt = 9;
jtulach@40
   552
        for (int y = height - 1; y >= 0; y--) {
jtulach@40
   553
            if (y == height / 2) {
jtulach@40
   554
                w.write("[W]  ");
jtulach@40
   555
            } else {
jtulach@40
   556
                w.write("     ");
jtulach@40
   557
            }
jtulach@40
   558
            boolean number = y % (spaceY + 1) == 0 && y > 0 && y < height - 1;
jtulach@40
   559
            if (number) {
jtulach@40
   560
                cnt--;
jtulach@40
   561
                w.write(Integer.toString(cnt));
jtulach@40
   562
                w.write("--");
jtulach@40
   563
            } else {
jtulach@40
   564
                w.write("   ");
jtulach@40
   565
            }
jtulach@9
   566
            for (int x = 0; x < width; x++) {
jtulach@9
   567
                w.write(desk[y][x]);
jtulach@9
   568
            }
jtulach@40
   569
            if (number) {
jtulach@40
   570
                w.write("--");
jtulach@40
   571
                w.write(Integer.toString(cnt));
jtulach@40
   572
            } else {
jtulach@40
   573
                w.write("   ");
jtulach@40
   574
            }
jtulach@40
   575
            if (y == height / 2) {
jtulach@40
   576
                w.write("  [E]");
jtulach@40
   577
            }
jtulach@9
   578
            w.write(System.getProperty("line.separator")); // NOI18N
jtulach@9
   579
        }
jtulach@40
   580
        w.write("        ");
jtulach@40
   581
        subColumnLine(width, spaceX, w);
jtulach@40
   582
        w.write(System.getProperty("line.separator")); // NOI18N
jtulach@40
   583
        w.write("        ");
jtulach@40
   584
        columnLine(width, spaceX, w);
jtulach@40
   585
        w.write(System.getProperty("line.separator")); // NOI18N
jtulach@40
   586
        w.write(System.getProperty("line.separator")); // NOI18N
jtulach@40
   587
        w.write("        ");
jtulach@40
   588
        northSouthSign(width, 'S', w);
jtulach@0
   589
    }
jtulach@11
   590
jtulach@11
   591
jtulach@13
   592
    private void paintLeftFences(char[][] desk, Direction endDirection, int fences, boolean currentTurn) {
jtulach@13
   593
        assert fences >= 0 && fences <= 10 : "Players: " + players;
jtulach@11
   594
        switch (endDirection) {
jtulach@40
   595
            case SOUTH: {
jtulach@11
   596
                for (int i = 0; i < fences; i++) {
jtulach@11
   597
                    desk[desk.length - 1][i + 1] = '|';
jtulach@11
   598
                }
jtulach@13
   599
                if (currentTurn) {
jtulach@13
   600
                    desk[desk.length - 1][fences + 1] = '*';
jtulach@13
   601
                }
jtulach@11
   602
                break;
jtulach@11
   603
            }
jtulach@40
   604
            case NORTH: {
jtulach@11
   605
                for (int i = 0; i < fences; i++) {
jtulach@11
   606
                    desk[0][i + 1] = '|';
jtulach@11
   607
                }
jtulach@13
   608
                if (currentTurn) {
jtulach@13
   609
                    desk[0][fences + 1] = '*';
jtulach@13
   610
                }
jtulach@11
   611
                break;
jtulach@11
   612
            }
jtulach@11
   613
            default:
jtulach@11
   614
                assert false;
jtulach@11
   615
        }
jtulach@11
   616
    }
jtulach@11
   617
jtulach@11
   618
jtulach@0
   619
    //
jtulach@0
   620
    // Standard Methods
jtulach@0
   621
    // 
jtulach@0
   622
jtulach@0
   623
    
jtulach@2
   624
    @Override
jtulach@0
   625
    public int hashCode () {
jtulach@0
   626
        return occupied.hashCode ();
jtulach@0
   627
    }
jtulach@0
   628
    
jtulach@2
   629
    @Override
jtulach@0
   630
    public boolean equals (Object o) {
jtulach@0
   631
        if (o instanceof Board) {
jtulach@0
   632
            Board b = (Board)o;
jtulach@0
   633
            return occupied.equals (b.occupied) && players.equals (b.players);
jtulach@0
   634
        }
jtulach@0
   635
        return false;
jtulach@0
   636
    }
jtulach@12
   637
jtulach@12
   638
    @Override
jtulach@12
   639
    public String toString() {
jtulach@12
   640
        StringWriter w = new StringWriter();
jtulach@12
   641
        try {
jtulach@12
   642
            write(w);
jtulach@12
   643
        } catch (IOException ex) {
jtulach@12
   644
            return ex.toString();
jtulach@12
   645
        }
jtulach@12
   646
        return w.toString();
jtulach@12
   647
    }
jtulach@12
   648
jtulach@0
   649
    
jtulach@0
   650
    //
jtulach@0
   651
    // Validation methods
jtulach@0
   652
    //
jtulach@0
   653
jtulach@0
   654
    /** Computes new position of a player and checks whether there is no
jtulach@0
   655
     * fence between the old and new.
jtulach@0
   656
     */
jtulach@0
   657
    private Player newPosition (Player old, Player.Direction direction) throws IllegalPositionException {
jtulach@0
   658
        return newPosition (old, direction, occupied);
jtulach@0
   659
    }
jtulach@0
   660
    
jtulach@0
   661
    
jtulach@3
   662
    private static Player newPosition (Player old, Player.Direction direction, BitSet occupied) throws IllegalPositionException {
jtulach@0
   663
        int nx = old.x;
jtulach@0
   664
        int ny = old.y;
jtulach@0
   665
        int fx = old.x;
jtulach@0
   666
        int fy = old.y;
jtulach@0
   667
        
jtulach@0
   668
        switch (direction) {
jtulach@0
   669
            case NORTH: 
jtulach@0
   670
                ny = old.y + 2;
jtulach@0
   671
                fy = old.y + 1;
jtulach@0
   672
                break;
jtulach@0
   673
            case SOUTH: 
jtulach@0
   674
                ny = old.y - 2; 
jtulach@0
   675
                fy = old.y - 1;
jtulach@0
   676
                break;
jtulach@0
   677
            case EAST: 
jtulach@0
   678
                nx = old.x + 2; 
jtulach@0
   679
                fx = old.x + 1;
jtulach@0
   680
                break;
jtulach@0
   681
            case WEST: 
jtulach@0
   682
                nx = old.x - 2; 
jtulach@0
   683
                fx = old.x - 1;
jtulach@0
   684
                break;
jtulach@0
   685
            default: throw new IllegalStateException ("Unknown direction: " + direction); // NOI18N
jtulach@0
   686
        }
jtulach@0
   687
        
jtulach@0
   688
        if (nx < 0 || nx > 16) throw new IllegalPositionException ("Wrong player position: " + nx + ":" + ny); // NOI18N
jtulach@0
   689
        if (ny < 0 || ny > 16) throw new IllegalPositionException ("Wrong player position: " + nx + ":" + ny); // NOI18N
jtulach@0
   690
        
jtulach@0
   691
        int fenceIndex = toIndex (fx, fy);
jtulach@0
   692
        if (occupied.get (fenceIndex)) {
jtulach@0
   693
            throw new IllegalPositionException ("You cannot go over fences"); // NOI18N
jtulach@0
   694
        }
jtulach@0
   695
        
jtulach@0
   696
        return new Player (nx, ny, old.getFences (), old.endDirection);
jtulach@0
   697
    }
jtulach@0
   698
    
jtulach@0
   699
    /** @param position the current position of the player
jtulach@0
   700
     * @param endDir the side the player wants to reach
jtulach@0
   701
     * @param fences bits set to 1 when fences are placed
jtulach@0
   702
     * @param reached bits on squares that were already reached (modified during run)
jtulach@0
   703
     * @return true if the end line can be reached
jtulach@0
   704
     */
jtulach@3
   705
    private static boolean accessibleFinalLine (Player position, Player.Direction endDir, BitSet fences, BitSet reached) {
jtulach@0
   706
        int index = toIndex (position);
jtulach@0
   707
        if (reached.get (index)) {
jtulach@0
   708
            // already visited without success
jtulach@0
   709
            return false;
jtulach@0
   710
        }
jtulach@0
   711
        
jtulach@0
   712
        switch (endDir) {
jtulach@0
   713
            case NORTH: 
jtulach@26
   714
                if (position.getYInternal() == 8) return true;
jtulach@0
   715
                break;
jtulach@0
   716
            case SOUTH:
jtulach@26
   717
                if (position.getYInternal() == 0) return true;
jtulach@0
   718
                break;
jtulach@0
   719
            case EAST:
jtulach@26
   720
                if (position.getXInternal() == 0) return true;
jtulach@0
   721
                break;
jtulach@0
   722
            case WEST:
jtulach@26
   723
                if (position.getXInternal() == 8) return true;
jtulach@0
   724
                break;
jtulach@0
   725
            default:
jtulach@0
   726
                throw new IllegalStateException ("Wrong direction: " + endDir); // NOI18N
jtulach@0
   727
        }
jtulach@0
   728
        
jtulach@0
   729
        reached.set (index);
jtulach@0
   730
        
jtulach@0
   731
        for (Player.Direction oneDirection : Player.Direction.values ()) {
jtulach@0
   732
            try {
jtulach@0
   733
                if (accessibleFinalLine (newPosition (position, oneDirection, fences), endDir, fences, reached)) {
jtulach@0
   734
                    return true;
jtulach@0
   735
                }
jtulach@0
   736
            } catch (IllegalPositionException ex) {
jtulach@0
   737
                // ok, try once more
jtulach@0
   738
            }
jtulach@0
   739
        }
jtulach@0
   740
        
jtulach@0
   741
        return false;
jtulach@0
   742
    }
jtulach@0
   743
    
jtulach@0
   744
    /** Computes mask of the occupried bits.
jtulach@0
   745
     */
jtulach@3
   746
    private static BitSet computeOccupied (
jtulach@3
   747
        Collection<Player> players, Collection<Fence> fences
jtulach@0
   748
    ) throws IllegalPositionException {
jtulach@23
   749
        BitSet occupied = new BitSet (20 * 30); // TBD this is just an upper guess
jtulach@0
   750
        
jtulach@0
   751
        for (Player p : players) {
jtulach@26
   752
            if (p.getXInternal() % 2 == 1) throw new IllegalPositionException ("Wrong player position: " + p); // NOI18N
jtulach@26
   753
            if (p.getYInternal() % 2 == 1) throw new IllegalPositionException ("Wrong player position: " + p); // NOI18N
jtulach@0
   754
            
jtulach@0
   755
            int index = toIndex (p);
jtulach@0
   756
            if (occupied.get (index)) {
jtulach@0
   757
                throw new IllegalPositionException ("There cannot be two players at " + p); // NOI18N
jtulach@0
   758
            }
jtulach@0
   759
            occupied.set (index);
jtulach@0
   760
        }
jtulach@0
   761
        
jtulach@0
   762
        for (Fence f : fences) {
jtulach@0
   763
            
jtulach@0
   764
            for (int i = -1; i <= 1; i++) {
jtulach@0
   765
                int index = toIndex (f, i);
jtulach@0
   766
                if (index < 0 || index > occupied.size ()) {
jtulach@0
   767
                    throw new IllegalPositionException ("Wrong fence position: " + f); // NOI18N
jtulach@0
   768
                }
jtulach@0
   769
                if (occupied.get (index)) {
jtulach@0
   770
                    throw new IllegalPositionException ("Fence collition: " + f); // NOI18N
jtulach@0
   771
                }
jtulach@0
   772
                
jtulach@0
   773
                occupied.set (index);
jtulach@0
   774
            }
jtulach@0
   775
            
jtulach@0
   776
        }
jtulach@0
   777
        
jtulach@0
   778
        return occupied;
jtulach@0
   779
    }
jtulach@0
   780
jtulach@0
   781
    /** Converts twodimensional coordinates of player to one dimensional index.
jtulach@0
   782
     */
jtulach@0
   783
    private static int toIndex (Player p) {
jtulach@26
   784
        return toIndex (p.getXInternal(), p.getYInternal());
jtulach@0
   785
    }
jtulach@0
   786
    
jtulach@0
   787
    /** Converts twodimensional coordinates of fence to one dimensional index.
jtulach@0
   788
     * @param f the fence
jtulach@0
   789
     * @param whichPart (-1, 0, 1) 
jtulach@0
   790
     */
jtulach@0
   791
    private static int toIndex (Fence f, int whichPart) {
jtulach@0
   792
        int x = f.getX ();
jtulach@0
   793
        int y = f.getY ();
jtulach@0
   794
        
jtulach@0
   795
        switch (f.getOrientation ()) {
jtulach@0
   796
            case HORIZONTAL: x += whichPart; break;
jtulach@0
   797
            case VERTICAL: y += whichPart; break;
jtulach@0
   798
            default: throw new IllegalStateException ("Wrong orientation: " + f.getOrientation ()); // NOI18N
jtulach@0
   799
        }
jtulach@0
   800
        
jtulach@0
   801
        return toIndex (x, y);
jtulach@0
   802
    }
jtulach@0
   803
    
jtulach@0
   804
    /** Diagonal conversion of two positive integers to one integer.
jtulach@0
   805
     * <pre>
jtulach@0
   806
     * y3 9
jtulach@0
   807
     * y2 5  8
jtulach@0
   808
     * y1 2  4  7
jtulach@0
   809
     * y0 0  1  3  6
jtulach@0
   810
     *   x0 x1 x2 x3
jtulach@0
   811
     * </pre>
jtulach@0
   812
     */
jtulach@0
   813
    private static int toIndex (int x, int y) {
jtulach@0
   814
        int max = x + y;
jtulach@0
   815
        int baseOnY0 = max * (max + 1) / 2;
jtulach@0
   816
        return baseOnY0 + y;
jtulach@0
   817
    }
jtulach@17
   818
jtulach@0
   819
}