Skip to content

Instantly share code, notes, and snippets.

@td2sk
Last active February 3, 2024 17:10
Show Gist options
  • Save td2sk/8baf287bd090eb9f02bd2d299515e206 to your computer and use it in GitHub Desktop.
Save td2sk/8baf287bd090eb9f02bd2d299515e206 to your computer and use it in GitHub Desktop.
■■□□□□□□□□■■
■■□ □■■
□□□ □□□
□ □
□ □
□ □
□□□ □□
■■□□□□□□□ □■
from __future__ import annotations
from enum import Enum, IntEnum
from logging import INFO, basicConfig, getLogger
basicConfig(level=INFO, filename="log.txt")
logger = getLogger(__name__)
class Direction(IntEnum):
"""アイテムの飛んでいく方向"""
UpperLeft = 0
LowerLeft = 1
LowerRight = 2
UpperRight = 3
@staticmethod
def tostring(direction: Direction):
return "⬉⬋⬊⬈"[direction]
def op(d: Direction):
"""方向を反転"""
return Direction((d + 2) & 3)
def cw(d: Direction):
"""方向を時計回りに回転
アイテムが壁に反射して時計回りに進行方向を変えたときの向きを返す
Examples:
>>> cw(Direction.UpperLeft)
Direction.UpperRight
"""
return Direction((d + 3) & 3)
def ccw(d: Direction):
"""方向を反時計回りに回転
アイテムが壁に反射して反時計回りに進行方向を変えたときの向きを返す
Examples:
>>> cw(Direction.UpperLeft)
Direction.LowerLeft
"""
return Direction((d + 1) & 3)
class ReflectedWallOrientation(IntEnum):
"""反射した壁の向き"""
LEFT = 0
DOWN = 1
RIGHT = 2
UP = 3
class Point:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
def __eq__(self, rhs):
return self.x == rhs.x and self.y == rhs.y
def __add__(self, rhs):
return Point(self.x + rhs.x, self.y + rhs.y)
def __repr__(self):
return f"P({self.x}, {self.y})"
# アイテムが飛んでいく方向の座標変化分
Forward = [Point(-1, -1), Point(-1, 1), Point(1, 1), Point(1, -1)]
# アイテムの進行方向に対して左手側のマス目の座標変化分
# 例: アイテムが右上に飛んでいるとき
#
# @: ここの座標
# ➚
LeftHandSide = [Point(-1, 0), Point(0, 1), Point(1, 0), Point(0, -1)]
# アイテムの進行方向に対して右手側のマス目の座標変化分
# 例: アイテムが右上に飛んでいるとき
#
# ➚@: ここの座標
RightHandSide = [Point(0, -1), Point(-1, 0), Point(0, 1), Point(1, 0)]
class Wall(Enum):
"""部屋の表現"""
Block = "■"
Reflect = "□"
Space = " "
class Room:
def __init__(self, data):
self.data = data
self.width = max(len(line) for line in data)
self.height = len(data)
def __getitem__(self, p: Point):
if p.y < 0 or self.height <= p.y or p.x < 0 or self.width <= p.x:
return Wall.Block
return Wall(self.data[p.y][p.x])
@staticmethod
def load(path: str):
with open(path, encoding=("utf-8")) as f:
data = [list(line) for line in f.read().splitlines()]
return Room(data)
class Path:
"""アイテムの飛んだルートと反射した壁の方向を記録"""
path: list[tuple[Point, Direction]]
reflected_wall_orientations: set[ReflectedWallOrientation]
catched: bool
crashed: bool
def __init__(self):
self.path = []
self.reflected_wall_orientations = set()
self.catched = False
self.crashed = False
def add(self, position: Point, direction: Direction):
self.path.append((position, direction))
def reflect(self, orig_direction: Direction, clockwise: bool):
# note: Direction, Orientation の具体的な Enum 数値に依存
if clockwise:
orientation = ReflectedWallOrientation(orig_direction)
else:
orientation = ReflectedWallOrientation(cw(orig_direction))
self.reflected_wall_orientations.add(orientation)
def catch(self):
self.catched = True
def crash(self):
self.crashed = True
def __len__(self):
return len(self.path)
def satisfy(self):
"""アイテムの飛行ルートが分裂条件を満たしているか"""
return len(self.reflected_wall_orientations) == 4
def dump(self, room: Room, player: Point, f):
for item, direction in self.path:
r = ["".join(l) for l in room.data]
r[item.y] = (
r[item.y][: item.x]
+ Direction.tostring(direction)
+ r[item.y][item.x + 1 :]
)
r[player.y] = r[player.y][: player.x] + "@" + r[player.y][player.x + 1 :]
print(
f"player: {player}, item: {item}",
file=f,
)
print("\n".join(r), file=f)
print("", file=f)
def _search(room: Room, player: Point, item: Point, direction: Direction, path: Path):
"""アイテムの飛行ルートを追跡
Args:
room (Room): room data
player (Point): シレンの座標
item (Point): 現在追跡中のアイテム位置
direction (Direction): アイテムの飛行方向
path (Path): アイテムの軌跡
Returns:
Path: アイテムの軌跡
"""
# アイテムがプレイヤー位置に来たら終了
# 投げた直後(len(path) == 0) のときは終了しない
if player == item and len(path) != 0:
path.catch()
return path
# 飛び先が普通の壁なら終了
if room[item] == Wall.Block:
# 壺が割れる
path.crash()
return path
path.add(item, direction)
f = item + Forward[direction]
l = item + LeftHandSide[direction]
r = item + RightHandSide[direction]
if room[l] == room[r] == Wall.Reflect:
# 角にあたって停止
# ⬊□
# □
return path
if room[f] == Wall.Reflect:
if room[l] == Wall.Reflect:
# 時計回りに反射
# ⬊□
# ⬋□
path.reflect(direction, clockwise=True)
return _search(room, player, r, cw(direction), path)
if room[r] == Wall.Reflect:
# 反時計回りに反射
# ⬉□
# ⬈□
path.reflect(direction, clockwise=False)
return _search(room, player, l, ccw(direction), path)
# 角で反射
return _search(room, player, item, op(direction), path)
return _search(room, player, f, direction, path)
def search(room: Room):
f = open("out.txt", "w", encoding="utf-8")
for y in range(room.height):
for x in range(room.width):
player = Point(x, y)
if room[player] != Wall.Space:
continue
for d in range(4):
d = Direction(d)
path = _search(room, player, player, d, Path())
if path.satisfy():
crashed = " , 壺割れる" if path.crashed else ""
catched = " , キャッチ" if path.catched else ""
print(
f"分裂成功: 初期位置: {player}, 投げる方向: {Direction.tostring(d)}{crashed}{catched}"
)
path.dump(room, player, f)
def main():
room = Room.load("input.txt")
search(room)
if __name__ == "__main__":
main()
@td2sk
Copy link
Author

td2sk commented Feb 3, 2024

判定ロジック
GFbIWmgboAA_wK2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment