All Downloads are FREE. Search and download functionalities are using the official Maven repository.

chesspresso.game.Game Maven / Gradle / Ivy

/*
 * Chessplorer-Lib - an open source chess library written in Java
 * Copyright (C) 2016 Chessplorer.org
 * Copyright (C) 2012-2016 Gerhard Kalab
 * Copyright (C) 2002-2003 Bernhard Seybold
 *
 * This software is published under the terms of the LGPL Software License,
 * a copy of which has been included with this distribution in the LICENSE.txt
 * file.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 */
package chesspresso.game;

import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import chesspresso.Chess;
import chesspresso.move.IllegalMoveException;
import chesspresso.move.Move;
import chesspresso.pgn.PGN;
import chesspresso.position.ImmutablePosition;
import chesspresso.position.Position;
import chesspresso.position.PositionChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Abstraction of a chess game.
 *
 * A chess game consists of the following parts:
 *  
  • {@link GameHeaderModel} containing information about the game header, * for instance white name, event, site *
  • {@link GameMoveModel} containing the moves, lines, comments of the game. *
  • a cursor and the current position in the game. *
* * If you only need the information, not a cursor, use {@link GameModel} consisting * of {@link GameHeaderModel} and {@link GameMoveModel}. * * The game offers the following groups of operation: *
    *
  • direct access to values of the game header *
  • methods to append or delete lines of the move model *
  • methods to handle listeners for game changes *
  • methods to walk through the game, beginning mit go *
  • a method to {@link #traverse(GameListener, boolean)} the game in postfix order * (the order used by {@link chesspresso.pgn.PGN}) *
* * @author Bernhard Seybold * @author Andreas Rudolph */ public class Game implements PositionChangeListener { private final static Logger LOGGER = LoggerFactory.getLogger( Game.class ); private static boolean DEBUG = false; private GameModel m_model; private GameHeaderModel m_header; private GameMoveModel m_moves; private Position m_position; private int m_cur; private boolean m_ignoreNotifications; private boolean m_alwaysAddLine; // during pgn parsing, always add new lines private List m_changeListeners; private boolean error=false; public Game() { this(new GameModel()); } public Game(GameModel gameModel) { setModel(gameModel); m_ignoreNotifications = false; m_alwaysAddLine = false; } public GameModel getModel() {return m_model;} public Position getPosition() {return m_position;} public int getCurNode() {return m_cur;} public int getRootNode() {return 0;} public void pack() { m_cur = m_moves.pack(m_cur); // TODO pack headers? } private void setModel(GameModel gameModel) { m_model = gameModel; m_header = gameModel.getHeaderModel(); m_moves = gameModel.getMoveModel(); String fen = m_header.getTag(PGN.TAG_FEN); if (fen != null) { setPosition(new Position (fen, false)); } else { setPosition(Position.createInitialPosition()); } } private void setPosition(Position position) { m_position = position; m_position.addPositionChangeListener(this); m_cur = 0; } public void setAlwaysAddLine(boolean alwaysAddLine) {m_alwaysAddLine = alwaysAddLine;} public void addChangeListener(GameModelChangeListener listener) { if (m_changeListeners == null) m_changeListeners = new ArrayList(); m_changeListeners.add(listener); } public void removeChangeListener(GameModelChangeListener listener) { m_changeListeners.remove(listener); if (m_changeListeners.size() == 0) m_changeListeners = null; } protected void fireMoveModelChanged() { if (m_changeListeners != null) { for (Iterator it=m_changeListeners.iterator(); it.hasNext(); ) { ((GameModelChangeListener)it.next()).moveModelChanged(this); } } } public void notifyPositionChanged(ImmutablePosition position) { } public void notifyMoveDone(ImmutablePosition position, short move) { if (DEBUG) LOGGER.debug("ChGame: move made in position " + move); if (!m_ignoreNotifications) { if (!m_alwaysAddLine) { short[] moves = getNextShortMoves(); for (int i=0; i } } } m_cur = m_moves.appendAsRightMostLine(m_cur, move); fireMoveModelChanged(); } } public void notifyMoveUndone(ImmutablePosition position) { if (DEBUG) LOGGER.debug("ChGame: move taken back in position"); if (!m_ignoreNotifications) { m_cur = m_moves.goBack(m_cur, true); } } public String getTag(String tagName) {return m_header.getTag(tagName);} public String[] getTags() {return m_header.getTags();} public void setTag(String tagName, String tagValue) { String fen = tagValue; if (PGN.TAG_FEN.equals(tagName)) { try { setPosition(new Position(fen, false)); } catch (IllegalArgumentException ex) { if (fen.trim().split(" ").length == 4) { // support broken FENs by Shredder fen = fen.trim() + " 0 1"; setPosition(new Position(fen, false)); } } } m_header.setTag(tagName, fen); } public String getEvent() {return m_header.getEvent();} public String getSite() {return m_header.getSite();} public String getDate() {return m_header.getDate();} public String getRound() {return m_header.getRound();} public String getWhite() {return m_header.getWhite();} public String getBlack() {return m_header.getBlack();} public String getResultStr() {return m_header.getResultStr();} public String getWhiteEloStr() {return m_header.getWhiteEloStr();} public String getBlackEloStr() {return m_header.getBlackEloStr();} public String getEventDate() {return m_header.getEventDate();} public String getECO() {return m_header.getECO();} public int getResult() {return m_header.getResult();} public int getWhiteElo() {return m_header.getWhiteElo();} public int getBlackElo() {return m_header.getBlackElo();} /** * Returns whether the given position occurs in the main line of this game. * *@param position the position to look for, must not be null *@return whether the given position occurs in the main line of this game */ public boolean containsPosition(ImmutablePosition position) { boolean res = false; int index = getCurNode(); gotoStart(true); for (;;) { if (m_position.getHashCode() == position.getHashCode()) {res = true; break;} if (!hasNextMove()) break; goForward(true); } gotoNode(index, true); return res; } /** * Returns info about the game consisting of white player, * black player and result. * *@return the info string */ public String getInfoString() { return getWhite() + " - " + getBlack() + " " + getResultStr(); } /** * Returns info about the game consisting of white player, * black player, event, site, date, result, and ECO. * *@return the info string */ public String getLongInfoString() { StringBuffer sb = new StringBuffer(); sb.append(getWhite()).append(" - ").append(getBlack()).append(", ").append(getEvent()); if (getRound() != null) { sb.append(" (").append(getRound()).append(") "); } sb.append(", ").append(getSite()).append(" ").append(getResult()); if (getECO() != null) { sb.append(" [").append(getECO()).append("]"); } return sb.toString(); } /** * Returns information to display at the header of a game. The information * is split in three parts: (1) white and black player plus their elos, (2) * event, site, date, rounf, and (3) the ECO. * *@param line which line to return (0..2) *@return the info string */ public String getHeaderString(int line) { if (line == 0) { StringBuffer sb = new StringBuffer(); sb.append(getWhite()); if (getWhiteElo() != 0) sb.append(" [").append(getWhiteElo()).append("]"); sb.append(" - ").append(getBlack()); if (getBlackElo() != 0) sb.append(" [").append(getBlackElo()).append("]"); sb.append(" ").append(getResultStr()).append(" (").append(getNumOfMoves()).append(")"); return sb.toString(); } else if (line == 1) { StringBuffer sb = new StringBuffer(); sb.append(getEvent()).append(", ").append(getSite()).append(", ").append(getDate()); sb.append(" [").append(getRound()).append("]"); return sb.toString(); } else if (line == 2) { return getECO(); } else { throw new RuntimeException("Only 3 header lines supported"); } } public boolean hasNag(short nag) {return m_moves.hasNag(m_cur, nag);} public short[] getNags() {return m_cur == 0 ? null : m_moves.getNags(m_cur);} public void addNag(short nag) {m_moves.addNag(m_cur, nag); fireMoveModelChanged();} public void removeNag(short nag) {if (m_moves.removeNag(m_cur, nag)) fireMoveModelChanged();} public String getComment() {return m_moves.getComment(m_cur);} public String getPreMoveComment() {return m_moves.getPreMoveComment(m_cur);} public String getPostMoveComment() {return m_moves.getComment(m_cur);} public void setComment(String comment) {if (m_moves.setComment(m_cur, comment)) fireMoveModelChanged();} public void removeComment() {if (m_moves.removeComment(m_cur)) fireMoveModelChanged();} public int getCurrentPly() {return m_position.getPlyNumber();} public int getCurrentMoveNumber() {return (m_position.getPlyNumber() + 1) / 2;} public int getNextMoveNumber() {return (m_position.getPlyNumber() + 2) / 2;} public int getNumOfPlies() { int num = 0; int index = 0; while (m_moves.hasNextMove(index)) { index = m_moves.goForward(index); num++; } return num; } public int getNumOfMoves() {return Chess.plyToMoveNumber(getNumOfPlies());} public int getTotalNumOfPlies() {return m_moves.getTotalNumOfPlies();} public Move getLastMove() { return m_position.getLastMove(); } public Move getNextMove() { return getNextMove(0); } public short getNextShortMove() { return getNextShortMove(0); } public Move getNextMove(int whichLine) { short shortMove = m_moves.getMove(m_moves.goForward(m_cur, whichLine)); if (shortMove == GameMoveModel.NO_MOVE) return null; // =====> try { m_position.setNotifyListeners(false); m_position.doMove(shortMove); Move move = m_position.getLastMove(); m_position.undoMove(); m_position.setNotifyListeners(true); return move; } catch (IllegalMoveException ex) { LOGGER.error( "Can't get next move!" ); LOGGER.error( "> " + ex.getLocalizedMessage(), ex ); return null; } } public short getNextShortMove(int whichLine) { return m_moves.getMove(m_moves.goForward(m_cur, whichLine)); } public boolean hasNextMove() {return m_moves.hasNextMove(m_cur);} public int getNumOfNextMoves() {return m_moves.getNumOfNextMoves(m_cur);} public short[] getNextShortMoves() { short[] moves = new short[m_moves.getNumOfNextMoves(m_cur)]; for (int i=0; i moves = " + m_moves.writeToString() ); LOGGER.error( "> cur = " + m_cur ); LOGGER.error( "> move =" + GameMoveModel.valueToString(move) ); LOGGER.error( "> " + ex.getLocalizedMessage(), ex ); } } m_position.setNotifyListeners(true); return moves; } public Move[] getMainLine() { int num = 0; int index = m_cur; while (m_moves.hasNextMove(index)) { index = m_moves.goForward(index); num++; } Move[] moves = new Move[num]; for (int i=0; i " + ex.getLocalizedMessage(), ex ); } } else { //LOGGER.error( "Can't go forward at end of line!", new Exception() ); } return false; } private Move goForwardAndGetMove(int whichLine, boolean silent) { if (DEBUG) LOGGER.debug("goForwardAndGetMove " + whichLine); int index = m_moves.goForward(m_cur, whichLine); short shortMove = m_moves.getMove(index); if (DEBUG) LOGGER.debug(" move = " + Move.getString(shortMove)); if (shortMove != GameMoveModel.NO_MOVE) { try { m_cur = index; m_ignoreNotifications = true; if (silent) m_position.setNotifyListeners(false); m_position.doMove(shortMove); Move move = m_position.getLastMove(); if (silent) m_position.setNotifyListeners(true); m_ignoreNotifications = false; return move; } catch (IllegalMoveException ex) { LOGGER.error( "Can't go forward!" ); LOGGER.error( "> " + ex.getLocalizedMessage(), ex ); } } else { //LOGGER.error( "Can't go forward at end of line!", new Exception() ); } return null; } private void gotoStart(boolean silent) { while (goBack(silent)) ; } private void gotoEndOfLine(boolean silent) { while (goForward(silent)) ; } private void goBackToLineBegin(boolean silent) { if (DEBUG) LOGGER.debug("goBackToLineBegin"); while (goBackInLine(silent)) ; } private void goBackToMainLine(boolean silent) { if (DEBUG) LOGGER.debug("goBackToMainLine"); goBackToLineBegin(silent); goBack(silent); goForward(silent); } private int getNumOfPliesToRoot(int node) { int plies = 0; while (node > 0) { node = m_moves.goBack(node, true); plies++; } return plies; } private int[] getNodesToRoot(int node) { int[] nodes; int i = 0; if (m_moves.getMove(node) != GameMoveModel.NO_MOVE) { nodes = new int[getNumOfPliesToRoot(node) + 1]; nodes[0] = node; i = 1; } else { nodes = new int[getNumOfPliesToRoot(node)]; // if we stand on a line end, don't include node in nodes to root i = 0; } for (; i < nodes.length; i++) { node = m_moves.goBack(node, true); nodes[i] = node; } return nodes; } public void gotoNode(int node, boolean silent) { int[] nodeNodes = getNodesToRoot(node); gotoStart(silent); for (int i = nodeNodes.length - 2; i >= 0; i--) { int nextMoveIndex = 0; for (int j = 1; j < getNumOfNextMoves(); j++) { if (m_moves.goForward(m_cur, j) == nodeNodes[i]) { nextMoveIndex = j; break; } } goForward(nextMoveIndex, silent); } m_cur = node; // now that we have made all the moves, set cur to node } public void gotoPosition(ImmutablePosition pos, boolean silent) { if (m_position.equals(pos)) return; int curNode = getCurNode(); gotoStart(true); do { if (m_position.equals(pos)) { int posNode = getCurNode(); gotoNode(curNode, true); gotoNode(posNode, silent); return; } } while (goForward(true)); } public void deleteCurrentLine(boolean silent) { int index = m_cur; if (goBack(silent)) { m_moves.deleteCurrentLine(index); fireMoveModelChanged(); } } /** * Method to traverse the game in postfix order * (first the lines, then the main line). This method is used by {@link chesspresso.pgn.PGN}. * *@param listener the listener to receive event when arriving at nodes *@param withLines whether or not to include lines of the current main line. */ public void traverse(GameListener listener, boolean withLines) { int index = getCurNode(); gotoStart(true); traverse(listener, withLines, m_position.getPlyNumber(), 0); gotoNode(index, true); } private void traverse(GameListener listener, boolean withLines, int plyNumber, int level) { while(hasNextMove()) { int numOfNextMoves = getNumOfNextMoves(); Move move = goForwardAndGetMove(true); listener.notifyMove(move, getNags(), getPreMoveComment(), getPostMoveComment(), plyNumber, level); if (withLines && numOfNextMoves > 1) { for (int i=1; i 0) listener.notifyLineEnd(level); } } plyNumber++; } } public void save(DataOutput out, int headerMode, int movesMode) throws IOException { m_model.save(out, headerMode, movesMode); } /** * Returns the hash code of the game, which is defined as the hash code of the * move model. That means two game are considered equal if they contain exactly the * same lines. The header does not matter. * *@return the hash code */ public int hashCode() { return getModel().hashCode(); } /** * Returns whether two games are equal. This is the case if they contain exactly the * same lines. The header does not matter. * *@return the hash code */ public boolean equals(Object obj) { if (obj == this) return true; if (!(obj instanceof Game)) return false; Game game = (Game)obj; return game.getModel().equals(getModel()); } /** * Returns a string represention of the game. Implemented as the string * represention of the header plus the move model. * *@return a string represention of the game */ public String toString() {return m_model.toString();} public void addPreMoveComment(String comment) { if (m_moves.addPreMoveComment(m_cur, comment)) fireMoveModelChanged(); } public void addPostMoveComment(String comment) { if (m_moves.addComment(m_cur, comment)) fireMoveModelChanged(); } public void addNullMove() throws IllegalMoveException { getPosition().doMove(Move.NULL_MOVE); getPosition().undoMove(); } public void promoteVariation() { // go to start of current variation while (goBackInLine(true)); int index = m_moves.promoteVariation(m_cur); if (index >= 0) { m_cur = index; fireMoveModelChanged(); } } public void deleteCurrentVariation() { int index = m_cur; while (goBackInLine(true)); if (goBack(true)) { m_moves.deleteCurrentVariation(index); fireMoveModelChanged(); } } public void setError(boolean b) { this.error = true; } public boolean hasError() { return this.error; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy