Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 65 additions & 92 deletions tilewe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment on lines -183 to +199
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function _PieceRotationPoint.__init__ refactored with the following changes:

for y, x in self.tiles:
for cy, cx in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
rel = (y + cy, x + cx)
Expand All @@ -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))

Expand All @@ -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:
Comment on lines -225 to +223
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function _create_piece refactored with the following changes:

"""
Adds a piece to the game data, including all rotation and
horizontal flips of that piece.
Expand All @@ -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):
Expand All @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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)
Comment on lines -421 to +419
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines 421-423 refactored with the following changes:

for _pt in _PRP_WITH_ADJ_REL_COORD:
_PRP_REL_COORDS.add(_pt)
_PRP_REL_COORDS = list(_PRP_REL_COORDS)
Expand Down Expand Up @@ -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
Comment on lines -667 to +663
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function Move.__eq__ refactored with the following changes:


def to_unique(self) -> 'Move':
"""
Expand Down Expand Up @@ -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
Comment on lines -717 to +712
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function Board.__init__ refactored with the following changes:

self._players: list[_Player] = []

chars = [
Expand All @@ -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':
Expand Down Expand Up @@ -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
Comment on lines -820 to +819
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function Board.is_legal refactored with the following changes:

This removes the following comments ( why? ):

# permutation must fit at the corner square

pc_rot = pc.rotations[move.rotation]

# piece rotation must have the contact
Expand All @@ -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:
Comment on lines -848 to +838
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function Board.n_legal_moves refactored with the following changes:

while prps != 0:
# get least significant bit
prp_id = (prps & -prps).bit_length() - 1
Expand All @@ -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]
)
Comment on lines -867 to +880
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function Board.generate_legal_moves refactored with the following changes:

This removes the following comments ( why? ):

# get least significant bit
# duplicate for loop so that we don't check the if statement for every permutation
# remove it so the next LSB is another PRP

return moves

def push_null(self) -> None:
Expand Down Expand Up @@ -998,7 +973,7 @@ def _push_prp(self, move: Move, prp: _PieceRotationPoint, tile: Tile) -> None:

def __str__(self):
out = ""

Comment on lines -1001 to +976
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function Board.__str__ refactored with the following changes:

board = self._tiles[::-1]

chars = None
Expand Down Expand Up @@ -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"
Expand Down
3 changes: 1 addition & 2 deletions tilewe/elo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function compute_elo_adjustment_2 refactored with the following changes:


def compute_elo_adjustment_n(elos: list[float], scores: list[int], K: int = 32):
"""
Expand Down
28 changes: 14 additions & 14 deletions tilewe/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
)
Comment on lines -113 to +121
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function LargestPieceEngine.on_search refactored with the following changes:


class MaximizeMoveDifferenceEngine(Engine):
"""
Expand Down Expand Up @@ -213,24 +213,24 @@ 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):
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function TileWeightEngine.__init__ refactored with the following changes:

"""
Current `weight_map` built-in options are 'wall_crawl' and 'turtle'
Can optionally provide a custom set of weights instead
"""

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
Expand Down
Loading