Skip to content

Commit

Permalink
day22: coverage minus vpython
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-ong committed Dec 28, 2023
1 parent 13e21b1 commit 3f87c89
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 104 deletions.
148 changes: 53 additions & 95 deletions day22/day22.py
Original file line number Diff line number Diff line change
@@ -1,96 +1,50 @@
import random
from typing import Any

import vpython

from day22.lib.classes import BoxData, Matrix, Vector3
from day22.lib.classes import BoxData, Matrix
from day22.lib.parsers import get_boxes
from day22.lib.vis import CAMERA_AXIS_1, CAMERA_POS_1, bind_keys, follow_block, init_vis

INPUT = "day22/input.txt"
INPUT_SMALL = "input-small.txt"


def construct_box(box_data: BoxData, color: vpython.vector) -> vpython.box:
return vpython.box(
pos=box_data.vpos,
length=box_data.length,
height=box_data.height,
width=box_data.width,
color=color,
)


def random_color() -> vpython.vector:
hsv = vpython.vector(random.random(), random.uniform(0.5, 1.0), 1.0)
return vpython.color.hsv_to_rgb(hsv)

INPUT_SMALL = "day22/input-small.txt"

CAMERA_START = vpython.vector(40.0164, 11.0337, 39.9238)
CAMERA_AXIS = -CAMERA_START

CAMERA_POS_1 = vpython.vector(111.512, 122.347, 65.0144)
CAMERA_AXIS_1 = vpython.vector(-83.3746, -20.0517, -69.4084)
AUTO_START = False
ANIMATE = True


class Visualization:
boxes: list[BoxData]
matrix: Matrix
has_started: bool

def __init__(self, auto_start: bool = False, animate: bool = True) -> None:
self.boxes = get_boxes(INPUT)
def __init__(self, boxes: list[BoxData], animate: bool = True) -> None:
self.boxes = boxes
self.boxes.sort(key=lambda x: x.z_val_bot)
self.matrix = Matrix()
self.init_vis()
self.has_started = False
vpython.scene.bind("keydown", self.on_key_input)
self.animate = animate
if auto_start:
self.start()

def init_vis(self) -> None:
"""Initialize vis boxes"""
for box in self.boxes:
color = random_color()
vbox = construct_box(box, color)
box.set_vbox(vbox)
ground = BoxData("ground", Vector3(0, 0, 0), Vector3(10, 10, 0))
construct_box(ground, random_color())
# init camera:
vpython.scene.camera.axis = CAMERA_AXIS
vpython.scene.camera.pos = CAMERA_START
vpython.scene.height = 600
vpython.scene.width = 400

def on_key_input(self, evt: Any) -> None:
character = evt.key
if character == "shift":
return
print(character)
if character in ["\n", "enter", "return"]:
self.start()
if character == "c":
print(vpython.scene.camera.pos)
print(vpython.scene.camera.axis)

def vis_sleep(self, time: float) -> None:
if self.animate:
vpython.sleep(time)
init_vis(self.boxes)
bind_keys(self.start)

self.has_started = False

def vis_rate(self, rate: float) -> None:
if self.animate:
vpython.rate(rate)

def follow_block(self, y: float, box: BoxData) -> None:
pos = vpython.scene.camera.pos
pos.y = max(y + box.start_pos.z, pos.y)
vpython.scene.camera.pos = pos
if self.animate:
follow_block(y, box)

def start(self) -> None:
if self.has_started:
if self.has_started: # pragma: no cover
return
self.has_started = True
camera_height = vpython.scene.camera.pos.y

if self.animate:
camera_height = vpython.scene.camera.pos.y
else:
camera_height = 0

for box in self.boxes:
while self.matrix.can_fall_down(box):
Expand All @@ -108,6 +62,7 @@ def start(self) -> None:

print(self.calculate_part1())
print(self.calculate_part2())

self.animate_part1()
self.animate_part2()

Expand All @@ -119,44 +74,47 @@ def calculate_part2(self) -> int:
return sum(len(box.recursive_fall({box})) for box in self.boxes)

