removed tabs, added game history

svn path=/trunk/; revision=6842
This commit is contained in:
Robert Ancell 2007-10-06 10:43:23 +00:00
parent 687bbccb2e
commit f18a39ba49
12 changed files with 882 additions and 979 deletions

6
TODO
View file

@ -4,10 +4,7 @@
Have pop-up menu on tab headers (i.e. game menu).
Advanced save options (i.e. PGN fields).
Supply application/x-chess-pgn icons
Add numbering to the board
LAN game support
Gnome Gaming Zone support
Crash dialog so users can easily make bug reports
Make configuration dialog (with AI config too)
Don't autosave games that have finished (move into a history file)
Reflect chess pieces in board
@ -15,10 +12,11 @@ Chess piece shadows
Anti-aliasing
Drag and drop 2D pieces
Drag and drop application/x-chess-pgn files
Add icons for human/network/ai players. Use Red Robot for the AI!
Use Red Robot for the AI player icon!
Share textures and display lists between games
Allow resigning
Unifiy save format with KDE's Knights interface
Allow multiple chess windows using dbus to make an app singleton - this means the history database can be synchronised
1.2+ Features
--------------

View file

@ -62,7 +62,7 @@
<accelerator key="L" modifiers="GDK_CONTROL_MASK" signal="activate"/>
<child internal-child="image">
<widget class="GtkImage" id="image60">
<widget class="GtkImage" id="image64">
<property name="visible">True</property>
<property name="stock">gtk-network</property>
<property name="icon_size">1</property>
@ -103,29 +103,6 @@
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="menu_end_game_item">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="label" translatable="yes">_End Game</property>
<property name="use_underline">True</property>
<signal name="activate" handler="_on_end_game_button_clicked" last_modification_time="Sun, 14 May 2006 14:07:07 GMT"/>
<accelerator key="W" modifiers="GDK_CONTROL_MASK" signal="activate"/>
<child internal-child="image">
<widget class="GtkImage" id="image61">
<property name="visible">True</property>
<property name="stock">gtk-close</property>
<property name="icon_size">1</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkSeparatorMenuItem" id="separatormenuitem1">
<property name="visible">True</property>
@ -163,7 +140,7 @@
<accelerator key="F11" modifiers="0" signal="activate"/>
<child internal-child="image">
<widget class="GtkImage" id="image62">
<widget class="GtkImage" id="image65">
<property name="visible">True</property>
<property name="stock">gtk-fullscreen</property>
<property name="icon_size">1</property>
@ -184,7 +161,7 @@
<accelerator key="F11" modifiers="0" signal="activate"/>
<child internal-child="image">
<widget class="GtkImage" id="image63">
<widget class="GtkImage" id="image66">
<property name="visible">True</property>
<property name="stock">gtk-leave-fullscreen</property>
<property name="icon_size">1</property>
@ -271,7 +248,7 @@
<property name="visible">True</property>
<property name="label" translatable="yes">_White Side</property>
<property name="use_underline">True</property>
<property name="active">True</property>
<property name="active">False</property>
<signal name="activate" handler="_on_board_view_changed" last_modification_time="Sun, 18 Mar 2007 02:42:01 GMT"/>
</widget>
</child>
@ -281,7 +258,7 @@
<property name="visible">True</property>
<property name="label" translatable="yes">_Black Side</property>
<property name="use_underline">True</property>
<property name="active">True</property>
<property name="active">False</property>
<property name="group">menu_side_white</property>
<signal name="activate" handler="_on_board_view_changed" last_modification_time="Sun, 18 Mar 2007 02:42:01 GMT"/>
</widget>
@ -303,7 +280,7 @@
<property name="visible">True</property>
<property name="label" translatable="yes">_Current Player</property>
<property name="use_underline">True</property>
<property name="active">True</property>
<property name="active">False</property>
<property name="group">menu_side_white</property>
<signal name="activate" handler="_on_board_view_changed" last_modification_time="Sun, 18 Mar 2007 02:42:01 GMT"/>
</widget>
@ -338,7 +315,7 @@
<property name="visible">True</property>
<property name="label" translatable="yes">_Long Algebraic</property>
<property name="use_underline">True</property>
<property name="active">True</property>
<property name="active">False</property>
<property name="group">menu_movef_human</property>
<signal name="activate" handler="_on_menu_movef_lan_activate" last_modification_time="Sat, 10 Feb 2007 03:06:49 GMT"/>
</widget>
@ -349,7 +326,7 @@
<property name="visible">True</property>
<property name="label" translatable="yes">_Standard Algebraic</property>
<property name="use_underline">True</property>
<property name="active">True</property>
<property name="active">False</property>
<property name="group">menu_movef_human</property>
<signal name="activate" handler="_on_menu_movef_san_activate" last_modification_time="Sat, 10 Feb 2007 03:06:49 GMT"/>
</widget>
@ -448,7 +425,7 @@
<signal name="activate" handler="_on_help_clicked" last_modification_time="Fri, 11 Aug 2006 12:43:28 GMT"/>
<child internal-child="image">
<widget class="GtkImage" id="image64">
<widget class="GtkImage" id="image67">
<property name="visible">True</property>
<property name="stock">gtk-help</property>
<property name="icon_size">1</property>
@ -596,34 +573,16 @@
</child>
<child>
<widget class="GtkToolButton" id="surrender_button">
<widget class="GtkToolButton" id="resign_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="label" translatable="yes">Surrender</property>
<property name="label" translatable="yes">Resign</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-dialog-warning</property>
<property name="visible_horizontal">True</property>
<property name="visible_vertical">True</property>
<property name="is_important">False</property>
<signal name="clicked" handler="_on_surrender_button_clicked" last_modification_time="Sun, 15 Jan 2006 18:21:18 GMT"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<widget class="GtkToolButton" id="end_game_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="tooltip" translatable="yes">End the current game</property>
<property name="label" translatable="yes">End Game</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-close</property>
<property name="visible_horizontal">True</property>
<property name="visible_vertical">True</property>
<property name="is_important">False</property>
<signal name="clicked" handler="_on_end_game_button_clicked" last_modification_time="Sun, 15 Jan 2006 18:21:10 GMT"/>
<signal name="clicked" handler="_on_resign_button_clicked" last_modification_time="Sun, 15 Jan 2006 18:21:18 GMT"/>
</widget>
<packing>
<property name="expand">False</property>

View file

@ -6,9 +6,12 @@ glchess_PYTHON = \
cecp.py \
config.py \
defaults.py \
display.py \
game.py \
glchess.py \
history.py \
__init__.py \
main.py \
network.py \
player.py \
uci.py

View file

