functioning tic-tac-toe :D
This commit is contained in:
parent
d034078c03
commit
b170e77cdb
|
@ -91,6 +91,13 @@
|
||||||
</child>
|
</child>
|
||||||
</widget>
|
</widget>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkImageMenuItem" id="tictactoe_menuitem">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">Play Tic Tac Toe</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<widget class="GtkImageMenuItem" id="manage_contact">
|
<widget class="GtkImageMenuItem" id="manage_contact">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
|
|
@ -50,7 +50,8 @@ if dbus_support.supported:
|
||||||
from music_track_listener import MusicTrackListener
|
from music_track_listener import MusicTrackListener
|
||||||
|
|
||||||
# XXX interface leaking into the back end?
|
# XXX interface leaking into the back end?
|
||||||
import session
|
from session import ChatControlSession
|
||||||
|
import tictactoe
|
||||||
|
|
||||||
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
|
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
|
||||||
'invisible', 'error']
|
'invisible', 'error']
|
||||||
|
@ -768,6 +769,7 @@ class ConnectionDisco:
|
||||||
q.addChild('feature', attrs = {'var': common.xmpp.NS_MOOD})
|
q.addChild('feature', attrs = {'var': common.xmpp.NS_MOOD})
|
||||||
q.addChild('feature', attrs = {'var': common.xmpp.NS_MOOD + '+notify'})
|
q.addChild('feature', attrs = {'var': common.xmpp.NS_MOOD + '+notify'})
|
||||||
q.addChild('feature', attrs = {'var': common.xmpp.NS_ESESSION_INIT})
|
q.addChild('feature', attrs = {'var': common.xmpp.NS_ESESSION_INIT})
|
||||||
|
q.addChild('feature', attrs = {'var': 'http://jabber.org/protocol/games'})
|
||||||
|
|
||||||
if (node is None or extension == 'cstates') and gajim.config.get('outgoing_chat_state_notifactions') != 'disabled':
|
if (node is None or extension == 'cstates') and gajim.config.get('outgoing_chat_state_notifactions') != 'disabled':
|
||||||
q.addChild('feature', attrs = {'var': common.xmpp.NS_CHATSTATES})
|
q.addChild('feature', attrs = {'var': common.xmpp.NS_CHATSTATES})
|
||||||
|
@ -779,6 +781,9 @@ class ConnectionDisco:
|
||||||
q.addChild('feature', attrs = {'var': common.xmpp.NS_PING})
|
q.addChild('feature', attrs = {'var': common.xmpp.NS_PING})
|
||||||
q.addChild('feature', attrs = {'var': common.xmpp.NS_TIME_REVISED})
|
q.addChild('feature', attrs = {'var': common.xmpp.NS_TIME_REVISED})
|
||||||
|
|
||||||
|
if node == 'http://jabber.org/protocol/games':
|
||||||
|
q.addChild('feature', attrs = {'var': 'http://jabber.org/protocol/games/tictactoe'})
|
||||||
|
|
||||||
if q.getChildren():
|
if q.getChildren():
|
||||||
self.connection.send(iq)
|
self.connection.send(iq)
|
||||||
raise common.xmpp.NodeProcessed
|
raise common.xmpp.NodeProcessed
|
||||||
|
@ -1538,7 +1543,20 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
||||||
if not mtype:
|
if not mtype:
|
||||||
mtype = 'normal'
|
mtype = 'normal'
|
||||||
|
|
||||||
if not mtype == 'groupchat':
|
game_invite = msg.getTag('invite', namespace='http://jabber.org/protocol/games')
|
||||||
|
if game_invite:
|
||||||
|
game = game_invite.getTag('game')
|
||||||
|
|
||||||
|
if game.getAttr('var') == 'http://jabber.org/protocol/games/tictactoe':
|
||||||
|
klass = tictactoe.TicTacToeSession
|
||||||
|
|
||||||
|
# this assumes that the invitation came with a thread_id we haven't seen
|
||||||
|
session = self.make_new_session(frm, thread_id, klass=klass)
|
||||||
|
|
||||||
|
session.invited(msg)
|
||||||
|
|
||||||
|
return
|
||||||
|
elif mtype != 'groupchat':
|
||||||
session = self.get_session(frm, thread_id, mtype)
|
session = self.get_session(frm, thread_id, mtype)
|
||||||
|
|
||||||
if thread_id and not session.received_thread_id:
|
if thread_id and not session.received_thread_id:
|
||||||
|
@ -1550,27 +1568,23 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
||||||
return
|
return
|
||||||
|
|
||||||
# check if the message is a xep70-confirmation-request
|
# check if the message is a xep70-confirmation-request
|
||||||
if msg.getTag('confirm') and msg.getTag('confirm').namespace == \
|
if msg.getTag('confirm', namespace=common.xmpp.NS_HTTP_AUTH):
|
||||||
common.xmpp.NS_HTTP_AUTH:
|
|
||||||
self._HttpAuthCB(con, msg)
|
self._HttpAuthCB(con, msg)
|
||||||
return
|
return
|
||||||
|
|
||||||
# check if the message is a XEP 0020 feature negotiation request
|
# check if the message is a XEP 0020 feature negotiation request
|
||||||
if msg.getTag('feature') and msg.getTag('feature').namespace == \
|
if msg.getTag('feature', namespace=common.xmpp.NS_FEATURE):
|
||||||
common.xmpp.NS_FEATURE:
|
|
||||||
if gajim.HAVE_PYCRYPTO:
|
if gajim.HAVE_PYCRYPTO:
|
||||||
self._FeatureNegCB(con, msg, session)
|
self._FeatureNegCB(con, msg, session)
|
||||||
return
|
return
|
||||||
|
|
||||||
# check if the message is initiating an ESession negotiation
|
# check if the message is initiating an ESession negotiation
|
||||||
if msg.getTag('init') and msg.getTag('init').namespace == \
|
if msg.getTag('init', namespace=common.xmpp.NS_ESESSION_INIT):
|
||||||
common.xmpp.NS_ESESSION_INIT:
|
|
||||||
self._InitE2ECB(con, msg, session)
|
self._InitE2ECB(con, msg, session)
|
||||||
|
|
||||||
encrypted = False
|
encrypted = False
|
||||||
|
|
||||||
e2e_tag = msg.getTag('c', namespace = common.xmpp.NS_STANZA_CRYPTO)
|
if msg.getTag('c', namespace = common.xmpp.NS_STANZA_CRYPTO):
|
||||||
if e2e_tag:
|
|
||||||
encrypted = True
|
encrypted = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -1701,12 +1715,16 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
||||||
if treat_as:
|
if treat_as:
|
||||||
mtype = treat_as
|
mtype = treat_as
|
||||||
|
|
||||||
|
# XXX horrible hack
|
||||||
|
if isinstance(session, ChatControlSession):
|
||||||
session.received(frm, msgtxt, tim, encrypted, mtype, subject, chatstate,
|
session.received(frm, msgtxt, tim, encrypted, mtype, subject, chatstate,
|
||||||
msg_id, composing_xep, user_nick, msghtml, form_node)
|
msg_id, composing_xep, user_nick, msghtml, form_node)
|
||||||
|
else:
|
||||||
|
session.received(msg)
|
||||||
# END messageCB
|
# END messageCB
|
||||||
|
|
||||||
# process and dispatch an error message
|
# process and dispatch an error message
|
||||||
def dispatch_error_message(self, msg, msgtxt, session, frm, tim, subject)
|
def dispatch_error_message(self, msg, msgtxt, session, frm, tim, subject):
|
||||||
error_msg = msg.getError()
|
error_msg = msg.getError()
|
||||||
|
|
||||||
if not error_msg:
|
if not error_msg:
|
||||||
|
@ -1761,7 +1779,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
||||||
except exceptions.PysqliteOperationalError, e:
|
except exceptions.PysqliteOperationalError, e:
|
||||||
self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
|
self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
|
||||||
|
|
||||||
def dispatch_invite_message(self, invite, frm)
|
def dispatch_invite_message(self, invite, frm):
|
||||||
item = invite.getTag('invite')
|
item = invite.getTag('invite')
|
||||||
|
|
||||||
jid_from = item.getAttr('from')
|
jid_from = item.getAttr('from')
|
||||||
|
@ -1843,8 +1861,11 @@ returns the session that we last sent a message to.'''
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def make_new_session(self, jid, thread_id = None, type = 'chat'):
|
def make_new_session(self, jid, thread_id=None, type='chat', klass=None):
|
||||||
sess = session.ChatControlSession(self, common.xmpp.JID(jid), thread_id, type)
|
if not klass:
|
||||||
|
klass = ChatControlSession
|
||||||
|
|
||||||
|
sess = klass(self, common.xmpp.JID(jid), thread_id, type)
|
||||||
|
|
||||||
if not jid in self.sessions:
|
if not jid in self.sessions:
|
||||||
self.sessions[jid] = {}
|
self.sessions[jid] = {}
|
||||||
|
|
|
@ -32,10 +32,23 @@ class StanzaSession(object):
|
||||||
else:
|
else:
|
||||||
self.thread_id = self.generate_thread_id()
|
self.thread_id = self.generate_thread_id()
|
||||||
|
|
||||||
|
self.loggable = True
|
||||||
|
|
||||||
self.last_send = 0
|
self.last_send = 0
|
||||||
self.status = None
|
self.status = None
|
||||||
self.negotiated = {}
|
self.negotiated = {}
|
||||||
|
|
||||||
|
def is_loggable(self):
|
||||||
|
account = self.conn.name
|
||||||
|
no_log_for = gajim.config.get_per('accounts', account, 'no_log_for')
|
||||||
|
|
||||||
|
if not no_log_for:
|
||||||
|
no_log_for = ''
|
||||||
|
|
||||||
|
no_log_for = no_log_for.split()
|
||||||
|
|
||||||
|
return self.loggable and account not in no_log_for and self.jid not in no_log_for
|
||||||
|
|
||||||
def generate_thread_id(self):
|
def generate_thread_id(self):
|
||||||
return "".join([random.choice(string.ascii_letters) for x in xrange(0,32)])
|
return "".join([random.choice(string.ascii_letters) for x in xrange(0,32)])
|
||||||
|
|
||||||
|
@ -132,8 +145,6 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
def __init__(self, conn, jid, thread_id, type = 'chat'):
|
def __init__(self, conn, jid, thread_id, type = 'chat'):
|
||||||
StanzaSession.__init__(self, conn, jid, thread_id, type = 'chat')
|
StanzaSession.__init__(self, conn, jid, thread_id, type = 'chat')
|
||||||
|
|
||||||
self.loggable = True
|
|
||||||
|
|
||||||
self.xes = {}
|
self.xes = {}
|
||||||
self.es = {}
|
self.es = {}
|
||||||
|
|
||||||
|
@ -891,17 +902,6 @@ otherwise, list the fields we haven't implemented'''
|
||||||
# preventing falsified messages from going through.
|
# preventing falsified messages from going through.
|
||||||
self.km_o = ''
|
self.km_o = ''
|
||||||
|
|
||||||
def is_loggable(self):
|
|
||||||
account = self.conn.name
|
|
||||||
no_log_for = gajim.config.get_per('accounts', account, 'no_log_for')
|
|
||||||
|
|
||||||
if not no_log_for:
|
|
||||||
no_log_for = ''
|
|
||||||
|
|
||||||
no_log_for = no_log_for.split()
|
|
||||||
|
|
||||||
return self.loggable and account not in no_log_for and self.jid not in no_log_for
|
|
||||||
|
|
||||||
def cancelled_negotiation(self):
|
def cancelled_negotiation(self):
|
||||||
StanzaSession.cancelled_negotiation(self)
|
StanzaSession.cancelled_negotiation(self)
|
||||||
self.enable_encryption = False
|
self.enable_encryption = False
|
||||||
|
|
|
@ -2345,6 +2345,9 @@ class RosterWindow:
|
||||||
contacts, account, self.on_execute_command))
|
contacts, account, self.on_execute_command))
|
||||||
|
|
||||||
else: # one resource
|
else: # one resource
|
||||||
|
tictactoe_menuitem = xml.get_widget( 'tictactoe_menuitem')
|
||||||
|
tictactoe_menuitem.connect('activate', self.play_tictactoe, contact, account, contact.resource)
|
||||||
|
|
||||||
start_chat_menuitem.connect('activate',
|
start_chat_menuitem.connect('activate',
|
||||||
self.on_open_chat_window, contact, account)
|
self.on_open_chat_window, contact, account)
|
||||||
execute_command_menuitem.connect('activate', self.on_execute_command,
|
execute_command_menuitem.connect('activate', self.on_execute_command,
|
||||||
|
@ -4278,6 +4281,17 @@ class RosterWindow:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def play_tictactoe(self, widget, contact, account, resource=None):
|
||||||
|
jid = contact.jid
|
||||||
|
|
||||||
|
if resource is not None:
|
||||||
|
jid = jid + u'/' + resource
|
||||||
|
|
||||||
|
import tictactoe
|
||||||
|
|
||||||
|
sess = gajim.connections[account].make_new_session(jid, klass=tictactoe.TicTacToeSession)
|
||||||
|
sess.begin()
|
||||||
|
|
||||||
def on_execute_command(self, widget, contact, account, resource=None):
|
def on_execute_command(self, widget, contact, account, resource=None):
|
||||||
'''Execute command. Full JID needed; if it is other contact,
|
'''Execute command. Full JID needed; if it is other contact,
|
||||||
resource is necessary. Widget is unnecessary, only to be
|
resource is necessary. Widget is unnecessary, only to be
|
||||||
|
|
|
@ -16,6 +16,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
|
||||||
|
|
||||||
self.control = None
|
self.control = None
|
||||||
|
|
||||||
|
# dispatch a received <message> stanza
|
||||||
def received(self, full_jid_with_resource, message, tim, encrypted, msg_type, subject, chatstate, msg_id, composing_xep, user_nick, xhtml, form_node):
|
def received(self, full_jid_with_resource, message, tim, encrypted, msg_type, subject, chatstate, msg_id, composing_xep, user_nick, xhtml, form_node):
|
||||||
|
|
||||||
jid = gajim.get_jid_without_resource(full_jid_with_resource)
|
jid = gajim.get_jid_without_resource(full_jid_with_resource)
|
||||||
|
@ -114,6 +115,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
|
||||||
encrypted, msg_type, subject, chatstate, msg_id,
|
encrypted, msg_type, subject, chatstate, msg_id,
|
||||||
composing_xep, user_nick, xhtml, form_node]))
|
composing_xep, user_nick, xhtml, form_node]))
|
||||||
|
|
||||||
|
# display the message or show notification in the roster
|
||||||
def roster_message(self, jid, msg, tim, encrypted=False, msg_type='',
|
def roster_message(self, jid, msg, tim, encrypted=False, msg_type='',
|
||||||
subject=None, resource='', msg_id=None, user_nick='',
|
subject=None, resource='', msg_id=None, user_nick='',
|
||||||
advanced_notif_num=None, xhtml=None, form_node=None):
|
advanced_notif_num=None, xhtml=None, form_node=None):
|
||||||
|
|
|
@ -0,0 +1,276 @@
|
||||||
|
from common import stanza_session
|
||||||
|
from common import xmpp
|
||||||
|
|
||||||
|
import pygtk
|
||||||
|
pygtk.require('2.0')
|
||||||
|
import gtk
|
||||||
|
from gtk import gdk
|
||||||
|
import cairo
|
||||||
|
|
||||||
|
# implements <http://pidgin-games.sourceforge.net/xep/tictactoe.html#invite>
|
||||||
|
|
||||||
|
class InvalidMove(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TicTacToeSession(stanza_session.StanzaSession):
|
||||||
|
def begin(self, rows = 3, cols = 3, role_s = 'x'):
|
||||||
|
self.rows = rows
|
||||||
|
self.cols = cols
|
||||||
|
|
||||||
|
self.role_s = role_s
|
||||||
|
|
||||||
|
if self.role_s == 'x':
|
||||||
|
self.role_o = 'o'
|
||||||
|
else:
|
||||||
|
self.role_o = 'x'
|
||||||
|
|
||||||
|
msg = xmpp.Message()
|
||||||
|
|
||||||
|
invite = msg.NT.invite
|
||||||
|
invite.setNamespace('http://jabber.org/protocol/games')
|
||||||
|
|
||||||
|
game = invite.NT.game
|
||||||
|
game.setAttr('var', 'http://jabber.org/protocol/games/tictactoe')
|
||||||
|
|
||||||
|
x = xmpp.DataForm(typ='submit')
|
||||||
|
|
||||||
|
game.addChild(node=x)
|
||||||
|
|
||||||
|
self.send(msg)
|
||||||
|
|
||||||
|
self.next_move_id = 1
|
||||||
|
self.state = 'sent_invite'
|
||||||
|
|
||||||
|
# received an invitation
|
||||||
|
def invited(self, msg):
|
||||||
|
invite = msg.getTag('invite', namespace='http://jabber.org/protocol/games')
|
||||||
|
game = invite.getTag('game')
|
||||||
|
x = game.getTag('x', namespace='jabber:x:data')
|
||||||
|
|
||||||
|
form = xmpp.DataForm(node=x)
|
||||||
|
|
||||||
|
if form.getField('role'):
|
||||||
|
self.role_o = form.getField('role').getValues()[0]
|
||||||
|
|
||||||
|
if form.getField('rows'):
|
||||||
|
self.rows = int(form.getField('rows').getValues()[0])
|
||||||
|
|
||||||
|
if form.getField('cols'):
|
||||||
|
self.cols = int(form.getField('cols').getValues()[0])
|
||||||
|
|
||||||
|
# XXX 'strike'
|
||||||
|
|
||||||
|
if not hasattr(self, 'rows'):
|
||||||
|
self.rows = 3
|
||||||
|
|
||||||
|
if not hasattr(self, 'cols'):
|
||||||
|
self.cols = 3
|
||||||
|
|
||||||
|
# the number of the move about to be made
|
||||||
|
self.next_move_id = 1
|
||||||
|
|
||||||
|
self.board = TicTacToeBoard(self, self.rows, self.cols)
|
||||||
|
|
||||||
|
# accept the invitation, join the game
|
||||||
|
response = xmpp.Message()
|
||||||
|
|
||||||
|
join = response.NT.join
|
||||||
|
join.setNamespace('http://jabber.org/protocol/games')
|
||||||
|
|
||||||
|
self.send(response)
|
||||||
|
|
||||||
|
if not hasattr(self, 'role_o') or self.role_o == 'x':
|
||||||
|
self.role_s = 'o'
|
||||||
|
self.role_o = 'x'
|
||||||
|
|
||||||
|
self.their_turn()
|
||||||
|
else:
|
||||||
|
self.role_s = 'x'
|
||||||
|
self.role_o = 'o'
|
||||||
|
|
||||||
|
self.our_turn()
|
||||||
|
|
||||||
|
def is_my_turn(self):
|
||||||
|
return self.state == 'get_input'
|
||||||
|
|
||||||
|
def received(self, msg):
|
||||||
|
# just sent an invitation, expecting a reply
|
||||||
|
if self.state == 'sent_invite':
|
||||||
|
if msg.getTag('join', namespace='http://jabber.org/protocol/games'):
|
||||||
|
self.board = TicTacToeBoard(self, self.rows, self.cols)
|
||||||
|
|
||||||
|
if self.role_s == 'x':
|
||||||
|
self.our_turn()
|
||||||
|
else:
|
||||||
|
self.their_turn()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
# ignore messages unless we're expecting a move
|
||||||
|
if self.state != 'waiting':
|
||||||
|
return
|
||||||
|
|
||||||
|
turn = msg.getTag('turn', namespace='http://jabber.org/protocol/games')
|
||||||
|
|
||||||
|
move = turn.getTag('move', namespace='http://jabber.org/protocol/games/tictactoe')
|
||||||
|
|
||||||
|
row = int(move.getAttr('row'))
|
||||||
|
col = int(move.getAttr('col'))
|
||||||
|
id = int(move.getAttr('id'))
|
||||||
|
|
||||||
|
if id != self.next_move_id:
|
||||||
|
print 'unexpected move id, lost a move somewhere?'
|
||||||
|
raise
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.board.mark(row, col, self.role_o)
|
||||||
|
except InvalidMove, e:
|
||||||
|
print 'received invalid move'
|
||||||
|
return
|
||||||
|
|
||||||
|
# XXX check win conditions
|
||||||
|
|
||||||
|
self.next_move_id += 1
|
||||||
|
|
||||||
|
self.our_turn()
|
||||||
|
|
||||||
|
def our_turn(self):
|
||||||
|
self.state = 'get_input'
|
||||||
|
self.board.win.set_title(self.board.title + ': your turn')
|
||||||
|
|
||||||
|
def their_turn(self):
|
||||||
|
self.state = 'waiting'
|
||||||
|
self.board.win.set_title(self.board.title + ': their turn')
|
||||||
|
|
||||||
|
# called when the board receives input
|
||||||
|
def move(self, row, column):
|
||||||
|
try:
|
||||||
|
self.board.mark(row, column, self.role_s)
|
||||||
|
except InvalidMove, e:
|
||||||
|
print 'invalid move'
|
||||||
|
return
|
||||||
|
|
||||||
|
self.send_move(row, column)
|
||||||
|
|
||||||
|
# XXX check win conditions
|
||||||
|
|
||||||
|
def send_move(self, row, column):
|
||||||
|
msg = xmpp.Message()
|
||||||
|
|
||||||
|
turn = msg.NT.turn
|
||||||
|
turn.setNamespace('http://jabber.org/protocol/games')
|
||||||
|
|
||||||
|
move = turn.NT.move
|
||||||
|
move.setNamespace('http://jabber.org/protocol/games/tictactoe')
|
||||||
|
|
||||||
|
move.setAttr('row', str(row))
|
||||||
|
move.setAttr('col', str(column))
|
||||||
|
move.setAttr('id', str(self.next_move_id))
|
||||||
|
|
||||||
|
self.send(msg)
|
||||||
|
|
||||||
|
self.next_move_id += 1
|
||||||
|
|
||||||
|
self.their_turn()
|
||||||
|
|
||||||
|
class TicTacToeBoard:
|
||||||
|
def __init__(self, session, rows, cols):
|
||||||
|
self.session = session
|
||||||
|
|
||||||
|
self.rows = rows
|
||||||
|
self.cols = cols
|
||||||
|
|
||||||
|
self.board = [ [None] * self.cols for r in xrange(self.rows) ]
|
||||||
|
|
||||||
|
self.setup_window()
|
||||||
|
|
||||||
|
def setup_window(self):
|
||||||
|
self.win = gtk.Window()
|
||||||
|
|
||||||
|
self.title = 'tic-tac-toe with %s' % self.session.jid
|
||||||
|
|
||||||
|
self.win.set_title(self.title)
|
||||||
|
self.win.set_app_paintable(True)
|
||||||
|
|
||||||
|
self.win.add_events(gdk.BUTTON_PRESS_MASK)
|
||||||
|
self.win.connect('button-press-event', self.clicked)
|
||||||
|
self.win.connect('expose-event', self.expose)
|
||||||
|
|
||||||
|
self.win.show_all()
|
||||||
|
|
||||||
|
def clicked(self, widget, event):
|
||||||
|
if not self.session.is_my_turn():
|
||||||
|
return
|
||||||
|
|
||||||
|
(height, width) = widget.get_size()
|
||||||
|
|
||||||
|
# convert click co-ordinates to row and column
|
||||||
|
|
||||||
|
row_height = height // self.rows
|
||||||
|
col_width = width // self.cols
|
||||||
|
|
||||||
|
row = int(event.y // row_height) + 1
|
||||||
|
column = int(event.x // col_width) + 1
|
||||||
|
|
||||||
|
self.session.move(row, column)
|
||||||
|
|
||||||
|
def expose(self, widget, event):
|
||||||
|
win = widget.window
|
||||||
|
|
||||||
|
cr = win.cairo_create()
|
||||||
|
|
||||||
|
cr.set_source_rgb(1.0, 1.0, 1.0)
|
||||||
|
|
||||||
|
cr.set_operator(cairo.OPERATOR_SOURCE)
|
||||||
|
cr.paint()
|
||||||
|
|
||||||
|
(width, height) = widget.get_size()
|
||||||
|
|
||||||
|
row_height = height // self.rows
|
||||||
|
col_width = width // self.cols
|
||||||
|
|
||||||
|
for i in xrange(self.rows):
|
||||||
|
for j in xrange(self.cols):
|
||||||
|
if self.board[i][j] == 'x':
|
||||||
|
self.draw_x(cr, i, j, row_height, col_width)
|
||||||
|
elif self.board[i][j] == 'o':
|
||||||
|
self.draw_o(cr, i, j, row_height, col_width)
|
||||||
|
|
||||||
|
def draw_x(self, cr, row, col, row_height, col_width):
|
||||||
|
cr.set_source_rgb(0, 0, 0)
|
||||||
|
|
||||||
|
top = row_height * (row + 0.2)
|
||||||
|
bottom = row_height * (row + 0.8)
|
||||||
|
|
||||||
|
left = col_width * (col + 0.2)
|
||||||
|
right = col_width * (col + 0.8)
|
||||||
|
|
||||||
|
cr.set_line_width(row_height / 5)
|
||||||
|
|
||||||
|
cr.move_to(left, top)
|
||||||
|
cr.line_to(right, bottom)
|
||||||
|
|
||||||
|
cr.move_to(right, top)
|
||||||
|
cr.line_to(left, bottom)
|
||||||
|
|
||||||
|
cr.stroke()
|
||||||
|
|
||||||
|
def draw_o(self, cr, row, col, row_height, col_width):
|
||||||
|
cr.set_source_rgb(0, 0, 0)
|
||||||
|
|
||||||
|
x = col_width * (col + 0.5)
|
||||||
|
y = row_height * (row + 0.5)
|
||||||
|
|
||||||
|
cr.arc(x, y, row_height/4, 0, 2.0*3.2) # slightly further than 2*pi
|
||||||
|
|
||||||
|
cr.set_line_width(row_height / 5)
|
||||||
|
cr.stroke()
|
||||||
|
|
||||||
|
# mark a move on the board
|
||||||
|
def mark(self, row, column, player):
|
||||||
|
if self.board[row-1][column-1]:
|
||||||
|
raise InvalidMove
|
||||||
|
else:
|
||||||
|
self.board[row-1][column-1] = player
|
||||||
|
|
||||||
|
self.win.queue_draw()
|
Loading…
Reference in New Issue