diff --git a/config.json b/config.json index e9cd57ec..9e2a089c 100644 --- a/config.json +++ b/config.json @@ -92,7 +92,6 @@ "name": "RPN Calculator", "uuid": "536d9f09-5910-4a26-93fd-2242667b0b87", "concepts": [ - "control-flow", "enums" ], "prerequisites": [ @@ -100,6 +99,18 @@ ], "status": "wip" }, + { + "slug": "bakery-order-system", + "name": "Bakery Order System", + "uuid": "a979b9a0-7fd9-46f7-bacf-f0044c3c9bb0", + "concepts": [ + "control-flow" + ], + "prerequisites": [ + "match-basics" + ], + "status": "beta" + }, { "slug": "welcome-to-tech-palace", "name": "Welcome To Tech Palace!", diff --git a/exercises/concept/bakery-order-system/.docs/hints.md b/exercises/concept/bakery-order-system/.docs/hints.md new file mode 100644 index 00000000..b9defecc --- /dev/null +++ b/exercises/concept/bakery-order-system/.docs/hints.md @@ -0,0 +1,28 @@ +# Hints + +## General + +- [The Cairo Book - Control Flow][tcb-control-flow] +- [The Cairo Book - Arrays][tcb-arrays] + +## 1. Calculate Order Total + +- Use `match` or `if`/`else if` to handle different pastry types +- Multiply the price per pastry by the quantity +- Each pastry type has a fixed price in coins + +## 2. Apply Volume Discounts + +- Use `if`/`else if` to check discount tiers from highest to lowest +- Remember that discounts should be applied as integer division (rounded down) +- 10% discount means multiply by 90 and divide by 100 + +## 3. Create Baking Schedule + +- Use a `loop` to build the schedule hour by hour +- Each hour, bake up to 5 orders (or whatever remains) +- Continue until all orders are scheduled +- Use `break` when no more orders remain + +[tcb-control-flow]: https://www.starknet.io/cairo-book/ch02-05-control-flow.html +[tcb-arrays]: https://www.starknet.io/cairo-book/ch03-01-arrays.html diff --git a/exercises/concept/bakery-order-system/.docs/instructions.md b/exercises/concept/bakery-order-system/.docs/instructions.md new file mode 100644 index 00000000..920a24c3 --- /dev/null +++ b/exercises/concept/bakery-order-system/.docs/instructions.md @@ -0,0 +1,44 @@ +# Instructions + +In this exercise you'll help manage a small bakery's daily operations using Cairo's control flow constructs. + +## 1. Calculate Order Total + +Implement `calculate_total` that calculates the cost of an order based on pastry type and quantity: + +- Croissant: 3 coins each +- Muffin: 2 coins each +- Cookie: 1 coin each + +```rust +calculate_total(Pastry::Croissant, 4) // Returns: 12 (4 * 3) +calculate_total(Pastry::Cookie, 10) // Returns: 10 (10 * 1) +``` + +## 2. Apply Volume Discounts + +Implement `apply_discount` that applies discounts based on order total. +Apply the percentage discount to the total, then round down to the nearest whole coin (floor). + +- Orders ≥ 20 coins: 10% discount +- Orders ≥ 10 coins: 5% discount +- Orders < 10 coins: no discount + +```rust +apply_discount(25) // Returns: 22 +apply_discount(15) // Returns: 14 +apply_discount(8) // Returns: 8 +``` + +## 3. Create Baking Schedule + +Implement `baking_schedule` that creates a daily baking plan. +The bakery bakes in batches of 5 orders per hour: + +```rust +baking_schedule(18) // Returns: [5, 5, 5, 3] (3 full batches + 3 remaining) +baking_schedule(10) // Returns: [5, 5] (2 full batches) +baking_schedule(3) // Returns: [3] (1 partial batch) +``` + +Use appropriate control flow constructs (`if`/`else`, `loop`) to solve each problem efficiently. diff --git a/exercises/concept/bakery-order-system/.docs/introduction.md b/exercises/concept/bakery-order-system/.docs/introduction.md new file mode 100644 index 00000000..08a141eb --- /dev/null +++ b/exercises/concept/bakery-order-system/.docs/introduction.md @@ -0,0 +1,67 @@ +# Introduction + +Control flow in Cairo allows you to control the execution path of your programs based on conditions and repetition. + +## Conditional Execution + +Use `if` expressions to execute code based on conditions: + +```rust +let number = 42; +if number > 0 { + println!("Positive number"); +} else if number < 0 { + println!("Negative number"); +} else { + println!("Zero"); +} +``` + +`if` is an expression, so it can return values: + +```rust +let result = if condition { 5 } else { 10 }; +``` + +## Loops + +Use `loop` to repeat code. +A loop runs indefinitely unless you stop it; use `break` to exit the loop when a condition is met. + +```rust +let mut counter = 0; +loop { + if counter == 5 { + break; + } + println!("Counter: {}", counter); + counter += 1; +} +``` + +Loops can return values using `break`: + +```rust +let result = loop { + counter += 1; + if counter == 10 { + break counter * 2; // Returns 20 + } +}; +``` + +Use `continue` to skip to the next iteration: + +```rust +let mut i = 0; +loop { + i += 1; + if i % 2 == 0 { + continue; // Skip even numbers + } + if i > 10 { + break; + } + println!("{}", i); // Only prints odd numbers +} +``` diff --git a/exercises/concept/bakery-order-system/.meta/config.json b/exercises/concept/bakery-order-system/.meta/config.json new file mode 100644 index 00000000..d7f7583f --- /dev/null +++ b/exercises/concept/bakery-order-system/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "0xNeshi" + ], + "files": { + "solution": [ + "src/lib.cairo" + ], + "test": [ + "tests/bakery_order_system.cairo" + ], + "exemplar": [ + ".meta/exemplar.cairo" + ], + "invalidator": [ + "Scarb.toml" + ] + }, + "blurb": "Learn Cairo's control flow constructs while managing a bakery's daily operations." +} diff --git a/exercises/concept/bakery-order-system/.meta/design.md b/exercises/concept/bakery-order-system/.meta/design.md new file mode 100644 index 00000000..d14ef88b --- /dev/null +++ b/exercises/concept/bakery-order-system/.meta/design.md @@ -0,0 +1,19 @@ +# Design + +## Learning Objectives + +- Know how to use control flow constructs. + +## Concepts + +- Control flow + +## Prerequisites + +- Match basics + +## Resources to Refer To + +- [Cairo Book - Control Flow][cf] + +[cf]: https://www.starknet.io/cairo-book/ch02-05-control-flow.html diff --git a/exercises/concept/bakery-order-system/.meta/exemplar.cairo b/exercises/concept/bakery-order-system/.meta/exemplar.cairo new file mode 100644 index 00000000..221a3b51 --- /dev/null +++ b/exercises/concept/bakery-order-system/.meta/exemplar.cairo @@ -0,0 +1,50 @@ +#[derive(Drop, PartialEq)] +pub enum Pastry { + Croissant, + Muffin, + Cookie, +} + +pub fn calculate_total(pastry: Pastry, quantity: u32) -> u32 { + let price_per_item = match pastry { + Pastry::Croissant => 3, + Pastry::Muffin => 2, + Pastry::Cookie => 1, + }; + + price_per_item * quantity +} + +pub fn apply_discount(total: u32) -> u32 { + if total >= 20 { + // 10% discount: multiply by 90, divide by 100 + (total * 90) / 100 + } else if total >= 10 { + // 5% discount: multiply by 95, divide by 100 + (total * 95) / 100 + } else { + // No discount + total + } +} + +pub fn baking_schedule(total_orders: u32) -> Array { + let mut schedule = ArrayTrait::new(); + let mut remaining_orders = total_orders; + + loop { + if remaining_orders == 0 { + break; + } + + if remaining_orders >= 5 { + schedule.append(5); + remaining_orders -= 5; + } else { + schedule.append(remaining_orders); + remaining_orders = 0; + } + }; + + schedule +} diff --git a/exercises/concept/bakery-order-system/Scarb.toml b/exercises/concept/bakery-order-system/Scarb.toml new file mode 100644 index 00000000..e5ec1ddf --- /dev/null +++ b/exercises/concept/bakery-order-system/Scarb.toml @@ -0,0 +1,7 @@ +[package] +name = "bakery_order_system" +version = "0.1.0" +edition = "2024_07" + +[dev-dependencies] +cairo_test = "2.9.2" diff --git a/exercises/concept/bakery-order-system/src/lib.cairo b/exercises/concept/bakery-order-system/src/lib.cairo new file mode 100644 index 00000000..9d32699c --- /dev/null +++ b/exercises/concept/bakery-order-system/src/lib.cairo @@ -0,0 +1,30 @@ +/// Types of pastries available in the bakery +#[derive(Drop, PartialEq)] +pub enum Pastry { + Croissant, + Muffin, + Cookie, +} + +/// Calculate the total cost of an order +pub fn calculate_total(pastry: Pastry, quantity: u32) -> u32 { + // Croissant: 3 coins, Muffin: 2 coins, Cookie: 1 coin + // Return total cost for the given quantity + panic!("implement `calculate_total`") +} + +/// Apply discount based on order size +pub fn apply_discount(total: u32) -> u32 { + // Orders >= 20 coins: 10% discount + // Orders >= 10 coins: 5% discount + // Orders < 10 coins: no discount + panic!("implement `apply_discount`") +} + +/// Generate the daily baking schedule +pub fn baking_schedule(total_orders: u32) -> Array { + // Bake in batches of 5 orders each + // Return an array showing how many orders are baked each hour + // Continue until all orders are completed + panic!("implement `baking_schedule`") +} diff --git a/exercises/concept/bakery-order-system/tests/bakery_order_system.cairo b/exercises/concept/bakery-order-system/tests/bakery_order_system.cairo new file mode 100644 index 00000000..128189b3 --- /dev/null +++ b/exercises/concept/bakery-order-system/tests/bakery_order_system.cairo @@ -0,0 +1,81 @@ +use bakery_order_system::{Pastry, calculate_total, apply_discount, baking_schedule}; + +#[test] +fn test_calculate_croissant_total() { + assert_eq!(calculate_total(Pastry::Croissant, 4), 12); + assert_eq!(calculate_total(Pastry::Croissant, 1), 3); +} + +#[test] +#[ignore] +fn test_calculate_muffin_total() { + assert_eq!(calculate_total(Pastry::Muffin, 5), 10); + assert_eq!(calculate_total(Pastry::Muffin, 3), 6); +} + +#[test] +#[ignore] +fn test_calculate_cookie_total() { + assert_eq!(calculate_total(Pastry::Cookie, 10), 10); + assert_eq!(calculate_total(Pastry::Cookie, 7), 7); +} + +#[test] +#[ignore] +fn test_no_discount() { + assert_eq!(apply_discount(5), 5); + assert_eq!(apply_discount(9), 9); +} + +#[test] +#[ignore] +fn test_five_percent_discount() { + assert_eq!(apply_discount(10), 9); // 10 * 0.95 = 9.5, rounded down to 9 + assert_eq!(apply_discount(15), 14); // 15 * 0.95 = 14.25, rounded down to 14 + assert_eq!(apply_discount(19), 18); // 19 * 0.95 = 18.05, rounded down to 18 +} + +#[test] +#[ignore] +fn test_ten_percent_discount() { + assert_eq!(apply_discount(20), 18); // 20 * 0.9 = 18 + assert_eq!(apply_discount(25), 22); // 25 * 0.9 = 22.5, rounded down to 22 + assert_eq!(apply_discount(30), 27); // 30 * 0.9 = 27 +} + +#[test] +#[ignore] +fn test_baking_schedule_multiple_batches() { + let result = baking_schedule(18); + let expected = [5, 5, 5, 3].span(); + + assert_eq!(result.len(), 4); + assert_eq!(result.span(), expected); +} + +#[test] +#[ignore] +fn test_baking_schedule_exact_batches() { + let result = baking_schedule(10); + let expected = [5, 5].span(); + + assert_eq!(result.len(), 2); + assert_eq!(result.span(), expected); +} + +#[test] +#[ignore] +fn test_baking_schedule_single_partial_batch() { + let result = baking_schedule(3); + let expected = [3].span(); + + assert_eq!(result.len(), 1); + assert_eq!(result.span(), expected); +} + +#[test] +#[ignore] +fn test_baking_schedule_zero_orders() { + let result = baking_schedule(0); + assert_eq!(result.len(), 0); +}