@ -28,6 +28,44 @@ f3 Bc8 34. Kf2 Bf5 35. Ra7 g6 36. Ra6+ Kc5 37. Ke1 Nf4 38. g3 Nxh3 39. Kd2 Kb5
40. Rd6 Kc5 41. Ra6 Nf2 42. g4 Bd3 43. Re6 1/2-1/2
"""
RESULT_INCOMPLETE = '*'
RESULT_WHITE_WIN = '1-0'
RESULT_BLACK_WIN = '0-1'
RESULT_DRAW = '1/2-1/2'
"""The required tags in a PGN file (the seven tag roster, STR)"""
TAG_EVENT = 'Event'
TAG_SITE = 'Site'
TAG_DATE = 'Date'
TAG_ROUND = 'Round'
TAG_WHITE = 'White'
TAG_BLACK = 'Black'
TAG_RESULT = 'Result'
"""Optional tags"""
TAG_TIME = 'Time'
TAG_FEN = 'FEN'
TAG_WHITE_TYPE = 'WhiteType'
TAG_WHITE_ELO = 'WhiteElo'
TAG_BLACK_TYPE = 'BlackType'
TAG_BLACK_ELO = 'BlackElo'
TAG_TIME_CONTROL = 'TimeControl'
TAG_TERMINATION = 'Termination'
# Values for the WhiteType and BlackType tag
PLAYER_HUMAN = 'human'
PLAYER_AI = 'program'
# Values for the Termination tag
TERMINATE_ABANDONED = 'abandoned'
TERMINATE_ADJUDICATION = 'adjudication'
TERMINATE_DEATH = 'death'
TERMINATE_EMERGENCY = 'emergency'
TERMINATE_NORMAL = 'normal'
TERMINATE_RULES_INFRACTION = 'rules infraction'
TERMINATE_TIME_FORFEIT = 'time forfeit'
TERMINATE_UNTERMINATED = 'unterminated'
# Comments are bounded by ';' to '\n' or '{' to '}'
# Lines starting with '%' are ignored and are used as an extension mechanism
# Strings are bounded by '"' and '"' and quotes inside the strings are escaped with '\"'
@ -82,11 +120,6 @@ class PGNToken:
SYMBOL_CONTINUATION_CHARACTERS = SYMBOL_START_CHARACTERS + '_+#=:-' + '/' # Not in spec but required from game draw and imcomplete
NAG_CONTINUATION_CHARACTERS = '0123456789'
GAME_TERMINATE_INCOMPLETE = '*'
GAME_TERMINATE_WHITE_WIN = '1-0'
GAME_TERMINATE_BLACK_WIN = '0-1'
GAME_TERMINATE_DRAW = '1/2-1/2'
data = None
lineNumber = -1
@ -288,10 +321,10 @@ class PGNGameParser:
elif token.type is PGNToken.SYMBOL:
# See if this is a game terminate
if token.data == PGNToken.GAME_TERMINATE_INCOMPLETE or \
token.data == PGNToken.GAME_TERMINATE_WHITE_WIN or \
token.data == PGNToken.GAME_TERMINATE_BLACK_WIN or \
token.data == PGNToken.GAME_TERMINATE_DRAW:
if token.data == RESULT_INCOMPLETE or \
token.data == RESULT_WHITE_WIN or \
token.data == RESULT_BLACK_WIN or \
token.data == RESULT_DRAW:
game = self.__game
self.__game = None
@ -418,42 +451,9 @@ class PGNMove:
class PGNGame:
"""
"""
"""The required tags in a PGN file (the seven tag roster, STR)"""
PGN_TAG_EVENT = 'Event'
PGN_TAG_SITE = 'Site'
PGN_TAG_DATE = 'Date'
PGN_TAG_ROUND = 'Round'
PGN_TAG_WHITE = 'White'
PGN_TAG_BLACK = 'Black'
PGN_TAG_RESULT = 'Result'
"""Optional tags"""
PGN_TAG_TIME = 'Time'
PGN_TAG_FEN = 'FEN'
PGN_TAG_WHITE_TYPE = 'WhiteType'
PGN_TAG_WHITE_ELO = 'WhiteElo'
PGN_TAG_BLACK_TYPE = 'BlackType'
PGN_TAG_BLACK_ELO = 'BlackElo'
PGN_TAG_TIME_CONTROL = 'TimeControl'
PGN_TAG_TERMINATION = 'Termination'
# Values for the WhiteType and BlackType tag
PGN_TYPE_HUMAN = 'human'
PGN_TYPE_AI = 'program'
# Values for the Termination tag
PGN_TERMINATE_ABANDONED = 'abandoned'
PGN_TERMINATE_ADJUDICATION = 'adjudication'
PGN_TERMINATE_DEATH = 'death'
PGN_TERMINATE_EMERGENCY = 'emergency'
PGN_TERMINATE_NORMAL = 'normal'
PGN_TERMINATE_RULES_INFRACTION = 'rules infraction'
PGN_TERMINATE_TIME_FORFEIT = 'time forfeit'
PGN_TERMINATE_UNTERMINATED = 'unterminated'
# The seven tag roster in the required order (REFERENCE)
__strTags = [PGN_TAG_EVENT, PGN_TAG_SITE, PGN_TAG_DATE, PGN_TAG_ROUND, PGN_TAG_WHITE, PGN_TAG_BLACK, PGN_TAG_RESULT]
__strTags = [TAG_EVENT, TAG_SITE, TAG_DATE, TAG_ROUND, TAG_WHITE, TAG_BLACK, TAG_RESULT]
# The tags in this game
__tagsByName = None
@ -463,13 +463,13 @@ class PGNGame:
def __init__(self):
# Set the default STR tags
self.__tagsByName = {}
self.setTag(self.PGN_TAG_EVENT, '?')
self.setTag(self.PGN_TAG_SITE, '?')
self.setTag(self.PGN_TAG_DATE, '????.??.??')
self.setTag(self.PGN_TAG_ROUND, '?')
self.setTag(self.PGN_TAG_WHITE, '?')
self.setTag(self.PGN_TAG_BLACK, '?')
self.setTag(self.PGN_TAG_RESULT, '*')
self.setTag(TAG_EVENT, '?')
self.setTag(TAG_SITE, '?')
self.setTag(TAG_DATE, '????.??.??')
self.setTag(TAG_ROUND, '?')
self.setTag(TAG_WHITE, '?')
self.setTag(TAG_BLACK, '?')
self.setTag(TAG_RESULT, '*')
self.__moves = []
@ -501,7 +501,7 @@ class PGNGame:
tokens.append('{' + m.comment + '}')
# Add result token to the end
tokens.append(self.__tagsByName[self.PGN_TAG_RESULT])
tokens.append(self.__tagsByName[TAG_RESULT])
# Print moves keeping the line length to less than 256 characters (PGN requirement)
line = ''

View file

@ -2,19 +2,20 @@
import os, os.path
import errno
import gettext
import gettext
APP_DATA_DIR = os.path.join('@prefix@', 'share')
ICON_DIR = os.path.join(APP_DATA_DIR, 'pixmaps')
TEXTURE_DIR = os.path.join(ICON_DIR, 'glchess')
GLADE_DIR = os.path.join(APP_DATA_DIR, 'glchess')
BASE_DIR = os.path.join(APP_DATA_DIR, 'glchess')
LOCALEDIR = os.path.join(APP_DATA_DIR, 'locale')
DATA_DIR = os.path.expanduser('~/.gnome2/glchess/')
LOG_DIR = os.path.join(DATA_DIR, 'logs')
CONFIG_FILE = os.path.join(DATA_DIR, 'config.xml')
AUTOSAVE_FILE = os.path.join(DATA_DIR, 'autosave.pgn')
LOCAL_AI_CONFIG = os.path.join(DATA_DIR, 'ai.xml')
APP_DATA_DIR = os.path.join('@prefix@', 'share')
ICON_DIR = os.path.join(APP_DATA_DIR, 'pixmaps')
TEXTURE_DIR = os.path.join(ICON_DIR, 'glchess')
GLADE_DIR = os.path.join(APP_DATA_DIR, 'glchess')
BASE_DIR = os.path.join(APP_DATA_DIR, 'glchess')
LOCALEDIR = os.path.join(APP_DATA_DIR, 'locale')
DATA_DIR = os.path.expanduser('~/.gnome2/glchess/')
LOG_DIR = os.path.join(DATA_DIR, 'logs')
CONFIG_FILE = os.path.join(DATA_DIR, 'config.xml')
HISTORY_DIR = os.path.join(DATA_DIR, 'history')
UNFINISHED_FILE = os.path.join(HISTORY_DIR, 'unfinished')
LOCAL_AI_CONFIG = os.path.join(DATA_DIR, 'ai.xml')
DOMAIN = 'gnome-games'
gettext.bindtextdomain(DOMAIN, LOCALEDIR)

438
src/lib/display.py Normal file
View file

@ -0,0 +1,438 @@
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
__license__ = 'GNU General Public License Version 2'
__copyright__ = 'Copyright 2005-2006 Robert Ancell'
import config
import player
import scene
import scene.cairo
import scene.opengl
import scene.human
import ui
import chess.board
class CairoPiece(scene.ChessPieceFeedback):
"""
"""
model = None
location = ''
def __init__(self, scene, piece):
self.scene = scene
self.piece = piece
def onDeleted(self):
"""Called by scene.ChessPieceFeedback"""
self.scene.pieces.pop(self.piece)
def onMoved(self):
"""Called by scene.ChessPieceFeedback"""
# If waiting for this piece then end players turn
if self.scene.waitingPiece is self:
self.scene.game.view.pieceMoved()
class SceneCairo(scene.SceneFeedback, scene.human.SceneHumanInput):
"""
"""
controller = None
# The game this scene is rendering
game = None
# TODO
pieces = None
# FIXME: Abort when scenes changed
waitingPiece = None
def __init__(self, chessGame):
"""
"""
self.controller = scene.cairo.Scene(self)
self.game = chessGame
self.pieces = {}
scene.human.SceneHumanInput.__init__(self)
def getPieces(self):
return self.pieces.values()
def movePiece(self, piece, location, delete, animate):
"""
"""
# Get the model for this piece creating one if it doesn't exist
try:
p = self.pieces[piece]
except KeyError:
# Make the new model
pieceName = {chess.board.PAWN: 'pawn', chess.board.ROOK: 'rook', chess.board.KNIGHT: 'knight',
chess.board.BISHOP: 'bishop', chess.board.QUEEN: 'queen', chess.board.KING: 'king'}[piece.getType()]
chessSet = {chess.board.WHITE: 'white', chess.board.BLACK: 'black'}[piece.getColour()]
p = CairoPiece(self, piece)
p.model = self.controller.addChessPiece(chessSet, pieceName, location, p)
self.pieces[piece] = p
# Move the model
p.location = location
p.model.move(location, delete, animate)
return p
# Extended methods
def onRedraw(self):
"""Called by scene.cairo.Scene"""
if self.game.view.controller is not None:
self.game.view.controller.render()
def startAnimation(self):
"""Called by scene.cairo.Scene"""
self.game.application.ui.controller.startAnimation()
def getSquare(self, x, y):
"""Called by scene.human.SceneHumanInput"""
return self.controller.getSquare(x, y)
def setBoardHighlight(self, coords):
"""Called by scene.human.SceneHumanInput"""
self.controller.setBoardHighlight(coords)
def playerIsHuman(self):
"""Called by scene.human.SceneHumanInput"""
return self.game.currentPlayerIsHuman()
def squareIsFriendly(self, coord):
"""Called by scene.human.SceneHumanInput"""
return self.playerIsHuman() and self.game.squareIsFriendly(coord)
def canMove(self, start, end):
"""Called by scene.human.SceneHumanInput"""
return self.playerIsHuman() and self.game.getCurrentPlayer().canMove(start, end) # FIXME: Promotion type
def moveHuman(self, start, end):
"""Called by scene.human.SceneHumanInput"""
self.game.moveHuman(start, end)
class OpenGLPiece(scene.ChessPieceFeedback):
"""
"""
model = None
location = ''
def __init__(self, scene, piece):
self.scene = scene
self.piece = piece
def onDeleted(self):
"""Called by scene.ChessPieceFeedback"""
self.scene.pieces.pop(self.piece)
def onMoved(self):
"""Called by scene.ChessPieceFeedback"""
# If waiting for this piece then end players turn
if self.scene.waitingPiece is self:
self.scene.waitingPiece = None
self.scene.game.getCurrentPlayer().endMove()
class SceneOpenGL(scene.SceneFeedback, scene.human.SceneHumanInput):
"""
"""
# The game this scene is rendering
game = None
# TODO
pieces = None
# FIXME: Abort when scenes changed
waitingPiece = None
def __init__(self, chessGame):
"""Constructor for a glChess scene.
'chessGame' is the game the scene is rendering (game.ChessGame).
"""
self.game = chessGame
self.pieces = {}
# Call parent constructors
scene.human.SceneHumanInput.__init__(self)
self.controller = scene.opengl.Scene(self)
def getPieces(self):
return self.pieces.values()
def movePiece(self, piece, location, delete, animate):
"""
"""
# Get the model for this piece creating one if it doesn't exist
try:
p = self.pieces[piece]
except KeyError:
# Make the new model
pieceName = {chess.board.PAWN: 'pawn', chess.board.ROOK: 'rook', chess.board.KNIGHT: 'knight',
chess.board.BISHOP: 'bishop', chess.board.QUEEN: 'queen', chess.board.KING: 'king'}[piece.getType()]
chessSet = {chess.board.WHITE: 'white', chess.board.BLACK: 'black'}[piece.getColour()]
p = OpenGLPiece(self, piece)
p.model = self.controller.addChessPiece(chessSet, pieceName, location, p)
self.pieces[piece] = p
# Move the model
p.location = location
p.model.move(location, delete)
return p
# Extended methods
def onRedraw(self):
"""Called by scene.opengl.Scene"""
if self.game.view.controller is not None:
self.game.view.controller.render()
def startAnimation(self):
"""Called by scene.opengl.Scene"""
self.game.application.ui.controller.startAnimation()
def getSquare(self, x, y):
"""Called by scene.human.SceneHumanInput"""
return self.controller.getSquare(x, y)
def setBoardHighlight(self, coords):
"""Called by scene.human.SceneHumanInput"""
self.controller.setBoardHighlight(coords)
def playerIsHuman(self):
"""Called by scene.human.SceneHumanInput"""
return self.game.currentPlayerIsHuman()
def squareIsFriendly(self, coord):
"""Called by scene.human.SceneHumanInput"""
return self.playerIsHuman() and self.game.squareIsFriendly(coord)
def canMove(self, start, end):
"""Called by scene.human.SceneHumanInput"""
return self.playerIsHuman() and self.game.getCurrentPlayer().canMove(start, end) # FIXME: Promotion type
def moveHuman(self, start, end):
"""Called by scene.human.SceneHumanInput"""
self.game.moveHuman(start, end)
class Splashscreen(ui.ViewFeedback):
"""
"""
application = None
scene = None
def __init__(self, application):
"""Constructor.
'application' is ???
"""
self.application = application
self.cairoScene = scene.cairo.Scene(self)
self.scene = scene.opengl.Scene(self)
def updateRotation(self, animate = True):
boardView = config.get('board_view')
if boardView == 'black':
rotation = 180.0
else:
rotation = 0.0
self.cairoScene.controller.setBoardRotation(rotation, animate)
self.scene.controller.setBoardRotation(rotation, animate)
def onRedraw(self):
"""Called by scene.SceneFeedback"""
self.controller.render()
def showBoardNumbering(self, showNumbering):
"""Called by ui.ViewFeedback"""
self.scene.showBoardNumbering(showNumbering)
self.cairoScene.showBoardNumbering(showNumbering)
def renderGL(self):
"""Called by ui.ViewFeedback"""
self.scene.render()
def renderCairoStatic(self, context):
"""Called by ui.ViewFeedback"""
return self.cairoScene.renderStatic(context)
def renderCairoDynamic(self, context):
"""Called by ui.ViewFeedback"""
self.cairoScene.renderDynamic(context)
def reshape(self, width, height):
"""Called by ui.View"""
self.scene.reshape(width, height)
self.cairoScene.reshape(width, height)
class View(ui.ViewFeedback):
"""
"""
# The game this view is rendering
game = None
# The scene for this view
scene = None
# The controller object for this view
controller = None
moveNumber = -1
def __init__(self, game):
"""Constructor.
'game' is ???
"""
self.game = game
# Use a Cairo scene by default - it will be replaced by an OpenGL one if that is the requested view
# I wanted to remove this but then scene is None and there are a number of issues...
# This should be cleaned up
self.scene = SceneCairo(game)
config.watch('board_view', self.__onBoardViewChanged)
def __onBoardViewChanged(self, key, value):
self.updateRotation()
def updateRotation(self, animate = True):
"""
"""
# Get the angle to face
p = self.game.getCurrentPlayer()
if p is self.game.getWhite():
rotation = 0.0
elif p is self.game.getBlack():
rotation = 180.0
# Decide if we should face this angle
boardView = config.get('board_view')
if boardView == 'white':
rotation = 0.0
elif boardView == 'black':
rotation = 180.0
elif boardView == 'human':
if not isinstance(p, player.HumanPlayer):
return
self.scene.controller.setBoardRotation(rotation, animate)
def pieceMoved(self):
"""
"""
if self.scene.waitingPiece is None:
return
self.scene.waitingPiece = None
self.game.getCurrentPlayer().endMove()
def showMoveHints(self, showHints):
"""Called by ui.ViewFeedback"""
self.scene.showMoveHints(showHints)
def showBoardNumbering(self, showNumbering):
"""Called by ui.ViewFeedback"""
self.scene.controller.showBoardNumbering(showNumbering)
def updateScene(self, sceneClass):
"""
"""
if isinstance(self.scene, sceneClass):
return
self.pieceMoved()
self.scene = sceneClass(self.game)
self.reshape(self.width, self.height)
self.setMoveNumber(self.moveNumber)
self.updateRotation(animate = False)
def renderGL(self):
"""Called by ui.ViewFeedback"""
self.updateScene(SceneOpenGL)
self.scene.controller.render()
def renderCairoStatic(self, context):
"""Called by ui.ViewFeedback"""
self.updateScene(SceneCairo)
return self.scene.controller.renderStatic(context)
def renderCairoDynamic(self, context):
"""Called by ui.ViewFeedback"""
self.updateScene(SceneCairo)
self.scene.controller.renderDynamic(context)
def reshape(self, width, height):
"""Called by ui.ViewFeedback"""
self.width = width
self.height = height
self.scene.controller.reshape(width, height)
def select(self, x, y):
"""Called by ui.ViewFeedback"""
self.scene.select(x, y)
def deselect(self, x, y):
"""Called by ui.ViewFeedback"""
self.scene.deselect(x, y)
def setMoveNumber(self, moveNumber):
"""Called by ui.ViewFeedback"""
self.moveNumber = moveNumber
# Lock the scene if not tracking the game
self.scene.enableHumanInput(moveNumber == -1)
# Get the state of this scene
piecesByLocation = self.game.getAlivePieces(moveNumber)
# Remove any models not present
requiredPieces = piecesByLocation.values()
for piece in self.scene.getPieces():
try:
requiredPieces.index(piece.piece)
except ValueError:
piece.model.move(piece.location, True)
# Move the models in the scene
for (location, piece) in piecesByLocation.iteritems():
self.scene.movePiece(piece, location, False, True)
# Can't wait for animation if not looking at the latest move
if moveNumber != -1:
self.pieceMoved()
def save(self, fileName = None):
"""Called by ui.ViewFeedback"""
if fileName is None:
fileName = self.game.fileName
assert(fileName is not None)
try:
f = file(fileName, 'w')
except IOError, e:
return e.args[1]
self.game.application.logger.addLine('Saving game %s to %s' % (repr(self.game.name), fileName))
pgnGame = chess.pgn.PGNGame()
self.game.toPGN(pgnGame)
lines = pgnGame.getLines()
for line in lines:
f.write(line + '\n')
f.write('\n')
f.close()
self.game.fileName = fileName
self.game.needsSaving = False
def getFileName(self):
"""Called by ui.ViewFeedback"""
return self.game.fileName
def needsSaving(self):
"""Called by ui.ViewFeedback"""
return self.game.needsSaving and (self.game.fileName is not None)

View file

@ -54,7 +54,7 @@ class GtkViewArea(gtk.DrawingArea):
# Make openGL drawable
if hasattr(gtk, 'gtkgl'):
gtk.gtkgl.widget_set_gl_capability(self, self.view.ui.notebook.glConfig)# FIXME:, share_list=glContext)
gtk.gtkgl.widget_set_gl_capability(self, self.view.ui.glConfig)# FIXME:, share_list=glContext)
# Connect signals
self.connect('realize', self.__init)
@ -369,13 +369,13 @@ class GtkView(glchess.ui.ViewController):
def setWhiteTime(self, total, current):
"""Extends glchess.ui.ViewController"""
self.whiteTime = (total, current)
if self.ui.notebook.getView() is self:
if self.ui.view is self:
self.ui.setTimers(self.whiteTime, self.blackTime)
def setBlackTime(self, total, current):
"""Extends glchess.ui.ViewController"""
self.blackTime = (total, current)
if self.ui.notebook.getView() is self:
if self.ui.view is self:
self.ui.setTimers(self.whiteTime, self.blackTime)
def setAttention(self, requiresAttention):

View file

@ -51,109 +51,6 @@ gtk.window_set_default_icon_name(ICON_NAME)
def loadGladeFile(name, root = None):
return gtk.glade.XML(os.path.join(GLADE_DIR, name), root, domain = DOMAIN)
class GtkGameNotebook(gtk.Notebook):
"""
"""
glConfig = None
defaultView = None
views = None
viewsByWidget = None
def __init__(self, ui):
"""
"""
self.ui = ui
self.views = []
self.viewsByWidget = {}
gtk.Notebook.__init__(self)
self.set_show_border(False)
# Make the tabs scrollable so the area is not resized
self.set_scrollable(True)
# Configure openGL
try:
gtk.gdkgl
except AttributeError:
self.glConfig = None
else:
display_mode = (gtk.gdkgl.MODE_RGB | gtk.gdkgl.MODE_DEPTH | gtk.gdkgl.MODE_DOUBLE)
try:
self.glConfig = gtk.gdkgl.Config(mode = display_mode)
except gtk.gdkgl.NoMatches:
display_mode &= ~gtk.gdkgl.MODE_DOUBLE
self.glConfig = gtk.gdkgl.Config(mode = display_mode)
self.set_show_tabs(False)
def setDefault(self, feedback):
"""
"""
assert(self.defaultView is None)
self.defaultView = chessview.GtkView(self.ui, '', feedback)
page = self.append_page(self.defaultView.widget)
self.set_current_page(page)
self.__updateTabVisibleState()
return self.defaultView
def addView(self, title, feedback):
"""
"""
moveFormat = glchess.config.get('move_format')
showComments = glchess.config.get('show_comments')
view = chessview.GtkView(self.ui, title, feedback, moveFormat = moveFormat, showComments = showComments)
self.views.append(view)
self.viewsByWidget[view.widget] = view
page = self.append_page(view.widget)
self.set_tab_label_text(view.widget, title)
self.set_current_page(page)
self.__updateTabVisibleState()
return view
def getView(self):
"""Get the currently selected view.
Returns the view (GtkView) on this page or None if no view here.
"""
# If splashscreen present then there is no view
if len(self.viewsByWidget) == 0:
return None
num = self.get_current_page()
if num < 0:
return None
widget = self.get_nth_page(num)
return self.viewsByWidget[widget]
def removeView(self, view):
"""Remove a view from the notebook.
'view' is the view to remove.
"""
self.remove_page(self.page_num(view.widget))
self.views.remove(view)
self.viewsByWidget.pop(view.widget)
self.__updateTabVisibleState()
def __updateTabVisibleState(self):
"""
"""
# Only show tabs if there is more than one game
self.set_show_tabs(len(self.viewsByWidget) > 1)
# Show/hide the default view
if len(self.viewsByWidget) == 0:
self.defaultView.widget.show()
else:
self.defaultView.widget.hide()
class GLibTimer(glchess.ui.Timer):
"""
"""
@ -271,9 +168,6 @@ class GtkUI(glchess.ui.UI):
__lastTime = None
__animationTimer = None
# The notebook containing games
notebook = None
# The Gtk+ list model of the available player types
__playerModel = None
@ -303,6 +197,8 @@ class GtkUI(glchess.ui.UI):
height = None
isFullscreen = False
isMaximised = False
view = None
def __init__(self, feedback):
"""Constructor for a GTK+ glChess GUI"""
@ -314,6 +210,8 @@ class GtkUI(glchess.ui.UI):
self.__networkGames = {}
self.__saveGameDialogs = {}
self.__joinGameDialogs = []
self._initOpenGL()
# Set the message panel to the tooltip style
# (copied from Gedit)
@ -346,12 +244,6 @@ class GtkUI(glchess.ui.UI):
self.__boardViewTypeByRadio[widget] = name
self.__boardViewRadioByType[name] = widget
# Make a notebook for the games
self.notebook = GtkGameNotebook(self)
self.notebook.connect_after('switch-page', self._on_view_changed)
self.__getWidget('game_viewport').add(self.notebook)
self.notebook.show_all()
# Create the model for the player types
self.__playerModel = gtk.ListStore(str, str, str)
iconTheme = gtk.icon_theme_get_default()
@ -371,14 +263,10 @@ class GtkUI(glchess.ui.UI):
combo.pack_start(cell, False)
combo.add_attribute(cell, 'text', 2)
self.defaultViewController = self.notebook.setDefault(None)
# Disble help support
if haveGnomeSupport:
self._gui.get_widget('menu_help').show()
self.defaultViewController.viewWidget.setRenderGL(self.__renderGL)
self.saveDialog = dialogs.SaveDialog(self)
# Watch for config changes
@ -389,6 +277,20 @@ class GtkUI(glchess.ui.UI):
'move_format', 'promotion_type', 'board_view',
'enable_networking']:
glchess.config.watch(key, self.__applyConfig)
def _initOpenGL(self):
# Configure openGL
try:
gtk.gdkgl
except AttributeError:
self.glConfig = None
else:
display_mode = (gtk.gdkgl.MODE_RGB | gtk.gdkgl.MODE_DEPTH | gtk.gdkgl.MODE_DOUBLE)
try:
self.glConfig = gtk.gdkgl.Config(mode = display_mode)
except gtk.gdkgl.NoMatches:
display_mode &= ~gtk.gdkgl.MODE_DOUBLE
self.glConfig = gtk.gdkgl.Config(mode = display_mode)
# Public methods
@ -452,17 +354,19 @@ class GtkUI(glchess.ui.UI):
# Get the human to play against this AI
if self.__defaultBlackAI is None:
self.__defaultBlackAI = name
def setDefaultView(self, feedback):
def setView(self, title, feedback):
"""Extends ui.UI"""
self.defaultViewController.feedback = feedback
return self.defaultViewController
def addView(self, title, feedback):
"""Extends ui.UI"""
view = self.notebook.addView(title, feedback)
view.viewWidget.setRenderGL(self.__renderGL)
return view
moveFormat = glchess.config.get('move_format')
showComments = glchess.config.get('show_comments')
self.view = chessview.GtkView(self, title, feedback, moveFormat = moveFormat, showComments = showComments)
self.view.viewWidget.setRenderGL(self.__renderGL)
viewport = self.__getWidget('game_viewport')
child = viewport.get_child()
if child is not None:
viewport.remove(child)
viewport.add(self.view.widget)
return self.view
def addLogWindow(self, title, executable, description):
"""
@ -590,14 +494,6 @@ class GtkUI(glchess.ui.UI):
return error
self.__saveGameDialogs.pop(view)
def _removeView(self, view):
"""Remove a view from the UI.
'view' is the view to remove.
"""
self.notebook.removeView(view)
self._updateViewButtons()
# Private methods
def __resize(self):
@ -673,33 +569,26 @@ class GtkUI(glchess.ui.UI):
self.__renderGL = value
menuItem = self.__getWidget('menu_view_3d')
menuItem.set_active(self.__renderGL)
self.notebook.defaultView.viewWidget.setRenderGL(self.__renderGL)
for view in self.notebook.views:
view.viewWidget.setRenderGL(self.__renderGL)
self.view.viewWidget.setRenderGL(self.__renderGL)
elif name == 'show_comments':
menuItem = self.__getWidget('menu_view_comment')
menuItem.set_active(value)
for view in self.notebook.views:
view.setShowComments(value)
self.view.setShowComments(value)
elif name == 'show_move_hints':
menuItem = self.__getWidget('menu_view_move_hints')
menuItem.set_active(value)
for view in self.notebook.views:
view.feedback.showMoveHints(value)
self.view.feedback.showMoveHints(value)
elif name == 'show_numbering':
menuItem = self.__getWidget('menu_view_numbering')
menuItem.set_active(value)
for view in self.notebook.views:
view.feedback.showBoardNumbering(value)
self.notebook.defaultView.feedback.showBoardNumbering(value)
self.view.feedback.showBoardNumbering(value)
elif name == 'move_format':
self._gui.get_widget('menu_movef_%s' % value).set_active(True)
for view in self.notebook.views:
view.setMoveFormat(value)
self.view.setMoveFormat(value)
elif name == 'promotion_type':
try:
@ -852,9 +741,7 @@ class GtkUI(glchess.ui.UI):
string += ' (latest)'
moveNumber = -1
view = self.notebook.getView()
if view is not None:
view._setMoveNumber(moveNumber)
self.view._setMoveNumber(moveNumber)
def _on_menu_movef_human_activate(self, widget):
"""Gtk+ callback"""
@ -928,36 +815,32 @@ class GtkUI(glchess.ui.UI):
def _on_view_changed(self, widget, page, pageNum):
"""Gtk+ callback"""
# Set the window title to the name of the game
view = self.notebook.getView()
title = _('Chess')
if view is not None:
title += " - %s" % view.title
if self.view is not None:
title += " - %s" % self.view.title
self._gui.get_widget('glchess_app').set_title(title)
# Set toolbar/menu buttons to state for this game
self._updateViewButtons()
# Update timers
if view is not None:
self.setTimers(view.whiteTime, view.blackTime)
if self.view is not None:
self.setTimers(self.view.whiteTime, self.view.blackTime)
def _updateViewButtons(self):
"""
"""
view = self.notebook.getView()
enableWidgets = (view is not None) and view.isActive
self.__getWidget('end_game_button').set_sensitive(enableWidgets)
enableWidgets = self.view is not None and self.view.isActive
self.__getWidget('save_game_button').set_sensitive(enableWidgets)
self.__getWidget('menu_save_item').set_sensitive(enableWidgets)
self.__getWidget('menu_save_as_item').set_sensitive(enableWidgets)
self.__getWidget('menu_end_game_item').set_sensitive(enableWidgets)
combo = self.__getWidget('history_combo')
if view is None:
if self.view is None:
if combo.get_model() != None:
combo.set_model(None)
else:
(model, selected) = view._getModel()
(model, selected) = self.view._getModel()
combo.set_model(model)
if selected < 0:
selected = len(model) + selected
@ -978,25 +861,15 @@ class GtkUI(glchess.ui.UI):
def _on_save_game_button_clicked(self, widget):
"""Gtk+ callback"""
view = self.notebook.getView()
if view.feedback.getFileName() is not None:
view.feedback.save()
elif not self.__saveGameDialogs.has_key(view):
self.__saveGameDialogs[view] = dialogs.GtkSaveGameDialog(self, view)
if self.view.feedback.getFileName() is not None:
self.view.feedback.save()
elif not self.__saveGameDialogs.has_key(self.view):
self.__saveGameDialogs[self.view] = dialogs.GtkSaveGameDialog(self, self.view)
def _on_save_as_game_button_clicked(self, widget):
"""Gtk+ callback"""
view = self.notebook.getView()
if not self.__saveGameDialogs.has_key(view):
self.__saveGameDialogs[view] = dialogs.GtkSaveGameDialog(self, view, view.feedback.getFileName())
def _on_end_game_button_clicked(self, widget):
"""Gtk+ callback"""
view = self.notebook.getView()
assert(view is not None)
if view.feedback is not None:
view.feedback.close()
if not self.__saveGameDialogs.has_key(self.view):
self.__saveGameDialogs[self.view] = dialogs.GtkSaveGameDialog(self, self.view, self.view.feedback.getFileName())
def _on_help_clicked(self, widget):
"""Gtk+ callback"""
@ -1113,9 +986,8 @@ class GtkUI(glchess.ui.UI):
def _quit(self):
# Check if any views need saving
viewsToSave = []
for view in self.notebook.views:
if view.feedback.needsSaving():
viewsToSave.append(view)
if self.view.feedback.needsSaving():
viewsToSave.append(self.view)
if len(viewsToSave) == 0:
self.feedback.onQuit()

