[tictactoe] Code cleanup, new config dialog

This commit is contained in:
Daniel Brötzmann
2019-08-19 09:22:57 +02:00
parent fa5679da4e
commit 58a6536ef1
3 changed files with 162 additions and 168 deletions

View File

@@ -0,0 +1,55 @@
#
# This file is part of the TicTacToe plugin for Gajim.
#
# Gajim is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
from gi.repository import GObject
from gi.repository import Gtk
from gajim.gtk.settings import SettingsDialog
from gajim.gtk.settings import SpinSetting
from gajim.gtk.const import Setting
from gajim.gtk.const import SettingType
from gajim.plugins.plugins_i18n import _
class TicTacToeConfigDialog(SettingsDialog):
def __init__(self, plugin, parent):
self.plugin = plugin
settings = [
Setting('BoardSizeSpinSetting', _('Board Size'),
SettingType.VALUE, self.plugin.config['board_size'],
callback=self.on_setting, data='board_size',
desc=_('Size of the board'),
props={'range_': (3, 10)})]
SettingsDialog.__init__(self, parent, _('TicTacToe Configuration'),
Gtk.DialogFlags.MODAL, settings, None,
extend=[
('BoardSizeSpinSetting', SizeSpinSetting)])
def on_setting(self, value, data):
self.plugin.config[data] = value
class SizeSpinSetting(SpinSetting):
__gproperties__ = {
"setting-value": (int, 'Size', '', 3, 10, 3,
GObject.ParamFlags.READWRITE), }
def __init__(self, *args, **kwargs):
SpinSetting.__init__(self, *args, **kwargs)

View File

