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