118
src/lib/history.py Normal file
View file

@ -0,0 +1,118 @@
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
__license__ = 'GNU General Public License Version 2'
__copyright__ = 'Copyright 2005-2006 Robert Ancell'
import os
import errno
import chess.pgn
from defaults import *
class GameHistory:
def getUnfinishedGame(self):
g = None
try:
f = file(UNFINISHED_FILE, 'r')
lines = f.readlines()
f.close()
lines.reverse()
index = 0
for line in lines:
fname = line.strip()
try:
p = chess.pgn.PGN(fname, 1)
except chess.pgn.Error, e:
print e.description
continue
except IOError, e:
print e.strerror
continue
else:
g = p[0]
break
index += 1
except IOError, e:
if e.errno != errno.ENOENT:
print 'Failed to read unfinished list'
return None
lines = []
else:
lines = lines[index:]
# Write the list back
try:
f = file(UNFINISHED_FILE, 'w')
lines.reverse()
f.writelines(lines)
f.close()
except IOError:
print 'Failed to read unfinished list'
return g
def load(self, date):
return
def _getFilename(self, game):
date = game.getTag(chess.pgn.TAG_DATE)
try:
(year, month, day) = date.split('.')
except ValueError:
directory = HISTORY_DIR
else:
directory = os.path.join(HISTORY_DIR, year, month, day)
# Create the directory
try:
os.makedirs(directory)
except OSError, e:
if e.errno != errno.EEXIST:
return None # FIXME
# Get a unique name for the file
count = 0
fname = os.path.join(directory, date)
while os.path.exists(fname):
count += 1
fname = os.path.join(directory, '%s-%d' % (date, count))
return fname
def save(self, g):
"""Save a game in the history.
'g' is the game to save
"""
fname = self._getFilename(g)
lines = g.getLines()
try:
f = file(fname, 'w')
for line in lines:
f.write(line + '\n')
f.write('\n')
f.close()
except IOError, e:
# FIXME: This should be in a dialog
self.logger.addLine('Unable to autosave to %s: %s' % (fname, str(e)))
# Update unfinished list
result = g.getTag(chess.pgn.TAG_RESULT)
try:
f = file(UNFINISHED_FILE, 'r')
lines = f.readlines()
f.close()
f = file(UNFINISHED_FILE, 'w')
for line in lines:
l = line.strip()
if l == fname and result != chess.pgn.RESULT_INCOMPLETE:
continue
f.write(l + '\n')
if result == chess.pgn.RESULT_INCOMPLETE:
f.write(fname + '\n')
f.close()
except IOError:
print 'Failed to update unfinished list'