def animate_part1(self) -> None:
if not self.animate:
return
vpython.scene.camera.pos = CAMERA_POS_1
vpython.scene.camera.axis = CAMERA_AXIS_1
for box in self.boxes:
if self.matrix.can_fly_up(box):
box.select()
self.vis_rate(60)

for box in self.boxes:
if self.matrix.can_fly_up(box):
box.unselect()
self.vis_rate(60)
if self.animate:
vpython.scene.camera.pos = CAMERA_POS_1
vpython.scene.camera.axis = CAMERA_AXIS_1
for box in self.boxes:
if self.matrix.can_fly_up(box):
box.select()
self.vis_rate(60)

for box in self.boxes:
if self.matrix.can_fly_up(box):
box.unselect()
self.vis_rate(60)

def animate_part2(self) -> None:
if not self.animate:
return
vpython.scene.camera.pos = CAMERA_POS_1
vpython.scene.camera.axis = CAMERA_AXIS_1
reversed_boxes = sorted(self.boxes, key=lambda box: box.end_pos.z, reverse=True)
for box in reversed_boxes:
to_fall = box.recursive_fall({box})
to_fall_sorted = sorted(to_fall, key=lambda x: x.z_val_bot)
if len(to_fall_sorted) == 0:
continue
if self.animate:
vpython.scene.camera.pos = CAMERA_POS_1
vpython.scene.camera.axis = CAMERA_AXIS_1
reversed_boxes = sorted(
self.boxes, key=lambda box: box.end_pos.z, reverse=True
)
for box in reversed_boxes:
to_fall = box.recursive_fall({box})
to_fall_sorted = sorted(to_fall, key=lambda x: x.z_val_bot)
if len(to_fall_sorted) == 0:
continue

for faller in to_fall_sorted:
faller.select()
for faller in to_fall_sorted:
faller.select()

self.vis_rate(20)
self.vis_rate(20)

for faller in to_fall:
faller.unselect()
for faller in to_fall:
faller.unselect()


def main() -> None:
auto_start = True
vis = Visualization(auto_start, True)
boxes: list[BoxData] = get_boxes(INPUT)
vis = Visualization(boxes, ANIMATE)

if AUTO_START:
vis.start()

while True:
vpython.rate(165) # we control sleeping
Expand Down
17 changes: 11 additions & 6 deletions day22/lib/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ class BoxData:
name: str
start_pos: Vector3
end_pos: Vector3
vbox: vpython.box = field(init=False, repr=False, hash=False)
vbox: Optional[vpython.box] = field(
init=False, repr=False, hash=False, default=None
)
supports: set["BoxData"] = field(
default_factory=set, hash=False, repr=False
) # list of blocks we support
Expand Down Expand Up @@ -59,15 +61,18 @@ def fall(self) -> None:
self.start_pos.z -= 1
self.end_pos.z -= 1
# vbox y == boxdata z
self.vbox.pos.y -= 1
if self.vbox is not None:
self.vbox.pos.y -= 1

def select(self) -> None:
self.vbox.pos.x += 30
self.vbox.pos.z -= 30
if self.vbox is not None:
self.vbox.pos.x += 30
self.vbox.pos.z -= 30

def unselect(self) -> None:
self.vbox.pos.x -= 30
self.vbox.pos.z += 30
if self.vbox is not None:
self.vbox.pos.x -= 30
self.vbox.pos.z += 30

def set_supports(self, supports: set["BoxData"]) -> None:
"""blocks under us"""
Expand Down
2 changes: 0 additions & 2 deletions day22/lib/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ def get_boxes(path: str) -> list[BoxData]:
with open(path, encoding="utf8") as file:
for index, line in enumerate(file):
line = line.strip()
if len(line) == 0:
break
vec1, vec2 = line.split("~")
from_vec = parse_vector(vec1)
to_vec = parse_vector(vec2)
Expand Down
96 changes: 96 additions & 0 deletions day22/lib/vis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import random
from typing import Any

import vpython

from day22.lib.classes import BoxData, Matrix, Vector3


def construct_box(box_data: BoxData, color: vpython.vector) -> vpython.box:
return vpython.box(
pos=box_data.vpos,
length=box_data.length,
height=box_data.height,
width=box_data.width,
color=color,
)


