commit 80d15f47a74586fa96f2bd9ae5b3e70535b4ec1a Author: FliegendeWurst <2012gdwu@web.de> Date: Sat Dec 10 16:43:23 2016 +0100 Add Aufgabe 3 diff --git a/Aufgabe3/Aufgabe3_windows64bit.exe b/Aufgabe3/Aufgabe3_windows64bit.exe new file mode 100755 index 0000000..43237c3 Binary files /dev/null and b/Aufgabe3/Aufgabe3_windows64bit.exe differ diff --git a/Aufgabe3/Cargo.toml b/Aufgabe3/Cargo.toml new file mode 100644 index 0000000..78edb5d --- /dev/null +++ b/Aufgabe3/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "Aufgabe3" +version = "0.1.0" +authors = ["Arne "] + +[dependencies] +colored = "1.3" diff --git a/Aufgabe3/lösung.txt b/Aufgabe3/lösung.txt new file mode 100644 index 0000000..b6efeb6 --- /dev/null +++ b/Aufgabe3/lösung.txt @@ -0,0 +1,7 @@ +[GUS, GUS, GUS, GUS, US, GUS] +[US, US, US, US, GUS, US, US, GUS, GUS, GUS, GUS, GUS, GUS, GUS, US, US, US, US, US, US, GUS, US] +[GUS, US, US, US, US, GUS, GUS, GUS, US, US, US, GUS, GUS, GUS, GUS, GUS, GUS, GUS, US, US, GUS, GUS, US, US, US, US, US, US, US, GUS, US, US, US, US, US, US, US, US, US, US, US, GUS, GUS, GUS, US, US, US, US, US, GUS, GUS, GUS, GUS, GUS, US, US, US, US, GUS, GUS, US, US, US, US, US, US, US, GUS, US, GUS, GUS, GUS, GUS, GUS, GUS, GUS, GUS, GUS, GUS, GUS, US, GUS, GUS, US, US, US, US, US, GUS, US] +bzw. +[↺, ↺, ↺, ↺, ↻, ↺] +[↻, ↻, ↻, ↻, ↺, ↻, ↻, ↺, ↺, ↺, ↺, ↺, ↺, ↺, ↻, ↻, ↻, ↻, ↻, ↻, ↺, ↻] +[↺, ↻, ↻, ↻, ↻, ↺, ↺, ↺, ↻, ↻, ↻, ↺, ↺, ↺, ↺, ↺, ↺, ↺, ↻, ↻, ↺, ↺, ↻, ↻, ↻, ↻, ↻, ↻, ↻, ↺, ↻, ↻, ↻, ↻, ↻, ↻, ↻, ↻, ↻, ↻, ↻, ↺, ↺, ↺, ↻, ↻, ↻, ↻, ↻, ↺, ↺, ↺, ↺, ↺, ↻, ↻, ↻, ↻, ↺, ↺, ↻, ↻, ↻, ↻, ↻, ↻, ↻, ↺, ↻, ↺, ↺, ↺, ↺, ↺, ↺, ↺, ↺, ↺, ↺, ↺, ↻, ↺, ↺, ↻, ↻, ↻, ↻, ↻, ↺, ↻] \ No newline at end of file diff --git a/Aufgabe3/rotation10_n.txt b/Aufgabe3/rotation10_n.txt new file mode 100644 index 0000000..4b297d7 --- /dev/null +++ b/Aufgabe3/rotation10_n.txt @@ -0,0 +1,9 @@ +7 +####### +# 333# + 2# +#1 2# +#1 2# +#100 2# +####### +Nicht nur Ausgänge unten sind erlaubt! diff --git a/Aufgabe3/rotation10_n.txt.bild.png b/Aufgabe3/rotation10_n.txt.bild.png new file mode 100644 index 0000000..b2391d0 Binary files /dev/null and b/Aufgabe3/rotation10_n.txt.bild.png differ diff --git a/Aufgabe3/rotation10_n_lösung.txt b/Aufgabe3/rotation10_n_lösung.txt new file mode 100644 index 0000000..91eee6d --- /dev/null +++ b/Aufgabe3/rotation10_n_lösung.txt @@ -0,0 +1,7 @@ +-3 -3 -3 -3 -3 -3 -3 +-3 -2 -2 -2 -2 -2 -3 +-3 -2 02 02 02 02 -3 +-3 -2 -2 -2 -2 00 -3 +-3 -2 03 -2 -2 00 -3 +-3 -2 03 01 01 01 -3 +-3 -3 03 -3 -3 -3 -3 \ No newline at end of file diff --git a/Aufgabe3/rotation10_n_lösung.txt.bild.png b/Aufgabe3/rotation10_n_lösung.txt.bild.png new file mode 100644 index 0000000..936f82c Binary files /dev/null and b/Aufgabe3/rotation10_n_lösung.txt.bild.png differ diff --git a/Aufgabe3/rotation11_n.txt b/Aufgabe3/rotation11_n.txt new file mode 100644 index 0000000..4944a11 --- /dev/null +++ b/Aufgabe3/rotation11_n.txt @@ -0,0 +1,11 @@ +7 +####### +# 222# + +#3 1# +#3 1# +#300 1# +####### +Mehrere Ausgänge sind möglich! +Hier würde Stab 2 aufgrund der Schwerkraft +ohne Drehung runterfallen. diff --git a/Aufgabe3/rotation11_n.txt.bild.png b/Aufgabe3/rotation11_n.txt.bild.png new file mode 100644 index 0000000..dced590 Binary files /dev/null and b/Aufgabe3/rotation11_n.txt.bild.png differ diff --git a/Aufgabe3/rotation11_n_lösung.txt b/Aufgabe3/rotation11_n_lösung.txt new file mode 100644 index 0000000..b91682e --- /dev/null +++ b/Aufgabe3/rotation11_n_lösung.txt @@ -0,0 +1,7 @@ +-3 -3 -3 -3 -1 -3 -3 +-3 -2 -2 -2 -2 -2 -3 +-3 03 03 03 -2 -2 -3 +-3 00 -2 -2 -2 -2 -3 +-3 00 -2 -2 02 -2 -3 +-3 01 01 01 02 -2 -3 +-3 -3 -3 -3 02 -3 -3 \ No newline at end of file diff --git a/Aufgabe3/rotation11_n_lösung.txt.bild.png b/Aufgabe3/rotation11_n_lösung.txt.bild.png new file mode 100644 index 0000000..663a031 Binary files /dev/null and b/Aufgabe3/rotation11_n_lösung.txt.bild.png differ diff --git a/Aufgabe3/rotation12_n.txt b/Aufgabe3/rotation12_n.txt new file mode 100644 index 0000000..c602418 --- /dev/null +++ b/Aufgabe3/rotation12_n.txt @@ -0,0 +1,12 @@ +7 +####### +#89555# +#6 7# +#4 3# +#41 3# +#42203# +####### +Kein Ausgang ist auch möglich! +Hier mit vielen Würfeln, um mein Programm zu quälen... +in ca. 6s gibt mein Programm aus, +dass es keine Lösung finden konnte diff --git a/Aufgabe3/rotation12_n.txt.bild.png b/Aufgabe3/rotation12_n.txt.bild.png new file mode 100644 index 0000000..38e0f38 Binary files /dev/null and b/Aufgabe3/rotation12_n.txt.bild.png differ diff --git a/Aufgabe3/rotation13_n.txt b/Aufgabe3/rotation13_n.txt new file mode 100644 index 0000000..945124e --- /dev/null +++ b/Aufgabe3/rotation13_n.txt @@ -0,0 +1,11 @@ +7 +####### +# 55# +# 44# +# 33# +# 22# +#0 11# +## ### +Größere Ausgänge sind auch möglich! +Allerdings ist nicht gewährleistet, dass der gesamte +Ausgang genutzt wird. diff --git a/Aufgabe3/rotation13_n.txt.bild.png b/Aufgabe3/rotation13_n.txt.bild.png new file mode 100644 index 0000000..41bbc36 Binary files /dev/null and b/Aufgabe3/rotation13_n.txt.bild.png differ diff --git a/Aufgabe3/rotation13_n_lösung.txt b/Aufgabe3/rotation13_n_lösung.txt new file mode 100644 index 0000000..1124fa7 --- /dev/null +++ b/Aufgabe3/rotation13_n_lösung.txt @@ -0,0 +1,7 @@ +-3 -3 -3 -3 -3 -3 -3 +-3 -2 -2 -2 05 05 -3 +-3 -2 -2 -2 04 04 -3 +-3 -2 -2 -2 03 03 -3 +-3 -2 -2 -2 02 02 -3 +-3 -2 -2 -2 01 01 -3 +-3 -3 -1 00 -3 -3 -3 \ No newline at end of file diff --git a/Aufgabe3/rotation13_n_lösung.txt.bild.png b/Aufgabe3/rotation13_n_lösung.txt.bild.png new file mode 100644 index 0000000..fe89c5f Binary files /dev/null and b/Aufgabe3/rotation13_n_lösung.txt.bild.png differ diff --git a/Aufgabe3/rotation14_c.txt b/Aufgabe3/rotation14_c.txt new file mode 100644 index 0000000..b3498db --- /dev/null +++ b/Aufgabe3/rotation14_c.txt @@ -0,0 +1,5 @@ +4 +#### +# 0# +#0 # +#### diff --git a/Aufgabe3/rotation14_c.txt.bild.png b/Aufgabe3/rotation14_c.txt.bild.png new file mode 100644 index 0000000..856f72c Binary files /dev/null and b/Aufgabe3/rotation14_c.txt.bild.png differ diff --git a/Aufgabe3/rotation15_c.txt b/Aufgabe3/rotation15_c.txt new file mode 100644 index 0000000..c8c2978 --- /dev/null +++ b/Aufgabe3/rotation15_c.txt @@ -0,0 +1,5 @@ +4 +### +# # +# 0# +#### diff --git a/Aufgabe3/rotation15_c.txt.bild.png b/Aufgabe3/rotation15_c.txt.bild.png new file mode 100644 index 0000000..279c5f0 Binary files /dev/null and b/Aufgabe3/rotation15_c.txt.bild.png differ diff --git a/Aufgabe3/rotation1_03.txt b/Aufgabe3/rotation1_03.txt new file mode 100644 index 0000000..cf91efe --- /dev/null +++ b/Aufgabe3/rotation1_03.txt @@ -0,0 +1,9 @@ +8 +######## +# 0# +# 0# +#112222# +#33 4# +#55 4# +#666 4# +### #### diff --git a/Aufgabe3/rotation1_03.txt.bild.png b/Aufgabe3/rotation1_03.txt.bild.png new file mode 100644 index 0000000..4537792 Binary files /dev/null and b/Aufgabe3/rotation1_03.txt.bild.png differ diff --git a/Aufgabe3/rotation1_03_lösung.txt b/Aufgabe3/rotation1_03_lösung.txt new file mode 100644 index 0000000..904d92a --- /dev/null +++ b/Aufgabe3/rotation1_03_lösung.txt @@ -0,0 +1,8 @@ +-3 -3 -3 -3 -3 -3 -3 -3 +-3 -2 -2 02 02 02 02 -3 +-3 -2 -2 -2 -2 -2 04 -3 +-3 -2 -2 -2 01 01 04 -3 +-3 -2 -2 -2 03 03 04 -3 +-3 -2 -2 -2 -2 05 05 -3 +-3 -2 -2 00 06 06 06 -3 +-3 -3 -3 00 -3 -3 -3 -3 \ No newline at end of file diff --git a/Aufgabe3/rotation1_03_lösung.txt.bild.png b/Aufgabe3/rotation1_03_lösung.txt.bild.png new file mode 100644 index 0000000..6b74e54 Binary files /dev/null and b/Aufgabe3/rotation1_03_lösung.txt.bild.png differ diff --git a/Aufgabe3/rotation2_03.txt b/Aufgabe3/rotation2_03.txt new file mode 100644 index 0000000..6f89571 --- /dev/null +++ b/Aufgabe3/rotation2_03.txt @@ -0,0 +1,13 @@ +12 +############ +# # +# 01 # +# 01 # +# 01 # +# 222222# +# 34 5 # +# 34 5 # +# 634 5 # +# 63477775 # +# 63888885 # +###### ##### diff --git a/Aufgabe3/rotation2_03.txt.bild.png b/Aufgabe3/rotation2_03.txt.bild.png new file mode 100644 index 0000000..5262ece Binary files /dev/null and b/Aufgabe3/rotation2_03.txt.bild.png differ diff --git a/Aufgabe3/rotation2_03_lösung.txt b/Aufgabe3/rotation2_03_lösung.txt new file mode 100644 index 0000000..40d9a4e --- /dev/null +++ b/Aufgabe3/rotation2_03_lösung.txt @@ -0,0 +1,12 @@ +-3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 +-3 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -3 +-3 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -3 +-3 02 02 02 02 02 02 -2 -2 -2 -2 -3 +-3 00 07 07 07 07 -2 -2 -2 -2 -2 -3 +-3 00 03 -2 05 -2 -2 -2 -2 -2 -2 -3 +-3 00 03 04 05 -2 -2 -2 -2 -2 -2 -3 +-3 06 03 04 05 -2 -2 -2 -2 -2 -2 -3 +-3 06 03 04 05 -2 -2 -2 -2 -2 -2 -3 +-3 06 03 04 05 -2 01 -2 -2 -2 -2 -3 +-3 08 08 08 08 08 01 -2 -2 -2 -2 -3 +-3 -3 -3 -3 -3 -3 01 -3 -3 -3 -3 -3 \ No newline at end of file diff --git a/Aufgabe3/rotation2_03_lösung.txt.bild.png b/Aufgabe3/rotation2_03_lösung.txt.bild.png new file mode 100644 index 0000000..70869de Binary files /dev/null and b/Aufgabe3/rotation2_03_lösung.txt.bild.png differ diff --git a/Aufgabe3/rotation3_03.txt b/Aufgabe3/rotation3_03.txt new file mode 100644 index 0000000..4275442 --- /dev/null +++ b/Aufgabe3/rotation3_03.txt @@ -0,0 +1,19 @@ +10 +########## +# # +# # +# # +# 7775# +# 11 5# +# 2 888# +#990233 # +#44066666# +##### #### + 9 sec + 89 dep + 373 MB + 270 MB i64 -> i32 + 172 MB i32 -> i8 + 90 MB cache-optimierung +7.35 sec opt. get_stäbchen (1800ns -> 1100ns) +6.75 sec opt. ^ with_capacity (1100ns -> 975ns) diff --git a/Aufgabe3/rotation3_03.txt.bild.png b/Aufgabe3/rotation3_03.txt.bild.png new file mode 100644 index 0000000..65627b2 Binary files /dev/null and b/Aufgabe3/rotation3_03.txt.bild.png differ diff --git a/Aufgabe3/rotation3_03_lösung.txt b/Aufgabe3/rotation3_03_lösung.txt new file mode 100644 index 0000000..326369c --- /dev/null +++ b/Aufgabe3/rotation3_03_lösung.txt @@ -0,0 +1,10 @@ +-3 -3 -3 -3 -3 -3 -3 -3 -3 -3 +-3 -2 -2 -2 -2 -2 -2 -2 -2 -3 +-3 -2 -2 -2 -2 -2 -2 -2 -2 -3 +-3 -2 -2 -2 -2 -2 -2 -2 -2 -3 +-3 -2 -2 -2 -2 -2 -2 -2 -2 -3 +-3 07 07 07 09 09 -2 -2 -2 -3 +-3 06 06 06 06 06 08 08 08 -3 +-3 04 04 -2 -2 -2 02 00 -2 -3 +-3 01 01 03 03 05 02 00 -2 -3 +-3 -3 -3 -3 -3 05 -3 -3 -3 -3 \ No newline at end of file diff --git a/Aufgabe3/rotation3_03_lösung.txt.bild.png b/Aufgabe3/rotation3_03_lösung.txt.bild.png new file mode 100644 index 0000000..436632e Binary files /dev/null and b/Aufgabe3/rotation3_03_lösung.txt.bild.png differ diff --git a/Aufgabe3/rotation4_n.txt b/Aufgabe3/rotation4_n.txt new file mode 100644 index 0000000..834785c --- /dev/null +++ b/Aufgabe3/rotation4_n.txt @@ -0,0 +1,16 @@ +10 +########## +# # +# # +# 8# +#99 8# +#7 66# +#5 0# +#4 1# +#3 2# +##### #### + 69 sek + 96 tiefe +607 MB ... + 66 sek ... + 30 sek ... diff --git a/Aufgabe3/rotation4_n.txt.bild.png b/Aufgabe3/rotation4_n.txt.bild.png new file mode 100644 index 0000000..7ed0620 Binary files /dev/null and b/Aufgabe3/rotation4_n.txt.bild.png differ diff --git a/Aufgabe3/rotation6_n.txt b/Aufgabe3/rotation6_n.txt new file mode 100644 index 0000000..ae11baa --- /dev/null +++ b/Aufgabe3/rotation6_n.txt @@ -0,0 +1,17 @@ +12 +############ +# # +# # +# # +# # +#4 # +#4 99# +#33 8# +#22 7# +#1 6# +#00 55555# +###### ##### + 44 sek +223 tiefe +297 MB + 18 sek ... diff --git a/Aufgabe3/rotation6_n.txt.bild.png b/Aufgabe3/rotation6_n.txt.bild.png new file mode 100644 index 0000000..92aa7f3 Binary files /dev/null and b/Aufgabe3/rotation6_n.txt.bild.png differ diff --git a/Aufgabe3/rotation7_n.txt b/Aufgabe3/rotation7_n.txt new file mode 100644 index 0000000..be9cc18 --- /dev/null +++ b/Aufgabe3/rotation7_n.txt @@ -0,0 +1,27 @@ +14 +############## +# # +# # +# # +# # +# # +# # +#4 # +#4 99# +#33 8# +#22 7# +#1 6# +#00 555555# +####### ###### + 54 sek + 223 tiefe +3000 MB +2100 MB + 770 MB i64 -> i8 + 48 sek ... + ^ + 340 MB ... + RAM sparen im Cache + 49 sek ... + ^ + 344 MB rustup + 53 sek rustup + 30 sek ... + get_stäbchen + ... + 20 sek ... + gravitationsoptimierung diff --git a/Aufgabe3/rotation7_n.txt.bild.png b/Aufgabe3/rotation7_n.txt.bild.png new file mode 100644 index 0000000..0a9e20e Binary files /dev/null and b/Aufgabe3/rotation7_n.txt.bild.png differ diff --git a/Aufgabe3/rotation8_n.txt b/Aufgabe3/rotation8_n.txt new file mode 100644 index 0000000..c1b393e --- /dev/null +++ b/Aufgabe3/rotation8_n.txt @@ -0,0 +1,15 @@ +14 +############## +# # +# # +# # +# 4# +#8 4# +#8 3# +#8 3# +#9 1# +#9 21# +#9 BB 520# +#9AA 520# +#6677 CCCCCCC# +####### ###### diff --git a/Aufgabe3/rotation8_n.txt.bild.png b/Aufgabe3/rotation8_n.txt.bild.png new file mode 100644 index 0000000..08841b6 Binary files /dev/null and b/Aufgabe3/rotation8_n.txt.bild.png differ diff --git a/Aufgabe3/rotation9_n.txt b/Aufgabe3/rotation9_n.txt new file mode 100644 index 0000000..ad5c04f --- /dev/null +++ b/Aufgabe3/rotation9_n.txt @@ -0,0 +1,7 @@ +5 +##### +# # +#1 0# +#1 0# +## ## +Ungerade Seitenlängen sind möglich! diff --git a/Aufgabe3/rotation9_n.txt.bild.png b/Aufgabe3/rotation9_n.txt.bild.png new file mode 100644 index 0000000..b500bae Binary files /dev/null and b/Aufgabe3/rotation9_n.txt.bild.png differ diff --git a/Aufgabe3/rotation9_n_lösung.txt b/Aufgabe3/rotation9_n_lösung.txt new file mode 100644 index 0000000..0809301 --- /dev/null +++ b/Aufgabe3/rotation9_n_lösung.txt @@ -0,0 +1,5 @@ +-3 -3 -3 -3 -3 +-3 -2 -2 -2 -3 +-3 -2 -2 00 -3 +-3 -2 01 00 -3 +-3 -3 01 -3 -3 \ No newline at end of file diff --git a/Aufgabe3/rotation9_n_lösung.txt.bild.png b/Aufgabe3/rotation9_n_lösung.txt.bild.png new file mode 100644 index 0000000..ba60c3c Binary files /dev/null and b/Aufgabe3/rotation9_n_lösung.txt.bild.png differ diff --git a/Aufgabe3/src/main.rs b/Aufgabe3/src/main.rs new file mode 100644 index 0000000..d26e9d7 --- /dev/null +++ b/Aufgabe3/src/main.rs @@ -0,0 +1,786 @@ +#![feature(non_ascii_idents)] // für umlaute in variablen_namen +#![feature(test)] // für benchmarks + +/* Generelle Informationen + * - die Makros set und get setten und getten + * jeweils einen Punkt im Raster + * - farbige Ausgabe des Puzzles nur mit Linux und evtl. Mac + * - Groß- und Kleinschreibung (in kommentaren) eher weniger + * - index eines Stabes == die Zahl in der Datei (0-9 und A-Z bzw. 0-36) + */ + +const RAHMEN: i8 = -3; +const LEER: i8 = -2; +const AUSGANG: i8 = -1; + +const MINIMALE_AUSGÄNGE: usize = 0; // problemlos veränderbar (Aufgabenstellung -> 1) +const MAXIMALE_AUSGÄNGE: usize = 1000; // problemlos veränderbar (Aufgabenstellung -> 1) + +#[cfg(target_os = "linux")] +extern crate colored; +#[cfg(target_os = "linux")] +use colored::*; + +use std::collections::{HashSet, HashMap}; +use std::env; +use std::error::Error; +use std::fs::File; +use std::io::prelude::*; +use std::time::Instant; +use std::path::Path; + + +#[derive(Clone, Eq, Hash, PartialEq)] +pub struct Puzzle { + raster: Vec> +} + +// meistens nur als Vec == stab verwendet +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Punkt { + x: usize, + y: usize +} + +// in der Gravitationsfunktion benutzt +#[derive(PartialEq)] +enum StabRichtung { + Horizontal, + Vertikal +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum DrehRichtung { + // UhrzeigerSinn (rechtsrum) + US, + // Gegen den UhrzeigerSinn (linksrum) + GUS +} + +// zum farbigen anzeigen des puzzles +#[cfg(target_os = "linux")] +impl std::fmt::Display for Puzzle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + for zeile in &self.raster { + for zelle in zeile { + match *zelle { + RAHMEN => write!(f, "{}", format!("{:02} ", zelle).black().bold())?, + LEER => write!(f, "{}", format!("{:02} ", zelle).black().bold())?, + AUSGANG => write!(f, "{}", format!("{:02} ", zelle).green().bold())?, + // hiermit werden die Farben abwechselnd verteilt, + // die gleiche Farbe wird aber evtl. an mehrere Stäbe verteilt + _ if zelle % 5 == 0 => write!(f, "{}", format!("{:02} ", zelle).red().bold())?, + _ if zelle % 5 == 1 => write!(f, "{}", format!("{:02} ", zelle).yellow().bold())?, + _ if zelle % 5 == 2 => write!(f, "{}", format!("{:02} ", zelle).blue().bold())?, + _ if zelle % 5 == 3 => write!(f, "{}", format!("{:02} ", zelle).purple().bold())?, + _ if zelle % 5 == 4 => write!(f, "{}", format!("{:02} ", zelle).cyan().bold())?, + _ => write!(f, "{:02} ", zelle)? + }; + } + write!(f, "\n")?; + } + Ok(()) + } +} + +// und nochmal für windows usw. (ohne farben) +#[cfg(not(target_os = "linux"))] +impl std::fmt::Display for Puzzle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + for zeile in &self.raster { + for zelle in zeile { + write!(f, "{:02} ", zelle)?; + } + write!(f, "\n")? + } + Ok(()) + } +} + +// weil ich es kann, wird bei mir [US, GUS] als [↻, ↺] angezeigt +impl std::fmt::Display for DrehRichtung { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if self == &DrehRichtung::US { + write!(f, "↻") + } else { + write!(f, "↺") + } + } +} + +// lädt ein Puzzle aus einer Datei und kann Fehler zurückgeben +pub fn lade_puzzle(dateiname: &str) -> Result { + let pfad = Path::new(dateiname); + + let mut datei = match File::open(&pfad) { + Err(fehler) => { return Err(format!("Konnte Datei {} nicht öffnen: {}", dateiname, + fehler.description())); }, + Ok(datei) => datei, + }; + + let mut s = String::new(); + if let Err(fehler) = datei.read_to_string(&mut s) { + return Err(format!("Konnte Datei {} nicht lesen: {}", + dateiname, fehler.description())); + } + let zeilen: Vec<&str> = s.split('\n').collect(); + let mut puzzle = Puzzle { raster: Vec::with_capacity(zeilen.len()) }; + let seitenlänge = zeilen[0].parse(); + if seitenlänge.is_err() { + return Err("erste Zeile der Datei enthält mehr oder weniger als eine Zahl".to_string()); + } + let seitenlänge = seitenlänge.unwrap(); + if seitenlänge < 3 { + return Err(format!("Puzzle-Raster ist zu klein, Seitenlänge == {:?}", seitenlänge).to_string()); + } + + // in den Beispielen ist der Index der Stäbe jeweils zwischen 0-9 + let mut gefundene_stab_indexe = Vec::new(); + let mut gefundene_ausgänge = 0; + + // skip(1) weil die erste Zeile nur die Seitenlänge ist + for (y, zeile) in zeilen.iter().skip(1).enumerate() { + // zeile nach der letzten raster-zeile erreicht + // (nach dem Raster könnten noch Kommentare folgen) + if y == seitenlänge { + break; + } + + let mut raster_zeile = Vec::with_capacity(zeile.len()); + for (x, buchstabe) in zeile.chars().enumerate() { + // der Ausgang kann entweder links, rechts, oben oder unten sein + if buchstabe == ' ' && ((x == 0 || x == zeile.len()-1) || (y == 0 || y == seitenlänge-1)) { + gefundene_ausgänge += 1; + if gefundene_ausgänge > MAXIMALE_AUSGÄNGE { + return Err("Zu viele Ausgänge in Datei".to_string()) + } + raster_zeile.push(AUSGANG); + continue; + } + // zahlen von 0-9 und buchstaben von A-Z oder a-z werden anerkannt (A == 10, Z == 36) + match buchstabe.to_digit(36) { + Some(zahl) => { + if !gefundene_stab_indexe.contains(&zahl) { + gefundene_stab_indexe.push(zahl); + } + raster_zeile.push(zahl as i8) + }, + // buchstabe ist keine zahl.. + None => { + match buchstabe { + '#' => raster_zeile.push(RAHMEN), + ' ' => raster_zeile.push(LEER), + // mit '_' wird jeder buchstabe "gematcht" + _ => return Err(format!("Unbekannter Buchstabe: {:?}", buchstabe)) + } + } + } + } + if seitenlänge != raster_zeile.len() { + return Err(format!("Seitenlänge stimmt nicht mit Länge der Raster-Zeile {:?} überein!", y+1)) + } + puzzle.raster.push(raster_zeile); + } + + if seitenlänge != puzzle.raster.len() { + return Err("Seitenlänge stimmt nicht mit Raster-Höhe überein".to_string()) + } + + if gefundene_stab_indexe.is_empty() { + return Err("Leider enthält das Raster keine Stäbe :(".to_string()) + } + + gefundene_stab_indexe.sort(); // aufsteigend, also z.b. [0, 1, ..] + let erster = gefundene_stab_indexe[0]; + if erster != 0 { + return Err("Die Stäbe müssen von 0 aufsteigend nummeriert sein.".to_string()) + } + // hier wird fold missbraucht, um sicherzustellen, dass die Stäbe + // von 0 ansteigend durchgängig nummeriert sind + gefundene_stab_indexe.iter().skip(1).fold(erster, |acc, &x| if (x as isize) - 1 == acc as isize { x } else { panic!("Die Stäbe sind nicht aufsteigend nummeriert :(") }); + + // Stäbe, bei denen nicht klar ist, ob sie vertikal oder + // horizontal vorliegen, werden hier erkannt + if !verifiziere_stäbchen(&finde_stäbchen(&puzzle.raster)) { + return Err("Stäbchen ergeben keinen Sinn".to_string()) + } + + // falls der Rahmen zu wenig Ausgänge enthält oder + // unvollständig ist, wird ein Fehler ausgegeben + if !verifiziere_rahmen(&puzzle.raster) { + return Err("Rahmen des Puzzles ist nicht korrekt aufgebaut".to_string()) + } + + Ok(puzzle) +} + +fn verifiziere_rahmen(raster: &[Vec]) -> bool { + // Rahmen, deren Länge zwei oder kleiner beträgt, + // könnene keine Stäbe oder Ausgänge enthalten + if raster.len() <= 2 { + false + } else { + let mut gefundene_ausgänge = 0; + for (y, zeile) in raster.iter().enumerate() { + // ist die erste und letzte erste Zahl ein ausgang oder der rahmen + if !(zeile[0] != RAHMEN || zeile.last() != Some(&RAHMEN)) + && !(zeile[0] != AUSGANG || zeile.last() != Some(&AUSGANG)) { + return false; + } + if y == 0 || y == raster.len()-1 { + // an den Ecken des Rahmens darf nur der Rahmen sein, + // und nicht z.b. der Ausgang + if zeile[0] != RAHMEN || zeile.last() != Some(&RAHMEN) { + return false; + } + for feld in zeile { + match *feld { + AUSGANG => gefundene_ausgänge += 1, + // in der obersten und untersten Zeile + // sollten keine Stäbe sein + _ if *feld >= 0 => return false, + _ => {} + } + } + } + } + // logisch + if !(gefundene_ausgänge >= MINIMALE_AUSGÄNGE) || !(gefundene_ausgänge <= MAXIMALE_AUSGÄNGE) { + false + } else { + true + } + } +} + +// überprüft, dass alle stäbchen eindeutig vertikal +// oder horizontal sind +fn verifiziere_stäbchen(stäbchen: &[Vec]) -> bool { + // keine Stäbchen -> unnötiges Puzzle?! + if stäbchen.len() == 0 { + false + } else { + for stab in stäbchen { + if !verifiziere_stab(stab) { + return false; + } + } + true + } +} +// überprüft, ob ein Stab eindeutig vertikal +// oder horizontal ist +fn verifiziere_stab(stab: &[Punkt]) -> bool { + // Stab mit Länge 0 -> eig. unmöglich + if stab.len() == 0 { + false + } else { + let eig_richtung = get_stäbchen_richtung(stab); + if eig_richtung == StabRichtung::Horizontal { + // bei horizontalen Stäben sollte die y-Koordinate immer gleich sein + let eig_y = stab[0].y; + for p in stab { + if p.y != eig_y { + return false; + } + } + } else { + // bei vertikalen Stäben sollte die x-Koordinate immer gleich sein + let eig_x = stab[0].x; + for p in stab { + if p.x != eig_x { + return false; + } + } + } + true + } +} + +pub fn drehe(mut puzzle: Puzzle, dir: &DrehRichtung) -> Puzzle { + // seitenlänge + let s = puzzle.raster.len(); + + // makros wie immer + macro_rules! get { + ($x:expr, $y:expr) => ( + puzzle.raster[$y][$x] + ) + } + macro_rules! set { + ($x:expr, $y:expr, $new:expr) => ( + puzzle.raster[$y][$x] = $new; + ) + } + let maxx; + let maxy; + if s % 2 == 0 { // gerade seitenlänge + // mit 'x' markierte Punkte werden unten in der Schleife + // unten bearbeitet + /* - - - - - - + * - x x - + * - x x - + * - - + * - - + * - - - - - */ + maxx = s/2-1; + maxy = s/2-1; + } else { // ungerade seitenlänge + // Punkt 'c' bleibt gleich + /* - - - - - - - + * - x x x - + * - x x x - + * - c - + * - - + * - - + * - - - - - - */ + maxx = ((s as f32/2.0)-0.5) as usize; + maxy = ((s as f32/2.0)-1.5) as usize; + } + // "Bereiche" oben-links bis unten-links + let mut ol; + let mut or; // enthält den Wert des Punktes, + let mut orx; // die x-Koordinate des Punktes und + let mut ory; // die y-Koordinate des Punktes + // usw. + let mut ur; let mut urx; let mut ury; + let mut ul; let mut ulx; let mut uly; + // oberer linker Bereich wird abgearbeitet + for oly in 0..maxy+1 { + for olx in 0..maxx+1 { + ol = get!(olx, oly); + orx = s-oly-1; ory = olx; or = get!(orx, ory); + urx = s-ory-1; ury = orx; ur = get!(urx, ury); + ulx = s-ury-1; uly = urx; ul = get!(ulx, uly); + if dir == &DrehRichtung::US { + set!(orx, ory, ol); + set!(urx, ury, or); + set!(ulx, uly, ur); + set!(olx, oly, ul); + // Ergebnis: (nur das Innere des Rahmens) + // Punkt B erhält den Wert von A (ol) + // C von B (or) + // D von C (ur) + // A von D (ul) + /* - A - - + * - - - B + * D - - - + * - - C - */ + } else { + // hier genau umgekehrt wie oben + set!(ulx, uly, ol); + set!(urx, ury, ul); + set!(orx, ory, ur); + set!(olx, oly, or); + } + } + } + + puzzle +} + +// findet alle Stäbe in einem Raster bzw. Puzzle +pub fn finde_stäbchen(raster: &[Vec]) -> Vec> { + // hier werden die stäbchen nach ihrem index sortiert gespeichert + let mut stäbchen = Vec::with_capacity(STÄBCHEN_ANZAHL_SCHÄTZUNG); + + // jeden Punkt besuchen + for (y, zeile) in raster.iter().enumerate() { + for (x, zelle) in zeile.iter().enumerate() { + // falls negativ: kein teil eines stabes + if !zelle.is_negative() { + // in richtigen "zahltyp" umrechnen + let zelle = *zelle as usize; + // falls der Vektor noch keinen Vektor für diesen + // Stab enthält, wird er bis dahin aufgefüllt + if stäbchen.is_empty() || stäbchen.len() - 1 < zelle { + for _ in stäbchen.len()..zelle+1 { + stäbchen.push(Vec::new()); + } + } + // danach wird der Punkt im Vektor gespeichert + stäbchen.get_mut(zelle).expect("Code 0-un").push(Punkt { x: x, y: y }); + } + } + } + + stäbchen +} + +fn get_stäbchen_richtung(stab: &[Punkt]) -> StabRichtung { + // falls der Stab kein Würfel ist und die x-Koordinaten der + // ersten beiden elemente gleich sind, + if stab.len() > 1 && stab[0].x == stab[1].x { + // ist er vertikal + StabRichtung::Vertikal + } else { + StabRichtung::Horizontal + } +} + +// diese Variable dient nur der Optimierung +// (diese Kapazität wird der Vektor, der in finde_stäbchen erstellt wird, +// zunächst haben (diese Kapatizät nachträglich zu vergrößern benötigt +// einige Zeit)) +const STÄBCHEN_ANZAHL_SCHÄTZUNG: usize = 10; + +pub fn wende_gravitation_an(mut puzzle: Puzzle) -> Puzzle { + // seitenlänge + let s = puzzle.raster.len(); + // makros wie immer + macro_rules! get { + ($x:expr, $y:expr) => ( + puzzle.raster[$y][$x]; + ) + } + macro_rules! set { + ($x:expr, $y:expr, $new:expr) => ( + puzzle.raster[$y][$x] = $new; + ) + } + + // erst alle Stäbchen finden + let stäbchen = finde_stäbchen(&puzzle.raster); + + // mithilfe dieses Vektor werden die Stäbe sortiert, s.u. + let mut unterste_teile_der_stäbe = Vec::with_capacity(STÄBCHEN_ANZAHL_SCHÄTZUNG); + for stab in &stäbchen { + // findet den Punkt mit der größten y-Koordinate, + // also den untersten + let unterster_punkt = stab.iter().max_by_key(|&x| x.y); + match unterster_punkt { + // dies passiert nur, falls der Stab keine Elemente hat (unmöglich) + None => panic!("Code 1-un"), + Some(punkt) => unterste_teile_der_stäbe.push(punkt) + } + } + // unterste Punkte der stäbe nach ihrer y-Koordinate sortieren, + // sodass die stäbe von unten nach oben + // auf einmal bewegt werden können + let mut sortiert = unterste_teile_der_stäbe.clone(); + sortiert.sort_by_key(|&x| x.y); + sortiert.reverse(); + + // diese Schleife beginnt also mit dem untersten Stab + for punkt_aus_stab in sortiert { + // stab_index == zahl des stabes (in den Beispielen nur 0-9) + let mut stab_index = None; + // manuelle Suche des punktes in unterste_punkt, + // um den eigentlichen Index des Stabes zu erfahren + for (index, punkt) in unterste_teile_der_stäbe.iter().enumerate() { + if punkt == &punkt_aus_stab { + stab_index = Some(index); + break; + } + } + let stab_index = stab_index.expect("Code 2-un"); + let stab = stäbchen.get(stab_index).expect("Code 3-un"); + let richtung = get_stäbchen_richtung(stab); + // die Schritte, die der Stab nach unten fallen soll + let mut schritte = 0; + + if richtung == StabRichtung::Vertikal { + let unterster_punkt = punkt_aus_stab; + // hier wird berechnet, wie viele der Punkte bis zum Rahmen (einschließlich dem Rahmen) + // hintereinander leer sind + for y in unterster_punkt.y+1..s { + if get!(unterster_punkt.x, y) == LEER || get!(unterster_punkt.x, y) == AUSGANG { + schritte += 1; + } else { // ein anderer Stab oder der Rahmen wurde "getroffen" + break; + } + } + } else { + // die y-Koordinate der horizontalen Stäbe ist für jeden teil gleich + let y_level = stab[0].y; + + // hier wird berechnet, wie viele der Punkte bis zum Rahmen (einschließlich dem Rahmen) + // hintereinander leer sind + for y in y_level+1..s { + // diese variable gibt an, ob der Stab noch weiter bewegt + // werden sollte + let mut sollte_bewegen = true; + for p in stab { + // falls unter irgendeinem der Punkte des Stabes kein freier Platz ist, + // sollte der Stab nicht weiter bewegt werden + if !(get!(p.x, y) == LEER || get!(p.x, y) == AUSGANG) { + sollte_bewegen = false; + break; + } + } + if sollte_bewegen { + schritte += 1; + } else { // ein anderer Stab oder der Rahmen wurde "getroffen" + break; + } + } + + } + + // falls schritte == 0 ist, passiert nichts + for offset in 0..schritte { + // erste vorherige Position mit LEER ersetzen + for p in stab { + set!(p.x, p.y+offset, LEER); + } + // dann neue Position mit stab_index füllen + for p in stab { + set!(p.x, p.y+offset+1, stab_index as i8); + } + } + } + puzzle +} + +// gibt an, ob das Puzzle gelöst ist +fn puzzle_gelöst(puzzle: &Puzzle) -> bool { + puzzle.raster + // unterer Teil des Rahmens + .last().expect("Code 4-un").iter() + .fold( + false, |acc, &x| + // falls dieses Element zu einem Stab gehört, + // wird acc auf true gesetzt + if x >= 0 { true } + else { acc }) +} + +// wendet einen Entscheidungsweg an (mithilfe eines caches) +fn wende_entscheidungen_an(puzzle: Puzzle, ew: &[DrehRichtung], cache: &mut HashMap, Puzzle>) -> Puzzle { + // ew == Entscheidungsweg + if cache.contains_key(ew) { + cache.get(ew).expect("Code 5-un").clone() + } else if ew.len() == 1 { // z.b. [US] oder [GUS] (cache lohnt nicht)s + wende_gravitation_an(drehe(puzzle, &ew[0])) + } else { + // Bsp. ew == [US, GUS, US] + let mut ew = Vec::from(ew); + // Bsp. US + let letzte_drehung = ew.pop().unwrap(); + // Bsp. Ergebnis des EWs [US, GUS] + let puzzle_vor_letzter_drehung = wende_entscheidungen_an(puzzle, &ew, cache); + // Bsp. Ergebnis der Drehung US auf vorige variable + let endzustand = wende_gravitation_an(drehe(puzzle_vor_letzter_drehung, &letzte_drehung)); + ew.push(letzte_drehung); + cache.insert(ew, endzustand.clone()); + endzustand + } +} + +pub fn erstelle_nächste_schicht(base: &[Vec]) -> Vec> { + let mut nächste_schicht = Vec::new(); + // Bsp. base == [[US]] + // -> nächste_schicht == [[US, US], [US, GUS]] + for path in base { + + let mut c1 = path.clone(); + let mut c2 = path.clone(); + c1.push(DrehRichtung::US); + c2.push(DrehRichtung::GUS); + nächste_schicht.push(c1); + nächste_schicht.push(c2); + } + nächste_schicht +} + +// findet einen EW, der das Puzzle löst oder stellt +// fest, dass es keine Lösung gibt +pub fn optimierte_breiten_suche(puzzle: Puzzle) -> Option> { + // EW == Entscheidungsweg, z.b. [US, GUS, US] + // cache wie in der Dokumentation beschriben + let mut cache = HashMap::new(); + // wie in der Dokumentation + let mut bekannte_zustände = HashSet::new(); + // ein leerer EW == vec![] + let mut base = vec![vec![]]; + let mut tiefe = 0; // für Fortschritts-Anzeige + let start_zeit = Instant::now(); // ^ + loop { + let paths: Vec> = cache.keys().cloned().collect(); + for path in paths { + // falls der Entscheidungsweg kleiner als die Tiefe ist, + // wird er eh nie mehr aus dem Cache geholt, + // weil der Cache immer zuerst versucht, die Entscheidungswege + // der letzten Ebene zu benutzen + // (betrifft hier also die ebene vor der letzten) + if path.len() < tiefe { + cache.remove(&path); // spart RAM + } + } + tiefe += 1; + + // aus den EWs der letzten Ebene werden + // die neuen EWs gebaut + let nächste_schicht = erstelle_nächste_schicht(&base); + + // falls diese leer ist, gibt es keine lösung + if nächste_schicht.is_empty() { + return None; + } + // eine neue Basis für die nächste runde wird erstellt + base = Vec::new(); + for ew in &nächste_schicht { + // EW auf Puzzle anwenden + let tmp_puzzle = wende_entscheidungen_an(puzzle.clone(), ew, &mut cache); + // falls noch nicht bekannt + if !bekannte_zustände.contains(&tmp_puzzle) { + // an base für nächste runde anfügen + base.push(ew.clone()); + } + if puzzle_gelöst(&tmp_puzzle) { + // puzzle gelöst: EW zurückgeben + return Some(ew.clone()); + } else { + // sonst: zustand merken + bekannte_zustände.insert(tmp_puzzle); + } + } + // "Fortschrittsanzeige" + println!("{:03} tiefe, {:06} nächste_schicht, {:07} bekannt, {:07} gecached, {:04} sekunden", tiefe, nächste_schicht.len(), bekannte_zustände.len(), cache.len(), start_zeit.elapsed().as_secs()); + } +} + +fn main() { + if let Some(dateiname) = env::args().nth(1) { + let puzzle = lade_puzzle(&dateiname); + if let Err(e) = puzzle { + println!("Fehler beim Einlesen: {:?}", e); + return; + } + + // Start-Zustand anzeigen + let puzzle = wende_gravitation_an(puzzle.unwrap()); + println!("{}", puzzle); + + if puzzle_gelöst(&puzzle) { + println!("Puzzle ist bereits gelöst?!"); + return + } + + // hoffentlich eine Lösung finden + let solution = optimierte_breiten_suche(puzzle.clone()); + if let Some(solution) = solution { + print!("Lösung: ["); + for (index, drehung) in solution.iter().enumerate() { + if index < solution.len()-1 { + print!("{}, ", drehung); + } else { + println!("{}]", drehung); + } + } + // gelöstes Puzzle anzeigen + println!("{}", wende_entscheidungen_an(puzzle, &solution, &mut HashMap::new())); + } else { + println!("keine lösung gefunden!"); + } + } else { + println!("Bitte so aufrufen: ./target/debug/Aufgabe3 "); + } +} + + +// ein paar tests +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn nächste_schicht() { + let nächste_schicht = erstelle_nächste_schicht(&vec![vec![DrehRichtung::US]]); + let erwartung = vec![vec![DrehRichtung::US, DrehRichtung::US], vec![DrehRichtung::US, DrehRichtung::GUS]]; + if nächste_schicht != erwartung { + panic!() + } + } + + #[test] + fn t1() { + let puzzle = lade_puzzle("rotation1_03.txt").expect("konnte datei nicht laden!"); + assert!(optimierte_breiten_suche(puzzle).expect("puzzle nicht gelöst").len() == 6); + } + #[test] + fn t2() { + let puzzle = lade_puzzle("rotation2_03.txt").expect("konnte datei nicht laden!"); + assert!(optimierte_breiten_suche(puzzle).expect("puzzle nicht gelöst").len() == 22); + } + #[test] + fn t3() { + let puzzle = lade_puzzle("rotation3_03.txt").expect("konnte datei nicht laden!"); + assert!(optimierte_breiten_suche(puzzle).expect("puzzle nicht gelöst").len() == 90); + } + + #[test] + fn stäbchen() { + let puzzle = lade_puzzle("rotation1_03.txt").expect("konnte datei nicht laden!"); + let expected = vec![vec![Punkt { x: 6, y: 1 }, Punkt { x: 6, y: 2 }], + vec![Punkt { x: 1, y: 3 }, Punkt { x: 2, y: 3 }], + vec![Punkt { x: 3, y: 3 }, Punkt { x: 4, y: 3 }, Punkt { x: 5, y: 3 }, Punkt { x: 6, y: 3 }], + vec![Punkt { x: 1, y: 4 }, Punkt { x: 2, y: 4 }], + vec![Punkt { x: 6, y: 4 }, Punkt { x: 6, y: 5 }, Punkt { x: 6, y: 6 }], + vec![Punkt { x: 1, y: 5 }, Punkt { x: 2, y: 5 }], + vec![Punkt { x: 1, y: 6 }, Punkt { x: 2, y: 6 }, Punkt { x: 3, y: 6 }]]; + assert_eq!(finde_stäbchen(&puzzle.raster), expected); + } +} + +// paar benchmarks +#[cfg(test)] +mod benchs { + extern crate test; + + use super::*; + use self::test::Bencher; + + /* deaktiviert wegen der Fortschrittsanzeige + #[bench] + fn test1(b: &mut Bencher) { + b.iter(|| { + let puzzle = lade_puzzle("rotation1_03.txt").expect("konnte datei nicht laden!"); + optimierte_breiten_suche(puzzle) + }); + } + #[bench] + fn test2(b: &mut Bencher) { + b.iter(|| { + let puzzle = lade_puzzle("rotation2_03.txt").expect("konnte datei nicht laden!"); + optimierte_breiten_suche(puzzle) + }); + } + */ + + #[bench] + fn drehe_us(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").expect("konnte datei nicht laden!"); + b.iter(|| { + drehe(puzzle.clone(), &test::black_box(DrehRichtung::US)) + }) + } + #[bench] + fn drehe_gus(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").expect("konnte datei nicht laden!"); + b.iter(|| { + drehe(puzzle.clone(), &test::black_box(DrehRichtung::GUS)) + }) + } + #[bench] + fn drehe_us_mit_grav(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").expect("konnte datei nicht laden!"); + b.iter(|| { + wende_gravitation_an(drehe(puzzle.clone(), &test::black_box(DrehRichtung::US))) + }) + } + #[bench] + fn drehe_gus_mit_grav(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").expect("konnte datei nicht laden!"); + b.iter(|| { + wende_gravitation_an(drehe(puzzle.clone(), &test::black_box(DrehRichtung::GUS))) + }) + } + #[bench] + fn get_stäbe(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").expect("konnte datei nicht laden!"); + b.iter(|| { + finde_stäbchen(&puzzle.raster) + }) + } +} diff --git a/Aufgabe3/src/main_map.rs b/Aufgabe3/src/main_map.rs new file mode 100644 index 0000000..b3e8117 --- /dev/null +++ b/Aufgabe3/src/main_map.rs @@ -0,0 +1,756 @@ +#![feature(non_ascii_idents)] +#![feature(custom_derive)] +#![feature(test)] +#![feature(plugin)] + +#![plugin(clippy)] + +#[cfg(target_os = "linux")] +extern crate colored; +#[cfg(target_os = "linux")] +use colored::*; + +use std::collections::{HashSet, HashMap}; +use std::env; +use std::error::Error; +use std::fs::File; +use std::io::prelude::*; +use std::time::Instant; +use std::path::Path; + +#[derive(Clone, Copy, PartialEq)] +pub struct Punkt { + x: usize, + y: usize +} + +#[derive(PartialEq)] +enum Stabrichtung { + Horizontal, + Vertikal +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct Puzzle { + raster: Vec> +} + +#[cfg(target_os = "linux")] +impl std::fmt::Display for Puzzle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + for row in &self.raster { + write!(f, "\n")?; + for cell in row { + match *cell { + WALL => write!(f, "{}", format!("{:02} ", cell).black().bold())?, + NIX => write!(f, "{}", format!("{:02} ", cell).black().bold())?, + AUSGANG => write!(f, "{}", format!("{:02} ", cell).green().bold())?, + _ if cell % 5 == 0 => write!(f, "{}", format!("{:02} ", cell).red().bold())?, + _ if cell % 5 == 1 => write!(f, "{}", format!("{:02} ", cell).yellow().bold())?, + _ if cell % 5 == 2 => write!(f, "{}", format!("{:02} ", cell).blue().bold())?, + _ if cell % 5 == 3 => write!(f, "{}", format!("{:02} ", cell).purple().bold())?, + _ if cell % 5 == 4 => write!(f, "{}", format!("{:02} ", cell).cyan().bold())?, + _ => write!(f, "{:02} ", cell)? + }; + } + } + write!(f, "\n") + } +} +#[cfg(not(target_os = "linux"))] +impl std::fmt::Display for Puzzle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + for row in &self.raster { + write!(f, "\n")?; + for cell in row { + write!(f, "{:02} ", cell)?; + } + } + write!(f, "\n") + } +} + +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub enum RotationDir { + // clockwise + CW, + // counterclockwise + CCW +} + +const WALL: i8 = -3; +const NIX: i8 = -2; +const AUSGANG: i8 = -1; + +#[derive(Default)] +struct MyMap<'a> { + realmap: HashMap, Puzzle>, + valuesset: HashSet<&'a Puzzle> +} + +use std::collections::hash_map::Keys; +trait SemiBidirMap<'a> { + fn insert(&'a mut self, key: Vec, value: Puzzle); + fn get(&'a self, key: &[RotationDir]) -> Option<&'a Puzzle>; + fn remove_key(&'a mut self, key: &Vec) -> Option; + fn contains_key(&'a self, key: &[RotationDir]) -> bool; + fn contains_value(&'a self, value: &Puzzle) -> bool; + fn keys(&'a self) -> Keys, Puzzle>; + fn len_keys(&'a self) -> usize; +} + +impl<'a> SemiBidirMap<'a> for MyMap<'a> { + fn get(&'a self, key: &[RotationDir]) -> Option<&'a Puzzle> { + self.realmap.get(key) + } + fn insert(&'a mut self, key: Vec, value: Puzzle) { + self.realmap.insert(key.clone(), value); + self.valuesset.insert(self.realmap.get(&key).unwrap()); + } + fn remove_key(&'a mut self, key: &Vec) -> Option { + self.realmap.remove(key) + } + fn contains_key(&'a self, key: &[RotationDir]) -> bool { + self.realmap.contains_key(key) + } + fn contains_value(&'a self, value: &Puzzle) -> bool { + self.valuesset.contains(value) + } + fn keys(&'a self) -> Keys, Puzzle> { + self.realmap.keys() + } + fn len_keys(&'a self) -> usize { + self.realmap.len() + } +} + +impl<'a> MyMap<'a> { + fn new() -> MyMap<'a> { + MyMap { realmap: HashMap::new(), valuesset: HashSet::new() } + } +} + +pub fn lade_puzzle(dateiname: &str) -> Result { + let path = Path::new(dateiname); + let display = path.display(); + + // Open the path in read-only mode, returns `io::Result` + let mut file = match File::open(&path) { + // The `description` method of `io::Error` returns a string that + // describes the error + Err(why) => { return Err(format!("couldn't open {}: {}", display, + why.description())); }, + Ok(file) => file, + }; + + // Read the file contents into a string, returns `io::Result` + let mut s = String::new(); + if let Err(why) = file.read_to_string(&mut s) { + return Err(format!("couldn't read {}: {}", display, + why.description())); + } + let mut p = Puzzle { raster: Vec::new() }; + let lines: Vec<&str> = s.split('\n').collect(); + let seitenlänge = lines[0].parse().expect("erste Zeile keine Zahl"); + + for (index, line) in lines.iter().skip(1).enumerate() { + // zeile nach der letzten zeile erreicht + if index == seitenlänge { + break; + } + + let mut line_vec = Vec::new(); + for (x, c) in line.chars().enumerate() { + // ausgang + if c == ' ' && ((x == 0 || x == line.len()-1) || (index == 0 || index == seitenlänge-1)) { + line_vec.push(AUSGANG); + continue; + } + match c.to_digit(36) { + Some(digit) => line_vec.push(digit as i8), + // -1 == " " + None => { + match c { + '#' => line_vec.push(WALL), + ' ' => line_vec.push(NIX), + _ => panic!("unbekannter buchstabe {:?}", c) + } + } + } + } + p.raster.push(line_vec); + //println!("{:?}: {:?}", index, line); + } + Ok(p) +} + +pub fn rotate(puzzle: Puzzle, dir: &RotationDir) -> Puzzle { + // seitenlänge + let s = puzzle.raster.len(); + let mut roted_raster = puzzle.raster.clone(); + macro_rules! set { + ($x:expr, $y:expr, $new:expr) => ( + roted_raster[$y][$x] = $new; + ) + } + + for (y, row) in puzzle.raster.iter().enumerate() { + for (x, cell) in row.iter().enumerate() { + match *dir { + RotationDir::CW => set!(s-y-1, x, *cell), + RotationDir::CCW => set!(y, s-x-1, *cell) + } + } + } + Puzzle { raster: roted_raster } +} + +fn get_stäbchen(raster: &[Vec]) -> HashMap> { + let mut stäbchen: HashMap> = HashMap::new(); + + for (y, row) in raster.iter().enumerate() { + for (x, cell) in row.iter().enumerate() { + if !cell.is_negative() { + let cell = *cell as u64; + let value = stäbchen.entry(cell).or_insert_with(Vec::new); + value.push(Punkt { x: x, y: y }); + } + } + } + + stäbchen +} + +fn get_stäbchen_richtung(stab: &[Punkt]) -> Stabrichtung { + if stab.len() > 1 && stab[0].x == stab[1].x { + Stabrichtung::Vertikal + } else { + Stabrichtung::Horizontal + } +} + +pub fn do_the_gravity(mut puzzle: Puzzle) -> Puzzle { + // seitenlänge + let s = puzzle.raster.len(); + macro_rules! get { + ($x:expr, $y:expr) => ( + puzzle.raster[$y][$x]; + ) + } + macro_rules! set { + ($x:expr, $y:expr, $new:expr) => ( + puzzle.raster[$y][$x] = $new; + ) + } + + let mut stäbchen1 = get_stäbchen(&puzzle.raster); + let mut stäbchen2 = HashMap::new(); + while stäbchen1 != stäbchen2 { + //println!("sloop"); + stäbchen1 = get_stäbchen(&puzzle.raster); + //println!("{:?}", stäbchen1); + for (index, stab) in &stäbchen1 { + let richtung = get_stäbchen_richtung(stab); + let stab = stab.iter(); + let mut move_possible = stab.clone() + .fold(true, |acc, &x| + // falls der Stab schon direkt über dem Rahmen ist, + // sollte er nicht bewegt werden + if x.y >= s-2 + // falls der Stab (horizontal) liegt und unter ihm kein Platz ist, + // sollte er nicht bewegt werden + || (richtung == Stabrichtung::Horizontal && ((get!(x.x, x.y+1) != NIX && get!(x.x, x.y+1) != AUSGANG))) { false } else { acc }); + + // falls der Stab (vertikal) steht, + if richtung == Stabrichtung::Vertikal { + // und der unterste Teil des Stabes + let lowest_punkt = stab.clone().max_by_key(|x| x.y).unwrap(); + // über/auf dem unteren Begrenzung liegt + if lowest_punkt.y+1 <= s-1 { + // und unter ihm bereits ein Stab liegt, + if get!(lowest_punkt.x, lowest_punkt.y+1) >= 0 { + // sollte er nicht bewegt werden + move_possible = false; + // sonderfall: unter dem Teil des Stabes liegt der Ausgang + } else if get!(lowest_punkt.x, lowest_punkt.y+1) == AUSGANG { + //println!("Puzzle ist möglich!"); + move_possible = true; + } + } + } + + if move_possible { + let stab: Vec<&Punkt> = stab.collect(); + for p in &stab { + set!(p.x, p.y, NIX); + } + let index = *index as i8; + for p in &stab { + set!(p.x, p.y+1, index); + } + } + } + stäbchen2 = stäbchen1; + stäbchen1 = get_stäbchen(&puzzle.raster); + //println!("eloop"); + } + //println!("EOG"); + puzzle +} + +fn puzzle_gelöst(puzzle: &Puzzle) -> bool { + puzzle.raster + .last().unwrap().iter() + .fold( + false, |acc, &x| + // falls dieses Element zu einem Stab gehört, + // wird acc auf true gesetzt + if x >= 0 { true } + else { acc }) +} + +fn apply_choices<'a>(puzzle: Puzzle, choices: &'a [RotationDir], cache: &'a mut MyMap<'a>) -> Puzzle { + if cache.contains_key(choices) { + //println!("{:?} cached", choices); + cache.get(choices).unwrap().clone() + } else if choices.len() == 1 { + //println!("len one"); + do_the_gravity(rotate(puzzle, &choices[0])) + } else { + let mut choices = Vec::from(choices); + let last_action = choices.pop().unwrap(); + let before_last = apply_choices(puzzle, &choices, cache); + //println!("before: {}", before_last); + let applied = do_the_gravity(rotate(before_last, &last_action)); + //println!("applied: {}", applied); + choices.push(last_action); + cache.insert(choices, applied.clone()); + applied + } +} + +pub fn build_children(base: &[Vec]) -> Vec> { + let mut children = Vec::new(); + for path in base { + let mut c1 = path.clone(); + let mut c2 = path.clone(); + c1.push(RotationDir::CW); + c2.push(RotationDir::CCW); + children.push(c1); + children.push(c2); + } + children +} + +pub fn breadth_first_search(puzzle: Puzzle) -> Option> { + //let mut cache = HashMap::new(); + let mut my_cache = MyMap::new(); + let mut known_outcomes = HashSet::new(); + let mut base_paths = vec![vec![]]; + let mut depth = 0; + let start = Instant::now(); + loop { + { + let paths: Vec> = my_cache.keys().cloned().collect(); + for path in paths { + if path.len() < depth { + my_cache.remove_key(&path); + } + } + } + depth += 1; + + let children = build_children(&base_paths); + if children.is_empty() { + return None; + } + base_paths = Vec::new(); + for child_path in &children { + let tmp_puzzle = apply_choices(puzzle.clone(), child_path, &mut my_cache); + // !known_outcomes.contains(&tmp_puzzle) + if !my_cache.contains_value(&tmp_puzzle) { + base_paths.push(child_path.clone()); + } else { + //cache.remove(path); would be removed next layer anyways + } + if puzzle_gelöst(&tmp_puzzle) { + return Some(child_path.clone()); + } else { + known_outcomes.insert(tmp_puzzle); + } + } + println!("{:03} depth, {:06} children, {:07} known, {:07} cached, {:04} sec", depth, children.len(), known_outcomes.len(), my_cache.len_keys(), start.elapsed().as_secs()); + } +} + + +fn main() { + if let Some(dateiname) = env::args().nth(1) { + //let pause = std::time::Duration::from_millis((16.6*4.0) as u64); + + let puzzle = lade_puzzle(&dateiname); + if let Err(e) = puzzle { + println!("Error: {:?}", e); + return; + } + let puzzle = puzzle.unwrap(); + println!("{}", puzzle); + let solution = breadth_first_search(puzzle.clone()); + if let Some(solution) = solution { + println!("lösung: {:?}", solution); + println!("{}", apply_choices(puzzle, &solution, &mut MyMap::new())); + } else { + println!("keine lösung gefunden!"); + } + /* + let steps = [RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CW]; + for s in steps.iter() { + println!("action: {:?}", s); + puzzle = do_the_gravity(rotate(puzzle, s)); + println!("{}", puzzle); + std::thread::sleep(pause); + } + */ + } else { + println!("Bitte so aufrufen: ./aufgabe3 "); + } +} + + +#[cfg(test)] +#[allow(useless_vec)] +mod tests { + use super::*; + + #[test] + fn children() { + let children = build_children(&vec![vec![RotationDir::CW]]); + let expected = vec![vec![RotationDir::CW, RotationDir::CW], vec![RotationDir::CW, RotationDir::CCW]]; + if children == expected { + () + } else { + panic!() + } + } + + #[test] + fn t1() { + let puzzle = lade_puzzle("rotation1_03.txt"); + if puzzle.is_err() { + panic!(); + } + let puzzle = puzzle.unwrap(); + //println!("Puzzle: {}", puzzle); + assert!(breadth_first_search(puzzle).unwrap().len() == 6); + } + + #[test] + fn t2() { + let puzzle = lade_puzzle("rotation2_03.txt"); + if puzzle.is_err() { + panic!(); + } + let puzzle = puzzle.unwrap(); + //println!("Puzzle: {}", puzzle); + assert!(breadth_first_search(puzzle).unwrap().len() == 22); + } + + #[test] + fn t3() { + let puzzle = lade_puzzle("rotation3_03.txt"); + if puzzle.is_err() { + panic!(); + } + let puzzle = puzzle.unwrap(); + //println!("Puzzle: {}", puzzle); + assert!(breadth_first_search(puzzle).unwrap().len() == 90); + } +} + +#[cfg(test)] +mod benchs { + extern crate test; + + use super::*; + use self::test::Bencher; + + #[bench] + fn test1(b: &mut Bencher) { + b.iter(|| { + let puzzle = lade_puzzle("rotation1_03.txt"); + if puzzle.is_err() { + panic!(); + } + let puzzle = puzzle.unwrap(); + //println!("Puzzle: {}", puzzle); + breadth_first_search(puzzle); + }); + } + #[bench] + fn test2(b: &mut Bencher) { + b.iter(|| { + let puzzle = lade_puzzle("rotation2_03.txt"); + if puzzle.is_err() { + panic!(); + } + let puzzle = puzzle.unwrap(); + //println!("Puzzle: {}", puzzle); + breadth_first_search(puzzle); + }); + } + #[bench] + fn rotate_cw(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").unwrap(); + b.iter(|| { + rotate(puzzle.clone(), &test::black_box(RotationDir::CW)) + }) + } + #[bench] + fn rotate_ccw(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").unwrap(); + b.iter(|| { + rotate(puzzle.clone(), &test::black_box(RotationDir::CCW)) + }) + } + #[bench] + fn rotate_cw_grav(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").unwrap(); + b.iter(|| { + do_the_gravity(rotate(puzzle.clone(), &test::black_box(RotationDir::CW))) + }) + } + #[bench] + fn rotate_ccw_grav(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").unwrap(); + b.iter(|| { + do_the_gravity(rotate(puzzle.clone(), &test::black_box(RotationDir::CCW))) + }) + } +} + + +/* +fn apply_choices(mut puzzle: Puzzle, choices: &Vec) -> Puzzle { + for rotation in choices { + puzzle = do_the_gravity(rotate(puzzle, rotation)); + } + puzzle +} +*/ + +/* +fn cmp_vectors(small: &Vec, large: &Vec) -> bool { + if small.len() > large.len() { + false + } else { + let mut same = true; + for (index, value) in small.iter().enumerate() { + if value != &large[index] { + same = false; + } + } + same + } +} +*/ + +/* +fn breadth_first_search(puzzle: Puzzle) -> Option> { + let mut choices = vec![RotationDir::CW]; + let mut known_outcomes = HashSet::new(); + let mut banned_paths = HashSet::new(); + let mut cont_counter = 0; + let mut ban_counter = 0; + loop { + for path in &banned_paths { + if cmp_vectors(&path, &choices) { + //println!("banned, skip"); + ban_counter += 1; + choices = next_combination(choices); + continue; + } + } + if cont_counter % 100 == 0 && cont_counter > 0 { + println!("{:06} conts, {:06} ban-conts.", cont_counter, ban_counter); + } + //println!("trying {:?}", choices); + let new = apply_choices(puzzle.clone(), &choices); + if known_outcomes.contains(&new) { + cont_counter += 1; + banned_paths.insert(choices.clone()); + choices = next_combination(choices); + continue; + } + if puzzle_gelöst(&new) { + return Some(choices); + } + known_outcomes.insert(new); + choices = next_combination(choices); + } +} +*/ +/* +fn breadth_first_search(puzzle: Puzzle, mut known_outcomes: &mut HashSet, mut choices: Vec) -> Option> { + //println!("base: {:?}, known: {:?}", choices, known_outcomes.len()); + choices.push(RotationDir::CW); + let puzzle_cw = apply_choices(puzzle.clone(), &choices); + if known_outcomes.contains(&puzzle_cw) { + + } else { + known_outcomes.insert(puzzle_cw.clone()); + if puzzle_gelöst(&puzzle_cw) { + return Some(choices); + } else { + if let Some(solution) = breadth_first_search(puzzle.clone(), &mut known_outcomes, choices.clone()) { + return Some(solution); + } + } + } + + choices.pop().unwrap(); + choices.push(RotationDir::CCW); + let puzzle_cw = apply_choices(puzzle.clone(), &choices); + if known_outcomes.contains(&puzzle_cw) { + return None; + } else { + known_outcomes.insert(puzzle_cw.clone()); + if puzzle_gelöst(&puzzle_cw) { + return Some(choices); + } else { + if let Some(solution) = breadth_first_search(puzzle.clone(), &mut known_outcomes, choices.clone()) { + return Some(solution); + } else { + return None + } + } + } + + None +} +*/ + +/* +pub fn next_combination(choices: Vec) -> Vec { + let mut choices = choices; + if choices.iter().filter(|&x| x == &RotationDir::CCW).count() == choices.len() { + choices = choices.iter().map(|_| RotationDir::CW).collect(); + choices.push(RotationDir::CW); + choices + } else { + let mut rchoices = choices.clone(); + rchoices.reverse(); + let mut cw_index = 0; + for (index, c) in rchoices.iter().enumerate() { + if c == &RotationDir::CW { + cw_index = rchoices.len()-index-1; + break; + } + } + choices[cw_index] = RotationDir::CCW; + for mut item in choices.iter_mut().skip(cw_index+1) { + *item = RotationDir::CW; + } + choices + } +} +*/ + +/* + #[test] + fn a() { + let next = next_combination(vec![RotationDir::CW]); + let expected = vec![RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn b() { + let next = next_combination(vec![RotationDir::CCW]); + let expected = vec![RotationDir::CW, RotationDir::CW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn aa() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CW]); + let expected = vec![RotationDir::CW, RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn ab() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CCW]); + let expected = vec![RotationDir::CCW, RotationDir::CW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn ba() { + let next = next_combination(vec![RotationDir::CCW, RotationDir::CW]); + let expected = vec![RotationDir::CCW, RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn bb() { + let next = next_combination(vec![RotationDir::CCW, RotationDir::CCW]); + let expected = vec![RotationDir::CW, RotationDir::CW, RotationDir::CW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn aba() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CCW, RotationDir::CW]); + let expected = vec![RotationDir::CW, RotationDir::CCW, RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn abb() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CCW, RotationDir::CCW]); + let expected = vec![RotationDir::CCW, RotationDir::CW, RotationDir::CW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn abba() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW]); + let expected = vec![RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + }*/ diff --git a/Aufgabe3/src/main_vec_y.rs b/Aufgabe3/src/main_vec_y.rs new file mode 100644 index 0000000..3be6df6 --- /dev/null +++ b/Aufgabe3/src/main_vec_y.rs @@ -0,0 +1,721 @@ +#![feature(non_ascii_idents)] +#![feature(custom_derive)] +#![feature(test)] +#![feature(plugin)] + +#![plugin(clippy)] + +#[cfg(target_os = "linux")] +extern crate colored; +#[cfg(target_os = "linux")] +use colored::*; + +use std::collections::{HashSet, HashMap}; +use std::env; +use std::error::Error; +use std::fs::File; +use std::io::prelude::*; +use std::time::Instant; +use std::path::Path; + +#[derive(Clone, Copy, PartialEq)] +pub struct Punkt { + x: usize, + y: usize +} + +#[derive(PartialEq)] +enum Stabrichtung { + Horizontal, + Vertikal +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct Puzzle { + raster: Vec> +} + +#[cfg(target_os = "linux")] +impl std::fmt::Display for Puzzle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + for row in &self.raster { + write!(f, "\n")?; + for cell in row { + match *cell { + WALL => write!(f, "{}", format!("{:02} ", cell).black().bold())?, + NIX => write!(f, "{}", format!("{:02} ", cell).black().bold())?, + AUSGANG => write!(f, "{}", format!("{:02} ", cell).green().bold())?, + _ if cell % 5 == 0 => write!(f, "{}", format!("{:02} ", cell).red().bold())?, + _ if cell % 5 == 1 => write!(f, "{}", format!("{:02} ", cell).yellow().bold())?, + _ if cell % 5 == 2 => write!(f, "{}", format!("{:02} ", cell).blue().bold())?, + _ if cell % 5 == 3 => write!(f, "{}", format!("{:02} ", cell).purple().bold())?, + _ if cell % 5 == 4 => write!(f, "{}", format!("{:02} ", cell).cyan().bold())?, + _ => write!(f, "{:02} ", cell)? + }; + } + } + write!(f, "\n") + } +} +#[cfg(not(target_os = "linux"))] +impl std::fmt::Display for Puzzle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + for row in &self.raster { + write!(f, "\n")?; + for cell in row { + write!(f, "{:02} ", cell)?; + } + } + write!(f, "\n") + } +} + +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub enum RotationDir { + // clockwise + CW, + // counterclockwise + CCW +} + +const WALL: i8 = -3; +const NIX: i8 = -2; +const AUSGANG: i8 = -1; + + +pub fn lade_puzzle(dateiname: &str) -> Result { + let path = Path::new(dateiname); + let display = path.display(); + + // Open the path in read-only mode, returns `io::Result` + let mut file = match File::open(&path) { + // The `description` method of `io::Error` returns a string that + // describes the error + Err(why) => { return Err(format!("couldn't open {}: {}", display, + why.description())); }, + Ok(file) => file, + }; + + // Read the file contents into a string, returns `io::Result` + let mut s = String::new(); + if let Err(why) = file.read_to_string(&mut s) { + return Err(format!("couldn't read {}: {}", display, + why.description())); + } + let mut p = Puzzle { raster: Vec::new() }; + let lines: Vec<&str> = s.split('\n').collect(); + let seitenlänge = lines[0].parse().expect("erste Zeile keine Zahl"); + + for (index, line) in lines.iter().skip(1).enumerate() { + // zeile nach der letzten zeile erreicht + if index == seitenlänge { + break; + } + + let mut line_vec = Vec::new(); + for (x, c) in line.chars().enumerate() { + // ausgang + if c == ' ' && ((x == 0 || x == line.len()-1) || (index == 0 || index == seitenlänge-1)) { + line_vec.push(AUSGANG); + continue; + } + match c.to_digit(36) { + Some(digit) => line_vec.push(digit as i8), + // -1 == " " + None => { + match c { + '#' => line_vec.push(WALL), + ' ' => line_vec.push(NIX), + _ => panic!("unbekannter buchstabe {:?}", c) + } + } + } + } + p.raster.push(line_vec); + //println!("{:?}: {:?}", index, line); + } + Ok(p) +} + +pub fn rotate(puzzle: Puzzle, dir: &RotationDir) -> Puzzle { + // seitenlänge + let s = puzzle.raster.len(); + let mut roted_raster = puzzle.raster.clone(); + macro_rules! set { + ($x:expr, $y:expr, $new:expr) => ( + roted_raster[$y][$x] = $new; + ) + } + + + for (y, row) in puzzle.raster.iter().enumerate() { + for (x, cell) in row.iter().enumerate() { + match *dir { + RotationDir::CW => set!(s-y-1, x, *cell), + RotationDir::CCW => set!(y, s-x-1, *cell) + } + } + } + + Puzzle { raster: roted_raster } +} + +pub fn get_stäbchen(raster: &[Vec]) -> HashMap> { + let mut stäbchen: HashMap> = HashMap::new(); + + for (y, row) in raster.iter().enumerate() { + for (x, cell) in row.iter().enumerate() { + if !cell.is_negative() { + let cell = *cell as u32; + let value = stäbchen.entry(cell).or_insert_with(Vec::new); + value.push(Punkt { x: x, y: y }); + } + } + } + + stäbchen +} + +fn get_stäbchen_richtung(stab: &[Punkt]) -> Stabrichtung { + if stab.len() > 1 && stab[0].x == stab[1].x { + Stabrichtung::Vertikal + } else { + Stabrichtung::Horizontal + } +} + +pub fn do_the_gravity(mut puzzle: Puzzle) -> Puzzle { + // seitenlänge + let s = puzzle.raster.len(); + macro_rules! get { + ($x:expr, $y:expr) => ( + puzzle.raster[$y][$x]; + ) + } + macro_rules! set { + ($x:expr, $y:expr, $new:expr) => ( + puzzle.raster[$y][$x] = $new; + ) + } + + let mut stäbchen1 = get_stäbchen(&puzzle.raster); + let mut stäbchen2 = HashMap::new(); + while stäbchen1 != stäbchen2 { + //println!("sloop"); + stäbchen1 = get_stäbchen(&puzzle.raster); + //println!("{:?}", stäbchen1); + for (index, stab) in &stäbchen1 { + let richtung = get_stäbchen_richtung(stab); + let stab = stab.iter(); + let mut move_possible = stab.clone() + .fold(true, |acc, &x| + // falls der Stab schon direkt über dem Rahmen ist, + // sollte er nicht bewegt werden + if x.y >= s-2 + // falls der Stab (horizontal) liegt und unter ihm kein Platz ist, + // sollte er nicht bewegt werden + || (richtung == Stabrichtung::Horizontal && ((get!(x.x, x.y+1) != NIX && get!(x.x, x.y+1) != AUSGANG))) { false } else { acc }); + + // falls der Stab (vertikal) steht, + if richtung == Stabrichtung::Vertikal { + // und der unterste Teil des Stabes + let lowest_punkt = stab.clone().max_by_key(|x| x.y).unwrap(); + // über/auf dem unteren Begrenzung liegt + if lowest_punkt.y+1 <= s-1 { + // und unter ihm bereits ein Stab liegt, + if get!(lowest_punkt.x, lowest_punkt.y+1) >= 0 { + // sollte er nicht bewegt werden + move_possible = false; + // sonderfall: unter dem Teil des Stabes liegt der Ausgang + } else if get!(lowest_punkt.x, lowest_punkt.y+1) == AUSGANG { + //println!("Puzzle ist möglich!"); + move_possible = true; + } + } + } + + if move_possible { + let index = *index as i8; + let stab: Vec<&Punkt> = stab.collect(); + for p in &stab { + set!(p.x, p.y, NIX); + } + for p in &stab { + set!(p.x, p.y+1, index); + } + } + } + stäbchen2 = stäbchen1; + stäbchen1 = get_stäbchen(&puzzle.raster); + //println!("eloop"); + } + //println!("EOG"); + puzzle +} + +fn puzzle_gelöst(puzzle: &Puzzle) -> bool { + puzzle.raster + .last().unwrap().iter() + .fold( + false, |acc, &x| + // falls dieses Element zu einem Stab gehört, + // wird acc auf true gesetzt + if x >= 0 { true } + else { acc }) +} + +fn apply_choices(puzzle: Puzzle, choices: &[RotationDir], cache: &mut HashMap, Puzzle>) -> Puzzle { + if cache.contains_key(choices) { + //println!("{:?} cached", choices); + cache.get(choices).unwrap().clone() + } else if choices.len() == 1 { + //println!("len one"); + do_the_gravity(rotate(puzzle, &choices[0])) + } else { + let mut choices = Vec::from(choices); + let last_action = choices.pop().unwrap(); + let before_last = apply_choices(puzzle, &choices, cache); + //println!("before: {}", before_last); + let applied = do_the_gravity(rotate(before_last, &last_action)); + //println!("applied: {}", applied); + choices.push(last_action); + cache.insert(choices, applied.clone()); + applied + } +} + +pub fn build_children(base: &[Vec]) -> Vec> { + let mut children = Vec::new(); + for path in base { + let mut c1 = path.clone(); + let mut c2 = path.clone(); + c1.push(RotationDir::CW); + c2.push(RotationDir::CCW); + children.push(c1); + children.push(c2); + } + children +} + +pub fn breadth_first_search(puzzle: Puzzle) -> Option> { + let mut cache = HashMap::new(); + let mut known_outcomes = HashSet::new(); + let mut base_paths = vec![vec![]]; + let mut depth = 0; + let start = Instant::now(); + let mut counter = 0; + loop { + { + let paths: Vec> = cache.keys().cloned().collect(); + for path in paths { + if path.len() < depth { + cache.remove(&path); + } + } + } + depth += 1; + + let children = build_children(&base_paths); + if children.is_empty() { + return None; + } + base_paths = Vec::new(); + for child_path in &children { + counter += 1; + let tmp_puzzle = apply_choices(puzzle.clone(), child_path, &mut cache); + if !known_outcomes.contains(&tmp_puzzle) { + if counter % 1001 == 0 { + println!("{}", tmp_puzzle); + } + base_paths.push(child_path.clone()); + } else { + } + if puzzle_gelöst(&tmp_puzzle) { + return Some(child_path.clone()); + } else { + known_outcomes.insert(tmp_puzzle); + } + } + println!("{:03} depth, {:06} children, {:07} known, {:07} cached, {:04} sec", depth, children.len(), known_outcomes.len(), cache.len(), start.elapsed().as_secs()); + counter = 1; + } +} + + +fn main() { + if let Some(dateiname) = env::args().nth(1) { + //let pause = std::time::Duration::from_millis((16.6*4.0) as u64); + + let puzzle = lade_puzzle(&dateiname); + if let Err(e) = puzzle { + println!("Error: {:?}", e); + return; + } + let puzzle = puzzle.unwrap(); + println!("{}", puzzle); + let solution = breadth_first_search(puzzle.clone()); + if let Some(solution) = solution { + println!("lösung: {:?}", solution); + println!("{}", apply_choices(puzzle, &solution, &mut HashMap::new())); + } else { + println!("keine lösung gefunden!"); + } + /* + let steps = [RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CW]; + for s in steps.iter() { + println!("action: {:?}", s); + puzzle = do_the_gravity(rotate(puzzle, s)); + println!("{}", puzzle); + std::thread::sleep(pause); + } + */ + } else { + println!("Bitte so aufrufen: ./aufgabe3 "); + } +} + + +#[cfg(test)] +#[allow(useless_vec)] +mod tests { + use super::*; + + #[test] + fn children() { + let children = build_children(&vec![vec![RotationDir::CW]]); + let expected = vec![vec![RotationDir::CW, RotationDir::CW], vec![RotationDir::CW, RotationDir::CCW]]; + if children == expected { + () + } else { + panic!() + } + } + + #[test] + fn t1() { + let puzzle = lade_puzzle("rotation1_03.txt"); + if puzzle.is_err() { + panic!(); + } + let puzzle = puzzle.unwrap(); + //println!("Puzzle: {}", puzzle); + assert!(breadth_first_search(puzzle).unwrap().len() == 6); + } + + #[test] + fn t2() { + let puzzle = lade_puzzle("rotation2_03.txt"); + if puzzle.is_err() { + panic!(); + } + let puzzle = puzzle.unwrap(); + //println!("Puzzle: {}", puzzle); + assert!(breadth_first_search(puzzle).unwrap().len() == 22); + } + + #[test] + fn t3() { + let puzzle = lade_puzzle("rotation3_03.txt"); + if puzzle.is_err() { + panic!(); + } + let puzzle = puzzle.unwrap(); + //println!("Puzzle: {}", puzzle); + assert!(breadth_first_search(puzzle).unwrap().len() == 90); + } +} + +#[cfg(test)] +mod benchs { + extern crate test; + + use super::*; + use self::test::Bencher; + + #[bench] + fn test1(b: &mut Bencher) { + b.iter(|| { + let puzzle = lade_puzzle("rotation1_03.txt"); + if puzzle.is_err() { + panic!(); + } + let puzzle = puzzle.unwrap(); + //println!("Puzzle: {}", puzzle); + breadth_first_search(puzzle); + }); + } + #[bench] + fn test2(b: &mut Bencher) { + b.iter(|| { + let puzzle = lade_puzzle("rotation2_03.txt"); + if puzzle.is_err() { + panic!(); + } + let puzzle = puzzle.unwrap(); + //println!("Puzzle: {}", puzzle); + breadth_first_search(puzzle); + }); + } + #[bench] + fn rotate_cw(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").unwrap(); + b.iter(|| { + rotate(puzzle.clone(), &test::black_box(RotationDir::CW)) + }) + } + #[bench] + fn rotate_ccw(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").unwrap(); + b.iter(|| { + rotate(puzzle.clone(), &test::black_box(RotationDir::CCW)) + }) + } + #[bench] + fn rotate_cw_grav(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").unwrap(); + b.iter(|| { + do_the_gravity(rotate(puzzle.clone(), &test::black_box(RotationDir::CW))) + }) + } + #[bench] + fn rotate_ccw_grav(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").unwrap(); + b.iter(|| { + do_the_gravity(rotate(puzzle.clone(), &test::black_box(RotationDir::CCW))) + }) + } + #[bench] + fn get_stäbe(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").unwrap(); + b.iter(|| { + get_stäbchen(&puzzle.raster) + }) + } +} + + +/* +fn apply_choices(mut puzzle: Puzzle, choices: &Vec) -> Puzzle { + for rotation in choices { + puzzle = do_the_gravity(rotate(puzzle, rotation)); + } + puzzle +} +*/ + +/* +fn cmp_vectors(small: &Vec, large: &Vec) -> bool { + if small.len() > large.len() { + false + } else { + let mut same = true; + for (index, value) in small.iter().enumerate() { + if value != &large[index] { + same = false; + } + } + same + } +} +*/ + +/* +fn breadth_first_search(puzzle: Puzzle) -> Option> { + let mut choices = vec![RotationDir::CW]; + let mut known_outcomes = HashSet::new(); + let mut banned_paths = HashSet::new(); + let mut cont_counter = 0; + let mut ban_counter = 0; + loop { + for path in &banned_paths { + if cmp_vectors(&path, &choices) { + //println!("banned, skip"); + ban_counter += 1; + choices = next_combination(choices); + continue; + } + } + if cont_counter % 100 == 0 && cont_counter > 0 { + println!("{:06} conts, {:06} ban-conts.", cont_counter, ban_counter); + } + //println!("trying {:?}", choices); + let new = apply_choices(puzzle.clone(), &choices); + if known_outcomes.contains(&new) { + cont_counter += 1; + banned_paths.insert(choices.clone()); + choices = next_combination(choices); + continue; + } + if puzzle_gelöst(&new) { + return Some(choices); + } + known_outcomes.insert(new); + choices = next_combination(choices); + } +} +*/ +/* +fn breadth_first_search(puzzle: Puzzle, mut known_outcomes: &mut HashSet, mut choices: Vec) -> Option> { + //println!("base: {:?}, known: {:?}", choices, known_outcomes.len()); + choices.push(RotationDir::CW); + let puzzle_cw = apply_choices(puzzle.clone(), &choices); + if known_outcomes.contains(&puzzle_cw) { + + } else { + known_outcomes.insert(puzzle_cw.clone()); + if puzzle_gelöst(&puzzle_cw) { + return Some(choices); + } else { + if let Some(solution) = breadth_first_search(puzzle.clone(), &mut known_outcomes, choices.clone()) { + return Some(solution); + } + } + } + + choices.pop().unwrap(); + choices.push(RotationDir::CCW); + let puzzle_cw = apply_choices(puzzle.clone(), &choices); + if known_outcomes.contains(&puzzle_cw) { + return None; + } else { + known_outcomes.insert(puzzle_cw.clone()); + if puzzle_gelöst(&puzzle_cw) { + return Some(choices); + } else { + if let Some(solution) = breadth_first_search(puzzle.clone(), &mut known_outcomes, choices.clone()) { + return Some(solution); + } else { + return None + } + } + } + + None +} +*/ + +/* +pub fn next_combination(choices: Vec) -> Vec { + let mut choices = choices; + if choices.iter().filter(|&x| x == &RotationDir::CCW).count() == choices.len() { + choices = choices.iter().map(|_| RotationDir::CW).collect(); + choices.push(RotationDir::CW); + choices + } else { + let mut rchoices = choices.clone(); + rchoices.reverse(); + let mut cw_index = 0; + for (index, c) in rchoices.iter().enumerate() { + if c == &RotationDir::CW { + cw_index = rchoices.len()-index-1; + break; + } + } + choices[cw_index] = RotationDir::CCW; + for mut item in choices.iter_mut().skip(cw_index+1) { + *item = RotationDir::CW; + } + choices + } +} +*/ + +/* + #[test] + fn a() { + let next = next_combination(vec![RotationDir::CW]); + let expected = vec![RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn b() { + let next = next_combination(vec![RotationDir::CCW]); + let expected = vec![RotationDir::CW, RotationDir::CW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn aa() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CW]); + let expected = vec![RotationDir::CW, RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn ab() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CCW]); + let expected = vec![RotationDir::CCW, RotationDir::CW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn ba() { + let next = next_combination(vec![RotationDir::CCW, RotationDir::CW]); + let expected = vec![RotationDir::CCW, RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn bb() { + let next = next_combination(vec![RotationDir::CCW, RotationDir::CCW]); + let expected = vec![RotationDir::CW, RotationDir::CW, RotationDir::CW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn aba() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CCW, RotationDir::CW]); + let expected = vec![RotationDir::CW, RotationDir::CCW, RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn abb() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CCW, RotationDir::CCW]); + let expected = vec![RotationDir::CCW, RotationDir::CW, RotationDir::CW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn abba() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW]); + let expected = vec![RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + }*/ diff --git a/Aufgabe3/src/main_yes_bi.rs b/Aufgabe3/src/main_yes_bi.rs new file mode 100644 index 0000000..26277bf --- /dev/null +++ b/Aufgabe3/src/main_yes_bi.rs @@ -0,0 +1,687 @@ +#![feature(non_ascii_idents)] +#![feature(custom_derive)] +#![feature(test)] +#![feature(plugin)] + +#![plugin(clippy)] + +extern crate colored; +use colored::*; + + +use std::collections::{HashSet, HashMap}; +use std::env; +use std::error::Error; +use std::fs::File; +use std::io::prelude::*; +use std::time::Instant; +use std::path::Path; + + +#[derive(PartialEq)] +enum Stabrichtung { + Horizontal, + Vertikal +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq)] +pub struct Punkt { + x: usize, + y: usize +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct Puzzle { + raster: Vec> +} + +impl std::fmt::Display for Puzzle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + for row in &self.raster { + write!(f, "\n")?; + for cell in row { + match *cell { + WALL => write!(f, "{}", format!("{:02} ", cell).black())?, + NIX => write!(f, "{}", format!("{:02} ", cell).black())?, + AUSGANG => write!(f, "{}", format!("{:02} ", cell).green())?, + _ if cell % 5 == 0 => write!(f, "{}", format!("{:02} ", cell).red())?, + _ if cell % 5 == 1 => write!(f, "{}", format!("{:02} ", cell).yellow())?, + _ if cell % 5 == 2 => write!(f, "{}", format!("{:02} ", cell).blue())?, + _ if cell % 5 == 3 => write!(f, "{}", format!("{:02} ", cell).purple())?, + _ if cell % 5 == 4 => write!(f, "{}", format!("{:02} ", cell).cyan())?, + _ => write!(f, "{:02} ", cell)? + }; + } + } + write!(f, "\n") + } +} + +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub enum RotationDir { + // clockwise + CW, + // counterclockwise + CCW +} + +const WALL: i64 = -3; +const NIX: i64 = -2; +const AUSGANG: i64 = -1; + + +pub fn lade_puzzle(dateiname: &str) -> Result { + let path = Path::new(dateiname); + let display = path.display(); + + // Open the path in read-only mode, returns `io::Result` + let mut file = match File::open(&path) { + // The `description` method of `io::Error` returns a string that + // describes the error + Err(why) => { return Err(format!("couldn't open {}: {}", display, + why.description())); }, + Ok(file) => file, + }; + + // Read the file contents into a string, returns `io::Result` + let mut s = String::new(); + if let Err(why) = file.read_to_string(&mut s) { + return Err(format!("couldn't read {}: {}", display, + why.description())); + } + let mut p = Puzzle { raster: Vec::new() }; + let lines: Vec<&str> = s.split('\n').collect(); + let seitenlänge = lines[0].parse().expect("erste Zeile keine Zahl"); + + for (index, line) in lines.iter().skip(1).enumerate() { + // zeile nach der letzten zeile erreicht + if index == seitenlänge { + break; + } + + let mut line_vec = Vec::new(); + for (x, c) in line.chars().enumerate() { + // ausgang + if c == ' ' && ((x == 0 || x == line.len()-1) || (index == 0 || index == seitenlänge-1)) { + line_vec.push(AUSGANG); + continue; + } + match c.to_digit(10) { + Some(digit) => line_vec.push(digit as i64), + // -1 == " " + None => { + match c { + '#' => line_vec.push(WALL), + ' ' => line_vec.push(NIX), + _ => panic!("unbekannter buchstabe {:?}", c) + } + } + } + } + p.raster.push(line_vec); + //println!("{:?}: {:?}", index, line); + } + Ok(p) +} + +pub fn rotate(puzzle: Puzzle, dir: &RotationDir) -> Puzzle { + // seitenlänge + let s = puzzle.raster.len(); + let mut raster = Vec::new(); + for r in &puzzle.raster { + let mut row_vec = Vec::new(); + for c in r { + row_vec.push(*c); + } + raster.push(row_vec); + } + let mut roted_raster = raster.clone(); + macro_rules! set { + ($x:expr, $y:expr, $new:expr) => ( + roted_raster[$y][$x] = $new; + ) + } + + let mut p = puzzle; + + let mut y = 0; + for row in raster { + for (x, cell) in row.iter().enumerate() { + match *dir { + RotationDir::CW => set!(s-y-1, x, *cell), + RotationDir::CCW => set!(y, s-x-1, *cell) + } + } + y += 1; + } + p.raster = roted_raster; + p +} + +fn get_stäbchen(raster: &[Vec]) -> HashMap> { + let mut stäbchen: HashMap> = HashMap::new(); + + for (y, row) in raster.iter().enumerate() { + for (x, cell) in row.iter().enumerate() { + if !cell.is_negative() { + let cell = *cell as u64; + let value = stäbchen.entry(cell).or_insert_with(Vec::new); + value.push(Punkt { x: x, y: y }); + } + } + } + + stäbchen +} + +fn get_stäbchen_richtung(stab: &[Punkt]) -> Stabrichtung { + if stab.len() == 1 { + // egal + return Stabrichtung::Vertikal; + } + if stab[0].x == stab[1].x { + Stabrichtung::Vertikal + } else { + Stabrichtung::Horizontal + } +} + +pub fn do_the_gravity(mut puzzle: Puzzle) -> Puzzle { + // seitenlänge + let s = puzzle.raster.len(); + macro_rules! get { + ($x:expr, $y:expr) => ( + puzzle.raster[$y][$x]; + ) + } + macro_rules! set { + ($x:expr, $y:expr, $new:expr) => ( + puzzle.raster[$y][$x] = $new; + ) + } + + let mut stäbchen1 = get_stäbchen(&puzzle.raster); + let mut stäbchen2 = HashMap::new(); + while stäbchen1 != stäbchen2 { + //println!("sloop"); + stäbchen1 = get_stäbchen(&puzzle.raster); + //println!("{:?}", stäbchen1); + for (index, stab) in &stäbchen1 { + let mut move_possible = true; + let richtung = get_stäbchen_richtung(stab); + let mut highest_y = 0; + let mut lowest_punkt = &stab[0]; + for p in stab { + if p.y == s-2 { + move_possible = false; + } + if p.y+1 <= s-1 && ((get!(p.x, p.y+1) != NIX && get!(p.x, p.y+1) != AUSGANG) && richtung == Stabrichtung::Horizontal) { + move_possible = false; + } + if richtung == Stabrichtung::Vertikal && p.y > highest_y { + highest_y = p.y; + lowest_punkt = p; + } + } + if richtung == Stabrichtung::Vertikal && lowest_punkt.y+1 <= s-1 { + if get!(lowest_punkt.x, lowest_punkt.y+1) >= 0 { + //println!("{:?} blocked-low because of {:?}, {:?}: {:?}", index, + // lowest_punkt.x, + // lowest_punkt.y+1, + // get!(lowest_punkt.x, lowest_punkt.y+1)); + move_possible = false; + } else if highest_y == s-2 && get!(lowest_punkt.x, lowest_punkt.y+1) == AUSGANG { + //println!("Puzzle ist möglich!"); + move_possible = true; + } + } + + + if move_possible { // checked + let new_stab = stab; + for p in stab { + set!(p.x, p.y, NIX); + } + let index = *index as i64; + for p in new_stab { + set!(p.x, p.y+1, index); + } + } + } + stäbchen2 = stäbchen1; + stäbchen1 = get_stäbchen(&puzzle.raster); + //println!("eloop"); + } + //println!("EOG"); + puzzle +} + +fn puzzle_gelöst(puzzle: &Puzzle) -> bool { + let mut ausgang_gefunden = false; + for row in &puzzle.raster { + for cell in row { + if *cell == AUSGANG { + ausgang_gefunden = true; + } + } + } + !ausgang_gefunden +} + +fn apply_choices(puzzle: Puzzle, choices: &[RotationDir], cache: &mut HashMap, Puzzle>) -> Puzzle { + if cache.contains_key(choices) { + //println!("{:?} cached", choices); + cache.get(choices).unwrap().clone() + } else if choices.len() == 1 { + //println!("len one"); + do_the_gravity(rotate(puzzle, &choices[0])) + } else { + let mut choices = Vec::from(choices); + let last_action = choices.pop().unwrap(); + let before_last = apply_choices(puzzle, &choices, cache); + //println!("before: {}", before_last); + let applied = do_the_gravity(rotate(before_last, &last_action)); + //println!("applied: {}", applied); + choices.push(last_action); + cache.insert(choices, applied.clone()); + applied + } +} + + + +pub fn build_children(base: &[Vec]) -> Vec> { + let mut children = Vec::new(); + for path in base { + let mut c1 = path.clone(); + let mut c2 = path.clone(); + c1.push(RotationDir::CW); + c2.push(RotationDir::CCW); + children.push(c1); + children.push(c2); + } + children +} + +pub fn breadth_first_search(puzzle: Puzzle) -> Option> { + let mut cache = HashMap::new(); + let mut known_outcomes = HashSet::new(); + let mut base_paths = vec![vec![]]; + let mut depth = 0; + let start = Instant::now(); + loop { + depth += 1; + + let children = build_children(&base_paths); + if children.is_empty() { + return None; + } + base_paths = Vec::new(); + for child_path in &children { + let tmp_puzzle = apply_choices(puzzle.clone(), child_path, &mut cache); + if !known_outcomes.contains(&tmp_puzzle) { + base_paths.push(child_path.clone()); + } else { + cache.remove(child_path); // save that RAM! + } + if puzzle_gelöst(&tmp_puzzle) { + return Some(child_path.clone()); + } else { + known_outcomes.insert(tmp_puzzle); + } + } + println!("{:03} depth, {:06} children, {:07} known, {:07} cached, {:04} sec", depth, children.len(), known_outcomes.len(), cache.len(), start.elapsed().as_secs()); + } +} + + + +fn main() { + if let Some(dateiname) = env::args().nth(1) { + //let pause = std::time::Duration::from_millis((16.6*4.0) as u64); + + let puzzle = lade_puzzle(&dateiname); + if let Err(e) = puzzle { + println!("Error: {:?}", e); + return; + } + let puzzle = puzzle.unwrap(); + println!("Puzzle: {}", puzzle); + println!("{:?}", puzzle); + //let puzzle = Puzzle { seitenlänge: 10, raster: vec![vec![-3, -3, -3, -3, -3, -3, -3, -3, -3, -3], vec![-3, -2, -2, -2, -2, -2, -2, -2, -2, -3], vec![-3, -2, -2, -2, -2, -2, -2, -2, -2, -3], vec![-3, -2, -2, -2, -2, -2, -2, -2, -2, -3], vec![-3, -2, -2, -2, -2, 7, 7, 7, 5, -3], vec![-3, -2, -2, -2, 1, 1, -2, -2, 5, -3], vec![-3, -2, -2, -2, 2, -2, 8, 8, 8, -3], vec![-3, 9, 9, 0, 2, 3, 3, -2, -2, -3], vec![-3, 4, 4, 0, 6, 6, 6, 6, 6, -3], vec![-3, -3, -3, -3, -3, -1, -3, -3, -3, -3]] }; + /* + //let puzzle = do_the_gravity(rotate(puzzle, &RotationDir::CW)); + let puzzle_a = apply_choices(puzzle.clone(), &vec![RotationDir::CW, RotationDir::CW, RotationDir::CCW], &mut HashMap::new()); + println!("Puzzlea: {}", puzzle_a); + let puzzle_b = do_the_gravity(rotate(puzzle.clone(), &RotationDir::CCW)); + let puzzle_b = do_the_gravity(rotate(puzzle_b, &RotationDir::CCW)); + let puzzle_b = do_the_gravity(rotate(puzzle_b, &RotationDir::CCW)); + let puzzle_b = do_the_gravity(rotate(puzzle_b, &RotationDir::CCW)); + let puzzle_b = do_the_gravity(rotate(puzzle_b, &RotationDir::CW)); + let puzzle_b = do_the_gravity(rotate(puzzle_b, &RotationDir::CCW)); + println!("Puzzleb: {}", puzzle_b); + */ + let solution = breadth_first_search(puzzle); + if let Some(solution) = solution { + println!("lösung: {:?}", solution); + } else { + println!("keine lösung gefunden!"); + } + /* + let steps = [RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CW]; + for s in steps.iter() { + println!("action: {:?}", s); + puzzle = do_the_gravity(rotate(puzzle, s)); + println!("{}", puzzle); + std::thread::sleep(pause); + } + */ + } else { + println!("Bitte so aufrufen: ./aufgabe3 "); + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn children() { + let children = build_children(&vec![vec![RotationDir::CW]]); + let expected = vec![vec![RotationDir::CW, RotationDir::CW], vec![RotationDir::CW, RotationDir::CCW]]; + if children == expected { + () + } else { + panic!() + } + } +} + +#[cfg(test)] +mod benchs { + extern crate test; + + use super::*; + use self::test::Bencher; + + #[bench] + fn test1(b: &mut Bencher) { + b.iter(|| { + let puzzle = lade_puzzle("rotation1_03.txt"); + if puzzle.is_err() { + panic!(); + } + let puzzle = puzzle.unwrap(); + //println!("Puzzle: {}", puzzle); + breadth_first_search(puzzle); + }); + } + #[bench] + fn test2(b: &mut Bencher) { + b.iter(|| { + let puzzle = lade_puzzle("rotation2_03.txt"); + if puzzle.is_err() { + panic!(); + } + let puzzle = puzzle.unwrap(); + //println!("Puzzle: {}", puzzle); + breadth_first_search(puzzle); + }); + } + #[bench] + fn rotate_cw(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").unwrap(); + b.iter(|| { + rotate(puzzle.clone(), &test::black_box(RotationDir::CW)) + }) + } + #[bench] + fn rotate_ccw(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").unwrap(); + b.iter(|| { + rotate(puzzle.clone(), &test::black_box(RotationDir::CCW)) + }) + } + #[bench] + fn rotate_cw_grav(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").unwrap(); + b.iter(|| { + do_the_gravity(rotate(puzzle.clone(), &test::black_box(RotationDir::CW))) + }) + } + #[bench] + fn rotate_ccw_grav(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").unwrap(); + b.iter(|| { + do_the_gravity(rotate(puzzle.clone(), &test::black_box(RotationDir::CCW))) + }) + } +} + + +/* +fn apply_choices(mut puzzle: Puzzle, choices: &Vec) -> Puzzle { + for rotation in choices { + puzzle = do_the_gravity(rotate(puzzle, rotation)); + } + puzzle +} +*/ + +/* +fn cmp_vectors(small: &Vec, large: &Vec) -> bool { + if small.len() > large.len() { + false + } else { + let mut same = true; + for (index, value) in small.iter().enumerate() { + if value != &large[index] { + same = false; + } + } + same + } +} +*/ + +/* +fn breadth_first_search(puzzle: Puzzle) -> Option> { + let mut choices = vec![RotationDir::CW]; + let mut known_outcomes = HashSet::new(); + let mut banned_paths = HashSet::new(); + let mut cont_counter = 0; + let mut ban_counter = 0; + loop { + for path in &banned_paths { + if cmp_vectors(&path, &choices) { + //println!("banned, skip"); + ban_counter += 1; + choices = next_combination(choices); + continue; + } + } + if cont_counter % 100 == 0 && cont_counter > 0 { + println!("{:06} conts, {:06} ban-conts.", cont_counter, ban_counter); + } + //println!("trying {:?}", choices); + let new = apply_choices(puzzle.clone(), &choices); + if known_outcomes.contains(&new) { + cont_counter += 1; + banned_paths.insert(choices.clone()); + choices = next_combination(choices); + continue; + } + if puzzle_gelöst(&new) { + return Some(choices); + } + known_outcomes.insert(new); + choices = next_combination(choices); + } +} +*/ +/* +fn breadth_first_search(puzzle: Puzzle, mut known_outcomes: &mut HashSet, mut choices: Vec) -> Option> { + //println!("base: {:?}, known: {:?}", choices, known_outcomes.len()); + choices.push(RotationDir::CW); + let puzzle_cw = apply_choices(puzzle.clone(), &choices); + if known_outcomes.contains(&puzzle_cw) { + + } else { + known_outcomes.insert(puzzle_cw.clone()); + if puzzle_gelöst(&puzzle_cw) { + return Some(choices); + } else { + if let Some(solution) = breadth_first_search(puzzle.clone(), &mut known_outcomes, choices.clone()) { + return Some(solution); + } + } + } + + choices.pop().unwrap(); + choices.push(RotationDir::CCW); + let puzzle_cw = apply_choices(puzzle.clone(), &choices); + if known_outcomes.contains(&puzzle_cw) { + return None; + } else { + known_outcomes.insert(puzzle_cw.clone()); + if puzzle_gelöst(&puzzle_cw) { + return Some(choices); + } else { + if let Some(solution) = breadth_first_search(puzzle.clone(), &mut known_outcomes, choices.clone()) { + return Some(solution); + } else { + return None + } + } + } + + None +} +*/ + +/* +pub fn next_combination(choices: Vec) -> Vec { + let mut choices = choices; + if choices.iter().filter(|&x| x == &RotationDir::CCW).count() == choices.len() { + choices = choices.iter().map(|_| RotationDir::CW).collect(); + choices.push(RotationDir::CW); + choices + } else { + let mut rchoices = choices.clone(); + rchoices.reverse(); + let mut cw_index = 0; + for (index, c) in rchoices.iter().enumerate() { + if c == &RotationDir::CW { + cw_index = rchoices.len()-index-1; + break; + } + } + choices[cw_index] = RotationDir::CCW; + for mut item in choices.iter_mut().skip(cw_index+1) { + *item = RotationDir::CW; + } + choices + } +} +*/ + +/* + #[test] + fn a() { + let next = next_combination(vec![RotationDir::CW]); + let expected = vec![RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn b() { + let next = next_combination(vec![RotationDir::CCW]); + let expected = vec![RotationDir::CW, RotationDir::CW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn aa() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CW]); + let expected = vec![RotationDir::CW, RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn ab() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CCW]); + let expected = vec![RotationDir::CCW, RotationDir::CW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn ba() { + let next = next_combination(vec![RotationDir::CCW, RotationDir::CW]); + let expected = vec![RotationDir::CCW, RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn bb() { + let next = next_combination(vec![RotationDir::CCW, RotationDir::CCW]); + let expected = vec![RotationDir::CW, RotationDir::CW, RotationDir::CW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn aba() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CCW, RotationDir::CW]); + let expected = vec![RotationDir::CW, RotationDir::CCW, RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn abb() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CCW, RotationDir::CCW]); + let expected = vec![RotationDir::CCW, RotationDir::CW, RotationDir::CW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn abba() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW]); + let expected = vec![RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + }*/ diff --git a/Aufgabe3/src/main_yes_s.rs b/Aufgabe3/src/main_yes_s.rs new file mode 100644 index 0000000..c0eec7b --- /dev/null +++ b/Aufgabe3/src/main_yes_s.rs @@ -0,0 +1,695 @@ +#![feature(non_ascii_idents)] +#![feature(custom_derive)] +#![feature(test)] +#![feature(plugin)] + +#![plugin(clippy)] + +#[macro_use] +extern crate lazy_static; +extern crate colored; +use colored::*; + +use std::collections::HashMap; +use std::env; +use std::error::Error; +use std::fs::File; +use std::io::prelude::*; +use std::time::Instant; +use std::path::Path; + + +#[derive(PartialEq)] +enum Stabrichtung { + Horizontal, + Vertikal +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq)] +pub struct Punkt { + x: usize, + y: usize +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct Puzzle { + seitenlänge: usize, + raster: Vec> +} + +impl std::fmt::Display for Puzzle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + for row in &self.raster { + write!(f, "\n")?; + for cell in row { + match *cell { + WALL => write!(f, "{}", format!("{:02} ", cell).black())?, + NIX => write!(f, "{}", format!("{:02} ", cell).black())?, + AUSGANG => write!(f, "{}", format!("{:02} ", cell).green())?, + _ if cell % 5 == 0 => write!(f, "{}", format!("{:02} ", cell).red())?, + _ if cell % 5 == 1 => write!(f, "{}", format!("{:02} ", cell).yellow())?, + _ if cell % 5 == 2 => write!(f, "{}", format!("{:02} ", cell).blue())?, + _ if cell % 5 == 3 => write!(f, "{}", format!("{:02} ", cell).purple())?, + _ if cell % 5 == 4 => write!(f, "{}", format!("{:02} ", cell).cyan())?, + _ => write!(f, "{:02} ", cell)? + }; + } + } + write!(f, "\n") + } +} + +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub enum RotationDir { + // clockwise + CW, + // counterclockwise + CCW +} + +const WALL: i64 = -3; +const NIX: i64 = -2; +const AUSGANG: i64 = -1; + + +pub fn lade_puzzle(dateiname: &str) -> Result { + let path = Path::new(dateiname); + let display = path.display(); + + // Open the path in read-only mode, returns `io::Result` + let mut file = match File::open(&path) { + // The `description` method of `io::Error` returns a string that + // describes the error + Err(why) => { return Err(format!("couldn't open {}: {}", display, + why.description())); }, + Ok(file) => file, + }; + + // Read the file contents into a string, returns `io::Result` + let mut s = String::new(); + if let Err(why) = file.read_to_string(&mut s) { + return Err(format!("couldn't read {}: {}", display, + why.description())); + } + let mut p = Puzzle { seitenlänge: 0, raster: Vec::new() }; + let lines: Vec<&str> = s.split('\n').collect(); + + for (index, line) in lines.iter().enumerate() { + if index == 0 { + let seitenlänge = line.parse().unwrap(); + p.seitenlänge = seitenlänge; + continue; + } + let index = index-1; + if index == p.seitenlänge { + break; + } + + let mut line_vec = Vec::new(); + for (x, c) in line.chars().enumerate() { + // ausgang + if c == ' ' && ((x == 0 || x == line.len()-1) || (index == 0 || index == p.seitenlänge-1)) { + line_vec.push(AUSGANG); + continue; + } + match c.to_digit(10) { + Some(digit) => line_vec.push(digit as i64), + // -1 == " " + None => { + match c { + '#' => line_vec.push(WALL), + ' ' => line_vec.push(NIX), + _ => panic!("unbekannter buchstabe {:?}", c) + } + } + } + } + p.raster.push(line_vec); + //println!("{:?}: {:?}", index, line); + } + Ok(p) +} + +pub fn rotate(puzzle: Puzzle, dir: &RotationDir) -> Puzzle { + let seitenlänge = puzzle.seitenlänge; + let s = seitenlänge; + let mut raster = Vec::new(); + for r in &puzzle.raster { + let mut row_vec = Vec::new(); + for c in r { + row_vec.push(*c); + } + raster.push(row_vec); + } + let mut roted_raster = raster.clone(); + macro_rules! set { + ($x:expr, $y:expr, $new:expr) => ( + roted_raster[$y][$x] = $new; + ) + } + + let mut p = puzzle; + + let mut y = 0; + for row in raster { + for (x, cell) in row.iter().enumerate() { + match *dir { + RotationDir::CW => set!(s-y-1, x, *cell), + RotationDir::CCW => set!(y, s-x-1, *cell) + } + } + y += 1; + } + p.raster = roted_raster; + p +} + +fn get_stäbchen(raster: &[Vec]) -> HashMap> { + let mut stäbchen: HashMap> = HashMap::new(); + + for (y, row) in raster.iter().enumerate() { + for (x, cell) in row.iter().enumerate() { + if !cell.is_negative() { + let cell = *cell as u64; + let value = stäbchen.entry(cell).or_insert_with(Vec::new); + value.push(Punkt { x: x, y: y }); + } + } + } + + stäbchen +} + +fn get_stäbchen_richtung(stab: &[Punkt]) -> Stabrichtung { + if stab.len() == 1 { + // egal + return Stabrichtung::Vertikal; + } + if stab[0].x == stab[1].x { + Stabrichtung::Vertikal + } else { + Stabrichtung::Horizontal + } +} + +pub fn do_the_gravity(mut puzzle: Puzzle) -> Puzzle { + macro_rules! get { + ($x:expr, $y:expr) => ( + puzzle.raster[$y][$x]; + ) + } + macro_rules! set { + ($x:expr, $y:expr, $new:expr) => ( + puzzle.raster[$y][$x] = $new; + ) + } + + let mut stäbchen1 = get_stäbchen(&puzzle.raster); + let mut stäbchen2 = HashMap::new(); + while stäbchen1 != stäbchen2 { + //println!("sloop"); + stäbchen1 = get_stäbchen(&puzzle.raster); + //println!("{:?}", stäbchen1); + for (index, stab) in &stäbchen1 { + let mut move_possible = true; + let richtung = get_stäbchen_richtung(stab); + let mut highest_y = 0; + let mut lowest_punkt = &stab[0]; + for p in stab { + if p.y == puzzle.seitenlänge-2 { + move_possible = false; + } + if p.y+1 <= puzzle.seitenlänge-1 && ((get!(p.x, p.y+1) != NIX && get!(p.x, p.y+1) != AUSGANG) && richtung == Stabrichtung::Horizontal) { + move_possible = false; + } + if richtung == Stabrichtung::Vertikal && p.y > highest_y { + highest_y = p.y; + lowest_punkt = p; + } + } + if richtung == Stabrichtung::Vertikal && lowest_punkt.y+1 <= puzzle.seitenlänge-1 { + if get!(lowest_punkt.x, lowest_punkt.y+1) >= 0 { + //println!("{:?} blocked-low because of {:?}, {:?}: {:?}", index, + // lowest_punkt.x, + // lowest_punkt.y+1, + // get!(lowest_punkt.x, lowest_punkt.y+1)); + move_possible = false; + } else if highest_y == puzzle.seitenlänge-2 && get!(lowest_punkt.x, lowest_punkt.y+1) == AUSGANG { + //println!("Puzzle ist möglich!"); + move_possible = true; + } + } + + if move_possible { + let mut new_stab = Vec::new(); + for p in stab { + set!(p.x, p.y, NIX); + new_stab.push(p); + } + let index = *index as i64; + for p in new_stab { + if p.y+1 < puzzle.seitenlänge { + //println!("setting {:?} {:?}", p.x, p.y+1); + set!(p.x, p.y+1, index); + } + } + } + } + stäbchen2 = stäbchen1; + stäbchen1 = get_stäbchen(&puzzle.raster); + //println!("eloop"); + } + //println!("EOG"); + puzzle +} + +fn puzzle_gelöst(puzzle: &Puzzle) -> bool { + let mut ausgang_gefunden = false; + for row in &puzzle.raster { + for cell in row { + if *cell == AUSGANG { + ausgang_gefunden = true; + } + } + } + !ausgang_gefunden +} + +fn apply_choices(puzzle: Puzzle, choices: &[RotationDir], cache: &mut HashMap, Puzzle>) -> Puzzle { + if cache.contains_key(choices) { + //println!("{:?} cached", choices); + cache.get(choices).unwrap().clone() + } else if choices.len() == 1 { + //println!("len one"); + do_the_gravity(rotate(puzzle, &choices[0])) + } else { + let mut choices = Vec::from(choices); + let last_action = choices.pop().unwrap(); + let before_last = apply_choices(puzzle, &choices, cache); + //println!("before: {}", before_last); + let applied = do_the_gravity(rotate(before_last, &last_action)); + //println!("applied: {}", applied); + choices.push(last_action); + cache.insert(choices, applied.clone()); + applied + } +} + + + +pub fn build_children(base: &[Vec]) -> Vec> { + let mut children = Vec::new(); + for path in base { + let mut c1 = path.clone(); + let mut c2 = path.clone(); + c1.push(RotationDir::CW); + c2.push(RotationDir::CCW); + children.push(c1); + children.push(c2); + } + children +} + +pub fn breadth_first_search(puzzle: Puzzle) -> Option> { + let mut cache = HashMap::new(); + let mut known_outcomes = Vec::new(); + let mut base_paths = vec![vec![]]; + let mut depth = 0; + let mut printed_depth = depth; + let start = Instant::now(); + loop { + let children = build_children(&base_paths); + depth += 1; + if children.is_empty() { + return None; + } + base_paths = Vec::new(); + for child_path in &children { + let tmp_puzzle = apply_choices(puzzle.clone(), child_path, &mut cache); + if !known_outcomes.contains(&tmp_puzzle) { + base_paths.push(child_path.clone()); + } + if puzzle_gelöst(&tmp_puzzle) { + return Some(child_path.clone()); + } else { + known_outcomes.push(tmp_puzzle); + } + } + if printed_depth != depth && depth % 1 == 0 { + println!("{:03} depth, {:05} children, {:06} known, {:06} cached, {:04} sec", depth, children.len(), known_outcomes.len(), cache.len(), start.elapsed().as_secs()); + printed_depth = depth; + } + } +} + + + +fn main() { + if let Some(dateiname) = env::args().nth(1) { + //let pause = std::time::Duration::from_millis((16.6*4.0) as u64); + + let puzzle = lade_puzzle(&dateiname); + if let Err(e) = puzzle { + println!("Error: {:?}", e); + return; + } + let puzzle = puzzle.unwrap(); + println!("Puzzle: {}", puzzle); + println!("{:?}", puzzle); + //let puzzle = Puzzle { seitenlänge: 10, raster: vec![vec![-3, -3, -3, -3, -3, -3, -3, -3, -3, -3], vec![-3, -2, -2, -2, -2, -2, -2, -2, -2, -3], vec![-3, -2, -2, -2, -2, -2, -2, -2, -2, -3], vec![-3, -2, -2, -2, -2, -2, -2, -2, -2, -3], vec![-3, -2, -2, -2, -2, 7, 7, 7, 5, -3], vec![-3, -2, -2, -2, 1, 1, -2, -2, 5, -3], vec![-3, -2, -2, -2, 2, -2, 8, 8, 8, -3], vec![-3, 9, 9, 0, 2, 3, 3, -2, -2, -3], vec![-3, 4, 4, 0, 6, 6, 6, 6, 6, -3], vec![-3, -3, -3, -3, -3, -1, -3, -3, -3, -3]] }; + /* + //let puzzle = do_the_gravity(rotate(puzzle, &RotationDir::CW)); + let puzzle_a = apply_choices(puzzle.clone(), &vec![RotationDir::CW, RotationDir::CW, RotationDir::CCW], &mut HashMap::new()); + println!("Puzzlea: {}", puzzle_a); + let puzzle_b = do_the_gravity(rotate(puzzle.clone(), &RotationDir::CCW)); + let puzzle_b = do_the_gravity(rotate(puzzle_b, &RotationDir::CCW)); + let puzzle_b = do_the_gravity(rotate(puzzle_b, &RotationDir::CCW)); + let puzzle_b = do_the_gravity(rotate(puzzle_b, &RotationDir::CCW)); + let puzzle_b = do_the_gravity(rotate(puzzle_b, &RotationDir::CW)); + let puzzle_b = do_the_gravity(rotate(puzzle_b, &RotationDir::CCW)); + println!("Puzzleb: {}", puzzle_b); + */ + let solution = breadth_first_search(puzzle); + if let Some(solution) = solution { + println!("lösung: {:?}", solution); + } else { + println!("keine lösung gefunden!"); + } + /* + let steps = [RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CW, RotationDir::CCW, RotationDir::CW]; + for s in steps.iter() { + println!("action: {:?}", s); + puzzle = do_the_gravity(rotate(puzzle, s)); + println!("{}", puzzle); + std::thread::sleep(pause); + } + */ + } else { + println!("Bitte so aufrufen: ./aufgabe3 "); + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn children() { + let children = build_children(&vec![vec![RotationDir::CW]]); + let expected = vec![vec![RotationDir::CW, RotationDir::CW], vec![RotationDir::CW, RotationDir::CCW]]; + if children == expected { + () + } else { + panic!() + } + } +} + +#[cfg(test)] +mod benchs { + extern crate test; + + use super::*; + use self::test::Bencher; + + #[bench] + fn test1(b: &mut Bencher) { + b.iter(|| { + let puzzle = lade_puzzle("rotation1_03.txt"); + if puzzle.is_err() { + panic!(); + } + let puzzle = puzzle.unwrap(); + //println!("Puzzle: {}", puzzle); + breadth_first_search(puzzle); + }); + } + #[bench] + fn test2(b: &mut Bencher) { + b.iter(|| { + let puzzle = lade_puzzle("rotation2_03.txt"); + if puzzle.is_err() { + panic!(); + } + let puzzle = puzzle.unwrap(); + //println!("Puzzle: {}", puzzle); + breadth_first_search(puzzle); + }); + } + #[bench] + fn rotate_cw(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").unwrap(); + b.iter(|| { + rotate(puzzle.clone(), &test::black_box(RotationDir::CW)) + }) + } + #[bench] + fn rotate_ccw(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").unwrap(); + b.iter(|| { + rotate(puzzle.clone(), &test::black_box(RotationDir::CCW)) + }) + } + #[bench] + fn rotate_cw_grav(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").unwrap(); + b.iter(|| { + do_the_gravity(rotate(puzzle.clone(), &test::black_box(RotationDir::CW))) + }) + } + #[bench] + fn rotate_ccw_grav(b: &mut Bencher) { + let puzzle = lade_puzzle("rotation3_03.txt").unwrap(); + b.iter(|| { + do_the_gravity(rotate(puzzle.clone(), &test::black_box(RotationDir::CCW))) + }) + } +} + + +/* +fn apply_choices(mut puzzle: Puzzle, choices: &Vec) -> Puzzle { + for rotation in choices { + puzzle = do_the_gravity(rotate(puzzle, rotation)); + } + puzzle +} +*/ + +/* +fn cmp_vectors(small: &Vec, large: &Vec) -> bool { + if small.len() > large.len() { + false + } else { + let mut same = true; + for (index, value) in small.iter().enumerate() { + if value != &large[index] { + same = false; + } + } + same + } +} +*/ + +/* +fn breadth_first_search(puzzle: Puzzle) -> Option> { + let mut choices = vec![RotationDir::CW]; + let mut known_outcomes = HashSet::new(); + let mut banned_paths = HashSet::new(); + let mut cont_counter = 0; + let mut ban_counter = 0; + loop { + for path in &banned_paths { + if cmp_vectors(&path, &choices) { + //println!("banned, skip"); + ban_counter += 1; + choices = next_combination(choices); + continue; + } + } + if cont_counter % 100 == 0 && cont_counter > 0 { + println!("{:06} conts, {:06} ban-conts.", cont_counter, ban_counter); + } + //println!("trying {:?}", choices); + let new = apply_choices(puzzle.clone(), &choices); + if known_outcomes.contains(&new) { + cont_counter += 1; + banned_paths.insert(choices.clone()); + choices = next_combination(choices); + continue; + } + if puzzle_gelöst(&new) { + return Some(choices); + } + known_outcomes.insert(new); + choices = next_combination(choices); + } +} +*/ +/* +fn breadth_first_search(puzzle: Puzzle, mut known_outcomes: &mut HashSet, mut choices: Vec) -> Option> { + //println!("base: {:?}, known: {:?}", choices, known_outcomes.len()); + choices.push(RotationDir::CW); + let puzzle_cw = apply_choices(puzzle.clone(), &choices); + if known_outcomes.contains(&puzzle_cw) { + + } else { + known_outcomes.insert(puzzle_cw.clone()); + if puzzle_gelöst(&puzzle_cw) { + return Some(choices); + } else { + if let Some(solution) = breadth_first_search(puzzle.clone(), &mut known_outcomes, choices.clone()) { + return Some(solution); + } + } + } + + choices.pop().unwrap(); + choices.push(RotationDir::CCW); + let puzzle_cw = apply_choices(puzzle.clone(), &choices); + if known_outcomes.contains(&puzzle_cw) { + return None; + } else { + known_outcomes.insert(puzzle_cw.clone()); + if puzzle_gelöst(&puzzle_cw) { + return Some(choices); + } else { + if let Some(solution) = breadth_first_search(puzzle.clone(), &mut known_outcomes, choices.clone()) { + return Some(solution); + } else { + return None + } + } + } + + None +} +*/ + +/* +pub fn next_combination(choices: Vec) -> Vec { + let mut choices = choices; + if choices.iter().filter(|&x| x == &RotationDir::CCW).count() == choices.len() { + choices = choices.iter().map(|_| RotationDir::CW).collect(); + choices.push(RotationDir::CW); + choices + } else { + let mut rchoices = choices.clone(); + rchoices.reverse(); + let mut cw_index = 0; + for (index, c) in rchoices.iter().enumerate() { + if c == &RotationDir::CW { + cw_index = rchoices.len()-index-1; + break; + } + } + choices[cw_index] = RotationDir::CCW; + for mut item in choices.iter_mut().skip(cw_index+1) { + *item = RotationDir::CW; + } + choices + } +} +*/ + +/* + #[test] + fn a() { + let next = next_combination(vec![RotationDir::CW]); + let expected = vec![RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn b() { + let next = next_combination(vec![RotationDir::CCW]); + let expected = vec![RotationDir::CW, RotationDir::CW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn aa() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CW]); + let expected = vec![RotationDir::CW, RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn ab() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CCW]); + let expected = vec![RotationDir::CCW, RotationDir::CW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn ba() { + let next = next_combination(vec![RotationDir::CCW, RotationDir::CW]); + let expected = vec![RotationDir::CCW, RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn bb() { + let next = next_combination(vec![RotationDir::CCW, RotationDir::CCW]); + let expected = vec![RotationDir::CW, RotationDir::CW, RotationDir::CW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn aba() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CCW, RotationDir::CW]); + let expected = vec![RotationDir::CW, RotationDir::CCW, RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn abb() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CCW, RotationDir::CCW]); + let expected = vec![RotationDir::CCW, RotationDir::CW, RotationDir::CW]; + if next == expected { + () + } else { + panic!() + } + } + + #[test] + fn abba() { + let next = next_combination(vec![RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CW]); + let expected = vec![RotationDir::CW, RotationDir::CCW, RotationDir::CCW, RotationDir::CCW]; + if next == expected { + () + } else { + panic!() + } + }*/