View file

@ -12,542 +12,23 @@ import os
import errno
from gettext import gettext as _
import traceback
import time
import config
import ui
import gtkui
import scene.cairo
import scene.opengl
import scene.human
import game
import player
import chess.board
import chess.lan
import chess.pgn
import ai
import network
import display
import history
from defaults import *
import chess.pgn
class MovePlayer(game.ChessPlayer):
"""This class provides a pseudo-player to watch for piece movements"""
# The game to control
__game = None
waitForMoves = False
def __init__(self, chessGame):
"""Constructor for a move player.
'chessGame' is the game to make changes to (ChessGame).
"""
self.__game = chessGame
game.ChessPlayer.__init__(self, '@move')
# Extended methods
def onPieceMoved(self, piece, start, end, delete):
"""Called by chess.board.ChessPlayer"""
if self.__game.view.moveNumber != -1:
return
p = self.__game.view.scene.movePiece(piece, end, delete, self.__game.isStarted())
# If a player move notify when animation completes
if self.__game.isStarted() and self.__game.view.moveNumber == -1 and start is not None and start != end:
self.__game.view.scene.waitingPiece = p
def onPlayerMoved(self, player, move):
"""Called by chess.board.ChessPlayer"""
self.__game.needsSaving = True
# Update clocks
if player is self.__game.getWhite():
if self.__game.wT is not None:
self.__game.wT.controller.pause()
if self.__game.bT is not None:
self.__game.bT.controller.run()
else:
if self.__game.bT is not None:
self.__game.bT.controller.pause()
if self.__game.wT is not None:
self.__game.wT.controller.run()
# Complete move if not waiting for visual indication of move end
if self.__game.view.moveNumber != -1:
player.endMove()
self.__game.view.controller.addMove(move)
def onGameEnded(self, game):
"""Called by chess.board.ChessPlayer"""
self.__game.view.controller.endGame(game)
class HumanPlayer(game.ChessPlayer):
"""
"""
__game = None
def __init__(self, chessGame, name):
"""Constructor.
'chessGame' is the game this player is in (game.ChessGame).
'name' is the name of this player (string).
"""
game.ChessPlayer.__init__(self, name)
self.__game = chessGame
def readyToMove(self):
# FIXME: ???
self.__game.view.controller.setAttention(True)
class AIPlayer(ai.Player):
"""
"""
def __init__(self, application, name, profile, level, description):
"""
"""
executable = profile.path
for arg in profile.arguments[1:]:
executable += ' ' + arg
self.window = application.ui.controller.addLogWindow(profile.name, executable, description)
ai.Player.__init__(self, name, profile, level)
def addText(self, text, style):
"""Called by ai.Player"""
self.window.addText(text, style)
class CairoPiece(scene.ChessPieceFeedback):
"""
"""
model = None
location = ''
def __init__(self, scene, piece):
self.scene = scene
self.piece = piece
def onDeleted(self):
"""Called by scene.ChessPieceFeedback"""
self.scene.pieces.pop(self.piece)
def onMoved(self):
"""Called by scene.ChessPieceFeedback"""
# If waiting for this piece then end players turn
if self.scene.waitingPiece is self:
self.scene.game.view.pieceMoved()
class SceneCairo(scene.SceneFeedback, scene.human.SceneHumanInput):
"""
"""
controller = None
# The game this scene is rendering
game = None
# TODO
moveNumber = None
pieces = None
# FIXME: Abort when scenes changed
waitingPiece = None
def __init__(self, chessGame):
"""
"""
self.controller = scene.cairo.Scene(self)
self.game = chessGame
self.pieces = {}
scene.human.SceneHumanInput.__init__(self)
def getPieces(self):
return self.pieces.values()
def movePiece(self, piece, location, delete, animate):
"""
"""
# Get the model for this piece creating one if it doesn't exist
try:
p = self.pieces[piece]
except KeyError:
# Make the new model
pieceName = {chess.board.PAWN: 'pawn', chess.board.ROOK: 'rook', chess.board.KNIGHT: 'knight',
chess.board.BISHOP: 'bishop', chess.board.QUEEN: 'queen', chess.board.KING: 'king'}[piece.getType()]
chessSet = {chess.board.WHITE: 'white', chess.board.BLACK: 'black'}[piece.getColour()]
p = CairoPiece(self, piece)
p.model = self.controller.addChessPiece(chessSet, pieceName, location, p)
self.pieces[piece] = p
# Move the model
p.location = location
p.model.move(location, delete, animate)
return p
# Extended methods
def onRedraw(self):
"""Called by scene.cairo.Scene"""
if self.game.view.controller is not None:
self.game.view.controller.render()
def startAnimation(self):
"""Called by scene.cairo.Scene"""
self.game.application.ui.controller.startAnimation()
def getSquare(self, x, y):
"""Called by scene.human.SceneHumanInput"""
return self.controller.getSquare(x, y)
def setBoardHighlight(self, coords):
"""Called by scene.human.SceneHumanInput"""
self.controller.setBoardHighlight(coords)
def playerIsHuman(self):
"""Called by scene.human.SceneHumanInput"""
return self.game.currentPlayerIsHuman()
def squareIsFriendly(self, coord):
"""Called by scene.human.SceneHumanInput"""
return self.playerIsHuman() and self.game.squareIsFriendly(coord)
def canMove(self, start, end):
"""Called by scene.human.SceneHumanInput"""
return self.playerIsHuman() and self.game.getCurrentPlayer().canMove(start, end) # FIXME: Promotion type
def moveHuman(self, start, end):
"""Called by scene.human.SceneHumanInput"""
self.game.moveHuman(start, end)
class OpenGLPiece(scene.ChessPieceFeedback):
"""
"""
model = None
location = ''
def __init__(self, scene, piece):
self.scene = scene
self.piece = piece
def onDeleted(self):
"""Called by scene.ChessPieceFeedback"""
self.scene.pieces.pop(self.piece)
def onMoved(self):
"""Called by scene.ChessPieceFeedback"""
# If waiting for this piece then end players turn
if self.scene.waitingPiece is self:
self.scene.waitingPiece = None
self.scene.game.getCurrentPlayer().endMove()
class SceneOpenGL(scene.SceneFeedback, scene.human.SceneHumanInput):
"""
"""
# The game this scene is rendering
game = None
# TODO
pieces = None
# FIXME: Abort when scenes changed
waitingPiece = None
def __init__(self, chessGame):
"""Constructor for a glChess scene.
'chessGame' is the game the scene is rendering (game.ChessGame).
"""
self.game = chessGame
self.pieces = {}
# Call parent constructors
scene.human.SceneHumanInput.__init__(self)
self.controller = scene.opengl.Scene(self)
def getPieces(self):
return self.pieces.values()
def movePiece(self, piece, location, delete, animate):
"""
"""
# Get the model for this piece creating one if it doesn't exist
try:
p = self.pieces[piece]
except KeyError:
# Make the new model
pieceName = {chess.board.PAWN: 'pawn', chess.board.ROOK: 'rook', chess.board.KNIGHT: 'knight',
chess.board.BISHOP: 'bishop', chess.board.QUEEN: 'queen', chess.board.KING: 'king'}[piece.getType()]
chessSet = {chess.board.WHITE: 'white', chess.board.BLACK: 'black'}[piece.getColour()]
p = OpenGLPiece(self, piece)
p.model = self.controller.addChessPiece(chessSet, pieceName, location, p)
self.pieces[piece] = p
# Move the model
p.location = location
p.model.move(location, delete)
return p
# Extended methods
def onRedraw(self):
"""Called by scene.opengl.Scene"""
if self.game.view.controller is not None:
self.game.view.controller.render()
def startAnimation(self):
"""Called by scene.opengl.Scene"""
self.game.application.ui.controller.startAnimation()
def getSquare(self, x, y):
"""Called by scene.human.SceneHumanInput"""
return self.controller.getSquare(x, y)
def setBoardHighlight(self, coords):
"""Called by scene.human.SceneHumanInput"""
self.controller.setBoardHighlight(coords)
def playerIsHuman(self):
"""Called by scene.human.SceneHumanInput"""
return self.game.currentPlayerIsHuman()
def squareIsFriendly(self, coord):
"""Called by scene.human.SceneHumanInput"""
return self.playerIsHuman() and self.game.squareIsFriendly(coord)
def canMove(self, start, end):
"""Called by scene.human.SceneHumanInput"""
return self.playerIsHuman() and self.game.getCurrentPlayer().canMove(start, end) # FIXME: Promotion type
def moveHuman(self, start, end):
"""Called by scene.human.SceneHumanInput"""
self.game.moveHuman(start, end)
class Splashscreen(ui.ViewFeedback):
"""
"""
application = None
scene = None
def __init__(self, application):
"""Constructor.
'application' is ???
"""
self.application = application
self.cairoScene = scene.cairo.Scene(self)
self.scene = scene.opengl.Scene(self)
def updateRotation(self, animate = True):
boardView = config.get('board_view')
if boardView == 'black':
rotation = 180.0
else:
rotation = 0.0
self.cairoScene.controller.setBoardRotation(rotation, animate)
self.scene.controller.setBoardRotation(rotation, animate)
def onRedraw(self):
"""Called by scene.SceneFeedback"""
self.controller.render()
def showBoardNumbering(self, showNumbering):
"""Called by ui.ViewFeedback"""
self.scene.showBoardNumbering(showNumbering)
self.cairoScene.showBoardNumbering(showNumbering)
def renderGL(self):
"""Called by ui.ViewFeedback"""
self.scene.render()
def renderCairoStatic(self, context):
"""Called by ui.ViewFeedback"""
return self.cairoScene.renderStatic(context)
def renderCairoDynamic(self, context):
"""Called by ui.ViewFeedback"""
self.cairoScene.renderDynamic(context)
def reshape(self, width, height):
"""Called by ui.View"""
self.scene.reshape(width, height)
self.cairoScene.reshape(width, height)
class View(ui.ViewFeedback):
"""
"""
# The game this view is rendering
game = None
# The scene for this view
scene = None
# The controller object for this view
controller = None
moveNumber = None
def __init__(self, game):
"""Constructor.
'game' is ???
"""
self.game = game
# Use a Cairo scene by default - it will be replaced by an OpenGL one if that is the requested view
# I wanted to remove this but then scene is None and there are a number of issues...
# This should be cleaned up
self.scene = SceneCairo(game)
config.watch('board_view', self.__onBoardViewChanged)
def __onBoardViewChanged(self, key, value):
self.updateRotation()
def updateRotation(self, animate = True):
"""
"""
# Get the angle to face
player = self.game.getCurrentPlayer()
if player is self.game.getWhite():
rotation = 0.0
elif player is self.game.getBlack():
rotation = 180.0
# Decide if we should face this angle
boardView = config.get('board_view')
if boardView == 'white':
rotation = 0.0
elif boardView == 'black':
rotation = 180.0
elif boardView == 'human':
if not isinstance(player, HumanPlayer):
return
self.scene.controller.setBoardRotation(rotation, animate)
def pieceMoved(self):
"""
"""
if self.scene.waitingPiece is None:
return
self.scene.waitingPiece = None
self.game.getCurrentPlayer().endMove()
def showMoveHints(self, showHints):
"""Called by ui.ViewFeedback"""
self.scene.showMoveHints(showHints)
def showBoardNumbering(self, showNumbering):
"""Called by ui.ViewFeedback"""
self.scene.controller.showBoardNumbering(showNumbering)
def updateScene(self, sceneClass):
"""
"""
if isinstance(self.scene, sceneClass):
return
self.pieceMoved()
self.scene = sceneClass(self.game)
self.reshape(self.width, self.height)
self.setMoveNumber(self.moveNumber)
self.updateRotation(animate = False)
def renderGL(self):
"""Called by ui.ViewFeedback"""
self.updateScene(SceneOpenGL)
self.scene.controller.render()
def renderCairoStatic(self, context):
"""Called by ui.ViewFeedback"""
self.updateScene(SceneCairo)
return self.scene.controller.renderStatic(context)
def renderCairoDynamic(self, context):
"""Called by ui.ViewFeedback"""
self.updateScene(SceneCairo)
self.scene.controller.renderDynamic(context)
def reshape(self, width, height):
"""Called by ui.ViewFeedback"""
self.width = width
self.height = height
self.scene.controller.reshape(width, height)
def select(self, x, y):
"""Called by ui.ViewFeedback"""
self.scene.select(x, y)
def deselect(self, x, y):
"""Called by ui.ViewFeedback"""
self.scene.deselect(x, y)
def setMoveNumber(self, moveNumber):
"""Called by ui.ViewFeedback"""
self.moveNumber = moveNumber
# Lock the scene if not tracking the game
self.scene.enableHumanInput(moveNumber == -1)
# Get the state of this scene
piecesByLocation = self.game.getAlivePieces(moveNumber)
# Remove any models not present
requiredPieces = piecesByLocation.values()
for piece in self.scene.getPieces():
try:
requiredPieces.index(piece.piece)
except ValueError:
piece.model.move(piece.location, True)
# Move the models in the scene
for (location, piece) in piecesByLocation.iteritems():
self.scene.movePiece(piece, location, False, True)
# Can't wait for animation if not looking at the latest move
if moveNumber != -1:
self.pieceMoved()
def save(self, fileName = None):
"""Called by ui.ViewFeedback"""
if fileName is None:
fileName = self.game.fileName
assert(fileName is not None)
try:
f = file(fileName, 'w')
except IOError, e:
return e.args[1]
self.game.application.logger.addLine('Saving game %s to %s' % (repr(self.game.name), fileName))
pgnGame = chess.pgn.PGNGame()
self.game.toPGN(pgnGame)
lines = pgnGame.getLines()
for line in lines:
f.write(line + '\n')
f.write('\n')
f.close()
self.game.fileName = fileName
self.game.needsSaving = False
def getFileName(self):
"""Called by ui.ViewFeedback"""
return self.game.fileName
def needsSaving(self):
"""Called by ui.ViewFeedback"""
return self.game.needsSaving and (self.game.fileName is not None)
def close(self):
"""Called by ui.ViewFeedback"""
# The user requests the game to end, for now we just do it
self.game.remove()
class PlayerTimer(ui.TimerFeedback):
"""
"""
@ -591,6 +72,9 @@ class ChessGame(game.ChessGame):
# The file this is saved to
fileName = None
needsSaving = True
# Date this game started
date = '????.??.??'
# Mapping between piece names and promotion types
__promotionMapping = {'queen': chess.board.QUEEN, 'knight': chess.board.KNIGHT, 'bishop': chess.board.BISHOP, 'rook': chess.board.ROOK}
@ -613,16 +97,18 @@ class ChessGame(game.ChessGame):
# Call parent constructor
game.ChessGame.__init__(self)
self.view = View(self)
self.view.controller = application.ui.controller.addView(name, self.view)
self.view = display.View(self)
self.view.controller = application.ui.controller.setView(name, self.view)
self.view.updateRotation(animate = False)
self.view.showMoveHints(config.get('show_move_hints') is True)
self.view.showBoardNumbering(config.get('show_numbering') is True)
# Watch for piece moves with a player
self.__movePlayer = MovePlayer(self)
self.__movePlayer = player.MovePlayer(self)
self.addSpectator(self.__movePlayer)
self.date = time.strftime('%Y.%m.%d')
def addAIPlayer(self, name, profile, level):
"""Create an AI player.
@ -634,10 +120,10 @@ class ChessGame(game.ChessGame):
Returns an AI player to use (game.ChessPlayer).
"""
description = _("'%(name)s' in '%(game)s'") % {'name': name, 'game': self.name}
player = AIPlayer(self.application, name, profile, level, description)
self.__aiPlayers.append(player)
self.application.watchAIPlayer(player)
return player
p = player.AIPlayer(self.application, name, profile, level, description)
self.__aiPlayers.append(p)
self.application.watchAIPlayer(p)
return p
def addHumanPlayer(self, name):
"""Create a human player.
@ -646,8 +132,7 @@ class ChessGame(game.ChessGame):
Returns a human player to use (game.ChessPlayer).
"""
player = HumanPlayer(self, name)
return player
return player.HumanPlayer(self, name)
def setTimer(self, duration, whiteTime, blackTime):
self.duration = duration
@ -668,8 +153,8 @@ class ChessGame(game.ChessGame):
Returns True if the current player is human and able to move.
"""
player = self.getCurrentPlayer()
return isinstance(player, HumanPlayer) and player.isReadyToMove()
p = self.getCurrentPlayer()
return isinstance(p, player.HumanPlayer) and p.isReadyToMove()
def squareIsFriendly(self, coord):
"""
@ -683,8 +168,8 @@ class ChessGame(game.ChessGame):
"""
"""
assert(self.currentPlayerIsHuman())
player = self.getCurrentPlayer()
if player is self.getWhite():
p = self.getCurrentPlayer()
if p is self.getWhite():
colour = chess.board.WHITE
else:
colour = chess.board.BLACK
@ -697,7 +182,7 @@ class ChessGame(game.ChessGame):
# Make the move
move = chess.lan.encode(colour, start, end, promotionType = promotionType)
player.move(move)
p.move(move)
# Notify move
self.view.controller.setAttention(False)
@ -710,32 +195,33 @@ class ChessGame(game.ChessGame):
white = self.getWhite()
black = self.getBlack()
pgnGame.setTag(pgnGame.PGN_TAG_EVENT, self.name)
pgnGame.setTag(pgnGame.PGN_TAG_WHITE, white.getName())
pgnGame.setTag(pgnGame.PGN_TAG_BLACK, black.getName())
pgnGame.setTag(chess.pgn.TAG_EVENT, self.name)
pgnGame.setTag(chess.pgn.TAG_WHITE, white.getName())
pgnGame.setTag(chess.pgn.TAG_BLACK, black.getName())
pgnGame.setTag(chess.pgn.TAG_DATE, self.date)
results = {game.RESULT_WHITE_WINS: chess.pgn.PGNToken.GAME_TERMINATE_WHITE_WIN,
game.RESULT_BLACK_WINS: chess.pgn.PGNToken.GAME_TERMINATE_BLACK_WIN,
game.RESULT_DRAW: chess.pgn.PGNToken.GAME_TERMINATE_DRAW}
results = {game.RESULT_WHITE_WINS: chess.pgn.RESULT_WHITE_WIN,
game.RESULT_BLACK_WINS: chess.pgn.RESULT_BLACK_WIN,
game.RESULT_DRAW: chess.pgn.RESULT_DRAW}
try:
value = results[self.result]
except KeyError:
pass
else:
pgnGame.setTag(pgnGame.PGN_TAG_RESULT, value)
pgnGame.setTag(chess.pgn.TAG_RESULT, value)
rules = {game.RULE_RESIGN: pgnGame.PGN_TERMINATE_ABANDONED,
game.RULE_TIMEOUT: pgnGame.PGN_TERMINATE_TIME_FORFEIT,
game.RULE_DEATH: pgnGame.PGN_TERMINATE_DEATH}
rules = {game.RULE_RESIGN: chess.pgn.TERMINATE_ABANDONED,
game.RULE_TIMEOUT: chess.pgn.TERMINATE_TIME_FORFEIT,
game.RULE_DEATH: chess.pgn.TERMINATE_DEATH}
try:
value = rules[self.rule]
except KeyError:
pass
else:
pgnGame.setTag(pgnGame.PGN_TAG_TERMINATION, value)
pgnGame.setTag(chess.pgn.TAG_TERMINATION, value)
if self.duration > 0:
pgnGame.setTag(pgnGame.PGN_TAG_TIME_CONTROL, str(self.duration))
pgnGame.setTag(chess.pgn.TAG_TIME_CONTROL, str(self.duration))
if self.wT is not None:
pgnGame.setTag('WhiteTime', str(self.wT.controller.getRemaining()))
if self.bT is not None:
@ -764,16 +250,16 @@ class ChessGame(game.ChessGame):
"""
return self.view.scene.controller.animate(timeStep)
def endMove(self, player):
game.ChessGame.endMove(self, player)
def endMove(self, p):
game.ChessGame.endMove(self, p)
self.view.updateRotation()
def remove(self):
"""Remove this game"""
# Remove AI player windows
for player in self.__aiPlayers:
player.window.close()
self.application.unwatchAIPlayer(player)
for p in self.__aiPlayers:
p.window.close()
self.application.unwatchAIPlayer(p)
# Stop the game
self.abort()
@ -797,8 +283,8 @@ class UI(ui.UIFeedback):
self.controller = gtkui.GtkUI(self)
self.application = application
self.splashscreen = Splashscreen(self)
self.splashscreen.controller = self.controller.setDefaultView(self.splashscreen)
self.splashscreen = display.Splashscreen(self)
self.splashscreen.controller = self.controller.setView('SPLASHSCREEN', self.splashscreen) # FIXME
self.ggzConfig = network.GGZConfig()
dialog = network.GGZNetworkDialog(self)
@ -892,14 +378,13 @@ class Application:
# The network game detector
__detector = None
# The games present
__games = None
# The game in progress
__game = None
def __init__(self):
"""Constructor for glChess application"""
self.__aiProfiles = {}
self.__games = []
self.ioHandlers = {}
self.networkConnections = {}
@ -907,6 +392,8 @@ class Application:
self.ui = UI(self)
self.history = history.GameHistory()
self.logger = self.ui.controller.addLogWindow('Application Log', '', '')
def addAIProfile(self, profile):
@ -931,22 +418,27 @@ class Application:
except KeyError:
return None
def watchAIPlayer(self, player):
def watchAIPlayer(self, p):
"""
"""
self.ioHandlers[player.fileno()] = player
self.ui.controller.watchFileDescriptor(player.fileno())
self.ioHandlers[p.fileno()] = p
self.ui.controller.watchFileDescriptor(p.fileno())
def unwatchAIPlayer(self, player):
def unwatchAIPlayer(self, p):
"""
"""
fd = player.fileno()
fd = p.fileno()
if fd is not None:
self.ioHandlers.pop(fd)
def setGame(self, g):
if self.__game is not None:
self._save(self.__game)
self.__game = g
def addNetworkGame(self, name):
g = ChessGame(self, name)
self.__games.append(g)
self.setGame(g)
return g
def addGame(self, name, whiteName, whiteType, blackName, blackType):
@ -964,22 +456,22 @@ class Application:
# Create the game
g = ChessGame(self, name)
self.__games.append(g)
self.setGame(g)
msg = ''
if whiteType is None:
player = g.addHumanPlayer(whiteName)
p = g.addHumanPlayer(whiteName)
else:
(profile, level) = whiteType
player = g.addAIPlayer(whiteName, self.__aiProfiles[profile], level)
g.setWhite(player)
p = g.addAIPlayer(whiteName, self.__aiProfiles[profile], level)
g.setWhite(p)
if blackType is None:
player = g.addHumanPlayer(blackName)
p = g.addHumanPlayer(blackName)
else:
(profile, level) = blackType
player = g.addAIPlayer(blackName, self.__aiProfiles[profile], level)
g.setBlack(player)
p = g.addAIPlayer(blackName, self.__aiProfiles[profile], level)
g.setBlack(p)
return g
@ -994,9 +486,9 @@ class Application:
gameProperties = ui.Game()
gameProperties.path = path
gameProperties.name = pgnGame.getTag(pgnGame.PGN_TAG_EVENT)
gameProperties.white.name = pgnGame.getTag(pgnGame.PGN_TAG_WHITE)
gameProperties.black.name = pgnGame.getTag(pgnGame.PGN_TAG_BLACK)
gameProperties.name = pgnGame.getTag(chess.pgn.TAG_EVENT)
gameProperties.white.name = pgnGame.getTag(chess.pgn.TAG_WHITE)
gameProperties.black.name = pgnGame.getTag(chess.pgn.TAG_BLACK)
moves = []
for pgnMove in pgnGame.getMoves():
moves.append(pgnMove.move)
@ -1031,6 +523,7 @@ class Application:
return
newGame = self.addGame(gameProperties.name, gameProperties.white.name, w, gameProperties.black.name, b)
newGame.date = pgnGame.getTag(chess.pgn.TAG_DATE)
newGame.fileName = path
if gameProperties.moves:
newGame.start(gameProperties.moves)
@ -1046,19 +539,19 @@ class Application:
moves[i].nag = pgnMoves[i].nag
# Get the last player to resign if the file specifies it
result = pgnGame.getTag(pgnGame.PGN_TAG_RESULT, None)
if result == chess.pgn.PGNToken.GAME_TERMINATE_DRAW:
result = pgnGame.getTag(chess.pgn.TAG_RESULT, None)
if result == chess.pgn.RESULT_DRAW:
if newGame.result == game.RESULT_IN_PROGRESS:
newGame.getCurrentPlayer().resign()
elif newGame.result != game.RESULT_DRAW:
print 'PGN file specifies draw, glChess expects a win'
elif result == chess.pgn.PGNToken.GAME_TERMINATE_WHITE_WIN:
elif result == chess.pgn.RESULT_WHITE_WIN:
print 'FIXME: Handle white win in PGN'
elif result == chess.pgn.PGNToken.GAME_TERMINATE_BLACK_WIN:
elif result == chess.pgn.RESULT_BLACK_WIN:
print 'FIXME: Handle black win in PGN'
duration = 0
value = pgnGame.getTag(pgnGame.PGN_TAG_TIME_CONTROL)
value = pgnGame.getTag(chess.pgn.TAG_TIME_CONTROL)
if value is not None:
timers = value.split(':')
try:
@ -1140,88 +633,37 @@ class Application:
def animate(self, timeStep):
"""
"""
animating = False
for g in self.__games:
if g.animate(timeStep):
animating = True
return animating
return self.__game.animate(timeStep)
def quit(self):
"""Quit glChess"""
# Notify the UI
self.ui.controller.close()
if self.__game is not None:
# Save the current game to the history
self._save(self.__game)
# Save any games not saved to a file
self.__autosave()
# Abort current games (will delete AIs etc)
for game in self.__games[:]:
game.abort()
# Abort current game (will delete AIs etc)
self.__game.abort()
# Exit the application
sys.exit()
# Private methods
def _removeGame(self, g):
"""
"""
self.__games.remove(g)
def _save(self, g):
if len(g.getMoves()) < 2:
return
pgnGame = chess.pgn.PGNGame()
g.toPGN(pgnGame)
self.history.save(pgnGame)
def __autoload(self):
"""Restore games from the autosave file"""
path = AUTOSAVE_FILE
try:
p = chess.pgn.PGN(path)
games = p[:]
except chess.pgn.Error, e:
self.logger.addLine('Ignoring invalid autoload file %s: %s' % (path, str(e)))
return
except IOError, e:
# The file doesn't have to exist...
if e.errno != errno.ENOENT:
self.logger.addLine('Unable to autoload from %s: %s' % (path, str(e)))
return
self.logger.addLine('Auto-loading from %s...' % path)
# Delete the file once loaded
try:
os.unlink(path)
except OSError:
pass
# Restore each game
for pgnGame in games:
pgnGame = self.history.getUnfinishedGame()
if pgnGame is not None:
self.addPGNGame(pgnGame, None)
def __autosave(self):
"""Save any open games to the autosave file"""
if len(self.__games) == 0:
return
fname = AUTOSAVE_FILE
self.logger.addLine('Auto-saving to %s...' % fname)
try:
f = file(fname, 'a')
for g in self.__games:
# Ignore games that are saved to a file
if g.fileName is not None:
continue
pgnGame = chess.pgn.PGNGame()
g.toPGN(pgnGame)
lines = pgnGame.getLines()
for line in lines:
f.write(line + '\n')
f.write('\n')
f.close()
except IOError, e:
# FIXME: This should be in a dialog
self.logger.addLine('Unable to autosave to %s: %s' % (fname, str(e)))
if __name__ == '__main__':
app = Application()

94
src/lib/player.py Normal file
View file

@ -0,0 +1,94 @@
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
__license__ = 'GNU General Public License Version 2'
__copyright__ = 'Copyright 2005-2006 Robert Ancell'
import game
import ai
class MovePlayer(game.ChessPlayer):
"""This class provides a pseudo-player to watch for piece movements"""
# The game to control
__game = None
waitForMoves = False
def __init__(self, chessGame):
"""Constructor for a move player.
'chessGame' is the game to make changes to (ChessGame).
"""
self.__game = chessGame
game.ChessPlayer.__init__(self, '@move')
# Extended methods
def onPieceMoved(self, piece, start, end, delete):
"""Called by chess.board.ChessPlayer"""
if self.__game.view.moveNumber != -1:
return
p = self.__game.view.scene.movePiece(piece, end, delete, self.__game.isStarted())
# If a player move notify when animation completes
if self.__game.isStarted() and self.__game.view.moveNumber == -1 and start is not None and start != end:
self.__game.view.scene.waitingPiece = p
def onPlayerMoved(self, p, move):
"""Called by chess.board.ChessPlayer"""
self.__game.needsSaving = True
# Update clocks
if p is self.__game.getWhite():
if self.__game.wT is not None:
self.__game.wT.controller.pause()
if self.__game.bT is not None:
self.__game.bT.controller.run()
else:
if self.__game.bT is not None:
self.__game.bT.controller.pause()
if self.__game.wT is not None:
self.__game.wT.controller.run()
# Complete move if not waiting for visual indication of move end
if self.__game.view.moveNumber != -1:
p.endMove()
self.__game.view.controller.addMove(move)
def onGameEnded(self, game):
"""Called by chess.board.ChessPlayer"""
self.__game.view.controller.endGame(game)
class HumanPlayer(game.ChessPlayer):
"""
"""
__game = None
def __init__(self, chessGame, name):
"""Constructor.
'chessGame' is the game this player is in (game.ChessGame).
'name' is the name of this player (string).
"""
game.ChessPlayer.__init__(self, name)
self.__game = chessGame
def readyToMove(self):
# FIXME: ???
self.__game.view.controller.setAttention(True)
class AIPlayer(ai.Player):
"""
"""
def __init__(self, application, name, profile, level, description):
"""
"""
executable = profile.path
for arg in profile.arguments[1:]:
executable += ' ' + arg
self.window = application.ui.controller.addLogWindow(profile.name, executable, description)
ai.Player.__init__(self, name, profile, level)
def addText(self, text, style):
"""Called by ai.Player"""
self.window.addText(text, style)

View file

@ -116,9 +116,6 @@ class NetworkController:
"""
pass
def close(self):
pass
class ViewFeedback:
"""Template class for feedback from a view object"""
@ -211,10 +208,6 @@ class ViewFeedback:
"""
pass
def close(self):
"""This method is called when the user requests this view be closed"""
pass
class ViewController:
"""Template class for methods to control a view"""
@ -251,11 +244,7 @@ class ViewController:
'requiresAttention' is a flag to show if this view requires attention.
"""
pass
def close(self):
"""Close this view"""
pass
class UIFeedback:
"""Template class for feedback from a UI"""
@ -419,19 +408,8 @@ class UI:
"""
return None
def setDefaultView(self, feedback):
"""Set the default view to render.
'feedback' is a object to report view events with (extends ViewFeedback).
This will override the previous default view.
Returns a view controller object (extends ViewController).
"""
return None
def addView(self, title, feedback):
"""Add a view to the UI.
def setView(self, title, feedback):
"""Set the view to display.
'title' is the title for the view (string).
'feedback' is a object to report view events with (extends ViewFeedback).