def random_color() -> vpython.vector:
hsv = vpython.vector(random.random(), random.uniform(0.5, 1.0), 1.0)
return vpython.color.hsv_to_rgb(hsv)


CAMERA_START = vpython.vector(40.0164, 11.0337, 39.9238)
CAMERA_AXIS = -CAMERA_START

CAMERA_POS_1 = vpython.vector(111.512, 122.347, 65.0144)
CAMERA_AXIS_1 = vpython.vector(-83.3746, -20.0517, -69.4084)


def init_vis(boxes: list[BoxData]) -> None:
"""Initialize vis boxes. Only called if we're visualizing"""
for box in boxes:
color = random_color()
vbox = construct_box(box, color)
box.set_vbox(vbox)

ground = BoxData("ground", Vector3(0, 0, 0), Vector3(10, 10, 0))
construct_box(ground, random_color())
# init camera:
vpython.scene.camera.axis = CAMERA_AXIS
vpython.scene.camera.pos = CAMERA_START
vpython.scene.height = 600
vpython.scene.width = 400


def bind_keys(on_key_down: Any) -> None:
def callback(evt: Any) -> None:
character = evt.key
if character == "shift":
return
print(character)
if character in ["\n", "enter", "return"]:
on_key_down()

vpython.scene.bind("keydown", callback)


def follow_block(y: float, box: BoxData) -> None:
"""force camera to follow a block"""
pos = vpython.scene.camera.pos
pos.y = max(y + box.start_pos.z, pos.y)
vpython.scene.camera.pos = pos


def animate_part1(boxes: list[BoxData], matrix: Matrix) -> None:
vpython.scene.camera.pos = CAMERA_POS_1
vpython.scene.camera.axis = CAMERA_AXIS_1
for box in boxes:
if matrix.can_fly_up(box):
box.select()
vpython.rate(60)

for box in boxes:
if matrix.can_fly_up(box):
box.unselect()
vpython.rate(60)


def animate_part2(boxes: list[BoxData]) -> None:
vpython.scene.camera.pos = CAMERA_POS_1
vpython.scene.camera.axis = CAMERA_AXIS_1
reversed_boxes = sorted(boxes, key=lambda box: box.end_pos.z, reverse=True)
for box in reversed_boxes:
to_fall = box.recursive_fall({box})
to_fall_sorted = sorted(to_fall, key=lambda x: x.z_val_bot)
if len(to_fall_sorted) == 0:
continue

for faller in to_fall_sorted:
faller.select()

vpython.rate(20)

for faller in to_fall:
faller.unselect()
Empty file added day22/tests/__init__.py
Empty file.
16 changes: 16 additions & 0 deletions day22/tests/test_day22.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import TYPE_CHECKING

from day22.day22 import INPUT_SMALL, Visualization
from day22.lib.parsers import get_boxes

if TYPE_CHECKING:
from day22.lib.classes import BoxData


def test_visualization() -> None:
boxes: list[BoxData] = get_boxes(INPUT_SMALL)
vis = Visualization(boxes, False)

vis.start()
assert vis.calculate_part1() == 5
assert vis.calculate_part2() == 7
10 changes: 10 additions & 0 deletions day22/tests/test_parsers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from day22.day22 import INPUT_SMALL
from day22.lib.classes import BoxData, Vector3
from day22.lib.parsers import get_boxes


def test_parser() -> None:
boxes: list[BoxData] = get_boxes(INPUT_SMALL)
assert len(boxes) == 7
assert boxes[0].start_pos == Vector3(1, 0, 1)
assert boxes[0].end_pos == Vector3(1, 2, 1)
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ exclude_lines = [
"if TYPE_CHECKING:",
"raise AssertionError",
"# pragma: no cover",
"if self.animate:",
]
omit = ["download_inputs.py", "maker.py"]
omit = ["download_inputs.py", "maker.py", "day22/lib/vis.py"]
precision = 2
skip_covered = true
fail_under = 0

0 comments on commit 3f87c89

Please sign in to comment.