~ubuntu-branches/ubuntu/utopic/pyscrabble/utopic

« back to all changes in this revision

Viewing changes to .pc/installation.patch/pyscrabble/net/server.py

  • Committer: Bazaar Package Importer
  • Author(s): Magnus Holmgren
  • Date: 2011-04-25 21:58:25 UTC
  • Revision ID: james.westby@ubuntu.com-20110425215825-wk0w127t7mgjrbjy
Tags: 1.6.2-5
* Convert package to source format 3.0 (quilt), meaning renaming the
  patches and converting their headers as well as dropping
  README.source.
* Convert to using dh_python2 (Closes: #616983).
* Run setup.py with --install-layout=deb to get filesystem layout right.
* Bump Debhelper compat level to 7.
* pyscrabble-server.README.Debian: Clarify that pyscrabble-server is not
  intended to be run from the command line directly (Closes: #611833).
* hosts.patch: Improve the code for adding new entries to the Additional
  Hosts list:
  * Fix C&P bug that caused the Game port to be validated twice but the 
    Web port not at all.  
  * Pre-fill the default port numbers to help the user (Closes: #566666).
  * Don't destroy the dialog until the entered values have been extracted.
* Increase Standards-Version to 3.9.2 without changes needed.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from twisted.internet import protocol, reactor, error
 
2
from twisted.protocols.basic import NetstringReceiver
 
3
from pyscrabble.command import helper
 
4
from pyscrabble.game.player import Player, User,PlayerInfo
 
5
from pyscrabble.game.game import ScrabbleGame, ScrabbleGameInfo
 
6
from pyscrabble.lookup import *
 
7
from pyscrabble.game import rank
 
8
from pyscrabble import audit
 
9
from pyscrabble import constants
 
10
from pyscrabble import db
 
11
from pyscrabble import exceptions
 
12
from pyscrabble import manager
 
13
from pyscrabble import serialize
 
14
from pyscrabble import util
 
15
import codecs
 
16
import datetime
 
17
import logging
 
18
import math
 
19
import os
 
20
import time
 
21
import zlib
 
22
try:
 
23
    set
 
24
except NameError:
 
25
    from sets import Set as set
 
26
 
 
27
logger = logging.getLogger("pyscrabble.net.server")
 
28
 
 
29
def upper(data):
 
30
    return data.upper()
 
31
 
 
32
class ScrabbleServerFactory(protocol.ServerFactory, object):
 
33
    '''
 
34
    ScrabbleServerFactory controls all the Games on the server and the Server Protocols for each
 
35
    client that is connected
 
36
    '''
 
37
    
 
38
    def __init__(self):
 
39
        '''
 
40
        Constructor.
 
41
        '''
 
42
        resources = manager.ResourceManager()
 
43
        self.clients = {}
 
44
        self.gameList = {}
 
45
        self.dicts = {}
 
46
        self.db = db.DB()
 
47
        self.maxUsersLoggedIn = 0
 
48
        self.startDate = util.Time(seconds=time.time(), dispDate=True)
 
49
        self.rankings = rank.Rankings( resources["config"][constants.RANK_CONFIG] )
 
50
        
 
51
        dir = resources["resources"][constants.DICT_DIR].path
 
52
        for lang in os.listdir( dir ):
 
53
            if not lang.islower(): continue # Avoids CVS directories
 
54
            self.dicts[lang] = set()
 
55
            for file in os.listdir( os.path.join(dir, lang) ):
 
56
                if not file.islower(): continue # Avoids CVS directories
 
57
                path = os.path.join(dir, lang, file)
 
58
                
 
59
                f = codecs.open(path, encoding='utf-8', mode='rb')
 
60
                lines = f.read().split()
 
61
                f.close()
 
62
                
 
63
                l = []
 
64
                for line in lines:
 
65
                    l.append( line.upper() )
 
66
                x = set( l )
 
67
                
 
68
                self.dicts[lang] = self.dicts[lang].union(x)
 
69
        
 
70
        for game in self.db.games.values():
 
71
            self.gameList[ game.getGameId() ] = game
 
72
 
 
73
    
 
74
    def removeClient(self, client):
 
75
        '''
 
76
        Remove a client
 
77
        
 
78
        @param client:
 
79
        '''
 
80
        
 
81
        if self.clients.has_key(client):
 
82
            del self.clients[client]
 
83
    
 
84
    def resetRanks(self):
 
85
        '''
 
86
        Reset users ranks
 
87
        '''
 
88
        for username in self.db.users.keys():
 
89
            self.db.users[username].setRank( 0 )
 
90
            self.db.users[username].rankName = self.rankings.getMinRank().name
 
91
    
 
92
    def auditUser(self, username, action, sync=True):
 
93
        '''
 
94
        Add an audit action for a user
 
95
        
 
96
        @param username:
 
97
        @param action:
 
98
        @param sync:
 
99
        '''
 
100
        try :
 
101
            u = util.getUnicode(username)
 
102
            self.db.users[u].addAuditAction( action )
 
103
            if sync:
 
104
                self.db.sync()
 
105
        except KeyError:
 
106
            pass
 
107
    
 
108
    def getServerBulletins(self):
 
109
        '''
 
110
        Return list of ServerBulletins
 
111
        '''
 
112
        if not self.db.messages.has_key(constants.SERVER_MESSAGE_KEY):
 
113
            return []
 
114
        else:
 
115
            return self.db.messages[constants.SERVER_MESSAGE_KEY]
 
116
        
 
117
    
 
118
    def addServerBulletin(self, message):
 
119
        '''
 
120
        Add server bulletin
 
121
        
 
122
        @param message: Bulletin message
 
123
        '''
 
124
        
 
125
        if not self.db.messages.has_key(constants.SERVER_MESSAGE_KEY):
 
126
            l = []
 
127
        else:
 
128
            l = self.db.messages[constants.SERVER_MESSAGE_KEY]
 
129
        
 
130
        b = self.createServerInfoMessage(message)
 
131
        l.append( util.ServerBulletin(data=b, id=util.getRandomId(), seconds=time.time()) )
 
132
        
 
133
        self.db.messages[constants.SERVER_MESSAGE_KEY] = l
 
134
        self.db.sync()
 
135
        
 
136
        for c in self.clients:
 
137
            c.postChatMessage( (b, True) )
 
138
    
 
139
    def deleteServerBulletin(self, id):
 
140
        '''
 
141
        Delete Server bulletin
 
142
        
 
143
        @param id: Bulletin ID
 
144
        '''
 
145
        l = self.db.messages[constants.SERVER_MESSAGE_KEY]
 
146
        key = int(id)
 
147
        l = [ m for m in l if m.id != key ]
 
148
        self.db.messages[constants.SERVER_MESSAGE_KEY] = l
 
149
        self.db.sync()
 
150
    
 
151
    def isLoggedIn(self, player):
 
152
        '''
 
153
        Check if a user was logged in
 
154
        
 
155
        @param player: Player
 
156
        '''
 
157
        
 
158
        for client,_player in self.clients.iteritems():
 
159
            if player.getUsername() ==  _player.getUsername():
 
160
                return True
 
161
        
 
162
        return False
 
163
    
 
164
    def getLoggedInPlayers(self):
 
165
        '''
 
166
        Retrieve listing of players logged in
 
167
        
 
168
        @return: Formatted string of players logged in
 
169
        '''
 
170
        str = ''
 
171
        for player in self.clients.values():
 
172
            str = str + player.getUsername() + ' '
 
173
        
 
174
        return str
 
175
        
 
176
    
 
177
    def bootUser(self, username):
 
178
        '''
 
179
        Boot a user from the server
 
180
        
 
181
        @param username: Username
 
182
        '''
 
183
        p = Player( username )
 
184
        c = self.getPlayerClient(p)
 
185
        
 
186
        if c is not None:
 
187
            for game in self.gameList.itervalues():
 
188
                if game.hasPlayer(p):
 
189
                    self.leaveGame(game.getGameId(), c, penalize=True, booted=True)
 
190
                    c.gameBoot(game.getGameId())
 
191
                if game.hasSpectator( player ):
 
192
                    cmd = helper.GameCommand(constants.GAME_LEAVE, game.getGameId(), '')
 
193
                    self.spectatorLeaveGame(cmd, client)
 
194
            c.logout()
 
195
            self.removeClient(c)
 
196
        
 
197
    
 
198
    def removeUser(self, username):
 
199
        '''
 
200
        Remove a user from the server.
 
201
        
 
202
        If the user is logged in, send a booted command
 
203
        
 
204
        @param username: Username
 
205
        '''
 
206
        self.bootUser(username)
 
207
        
 
208
        del self.db.users[util.getUnicode(username)]
 
209
        self.db.sync()
 
210
        
 
211
    
 
212
    def updateUser(self, user):
 
213
        '''
 
214
        Update a user
 
215
        
 
216
        @param user: User
 
217
        '''
 
218
        
 
219
        self.db.users[ user.getUsername() ] = user
 
220
    
 
221
    def doChangePassword(self, username, newPassword):
 
222
        '''
 
223
        Change a users password
 
224
         
 
225
        @param username:
 
226
        @param newPassword:
 
227
        '''
 
228
        
 
229
        user = self.db.users[util.getUnicode(username)]
 
230
        user.setPassword(newPassword)
 
231
                
 
232
 
 
233
    def getUsers(self):
 
234
        '''
 
235
        Return user objects
 
236
        
 
237
        @return: Dictionary of users
 
238
        '''
 
239
        x = self.db.users.values()[:]
 
240
        x.sort( lambda x,y : cmp(x.lastLogin, y.lastLogin) )
 
241
        return x
 
242
    
 
243
    def getUser(self, username):
 
244
        '''
 
245
        Get a user
 
246
        
 
247
        @param username: Username
 
248
        @return: User obect or not
 
249
        '''
 
250
        u = util.getUnicode(username)
 
251
        if self.db.users.has_key(u):
 
252
            return self.db.users[ u ]
 
253
        else:
 
254
            return None
 
255
 
 
256
    def stopFactory(self):
 
257
        '''
 
258
        Callback when the factory is stopping. Close the users file
 
259
        '''
 
260
        self.db.close()
 
261
 
 
262
    def buildProtocol(self, addr):
 
263
        '''
 
264
        Build a Server Protocol instance for each connection
 
265
        
 
266
        @param addr: Incoming address
 
267
        '''
 
268
        
 
269
        p = ScrabbleServer()
 
270
        p.factory = self
 
271
        p.addr = addr
 
272
        
 
273
        return p
 
274
    
 
275
    def hasUsers(self):
 
276
        '''
 
277
        Check to see if the server has users
 
278
        
 
279
        @return: True if there are users defined on the server
 
280
        '''
 
281
        return len(self.db.users) != 0
 
282
        
 
283
    
 
284
    def isUserAdmin(self, username):
 
285
        '''
 
286
        Check to see if a user is administrator
 
287
        
 
288
        @param username: Username
 
289
        @return: True if the user is an Administrator
 
290
        '''
 
291
        u = util.getUnicode(username)
 
292
        if not self.db.users.has_key(u):
 
293
            return False
 
294
        
 
295
        user = self.db.users[u]
 
296
        return user.isAdmin()
 
297
    
 
298
    def addNewUser(self, username, password, isAdmin):
 
299
        '''
 
300
        Add a new user
 
301
        
 
302
        @param username:
 
303
        @param password:
 
304
        @param isAdmin: True if the user is an administrator
 
305
        '''
 
306
        
 
307
        if username.upper() in map(upper, self.db.users.keys()):
 
308
            return False, USER_ALREADY_EXISTS
 
309
        
 
310
        if username in constants.RESERVED_NAMES:
 
311
            return False, USERNAME_NOT_ALLOWED
 
312
        
 
313
        if (len(username) > constants.MAX_NAME_LENGTH):
 
314
            return False, USERNAME_MUST_BE_LESS_THAN
 
315
        
 
316
        # Ensure that the first user created is an Admin
 
317
        if not self.hasUsers():
 
318
            isAdmin = True
 
319
        
 
320
        user = User( username, password, isAdmin )
 
321
        user.rankName = self.rankings.getMinRank().name
 
322
        self.db.users[ user.getUsername() ] = user
 
323
        self.db.sync()
 
324
        
 
325
        return True, SUCCESS
 
326
        
 
327
    
 
328
    def getGameListing(self):
 
329
        '''
 
330
        Return a Listing of ScrabbleGameInfo objects about each Game on the server
 
331
        
 
332
        @return: List
 
333
        '''
 
334
        return [ScrabbleGameInfo(game) for game in self.gameList.values()]
 
335
    
 
336
    def createGame(self, gameId, client, options):
 
337
        '''
 
338
        Create a new game
 
339
        
 
340
        @param gameId: Game ID
 
341
        @param client: Client
 
342
        @param options: Options
 
343
        '''
 
344
        
 
345
        if len(gameId) > constants.MAX_NAME_LENGTH:
 
346
            
 
347
            client.showError( ServerMessage( [GAME_NAME_MUST_BE_LESS_THAN, str(constants.MAX_NAME_LENGTH), CHARACTERS] ) )
 
348
            return
 
349
        
 
350
        if not self.gameList.has_key(gameId):
 
351
            game = ScrabbleGame( gameId, options )
 
352
            game.creator = self.clients[client].getUsername()
 
353
            self.gameList[ gameId ] = game
 
354
            self.refreshGameList()
 
355
        else:
 
356
            client.showError( ServerMessage([GAME_ALREADY_EXISTS]) )
 
357
 
 
358
 
 
359
    def deleteGame(self, gameId):
 
360
        '''
 
361
        Boot all the players and spectators from the game and then call removeGame
 
362
        
 
363
        @param gameId: Game ID
 
364
        '''
 
365
        
 
366
        if (not self.gameList.has_key(gameId)):
 
367
            return
 
368
        
 
369
        game = self.gameList[ gameId ]
 
370
        game.setComplete()
 
371
        
 
372
        self.sendGameInfoMessage(gameId, [SERVER_DELETE_GAME], client=None, level=constants.GAME_LEVEL)
 
373
        
 
374
        for player in game.getPlayers():
 
375
            c = self.getPlayerClient(player)
 
376
            if c:
 
377
                c.gameLeave(gameId, True)
 
378
        
 
379
        for s in game.getSpectators():
 
380
            c = self.getPlayerClient(s)
 
381
            if c:
 
382
                c.gameLeave(gameId, True)
 
383
    
 
384
        if not game.isPaused():
 
385
            if len(game.getPlayers()) == 0:
 
386
                self.removeGame(gameId)
 
387
        else:
 
388
            if len(game.getPending()) == len(game.getPlayers()):
 
389
                self.removeGame(gameId)
 
390
        
 
391
        self.refreshGameList()
 
392
    
 
393
    
 
394
    def removeGame(self, gameId):
 
395
        '''
 
396
        Remove a game from the system and database
 
397
        
 
398
        @param gameId:
 
399
        '''
 
400
        if self.gameList.has_key(gameId):
 
401
            del self.gameList[ gameId ] 
 
402
        if self.db.games.has_key(gameId):
 
403
            del self.db.games[ gameId ]
 
404
            self.db.sync()
 
405
 
 
406
    # Get a players client
 
407
    def getPlayerClient(self, player):
 
408
        '''
 
409
        Get the client protocol belonging to C{player}
 
410
        
 
411
        @param player: Player
 
412
        '''
 
413
        
 
414
        for client,_player in self.clients.iteritems():
 
415
            if player.getUsername() == _player.getUsername():
 
416
                return client
 
417
 
 
418
    # Authenticate a user
 
419
    def authenticate(self, username, password):
 
420
        '''
 
421
        Authenticate a user
 
422
        
 
423
        @param username:
 
424
        @param password:
 
425
        '''
 
426
        u = util.getUnicode(username)
 
427
        if self.db.users.has_key(u):
 
428
            user = self.db.users[u]
 
429
            if user.getPassword() == password:
 
430
                return True
 
431
 
 
432
        return False
 
433
    
 
434
    def sendGameStats(self, gameId):
 
435
        '''
 
436
        Send game statistics to each person in the game
 
437
        
 
438
        @param gameId: Game ID
 
439
        '''
 
440
        game = self.gameList[ gameId ]
 
441
        
 
442
        for p in game.getPlayers():
 
443
            c = self.getPlayerClient(p)
 
444
            if c:
 
445
                c.sendGameStats(gameId, game.getStats())
 
446
        
 
447
        for s in game.getSpectators():
 
448
            c = self.getPlayerClient(s)
 
449
            if c:
 
450
                c.sendGameStats(gameId, game.getStats())
 
451
    
 
452
    def sendSpectatorList(self, gameId):
 
453
        '''
 
454
        Send list of spectators
 
455
        
 
456
        @param gameId: Game ID
 
457
        '''
 
458
        game = self.gameList[ gameId ]
 
459
        
 
460
        l = game.getSpectators()
 
461
 
 
462
        for p in game.getPlayers():
 
463
            c = self.getPlayerClient(p)
 
464
            if c:
 
465
                c.gameSendSpectators(gameId, l)
 
466
        
 
467
        for s in game.getSpectators():
 
468
            c = self.getPlayerClient(s)
 
469
            if c:
 
470
                c.gameSendSpectators(gameId, l)
 
471
        
 
472
        
 
473
    
 
474
    def sendGameInfoMessage(self, gameId, message, client=None, level=constants.GAME_LEVEL):
 
475
        '''
 
476
        Send an information message to the players of a Game
 
477
        
 
478
        @param gameId: Game ID
 
479
        @param message: Message
 
480
        '''
 
481
        if (client == None):
 
482
            message = self.createServerChatMessage("GAME", message)
 
483
        else:
 
484
            message = self.createChatMessage(client.getUsername(), message)
 
485
        
 
486
        game = self.gameList[ gameId ]
 
487
        for p in game.getPlayers():
 
488
            c = self.getPlayerClient(p)
 
489
            if c:
 
490
                c.gameInfo(game.getGameId(), [(level, message)])
 
491
        
 
492
        for s in game.getSpectators():
 
493
            c = self.getPlayerClient(s)
 
494
            if c:
 
495
                c.gameInfo(game.getGameId(), [(level, message)])
 
496
        
 
497
        game.appendLogMessage( (level, message) )
 
498
        
 
499
    # Change a users password
 
500
    def changePassword(self, command, client):
 
501
        '''
 
502
        Change password
 
503
        
 
504
        @param command: LoginCommand
 
505
        @param client: ScrabbleServer Protocol
 
506
        '''
 
507
        
 
508
        if command.getUsername() != None and len(command.getUsername()) != 0:
 
509
            user = self.db.users[ command.getUsername() ]
 
510
        else:
 
511
            player = self.clients[client]
 
512
            user = self.db.users[ player.getUsername() ]
 
513
        
 
514
        if (user.getPassword() == command.getData()): # Data will have the old password
 
515
            user.setPassword( command.getPassword() )
 
516
            self.db.users[ user.getUsername() ] = user
 
517
            self.db.sync()
 
518
        else:
 
519
            client.showError( ServerMessage([INVALID_OLD_PASSWORD]) )
 
520
 
 
521
    def loginUser(self, player, client):
 
522
        '''
 
523
        Log a user into the system. Join the main chat
 
524
        
 
525
        @param player:
 
526
        @param client:
 
527
        '''
 
528
        
 
529
        self.joinChat( player, client )
 
530
        self.clients[client] = player
 
531
        
 
532
        self.db.users[player.getUsername()].setLastLogin( time.time() )
 
533
        self.db.sync()
 
534
        
 
535
        if len(self.clients) > self.maxUsersLoggedIn:
 
536
            self.maxUsersLoggedIn = len(self.clients)
 
537
    
 
538
    def handlePrivateMessageCommand(self, command, client):
 
539
        '''
 
540
        Send a private message
 
541
        
 
542
        @param command:
 
543
        @param client:
 
544
        '''
 
545
        
 
546
        
 
547
        if not self.db.users.has_key(command.getRecipient()):
 
548
            client.showError( ServerMessage([command.getRecipient(), DOES_NOT_EXIST]) )
 
549
            return
 
550
        
 
551
        p = Player( command.getRecipient() )
 
552
        c = self.getPlayerClient(p)
 
553
        
 
554
        sender = self.clients[client].getUsername()
 
555
        recipient = command.getRecipient()
 
556
        data = command.getData()
 
557
        
 
558
        if c is None:
 
559
            if not self.db.messages.has_key(recipient):
 
560
                l = []
 
561
            else:
 
562
                l = self.db.messages[recipient]
 
563
                self.db.messages[recipient] = []
 
564
            
 
565
            num = util.getRandomId()
 
566
            msg = util.PrivateMessage(sender, data, num, time=util.Time(seconds=time.time(), dispDate=True))
 
567
            
 
568
            l.append( msg )
 
569
            self.db.messages[recipient] = l
 
570
            self.db.sync()
 
571
            
 
572
            client.sendPrivateMessage(recipient, self.createChatMessage(sender, data))
 
573
            client.showInfo( ServerMessage([MESSAGE_SENT, ': %s' % recipient]) )
 
574
            
 
575
            return
 
576
            
 
577
        msg = self.createChatMessage(sender, data)    
 
578
        c.sendPrivateMessage(sender, msg)
 
579
        client.sendPrivateMessage(recipient, msg)
 
580
        
 
581
 
 
582
    def handleGameCommand(self, command, client):
 
583
        '''
 
584
        Handle a game command
 
585
        
 
586
        @param command: GameCommand
 
587
        @param client: ScrabbleServer Protocol
 
588
        '''
 
589
        
 
590
        
 
591
        if (command.getCommand() == constants.GAME_GET_LETTERS):
 
592
            letters = self.game.getLetters( int(command.getData()) )
 
593
            client.sendLetters( letters )
 
594
 
 
595
        if (command.getCommand() == constants.GAME_LIST):
 
596
            client.sendGameList( self.getGameListing() )
 
597
 
 
598
        if (command.getCommand() == constants.GAME_JOIN):
 
599
            self.joinGame(command, client)
 
600
 
 
601
        if (command.getCommand() == constants.GAME_START):
 
602
            self.startGame( command.getGameId(), client )
 
603
 
 
604
        if (command.getCommand() == constants.GAME_LEAVE):
 
605
            if not self.gameList.has_key(command.getGameId()):
 
606
                return
 
607
                
 
608
            game = self.gameList[ command.getGameId() ]
 
609
            if game.hasPlayer(self.clients[client]):
 
610
                self.leaveGame( command.getGameId(), client )
 
611
            if game.hasSpectator(self.clients[client]):
 
612
                self.spectatorLeaveGame(command, client)
 
613
 
 
614
        if (command.getCommand() == constants.GAME_SEND_MOVE):
 
615
            onboard, moves = command.getData()
 
616
            self.gameSendMove( command.getGameId(), onboard, moves, client )
 
617
 
 
618
        if (command.getCommand() == constants.GAME_CREATE):
 
619
            self.createGame( command.getGameId(), client, command.getData() )
 
620
        if (command.getCommand() == constants.GAME_PASS):
 
621
            self.gamePassMove( command.getGameId(), client )
 
622
 
 
623
        if (command.getCommand() == constants.GAME_PAUSE):
 
624
            self.saveGame(command, client)
 
625
 
 
626
        if (command.getCommand() == constants.GAME_UNPAUSE):
 
627
            game = self.gameList[ command.getGameId() ]
 
628
            
 
629
            if self.clients[client].getUsername() != game.creator:
 
630
                client.gameError(game.getGameId(), ServerMessage([NOT_CREATOR]))
 
631
                return
 
632
            
 
633
            # We can only unpause if all former players are present
 
634
            if (len(game.getPending()) > 0):
 
635
                client.gameError(game.getGameId(), ServerMessage([REQUIRED_NOT_MET]))
 
636
                return
 
637
            
 
638
            game.unPause()
 
639
            for player in game.getPlayers():
 
640
                c = self.getPlayerClient(player)
 
641
                c.unPauseGame( game.getGameId() )
 
642
            self.refreshGameList()
 
643
            self.sendGameInfoMessage(command.getGameId(), [GAME_RESUMED], None, level=constants.GAME_LEVEL)
 
644
            self.doGameTurn(game.getGameId(), wasUnpaused=True)
 
645
            
 
646
            del self.db.games[ game.getGameId() ]
 
647
            self.db.sync()
 
648
 
 
649
        if (command.getCommand() == constants.GAME_TRADE_LETTERS):
 
650
            self.tradeLetters(command, client)
 
651
        
 
652
        if (command.getCommand() == constants.GAME_CHAT_MESSAGE):
 
653
            _level = constants.GAME_LEVEL
 
654
            game = self.gameList[ command.getGameId() ]
 
655
            if ( game.hasPlayer(self.clients[client]) ):
 
656
                _level = constants.PLAYER_LEVEL
 
657
            elif ( game.hasSpectator(self.clients[client]) ):
 
658
                _level = constants.SPECTATOR_LEVEL
 
659
            
 
660
            if _level == constants.SPECTATOR_LEVEL:
 
661
                if not game.isSpectatorChatEnabled():
 
662
                    command.setCommand( constants.GAME_ERROR )
 
663
                    command.setData( ServerMessage([SPECTATOR_CHAT_DISABLED]) )
 
664
                    client.writeCommand(command)
 
665
                    return
 
666
            
 
667
            self.sendGameInfoMessage(command.getGameId(), command.getData(), self.clients[client], level=_level)
 
668
        
 
669
        if (command.getCommand() == constants.GAME_SPECTATOR_JOIN):
 
670
            game = self.gameList[ command.getGameId() ]
 
671
            if not game.isSpectatorsAllowed():
 
672
                command.setCommand( constants.GAME_JOIN_DENIED )
 
673
                command.setData( ServerMessage([SPECTATORS_BANNED]) )
 
674
                client.denyJoinGame(command)
 
675
                return
 
676
            self.spectatorJoinGame(command, client)
 
677
        
 
678
        if (command.getCommand() == constants.GAME_SPECTATOR_CHAT_SET):
 
679
            game = self.gameList[ command.getGameId() ]
 
680
            game.setSpectatorChatEnabled(command.getData())
 
681
            
 
682
            if game.isSpectatorChatEnabled():
 
683
                self.sendGameInfoMessage(command.getGameId(), [self.clients[client].getUsername(), ENABLE_SPEC_CHAT], client=None, level=constants.GAME_LEVEL)
 
684
            else:
 
685
                self.sendGameInfoMessage(command.getGameId(), [self.clients[client].getUsername(), DISABLE_SPEC_CHAT], client=None, level=constants.GAME_LEVEL)
 
686
            
 
687
            for p in game.getPlayers():
 
688
                c = self.getPlayerClient(p)
 
689
                c.writeCommand(command)
 
690
        
 
691
        if (command.getCommand() == constants.GAME_SPECTATOR_SET):
 
692
            game = self.gameList[ command.getGameId() ]
 
693
            game.setSpectatorsAllowed(command.getData())
 
694
            
 
695
            if game.isSpectatorsAllowed():
 
696
                self.sendGameInfoMessage(command.getGameId(), [self.clients[client].getUsername(), ENABLE_SPEC], client=None, level=constants.GAME_LEVEL)
 
697
            else:
 
698
                self.sendGameInfoMessage(command.getGameId(), [self.clients[client].getUsername(), DISABLE_SPEC], client=None, level=constants.GAME_LEVEL)
 
699
            
 
700
            for p in game.getPlayers():
 
701
                c = self.getPlayerClient(p)
 
702
                c.writeCommand(command)
 
703
            
 
704
            if not game.isSpectatorsAllowed():
 
705
                for s in game.getSpectators():
 
706
                    c = self.getPlayerClient(s)
 
707
                    self.spectatorLeaveGame(command, c)
 
708
                    c.gameBoot(command.getGameId())
 
709
        
 
710
        if (command.getCommand() == constants.GAME_TIME_EXPIRE):
 
711
            self.gameTimeExpired( command.getGameId(), client )
 
712
        
 
713
        if (command.getCommand() == constants.GAME_MOVE_TIME_EXPIRE):
 
714
            self.moveTimeExpired(command.getGameId(), client )
 
715
            
 
716
 
 
717
    def handleChatCommand(self, command, client):
 
718
        '''
 
719
        Handle a chat command
 
720
        
 
721
        @param command: ChatCommand
 
722
        @param client: ScrabbleServer Protocol
 
723
        '''
 
724
        
 
725
        if (command.getCommand() == constants.CHAT_JOIN):
 
726
            self.joinChat( self.clients[client], client )
 
727
            
 
728
        if (command.getCommand() == constants.CHAT_LEAVE):
 
729
            if self.clients.has_key(client):
 
730
                player = self.clients[client]
 
731
                
 
732
                # Remove player from game and notify other players
 
733
                for game in self.gameList.values():
 
734
                    if game.hasPlayer( player ):
 
735
                        self.leaveGame( game.getGameId(), client, command.getData() )
 
736
                    
 
737
                    if game.hasSpectator( player ):
 
738
                        cmd = helper.GameCommand(constants.GAME_LEAVE, game.getGameId(), '')
 
739
                        self.spectatorLeaveGame(cmd, client)
 
740
                
 
741
                self.removeClient(client)
 
742
                self.leaveChat( player )
 
743
                client.logout()
 
744
                
 
745
 
 
746
        if (command.getCommand() == constants.CHAT_USERS):
 
747
            self.sendUserList(client)
 
748
    
 
749
        if (command.getCommand() == constants.CHAT_MESSAGE):
 
750
            self.postChatMessage( self.clients[client], command.getData() )
 
751
        
 
752
        if (command.getCommand() == constants.USER_INFO):
 
753
            if not self.db.users.has_key(command.getUsername()):
 
754
                client.showError( ServerMessage([command.getUsername(), DOES_NOT_EXIST]) )
 
755
                return
 
756
            u = self.db.users[command.getUsername()].clone()
 
757
            u.status = self.getUserStatus(command.getUsername())
 
758
            client.sendUserInfo(u)
 
759
        
 
760
        if (command.getCommand() == constants.SERVER_STATS):
 
761
            client.sendServerStats(self.getStats())
 
762
        
 
763
        if (command.getCommand() == constants.CHECK_MESSAGES):
 
764
            # Print server bulletins first
 
765
            if self.db.messages.has_key(constants.SERVER_MESSAGE_KEY):
 
766
                for message in self.db.messages[constants.SERVER_MESSAGE_KEY]:
 
767
                    client.postChatMessage( (message.data, True) )
 
768
            
 
769
            key = self.clients[client].getUsername()
 
770
            if self.db.messages.has_key(key):
 
771
                if len(self.db.messages[key]) > 0:
 
772
                    new = False
 
773
                    for m in self.db.messages[key]:
 
774
                        if not m.read:
 
775
                            new = True
 
776
                    
 
777
                    if new:
 
778
                        client.postChatMessage( (self.createServerInfoMessage(MESSAGES_AVAILABLE), True) )
 
779
                    else:
 
780
                        client.postChatMessage( (self.createServerInfoMessage(OLD_MESSAGES_AVAILABLE), True) )
 
781
                
 
782
        
 
783
        if (command.getCommand() == constants.GET_MESSAGES):
 
784
            key = self.clients[client].getUsername()
 
785
            if self.db.messages.has_key(key):
 
786
                for m in self.db.messages[key]:
 
787
                    m.read = True
 
788
                client.sendOfflineMessages( self.db.messages[key] )
 
789
            else:
 
790
                client.sendOfflineMessages( [] )
 
791
            self.db.sync()
 
792
        
 
793
        if (command.getCommand() == constants.DELETE_MESSAGE):
 
794
            key = self.clients[client].getUsername()
 
795
            l = self.db.messages[key]
 
796
            data = int(command.getData())
 
797
            l = [ m for m in l if m.id != data ]
 
798
            if len(l) != 0:
 
799
                self.db.messages[key] = l
 
800
            else:
 
801
                del self.db.messages[key]
 
802
            self.db.sync()
 
803
 
 
804
    # Alert other users that a user has joined
 
805
    def joinChat(self, player, client):
 
806
        '''
 
807
        User joins chat
 
808
        
 
809
        @param player:
 
810
        @param client:
 
811
        '''
 
812
        
 
813
        for c in self.clients.keys():
 
814
            if (c != client):
 
815
                c.joinChat( player.getUsername() )
 
816
        
 
817
        for c in self.clients.keys():
 
818
            c.postChatMessage( (self.createLoginMessage(player.getUsername()), True) )
 
819
        
 
820
        self.auditUser( player.getUsername(), audit.LogonAction(player.getUsername()) )
 
821
 
 
822
    # Log a user of the system
 
823
    def leaveChat(self, player):
 
824
        '''
 
825
        User leaves chat
 
826
        
 
827
        @param player:
 
828
        '''
 
829
        
 
830
        for c in self.clients.keys():
 
831
            c.postChatMessage( (self.createLogoutMessage(player.getUsername()),True) )
 
832
                
 
833
        for c in self.clients.keys():
 
834
            self.sendUserList(c)
 
835
        
 
836
        self.auditUser( player.getUsername(), audit.LogoffAction(player.getUsername()) )
 
837
 
 
838
    # Post a chat message
 
839
    def postChatMessage(self, player, msg):
 
840
        '''
 
841
        User posts a chat message
 
842
        
 
843
        @param player:
 
844
        @param msg:
 
845
        '''
 
846
        
 
847
        for c in self.clients.keys():
 
848
            c.postChatMessage( (self.createChatMessage(player.getUsername(), msg), False) )
 
849
 
 
850
    # Send the list of users to the client
 
851
    def sendUserList(self, client):
 
852
        '''
 
853
        Send the client a list of all users on the server
 
854
        
 
855
        @param client:
 
856
        '''
 
857
        
 
858
        client.sendUserList( [ self.clients[c] for c in self.clients.keys()] )
 
859
 
 
860
    # Create formatted chat message
 
861
    def createServerChatMessage(self, username, msg_keys):
 
862
        '''
 
863
        Helper function to create a server chat message
 
864
        
 
865
        @param username:
 
866
        @param msg_keys:
 
867
        '''
 
868
        
 
869
        x = []
 
870
        x.append( "<%s>" % (username) )
 
871
        x.extend( msg_keys )
 
872
        x.append( "\n" )
 
873
        
 
874
        
 
875
        return ServerMessage(x, util.Time(seconds=time.time(), dispDate=False))
 
876
    
 
877
    def createServerInfoMessage(self, msg):
 
878
        '''
 
879
        Helper function to create a serverchat message
 
880
        
 
881
        @param msg:
 
882
        '''
 
883
        
 
884
        return ServerMessage([msg,"\n"], util.Time(seconds=time.time(), dispDate=True))
 
885
 
 
886
    # Create formatted chat message
 
887
    def createChatMessage(self, username, msg):
 
888
        '''
 
889
        Helper function to create a chat message
 
890
        
 
891
        @param username:
 
892
        @param msg:
 
893
        '''
 
894
        return ServerMessage(["<%s> %s\n" % (username, util.getUnicode(msg))],util.Time(seconds=time.time(), dispDate=False))
 
895
 
 
896
    # Create logout message
 
897
    def createLogoutMessage(self, username):
 
898
        '''
 
899
        Helper function to create a logout message
 
900
        
 
901
        @param username:
 
902
        '''
 
903
        
 
904
        
 
905
        return ServerMessage(["<%s>" % (username), LOGGED_OUT, "\n"], util.Time(seconds=time.time(), dispDate=False))
 
906
    
 
907
    # Create logout message
 
908
    def createLoginMessage(self, username):
 
909
        '''
 
910
        Helper function to create a login message
 
911
        
 
912
        @param username:
 
913
        '''
 
914
        return ServerMessage(["<%s>" % (username), LOGGED_IN, "\n"], util.Time(seconds=time.time(), dispDate=False))
 
915
 
 
916
    def refreshGameList(self):
 
917
        '''
 
918
        Send all clients the current game list
 
919
        '''
 
920
        
 
921
        for c in self.clients.keys():
 
922
            c.sendGameList( [ScrabbleGameInfo(game) for game in self.gameList.values()] )
 
923
        
 
924
    # Join a game
 
925
    def joinGame(self, command, client):
 
926
        '''
 
927
        User joins a game
 
928
        
 
929
        @param command: GameCommand
 
930
        @param client: ScrabbleServer protocol
 
931
        '''
 
932
        game = self.gameList[ command.getGameId() ]
 
933
        if (game.isStarted()):
 
934
            command.setData( ServerMessage([CANNOT_JOIN_STARTED]) )
 
935
            command.setCommand( constants.GAME_JOIN_DENIED )
 
936
            client.denyJoinGame(command)
 
937
            return
 
938
        
 
939
        if (game.getNumberOfPlayers() == constants.MAX_PLAYERS):
 
940
            command.setData( ServerMessage([GAME_FULL]) )
 
941
            command.setCommand( constants.GAME_JOIN_DENIED )
 
942
            client.denyJoinGame(command)
 
943
            return
 
944
 
 
945
        p = self.clients[client].clone()
 
946
        
 
947
        if (game.isPaused() and not game.hasPlayer(p)):
 
948
            command.setData( ServerMessage([CANNOT_JOIN_STARTED]) )
 
949
            command.setCommand( constants.GAME_JOIN_DENIED )
 
950
            client.denyJoinGame(command)
 
951
            return
 
952
 
 
953
        if not game.hasPlayer( p ):
 
954
            game.addPlayer( p )
 
955
        else:
 
956
            game.removePending( p )
 
957
        
 
958
        command.setCommand( constants.GAME_JOIN_OK )
 
959
        client.acceptJoinGame( command, game.options )
 
960
        
 
961
        time = None
 
962
        if game.options.has_key(OPTION_TIMED_GAME):
 
963
            time = int(game.options[OPTION_TIMED_GAME])
 
964
        elif game.options.has_key(OPTION_MOVE_TIME):
 
965
            time = int(game.options[OPTION_MOVE_TIME])
 
966
        
 
967
        if time is not None:
 
968
            p.setInitialTime( time )
 
969
        
 
970
        players = game.getPlayers()
 
971
        pending = game.getPending()
 
972
        
 
973
        self.sendGameScores(game.getGameId())
 
974
        
 
975
        client.sendMoves( game.getGameId(), game.getMoves() )
 
976
        client.sendGameStats( game.getGameId(), game.getStats() )
 
977
        client.gameInfo( game.getGameId(), game.getLog() )
 
978
        client.gameSendSpectators( game.getGameId(), game.getSpectators() )
 
979
        client.sendGameOptions( game.getGameId(), game.getOptions() )
 
980
        
 
981
        client.setSpectatorChatEnabled(game.getGameId(), game.isSpectatorChatEnabled())
 
982
        client.setSpectatorsAllowed(game.getGameId(), game.isSpectatorsAllowed())
 
983
        
 
984
        if (game.isPaused()):
 
985
            client.pauseGame( game.getGameId() )
 
986
 
 
987
        if (game.isPaused() and game.isInProgress()):
 
988
            
 
989
            player = game.getPlayer( self.clients[client] )
 
990
            
 
991
            letters = game.getLetters( player.getNumberOfLettersNeeded() )
 
992
            if (len(letters) > 0):
 
993
                player.addLetters(letters)
 
994
            client.sendLetters(game.getGameId(), player.getLetters())
 
995
 
 
996
            # If there is only one person (the new person), make sure that person has control of the board
 
997
            #if (len(players) == 1):
 
998
            #    self.doGameTurn( game.getGameId() )
 
999
 
 
1000
        self.refreshGameList()
 
1001
        
 
1002
    # Start the game
 
1003
    def startGame(self, gameId, client):
 
1004
        '''
 
1005
        User starts a game
 
1006
        
 
1007
        @param gameId: Game ID
 
1008
        @param client: ScrabbleServer Protocol
 
1009
        '''
 
1010
        
 
1011
        game = self.gameList[ gameId ]
 
1012
        
 
1013
        if self.clients[client].getUsername() != game.creator:
 
1014
            client.gameError(gameId, ServerMessage([NOT_CREATOR]))
 
1015
            return
 
1016
        
 
1017
        if (game.isStarted()):
 
1018
            client.gameError(gameId, ServerMessage([GAME_ALREADY_STARTED]))
 
1019
            return
 
1020
 
 
1021
        game.start()
 
1022
        
 
1023
        time = None
 
1024
        if game.options.has_key(OPTION_TIMED_GAME):
 
1025
            time = int(game.options[OPTION_TIMED_GAME])
 
1026
        elif game.options.has_key(OPTION_MOVE_TIME):
 
1027
            time = int(game.options[OPTION_MOVE_TIME])
 
1028
            
 
1029
        for player in game.getPlayers():
 
1030
            c = self.getPlayerClient(player)
 
1031
            letters = game.getLetters( player.getNumberOfLettersNeeded() )
 
1032
            player.addLetters(letters)
 
1033
            if time is not None:
 
1034
                player.setInitialTime( time )
 
1035
            c.sendLetters( game.getGameId(), letters )
 
1036
        
 
1037
        self.sendGameInfoMessage(gameId, [gameId, STARTED], client=None, level=constants.GAME_LEVEL)
 
1038
        self.sendGameScores(game.getGameId())
 
1039
 
 
1040
        self.doGameTurn( gameId )
 
1041
        self.refreshGameList()
 
1042
 
 
1043
    # Turn gameplayer over to the next player and notify other players of whose turn it is
 
1044
    def doGameTurn(self, gameId, wasUnpaused=False ):
 
1045
        '''
 
1046
        Turn control of the board to the next player in the game
 
1047
        
 
1048
        @param gameId: Game ID
 
1049
        '''
 
1050
        
 
1051
        game = self.gameList[ gameId ]
 
1052
        
 
1053
        player = game.getNextPlayer()
 
1054
        
 
1055
        if player is None:
 
1056
            return
 
1057
        
 
1058
        player.stamp = datetime.datetime.now()
 
1059
        client = self.getPlayerClient(player)
 
1060
        
 
1061
        if game.timer is not None and game.timer.active():
 
1062
            game.timer.cancel()
 
1063
        
 
1064
        time = player.time
 
1065
        if game.options.has_key(OPTION_MOVE_TIME):
 
1066
            if not wasUnpaused:
 
1067
                time = datetime.timedelta(seconds=60 * int(game.options[OPTION_MOVE_TIME]))
 
1068
        
 
1069
        if game.options.has_key(OPTION_MOVE_TIME):
 
1070
            game.timer = reactor.callLater(time.seconds, self.moveTimeExpired, gameId, client)
 
1071
        elif game.options.has_key(OPTION_TIMED_GAME):
 
1072
            if game.options.has_key(OPTION_TIMED_LIMIT):
 
1073
                t = 60 * int(game.options[OPTION_TIMED_LIMIT])
 
1074
            else:
 
1075
                t = 0
 
1076
            x = datetime.timedelta(seconds=t + time.seconds)
 
1077
            if game.options.has_key(OPTION_TIMED_LIMIT):
 
1078
                if time.days < 0:
 
1079
                    x = -time
 
1080
                    x = datetime.timedelta(seconds=t - x.seconds )
 
1081
            game.timer = reactor.callLater(x.seconds, self.gameTimeExpired, gameId, client)
 
1082
            
 
1083
        client.gameTurnCurrent(gameId, time)
 
1084
        
 
1085
        for _player in game.getPlayers():
 
1086
            _client = self.getPlayerClient(_player)
 
1087
            if (_player != player):
 
1088
                _client.gameTurnOther( gameId, PlayerInfo(player.getUsername(), player.getScore(), len(player.getLetters()), player.time ))
 
1089
        
 
1090
        for s in game.getSpectators():
 
1091
            c = self.getPlayerClient(s)
 
1092
            c.gameTurnOther( gameId, PlayerInfo(player.getUsername(), player.getScore(), len(player.getLetters()), player.time ))
 
1093
        
 
1094
        self.sendGameInfoMessage(gameId, [player.getUsername(),TURN], client=None, level=constants.GAME_LEVEL)
 
1095
        self.sendGameStats(gameId)
 
1096
        if game.options[OPTION_SHOW_COUNT]:
 
1097
            self.sendLetterDistribution(gameId)
 
1098
    
 
1099
    def sendLetterDistribution(self, gameId):
 
1100
        '''
 
1101
        Send the letter distribution
 
1102
        
 
1103
        @param gameId: Game ID
 
1104
        '''
 
1105
        game = self.gameList[ gameId ]
 
1106
        
 
1107
        for p in game.getPlayers():
 
1108
            c = self.getPlayerClient(p)
 
1109
            c.sendLetterDistribution( gameId, game.getDistribution() )
 
1110
        
 
1111
        for s in game.getSpectators():
 
1112
            c = self.getPlayerClient(s)
 
1113
            c.sendLetterDistribution( gameId, game.getDistribution() )
 
1114
        
 
1115
    
 
1116
    def moveTimeExpired(self, gameId, client):
 
1117
        '''
 
1118
        Move time for a player has expired
 
1119
        
 
1120
        @param gameId:
 
1121
        @param client:
 
1122
        '''
 
1123
        game = self.gameList[ gameId ]
 
1124
        player = game.getPlayer( self.clients[client] )
 
1125
        player.time = datetime.timedelta(seconds=60 * int(game.options[OPTION_MOVE_TIME]))
 
1126
 
 
1127
        self.sendGameScores(gameId)
 
1128
        self.sendGameInfoMessage(gameId, [player.getUsername(),MOVE_OUT_OF_TIME], client=None, level=constants.GAME_LEVEL)
 
1129
        self.doGameTurn(gameId)
 
1130
    
 
1131
    def gameTimeExpired(self, gameId, client):
 
1132
        '''
 
1133
        Game time expired
 
1134
        
 
1135
        @param gameId: Game ID
 
1136
        @param client: Player client who has run out of time
 
1137
        '''
 
1138
        game = self.gameList[ gameId ]
 
1139
        player = game.getPlayer( self.clients[client] )
 
1140
 
 
1141
        self.sendGameInfoMessage(gameId, [player.getUsername(),OUT_OF_TIME], client=None, level=constants.GAME_LEVEL)
 
1142
        player.time = datetime.timedelta(seconds=0)
 
1143
        
 
1144
        self.gameOver(game, player)
 
1145
 
 
1146
    # Player leave game
 
1147
    def leaveGame(self, gameId, client, penalize=True, booted=False):
 
1148
        '''
 
1149
        Player leaves a game
 
1150
        
 
1151
        @param gameId: Game ID
 
1152
        @param client: ScrabbleServer Protocol
 
1153
        @param penalize: Flag to penalize the player a loss.  Not penalized if the connection was not closed cleanly
 
1154
        '''
 
1155
        
 
1156
        game = self.gameList[ gameId ]
 
1157
        player = game.getPlayer( self.clients[client] )
 
1158
        
 
1159
        if (game.isPaused() and not booted):
 
1160
            game.addPending( player )
 
1161
            return
 
1162
        
 
1163
        if not game.isComplete() and len(game.getPlayers()) > 1:
 
1164
            if game.isStarted():
 
1165
                if game.options[OPTION_RANKED] and penalize:
 
1166
                    self.db.users[ player.getUsername() ].addLoss(None)
 
1167
        
 
1168
        game.playerLeave(player)
 
1169
        
 
1170
        self.sendGameScores(gameId)
 
1171
        
 
1172
        self.sendGameInfoMessage(gameId, [player.getUsername(),LEFT_GAME], client=None, level=constants.GAME_LEVEL)
 
1173
 
 
1174
        # If there are no more players left, remove the game
 
1175
        if len(game.getPlayers()) == 0 and len(game.getSpectators()) == 0 and not game.isPaused():
 
1176
            #for s in game.getSpectators():
 
1177
            #    c = self.getPlayerClient( s )
 
1178
            #    c.gameLeave( game.getGameId() )
 
1179
            if game.timer is not None and game.timer.active():
 
1180
                game.timer.cancel()
 
1181
            del self.gameList[ gameId ]
 
1182
        elif not game.isComplete() and not game.isPaused():
 
1183
            if game.isCurrentPlayer( player ):
 
1184
                self.doGameTurn( gameId )
 
1185
 
 
1186
        self.refreshGameList()
 
1187
        
 
1188
    
 
1189
    def checkServerStats(self, player, moves):
 
1190
        '''
 
1191
        Check if this move is a new server stat
 
1192
        
 
1193
        @param player: Player
 
1194
        @param moves: Moves
 
1195
        '''
 
1196
        
 
1197
        score = 0
 
1198
        allDisp = ''
 
1199
        for move in moves:
 
1200
            score = score + move.getScore()
 
1201
            newdisp = '%s (%s) by %s' % (move.getWord(), str(move.getScore()), player.getUsername())
 
1202
            if self.db.stats.has_key(STAT_HIGHEST_SCORING_WORD):
 
1203
                data,disp = self.db.stats[STAT_HIGHEST_SCORING_WORD]
 
1204
                if (move.getScore() > data.getScore()):
 
1205
                    self.db.stats[STAT_HIGHEST_SCORING_WORD] = move,newdisp
 
1206
            else:
 
1207
                self.db.stats[STAT_HIGHEST_SCORING_WORD] = move,newdisp
 
1208
            
 
1209
            newdisp = '%s (%s) by %s' % (move.getWord(), str(move.length()), player.getUsername())
 
1210
            if self.db.stats.has_key(STAT_LONGEST_WORD):
 
1211
                data,disp = self.db.stats[STAT_LONGEST_WORD]
 
1212
                if (move.length() > data.length()):
 
1213
                    self.db.stats[STAT_LONGEST_WORD] = move,newdisp
 
1214
            else:
 
1215
                self.db.stats[STAT_LONGEST_WORD] = move,newdisp
 
1216
            allDisp = allDisp + '%s (%s) ' % (move.getWord(), str(move.getScore()))
 
1217
        
 
1218
        allDisp = allDisp + 'by %s' % player.getUsername()
 
1219
        d = allDisp
 
1220
        d = '%s, ' % str(score) + d
 
1221
        if self.db.stats.has_key(STAT_HIGHEST_SCORING_MOVE):
 
1222
            data, disp = self.db.stats[STAT_HIGHEST_SCORING_MOVE]
 
1223
            if score > data:
 
1224
                self.db.stats[STAT_HIGHEST_SCORING_MOVE] = score, d
 
1225
        else:
 
1226
            self.db.stats[STAT_HIGHEST_SCORING_MOVE] = score, d
 
1227
        
 
1228
        d = allDisp
 
1229
        d = '%s, ' % str(len(moves)) + d
 
1230
        if self.db.stats.has_key(STAT_MOST_WORDS_IN_MOVE):
 
1231
            data, disp = self.db.stats[STAT_MOST_WORDS_IN_MOVE]
 
1232
            if len(moves) > data:
 
1233
                self.db.stats[STAT_MOST_WORDS_IN_MOVE] = len(moves), d
 
1234
        else:
 
1235
            self.db.stats[STAT_MOST_WORDS_IN_MOVE] = len(moves), d
 
1236
        
 
1237
        self.db.sync()
 
1238
        
 
1239
 
 
1240
    # Player send move to game
 
1241
    def gameSendMove(self, gameId, onboard, moves, client):
 
1242
        '''
 
1243
        User sends moves to the game
 
1244
        
 
1245
        @param gameId: Game ID
 
1246
        @param onboard: Move containing letters put on the board
 
1247
        @param moves: List of Moves formed
 
1248
        @param client: ScrabbleServer Protocol
 
1249
        '''
 
1250
        
 
1251
        game = self.gameList[ gameId ]
 
1252
        player = game.getPlayer( self.clients[client] )
 
1253
        
 
1254
        if not player == game.getCurrentPlayer():
 
1255
            return
 
1256
        
 
1257
        if (game.isPaused()):
 
1258
            client.gameError(gameId, ServerMessage([MOVE_GAME_PAUSED]) )
 
1259
            return
 
1260
        if (not game.isInProgress()):
 
1261
            client.gameError(gameId, ServerMessage([NOT_IN_PROGRESS]) )
 
1262
            return
 
1263
            
 
1264
        
 
1265
        # Validate word in dictionary and not on the board alread
 
1266
        words = []
 
1267
        for move in moves:
 
1268
            word = util.getUnicode( move.getWord() )
 
1269
            if word not in self.dicts[ game.options[OPTION_RULES] ]:
 
1270
                client.gameError( gameId, ServerMessage([word, NOT_IN_DICT]) )
 
1271
                return
 
1272
            words.append( word )
 
1273
        
 
1274
        
 
1275
        
 
1276
        client.acceptMove(gameId)
 
1277
        score = self.getMovesScore(game, moves)
 
1278
        letters = self.getLettersFromMove(onboard)
 
1279
        player.removeLetters( letters )
 
1280
        player.addScore( score )
 
1281
        
 
1282
        self.removeModifiers(game, moves)
 
1283
        
 
1284
        game.addMoves(moves, player)
 
1285
        
 
1286
        game.resetPassCount()
 
1287
        
 
1288
        if len(game.getPlayers()) > 1:
 
1289
            self.checkServerStats(player, moves)
 
1290
        
 
1291
        # If the player used all 7 of his/her letters, give them an extra 50
 
1292
        if onboard.length() == 7:
 
1293
            player.addScore( constants.BINGO_BONUS_SCORE )
 
1294
            self.sendGameInfoMessage(gameId, [player.getUsername(), MADE_A_BINGO, '(%s)' % str(constants.BINGO_BONUS_SCORE)], client=None, level=constants.GAME_LEVEL)
 
1295
        
 
1296
        for p in game.getPlayers():
 
1297
            c = self.getPlayerClient(p)
 
1298
            c.sendMoves( gameId, moves )
 
1299
        
 
1300
        for s in game.getSpectators():
 
1301
            c = self.getPlayerClient(s)
 
1302
            c.sendMoves( gameId, moves )
 
1303
            
 
1304
        for move in moves:
 
1305
            self.sendGameInfoMessage(gameId, [player.getUsername(), HAS_ADDED, ' %s (%d)' % (move.getWord(), move.getScore())], client=None, level=constants.GAME_LEVEL)
 
1306
            
 
1307
            # If the player used all his/her letters and there are no more letters in the bag, the game is over
 
1308
        if (len(player.getLetters()) == 0 and game.isBagEmpty()):
 
1309
            
 
1310
            # Subtract everyones letter points
 
1311
            # Give points to the person who went out
 
1312
            players = game.getPlayers()
 
1313
            for p in players:
 
1314
                if p == player: 
 
1315
                    continue # Skip winner
 
1316
                    
 
1317
                letters = p.getLetters()
 
1318
                lmsg = ''
 
1319
                wmsg = ''
 
1320
                for letter in letters:
 
1321
                    p.addScore( letter.getScore() * -1 )
 
1322
                    player.addScore( letter.getScore() )
 
1323
                    lmsg = lmsg + '%s(%d) ' % (letter.getLetter(), letter.getScore() * -1)
 
1324
                    wmsg = wmsg + '%s(%d) ' % (letter.getLetter(), letter.getScore())
 
1325
                
 
1326
                self.sendGameInfoMessage(gameId, [p.getUsername(), LOSES, lmsg], client=None, level=constants.GAME_LEVEL)
 
1327
                self.sendGameInfoMessage(gameId, [player.getUsername(), GAINS, wmsg, FROM, p.getUsername()], client=None, level=constants.GAME_LEVEL)
 
1328
            
 
1329
            self.sendGameScores(game.getGameId())
 
1330
            
 
1331
            self.gameOver(game)
 
1332
            return
 
1333
 
 
1334
        letters = game.getLetters( player.getNumberOfLettersNeeded() )
 
1335
        if (len(letters) > 0):
 
1336
            player.addLetters(letters)
 
1337
            client.sendLetters(gameId, player.getLetters())
 
1338
        
 
1339
        
 
1340
        if game.options.has_key(OPTION_TIMED_GAME):
 
1341
            player.time = player.time - (datetime.datetime.now() - player.stamp)
 
1342
            player.time = datetime.timedelta(days=player.time.days, seconds=player.time.seconds+1 ) # +1 account for error
 
1343
            if game.timer is not None and game.timer.active():
 
1344
                game.timer.cancel()
 
1345
        
 
1346
        self.sendGameScores(game.getGameId())
 
1347
        
 
1348
        if game.isBagEmpty() or game.getCountLetters() < 7:
 
1349
            for p in game.getPlayers():
 
1350
                c = self.getPlayerClient(p)
 
1351
                c.gameBagEmpty(gameId)
 
1352
            for s in game.getSpectators():
 
1353
                c = self.getPlayerClient(s)
 
1354
                c.gameBagEmpty(gameId)
 
1355
            
 
1356
 
 
1357
        # Next player
 
1358
        self.doGameTurn(gameId)
 
1359
 
 
1360
    # Get the letters from the moves
 
1361
    def getLettersFromMove(self, move):
 
1362
        '''
 
1363
        Get the letters in a move
 
1364
        
 
1365
        @param move: Move
 
1366
        @return: List of letters in C{move}
 
1367
        '''
 
1368
        
 
1369
        letters = []
 
1370
        
 
1371
        for letter, x, y in move.getTiles():
 
1372
            letters.append( letter.clone() )
 
1373
 
 
1374
        return letters
 
1375
    
 
1376
    # Get the score of the list of moves
 
1377
    def getMovesScore(self, game, moves):
 
1378
        '''
 
1379
        Get the total score for a list of Moves
 
1380
        
 
1381
        @param game: ScrabbleGame
 
1382
        @param moves: List of Moves
 
1383
        @return: Total score for the list of Moves
 
1384
        '''
 
1385
        
 
1386
        total = 0
 
1387
        
 
1388
        for move in moves:
 
1389
            score = 0
 
1390
            apply = 0
 
1391
            modifier = constants.TILE_NORMAL
 
1392
            m_x = -1
 
1393
            m_y = -1
 
1394
            for letter,x,y in move.getTiles():
 
1395
                m = util.getTileModifier(x,y)
 
1396
                if m in constants.LETTER_MODIFIERS and not game.hasUsedModifier((x,y)):
 
1397
                    score = score + (m * letter.getScore())
 
1398
                else:
 
1399
                    score = score + letter.getScore()
 
1400
    
 
1401
                if (m >= modifier and not game.hasUsedModifier((x,y))):
 
1402
                    modifier = m
 
1403
                    if m in constants.WORD_MODIFIERS:
 
1404
                        apply = apply + 1
 
1405
                    m_x = x
 
1406
                    m_y = y
 
1407
 
 
1408
            if modifier in constants.WORD_MODIFIERS and not game.hasUsedModifier((m_x,m_y)):
 
1409
                
 
1410
                if util.isCenter(m_x, m_y):
 
1411
                    if game.options[OPTION_CENTER_TILE]:
 
1412
                        score = score * (modifier/2)
 
1413
                else:
 
1414
                    score = score * ((modifier/2) ** apply)
 
1415
                
 
1416
            move.setScore( score )
 
1417
            total = total + score
 
1418
        
 
1419
        return total
 
1420
    
 
1421
    def removeModifiers(self, game, moves):
 
1422
        '''
 
1423
        Mark off modifiers that are used in this move
 
1424
        
 
1425
        @param game: Game
 
1426
        @param moves: List of moves
 
1427
        '''
 
1428
        for move in moves:
 
1429
            for letter,x,y in move.getTiles():
 
1430
                m = util.getTileModifier(x,y)
 
1431
                if m in constants.LETTER_MODIFIERS and not game.hasUsedModifier((x,y)):
 
1432
                    game.addUsedModifier( (x,y) )
 
1433
                if m in constants.WORD_MODIFIERS and not game.hasUsedModifier((x,y)):
 
1434
                    game.addUsedModifier( (x,y) )
 
1435
        
 
1436
 
 
1437
 
 
1438
    # Player passes a move.  If all players pass, the game is over
 
1439
    def gamePassMove(self, gameId, client):
 
1440
        '''
 
1441
        Player passes his/her turn
 
1442
        
 
1443
        @param gameId: Game ID
 
1444
        @param client: ScrabbleServer Protocol
 
1445
        '''
 
1446
        
 
1447
        game = self.gameList[ gameId ]
 
1448
        player = game.getPlayer( self.clients[client] )
 
1449
        
 
1450
        if not player == game.getCurrentPlayer():
 
1451
            return
 
1452
        
 
1453
        if (not game.isInProgress()):
 
1454
            client.gameError(gameId, ServerMessage([NOT_IN_PROGRESS]) )
 
1455
            return
 
1456
        
 
1457
        if (game.isPaused()):
 
1458
            client.gameError(gameId, ServerMessage([PASS_PAUSED]) )
 
1459
            return
 
1460
        
 
1461
        try:
 
1462
            self.sendGameInfoMessage(gameId, [player.getUsername(), HAS_PASSED], client=None, level=constants.GAME_LEVEL)
 
1463
            
 
1464
            if game.options.has_key(OPTION_TIMED_GAME):
 
1465
                player.time = player.time - (datetime.datetime.now() - player.stamp)
 
1466
                player.time = datetime.timedelta(days=player.time.days, seconds=player.time.seconds)
 
1467
                if game.timer is not None and game.timer.active():
 
1468
                    game.timer.cancel()
 
1469
            
 
1470
            game.passMove()
 
1471
            
 
1472
            self.sendGameScores(gameId)
 
1473
            
 
1474
            self.doGameTurn(gameId)
 
1475
        except exceptions.GameOverException:
 
1476
            # If everyone has passed, assume that everyone still has letters
 
1477
            # Subtract everyones letter points
 
1478
            players = game.getPlayers()
 
1479
            for player in players:
 
1480
                letters = player.getLetters()
 
1481
                msg = ''
 
1482
                for letter in letters:
 
1483
                    player.addScore( letter.getScore() * -1 )
 
1484
                    msg = msg + '%s(%d) ' % (letter.getLetter(), letter.getScore() * -1)
 
1485
                
 
1486
                self.sendGameInfoMessage(gameId, [player.getUsername(), LOSES, msg], client=None, level=constants.GAME_LEVEL)
 
1487
            
 
1488
            self.sendGameScores(game.getGameId())
 
1489
            self.gameOver(game)
 
1490
    
 
1491
    def sendGameScores(self, gameId):
 
1492
        '''
 
1493
        Send game scores
 
1494
        '''
 
1495
        game = self.gameList[ gameId ]
 
1496
        players = game.getPlayers()
 
1497
        
 
1498
        for p in players:
 
1499
            c = self.getPlayerClient(p)
 
1500
            if (c):
 
1501
                c.sendGameUserList( game.getGameId(), self.getGamePlayerInfo(gameId, removePending=True) )
 
1502
        
 
1503
        for s in game.getSpectators():
 
1504
            c = self.getPlayerClient(s)
 
1505
            if (c):
 
1506
                c.sendGameUserList( game.getGameId(), self.getGamePlayerInfo(gameId, removePending=False) )
 
1507
    
 
1508
    def getGamePlayerInfo(self, gameId, removePending=False):
 
1509
        '''
 
1510
        Return list of PlayerInfo objects for each Player in a game
 
1511
        
 
1512
        @param gameId: Game ID
 
1513
        @param removePending: True to remove pending players from list
 
1514
        '''
 
1515
        game = self.gameList[ gameId ]
 
1516
        players = game.getPlayers()
 
1517
        
 
1518
        if removePending:
 
1519
            if (game.isPaused()):
 
1520
                x = []
 
1521
                pending = game.getPending()
 
1522
                for p in players:
 
1523
                    if p not in pending:
 
1524
                        x.append(p)
 
1525
                players = x
 
1526
        
 
1527
        return [ PlayerInfo(p.getUsername(), p.getScore(), len(p.getLetters()), util.TimeDeltaWrapper(p.time)) for p in players ]
 
1528
        
 
1529
        
 
1530
 
 
1531
    def gameOver(self, game, exclude=None):
 
1532
        '''
 
1533
        Declare game over for C{game}
 
1534
        
 
1535
        @param game: Game
 
1536
        @param exclude: Player to exclude
 
1537
        '''
 
1538
        
 
1539
        if game.timer is not None and game.timer.active():
 
1540
            game.timer.cancel()
 
1541
        
 
1542
        if game.options.has_key(OPTION_TIMED_GAME):
 
1543
            for p in game.getPlayers():
 
1544
                t = p.time
 
1545
                if t.days < 0:
 
1546
                    t = -t
 
1547
                    mins = math.ceil( (t.seconds / 60.0) )
 
1548
                    score = int((mins * constants.OVERTIME_PENALTY) * -1)
 
1549
                    p.addScore( score )
 
1550
                    msg = '%d points' % score
 
1551
                    self.sendGameInfoMessage(game.getGameId(), [p.getUsername(), LOSES, msg], client=None, level=constants.GAME_LEVEL)
 
1552
            
 
1553
        winners = game.getWinners(exclude)
 
1554
        
 
1555
        if len(winners) > 0:
 
1556
            newdisp = '%s by ' % (winners[0].getScore())
 
1557
            for w in winners:
 
1558
                newdisp = newdisp + w.getUsername() + ' '
 
1559
            
 
1560
            if len(game.getPlayers()) > 1:    
 
1561
                if self.db.stats.has_key(STAT_HIGHEST_TOTAL_SCORE):
 
1562
                    data,disp = self.db.stats[STAT_HIGHEST_TOTAL_SCORE]
 
1563
                    if (winners[0].getScore() > data):
 
1564
                        self.db.stats[STAT_HIGHEST_TOTAL_SCORE] = winners[0].getScore(),newdisp
 
1565
                else:
 
1566
                    self.db.stats[STAT_HIGHEST_TOTAL_SCORE] = winners[0].getScore(),newdisp
 
1567
        
 
1568
        winners = game.getWinners(exclude)
 
1569
        
 
1570
        # If there is less than one player in the game, don't count the score
 
1571
        count = len(game.getPlayers()) > 1
 
1572
        
 
1573
        if len(winners) > 0:
 
1574
            if len(winners) == 1:
 
1575
                winner = winners[0]
 
1576
                self.sendGameInfoMessage(game.getGameId(), ['%s (%d)' % (winner.username, int(winner.score)), HAS_WON], client=None, level=constants.GAME_LEVEL)
 
1577
                if count:
 
1578
                    if game.options[OPTION_RANKED]:
 
1579
                        self.db.users[ winner.getUsername() ].addWin( game.getPlayers() )
 
1580
                        self.setRankForPlayer( winner.getUsername() )
 
1581
                        self.auditUser( winner.getUsername(), audit.GameWinAction(winner, game.name, game.getPlayers()), False )
 
1582
            else:
 
1583
                msg = ''
 
1584
                for winner in winners:
 
1585
                    msg += '%s (%d)' % (winner.username, int(winner.score))
 
1586
                    msg += ', '
 
1587
                    if count:
 
1588
                        if game.options[OPTION_RANKED]:
 
1589
                            self.db.users[ winner.getUsername() ].addTie( winners )
 
1590
                            self.auditUser( winner.getUsername(), audit.GameTieAction(winner, game.name, winners), False )
 
1591
                msg = msg[:-2]
 
1592
                self.sendGameInfoMessage(game.getGameId(), [msg, HAVE_TIED], client=None, level=constants.GAME_LEVEL)
 
1593
        
 
1594
        for p in game.getPlayers():
 
1595
            if p not in winners:
 
1596
                if count:
 
1597
                    if game.options[OPTION_RANKED]:
 
1598
                        self.db.users[ p.getUsername() ].addLoss(winners)
 
1599
                        self.auditUser( p.getUsername(), audit.GameLossAction(winners, game.name, p), False )
 
1600
            c = self.getPlayerClient( p )
 
1601
            c.gameOver( game.getGameId() )
 
1602
        
 
1603
        for s in game.getSpectators():
 
1604
            c = self.getPlayerClient( s )
 
1605
            c.gameLeave( game.getGameId() )
 
1606
        
 
1607
        self.sendGameScores(game.getGameId())
 
1608
        
 
1609
        self.gameList[ game.getGameId() ].setComplete()
 
1610
        self.refreshGameList()
 
1611
        
 
1612
        self.db.sync()
 
1613
 
 
1614
    # Player trades in current set of letters for new letters
 
1615
    def tradeLetters(self, command, client):
 
1616
        '''
 
1617
        Player trades letters
 
1618
        
 
1619
        @param command: GameCommand
 
1620
        @param client: ScrabbleServer Protocol
 
1621
        '''
 
1622
        
 
1623
        game = self.gameList[ command.getGameId() ]
 
1624
        player = game.getPlayer( self.clients[client] )
 
1625
        
 
1626
        if not player == game.getCurrentPlayer():
 
1627
            return
 
1628
        
 
1629
        if (not game.isInProgress()):
 
1630
            client.gameError(gameId, ServerMessage([NOT_IN_PROGRESS]) )
 
1631
            return
 
1632
        
 
1633
        l = command.getData()
 
1634
        num = len(l)
 
1635
        player.removeLetters( l )
 
1636
        letters = game.getLetters( player.getNumberOfLettersNeeded() )
 
1637
        player.addLetters( letters )
 
1638
        game.returnLetters( l )
 
1639
        client.sendLetters( game.getGameId(), player.getLetters() )
 
1640
        
 
1641
        game.resetPassCount()
 
1642
        
 
1643
        if game.options.has_key(OPTION_TIMED_GAME):
 
1644
            player.time = player.time - (datetime.datetime.now() - player.stamp)
 
1645
            player.time = datetime.timedelta(days=player.time.days, seconds=player.time.seconds)
 
1646
            if game.timer is not None and game.timer.active():
 
1647
                game.timer.cancel()
 
1648
        
 
1649
        self.sendGameScores(command.getGameId())
 
1650
        
 
1651
        self.sendGameInfoMessage(game.getGameId(), [player.getUsername(),HAS_TRADED, '%s' % str(num), util.ternary(num == 1, LETTER, LETTERS)], client=None, level=constants.GAME_LEVEL)
 
1652
        
 
1653
        self.doGameTurn(game.getGameId())
 
1654
    
 
1655
    
 
1656
    def spectatorJoinGame(self, command, client):
 
1657
        '''
 
1658
        Spectator joins the game
 
1659
        
 
1660
        @param command:
 
1661
        @param client:
 
1662
        '''
 
1663
        game = self.gameList[ command.getGameId() ]
 
1664
        
 
1665
        command.setCommand( constants.GAME_SPECTATE_JOIN_OK )
 
1666
        client.acceptJoinGame( command, game.options  )
 
1667
 
 
1668
        player = self.clients[client].clone()
 
1669
        game.addSpectator( player )
 
1670
        
 
1671
        client.sendGameUserList( game.getGameId(), self.getGamePlayerInfo(game.getGameId()) )
 
1672
        client.sendMoves( game.getGameId(), game.getMoves() )
 
1673
        client.gameInfo( game.getGameId(), game.getLog() )
 
1674
        client.sendGameStats( game.getGameId(), game.getStats() )
 
1675
        client.sendGameOptions( game.getGameId(), game.getOptions() )
 
1676
        
 
1677
        if game.options.has_key(OPTION_TIMED_GAME) or game.options.has_key(OPTION_MOVE_TIME):
 
1678
            if not game.isPaused():
 
1679
                p = game.getCurrentPlayer()
 
1680
                if p is not None:
 
1681
                    time = p.time - (datetime.datetime.now() - p.stamp)
 
1682
                    time = datetime.timedelta(days=p.time.days, seconds=time.seconds+1 ) # +1 account for error
 
1683
                    client.gameTurnOther( game.getGameId(), PlayerInfo(p.getUsername(), p.getScore(), len(p.getLetters()), time ))
 
1684
            
 
1685
        self.sendGameInfoMessage(game.getGameId(), [player.getUsername(), IS_SPECTATING], client=None, level=constants.GAME_LEVEL)
 
1686
        self.sendSpectatorList( game.getGameId() )
 
1687
    
 
1688
    def spectatorLeaveGame(self, command, client):
 
1689
        '''
 
1690
        Spectator leaves the game
 
1691
        
 
1692
        @param command:
 
1693
        @param client:
 
1694
        '''
 
1695
        game = self.gameList[ command.getGameId() ]
 
1696
        game.spectatorLeave(self.clients[client])
 
1697
        
 
1698
        self.sendGameInfoMessage(game.getGameId(), [self.clients[client].getUsername(), NO_LONGER_SPECTATING], client=None, level=constants.GAME_LEVEL)
 
1699
        self.sendSpectatorList( game.getGameId() )
 
1700
        if len(game.getPlayers()) == 0 and len(game.getSpectators()) == 0 and not game.isPaused():
 
1701
            if game.timer is not None and game.timer.active():
 
1702
                game.timer.cancel()
 
1703
            del self.gameList[ game.getGameId() ]
 
1704
            self.refreshGameList()
 
1705
    
 
1706
    def getStats(self):
 
1707
        '''
 
1708
        Retrieve list of game stats and the list of users
 
1709
        
 
1710
        @return: List of tuples (stat_name, stat_value), users
 
1711
        '''
 
1712
        
 
1713
        s = []
 
1714
        s.append( (ServerMessage([NUMBER_USERS]), str(len(self.db.users))) )
 
1715
        s.append( (ServerMessage([MOST_USERS]), str(self.maxUsersLoggedIn)) )
 
1716
        s.append( (ServerMessage([UPTIME]), self.startDate) )
 
1717
        s.append( (ServerMessage([SERVER_VERSION]), constants.VERSION) )
 
1718
        
 
1719
        for key,value in self.db.stats.iteritems():
 
1720
            data,disp = value
 
1721
            s.append ( (ServerMessage([key]), disp) )
 
1722
        
 
1723
        users = []
 
1724
        for user in self.db.users.values():
 
1725
            users.append( (user.getUsername(), int(user.getNumericStat(constants.STAT_WINS)), int(user.getNumericStat(constants.STAT_LOSSES)), int(user.getNumericStat(constants.STAT_TIES)), user.rankName) )
 
1726
        
 
1727
            
 
1728
        return s, users, self.rankings.getRankInfo()
 
1729
    
 
1730
    def setRankForPlayer(self, username):
 
1731
        '''
 
1732
        Set rank for a player
 
1733
        
 
1734
        @param username: Username
 
1735
        '''
 
1736
        u = util.getUnicode(username)
 
1737
        r = self.db.users[u].getNumericStat(constants.STAT_RANK)
 
1738
        rank = self.rankings.getRankByWins(r)
 
1739
        self.db.users[u].rankName = rank.name
 
1740
    
 
1741
    def getUserStatus(self, username):
 
1742
        '''
 
1743
        Get users status
 
1744
        
 
1745
        @param username:
 
1746
        @return: ServerMessage detailing users activity on the system
 
1747
        '''
 
1748
        p,c = None,None
 
1749
        for client,player in self.clients.iteritems():
 
1750
            if player.getUsername() == username:
 
1751
                p,c = player,client
 
1752
                break
 
1753
        
 
1754
        if p is not None and c is not None:
 
1755
            message = []
 
1756
            playing = []
 
1757
            watching = []
 
1758
            for game in self.gameList.itervalues():
 
1759
                if game.hasPlayer(p):
 
1760
                    playing.append( game.getGameId() )
 
1761
            
 
1762
            for game in self.gameList.itervalues():
 
1763
                if game.hasSpectator(p):
 
1764
                    watching.append( game.getGameId() )
 
1765
            
 
1766
            if len(playing) > 0:
 
1767
                message.append(PLAYING)
 
1768
                count = 0
 
1769
                for game in playing:
 
1770
                    if count != 0:
 
1771
                        message.append(',')
 
1772
                    message.append(game)
 
1773
                    count += 1
 
1774
            
 
1775
            if len(watching) > 0:
 
1776
                if len(message) > 0:
 
1777
                    message.append( '-' )
 
1778
                    
 
1779
                message.append(WATCHING)
 
1780
                count = 0
 
1781
                for game in watching:
 
1782
                    if count != 0:
 
1783
                        message.append(',')
 
1784
                    message.append(game)
 
1785
                    count += 1
 
1786
            
 
1787
            if len(message) == 0:
 
1788
                message = [ ONLINE ]
 
1789
            
 
1790
            return ServerMessage(message)
 
1791
        else:
 
1792
            return ServerMessage([OFFLINE])
 
1793
 
 
1794
 
 
1795
    def saveGame(self, command, client):
 
1796
        '''
 
1797
        Save the game
 
1798
        
 
1799
        @param command:
 
1800
        @param client:
 
1801
        '''
 
1802
        game = self.gameList[ command.getGameId() ]
 
1803
            
 
1804
        if self.clients[client].getUsername() != game.creator:
 
1805
            client.gameError(game.getGameId(), ServerMessage([NOT_CREATOR]))
 
1806
            return
 
1807
        
 
1808
        if game.options.has_key(OPTION_TIMED_GAME) or game.options.has_key(OPTION_MOVE_TIME):
 
1809
            player = game.getCurrentPlayer()
 
1810
            player.time = player.time - (datetime.datetime.now() - player.stamp)
 
1811
            player.time = datetime.timedelta(days=player.time.days, seconds=player.time.seconds+1 ) # +1 account for error
 
1812
        
 
1813
        game.pause()
 
1814
        for player in game.getPlayers():
 
1815
            c = self.getPlayerClient(player)
 
1816
            c.pauseGame( game.getGameId() )
 
1817
        self.refreshGameList()
 
1818
        
 
1819
        self.db.games[ game.getGameId() ] = game
 
1820
        self.db.sync()
 
1821
        self.sendGameInfoMessage(command.getGameId(), [GAME_SAVED], None, level=constants.GAME_LEVEL)
 
1822
        
 
1823
        for player in game.getPlayers():
 
1824
            c = self.getPlayerClient(player)
 
1825
            c.gameLeave( game.getGameId() )
 
1826
        
 
1827
        
 
1828
        
 
1829
        
 
1830
        
 
1831
        
 
1832
        
 
1833
        
 
1834
        
 
1835
 
 
1836
 
 
1837
 
 
1838
 
 
1839
 
 
1840
class ScrabbleServer(NetstringReceiver):
 
1841
    '''
 
1842
    Server Protocol.
 
1843
    
 
1844
    This class is responsible for shuttling data to and from a client.
 
1845
    
 
1846
    There will be one instance of this class per client connected to the ServerFactory
 
1847
    '''
 
1848
    
 
1849
    
 
1850
    def __init__(self):
 
1851
        '''
 
1852
        Constructor
 
1853
        '''
 
1854
        
 
1855
        self.command = helper.CommandCreator()
 
1856
        self.username = None
 
1857
 
 
1858
    def stringReceived(self, data):
 
1859
        '''
 
1860
        Callback when data is received from the client
 
1861
        
 
1862
        Parse the data into a Command and handle it.
 
1863
        
 
1864
        @param data: Text data representing a command
 
1865
        @see: L{pyscrabble.command.helper.Command}
 
1866
        '''
 
1867
        logger.debug('Incoming: %s %s %s' % (repr(self.username), self.addr.host, data))
 
1868
        
 
1869
        command = serialize.loads( data )
 
1870
        
 
1871
        if ( isinstance(command, helper.LoginCommand) ):
 
1872
            self.handleLoginCommand( command )
 
1873
            return
 
1874
            
 
1875
        if ( isinstance(command, helper.ChatCommand) ):
 
1876
            self.factory.handleChatCommand( command, self )
 
1877
            return
 
1878
 
 
1879
        if ( isinstance(command, helper.GameCommand) ):
 
1880
            self.factory.handleGameCommand( command, self )
 
1881
            return
 
1882
        
 
1883
        if ( isinstance(command, helper.PrivateMessageCommand) ):
 
1884
            self.factory.handlePrivateMessageCommand(command, self)
 
1885
            return
 
1886
 
 
1887
    def handleLoginCommand(self, command):
 
1888
        '''
 
1889
        Handle a login command
 
1890
        
 
1891
        Callback to the Factory to authenticate the user
 
1892
        
 
1893
        @param command: LoginCommand
 
1894
        @see: L{pyscrabble.command.helper.LoginCommand}
 
1895
        '''
 
1896
        
 
1897
        
 
1898
        if (command.getCommand() == constants.LOGIN_INIT):
 
1899
            
 
1900
            # Check version
 
1901
            version = command.getData()
 
1902
            if (version is '' or version < constants.REQUIRED_VERSION):
 
1903
                command.setCommand( constants.LOGIN_DENIED )
 
1904
                command.setData( ServerMessage([REQ_VERSION, constants.REQUIRED_VERSION]) )
 
1905
            else:
 
1906
                
 
1907
                player = Player( command.getUsername() )
 
1908
                
 
1909
                if (self.factory.authenticate(command.getUsername(), command.getPassword())):
 
1910
                    if (self.factory.isLoggedIn(player)):
 
1911
                        c = self.factory.getPlayerClient(player)
 
1912
                        if c is not None:
 
1913
                            self.factory.removeClient(c)
 
1914
                            c.transport.loseConnection()
 
1915
                            
 
1916
                    self.username = command.getUsername()
 
1917
                    command.setCommand( constants.LOGIN_OK )
 
1918
                    self.factory.loginUser( player, self )
 
1919
                else:
 
1920
                    command.setData( ServerMessage([INVALID_USERNAME_PASSWORD]) )
 
1921
                    command.setCommand( constants.LOGIN_DENIED )
 
1922
                
 
1923
            self.writeCommand( command )
 
1924
 
 
1925
        if (command.getCommand() == constants.NEW_USER):
 
1926
            self.factory.createNewUser(command, self)
 
1927
            return
 
1928
 
 
1929
        if (command.getCommand() == constants.CHANGE_PASSWORD):
 
1930
            self.factory.changePassword(command, self)
 
1931
            return
 
1932
            
 
1933
    def joinChat(self, username):
 
1934
        '''
 
1935
        User joins the chatroom
 
1936
        
 
1937
        @param username: Username
 
1938
        '''
 
1939
        
 
1940
        command = self.command.createJoinChatCommand(username)
 
1941
        self.writeCommand( command )
 
1942
 
 
1943
    def sendUserList(self, users):
 
1944
        '''
 
1945
        Send List of Players on the server
 
1946
        
 
1947
        @param users: List of Players
 
1948
        @see: L{pyscrabble.game.player.Player}
 
1949
        '''
 
1950
        
 
1951
        command = self.command.createGetChatUsersCommand( users )
 
1952
        self.writeCommand( command )
 
1953
 
 
1954
    def postChatMessage(self, message):
 
1955
        '''
 
1956
        Post a chat message
 
1957
        
 
1958
        @param message: Message text
 
1959
        '''
 
1960
        
 
1961
        command = self.command.createPostChatMessageCommand(message)
 
1962
        self.writeCommand( command )
 
1963
 
 
1964
    def sendLetters(self, gameId, letters):
 
1965
        '''
 
1966
        Send Game Letters
 
1967
        
 
1968
        @param gameId: Game ID
 
1969
        @param letters: List of Letters
 
1970
        @see: L{pyscrabble.game.pieces.Letter}
 
1971
        '''
 
1972
        
 
1973
        command = self.command.createGetLettersCommand( gameId, letters )
 
1974
        self.writeCommand( command )
 
1975
    
 
1976
    def sendLetterDistribution(self, gameId, distribution):
 
1977
        '''
 
1978
        Send letter distribution
 
1979
        
 
1980
        @param gameId: GameID
 
1981
        @param distribution: dict(Letter,count)
 
1982
        '''
 
1983
        command = self.command.createGameDistributionCommand( gameId, distribution )
 
1984
        self.writeCommand( command )
 
1985
        
 
1986
 
 
1987
    def sendGameList(self, gameList):
 
1988
        '''
 
1989
        Send List of Games on the server
 
1990
        
 
1991
        @param gameList: List of Games on the server
 
1992
        @see: L{pyscrabble.game.game.ScrabbleGameInfo}
 
1993
        '''
 
1994
        
 
1995
        command = self.command.createGetGameListCommand( gameList )
 
1996
        self.writeCommand( command )
 
1997
 
 
1998
    def sendGameUserList(self, gameId, users):
 
1999
        '''
 
2000
        Send List of Players in a game
 
2001
        
 
2002
        @param gameId: Game ID
 
2003
        @param users: List of Players
 
2004
        @see: L{pyscrabble.game.player.Player}
 
2005
        '''
 
2006
        
 
2007
        command = self.command.createGameUserListCommand( gameId, users )
 
2008
        self.writeCommand( command )
 
2009
 
 
2010
    def denyJoinGame(self, command):
 
2011
        '''
 
2012
        Deny the users join game request
 
2013
        
 
2014
        @param command: GameCommand
 
2015
        @see: L{pyscrabble.command.helper.GameCommand}
 
2016
        '''
 
2017
        
 
2018
        self.writeCommand( command )
 
2019
 
 
2020
    def acceptJoinGame(self, command, options):
 
2021
        '''
 
2022
        Accept the users join game request
 
2023
        
 
2024
        @param command: GameCommand
 
2025
        @param options: Game options dict
 
2026
        @see: L{pyscrabble.command.helper.GameCommand}
 
2027
        '''
 
2028
        command.setData( options )
 
2029
        self.writeCommand( command )
 
2030
 
 
2031
    def gameTurnOther(self, gameId, player):
 
2032
        '''
 
2033
        Notify the user that it is C{player}'s turn
 
2034
        
 
2035
        @param gameId: Game ID
 
2036
        @param player: Player who has control of the board
 
2037
        '''
 
2038
        
 
2039
        command = self.command.createGameTurnOtherCommand(gameId, player)
 
2040
        self.writeCommand( command )
 
2041
 
 
2042
    def gameTurnCurrent(self, gameId, time):
 
2043
        '''
 
2044
        Notify the user that he/she has control of the board
 
2045
        
 
2046
        @param gameId: Game ID
 
2047
        @param time: Time left
 
2048
        @see: L{pyscrabble.command.helper.GameCommand}
 
2049
        '''
 
2050
        
 
2051
        command = self.command.createGameTurnCurrentCommand(gameId, time)
 
2052
        self.writeCommand( command )
 
2053
 
 
2054
    def sendMoves(self, gameId, moves):
 
2055
        '''
 
2056
        Notify the user that C{moves} have been posted to a Game
 
2057
        
 
2058
        @param gameId: Game ID
 
2059
        @param moves: List of Moves
 
2060
        @see: L{pyscrabble.game.pieces.Move}
 
2061
        '''
 
2062
        
 
2063
        command = self.command.createGameSendMoveCommand(gameId, moves)
 
2064
        self.writeCommand( command )
 
2065
 
 
2066
    def acceptMove(self, gameId):
 
2067
        '''
 
2068
        Notify the user that their submitted moves have been accepted
 
2069
        
 
2070
        @param gameId: Game ID
 
2071
        '''
 
2072
        
 
2073
        command = self.command.createGameAcceptMoveCommand(gameId)
 
2074
        self.writeCommand( command )
 
2075
 
 
2076
    def gameError(self, gameId, msg):
 
2077
        '''
 
2078
        Notify the user of a Game Error
 
2079
        
 
2080
        @param gameId: Game ID
 
2081
        @param msg: Error message
 
2082
        @see: L{pyscrabble.command.helper.GameCommand}
 
2083
        '''
 
2084
        
 
2085
        command = self.command.createGameErrorCommand(gameId, msg)
 
2086
        self.writeCommand( command )
 
2087
    
 
2088
    def showError(self, msg):
 
2089
        '''
 
2090
        Show a General Error message
 
2091
        
 
2092
        @param msg: Error message
 
2093
        '''
 
2094
        
 
2095
        command = self.command.createErrorCommand(msg)
 
2096
        self.writeCommand( command )
 
2097
    
 
2098
    def showInfo(self, msg):
 
2099
        '''
 
2100
        Show a General Info message
 
2101
        
 
2102
        @param msg: Info message
 
2103
        '''
 
2104
        
 
2105
        command = self.command.createInfoCommand(msg)
 
2106
        self.writeCommand( command )
 
2107
 
 
2108
    def gameLeave(self, gameId, disableChat = False):
 
2109
        '''
 
2110
        Notify the user that they are leaving the game
 
2111
        
 
2112
        @param gameId: Game ID
 
2113
        @see: L{pyscrabble.command.helper.GameCommand}
 
2114
        '''
 
2115
        
 
2116
        command = self.command.createGameLeaveCommand(gameId, disableChat)
 
2117
        self.writeCommand( command )
 
2118
    
 
2119
    def gameBoot(self, gameId):
 
2120
        '''
 
2121
        Boot a user from the game
 
2122
        
 
2123
        @param gameId: Game ID
 
2124
        @see: L{pyscrabble.command.helper.GameCommand}
 
2125
        '''
 
2126
        
 
2127
        command = self.command.createGameBootCommand(gameId)
 
2128
        self.writeCommand( command )
 
2129
    
 
2130
    def gameOver(self, gameId):
 
2131
        '''
 
2132
        Notify the user the game is over
 
2133
        
 
2134
        @param gameId: Game ID
 
2135
        @see: L{pyscrabble.command.helper.GameCommand}
 
2136
        '''
 
2137
        
 
2138
        command = self.command.createGameOverCommand(gameId)
 
2139
        self.writeCommand( command )
 
2140
 
 
2141
    def gameInfo(self, gameId, tup):
 
2142
        '''
 
2143
        Send a Game Info message
 
2144
        
 
2145
        @param gameId: Game ID
 
2146
        @param tup: A Tuple containing (boolean, message).  If boolean is true, it is a Server info Message.  Else it is a Player info message.
 
2147
        @see: L{pyscrabble.net.server.ServerFactory.sendGameInfoMessage}
 
2148
        '''
 
2149
        
 
2150
        command = self.command.createGameInfoCommand(gameId, tup)
 
2151
        self.writeCommand( command )
 
2152
 
 
2153
    def pauseGame(self, gameId):
 
2154
        '''
 
2155
        Notify the user that the Game is paused
 
2156
        
 
2157
        @param gameId: Game ID
 
2158
        @see: L{pyscrabble.command.helper.GameCommand}
 
2159
        '''
 
2160
        
 
2161
        command = self.command.createGamePauseCommand(gameId)
 
2162
        self.writeCommand( command )
 
2163
 
 
2164
    def unPauseGame(self, gameId):
 
2165
        '''
 
2166
        Notify the user that the Game is unpaused
 
2167
        
 
2168
        @param gameId: Game ID
 
2169
        '''
 
2170
        
 
2171
        command = self.command.createGameUnpauseCommand(gameId)
 
2172
        self.writeCommand( command )
 
2173
 
 
2174
    def connectionLost(self, reason):
 
2175
        '''
 
2176
        This users client has been disconnected.
 
2177
        
 
2178
        Remove them from the server
 
2179
        
 
2180
        @param reason: Failure
 
2181
        @see: L{twisted.python.failure.Failure}
 
2182
        '''
 
2183
        command = self.command.createLeaveChatCommand()
 
2184
        command.setData( isinstance(reason.value, error.ConnectionDone) )
 
2185
        self.factory.handleChatCommand(command, self)
 
2186
 
 
2187
    def logout(self):
 
2188
        '''
 
2189
        Log the user out of the game
 
2190
        
 
2191
        @see: L{pyscrabble.command.helper.LoginCommand}
 
2192
        '''
 
2193
        
 
2194
        command = self.command.createGoodbyeCommand()
 
2195
        self.writeCommand( command )
 
2196
    
 
2197
    def boot(self):
 
2198
        '''
 
2199
        Remove the user from the game
 
2200
        
 
2201
        @see: L{pyscrabble.command.helper.LoginCommand}
 
2202
        '''
 
2203
        
 
2204
        command = self.command.createBootedCommand()
 
2205
        self.writeCommand( command )
 
2206
    
 
2207
    def sendPrivateMessage(self, sender, data):
 
2208
        '''
 
2209
        Send a private message to this user
 
2210
        
 
2211
        @param sender: Username of the sender
 
2212
        @param data: Message text
 
2213
        @see: L{pyscrabble.command.helper.PrivateMessageCommand}
 
2214
        ''' 
 
2215
        
 
2216
        command = self.command.createPrivateMessageCommand(sender, '', data)
 
2217
        self.writeCommand( command )
 
2218
    
 
2219
    def setSpectatorChatEnabled(self, gameId, flag):
 
2220
        '''
 
2221
        Set Spectator Chat Enabeld
 
2222
        
 
2223
        @param gameId: Game ID
 
2224
        @param flag: True to enable Spectator Chatting
 
2225
        '''
 
2226
        command = self.command.createGameSpectatorChatCommand(gameId, flag)
 
2227
        self.writeCommand( command )
 
2228
    
 
2229
    def setSpectatorsAllowed(self, gameId, flag):
 
2230
        '''
 
2231
        Set Spectators allowed
 
2232
        
 
2233
        @param gameId: Game ID
 
2234
        @param flag: True to allow Spectators
 
2235
        '''
 
2236
        command = self.command.createGameSpectatorSetCommand(gameId, flag)
 
2237
        self.writeCommand( command )
 
2238
    
 
2239
    def sendGameStats(self, gameId, stats):
 
2240
        '''
 
2241
        Send Game stats
 
2242
        
 
2243
        @param gameId: Game ID
 
2244
        @param stats: Stats
 
2245
        '''
 
2246
        command = self.command.createGameStatsCommand(gameId, stats)
 
2247
        self.writeCommand( command )
 
2248
    
 
2249
    def sendGameOptions(self, gameId, options):
 
2250
        '''
 
2251
        Send Game options
 
2252
        
 
2253
        @param gameId: Game ID
 
2254
        @param options: Pptions
 
2255
        '''
 
2256
        command = self.command.createGameSendOptionsCommand(gameId, options)
 
2257
        self.writeCommand( command )
 
2258
    
 
2259
    def gameBagEmpty(self, gameId):
 
2260
        '''
 
2261
        Notify the client that the game bag is empty
 
2262
        
 
2263
        @param gameId: Game ID
 
2264
        '''
 
2265
        command = self.command.createGameBagEmptyCommand(gameId)
 
2266
        self.writeCommand( command )
 
2267
    
 
2268
    def gameSendSpectators(self, gameId, list):
 
2269
        '''
 
2270
        Send the list of spectators in a game
 
2271
        
 
2272
        @param gameId: Game ID
 
2273
        @param list: List
 
2274
        '''
 
2275
        command = self.command.createGameSendSpectatorsCommand(gameId, list)
 
2276
        self.writeCommand( command )
 
2277
    
 
2278
    def sendUserInfo(self, user):
 
2279
        '''
 
2280
        Send user info
 
2281
        
 
2282
        @param user: Username
 
2283
        '''
 
2284
        command = self.command.createUserInfoCommand(user.getUsername(), user)
 
2285
        self.writeCommand( command )
 
2286
    
 
2287
    def sendServerStats(self, stats):
 
2288
        '''
 
2289
        Send Server stats
 
2290
        
 
2291
        @param stats: Stats
 
2292
        '''
 
2293
        command = self.command.createServerStatsCommand(stats)
 
2294
        self.writeCommand( command )
 
2295
    
 
2296
    def sendOfflineMessages(self, messages):
 
2297
        '''
 
2298
        Send offline messages
 
2299
        
 
2300
        @param messages: List of PrivateMessages
 
2301
        '''
 
2302
        command = self.command.createGetMessagesCommand(messages)
 
2303
        self.writeCommand( command )
 
2304
        
 
2305
        
 
2306
    def writeCommand(self, command):
 
2307
        '''
 
2308
        Write the command data to the client
 
2309
        
 
2310
        @param command:
 
2311
        '''
 
2312
        dumped = serialize.dumps(command)
 
2313
        x = zlib.compress( dumped )
 
2314
        logger.debug('Outgoing: %s %s %s' % (self.username, self.addr.host,str(command)))
 
2315
        self.sendString( x )
 
2316
 
 
2317
    def __repr__(self):
 
2318
        '''
 
2319
        String representation of this Protocol.
 
2320
        "username host"
 
2321
        '''
 
2322
        
 
2323
        return "%s (%s) " % (self.username, self.addr.host)
 
2324