class LocalGameService: def __init__(self, width: int, height: int, player_count: int): self.board = Board(width, height) start_point_distance = self.board.cell_count // (player_count + 1) self.players = [] # Init Player y_start_positions = list(range(1, self.board.width - 1)) x_start_positions = list(range(1, self.board.height - 1)) del y_start_positions[::2] del x_start_positions[::2] start_positions = list( itertools.product(y_start_positions, x_start_positions)) random.shuffle(start_positions) for player_id in range(1, player_count + 1): start_cell = start_positions.pop() player = Player( player_id, PlayerState(random.choice(list(PlayerDirection)), 1, start_cell[0], start_cell[1])) self.board.set_cell(player.current_state.position_x, player.current_state.position_y, player.player_id) self.players.append(player) self.is_started = False self.deadline = None self.on_round_start = Event() self.all_player_moved = False # Starts the Game and sends first Notification to the Player def start(self): self.is_started = True self.__reset_deadline() self.__notify_player() self.__wait_and_end_round() # processes an User interaction def do_action(self, player: int, player_action): if not self.is_started: raise Exception('Game is not started') elif self.players[player - 1].next_action is None: self.players[player - 1].next_action = player_action else: self.players[player - 1].is_active = False if self.__is_running() and all( not p.is_active or p.next_action is not None for p in self.players): self.all_player_moved = True def __notify_player(self): self.on_round_start.notify( json.dumps({ "width": self.board.width, "height": self.board.height, "cells": self.board.cells, "players": { player.player_id: player.to_dict() for player in self.players }, "you": 1, "running": self.__is_running(), "deadline": self.deadline.replace(microsecond=0).isoformat("T") + "Z" })) def __reset_deadline(self): deadline_seconds = SIMULATION_DEADLINE if not deadline_seconds: # default deadline is 1 Day deadline_seconds = 60 * 60 * 24 self.deadline = datetime.utcnow() + timedelta(seconds=deadline_seconds) def __is_running(self) -> bool: return self.is_started and sum(p.is_active for p in self.players) > 1 # is running in extra thread: checks the deadline and ends round def __wait_and_end_round(self): while self.__is_running(): time.sleep(0.1) if self.all_player_moved or self.deadline and self.deadline < datetime.utcnow( ): self.__reset_deadline() for player in self.players: if player.is_active: player.do_action_and_move() for point in player.current_state.steps_to_this_point: self.board.set_cell(point[0], point[1], player.player_id) player.is_active &= player.current_state.verify_state( self.board) for player in self.players: if player.is_active: for point in player.current_state.steps_to_this_point: if self.board[point[1]][point[0]] == -1: player.is_active = False self.all_player_moved = False self.__notify_player()
class Connection: """The component that handles managing the connection to the TA server.""" def __init__(self): self._state = STATE_DISCONNECTED self._proto = None self._host = None self._port = None self._socket = None self.stateChanged = Event() self.lineReceived = Event() self.xmlReceived = Event() self.callback = lambda:None self._sockets = [] self._parser = None def connect(self, host, port): """Start connecting to a given host/port, terminating current connection if necessary.""" if not self.isDisconnected(): self.disconnect() self._host = host self._port = port #reactor.connectTCP(host, port, self) self._socket = TiberiaSocket(self) self._socket.callback = self.callback self._sockets.append(self._socket) self._socket.connect(host,port) self._parser = ta2.LineParser() self._enterState(STATE_CONNECTING) def disconnect(self): """Disconnect from the current server if connected.""" if not self.isDisconnected(): # TODO: make this not warn if you cancel during a connection #self._proto.transport.loseConnection() self._socket.close() self._enterDisconnectedState() def getHost(self): return self._host def getPort(self): return self._port def getState(self): return self._state def isDisconnected(self): return self._state == STATE_DISCONNECTED def isConnecting(self): return self._state == STATE_CONNECTING def isConnected(self): return self._state == STATE_CONNECTED def sendRaw(self, data): """Send byte data to the server.""" if self.isConnected(): self._socket.send(data) def _enterDisconnectedState(self, reason=REASON_USER): self._proto = None self._host = None self._port = None self._socket = None self._enterState(STATE_DISCONNECTED, reason) self._parser = None def _enterState(self, state, reason=None): oldState = self._state self._state = state if state != oldState: self.stateChanged.notify(state, reason) def _disconnected(self, sock): if sock is self._socket: self._enterDisconnectedState(REASON_CONNECTION_LOST) def _connectionFailed(self, sock): if sock is self._socket: self._enterDisconnectedState(REASON_CONNECTION_FAILED) def _connected(self, sock): if sock is self._socket: self._enterState(STATE_CONNECTED) def _dataReceived(self, sock, data): if sock is not self._socket: return self._parser.queueData(data) # TODO: This is rather ugly! :( while True: lineChunks = self._parser.getLine() if lineChunks is None: break textChunks = [] xmlChunks = [] for c in lineChunks: if isinstance(c, ta2.XmlChunk): self.xmlReceived.notify(c.xml) xmlChunks.append(c) else: textChunks.append(c) if (len(textChunks) > 0 or len(xmlChunks) == 0): self.lineReceived.notify(textChunks) def update(self): #print 'Connection.update()' socketDied = False for s in self._sockets: s.update() if s.state == SocketWrapper.ST_DISCONNECTED: socketDied = True if socketDied: self._sockets = [x for x in self._sockets if x.state != SocketWrapper.ST_DISCONNECTED]