From f342bfb72208e9e44785a1b1798b3432e89b103c Mon Sep 17 00:00:00 2001 From: wurstsalat Date: Tue, 26 Oct 2021 22:09:00 +0200 Subject: [PATCH] [tictactoe] Remove plugin (may be ported later) --- tictactoe/__init__.py | 1 - tictactoe/config_dialog.py | 55 --- tictactoe/manifest.ini | 9 - tictactoe/plugin.py | 784 ------------------------------------- tictactoe/tictactoe.png | Bin 1424 -> 0 bytes 5 files changed, 849 deletions(-) delete mode 100644 tictactoe/__init__.py delete mode 100644 tictactoe/config_dialog.py delete mode 100644 tictactoe/manifest.ini delete mode 100644 tictactoe/plugin.py delete mode 100644 tictactoe/tictactoe.png diff --git a/tictactoe/__init__.py b/tictactoe/__init__.py deleted file mode 100644 index e5664bc..0000000 --- a/tictactoe/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .plugin import TictactoePlugin diff --git a/tictactoe/config_dialog.py b/tictactoe/config_dialog.py deleted file mode 100644 index 9ff4f02..0000000 --- a/tictactoe/config_dialog.py +++ /dev/null @@ -1,55 +0,0 @@ -# -# 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 . - -from gi.repository import GObject -from gi.repository import Gtk - -from gajim.gui.settings import SettingsDialog -from gajim.gui.settings import SpinSetting -from gajim.gui.const import Setting -from gajim.gui.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) diff --git a/tictactoe/manifest.ini b/tictactoe/manifest.ini deleted file mode 100644 index 23a3166..0000000 --- a/tictactoe/manifest.ini +++ /dev/null @@ -1,9 +0,0 @@ -[info] -name: Tic tac toe -short_name: tictactoe -version: 1.8.2 -description: Play Tic-tac-toe. -authors = Yann Leboulanger -homepage = https://dev.gajim.org/gajim/gajim-plugins/wikis/TictactoePlugin -min_gajim_version: 1.4.0-dev1 -max_gajim_version: 1.4.90 diff --git a/tictactoe/plugin.py b/tictactoe/plugin.py deleted file mode 100644 index 487368b..0000000 --- a/tictactoe/plugin.py +++ /dev/null @@ -1,784 +0,0 @@ -# -# Copyright (C) 2011 Yann Leboulanger -# -# 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; version 3 only. -# -# 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 . -# - -''' -Tictactoe plugin. - -:author: Yann Leboulanger -:since: 21 November 2011 -:copyright: Copyright (2011) Yann Leboulanger -:license: GPL -''' - -import string -import itertools -import random - -from functools import partial - -from gi.repository import Gtk -from gi.repository import Gdk -from gi.repository import Gio -from gi.repository import GLib - -import nbxmpp - -from gajim.common import app -from gajim.common import ged -from gajim.common.connection_handlers_events import InformationEvent - -from gajim.gui.dialogs import DialogButton -from gajim.gui.dialogs import ConfirmationDialog - -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_TICTACTOE = NS_GAMES + '/tictactoe' - - -class TictactoePlugin(GajimPlugin): - @log_calls('TictactoePlugin') - 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.config_dialog = partial(TicTacToeConfigDialog, self) - self.events_handlers = { - 'decrypted-message-received': ( - ged.PREGUI, self._on_message_received), - } - - self.gui_extension_points = { - 'chat_control': (self.connect_with_chat_control, - self.disconnect_from_chat_control), - 'chat_control_base_update_toolbar': ( - self.update_button_state, None), - 'update_caps': (self._update_caps, None), - } - - self.config_default_values = { - 'board_size': (5, ''), - } - - self.controls = [] - self.announce_caps = True - - @log_calls('TictactoePlugin') - def _update_caps(self, _account, features): - if not self.announce_caps: - return - - features.append(NS_GAMES) - features.append(NS_GAMES_TICTACTOE) - - @log_calls('TictactoePlugin') - def activate(self): - self.announce_caps = True - for con in app.connections.values(): - con.get_module('Caps').update_caps() - - @log_calls('TictactoePlugin') - def deactivate(self): - self.announce_caps = False - for con in app.connections.values(): - con.get_module('Caps').update_caps() - - @log_calls('TictactoePlugin') - def connect_with_chat_control(self, control): - if not control.is_chat: - return - - base = Base(self, control) - self.controls.append(base) - # Already existing session? - conn = app.connections[control.account] - sessions = conn.get_sessions(control.contact.jid) - tictactoes = [s for s in sessions if isinstance( - s, TicTacToeSession)] - if tictactoes: - base.tictactoe = tictactoes[0] - base.enable_action(True) - - @log_calls('TictactoePlugin') - def disconnect_from_chat_control(self, _chat_control): - for base in self.controls: - base.disconnect_from_chat_control() - self.controls = [] - - @log_calls('TictactoePlugin') - def update_button_state(self, control): - for base in self.controls: - if base.chat_control == control: - if (control.contact.supports(NS_GAMES) and - control.contact.supports(NS_GAMES_TICTACTOE)): - base.enable_action(True) - else: - base.enable_action(False) - - @log_calls('TictactoePlugin') - def show_request_dialog(self, obj, session): - def _on_accept(): - session.invited(obj.stanza) - - def _on_decline(): - session.decline_invitation() - - account = obj.conn.name - client = app.get_client(account) - contact = client.get_module('Contacts').get_contact(obj.jid) - - ConfirmationDialog( - _('Incoming Tictactoe'), - _('Incoming Tictactoe Invitation'), - _('%(name)s (%(jid)s) wants to play tictactoe with you.') % { - 'name': contact.name, 'jid': obj.jid}, - [DialogButton.make('Cancel', - text=_('_Decline'), - callback=_on_decline), - DialogButton.make('OK', - text=_('_Accept'), - callback=_on_accept)], - modal=False, - transient_for=app.window).show() - - @log_calls('TictactoePlugin') - def _on_message_received(self, event): - if isinstance(event.session, TicTacToeSession): - event.session.received(event.stanza) - game_invite = event.stanza.getTag('invite', namespace=NS_GAMES) - if game_invite: - game = game_invite.getTag('game') - if game and game.getAttr('var') == NS_GAMES_TICTACTOE: - session = event.conn.make_new_session( - event.fjid, event.properties.thread, cls=TicTacToeSession) - self.show_request_dialog(event, session) - - -class Base(): - def __init__(self, plugin, chat_control): - self.plugin = plugin - self.chat_control = chat_control - self.contact = self.chat_control.contact - self.account = self.chat_control.account - self.fjid = self.contact.jid - self.add_action() - self.tictactoe = None - - def add_action(self): - action_name = 'toggle-tictactoe-' + self.chat_control.control_id - act = Gio.SimpleAction.new_stateful( - action_name, None, GLib.Variant.new_boolean(False)) - act.connect('change-state', self.on_tictactoe_button_toggled) - app.window.add_action(act) - - self.chat_control.control_menu.append( - 'Tic Tac Toe', 'win.' + action_name) - - def enable_action(self, state): - action_name = 'toggle-tictactoe-' + self.chat_control.control_id - app.window.lookup_action(action_name).set_enabled(state) - - def on_tictactoe_button_toggled(self, action, param): - """ - Popup whiteboard - """ - action.set_state(param) - state = param.get_boolean() - if state: - if not self.tictactoe: - self.start_tictactoe() - else: - self.stop_tictactoe('resign') - - def start_tictactoe(self): - self.tictactoe = app.connections[self.account].make_new_session( - self.fjid, cls=TicTacToeSession) - self.tictactoe.base = self - self.tictactoe.begin() - - def stop_tictactoe(self, reason=None): - self.tictactoe.end_game(reason) - if hasattr(self.tictactoe, 'board'): - self.tictactoe.board.win.destroy() - self.tictactoe = None - - def disconnect_from_chat_control(self): - menu = self.chat_control.control_menu - for item in range(menu.get_n_items()): - label = menu.get_item_attribute_value(item, 'label') - if label.get_string() == 'Tic Tac Toe': - menu.remove(item) - break - - -class InvalidMove(Exception): - pass - - -class TicTacToeSession(): - def __init__(self, conn, jid, thread_id, type_): - self.conn = conn - self.jid = jid - self.type_ = type_ - self.resource = jid.resource - - if thread_id: - self.received_thread_id = True - self.thread_id = thread_id - else: - self.received_thread_id = False - self.thread_id = self.generate_thread_id() - - client = app.get_client(conn.name) - contact = client.get_module('Contacts').get_contact(jid) - self.name = contact.name - self.base = None - self.control = None - self.enable_encryption = False - - @staticmethod - def is_loggable(): - return False - - def send(self, msg): - if self.thread_id: - msg.NT.thread = self.thread_id - - msg.setAttr('to', self.get_to()) - self.conn.send_stanza(msg) - - def get_to(self): - to = str(self.jid) - jid = app.get_jid_without_resource(to) - if self.resource: - jid += '/' + self.resource - return jid - - @staticmethod - def generate_thread_id(): - return ''.join( - [f(string.ascii_letters) for f in itertools.repeat( - random.choice, 32)] - ) - - # Initiate a session - def begin(self, role_s='x'): - self.rows = self.base.plugin.config['board_size'] - self.cols = self.base.plugin.config['board_size'] - - self.role_s = role_s - - self.strike = self.base.plugin.config['board_size'] - - if self.role_s == 'x': - self.role_o = 'o' - else: - self.role_o = 'x' - - self.send_invitation() - - self.next_move_id = 1 - self.received = self.wait_for_invite_response - - def send_invitation(self): - msg = nbxmpp.Message() - - invite = msg.NT.invite - invite.setNamespace(NS_GAMES) - invite.setAttr('type', 'new') - - game = invite.NT.game - game.setAttr('var', NS_GAMES_TICTACTOE) - - x = nbxmpp.DataForm(typ='submit') - f = x.setField('role') - f.setType('list-single') - f.setValue('x') - f = x.setField('rows') - f.setType('text-single') - f.setValue(str(self.base.plugin.config['board_size'])) - f = x.setField('cols') - f.setType('text-single') - f.setValue(str(self.base.plugin.config['board_size'])) - f = x.setField('strike') - f.setType('text-single') - f.setValue(str(self.base.plugin.config['board_size'])) - - game.addChild(node=x) - - self.send(msg) - - def read_invitation(self, msg): - invite = msg.getTag('invite', namespace=NS_GAMES) - game = invite.getTag('game') - x = game.getTag('x', namespace='jabber:x:data') - - form = nbxmpp.DataForm(node=x) - - if form.getField('role'): - self.role_o = form.getField('role').getValues()[0] - else: - self.role_o = 'x' - - if form.getField('rows'): - self.rows = int(form.getField('rows').getValues()[0]) - else: - self.rows = 3 - - if form.getField('cols'): - self.cols = int(form.getField('cols').getValues()[0]) - else: - self.cols = 3 - - # Number in a row needed to win - if form.getField('strike'): - self.strike = int(form.getField('strike').getValues()[0]) - else: - self.strike = 3 - - # Received an invitation - def invited(self, msg): - self.read_invitation(msg) - - # The number of the move about to be made - self.next_move_id = 1 - - # Display the board - self.board = TicTacToeBoard(self, self.rows, self.cols) - - # Accept the invitation, join the game - response = nbxmpp.Message() - - join = response.NT.join - join.setNamespace(NS_GAMES) - - self.send(response) - - if self.role_o == 'x': - self.role_s = 'o' - - self.their_turn() - else: - self.role_s = 'x' - self.role_o = 'o' - - self.our_turn() - - # Just sent an invitation, expecting a reply - def wait_for_invite_response(self, msg): - if msg.getTag('join', namespace=NS_GAMES): - self.board = TicTacToeBoard(self, self.rows, self.cols) - - if self.role_s == 'x': - self.our_turn() - else: - self.their_turn() - - elif msg.getTag('decline', namespace=NS_GAMES): - app.nec.push_incoming_event( - InformationEvent( - None, - conn=self.conn, - level='info', - pri_txt=_('Invitation Declined'), - sec_txt=_('%(name)s declined your invitation to play Tic ' - 'Tac Toe.') % {'name': self.name})) - self.conn.delete_session(str(self.jid), self.thread_id) - - def decline_invitation(self): - msg = nbxmpp.Message() - - terminate = msg.NT.decline - terminate.setNamespace(NS_GAMES) - - self.send(msg) - - def treat_terminate(self, msg): - term = msg.getTag('terminate', namespace=NS_GAMES) - if term: - if term.getAttr('reason') == 'resign': - self.board.state = 'resign' - self.board.win.queue_draw() - self.received = self.game_over - return True - - # Silently ignores any received messages - def ignore(self, msg): - self.treat_terminate(msg) - - def game_over(self, msg): - invite = msg.getTag('invite', namespace=NS_GAMES) - - # Ignore messages unless they're renewing the game - if invite and invite.getAttr('type') == 'renew': - self.invited(msg) - - def wait_for_move(self, msg): - if self.treat_terminate(msg): - return - turn = msg.getTag('turn', namespace=NS_GAMES) - move = turn.getTag('move', namespace=NS_GAMES_TICTACTOE) - - row = int(move.getAttr('row')) - col = int(move.getAttr('col')) - id_ = int(move.getAttr('id')) - - if id_ != self.next_move_id: - log.warning('unexpected move id, lost a move somewhere?') - return - - try: - self.board.mark(row, col, self.role_o) - except InvalidMove: - # Received an invalid move, end the game. - self.board.cheated() - self.end_game('cheating') - self.received = self.game_over - return - - # Check win conditions - if self.board.check_for_strike(self.role_o, row, col, self.strike): - self.lost() - elif self.board.full(): - self.drawn() - else: - self.next_move_id += 1 - - self.our_turn() - - def is_my_turn(self): - return self.received == self.ignore - - def our_turn(self): - # Ignore messages until we've made our move - self.received = self.ignore - self.board.set_title('your turn') - - def their_turn(self): - self.received = self.wait_for_move - self.board.set_title('their turn') - - # called when the board receives input - def move(self, row, col): - try: - self.board.mark(row, col, self.role_s) - except InvalidMove: - log.warning('You made an invalid move') - return - - self.send_move(row, col) - - # Check win conditions - if self.board.check_for_strike(self.role_s, row, col, self.strike): - self.won() - elif self.board.full(): - self.drawn() - else: - self.next_move_id += 1 - - self.their_turn() - - # Sends a move message - def send_move(self, row, column): - msg = nbxmpp.Message() - msg.setType('chat') - - turn = msg.NT.turn - turn.setNamespace(NS_GAMES) - - move = turn.NT.move - move.setNamespace(NS_GAMES_TICTACTOE) - - move.setAttr('row', str(row)) - move.setAttr('col', str(column)) - move.setAttr('id', str(self.next_move_id)) - - self.send(msg) - - # Sends a termination message and ends the game - def end_game(self, reason): - msg = nbxmpp.Message() - - terminate = msg.NT.terminate - terminate.setNamespace(NS_GAMES) - terminate.setAttr('reason', reason) - - self.send(msg) - - self.received = self.game_over - - def won(self): - self.end_game('won') - self.board.won() - - def lost(self): - self.end_game('lost') - self.board.lost() - - def drawn(self): - self.end_game('draw') - self.board.drawn() - - -class DrawBoard(Gtk.DrawingArea): - def __init__(self): - Gtk.DrawingArea.__init__(self) - self.set_size_request(200, 200) - self.set_property('expand', True) - - -class TicTacToeBoard: - def __init__(self, session, rows, cols): - self.session = session - - self.state = 'None' - - self.rows = rows - self.cols = cols - - self.board = [[None] * self.cols for r in range(self.rows)] - - self.setup_window() - - # Check if the last move (at row r and column c) won the game - def check_for_strike(self, p, r, c, strike): - # Number in a row: up and down, left and right - tallyI = 0 - tally_ = 0 - - # Number in a row: diagonal - # (imagine L or F as two sides of a right triangle: L\ or F/) - tallyL = 0 - tallyF = 0 - - # Convert real columns to internal columns - r -= 1 - c -= 1 - - for d in range(-strike, strike): - r_in_range = 0 <= r+d < self.rows - c_in_range = 0 <= c+d < self.cols - - # Vertical check - if r_in_range: - tallyI = tallyI + 1 - if self.board[r+d][c] != p: - tallyI = 0 - - # Horizontal check - if c_in_range: - tally_ = tally_ + 1 - if self.board[r][c+d] != p: - tally_ = 0 - - # Diagonal checks - if r_in_range and c_in_range: - tallyL = tallyL + 1 - if self.board[r+d][c+d] != p: - tallyL = 0 - - if r_in_range and 0 <= c-d < self.cols: - tallyF = tallyF + 1 - if self.board[r+d][c-d] != p: - tallyF = 0 - - if any([t == strike for t in (tallyL, tallyF, tallyI, tally_)]): - return True - - return False - - # Is the board full? - def full(self): - for r in range(self.rows): - for c in range(self.cols): - if self.board[r][c] is None: - return False - - return True - - def setup_window(self): - self.win = Gtk.Window() - draw = DrawBoard() - self.win.add(draw) - - self.title_prefix = _('Tic Tac Toe with %s') % self.session.name - self.set_title() - - self.win.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) - self.win.connect('button-press-event', self.clicked) - - draw.connect('draw', self.do_draw) - - self.win.show_all() - - def clicked(self, widget, event): - if not self.session.is_my_turn(): - return - - (width, height) = 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) - - # This actually draws the board - def do_draw(self, _widget, cr): - cr.set_source_rgb(1.0, 1.0, 1.0) - - layout = PangoCairo.create_layout(cr) - text_height = layout.get_pixel_extents()[1].height - - (width, height) = self.win.get_size() - - row_height = (height - text_height) // self.rows - col_width = width // self.cols - - cr.set_source_rgb(0, 0, 0) - cr.set_line_width(2) - for x in range(1, self.cols): - cr.move_to(col_width * x, 0) - cr.line_to(col_width * x, height - text_height) - for x in range(1, self.rows): - cr.move_to(0, row_height * x) - cr.line_to(width, row_height * x) - cr.stroke() - - cr.move_to(0, height - text_height) - if self.state == 'None': - if self.session.is_my_turn(): - txt = _('It’s your turn') - else: - txt = _('It’s %(name)s\'s turn') % {'name': self.session.name} - elif self.state == 'won': - txt = _('You won!') - elif self.state == 'lost': - txt = _('You lost!') - elif self.state == 'resign': # Other part resigned - txt = _('%(name)s capitulated') % {'name': self.session.name} - elif self.state == 'cheated': # Other part cheated - txt = _('%(name)s cheated') % {'name': self.session.name} - else: # Draw - txt = _('It’s a draw') - layout.set_text(txt, -1) - # Inform Pango to re-layout the text with the new transformation - PangoCairo.update_layout(cr, layout) - PangoCairo.show_layout(cr, layout) - - for i in range(self.rows): - for j in range(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): - if self.session.role_s == 'x': - color = '#3d79fb' # Out - else: - color = '#f03838' # Red - rgba = Gdk.RGBA() - rgba.parse(color) - cr.set_source_rgba(rgba.red, rgba.green, rgba.blue, rgba.alpha) - - 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): - if self.session.role_s == 'o': - color = '#3d79fb' # out - else: - color = '#f03838' # red - rgba = Gdk.RGBA() - rgba.parse(color) - cr.set_source_rgba(rgba.red, rgba.green, rgba.blue, rgba.alpha) - - 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 - self.board[row-1][column-1] = player - - self.win.queue_draw() - - def set_title(self, suffix=None): - str_ = self.title_prefix - - if suffix: - str_ += ': ' + suffix - - self.win.set_title(str_) - - def won(self): - self.state = 'won' - self.set_title(_('You won!')) - self.win.queue_draw() - - def lost(self): - self.state = 'lost' - self.set_title(_('You’ve lost.')) - self.win.queue_draw() - - def drawn(self): - self.state = 'drawn' - self.win.set_title(_('%s: it’s a draw.') % self.title_prefix) - self.win.queue_draw() - - def cheated(self): - self.state == 'cheated' - self.win.queue_draw() diff --git a/tictactoe/tictactoe.png b/tictactoe/tictactoe.png deleted file mode 100644 index e91fd3fcc624340cf8d287c05da2237e34676f63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1424 zcmV;B1#kL^P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2ipr3 z6bw6}t9^6;00j_9L_t(Y$BouqjGa{#2HxBlE*W* zmwc#;G#k<-wfgIlYUZI}DfSSbyz$1z=0jYLN65<{_z3=*IT)P4NN^-bsShw1+*$Dc zyme~|UdTO%sY)e{lhf2xdte^GCT6fYQv`>CuO?0h`!joU?;u-dGyFD2@M<0#+mX4d z!a?YEyQfe&eE8&ifYUvaiq-f6)(0=l{tRA3&fJysc-@z|9#x~Gzq5S#o_yt%{lla& zR7mpxHu|7xDK_*=v}O+A@!)u-ezAs$mxJdbVl*Zvzmrd%yt!Pru2QR|^SzjC0eg$~ zAfC?51do?&$_!;TVmfG~IROu4vZ0|}sF|60I&yhMvuT=8sif`#fEUQk;I-gDa82+e z?#DNSk0$<8%n7(H_;iepj^?ZD*TspMduY`ZMb>HNB}=L?IoT+Xs@0U*?QRgMm;EeB zQK7LW4DbD=4?p$+=ZBgq-CO+rC4PN5-NB%-2G_4NQ=GG6l2%Q%P8u55JiUyWZP<@p z#0=Ey)#Ad1R;Ld*YmeaHc(ymjMTbgn2DCd^iM0i;rhM?6B^_FUGCI>~p!#OJsXKIxef%BD0n!z|)M6)-}on~+C?H+!oLMQs;@(;XDqeNrHe1%D=aTUYO`Ft|V7=DVz z$U>c!XiUrrysqSd9C3F}>6UJ_S}a{!t)r+G`!DVL49)RhTXJh=9dQPiF9Psl@K>+{ z*XERV=dyev+U*Y(Mg1(1a#5{!xdK1V0|WPF3j7dT6aT^~Y+4jx**n>tnI{rE^U|ev zi&z~+@s06I?mtBqVRcZ$WNx>AfbU^r=0Jb1x3PIaz}(Z(5&SIiDpV#WUWtK$-{!Ks zW9+RJ8zHw^Ly2R-7o$=+i2E`po1E!$bVg6?7tC-8K8LmMX@q)xud=KSPS30%bh7-8PSGaYMCrj3nFIa@7%v9zj+*h*g;-6OQ=BQM@zeM|JUlH4V`iFD7-NCnluLqwG zE+wWH16U+lnY-|8@Jz{{)oOo_h$$-v_r=)M=}t@$Epo|t+1s#3 zIr$PQXL@;btE6exzj=>mwv71r&kOvCc%4>>P7-AybxYBW$R*l+Ej>#7p=izICM|U4 zWCE*u%Vob|=G+d)YKMGgdYc!EKl<)3PFlWa#Pjc1oHe@adw2n#jSS}>*}ge*EORuo zHuGHOC;y`~^IJ}}TD88IemLNU3GBxDlEZy5crKQYyJl(jMY0h!BKhWzII@|1XBe+(N(h`%IPnEsz?M2kE_pHD|O>=I}v+#~N e<05*a1^x@Xt>`T2HhtFs0000