def __init__(self,name,options,server=False,id=str(uuid4())): self.name = name self.names = [name] pygame.init() self.windowsize = (1024,768) self.menuback = pygame.image.load("./cards/titlescreen.gif").convert() self.screen = pygame.display.set_mode(self.windowsize) self.g = CanastaRound() self.p = HumanPlayer() self.factory = ReconnectingPBClientFactory() self.port = 7171 self.server = server self.id = UUID(id) self.clock = pygame.time.Clock() self.rejected = False self.connected = False self.controller = False self.options = options self.positions = [None]*4 self.pause = False self.starting = False self.start_game = False self.start_match = False self.cancel = False self.started = False self.initialized = False self.new_players = True self.shut_down = False self.shutting_down = False self.CommandQueue = [] self.desktop = Desktop()
def __init__(self,controller_id): self.g = CanastaRound(images=False) self.playerlist = {} self.idlist = [] self.players = [] self.accept_clients = True self.game_started = False self.players_ready = [False]*4 self.wait_response = False self.pause = False self.executing = False self.computing = False self.controller_id = UUID(controller_id) self.shut_down = False self.last_command_time = 0 if DEBUGGER: print "server started"
class CanastaServer(pb.Root): def __init__(self,controller_id): self.g = CanastaRound(images=False) self.playerlist = {} self.idlist = [] self.players = [] self.accept_clients = True self.game_started = False self.players_ready = [False]*4 self.wait_response = False self.pause = False self.executing = False self.computing = False self.controller_id = UUID(controller_id) self.shut_down = False self.last_command_time = 0 if DEBUGGER: print "server started" def remote_debug(self): self.g.roundOver=True for player in self.playerlist: player[2].callRemote("debug") def remote_Reset(self,id): if self.playerlist[str(id)][1]: self.gameReset() def infoChat(self,message): """ Send a server informational message to the chat windows of the clients """ command = CanastaCommand(CHAT,[message],[INFO_STRING]) self.sendChat(command) def tryGoOut(self,id): """ Query the current player's partner to ask whether the player can go out. To prevent table talk, chatting (and all other command execution) is blocked until the partner responds. """ if self.g.canGoOut(): message = self.playerlist[str(id)][0] + " asked to go out" self.infoChat(message) opp_pos = self.playerset.index(str(id)) + 2 if opp_pos > 3: opp_pos -= 4 opp_player = self.playerlist[self.playerset[opp_pos]] self.wait_response = True if isinstance(opp_player[2],ComputerPlayer): result = opp_player[2].mayIGoOut() self.goOutNow(result) else: opp_player[2].callRemote("goOut").addCallbacks(self.goOutNow,self.goOutError) else: return def goOutError(self,obj): if DEBUGGER: print ["error going out",obj] def goOutNow(self,obj): """ Executed after querying a partner about going out. If response is yes, meld any staged cards and discard to end the round. If response is no, send a special command that prevents the player from going out this turn. """ if obj: self.infoChat("Partner said yes") code1 = CanastaCommand(MELD_CARDS,[],[]) code2 = CanastaCommand(DISCARD_CARD,[1],[]) self.execCommand(code1) self.execCommand(code2) else: self.infoChat("Partner said no") code = CanastaCommand(BLOCK_OUT,[],[]) self.execCommand(code) self.wait_response = False def replacePlayer(self,which,name): self.need_replace = which self.pause = True for id in self.idlist: player = self.playerlist[str(id)][2] print id,player if self.playerlist[str(id)][1]: player.callRemote("lostPlayer",name).addCallback(self.gotReplacement) else: player.callRemote("waitPlayer",name) def gotReplacement(self,obj): if obj: p = ComputerPlayer(self.need_replace) self.playerset[self.need_replace] = str(self.need_replace) self.playerlist[str(self.need_replace)] = ["Computer " + str(self.need_replace),False,p] print "replacing", self.need_replace,self.g.playernames self.g.playernames[self.need_replace] = "Computer " + str(self.need_replace) for p in self.idlist: player = self.playerlist[p] player[2].callRemote("updateNames",self.remote_Names()) else: self.playerset.remove(self.playerset[self.need_replace]) self.gameReset() for id in self.idlist: self.playerlist[id][2].callRemote("unPause") self.pause = False def removeDeadClient(self,player,p): """ Removes disconnected clients from the server's records. If the dead client was an observer, continue on with the game. If they were a player, end the game and kick the other clients back to the player-assignment stage. """ pname = self.playerlist[str(p)][0] self.idlist.remove(p) self.players.remove(self.playerlist[str(p)][0]) del self.playerlist[str(p)] if self.game_started: if p in self.playerset: which = self.playerset.index(p) if DEBUGGER: print "Player dropped off, querying" self.replacePlayer(which,pname) else: self.observerset.remove(p) if DEBUGGER: print "Observer dropped off, removing them" for p in self.idlist: player = self.playerlist[p] player[2].callRemote("removeName",pname) try: self.g.playernames[which] = None except: pass self.infoChat(pname + " has disconnected") def doneExecuting(self,obj=None): self.executing = False def execCommand(self,command): """ Excute a game command both locally and on the client players. If the round is over, update scores and prepare to wait for the clients. """ self.executing = True cur_turn = self.g.turn self.g.execCode(command) if DEBUGGER: print ["server executed",self.g.lastCommand,self.g.lastReturn] for turn, p in enumerate(self.playerset+self.observerset): player = self.playerlist[str(p)][2] if isinstance(player,ComputerPlayer): pass elif self.g.lastReturn: try: player.callRemote("readCommand",command).addCallbacks(self.sentCommand,self.errCommand) except: self.removeDeadClient(player,p) if self.g.lastReturn: self.updateComputers(cur_turn) if self.g.roundOver: if self.g.lastReturn: if DEBUGGER: print "ROUND OVER *** server updating score" team1round = self.g.cardPoints(1) + self.g.specialPoints(1,params=False) - self.g.handPoints(1) team2round = self.g.cardPoints(2) + self.g.specialPoints(2,params=False) - self.g.handPoints(2) self.g.team1score += team1round self.g.team2score += team2round for turn, p in enumerate(self.playerset): player = self.playerlist[str(p)][2] if isinstance(player,ComputerPlayer): self.players_ready[turn] = player.isReady() else: self.players_ready[turn] = False self.doneExecuting() def sendChat(self,command): """ Sends a chat message to all clients. """ self.g.execCode(command) if DEBUGGER: print [self.g.lastCommand,self.g.lastReturn] for p in self.idlist: player = self.playerlist[str(p)][2] if not isinstance(player,ComputerPlayer): player.callRemote("readCommand",command) def notClosed(self,obj): if DEBUGGER: print "Client did not close correctly:",obj self.is_closed = True def isClosed(self,obj): if DEBUGGER: print "Client closed correctly:",obj self.is_closed = True def gameReset(self): self.executing = True to_remove = [] for id in self.playerset: player = self.playerlist[str(id)][2] if isinstance(player,ComputerPlayer): print self.playerlist,id,self.g.playernames self.g.playernames.remove(self.playerlist[str(id)][0]) del self.playerlist[str(id)] to_remove.append(id) for id in to_remove: self.playerset.remove(id) for turn, p in enumerate(self.playerset+self.observerset): player = self.playerlist[str(p)][2] player.callRemote("updateNames",self.remote_Names()) player.callRemote("resetGame") self.playerset = [] self.observerset = [] self.game_started = False self.executing = False def nextRound(self): """ Start the next round. Reset the scores if someone went over 5000 in the last round. """ if not(False in self.players_ready): if DEBUGGER: print "initializing the next round" if ((self.g.team1score>5000) | (self.g.team2score>5000)) & (self.g.team1score != self.g.team2score): self.gameReset() else: self.initRound() self.initClients() def newGame(self): """ Tell the clients that a new game is starting, so they should reset their scores. """ self.g.newGame() for pos, p in enumerate(self.playerset+self.observerset): player = self.playerlist[p][2] if isinstance(player,ComputerPlayer): pass else: if DEBUGGER: print "resetting the game" player.callRemote("newGame") def initGame(self,options): """ Send the clients the global game settings: the gameplay options, the client's position on the board, and the names of the players. """ for pos, p in enumerate(self.playerset+self.observerset): player = self.playerlist[str(p)][2] if isinstance(player,ComputerPlayer): pass else: if DEBUGGER: print "initializing the client" player.callRemote("initGame",self.g.playernames,pos,options) def initRound(self): """ Initialize the local game object for the current round. This object will be kept in sync with the parallel objects that are held by the clients. """ self.g.initCanasta() self.g.dealRound() def initClients(self): """ Initialize the round by sending the clients an object describing the deal. After this is called, all game objects held by the server and the clients should be in an identical state. """ for pos, p in enumerate(self.playerset+self.observerset): player = self.playerlist[p][2] if p in self.observerset: player.callRemote("resetRound") if isinstance(player,ComputerPlayer): player.initRound(self.g.initStatus()) else: if DEBUGGER: print "starting the client round" player.callRemote("initRound") if DEBUGGER: print "initializing the cards" status = self.g.initStatus() player.callRemote("readInit",status) self.players_ready = [False]*4 def remote_joinServer(self, client, name, id, version): if not self.accept_clients: return "This server is not accepting connections -- it is probably in one-player mode." elif version != VERSION: return "Error: incompatible client, this host requires version "+VERSION[0]+"."+VERSION[1]+"."+VERSION[2] count = 1 newname = name while newname in self.players: newname = name + str(count) count += 1 name = newname if DEBUGGER: print ["player",name,"joined",id] if self.game_started: while self.executing | self.computing: pass self.executing = True sleep(LOOP_RATE*2) if DEBUGGER: print "Adding player as observer in position",len(self.observerset)+4 client.callRemote("initGame",self.g.playernames+[name],len(self.observerset)+4,self.g.options) client.callRemote("initRound") if DEBUGGER: print "initializing the cards" status = self.g.initStatus() self.observerset.append(id) self.g.playernames.append(name) client.callRemote("readInit",status) else: self.g.playernames = [None]*4 + self.players + [name] self.playerlist[id] = [name,False,client] if UUID(id) == self.controller_id: self.playerlist[id][1]=True self.players.append(name) self.idlist.append(id) for index, p in enumerate(self.idlist): player = self.playerlist[p] player[2].callRemote("updateNames",self.remote_Names()) if not self.game_started: player[2].callRemote("initGame",self.g.playernames,4+index,CanastaOptions()) else: pass self.infoChat(name+" has arrived.") client.callRemote("lookAlive").addCallback(self.doneExecuting) return self.playerlist[id][1] def remote_blockConnections(self): self.accept_clients = False def remote_hangUp(self,id): player = self.playerlist[id][2] self.removeDeadClient(player,id) def remote_takeCanastaCommand(self, id, command): """ The main command invoked by clients that want to execute commands. Chats should go through the back channel, but they will be redirected if they show up here. Game commands are allowed through only on the player's turn. (For efficiency, the client is programmed to only send commands on its turn). GO_OUT is a special command, which cannot be executed by the game engine (it will give an error if it is passed.) It is trapped by the server, which then tests whether the client can go out, and if so it queries the partner. All commands and chats are blocked if the server is waiting on a request to go out. """ if self.pause: return self.last_command_time = 0 client = self.playerlist[str(id)][2] if command.action == GO_OUT: if DEBUGGER: print "got go-out ask" self.tryGoOut(id) elif (not self.wait_response): if command.action == CHAT: self.sendChat(command) elif self.g.turn == self.playerset.index(str(id)): self.execCommand(command) def sentCommand(self,obj): if DEBUGGER: print "successfully sent command",obj def errCommand(self,obj): if DEBUGGER: print "error sending command",obj def remote_takeChat(self, id, command): self.last_command_time = 0 """ Back channel for sending chat messages, which does not check turn status. Blocked if the server is waiting on a request to go out. """ client = self.playerlist[str(id)][2] if (command.action == CHAT) & (not self.wait_response): self.sendChat(command) def remote_Names(self): namelist = [] if self.game_started: ids = self.playerset + self.observerset else: ids = self.idlist for i in ids: namelist.append(self.playerlist[i][0]) return namelist def remote_assignPlayers(self,id,poslist): self.all_computers = True if self.playerlist[str(id)][1]: self.playerset = [] self.observerset = [] names = [] positions = [None]*4 for index, p in enumerate(poslist): try: positions[index] = self.idlist[self.players.index(p)] except: pass for index, p in enumerate(positions): if p==None: cur_p = ComputerPlayer(index) self.playerset.append(str(index)) self.playerlist[str(index)] = ["Computer " + str(index),False,cur_p] names.append("Computer " + str(index)) else: self.all_computers = False self.playerset.append(p) names.append(poslist[index]) for i in self.idlist: if i not in positions: self.observerset.append(i) names.append(self.playerlist[str(i)][0]) return names def remote_Start(self,id,poslist,options): """ Start a game and initialize all the player positions, inserting computers where no human player is specified. This function only accepts commands from the controlling client, which is the first client to connect to the server. It is intended that this will always be the local player whose application has launched the server as its subprocess. """ if self.playerlist[str(id)][1]: names = self.remote_assignPlayers(id,poslist) if self.all_computers: options.animation=2000 else: options.animation=None self.g.gameStart(names,-1,options) print "NAMELIST:",self.g.playernames self.initGame(options) self.initRound() self.initClients() self.game_started = True self.infoChat("Game on!") def remote_isReady(self,id): """ Called by clients between rounds, to indicate that they are ready to start the next round. The server won't initialize the round until every client gives the 'ready' signal. """ if DEBUGGER: print "got ready",id client = self.playerlist[str(id)][2] self.players_ready[self.playerset.index(str(id))] = True if DEBUGGER: print self.players_ready def remote_Shutdown(self,id): client = self.playerlist[str(id)][2] controller = self.playerlist[str(id)][1] players = self.idlist if controller: self.executing = True for p in players: player = self.playerlist[str(p)][2] controller = self.playerlist[str(p)][1] name = self.playerlist[str(p)][0] if not isinstance(player,ComputerPlayer): if not controller: self.is_closed = False if DEBUGGER: print ["killing player",name] try: player.callRemote("endGame").addCallbacks(self.isClosed,self.notClosed) except: self.notClosed("Dead Client") while not self.is_closed: reactor.iterate() self.shut_down = True def updateComputers(self,cur_turn): """ Update the computer players on the results of the last play. If this is called more than once between commands, it will result in memory errors in the computer players. """ for turn, p in enumerate(self.playerset): player = self.playerlist[str(p)][2] if isinstance(player,ComputerPlayer): if DEBUGGER: print ["updating computer",turn] if cur_turn==turn: state = self.g.curState(active=True) else: state = self.g.curState(active=False) player.readPlay(state) def playLocals(self): """ The server's main computation loop, which runs concurrently with the Twisted reactor loop. It continually checks whether it's a computer player's turn, and if so it solicits a command. """ if self.shut_down: reactor.stop() elif self.game_started & (not self.executing) & (not self.g.roundOver) & (not self.pause): self.computing = True if isinstance(self.playerlist[self.playerset[self.g.turn]][2],ComputerPlayer) & (not self.g.roundOver): cur_turn = self.g.turn play = self.playerlist[self.playerset[self.g.turn]][2].getPlay(self.g.curState()) if play.action == CHAT: self.sendChat(play) else: self.execCommand(play) else: self.last_command_time += 1 if self.last_command_time > TIMEOUT/LOOP_RATE: self.last_command_time = 0 for turn, p in enumerate(self.playerset+self.observerset): player = self.playerlist[str(p)][2] if not isinstance(player,ComputerPlayer): try: player.callRemote("lookAlive") except: self.removeDeadClient(player,p) self.computing = False elif self.g.roundOver: self.nextRound()
class CanastaClient(pb.Referenceable): """ Handles all user input and graphical display. Connects to a server to make plays. All games, including the one-player game, use the client. """ def __init__(self,name,options,server=False,id=str(uuid4())): self.name = name self.names = [name] pygame.init() self.windowsize = (1024,768) self.menuback = pygame.image.load("./cards/titlescreen.gif").convert() self.screen = pygame.display.set_mode(self.windowsize) self.g = CanastaRound() self.p = HumanPlayer() self.factory = ReconnectingPBClientFactory() self.port = 7171 self.server = server self.id = UUID(id) self.clock = pygame.time.Clock() self.rejected = False self.connected = False self.controller = False self.options = options self.positions = [None]*4 self.pause = False self.starting = False self.start_game = False self.start_match = False self.cancel = False self.started = False self.initialized = False self.new_players = True self.shut_down = False self.shutting_down = False self.CommandQueue = [] self.desktop = Desktop() def callDebug(self,obj): if DEBUGGER: obj.callRemote("debug") def remote_debug(self): self.g.roundOver=True def Connect(self,obj): if DEBUGGER: print "Connection established with server" obj.callRemote("joinServer",self,self.name,str(self.id),VERSION).addCallback(self.isController) def Disconnect(self,obj): if DEBUGGER: print "Hanging up connection" obj.callRemote("hangUp",str(self.id)).addCallback(self.isDisconnected) def isDisconnected(self,obj): if DEBUGGER: print "Closed connection with server:",obj self.shut_down = True def failConnect(self,obj): if self.cancel: return "cancel" else: if DEBUGGER: print "Failed to connect, retrying..." sleep(1) reactor.connectTCP("localhost",self.port, self.factory) self.factory.getRootObject().addCallbacks(self.Connect,self.failConnect) def isController(self,obj): if isinstance(obj,str): print obj self.rejected = True reactor.stop() else: self.connected = True self.controller = obj def remote_lookAlive(self): return True def getNames(self,obj): obj.callRemote("Names").addCallback(self.gotNames) def remote_updateNames(self,namelist): self.names = namelist if self.initialized: print "got a new name",namelist if self.initialized: self.g.playernames.append(namelist[-1]) self.new_players = True def remote_removeName(self,name): if DEBUGGER: print "removing " + name + " from name list" if self.initialized: self.g.playernames.remove(name) self.names.remove(name) try: self.positions.remove(name) except: pass self.new_players = True def gotNames(self,obj): self.names = obj def startServer(self,obj): self.oneRef = obj self.oneRef.callRemote("Start",str(self.id),self.player_positions,self.options).addCallbacks(self.didStart,self.failStart) def didStart(self,obj): if DEBUGGER: print "Game started on the server" self.start_match = True if not self.server: self.factory.getRootObject().addCallbacks(self.blockClients) def failStart(self,obj): if DEBUGGER: print "Failed to start game on the server, retrying..." sleep(1) self.factory.getRootObject().addCallbacks(self.startServer,self.failStart) def blockClients(self,obj): obj.callRemote("blockConnections") def notClosed(self,obj): if DEBUGGER: print "Server did not close correctly:",obj self.shut_down = True def isClosed(self,obj): if DEBUGGER: print "Server closed correctly:",obj self.shut_down = True def stopServer(self,obj): self.shutting_down = True self.oneRef = obj self.oneRef.callRemote("Shutdown",str(self.id)).addCallbacks(self.isClosed,self.notClosed) def reportReady(self,obj): self.started=False self.initialized=False self.oneRef = obj if self.g.human in [0,1,2,3]: self.oneRef.callRemote("isReady",str(self.id)) def SendCommand(self,obj): self.oneRef = obj self.oneRef.callRemote("takeCanastaCommand",str(self.id),self.lastplay) def clearCommand(self,obj): self.lastplay=CanastaCommand(NO_PLAY,[],[]) def SendChat(self,obj): if DEBUGGER: print "sending chat" if len(self.chatwin.chattxt.text)>0: self.lastchat = CanastaCommand(CHAT,[self.chatwin.chattxt.text,self.g.myPos]) self.chatwin.chattxt.text = "" self.oneRef = obj self.oneRef.callRemote("takeChat",str(self.id),self.lastchat) def remote_initGame(self,players,human,options): if DEBUGGER: print "client game initialized" if options.animation == None: options.animation = self.options.animation self.g.gameStart(players,human,options) try: self.chatwin.close() except: pass self.genChat(self.g.CHATX[0],self.g.CHATX[1]) self.g.initCanasta(nextround=False) self.chatwin.enabled = True self.screen = pygame.display.set_mode(self.windowsize,RESIZABLE) def remote_resetRound(self): self.started = False self.initialized = False def remote_resetGame(self): self.starting = False self.start_game = False self.start_match = False self.cancel = False self.started = False self.initialized = False self.new_players = True self.shut_down = False self.shutting_down = False self.CommandQueue = [] def remote_newGame(self): if DEBUGGER: print "client game reset" self.g.newGame() def remote_initRound(self): try: self.desktop.query.close() except: pass if DEBUGGER: print "client round initialized" self.g.initCanasta() try: self.chatwin.close() except: pass self.genChat(self.g.curchatx,self.g.CHATX[1]) self.chatwin.enabled = True self.started = True def remote_readInit(self,status): if not self.initialized: if DEBUGGER: print "client got status" self.g.readInit(status) self.initialized = True def remote_readCommand(self,command): """ Read a command from the server. Chats are executed immediately, everything else is queued for execution later. """ if command.action==CHAT: self.g.execCode(command) return else: self.CommandQueue.append(command) retcode = True return [self.g.lastCommand,retcode] def execCommand(self,command): """ Execute a command on the local game object. Uses items that have previously been queued from the server. """ if self.g.turn==self.g.myPos: invis = False else: invis = True self.g.execCode(command,invisible=invis) retcode = self.g.lastReturn if DEBUGGER: print "result was",self.g.lastReturn if self.g.lastReturn: if self.g.roundOver: if DEBUGGER: print "ROUND OVER *** client updating score" team1round = self.g.cardPoints(1) + self.g.specialPoints(1,params=False) - self.g.handPoints(1) team2round = self.g.cardPoints(2) + self.g.specialPoints(2,params=False) - self.g.handPoints(2) self.g.team1score += team1round self.g.team2score += team2round self.genEndRound() self.clearCommand(None) if DEBUGGER & (not retcode): print "WARNING: command failed" return [self.g.lastCommand,retcode] def remote_lostPlayer(self,name): def ComputerOnClick(button): self.result = True self.desktop.query.close() self.desktop.query.position = (0,0) self.desktop.query.size = (0,0) def ResetOnClick(button): self.result = False self.desktop.query.close() self.desktop.query.position = (0,0) self.desktop.query.size = (0,0) self.result = None try: self.desktop.query.close() except: pass defaultStyle.init(gui) endStyle = {'font-color': (255,255,255), 'font': font.Font(None,20), 'autosize': True, "antialias": True,'border-width': False, 'border-color': (0,0,0), 'wordwrap': False} self.desktop.query = Window(position = (250,180), size = (500,200), parent = self.desktop, text = "Lost Player", closeable = False, shadeable = False) labelStyleCopy = gui.defaultLabelStyle.copy() Label(position = (30,50),size = (100,0), parent = self.desktop.query, text = str(name) + " has disconnected", style = endStyle) Label(position = (30,75),size = (100,0), parent = self.desktop.query, text = "Do you want to replace them with a computer player?", style = endStyle) Label(position = (30,100),size = (100,0), parent = self.desktop.query, text = "Or end this game and start a new one?", style = endStyle) Label(position = (30,125),size = (100,0), parent = self.desktop.query, text = "If you replace them, you can add them back in if they reconnect", style = endStyle) Computer_button = Button(position = (30,170), size = (175,0), parent = self.desktop.query, text = "Replace with computer") Computer_button.onClick = ComputerOnClick Reset_button = Button(position = (270,170), size = (175,0), parent = self.desktop.query, text = "Start over with a new game") Reset_button.onClick = ResetOnClick while self.result == None: self.defaultInput() self.DrawQuery() return self.result def remote_waitPlayer(self,name): defaultStyle.init(gui) endStyle = {'font-color': (255,255,255), 'font': font.Font(None,20), 'autosize': True, "antialias": True,'border-width': False, 'border-color': (0,0,0), 'wordwrap': False} self.desktop.query = Window(position = (250,180), size = (500,200), parent = self.desktop, text = "Lost Player", closeable = False, shadeable = False) labelStyleCopy = gui.defaultLabelStyle.copy() Label(position = (30,50),size = (100,0), parent = self.desktop.query, text = str(name) + " has disconnected", style = endStyle) Label(position = (30,100),size = (100,0), parent = self.desktop.query, text = "Waiting for the game host to replace them or restart the game", style = endStyle) self.pause = True return def askReset(self): def YesOnClick(button): self.pause = False def1 = self.factory.getRootObject() def1.addCallback(self.gameReset) self.desktop.query.close() self.desktop.query.position = (0,0) self.desktop.query.size = (0,0) def NoOnClick(button): self.pause = False self.desktop.query.close() self.desktop.query.position = (0,0) self.desktop.query.size = (0,0) defaultStyle.init(gui) endStyle = {'font-color': (255,255,255), 'font': font.Font(None,24), 'autosize': True, "antialias": True,'border-width': False, 'border-color': (0,0,0), 'wordwrap': False} self.desktop.query = Window(position = (350,250), size = (300,200), parent = self.desktop, text = "Reset", closeable = False, shadeable = False) labelStyleCopy = gui.defaultLabelStyle.copy() Label(position = (100,50),size = (100,0), parent = self.desktop.query, text = "Reset the game", style = endStyle) Label(position = (100,100),size = (100,0), parent = self.desktop.query, text = "Are you sure?", style = endStyle) Yes_button = Button(position = (50,150), size = (40,0), parent = self.desktop.query, text = "Yes") No_button = Button(position = (200,150), size = (40,0), parent = self.desktop.query, text = "No") Yes_button.onClick = YesOnClick No_button.onClick = NoOnClick self.pause = True return def gameReset(self,obj): obj.callRemote("Reset",str(self.id)) def remote_unPause(self): try: self.desktop.query.close() self.desktop.query.position = (0,0) self.desktop.query.size = (0,0) except: pass self.pause = False def remote_goOut(self): """ Called remotely by the server when the client's partner has asked to go out. Waits for user input. """ self.done = False def OKOnClick(button): self.done = True self.response = True def cancelOnClick(button): self.done = True self.response = False defaultStyle.init(gui) defaultStyle.init(gui) desktop_main = Desktop() desktop = Window(position = (300,220), size = (400,200), parent = desktop_main, text = "Go Out", closeable = False, shadeable = False) desktop.onClose = cancelOnClick labelStyleCopy = gui.defaultLabelStyle.copy() Label(position = (100,100),size = (200,0), parent = desktop, text = 'Your partner asked: "May I go out?"', style = labelStyleCopy) OK_button = Button(position = (100,140), size = (50,0), parent = desktop, text = "Yes") cancel_button = Button(position = (200,140), size = (50,0), parent = desktop, text = "No") OK_button.onClick = OKOnClick cancel_button.onClick = cancelOnClick while not self.done: #Handle Input Events for event in gui.setEvents(): if event.type == QUIT: return elif event.type == KEYDOWN and event.key == K_ESCAPE: return self.DrawGame(flip=False) desktop_main.update() desktop_main.draw() pygame.display.flip() return self.response def remote_endGame(self): """ Called by the server on all non-controlling clients after the controller sends the kill signal. Should send a confirmation and then kill the reactor in the next input loop. """ if DEBUGGER: print "quitting client" pygame.quit() self.shut_down = True def overWindow(self): result = False try: temp = self.desktop.assign assign = True assign_pos = self.desktop.assign.position assign_size = self.desktop.assign.size except: assign = False try: temp = self.desktop.query query = True query_pos = self.desktop.query.position query_size = self.desktop.query.size except: query = False try: temp = self.chatwin chat = True chat_pos = self.chatwin.position chat_size = self.chatwin.size except: chat = False if gui.events != None: for event in gui.events: if event.type in [MOUSEBUTTONUP,MOUSEBUTTONDOWN,MOUSEMOTION]: if assign: if (event.pos[0] > assign_pos[0]) & (event.pos[0]<assign_pos[0]+assign_size[0]) & (event.pos[1] > assign_pos[1]) & (event.pos[1]<assign_pos[1]+assign_size[1]): result = True if query: if (event.pos[0] > query_pos[0]) & (event.pos[0]<query_pos[0]+query_size[0]) & (event.pos[1] > query_pos[1]) & (event.pos[1]<query_pos[1]+query_size[1]): result = True if chat: if (event.pos[0] > chat_pos[0]) & (event.pos[0]<chat_pos[0]+chat_size[0]) & (event.pos[1] > chat_pos[1]) & (event.pos[1]<chat_pos[1]+chat_size[1]): result = True return result def defaultInput(self,chat=True): if self.chatwin.chattxt.hasFocus: self.g.enterchat = True else: self.g.enterchat = False play = self.p.getPlay(self.g,gui.events) if play.action == QUIT_GAME: def1 = self.factory.getRootObject() if self.controller: def1.addCallbacks(self.stopServer) else: def1.addCallbacks(self.Disconnect) play = CanastaCommand(NO_PLAY,[],[]) elif (play.action == CHAT) & chat: self.lastchat = play def1 = self.factory.getRootObject() def1.addCallback(self.SendChat) play = CanastaCommand(NO_PLAY,[],[]) elif play.action == RESIZE: self.screen = pygame.display.set_mode(play.arglist[0],RESIZABLE) self.windowsize = play.arglist[0] self.chatwin.close() self.genChat(self.g.curchatx,self.g.CHATX[1]) play = CanastaCommand(NO_PLAY,[],[]) if self.g.animating: self.g.animate() else: try: self.execCommand(self.CommandQueue.pop(0)) except: pass return play def getInput(self): """ The main user input loop. Runs concurrently with Twisted's main reactor, so that the user can enter commands whenever they want. Game commands are sent to the server for execution. If valid, the server will call back to execute them locally. Chats are sent to the server through the special back channel that allows them to be distributed even if it's someone else's turn. Cards can be moved or selected at any time, but commands will not be executed unless it's the client's turn. For efficiency, the client is coded to only submit commands on its turn. For stability, however, the server is programmed to check for the turn before executing any non-chat command it receives. """ self.clock.tick(40) if self.overWindow(): self.p.over_window = True else: self.p.over_window = False if self.cancel: reactor.stop() if self.shut_down: try: pygame.quit() except: pass try: reactor.stop() except: pass elif self.pause: self.defaultInput() self.DrawQuery() elif self.shutting_down: pass elif self.started & self.initialized: if self.chatwin.chattxt.hasFocus: self.g.enterchat = True else: self.g.enterchat = False play = self.defaultInput() if (self.g.roundOver) | (self.p.viewhelp==1): if self.p.viewhelp==1: try: if self.desktop.query.wintype != "help": try: self.desktop.query.close() self.desktop.query.position = (0,0) self.desktop.query.size = (0,0) self.genHelp() except: self.genHelp() except: self.genHelp() self.DrawQuery() else: self.DrawGame() if play.action == RESET: if self.controller: self.askReset() elif play.action != NO_PLAY: if self.g.turn==self.g.myPos: self.lastplay = play def1 = self.factory.getRootObject() def1.addCallback(self.SendCommand) if self.p.viewhelp==0: try: if self.desktop.query.wintype == "help": self.desktop.query.close() self.desktop.query.position = (0,0) self.desktop.query.size = (0,0) except: pass elif (not self.start_match) & self.controller & (not self.server): for event in pygame.event.get(): if event.type == QUIT: self.cancel = True elif event.type == KEYDOWN and event.key == K_ESCAPE: self.cancel = True if self.connected: self.DrawWait("Waiting for game to start...") else: self.DrawWait("Connecting to the game server...") if not self.starting: if DEBUGGER: print "Starting the server" self.factory.getRootObject().addCallbacks(self.startServer,self.failStart) self.starting = True elif (not self.server) | self.start_match: if self.connected: if self.chatwin.chattxt.hasFocus: self.g.enterchat = True else: self.g.enterchat = False self.DrawWait("Waiting for game to start...") play = self.p.getPlay(self.g,gui.events) if play.action == QUIT_GAME: def1 = self.factory.getRootObject() if self.controller: def1.addCallbacks(self.stopServer) else: def1.addCallbacks(self.Disconnect) elif play.action == CHAT: self.lastchat = play def1 = self.factory.getRootObject() def1.addCallback(self.SendChat) else: for event in pygame.event.get(): if event.type == QUIT: self.shut_down = True elif event.type == KEYDOWN and event.key == K_ESCAPE: self.shut_down = True self.DrawWait("Connecting to the game server...") elif not self.connected: self.DrawWait("Setting up the game server...") else: if not self.start_game: if self.chatwin.chattxt.hasFocus: self.g.enterchat = True else: self.g.enterchat = False play = self.p.getPlay(self.g,gui.events) #Handle Input Events for event in gui.events: if event.type == KEYDOWN and event.key == 13: if not self.g.enterchat: self.start_game = True else: pass if play.action == QUIT_GAME: def1 = self.factory.getRootObject() def1.addCallbacks(self.stopServer) elif play.action == CHAT: self.lastchat = play def1 = self.factory.getRootObject() def1.addCallback(self.SendChat) elif not self.starting: playernames=[] for index, p in enumerate(self.positions): playernames.append(p) self.player_positions = playernames self.factory.getRootObject().addCallback(self.startServer) self.starting = True self.DrawAssign() def readyStartGame(self): self.start_game = True ##################################### #Drawing routines ##################################### def DrawAssign(self): def OKOnClick(button): self.start_game = True self.desktop.assign.close() self.desktop.assign.position = (0,0) self.desktop.assign.size = (0,0) def assignPlayer(arg): which = self.glist.index(arg.name) last = self.gr[which] if DEBUGGER: print arg.name,self.glist,which,last if arg.value==None: return loc = posnames.index(arg.text) #Assign an observer. Allows starting a game with no human players only if the debugger is on. if (loc == 4) & ((positions != [None]*4) | DEBUGGER): try: self.gr[which].value = False except: pass for i in range(len(positions)): if positions[i]==arg.name: positions[i]=None self.gr[which] = arg #Reject the assign if it conflicts with another player's assignment or leaves no human players with the debugger off. elif (positions[loc]!=None) | ((loc==4) & (positions==[None]*4) & (not DEBUGGER)): print "BZZT!" arg.value = False self.gr[which].value = True #Otherwise, assign the position else: try: self.gr[which].value = False except: pass for i in range(len(positions)): if positions[i]==arg.name: positions[i]=None positions[loc] = arg.name self.gr[which] = arg if DEBUGGER: print positions self.positions = positions playernames=self.names if self.new_players: try: self.desktop.assign.close() self.desktop.query.position = (0,0) self.desktop.query.size = (0,0) except: pass optionsurf = pygame.image.load("./art/optionbox.png").convert_alpha() self.desktop_main = Desktop() self.desktop.assign = Window(position = (100,100), size = (800,600), parent = self.desktop, text = "Assign players", closeable = False, shadeable = False) self.gr = [] self.glist = [] defaultStyle.init(gui) labelStyleCopy = gui.defaultLabelStyle.copy() labelStyleCopy['wordwrap'] = True if self.positions == [None]*4: default = True else: default = False positions = self.positions posnames = ["Bottom","Left","Top","Right","Observer"] for index, p in enumerate(self.names): if index<4: def_pos = posnames[index] else: def_pos = None if index==0: label1 = Label(position = (125,125),size = (200,0), parent = self.desktop.assign, text = "Players will appear here when they connect to your game.", style = labelStyleCopy) label2 = Label(position = (125,145),size = (200,0), parent = self.desktop.assign, text = "Assign them to a player position and press the start button when ready.", style = labelStyleCopy) label3 = Label(position = (125,165),size = (200,0), parent = self.desktop.assign, text = "Unfilled positions will be filled with computer players.", style = labelStyleCopy) label3 = Label(position = (125,185),size = (200,0), parent = self.desktop.assign, text = "Observers can watch the game and use the chat window, and may be included in the next round.", style = labelStyleCopy) label_name = Label(position = (125,220 + index*25),size = (200,0), parent = self.desktop.assign, text = p, style = labelStyleCopy) if (index<4) & default: positions[index] = p self.gr.append(None) self.glist.append(p) for index2, pos in enumerate(posnames): o = CheckBox(position = (200+index2*75,220+index*25), parent = self.desktop.assign, text = pos, style = gui.createOptionBoxStyle(gui.defaultFont, optionsurf, 12, (255,255,255), (100,100,100), autosize = True)) o.name = p print p o.index = index2 try: if positions[index2]==p: o.value = True self.gr[index] = o except: pass if (index2==4) & (self.gr[index]==None): o.value = True o.onValueChanged = assignPlayer OK_button = Button(position = (125,400), size = (50,0), parent = self.desktop.assign, text = "Start") OK_button.onClick = OKOnClick self.positions = positions self.new_players = False self.DrawGame() def DrawWait(self,message): defaultStyle.init(gui) desktop_main = Desktop() desktop = Window(position = (300,220), size = (400,200), parent = desktop_main, text = "Waiting", closeable = False, shadeable = False) labelStyleCopy = gui.defaultLabelStyle.copy() Label(position = (100,75),size = (50,0), parent = desktop, text = message, style = labelStyleCopy) if not self.connected: font = pygame.font.Font("FreeSans.ttf", 30) self.titletext = font.render("Play against the computer",1,(255,255,255)) self.titletext2 = font.render("Start a network game",1,(255,255,255)) self.titletext3 = font.render("Join a network game",1,(255,255,255)) self.titletext4 = font.render("Options",1,(255,255,255)) self.titlepos = self.titletext.get_rect() self.titlepos.centerx = 512 self.titlepos.centery = 350 self.titlepos2 = self.titletext2.get_rect() self.titlepos2.centerx = 512 self.titlepos2.centery = 450 self.titlepos3 = self.titletext3.get_rect() self.titlepos3.centerx = 512 self.titlepos3.centery = 550 self.titlepos4 = self.titletext4.get_rect() self.titlepos4.centerx = 512 self.titlepos4.centery = 650 self.screen.fill((0,0,255)) #YOUR RENDERING HERE!!! self.screen.blit(self.menuback, (0,0)) self.screen.blit(self.titletext,self.titlepos) self.screen.blit(self.titletext2,self.titlepos2) self.screen.blit(self.titletext3,self.titlepos3) self.screen.blit(self.titletext4,self.titlepos4) else: self.DrawGame(flip=False) #Last thing to draw, desktop try: desktop_main.update() desktop_main.draw() except: pass #Flips! pygame.display.flip() def DrawGame(self,flip=True): screen = self.screen g = self.g p = self.p playernames = self.g.playernames # DRAWING screen.fill((0x00, 0xb0, 0x00)) #Stage area pygame.draw.rect(screen,(0,0,0),(g.curstagexy[0]-5,g.curstagexy[1]-5,g.curstagexy[2]+10,g.curstagexy[3]+10)) pygame.draw.rect(screen,(0,255,0),g.curstagexy) sr = screen.get_rect() centerx = sr.centerx centery = sr.centery #Turn arrows arrow_length = 30 headw = 10 headl = 15 if g.turn==0: fl = [[(centerx,centery+20),(centerx,centery+20+arrow_length)], [(centerx-headw,centery+20+arrow_length-headl),(centerx,centery+20+arrow_length)], [(centerx+headw,centery+20+arrow_length-headl),(centerx,centery+20+arrow_length)]] elif g.turn==1: fl = [[(centerx-90,centery-50),(centerx-90-arrow_length,centery-50)], [(centerx-90-arrow_length+headl,centery-50-headw),(centerx-90-arrow_length,centery-50)], [(centerx-90-arrow_length+headl,centery-50+headw),(centerx-90-arrow_length,centery-50)]] elif g.turn==2: fl = [[(centerx,centery-90),(centerx,centery-90-arrow_length)], [(centerx-headw,centery-90-arrow_length+headl),(centerx,centery-90-arrow_length)], [(centerx+headw,centery-90-arrow_length+headl),(centerx,centery-90-arrow_length)]] elif g.turn==3: fl = [[(centerx+100,centery-50),(centerx+100+arrow_length,centery-50)], [(centerx+100+arrow_length-headl,centery-headw-50),(centerx+100+arrow_length,centery-50)], [(centerx+100+arrow_length-headl,centery+headw-50),(centerx+100+arrow_length,centery-50)]] for points in fl: pygame.draw.aaline(screen,(200,0,0), points[0], points[1]) if g.roundOver: for c in g.cardGroup.cards: if (c.side==0) & c.location in range(3,401): c.flip() pygame.draw.rect(screen,(0,0,0),(g.curlocxy[1][0]+2,g.curlocxy[1][1]+2,70,90),2) g.cardGroup.draw(screen) if g.selectionRect.width > 0 and g.selectionRect.height > 0: pygame.draw.rect(screen,(0xff,0xff,0x00),g.selectionRect,3) if g.curState().turnState == PRE_DRAW: state_text = "Draw or pick up" else: state_text = "Meld or discard" #Score area pygame.draw.rect(screen,(0,0,0),(g.curscorex-5,5,280,60)) pygame.draw.rect(screen,(0,0,255),(g.curscorex,10,270,50)) font = pygame.font.Font("freesansbold.ttf", 14) font2 = pygame.font.Font("FreeSans.ttf", 14) roundtext = font.render("%s%s" % ("ROUND ",g.round),1,(255,255,255)) team1text = font.render("%s%s" % ("Team 1: ",g.team1score),1,(255,255,255)) team2text = font.render("%s%s" % ("Team 2: ",g.team2score),1,(255,255,255)) curteamtext = font2.render("%s%s%s" % (playernames[g.turn],": ",state_text),1,(255,255,255)) roundpos = roundtext.get_rect() roundpos.centerx = g.curscorex + 40 roundpos.centery = 23 team1pos = roundtext.get_rect() team1pos.centerx = g.curscorex + 110 team1pos.centery = 23 team2pos = roundtext.get_rect() team2pos.centerx = g.curscorex + 210 team2pos.centery = 23 curteampos = roundtext.get_rect() curteampos.centerx = g.curscorex + 40 curteampos.centery = 43 screen.blit(roundtext,roundpos) screen.blit(team1text,team1pos) screen.blit(team2text,team2pos) screen.blit(curteamtext,curteampos) #Chat window self.chatwin.chatdisplay.text = "" if not self.chatwin.shaded: for i in range(-1,-8,-1): try: text = self.g.chatlist[i] count = 0 rendered = gui.wrapText(text + "\n" + self.chatwin.chatdisplay.text,self.chatwin.chatdisplay.style['font'],self.chatwin.chatdisplay.size[0]) for char in rendered: if char=="\n": count += 1 if count<9: self.chatwin.chatdisplay.text = self.g.chatlist[i] + "\n" +self.chatwin.chatdisplay.text except: pass gui.setEvents() try: self.desktop.update() except: pass self.desktop.draw() if flip: pygame.display.flip() def genEndRound(self): self.next_round = False def ContinueOnClick(button): self.desktop.query.close() self.desktop.query.position = (0,0) self.desktop.query.size = (0,0) def1 = self.factory.getRootObject() def1.addCallback(self.reportReady) team1round = self.g.cardPoints(1) + self.g.specialPoints(1,params=False) - self.g.handPoints(1) team2round = self.g.cardPoints(2) + self.g.specialPoints(2,params=False) - self.g.handPoints(2) font2 = pygame.font.Font("FreeSans.ttf", 20) team1specials = self.g.specialPoints(1,params=True) team2specials = self.g.specialPoints(2,params=True) if team1specials[2]==8: team1specials[2]=4 if team2specials[2]==8: team2specials[2]=4 if team1specials[3]==1: team1out = "Went out first" elif team1specials[3]==2: team1out = "Went out concealed" else: team1out = "" if team2specials[3]==1: team2out = "Went out first" elif team2specials[3]==2: team2out = "Went out concealed" else: team2out = "" endtext1 = ["Team 1:", str(team1round)+ " points", str(self.g.cardPoints(1)) + " face value", "-" + str(self.g.handPoints(1)) + " points in hand", str(team1specials[0])+" red canastas", str(team1specials[1])+" black canastas", str(team1specials[2])+" red threes", str(team1specials[4])+" wild card canastas", team1out] endtext2 = ["Team 2:", str(team2round)+ " points", str(self.g.cardPoints(2)) + " face value", "-" + str(self.g.handPoints(2)) + " points in hand", str(team2specials[0])+" red canastas", str(team2specials[1])+" black canastas", str(team2specials[2])+" red threes", str(team2specials[4])+" wild card canastas", team2out] if (self.g.team1score>=5000) & (self.g.team1score>self.g.team2score): endtext1.append("Team 1 is the winner!") if (self.g.team2score>=5000) & (self.g.team2score>self.g.team1score): endtext2.append("Team 2 is the winner!") defaultStyle.init(gui) endStyle = {'font-color': (255,255,255), 'font': font.Font(None,24), 'autosize': True, "antialias": True,'border-width': False, 'border-color': (0,0,0), 'wordwrap': False} self.desktop.query = Window(position = (250,180), size = (500,400), parent = self.desktop, text = "Round Over", closeable = False, shadeable = False) labelStyleCopy = gui.defaultLabelStyle.copy() Label(position = (200,40),size = (100,0), parent = self.desktop.query, text = "Round " + str(self.g.round) + " over", style = endStyle) for pos, t in enumerate(endtext1): Label(position = (50,80+25*pos),size = (200,0), parent = self.desktop.query, text = t, style = endStyle) for pos, t in enumerate(endtext2): Label(position = (300,80+25*pos),size = (200,0), parent = self.desktop.query, text = t, style = endStyle) Cont_button = Button(position = (200,350), size = (70,0), parent = self.desktop.query, text = "Continue") Cont_button.onClick = ContinueOnClick def genHelp(self): def helpClose(button): self.p.viewhelp = 0 self.desktop.query.wintype = None self.desktop.query.position = (0,0) self.desktop.query.size = (0,0) text = [ "Canasty v0.1", "-----------------------", "F1 - Display this help text.", "ESC - Quit.", "Click the scoreboard to re-sort your cards", "Click the pile to draw a card", "Click the discard pile to pick it up", "(first stage or select cards", "that you need to meld)", "Drag a card to the pile to discard it", "Select cards and right-click to meld", "(or drag them onto an existing meld)", "Drag melds to the stage area to stage them", "Left-click the stage to meld it", "(right-click to clear it)", "-----------------------", "See manual for alternate keyboard controls"] defaultStyle.init(gui) helpStyle = {'font-color': (255,255,255), 'font': font.Font(None,24), 'autosize': True, "antialias": True,'border-width': False, 'border-color': (0,0,0), 'wordwrap': False} self.desktop.query = Window(position = (300,120), size = (400,500), parent = self.desktop, text = "Help", closeable = True, shadeable = False) self.desktop.query.onClose = helpClose self.desktop.query.wintype = "help" labelStyleCopy = gui.defaultLabelStyle.copy() for pos, t in enumerate(text): Label(position = (30,35+25*pos),size = (200,0), parent = self.desktop.query, text = t, style = helpStyle) def genChat(self,x,y): defaultStyle.init(gui) self.chatwin = Window(position = (x,y), size = (280,130), parent = self.desktop, text = "", closeable = False, shadeable = True) labelStyleCopy = gui.defaultLabelStyle.copy() labelStyleCopy['autosize'] = False labelStyleCopy['wordwrap'] = True labelStyleCopy['font'] = pygame.font.Font("FreeSans.ttf", 12) textboxStyleCopy = gui.defaultTextBoxStyle.copy() textboxStyleCopy['border-width'] = 1 textboxStyleCopy['font'] = pygame.font.Font("FreeSans.ttf", 12) self.chatwin.chatdisplay = Label(position = (5,5),size = (270,105), parent = self.chatwin, text = "", style = labelStyleCopy) self.chatwin.chattxt = TextBox(position = (5,107), size = (270, 0), parent =self.chatwin, text = "", style = textboxStyleCopy) self.chatwin.enabled = True def DrawQuery(self): self.DrawGame(flip=False) #Flips! pygame.display.flip()