1
################################################################################
2
# copyright 2008 Gabriel Pettier <gabriel.pettier@gmail.com> #
4
# This file is part of ultimate-smash-friends #
6
# ultimate-smash-friends is free software: you can redistribute it and/or #
7
# modify it under the terms of the GNU General Public License as published by #
8
# the Free Software Foundation, either version 3 of the License, or (at your #
9
# option) any later version. #
11
# ultimate-smash-friends is distributed in the hope that it will be useful, but#
12
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or#
13
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
16
# You should have received a copy of the GNU General Public License along with #
17
# ultimate-smash-friends. If not, see <http://www.gnu.org/licenses/>. #
18
################################################################################
22
from threading import Thread, Semaphore
23
from time import sleep, time
26
from config import config
27
from debug_utils import LOG
32
a.connect(nick='1', character='stick-tiny', votemap='baselevel')
33
b.connect(nick='2', character='stick-red', votemap='maryoland')
35
class NetworkError (RuntimeError):
49
def server_thread(client_socket, server, num, game_instance):
51
This fonction is called by the serverside to deal with a connected client,
52
it will translate recieved messages to the game object.
56
while not server.quit:
57
# 10 network updates per seconds is already a lot.
58
while time() < prec_time + 0.10:
61
# we use Fixed Lenght (2 chars) messages, as we know this is always a
62
# player number on the client + a key (A, B, U=UP, D=Down, L=Left,
65
while len(msg) < MSGLEN:
66
chunk = client_socket.recv(MSGLEN - len(msg))
68
LOG().log('error client, link broken')
74
if msg == 'AU' and game_instance.accepting:
77
chunk = client_socket.recv(5)
79
LOG().log('error client, link broken')
86
player = server.new_player_from_string(msg[2:])
87
server.players[num] = (
96
server.votemap[num]=player['votemap']
101
key = 'PL' + str(num * 10 + int(msg[0])) + '_'
102
# FIXME: maybe save this dict somewhere as a class property.
103
key += key_dict[msg[1]]
105
# As messages is a shared resource we need to lock it.
106
server.lock.acquire()
107
server.messages.append(key)
108
server.lock.release()
110
class Client(object):
112
Using this class, a client will send any input event from the player on this
113
machine, and will recieve an acknowledgment in response.
116
def __init__(self, sock=None):
117
self.lock = Semaphore()
120
self.socket = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
121
LOG().log('client socket created')
125
def connect(self, host="127.0.0.1", port=config['NETWORK_PORT'], nick='',
126
character='stick-tiny', votemap='', listening=True):
128
This attempt to connect our client socket to the server socket and
129
send all information to server, about who we are and who we play.
130
(there may be more than one player an a client and the server need to
131
known what caracters theses players plays.
133
this fonction accept a dict of values like.
134
{'nick': ..., 'character':..., 'votemap': ...}
136
the stream sent is terminated with a ^D character ().
140
LOG().log('connecting to game server')
142
self.socket.connect((host,port))
148
msg = ('AU'+'N'+(nick or character)+','+
154
self.socket.sendall(msg)
156
def send(self, key, action='down'):
158
This add a key event to be sent to the game server. Action can be up or
159
down, down if player pushed the key, up if he released it.
163
LOG().log('sent key '+key+' to game server')
169
Close the socket connection.
176
Send all the pending messages to the server.
180
self.socket.sendall(self.messages)
186
Recieve all the data from the server necessary to update our display.
189
LOG().log('recieve new data from server')
191
chunk = self.socket.recv(5)
193
LOG().log('error server, link broken')
200
# parse msg to extract infos about level and characters.
201
# IN is for INIT: the game is beginning
203
infos = msg[2:].split(',')
206
if k == 'LE': # LEVEL
208
elif k == 'PL': # PLAYERS
209
self.players.append(v)
211
# UP is for UPDATE this is an update of the game
212
elif msg[:2] == 'UP':
213
self.players = msg[2:].split(';')
217
class Server(object):
219
This class maintain a server socket that will listen for client connections
220
and will create client socket to deal with client connections.
223
def __init__(self, game_instance=None, sock=None):
225
self.game_instance = game_instance
227
self.lock = Semaphore()
228
self.serversocket = socket.socket(
232
self.serversocket.bind(
234
self.serversocket.getsockname()[0],
235
config['NETWORK_PORT']
239
self.serversocket.listen(5)
248
We wait for the thread to die.
254
def listen(self, clients=[]):
256
Launch the server connection socket listener.
259
Thread( target = self.listen_function, args=(self.game_instance,) ).start()
264
def new_player_from_string(self, data):
266
Data is a stream sent by the client, this function return a dict of
267
params to create the client character.
270
params = data[:-1].split(',')
281
return {'nick': nick, 'character': character, 'votemap': votemap}
283
def listen_function(self, game_instance):
285
Listen for new connexion, and fork them to threads with their socket.
291
while num < self.game_instance.num_players:
292
(clientsocket, address) = self.serversocket.accept()
293
LOG().log('new client accepted :D')
294
self.players.append([])
295
self.votemap.append([])
296
Thread(target=server_thread, args=(clientsocket, self, num, game_instance)).start()
298
LOG().log('client threaded away ;)')
300
sleep(.5) # FIXME: need to be sure that all informations are recieved
301
LOG().log("enougth players, launching game")
302
list_level = [(self.votemap.count(a), a) for a in set(self.votemap)]
303
list_level.sort(reverse=True)
304
LOG().log(self.players)
305
self.game_instance.begin(players_=self.players, level=list_level[0][1])
309
Get the next key event sent by a player.
312
if len(self.messages) == 0:
316
event = self.messages.pop(0)