From 4f55cdd67538063845e9f8fed56b618504eeaeb8 Mon Sep 17 00:00:00 2001 From: Sourcery AI <> Date: Sat, 18 Nov 2023 02:29:00 +0000 Subject: [PATCH] 'Refactored by Sourcery' --- tilewe/__init__.py | 157 ++++++++++++++++++------------------------- tilewe/elo.py | 3 +- tilewe/engine.py | 28 ++++---- tilewe/tournament.py | 21 +++--- 4 files changed, 90 insertions(+), 119 deletions(-) diff --git a/tilewe/__init__.py b/tilewe/__init__.py index 16a99fa..0380ad6 100644 --- a/tilewe/__init__.py +++ b/tilewe/__init__.py @@ -180,25 +180,23 @@ class _PieceRotationPoint: def __init__(self, name: str, rot: _PieceRotation, pt: Tile): self.id = len(_PIECE_ROTATION_POINTS) - self.as_set = 1 << self.id + self.as_set = 1 << self.id global _PRP_SET_ALL _PRP_SET_ALL |= self.as_set - self.piece = rot.piece - self.rotation = rot - self.piece_id = self.piece.id - self.name = name - self.contact = pt + self.piece = rot.piece + self.rotation = rot + self.piece_id = self.piece.id + self.name = name + self.contact = pt _PIECE_ROTATION_POINTS.append(self) dy, dx = pt - + self.tiles: list[Tile] = [] self.adjacent: set[Tile] = set() self.corners: set[Tile] = set() - for y, x in rot.tiles: - self.tiles.append((y - dy, x - dx)) - + self.tiles.extend((y - dy, x - dx) for y, x in rot.tiles) for y, x in self.tiles: for cy, cx in [(-1, 0), (1, 0), (0, -1), (0, 1)]: rel = (y + cy, x + cx) @@ -210,7 +208,7 @@ def __init__(self, name: str, rot: _PieceRotation, pt: Tile): rel = (y + cy, x + cx) if rel not in self.tiles and rel not in self.adjacent: self.corners.add(rel) - + self.adjacent = sorted(list(self.adjacent)) self.corners = sorted(list(self.corners)) @@ -222,7 +220,7 @@ def __init__(self, name: str, rot: _PieceRotation, pt: Tile): _PIECE_ROTATION_POINTS: list[_PieceRotationPoint] = [] _PRP_SET_ALL: _PrpSet = 0 -def _create_piece(name: str, shape: list[list[int]]) -> Piece: +def _create_piece(name: str, shape: list[list[int]]) -> Piece: """ Adds a piece to the game data, including all rotation and horizontal flips of that piece. @@ -240,11 +238,11 @@ def _create_piece(name: str, shape: list[list[int]]) -> Piece: The new id assigned to this piece """ - global PIECE_COUNT - id = PIECE_COUNT - PIECE_COUNT += 1 - pc = _Piece(name, id) - _PIECES.append(pc) + global PIECE_COUNT + id = PIECE_COUNT + PIECE_COUNT += 1 + pc = _Piece(name, id) + _PIECES.append(pc) f_names = [] def add(suffix: str, arr: np.ndarray): @@ -253,8 +251,8 @@ def add(suffix: str, arr: np.ndarray): for uniqueness across rotations and horizontal flips. """ - rot = None - unique = True + rot = None + unique = True cur_rot = len(pc.rotations) true_rot = cur_rot # assume rotation is unique @@ -268,34 +266,34 @@ def add(suffix: str, arr: np.ndarray): if rot is None: rot = _PieceRotation(name + suffix, pc, len(pc.rotations), arr) - f_names.append(suffix + "f") - pc.rotations.append(rot) - pc.unique.append(unique) - pc.true_rot.append(true_rot) - pc.true_rot_for.append([]) + f_names.append(f"{suffix}f") + pc.rotations.append(rot) + pc.unique.append(unique) + pc.true_rot.append(true_rot) + pc.true_rot_for.append([]) pc.true_rot_for[true_rot].append(cur_rot) # original shape, north - cur = np.array(shape, dtype=np.uint8)[::-1] + cur = np.array(shape, dtype=np.uint8)[::-1] add("n", cur) # east - cur = np.rot90(cur, 1) + cur = np.rot90(cur, 1) add("e", cur) # south - cur = np.rot90(cur, 1) + cur = np.rot90(cur, 1) add("s", cur) # west - cur = np.rot90(cur, 1) + cur = np.rot90(cur, 1) add("w", cur) # flipped - n_unflipped = len(pc.rotations) + n_unflipped = len(pc.rotations) for i in range(n_unflipped): add(f_names[i], np.fliplr(pc.rotations[i].shape)) - + return id # setup the 21 piece shapes @@ -418,9 +416,7 @@ def add(suffix: str, arr: np.ndarray): for _tile in _pt.adjacent: _PRP_WITH_ADJ_REL_COORD[_tile] |= _pt.as_set -_PRP_REL_COORDS: set[Tile] = set() -for _pt in _PRP_WITH_REL_COORD: - _PRP_REL_COORDS.add(_pt) +_PRP_REL_COORDS: set[Tile] = set(_PRP_WITH_REL_COORD) for _pt in _PRP_WITH_ADJ_REL_COORD: _PRP_REL_COORDS.add(_pt) _PRP_REL_COORDS = list(_PRP_REL_COORDS) @@ -664,10 +660,7 @@ def is_equal(self, value: 'Move') -> bool: self.to_tile == value.to_tile def __eq__(self, value: object) -> bool: - if isinstance(value, Move): - return self.is_equal(value) - else: - return False + return self.is_equal(value) if isinstance(value, Move) else False def to_unique(self) -> 'Move': """ @@ -714,9 +707,9 @@ def __init__(self, n_players: int): if n_players < 1 or n_players > 4: raise Exception("Number of players must be between 1 and 4") - self._state: list[_BoardState] = [] - self._tiles = np.zeros((20, 20), dtype=np.uint8) - self._n_players = n_players + self._state: list[_BoardState] = [] + self._tiles = np.zeros((20, 20), dtype=np.uint8) + self._n_players = n_players self._players: list[_Player] = [] chars = [ @@ -725,12 +718,10 @@ def __init__(self, n_players: int): 'R', 'G' ] - for i in range(n_players): - self._players.append(_Player(chars[i], i, self)) - + self._players.extend(_Player(chars[i], i, self) for i in range(n_players)) self.current_player: Color = BLUE - self.finished = False - self.ply = 0 + self.finished = False + self.ply = 0 self.moves: list[Move] = [] def copy_current_state(self) -> 'Board': @@ -817,15 +808,15 @@ def is_legal(self, move: Move, for_player: Color=None) -> bool: # target tile must be empty if move.to_tile is None or self.color_at(move.to_tile) != NO_COLOR: return False - + # piece must be real if move.piece is None or move.piece >= len(_PIECES) or move.piece < 0: - return False + return False pc = _PIECES[move.piece] # rotation must be real if move.rotation is None or move.rotation >= len(ROTATIONS) or move.rotation < 0: - return False + return False pc_rot = pc.rotations[move.rotation] # piece rotation must have the contact @@ -835,21 +826,16 @@ def is_legal(self, move: Move, for_player: Color=None) -> bool: # available permutations at the requested tile prps = player.corners.get(move.to_tile, None) - if prps is None: - return False - - # permutation must fit at the corner square - return (prps & prp.as_set) != 0 + return False if prps is None else (prps & prp.as_set) != 0 def n_legal_moves(self, unique: bool=True, for_player: Color=None): player = self._players[self.current_player if for_player is None else for_player] total = 0 - if unique: - for prps in player.corners.values(): - total += prps.bit_count() - else: - for prps in player.corners.values(): + for prps in player.corners.values(): + if unique: + total += prps.bit_count() + else: while prps != 0: # get least significant bit prp_id = (prps & -prps).bit_length() - 1 @@ -864,45 +850,34 @@ def n_legal_moves(self, unique: bool=True, for_player: Color=None): return total def generate_legal_moves(self, unique: bool=True, for_player: Color=None): - moves: list[Move] = [] + moves: list[Move] = [] player = self._players[self.current_player if for_player is None else for_player] # duplicate for loop so that we don't check the if statement for every permutation - if unique: - for to_sq, prps in player.corners.items(): - while prps != 0: - # get least significant bit - prp_id = (prps & -prps).bit_length() - 1 - # remove it so the next LSB is another PRP - prps ^= 1 << prp_id - - prp = _PIECE_ROTATION_POINTS[prp_id] - + for to_sq, prps in player.corners.items(): + # duplicate for loop so that we don't check the if statement for every permutation + while prps != 0: + # get least significant bit + prp_id = (prps & -prps).bit_length() - 1 + # remove it so the next LSB is another PRP + prps ^= 1 << prp_id + + prp = _PIECE_ROTATION_POINTS[prp_id] + + # duplicate for loop so that we don't check the if statement for every permutation + if unique: moves.append(Move( prp.piece.id, prp.rotation.rotation, prp.contact, to_sq )) - else: - for to_sq, prps in player.corners.items(): - while prps != 0: - # get least significant bit - prp_id = (prps & -prps).bit_length() - 1 - # remove it so the next LSB is another PRP - prps ^= 1 << prp_id - - prp = _PIECE_ROTATION_POINTS[prp_id] - + else: # include permutations with non-unique rotations/flips - for rot in prp.piece.true_rot_for[prp.rotation.rotation]: - moves.append(Move( - prp.piece.id, - rot, - prp.contact, - to_sq - )) - + moves.extend( + Move(prp.piece.id, rot, prp.contact, to_sq) + for rot in prp.piece.true_rot_for[prp.rotation.rotation] + ) return moves def push_null(self) -> None: @@ -998,7 +973,7 @@ def _push_prp(self, move: Move, prp: _PieceRotationPoint, tile: Tile) -> None: def __str__(self): out = "" - + board = self._tiles[::-1] chars = None @@ -1027,14 +1002,12 @@ def __str__(self): for player in self._players: out += f"{player.name}: {int(player.score)} " - pcs = [_PIECES[pc] for pc in self._remaining_piece_set(player.id)] - - if len(pcs) > 0: + if pcs := [_PIECES[pc] for pc in self._remaining_piece_set(player.id)]: pcs = sorted(pcs, key=lambda x: x.id) out += "( " for pc in pcs: - out += pc.name + " " + out += f"{pc.name} " out += ")\n" else: out += "( )\n" diff --git a/tilewe/elo.py b/tilewe/elo.py index 6b9e771..8054af9 100644 --- a/tilewe/elo.py +++ b/tilewe/elo.py @@ -56,8 +56,7 @@ def compute_elo_adjustment_2(elo1: float, elo2: float, outcome: float, K: int = p1_win_probability = elo_win_probability(elo1, elo2) new_elo1 = elo1 + K * (outcome - p1_win_probability) - delta_elo = new_elo1 - elo1 - return delta_elo + return new_elo1 - elo1 def compute_elo_adjustment_n(elos: list[float], scores: list[int], K: int = 32): """ diff --git a/tilewe/engine.py b/tilewe/engine.py index 197dfff..210313d 100644 --- a/tilewe/engine.py +++ b/tilewe/engine.py @@ -110,15 +110,15 @@ def __init__(self, name: str="LargestPiece"): super().__init__(name) def on_search(self, board: tilewe.Board, _seconds: float) -> tilewe.Move: - moves = board.generate_legal_moves(unique=True) + moves = board.generate_legal_moves(unique=True) random.shuffle(moves) - best = max(moves, key=lambda m: - tilewe.n_piece_tiles(m.piece) * 100 + - tilewe.n_piece_corners(m.piece) * 10 + - tilewe.n_piece_contacts(m.piece)) - - return best + return max( + moves, + key=lambda m: tilewe.n_piece_tiles(m.piece) * 100 + + tilewe.n_piece_corners(m.piece) * 10 + + tilewe.n_piece_contacts(m.piece), + ) class MaximizeMoveDifferenceEngine(Engine): """ @@ -213,7 +213,7 @@ class TileWeightEngine(Engine): 'turtle': TURTLE_WEIGHTS } - def __init__(self, name: str="TileWeight", weight_map: str='wall_crawl', custom_weights: list[int | float]=None): + def __init__(self, name: str="TileWeight", weight_map: str='wall_crawl', custom_weights: list[int | float]=None): """ Current `weight_map` built-in options are 'wall_crawl' and 'turtle' Can optionally provide a custom set of weights instead @@ -221,16 +221,16 @@ def __init__(self, name: str="TileWeight", weight_map: str='wall_crawl', custom_ super().__init__(name) - if custom_weights is not None: - if len(custom_weights) != 20 * 20: - raise Exception("TileWeightEngine custom_weights must be a list of exactly 400 values") - self.weights = custom_weights - - else: + if custom_weights is None: if weight_map not in self.weight_maps: raise Exception("TileWeightEngine given invalid weight_map choice") self.weights = self.weight_maps[weight_map] + elif len(custom_weights) != 20 * 20: + raise Exception("TileWeightEngine custom_weights must be a list of exactly 400 values") + else: + self.weights = custom_weights + def on_search(self, board: tilewe.Board, _seconds: float) -> tilewe.Move: cur_player = board.current_player diff --git a/tilewe/tournament.py b/tilewe/tournament.py index 157f430..a104062 100644 --- a/tilewe/tournament.py +++ b/tilewe/tournament.py @@ -100,8 +100,7 @@ def average_match_duration(self) -> float: return self.total_time / max(1, self.total_games) def get_matches_by_engine(self, engine: int) -> list[MatchData]: - filtered_matches = [x for x in self.match_data if engine in x.engines] - return filtered_matches + return [x for x in self.match_data if engine in x.engines] def get_game_count_by_engine(self, engine: int) -> int: return self.game_counts[engine] @@ -135,7 +134,7 @@ def get_engine_rankings_display(self, sort_by: str = 'elo_end', sort_dir: str = return f"Invalid sort field '{sort_by}', must specify a list of length >0" if not isinstance(sort_attr[0], int) and not isinstance(sort_attr[0], float): return f"Invalid sort field '{sort_by}', must specify a numeric list" - if sort_dir != 'asc' and sort_dir != 'desc': + if sort_dir not in ['asc', 'desc']: return f"Invalid sort direction '{sort_dir}', try 'asc' or 'desc'" out = f"Ranking by {sort_by} {sort_dir}:\n" @@ -184,11 +183,11 @@ def __init__(self, engines: list[Engine], move_seconds: int=60): The default time control if `play` doesn't override it """ - if (len(engines) < 1): + if not engines: raise Exception("Number of engines must be greater than 0") if move_seconds <= 0: raise Exception("Must allow greater than 0 seconds per move") - + self.engines = list(engines) self._seconds = move_seconds self.move_seconds = self._seconds @@ -227,11 +226,11 @@ def play( raise Exception("Must use at least one thread") if players_per_game < 1 or players_per_game > 4: raise Exception("Must have 1 to 4 players per game") - + self.move_seconds = move_seconds if move_seconds is not None else self._seconds if self.move_seconds <= 0: raise Exception("Must allow greater than 0 seconds per move") - + # initialize trackers and game controls N = len(self.engines) total_games = 0 @@ -240,7 +239,7 @@ def play( elos = [0 for _ in range(N)] totals = [0 for _ in range(N)] - initial_elos = [i for i in elos] + initial_elos = list(elos) match_results: list[MatchData] = [] # helper for getting engine rank summaries @@ -252,7 +251,7 @@ def get_engine_rankings() -> str: name = self.engines[engine].name win_count, game_count = wins[engine], games[engine] score, elo = totals[engine], elos[engine] - + win_rate = f"{(win_count / game_count * 100):>8.2f}%" if game_count > 0 else f"{'-':>9}" avg_score = f"{(score / game_count):>10.2f}" if game_count > 0 else f"{'-':>10}" @@ -261,7 +260,7 @@ def get_engine_rankings() -> str: return out # prepare turn orders for the various games - args = [] + args = [] for _ in range(n_games): order = list(range(N)) random.shuffle(order) @@ -294,7 +293,7 @@ def get_engine_rankings() -> str: player_names = [self.engines[i].name for i in game_players] player_scores = [scores[i] for i in game_players] winner_names = [self.engines[i].name for i in winners] - + # if there are enough players, compute elo changes if board.n_players > 1: player_elos = [elos[i] for i in game_players]