From 8f9565bdff9db635fa7faa63c7a78045f70790a0 Mon Sep 17 00:00:00 2001 From: Nenad Date: Wed, 2 Oct 2024 08:10:33 +0200 Subject: [PATCH] Implement `high-scores` exercise (#287) * Implement high-scores * Fix dot to a comma in config.json --- config.json | 8 ++ .../high-scores/.docs/instructions.md | 6 ++ .../practice/high-scores/.meta/config.json | 21 ++++ .../practice/high-scores/.meta/example.cairo | 77 ++++++++++++++ .../practice/high-scores/.meta/tests.toml | 46 ++++++++ exercises/practice/high-scores/Scarb.toml | 7 ++ exercises/practice/high-scores/src/lib.cairo | 25 +++++ .../high-scores/tests/high_scores.cairo | 100 ++++++++++++++++++ 8 files changed, 290 insertions(+) create mode 100644 exercises/practice/high-scores/.docs/instructions.md create mode 100644 exercises/practice/high-scores/.meta/config.json create mode 100644 exercises/practice/high-scores/.meta/example.cairo create mode 100644 exercises/practice/high-scores/.meta/tests.toml create mode 100644 exercises/practice/high-scores/Scarb.toml create mode 100644 exercises/practice/high-scores/src/lib.cairo create mode 100644 exercises/practice/high-scores/tests/high_scores.cairo diff --git a/config.json b/config.json index 2de4b60b..f8e3c013 100644 --- a/config.json +++ b/config.json @@ -748,6 +748,14 @@ "prerequisites": [], "difficulty": 2 }, + { + "slug": "high-scores", + "name": "High Scores", + "uuid": "993e43d8-e31b-4dd5-a73d-f5e99b499207", + "practices": [], + "prerequisites": [], + "difficulty": 3 + }, { "slug": "sum-of-multiples", "name": "Sum of Multiples", diff --git a/exercises/practice/high-scores/.docs/instructions.md b/exercises/practice/high-scores/.docs/instructions.md new file mode 100644 index 00000000..55802488 --- /dev/null +++ b/exercises/practice/high-scores/.docs/instructions.md @@ -0,0 +1,6 @@ +# Instructions + +Manage a game player's High Score list. + +Your task is to build a high-score component of the classic Frogger game, one of the highest selling and most addictive games of all time, and a classic of the arcade era. +Your task is to write methods that return the highest score from the list, the last added score and the three highest scores. diff --git a/exercises/practice/high-scores/.meta/config.json b/exercises/practice/high-scores/.meta/config.json new file mode 100644 index 00000000..94114ad6 --- /dev/null +++ b/exercises/practice/high-scores/.meta/config.json @@ -0,0 +1,21 @@ +{ + "authors": [ + "0xNeshi" + ], + "files": { + "solution": [ + "src/lib.cairo" + ], + "test": [ + "tests/high_scores.cairo" + ], + "example": [ + ".meta/example.cairo" + ], + "invalidator": [ + "Scarb.toml" + ] + }, + "blurb": "Manage a player's High Score list.", + "source": "Tribute to the eighties' arcade game Frogger" +} diff --git a/exercises/practice/high-scores/.meta/example.cairo b/exercises/practice/high-scores/.meta/example.cairo new file mode 100644 index 00000000..47041474 --- /dev/null +++ b/exercises/practice/high-scores/.meta/example.cairo @@ -0,0 +1,77 @@ +use core::dict::Felt252Dict; + +#[derive(Drop,)] +pub struct HighScores { + scores: Array, +} + +#[generate_trait] +pub impl HighScoresImpl of HighScoresTrait { + fn new(scores: Array) -> HighScores { + HighScores { scores } + } + + fn scores(self: @HighScores) -> Span { + self.scores.span() + } + + fn latest(self: @HighScores) -> Option { + match self.scores.get(self.scores.len() - 1) { + Option::Some(boxed) => Option::Some(*boxed.unbox()), + _ => Option::None + } + } + + fn personal_best(self: @HighScores) -> Option { + if self.scores.is_empty() { + return Option::None; + }; + let mut max = 0; + for score in self.scores.span() { + if score > @max { + max = *score; + } + }; + Option::Some(max) + } + + fn personal_top_three(self: @HighScores) -> Span { + let sorted = insertion_sort(self.scores.span()); + let take = if sorted.len() > 3 { + 3 + } else { + sorted.len() + }; + sorted.span().slice(0, take) + } +} + +fn insertion_sort(span: Span) -> Array { + if span.is_empty() { + return array![]; + } + + // Felt252Dict enables swapping and moving values + let mut sorted: Felt252Dict = Default::default(); + sorted.insert(0, *span[0]); + + // insert all elements in their sorted position + for i in 1 + ..span + .len() { + let elem = *span[i]; + let mut j: felt252 = i.into(); + while j != 0 && elem > sorted.get(j - 1) { + sorted.insert(j, sorted.get(j - 1)); + j -= 1; + }; + sorted.insert(j.into(), elem); + }; + + // collect all elements into an array + let mut sorted_arr: Array = array![]; + for i in 0..span.len() { + sorted_arr.append(sorted.get(i.into())); + }; + sorted_arr +} diff --git a/exercises/practice/high-scores/.meta/tests.toml b/exercises/practice/high-scores/.meta/tests.toml new file mode 100644 index 00000000..7c946338 --- /dev/null +++ b/exercises/practice/high-scores/.meta/tests.toml @@ -0,0 +1,46 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[1035eb93-2208-4c22-bab8-fef06769a73c] +description = "List of scores" + +[6aa5dbf5-78fa-4375-b22c-ffaa989732d2] +description = "Latest score" + +[b661a2e1-aebf-4f50-9139-0fb817dd12c6] +description = "Personal best" + +[3d996a97-c81c-4642-9afc-80b80dc14015] +description = "Top 3 scores -> Personal top three from a list of scores" + +[1084ecb5-3eb4-46fe-a816-e40331a4e83a] +description = "Top 3 scores -> Personal top highest to lowest" + +[e6465b6b-5a11-4936-bfe3-35241c4f4f16] +description = "Top 3 scores -> Personal top when there is a tie" + +[f73b02af-c8fd-41c9-91b9-c86eaa86bce2] +description = "Top 3 scores -> Personal top when there are less than 3" + +[16608eae-f60f-4a88-800e-aabce5df2865] +description = "Top 3 scores -> Personal top when there is only one" + +[2df075f9-fec9-4756-8f40-98c52a11504f] +description = "Top 3 scores -> Latest score after personal top scores" + +[809c4058-7eb1-4206-b01e-79238b9b71bc] +description = "Top 3 scores -> Scores after personal top scores" + +[ddb0efc0-9a86-4f82-bc30-21ae0bdc6418] +description = "Top 3 scores -> Latest score after personal best" + +[6a0fd2d1-4cc4-46b9-a5bb-2fb667ca2364] +description = "Top 3 scores -> Scores after personal best" diff --git a/exercises/practice/high-scores/Scarb.toml b/exercises/practice/high-scores/Scarb.toml new file mode 100644 index 00000000..679160df --- /dev/null +++ b/exercises/practice/high-scores/Scarb.toml @@ -0,0 +1,7 @@ +[package] +name = "high_scores" +version = "0.1.0" +edition = "2024_07" + +[dev-dependencies] +cairo_test = "2.8.2" diff --git a/exercises/practice/high-scores/src/lib.cairo b/exercises/practice/high-scores/src/lib.cairo new file mode 100644 index 00000000..9365d5b8 --- /dev/null +++ b/exercises/practice/high-scores/src/lib.cairo @@ -0,0 +1,25 @@ +#[derive(Drop,)] +pub struct HighScores {} + +#[generate_trait] +pub impl HighScoresImpl of HighScoresTrait { + fn new(scores: Array) -> HighScores { + panic!("implement `new`") + } + + fn scores(self: @HighScores) -> Span { + panic!("implement `scores`") + } + + fn latest(self: @HighScores) -> Option { + panic!("implement `latest`") + } + + fn personal_best(self: @HighScores) -> Option { + panic!("implement `personal_best`") + } + + fn personal_top_three(self: @HighScores) -> Span { + panic!("implement `personal_top_three`") + } +} diff --git a/exercises/practice/high-scores/tests/high_scores.cairo b/exercises/practice/high-scores/tests/high_scores.cairo new file mode 100644 index 00000000..ebba147b --- /dev/null +++ b/exercises/practice/high-scores/tests/high_scores.cairo @@ -0,0 +1,100 @@ +use high_scores::HighScoresTrait as HighScores; + +#[test] +fn list_of_scores() { + let expected: Array = array![30, 50, 20, 70]; + let high_scores = HighScores::new(expected.clone()); + assert_eq!(high_scores.scores(), expected.span()); +} + +#[test] +#[ignore] +fn latest_score() { + let high_scores = HighScores::new(array![100, 0, 90, 30]); + let expected = Option::Some(30); + assert_eq!(high_scores.latest(), expected); +} + +#[test] +#[ignore] +fn personal_best() { + let high_scores = HighScores::new(array![40, 100, 70]); + let expected = Option::Some(100); + assert_eq!(high_scores.personal_best(), expected); +} + +#[test] +#[ignore] +fn personal_top_three_from_a_list_of_scores() { + let high_scores = HighScores::new(array![10, 30, 90, 30, 100, 20, 10, 0, 30, 40, 40, 70, 70]); + let expected = array![100, 90, 70].span(); + assert_eq!(high_scores.personal_top_three(), expected); +} + +#[test] +#[ignore] +fn personal_top_highest_to_lowest() { + let high_scores = HighScores::new(array![20, 10, 30]); + let expected = array![30, 20, 10].span(); + assert_eq!(high_scores.personal_top_three(), expected); +} + +#[test] +#[ignore] +fn personal_top_when_there_is_a_tie() { + let high_scores = HighScores::new(array![40, 20, 40, 30]); + let expected = array![40, 40, 30].span(); + assert_eq!(high_scores.personal_top_three(), expected); +} + +#[test] +#[ignore] +fn personal_top_when_there_are_less_than_3() { + let high_scores = HighScores::new(array![30, 70]); + let expected = array![70, 30].span(); + assert_eq!(high_scores.personal_top_three(), expected); +} + +#[test] +#[ignore] +fn personal_top_when_there_is_only_one() { + let high_scores = HighScores::new(array![40]); + let expected = array![40].span(); + assert_eq!(high_scores.personal_top_three(), expected); +} + +#[test] +#[ignore] +fn latest_score_after_personal_top_scores() { + let high_scores = HighScores::new(array![70, 50, 20, 30]); + let expected = Option::Some(30); + high_scores.personal_top_three(); + assert_eq!(high_scores.latest(), expected); +} + +#[test] +#[ignore] +fn scores_after_personal_top_scores() { + let expected: Array = array![30, 50, 20, 70]; + let high_scores = HighScores::new(expected.clone()); + high_scores.personal_top_three(); + assert_eq!(high_scores.scores(), expected.span()); +} + +#[test] +#[ignore] +fn latest_score_after_personal_best() { + let high_scores = HighScores::new(array![20, 70, 15, 25, 30]); + let expected = Option::Some(30); + high_scores.personal_best().unwrap(); + assert_eq!(high_scores.latest(), expected); +} + +#[test] +#[ignore] +fn scores_after_personal_best() { + let expected: Array = array![20, 70, 15, 25, 30]; + let high_scores = HighScores::new(expected.clone()); + high_scores.personal_best().unwrap(); + assert_eq!(high_scores.scores(), expected.span()); +}