From a8293914f36505d40864f20adf812195f231bd13 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Mon, 23 Feb 2026 21:21:41 -0800 Subject: [PATCH 1/9] template for lunar lockout --- games/src/games/game_manager.py | 1 + games/src/games/lunar_lockout.py | 115 +++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 games/src/games/lunar_lockout.py diff --git a/games/src/games/game_manager.py b/games/src/games/game_manager.py index 28148b9..56cbd3d 100644 --- a/games/src/games/game_manager.py +++ b/games/src/games/game_manager.py @@ -7,6 +7,7 @@ "clobber": Clobber, "horses": Horses, "pancakes": Pancakes, + "lunar_lockout": LunarLockout, } def validate(game_id: str, variant_id: str) -> bool: diff --git a/games/src/games/lunar_lockout.py b/games/src/games/lunar_lockout.py new file mode 100644 index 0000000..2178af7 --- /dev/null +++ b/games/src/games/lunar_lockout.py @@ -0,0 +1,115 @@ +from models import Game, Value, StringMode +from typing import Optional + +class LunarLockout(Game): + id = 'lunar_lockout' + variants = ["5x5"] + n_players = 1 + cyclic = True + _move_up = 0b00 + _move_down = 0b10 + _move_right = 0b01 + _move_left = 0b11 + + # Store the variant and board dimensions (5x5). + # Define constants: center square index (12), removed-robot value (31), and total robots (5). + # Robot index 0 always represents the red robot. + # Prepare any movement direction offsets needed for row/column stepping. + def __init__(self, variant_id: str): + """ + Define instance variables here (i.e. variant information) + """ + if variant_id not in LunarLockout.variants: + raise ValueError("Variant not defined") + self._variant_id = variant_id + self._cols = int(variant_id[0]) + self._rows = int(variant_id[2]) + self.board_size = [int(i) for i in self._variant_id.split(sep="x")] + + + # Select starting squares for all robots with no duplicates. + # Ensure the red robot is active and not already at a terminal condition. + # Keep robot ordering consistent (red first). + # Encode the five robot positions into a single integer state. + def start(self) -> int: + """ + Returns the starting position of the game. + """ + pass + + + # Decode the state into robot positions. + # Skip robots marked as removed (31). + # For each active robot, attempt movement in the four directions. + # Movement must stay within the same row for left/right and same column for up/down. + # While scanning, consider only the nearest active robot as a blocker. + # Add a move for every direction; absence of a blocker means the robot will leave the board. + # Encode moves as (robot_index * 4 + direction). + def generate_moves(self, position: int) -> list[int]: + """ + Returns a list of positions given the input position. + """ + pass + + + # Decode the state and identify the robot and direction from the move. + # If the chosen robot is already removed, return the original state. + # Slide in the chosen direction until the nearest blocking robot is found. + # If a blocker exists, stop immediately before it. + # If no blocker exists, mark the robot as removed (31). + # Do not allow two active robots to occupy the same square. + # Re-encode the updated positions into the new state. + def do_move(self, position: int, move: int) -> int: + """ + Returns the resulting position of applying move to position. + """ + pass + + + # Decode the state. + # Return Win if the red robot occupies the center square (12). + # Return Lose if the red robot has been removed (31). + # Otherwise return None for a non-terminal position. + def primitive(self, position: int) -> Optional[Value]: + """ + Returns a Value enum which defines whether the current position is a win, loss, or non-terminal. + """ + pass + + + # Convert the encoded state into a readable 5x5 board. + # Display the center square and all active robots. + # Do not display removed robots. + def to_string(self, position: int, mode: StringMode) -> str: + """ + Returns a string representation of the position based on the given mode. + """ + pass + + + # Parse a readable board layout into robot positions. + # Validate positions are within bounds and not duplicated. + # Assign removed status to any robot not present. + # Encode the positions into an integer state. + def from_string(self, strposition: str) -> int: + """ + Returns the position from a string representation of the position. + Input string is StringMode.Readable. + """ + pass + + + # Decode the move into robot index and direction. + # Return a readable description of the movement direction. + def move_to_string(self, move: int, mode: StringMode) -> str: + """ + Returns a string representation of the move based on the given mode. + """ + pass + + + # Helper responsibilities: + # Provide pack and unpack functions converting between the integer state and the five robot positions. + # Convert between index and (row, column) coordinates. + # Provide stepping logic for movement along rows and columns. + # Provide alignment checks for same-row and same-column detection. \ No newline at end of file From 2d4d9ff58925059f77c31d90fa3a2c2942a643b3 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Mon, 23 Feb 2026 22:18:35 -0800 Subject: [PATCH 2/9] update --- games/src/games/lunar_lockout.py | 50 +++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/games/src/games/lunar_lockout.py b/games/src/games/lunar_lockout.py index 2178af7..4b6de2d 100644 --- a/games/src/games/lunar_lockout.py +++ b/games/src/games/lunar_lockout.py @@ -3,17 +3,20 @@ class LunarLockout(Game): id = 'lunar_lockout' - variants = ["5x5"] + variants = ["puzzle1"] + board_size = ["5x5"] n_players = 1 cyclic = True - _move_up = 0b00 - _move_down = 0b10 - _move_right = 0b01 - _move_left = 0b11 + + _move_up = 0 + _move_down = 1 + _move_right = 2 + _move_left = 3 # Store the variant and board dimensions (5x5). # Define constants: center square index (12), removed-robot value (31), and total robots (5). # Robot index 0 always represents the red robot. + # Each robot position is encoded using 5 bits (values 0–31), producing a 25-bit packed state. # Prepare any movement direction offsets needed for row/column stepping. def __init__(self, variant_id: str): """ @@ -22,9 +25,32 @@ def __init__(self, variant_id: str): if variant_id not in LunarLockout.variants: raise ValueError("Variant not defined") self._variant_id = variant_id - self._cols = int(variant_id[0]) - self._rows = int(variant_id[2]) - self.board_size = [int(i) for i in self._variant_id.split(sep="x")] + + size_string = LunarLockout.board_size[0] # "5x5" + self._rows, self._cols = map(int, size_string.split("x")) + self._cells = self._rows * self._cols + + # Exit Position + self._center = self._cells // 2 + + # Robot configs + self._robot_count = 5 + self._red_index = 0 + + # Removed robots value + self._removed = 31 + + # Constants + self._bits_per_robot = 5 + self._mask = 0b11111 + + # movement directions + self._directions = { + self._move_up: (-1, 0), + self._move_down: ( 1, 0), + self._move_left: ( 0, -1), + self._move_right: ( 0, 1), + } # Select starting squares for all robots with no duplicates. @@ -42,7 +68,7 @@ def start(self) -> int: # Skip robots marked as removed (31). # For each active robot, attempt movement in the four directions. # Movement must stay within the same row for left/right and same column for up/down. - # While scanning, consider only the nearest active robot as a blocker. + # While scanning, ignore removed robots and consider only the nearest active robot as a blocker. # Add a move for every direction; absence of a blocker means the robot will leave the board. # Encode moves as (robot_index * 4 + direction). def generate_moves(self, position: int) -> list[int]: @@ -54,9 +80,10 @@ def generate_moves(self, position: int) -> list[int]: # Decode the state and identify the robot and direction from the move. # If the chosen robot is already removed, return the original state. - # Slide in the chosen direction until the nearest blocking robot is found. + # Slide in the chosen direction while staying aligned to the same row or column. + # Stop when the nearest blocking robot is found. # If a blocker exists, stop immediately before it. - # If no blocker exists, mark the robot as removed (31). + # If the scan reaches the board edge with no blocker, the robot leaves the board and is marked removed (31). # Do not allow two active robots to occupy the same square. # Re-encode the updated positions into the new state. def do_move(self, position: int, move: int) -> int: @@ -110,6 +137,7 @@ def move_to_string(self, move: int, mode: StringMode) -> str: # Helper responsibilities: # Provide pack and unpack functions converting between the integer state and the five robot positions. + # Pack and unpack must be exact inverses and produce a single canonical representation of every board state. # Convert between index and (row, column) coordinates. # Provide stepping logic for movement along rows and columns. # Provide alignment checks for same-row and same-column detection. \ No newline at end of file From 0bf2a0b49aa9046e38f80571eb1b4fbce66ed024 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Mon, 23 Feb 2026 22:23:21 -0800 Subject: [PATCH 3/9] pack and unpack helper --- games/src/games/lunar_lockout.py | 60 ++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/games/src/games/lunar_lockout.py b/games/src/games/lunar_lockout.py index 4b6de2d..27187c1 100644 --- a/games/src/games/lunar_lockout.py +++ b/games/src/games/lunar_lockout.py @@ -7,7 +7,7 @@ class LunarLockout(Game): board_size = ["5x5"] n_players = 1 cyclic = True - + _move_up = 0 _move_down = 1 _move_right = 2 @@ -26,9 +26,14 @@ def __init__(self, variant_id: str): raise ValueError("Variant not defined") self._variant_id = variant_id - size_string = LunarLockout.board_size[0] # "5x5" + # Board dimensions + size_string = LunarLockout.board_size[0] self._rows, self._cols = map(int, size_string.split("x")) self._cells = self._rows * self._cols + # Limits + self._max_row = self._rows - 1 + self._max_col = self._cols - 1 + self.row_stride = self._cols # Exit Position self._center = self._cells // 2 @@ -36,15 +41,13 @@ def __init__(self, variant_id: str): # Robot configs self._robot_count = 5 self._red_index = 0 - - # Removed robots value self._removed = 31 - # Constants + # Encoding self._bits_per_robot = 5 self._mask = 0b11111 - # movement directions + # Directions self._directions = { self._move_up: (-1, 0), self._move_down: ( 1, 0), @@ -140,4 +143,47 @@ def move_to_string(self, move: int, mode: StringMode) -> str: # Pack and unpack must be exact inverses and produce a single canonical representation of every board state. # Convert between index and (row, column) coordinates. # Provide stepping logic for movement along rows and columns. - # Provide alignment checks for same-row and same-column detection. \ No newline at end of file + # Provide alignment checks for same-row and same-column detection. + + + def pack(self, robots: list[int]) -> int: + ''' + Converts a list of robot positions into a single integer state. + + robots is a list of length 5 in the fixed order: + [red, helper1, helper2, helper3, helper4] + + Each position is stored using 5 bits (values 0-31). + 0-24 represent board squares and 31 represents a removed robot. + ''' + state = 0 + # Insert each robot position into the integer from left to right. + # Shift the existing bits 5 places and place the new position + # into the lowest 5 bits. + for position in robots: + if position < 0 or position > self._mask: + raise ValueError("Invalid robot position") + + state = (state << self._bits_per_robot) | position + + return state + + def unpack(self, state: int) -> list[int]: + """ + Converts an encoded integer state back into robot positions. + + Returns a list of length 5 in the order: + [red, helper1, helper2, helper3, helper4] + + The function repeatedly reads the lowest 5 bits to recover + each robot position and shifts the state right after each read. + """ + robots = [0] * self._robot_count + + # Extract robots in reverse order because the last robot was + # stored in the lowest 5 bits. + for i in range(self._robot_count - 1, -1, -1): + robots[i] = state & self._mask # read last 5 bits + state >>= self._bits_per_robot # remove those 5 bits + + return robots \ No newline at end of file From af6164ffe36f2aa80e709634dff2a96de75c062e Mon Sep 17 00:00:00 2001 From: Kelvin Date: Tue, 24 Feb 2026 10:53:05 -0800 Subject: [PATCH 4/9] clean up pack & unpack --- games/src/games/lunar_lockout.py | 52 +++++++++++++++++--------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/games/src/games/lunar_lockout.py b/games/src/games/lunar_lockout.py index 27187c1..f0f72f2 100644 --- a/games/src/games/lunar_lockout.py +++ b/games/src/games/lunar_lockout.py @@ -30,19 +30,17 @@ def __init__(self, variant_id: str): size_string = LunarLockout.board_size[0] self._rows, self._cols = map(int, size_string.split("x")) self._cells = self._rows * self._cols + # Exit Position + self._center = self._cells // 2 # Limits self._max_row = self._rows - 1 self._max_col = self._cols - 1 - self.row_stride = self._cols - - # Exit Position - self._center = self._cells // 2 + self.row_stride = self._cols # for jumping through rows # Robot configs self._robot_count = 5 self._red_index = 0 self._removed = 31 - # Encoding self._bits_per_robot = 5 self._mask = 0b11111 @@ -145,45 +143,49 @@ def move_to_string(self, move: int, mode: StringMode) -> str: # Provide stepping logic for movement along rows and columns. # Provide alignment checks for same-row and same-column detection. - def pack(self, robots: list[int]) -> int: - ''' - Converts a list of robot positions into a single integer state. + """ + Takes the 5 robot positions and compresses them into one binary. - robots is a list of length 5 in the fixed order: + robots must be a list in this exact order: [red, helper1, helper2, helper3, helper4] - Each position is stored using 5 bits (values 0-31). - 0-24 represent board squares and 31 represents a removed robot. - ''' + Each robot position is a number: + 0-24 = a square on the board + 31 = the robot has been removed + + We store each position using 5 bits. + """ state = 0 - # Insert each robot position into the integer from left to right. - # Shift the existing bits 5 places and place the new position - # into the lowest 5 bits. + # Add each robot position into the integer one at a time. + # Shifting left makes space for the next robot, and the OR + # places the new value into those 5 empty bits. for position in robots: if position < 0 or position > self._mask: raise ValueError("Invalid robot position") state = (state << self._bits_per_robot) | position - return state - + + def unpack(self, state: int) -> list[int]: """ - Converts an encoded integer state back into robot positions. + Reverses pack(): takes the encoded binary and recovers the + original robot positions. - Returns a list of length 5 in the order: + Returns a list: [red, helper1, helper2, helper3, helper4] - The function repeatedly reads the lowest 5 bits to recover - each robot position and shifts the state right after each read. + The function repeatedly reads the last 5 bits of the number to + get a robot position, then shifts the number right to remove + those bits. """ robots = [0] * self._robot_count - # Extract robots in reverse order because the last robot was - # stored in the lowest 5 bits. + # Read robot locations backwards because the last robot inserted + # is stored in the lowest 5 bits of the integer. for i in range(self._robot_count - 1, -1, -1): - robots[i] = state & self._mask # read last 5 bits - state >>= self._bits_per_robot # remove those 5 bits + robots[i] = state & self._mask # get last 5 bits + state >>= self._bits_per_robot # discard those bits return robots \ No newline at end of file From c8ac0855a8e6faf466a2a80bb24ace3723220f62 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Tue, 24 Feb 2026 22:52:14 -0800 Subject: [PATCH 5/9] complete generate moves and do move --- games/src/games/lunar_lockout.py | 79 ++++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 10 deletions(-) diff --git a/games/src/games/lunar_lockout.py b/games/src/games/lunar_lockout.py index f0f72f2..82bf656 100644 --- a/games/src/games/lunar_lockout.py +++ b/games/src/games/lunar_lockout.py @@ -44,7 +44,6 @@ def __init__(self, variant_id: str): # Encoding self._bits_per_robot = 5 self._mask = 0b11111 - # Directions self._directions = { self._move_up: (-1, 0), @@ -67,17 +66,35 @@ def start(self) -> int: # Decode the state into robot positions. # Skip robots marked as removed (31). - # For each active robot, attempt movement in the four directions. - # Movement must stay within the same row for left/right and same column for up/down. - # While scanning, ignore removed robots and consider only the nearest active robot as a blocker. - # Add a move for every direction; absence of a blocker means the robot will leave the board. + # Each remaining robot can be pushed in four directions (UP, RIGHT, DOWN, LEFT). # Encode moves as (robot_index * 4 + direction). def generate_moves(self, position: int) -> list[int]: """ Returns a list of positions given the input position. - """ - pass - + A move is encoded as: + move = robot_index * 4 + direction + Directions: + 0 = UP + 1 = RIGHT + 2 = DOWN + 3 = LEFT + Move: + robot index 0 = 0-3 + 1 = 4-7 + .... + """ + robots = self.unpack(position) + moves = [] + for robot_index in range(self._robot_count): + # Skip robots that have been removed + if robots[robot_index] == self._removed: + continue + # Each active robot can attempt all four directions + for direction in range(4): + move = robot_index * 4 + direction + moves.append(move) + return moves + # Decode the state and identify the robot and direction from the move. # If the chosen robot is already removed, return the original state. @@ -91,7 +108,50 @@ def do_move(self, position: int, move: int) -> int: """ Returns the resulting position of applying move to position. """ - pass + robot_positions = self.unpack(position) + # Get new robot position and direction + robot_index = move // 4 # 7/4 = index 1 + direction = move % 4 # 7%4 = direction 3 + + # Handle removed robot + if robot_positions[robot_index] == self._removed: + return position + + # Current Robot position + cell = robot_positions[robot_index] + row = cell // self._cols + col = cell % self._cols + # Move Direction Step + move_row, move_col = self._directions[direction] # Gives step by step movements + + # Move Robots + while True: + next_row = row + move_row + next_col = col + move_col + + # Handle robot getting out of space + if not (0 <= next_row < self._rows and 0 <= next_col < self._cols): + robot_positions[robot_index] = self._removed + break + + next_cell = next_row * self._cols + next_col + + # Move robot until block + blocked = False + for i in range(self._robot_count): + if i != robot_index and robot_positions[i] == next_cell: + blocked = True + break + if blocked: + break + + row = next_row + col = next_col + + if robot_positions[robot_index] != self._removed: + robot_positions[robot_index] = row * self._cols + col + + return self.pack(robot_positions) # Decode the state. @@ -187,5 +247,4 @@ def unpack(self, state: int) -> list[int]: for i in range(self._robot_count - 1, -1, -1): robots[i] = state & self._mask # get last 5 bits state >>= self._bits_per_robot # discard those bits - return robots \ No newline at end of file From f11a03218d573b75665d6ea7b22149f5eff3ba80 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Tue, 24 Feb 2026 23:08:05 -0800 Subject: [PATCH 6/9] small fixes --- games/src/games/{lunar_lockout.py => LunarLockout.py} | 9 ++++++++- games/src/games/game_manager.py | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) rename games/src/games/{lunar_lockout.py => LunarLockout.py} (97%) diff --git a/games/src/games/lunar_lockout.py b/games/src/games/LunarLockout.py similarity index 97% rename from games/src/games/lunar_lockout.py rename to games/src/games/LunarLockout.py index 82bf656..c6bd452 100644 --- a/games/src/games/lunar_lockout.py +++ b/games/src/games/LunarLockout.py @@ -61,7 +61,14 @@ def start(self) -> int: """ Returns the starting position of the game. """ - pass + # All values must be 0–24 and no duplicates. + red = 0 + r1 = 4 + r2 = 20 + r3 = 24 + r4 = 12 + robots = [red, r1, r2, r3, r4] + return self.pack(robots) # Decode the state into robot positions. diff --git a/games/src/games/game_manager.py b/games/src/games/game_manager.py index 56cbd3d..733354a 100644 --- a/games/src/games/game_manager.py +++ b/games/src/games/game_manager.py @@ -2,6 +2,7 @@ from .horses import Horses from .pancakes import Pancakes from models import * +from .LunarLockout import LunarLockout game_list = { "clobber": Clobber, From 13a2c80f726fecd5bbb024a62c11652325d7f3bb Mon Sep 17 00:00:00 2001 From: Kelvin Date: Wed, 25 Feb 2026 10:35:41 -0800 Subject: [PATCH 7/9] final --- games/src/games/LunarLockout.py | 66 +++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/games/src/games/LunarLockout.py b/games/src/games/LunarLockout.py index c6bd452..425ccc7 100644 --- a/games/src/games/LunarLockout.py +++ b/games/src/games/LunarLockout.py @@ -61,14 +61,21 @@ def start(self) -> int: """ Returns the starting position of the game. """ - # All values must be 0–24 and no duplicates. + + # # All values must be 0–24 and no duplicates. + # red = 0 + # r1 = 4 + # r2 = 20 + # r3 = 24 + # r4 = 12 + # robots = [red, r1, r2, r3, r4] + # return self.pack(robots) red = 0 - r1 = 4 - r2 = 20 - r3 = 24 - r4 = 12 - robots = [red, r1, r2, r3, r4] - return self.pack(robots) + helpers = [6, 8, 16, 18] + if red == 12: + raise ValueError("Red cannot start at exit") + + return self.pack([red] + helpers) # Decode the state into robot positions. @@ -179,7 +186,18 @@ def to_string(self, position: int, mode: StringMode) -> str: """ Returns a string representation of the position based on the given mode. """ - pass + robots = unpack(position) + + board = [["." for _ in range(5)] for _ in range(5)] + board[2][2] = "X" + symbols = ["R", "A", "B", "C", "D"] + for i, pos in enumerate(robots): + if pos == 31: + continue + r = pos // 5 + c = pos % 5 + board[r][c] = symbols[i] + return "\n".join(" ".join(row) for row in board) # Parse a readable board layout into robot positions. @@ -191,8 +209,24 @@ def from_string(self, strposition: str) -> int: Returns the position from a string representation of the position. Input string is StringMode.Readable. """ - pass - + lines = strposition.strip().split("\n") + + robots = [31, 31, 31, 31, 31] + symbol_map = { + "R": 0, + "A": 1, + "B": 2, + "C": 3, + "D": 4 + } + for r in range(5): + cells = lines[r].split() + for c in range(5): + cell = cells[c] + if cell in symbol_map: + idx = r * 5 + c + robots[symbol_map[cell]] = idx + return self.pack(robots) # Decode the move into robot index and direction. # Return a readable description of the movement direction. @@ -200,7 +234,17 @@ def move_to_string(self, move: int, mode: StringMode) -> str: """ Returns a string representation of the move based on the given mode. """ - pass + robot = move // 4 + direction = move % 4 + + directions = ["UP", "RIGHT", "DOWN", "LEFT"] + + if robot == 0: + name = "Red" + else: + name = f"Robot {robot}" + + return f"{name} {directions[direction]}" # Helper responsibilities: From f8d3075d1a288c5fa1b1a6daeab12bf0fe19eeb6 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Wed, 25 Feb 2026 10:48:48 -0800 Subject: [PATCH 8/9] fix --- games/src/games/LunarLockout.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/games/src/games/LunarLockout.py b/games/src/games/LunarLockout.py index 425ccc7..0c4000b 100644 --- a/games/src/games/LunarLockout.py +++ b/games/src/games/LunarLockout.py @@ -62,20 +62,16 @@ def start(self) -> int: Returns the starting position of the game. """ - # # All values must be 0–24 and no duplicates. - # red = 0 - # r1 = 4 - # r2 = 20 - # r3 = 24 - # r4 = 12 - # robots = [red, r1, r2, r3, r4] - # return self.pack(robots) + # All values must be 0–24 and no duplicates. red = 0 - helpers = [6, 8, 16, 18] + r1 = 6 + r2 = 8 + r3 = 16 + r4 = 18 + robots = [red, r1, r2, r3, r4] if red == 12: raise ValueError("Red cannot start at exit") - - return self.pack([red] + helpers) + return self.pack(robots) # Decode the state into robot positions. @@ -176,7 +172,17 @@ def primitive(self, position: int) -> Optional[Value]: """ Returns a Value enum which defines whether the current position is a win, loss, or non-terminal. """ - pass + robot_positions = self.unpack(position) + red_position = robot_positions[self._red_index] + + # reach the goal? + if red_position == self._center: + return Value.Win + + if red_position == self._removed: + return Value.Loss + + return None # Convert the encoded state into a readable 5x5 board. @@ -186,7 +192,7 @@ def to_string(self, position: int, mode: StringMode) -> str: """ Returns a string representation of the position based on the given mode. """ - robots = unpack(position) + robots = self.unpack(position) board = [["." for _ in range(5)] for _ in range(5)] board[2][2] = "X" From 7b86f65367103a4b74b6514838475b09896d3bfc Mon Sep 17 00:00:00 2001 From: Kelvin Date: Wed, 25 Feb 2026 11:08:18 -0800 Subject: [PATCH 9/9] final --- games/src/games/LunarLockout.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/games/src/games/LunarLockout.py b/games/src/games/LunarLockout.py index 0c4000b..490ff1b 100644 --- a/games/src/games/LunarLockout.py +++ b/games/src/games/LunarLockout.py @@ -61,13 +61,12 @@ def start(self) -> int: """ Returns the starting position of the game. """ - # All values must be 0–24 and no duplicates. - red = 0 - r1 = 6 - r2 = 8 - r3 = 16 - r4 = 18 + red = 7 + r1 = 11 + r2 = 13 + r3 = 17 + r4 = 22 robots = [red, r1, r2, r3, r4] if red == 12: raise ValueError("Red cannot start at exit") @@ -280,7 +279,6 @@ def pack(self, robots: list[int]) -> int: for position in robots: if position < 0 or position > self._mask: raise ValueError("Invalid robot position") - state = (state << self._bits_per_robot) | position return state