Add day 4

I really enjoyed day 4, I thought it was a particularly beautiful puzzle
where part 2 led on very nicely from part 1. Then again, I do always
tend to like these self-referential things

I started off putting all my integers to the minimum size, but in part 2
I gave up and just used usize, because I didn't really know how big the
numbers were likely to grow... this was fulled by me initially
misreading it as being based on the "score" not the "n" which would have
resulted in much larger numbers

Here's to hoping for an as-satisfying day 5!

Change-Id: I564559adbadd0713d18bffaba4b6d9d7f0d77f29
Reviewed-on: https://git.clicks.codes/c/Minion/AdventOfCode/2023/+/165
diff --git a/day4/Cargo.lock b/day4/Cargo.lock
new file mode 100644
index 0000000..e92c02c
--- /dev/null
+++ b/day4/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "day4"
+version = "0.1.0"
diff --git a/day4/Cargo.toml b/day4/Cargo.toml
new file mode 100644
index 0000000..8842130
--- /dev/null
+++ b/day4/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "day4"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/day4/src/main.rs b/day4/src/main.rs
new file mode 100644
index 0000000..102cf85
--- /dev/null
+++ b/day4/src/main.rs
@@ -0,0 +1,74 @@
+use std::collections::HashMap;
+
+fn main() {
+    let input = include_str!("../input.txt");
+
+    let mut scores = vec![];
+    let mut scratchcards: HashMap<usize, usize> = HashMap::new();
+
+    let mut index = 0;
+
+    for card in input.split('\n') {
+        if card == "" {
+            continue;
+        }
+
+        index += 1;
+
+        let data = card.split(": ").collect::<Vec<_>>()[1];
+
+        let [ winner_str, our_str ] = data.split('|').collect::<Vec<_>>()[..] else { panic!("Invalid card") };
+
+        let mut winners: Vec<u8> = vec![];
+        let mut ours: Vec<u8> = vec![];
+
+        for num in winner_str.split(' ') {
+            if num.is_empty() {
+                continue;  // there are some double-spaces in the cards
+            }
+
+            winners.push(num.parse().unwrap());
+        }
+
+        println!("- the winning numbers are {:?}", winners);
+
+        for num in our_str.split(' ') {
+            if num.is_empty() {
+                continue;  // there are some double-spaces in the cards
+            }
+
+            ours.push(num.parse().unwrap());
+        }
+
+        println!("- our numbers are {:?}", ours);
+
+        let mut n = 0;
+        for num in ours {
+            if winners.contains(&num) {
+                println!("  - {} is a winner!", num);
+                n += 1
+            }
+        }
+
+        if n != 0 {
+            let score = 2_usize.pow(n - 1);
+            scores.push(score);
+            println!("Card {} has {} winning numbers, scoring it {} points!", index, n, score);
+
+            let factor = scratchcards.get(&index).unwrap_or(&0) + 1;
+            println!("- Adding {} cards to each of the next {} numbers", factor, n);
+            for i in index+1..index + 1 + TryInto::<usize>::try_into(n).unwrap() {
+                let old_copies = scratchcards.get(&i).unwrap_or(&0);
+                println!("  - Adding {} cards to the {} that were already in card {}", factor, old_copies, i);
+                scratchcards.insert(i, old_copies + factor);
+            }
+        } else {
+            println!("This card didn't win at all, better luck next time...");
+        }
+    }
+
+    println!("The total score was {}", scores.iter().sum::<usize>());
+
+    let clones: usize = scratchcards.into_values().sum();
+    println!("You have {} scratchcard clones, plus the original {}, which leaves you with a total of {}", clones, index, clones + index);
+}