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