/** * The chess framework library. * More information is available at http://www.jinchess.com/. * Copyright (C) 2002 Alexander Maryanovsky. * All rights reserved. * * The chess framework library is free software; you can redistribute * it and/or modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * The chess framework library is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the chess framework library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package free.chess; import free.chess.event.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; import java.util.StringTokenizer; import free.util.Utilities; /** * Represents a position in one of the chess wild variants. The Position class * itself is generally variant independent, but it does make some assumptions, * such as the board being a 8x8 container of pieces, there being two players * and other things common to most chess variants. * IMPORTANT: This class is not thread safe. */ public final class Position{ /** * The WildVariant of this Position. */ private final WildVariant variant; /** * A matrix to keep references to the pieces. */ private final Piece [][] pieces = new Piece[8][8]; /** * The Modifier of this Position. */ private final Modifier modifier; /** * The Player whose turn it currently is in this Position. */ private Player currentPlayer; /** * A FEN representation of the position. */ private String positionFEN; /** * Only one ChangeEvent is needed per model instance since the * event's only (read-only) state is the source property. The source * of events generated here is always "this". */ protected transient ChangeEvent changeEvent = null; /** * The listeners waiting for model changes. */ protected EventListenerList listenerList = new EventListenerList(); /** * Creates a new Position with the regular WildVariant (normal chess). * * @see #init() */ public Position(){ this(Chess.getInstance()); } /** * Creates a new Position with the given WildVariant. */ public Position(WildVariant variant){ this.variant = variant; this.modifier = new Modifier(this); init(); } /** * Creates a new Position which is exactly like the given Position. */ public Position(Position source){ this.variant = source.variant; this.modifier = new Modifier(this); copyFrom(source); } /** * Returns the WildVariant of this Position. */ public WildVariant getVariant(){ return variant; } /** * Returns the Piece at the given Square. * * @param square The location of the piece to return. */ public Piece getPieceAt(Square square){ return getPieceAt(square.getFile(),square.getRank()); } /** * Returns the piece at the square with the given file and rank. */ public Piece getPieceAt(int file, int rank){ return pieces[file][rank]; } /** * Returns the piece at the square specified by the given string, as if by * {@link Square#parseSquare(String)}. */ public Piece getPieceAt(String square){ return getPieceAt(Square.parseSquare(square)); } /** * Puts the given piece at the given square, replacing the piece that was * there before. * * @param piece The piece to put. * @param square The square where to put the piece. */ public void setPieceAt(Piece piece, Square square){ setPieceAtImpl(piece, square); fireStateChanged(); } /** * Puts the given piece at the given square, replacing the piece that was * there before. * * @param piece The piece to put. * @param square The square where to put the piece. */ public void setPieceAt(Piece piece, String square){ setPieceAt(piece, Square.parseSquare(square)); } /** * Returns the player whose turn it is in this position, the "current" * player. */ public Player getCurrentPlayer(){ return currentPlayer; } /** * Sets the current player in this position to be the given player. * * @param player The player whose turn it is next. */ public void setCurrentPlayer(Player player){ setCurrentPlayerImpl(player); fireStateChanged(); } /** * Sets this Position to represent the position represented by * the given string. The string should represent a position by specifying * 64 characters indicating what occupies (-PNBRQKpnbrqk) each square, in * lexigraphic order (a8, b8, ..., h1). The player to move will be * the player with the white pieces. * * @param pos The string representing the position. * * @throws PositionFormatException if the given string is not in the * expected format. */ public void setLexigraphic(String pos) throws PositionFormatException{ if (pos.length() < 64) throw new PositionFormatException("Less than 64 letters in the string: " + pos); int i = 0; try{ for (int rank = 7; rank >= 0; rank--){ for (int file = 0; file < 8; file++){ setPieceAtImpl(variant.parsePiece("" + pos.charAt(i++)), Square.getInstance(file, rank)); } } } catch (IllegalArgumentException e){ throw new PositionFormatException(e); } setCurrentPlayerImpl(Player.WHITE_PLAYER); fireStateChanged(); } /** * Returns a string representing the position by 64 characters indicating what * occupies (obtained by calling toShortColorString() on each piece, '-' for * no piece) each square, in lexigraphic order (a8, b8, ..., h1). */ public String getLexigraphic(){ StringBuffer buf = new StringBuffer(64); for (int rank=7; rank>=0; rank--) for (int file=0; file<8; file++){ Piece piece = getPieceAt(Square.getInstance(file, rank)); if (piece==null) buf.append("-"); else buf.append(piece.toShortColorString()); } return buf.toString(); } /** * Sets this Position to represent the position described by the given string * in FEN format. The FEN format is described at * http://www.very-best.de/pgn-spec.htm#16.1. * The characters describing pieces aren't limited to the chess set (like in * FEN), but are determined by the WildVariant of this position. * * @throws PositionFormatException if the given string is not in the expected * format. */ public void setFEN(String fen) throws PositionFormatException{ StringTokenizer fenTokenizer = new StringTokenizer(fen, " "); if (fenTokenizer.countTokens() != 6) throw new PositionFormatException("Wrong amount of fields"); String pos = fenTokenizer.nextToken(); StringTokenizer ranks = new StringTokenizer(pos,"/"); if (ranks.countTokens() != 8) throw new PositionFormatException("Wrong amount of ranks"); for (int rank = 7; rank >= 0; rank--){ String rankString = ranks.nextToken(); int file = 0; for (int i = 0; i < rankString.length(); i++){ if (file > 7) throw new PositionFormatException("Rank " + rank + " extends beyond the board"); char c = rankString.charAt(i); if (Character.isDigit(c)){ int emptyFiles = Character.digit(c, 10); while (emptyFiles-- > 0){ setPieceAtImpl(null, Square.getInstance(file, rank)); file++; } } else{ try{ setPieceAtImpl(variant.parsePiece(String.valueOf(c)), Square.getInstance(file, rank)); file++; } catch (IllegalArgumentException e){ throw new PositionFormatException(e); } } } if (file != 8) throw new PositionFormatException("Rank " + rank + " is a few files short"); } String colorToMove = fenTokenizer.nextToken(); if (colorToMove.length() != 1) throw new PositionFormatException("Wrong amount of characters in active color indicator: " + colorToMove); if (colorToMove.equals("w")) setCurrentPlayerImpl(Player.WHITE_PLAYER); else if (colorToMove.equals("b")) setCurrentPlayerImpl(Player.BLACK_PLAYER); else throw new PositionFormatException("Wrong active color indicator: " + colorToMove); this.positionFEN = fen; } /** * Returns the FEN representation of this Position. May return * null if the current position wasn't set via the setFEN method. * Note that as soon as the position is changed after setFEN was called, this * method will return null. */ public String getFEN(){ return positionFEN; } /** * Sets this Position to the initial position. */ public final void init(){ variant.init(this); } /** * Clears this position of any pieces. The current player is set to the * player with the White pieces. */ public void clear(){ for (int file=0;file<8;file++) for (int rank=0;rank<8;rank++) setPieceAtImpl(null,Square.getInstance(file,rank)); setCurrentPlayerImpl(Player.WHITE_PLAYER); fireStateChanged(); } /** * Makes the given Move on this position. This method first fires a MoveEvent * and then a ChangeEvent. * * @param move The move to make. * * @throws IllegalArgumentException if the given Move is incompatible with * the wild variant of this Position. */ public void makeMove(Move move){ variant.makeMove(move, this, modifier); fireMoveMade(move); fireStateChanged(); } /** * Makes this position a copy of the given position by setting it to * the same state. The WildVariants of the Positions must match. * * @param position The position to copy. */ public void copyFrom(Position position){ if (!variant.equals(position.variant)) throw new IllegalArgumentException("The WildVariants of the positions don't match"); for (int file = 0; file < pieces.length; file++){ for (int rank = 0; rank < pieces[file].length; rank++){ pieces[file][rank] = position.pieces[file][rank]; } } setCurrentPlayerImpl(position.getCurrentPlayer()); this.positionFEN = position.positionFEN; fireStateChanged(); } /** * Puts the given piece at the given square, replacing the piece that was * there before. The difference between this and the setPieceAt(Piece,Square) * method is that this method does not fire a ChangeEvent. It's useful when * you want to do a series of changes and only then fire a ChangeEvent. * * @param piece The piece to put. * @param square The square where to put the piece. * * @see #setPieceAt(Piece, Square); */ private void setPieceAtImpl(Piece piece, Square square){ pieces[square.getFile()][square.getRank()] = piece; positionFEN = null; } /** * Sets the current player in this position to be the given player. * The difference between this and the setCurrentPlayer(Player) * method is that this method does not fire a ChangeEvent. It's useful * when you want to do a series of changes and only then fire a ChangeEvent. * * @param player The player whose turn it is next. */ private void setCurrentPlayerImpl(Player player){ this.currentPlayer = player; positionFEN = null; } /** * Adds a ChangeListener. The change listeners are run each * time the Position changes. * * @param l the ChangeListener to add * @see #removeChangeListener */ public void addChangeListener(ChangeListener l) { listenerList.add(ChangeListener.class, l); } /** * Removes a ChangeListener. * * @param l the ChangeListener to remove * @see #addChangeListener */ public void removeChangeListener(ChangeListener l) { listenerList.remove(ChangeListener.class, l); } /** * Run each ChangeListeners stateChanged() method. */ protected void fireStateChanged(){ Object [] listeners = listenerList.getListenerList(); for (int i = listeners.length-2; i>=0; i-=2 ){ if (listeners[i] == ChangeListener.class){ if (changeEvent == null){ changeEvent = new ChangeEvent(this); } ((ChangeListener)listeners[i+1]).stateChanged(changeEvent); } } } /** * Adds a MoveListener. The change listeners are run each * time a move is made in this position. * * @param l the ChangeListener to add * @see #removeChangeListener */ public void addMoveListener(MoveListener l) { listenerList.add(MoveListener.class, l); } /** * Removes a MoveListener. * * @param l the ChangeListener to remove * @see #addChangeListener */ public void removeMoveListener(MoveListener l) { listenerList.remove(MoveListener.class, l); } /** * Run each MoveListeners moveMade() method. * * @param move The Move that was made. */ protected void fireMoveMade(Move move){ MoveEvent evt = new MoveEvent(this,move); Object [] listeners = listenerList.getListenerList(); for (int i = listeners.length-2; i>=0; i-=2 ){ if (listeners[i] == MoveListener.class){ ((MoveListener)listeners[i+1]).moveMade(evt); } } } /** * Returns a textual representation of the board. */ public String toString(){ if (getCurrentPlayer().isWhite()) return "White to move in "+getLexigraphic(); else return "Black to move in "+getLexigraphic(); } /** * Returns true iff the specified Position is the same as this * one. */ public boolean equals(Position pos){ if (!variant.equals(pos.variant)) return false; if (!currentPlayer.equals(pos.currentPlayer)) return false; for (int file = 0; file < pieces.length; file++) for (int rank = 0; rank < pieces[file].length; rank++) if (!Utilities.areEqual(pieces[file][rank], pos.pieces[file][rank])) return false; return true; } /** * Returns true iff the specified object is a Position and * represents the same position as this one. */ public boolean equals(Object obj){ if (!(obj instanceof Position)) return false; return equals((Position)obj); } /** * Returns the hashcode of this position. */ public int hashCode(){ int result = 17; result = 37*result + variant.hashCode(); result = 37*result + currentPlayer.hashCode(); for (int file = 0; file < pieces.length; file++) for (int rank = 0; rank < pieces[file].length; rank++){ Piece piece = pieces[file][rank]; int c = (piece == null) ? 0 : piece.hashCode(); result = 37*result + c; } return result; } /** * Instances of this class is allowed to make modifications to a Position * without triggering the Position instance to fire any events. It should only * be used by WildVariant implementations for making multi-step atomic changes * which don't trigger firing many events to a Position and the firing an event * indicating the change is done. */ public static final class Modifier{ /** * The Position we're modifying. Don't eliminate this by making the Modifier * class nonstatic because it causes Jikes to create invalid bytecode. * See http://www-124.ibm.com/pipermail/jikes-dev/2000-November/001585.html */ private final Position position; /** * Creates a new PositionModifier with the given Position. */ private Modifier(Position position){ this.position = position; } /** * Puts the given piece at the given Square. */ public void setPieceAt(Piece piece, Square square){ position.setPieceAtImpl(piece, square); } /** * Sets the current player. */ public void setCurrentPlayer(Player player){ position.setCurrentPlayerImpl(player); } } }