diff --git a/90_Tower/rust/Cargo.toml b/90_Tower/rust/Cargo.toml new file mode 100644 index 00000000..1ec69633 --- /dev/null +++ b/90_Tower/rust/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rust" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/90_Tower/rust/src/disk.rs b/90_Tower/rust/src/disk.rs new file mode 100644 index 00000000..9d5b4b6d --- /dev/null +++ b/90_Tower/rust/src/disk.rs @@ -0,0 +1,28 @@ +pub struct Disk { + pub size: u8, +} + +impl Disk { + pub fn new(size: u8) -> Self { + Disk { size } + } + + pub fn draw(&self) { + let draw_space = || { + let space_amount = (15 - self.size) / 2; + + if space_amount > 0 { + for _ in 0..space_amount { + print!(" "); + } + } + }; + + draw_space(); + for _ in 0..self.size { + print!("*"); + } + draw_space(); + print!(" "); + } +} diff --git a/90_Tower/rust/src/game.rs b/90_Tower/rust/src/game.rs new file mode 100644 index 00000000..50ef0fe1 --- /dev/null +++ b/90_Tower/rust/src/game.rs @@ -0,0 +1,138 @@ +use crate::{ + disk::Disk, + needle::Needle, + util::{self, prompt, PromptResult}, +}; + +pub struct Game { + pub needles: Vec, + disk_count: u8, + moves: usize, +} + +impl Game { + pub fn new() -> Self { + let mut needles = Vec::new(); + let disk_count = util::get_disk_count(); + + for i in 1..=3 { + let disks = match i { + 1 => { + let mut disks = Vec::new(); + + let mut half_size = 7; + for _ in (1..=disk_count).rev() { + disks.push(Disk::new(half_size * 2 + 1)); + half_size -= 1; + } + + disks + } + 2 | 3 => Vec::new(), + _ => panic!("THERE MUST BE EXACTLY THREE NEEDLES!"), + }; + + needles.push(Needle { disks, number: i }); + } + + Game { + needles, + disk_count, + moves: 0, + } + } + + pub fn update(&mut self) -> bool { + self.draw(); + + loop { + let (disk_index, from_needle_index) = self.get_disk_to_move(); + let to_needle_index = self.ask_which_needle(); + + if from_needle_index == to_needle_index { + println!("DISK IS ALREADY AT THAT NEEDLE!"); + break; + } + + let to_needle = &self.needles[to_needle_index]; + + if to_needle.disks.len() == 0 + || to_needle.disks[0].size > self.needles[from_needle_index].disks[disk_index].size + { + self.move_disk(disk_index, from_needle_index, to_needle_index); + break; + } else { + println!("CAN'T PLACE ON A SMALLER DISK!"); + } + } + + if self.needles[2].disks.len() == self.disk_count as usize { + self.draw(); + println!("CONGRATULATIONS!!"); + println!("YOU HAVE PERFORMED THE TASK IN {} MOVES.", self.moves); + return true; + } + + false + } + + pub fn draw(&self) { + println!(""); + for r in (1..=7).rev() { + for n in &self.needles { + n.draw(r) + } + println!(""); + } + println!(""); + } + + fn get_disk_to_move(&self) -> (usize, usize) { + loop { + if let PromptResult::Number(n) = prompt(true, "WHICH DISK WOULD YOU LIKE TO MOVE?") { + let smallest_disk = 15 - ((self.disk_count - 1) * 2); + + if n < smallest_disk as i32 || n > 15 || (n % 2) == 0 { + println!("PLEASE ENTER A VALID DISK!") + } else { + for (n_i, needle) in self.needles.iter().enumerate() { + if let Some((i, _)) = needle + .disks + .iter() + .enumerate() + .find(|(_, disk)| disk.size == n as u8) + { + if i == (needle.disks.len() - 1) { + return (i, n_i); + } + + println!("THAT DISK IS BELOW ANOTHER ONE. MAKE ANOTHER CHOICE."); + } + } + } + } + } + } + + fn ask_which_needle(&self) -> usize { + loop { + if let PromptResult::Number(n) = prompt(true, "PLACE DISK ON WHICH NEEDLE?") { + if n <= 0 || n > 3 { + println!("PLEASE ENTER A VALID NEEDLE."); + } else { + return (n - 1) as usize; + } + } + } + } + + fn move_disk(&mut self, disk: usize, from: usize, to: usize) { + let from = &mut self.needles[from]; + let size = from.disks[disk].size; + + from.disks.remove(disk); + self.needles[to].add(size); + + self.moves += 1; + } +} diff --git a/90_Tower/rust/src/main.rs b/90_Tower/rust/src/main.rs new file mode 100644 index 00000000..5ec26079 --- /dev/null +++ b/90_Tower/rust/src/main.rs @@ -0,0 +1,50 @@ +use game::Game; +use util::PromptResult; + +mod disk; +mod game; +mod needle; +mod util; + +fn main() { + println!("\n\n\t\tTOWERS"); + println!("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n"); + + println!("TOWERS OF HANOI PUZZLE\n"); + + println!("YOU MUST TRANSFER THE DISKS FROM THE LEFT TO THE RIGHT"); + println!("TOWER, ON AT A TIME, NEVER PUTTING A LARGER DISK ON A"); + println!("SMALLER DISK.\n"); + + let mut quit = false; + + while !quit { + let mut game = Game::new(); + + println!(""); + println!( + r#"IN THIS PROGRAM, WE SHALL REFER TO DISKS BY NUMERICAL CODE. +3 WILL REPRESENT THE SMALLEST DISK, 5 THE NEXT SIZE, +7 THE NEXT, AND SO ON, UP TO 15. IF YOU DO THE PUZZLE WITH +2 DISKS, THEIR CODE NAMES WOULD BE 13 AND 15. WITH 3 DISKS +THE CODE NAMES WOULD BE 11, 13 AND 15, ETC. THE NEEDLES +ARE NUMBERED FROM LEFT TO RIGHT, 1 TO 3. WE WILL +START WITH THE DISKS ON NEEDLE 1, AND ATTEMPT TO MOVE THEM +TO NEEDLE 3. + +GOOD LUCK!"# + ); + + loop { + if game.update() { + break; + } + } + + if let PromptResult::YesNo(r) = util::prompt(false, "TRY AGAIN (YES OR NO)?") { + quit = !r; + } + } + + println!("THANKS FOR THE GAME!"); +} diff --git a/90_Tower/rust/src/needle.rs b/90_Tower/rust/src/needle.rs new file mode 100644 index 00000000..b2286eaf --- /dev/null +++ b/90_Tower/rust/src/needle.rs @@ -0,0 +1,26 @@ +use crate::disk::Disk; + +pub struct Needle { + pub disks: Vec, + pub number: u8, +} + +impl Needle { + pub fn draw(&self, row: u8) { + let row = row as usize; + + if self.disks.len() >= row { + self.disks[row - 1].draw(); + } else { + let offset = " "; + + print!("{offset}"); + print!("*"); + print!("{offset} "); + } + } + + pub fn add(&mut self, size: u8) { + self.disks.push(Disk { size }); + } +} diff --git a/90_Tower/rust/src/util.rs b/90_Tower/rust/src/util.rs new file mode 100644 index 00000000..9feacf94 --- /dev/null +++ b/90_Tower/rust/src/util.rs @@ -0,0 +1,51 @@ +use std::io; + +pub enum PromptResult { + Number(i32), + YesNo(bool), +} + +pub fn prompt(numeric: bool, msg: &str) -> PromptResult { + use PromptResult::*; + loop { + println!("{}", msg); + + let mut input = String::new(); + + io::stdin() + .read_line(&mut input) + .expect("Failed to read line."); + + let input = input.trim().to_string(); + + if numeric { + if let Ok(n) = input.parse::() { + return Number(n); + } + + println!("PLEASE ENTER A NUMBER.") + } else { + match input.to_uppercase().as_str() { + "YES" | "Y" => return YesNo(true), + "NO" | "N" => return YesNo(false), + _ => println!("PLEASE ENTER (Y)ES OR (N)O."), + } + } + } +} + +pub fn get_disk_count() -> u8 { + loop { + if let PromptResult::Number(n) = + prompt(true, "HOW MANY DISKS DO YOU WANT TO MOVE (7 IS MAX)?") + { + if n <= 2 { + println!("THERE MUST BE AT LEAST 3 DISKS!") + } else if n > 7 { + println!("THERE CAN'T BE MORE THAN 7 DISKS!") + } else { + return n as u8; + } + } + } +}