4 changed files with 201 additions and 220 deletions
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
from pathlib import Path |
||||
from typing import List, Tuple |
||||
|
||||
import numpy as np |
||||
|
||||
from common.data_models import Block, Field, Rewards, Solution |
||||
|
||||
|
||||
def parse_puzzle(filename: str) -> Tuple[Field, List[Block], Rewards]: |
||||
"""Parse puzzle specification into a Field, Block list and Rewards.""" |
||||
lines = Path(filename).read_text().splitlines() |
||||
|
||||
width, height = map(int, lines.pop(0).split()) |
||||
field = Field(width, height) |
||||
|
||||
nr_blocks, nr_rewards = map(int, lines.pop(0).split()) |
||||
|
||||
blocks = [] |
||||
for block_id in range(nr_blocks): |
||||
width, height, color = lines.pop(0).split() |
||||
width, height = int(width), int(height) |
||||
values = [ |
||||
"" if v == "0" else v for v in lines.pop(0).split() |
||||
] # replace 0 with empty string for empty value |
||||
values = np.reshape(values, (height, width)) |
||||
blocks.append(Block(block_id, width, height, color, values)) |
||||
|
||||
points = {} |
||||
multiplication_factors = {} |
||||
for i in range(nr_rewards): |
||||
color, point, multiplication_factor = lines.pop(0).split() |
||||
points[color] = int(point) |
||||
multiplication_factors[color] = int(multiplication_factor) |
||||
|
||||
return field, blocks, Rewards(points, multiplication_factors) |
||||
|
||||
|
||||
def parse_solution(filename: str) -> Solution: |
||||
"""Read and parse a solution from file""" |
||||
fp = open(filename, "r") |
||||
lines = fp.readlines() |
||||
solution = Solution() |
||||
for line in lines: |
||||
block_id, block_position = map(int, line.split()) |
||||
solution.block_ids.append(block_id) |
||||
solution.block_positions.append(block_position) |
||||
|
||||
return solution |
||||
@ -1,155 +0,0 @@
@@ -1,155 +0,0 @@
|
||||
from pathlib import Path |
||||
from typing import List, Tuple |
||||
|
||||
import numpy as np |
||||
|
||||
from common.data_models import Block, Field, Rewards, Solution |
||||
|
||||
|
||||
def parse_puzzle(filename: str) -> Tuple[Field, List[Block], Rewards]: |
||||
"""Parse puzzle specification into a Field, Block list and Rewards.""" |
||||
lines = Path(filename).read_text().splitlines() |
||||
|
||||
width, height = map(int, lines.pop(0).split()) |
||||
field = Field(width, height) |
||||
|
||||
nr_blocks, nr_rewards = map(int, lines.pop(0).split()) |
||||
|
||||
blocks = [] |
||||
for block_id in range(nr_blocks): |
||||
width, height, color = lines.pop(0).split() |
||||
width, height = int(width), int(height) |
||||
values = [ |
||||
"" if v == "0" else v for v in lines.pop(0).split() |
||||
] # replace 0 with empty string for empty value |
||||
values = np.reshape(values, (height, width)) |
||||
blocks.append(Block(block_id, width, height, color, values)) |
||||
|
||||
points = {} |
||||
multiplication_factors = {} |
||||
for i in range(nr_rewards): |
||||
color, point, multiplication_factor = lines.pop(0).split() |
||||
points[color] = int(point) |
||||
multiplication_factors[color] = int(multiplication_factor) |
||||
|
||||
return field, blocks, Rewards(points, multiplication_factors) |
||||
|
||||
def write_solution(solution: Solution, filename: str) -> None: |
||||
"""Write an output file containing a solution block list""" |
||||
with open(filename, "w") as fp: |
||||
for block_id, block_position in zip( |
||||
solution.block_ids, solution.block_positions |
||||
): |
||||
print(f"{block_id} {block_position}", file=fp) |
||||
|
||||
|
||||
def parse_solution(filename: str) -> Solution: |
||||
"""Read and parse a solution from file""" |
||||
fp = open(filename, "r") |
||||
lines = fp.readlines() |
||||
solution = Solution() |
||||
for line in lines: |
||||
block_id, block_position = map(int, line.split()) |
||||
solution.block_ids.append(block_id) |
||||
solution.block_positions.append(block_position) |
||||
|
||||
return solution |
||||
|
||||
|
||||
def fit(field: Field, block: Block, x: int, y: int) -> bool: |
||||
"""Check if the block fits with the left bottom on (x, y) |
||||
|
||||
field: where field.values is an array where first row is the bottom of the playing field |
||||
block: where block.values is an array where first row is the bottom of the block |
||||
x, y: start position of left bottom of the block's bounding box |
||||
""" |
||||
out_of_bounds_on_the_right = x + block.width > field.width |
||||
out_of_bounds_on_the_top = y + block.height > field.height |
||||
out_of_bounds_on_the_left = x < 0 |
||||
out_of_bounds_on_the_bottom = y < 0 |
||||
if ( |
||||
out_of_bounds_on_the_right |
||||
or out_of_bounds_on_the_top |
||||
or out_of_bounds_on_the_bottom |
||||
or out_of_bounds_on_the_left |
||||
): |
||||
return False |
||||
|
||||
# select area of the field where the block should fit |
||||
selected_field_area = field.values[y : y + block.height, x : x + block.width] |
||||
for block_row, field_row in zip(block.values, selected_field_area): |
||||
for block_value, field_value in zip(block_row, field_row): |
||||
# block fits if: field value or block value is an empty string |
||||
if block_value and field_value: |
||||
return False |
||||
return True |
||||
|
||||
|
||||
def update(field: Field, block: Block, x: int, y: int) -> None: |
||||
"""Update the field by dropping the block on position (x,y)""" |
||||
for update_y, block_row in zip(range(y, y + block.height), block.values): |
||||
field_row = field.values[update_y][x : x + block.width] |
||||
|
||||
# make sure we don't overwrite an existing field block if the block |
||||
# has an empty space where in the field the value is filled |
||||
new_row = [f if f else b for f, b in zip(field_row, block_row)] |
||||
field.values[update_y][x : x + block.width] = new_row |
||||
|
||||
|
||||
def drop_block(field: Field, block: Block, x: int) -> None: |
||||
"""Try to add the block on position x; |
||||
If it fits then update the field, if not then raise exception. |
||||
|
||||
x: position of the most left part of the block |
||||
""" |
||||
y = field.height - block.height |
||||
fits_somewhere = False |
||||
while fit(field, block, x, y): |
||||
fits_somewhere = True |
||||
y -= 1 |
||||
|
||||
if not fits_somewhere: |
||||
raise Exception(f"{block} does not fit on x: {x}") |
||||
update(field, block, x, y + 1) |
||||
|
||||
|
||||
def drop_blocks(field: Field, blocks: List[Block], solution: Solution) -> None: |
||||
"""Drop the blocks for the solution in the playing field""" |
||||
if len(solution.block_ids) != len(set(solution.block_ids)): |
||||
raise Exception("You can only use each block once") |
||||
if (max(solution.block_ids) >= len(blocks)) or (min(solution.block_ids) < 0): |
||||
raise Exception("You used a block id that doesn't exist") |
||||
|
||||
for block_id, block_position in zip(solution.block_ids, solution.block_positions): |
||||
drop_block(field, blocks[block_id], block_position) |
||||
|
||||
def score_row(row: np.array, rewards: Rewards) -> int: |
||||
def color_points(row: list, rewards: Rewards) -> int: |
||||
score = 0 |
||||
for color in row: |
||||
if color == "": |
||||
score -= 1 |
||||
else: |
||||
score += rewards.points[color] |
||||
return score |
||||
|
||||
def multiplication_factor_for_full_color_row(row: list, rewards: Rewards) -> int: |
||||
if row[0] != "" and len(set(row)) == 1: |
||||
return rewards.multiplication_factors[row[0]] |
||||
return 1 |
||||
|
||||
full_row = not any(row == "") |
||||
empty_row = "".join(row) == "" |
||||
score = 0 |
||||
if not empty_row: |
||||
score += color_points(row, rewards) |
||||
if full_row: |
||||
score *= multiplication_factor_for_full_color_row(row, rewards) |
||||
return score |
||||
|
||||
def score_solution(field: Field, rewards: Rewards) -> int: |
||||
score = 0 |
||||
for row in field.values: |
||||
score += score_row(row, rewards) |
||||
return score |
||||
|
||||
Loading…
Reference in new issue