@@ -1,63 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkWindow" id="window1">
<property name="can_focus">False</property>
<child>
<object class="GtkBox" id="vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="hbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="board_size_label">
<property name="width_request">133</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Board size</property>
<property name="ellipsize">start</property>
<property name="single_line_mode">True</property>
<property name="track_visited_links">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="board_size">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="has_tooltip">True</property>
<property name="tooltip_text" translatable="yes">Preview size(10-512)</property>
<property name="invisible_char">●</property>
<property name="width_chars">6</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
<signal name="value-changed" handler="board_size_value_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -1,21 +1,20 @@
## plugins/tictactoe/plugin.py #
## # Copyright (C) 2011 Yann Leboulanger <asterix AT lagaule.org>
## Copyright (C) 2011 Yann Leboulanger <asterix AT lagaule.org> #
## # This file is part of the TicTacToe plugin for Gajim.
## This file is part of Gajim. #
## # Gajim is free software; you can redistribute it and/or modify
## Gajim is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published
## it under the terms of the GNU General Public License as published # by the Free Software Foundation; version 3 only.
## by the Free Software Foundation; version 3 only. #
## # Gajim is distributed in the hope that it will be useful,
## Gajim is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of
## but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details.
## GNU General Public License for more details. #
## # You should have received a copy of the GNU General Public License
## You should have received a copy of the GNU General Public License # along with Gajim. If not, see <http://www.gnu.org/licenses/>.
## along with Gajim. If not, see <http://www.gnu.org/licenses/>. #
##
''' '''
Tictactoe plugin. Tictactoe plugin.
@@ -30,36 +29,53 @@ import string
import itertools import itertools
import random import random
import nbxmpp from functools import partial
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import Gdk from gi.repository import Gdk
from gi.repository import Gio from gi.repository import Gio
from gi.repository import GLib from gi.repository import GLib
import gi
gi.require_version('PangoCairo', '1.0')
from gi.repository import PangoCairo
from gajim.common import helpers import nbxmpp
from gajim.common import app
from gajim.plugins import GajimPlugin
from gajim.plugins.helpers import log_calls, log
from gajim.plugins.gui import GajimPluginConfigDialog
from gajim import chat_control from gajim import chat_control
from gajim.common import app
from gajim.common import ged from gajim.common import ged
from gajim.common import helpers
from gajim.common.connection_handlers_events import InformationEvent from gajim.common.connection_handlers_events import InformationEvent
from gajim.plugins.plugins_i18n import _
from gajim.gtk.dialogs import DialogButton from gajim.gtk.dialogs import DialogButton
from gajim.gtk.dialogs import NewConfirmationDialog from gajim.gtk.dialogs import NewConfirmationDialog
from gajim.plugins import GajimPlugin
from gajim.plugins.helpers import log
from gajim.plugins.helpers import log_calls
from gajim.plugins.plugins_i18n import _
from tictactoe.config_dialog import TicTacToeConfigDialog
try:
import gi
gi.require_version('PangoCairo', '1.0')
from gi.repository import PangoCairo
HAS_PANGOCAIRO = True
except ImportError:
HAS_PANGOCAIRO = False
NS_GAMES = 'http://jabber.org/protocol/games' NS_GAMES = 'http://jabber.org/protocol/games'
NS_GAMES_TICTACTOE = NS_GAMES + '/tictactoe' NS_GAMES_TICTACTOE = NS_GAMES + '/tictactoe'
class TictactoePlugin(GajimPlugin): class TictactoePlugin(GajimPlugin):
@log_calls('TictactoePlugin') @log_calls('TictactoePlugin')
def init(self): def init(self):
if not HAS_PANGOCAIRO:
self.activatable = False
self.config_dialog = None
self.available_text = _('TicTacToe requires PangoCairo to run')
self.description = _('Play Tictactoe.') self.description = _('Play Tictactoe.')
self.config_dialog = TictactoePluginConfigDialog(self) self.config_dialog = partial(TicTacToeConfigDialog, self)
self.events_handlers = { self.events_handlers = {
'decrypted-message-received': ( 'decrypted-message-received': (
ged.PREGUI, self._nec_decrypted_message_received), ged.PREGUI, self._nec_decrypted_message_received),
@@ -109,8 +125,8 @@ class TictactoePlugin(GajimPlugin):
# Already existing session? # Already existing session?
conn = app.connections[control.account] conn = app.connections[control.account]
sessions = conn.get_sessions(control.contact.jid) sessions = conn.get_sessions(control.contact.jid)
tictactoes = [s for s in sessions if isinstance(s, tictactoes = [s for s in sessions if isinstance(
TicTacToeSession)] s, TicTacToeSession)]
if tictactoes: if tictactoes:
base.tictactoe = tictactoes[0] base.tictactoe = tictactoes[0]
base.enable_action(True) base.enable_action(True)
@@ -125,8 +141,8 @@ class TictactoePlugin(GajimPlugin):
def update_button_state(self, control): def update_button_state(self, control):
for base in self.controls: for base in self.controls:
if base.chat_control == control: if base.chat_control == control:
if control.contact.supports(NS_GAMES) and \ if (control.contact.supports(NS_GAMES) and
control.contact.supports(NS_GAMES_TICTACTOE): control.contact.supports(NS_GAMES_TICTACTOE)):
base.enable_action(True) base.enable_action(True)
else: else:
base.enable_action(False) base.enable_action(False)
@@ -166,13 +182,13 @@ class TictactoePlugin(GajimPlugin):
obj.session.received(obj.stanza) obj.session.received(obj.stanza)
game_invite = obj.stanza.getTag('invite', namespace=NS_GAMES) game_invite = obj.stanza.getTag('invite', namespace=NS_GAMES)
if game_invite: if game_invite:
account = obj.conn.name
game = game_invite.getTag('game') game = game_invite.getTag('game')
if game and game.getAttr('var') == NS_GAMES_TICTACTOE: if game and game.getAttr('var') == NS_GAMES_TICTACTOE:
session = obj.conn.make_new_session(obj.fjid, obj.thread_id, session = obj.conn.make_new_session(obj.fjid, obj.thread_id,
cls=TicTacToeSession) cls=TicTacToeSession)
self.show_request_dialog(obj, session) self.show_request_dialog(obj, session)
class Base(object): class Base(object):
def __init__(self, plugin, chat_control): def __init__(self, plugin, chat_control):
self.plugin = plugin self.plugin = plugin
@@ -230,9 +246,11 @@ class Base(object):
menu.remove(i) menu.remove(i)
break break
class InvalidMove(Exception): class InvalidMove(Exception):
pass pass
class TicTacToeSession(object): class TicTacToeSession(object):
def __init__(self, conn, jid, thread_id, type_): def __init__(self, conn, jid, thread_id, type_):
self.conn = conn self.conn = conn
@@ -266,7 +284,10 @@ class TicTacToeSession(object):
def get_to(self): def get_to(self):
to = str(self.jid) to = str(self.jid)
return app.get_jid_without_resource(to) + '/' + self.resource jid = app.get_jid_without_resource(to)
if self.resource:
jid += '/' + self.resource
return jid
def generate_thread_id(self): def generate_thread_id(self):
return ''.join( return ''.join(
@@ -274,7 +295,7 @@ class TicTacToeSession(object):
random.choice, 32)] random.choice, 32)]
) )
# initiate a session # Initiate a session
def begin(self, role_s='x'): def begin(self, role_s='x'):
self.rows = self.base.plugin.config['board_size'] self.rows = self.base.plugin.config['board_size']
self.cols = self.base.plugin.config['board_size'] self.cols = self.base.plugin.config['board_size']
@@ -343,23 +364,23 @@ class TicTacToeSession(object):
else: else:
self.cols = 3 self.cols = 3
# number in a row needed to win # Number in a row needed to win
if form.getField('strike'): if form.getField('strike'):
self.strike = int(form.getField('strike').getValues()[0]) self.strike = int(form.getField('strike').getValues()[0])
else: else:
self.strike = 3 self.strike = 3
# received an invitation # Received an invitation
def invited(self, msg): def invited(self, msg):
self.read_invitation(msg) self.read_invitation(msg)
# the number of the move about to be made # The number of the move about to be made
self.next_move_id = 1 self.next_move_id = 1
# display the board # Display the board
self.board = TicTacToeBoard(self, self.rows, self.cols) self.board = TicTacToeBoard(self, self.rows, self.cols)
# accept the invitation, join the game # Accept the invitation, join the game
response = nbxmpp.Message() response = nbxmpp.Message()
join = response.NT.join join = response.NT.join
@@ -377,7 +398,7 @@ class TicTacToeSession(object):
self.our_turn() self.our_turn()
# just sent an invitation, expecting a reply # Just sent an invitation, expecting a reply
def wait_for_invite_response(self, msg): def wait_for_invite_response(self, msg):
if msg.getTag('join', namespace=NS_GAMES): if msg.getTag('join', namespace=NS_GAMES):
self.board = TicTacToeBoard(self, self.rows, self.cols) self.board = TicTacToeBoard(self, self.rows, self.cols)
@@ -388,13 +409,15 @@ class TicTacToeSession(object):
self.their_turn() self.their_turn()
elif msg.getTag('decline', namespace=NS_GAMES): elif msg.getTag('decline', namespace=NS_GAMES):
app.nec.push_incoming_event(InformationEvent(None, conn=self.conn, app.nec.push_incoming_event(
level='info', pri_txt=_('Invitation refused'), InformationEvent(
sec_txt=_('%(name)s refused your invitation to play tic tac ' None,
'toe.') % {'name': self.name})) conn=self.conn,
level='info',
pri_txt=_('Invitation refused'),
sec_txt=_('%(name)s refused your invitation to play tic '
'tac toe.') % {'name': self.name}))
self.conn.delete_session(str(self.jid), self.thread_id) self.conn.delete_session(str(self.jid), self.thread_id)
if self.base:
self.base.button.set_active(False)
def decline_invitation(self): def decline_invitation(self):
msg = nbxmpp.Message() msg = nbxmpp.Message()
@@ -413,14 +436,14 @@ class TicTacToeSession(object):
self.received = self.game_over self.received = self.game_over
return True return True
# silently ignores any received messages # Silently ignores any received messages
def ignore(self, msg): def ignore(self, msg):
self.treat_terminate(msg) self.treat_terminate(msg)
def game_over(self, msg): def game_over(self, msg):
invite = msg.getTag('invite', namespace=NS_GAMES) invite = msg.getTag('invite', namespace=NS_GAMES)
# ignore messages unless they're renewing the game # Ignore messages unless they're renewing the game
if invite and invite.getAttr('type') == 'renew': if invite and invite.getAttr('type') == 'renew':
self.invited(msg) self.invited(msg)
@@ -440,14 +463,14 @@ class TicTacToeSession(object):
try: try:
self.board.mark(row, col, self.role_o) self.board.mark(row, col, self.role_o)
except InvalidMove as e: except InvalidMove:
# received an invalid move, end the game. # Received an invalid move, end the game.
self.board.cheated() self.board.cheated()
self.end_game('cheating') self.end_game('cheating')
self.received = self.game_over self.received = self.game_over
return return
# check win conditions # Check win conditions
if self.board.check_for_strike(self.role_o, row, col, self.strike): if self.board.check_for_strike(self.role_o, row, col, self.strike):
self.lost() self.lost()
elif self.board.full(): elif self.board.full():
@@ -461,7 +484,7 @@ class TicTacToeSession(object):
return self.received == self.ignore return self.received == self.ignore
def our_turn(self): def our_turn(self):
# ignore messages until we've made our move # Ignore messages until we've made our move
self.received = self.ignore self.received = self.ignore
self.board.set_title('your turn') self.board.set_title('your turn')
@@ -473,13 +496,13 @@ class TicTacToeSession(object):
def move(self, row, col): def move(self, row, col):
try: try:
self.board.mark(row, col, self.role_s) self.board.mark(row, col, self.role_s)
except InvalidMove as e: except InvalidMove:
log.warn('you made an invalid move') log.warn('You made an invalid move')
return return
self.send_move(row, col) self.send_move(row, col)
# check win conditions # Check win conditions
if self.board.check_for_strike(self.role_s, row, col, self.strike): if self.board.check_for_strike(self.role_s, row, col, self.strike):
self.won() self.won()
elif self.board.full(): elif self.board.full():
@@ -489,7 +512,7 @@ class TicTacToeSession(object):
self.their_turn() self.their_turn()
# sends a move message # Sends a move message
def send_move(self, row, column): def send_move(self, row, column):
msg = nbxmpp.Message() msg = nbxmpp.Message()
msg.setType('chat') msg.setType('chat')
@@ -506,7 +529,7 @@ class TicTacToeSession(object):
self.send(msg) self.send(msg)
# sends a termination message and ends the game # Sends a termination message and ends the game
def end_game(self, reason): def end_game(self, reason):
msg = nbxmpp.Message() msg = nbxmpp.Message()
@@ -547,22 +570,22 @@ class TicTacToeBoard:
self.rows = rows self.rows = rows
self.cols = cols self.cols = cols
self.board = [ [None] * self.cols for r in range(self.rows) ] self.board = [[None] * self.cols for r in range(self.rows)]
self.setup_window() self.setup_window()
# check if the last move (at row r and column c) won the game # Check if the last move (at row r and column c) won the game
def check_for_strike(self, p, r, c, strike): def check_for_strike(self, p, r, c, strike):
# number in a row: up and down, left and right # Number in a row: up and down, left and right
tallyI = 0 tallyI = 0
tally_ = 0 tally_ = 0
# number in a row: diagonal # Number in a row: diagonal
# (imagine L or F as two sides of a right triangle: L\ or F/) # (imagine L or F as two sides of a right triangle: L\ or F/)
tallyL = 0 tallyL = 0
tallyF = 0 tallyF = 0
# convert real columns to internal columns # Convert real columns to internal columns
r -= 1 r -= 1
c -= 1 c -= 1
@@ -570,19 +593,19 @@ class TicTacToeBoard:
r_in_range = 0 <= r+d < self.rows r_in_range = 0 <= r+d < self.rows
c_in_range = 0 <= c+d < self.cols c_in_range = 0 <= c+d < self.cols
# vertical check # Vertical check
if r_in_range: if r_in_range:
tallyI = tallyI + 1 tallyI = tallyI + 1
if self.board[r+d][c] != p: if self.board[r+d][c] != p:
tallyI = 0 tallyI = 0
# horizontal check # Horizontal check
if c_in_range: if c_in_range:
tally_ = tally_ + 1 tally_ = tally_ + 1
if self.board[r][c+d] != p: if self.board[r][c+d] != p:
tally_ = 0 tally_ = 0
# diagonal checks # Diagonal checks
if r_in_range and c_in_range: if r_in_range and c_in_range:
tallyL = tallyL + 1 tallyL = tallyL + 1
if self.board[r+d][c+d] != p: if self.board[r+d][c+d] != p:
@@ -598,11 +621,11 @@ class TicTacToeBoard:
return False return False
# is the board full? # Is the board full?
def full(self): def full(self):
for r in range(self.rows): for r in range(self.rows):
for c in range(self.cols): for c in range(self.cols):
if self.board[r][c] == None: if self.board[r][c] is None:
return False return False
return True return True
@@ -628,16 +651,16 @@ class TicTacToeBoard:
(width, height) = widget.get_size() (width, height) = widget.get_size()
# convert click co-ordinates to row and column # Convert click co-ordinates to row and column
row_height = height // self.rows row_height = height // self.rows
col_width = width // self.cols col_width = width // self.cols
row = int(event.y // row_height) + 1 row = int(event.y // row_height) + 1
column = int(event.x // col_width) + 1 column = int(event.x // col_width) + 1
self.session.move(row, column) self.session.move(row, column)
# this actually draws the board # This actually draws the board
def do_draw(self, widget, cr): def do_draw(self, widget, cr):
cr.set_source_rgb(1.0, 1.0, 1.0) cr.set_source_rgb(1.0, 1.0, 1.0)
@@ -647,7 +670,7 @@ class TicTacToeBoard:
(width, height) = self.win.get_size() (width, height) = self.win.get_size()
row_height = (height - text_height) // self.rows row_height = (height - text_height) // self.rows
col_width = width // self.cols col_width = width // self.cols
cr.set_source_rgb(0, 0, 0) cr.set_source_rgb(0, 0, 0)
cr.set_line_width(2) cr.set_line_width(2)
@@ -669,11 +692,11 @@ class TicTacToeBoard:
txt = _('You won !') txt = _('You won !')
elif self.state == 'lost': elif self.state == 'lost':
txt = _('You lost !') txt = _('You lost !')
elif self.state == 'resign': # other part resigned elif self.state == 'resign': # Other part resigned
txt = _('%(name)s capitulated') % {'name': self.session.name} txt = _('%(name)s capitulated') % {'name': self.session.name}
elif self.state == 'cheated': # other part cheated elif self.state == 'cheated': # Other part cheated
txt = _('%(name)s cheated') % {'name': self.session.name} txt = _('%(name)s cheated') % {'name': self.session.name}
else: #draw else: # Draw
txt = _('It\'s a draw') txt = _('It\'s a draw')
layout.set_text(txt, -1) layout.set_text(txt, -1)
# Inform Pango to re-layout the text with the new transformation # Inform Pango to re-layout the text with the new transformation
@@ -689,9 +712,9 @@ class TicTacToeBoard:
def draw_x(self, cr, row, col, row_height, col_width): def draw_x(self, cr, row, col, row_height, col_width):
if self.session.role_s == 'x': if self.session.role_s == 'x':
color = '#3d79fb' # out color = '#3d79fb' # Out
else: else:
color = '#f03838' # red color = '#f03838' # Red
rgba = Gdk.RGBA() rgba = Gdk.RGBA()
rgba.parse(color) rgba.parse(color)
cr.set_source_rgba(rgba.red, rgba.green, rgba.blue, rgba.alpha) cr.set_source_rgba(rgba.red, rgba.green, rgba.blue, rgba.alpha)
@@ -724,12 +747,12 @@ class TicTacToeBoard:
x = col_width * (col + 0.5) x = col_width * (col + 0.5)
y = row_height * (row + 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.arc(x, y, row_height/4, 0, 2.0*3.2) # Slightly further than 2*pi
cr.set_line_width(row_height / 5) cr.set_line_width(row_height / 5)
cr.stroke() cr.stroke()
# mark a move on the board # Mark a move on the board
def mark(self, row, column, player): def mark(self, row, column, player):
if self.board[row-1][column-1]: if self.board[row-1][column-1]:
raise InvalidMove raise InvalidMove
@@ -738,7 +761,7 @@ class TicTacToeBoard:
self.win.queue_draw() self.win.queue_draw()
def set_title(self, suffix = None): def set_title(self, suffix=None):
str_ = self.title_prefix str_ = self.title_prefix
if suffix: if suffix:
@@ -764,24 +787,3 @@ class TicTacToeBoard:
def cheated(self): def cheated(self):
self.state == 'cheated' self.state == 'cheated'
self.win.queue_draw() self.win.queue_draw()
class TictactoePluginConfigDialog(GajimPluginConfigDialog):
def init(self):
self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path(
'config_dialog.ui')
self.xml = Gtk.Builder()
self.xml.set_translation_domain('gajim_plugins')
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH, ['vbox1'])
self.board_size_spinbutton = self.xml.get_object('board_size')
self.board_size_spinbutton.get_adjustment().configure(3, 3, 10, 1, 1, 0)
vbox = self.xml.get_object('vbox1')
self.get_child().pack_start(vbox, True, True, 0)
self.xml.connect_signals(self)
def on_run(self):
self.board_size_spinbutton.set_value(self.plugin.config['board_size'])
def board_size_value_changed(self, spinbutton):
self.plugin.config['board_size'] = int(spinbutton.get_value())