def getCompletedPuzzleCleanedImage(imagePuzzle: Puzzle) -> Grid: # Removing puzzle piece borders cleanedPiecesData = {} for p in imagePuzzle.pieces.values(): cleanedPiecesData[p.id] = [[ p.grid.data[y][x] for x in range(1, p.grid.width - 1) ] for y in range(1, p.grid.height - 1)] # Each pieces have same dimensions piecesWidth = Grid(next(iter(cleanedPiecesData.values()))).width piecesHeight = Grid(next(iter(cleanedPiecesData.values()))).height # Puzzle dimensions puzzleWidth = max([p.x for p in imagePuzzle.matchedPiecePositions.keys()]) + 1 puzzleHeight = max([p.y for p in imagePuzzle.matchedPiecePositions.keys()]) + 1 # Joining pieces together fullImage = [[SYMBOL.SEA.value for x in range(piecesWidth * puzzleWidth)] for y in range(piecesHeight * puzzleHeight)] for py in range(puzzleHeight): for px in range(puzzleWidth): puzzlePieceId = imagePuzzle.matchedPiecePositions[Coordinate( px, py)].id for y in range(piecesHeight): for x in range(piecesWidth): fullImage[py * piecesHeight + y][px * piecesWidth + x] = cleanedPiecesData[puzzlePieceId][y][x] return Grid(fullImage)
def remapSeatsNeighbors(grid: Grid, seatsGrid: dict) -> None: directions = [Coordinate(*d) for d in getAdjacentDirections(dimensions=2)] # Map seat grid neighbors with new rules for p, seat in seatsGrid.items(): visibleAdjacentSeats = [] for d in directions: position = Coordinate(p.x, p.y) while utils.inbetween(0, position.x, grid.width-1) and utils.inbetween(0, position.y, grid.height-1): position = Coordinate(position.x + d.x, position.y + d.y) if position in seatsGrid: visibleAdjacentSeats.append(seatsGrid[position]) break seat.adjacentSeats = visibleAdjacentSeats
def mapSeats(grid: Grid) -> dict: seatsGrid = {} # Map seat grid for y in range(0, grid.height): for x in range(0, grid.width): s = SYMBOL(grid.data[y][x]) if s != SYMBOL.FLOOR: p = Coordinate(x, y) seatsGrid[p] = Seat(p, s) return seatsGrid
def moveShipWithWaypoint(instructions: list, shipPosition: Coordinate, wayPointPosition: Coordinate) -> (Coordinate, Coordinate, ACTION): currentShipPosition = Coordinate(shipPosition.x, shipPosition.y) currentWaypointPosition = Coordinate(wayPointPosition.x, wayPointPosition.y) for action, value in instructions: if action in DIRECTION: # Move waypoint only d = DIRECTION[action] currentWaypointPosition = Coordinate(currentWaypointPosition.x + value * d.x, currentWaypointPosition.y + value * d.y) elif action in ROTATION: # Move waypoint only for _ in range(getNbTurns(value)): currentWaypointPosition = rotatePosition(currentWaypointPosition, action) elif action == ACTION.FORWARD: # Move ship towards waypoint (movement amplified by waypoint) currentShipPosition = Coordinate(currentShipPosition.x + value * currentWaypointPosition.x, currentShipPosition.y + value * currentWaypointPosition.y) else: print(action) raise Exception('Unknown action !') return (currentShipPosition, currentWaypointPosition, direction)
def moveShip(instructions: list, startingPosition: Coordinate, startingDirection: ACTION) -> (Coordinate, ACTION): position = Coordinate(startingPosition.x, startingPosition.y) direction = startingDirection for action, value in instructions: if action in DIRECTION: # Move ship position, but keep direction the same d = DIRECTION[action] position = Coordinate(position.x + value * d.x, position.y + value * d.y) elif action in ROTATION: # Rotate ship for _ in range(getNbTurns(value)): direction = ROTATION[action][direction] elif action == ACTION.FORWARD: # Move ship position in direction d = DIRECTION[direction] position = Coordinate(position.x + value * d.x, position.y + value * d.y) else: print(action) raise Exception('Unknown action !') return (position, direction)
def printCompletedPuzzlePieceIDs(self) -> None: xMax = max([p.x for p in self.matchedPiecePositions.keys()]) yMax = max([p.y for p in self.matchedPiecePositions.keys()]) separator = '---'.join(['-' * 4 for i in range(xMax + 1)]) + '------' print('Completed puzzle:') print(separator) for y in range(yMax + 1): print( '| ', ' '.join([ self.matchedPiecePositions[Coordinate(x, y)].id for x in range(xMax + 1) ]), ' |') print(separator, '\n')
def findSeaMonsters(image: Grid, seaMonster: Grid) -> int: # Keep only positions to match seaMonsterMatchingPositions = set() for y in range(seaMonster.height): for x in range(seaMonster.width): if seaMonster.data[y][x] == SYMBOL.HAZARD.value: seaMonsterMatchingPositions.add(Coordinate(x, y)) # Find all sea monsters seaMonstersCount = 0 y = 0 while y + seaMonster.height < image.height: x = 0 while x + seaMonster.width < image.width: # Check if sea monster in current view seaMonsterVisible = all( image.data[y + p.y][x + p.x] == SYMBOL.HAZARD.value for p in seaMonsterMatchingPositions) if seaMonsterVisible is True: # Increasing sea monsters count seaMonstersCount += 1 # Marking sea monster on image for p in seaMonsterMatchingPositions: image.data[y + p.y][x + p.x] = SYMBOL.SEA_MONSTER.value # Skipping to next possible non overlapping sea monster x += seaMonster.width else: x += 1 if seaMonsterVisible is True: # Skipping to next possible non overlapping sea monster y += seaMonster.height else: y += 1 return seaMonstersCount
def rotatePosition(p: Coordinate, r: ROTATION): if r == ACTION.RIGHT: # Clockwise return Coordinate(p.y, -p.x) else: # Counterclockwise return Coordinate(-p.y, p.x)
########################### # region COMMON # endregion COMMON ########################### class ACTION(Enum): NORTH = 'N' SOUTH = 'S' EAST = 'E' WEST = 'W' LEFT = 'L' RIGHT = 'R' FORWARD = 'F' DIRECTION = { ACTION.NORTH: Coordinate(0, 1), ACTION.SOUTH: Coordinate(0, -1), ACTION.EAST: Coordinate(1, 0), ACTION.WEST: Coordinate(-1, 0) } ROTATION = { ACTION.LEFT: { ACTION.NORTH: ACTION.WEST, ACTION.SOUTH: ACTION.EAST, ACTION.EAST: ACTION.NORTH, ACTION.WEST: ACTION.SOUTH }, ACTION.RIGHT: { ACTION.NORTH: ACTION.EAST, ACTION.SOUTH: ACTION.WEST,
def solve(self, log=False) -> None: # Warning: currrent solving solution only works if each puzzle piece borders match with a single other puzzle piece ! if set([ p for id, p in self.pieces.items() if p.getNbMatchingPieces() > 4 ]): raise Exception( 'Solution cannot work for puzzle with non unique matching piece borders !' ) if len(self.matchedPiecePositions.keys()) > 0: return # Already solved print('Solving puzzle...\n------------------') # 1) Starting with any corner in fixed orientation startingCorner = self._findStartingCorner() # 2) Adding all pieces clockwise in a spiral from starting corner matchedPieces = set() currentPiece = startingCorner currentPosition = Coordinate(0, 0) currentDirection = BORDER_TYPE.RIGHT while True: # Adding current piece to solved puzzle matchedPieces.add(currentPiece) self.matchedPiecePositions[currentPosition] = currentPiece # Finding next piece nextPiece = next(iter( currentPiece.matchingPiece[currentDirection])) if nextPiece in matchedPieces: if len(matchedPieces) == self.nbPieces: break # Done, all pieces are matched # Changing direction inward currentDirection = NEXT_CLOCKWISE_DIRECTION[currentDirection] nextPiece = next( iter(currentPiece.matchingPiece[currentDirection])) if log: print('(%s, %s) currentPiece %s --> %s --> %s' % (currentPosition.x, currentPosition.y, currentPiece.id, currentDirection.name, nextPiece.id)) # Flipping / rotating next piece until adjacent border matched oppositeDirection = OPPOSITE_DIRECTION[currentDirection] currentBorderToMatch = currentPiece.getBorder( currentDirection).data currentFlippedBorderToMatch = currentBorderToMatch[::-1] while currentBorderToMatch != nextPiece.getBorder( oppositeDirection).data: if currentFlippedBorderToMatch == nextPiece.getBorder( oppositeDirection).data: if BORDER_TYPE_ORIENTATION[ currentDirection] == ORIENTATION.HORIZONTAL: nextPiece.flipY() else: nextPiece.flipX() else: nextPiece.rotate() # Moving to next piece currentPiece = nextPiece currentPosition = ADJACENT_POSITION[currentDirection]( currentPosition) if nextPiece in self.cornerPieces: # Changing direction at border (so we don't leave puzzle area) currentDirection = NEXT_CLOCKWISE_DIRECTION[currentDirection] print('------------------\n')
BORDER_TYPE.LEFT: BORDER_TYPE.RIGHT, BORDER_TYPE.RIGHT: BORDER_TYPE.LEFT } NEXT_CLOCKWISE_DIRECTION = { # Current direction: next clockwise direction BORDER_TYPE.TOP: BORDER_TYPE.RIGHT, BORDER_TYPE.RIGHT: BORDER_TYPE.BOTTOM, BORDER_TYPE.BOTTOM: BORDER_TYPE.LEFT, BORDER_TYPE.LEFT: BORDER_TYPE.TOP, } ADJACENT_POSITION = { # Current direction: next position BORDER_TYPE.TOP: lambda p: Coordinate(p.x, p.y - 1), BORDER_TYPE.BOTTOM: lambda p: Coordinate(p.x, p.y + 1), BORDER_TYPE.LEFT: lambda p: Coordinate(p.x - 1, p.y), BORDER_TYPE.RIGHT: lambda p: Coordinate(p.x + 1, p.y) } class PuzzlePieceBorder(): def __init__(self, type: BORDER_TYPE, data: list): self.type = type self.data = data def __str__(self):