467 lines
16 KiB
Rust
467 lines
16 KiB
Rust
use std::collections::HashMap;
|
|
|
|
const INPUT: &str = include_str!("../../data/day20/input");
|
|
|
|
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)]
|
|
enum Side {
|
|
Top,
|
|
Right,
|
|
Bottom,
|
|
Left,
|
|
}
|
|
|
|
impl Side {
|
|
fn opposite(self) -> Self {
|
|
match self {
|
|
Side::Top => Side::Bottom,
|
|
Side::Right => Side::Left,
|
|
Side::Bottom => Side::Top,
|
|
Side::Left => Side::Right,
|
|
}
|
|
}
|
|
|
|
fn transform(self, width: usize, flip: bool, row: usize, col: usize) -> (usize, usize) {
|
|
let (row, col) = if flip {
|
|
(row, width - col - 1)
|
|
} else {
|
|
(row, col)
|
|
};
|
|
let (row, col) = match self {
|
|
Side::Top => (row, col),
|
|
Side::Right => (col, width - row - 1),
|
|
Side::Bottom => (width - row - 1, width - col - 1),
|
|
Side::Left => (width - col - 1, row),
|
|
};
|
|
(row, col)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
|
|
struct Edge {
|
|
len: u32,
|
|
mask: u32,
|
|
}
|
|
|
|
impl Edge {
|
|
fn new<I>(bits: I) -> Self
|
|
where
|
|
I: IntoIterator<Item=bool>,
|
|
{
|
|
let (len, mask) = bits.into_iter().fold((0u32, 0u32), |(len, mask), bit| {
|
|
(len+1, (mask << 1) + if bit { 1 } else { 0 })
|
|
});
|
|
Edge { len, mask }
|
|
}
|
|
|
|
fn norm_dir(self) -> Self {
|
|
let rev_mask = self.mask.reverse_bits() >> (32 - self.len);
|
|
if self.mask < rev_mask {
|
|
Edge { len: self.len, mask: self.mask }
|
|
} else {
|
|
Edge { len: self.len, mask: rev_mask }
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for Edge {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
use std::fmt::Write;
|
|
f.write_char('"')?;
|
|
for ndx in 0..self.len {
|
|
let v = 0 != self.mask & (1 << ndx);
|
|
f.write_char(if v { '#' } else { '.' })?;
|
|
}
|
|
f.write_char('"')
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
struct Mask {
|
|
lines: [[bool; 20]; 3],
|
|
}
|
|
|
|
impl Mask {
|
|
const EMPTY: Mask = Mask { lines: [[false; 20]; 3] };
|
|
const DEFAULT: Mask = {
|
|
let mut lines = Self::EMPTY.lines;
|
|
lines[0][18] = true;
|
|
lines[1][0] = true;
|
|
lines[1][5] = true;
|
|
lines[1][6] = true;
|
|
lines[1][11] = true;
|
|
lines[1][12] = true;
|
|
lines[1][17] = true;
|
|
lines[1][18] = true;
|
|
lines[1][19] = true;
|
|
lines[2][1] = true;
|
|
lines[2][4] = true;
|
|
lines[2][7] = true;
|
|
lines[2][10] = true;
|
|
lines[2][13] = true;
|
|
lines[2][16] = true;
|
|
Self { lines }
|
|
};
|
|
|
|
fn _flip_y(self) -> Self {
|
|
let [a,b,c] = self.lines;
|
|
Self { lines: [c, b, a] }
|
|
}
|
|
|
|
fn _flip_x(self) -> Self {
|
|
let [mut a, mut b, mut c] = self.lines;
|
|
a.reverse();
|
|
b.reverse();
|
|
c.reverse();
|
|
Self { lines: [a, b, c] }
|
|
}
|
|
|
|
fn all_masks() -> Vec<Mask> {
|
|
let def = Self::DEFAULT;
|
|
let flip_x = def._flip_x();
|
|
vec![
|
|
def,
|
|
def._flip_y(),
|
|
flip_x,
|
|
flip_x._flip_y(),
|
|
]
|
|
}
|
|
|
|
fn matches_horizontal(&self, tile: &Tile, row: usize, col: usize) -> bool {
|
|
if row + 3 > tile.width || col + 20 > tile.width {
|
|
return false;
|
|
}
|
|
for y in 0..3 {
|
|
for x in 0..20 {
|
|
if self.lines[y][x] && !tile[(row+y, col+x)] {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
fn mark_horizontal(&self, tile: &mut Tile, row: usize, col: usize) {
|
|
for y in 0..3 {
|
|
for x in 0..20 {
|
|
if self.lines[y][x] {
|
|
tile[(row+y, col+x)] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn matches_vertical(&self, tile: &Tile, row: usize, col: usize) -> bool {
|
|
if row + 20 > tile.width || col + 3 > tile.width {
|
|
return false;
|
|
}
|
|
for y in 0..3 {
|
|
for x in 0..20 {
|
|
if self.lines[y][x] && !tile[(row+x, col+y)] {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
fn mark_vertical(&self, tile: &mut Tile, row: usize, col: usize) {
|
|
for y in 0..3 {
|
|
for x in 0..20 {
|
|
if self.lines[y][x] {
|
|
tile[(row+x, col+y)] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for Mask {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
use std::fmt::Write;
|
|
f.write_char('\n')?;
|
|
for y in 0..3 {
|
|
for x in 0..20 {
|
|
f.write_char(if self.lines[y][x] { '#' } else { '.' })?;
|
|
}
|
|
f.write_char('\n')?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
|
struct Tile {
|
|
width: usize,
|
|
data: Vec<bool>,
|
|
}
|
|
|
|
impl Tile {
|
|
fn parse(block: &str) -> (u32, Self) {
|
|
let head_split = block.find(":\n").unwrap();
|
|
let id = block[..head_split].strip_prefix("Tile ").unwrap().parse::<u32>().unwrap();
|
|
let block = &block[head_split+2..];
|
|
let first_line = block.lines().next().unwrap();
|
|
let width = first_line.len();
|
|
let data = block.lines()
|
|
.map(|l| {
|
|
assert_eq!(l.len(), width);
|
|
l.chars().map(|c| c == '#')
|
|
})
|
|
.flatten().collect::<Vec<_>>();
|
|
assert_eq!(data.len(), width * width);
|
|
|
|
(id, Self { width, data })
|
|
}
|
|
|
|
fn edge(&self, side: Side) -> Edge {
|
|
match side {
|
|
Side::Top => Edge::new(self.data[..self.width].iter().copied()),
|
|
Side::Bottom => Edge::new(self.data[self.width*(self.width-1)..].iter().copied()),
|
|
Side::Left => Edge::new((0..self.width).map(|n| self.data[n*self.width])),
|
|
Side::Right => Edge::new((0..self.width).map(|n| self.data[(n+1)*self.width-1])),
|
|
}
|
|
}
|
|
|
|
// rotate/flip given previous sides to top and left
|
|
fn rotate(&self, top: Side, left: Side) -> Self {
|
|
let flip = match (top, left) {
|
|
(Side::Top, Side::Left) => false,
|
|
(Side::Top, Side::Right) => true,
|
|
(Side::Right, Side::Top) => false,
|
|
(Side::Right, Side::Bottom) => true,
|
|
(Side::Bottom, Side::Right) => false,
|
|
(Side::Bottom, Side::Left) => true,
|
|
(Side::Left, Side::Bottom) => false,
|
|
(Side::Left, Side::Top) => true,
|
|
_ => panic!("invalid rotation: {:?} -> Top and {:?} -> Left", top, left),
|
|
};
|
|
let data = (0..self.width).map(|row| {
|
|
(0..self.width).map(move |col| {
|
|
let (row, col) = top.transform(self.width, flip, row, col);
|
|
self[(row, col)]
|
|
})
|
|
}).flatten().collect();
|
|
Self { width: self.width, data }
|
|
}
|
|
|
|
fn find_monster(&self) -> Tile {
|
|
let mut marked = Self { width: self.width, data: self.data.iter().map(|_| false).collect() };
|
|
let patterns = Mask::all_masks();
|
|
for row in 0..self.width {
|
|
for col in 0..self.width {
|
|
for pattern in &patterns {
|
|
if pattern.matches_horizontal(self, row, col) {
|
|
pattern.mark_horizontal(&mut marked, row, col);
|
|
}
|
|
if pattern.matches_vertical(self, row, col) {
|
|
pattern.mark_vertical(&mut marked, row, col);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
marked
|
|
}
|
|
}
|
|
|
|
impl std::ops::Index<(usize, usize)> for Tile {
|
|
type Output = bool;
|
|
|
|
fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
|
|
assert!(col <= self.width);
|
|
&self.data[row * self.width + col]
|
|
}
|
|
}
|
|
|
|
impl std::ops::IndexMut<(usize, usize)> for Tile {
|
|
fn index_mut(&mut self, (row, col): (usize, usize)) -> &mut Self::Output {
|
|
assert!(col <= self.width);
|
|
&mut self.data[row * self.width + col]
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for Tile {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
use std::fmt::Write;
|
|
f.write_char('\n')?;
|
|
for y in 0..self.width {
|
|
for x in 0..self.width {
|
|
f.write_char(if self[(y, x)] { '#' } else { '.' })?;
|
|
}
|
|
f.write_char('\n')?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
struct TileDetailed {
|
|
tile: Tile,
|
|
edge_to_side: HashMap<Edge, Side>,
|
|
side_to_edge: HashMap<Side, Edge>,
|
|
}
|
|
|
|
impl TileDetailed {
|
|
fn edge(&self, side: Side) -> Edge {
|
|
*self.side_to_edge.get(&side).unwrap()
|
|
}
|
|
|
|
fn parse(block: &str) -> (u32, Self) {
|
|
let (id, tile) = Tile::parse(block);
|
|
|
|
let edges = vec![
|
|
(Side::Top, tile.edge(Side::Top).norm_dir()),
|
|
(Side::Right, tile.edge(Side::Right).norm_dir()),
|
|
(Side::Bottom, tile.edge(Side::Bottom).norm_dir()),
|
|
(Side::Left, tile.edge(Side::Left).norm_dir()),
|
|
];
|
|
let edge_to_side = edges.iter().map(|&(side, edge)| (edge, side)).collect();
|
|
let side_to_edge = edges.into_iter().collect();
|
|
|
|
(id, Self { tile, edge_to_side, side_to_edge })
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
struct TilePlacement {
|
|
tile: u32,
|
|
top_side: Side,
|
|
bottom_edge: Edge,
|
|
left_side: Side,
|
|
right_edge: Edge,
|
|
}
|
|
|
|
fn main() {
|
|
let tiles: HashMap<u32, TileDetailed> = INPUT.trim().split("\n\n").map(TileDetailed::parse).collect();
|
|
let mut edges: HashMap<Edge, Vec<(u32, Side)>> = HashMap::new();
|
|
for (&id, tile) in &tiles {
|
|
for (&side, &edge) in &tile.side_to_edge {
|
|
edges.entry(edge).or_default().push((id, side));
|
|
}
|
|
}
|
|
let single_edges = edges.iter().filter_map(|(_edge, id_sides)| {
|
|
if id_sides.len() == 1 {
|
|
Some(id_sides[0])
|
|
} else {
|
|
assert!(id_sides.len() == 2);
|
|
None
|
|
}
|
|
}).collect::<Vec<_>>();
|
|
let mut single_edges_per_tile = HashMap::<u32, Vec<Side>>::new();
|
|
for &(tile_id, side) in &single_edges {
|
|
single_edges_per_tile.entry(tile_id).or_default().push(side);
|
|
}
|
|
let corners = single_edges_per_tile.iter().filter_map(|(&tile_id, sides)| {
|
|
if sides.len() == 2 {
|
|
Some(tile_id)
|
|
} else {
|
|
None
|
|
}
|
|
}).collect::<Vec<_>>();
|
|
let corners_prod = corners.iter().map(|&tile_id| tile_id as u64).product::<u64>();
|
|
println!("Product of corner tile ids: {}", corners_prod);
|
|
|
|
let mut grid_tiles: [[Option<TilePlacement>; 12]; 12] = [[None; 12]; 12];
|
|
for row in 0..12 {
|
|
if row == 0 {
|
|
// orient top left tile
|
|
let tile: u32 = corners.iter().copied().min().unwrap();
|
|
let top_side: Side = *single_edges_per_tile.get(&tile).unwrap().iter().min().unwrap();
|
|
let bottom_edge = tiles.get(&tile).unwrap().edge(top_side.opposite());
|
|
let left_side: Side = *single_edges_per_tile.get(&tile).unwrap().iter().max().unwrap();
|
|
let right_edge = tiles.get(&tile).unwrap().edge(left_side.opposite());
|
|
grid_tiles[0][0] = Some(TilePlacement { tile, top_side, bottom_edge, left_side, right_edge });
|
|
} else {
|
|
// left column; look at tile above
|
|
let tile_top = grid_tiles[row-1][0].unwrap().tile;
|
|
let top_edge = grid_tiles[row-1][0].unwrap().bottom_edge;
|
|
let (tile, top_side) = edges.get(&top_edge).unwrap().iter().filter_map(|&(tile_id, side)| {
|
|
if tile_id != tile_top {
|
|
Some((tile_id, side))
|
|
} else {
|
|
None
|
|
}
|
|
}).next().unwrap();
|
|
let bottom_side = top_side.opposite();
|
|
let left_side = *single_edges_per_tile.get(&tile).unwrap().iter().filter(|&&side| side != bottom_side).next().unwrap();
|
|
assert_ne!(top_side, left_side);
|
|
assert_ne!(top_side, left_side.opposite());
|
|
let bottom_edge = tiles.get(&tile).unwrap().edge(top_side.opposite());
|
|
let right_edge = tiles.get(&tile).unwrap().edge(left_side.opposite());
|
|
grid_tiles[row][0] = Some(TilePlacement { tile, top_side, bottom_edge, left_side, right_edge });
|
|
}
|
|
// complete row
|
|
for col in 1..12 {
|
|
// look at tile to the left
|
|
let tile_left = grid_tiles[row][col-1].unwrap().tile;
|
|
let left_edge = grid_tiles[row][col-1].unwrap().right_edge;
|
|
let (tile, left_side) = edges.get(&left_edge).unwrap().iter().filter_map(|&(tile_id, side)| {
|
|
if tile_id != tile_left {
|
|
Some((tile_id, side))
|
|
} else {
|
|
None
|
|
}
|
|
}).next().unwrap();
|
|
let right_edge = tiles.get(&tile).unwrap().edge(left_side.opposite());
|
|
let top_side;
|
|
if row == 0 {
|
|
if col == 11 {
|
|
// top right corner
|
|
let right_side = left_side.opposite();
|
|
top_side = *single_edges_per_tile.get(&tile).unwrap().iter().filter(|&&side| side != right_side).next().unwrap();
|
|
} else {
|
|
top_side = *single_edges_per_tile.get(&tile).unwrap().iter().next().unwrap();
|
|
}
|
|
} else {
|
|
let tile_top = grid_tiles[row-1][col].unwrap().tile;
|
|
let top_edge = grid_tiles[row-1][col].unwrap().bottom_edge;
|
|
top_side = edges.get(&top_edge).unwrap().iter().filter_map(|&(tile_id, side)| {
|
|
if tile_id != tile_top {
|
|
assert_eq!(tile, tile_id);
|
|
Some(side)
|
|
} else {
|
|
None
|
|
}
|
|
}).next().unwrap();
|
|
}
|
|
assert_ne!(left_side, top_side);
|
|
assert_ne!(left_side, top_side.opposite());
|
|
let bottom_edge = tiles.get(&tile).unwrap().edge(top_side.opposite());
|
|
grid_tiles[row][col] = Some(TilePlacement { tile, top_side, bottom_edge, left_side, right_edge });
|
|
}
|
|
}
|
|
|
|
let mut rotated_tiles = Vec::new();
|
|
let mut picture = Tile { width: 12 * 8, data: Vec::new() };
|
|
picture.data.resize(picture.width * picture.width, false);
|
|
for row in 0..12 {
|
|
rotated_tiles.push(Vec::new());
|
|
for col in 0..12 {
|
|
let tp = grid_tiles[row][col].unwrap();
|
|
let tile = tiles.get(&tp.tile).unwrap().tile.rotate(tp.top_side, tp.left_side);
|
|
rotated_tiles[row].push(tile.clone());
|
|
assert_eq!(rotated_tiles[row][col], tile);
|
|
|
|
// check edges
|
|
if row > 0 {
|
|
assert_eq!(rotated_tiles[row-1][col].edge(Side::Bottom), tile.edge(Side::Top));
|
|
}
|
|
if col > 0 {
|
|
assert_eq!(rotated_tiles[row][col-1].edge(Side::Right), tile.edge(Side::Left));
|
|
}
|
|
|
|
// assemble full picture
|
|
for y in 0..tile.width-2 {
|
|
for x in 0..tile.width-2 {
|
|
picture[(row*8+y, col*8+x)] = tile[(y+1, x+1)];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
println!("{:?}", picture);
|
|
let monsters = picture.find_monster();
|
|
println!("{:?}", monsters);
|
|
let sea_count = picture.data.iter().filter(|&&v| v).count();
|
|
let monster_count = monsters.data.iter().filter(|&&v| v).count();
|
|
println!("Roughness: {}", sea_count - monster_count);
|
|
}
|