From ee2f08a6953e3d7e2e42987c3a19467aaf07d487 Mon Sep 17 00:00:00 2001 From: jhaydeeee-web Date: Tue, 28 Jul 2026 07:26:52 +0100 Subject: [PATCH 1/4] fix: add decBy function to Counter contract and enable decrement tests Removes describe.only restriction and adds the missing decBy(uint) function so the Decrement event test can pass alongside the increment tests. Co-Authored-By: Claude Sonnet 5 --- .../hardhat_test/contracts/Counter.sol | 10 ++++++++-- wk-6 Testing/hardhat_test/test/Counter.ts | 20 +++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/wk-6 Testing/hardhat_test/contracts/Counter.sol b/wk-6 Testing/hardhat_test/contracts/Counter.sol index 9cc6e17..dc5430f 100644 --- a/wk-6 Testing/hardhat_test/contracts/Counter.sol +++ b/wk-6 Testing/hardhat_test/contracts/Counter.sol @@ -26,9 +26,15 @@ contract Counter { } function dec() public { - require(x > 0, "dec: counter should be positive"); + // require(x > 0, "dec: counter should be positive"); x--; emit Decrement(1); } - + + function decBy(uint by) public { + require(by > 0, "decBy: decrement should be positive"); + x -= by; + emit Decrement(by); + } + } diff --git a/wk-6 Testing/hardhat_test/test/Counter.ts b/wk-6 Testing/hardhat_test/test/Counter.ts index c9d6a6d..7b188eb 100644 --- a/wk-6 Testing/hardhat_test/test/Counter.ts +++ b/wk-6 Testing/hardhat_test/test/Counter.ts @@ -28,7 +28,7 @@ describe("Test Counter Contract", function () { }) - describe.only("Incrementing the counter", () =>{ + describe("Incrementing the counter", () =>{ it("Should increment x by 1", async function () { await counter.inc(); @@ -44,8 +44,24 @@ describe("Test Counter Contract", function () { }); + describe("Decrementing the counter", () =>{ + it("Should decrement x by 1", async function () { + await counter.dec(); + + const blockchainX = await counter.x(); + + expect(blockchainX).to.equal(x - 1); + }); + it("Should emit the Decrement event when calling the dec() function", async function () { + await expect(counter.decBy(1)).to.emit(counter, "Decrement").withArgs(1n); + }); -}); + }); + + +}); + + \ No newline at end of file From ddd4bcf3112dd1022661913c7305fd1bef8a0d62 Mon Sep 17 00:00:00 2001 From: Valreb001 Date: Tue, 1 Sep 2026 21:59:46 +0100 Subject: [PATCH 2/4] Hangman assignment-Valentina --- rust/Assignment/my_First_Project/.gitignore | 1 + rust/Assignment/my_First_Project/Cargo.toml | 8 ++ rust/Assignment/my_First_Project/src/main.rs | 77 ++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 rust/Assignment/my_First_Project/.gitignore create mode 100644 rust/Assignment/my_First_Project/Cargo.toml create mode 100644 rust/Assignment/my_First_Project/src/main.rs diff --git a/rust/Assignment/my_First_Project/.gitignore b/rust/Assignment/my_First_Project/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/rust/Assignment/my_First_Project/.gitignore @@ -0,0 +1 @@ +/target diff --git a/rust/Assignment/my_First_Project/Cargo.toml b/rust/Assignment/my_First_Project/Cargo.toml new file mode 100644 index 0000000..14c7cd0 --- /dev/null +++ b/rust/Assignment/my_First_Project/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "my_First_Project" +version = "0.1.0" +edition = "2024" + + +[dependencies] +rand = "0.10.2" diff --git a/rust/Assignment/my_First_Project/src/main.rs b/rust/Assignment/my_First_Project/src/main.rs new file mode 100644 index 0000000..066a8ff --- /dev/null +++ b/rust/Assignment/my_First_Project/src/main.rs @@ -0,0 +1,77 @@ +use std::io; +use rand::RngExt; +use std::cmp::Ordering; + +fn main() { + // println!("Hello, world!"); + // println!("Guess the number!"); + // let number = rand::rng().random_range(1..=100); + + // println!("Please input your guess."); + + // let mut guess = String::new(); + + // io::stdin() + // .read_line(&mut guess) + // .expect("Failed to read line"); + + // // println!("You guessed: {}", guess); + // // println!("You guessed: {guess}"); + // let guess: u32 = guess.trim().parse().expect("Please type a number!"); + + // println!("You guessed: {}", guess); + + // match guess.cmp(&number){ + // Ordering::Less => println!("Too small"), + // Ordering::Greater => println!("Too big"), + // Ordering::Equal => println!("You win"), + // } + + println!("We're playing a little Hangman game, you have 5 attempts to save him!!!"); + + println! ("Guess the number!"); + + let fixed_number = rand::rng().random_range(1..=100); + + + let max_guess = 5; + let mut guessed_number = 0; + + + loop{ + + guessed_number += 1; + println!("Input your guess to save him, {guessed_number} attempt(s) out of {max_guess} attempts"); + + let mut guess = String::new(); + + io:: stdin() + .read_line(&mut guess) + .expect("Failed to read line"); + + let guess: u32 = match guess.trim().parse() { + Ok(num) => num, + Err(_) => { + println!("Please input a number!!!"); + continue; + } + }; + + println!("You guessed {guess}"); + + match guess.cmp(&fixed_number) { + Ordering::Less => println!("Too small, try again"), + Ordering::Greater => println!("Too big, try again"), + Ordering::Equal => { + println!{"You win, you saved him!!!Hoorayyy!!!"}; + break; + } + } + + if guessed_number >= max_guess { + println!("You lost, he is dead!!!"); + break; + } + + } +} From f91711ffda0340a19044601a7c698c98bbb0866a Mon Sep 17 00:00:00 2001 From: Valreb001 Date: Wed, 2 Sep 2026 10:56:37 +0100 Subject: [PATCH 3/4] Hangman-Valentina --- rust/{Assignment => }/my_First_Project/.gitignore | 0 rust/{Assignment => }/my_First_Project/Cargo.toml | 0 rust/{Assignment => }/my_First_Project/src/main.rs | 2 ++ 3 files changed, 2 insertions(+) rename rust/{Assignment => }/my_First_Project/.gitignore (100%) rename rust/{Assignment => }/my_First_Project/Cargo.toml (100%) rename rust/{Assignment => }/my_First_Project/src/main.rs (94%) diff --git a/rust/Assignment/my_First_Project/.gitignore b/rust/my_First_Project/.gitignore similarity index 100% rename from rust/Assignment/my_First_Project/.gitignore rename to rust/my_First_Project/.gitignore diff --git a/rust/Assignment/my_First_Project/Cargo.toml b/rust/my_First_Project/Cargo.toml similarity index 100% rename from rust/Assignment/my_First_Project/Cargo.toml rename to rust/my_First_Project/Cargo.toml diff --git a/rust/Assignment/my_First_Project/src/main.rs b/rust/my_First_Project/src/main.rs similarity index 94% rename from rust/Assignment/my_First_Project/src/main.rs rename to rust/my_First_Project/src/main.rs index 066a8ff..a26bd74 100644 --- a/rust/Assignment/my_First_Project/src/main.rs +++ b/rust/my_First_Project/src/main.rs @@ -6,6 +6,7 @@ fn main() { // println!("Hello, world!"); // println!("Guess the number!"); // let number = rand::rng().random_range(1..=100); + // let number = rand::thread_rng().gen_range(1..=100); // println!("Please input your guess."); @@ -74,4 +75,5 @@ fn main() { } } + println!("The number was: {fixed_number}"); } From 697a979a90813f0a641866a013dfb53f10a0f166 Mon Sep 17 00:00:00 2001 From: Valreb001 Date: Thu, 3 Sep 2026 11:21:18 +0100 Subject: [PATCH 4/4] Fibonacci sequence-Valentina Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01GgXbNew5d8qzHTCw7BLEoh --- .../fibonnaci_dungeon}/.gitignore | 0 .../fibonnaci_dungeon/BOSS_FIGHT.md | 98 ++++ .../fibonnaci_dungeon/Cargo.toml | 6 + .../fibonnaci_dungeon/src/main.rs | 461 ++++++++++++++++++ rust/Assignment/my_First_Project/.gitignore | 1 + .../my_First_Project/Cargo.toml | 0 .../my_First_Project/src/main.rs | 0 7 files changed, 566 insertions(+) rename rust/{my_First_Project => Assignment/Fibonnaci Assignment/fibonnaci_dungeon}/.gitignore (100%) create mode 100644 rust/Assignment/Fibonnaci Assignment/fibonnaci_dungeon/BOSS_FIGHT.md create mode 100644 rust/Assignment/Fibonnaci Assignment/fibonnaci_dungeon/Cargo.toml create mode 100644 rust/Assignment/Fibonnaci Assignment/fibonnaci_dungeon/src/main.rs create mode 100644 rust/Assignment/my_First_Project/.gitignore rename rust/{ => Assignment}/my_First_Project/Cargo.toml (100%) rename rust/{ => Assignment}/my_First_Project/src/main.rs (100%) diff --git a/rust/my_First_Project/.gitignore b/rust/Assignment/Fibonnaci Assignment/fibonnaci_dungeon/.gitignore similarity index 100% rename from rust/my_First_Project/.gitignore rename to rust/Assignment/Fibonnaci Assignment/fibonnaci_dungeon/.gitignore diff --git a/rust/Assignment/Fibonnaci Assignment/fibonnaci_dungeon/BOSS_FIGHT.md b/rust/Assignment/Fibonnaci Assignment/fibonnaci_dungeon/BOSS_FIGHT.md new file mode 100644 index 0000000..b6f902b --- /dev/null +++ b/rust/Assignment/Fibonnaci Assignment/fibonnaci_dungeon/BOSS_FIGHT.md @@ -0,0 +1,98 @@ +# šŸ‘‘ The Boss Fight — Written Trial by the Dungeon Master + +*(Part 5 — no blade helps here, only understanding.)* + +## 1. Why does the un-warded dungeon explode exponentially, while the warded one grows linearly? + +The un-warded dungeon is a **tree**: every recursive call `build_fib_tree(k)` / +`evaluate_tree(k)` creates a brand-new room, even if a room with that exact +value `k` was already built somewhere else in the dungeon. Room `n-2` is not +just *similar to* the left child's right grandchild — it is rebuilt as an +entirely separate object. Floor 3 proved this count precisely: + +``` +N(n) = 2*F(n+1) - 1 = O(phi^n), phi = (1+sqrt(5))/2 ā‰ˆ 1.618 +``` + +Each step down costs a constant amount of work but the number of *distinct +calls* still doubles-ish every couple of levels, because the same room value +is reachable by exponentially many different paths from the root (a path is +any sequence of "-1"/"-2" steps summing to `n - k`, and the number of such +paths is itself Fibonacci-shaped). Nothing is shared, so nothing is saved. + +The warded dungeon is a **DAG**. The very first time room `k` is requested, +`cast_memory_ward` builds and evaluates it and stores the `Rc` in +the `Ward` (a `HashMap>`). Every subsequent request for +room `k` — no matter how many different corridors lead there — finds the +entry already in the map and returns a cheap `Rc::clone` (a pointer-copy plus +a refcount bump) instead of recursing further. Since there are only `n + 1` +*distinct* room values (`0..=n`), the ward is populated exactly `n + 1` +times, and each population does O(1) work beyond the two (already-cached) +recursive lookups. Total work: **O(n)**, confirmed by the demo's table +(`rooms (warded)` column is exactly `n + 1` for `n = 10, 20, 30`). + +The exponential blow-up was never inherent to Fibonacci — it was the cost of +refusing to remember an answer you already computed. + +## 2. How is the Memory Ward secretly just top-down Dynamic Programming wearing a costume? + +Top-down DP (a.k.a. memoized recursion) is exactly: *write the naive +recursive solution, but before recursing, check a cache keyed by the +subproblem's parameters; after computing a result, store it in that cache +before returning.* That is a literal, line-by-line description of +`cast_memory_ward`: + +- The **cache** is the `Ward` (`HashMap>`), keyed by + room value `n` — the subproblem's only parameter. +- The **base cases** (`n == 0`, `n == 1`) are the DP's base cases. +- The **recurrence** `result = left.result + right.result` is the DP's + transition, identical to `dp[n] = dp[n-1] + dp[n-2]`. +- The **cache check at the top** (`if let Some(existing) = ward.get(&n) { + return Rc::clone(existing); }`) is precisely "if `dp[n]` is already + computed, return it." +- The **cache write before returning** (`ward.insert(n, Rc::clone(&room))`) + is `dp[n] = result`. + +The only costume is that instead of storing a plain number in `dp[n]`, we +store a whole `Rc` — value, children, and result together — so +the memo table doubles as the DAG's node storage. Shared subtrees in the DAG +*are* memo-table hits; the DAG is just what a call graph looks like once you +draw an edge for every cache hit instead of silently discarding it. + +## 3. If you explored the un-warded dungeon level-by-level (breadth-first) instead of depth-by-depth, what would each level represent? + +Breadth-first search visits all rooms at distance `d` from the entrance +before any room at distance `d+1`. In this dungeon, "distance from the +entrance" is the number of corridors taken, i.e. the number of `-1`/`-2` +steps subtracted from `n` so far. So **level `d` is exactly the set of all +rooms reachable by paths of length `d`** — every room whose value equals `n` +minus some composition of `d` steps drawn from `{1, 2}`. + +Concretely: level 0 is just `{n}` (the entrance). Level 1 is `{n-1, n-2}` +(left corridor, right corridor). Level 2 is the four two-step paths `LL, LR, +RL, RR`, giving values `{n-2, n-3, n-3, n-4}` — note `n-3` already shows up +twice, as two separate un-warded room instances, even this early. In +general, level `d` contains one room instance +for every composition of `d` into parts `1` and `2`, and the room's *value* +at that position is `n` minus the sum of the composition's parts used so far +along that particular path — so distinct paths of the same length `d` can +and do land on the same value. + +Put differently: **level `d` is the `d`-th anti-diagonal of the call tree**, +and the multiset of values appearing at level `d` is the un-warded, still +fully-exploded version of "all the ways to spend `d` steps of size 1 or 2" — +the same compositions-of-`d` count (`fib(d+1)`) that Floor 3 used to explain +why Room 5 keeps reappearing. BFS doesn't fix any of the redundancy the +Memory Ward fixes — it just re-slices the same exponential tree by distance +from the root instead of by recursive call order. The ward's linear DAG, +by contrast, would BFS in exactly `n + 1` distinct levels — one per room +value — because merged nodes only get visited (and enqueued) once. + +--- + +**Complexity summary** + +| | Rooms built | Work per room | Total time | Total space | +|---|---|---|---|---| +| Cursed (un-warded) | `N(n) = 2F(n+1)-1` | O(1) | **O(phi^n)** | O(n) stack + O(phi^n) if materialized | +| Warded (memoized DAG) | `n + 1` | O(1) amortized | **O(n)** | **O(n)** (ward + DAG nodes) | diff --git a/rust/Assignment/Fibonnaci Assignment/fibonnaci_dungeon/Cargo.toml b/rust/Assignment/Fibonnaci Assignment/fibonnaci_dungeon/Cargo.toml new file mode 100644 index 0000000..253754c --- /dev/null +++ b/rust/Assignment/Fibonnaci Assignment/fibonnaci_dungeon/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "fibonnaci_dungeon" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/rust/Assignment/Fibonnaci Assignment/fibonnaci_dungeon/src/main.rs b/rust/Assignment/Fibonnaci Assignment/fibonnaci_dungeon/src/main.rs new file mode 100644 index 0000000..5565d1f --- /dev/null +++ b/rust/Assignment/Fibonnaci Assignment/fibonnaci_dungeon/src/main.rs @@ -0,0 +1,461 @@ +//! 🌲 The Fibonacci Dungeon — A Recursive Descent Quest +//! +//! Run with `cargo run` to walk all four floors plus the bonus vault. +//! The written Boss Fight trial (Part 5) lives in BOSS_FIGHT.md. +//! +//! Everything lives in this one file on purpose — the dungeon is small +//! enough that splitting it into modules just adds ceremony. + +use std::collections::{HashMap, HashSet}; +use std::rc::Rc; + +// --------------------------------------------------------------------- +// Floor 1 — The Spawning Chamber (Part 1: build the recursive tree) +// --------------------------------------------------------------------- + +/// A single room in the dungeon. +struct Node { + value: u64, + left: Option>, + right: Option>, + result: Option, +} + +impl Node { + fn leaf(value: u64) -> Self { + Node { value, left: None, right: None, result: None } + } +} + +/// Recursively spawns the dungeon for depth `n`. Rooms 0 and 1 are sealed +/// chambers (leaves, dead ends). Every other room `n` opens a left corridor +/// to room `n-1` and a right corridor to room `n-2`. +fn build_fib_tree(n: u64) -> Node { + if n == 0 || n == 1 { + return Node::leaf(n); + } + let left = build_fib_tree(n - 1); + let right = build_fib_tree(n - 2); + Node { value: n, left: Some(Box::new(left)), right: Some(Box::new(right)), result: None } +} + +// --------------------------------------------------------------------- +// Floor 2 — The Descent (Part 2: post-order evaluation) +// --------------------------------------------------------------------- + +/// Walks the dungeon post-order (both corridors before the room itself), +/// filling in `result` at every room. Returns the treasure collected in +/// `node`. Rule of the dungeon: no Binet's-formula shortcuts — this is a +/// genuine traversal, so the gold really is earned by walking the rooms. +fn evaluate_tree(node: &mut Node) -> u64 { + let treasure = match (node.left.as_deref_mut(), node.right.as_deref_mut()) { + (None, None) => if node.value == 0 { 0 } else { 1 }, + (Some(left), Some(right)) => evaluate_tree(left) + evaluate_tree(right), + _ => unreachable!("a room always has zero or two corridors"), + }; + node.result = Some(treasure); + treasure +} + +// --------------------------------------------------------------------- +// Floor 3 — The Cartographer's Trial (Part 3: structural analysis) +// --------------------------------------------------------------------- + +/// Survey report: (total rooms, sealed chambers, dungeon depth/height). +fn analyze_tree(node: &Node) -> (u64, u64, u64) { + match (node.left.as_deref(), node.right.as_deref()) { + (None, None) => (1, 1, 0), + (Some(left), Some(right)) => { + let (lt, ll, ld) = analyze_tree(left); + let (rt, rl, rd) = analyze_tree(right); + (1 + lt + rt, ll + rl, 1 + ld.max(rd)) + } + _ => unreachable!("a room always has zero or two corridors"), + } +} + +/// Standard Fibonacci, F(0)=0, F(1)=1, computed iteratively. Structural +/// bookkeeping for the survey, not the dungeon's treasure (that stays +/// banned from closed-form shortcuts on Floor 2). +fn fib(n: u64) -> u64 { + let (mut a, mut b) = (0u64, 1u64); + for _ in 0..n { + let next = a + b; + a = b; + b = next; + } + a +} + +fn predicted_total_rooms(n: u64) -> u64 { + 2 * fib(n + 1) - 1 +} + +fn predicted_sealed_chambers(n: u64) -> u64 { + fib(n + 1) +} + +fn predicted_depth(n: u64) -> u64 { + if n == 0 { 0 } else { n - 1 } +} + +/// Counts how many rooms carry `target`'s value anywhere in the dungeon — +/// used to demonstrate the redundancy the Memory Ward fixes. +fn count_value_occurrences(node: &Node, target: u64) -> u64 { + let here = u64::from(node.value == target); + let l = node.left.as_deref().map_or(0, |l| count_value_occurrences(l, target)); + let r = node.right.as_deref().map_or(0, |r| count_value_occurrences(r, target)); + here + l + r +} + +const SCROLL: &str = r#" +šŸ“œ The Cartographer's Scroll — Room-Count Derivation +===================================================== + +Let N(n) be the number of rooms in the dungeon of depth n (sealed and open +together). Rooms 0 and 1 are single sealed chambers: + + N(0) = 1 + N(1) = 1 + +Every other room n opens exactly two corridors, to rooms n-1 and n-2, plus +itself: + + N(n) = 1 + N(n-1) + N(n-2) for n >= 2 + +Because every room has either 0 or 2 corridors (never 1), the dungeon is a +*full* binary tree. In any full binary tree, internal nodes I and leaves L +satisfy I = L - 1, so total nodes N = I + L = 2L - 1. + +The leaves are exactly the sealed chambers reached by the recursion, and by +induction their count follows the ordinary Fibonacci recurrence +L(n) = L(n-1) + L(n-2) with L(0) = L(1) = 1, which gives: + + L(n) = F(n+1) (F = standard Fibonacci, F(0)=0, F(1)=1) + +Therefore: + + N(n) = 2*F(n+1) - 1 + +Dungeon depth (height) H(n): the left corridor (n-1) is always at least as +deep as the right (n-2), so H(n) = 1 + H(n-1), with H(0) = H(1) = 0, giving: + + H(n) = n - 1 for n >= 1, H(0) = 0 + +Why O(phi^n)? Binet's formula gives F(n) = (phi^n - psi^n) / sqrt(5), where +phi = (1+sqrt(5))/2 ~ 1.618 is the golden ratio and |psi| < 1, so +F(n) = Theta(phi^n). Since N(n) = 2*F(n+1) - 1, the room count — and thus +the number of recursive calls build_fib_tree/evaluate_tree make — grows at +exactly the same golden-ratio rate as the treasure value itself: O(phi^n). +"#; + +// --------------------------------------------------------------------- +// Floor 4 — The Memory Ward (Part 4: memoize the tree into a DAG) +// --------------------------------------------------------------------- + +/// A warded room. Two different corridors are allowed to point at the +/// same `Rc` — that's what turns the tree into a DAG. +struct WardedRoom { + value: u64, + left: Option>, + right: Option>, + result: u64, +} + +/// The ward: a spellbook mapping a room's value to the one true instance +/// of that room, built the first time it's needed. +type Ward = HashMap>; + +/// Casts the Memory Ward: builds AND evaluates room `n` at most once ever. +/// Any later corridor that would lead to a room already in the ward links +/// back to that original `Rc` instead of reconstructing it from scratch. +/// This is our own spell — a plain `HashMap` cache, no memoization crate. +fn cast_memory_ward(n: u64, ward: &mut Ward) -> Rc { + if let Some(existing) = ward.get(&n) { + return Rc::clone(existing); + } + let room = if n == 0 || n == 1 { + Rc::new(WardedRoom { value: n, left: None, right: None, result: if n == 0 { 0 } else { 1 } }) + } else { + let left = cast_memory_ward(n - 1, ward); + let right = cast_memory_ward(n - 2, ward); + let result = left.result + right.result; + Rc::new(WardedRoom { value: n, left: Some(left), right: Some(right), result }) + }; + ward.insert(n, Rc::clone(&room)); + room +} + +/// Proves the tree really became a DAG: walks from `root` and returns every +/// room reachable by more than one corridor, checked by `Rc` pointer +/// identity (the literal same room in memory, not just an equal value). +fn find_shared_rooms(root: &Rc) -> Vec { + let mut seen_ptrs: Vec<*const WardedRoom> = Vec::new(); + let mut shared = Vec::new(); + let mut stack = vec![Rc::clone(root)]; + while let Some(room) = stack.pop() { + let ptr = Rc::as_ptr(&room); + if seen_ptrs.contains(&ptr) { + continue; + } + seen_ptrs.push(ptr); + // >2 because `room` itself plus the ward's own cache entry are + // always at least 1 each; a genuinely shared room has parents too. + if Rc::strong_count(&room) > 2 { + shared.push(room.value); + } + if let Some(left) = &room.left { + stack.push(Rc::clone(left)); + } + if let Some(right) = &room.right { + stack.push(Rc::clone(right)); + } + } + shared.sort_unstable(); + shared.dedup(); + shared +} + +/// Counts rooms in the *un-warded* (cursed) dungeon by genuine recursive +/// descent, without materializing a full `Node` tree in memory, so it stays +/// cheap enough to run out to n=30 while still paying the real exponential +/// call cost. +fn count_cursed_rooms(n: u64) -> u64 { + if n == 0 || n == 1 { + return 1; + } + 1 + count_cursed_rooms(n - 1) + count_cursed_rooms(n - 2) +} + +// --------------------------------------------------------------------- +// Bonus Vault — map n=6, gold-highlighting duplicates, plus Graphviz +// --------------------------------------------------------------------- + +/// Prints an indented-text map of the dungeon (depth-first, root first). +/// The first room to carry a given value prints plain; every later room +/// carrying a value already seen is marked gold — exactly the rooms the +/// Memory Ward will collapse into one shared node. +fn print_map(node: &Node, prefix: &str, is_last: bool, is_root: bool, seen: &mut HashSet) { + let branch = if is_root { "" } else if is_last { "└── " } else { "ā”œā”€ā”€ " }; + let duplicate = !seen.insert(node.value); + let tag = if duplicate { " 🟔 GOLD (duplicate — would be shared after the ward)" } else { "" }; + println!("{prefix}{branch}Room {}{tag}", node.value); + + let new_prefix = if is_root { + String::new() + } else if is_last { + format!("{prefix} ") + } else { + format!("{prefix}│ ") + }; + + let children: Vec<&Node> = [node.left.as_deref(), node.right.as_deref()].into_iter().flatten().collect(); + let last_index = children.len().saturating_sub(1); + for (i, child) in children.iter().enumerate() { + print_map(child, &new_prefix, i == last_index, false, seen); + } +} + +/// Emits a Graphviz `.dot` description of the dungeon, coloring duplicate +/// rooms (by the same first-seen rule as `print_map`) gold. +fn to_graphviz(root: &Node) -> String { + let mut out = String::from("digraph FibonacciDungeon {\n node [shape=circle, fontname=\"monospace\"];\n"); + let mut seen = HashSet::new(); + let mut counter = 0u64; + write_graphviz(root, &mut out, &mut seen, &mut counter); + out.push_str("}\n"); + out +} + +fn write_graphviz(node: &Node, out: &mut String, seen: &mut HashSet, counter: &mut u64) -> u64 { + let id = *counter; + *counter += 1; + let duplicate = !seen.insert(node.value); + let style = if duplicate { "style=filled, fillcolor=gold" } else { "style=filled, fillcolor=white" }; + out.push_str(&format!(" n{id} [label=\"{}\", {style}];\n", node.value)); + + if let Some(left) = node.left.as_deref() { + let lid = write_graphviz(left, out, seen, counter); + out.push_str(&format!(" n{id} -> n{lid} [label=\"L\"];\n")); + } + if let Some(right) = node.right.as_deref() { + let rid = write_graphviz(right, out, seen, counter); + out.push_str(&format!(" n{id} -> n{rid} [label=\"R\"];\n")); + } + id +} + +// --------------------------------------------------------------------- +// Orchestration +// --------------------------------------------------------------------- + +fn main() { + floor1_and_2(); + floor3(); + floor4(); + bonus_vault_demo(); +} + +fn floor1_and_2() { + println!("🌲 FLOOR 1 — The Spawning Chamber"); + println!("================================="); + let n = 10; + let mut root = build_fib_tree(n); + println!("Built the dungeon for depth n={n}. Entrance room value: {}", root.value); + + println!("\nāš”ļø FLOOR 2 — The Descent"); + println!("========================="); + let treasure = evaluate_tree(&mut root); + let expected = fib(n); + println!("Treasure collected walking back to the entrance: {treasure} gold"); + println!("fib({n}) via the textbook recurrence: {expected}"); + assert_eq!(treasure, expected, "the dungeon lied about its gold"); + println!("āœ… root.result matches fib({n}) exactly — no shortcuts taken."); +} + +fn floor3() { + println!("\n🧭 FLOOR 3 — The Cartographer's Trial"); + println!("======================================"); + println!( + "{:>3} | {:>10} {:>10} | {:>10} {:>10} | {:>8} {:>8}", + "n", "rooms", "(predicted)", "sealed", "(predicted)", "depth", "(pred.)" + ); + for n in [5, 10, 15, 20] { + let root = build_fib_tree(n); + let (rooms, sealed, depth) = analyze_tree(&root); + println!( + "{:>3} | {:>10} {:>10} | {:>10} {:>10} | {:>8} {:>8}", + n, rooms, predicted_total_rooms(n), sealed, predicted_sealed_chambers(n), depth, predicted_depth(n) + ); + assert_eq!(rooms, predicted_total_rooms(n)); + assert_eq!(sealed, predicted_sealed_chambers(n)); + assert_eq!(depth, predicted_depth(n)); + } + + println!("\nBoss taunt check — how many times does Room 5 get rebuilt?"); + for n in [8, 10, 12, 14] { + let root = build_fib_tree(n); + let count = count_value_occurrences(&root, 5); + println!(" depth n={n:>2}: Room 5 appears {count:>3} times"); + } + + println!("{SCROLL}"); +} + +fn floor4() { + println!("\nšŸ”® FLOOR 4 — The Memory Ward"); + println!("============================="); + println!("{:>4} | {:>16} | {:>16}", "n", "rooms (cursed)", "rooms (warded)"); + for n in [10u64, 20, 30] { + let cursed = count_cursed_rooms(n); + let mut ward: Ward = Ward::new(); + let warded_root = cast_memory_ward(n, &mut ward); + let warded = ward.len(); + println!("{:>4} | {:>16} | {:>16}", n, cursed, warded); + assert_eq!(warded_root.result, fib(n), "the ward computed the wrong treasure"); + assert_eq!(warded as u64, n + 1, "the ward should hold exactly n+1 distinct rooms"); + } + println!( + "\nCursed growth is O(phi^n) — exponential, matching Floor 3's N(n) = 2*F(n+1)-1.\n\ +Warded growth is O(n) — linear, since each distinct room value 0..=n is\n\ +built and evaluated exactly once, so |ward| = n + 1 for n >= 2." + ); + + let mut small_ward: Ward = Ward::new(); + let small_root = cast_memory_ward(6, &mut small_ward); + let shared = find_shared_rooms(&small_root); + println!( + "\nProof it's a real DAG (n=6): rooms reached by more than one corridor\n\ +(same Rc, not just an equal value): {shared:?}" + ); +} + +fn bonus_vault_demo() { + println!("\nšŸ’Ž BONUS VAULT — Dungeon Map for n=6"); + println!("======================================"); + let root = build_fib_tree(6); + let mut seen = HashSet::new(); + print_map(&root, "", true, true, &mut seen); + + println!("\nGraphviz export (paste into https://dreampuf.github.io/GraphvizOnline/ or `dot`):\n"); + println!("{}", to_graphviz(&root)); +} + +// --------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn floor1_leaves_are_sealed_with_no_corridors() { + let zero = build_fib_tree(0); + assert_eq!(zero.value, 0); + assert!(zero.left.is_none() && zero.right.is_none()); + + let one = build_fib_tree(1); + assert_eq!(one.value, 1); + assert!(one.left.is_none() && one.right.is_none()); + } + + #[test] + fn floor2_treasure_matches_fib_for_many_n() { + for n in 0..=25 { + let mut root = build_fib_tree(n); + let treasure = evaluate_tree(&mut root); + assert_eq!(treasure, fib(n), "mismatch at n={n}"); + assert_eq!(root.result, Some(treasure)); + } + } + + #[test] + fn floor3_survey_matches_formulas() { + for n in 0..=20 { + let root = build_fib_tree(n); + let (rooms, sealed, depth) = analyze_tree(&root); + assert_eq!(rooms, predicted_total_rooms(n), "rooms mismatch at n={n}"); + assert_eq!(sealed, predicted_sealed_chambers(n), "sealed mismatch at n={n}"); + assert_eq!(depth, predicted_depth(n), "depth mismatch at n={n}"); + } + } + + #[test] + fn floor4_ward_is_correct_and_linear() { + for n in [0u64, 1, 2, 5, 10, 20, 30] { + let mut ward: Ward = Ward::new(); + let root = cast_memory_ward(n, &mut ward); + assert_eq!(root.result, fib(n), "ward gave wrong treasure at n={n}"); + // For n<=1 the requested room *is* the base case, so recursion + // never touches the other base value and the ward holds just + // that one room. From n=2 on, reaching room n always requires + // walking down to both 0 and 1, so every value in 0..=n gets + // cached exactly once: |ward| = n + 1. + let expected = if n <= 1 { 1 } else { n + 1 }; + assert_eq!(ward.len() as u64, expected, "unexpected ward size at n={n}"); + } + } + + #[test] + fn bonus_vault_flags_every_repeated_value_as_duplicate() { + let root = build_fib_tree(6); + let mut seen = HashSet::new(); + let mut first_seen = HashSet::new(); + let mut stack = vec![&root]; + while let Some(node) = stack.pop() { + if !first_seen.insert(node.value) { + seen.insert(node.value); + } + if let Some(r) = node.right.as_deref() { + stack.push(r); + } + if let Some(l) = node.left.as_deref() { + stack.push(l); + } + } + for v in 0..=4 { + assert!(seen.contains(&v), "value {v} should repeat in an n=6 dungeon"); + } + } +} diff --git a/rust/Assignment/my_First_Project/.gitignore b/rust/Assignment/my_First_Project/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/rust/Assignment/my_First_Project/.gitignore @@ -0,0 +1 @@ +/target diff --git a/rust/my_First_Project/Cargo.toml b/rust/Assignment/my_First_Project/Cargo.toml similarity index 100% rename from rust/my_First_Project/Cargo.toml rename to rust/Assignment/my_First_Project/Cargo.toml diff --git a/rust/my_First_Project/src/main.rs b/rust/Assignment/my_First_Project/src/main.rs similarity index 100% rename from rust/my_First_Project/src/main.rs rename to rust/Assignment/my_First_Project/src/main.rs