# TOFU
# Copyright (C) 2005 Jean-Baptiste LAMY
#
# This program 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 2 of the License, or
# (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""tofu.client
This module implements a Tofu client.
To lauch a server, create a GameInterface instance, import this module,
and then call serve_forever().
"""
from twisted.internet.protocol import DatagramProtocol, Protocol, Factory, ClientFactory
from twisted.protocols.basic import LineReceiver, NetstringReceiver
from twisted.python.failure import Failure
#from twisted.internet import reactor
import twisted.internet.selectreactor
#reactor = twisted.internet.selectreactor.SelectReactor()
import sys, struct
import tofu
try: set
except: from sets import Set as set
class ClientServerError(StandardError): pass
class UDP(DatagramProtocol):
def startProtocol(self):
a = "hello"
self.transport.write(a, ("127.0.0.1", 9999))
def datagramReceived(self, data, (host, port)):
print "UDP received %r from %s:%d" % (data, host, port)
PLANNED_ARRIVAL_UIDS = set()
WAITERS = {}
class Waiter(object):
def __init__(self, callback = lambda *args: None):
self.callback = callback
self.uniques = set([])
self.nb_waited_unique = 0
def wait_for(self, uid, ask_for_it = 1):
if tofu.Unique.hasuid(uid): # Already available
self.uniques.add(tofu.Unique.getbyuid(uid))
else:
#if ask_for_it:
# if not uid in tofu.NOTIFIER.uids_arrival_planned:
# tofu.NOTIFIER.ask_unique(uid)
self.nb_waited_unique += 1
waiters = WAITERS.get(uid)
if not waiters: waiters = WAITERS[uid] = []
waiters.append(self)
def arrived(self, unique):
self.uniques.add(unique)
self.nb_waited_unique -= 1
if self.nb_waited_unique == 0: self.callback(*self.uniques)
def start(self):
if self.nb_waited_unique == 0: self.callback(*self.uniques)
MAX_LENGTH = 99999
class Notifier(NetstringReceiver, tofu.Notifier):
def __init__(self):
tofu.Notifier.__init__(self)
self.errors = []
self.MAX_LENGTH = MAX_LENGTH
self.uids_arrival_planned = set()
def connectionMade(self):
self.sendString(tofu.CODE_CHECK_VERSION + tofu.VERSION)
tofu.GAME_INTERFACE.ready(self)
def login_player(self, filename, password, client_side_data = ""):
self.sendString("%s%s\n%s\n%s" % (tofu.CODE_LOGIN_PLAYER, filename, password, client_side_data))
def logout_player(self):
self.sendString(tofu.CODE_LOGOUT_PLAYER)
def stringReceived(self, data):
#print "TCP receive:", repr(data)
#print len(data)
code = data[0]
#print "* Tofu * receiving code %s..." % code
if code == tofu.CODE_DATA_STATE:
state = tofu.YourState.undump(data[5:])
uid = struct.unpack("!i", data[1:5])[0]
if tofu.Unique.hasuid(uid): tofu.Unique.getbyuid(uid).doer.push(state)
elif state.is_crucial():
pass
# XXX
# waiter = Waiter(lambda mobile: mobile.doer.push(state))
# waiter.wait_for(uid)
# waiter.start()
elif code == tofu.CODE_OWN_CONTROL:
uid = struct.unpack("!i", data[1:])[0]
print "* Tofu * Owning mobile %s..." % uid
#def own_control(mobile):
# print "own_control", mobile.level
# tofu.IDLER.next_round_tasks.append(mobile.control_owned)
#waiter = Waiter(own_control)
waiter = Waiter(lambda mobile: mobile.control_owned())
waiter.wait_for(uid, 0)
waiter.start()
elif code == tofu.CODE_REMOVE_MOBILE:
uid = struct.unpack("!i", data[1:])[0]
print "* Tofu * Removing mobile %s..." % uid
def remove_mobile(mobile):
mobile.level.remove_mobile(mobile)
mobile.discard()
#def remove_mobile(mobile):
# def remove_mobile2():
# print "* Tofu * Mobile %s removed !" % mobile.uid
# mobile.level.remove_mobile(mobile)
# mobile.discard()
# tofu.IDLER.next_round_tasks.append(remove_mobile2)
waiter = Waiter(remove_mobile)
waiter.wait_for(uid, 0)
waiter.start()
elif code == tofu.CODE_ADD_MOBILE:
mobile_uid = struct.unpack("!i", data[1:5])[0]
level_uid = struct.unpack("!i", data[5:9])[0]
print "* Tofu * Adding mobile %s in level %s..." % (mobile_uid, level_uid)
def add_mobile(*args):
mobile = tofu.Unique.getbyuid(mobile_uid)
level = tofu.Unique.getbyuid(level_uid )
if not mobile in level.mobiles: mobile.level.add_mobile(mobile)
waiter = Waiter(add_mobile)
waiter.wait_for(mobile_uid)
waiter.wait_for(level_uid)
waiter.start()
elif code == tofu.CODE_DATA_UNIQUE:
print "* Tofu * Receiving unique..."
unique = tofu.Unique.undump(data[1:])
unique.received()
assert (not hasattr(unique, "level")) or (not unique.level) or (unique.level in unique.level._alls2.values()), "Level sent with non-level unique !"
self.arrived(unique)
elif code == tofu.CODE_ENTER_LEVEL:
uid = struct.unpack("!i", data[1:])[0]
print "* Tofu * Entering level %s..." % uid
#self.uids_arrival_planned.add(uid) # The server will send it
# Previous level is outdated => drop it
if tofu.Unique.hasuid(uid): tofu.Unique.getbyuid(uid).set_active(0)
waiter = Waiter(lambda *uniques: None)
waiter.wait_for(uid)
waiter.start()
elif code == tofu.CODE_ERROR:
print "* Tofu * Server error: %s" % data[1:]
#self.errors.append(data[1:])
raise ClientServerError(data[1:])
def arrived(self, unique):
print "* Tofu * Received unique %s %s." % (unique.uid, unique)
waiters = WAITERS.get(unique.uid)
if waiters:
for waiter in waiters: waiter.arrived(unique)
del WAITERS[unique.uid]
if hasattr(unique, "mobiles"):
for mobile in unique.mobiles: self.arrived(mobile)
self.uids_arrival_planned.discard(unique.uid)
def ask_unique(self, uid):
print "* Tofu * Ask for UID %s..." % uid
self.uids_arrival_planned.add(uid)
self.sendString(tofu.CODE_ASK_UNIQUE + struct.pack("!i", uid))
def notify_action(self, mobile, action):
self.sendString(tofu.CODE_DATA_ACTION + "%s%s" % (mobile.dumpuid(), action.dump()))
def notify_add_mobile (self, mobile): pass
def notify_remove_mobile(self, mobile): pass
def check_level_activity(self, level):
for mobile in level.mobiles:
if not mobile.controller.remote:
level.set_active(1)
return
# No local user for this level => we can inactive it
level.set_active(0)
def notify_discard(self, unique):
# The client NEVER saves data
pass
class TCPFactory(ClientFactory):
protocol = Notifier
def clientConnectionFailed(self, connector, reason):
m = reason.getErrorMessage()
print "* Tofu * Connection failed:", m
tofu.GAME_INTERFACE.network_error(m)
def clientConnectionLost(self, connector, reason):
m = reason.getErrorMessage()
print "* Tofu * Connection lost:", m
tofu.GAME_INTERFACE.network_error(m)
def serve_forever(host = "localhost", port = 6900, *args, **kargs):
"""serve_forever(host = "localhost", port = 6900, *ARGS, **KARGS)
Starts a game client, and connect to HOST on port PORT.
ARGS and KARGS are passed to GameInterface.__init__()."""
#reactor.listenUDP(0, UDP())
#twisted.internet.selectreactor.install()
reactor = twisted.internet.reactor
tofu.YourGameInterface(*args, **kargs)
factory = TCPFactory()
twisted.internet.reactor.connectTCP(host, port, factory)
try:
return tofu.IDLER.idle()
finally:
tofu.NOTIFIER.transport.loseConnection()
tofu.IDLER.reactor.iterate()
tofu.IDLER.reactor.runUntilCurrent()
tofu.IDLER.reactor.removeAll()
tofu.IDLER.reactor.disconnectAll()
# We need to start the reactor in order to be able to stop it !!!
# Else the program cannot ends normally.
tofu.IDLER.reactor.callLater(0.0, reactor.stop); reactor.run()
syntax highlighted by Code2HTML, v. 0.9.1