def _handle(cls, msg: 'proto.TileExchange', client: 'Client', game: 'Game'): if len(game.free_tiles) < 7: client.send_msg( proto.ActionRejected('There are less than 7 tiles left!')) elif not msg.tile_ids: client.send_msg( proto.ActionRejected( 'Tile exchange requires at least one selected tile!')) else: tiles = [ tile for tile in client.player.tiles if tile.id in msg.tile_ids ] tile_count = len(tiles) if len(msg.tile_ids) == tile_count: client.player.tiles = [ tile for tile in client.player.tiles if tile not in tiles ] game.free_tiles += tiles random.shuffle(game.free_tiles) client.player.tiles += game.free_tiles[:tile_count] game.free_tiles = game.free_tiles[tile_count:] game.send_to_all( proto.Notification(f'{client.name} exchanged tiles'), client.player_id) client.send_msg(proto.Notification('You exchanged tiles')) _end_turn_without_score(client, game) else: client.send_msg( proto.ActionRejected( 'Selected tiles do not belong to player!'))
def _end_turn_without_score(client: 'Client', game: 'Game'): if game.turns_without_score == 5: game.send_to_all( proto.Notification('6 consecutive scoreless turns have occurred!')) for client_ in game.clients: deduction = sum(tile.points for tile in client_.player.tiles) client_.send_msg( proto.Notification(f'Deducted {deduction} points')) client_.player.score -= sum(tile.points for tile in client_.player.tiles) end_game = proto.EndGame([ proto.EndGamePlayer(client.player_id, client.player.score) for client in game.clients ]) game.send_to_all(end_game) game.lobby = True else: game.turns_without_score += 1 game.send_to_all( proto.EndTurn(game.turn_player_id, client.player.score, [])) game.turn_player_id = game.clients[(game.clients.index(client) + 1) % len(game.clients)].player_id player_tile_counts = [ proto.StartTurnPlayer(client.player_id, len(client.player.tiles)) for client in game.clients ] for client_ in game.clients: start_turn = proto.StartTurn(game.turn_player_id, len(game.free_tiles), client_.player.tiles, player_tile_counts) client_.send_msg(start_turn)
def _handle(cls, msg: 'proto.Leave', client: 'Client', game: 'Game'): i = game.clients.index(client) del game.clients[i] game.send_to_all(proto.PlayerLeft(client.player_id)) if game.lobby: if len(game.clients) > 1 and all(client.ready for client in game.clients): _start_game(game) elif len(game.clients) < 2: for client_ in game.clients: deduction = sum(tile.points for tile in client_.player.tiles) client_.send_msg( proto.Notification(f'Deducted {deduction} points')) client_.player.score -= sum(tile.points for tile in client_.player.tiles) end_game = proto.EndGame([ proto.EndGamePlayer(client.player_id, client.player.score) for client in game.clients ]) game.send_to_all(end_game) game.lobby = True elif game.turn_player_id == client.player_id: game.free_tiles += client.player.tiles random.shuffle(game.free_tiles) game.turn_player_id = game.clients[i % len(game.clients)].player_id tiles_left = len(game.free_tiles) player_tile_counts = [ proto.StartTurnPlayer(client.player_id, len(client.player.tiles)) for client in game.clients ] for client_ in game.clients: client_.send_msg( proto.StartTurn(game.turn_player_id, tiles_left, client_.player.tiles, player_tile_counts))
def _start_game(game: 'Game'): game.board = Board() game.load_tiles() game.turns_without_score = 0 for client in game.clients: client.ready = False player = client.player = Player() player.tiles = game.free_tiles[:7] game.free_tiles = game.free_tiles[7:] game.send_to_all(proto.Notification('Game started!')) game.turn_player_id = game.clients[random.randint(0, len(game.clients) - 1)].player_id tiles_left = len(game.free_tiles) player_tile_counts = [ proto.StartTurnPlayer(client.player_id, 7) for client in game.clients ] for client in game.clients: start_turn = proto.StartTurn(game.turn_player_id, tiles_left, client.player.tiles, player_tile_counts) client.send_msg(start_turn) game.lobby = False
def _handle(cls, msg: 'proto.PlaceTiles', client: 'Client', game: 'Game'): if not msg.tile_placements: game.send_to_all(proto.Notification(f'{client.name} skipped'), client.player_id) client.send_msg(proto.Notification('You skipped')) _end_turn_without_score(client, game) return player_tiles_by_id = {tile.id: tile for tile in client.player.tiles} tiles = [ FullTile(player_tiles_by_id[tile.id], tile) for tile in msg.tile_placements if tile.id in player_tiles_by_id ] tile_count = len(tiles) if len(msg.tile_placements) != tile_count: client.send_msg( proto.ActionRejected('Placed tiles do not belong to player!')) return if any(not tile.letter for tile in tiles): client.send_msg( proto.ActionRejected('Blank tiles must be assigned a letter!')) return if all(tile.row == tiles[0].row for tile in tiles): def accessor(coord1, coord2): return game.board.squares[coord1][coord2] elif all(tile.col == tiles[0].col for tile in tiles): def accessor(coord1, coord2): return game.board.squares[coord2][coord1] for tile in tiles: tile.row, tile.col = tile.col, tile.row else: client.send_msg( proto.ActionRejected( 'Tiles must form a horizontal or vertical line!')) return row = tiles[0].row tiles.sort(key=lambda tile: tile.col) tiles_by_col = {tile.col: tile for tile in tiles} for tile in tiles: if tile.row not in range(15) or tile.col not in range( 15) or tiles_by_col.get(tile.col) != tile or accessor( tile.row, tile.col).tile: client.send_msg( proto.ActionRejected( 'Tiles are overlapping or out of bounds!')) return for col in range(tiles[0].col + 1, tiles[-1].col + 1): if not accessor(row, col).tile and col not in tiles_by_col: client.send_msg( proto.ActionRejected('Tiles must form a single line!')) return if not accessor(7, 7).tile: if row != 7 or 7 not in tiles_by_col: client.send_msg( proto.ActionRejected( 'The center square must be populated!')) return elif tile_count == 1: client.send_msg( proto.ActionRejected( 'The first word must be at least 2 characters long!')) return def count_word(tile_from: 'FullTile', horizontal: bool = False) -> Optional['WordCounter']: counter = WordCounter() for i in range( (tile_from.col if horizontal else tile_from.row) - 1, -1, -1): tile = accessor(row, i).tile if horizontal else accessor( i, tile_from.col).tile if not tile: break counter.points += tile.points counter.word = tile.letter + counter.word counter.is_connected = True for i in range(tile_from.col if horizontal else tile_from.row, 15): square = accessor(row, i) if horizontal else accessor( i, tile_from.col) if square.tile: tile = square.tile counter.points += tile.points counter.is_connected = True elif (i in tiles_by_col) if horizontal else (i == tile_from.row): tile = tiles_by_col[i] if horizontal else tile_from if square.type == SquareType.DLS: counter.points += 2 * tile.points elif square.type == SquareType.TLS: counter.points += 3 * tile.points else: counter.points += tile.points if square.type == SquareType.DWS: counter.multiplier *= 2 elif square.type == SquareType.TWS: counter.multiplier *= 3 else: break counter.word += tile.letter return counter if len(counter.word) > 1 else None word_counters = [] horizontal_counter = count_word(tiles[0], True) if horizontal_counter: word_counters.append(horizontal_counter) for tile in tiles: word_counter = count_word(tile) if word_counter: word_counters.append(word_counter) if all(not counter.is_connected for counter in word_counters) and accessor(7, 7).tile: client.send_msg( proto.ActionRejected('Must connect with pre-existing tiles!')) return global words invalid_words = { counter.word for counter in word_counters if counter.word not in words } if invalid_words: client.send_msg( proto.ActionRejected( f'Invalid word{"" if len(invalid_words) == 1 else "s"}: {", ".join(invalid_words)}' )) return for counter in word_counters: score = counter.points * counter.multiplier client.player.score += score game.send_to_all( proto.Notification(f'{counter.word} - {score} points')) if tile_count == 7: client.player.score += 50 game.send_to_all(proto.Notification('Bingo! - 50 points')) for tile in tiles: accessor(tile.row, tile.col).tile = tile placed_tiles = [ proto.EndTurnTile(tile.position, tile.points, tile.letter) for tile in tiles ] game.send_to_all( proto.EndTurn(game.turn_player_id, client.player.score, placed_tiles)) game.turns_without_score = 0 tile_ids = {tile.id for tile in tiles} client.player.tiles = [ tile for tile in client.player.tiles if tile.id not in tile_ids ] if game.free_tiles: take_tiles_count = min(len(game.free_tiles), tile_count) client.player.tiles += game.free_tiles[:take_tiles_count] game.free_tiles = game.free_tiles[take_tiles_count:] elif not client.player.tiles: game.send_to_all( proto.Notification(f'{client.name} has played out!'), client.player_id) client.send_msg(proto.Notification('You have played out!')) all_sums = 0 for client_ in game.clients: if client_ != client: deduction = sum(tile.points for tile in client_.player.tiles) client_.player.score -= deduction all_sums += deduction client_.send_msg( proto.Notification(f'Deducted {deduction} points')) client.player.score += all_sums client.send_msg(proto.Notification(f'Awarded {all_sums} points')) end_game = proto.EndGame([ proto.EndGamePlayer(client.player_id, client.player.score) for client in game.clients ]) game.send_to_all(end_game) game.lobby = True return game.turn_player_id = game.clients[(game.clients.index(client) + 1) % len(game.clients)].player_id player_tile_counts = [ proto.StartTurnPlayer(client.player_id, len(client.player.tiles)) for client in game.clients ] for client_ in game.clients: start_turn = proto.StartTurn(game.turn_player_id, len(game.free_tiles), client_.player.tiles, player_tile_counts) client_.send_msg(start_turn)