1 /** 2 * The chess framework library. 3 * More information is available at http://www.jinchess.com/. 4 * Copyright (C) 2002 Alexander Maryanovsky. 5 * All rights reserved. 6 * 7 * The chess framework library is free software; you can redistribute 8 * it and/or modify it under the terms of the GNU Lesser General Public License 9 * as published by the Free Software Foundation; either version 2 of the 10 * License, or (at your option) any later version. 11 * 12 * The chess framework library is distributed in the hope that it will 13 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 15 * General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public License 18 * along with the chess framework library; if not, write to the Free Software 19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 */ 21 22 package free.chess; 23 24 import free.chess.event.*; 25 import javax.swing.event.ChangeEvent; 26 import javax.swing.event.ChangeListener; 27 import javax.swing.event.EventListenerList; 28 import java.util.StringTokenizer; 29 import free.util.Utilities; 30 31 32 /** 33 * Represents a position in one of the chess wild variants. The Position class 34 * itself is generally variant independent, but it does make some assumptions, 35 * such as the board being a 8x8 container of pieces, there being two players 36 * and other things common to most chess variants. 37 * <B>IMPORTANT:</B> This class is not thread safe. 38 */ 39 40 public final class Position{ 41 42 43 44 /** 45 * The WildVariant of this Position. 46 */ 47 48 private final WildVariant variant; 49 50 51 52 53 /** 54 * A matrix to keep references to the pieces. 55 */ 56 57 private final Piece [][] pieces = new Piece[8][8]; 58 59 60 61 62 63 /** 64 * The Modifier of this Position. 65 */ 66 67 private final Modifier modifier; 68 69 70 71 72 73 /** 74 * The Player whose turn it currently is in this Position. 75 */ 76 77 private Player currentPlayer; 78 79 80 81 82 /** 83 * A FEN representation of the position. 84 */ 85 86 private String positionFEN; 87 88 89 90 91 92 /** 93 * Only one ChangeEvent is needed per model instance since the 94 * event's only (read-only) state is the source property. The source 95 * of events generated here is always "this". 96 */ 97 98 protected transient ChangeEvent changeEvent = null; 99 100 101 102 103 /** 104 * The listeners waiting for model changes. 105 */ 106 107 protected EventListenerList listenerList = new EventListenerList(); 108 109 110 111 112 /** 113 * Creates a new Position with the regular WildVariant (normal chess). 114 * 115 * @see #init() 116 */ 117 Position()118 public Position(){ 119 this(Chess.getInstance()); 120 } 121 122 123 124 125 126 /** 127 * Creates a new Position with the given WildVariant. 128 */ 129 Position(WildVariant variant)130 public Position(WildVariant variant){ 131 this.variant = variant; 132 this.modifier = new Modifier(this); 133 init(); 134 } 135 136 137 138 139 /** 140 * Creates a new Position which is exactly like the given Position. 141 */ 142 Position(Position source)143 public Position(Position source){ 144 this.variant = source.variant; 145 this.modifier = new Modifier(this); 146 copyFrom(source); 147 } 148 149 150 151 152 /** 153 * Returns the WildVariant of this Position. 154 */ 155 getVariant()156 public WildVariant getVariant(){ 157 return variant; 158 } 159 160 161 162 163 /** 164 * Returns the Piece at the given Square. 165 * 166 * @param square The location of the piece to return. 167 */ 168 getPieceAt(Square square)169 public Piece getPieceAt(Square square){ 170 return getPieceAt(square.getFile(),square.getRank()); 171 } 172 173 174 175 176 177 /** 178 * Returns the piece at the square with the given file and rank. 179 */ 180 getPieceAt(int file, int rank)181 public Piece getPieceAt(int file, int rank){ 182 return pieces[file][rank]; 183 } 184 185 186 187 188 /** 189 * Returns the piece at the square specified by the given string, as if by 190 * {@link Square#parseSquare(String)}. 191 */ 192 getPieceAt(String square)193 public Piece getPieceAt(String square){ 194 return getPieceAt(Square.parseSquare(square)); 195 } 196 197 198 199 200 201 /** 202 * Puts the given piece at the given square, replacing the piece that was 203 * there before. 204 * 205 * @param piece The piece to put. 206 * @param square The square where to put the piece. 207 */ 208 setPieceAt(Piece piece, Square square)209 public void setPieceAt(Piece piece, Square square){ 210 setPieceAtImpl(piece, square); 211 fireStateChanged(); 212 } 213 214 215 216 217 218 /** 219 * Puts the given piece at the given square, replacing the piece that was 220 * there before. 221 * 222 * @param piece The piece to put. 223 * @param square The square where to put the piece. 224 */ 225 setPieceAt(Piece piece, String square)226 public void setPieceAt(Piece piece, String square){ 227 setPieceAt(piece, Square.parseSquare(square)); 228 } 229 230 231 232 233 /** 234 * Returns the player whose turn it is in this position, the "current" 235 * player. 236 */ 237 getCurrentPlayer()238 public Player getCurrentPlayer(){ 239 return currentPlayer; 240 } 241 242 243 244 245 /** 246 * Sets the current player in this position to be the given player. 247 * 248 * @param player The player whose turn it is next. 249 */ 250 setCurrentPlayer(Player player)251 public void setCurrentPlayer(Player player){ 252 setCurrentPlayerImpl(player); 253 fireStateChanged(); 254 } 255 256 257 258 259 /** 260 * Sets this Position to represent the position represented by 261 * the given string. The string should represent a position by specifying 262 * 64 characters indicating what occupies (-PNBRQKpnbrqk) each square, in 263 * lexigraphic order (a8, b8, ..., h1). The player to move will be 264 * the player with the white pieces. 265 * 266 * @param pos The string representing the position. 267 * 268 * @throws PositionFormatException if the given string is not in the 269 * expected format. 270 */ 271 setLexigraphic(String pos)272 public void setLexigraphic(String pos) throws PositionFormatException{ 273 if (pos.length() < 64) 274 throw new PositionFormatException("Less than 64 letters in the string: " + pos); 275 276 int i = 0; 277 try{ 278 for (int rank = 7; rank >= 0; rank--){ 279 for (int file = 0; file < 8; file++){ 280 setPieceAtImpl(variant.parsePiece("" + pos.charAt(i++)), Square.getInstance(file, rank)); 281 } 282 } 283 } catch (IllegalArgumentException e){ 284 throw new PositionFormatException(e); 285 } 286 287 setCurrentPlayerImpl(Player.WHITE_PLAYER); 288 289 fireStateChanged(); 290 } 291 292 293 294 295 296 /** 297 * Returns a string representing the position by 64 characters indicating what 298 * occupies (obtained by calling toShortColorString() on each piece, '-' for 299 * no piece) each square, in lexigraphic order (a8, b8, ..., h1). 300 */ 301 getLexigraphic()302 public String getLexigraphic(){ 303 StringBuffer buf = new StringBuffer(64); 304 for (int rank=7; rank>=0; rank--) 305 for (int file=0; file<8; file++){ 306 Piece piece = getPieceAt(Square.getInstance(file, rank)); 307 if (piece==null) 308 buf.append("-"); 309 else 310 buf.append(piece.toShortColorString()); 311 } 312 313 return buf.toString(); 314 } 315 316 317 318 319 320 321 /** 322 * Sets this Position to represent the position described by the given string 323 * in FEN format. The FEN format is described at 324 * <A HREF="http://www.very-best.de/pgn-spec.htm#16.1">http://www.very-best.de/pgn-spec.htm#16.1</A>. 325 * The characters describing pieces aren't limited to the chess set (like in 326 * FEN), but are determined by the WildVariant of this position. 327 * 328 * @throws PositionFormatException if the given string is not in the expected 329 * format. 330 */ 331 setFEN(String fen)332 public void setFEN(String fen) throws PositionFormatException{ 333 StringTokenizer fenTokenizer = new StringTokenizer(fen, " "); 334 if (fenTokenizer.countTokens() != 6) 335 throw new PositionFormatException("Wrong amount of fields"); 336 337 String pos = fenTokenizer.nextToken(); 338 StringTokenizer ranks = new StringTokenizer(pos,"/"); 339 if (ranks.countTokens() != 8) 340 throw new PositionFormatException("Wrong amount of ranks"); 341 342 for (int rank = 7; rank >= 0; rank--){ 343 String rankString = ranks.nextToken(); 344 int file = 0; 345 for (int i = 0; i < rankString.length(); i++){ 346 if (file > 7) 347 throw new PositionFormatException("Rank " + rank + " extends beyond the board"); 348 349 char c = rankString.charAt(i); 350 if (Character.isDigit(c)){ 351 int emptyFiles = Character.digit(c, 10); 352 while (emptyFiles-- > 0){ 353 setPieceAtImpl(null, Square.getInstance(file, rank)); 354 file++; 355 } 356 } 357 else{ 358 try{ 359 setPieceAtImpl(variant.parsePiece(String.valueOf(c)), Square.getInstance(file, rank)); 360 file++; 361 } catch (IllegalArgumentException e){ 362 throw new PositionFormatException(e); 363 } 364 } 365 } 366 if (file != 8) 367 throw new PositionFormatException("Rank " + rank + " is a few files short"); 368 } 369 370 String colorToMove = fenTokenizer.nextToken(); 371 if (colorToMove.length() != 1) 372 throw new PositionFormatException("Wrong amount of characters in active color indicator: " + colorToMove); 373 if (colorToMove.equals("w")) 374 setCurrentPlayerImpl(Player.WHITE_PLAYER); 375 else if (colorToMove.equals("b")) 376 setCurrentPlayerImpl(Player.BLACK_PLAYER); 377 else 378 throw new PositionFormatException("Wrong active color indicator: " + colorToMove); 379 380 this.positionFEN = fen; 381 } 382 383 384 385 386 387 /** 388 * Returns the FEN representation of this Position. May return 389 * <code>null</code> if the current position wasn't set via the setFEN method. 390 * Note that as soon as the position is changed after setFEN was called, this 391 * method will return <code>null</code>. 392 */ 393 getFEN()394 public String getFEN(){ 395 return positionFEN; 396 } 397 398 399 400 401 402 403 /** 404 * Sets this Position to the initial position. 405 */ 406 init()407 public final void init(){ 408 variant.init(this); 409 } 410 411 412 413 414 /** 415 * Clears this position of any pieces. The current player is set to the 416 * player with the White pieces. 417 */ 418 clear()419 public void clear(){ 420 for (int file=0;file<8;file++) 421 for (int rank=0;rank<8;rank++) 422 setPieceAtImpl(null,Square.getInstance(file,rank)); 423 setCurrentPlayerImpl(Player.WHITE_PLAYER); 424 425 fireStateChanged(); 426 } 427 428 429 430 431 /** 432 * Makes the given Move on this position. This method first fires a MoveEvent 433 * and then a ChangeEvent. 434 * 435 * @param move The move to make. 436 * 437 * @throws IllegalArgumentException if the given Move is incompatible with 438 * the wild variant of this Position. 439 */ 440 makeMove(Move move)441 public void makeMove(Move move){ 442 variant.makeMove(move, this, modifier); 443 fireMoveMade(move); 444 fireStateChanged(); 445 } 446 447 448 449 450 /** 451 * Makes this position a copy of the given position by setting it to 452 * the same state. The WildVariants of the Positions must match. 453 * 454 * @param position The position to copy. 455 */ 456 copyFrom(Position position)457 public void copyFrom(Position position){ 458 if (!variant.equals(position.variant)) 459 throw new IllegalArgumentException("The WildVariants of the positions don't match"); 460 461 for (int file = 0; file < pieces.length; file++){ 462 for (int rank = 0; rank < pieces[file].length; rank++){ 463 pieces[file][rank] = position.pieces[file][rank]; 464 } 465 } 466 467 setCurrentPlayerImpl(position.getCurrentPlayer()); 468 469 this.positionFEN = position.positionFEN; 470 471 fireStateChanged(); 472 } 473 474 475 476 477 /** 478 * Puts the given piece at the given square, replacing the piece that was 479 * there before. The difference between this and the setPieceAt(Piece,Square) 480 * method is that this method does not fire a ChangeEvent. It's useful when 481 * you want to do a series of changes and only then fire a ChangeEvent. 482 * 483 * @param piece The piece to put. 484 * @param square The square where to put the piece. 485 * 486 * @see #setPieceAt(Piece, Square); 487 */ 488 setPieceAtImpl(Piece piece, Square square)489 private void setPieceAtImpl(Piece piece, Square square){ 490 pieces[square.getFile()][square.getRank()] = piece; 491 positionFEN = null; 492 } 493 494 495 496 497 /** 498 * Sets the current player in this position to be the given player. 499 * The difference between this and the setCurrentPlayer(Player) 500 * method is that this method does not fire a ChangeEvent. It's useful 501 * when you want to do a series of changes and only then fire a ChangeEvent. 502 * 503 * @param player The player whose turn it is next. 504 */ 505 setCurrentPlayerImpl(Player player)506 private void setCurrentPlayerImpl(Player player){ 507 this.currentPlayer = player; 508 positionFEN = null; 509 } 510 511 512 513 514 /** 515 * Adds a ChangeListener. The change listeners are run each 516 * time the Position changes. 517 * 518 * @param l the ChangeListener to add 519 * @see #removeChangeListener 520 */ 521 addChangeListener(ChangeListener l)522 public void addChangeListener(ChangeListener l) { 523 listenerList.add(ChangeListener.class, l); 524 } 525 526 527 528 529 /** 530 * Removes a ChangeListener. 531 * 532 * @param l the ChangeListener to remove 533 * @see #addChangeListener 534 */ 535 removeChangeListener(ChangeListener l)536 public void removeChangeListener(ChangeListener l) { 537 listenerList.remove(ChangeListener.class, l); 538 } 539 540 541 542 543 /** 544 * Run each ChangeListeners stateChanged() method. 545 */ 546 fireStateChanged()547 protected void fireStateChanged(){ 548 Object [] listeners = listenerList.getListenerList(); 549 for (int i = listeners.length-2; i>=0; i-=2 ){ 550 if (listeners[i] == ChangeListener.class){ 551 if (changeEvent == null){ 552 changeEvent = new ChangeEvent(this); 553 } 554 ((ChangeListener)listeners[i+1]).stateChanged(changeEvent); 555 } 556 } 557 } 558 559 560 561 562 /** 563 * Adds a MoveListener. The change listeners are run each 564 * time a move is made in this position. 565 * 566 * @param l the ChangeListener to add 567 * @see #removeChangeListener 568 */ 569 addMoveListener(MoveListener l)570 public void addMoveListener(MoveListener l) { 571 listenerList.add(MoveListener.class, l); 572 } 573 574 575 576 /** 577 * Removes a MoveListener. 578 * 579 * @param l the ChangeListener to remove 580 * @see #addChangeListener 581 */ 582 removeMoveListener(MoveListener l)583 public void removeMoveListener(MoveListener l) { 584 listenerList.remove(MoveListener.class, l); 585 } 586 587 588 589 590 /** 591 * Run each MoveListeners moveMade() method. 592 * 593 * @param move The Move that was made. 594 */ 595 fireMoveMade(Move move)596 protected void fireMoveMade(Move move){ 597 MoveEvent evt = new MoveEvent(this,move); 598 Object [] listeners = listenerList.getListenerList(); 599 for (int i = listeners.length-2; i>=0; i-=2 ){ 600 if (listeners[i] == MoveListener.class){ 601 ((MoveListener)listeners[i+1]).moveMade(evt); 602 } 603 } 604 } 605 606 607 608 609 /** 610 * Returns a textual representation of the board. 611 */ 612 toString()613 public String toString(){ 614 if (getCurrentPlayer().isWhite()) 615 return "White to move in "+getLexigraphic(); 616 else 617 return "Black to move in "+getLexigraphic(); 618 } 619 620 621 622 623 /** 624 * Returns true iff the specified <code>Position</code> is the same as this 625 * one. 626 */ 627 equals(Position pos)628 public boolean equals(Position pos){ 629 if (!variant.equals(pos.variant)) 630 return false; 631 632 if (!currentPlayer.equals(pos.currentPlayer)) 633 return false; 634 635 for (int file = 0; file < pieces.length; file++) 636 for (int rank = 0; rank < pieces[file].length; rank++) 637 if (!Utilities.areEqual(pieces[file][rank], pos.pieces[file][rank])) 638 return false; 639 640 return true; 641 } 642 643 644 645 646 /** 647 * Returns true iff the specified object is a <code>Position</code> and 648 * represents the same position as this one. 649 */ 650 equals(Object obj)651 public boolean equals(Object obj){ 652 if (!(obj instanceof Position)) 653 return false; 654 655 return equals((Position)obj); 656 } 657 658 659 660 661 /** 662 * Returns the hashcode of this position. 663 */ 664 hashCode()665 public int hashCode(){ 666 int result = 17; 667 result = 37*result + variant.hashCode(); 668 result = 37*result + currentPlayer.hashCode(); 669 for (int file = 0; file < pieces.length; file++) 670 for (int rank = 0; rank < pieces[file].length; rank++){ 671 Piece piece = pieces[file][rank]; 672 int c = (piece == null) ? 0 : piece.hashCode(); 673 result = 37*result + c; 674 } 675 676 return result; 677 } 678 679 680 681 682 /** 683 * Instances of this class is allowed to make modifications to a Position 684 * without triggering the Position instance to fire any events. It should only 685 * be used by WildVariant implementations for making multi-step atomic changes 686 * which don't trigger firing many events to a Position and the firing an event 687 * indicating the change is done. 688 */ 689 690 public static final class Modifier{ 691 692 693 694 /** 695 * The Position we're modifying. Don't eliminate this by making the Modifier 696 * class nonstatic because it causes Jikes to create invalid bytecode. 697 * See http://www-124.ibm.com/pipermail/jikes-dev/2000-November/001585.html 698 */ 699 700 private final Position position; 701 702 703 704 /** 705 * Creates a new PositionModifier with the given Position. 706 */ 707 Modifier(Position position)708 private Modifier(Position position){ 709 this.position = position; 710 } 711 712 713 714 /** 715 * Puts the given piece at the given Square. 716 */ 717 setPieceAt(Piece piece, Square square)718 public void setPieceAt(Piece piece, Square square){ 719 position.setPieceAtImpl(piece, square); 720 } 721 722 723 724 /** 725 * Sets the current player. 726 */ 727 setCurrentPlayer(Player player)728 public void setCurrentPlayer(Player player){ 729 position.setCurrentPlayerImpl(player); 730 } 731 732 733 } 734 735 736 } 737