Initial version of Talos Solver

This commit is contained in:
2023-08-27 15:43:16 -04:00
commit 1f33cb37d2
5 changed files with 706 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

45
.vscode/launch.json vendored Normal file
View File

@@ -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}"
}
]
}

171
Cargo.lock generated Normal file
View File

@@ -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"

9
Cargo.toml Normal file
View File

@@ -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"

480
src/main.rs Normal file
View File

@@ -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<RelCoord>
}
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<Pattern>
}
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<AbsCoord>,
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<Piece>) -> Option<Field> {
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);
// }
}