From 1f33cb37d283ee494447c7660aa02690108ca863 Mon Sep 17 00:00:00 2001 From: felixm Date: Sun, 27 Aug 2023 15:43:16 -0400 Subject: [PATCH] Initial version of Talos Solver --- .gitignore | 1 + .vscode/launch.json | 45 +++++ Cargo.lock | 171 ++++++++++++++++ Cargo.toml | 9 + src/main.rs | 480 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 706 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e7913b6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'talos_solver'", + "cargo": { + "args": [ + "build", + "--bin=talos_solver", + "--package=talos_solver" + ], + "filter": { + "name": "talos_solver", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'talos_solver'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=talos_solver", + "--package=talos_solver" + ], + "filter": { + "name": "talos_solver", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d6565cd --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,171 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "colored" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +dependencies = [ + "is-terminal", + "lazy_static", + "windows-sys", +] + +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + +[[package]] +name = "rustix" +version = "0.38.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bfe0f2582b4931a45d1fa608f8a8722e8b3c7ac54dd6d5f3b3212791fedef49" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "talos_solver" +version = "0.1.0" +dependencies = [ + "colored", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..61bca3d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "talos_solver" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +colored = "2.0" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..48c0ffc --- /dev/null +++ b/src/main.rs @@ -0,0 +1,480 @@ +use std::fmt; +use colored::{Color, Colorize}; + + +#[derive(Debug, PartialEq, Clone)] +struct RelCoord { + x: u32, + y: u32, +} + +#[derive(Debug, PartialEq, Clone)] +struct AbsCoord { + x: u32, + y: u32, +} + +impl AbsCoord { + fn add(&self, offset: &RelCoord) -> Self { + Self { + x: self.x + offset.x, + y: self.y + offset.y, + } + } +} + +#[derive(Debug, PartialEq, Clone)] +struct Pattern { + coords: Vec +} + +impl fmt::Display for Pattern { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Determine the bounding box for the pattern. + let max_x = self.coords.iter().map(|coord| coord.x).max().unwrap_or(0); + let max_y = self.coords.iter().map(|coord| coord.y).max().unwrap_or(0); + + // Initialize a 2D grid to hold the pattern representation. + let mut grid = vec![vec![' '; (max_x + 1) as usize]; (max_y + 1) as usize]; + + // Place the blocks of the pattern onto the grid. + for coord in &self.coords { + grid[coord.y as usize][coord.x as usize] = '#'; + } + + // Convert the 2D grid to a string. + for row in grid.iter() { + for cell in row.iter() { + write!(f, "{}", cell)?; + } + writeln!(f)?; + } + + Ok(()) + } +} + +#[derive(Debug, Clone)] +struct Piece { + name: String, + patterns: Vec +} + +impl Piece { + #[allow(dead_code)] + pub fn hero () -> Self { + let coords_vert = vec![ + RelCoord {x: 0, y: 0}, + RelCoord {x: 0, y: 1}, + RelCoord {x: 0, y: 2}, + RelCoord {x: 0, y: 3}, + ]; + let coords_hori = vec![ + RelCoord {x: 0, y: 0}, + RelCoord {x: 1, y: 0}, + RelCoord {x: 2, y: 0}, + RelCoord {x: 3, y: 0}, + ]; + Self { + name: "Hero".to_string(), + patterns: vec![ + Pattern { coords: coords_vert }, + Pattern { coords: coords_hori }, + ] + } + } + + #[allow(dead_code)] + pub fn teewee () -> Self { + let coords_up = vec![ + RelCoord {x: 1, y: 0}, + RelCoord {x: 0, y: 1}, + RelCoord {x: 1, y: 1}, + RelCoord {x: 2, y: 1}, + ]; + let coords_right = vec![ + RelCoord {x: 0, y: 0}, + RelCoord {x: 0, y: 1}, + RelCoord {x: 0, y: 2}, + RelCoord {x: 1, y: 1}, + ]; + let coords_down = vec![ + RelCoord {x: 0, y: 0}, + RelCoord {x: 1, y: 0}, + RelCoord {x: 2, y: 0}, + RelCoord {x: 1, y: 1}, + ]; + let coords_left = vec![ + RelCoord {x: 0, y: 1}, + RelCoord {x: 1, y: 0}, + RelCoord {x: 1, y: 1}, + RelCoord {x: 1, y: 2}, + ]; + Self { + name: "Teewee".to_string(), + patterns: vec![ + Pattern { coords: coords_up }, + Pattern { coords: coords_right }, + Pattern { coords: coords_down }, + Pattern { coords: coords_left }, + ] + } + } + + #[allow(dead_code)] + pub fn blue_ricky() -> Self { + let coords_upright = vec![ + RelCoord { x: 1, y: 0 }, + RelCoord { x: 1, y: 1 }, + RelCoord { x: 1, y: 2 }, + RelCoord { x: 0, y: 2 }, + ]; + let coords_right = vec![ + RelCoord { x: 0, y: 0 }, + RelCoord { x: 0, y: 1 }, + RelCoord { x: 1, y: 1 }, + RelCoord { x: 2, y: 1 }, + ]; + let coords_downright = vec![ + RelCoord { x: 0, y: 0 }, + RelCoord { x: 0, y: 1 }, + RelCoord { x: 0, y: 2 }, + RelCoord { x: 1, y: 0 }, + ]; + let coords_left = vec![ + RelCoord { x: 0, y: 0 }, + RelCoord { x: 1, y: 0 }, + RelCoord { x: 2, y: 0 }, + RelCoord { x: 2, y: 1 }, + ]; + Self { + name: "Blue Ricky".to_string(), + patterns: vec![ + Pattern { coords: coords_upright }, + Pattern { coords: coords_right }, + Pattern { coords: coords_downright }, + Pattern { coords: coords_left }, + ] + } + } + + #[allow(dead_code)] + pub fn orange_ricky() -> Self { + let coords_upright = vec![ + RelCoord { x: 0, y: 0 }, + RelCoord { x: 0, y: 1 }, + RelCoord { x: 0, y: 2 }, + RelCoord { x: 1, y: 2 }, + ]; + let coords_right = vec![ + RelCoord { x: 0, y: 0 }, + RelCoord { x: 1, y: 0 }, + RelCoord { x: 2, y: 0 }, + RelCoord { x: 0, y: 1 }, + ]; + let coords_downright = vec![ + RelCoord { x: 0, y: 0 }, + RelCoord { x: 1, y: 0 }, + RelCoord { x: 1, y: 1 }, + RelCoord { x: 1, y: 2 }, + ]; + let coords_left = vec![ + RelCoord { x: 2, y: 0 }, + RelCoord { x: 0, y: 1 }, + RelCoord { x: 1, y: 1 }, + RelCoord { x: 2, y: 1 }, + ]; + Self { + name: "Orange Ricky".to_string(), + patterns: vec![ + Pattern { coords: coords_upright }, + Pattern { coords: coords_right }, + Pattern { coords: coords_downright }, + Pattern { coords: coords_left }, + ] + } + } + + #[allow(dead_code)] + pub fn smashboy() -> Self { + let coords = vec![ + RelCoord { x: 0, y: 0 }, + RelCoord { x: 0, y: 1 }, + RelCoord { x: 1, y: 0 }, + RelCoord { x: 1, y: 1 }, + ]; + Self { + name: "Smashboy".to_string(), + patterns: vec![ + Pattern { coords }, + ] + } + } + + #[allow(dead_code)] + pub fn cleveland_z () -> Self { + let flat = vec![ + RelCoord {x: 0, y: 0}, + RelCoord {x: 1, y: 0}, + RelCoord {x: 1, y: 1}, + RelCoord {x: 2, y: 1}, + ]; + let upright = vec![ + RelCoord {x: 1, y: 0}, + RelCoord {x: 1, y: 1}, + RelCoord {x: 0, y: 1}, + RelCoord {x: 0, y: 2}, + ]; + Self { + name: "Cleveland Z".to_string(), + patterns: vec![ + Pattern { coords: flat }, + Pattern { coords: upright }, + ] + } + } + + #[allow(dead_code)] + pub fn rhode_island_z () -> Self { + let flat = vec![ + RelCoord {x: 0, y: 1}, + RelCoord {x: 1, y: 0}, + RelCoord {x: 1, y: 1}, + RelCoord {x: 2, y: 0}, + ]; + let upright = vec![ + RelCoord {x: 0, y: 0}, + RelCoord {x: 0, y: 1}, + RelCoord {x: 1, y: 1}, + RelCoord {x: 1, y: 2}, + ]; + Self { + name: "Rhode Island Z".to_string(), + patterns: vec![ + Pattern { coords: flat }, + Pattern { coords: upright }, + ] + } + } + + +} + + +impl fmt::Display for Piece { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:\n\n", self.name.red())?; + for pattern in self.patterns.iter() { + write!(f, "{}\n", pattern)?; + } + Ok(()) + } +} + +/// A Field is a list of available (empty) coordinates. +#[derive(Debug, Clone)] +struct Field { + width: u32, + height: u32, + empty_coords: Vec, + patterns: Vec<(AbsCoord, Pattern)>, +} + +impl Field { + pub fn new(width: u32, height: u32) -> Self { + let mut empty_coords = Vec::new(); + for x in 0..width { + for y in 0..height { + empty_coords.push(AbsCoord { x: x, y: y }); + } + } + Self { width, height, empty_coords, patterns: Vec::new() } + } + + fn add_pattern(&mut self, location: &AbsCoord, pattern: &Pattern) -> bool { + // Convert the pattern's relative coordinates to absolute coordinates + let abs_coords: Vec<_> = pattern.coords.iter().map(|rel_coord| location.add(rel_coord)).collect(); + if abs_coords.iter().all(|abs_coord| self.empty_coords.contains(abs_coord)) { + self.empty_coords.retain(|empty_coord| !abs_coords.contains(empty_coord)); + self.patterns.push((location.clone(), pattern.clone())); + true + } else { + false + } + } + fn fits(&self, location: &AbsCoord, pattern: &Pattern) -> bool { + let abs_coords: Vec<_> = pattern.coords.iter().map(|rel_coord| location.add(rel_coord)).collect(); + if abs_coords.iter().all(|abs_coord| self.empty_coords.contains(abs_coord)) { + true + } else { + false + } + } +} + +impl fmt::Display for Field { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut grid = vec![vec![' '.to_string().color(Color::White); self.width as usize]; self.height as usize]; + + let colors = vec![Color::Red, Color::Green, Color::Yellow, Color::Blue, Color::Magenta, Color::Cyan, Color::White, Color::Black]; + for (index, (location, pattern)) in self.patterns.iter().enumerate() { + let color = colors[index % colors.len()]; // This makes sure we loop back to the start if there are more patterns than colors. + for rel_coord in &pattern.coords { + let abs_coord = location.add(rel_coord); + grid[abs_coord.y as usize][abs_coord.x as usize] = '#'.to_string().color(color); + } + } + + for row in grid.iter() { + for cell in row.iter() { + write!(f, "{}", cell)?; + } + writeln!(f)?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::AbsCoord; + use crate::Field; + use crate::Piece; + use crate::solve; + + #[test] + fn test_add_pattern() { + let mut field = Field::new(3, 3); + assert_eq!(field.add_pattern(&AbsCoord {x: 0, y: 0 }, &Piece::blue_ricky().patterns[0]), true); + assert_eq!(field.add_pattern(&AbsCoord {x: 0, y: 0 }, &Piece::blue_ricky().patterns[1]), false); + } + + #[test] + fn test_solve_no_pieces() { + let field = Field::new(5, 5); + let pieces = vec![]; + let result = solve(field, pieces); + assert!(result.is_none()); + } + + #[test] + fn test_solve_no_space() { + let field = Field::new(0, 0); + let pieces = vec![ + Piece::smashboy(), + ]; + let result = solve(field, pieces); + assert!(result.is_none()); + } + + #[test] + fn test_solve_base_case() { + let field = Field::new(0, 0); + let pieces = vec![]; + let result = solve(field, pieces); + assert!(result.is_some()); + } + + #[test] + fn test_solve_four_smashboys() { + let field = Field::new(4, 4); + let pieces = vec![ + Piece::smashboy(), + Piece::smashboy(), + Piece::smashboy(), + Piece::smashboy(), + ]; + let result = solve(field, pieces); + assert!(result.is_some()); + } + + #[test] + fn test_two_two_one() { + let field = Field::new(4, 5); + let pieces = vec![ + Piece::smashboy(), + Piece::smashboy(), + Piece::blue_ricky(), + Piece::blue_ricky(), + Piece::hero(), + ]; + let result = solve(field, pieces); + assert!(result.is_some()); + } + + #[test] + fn test_two_two_one_fail() { + let field = Field::new(4, 5); + let pieces = vec![ + Piece::smashboy(), + Piece::smashboy(), + Piece::blue_ricky(), + Piece::orange_ricky(), + Piece::hero(), + ]; + let result = solve(field, pieces); + assert!(result.is_some()); + } +} + +fn solve(field: Field, pieces: Vec) -> Option { + if field.empty_coords.is_empty() { + if pieces.is_empty() { + return Some(field); + } else { + return None; + } + } else if pieces.is_empty() { + return None; + } + + for empty_coord in &field.empty_coords.as_slice()[0..2] { + for (piece_index, piece) in pieces.iter().enumerate() { + for pattern in &piece.patterns { + if field.fits(&empty_coord, &pattern) { + let mut new_field: Field = field.clone(); + let mut new_pieces = pieces.clone(); + new_pieces.remove(piece_index); + new_field.add_pattern(&empty_coord, &pattern); + match solve(new_field, new_pieces) { + Some(field) => return Some(field), + None => {}, + } + } + } + } + } + None +} + +fn main() { + let field = Field::new(6, 6); + let pieces = vec![ + Piece::cleveland_z(), // -_ + Piece::cleveland_z(), // -_ + Piece::cleveland_z(), // -_ + Piece::orange_ricky(), // ___| + Piece::orange_ricky(), // ___| + Piece::blue_ricky(), // |___ + Piece::blue_ricky(), // |___ + Piece::teewee(), + Piece::teewee(), + // Piece::cleveland_z(), // -_ + // Piece::rhode_island_z(), // _- + // Piece::teewee(), + // Piece::hero(), + // Piece::orange_ricky(), // ___| + // Piece::blue_ricky(), // |___ + // Piece::smashboy(), + ]; + let result = solve(field, pieces); + match result { + Some(field) => println!("{}", field), + None => println!("No solution",) + }; + + // for piece in &pieces { + // print!("{}", piece); + // } +}