From 5de55da3b77c256c3bd28d7f8a4f618a2cd6cd7c Mon Sep 17 00:00:00 2001 From: Jeremy Walker Date: Mon, 20 Jan 2025 16:53:19 +0000 Subject: [PATCH] Add Level 3 content: Space Invaders + More! (#7325) * Add spot checker * Remove unused code (#7327) * Remove main functions * Add function use detectors * Finish off exercise * Add laser * Fix nested ands * Progress * Progress * Add new images * Add more instructions * Finish space invaders * Add digital clock exercise * Add if and rock-paper-scissors * Add rock paper sciissors * Improve exercises * Add instructions * Add more rbs images * Add rps description * Overflow-auto pre tag in instructions (#7345) * Parse md of errors * Improve error handling * Add rainbow ball * Improve maze * Add more interesting looking mazes * Don't wait for timeline completion if the timeline doesn't exist (#7346) * Show last test if all tests pass (#7347) * Fix tests * Add annotations for if statements * Update descriptions --------- Co-authored-by: Aron Demeter <66035744+dem4ron@users.noreply.github.com> Co-authored-by: dem4ron --- app/css/bootcamp/components/completed-bar.css | 2 +- .../components/editor-information-tooltip.css | 6 + .../bootcamp/components/exercise-widget.css | 42 +-- .../bootcamp/components/project-widget.css | 44 +-- app/css/bootcamp/components/prose.css | 1 + app/css/bootcamp/components/scenario.css | 2 +- app/css/bootcamp/exercises/digital-clock.css | 48 ++++ app/css/bootcamp/exercises/draw.css | 124 ++++++++- app/css/bootcamp/exercises/maze.css | 46 +++- .../exercises/rock-paper-scissors.css | 69 +++++ app/css/bootcamp/exercises/space-invaders.css | 62 +++++ app/css/packs/bootcamp-ui.css | 3 + app/fonts/ds-digi.woff2 | Bin 0 -> 6484 bytes .../assets/digital-clock/background.png | Bin 0 -> 17604 bytes .../assets/rock-paper-scissors/paper.png | Bin 0 -> 50523 bytes .../assets/rock-paper-scissors/rock.png | Bin 0 -> 48471 bytes .../assets/rock-paper-scissors/scissors.png | Bin 0 -> 39897 bytes .../bootcamp/assets/space-invaders/a1-bl.svg | 10 + .../bootcamp/assets/space-invaders/a1-br.svg | 10 + .../bootcamp/assets/space-invaders/a1-tl.svg | 10 + .../bootcamp/assets/space-invaders/a1-tr.svg | 10 + .../bootcamp/assets/space-invaders/laser.svg | 14 + .../end-line-information/describeError.ts | 4 +- .../SolveExercisePage/Tasks/useTasks.tsx | 15 +- .../TestResultsView/TestResultInfo.tsx | 5 +- .../SolveExercisePage/exercises/Exercise.ts | 41 ++- .../exercises/draw/DrawExercise.tsx | 41 ++- .../exercises/golf/GolfExercise.tsx | 97 +++++++ .../exercises/golf/fireFireworks.tsx | 0 .../exercises/maze/MazeExercise.tsx | 12 + .../RockPaperScissorsExercise.tsx | 107 ++++++++ .../space_invaders/SpaceInvadersExercise.tsx | 255 ++++++++++++++++++ .../exercises/time/DigitalClockExercise.tsx | 134 +++++++++ .../exercises/wordle/WordleExercise.tsx | 4 + .../getFirstFailingOrFirstTest.ts | 23 -- .../getFirstFailingOrLastTest.ts | 27 ++ .../useConstructRunCode.ts | 4 +- .../SolveExercisePage/test-runner/expect.ts | 7 + .../execProjectTest.ts | 13 +- .../generateExpects.ts | 1 - .../SolveExercisePage/utils/exerciseMap.ts | 8 + .../components/bootcamp/types/Matchers.d.ts | 1 + app/javascript/interpreter/error.ts | 1 + app/javascript/interpreter/executor.ts | 5 +- app/javascript/interpreter/frames.ts | 103 ++++++- app/javascript/interpreter/interpreter.ts | 4 +- .../interpreter/languages/jikiscript/error.ts | 4 +- .../languages/jikiscript/parser.ts | 56 +++- .../languages/jikiscript/scanner.ts | 15 +- .../interpreter/languages/jikiscript/token.ts | 3 +- .../interpreter/locales/en/translation.json | 29 +- .../locales/system/translation.json | 5 +- .../bootcamp/exercise_widget.html.haml | 2 +- .../bootcamp/project_widget.html.haml | 2 +- app/views/layouts/bootcamp-ui.haml | 10 + bootcamp_content/concepts/conditionals.md | 42 +++ bootcamp_content/concepts/config.json | 5 +- bootcamp_content/concepts/else-statements.md | 0 .../concepts/functions-arguments.md | 2 +- bootcamp_content/projects/drawing/config.json | 3 +- .../exercises/jumbled-house/config.json | 1 - .../drawing/exercises/loops/config.json | 2 +- .../drawing/exercises/penguin/config.json | 1 - .../exercises/rainbow-ball/config.json | 47 ++++ .../exercises/rainbow-ball/example.jiki | 43 +++ .../exercises/rainbow-ball/introduction.md | 60 +++++ .../drawing/exercises/rainbow-ball/stub.jiki | 15 ++ .../drawing/exercises/rainbow-ball/task-1.md | 1 + .../drawing/exercises/rainbow/config.json | 1 - .../exercises/sleepy-house/config.json | 52 ++++ .../exercises/sleepy-house/example.jiki | 97 +++++++ .../exercises/sleepy-house/introduction.md | 38 +++ .../drawing/exercises/sleepy-house/stub.jiki | 86 ++++++ .../drawing/exercises/sleepy-house/task-1.md | 18 ++ .../exercises/sprouting-flower/config.json | 21 +- .../exercises/structured-house/config.json | 1 - .../drawing/exercises/sunset/config.json | 1 - bootcamp_content/projects/golf/config.json | 2 +- .../golf/exercises/rolling-ball/config.json | 1 - .../golf/exercises/shot-checker/config.json | 209 ++++++++++++++ .../golf/exercises/shot-checker/example.jiki | 25 ++ .../exercises/shot-checker/introduction.md | 51 ++++ .../golf/exercises/shot-checker/stub.jiki | 21 ++ .../golf/exercises/shot-checker/task-1.md | 1 + .../exercises/automated-solve/config.json | 33 ++- .../exercises/automated-solve/introduction.md | 27 +- .../maze/exercises/automated-solve/stub.jiki | 9 +- .../maze/exercises/automated-solve/task-1.md | 7 +- .../maze/exercises/automated-solve/task-2.md | 9 +- .../maze/exercises/automated-solve/task-3.md | 9 +- .../maze/exercises/automated-solve/task-4.md | 9 +- .../maze/exercises/implement-move/config.json | 2 +- .../maze/exercises/manual-solve/config.json | 1 - .../exercises/even-or-odd/config.json | 2 +- .../positive-negative-or-zero/config.json | 2 +- .../projects/rock-paper-scissors/config.json | 2 +- .../exercises/determine-winner/config.json | 186 +++++++++++++ .../exercises/determine-winner/example.jiki | 19 ++ .../exercises/determine-winner/example2.jiki | 19 ++ .../determine-winner/introduction.md | 38 +++ .../exercises/determine-winner/stub.jiki | 6 + .../{basic => determine-winner}/task-1.md | 0 .../{basic => determine-winner}/task-2.md | 0 .../{basic => determine-winner}/task-3.md | 0 .../config.json | 2 +- .../example.jiki | 0 .../introduction.md | 0 .../{basic => win-with-a-function}/stub.jiki | 0 .../exercises/win-with-a-function/task-1.md | 9 + .../exercises/win-with-a-function/task-2.md | 7 + .../exercises/win-with-a-function/task-3.md | 5 + .../projects/space-invaders/config.json | 6 + .../exercises/scroll-and-shoot/config.json | 28 ++ .../exercises/scroll-and-shoot/example.jiki | 25 ++ .../scroll-and-shoot/introduction.md | 34 +++ .../exercises/scroll-and-shoot/stub.jiki | 9 + .../exercises/scroll-and-shoot/task-1.md | 3 + .../projects/space-invaders/introduction.md | 9 + bootcamp_content/projects/time/config.json | 6 + .../time/exercises/digital-clock/config.json | 143 ++++++++++ .../time/exercises/digital-clock/example.jiki | 14 + .../exercises/digital-clock/introduction.md | 31 +++ .../time/exercises/digital-clock/stub.jiki | 8 + .../time/exercises/digital-clock/task-1.md | 3 + .../time/exercises/digital-clock/task-2.md | 3 + .../time/exercises/digital-clock/task-3.md | 3 + .../time/exercises/digital-clock/task-4.md | 3 + .../projects/time/introduction.md | 7 + .../exercises/cloud-rain-sun/config.json | 1 - .../weather/exercises/sunshine/config.json | 1 - db/bootcamp_seeds.rb | 9 +- package.json | 3 +- .../languages/javascript/parser.test.ts | 6 +- .../languages/jikiscript/interpreter.test.ts | 39 ++- .../languages/jikiscript/scanner.test.ts | 4 + .../languages/jikiscript/syntaxErrors.test.ts | 21 +- yarn.lock | 5 + 137 files changed, 2969 insertions(+), 246 deletions(-) create mode 100644 app/css/bootcamp/exercises/digital-clock.css create mode 100644 app/css/bootcamp/exercises/rock-paper-scissors.css create mode 100644 app/css/bootcamp/exercises/space-invaders.css create mode 100644 app/fonts/ds-digi.woff2 create mode 100644 app/images/bootcamp/assets/digital-clock/background.png create mode 100644 app/images/bootcamp/assets/rock-paper-scissors/paper.png create mode 100644 app/images/bootcamp/assets/rock-paper-scissors/rock.png create mode 100644 app/images/bootcamp/assets/rock-paper-scissors/scissors.png create mode 100644 app/images/bootcamp/assets/space-invaders/a1-bl.svg create mode 100644 app/images/bootcamp/assets/space-invaders/a1-br.svg create mode 100644 app/images/bootcamp/assets/space-invaders/a1-tl.svg create mode 100644 app/images/bootcamp/assets/space-invaders/a1-tr.svg create mode 100644 app/images/bootcamp/assets/space-invaders/laser.svg create mode 100644 app/javascript/components/bootcamp/SolveExercisePage/exercises/golf/GolfExercise.tsx create mode 100644 app/javascript/components/bootcamp/SolveExercisePage/exercises/golf/fireFireworks.tsx create mode 100644 app/javascript/components/bootcamp/SolveExercisePage/exercises/rock_paper_scissors/RockPaperScissorsExercise.tsx create mode 100644 app/javascript/components/bootcamp/SolveExercisePage/exercises/space_invaders/SpaceInvadersExercise.tsx create mode 100644 app/javascript/components/bootcamp/SolveExercisePage/exercises/time/DigitalClockExercise.tsx delete mode 100644 app/javascript/components/bootcamp/SolveExercisePage/hooks/useConstructRunCode/getFirstFailingOrFirstTest.ts create mode 100644 app/javascript/components/bootcamp/SolveExercisePage/hooks/useConstructRunCode/getFirstFailingOrLastTest.ts create mode 100644 bootcamp_content/concepts/else-statements.md create mode 100644 bootcamp_content/projects/drawing/exercises/rainbow-ball/config.json create mode 100644 bootcamp_content/projects/drawing/exercises/rainbow-ball/example.jiki create mode 100644 bootcamp_content/projects/drawing/exercises/rainbow-ball/introduction.md create mode 100644 bootcamp_content/projects/drawing/exercises/rainbow-ball/stub.jiki create mode 100644 bootcamp_content/projects/drawing/exercises/rainbow-ball/task-1.md create mode 100644 bootcamp_content/projects/drawing/exercises/sleepy-house/config.json create mode 100644 bootcamp_content/projects/drawing/exercises/sleepy-house/example.jiki create mode 100644 bootcamp_content/projects/drawing/exercises/sleepy-house/introduction.md create mode 100644 bootcamp_content/projects/drawing/exercises/sleepy-house/stub.jiki create mode 100644 bootcamp_content/projects/drawing/exercises/sleepy-house/task-1.md create mode 100644 bootcamp_content/projects/golf/exercises/shot-checker/config.json create mode 100644 bootcamp_content/projects/golf/exercises/shot-checker/example.jiki create mode 100644 bootcamp_content/projects/golf/exercises/shot-checker/introduction.md create mode 100644 bootcamp_content/projects/golf/exercises/shot-checker/stub.jiki create mode 100644 bootcamp_content/projects/golf/exercises/shot-checker/task-1.md create mode 100644 bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/config.json create mode 100644 bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/example.jiki create mode 100644 bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/example2.jiki create mode 100644 bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/introduction.md create mode 100644 bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/stub.jiki rename bootcamp_content/projects/rock-paper-scissors/exercises/{basic => determine-winner}/task-1.md (100%) rename bootcamp_content/projects/rock-paper-scissors/exercises/{basic => determine-winner}/task-2.md (100%) rename bootcamp_content/projects/rock-paper-scissors/exercises/{basic => determine-winner}/task-3.md (100%) rename bootcamp_content/projects/rock-paper-scissors/exercises/{basic => win-with-a-function}/config.json (99%) rename bootcamp_content/projects/rock-paper-scissors/exercises/{basic => win-with-a-function}/example.jiki (100%) rename bootcamp_content/projects/rock-paper-scissors/exercises/{basic => win-with-a-function}/introduction.md (100%) rename bootcamp_content/projects/rock-paper-scissors/exercises/{basic => win-with-a-function}/stub.jiki (100%) create mode 100644 bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/task-1.md create mode 100644 bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/task-2.md create mode 100644 bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/task-3.md create mode 100644 bootcamp_content/projects/space-invaders/config.json create mode 100644 bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/config.json create mode 100644 bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/example.jiki create mode 100644 bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/introduction.md create mode 100644 bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/stub.jiki create mode 100644 bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/task-1.md create mode 100644 bootcamp_content/projects/space-invaders/introduction.md create mode 100644 bootcamp_content/projects/time/config.json create mode 100644 bootcamp_content/projects/time/exercises/digital-clock/config.json create mode 100644 bootcamp_content/projects/time/exercises/digital-clock/example.jiki create mode 100644 bootcamp_content/projects/time/exercises/digital-clock/introduction.md create mode 100644 bootcamp_content/projects/time/exercises/digital-clock/stub.jiki create mode 100644 bootcamp_content/projects/time/exercises/digital-clock/task-1.md create mode 100644 bootcamp_content/projects/time/exercises/digital-clock/task-2.md create mode 100644 bootcamp_content/projects/time/exercises/digital-clock/task-3.md create mode 100644 bootcamp_content/projects/time/exercises/digital-clock/task-4.md create mode 100644 bootcamp_content/projects/time/introduction.md diff --git a/app/css/bootcamp/components/completed-bar.css b/app/css/bootcamp/components/completed-bar.css index cf8cfbf2d7..21be8800b1 100644 --- a/app/css/bootcamp/components/completed-bar.css +++ b/app/css/bootcamp/components/completed-bar.css @@ -13,7 +13,7 @@ @apply text-18 leading-150 font-semibold text-gray-900; } .stat { - @apply font-semibold text-gray-600; + @apply font-semibold text-darkSuccessGreen; } c-prominent-link { diff --git a/app/css/bootcamp/components/editor-information-tooltip.css b/app/css/bootcamp/components/editor-information-tooltip.css index 726c9899ca..12cd4a432c 100644 --- a/app/css/bootcamp/components/editor-information-tooltip.css +++ b/app/css/bootcamp/components/editor-information-tooltip.css @@ -59,6 +59,12 @@ @apply pt-10 px-20 pb-12; @apply relative z-tooltip-content bg-white; @apply rounded-b-8; + & > *:first-child { + margin-top: 0; + } + & > *:last-child { + margin-bottom: 0; + } } .tooltip-arrow { border-right-color: rgb(239 68 68); diff --git a/app/css/bootcamp/components/exercise-widget.css b/app/css/bootcamp/components/exercise-widget.css index 3a6caeb42f..19447fbb55 100644 --- a/app/css/bootcamp/components/exercise-widget.css +++ b/app/css/bootcamp/components/exercise-widget.css @@ -3,25 +3,6 @@ @apply flex flex-col items-stretch; @apply bg-white; - &.available, - &.in_progress { - @apply shadow-base; - - .tag { - @apply border-gray-300; - background-image: url("icons/bootcamp-available.svg"); - } - } - &.locked { - @apply bg-gray-200; - @apply opacity-[0.5] cursor-not-allowed; - - .tag { - @apply border-gray-400; - background-image: url("icons/lock.svg"); - } - } - img { @apply w-[80px] h-[80px] mr-12; } @@ -51,8 +32,29 @@ @apply ml-auto; @apply border-1 rounded-circle; + } - &.completed { + &.available, + &.in_progress { + @apply shadow-base; + + .tag { + @apply border-gray-300; + background-image: url("icons/bootcamp-available.svg"); + } + } + &.locked { + @apply bg-gray-200; + @apply opacity-[0.5] cursor-not-allowed; + + .tag { + @apply border-gray-400; + background-image: url("icons/lock.svg"); + } + } + + &.completed { + .tag { background: #e7fdf6; border-color: #43b593; color: #43b593; diff --git a/app/css/bootcamp/components/project-widget.css b/app/css/bootcamp/components/project-widget.css index f607675d73..ef84f4f29c 100644 --- a/app/css/bootcamp/components/project-widget.css +++ b/app/css/bootcamp/components/project-widget.css @@ -4,26 +4,6 @@ @apply bg-white; @apply relative; - &.available { - @apply shadow-base; - - .tag { - @apply border-gray-300; - background-image: url("icons/bootcamp-available.svg"); - } - } - &.completed { - } - &.locked { - @apply bg-gray-200; - @apply opacity-[0.5] cursor-not-allowed; - - .tag { - @apply border-gray-400; - background-image: url("icons/lock.svg"); - } - } - img { @apply w-[80px] h-[80px] mr-16; } @@ -46,11 +26,33 @@ @apply ml-auto; @apply border-1 rounded-circle; + } - &.completed { + &.available { + @apply shadow-base; + + .tag { + @apply border-gray-300; + background-image: url("icons/bootcamp-available.svg"); + } + } + &.completed { + .tag { background: #e7fdf6; border-color: #43b593; color: #43b593; + background-image: url("icons/bootcamp-completed-check-circle.svg"); + background-repeat: no-repeat; + } + } + + &.locked { + @apply bg-gray-200; + @apply opacity-[0.5] cursor-not-allowed; + + .tag { + @apply border-gray-400; + background-image: url("icons/lock.svg"); } } } diff --git a/app/css/bootcamp/components/prose.css b/app/css/bootcamp/components/prose.css index 0056bf51a2..a18a0b2b7e 100644 --- a/app/css/bootcamp/components/prose.css +++ b/app/css/bootcamp/components/prose.css @@ -47,6 +47,7 @@ code { font-size: calc(var(--prose-base-text-size) - 1px); } + @apply overflow-auto; } *:not(pre) > code { @apply bg-blue-100; diff --git a/app/css/bootcamp/components/scenario.css b/app/css/bootcamp/components/scenario.css index 9cfc057abd..4e8b16c31d 100644 --- a/app/css/bootcamp/components/scenario.css +++ b/app/css/bootcamp/components/scenario.css @@ -15,7 +15,7 @@ @apply py-16 px-12; h3 { - @apply text-18 leading-140; + @apply text-17 leading-140; @apply mb-12; strong { @apply font-semibold; diff --git a/app/css/bootcamp/exercises/digital-clock.css b/app/css/bootcamp/exercises/digital-clock.css new file mode 100644 index 0000000000..944fade015 --- /dev/null +++ b/app/css/bootcamp/exercises/digital-clock.css @@ -0,0 +1,48 @@ +#bootcamp-solve-exercise-page { + .exercise-digital-clock { + background-image: url("bootcamp/assets/digital-clock/background.png"); + background-size: cover; + position: relative; + container-type: size; + + .time, + .meridiem { + font-family: "DSDigital"; + color: #ec1d26; + position: absolute; + font-weight: bold; + } + .time { + top: 32%; + left: 14%; + text-align: center; + font-size: 26cqw; + letter-spacing: 1cqw; + @apply grid; + grid-template-columns: 3fr 1fr 3fr; + + .hour, + .minute { + @apply grid grid-cols-2; + } + + .h1, + .h2, + .m1, + .m2 { + @apply text-right; + } + } + .meridiem { + font-size: 8cqw; + letter-spacing: 1cqw; + right: 10%; + &.am { + top: 36%; + } + &.pm { + top: 46%; + } + } + } +} diff --git a/app/css/bootcamp/exercises/draw.css b/app/css/bootcamp/exercises/draw.css index 0ccfaf44e3..833906e0e2 100644 --- a/app/css/bootcamp/exercises/draw.css +++ b/app/css/bootcamp/exercises/draw.css @@ -1,5 +1,6 @@ #bootcamp-solve-exercise-page { - .exercise-draw { + .exercise-draw, + .exercise-golf { @apply border-1 border-[#efefef] rounded-5; @apply bg-white; .bg-grid { @@ -15,3 +16,124 @@ } } } + +#bootcamp-solve-exercise-page { + .exercise-golf { + overflow: hidden; + .pyro { + position: absolute; + top: -20%; + left: 0; + width: 100%; + height: 100%; + overflow: hidden; + } + + .pyro > .before, + .pyro > .after { + position: absolute; + width: 5px; + height: 5px; + border-radius: 50%; + box-shadow: 0 0 #fff; + animation: bang 0.5s ease-out infinite backwards, + gravity 0.5s ease-in infinite backwards, + position 2.5s linear infinite backwards; + } + + .pyro > .after { + animation-delay: 1.25s, 1.25s, 1.25s; + animation-duration: 1.25s, 1.25s, 6.25s; + } + + @keyframes bang { + to { + box-shadow: -22.8px -0.21px hsl(205, 100%, 50%), + 63.07px -245.43px hsl(192, 100%, 50%), + 65.32px -185.75px hsl(179, 100%, 50%), + 102.49px -2.39px hsl(329, 100%, 50%), + 31.87px -27.35px hsl(45, 100%, 50%), + -118.2px -96.67px hsl(161, 100%, 50%), + 133.1px -241.29px hsl(335, 100%, 50%), + -119.48px -116.08px hsl(307, 100%, 50%), + 19.64px -78.75px hsl(208, 100%, 50%), + -89.42px -210.48px hsl(1, 100%, 50%), + 18.92px 15.69px hsl(258, 100%, 50%), + 95.55px -41.73px hsl(110, 100%, 50%), + 69.81px -137.35px hsl(33, 100%, 50%), + -135.49px -245.52px hsl(280, 100%, 50%), + -134.59px -154.29px hsl(101, 100%, 50%), + 104.7px -123.09px hsl(233, 100%, 50%), + -57.68px -155.87px hsl(150, 100%, 50%), + -76.97px -114.88px hsl(268, 100%, 50%), + -92.16px -217.84px hsl(132, 100%, 50%), + -58.75px -150.84px hsl(184, 100%, 50%), + -149.62px -99.51px hsl(111, 100%, 50%), + 58.01px 27.91px hsl(40, 100%, 50%), + -132.26px -12.65px hsl(218, 100%, 50%), + -90.31px -182.81px hsl(92, 100%, 50%), + 68.37px -236.77px hsl(119, 100%, 50%), + -70.38px -231.64px hsl(214, 100%, 50%), + 67.73px -22.6px hsl(191, 100%, 50%), + -51.2px -193.87px hsl(318, 100%, 50%), + 107.13px 21.89px hsl(300, 100%, 50%), + 83.84px -180.51px hsl(73, 100%, 50%), + 122.64px -23.57px hsl(354, 100%, 50%), + 88.04px -149.07px hsl(316, 100%, 50%), + 94.75px 8.9px hsl(296, 100%, 50%), + 104.12px 37.5px hsl(86, 100%, 50%), + -22.38px -211.89px hsl(209, 100%, 50%), + 95.01px -140.74px hsl(164, 100%, 50%), + 65.33px -155.1px hsl(123, 100%, 50%), + -93.98px -243.33px hsl(315, 100%, 50%), + 109.75px -140.47px hsl(308, 100%, 50%), + -0.31px -131.13px hsl(37, 100%, 50%), + 16.14px -239.49px hsl(85, 100%, 50%), + -16.31px 30.98px hsl(141, 100%, 50%), + -12.49px -110.72px hsl(200, 100%, 50%), + 149.21px -93.92px hsl(200, 100%, 50%), + -75.92px 47.33px hsl(129, 100%, 50%), + 20.02px -28.57px hsl(134, 100%, 50%), + 147.71px -167.23px hsl(244, 100%, 50%), + -129.99px -178.95px hsl(206, 100%, 50%), + -16.7px -63.95px hsl(304, 100%, 50%), + -134.97px -115.69px hsl(211, 100%, 50%); + } + } + + @keyframes gravity { + to { + transform: translateY(200px); + opacity: 0; + } + } + + @keyframes position { + 0%, + 19.9% { + margin-top: 10%; + margin-left: 40%; + } + 20%, + 39.9% { + margin-top: 40%; + margin-left: 30%; + } + 40%, + 59.9% { + margin-top: 20%; + margin-left: 70%; + } + 60%, + 79.9% { + margin-top: 30%; + margin-left: 20%; + } + 80%, + 99.9% { + margin-top: 30%; + margin-left: 80%; + } + } + } +} diff --git a/app/css/bootcamp/exercises/maze.css b/app/css/bootcamp/exercises/maze.css index 0c61a5e4e4..782c333880 100644 --- a/app/css/bootcamp/exercises/maze.css +++ b/app/css/bootcamp/exercises/maze.css @@ -55,16 +55,54 @@ background-color: white; border: 0.5px solid #000; &.blocked { - background-color: red; + background: radial-gradient( + circle, + transparent 20%, + #f9dcdc 20%, + #f9dcdc 80%, + transparent 80%, + transparent + ), + radial-gradient( + circle, + transparent 20%, + #f9dcdc 20%, + #f9dcdc 80%, + transparent 80%, + transparent + ) + 10px 10px, + linear-gradient(#f74545 0.8px, transparent 0.8px) 0 -0.4px, + linear-gradient(90deg, #f74545 0.8px, #f9dcdc 0.8px) -0.4px 0; + background-size: 10px 10px, 10px 10px, 5px 5px, 5px 5px; } &.start { - background-color: lightblue; + background-color: #e4e6ff; } &.target { - background-color: lightgreen; + background-color: #8effb3; + opacity: 1; + background-image: radial-gradient( + circle at center center, + #08b600, + #8effb3 + ), + repeating-radial-gradient( + circle at center center, + #08b600, + #08b600, + 11px, + transparent 22px, + transparent 11px + ); + background-blend-mode: multiply; } &.bomb { - background-color: purple; + @apply grid place-items-center; + &:before { + content: "🔥"; + font-size: 140%; + } } } diff --git a/app/css/bootcamp/exercises/rock-paper-scissors.css b/app/css/bootcamp/exercises/rock-paper-scissors.css new file mode 100644 index 0000000000..0f85366e26 --- /dev/null +++ b/app/css/bootcamp/exercises/rock-paper-scissors.css @@ -0,0 +1,69 @@ +#bootcamp-solve-exercise-page { + .exercise-rock-paper-scissors { + .container { + @apply w-full h-full; + @apply pt-[24%] px-[6%]; + @apply grid grid-cols-2 gap-[10%]; + container-type: size; + } + &.result-player_1 { + .player-1 { + @apply border-purple; + } + } + &.result-player_2 { + .player-2 { + @apply border-purple; + } + } + .player-1 { + &:after { + content: "Player 1"; + } + } + + .player-2 { + &:before { + transform: rotateY(180deg); + } + &:after { + content: "Player 2"; + } + } + .player-1, + .player-2 { + aspect-ratio: 1; + position: relative; + @apply font-semibold text-[7cqw]; + @apply border-3 border-borderColor5; + @apply rounded-8; + &::before { + content: ""; + @apply absolute inset-0; + @apply bg-center bg-no-repeat; + background-size: 80%; + } + + &:after { + position: absolute; + bottom: -30%; + @apply inset-x-0 text-center; + } + &.rock { + &:before { + background-image: url("bootcamp/assets/rock-paper-scissors/rock.png"); + } + } + &.paper { + &:before { + background-image: url("bootcamp/assets/rock-paper-scissors/paper.png"); + } + } + &.scissors { + &:before { + background-image: url("bootcamp/assets/rock-paper-scissors/scissors.png"); + } + } + } + } +} diff --git a/app/css/bootcamp/exercises/space-invaders.css b/app/css/bootcamp/exercises/space-invaders.css new file mode 100644 index 0000000000..ab31a9c7af --- /dev/null +++ b/app/css/bootcamp/exercises/space-invaders.css @@ -0,0 +1,62 @@ +#bootcamp-solve-exercise-page { + .exercise-space-invaders { + background-color: #222; + background-image: radial-gradient(rgba(255, 255, 255, 0.05), black); + .laser { + --laser-width: 20%; + width: var(--laser-width); + height: calc(var(--laser-width) * (115 / 200)); + transform: translateX(-50%); + + background-size: cover; + background-image: url("bootcamp/assets/space-invaders/laser.svg"); + + position: absolute; + bottom: 2%; + left: 12%; + + z-index: 20; + } + + .alien { + --alien-width: 8%; + width: var(--alien-width); + height: calc(var(--alien-width) * (160 / 200)); + transform: translateX(-50%); + + @apply absolute top-[20%] left-[20%]; + + @apply grid grid-cols-2; + filter: invert(1); + + .tl, + .tr, + .bl, + .br { + background-size: cover; + } + .tl { + background-image: url("bootcamp/assets/space-invaders/a1-tl.svg"); + } + .tr { + background-image: url("bootcamp/assets/space-invaders/a1-tr.svg"); + } + .bl { + background-image: url("bootcamp/assets/space-invaders/a1-bl.svg"); + } + .br { + background-image: url("bootcamp/assets/space-invaders/a1-br.svg"); + } + } + + .shot { + position: absolute; + opacity: 0; + width: 3px; + height: 15px; + background-color: white; + z-index: 10; + transform: translateX(-50%); + } + } +} diff --git a/app/css/packs/bootcamp-ui.css b/app/css/packs/bootcamp-ui.css index f7123ea671..b736be33fb 100644 --- a/app/css/packs/bootcamp-ui.css +++ b/app/css/packs/bootcamp-ui.css @@ -39,5 +39,8 @@ @import "../bootcamp/exercises/draw.css"; @import "../bootcamp/exercises/maze.css"; @import "../bootcamp/exercises/wordle.css"; +@import "../bootcamp/exercises/space-invaders.css"; +@import "../bootcamp/exercises/digital-clock.css"; +@import "../bootcamp/exercises/rock-paper-scissors.css"; @import "../components/textblock"; diff --git a/app/fonts/ds-digi.woff2 b/app/fonts/ds-digi.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..e492e134d2e4b04f51c39864a0ebf4661b5ed3ee GIT binary patch literal 6484 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 literal 0 HcmV?d00001 diff --git a/app/images/bootcamp/assets/digital-clock/background.png b/app/images/bootcamp/assets/digital-clock/background.png new file mode 100644 index 0000000000000000000000000000000000000000..5cd6685486225076a5e90d72b826a3585ba4a77e GIT binary patch literal 17604 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 literal 0 HcmV?d00001 diff --git a/app/images/bootcamp/assets/rock-paper-scissors/paper.png b/app/images/bootcamp/assets/rock-paper-scissors/paper.png new file mode 100644 index 0000000000000000000000000000000000000000..949f16d1a923d804715b80ab9d5eaef5bf26d802 GIT binary patch literal 50523 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

(O^+N>@Dr07YF*nKD(S=Mm-a`UzkFpSjX|=FFM8 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 literal 0 HcmV?d00001 diff --git a/app/images/bootcamp/assets/rock-paper-scissors/rock.png b/app/images/bootcamp/assets/rock-paper-scissors/rock.png new file mode 100644 index 0000000000000000000000000000000000000000..5ba88f54416868c48b2d25961eeaadf5faaa4dfe GIT binary patch literal 48471 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 b$9(>7K&p%t--2`m1AOFUls;BV83+9j{bJ5l literal 0 HcmV?d00001 diff --git a/app/images/bootcamp/assets/rock-paper-scissors/scissors.png b/app/images/bootcamp/assets/rock-paper-scissors/scissors.png new file mode 100644 index 0000000000000000000000000000000000000000..f5e7f99dfdfa273f020c1b5251317034520e3188 GIT binary patch literal 39897 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 + + + + + + + + + diff --git a/app/images/bootcamp/assets/space-invaders/a1-br.svg b/app/images/bootcamp/assets/space-invaders/a1-br.svg new file mode 100644 index 0000000000..06b75fe02c --- /dev/null +++ b/app/images/bootcamp/assets/space-invaders/a1-br.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/images/bootcamp/assets/space-invaders/a1-tl.svg b/app/images/bootcamp/assets/space-invaders/a1-tl.svg new file mode 100644 index 0000000000..689850c75c --- /dev/null +++ b/app/images/bootcamp/assets/space-invaders/a1-tl.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/images/bootcamp/assets/space-invaders/a1-tr.svg b/app/images/bootcamp/assets/space-invaders/a1-tr.svg new file mode 100644 index 0000000000..bc6e523f1c --- /dev/null +++ b/app/images/bootcamp/assets/space-invaders/a1-tr.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/images/bootcamp/assets/space-invaders/laser.svg b/app/images/bootcamp/assets/space-invaders/laser.svg new file mode 100644 index 0000000000..9f132be484 --- /dev/null +++ b/app/images/bootcamp/assets/space-invaders/laser.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/app/javascript/components/bootcamp/SolveExercisePage/CodeMirror/extensions/end-line-information/describeError.ts b/app/javascript/components/bootcamp/SolveExercisePage/CodeMirror/extensions/end-line-information/describeError.ts index 930b3a585e..81664fd090 100644 --- a/app/javascript/components/bootcamp/SolveExercisePage/CodeMirror/extensions/end-line-information/describeError.ts +++ b/app/javascript/components/bootcamp/SolveExercisePage/CodeMirror/extensions/end-line-information/describeError.ts @@ -4,6 +4,8 @@ * */ +import { marked } from 'marked' + import type { StaticError } from '@/interpreter/error' import { SyntaxError } from '@/interpreter/error' @@ -18,7 +20,7 @@ export function describeError(error: StaticError) { } let output = `

${errorHeading}

` - output += `

${error.message}

` + output += `
${marked.parse(error.message)}` if (error.context && error.context.didYouMean) { if (error.context.didYouMean.variable) { output += `

Did you mean ${error.context.didYouMean.variable}?

` diff --git a/app/javascript/components/bootcamp/SolveExercisePage/Tasks/useTasks.tsx b/app/javascript/components/bootcamp/SolveExercisePage/Tasks/useTasks.tsx index 37625497c8..a95e64b35c 100644 --- a/app/javascript/components/bootcamp/SolveExercisePage/Tasks/useTasks.tsx +++ b/app/javascript/components/bootcamp/SolveExercisePage/Tasks/useTasks.tsx @@ -4,6 +4,7 @@ import { type NextExercise, completeSolution } from './completeSolution' import type { TaskStore } from '../store/taskStore/taskStore' import useAnimationTimelineStore from '../store/animationTimelineStore' import { launchConfetti } from './launchConfetti' +import useTestStore from '../store/testStore' export type FinishLessonModalView = | 'initial' @@ -34,6 +35,7 @@ export function useTasks({ links: { completeSolution: completeSolutionLink }, } = useContext(SolveExercisePageContext) const { isTimelineComplete } = useAnimationTimelineStore() + const { inspectedTestResult } = useTestStore() // Setup stage means stores are being set up - so we are in the initialising state in the lifecycle of the app // see useSetupStores.ts @@ -50,12 +52,12 @@ export function useTasks({ } isSetupStage.current = false } else { - if ( - areAllTasksCompleted && - isTimelineComplete && - !wasFinishLessonModalShown - ) { - console.log('being ehre') + const shouldShowModal = areAllTasksCompleted && !wasFinishLessonModalShown + const hasTimeline = !!inspectedTestResult?.animationTimeline + // if we don't have a timeline, we don't need to wait for it to be complete + const isTimelineReady = hasTimeline ? isTimelineComplete : true + + if (shouldShowModal && isTimelineReady) { setIsFinishModalOpen(true) launchConfetti() setWasFinishLessonModalShown(true) @@ -65,6 +67,7 @@ export function useTasks({ areAllTasksCompleted, wasFinishLessonModalShown, isTimelineComplete, + inspectedTestResult, solution.status, ]) diff --git a/app/javascript/components/bootcamp/SolveExercisePage/TestResultsView/TestResultInfo.tsx b/app/javascript/components/bootcamp/SolveExercisePage/TestResultsView/TestResultInfo.tsx index 399274530a..c9982e09e5 100644 --- a/app/javascript/components/bootcamp/SolveExercisePage/TestResultsView/TestResultInfo.tsx +++ b/app/javascript/components/bootcamp/SolveExercisePage/TestResultsView/TestResultInfo.tsx @@ -15,7 +15,10 @@ export function TestResultInfo({ return null } if (firstExpect.testsType === 'state') { - return + let errorHtml = firstExpect.errorHtml || '' + errorHtml = errorHtml.replace(/{value}/, firstExpect.actual) + + return } else { return ( <> diff --git a/app/javascript/components/bootcamp/SolveExercisePage/exercises/Exercise.ts b/app/javascript/components/bootcamp/SolveExercisePage/exercises/Exercise.ts index d4247aa7cd..e7148dcdd0 100644 --- a/app/javascript/components/bootcamp/SolveExercisePage/exercises/Exercise.ts +++ b/app/javascript/components/bootcamp/SolveExercisePage/exercises/Exercise.ts @@ -1,5 +1,7 @@ +import { func } from 'prop-types' import type { Animation } from '../AnimationTimeline/AnimationTimeline' -import type { ExternalFunction } from '@/interpreter/executor' +import type { ExecutionContext, ExternalFunction } from '@/interpreter/executor' +import { InterpretResult } from '@/interpreter/interpreter' export abstract class Exercise { public availableFunctions!: ExternalFunction[] @@ -10,6 +12,7 @@ export abstract class Exercise { protected view!: HTMLElement protected container!: HTMLElement + protected functionCalls: Record> = {} public constructor(private slug: String) { this.createView() @@ -19,6 +22,39 @@ export abstract class Exercise { return code } + public recordFunctionUse(name: string, ...args) { + this.functionCalls[name] ||= {} + this.functionCalls[name][JSON.stringify(args)] ||= 0 + this.functionCalls[name][JSON.stringify(args)] += 1 + } + + public wasFunctionUsed( + _: ExecutionContext | InterpretResult, + name: string, + args: any[] | null, + times?: number + ): boolean { + let timesCalled + + if (this.functionCalls[name] === undefined) { + timesCalled = 0 + } else if (args !== null && args !== undefined) { + timesCalled = this.functionCalls[name][JSON.stringify(args)] + } else { + timesCalled = Object.values(this.functionCalls[name]).reduce( + (acc, count) => { + return acc + count + }, + 0 + ) + } + + if (times === null || times === undefined) { + return timesCalled >= 1 + } + return timesCalled === times + } + public lineNumberOffset = 0 public addAnimation(animation: Animation) { @@ -33,9 +69,6 @@ export abstract class Exercise { this.view.classList.add(cssClass) this.view.style.display = 'none' document.body.appendChild(this.view) - - this.container = document.createElement('div') - this.view.appendChild(this.container) } public getView(): HTMLElement { diff --git a/app/javascript/components/bootcamp/SolveExercisePage/exercises/draw/DrawExercise.tsx b/app/javascript/components/bootcamp/SolveExercisePage/exercises/draw/DrawExercise.tsx index c87412662c..3bdee6c3e0 100644 --- a/app/javascript/components/bootcamp/SolveExercisePage/exercises/draw/DrawExercise.tsx +++ b/app/javascript/components/bootcamp/SolveExercisePage/exercises/draw/DrawExercise.tsx @@ -79,8 +79,8 @@ export default class DrawExercise extends Exercise { private penColor = '#333333' private fillColor: FillColor = { type: 'hex', color: '#ff0000' } - constructor() { - super('draw') + constructor(slug = 'draw') { + super(slug) Object.assign(this.view.style, { display: 'none', @@ -133,7 +133,6 @@ export default class DrawExercise extends Exercise { // providing these as constant values saves us from recalculating them every time // update these values if the tooltip style changes // measure max tooltip width/height with the fn below - // console.log(this.tooltip.getBoundingClientRect().width, this.tooltip.getBoundingClientRect().height) const maxTooltipWidth = 75 const maxTooltipHeight = 32 // handle tooltip overflow-x @@ -287,6 +286,40 @@ export default class DrawExercise extends Exercise { return colors.size >= count } + public checkCanvasCoverage(_: InterpretResult, requiredPercentage) { + const gridSize = 100 + const grid = Array.from({ length: gridSize }, () => + Array(gridSize).fill(false) + ) + + // Iterate through each circle + this.shapes.forEach((shape) => { + if (!(shape instanceof Circle)) { + return + } + for (let x = 0; x < gridSize; x++) { + for (let y = 0; y < gridSize; y++) { + const distanceSquared = (x - shape.cx) ** 2 + (y - shape.cy) ** 2 + if (distanceSquared <= shape.radius ** 2) { + grid[x][y] = true // Mark grid point as covered + } + } + } + }) + + // Count covered points + let coveredPoints = 0 + grid.forEach((row) => { + coveredPoints += row.filter((point) => point).length + }) + + // Calculate coverage percentage + const totalPoints = gridSize * gridSize + const percentage = (coveredPoints / totalPoints) * 100 + + return percentage >= requiredPercentage + } + public assertAllArgumentsAreVariables(interpreterResult: InterpretResult) { return interpreterResult.frames.every((frame: Frame) => { if (!(frame.context instanceof ExpressionStatement)) { @@ -527,7 +560,7 @@ export default class DrawExercise extends Exercise { public availableFunctions = [ { - name: 'rand', + name: 'random_number', func: (_: any, min: number, max: number) => { return Math.floor(Math.random() * (max - min + 1)) + min }, diff --git a/app/javascript/components/bootcamp/SolveExercisePage/exercises/golf/GolfExercise.tsx b/app/javascript/components/bootcamp/SolveExercisePage/exercises/golf/GolfExercise.tsx new file mode 100644 index 0000000000..37ffd09065 --- /dev/null +++ b/app/javascript/components/bootcamp/SolveExercisePage/exercises/golf/GolfExercise.tsx @@ -0,0 +1,97 @@ +import type { ExecutionContext } from '@/interpreter/executor' +import { InterpretResult } from '@/interpreter/interpreter' +import { + CallExpression, + Expression, + LiteralExpression, +} from '@/interpreter/expression' +import { ExpressionStatement } from '@/interpreter/statement' +import { Frame } from '@/interpreter/frames' +import DrawExercise from '../draw' + +export default class GolfExercise extends DrawExercise { + constructor() { + super('golf') + + // Set some defaults + this.shotLength = 0 + } + + setShotLength(length: number) { + this.shotLength = length + } + getShotLength(_: ExecutionContext): number { + return this.shotLength + } + fireFireworks(executionCtx: ExecutionContext) { + this.recordFunctionUse('fire_fireworks') + + const pyro = document.createElement('div') + pyro.classList.add('pyro') + pyro.style.opacity = '0' + pyro.innerHTML = ` +
+
+ ` + this.view.appendChild(pyro) + + this.addAnimation({ + targets: `#${this.view.id} .pyro`, + duration: 1, + transformations: { + opacity: 1, + }, + offset: executionCtx.getCurrentTime(), + }) + this.addAnimation({ + targets: `#${this.view.id} .pyro`, + duration: 1, + transformations: { + opacity: 0, + }, + offset: executionCtx.getCurrentTime() + 2500, + }) + } + + // TODO: How do I get just the ones I want out of DrawExercise + // (circle, fillColorHex, fillColorRGB, fillColorHSL) + // and then add the new ones to this? + public availableFunctions = [ + { + name: 'clear', + func: this.clear.bind(this), + description: 'Clears the canvas.', + }, + { + name: 'get_shot_length', + func: this.getShotLength.bind(this), + description: "Returns the length of the player's shot", + }, + { + name: 'fire_fireworks', + func: this.fireFireworks.bind(this), + description: 'Fires celebratory fireworks.', + }, + { + name: 'circle', + func: this.circle.bind(this), + description: + 'It drew a circle with its center at (${arg1}, ${arg2}), and a radius of ${arg3}.', + }, + { + name: 'fill_color_hex', + func: this.fillColorHex.bind(this), + description: 'Changes the fill color using a hex string', + }, + { + name: 'fill_color_rgb', + func: this.fillColorRGB.bind(this), + description: 'Changes the fill color using three RGB values', + }, + { + name: 'fill_color_hsl', + func: this.fillColorHSL.bind(this), + description: 'Changes the fill color using three HSL values', + }, + ] +} diff --git a/app/javascript/components/bootcamp/SolveExercisePage/exercises/golf/fireFireworks.tsx b/app/javascript/components/bootcamp/SolveExercisePage/exercises/golf/fireFireworks.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/javascript/components/bootcamp/SolveExercisePage/exercises/maze/MazeExercise.tsx b/app/javascript/components/bootcamp/SolveExercisePage/exercises/maze/MazeExercise.tsx index c0f634a7e0..c7317fd7de 100644 --- a/app/javascript/components/bootcamp/SolveExercisePage/exercises/maze/MazeExercise.tsx +++ b/app/javascript/components/bootcamp/SolveExercisePage/exercises/maze/MazeExercise.tsx @@ -31,6 +31,9 @@ export default class MazeExercise extends Exercise { public constructor() { super('maze') + this.container = document.createElement('div') + this.view.appendChild(this.container) + this.cells = document.createElement('div') this.cells.classList.add('cells') this.container.appendChild(this.cells) @@ -99,6 +102,15 @@ export default class MazeExercise extends Exercise { this.gameOverWin(executionCtx) } + // If you hit an invalid square, blow up. + if (square === 4) { + executionCtx.logicError('Ouch! You walked into the fire!') + executionCtx.updateState('gameOver', true) + return + } else if (square === 3) { + this.gameOverWin(executionCtx) + } + this.addAnimation({ targets: this.characterSelector, duration: this.duration, diff --git a/app/javascript/components/bootcamp/SolveExercisePage/exercises/rock_paper_scissors/RockPaperScissorsExercise.tsx b/app/javascript/components/bootcamp/SolveExercisePage/exercises/rock_paper_scissors/RockPaperScissorsExercise.tsx new file mode 100644 index 0000000000..7daaf499b7 --- /dev/null +++ b/app/javascript/components/bootcamp/SolveExercisePage/exercises/rock_paper_scissors/RockPaperScissorsExercise.tsx @@ -0,0 +1,107 @@ +import React from 'react' +import { Exercise } from '../Exercise' +import { ExecutionContext } from '@/interpreter/executor' + +type Choice = 'rock' | 'paper' | 'scissors' +type Result = 'player_1' | 'player_2' | 'tie' + +export default class RockPaperScissorsExercise extends Exercise { + private player1Choice?: Choice + private player2Choice?: Choice + private expectedResult?: Result + private result?: Result + + public constructor() { + super('rock-paper-scissors') + + this.container = document.createElement('div') + this.container.classList.add('container') + this.view.appendChild(this.container) + + this.player1Elem = document.createElement('div') + this.player1Elem.classList.add('player', 'player-1') + this.container.appendChild(this.player1Elem) + + this.player2Elem = document.createElement('div') + this.player2Elem.classList.add('player', 'player-2') + this.container.appendChild(this.player2Elem) + } + + public getState() { + return { result: this.result } + } + + public setChoices(player1: Choice, player2: Choice) { + this.player1Choice = player1 + this.player2Choice = player2 + this.expectedResult = this.determineCorrectResult() + + this.player1Elem.classList.add(`${player1}`) + this.player2Elem.classList.add(`${player2}`) + this.view.classList.add(`result-${this.expectedResult}`) + } + + private determineCorrectResult(): Result | undefined { + if (!this.player1Choice || !this.player2Choice) { + return undefined + } + + if (this.player1Choice === this.player2Choice) { + return 'tie' + } + if (this.player1Choice === 'rock' && this.player2Choice === 'scissors') { + return 'player_1' + } + if (this.player1Choice === 'scissors' && this.player2Choice === 'paper') { + return 'player_1' + } + if (this.player1Choice === 'paper' && this.player2Choice === 'rock') { + return 'player_1' + } + + return 'player_2' + } + + public getPlayer1Choice(_: ExecutionContext): Choice | undefined { + return this.player1Choice + } + + public getPlayer2Choice(_: ExecutionContext): Choice | undefined { + return this.player2Choice + } + + public announceResult(executionCtx: ExecutionContext, result: Result) { + if (result !== 'player_1' && result !== 'player_2' && result !== 'tie') { + executionCtx.logicError( + 'Oh no! You announced an invalid result. There\'s chaos in the playing hall! Please announce either "player1", "player2" or "tie".' + ) + } + + this.result = result + if (result !== this.expectedResult) { + // TODO: Change logic error to be paramatized and sanitize the strings in the interpreter. + executionCtx.logicError( + `Oh no! You announced the wrong result. There's chaos in the playing hall!\n\nYou should have announced \`"${this.expectedResult}"\` but you announced \`"${result}"\`.` + ) + } + } + + public availableFunctions = [ + { + name: 'announce_result', + func: this.announceResult.bind(this), + description: + 'Announces the result of the game - a string of "player_1", "player_2" or "tie"', + }, + { + name: 'get_player_1_choice', + func: this.getPlayer1Choice.bind(this), + description: 'Returns the choice of player 1', + }, + { + name: 'get_player_2_choice', + func: this.getPlayer2Choice.bind(this), + description: 'Returns the choice of player 2', + }, + ] +} diff --git a/app/javascript/components/bootcamp/SolveExercisePage/exercises/space_invaders/SpaceInvadersExercise.tsx b/app/javascript/components/bootcamp/SolveExercisePage/exercises/space_invaders/SpaceInvadersExercise.tsx new file mode 100644 index 0000000000..adae680419 --- /dev/null +++ b/app/javascript/components/bootcamp/SolveExercisePage/exercises/space_invaders/SpaceInvadersExercise.tsx @@ -0,0 +1,255 @@ +import React from 'react' +import { Exercise } from '../Exercise' +import { ExecutionContext } from '@/interpreter/executor' + +type GameStatus = 'running' | 'won' | 'lost' +type AlienStatus = 'alive' | 'dead' +class Alien { + public status: AlienStatus + + public constructor( + public elem: HTMLElement, + row: number, + col: number, + type: number + ) { + this.status = 'alive' + } +} +export default class SpaceInvadersExercise extends Exercise { + private gameStatus: GameStatus = 'running' + private moveDuration = 200 + private shotDuration = 1000 + + private minLaserPosition = 0 + private maxLaserPosition = 10 + private laserStart = 12 + private laserStep = 7.5 + private laserPositions = Array.from( + { length: this.maxLaserPosition + 1 }, + (_, idx) => this.laserStart + idx * this.laserStep + ) + private laserPosition = 0 + + public constructor() { + super('space-invaders') + + this.laser = document.createElement('div') + this.laser.classList.add('laser') + this.laser.style.left = `${this.laserPositions[this.laserPosition]}%` + this.view.appendChild(this.laser) + + this.addAliens([ + [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], + [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], + [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], + ]) + } + + public getState() { + return { gameStatus: this.gameStatus } + } + + private addAliens(rows) { + this.aliens = rows.map((row, rowIdx) => { + return row.map((type, colIdx) => { + if (type === 0) return null + return this.addAlien(rowIdx, colIdx, type) + }) + }) + } + + private addAlien(row: number, col: number, type: number) { + const alien = document.createElement('div') + alien.classList.add('alien') + alien.id = `alien-${Math.random().toString(36).slice(2, 11)}` + alien.style.left = `${this.laserStart + col * this.laserStep}%` + alien.style.top = `${10 + row * 11}%` + + const parts = ['tl', 'tr', 'bl', 'br'] + parts.forEach((pos) => { + const part = document.createElement('div') + part.classList.add(pos) + alien.appendChild(part) + }) + this.view.appendChild(alien) + + return new Alien(alien, row, col, type) + } + + private moveLaser(executionCtx: ExecutionContext) { + this.addAnimation({ + targets: `#${this.view.id} .laser`, + duration: this.moveDuration, + transformations: { + opacity: 1, + left: `${this.laserPositions[this.laserPosition]}%`, + }, + offset: executionCtx.getCurrentTime(), + }) + executionCtx.fastForward(this.moveDuration) + } + + private checkForWin(executionCtx: ExecutionContext) { + const win = this.aliens.every((row) => + row.every((alien) => alien === null || alien.status === 'dead') + ) + if (win) { + this.gameStatus = 'won' + executionCtx.updateState('gameOver', true) + } + } + + public isAlienAbove(executionCtx: ExecutionContext): boolean { + return this.aliens.some((row) => { + const alien = row[this.laserPosition] + if (alien === null) { + return false + } + if (alien.status == 'dead') { + return false + } + + return true + }) + } + + public shoot(executionCtx: ExecutionContext) { + let targetRow = null + let targetAlien: Alien | null = null + this.aliens.forEach((row, rowIdx) => { + const alien = row[this.laserPosition] + if (alien === null) { + return + } + if (alien.status == 'dead') { + return + } + + targetRow = rowIdx + targetAlien = row[this.laserPosition] + }) + + let targetTop + if (targetRow === null) { + targetTop = -10 + } else { + targetTop = `${10 + targetRow * 11}%` + } + + // TODO: Vary speed based on distance + const duration = this.shotDuration + + const shot = document.createElement('div') + shot.classList.add('shot') + shot.id = `shot-${Math.random().toString(36).slice(2, 11)}` + shot.style.left = `${this.laserPositions[this.laserPosition]}%` + shot.style.top = '85%' + shot.style.opacity = '0' + this.view.appendChild(shot) + + this.addAnimation({ + targets: `#${this.view.id} #${shot.id}`, + duration: 1, + transformations: { opacity: 1 }, + offset: executionCtx.getCurrentTime(), + }) + this.addAnimation({ + targets: `#${this.view.id} #${shot.id}`, + duration: duration, + transformations: { top: targetTop }, + offset: executionCtx.getCurrentTime(), + easing: 'linear', + }) + + if (targetAlien === null) { + executionCtx.logicError('Oh no, you missed. Wasting ammo is not allowed!') + this.gameStatus = 'lost' + executionCtx.updateState('gameOver', true) + } else { + const alien = targetAlien as Alien + alien.status = 'dead' + ;[ + ['tl', -10, -10, -180], + ['tr', 10, -10, 180], + ['bl', -10, 10, -180], + ['br', 10, 10, 180], + ].forEach(([pos, x, y, rotate]) => { + this.addAnimation({ + targets: `#${this.view.id} #${alien.elem.id} .${pos}`, + duration: 300, + transformations: { + translateX: x, + translateY: y, + rotate: rotate, + opacity: 0, + }, + offset: executionCtx.getCurrentTime() + duration, + }) + }) + this.addAnimation({ + targets: `#${this.view.id} #${shot.id}`, + duration: 1, + transformations: { opacity: 0 }, + offset: executionCtx.getCurrentTime() + duration, + }) + executionCtx.fastForward(30) + + this.checkForWin(executionCtx) + } + + // const target = this.aliens[3][this.laserPosition] + // target = this.aliens[0][0] + // this.laserLeft -= this.moveStep + // if (this.laserLeft < 0) { + // } + } + + public moveLeft(executionCtx: ExecutionContext) { + if (this.laserPosition == this.minLaserPosition) { + executionCtx.logicError('Oh no, you tried to move off the edge!') + executionCtx.updateState('gameOver', true) + } + + this.laserPosition -= 1 + this.moveLaser(executionCtx) + } + + public moveRight(executionCtx: ExecutionContext) { + if (this.laserPosition == this.maxLaserPosition) { + executionCtx.logicError('Oh no, you tried to move off the edge!') + executionCtx.updateState('gameOver', true) + } + + this.laserPosition += 1 + this.moveLaser(executionCtx) + } + + public availableFunctions = [ + { + name: 'runGame', + func: this.runGame, + description: '', + }, + { + name: 'move_left', + func: this.moveLeft.bind(this), + description: '', + }, + { + name: 'move_right', + func: this.moveRight.bind(this), + description: '', + }, + { + name: 'shoot', + func: this.shoot.bind(this), + description: '', + }, + { + name: 'is_alien_above', + func: this.isAlienAbove.bind(this), + description: '', + }, + ] +} diff --git a/app/javascript/components/bootcamp/SolveExercisePage/exercises/time/DigitalClockExercise.tsx b/app/javascript/components/bootcamp/SolveExercisePage/exercises/time/DigitalClockExercise.tsx new file mode 100644 index 0000000000..9723fcb9f7 --- /dev/null +++ b/app/javascript/components/bootcamp/SolveExercisePage/exercises/time/DigitalClockExercise.tsx @@ -0,0 +1,134 @@ +import React from 'react' +import { Exercise } from '../Exercise' +import { ExecutionContext } from '@/interpreter/executor' + +type GameStatus = 'running' | 'won' | 'lost' +type AlienStatus = 'alive' | 'dead' +class Alien { + public status: AlienStatus + + public constructor( + public elem: HTMLElement, + row: number, + col: number, + type: number + ) { + this.status = 'alive' + } +} +export default class DigitalClockExercise extends Exercise { + private displayedTime?: String + + public constructor() { + super('digital-clock') + + const time = new Date() + this.hours = time.getHours() + this.minutes = time.getMinutes() + + this.timeElem = document.createElement('div') + this.timeElem.classList.add('time') + this.view.appendChild(this.timeElem) + + this.hourElem = document.createElement('div') + this.hourElem.classList.add('hour') + this.timeElem.appendChild(this.hourElem) + + this.h1Elem = document.createElement('div') + this.h1Elem.classList.add('h1') + this.hourElem.appendChild(this.h1Elem) + + this.h2Elem = document.createElement('div') + this.h2Elem.classList.add('h2') + this.hourElem.appendChild(this.h2Elem) + + this.colonElem = document.createElement('div') + this.colonElem.classList.add('colon') + this.colonElem.innerText = ':' + this.timeElem.appendChild(this.colonElem) + + this.minuteElem = document.createElement('div') + this.minuteElem.classList.add('minute') + this.timeElem.appendChild(this.minuteElem) + + this.m1Elem = document.createElement('div') + this.m1Elem.classList.add('m1') + this.minuteElem.appendChild(this.m1Elem) + + this.m2Elem = document.createElement('div') + this.m2Elem.classList.add('m2') + this.minuteElem.appendChild(this.m2Elem) + + this.meridiem = document.createElement('div') + this.meridiem.classList.add('meridiem') + this.view.appendChild(this.meridiem) + } + + public getState() { + console.log(this.displayedTime) + return { displayedTime: this.displayedTime } + } + + public setTime(hours: number, minutes: number) { + this.hours = hours + this.minutes = minutes + } + + public didDisplayCurrentTime(executionCtx: ExecutionContext) { + if (this.displayedTime === undefined) { + return false + } + + const ampm = this.hours >= 12 ? 'pm' : 'am' + const normalisedHours = this.hours > 12 ? this.hours - 12 : this.hours + + return this.displayedTime == `${normalisedHours}:${this.minutes}${ampm}` + } + + public currentTimeHour(_: ExecutionContext): number { + return this.hours + } + public currentTimeMinute(_: ExecutionContext): number { + return this.minutes + } + + public displayTime( + executionCtx: ExecutionContext, + hours: string, + mins: string, + ampm: string + ) { + this.displayedTime = `${hours}:${mins}${ampm}` + + const [h1, h2] = String(hours).padStart(2, '0').split('') + const [m1, m2] = String(mins).padStart(2, '0').split('') + + this.h1Elem.innerText = h1 + this.h2Elem.innerText = h2 + this.m1Elem.innerText = m1 + this.m2Elem.innerText = m2 + + if (ampm === 'am' || ampm === 'pm') { + this.meridiem.innerText = ampm + this.meridiem.classList.add(ampm) + } + } + + public availableFunctions = [ + { + name: 'current_time_hour', + func: this.currentTimeHour.bind(this), + description: 'Returns the current hour', + }, + { + name: 'current_time_minute', + func: this.currentTimeMinute.bind(this), + description: 'Returns the current minute', + }, + { + name: 'display_time', + func: this.displayTime.bind(this), + description: 'Writes the hour, minute and am/pm onto the digital display', + }, + ] +} diff --git a/app/javascript/components/bootcamp/SolveExercisePage/exercises/wordle/WordleExercise.tsx b/app/javascript/components/bootcamp/SolveExercisePage/exercises/wordle/WordleExercise.tsx index 7e5897609d..dd852d03e6 100644 --- a/app/javascript/components/bootcamp/SolveExercisePage/exercises/wordle/WordleExercise.tsx +++ b/app/javascript/components/bootcamp/SolveExercisePage/exercises/wordle/WordleExercise.tsx @@ -83,6 +83,10 @@ export default class WordleExercise extends Exercise { public availableFunctions = [] private setupView() { + this.container = document.createElement('div') + this.container.classList.add('container') + this.view.appendChild(this.container) + const board = document.createElement('div') board.classList.add('board') this.container.appendChild(board) diff --git a/app/javascript/components/bootcamp/SolveExercisePage/hooks/useConstructRunCode/getFirstFailingOrFirstTest.ts b/app/javascript/components/bootcamp/SolveExercisePage/hooks/useConstructRunCode/getFirstFailingOrFirstTest.ts deleted file mode 100644 index 86c2fa20fb..0000000000 --- a/app/javascript/components/bootcamp/SolveExercisePage/hooks/useConstructRunCode/getFirstFailingOrFirstTest.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Returns the first failing test or the first test if all tests pass - */ -export function getFirstFailingOrFirstTest( - testResults: TestSuiteResult, - inspectedTestResult: NewTestResult | null -): NewTestResult { - // if inspectedTestResult is already set and it fails again, keep it. - if ( - inspectedTestResult && - inspectedTestResult.status === 'fail' && - testResults.tests[inspectedTestResult.testIndex].status === 'fail' - ) { - return testResults.tests[inspectedTestResult.testIndex] - } else { - const firstFailingOrFirstIndex = Math.max( - testResults.tests.findIndex((test) => test.status === 'fail'), - 0 - ) - - return testResults.tests[firstFailingOrFirstIndex] - } -} diff --git a/app/javascript/components/bootcamp/SolveExercisePage/hooks/useConstructRunCode/getFirstFailingOrLastTest.ts b/app/javascript/components/bootcamp/SolveExercisePage/hooks/useConstructRunCode/getFirstFailingOrLastTest.ts new file mode 100644 index 0000000000..4860b962b0 --- /dev/null +++ b/app/javascript/components/bootcamp/SolveExercisePage/hooks/useConstructRunCode/getFirstFailingOrLastTest.ts @@ -0,0 +1,27 @@ +/** + * Returns the first failing test or the last test if all tests pass + */ +export function getFirstFailingOrLastTest( + testResults: TestSuiteResult, + inspectedTestResult: NewTestResult | null +): NewTestResult { + // if inspectedTestResult is already set and it fails again, keep it. + if ( + inspectedTestResult && + inspectedTestResult.status === 'fail' && + testResults.tests[inspectedTestResult.testIndex].status === 'fail' + ) { + return testResults.tests[inspectedTestResult.testIndex] + } else { + const firstFailingOrLastIndex = (() => { + const firstFailingIndex = testResults.tests.findIndex( + (test) => test.status === 'fail' + ) + return firstFailingIndex !== -1 + ? firstFailingIndex + : testResults.tests.length - 1 + })() + + return testResults.tests[firstFailingOrLastIndex] + } +} diff --git a/app/javascript/components/bootcamp/SolveExercisePage/hooks/useConstructRunCode/useConstructRunCode.ts b/app/javascript/components/bootcamp/SolveExercisePage/hooks/useConstructRunCode/useConstructRunCode.ts index d78bdcbf6f..e4dbbadd01 100644 --- a/app/javascript/components/bootcamp/SolveExercisePage/hooks/useConstructRunCode/useConstructRunCode.ts +++ b/app/javascript/components/bootcamp/SolveExercisePage/hooks/useConstructRunCode/useConstructRunCode.ts @@ -8,7 +8,7 @@ import generateAndRunTestSuite from '../../test-runner/generateAndRunTestSuite/g import { getAndInitializeExerciseClass } from '../../utils/exerciseMap' import { showError } from '../../utils/showError' import { submitCode } from './submitCode' -import { getFirstFailingOrFirstTest } from './getFirstFailingOrFirstTest' +import { getFirstFailingOrLastTest } from './getFirstFailingOrLastTest' import type { EditorView } from 'codemirror' import { getCodeMirrorFieldValue } from '../../CodeMirror/getCodeMirrorFieldValue' import { readOnlyRangesStateField } from '../../CodeMirror/extensions/read-only-ranges/readOnlyRanges' @@ -118,7 +118,7 @@ export function useConstructRunCode({ markTaskAsCompleted(testResults) - const automaticallyInspectedTest = getFirstFailingOrFirstTest( + const automaticallyInspectedTest = getFirstFailingOrLastTest( testResults, inspectedTestResult ) diff --git a/app/javascript/components/bootcamp/SolveExercisePage/test-runner/expect.ts b/app/javascript/components/bootcamp/SolveExercisePage/test-runner/expect.ts index fdc0009209..1c03f3143c 100644 --- a/app/javascript/components/bootcamp/SolveExercisePage/test-runner/expect.ts +++ b/app/javascript/components/bootcamp/SolveExercisePage/test-runner/expect.ts @@ -50,6 +50,13 @@ export function expect({ pass: actual === true, } }, + toBeFalse() { + return { + ...returnObject, + expected: false, + pass: actual === false, + } + }, toEqual(expected: any) { return { ...returnObject, diff --git a/app/javascript/components/bootcamp/SolveExercisePage/test-runner/generateAndRunTestSuite/execProjectTest.ts b/app/javascript/components/bootcamp/SolveExercisePage/test-runner/generateAndRunTestSuite/execProjectTest.ts index ee3ae8450d..f252412b8a 100644 --- a/app/javascript/components/bootcamp/SolveExercisePage/test-runner/generateAndRunTestSuite/execProjectTest.ts +++ b/app/javascript/components/bootcamp/SolveExercisePage/test-runner/generateAndRunTestSuite/execProjectTest.ts @@ -1,4 +1,4 @@ -import { interpret } from '@/interpreter/interpreter' +import { evaluateFunction, interpret } from '@/interpreter/interpreter' import { type Project } from '@/components/bootcamp/SolveExercisePage/utils/exerciseMap' import type { Exercise } from '../../exercises/Exercise' import { AnimationTimeline } from '../../AnimationTimeline/AnimationTimeline' @@ -27,7 +27,16 @@ export function execProjectTest( language: 'JikiScript', languageFeatures: options.config.interpreterOptions, } - const evaluated = interpret(options.studentCode, context) + let evaluated + if (testData.function) { + evaluated = evaluateFunction( + options.studentCode, + context, + testData.function + ) + } else { + evaluated = interpret(options.studentCode, context) + } const { frames } = evaluated diff --git a/app/javascript/components/bootcamp/SolveExercisePage/test-runner/generateAndRunTestSuite/generateExpects.ts b/app/javascript/components/bootcamp/SolveExercisePage/test-runner/generateAndRunTestSuite/generateExpects.ts index f7675fb4d1..1b1712e494 100644 --- a/app/javascript/components/bootcamp/SolveExercisePage/test-runner/generateAndRunTestSuite/generateExpects.ts +++ b/app/javascript/components/bootcamp/SolveExercisePage/test-runner/generateAndRunTestSuite/generateExpects.ts @@ -66,7 +66,6 @@ function generateExpectsForStateTests( : argsString.split(',').map((arg) => safe_eval(arg.trim())) // And then we get the function and call it. - // console.log(fnName) const fn = exercise[fnName] actual = fn.bind(exercise).call(exercise, interpreterResult, ...args) } diff --git a/app/javascript/components/bootcamp/SolveExercisePage/utils/exerciseMap.ts b/app/javascript/components/bootcamp/SolveExercisePage/utils/exerciseMap.ts index 2b0eca9941..02319d7a9c 100644 --- a/app/javascript/components/bootcamp/SolveExercisePage/utils/exerciseMap.ts +++ b/app/javascript/components/bootcamp/SolveExercisePage/utils/exerciseMap.ts @@ -1,6 +1,10 @@ import DrawExercise from '../exercises/draw/DrawExercise' import MazeExercise from '../exercises/maze/MazeExercise' +import SpaceInvadersExercise from '../exercises/space_invaders/SpaceInvadersExercise' import WordleExercise from '../exercises/wordle/WordleExercise' +import GolfExercise from '../exercises/golf/GolfExercise' +import DigitalClockExercise from '../exercises/time/DigitalClockExercise' +import RockPaperScissorsExercise from '../exercises/rock_paper_scissors/RockPaperScissorsExercise' import { Exercise } from '../exercises/Exercise' @@ -10,6 +14,10 @@ const projectsCache = new Map() projectsCache.set('draw', DrawExercise) projectsCache.set('maze', MazeExercise) projectsCache.set('wordle', WordleExercise) +projectsCache.set('golf', GolfExercise) +projectsCache.set('space-invaders', SpaceInvadersExercise) +projectsCache.set('digital-clock', DigitalClockExercise) +projectsCache.set('rock-paper-scissors', RockPaperScissorsExercise) export default projectsCache diff --git a/app/javascript/components/bootcamp/types/Matchers.d.ts b/app/javascript/components/bootcamp/types/Matchers.d.ts index e2bf92f3f1..4be6551611 100644 --- a/app/javascript/components/bootcamp/types/Matchers.d.ts +++ b/app/javascript/components/bootcamp/types/Matchers.d.ts @@ -1,6 +1,7 @@ declare type AvailableMatchers = | 'toBe' | 'toBeTrue' + | 'toBeFalse' | 'toExist' | 'toNotExist' | 'toEqual' diff --git a/app/javascript/interpreter/error.ts b/app/javascript/interpreter/error.ts index e7a5aa5ae9..4c246318f7 100644 --- a/app/javascript/interpreter/error.ts +++ b/app/javascript/interpreter/error.ts @@ -36,6 +36,7 @@ export type RuntimeErrorType = | 'OperandsMustBeTwoNumbersOrTwoStrings' | 'InvalidIndexGetterTarget' | 'InvalidIndexSetterTarget' + | 'UnexpectedEqualsForEquality' export type StaticErrorType = | DisabledLanguageFeatureErrorType diff --git a/app/javascript/interpreter/executor.ts b/app/javascript/interpreter/executor.ts index 95d3a7746f..148397f394 100644 --- a/app/javascript/interpreter/executor.ts +++ b/app/javascript/interpreter/executor.ts @@ -273,7 +273,6 @@ export class Executor } if (count > 1000) { - console.log('HERE') this.error( 'RepeatCountMustBeLessThanOneThousand', statement.count.location, @@ -708,6 +707,10 @@ export class Executor ...result, value: left.value % right.value, } + case 'EQUAL': + this.error('UnexpectedEqualsForEquality', expression.location, { + expression, + }) } this.error('InvalidBinaryExpression', expression.location, { expression }) diff --git a/app/javascript/interpreter/frames.ts b/app/javascript/interpreter/frames.ts index 072e353bc3..794533433f 100644 --- a/app/javascript/interpreter/frames.ts +++ b/app/javascript/interpreter/frames.ts @@ -4,8 +4,16 @@ import { RuntimeError } from './error' export type FrameExecutionStatus = 'SUCCESS' | 'ERROR' import type { EvaluationResult } from './evaluation-result' import type { ExternalFunction } from './executor' -import { Expression } from './expression' -import { Statement } from './statement' +import { + BinaryExpression, + Expression, + GroupingExpression, + LiteralExpression, + LogicalExpression, + VariableExpression, +} from './expression' +import { IfStatement, Statement } from './statement' +import exp from 'constants' export type FrameType = 'ERROR' | 'REPEAT' | 'EXPRESSION' @@ -105,12 +113,93 @@ function describeForeachStatement(frame: FrameWithResult) { return output } -function describeIfStatement(frame: FrameWithResult) { - // TODO!! +function describeExpression(expression: Expression) { + if (expression instanceof VariableExpression) { + return describeVariableExpression(expression) + } + if (expression instanceof LiteralExpression) { + return describeLiteralExpression(expression) + } + if (expression instanceof GroupingExpression) { + return describeGroupingExpression(expression) + } + if (expression instanceof BinaryExpression) { + return describeBinaryExpression(expression) + } + if (expression instanceof LogicalExpression) { + return describeLogicalExpression(expression) + } + return '' +} + +function describeVariableExpression(expression: VariableExpression): string { + return `the ${expression.name.lexeme} variable` +} + +function describeLiteralExpression(expression: LiteralExpression): string { + let value = expression.value + if (typeof expression.value === 'string') { + value = '"' + expression.value + '"' + } + + return `${value}` +} +function describeOperator(operator: string): string { + switch (operator) { + case 'GREATER': + return 'greater than' + case 'LESS': + return 'less than' + case 'GREATER_EQUAL': + return 'greater than or equal to' + case 'LESS_EQUAL': + return 'less than or equal to' + case 'STRICT_EQUALITY': + return 'equal to' + case 'STRICT_INEQUALITY': + return 'not equal to' + } + return '' - // let output = `

This checks to see whether ${frame.result.condition.left.obj.expression} is greater than ${frame.result.condition.right.name}.

` - // output += `

In this case, ${frame.result.condition.left.obj.expression} is set to ${frame.result.condition.left.obj.value} and ${frame.result.condition.right.name} is set to ${frame.result.condition.right.value} so the result is ${frame.result.value}.

` - // return output +} + +function describeBinaryExpression(expression: BinaryExpression): string { + if (expression instanceof BinaryExpression) { + const left = describeExpression(expression.left) + const right = describeExpression(expression.right) + const operator = describeOperator(expression.operator.type) + return `${left} was ${operator} ${right}` + } + return '' +} + +function describeLogicalExpression(expression: LogicalExpression): string { + let prefix = '' + if (expression.operator.type == 'AND') { + prefix = 'both' + } + const left = describeExpression(expression.left) + const right = describeExpression(expression.right) + + return `${prefix} ${left}, and ${right} were true` +} + +function describeGroupingExpression(expression: GroupingExpression): string { + return describeExpression(expression.inner) +} + +function describeCondition(expression: Expression): string { + return describeExpression(expression) +} + +function describeIfStatement(frame: FrameWithResult) { + const ifStatement = frame.context as IfStatement + const conditionDescription = describeCondition(ifStatement.condition) + let output = ` +

This checked whether ${conditionDescription}.

+

The result was ${frame.result.value}.

+ ` + return output } function describeReturnStatement(frame: FrameWithResult) { let output = `

This returned the value of ${frame.result.value.name}, which in this case is ${frame.result.value.value}.

` diff --git a/app/javascript/interpreter/interpreter.ts b/app/javascript/interpreter/interpreter.ts index 70997b4b31..c8f9d90dc0 100644 --- a/app/javascript/interpreter/interpreter.ts +++ b/app/javascript/interpreter/interpreter.ts @@ -135,9 +135,7 @@ export function evaluateFunction( ): EvaluateFunctionResult { const interpreter = new Interpreter(sourceCode, context) interpreter.compile() - const res = interpreter.evaluateFunction(functionCall, ...args) - // console.log(res) - return res + return interpreter.evaluateFunction(functionCall, ...args) } export class Interpreter { diff --git a/app/javascript/interpreter/languages/jikiscript/error.ts b/app/javascript/interpreter/languages/jikiscript/error.ts index a75dd3a590..f4a85225b6 100644 --- a/app/javascript/interpreter/languages/jikiscript/error.ts +++ b/app/javascript/interpreter/languages/jikiscript/error.ts @@ -1,6 +1,5 @@ export type SyntaxErrorType = | 'UnknownCharacter' - | 'UnknownCharacterEquals' | 'MissingCommaAfterParameters' | 'MissingDoToStartBlock' | 'MissingEndAfterBlock' @@ -65,3 +64,6 @@ export type SyntaxErrorType = | 'UnexpectedVariableExpressionAfterIf' | 'UnexpectedVariableExpressionAfterIfWithPotentialTypo' | 'DuplicateParameterName' + | 'MissingTimesInRepeat' + | 'UnexpectedEqualsForAssignment' + | 'UnexpectedEqualsForEquality' diff --git a/app/javascript/interpreter/languages/jikiscript/parser.ts b/app/javascript/interpreter/languages/jikiscript/parser.ts index 7b40a7ea3c..f3d8f5b3ee 100644 --- a/app/javascript/interpreter/languages/jikiscript/parser.ts +++ b/app/javascript/interpreter/languages/jikiscript/parser.ts @@ -214,6 +214,9 @@ export class Parser implements GenericParser { }) } + // Guard mistaken equals sign for assignment + this.guardEqualsSignForAssignment(this.peek()) + this.consume('TO', 'MissingToAfterVariableNameToInitializeValue', { name: name.lexeme, }) @@ -300,9 +303,16 @@ export class Parser implements GenericParser { } this.consume('DO', 'MissingDoToStartBlock', { type: 'if' }) - const thenBranch = this.blockStatement('if', { allowElse: true }) + const thenBranch = this.blockStatement('if', { + allowElse: true, + consumeEnd: false, + }) let elseBranch: Statement | null = null + // if(this.previous(2).type == "END") { + // console.log("Are we done twice?") + // // We're in a nested situation. We're done. + // } if (this.match('ELSE')) { if (this.match('IF')) { elseBranch = this.ifStatement() @@ -311,10 +321,12 @@ export class Parser implements GenericParser { elseBranch = this.blockStatement('else') } } else { - // this.consume("END", "MissingEndAfterIfBody"); - // this.consumeEndOfLine(); + this.consume('END', 'MissingEndAfterBlock', { type: 'if' }) + this.consumeEndOfLine() } + // console.log(condition, thenBranch, elseBranch, ifToken, this.previous()); + return new IfStatement( condition, thenBranch, @@ -412,11 +424,11 @@ export class Parser implements GenericParser { private blockStatement( type: string, - { allowElse } = { allowElse: false } + { allowElse, consumeEnd } = { allowElse: false, consumeEnd: true } ): BlockStatement { const doToken = this.previous() this.consumeEndOfLine() - const statements = this.block(type, { allowElse: allowElse }) + const statements = this.block(type, { allowElse, consumeEnd }) return new BlockStatement( statements, @@ -426,7 +438,7 @@ export class Parser implements GenericParser { private block( type: string, - { allowElse } = { allowElse: false } + { allowElse, consumeEnd } = { allowElse: false, consumeEnd: true } ): Statement[] { const statements: Statement[] = [] @@ -438,7 +450,7 @@ export class Parser implements GenericParser { statements.push(this.statement()) } - if (!allowElse || this.peek().type != 'ELSE') { + if (consumeEnd && (!allowElse || this.peek().type != 'ELSE')) { this.consume('END', 'MissingEndAfterBlock', { type }) this.consumeEndOfLine() } @@ -540,13 +552,24 @@ export class Parser implements GenericParser { ) } + this.guardEqualsSignForEquality(this.peek()) + return expr } private comparison(): Expression { let expr = this.term() - while (this.match('GREATER', 'GREATER_EQUAL', 'LESS', 'LESS_EQUAL')) { + while ( + this.match( + 'GREATER', + 'GREATER_EQUAL', + 'LESS', + 'LESS_EQUAL', + 'STRICT_EQUALITY', + 'STRICT_INEQUALITY' + ) + ) { const operator = this.previous() const right = this.term() expr = new BinaryExpression( @@ -902,6 +925,19 @@ export class Parser implements GenericParser { ) } + private guardEqualsSignForAssignment(name: Token) { + if (this.peek().type == 'EQUAL') { + this.error('UnexpectedEqualsForAssignment', this.peek().location, { + name: name.lexeme, + }) + } + } + private guardEqualsSignForEquality(token: Token) { + if (token.type == 'EQUAL') { + this.error('UnexpectedEqualsForEquality', token.location) + } + } + private isAtEnd(): boolean { return this.peek().type == 'EOF' } @@ -914,8 +950,8 @@ export class Parser implements GenericParser { return this.tokens[this.current + (n - 1)] } - private previous(): Token { - return this.tokens[this.current - 1] + private previous(n = 1): Token { + return this.tokens[this.current - n] } } export function parse( diff --git a/app/javascript/interpreter/languages/jikiscript/scanner.ts b/app/javascript/interpreter/languages/jikiscript/scanner.ts index 99cf9044f9..e6f4157bb7 100644 --- a/app/javascript/interpreter/languages/jikiscript/scanner.ts +++ b/app/javascript/interpreter/languages/jikiscript/scanner.ts @@ -48,8 +48,8 @@ export class Scanner { if: 'IF', in: 'IN', is: 'STRICT_EQUALITY', - not: 'NOT', null: 'NULL', + not: 'NOT', or: 'OR', repeat: 'REPEAT', repeat_until_game_over: 'REPEAT_UNTIL_GAME_OVER', @@ -78,6 +78,8 @@ export class Scanner { '%': this.tokenizePercent, '>': this.tokenizeGreater, '<': this.tokenizeLess, + '!': this.tokenizeBang, + '=': this.tokenizeEqual, ' ': this.tokenizeWhitespace, '\t': this.tokenizeWhitespace, '\r': this.tokenizeWhitespace, @@ -118,10 +120,6 @@ export class Scanner { } else if (this.isAlpha(c)) { this.tokenizeIdentifier() } else { - if (c == '=') { - this.error('UnknownCharacterEquals') - } - this.error('UnknownCharacter', { character: c, }) @@ -147,6 +145,13 @@ export class Scanner { /* This first set of tokenizers are simple. They consume a single character and add a token to the list of tokens, * or do simple checks for the next characters (e.g. "++") */ + private tokenizeBang() { + this.addToken(this.match('=') ? 'STRICT_INEQUALITY' : 'NOT') + } + + private tokenizeEqual() { + this.addToken(this.match('=') ? 'STRICT_EQUALITY' : 'EQUAL') + } private tokenizeLeftParanthesis() { this.addToken('LEFT_PAREN') } diff --git a/app/javascript/interpreter/languages/jikiscript/token.ts b/app/javascript/interpreter/languages/jikiscript/token.ts index d61f3b58f7..12d91fffce 100644 --- a/app/javascript/interpreter/languages/jikiscript/token.ts +++ b/app/javascript/interpreter/languages/jikiscript/token.ts @@ -17,14 +17,15 @@ export type TokenType = | 'RIGHT_PAREN' | 'SLASH' | 'STAR' + | 'NOT' // One, two or three character tokens. - | 'NOT' | 'DOLLAR_LEFT_BRACE' | 'GREATER_EQUAL' | 'GREATER' | 'LESS_EQUAL' | 'LESS' + | 'EQUAL' // Literals | 'IDENTIFIER' diff --git a/app/javascript/interpreter/locales/en/translation.json b/app/javascript/interpreter/locales/en/translation.json index 9028087aa1..9ca669598d 100644 --- a/app/javascript/interpreter/locales/en/translation.json +++ b/app/javascript/interpreter/locales/en/translation.json @@ -12,7 +12,7 @@ "MissingConstantName": "Expect constant name.", "MissingDoToStartBlock": "Are you missing a `do` to start the {{type}} body?", "MissingDoubleQuoteToStartString": "Did you forget the start quote for the \"{{string}}\" string?", - "MissingDoubleQuoteToTerminateString": "Did you forget the end quote for the \"{{string}}\" string?", + "MissingDoubleQuoteToTerminateString": "Did you forget to add end quote? Maybe you meant to write:\n\n```\"{{string}}\"```", "MissingEndAfterBlock": "Are you missing an `end` to finish the {{type}} body?", "MissingEndOfLine": "We didn't expect `{{current}}` to appear on this line after the `{{previous}}`. {{suggestion}}", "MissingExpression": "Expect expression.", @@ -28,19 +28,15 @@ "MissingLetInForeachCondition": "Expected 'let' in foreach condition", "MissingOfAfterElementNameInForeach": "Expected 'of' after element name in 'foreach'.", "MissingParameterName": "Did you forget to add a parameter after `with`?", - "MissingRightBraceAfterBlock": "Expect '}' after block.", - "MissingRightBraceAfterMapElements": "Expect '}' after map elements.", - "MissingRightBraceToTerminatePlaceholder": "Missing right brace ('}') to terminate placeholder.", - "MissingRightBracketAfterFieldNameOrIndex": "Expect ']' after field name or index", - "MissingRightBracketAfterListElements": "Expect ']' after list elements.", + "MissingRightBraceAfterBlock": "Expect `}` after block.", + "MissingRightBraceAfterMapElements": "Expect `}` after map elements.", + "MissingRightBraceToTerminatePlaceholder": "Missing right brace (`}`) to terminate placeholder.", + "MissingRightBracketAfterFieldNameOrIndex": "Expect `]` after field name or index", + "MissingRightBracketAfterListElements": "Expect `]` after list elements.", "MissingRightParenthesisAfterFunctionCall": "Did you forget the end parenthesis when trying to call the {{function}} function?", - "MissingRightParenthesisAfterDoWhileCondition": "Expected ')' after 'do/while' condition", - "MissingRightParenthesisAfterExpression": "Expect ')' after expression.", + "MissingRightParenthesisAfterExpression": "Did you forget to add a `)`?", "MissingRightParenthesisAfterExpressionWithPotentialTypo": "Do you have a typo here? Did you mean `{{potential}}` instead of `{{actual}}`?", - "MissingRightParenthesisAfterForeachElement": "Expected ')' after 'foreach' element", - "MissingRightParenthesisAfterIfCondition": "Expect ')' after if condition.", - "MissingRightParenthesisAfterParameters": "Expect ')' after parameters.", - "MissingRightParenthesisAfterWhileCondition": "Expected ')' after 'while' condition", + "MissingRightParenthesisAfterParameters": "Did you forget an `)` after parameters.", "MissingWithBeforeParameters": "Did you forget the `with` keyword before your parameters?", "MissingStringAsKey": "Expect string as key.", "MissingToAfterVariableNameToInitializeValue": "Did you forget to add the `to` after `{{name}}`?", @@ -53,12 +49,13 @@ "NumberStartsWithZero": "A number cannot start with a zero. Did you mean `{{suggestion}}`?", "NumberWithMultipleDecimalPoints": "A number can only have one decimal point. Did you mean `{{suggestion}}`?", "UnknownCharacter": "Unknown character: '{{character}}'.", - "UnknownCharacterEquals": "We don't use the equals sign ('=') in JikiScript. Use `to` in set statements or `equals` in comparisons", + "UnexpectedEqualsForAssignment": "We don't use the equals sign (`=`) to assign variables in JikiScript. Use the `to` keyword instead.", "UnexpectedElseWithoutIf": "We can't work out which if statement this `else` belongs to. Did you forget to add an `if` statement before this `else`?", - "UnexpectedLiteralExpressionAfterIf": "Did you forget to compare to things? In JikiScript the comparison (`is`, `equals`, `>`, etc are always required).", + "UnexpectedLiteralExpressionAfterIf": "Did you forget to compare to things? In JikiScript the comparison (`is`, `==`, `>`, etc are always required).", "UnexpectedSpaceInIdentifier": "Did you accidently put a space in the middle of a variable name?", - "UnexpectedVariableExpressionAfterIf": "Did you forget to compare to things? In JikiScript the comparison (`is`, `equals`, `>`, etc are always required).", - "UnexpectedVariableExpressionAfterIfWithPotentialTypo": "We were expecting some sort of comparison here. Did you mean to type `{{potential}}` instead of `{{actual}}`?" + "UnexpectedVariableExpressionAfterIf": "Did you forget to compare to things? In JikiScript the comparison (`is`, `==`, `>`, etc are always required).", + "UnexpectedVariableExpressionAfterIfWithPotentialTypo": "We were expecting some sort of comparison here. Did you mean to type `{{potential}}` instead of `{{actual}}`?", + "UnexpectedEqualsForEquality": "To say \"equals\" in JikiScript, we use two equals signs (`==`). Did you forget one?" }, "semantic": { "CannotAssignToConstant": "Cannot re-assign value of constant.", diff --git a/app/javascript/interpreter/locales/system/translation.json b/app/javascript/interpreter/locales/system/translation.json index 099ee1baa4..01cb91371e 100644 --- a/app/javascript/interpreter/locales/system/translation.json +++ b/app/javascript/interpreter/locales/system/translation.json @@ -55,8 +55,9 @@ "UnexpectedLiteralExpressionAfterIf": "UnexpectedLiteralExpressionAfterIf", "UnexpectedVariableExpressionAfterIfWithPotentialTypo": "UnexpectedVariableExpressionAfterIfWithPotentialTypo: actual: {{actual}}, potential: {{potential}}", "UnknownCharacter": "UnknownCharacter: character: {{character}}", - "UnknownCharacterEquals": "UnknownCharacterEquals", - "UnexpectedSpaceInIdentifier": "UnexpectedSpaceInIdentifier: first_half: {{first_half}}, second_half: {{second_half}}" + "UnexpectedSpaceInIdentifier": "UnexpectedSpaceInIdentifier: first_half: {{first_half}}, second_half: {{second_half}}", + "UnexpectedEqualsForEquality": "UnexpectedEqualsForEquality", + "UnexpectedEqualsForAssignment": "UnexpectedEqualsForAssignment" }, "semantic": { "CannotAssignToConstant": "CannotAssignToConstant", diff --git a/app/views/components/bootcamp/exercise_widget.html.haml b/app/views/components/bootcamp/exercise_widget.html.haml index e0133163ca..406bd8b5de 100644 --- a/app/views/components/bootcamp/exercise_widget.html.haml +++ b/app/views/components/bootcamp/exercise_widget.html.haml @@ -6,7 +6,7 @@ .flex.items-start .title .project-title= project.title - .tag{ class: status.to_s } + .tag .exercise-title = exercise.title .description= exercise.description diff --git a/app/views/components/bootcamp/project_widget.html.haml b/app/views/components/bootcamp/project_widget.html.haml index 4ab1bbfe77..f2723427e5 100644 --- a/app/views/components/bootcamp/project_widget.html.haml +++ b/app/views/components/bootcamp/project_widget.html.haml @@ -4,5 +4,5 @@ .flex.flex-col.flex-grow .flex.items-start .title= project.title - .tag{ class: status.to_s } + .tag .description= project.description diff --git a/app/views/layouts/bootcamp-ui.haml b/app/views/layouts/bootcamp-ui.haml index 73ee56ad7b..9f00750ab0 100644 --- a/app/views/layouts/bootcamp-ui.haml +++ b/app/views/layouts/bootcamp-ui.haml @@ -40,6 +40,16 @@ -webkit-font-smoothing: antialiased; } + // Load other fonts used by the editor + + :css + @font-face { + font-display: fallback; + font-family: DSDigital; + font-weight: 400; + src: url(#{asset_path('ds-digi.woff2')}) format('woff2'); + } + %meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" } %title= content_for(:title) || "Bootcamp" %meta{ content: "width=device-width,initial-scale=1", name: "viewport" } diff --git a/bootcamp_content/concepts/conditionals.md b/bootcamp_content/concepts/conditionals.md index e69de29bb2..9234a3fd52 100644 --- a/bootcamp_content/concepts/conditionals.md +++ b/bootcamp_content/concepts/conditionals.md @@ -0,0 +1,42 @@ +# Conditionals + +Often we need to only run code in certain situations. +To achieve this we use `if` statements - also know as conditionals. + +We specifiy the `if` keyword, then a **condition** then a block of code we want to run if that condition is `true` + + + +### Conditions + +A condition is something that is either `true` or `false`. +Conditions are normally relationships between two things, such as "is this the same as that" or "is this bigger than that". + + + +In JikiScript there are 6 different conditions we can use: + + + +### Variables + +It's most common to use a variable as part of an `if` statement. +We get the value from the box, and check how it relates to another number. + +For example, imagine we're creating a Bouncer Robot to work at a nightclub. +There's a rule that everyone has to be older than 20 to be allowed in. +The code for that might look like this: + + + +### Functions + +We can also use the results of functions directly in if statements + +Rather than setting a variable, we can use a function to get a result, and then compare that to our condition. + +For example, imagine Jiki has a list of everyone's name and age. +We can use a function where he gets someone's age based on their name. +And then he compares it to the entry critera for the club: + + diff --git a/bootcamp_content/concepts/config.json b/bootcamp_content/concepts/config.json index 669263cc3b..af5adcb07c 100644 --- a/bootcamp_content/concepts/config.json +++ b/bootcamp_content/concepts/config.json @@ -78,8 +78,9 @@ }, { "slug": "conditionals", + "parent": "flow-control", "title": "Conditionals", - "description": "", + "description": "Learn about if and else statements.", "level": 3 }, { @@ -118,7 +119,7 @@ { "slug": "variables-changing", "parent": "variables", - "title": "Changing variables", + "title": "Change variables", "description": "Learn how to change variables", "level": 2 }, diff --git a/bootcamp_content/concepts/else-statements.md b/bootcamp_content/concepts/else-statements.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bootcamp_content/concepts/functions-arguments.md b/bootcamp_content/concepts/functions-arguments.md index ceae1c77c0..35021f3983 100644 --- a/bootcamp_content/concepts/functions-arguments.md +++ b/bootcamp_content/concepts/functions-arguments.md @@ -10,7 +10,7 @@ rectangle(); Your instinct is probably that’ll draw a rectangle. But go a little deeper. Where does it draw the rectangle? How big should it be? The actual answer is that if you run that program it’ll error telling you that you’ve not given it enough information. -The functions you used to solve the maze all did the same thing every time you used them. When you used `move` it always moved the character one step forward (or errored if it couldn’t). When you used `turn_left` , the character always turned left. But most of the time when we use functions, we want them to do something based on \*\*\*\*information we give them. +The functions you used to solve the maze all did the same thing every time you used them. When you used `move` it always moved the character one step forward (or errored if it couldn’t). When you used `turn_left` , the character always turned left. But most of the time when we use functions, we want them to do something based on information we give them. So let’s expand our mental image of what a functions look like, and add a version that has inputs. diff --git a/bootcamp_content/projects/drawing/config.json b/bootcamp_content/projects/drawing/config.json index 1914c8057c..1679234694 100644 --- a/bootcamp_content/projects/drawing/config.json +++ b/bootcamp_content/projects/drawing/config.json @@ -8,6 +8,7 @@ "structured-house", "rainbow", "sunset", - "sprouting-flower" + "sprouting-flower", + "rainbow-ball" ] } diff --git a/bootcamp_content/projects/drawing/exercises/jumbled-house/config.json b/bootcamp_content/projects/drawing/exercises/jumbled-house/config.json index 7603c8956a..0fbee11e98 100644 --- a/bootcamp_content/projects/drawing/exercises/jumbled-house/config.json +++ b/bootcamp_content/projects/drawing/exercises/jumbled-house/config.json @@ -13,7 +13,6 @@ { "slug": "draw-the-house", "name": "Draw the house.", - "function": "main", "checks": [ { "name": "getRectangleAt(20,50,60,40)", diff --git a/bootcamp_content/projects/drawing/exercises/loops/config.json b/bootcamp_content/projects/drawing/exercises/loops/config.json index 990a2984f1..e5421eb0c0 100644 --- a/bootcamp_content/projects/drawing/exercises/loops/config.json +++ b/bootcamp_content/projects/drawing/exercises/loops/config.json @@ -2,7 +2,7 @@ "title": "Loops", "description": "Create 5 rectangles", "project_type": "draw", - "level": 3, + "level": 100, "concepts": ["loops-repeat"], "tests_type": "state", "interpreter_options": { diff --git a/bootcamp_content/projects/drawing/exercises/penguin/config.json b/bootcamp_content/projects/drawing/exercises/penguin/config.json index 6743558a2a..e780982b7a 100644 --- a/bootcamp_content/projects/drawing/exercises/penguin/config.json +++ b/bootcamp_content/projects/drawing/exercises/penguin/config.json @@ -15,7 +15,6 @@ "slug": "draw-scence", "name": "Make the penguin symmetrical.", "description_html": "Fix all the TODO comments to make the penguin symmetrical.", - "function": "main", "checks": [ { "name": "getRectangleAt(0, 0, 100, 100)", diff --git a/bootcamp_content/projects/drawing/exercises/rainbow-ball/config.json b/bootcamp_content/projects/drawing/exercises/rainbow-ball/config.json new file mode 100644 index 0000000000..1c416ff8e5 --- /dev/null +++ b/bootcamp_content/projects/drawing/exercises/rainbow-ball/config.json @@ -0,0 +1,47 @@ +{ + "title": "Rainbow Ball", + "description": "Paint the canvas using a rolling rainbow ball", + "project_type": "draw", + "level": 3, + "idx": 4, + "concepts": [], + "tests_type": "state", + "readonly_ranges": [], + "interpreter_options": { + "repeat_delay": 10 + }, + "tasks": [ + { + "name": "Draw the scene", + "tests": [ + { + "slug": "draw-scence", + "name": "Animate and draw.", + "description_html": "Animate the ball - make a rainbow!", + "checks": [ + { + "name": "getCircleAt(5, 5, 10)", + "matcher": "toExist", + "error_html": "The first circle is not right." + }, + { + "name": "getCircleAt(7, 6, 10)", + "matcher": "toExist", + "error_html": "The second circle is not right." + }, + { + "name": "checkCanvasCoverage(80)", + "matcher": "toBeTrue", + "error_html": "Less than 80% of the canvas is painted." + }, + { + "name": "checkUniqueColoredCircles(255)", + "matcher": "toBeTrue", + "error_html": "There are not 255 different colored circles." + } + ] + } + ] + } + ] +} diff --git a/bootcamp_content/projects/drawing/exercises/rainbow-ball/example.jiki b/bootcamp_content/projects/drawing/exercises/rainbow-ball/example.jiki new file mode 100644 index 0000000000..f8e89eeebd --- /dev/null +++ b/bootcamp_content/projects/drawing/exercises/rainbow-ball/example.jiki @@ -0,0 +1,43 @@ +// These variables expect to be incremented before +// they are used to draw the first circle +set x to 3 +set y to 4 +set hue to 99 + +// These never need to change +set saturation to 80 +set luminosity to 50 + +set x_direction to 2 +set y_direction to 1 +set hue_direction to 1 + +repeat 1000 times do + change x to x + x_direction + change y to y + y_direction + change hue to hue + hue_direction + + if x <= 0 do + change x_direction to random_number(1,5) + end + if x >= 100 do + change x_direction to random_number(-1,-5) + end + + if y <= 0 do + change y_direction to random_number(1,5) + end + if y >= 100 do + change y_direction to -random_number(1,5) + end + + if hue_direction <= 0 do + change hue_direction to 1 + end + if hue_direction >= 255 do + change hue_direction to -1 + end + + fill_color_hsl(hue, 80, 50) + circle(x, y, 10) +end \ No newline at end of file diff --git a/bootcamp_content/projects/drawing/exercises/rainbow-ball/introduction.md b/bootcamp_content/projects/drawing/exercises/rainbow-ball/introduction.md new file mode 100644 index 0000000000..9b9965319a --- /dev/null +++ b/bootcamp_content/projects/drawing/exercises/rainbow-ball/introduction.md @@ -0,0 +1,60 @@ +# Rainbow + +Welcome to Rainbow Ball. Your job is to create a ball that randomly bounces around the canvas, drawing a rainbow in its wake! + +It should look something like this: + + + +As part of this exercise, you have a new function called `random_number(min, max)` which returns a random number between the min and max that you input. + +Take a few minutes to think through how you could solve this! Remember, this bit is probably the most valuable part of the exercise, so take your time and **write down your ideas before you read the instructions below.** When you've got an idea of your approach, read on! + +
+ +## Instructions + +This exercise is all about having some variables that are responsible for the position of the ball, which steadily increase or decrease. And other variables that control **how** the ball is moving and change when certain criteria are met. + +### Drawing + +- The first circle you draw should be at `(5,5)`. +- All the circles should have a radius of `10`. +- The color of the circle should use HSL, starting with a hue of `100` (green), a saturation of `80` (bold colors), and a luminosity of `50` (mid-brightness). + +### Animating + +- To start with, in each iteration you should move it `2` to the right and `1` down. +- The hue should increase by `1` each time, until it gets to the maximum (`255`) then start reducing again. The saturation and luminosity don't need to change. + +### Bouncing + +- Once the ball reaches the edge of the canvas it should change direction. (Check the hints below if you can't work out how to do this). +- To make things more fun you should change direction using the `random_number(min, max)` function, choose `min` and `max` that give the style of animation you want. + +## To pass the checks + +We've given you a lot of leeway in this exercise. We check that: + +- The first few circles are correct +- Over 75% of the canvas gets painted. + +The numbers that you choose to achieve that are up to you. You probably want a repeat block that iterates between `500` and `1000` times. + +## Functions + +The functions used in this exercise are: + +- `circle(x, y, radius)` +- `fill_color_hsl(hue, saturation, luminance)` +- `random_number(min, max)` + +
+ +## Hints + +At the start you move the circle `2` to the right and `1` down. Think about what these numbers are. They're the **direction** that the ball is moving in. Use variables for them. + +Once the ball hits the edge, the direction it's moving in need to change, so you need to update those direction variables. + +The hue works in the same way. diff --git a/bootcamp_content/projects/drawing/exercises/rainbow-ball/stub.jiki b/bootcamp_content/projects/drawing/exercises/rainbow-ball/stub.jiki new file mode 100644 index 0000000000..04f062c9e5 --- /dev/null +++ b/bootcamp_content/projects/drawing/exercises/rainbow-ball/stub.jiki @@ -0,0 +1,15 @@ +// TODO: Create your variables +// Remember that if the variables are updated at the +// start of the repeat loop, then need to start lower here. +// set x to ... +// set y to ... +// set hue to ... + +// TODO: You'll need to increase this to cover the canvas! +repeat 120 times do + // TODO: Update variables + + // Draw the circle + fill_color_hsl(100, 80, 50) + circle(5, 5, 10) +end \ No newline at end of file diff --git a/bootcamp_content/projects/drawing/exercises/rainbow-ball/task-1.md b/bootcamp_content/projects/drawing/exercises/rainbow-ball/task-1.md new file mode 100644 index 0000000000..0a57b76c0f --- /dev/null +++ b/bootcamp_content/projects/drawing/exercises/rainbow-ball/task-1.md @@ -0,0 +1 @@ +Paint the canvas with rainbows. Have fun! diff --git a/bootcamp_content/projects/drawing/exercises/rainbow/config.json b/bootcamp_content/projects/drawing/exercises/rainbow/config.json index 24eefeff81..830bee2f54 100644 --- a/bootcamp_content/projects/drawing/exercises/rainbow/config.json +++ b/bootcamp_content/projects/drawing/exercises/rainbow/config.json @@ -18,7 +18,6 @@ "slug": "draw-scence", "name": "Draw the rainbow.", "description_html": "Paint 100 beautiful rectangles", - "function": "main", "checks": [ { "name": "getRectangleAt(1, 0, undefined, 100)", diff --git a/bootcamp_content/projects/drawing/exercises/sleepy-house/config.json b/bootcamp_content/projects/drawing/exercises/sleepy-house/config.json new file mode 100644 index 0000000000..e179a26103 --- /dev/null +++ b/bootcamp_content/projects/drawing/exercises/sleepy-house/config.json @@ -0,0 +1,52 @@ +{ + "title": "Sleepy House", + "description": "Animate colors to make the house enter nighttime!", + "project_type": "draw", + "level": 6, + "idx": 1, + "concepts": [], + "tests_type": "state", + "tasks": [ + { + "name": "Animate the scene", + "tests": [ + { + "slug": "animate-hoouse", + "name": "Animate the scene.", + "checks": [ + { + "name": "getRectangleAt(20,50,60,40)", + "matcher": "toExist", + "error_html": "The frame of the house is not correct - you shouldn't need to move this in this exercise." + }, + { + "name": "getTriangleAt(16,50, 50,30, 84,50)", + "matcher": "toExist", + "error_html": "The roof of the house is not at the correct position - you shouldn't need to move this in this exercise." + }, + { + "name": "getRectangleAt(30,55,12,13)", + "matcher": "toExist", + "error_html": "The left window frame isn't positioned correctly - you shouldn't need to move this in this exercise." + }, + { + "name": "getRectangleAt(58,55,12,13)", + "matcher": "toExist", + "error_html": "The right window frame isn't positioned correctly - you shouldn't need to move this in this exercise." + }, + { + "name": "getRectangleAt(43,72,14,18)", + "matcher": "toExist", + "error_html": "The door frame isn't positioned correctly - you shouldn't need to move this in this exercise." + }, + { + "name": "getCircleAt(55,81,1)", + "matcher": "toExist", + "error_html": "The door knob isn't positiioned correctly - you shouldn't need to move this in this exercise." + } + ] + } + ] + } + ] +} diff --git a/bootcamp_content/projects/drawing/exercises/sleepy-house/example.jiki b/bootcamp_content/projects/drawing/exercises/sleepy-house/example.jiki new file mode 100644 index 0000000000..bde687feda --- /dev/null +++ b/bootcamp_content/projects/drawing/exercises/sleepy-house/example.jiki @@ -0,0 +1,97 @@ +// Sky variables +set sky_color to "#add8e6" + +set sky_h to 232 +set sky_s to 100 +set sky_l to 50 + +set sky_left to 0 +set sky_top to 0 +set sky_width to 100 +set sky_height to 100 + +// Grass variables +set grass_color to "#3cb372" +set grass_left to 0 +set grass_top to 80 +set grass_width to 100 +set grass_height to 20 + +// House Frame variables +set house_color to "#f0985b" +set house_left to 20 +set house_top to 50 +set house_width to 60 +set house_height to 40 + +// Roof variables +set roof_color to "#8b4513" +set roof_overhang to 4 +set roof_height to 20 +set roof_left to house_left - roof_overhang +set roof_right to house_left + house_width + roof_overhang +set roof_peak_x to house_left + house_width / 2 +set roof_peak_y to house_top - roof_height +set roof_base_y to house_top + +// Left window variables +set window_color to "#FFFFFF" +set window1_left to 30 +set window1_top to 55 +set window_width to 12 +set window_height to 13 + +// Right window variables +set window2_left to 58 +set window2_top to 55 + +// Door variables +set door_color to "#A0512D" +set door_left to 43 +set door_top to 72 +set door_width to 14 +set door_height to 18 + +// Door knob variables +set knob_color to "#FFDF00" +set knob_center_x to 55 +set knob_center_y to 81 +set knob_radius to 1 + +repeat 100 times do + + change sky_l to sky_l - 1 + + // The sky + fill_color_hsl(sky_h, sky_s, sky_l) + rectangle(sky_left, sky_top, sky_width, sky_height) + + // The grass + fill_color_hex(grass_color) + rectangle(grass_left, grass_top, grass_width, grass_height) + + // The frame of the house + fill_color_hex(house_color) + rectangle(house_left, house_top, house_width, house_height) + + // The roof + fill_color_hex(roof_color) + triangle(roof_left, roof_base_y, roof_peak_x, roof_peak_y, roof_right, roof_base_y) + + // The left window + fill_color_hex(window_color) + rectangle(window1_left, window1_top, window_width, window_height) + + // The second window + fill_color_hex(window_color) + rectangle(window2_left, window2_top, window_width, window_height) + + // The door + fill_color_hex(door_color) + rectangle(door_left, door_top, door_width, door_height) + + // The door knob + fill_color_hex(knob_color) + circle(knob_center_x, knob_center_y, knob_radius) + +end \ No newline at end of file diff --git a/bootcamp_content/projects/drawing/exercises/sleepy-house/introduction.md b/bootcamp_content/projects/drawing/exercises/sleepy-house/introduction.md new file mode 100644 index 0000000000..bf222390d8 --- /dev/null +++ b/bootcamp_content/projects/drawing/exercises/sleepy-house/introduction.md @@ -0,0 +1,38 @@ +# Structured House + +Your task is to use variables to build the house. + +Change all the inputs in the functions to use variables. + +Set all the variables at the top before the first functions are used. We've set the first few to get you started. + +Every variable should either be: + +- A number that is specified in the instructions (e.g. the height of the frame); or +- A formula between two variables (e.g. `set roof_left to house_left - roof_overhang`) or a variable and a number specified in the instructions (e.g. `set door_knob_right to door_right - 1`). + +Do **not** manually set variables to numbers you've calculated yourself (e.g. DO NOT set `roof_left = 16`) + +The purpose of this exercise is get keep pushing you towards structured, ordered thinking. Take your time. + +As a reminder, the house should continue to look like this: + + + +### House Instructions + +- The frame of the house (the big rectangle) should be 60 wide and 40 height. It should have it's top-left corner at 20x50. +- The roof sits snuggly on top of the house's frame. It should overhang the left and right of the house by 4 on each side. It should have a height of 20, and it's point should be centered horizontally (50). +- The windows are both the same size, with have a width of 12 and a height of 13. They both sit 5 from the top of the house frame, and 10 inset from the sides. +- The door is 14 wide and 18 tall, and sits at the bottom of the house in the center. +- The little door knob has a radius of 1, is inset 1 from the right, and is vertically centered in the door. + +### Functions + +The house uses the following functions: + +- `circle(x, y, radius)` +- `rectangle(x, y, width, height)` +- `ellipse(center_x, center_y, radius_x, radius_y)` +- `triangle(x1,y1, x2,y2, x3,y3)` +- `fill_color_hex(hex)` diff --git a/bootcamp_content/projects/drawing/exercises/sleepy-house/stub.jiki b/bootcamp_content/projects/drawing/exercises/sleepy-house/stub.jiki new file mode 100644 index 0000000000..1a85f17506 --- /dev/null +++ b/bootcamp_content/projects/drawing/exercises/sleepy-house/stub.jiki @@ -0,0 +1,86 @@ +// Sky variables +set sky_color to "#add8e6" +set sky_left to 0 +set sky_top to 0 +set sky_width to 100 +set sky_height to 100 + +// Grass variables +set grass_color to "#3cb372" +set grass_left to 0 +set grass_top to 80 +set grass_width to 100 +set grass_height to 20 + +// House Frame variables +set house_color to "#f0985b" +set house_left to 20 +set house_top to 50 +set house_width to 60 +set house_height to 40 + +// Roof variables +set roof_color to "#8b4513" +set roof_overhang to 4 +set roof_height to 20 +set roof_left to house_left - roof_overhang +set roof_right to house_left + house_width + roof_overhang +set roof_peak_x to house_left + house_width / 2 +set roof_peak_y to house_top - roof_height +set roof_base_y to house_top + +// Left window variables +set window_color to "#FFFFFF" +set window1_left to 30 +set window1_top to 55 +set window_width to 12 +set window_height to 13 + +// Right window variables +set window2_left to 58 +set window2_top to 55 + +// Door variables +set door_color to "#A0512D" +set door_left to 43 +set door_top to 72 +set door_width to 14 +set door_height to 18 + +// Door knob variables +set knob_color to "#FFDF00" +set knob_center_x to 55 +set knob_center_y to 81 +set knob_radius to 1 + +// The sky +fill_color_hex(sky_color) +rectangle(sky_left, sky_top, sky_width, sky_height) + +// The grass +fill_color_hex(grass_color) +rectangle(grass_left, grass_top, grass_width, grass_height) + +// The frame of the house +fill_color_hex(house_color) +rectangle(house_left, house_top, house_width, house_height) + +// The roof +fill_color_hex(roof_color) +triangle(roof_left, roof_base_y, roof_peak_x, roof_peak_y, roof_right, roof_base_y) + +// The left window +fill_color_hex(window_color) +rectangle(window1_left, window1_top, window_width, window_height) + +// The second window +fill_color_hex(window_color) +rectangle(window2_left, window2_top, window_width, window_height) + +// The door +fill_color_hex(door_color) +rectangle(door_left, door_top, door_width, door_height) + +// The door knob +fill_color_hex(knob_color) +circle(knob_center_x, knob_center_y, knob_radius) \ No newline at end of file diff --git a/bootcamp_content/projects/drawing/exercises/sleepy-house/task-1.md b/bootcamp_content/projects/drawing/exercises/sleepy-house/task-1.md new file mode 100644 index 0000000000..2fc07c6618 --- /dev/null +++ b/bootcamp_content/projects/drawing/exercises/sleepy-house/task-1.md @@ -0,0 +1,18 @@ +Change all the function calls to use variables not numbers. + +For example, change: + +``` +// The frame of the house +rectangle(house_left,50,60,40) +``` + +to + +``` +set house_left to 20 +set house_top to 50 +set house_width to 60 +set house_height to 40 +rectangle(house_left, house_top, house_width, house_height) +``` diff --git a/bootcamp_content/projects/drawing/exercises/sprouting-flower/config.json b/bootcamp_content/projects/drawing/exercises/sprouting-flower/config.json index 80a3a6236d..f5cd53d727 100644 --- a/bootcamp_content/projects/drawing/exercises/sprouting-flower/config.json +++ b/bootcamp_content/projects/drawing/exercises/sprouting-flower/config.json @@ -18,58 +18,57 @@ "slug": "draw-scence", "name": "Make the flower sprout.", "description_html": "Take it one step at a time!", - "function": "main", "checks": [ { "name": "getCircleAt(50, 89, 0.4)", "matcher": "toExist", - "error_html": "First Flower" + "error_html": "The first Flower Head isn't correct." }, { "name": "getCircleAt(50, 30, 24)", "matcher": "toExist", - "error_html": "Final Flower" + "error_html": "The final Flower Head isn't correct." }, { "name": "getCircleAt(50, 89, 0.1)", "matcher": "toExist", - "error_html": "First Pistil" + "error_html": "The first Pistil isn't correct." }, { "name": "getCircleAt(50, 30, 6)", "matcher": "toExist", - "error_html": "Final Pistil" + "error_html": "The final Pistil isn't correct." }, { "name": "getRectangleAt(49.95, 89, 0.1, 1)", "matcher": "toExist", - "error_html": "First Stem" + "error_html": "The first Stem isn't correct." }, { "name": "getRectangleAt(47, 30, 6, 60)", "matcher": "toExist", - "error_html": "Final Stem" + "error_html": "The final Stem isn't correct." }, { "name": "getEllipseAt(49.75, 89.5, 0.2, 0.08)", "matcher": "toExist", - "error_html": "First Left Leaf" + "error_html": "The first Left Leaf isn't correct." }, { "name": "getEllipseAt(35, 60, 12, 4.8)", "matcher": "toExist", - "error_html": "Final Left Leaf" + "error_html": "The final Left Leaf isn't correct." }, { "name": "getEllipseAt(50.25, 89.5, 0.2, 0.08)", "matcher": "toExist", - "error_html": "First Right Leaf" + "error_html": "The first Right Leaf isn't correct." }, { "name": "getEllipseAt(65, 60, 12, 4.8)", "matcher": "toExist", - "error_html": "Final Right Leaf" + "error_html": "The final Right Leaf isn't correct." } ] } diff --git a/bootcamp_content/projects/drawing/exercises/structured-house/config.json b/bootcamp_content/projects/drawing/exercises/structured-house/config.json index 7d8d6fe68c..adf9e72842 100644 --- a/bootcamp_content/projects/drawing/exercises/structured-house/config.json +++ b/bootcamp_content/projects/drawing/exercises/structured-house/config.json @@ -13,7 +13,6 @@ { "slug": "draw-the-house", "name": "Draw the house.", - "function": "main", "checks": [ { "name": "getRectangleAt(20,50,60,40)", diff --git a/bootcamp_content/projects/drawing/exercises/sunset/config.json b/bootcamp_content/projects/drawing/exercises/sunset/config.json index 926ec55662..d37eb0e2c9 100644 --- a/bootcamp_content/projects/drawing/exercises/sunset/config.json +++ b/bootcamp_content/projects/drawing/exercises/sunset/config.json @@ -18,7 +18,6 @@ "slug": "draw-scene", "name": "Make the sun set", "description_html": "Animate the sun and the sky to make it look like the sun is setting.", - "function": "main", "checks": [ { "name": "getCircleAt(50, 11, 5.2)", diff --git a/bootcamp_content/projects/golf/config.json b/bootcamp_content/projects/golf/config.json index ab92745805..048417b316 100644 --- a/bootcamp_content/projects/golf/config.json +++ b/bootcamp_content/projects/golf/config.json @@ -2,5 +2,5 @@ "slug": "golf", "title": "Golf", "description": "Let's play golf!", - "exercises": ["rolling-ball"] + "exercises": ["rolling-ball", "shot-checker"] } diff --git a/bootcamp_content/projects/golf/exercises/rolling-ball/config.json b/bootcamp_content/projects/golf/exercises/rolling-ball/config.json index 2069031952..2acbb2b05e 100644 --- a/bootcamp_content/projects/golf/exercises/rolling-ball/config.json +++ b/bootcamp_content/projects/golf/exercises/rolling-ball/config.json @@ -18,7 +18,6 @@ "slug": "draw-scene", "name": "Make the ball roll to the hole", "description_html": "Animate the ball's x coordinate to make it roll to the hole", - "function": "main", "setup_functions": [ [ "setBackgroundImage", diff --git a/bootcamp_content/projects/golf/exercises/shot-checker/config.json b/bootcamp_content/projects/golf/exercises/shot-checker/config.json new file mode 100644 index 0000000000..1f4218aa9e --- /dev/null +++ b/bootcamp_content/projects/golf/exercises/shot-checker/config.json @@ -0,0 +1,209 @@ +{ + "title": "Shot Checker", + "description": "Check to see if the ball lands in the hole", + "project_type": "golf", + "level": 3, + "idx": 2, + "concepts": [], + "tests_type": "state", + "interpreter_options": { + "repeat_delay": 20 + }, + "readonly_ranges": [], + "tasks": [ + { + "name": "Handle the ball being too short", + "tests": [ + { + "slug": "too-short", + "name": "Golfer hits ball 23 (too short).", + "description_html": "Make the ball roll 23 from the tee.", + "setup_functions": [ + ["setShotLength", [23]], + [ + "setBackgroundImage", + [ + "https://assets.exercism.org/bootcamp/graphics/golf-shot-checker.png" + ] + ] + ], + "checks": [ + { + "name": "getCircleAt(29, 75, 3)", + "matcher": "toNotExist", + "error_html": "The ball seems to go too far to the left." + }, + { + "name": "getCircleAt(30, 75, 3)", + "matcher": "toExist", + "error_html": "The ball doesn't seem to start in the right place." + }, + { + "name": "getCircleAt(53, 75, 3)", + "matcher": "toExist", + "error_html": "The ball doesn't seem to reach the correct finishing point." + }, + { + "name": "getCircleAt(54, 75, 3)", + "matcher": "toNotExist", + "error_html": "The ball seems to go too far to the right." + }, + { + "name": "wasFunctionUsed('fire_fireworks', null)", + "matcher": "toBeFalse", + "error_html": "The fireworks shouldn't be fired as the ball didn't land in the hole." + } + ] + }, + { + "slug": "too-long", + "name": "Golfer hits ball 70 (too long).", + "description_html": "Make the ball roll 70 from the tee.", + "setup_functions": [ + ["setShotLength", [70]], + [ + "setBackgroundImage", + [ + "https://assets.exercism.org/bootcamp/graphics/golf-shot-checker.png" + ] + ] + ], + "checks": [ + { + "name": "getCircleAt(29, 75, 3)", + "matcher": "toNotExist", + "error_html": "The ball seems to go too far to the left." + }, + { + "name": "getCircleAt(30, 75, 3)", + "matcher": "toExist", + "error_html": "The ball doesn't seem to start in the right place." + }, + { + "name": "getCircleAt(100, 75, 3)", + "matcher": "toExist", + "error_html": "The ball doesn't seem to reach the correct finishing point." + }, + { + "name": "getCircleAt(101, 75, 3)", + "matcher": "toNotExist", + "error_html": "The ball seems to go too far to the right." + }, + { + "name": "getCircleAt(100, 76, 3)", + "matcher": "toNotExist", + "error_html": "The ball tried to sink into the ground beyond the hole." + }, + { + "name": "wasFunctionUsed('fire_fireworks', null)", + "matcher": "toBeFalse", + "error_html": "The fireworks shouldn't be fired as the ball didn't land in the hole." + } + ] + }, + { + "slug": "just-inside-left", + "name": "Golfer hits ball just in the hole.", + "description_html": "Roll the ball 56 from the tee, sink it into the hole, and fire some fireworks!", + "setup_functions": [ + ["setShotLength", [56]], + [ + "setBackgroundImage", + [ + "https://assets.exercism.org/bootcamp/graphics/golf-shot-checker.png" + ] + ] + ], + "checks": [ + { + "name": "getCircleAt(29, 75, 3)", + "matcher": "toNotExist", + "error_html": "The ball seems to go too far to the left." + }, + { + "name": "getCircleAt(30, 75, 3)", + "matcher": "toExist", + "error_html": "The ball doesn't seem to start in the right place." + }, + { + "name": "getCircleAt(86, 75, 3)", + "matcher": "toExist", + "error_html": "The ball doesn't seem to roll the correct length." + }, + { + "name": "getCircleAt(86, 83, 3)", + "matcher": "toExist", + "error_html": "The ball doesn't seem to reach the correct finishing point." + }, + { + "name": "getCircleAt(86, 84, 3)", + "matcher": "toExist", + "error_html": "The ball doesn't seem to reach the correct finishing point." + }, + { + "name": "getCircleAt(86, 85, 3)", + "matcher": "toNotExist", + "error_html": "The ball seems to go too far to the right." + }, + { + "name": "wasFunctionUsed('fire_fireworks', null)", + "matcher": "toBeTrue", + "error_html": "The fireworks didn't fire." + } + ] + }, + { + "slug": "just-inside-right", + "name": "Golfer just about keeps the ball in the hole.", + "description_html": "Roll the ball 63 from the tee, sink it into the hole, and fire some fireworks!", + "setup_functions": [ + ["setShotLength", [63]], + [ + "setBackgroundImage", + [ + "https://assets.exercism.org/bootcamp/graphics/golf-shot-checker.png" + ] + ] + ], + "checks": [ + { + "name": "getCircleAt(29, 75, 3)", + "matcher": "toNotExist", + "error_html": "The ball seems to go too far to the left." + }, + { + "name": "getCircleAt(30, 75, 3)", + "matcher": "toExist", + "error_html": "The ball doesn't seem to start in the right place." + }, + { + "name": "getCircleAt(93, 75, 3)", + "matcher": "toExist", + "error_html": "The ball doesn't seem to roll the correct length." + }, + { + "name": "getCircleAt(93, 83, 3)", + "matcher": "toExist", + "error_html": "The ball doesn't seem to reach the correct finishing point." + }, + { + "name": "getCircleAt(93, 84, 3)", + "matcher": "toExist", + "error_html": "The ball doesn't seem to reach the correct finishing point." + }, + { + "name": "getCircleAt(93, 85, 3)", + "matcher": "toNotExist", + "error_html": "The ball seems to go too far to the right." + }, + { + "name": "wasFunctionUsed('fire_fireworks', null)", + "matcher": "toBeTrue", + "error_html": "The fireworks didn't fire." + } + ] + } + ] + } + ] +} diff --git a/bootcamp_content/projects/golf/exercises/shot-checker/example.jiki b/bootcamp_content/projects/golf/exercises/shot-checker/example.jiki new file mode 100644 index 0000000000..10892291de --- /dev/null +++ b/bootcamp_content/projects/golf/exercises/shot-checker/example.jiki @@ -0,0 +1,25 @@ +set x to 29 +set y to 75 +set radius to 3 +set shot_length to get_shot_length() + +fill_color_hex("orange") + +repeat shot_length + 1 times do + clear() + + change x to x + 1 + + circle(x, y, 3) +end + +if shot_length >= 56 and shot_length <= 65 do + repeat 9 times do + clear() + + change y to y + 1 + circle(x, y, 3) + end + + fire_fireworks() +end \ No newline at end of file diff --git a/bootcamp_content/projects/golf/exercises/shot-checker/introduction.md b/bootcamp_content/projects/golf/exercises/shot-checker/introduction.md new file mode 100644 index 0000000000..ec2b8a4ba1 --- /dev/null +++ b/bootcamp_content/projects/golf/exercises/shot-checker/introduction.md @@ -0,0 +1,51 @@ +# Rolling ball + +Let's make the golf game a little more interesting! We've given you a new function to use called `get_shot_length()`. This function returns the length of the shot the golfer has hit. + +In each scenario, the golfer hits the ball a different length. Click through the `1`, `2`, `3`, `4` boxes to see how far the golfer has hit the ball. Your job is to write code that makes **all the scenarios work**. + +## Instructions + +You have two things to achieve: + +1. Move the ball as far as golfer hits it. +2. If the ball lands over the hole (the shot length is `56`, `57`, `58`, `59`, `60`, `61`, `62` or `63`): + - Animate the ball dropping down until into the hole its `y` value is `84` + - Shoot some fireworks! + +## Your Functions + +You'll use the following functions to control the game: + +- `clear()` +- `circle(center_x, center_y, radius)` +- `fill_color_hex(hex)` +- `get_shot_length()`: Returns a number which is the length of the shot. +- `fire_fireworks()`: Fires off some celebratory fireworks. + +You'll also need to use the `set`, `change`, `repeat` and `if` keywords. + +The positioning is slightly different from the first exercise: + +- The ball has a radius of `3`. +- It sits on the grass at a `y` of `75`. +- It starts on the tee at `30` from the left. + +## Hints + +### Hint 1 + +The result of `get_shot_length()` tells you how many `x` the ball moves forward. So if shot length is `5` and the ball's starting position is `30`, then its final position should be `35`, which is `6` frames of animation (30, 31, 32, 33, 34, 35). + +### Hint 2 + +Remember, wherever you can use a number, you can use a variable or the result of a function instead. + +e.g. All of these are valid ways to write code (presuming Jiki has the relevant functions on his shelves): + +``` +circle(1, 2, 3) +circle(x, y, r) +circle(calculate_sun_left(), calculate_sun_width(), calculate_sun_radius()) +circle(1, y, calculate_sun_radius()) +``` diff --git a/bootcamp_content/projects/golf/exercises/shot-checker/stub.jiki b/bootcamp_content/projects/golf/exercises/shot-checker/stub.jiki new file mode 100644 index 0000000000..390c3431f9 --- /dev/null +++ b/bootcamp_content/projects/golf/exercises/shot-checker/stub.jiki @@ -0,0 +1,21 @@ +// Set our initial variables +set tee_position to 30 +set grass_y to 75 +set ball_x to tee_position - 1 // We increment this before we use it. +set ball_radius to 3 + +// As we're not changing colors, we can +// set this outside of the repeat block +fill_color_hex("orange") + +// TODO: Change how far the ball rolls +repeat 10 times do + // Update variables + change ball_x to ball_x + 1 + + // Draw the ball + clear() + circle(ball_x, grass_y, ball_radius) +end + +// TODO: Handle the ball landing in the hole. \ No newline at end of file diff --git a/bootcamp_content/projects/golf/exercises/shot-checker/task-1.md b/bootcamp_content/projects/golf/exercises/shot-checker/task-1.md new file mode 100644 index 0000000000..3528cd11cd --- /dev/null +++ b/bootcamp_content/projects/golf/exercises/shot-checker/task-1.md @@ -0,0 +1 @@ +Make the ball roll to the hole! diff --git a/bootcamp_content/projects/maze/exercises/automated-solve/config.json b/bootcamp_content/projects/maze/exercises/automated-solve/config.json index 2574b12b85..3aa52fb7d3 100644 --- a/bootcamp_content/projects/maze/exercises/automated-solve/config.json +++ b/bootcamp_content/projects/maze/exercises/automated-solve/config.json @@ -1,8 +1,8 @@ { "title": "Programatically solve a maze", - "description": "Programatically solve a maze", + "description": "Empower your blob to solve any maze with code!", "level": 3, - "idx": 1, + "idx": 6, "concepts": ["Conditionals", "loops-repeat"], "project_type": "maze", "tests_type": "state", @@ -43,7 +43,7 @@ ] }, { - "name": "Turn left if you can't move straight", + "name": "Turn left if you can", "tests": [ { "slug": "left-turn", @@ -130,16 +130,15 @@ [0, 1, 1, 1, 0, 1, 1, 1, 1], [0, 1, 1, 1, 0, 1, 1, 1, 1], [0, 0, 0, 0, 0, 1, 1, 1, 1], - [1, 1, 1, 1, 4, 1, 1, 1, 1], - [1, 1, 1, 1, 0, 1, 1, 1, 1], - [1, 1, 1, 1, 0, 1, 1, 1, 1] + [1, 4, 1, 1, 4, 1, 1, 1, 1], + [1, 4, 4, 4, 4, 1, 1, 1, 1], + [1, 1, 1, 1, 4, 1, 1, 1, 1] ] ] ], ["setupDirection", ["down"]], ["setupPosition", [0, 0]] ], - "function": "solve_maze", "checks": [ { "name": "position", @@ -164,9 +163,9 @@ [1, 1, 1, 2, 1, 1, 1, 1, 1], [1, 1, 1, 0, 1, 1, 1, 1, 1], [1, 1, 1, 0, 1, 1, 1, 1, 1], - [1, 1, 1, 0, 1, 1, 0, 1, 1], - [1, 1, 1, 0, 1, 1, 0, 1, 1], - [1, 1, 4, 0, 0, 0, 0, 0, 1], + [1, 4, 4, 0, 1, 1, 0, 1, 1], + [1, 4, 1, 0, 1, 1, 0, 1, 1], + [1, 4, 4, 0, 0, 0, 0, 0, 1], [1, 1, 1, 0, 1, 1, 1, 1, 1], [3, 0, 0, 0, 1, 1, 1, 1, 1], [1, 1, 1, 0, 1, 1, 1, 1, 1] @@ -176,7 +175,6 @@ ["setupDirection", ["down"]], ["setupPosition", [3, 0]] ], - "function": "solve_maze", "checks": [ { "name": "position", @@ -194,13 +192,13 @@ [ [ [2, 1, 1, 1, 1, 1, 1, 1, 1], - [0, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 0, 0, 1, 1, 1, 1], [0, 1, 1, 1, 0, 0, 0, 0, 1], - [0, 1, 1, 1, 0, 1, 1, 1, 1], - [0, 1, 1, 1, 4, 1, 1, 1, 1], - [0, 0, 0, 0, 0, 1, 1, 1, 1], - [1, 1, 1, 1, 0, 1, 1, 1, 1], - [1, 1, 1, 1, 0, 1, 1, 1, 1], + [0, 1, 1, 0, 0, 1, 0, 1, 1], + [0, 1, 1, 1, 0, 1, 0, 1, 1], + [0, 0, 0, 0, 0, 1, 0, 0, 1], + [1, 4, 1, 1, 0, 1, 1, 0, 1], + [1, 4, 4, 4, 0, 1, 0, 0, 1], [1, 1, 1, 1, 3, 1, 1, 1, 1] ] ] @@ -208,7 +206,6 @@ ["setupDirection", ["down"]], ["setupPosition", [0, 0]] ], - "function": "solve_maze", "checks": [ { "name": "position", diff --git a/bootcamp_content/projects/maze/exercises/automated-solve/introduction.md b/bootcamp_content/projects/maze/exercises/automated-solve/introduction.md index bf45496945..f1d79bb383 100644 --- a/bootcamp_content/projects/maze/exercises/automated-solve/introduction.md +++ b/bootcamp_content/projects/maze/exercises/automated-solve/introduction.md @@ -1,11 +1,26 @@ # Solve the Maze -Your task is to solve the following maze. +The first exercise you solved was manually moving your character around the maze. Already in Level 3, you're ready to solve any maze programmatically using code! -You have three functions you can use: +To make that possible, we're giving you two new functions: -- `move()` which moves the character one step forward -- `turn_left()` turns the character left (relative to the direction they're currently facing) -- `turn_right()` turns the character right (relative to the direction they're currently facing) +- `can_turn_left()`: returns `true` if the character can turn left. +- `can_turn_right()`: returns `true` if the character can turn right. +- `can_move()`: returns `true` if the character can move forward. -Remember to use one function per line. +With those two functions and the `move()`, `turn_left()` and `turn_right()` you had in Level 1, you can solve any maze. + +Spend a little time trying to work out how (maybe 15-30 minutes). Treat it as a fun logic puzzle. Get some paper and draw things out. Then when you want to check your method (or if you give up), read the instructions below + +
+ +## Instructions + +You can solve all the mazes using the following method (this is what we call an "algorithm" - a method of solving a problem): + +- If you can turn left, turn left and move forward +- Otherwise, if you can move forward, move forward. +- Otherwise if you can turn right, turn right and move forward. +- Otherwise turn around + +This exercise is broken into different tasks. Follow the instructions below and as you completed each task, one or more the scenarios will complete, and more instructions will appear below! diff --git a/bootcamp_content/projects/maze/exercises/automated-solve/stub.jiki b/bootcamp_content/projects/maze/exercises/automated-solve/stub.jiki index 27cc684a7b..6fa95fcc83 100644 --- a/bootcamp_content/projects/maze/exercises/automated-solve/stub.jiki +++ b/bootcamp_content/projects/maze/exercises/automated-solve/stub.jiki @@ -1,6 +1,3 @@ -// You can use move(), turn_left() and turn_right() -// Use the functions in the order you want your character -// to use them to solve them maze. We'll start you off by -// moving the character one step forward. - -move() \ No newline at end of file +repeat_until_game_over do + // TODO: Implement the algorithm +end \ No newline at end of file diff --git a/bootcamp_content/projects/maze/exercises/automated-solve/task-1.md b/bootcamp_content/projects/maze/exercises/automated-solve/task-1.md index b470c01201..601d75be06 100644 --- a/bootcamp_content/projects/maze/exercises/automated-solve/task-1.md +++ b/bootcamp_content/projects/maze/exercises/automated-solve/task-1.md @@ -1,8 +1,3 @@ # Task 1 -We've started by adding a single `move()` for you, which will move the character one step forward. - -Use the "Run code" button to see how close you're getting. - -It's good practice to get into the habit of running your code reguarly. -In this exercise, we'd recommend running it after adding each new instruction, although you might like to try and challenge yourself to solve it all in one go instead! +Move forward to the end of the maze. diff --git a/bootcamp_content/projects/maze/exercises/automated-solve/task-2.md b/bootcamp_content/projects/maze/exercises/automated-solve/task-2.md index b470c01201..11206813bc 100644 --- a/bootcamp_content/projects/maze/exercises/automated-solve/task-2.md +++ b/bootcamp_content/projects/maze/exercises/automated-solve/task-2.md @@ -1,8 +1,3 @@ -# Task 1 +# Task 2 -We've started by adding a single `move()` for you, which will move the character one step forward. - -Use the "Run code" button to see how close you're getting. - -It's good practice to get into the habit of running your code reguarly. -In this exercise, we'd recommend running it after adding each new instruction, although you might like to try and challenge yourself to solve it all in one go instead! +Now implement the second step. If there's a path to the left, take it! diff --git a/bootcamp_content/projects/maze/exercises/automated-solve/task-3.md b/bootcamp_content/projects/maze/exercises/automated-solve/task-3.md index b470c01201..3d57c75ba4 100644 --- a/bootcamp_content/projects/maze/exercises/automated-solve/task-3.md +++ b/bootcamp_content/projects/maze/exercises/automated-solve/task-3.md @@ -1,8 +1,3 @@ -# Task 1 +# Task 3 -We've started by adding a single `move()` for you, which will move the character one step forward. - -Use the "Run code" button to see how close you're getting. - -It's good practice to get into the habit of running your code reguarly. -In this exercise, we'd recommend running it after adding each new instruction, although you might like to try and challenge yourself to solve it all in one go instead! +Great! Now we deal with right turns. If there's not a path to the left or straight ahead, take the path to the right. diff --git a/bootcamp_content/projects/maze/exercises/automated-solve/task-4.md b/bootcamp_content/projects/maze/exercises/automated-solve/task-4.md index b470c01201..1363bab382 100644 --- a/bootcamp_content/projects/maze/exercises/automated-solve/task-4.md +++ b/bootcamp_content/projects/maze/exercises/automated-solve/task-4.md @@ -1,8 +1,3 @@ -# Task 1 +# Task 4 -We've started by adding a single `move()` for you, which will move the character one step forward. - -Use the "Run code" button to see how close you're getting. - -It's good practice to get into the habit of running your code reguarly. -In this exercise, we'd recommend running it after adding each new instruction, although you might like to try and challenge yourself to solve it all in one go instead! +Great work. Now you need to handle what happens when you get to a dead-end. In that case you need to turn around! diff --git a/bootcamp_content/projects/maze/exercises/implement-move/config.json b/bootcamp_content/projects/maze/exercises/implement-move/config.json index 739f81dd0a..6a3b694b20 100644 --- a/bootcamp_content/projects/maze/exercises/implement-move/config.json +++ b/bootcamp_content/projects/maze/exercises/implement-move/config.json @@ -2,7 +2,7 @@ "title": "Implement move", "description": "Implement the move function", "project_type": "maze", - "level": 5, + "level": 100, "tests_type": "state", "tasks": [ { diff --git a/bootcamp_content/projects/maze/exercises/manual-solve/config.json b/bootcamp_content/projects/maze/exercises/manual-solve/config.json index a5c58c8724..11727f988f 100644 --- a/bootcamp_content/projects/maze/exercises/manual-solve/config.json +++ b/bootcamp_content/projects/maze/exercises/manual-solve/config.json @@ -33,7 +33,6 @@ ["setupDirection", ["down"]], ["setupPosition", [0, 0]] ], - "function": "runGame", "checks": [ { "name": "position", diff --git a/bootcamp_content/projects/number-puzzles/exercises/even-or-odd/config.json b/bootcamp_content/projects/number-puzzles/exercises/even-or-odd/config.json index 2cb93d8bc9..ff1998b56b 100644 --- a/bootcamp_content/projects/number-puzzles/exercises/even-or-odd/config.json +++ b/bootcamp_content/projects/number-puzzles/exercises/even-or-odd/config.json @@ -2,7 +2,7 @@ "title": "Even or Odd", "description": "Determine if a number is even or odd", "concepts": ["strings-using", "conditionals"], - "level": 3, + "level": 4, "idx": 1, "tasks": [ { diff --git a/bootcamp_content/projects/number-puzzles/exercises/positive-negative-or-zero/config.json b/bootcamp_content/projects/number-puzzles/exercises/positive-negative-or-zero/config.json index 2a4cce8ef9..3e775089b0 100644 --- a/bootcamp_content/projects/number-puzzles/exercises/positive-negative-or-zero/config.json +++ b/bootcamp_content/projects/number-puzzles/exercises/positive-negative-or-zero/config.json @@ -2,7 +2,7 @@ "title": "Positive, Negative or Zero", "description": "Determine if a number is positive, negative or zero", "concepts": ["strings-using", "conditionals"], - "level": 3, + "level": 4, "idx": 1, "tasks": [ { diff --git a/bootcamp_content/projects/rock-paper-scissors/config.json b/bootcamp_content/projects/rock-paper-scissors/config.json index 3915c85080..3f8ec43fed 100644 --- a/bootcamp_content/projects/rock-paper-scissors/config.json +++ b/bootcamp_content/projects/rock-paper-scissors/config.json @@ -2,5 +2,5 @@ "slug": "rock-paper-scissors", "title": "Rock, Paper, Scissors", "description": "Implement the classic game", - "exercises": ["basic"] + "exercises": ["determine-winner"] } diff --git a/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/config.json b/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/config.json new file mode 100644 index 0000000000..c144a17ff4 --- /dev/null +++ b/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/config.json @@ -0,0 +1,186 @@ +{ + "title": "Determine Winner", + "description": "Determine the winner of Rock, Paper, Scissors", + "project_type": "rock-paper-scissors", + "level": 3, + "idx": 3, + "tests_type": "state", + "tasks": [ + { + "name": "Player 1 chooses paper", + "tests": [ + { + "slug": "paper-vs-paper", + "name": "Paper vs Paper", + "description_html": "It's a draw. Announce it correctly!", + + "setup_functions": [["setChoices", ["paper", "paper"]]], + "checks": [ + { + "name": "result", + "matcher": "toExist", + "error_html": "You didn't announce a result!" + }, + { + "name": "result", + "value": "tie", + "error_html": "Paper vs Paper should be a tie but it was a {value}" + } + ] + }, + { + "slug": "paper-vs-rock", + "name": "Paper vs Rock", + "description_html": "Player 1's paper beats player 2's rock. Announce player 1 as the winner!", + + "setup_functions": [["setChoices", ["paper", "rock"]]], + "checks": [ + { + "name": "result", + "matcher": "toExist", + "error_html": "You didn't announce a result!" + }, + { + "name": "result", + "value": "player_1", + "error_html": "Paper vs Rock should be a win for player 1 but it was a {value}" + } + ] + }, + { + "slug": "paper-vs-scissors", + "name": "Paper vs Scissors", + "description_html": "Player 2's scissors beat player 1's paper. Announce player 2 as the winner.", + + "setup_functions": [["setChoices", ["paper", "scissors"]]], + "checks": [ + { + "name": "result", + "matcher": "toExist", + "error_html": "You didn't announce a result!" + }, + { + "name": "result", + "value": "player_2", + "error_html": "Paper vs Scissors should be a win for player 2 but it was a {value}" + } + ] + }, + { + "slug": "rock-vs-paper", + "name": "Rock vs Paper", + "description_html": "Player 2's paper beat player 1's rock. Announce player 2 as the winner.", + + "setup_functions": [["setChoices", ["rock", "paper"]]], + "checks": [ + { + "name": "result", + "matcher": "toExist", + "error_html": "You didn't announce a result!" + }, + { + "name": "result", + "value": "player_2", + "error_html": "Rock vs Paper should be a win for player 2 but it was a {value}" + } + ] + }, + { + "slug": "rock-vs-rock", + "name": "Rock vs Rock", + "description_html": "It's a draw. Announce it correctly!", + + "setup_functions": [["setChoices", ["rock", "rock"]]], + "checks": [ + { + "name": "result", + "matcher": "toExist", + "error_html": "You didn't announce a result!" + }, + { + "name": "result", + "value": "tie", + "error_html": "Rock vs Rock should be a tie but it was a {value}" + } + ] + }, + { + "slug": "rock-vs-scissors", + "name": "Rock vs Scissors", + "description_html": "Player 1's rock beat player 2's scissors. Announce player 1 as the winner.", + + "setup_functions": [["setChoices", ["rock", "scissors"]]], + "checks": [ + { + "name": "result", + "matcher": "toExist", + "error_html": "You didn't announce a result!" + }, + { + "name": "result", + "value": "player_1", + "error_html": "Rock vs Scissors should be a win for player 1 but it was a {value}" + } + ] + }, + { + "slug": "scissors-vs-paper", + "name": "Scissors vs Paper", + "description_html": "Player 1's scissors beat player 2's paper. Announce player 1 as the winner.", + + "setup_functions": [["setChoices", ["scissors", "paper"]]], + "checks": [ + { + "name": "result", + "matcher": "toExist", + "error_html": "You didn't announce a result!" + }, + { + "name": "result", + "value": "player_1", + "error_html": "Scissors vs Paper should be a win for player 1 but it was a {value}" + } + ] + }, + { + "slug": "scissors-vs-rock", + "name": "Scissors vs Rock", + "description_html": "Player 2's rock beat player 1's scissors. Announce player 2 as the winner.", + + "setup_functions": [["setChoices", ["scissors", "rock"]]], + "checks": [ + { + "name": "result", + "matcher": "toExist", + "error_html": "You didn't announce a result!" + }, + { + "name": "result", + "value": "player_2", + "error_html": "Scissors vs Rock should be a win for player 2 but it was a {value}" + } + ] + }, + { + "slug": "scissors-vs-scissors", + "name": "Scissors vs Scissors", + "description_html": "It's a draw. Announce it correctly!", + + "setup_functions": [["setChoices", ["scissors", "scissors"]]], + "checks": [ + { + "name": "result", + "matcher": "toExist", + "error_html": "You didn't announce a result!" + }, + { + "name": "result", + "value": "tie", + "error_html": "Scissors vs Scissors should be a tie but it was a {value}" + } + ] + } + ] + } + ] +} diff --git a/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/example.jiki b/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/example.jiki new file mode 100644 index 0000000000..2714079e95 --- /dev/null +++ b/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/example.jiki @@ -0,0 +1,19 @@ +set player_1_choice to get_player_1_choice() +set player_2_choice to get_player_2_choice() + +set result to "player_2" + +if player_1_choice == player_2_choice do + change result to "tie" + +else if player_1_choice == "rock" and player_2_choice == "scissors" do + change result to "player_1" + +else if player_1_choice == "scissors" and player_2_choice == "paper" do + change result to "player_1" + +else if player_1_choice == "paper" and player_2_choice == "rock" do + change result to "player_1" +end + +announce_result(result) \ No newline at end of file diff --git a/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/example2.jiki b/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/example2.jiki new file mode 100644 index 0000000000..70b96334c3 --- /dev/null +++ b/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/example2.jiki @@ -0,0 +1,19 @@ +set player_1_choice to get_player_1_choice() +set player_2_choice to get_player_2_choice() + +set result to "player_2" + +if player_1_choice equals player_2_choice do + change result to "tie" + +else if player_1_choice is "rock" and player_2_choice is "scissors" do + change result to "player_1" + +else if player_1_choice is "scissors" and player_2_choice is "paper" do + change result to "player_1" + +else if player_1_choice is "paper" and player_2_choice is "rock" do + change result to "player_1" +end + +announce_result(result) diff --git a/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/introduction.md b/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/introduction.md new file mode 100644 index 0000000000..6f67d195a4 --- /dev/null +++ b/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/introduction.md @@ -0,0 +1,38 @@ +# Determine Winner + +To start our game of Rock, Paper, Scissors, we're going to write some code to determine the winner. + +It Rock, Paper, Scissors, two players each choose rock, paper or scissors. You then compare the choices to see who's won: + +- If both players choose the same, it's a tie. +- Rock blunts scissors (rock wins). +- Scissors cut paper (scissors wins). +- Paper smothers rock (paper wins). + +Your job is to compare the choices the players have made and announce the winner to the playing hall. + +### Functions + +You have three functions to use: + +- `get_player_1_choice()`: Returns player 1's choice - one of `"rock"`, `"paper"` or `"scissors"`. +- `get_player_2_choice()`: Returns player 2's choice. Also `"rock"`, `"paper"` or `"scissors"`. +- `announce_result(result)`: You announce the result, using one of `"player_1"`, `"player_2"`, or `"tie"` as an input. + +### How to use the functions + +In case its not clear how to use the functions, imagine this parallel universe where if player 1 chooses `"paper"`, the game is always a tie. In that situation you could write the following code: + +``` +if(get_player_1_choice() == "paper") do + announce_result("tie") +end +``` + +### Bonus Challenges + +Want some extra practice? + +- Can you solve the exercise only using the `get_player_1_choice()` and `get_player_2_choice()` functions once in your program (imagine how inefficient it is to have to keep running a whole machine to get the player's choices over and over again). +- Can you solve the exercise with `announce_result(...)` only appearing once in your code? +- Can you solve the exercise using the first two bonus conditions and only 13 lines of code (not including blank lines or comments)? diff --git a/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/stub.jiki b/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/stub.jiki new file mode 100644 index 0000000000..e0449bc1b2 --- /dev/null +++ b/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/stub.jiki @@ -0,0 +1,6 @@ +// This is a scarily blank bit of starting code... + +// Start by getting the first scenario correct +// Then get the scenario correct, etc. +// Then work out whether there's a way to make your +// code prettier or more efficient at the end! \ No newline at end of file diff --git a/bootcamp_content/projects/rock-paper-scissors/exercises/basic/task-1.md b/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/task-1.md similarity index 100% rename from bootcamp_content/projects/rock-paper-scissors/exercises/basic/task-1.md rename to bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/task-1.md diff --git a/bootcamp_content/projects/rock-paper-scissors/exercises/basic/task-2.md b/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/task-2.md similarity index 100% rename from bootcamp_content/projects/rock-paper-scissors/exercises/basic/task-2.md rename to bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/task-2.md diff --git a/bootcamp_content/projects/rock-paper-scissors/exercises/basic/task-3.md b/bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/task-3.md similarity index 100% rename from bootcamp_content/projects/rock-paper-scissors/exercises/basic/task-3.md rename to bootcamp_content/projects/rock-paper-scissors/exercises/determine-winner/task-3.md diff --git a/bootcamp_content/projects/rock-paper-scissors/exercises/basic/config.json b/bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/config.json similarity index 99% rename from bootcamp_content/projects/rock-paper-scissors/exercises/basic/config.json rename to bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/config.json index a4fea860af..7b61d0c288 100644 --- a/bootcamp_content/projects/rock-paper-scissors/exercises/basic/config.json +++ b/bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/config.json @@ -2,7 +2,7 @@ "title": "Rock Paper Scissors", "description": "Calculate the correct result", "concepts": ["conditionals"], - "level": 3, + "level": 4, "idx": 1, "tests_type": "io", "tasks": [ diff --git a/bootcamp_content/projects/rock-paper-scissors/exercises/basic/example.jiki b/bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/example.jiki similarity index 100% rename from bootcamp_content/projects/rock-paper-scissors/exercises/basic/example.jiki rename to bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/example.jiki diff --git a/bootcamp_content/projects/rock-paper-scissors/exercises/basic/introduction.md b/bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/introduction.md similarity index 100% rename from bootcamp_content/projects/rock-paper-scissors/exercises/basic/introduction.md rename to bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/introduction.md diff --git a/bootcamp_content/projects/rock-paper-scissors/exercises/basic/stub.jiki b/bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/stub.jiki similarity index 100% rename from bootcamp_content/projects/rock-paper-scissors/exercises/basic/stub.jiki rename to bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/stub.jiki diff --git a/bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/task-1.md b/bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/task-1.md new file mode 100644 index 0000000000..830e498063 --- /dev/null +++ b/bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/task-1.md @@ -0,0 +1,9 @@ +# Task 1 + +There are a few different ways to approach this exercise, but let's solve it by breaking it down based on player 1's choice. + +To start with, return the correct value based on when player 1 choices "paper": + +- If player 2 also chooses paper, we should return `"tie"` +- If player 2 chooses "rock", then the paper smoothers the rock so player 1 wins (return `"player_1"`) +- If player 2 chooses "scissors", then the rock blunts the scissors so player 2 wins (return `"player_2"`) diff --git a/bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/task-2.md b/bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/task-2.md new file mode 100644 index 0000000000..cfa6f86026 --- /dev/null +++ b/bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/task-2.md @@ -0,0 +1,7 @@ +# Task 2 + +Great, now let's consider what happens if player 1 chooses "rock': + +- If player 2 also chooses rock, we should return `"tie"` +- If player 2 chooses "paper", then the paper smoothers the rock so player 2 wins (return `"player_2"`) +- If player 2 chooses "scissors", then the rock blunts the scissors so player 1 wins (return `"player_1"`) diff --git a/bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/task-3.md b/bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/task-3.md new file mode 100644 index 0000000000..0ffe0edf7e --- /dev/null +++ b/bootcamp_content/projects/rock-paper-scissors/exercises/win-with-a-function/task-3.md @@ -0,0 +1,5 @@ +# Task 3 + +Nice work. So we're two-thirds of the way there. The final situation is if player 1 chooses scissors. + +This time, we'll leave the logic for you to work out! diff --git a/bootcamp_content/projects/space-invaders/config.json b/bootcamp_content/projects/space-invaders/config.json new file mode 100644 index 0000000000..a1ac505636 --- /dev/null +++ b/bootcamp_content/projects/space-invaders/config.json @@ -0,0 +1,6 @@ +{ + "slug": "space-invaders", + "title": "Space Invaders", + "description": "Recreate the classic arcade game Space Invaders.", + "exercises": ["scroll-and-shoot"] +} diff --git a/bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/config.json b/bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/config.json new file mode 100644 index 0000000000..23ece591a9 --- /dev/null +++ b/bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/config.json @@ -0,0 +1,28 @@ +{ + "title": "Scroll and Shoot", + "description": "Move your laser from left to right and shoot the aliens.", + "project_type": "space-invaders", + "level": 3, + "idx": 5, + "tests_type": "state", + "tasks": [ + { + "name": "Move your laser from left to right and shoot the aliens.", + "tests": [ + { + "slug": "scroll-and-shoot", + "name": "Scroll and Shoot", + "description_html": "Move your laser from left to right and shoot the aliens.", + "checks": [ + { + "name": "gameStatus", + "value": "won", + "matcher": "toEqual", + "error_html": "You didn't shoot down all the aliens." + } + ] + } + ] + } + ] +} diff --git a/bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/example.jiki b/bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/example.jiki new file mode 100644 index 0000000000..83dcebc574 --- /dev/null +++ b/bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/example.jiki @@ -0,0 +1,25 @@ +set direction to "right" +set steps to 0 + +repeat_until_game_over do + change steps to steps + 1 + + if direction is "right" do + move_right() + + if (steps > 9) do + change direction to "left" + change steps to 0 + end + else do + move_left() + if (steps > 9) do + change direction to "right" + change steps to 0 + end + end + + if is_alien_above() do + shoot() + end +end \ No newline at end of file diff --git a/bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/introduction.md b/bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/introduction.md new file mode 100644 index 0000000000..b6d13dab4e --- /dev/null +++ b/bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/introduction.md @@ -0,0 +1,34 @@ +# Space Invaders + +Welcome to the first Space Invaders exercise. This is a classic 1970s game, and one of the first I ever played! + +By the end of the Bootcamp you'll have built this from scratch. But for now, your job is to play and win the game, by shooting down all the aliens. + +You can move the laser left and right using the `move_left()` and `move_right()` functions. You can experiment to see how far left and right you can move. If you go off the edge of the screen, you lose. + +As you move, you need to check whether there's an alien above you using the `is_alien_above()` function and then `shoot()` it if so. If you shoot when there's not an alien, you'll lose the game - wasting ammo is not allowed! + +Once all the aliens have been shot down, you win! + +### Reference + +Functions: + +- `move_left()`: Moves the laser to the left +- `move_right()`: Moves the laser to the right +- `is_alien_above()`: Returns `true` if there's an alien directly above you, or `false` if not. +- `shoot()`: Shoots upwards. + +You'll also need the `set`, `change`, `if` and `repeat_until_game_over` concepts. To start with, you might find it better to use `repeat` with a fixed number of times, so that your code doesn't run forever! + +### There's many ways to solve this! + +We've now entered the point of the course where there are **lots** (probably hundreds) of ways to solve this exercise. There are different tradeoffs between different approaches. Over time you'll learn "best practices" and why some ways are better than others, but for now don't get hung up on that. Your job is **just to solve the exercise**. + +In the Labs sessions I'll start discussing different ways to solve things, and you can tell me about what approach you took! + +### Hints + +As normal, it's important to break the exercise down into steps. You probably want to get the laser moving from side to side first, then add the logic for detecting and shooting aliens afterwards. + +Much of the logic is similar to the rainbow ball exercise, but it's a little different as you aren't moving using coordinates. You'll need to use different variables and take a slightly different approach, but the core logic of keeping track of where you are is the same. diff --git a/bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/stub.jiki b/bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/stub.jiki new file mode 100644 index 0000000000..f263fcf913 --- /dev/null +++ b/bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/stub.jiki @@ -0,0 +1,9 @@ +// TODO: Set any initial variables here + +// Consider changing this to a `repeat` with a smaller +// amount of iterations to start with. +repeat_until_game_over do + // TODO: Move the laser and shoot the aliens + // Write out all the logical steps using comments first + // the write the acutal JikiScript to make things happen. +end \ No newline at end of file diff --git a/bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/task-1.md b/bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/task-1.md new file mode 100644 index 0000000000..6a45d89dae --- /dev/null +++ b/bootcamp_content/projects/space-invaders/exercises/scroll-and-shoot/task-1.md @@ -0,0 +1,3 @@ +# Task 1 + +Save earth from the aliens! diff --git a/bootcamp_content/projects/space-invaders/introduction.md b/bootcamp_content/projects/space-invaders/introduction.md new file mode 100644 index 0000000000..e842a516e3 --- /dev/null +++ b/bootcamp_content/projects/space-invaders/introduction.md @@ -0,0 +1,9 @@ +# Space Invaders + +## Overview + +Space Invaders is classic game from the 1970s. + +The goal is to defeat waves of aliens as they descend to earth. You have a horizontally moving laser that can shoot up at them. You continue until they finally reach the ground! + +By the end of the Bootcamp, you'll have made this game from scratch and be able to play it in a browser! diff --git a/bootcamp_content/projects/time/config.json b/bootcamp_content/projects/time/config.json new file mode 100644 index 0000000000..252e15cdb6 --- /dev/null +++ b/bootcamp_content/projects/time/config.json @@ -0,0 +1,6 @@ +{ + "slug": "time", + "title": "Time", + "description": "Adventures in time", + "exercises": ["digital-clock"] +} diff --git a/bootcamp_content/projects/time/exercises/digital-clock/config.json b/bootcamp_content/projects/time/exercises/digital-clock/config.json new file mode 100644 index 0000000000..80c87ee734 --- /dev/null +++ b/bootcamp_content/projects/time/exercises/digital-clock/config.json @@ -0,0 +1,143 @@ +{ + "title": "Digital Clock", + "description": "Display the time on a digital clock", + "project_type": "digital-clock", + "available_functions": [], + "level": 3, + "idx": 1, + "tests_type": "state", + "tasks": [ + { + "name": "Display morning times", + "tests": [ + { + "slug": "morning-1", + "name": "Early morning", + "description_html": "Display 6:35 as \"6:35am\"", + "setup_functions": [["setTime", [6, 35]]], + "checks": [ + { + "name": "displayedTime", + "matcher": "toExist", + "error_html": "The clock didn't get updated. Make sure you use the `display_time` function." + }, + { + "name": "displayedTime", + "value": "6:35am", + "matcher": "toEqual", + "error_html": "The clock didn't display 6:35am" + } + ] + }, + { + "slug": "morning-2", + "name": "Late morning", + "description_html": "Display 11:04 as \"11:04am\"", + "setup_functions": [["setTime", [11, 4]]], + "checks": [ + { + "name": "displayedTime", + "matcher": "toExist", + "error_html": "The clock didn't get updated. Make sure you use the `display_time` function." + }, + { + "name": "displayedTime", + "value": "11:4am", + "matcher": "toEqual", + "error_html": "The clock didn't display 11:04am" + } + ] + } + ] + }, + { + "name": "Display afternoon times", + "tests": [ + { + "slug": "afternoon-1", + "name": "Early afternoon", + "description_html": "Display the time 12:19 as \"12:19 pm\"", + "setup_functions": [["setTime", [12, 19]]], + "checks": [ + { + "name": "displayedTime", + "matcher": "toExist", + "error_html": "The clock didn't get updated. Make sure you use the `display_time` function." + }, + { + "name": "displayedTime", + "value": "12:19pm", + "matcher": "toEqual", + "error_html": "The clock didn't display 12:19pm" + } + ] + }, + { + "slug": "afternoon-2", + "name": "Late evening", + "description_html": "Display the time 23:32 as \"11:32 pm\"", + "setup_functions": [["setTime", [23, 32]]], + "checks": [ + { + "name": "displayedTime", + "matcher": "toExist", + "error_html": "The clock didn't get updated. Make sure you use the `display_time` function." + }, + { + "name": "displayedTime", + "value": "11:32pm", + "matcher": "toEqual", + "error_html": "The clock didn't display 11:32pm" + } + ] + } + ] + }, + { + "name": "Display midnight", + "tests": [ + { + "slug": "midnight", + "name": "Display midnight", + "description_html": "Display midnight as \"0:00am\"", + "setup_functions": [["setTime", [0, 0]]], + "checks": [ + { + "name": "displayedTime", + "matcher": "toExist", + "error_html": "The clock didn't get updated. Make sure you use the `display_time` function." + }, + { + "name": "displayedTime", + "value": "0:0am", + "matcher": "toEqual", + "error_html": "The clock didn't display 00:00am" + } + ] + } + ] + }, + { + "name": "Display the current time", + "tests": [ + { + "slug": "now", + "name": "Display the current time", + "description_html": "Display the current time", + "checks": [ + { + "name": "displayedTime", + "matcher": "toExist", + "error_html": "The clock didn't get updated. Make sure you use the `display_time` function." + }, + { + "name": "didDisplayCurrentTime()", + "matcher": "toBeTrue", + "error_html": "The clock didn't display the current time" + } + ] + } + ] + } + ] +} diff --git a/bootcamp_content/projects/time/exercises/digital-clock/example.jiki b/bootcamp_content/projects/time/exercises/digital-clock/example.jiki new file mode 100644 index 0000000000..aa5bdfd32e --- /dev/null +++ b/bootcamp_content/projects/time/exercises/digital-clock/example.jiki @@ -0,0 +1,14 @@ +set hour to current_time_hour() +set minutes to current_time_minute() + +set indicator to "am" + +if(hour >= 12) do + change indicator to "pm" +end + +if(hour > 12) do + change hour to hour - 12 +end + +display_time(hour, minutes, indicator) \ No newline at end of file diff --git a/bootcamp_content/projects/time/exercises/digital-clock/introduction.md b/bootcamp_content/projects/time/exercises/digital-clock/introduction.md new file mode 100644 index 0000000000..cb39ba252a --- /dev/null +++ b/bootcamp_content/projects/time/exercises/digital-clock/introduction.md @@ -0,0 +1,31 @@ +# Digital Clock + +Welcome to the Time project. + +In this first exercise you're going to use two new functions that we've given Jiki: + +- `current_time_hour()`: Returns the current hour using 24 hour time (e.g. 15 minutes to midnight would return `23`) as a number. +- `current_time_minute()`: Returns the current minute as a number. + +Your job is to update a digital clock based on whatever numbers those functions give back. + +The digital clock expects the numbers to be in a 12 hour format with an `am` or `pm` (what's called the "meridien"). + +So for example: + +``` +7:45 -> 7:45am +19:45 -> 7:45pm +``` + +To display the time on the clock you use the `display_time(hour, minutes, meridien)` function. + +## Scenarios + +In this exercise, we introduce different **scenarios** for the first time. + +Different scenarios test that your code works in different situations. In this exercise, the current time changes in each scenario. So in one scenario, the time might be `07:45` and in another it might be `21:33`. + +Once you click "Run Scenarios", click through the `1`, `2`, `3`, ... boxes to see the different scenarios and whether your code solved that scenario or not. + +Your job is to write code that makes **all the scenarios work**. diff --git a/bootcamp_content/projects/time/exercises/digital-clock/stub.jiki b/bootcamp_content/projects/time/exercises/digital-clock/stub.jiki new file mode 100644 index 0000000000..ef31fc3678 --- /dev/null +++ b/bootcamp_content/projects/time/exercises/digital-clock/stub.jiki @@ -0,0 +1,8 @@ +// TODO: Use the current_time_hour() and +// current_time_minute() functions to get the +// current time and minutes for the scenario. + +// Convert the hour into "12 hour" format and +// determine whether it's "am" or "pm". + +// TODO: Use display_time to update the clock \ No newline at end of file diff --git a/bootcamp_content/projects/time/exercises/digital-clock/task-1.md b/bootcamp_content/projects/time/exercises/digital-clock/task-1.md new file mode 100644 index 0000000000..2054bb52f2 --- /dev/null +++ b/bootcamp_content/projects/time/exercises/digital-clock/task-1.md @@ -0,0 +1,3 @@ +# Task 1 + +Output the time on the clock! diff --git a/bootcamp_content/projects/time/exercises/digital-clock/task-2.md b/bootcamp_content/projects/time/exercises/digital-clock/task-2.md new file mode 100644 index 0000000000..2054bb52f2 --- /dev/null +++ b/bootcamp_content/projects/time/exercises/digital-clock/task-2.md @@ -0,0 +1,3 @@ +# Task 1 + +Output the time on the clock! diff --git a/bootcamp_content/projects/time/exercises/digital-clock/task-3.md b/bootcamp_content/projects/time/exercises/digital-clock/task-3.md new file mode 100644 index 0000000000..2054bb52f2 --- /dev/null +++ b/bootcamp_content/projects/time/exercises/digital-clock/task-3.md @@ -0,0 +1,3 @@ +# Task 1 + +Output the time on the clock! diff --git a/bootcamp_content/projects/time/exercises/digital-clock/task-4.md b/bootcamp_content/projects/time/exercises/digital-clock/task-4.md new file mode 100644 index 0000000000..2054bb52f2 --- /dev/null +++ b/bootcamp_content/projects/time/exercises/digital-clock/task-4.md @@ -0,0 +1,3 @@ +# Task 1 + +Output the time on the clock! diff --git a/bootcamp_content/projects/time/introduction.md b/bootcamp_content/projects/time/introduction.md new file mode 100644 index 0000000000..43a2d3ae42 --- /dev/null +++ b/bootcamp_content/projects/time/introduction.md @@ -0,0 +1,7 @@ +# Maze + +## Overview + +Using time is a common and key programming principle. From countdowns to encryption algorithms, its an important thing to be familiar with, and can be suprisingly tricky. + +Did you know there are 38 time zones in the world, that's over 1.5x the amount of hours in the day. Wild! diff --git a/bootcamp_content/projects/weather/exercises/cloud-rain-sun/config.json b/bootcamp_content/projects/weather/exercises/cloud-rain-sun/config.json index 6dbae045ab..e4097adf34 100644 --- a/bootcamp_content/projects/weather/exercises/cloud-rain-sun/config.json +++ b/bootcamp_content/projects/weather/exercises/cloud-rain-sun/config.json @@ -14,7 +14,6 @@ { "slug": "draw-scene", "name": "Draw the scene.", - "function": "main", "description_html": "Fill in the outline to look like the image in the instructions", "setup_functions": [ [ diff --git a/bootcamp_content/projects/weather/exercises/sunshine/config.json b/bootcamp_content/projects/weather/exercises/sunshine/config.json index fd7462a152..af00163cdd 100644 --- a/bootcamp_content/projects/weather/exercises/sunshine/config.json +++ b/bootcamp_content/projects/weather/exercises/sunshine/config.json @@ -14,7 +14,6 @@ { "slug": "draw-scene", "name": "Draw the scene.", - "function": "main", "description_html": "Add the sun to its spikes.", "checks": [ { diff --git a/db/bootcamp_seeds.rb b/db/bootcamp_seeds.rb index 714a75ee1d..b15fba4f67 100644 --- a/db/bootcamp_seeds.rb +++ b/db/bootcamp_seeds.rb @@ -49,14 +49,13 @@ def exercise_config_for(project_slug, end projects = %w[ - two-fer - rock-paper-scissors - number-puzzles drawing maze - wordle weather golf + space-invaders + time + rock-paper-scissors ] projects.each do |project_slug| @@ -84,7 +83,7 @@ def exercise_config_for(project_slug, title: exercise_config[:title], description: exercise_config[:description], level_idx: exercise_config[:level], - concepts: exercise_config[:concepts].map do |slug| + concepts: (exercise_config[:concepts] || []).map do |slug| Bootcamp::Concept.find_by!(slug:) end ) diff --git a/package.json b/package.json index b3647b1d13..9973f0d589 100644 --- a/package.json +++ b/package.json @@ -21,12 +21,12 @@ "@exercism/active-background": "^0.6.2", "@exercism/codemirror-lang-arturo": "^0.1.6", "@exercism/codemirror-lang-gleam": "^2.0.1", + "@exercism/codemirror-lang-jikiscript": "0.0.3", "@exercism/codemirror-lang-uiua": "^0.0.4", "@exercism/codemirror-lang-wren": "https://github.com/exercism/codemirror-lang-wren", "@exercism/highlightjs-arturo": "^0.0.2", "@exercism/highlightjs-gdscript": "^0.0.1", "@exercism/highlightjs-uiua": "^0.0.4", - "@exercism/codemirror-lang-jikiscript": "0.0.3", "@exercism/twine2-story-format": "https://github.com/exercism/twine2-story-format.git", "@floating-ui/dom": "^1.6.12", "@gleam-lang/highlight.js-gleam": "^1.0.0", @@ -87,6 +87,7 @@ "lodash.clonedeep": "^4.5.0", "lodash.isequal": "^4.5.0", "lottie-web": "^5.12.2", + "marked": "^15.0.6", "mousetrap": "^1.6.5", "nim-codemirror-mode": "^0.3.0", "pluralize": "^8.0.0", diff --git a/test/javascript/interpreter/languages/javascript/parser.test.ts b/test/javascript/interpreter/languages/javascript/parser.test.ts index ea193658a2..a056d1c669 100644 --- a/test/javascript/interpreter/languages/javascript/parser.test.ts +++ b/test/javascript/interpreter/languages/javascript/parser.test.ts @@ -1107,17 +1107,17 @@ describe('error', () => { }) test('unterminated - end of file', () => { expect(() => parse('"abc')).toThrow( - 'Did you forget the end quote for the "abc" string?' + `Did you forget to add end quote? Maybe you meant to write:\n\n\`\`\`\"abc\"\`\`\`` ) }) test('unterminated - end of line', () => { expect(() => parse('"abc\nsomething_else"')).toThrow( - 'Did you forget the end quote for the "abc" string?' + `Did you forget to add end quote? Maybe you meant to write:\n\n\`\`\`\"abc\"\`\`\`` ) }) test('unterminated - newline in string', () => { expect(() => parse('"abc\n"')).toThrow( - 'Did you forget the end quote for the "abc" string?' + `Did you forget to add end quote? Maybe you meant to write:\n\n\`\`\`\"abc\"\`\`\`` ) }) }) diff --git a/test/javascript/interpreter/languages/jikiscript/interpreter.test.ts b/test/javascript/interpreter/languages/jikiscript/interpreter.test.ts index 5dabac31e6..b5fc4ba655 100644 --- a/test/javascript/interpreter/languages/jikiscript/interpreter.test.ts +++ b/test/javascript/interpreter/languages/jikiscript/interpreter.test.ts @@ -4,6 +4,7 @@ import { evaluateJikiScriptFunction as evaluateFunction, } from '@/interpreter/interpreter' import type { ExecutionContext } from '@/interpreter/executor' +import { error } from 'jquery' describe('statements', () => { describe('expression', () => { @@ -23,7 +24,7 @@ describe('statements', () => { describe('unary', () => { test('negation', () => { - const { frames } = interpret('set x to not true') + const { frames } = interpret('set x to !true') expect(frames).toBeArrayOfSize(1) expect(frames[0].status).toBe('SUCCESS') expect(frames[0].variables).toMatchObject({ x: false }) @@ -577,7 +578,7 @@ describe('statements', () => { expect(frames[1].variables).toMatchObject({ x: 3 }) }) - test('nested', () => { + test('stacked', () => { const { frames } = interpret(` if true is false do set x to 2 @@ -595,6 +596,40 @@ describe('statements', () => { expect(frames[2].status).toBe('SUCCESS') expect(frames[2].variables).toMatchObject({ x: 3 }) }) + test('nested if', () => { + const { error, frames } = interpret(` + set x to 1 + if true is true do + change x to 2 + if true is true do + change x to 3 + end + end + `) + expect(error).toBeNull() + expect(frames).toBeArrayOfSize(5) + frames.forEach((frame) => { + expect(frame.status).toBe('SUCCESS') + }) + }) + test('nested if/else', () => { + const { error, frames } = interpret(` + set x to 1 + if true is true do + change x to 2 + if true is true do + change x to 3 + end + else do + change x to 4 + end + `) + expect(error).toBeNull() + expect(frames).toBeArrayOfSize(5) + frames.forEach((frame) => { + expect(frame.status).toBe('SUCCESS') + }) + }) }) describe('repeat', () => { diff --git a/test/javascript/interpreter/languages/jikiscript/scanner.test.ts b/test/javascript/interpreter/languages/jikiscript/scanner.test.ts index 38c0bae283..ee0f4d09d0 100644 --- a/test/javascript/interpreter/languages/jikiscript/scanner.test.ts +++ b/test/javascript/interpreter/languages/jikiscript/scanner.test.ts @@ -15,6 +15,8 @@ describe('single-character', () => { ['+', 'PLUS'], ['*', 'STAR'], ['/', 'SLASH'], + ['=', 'EQUAL'], + ['!', 'NOT'], ])("'%s' token", (source: string, expectedType: string) => { const tokens = scan(source) expect(tokens[0].type).toBe(expectedType as TokenType) @@ -29,6 +31,8 @@ describe('one, two or three characters', () => { ['>=', 'GREATER_EQUAL'], ['<', 'LESS'], ['<=', 'LESS_EQUAL'], + ['!=', 'STRICT_INEQUALITY'], + ['==', 'STRICT_EQUALITY'], ])("'%s' token", (source: string, expectedType: string) => { const tokens = scan(source) expect(tokens[0].type).toBe(expectedType as TokenType) diff --git a/test/javascript/interpreter/languages/jikiscript/syntaxErrors.test.ts b/test/javascript/interpreter/languages/jikiscript/syntaxErrors.test.ts index 6d219982d3..85160c339b 100644 --- a/test/javascript/interpreter/languages/jikiscript/syntaxErrors.test.ts +++ b/test/javascript/interpreter/languages/jikiscript/syntaxErrors.test.ts @@ -64,7 +64,9 @@ describe('syntax errors', () => { describe('assignment', () => { test('using = by accident', () => { - expect(() => parse('set 123 = "value"')).toThrow('UnknownCharacterEquals') + expect(() => parse('set value = "value"')).toThrow( + 'UnexpectedEqualsForAssignment' + ) }) test('number as variable name', () => { @@ -242,15 +244,20 @@ describe('syntax errors', () => { }) describe('invalid characters', () => { - test('using = by accident', () => { - expect(() => parse('set 123 to x = "value"')).toThrow( - 'UnknownCharacterEquals' + test('using = for assignment', () => { + expect(() => parse('set x = "value"')).toThrow( + 'UnexpectedEqualsForAssignment' ) }) - test('using == by accident', () => { - expect(() => parse('set 123 to x == "value"')).toThrow( - 'UnknownCharacterEquals' + test('using = for equality', () => { + expect(() => parse('if a = "value"')).toThrow( + 'UnexpectedEqualsForEquality' + ) + }) + test('using = for equality in assignment', () => { + expect(() => parse('set a to x = "value"')).toThrow( + 'UnexpectedEqualsForEquality' ) }) }) diff --git a/yarn.lock b/yarn.lock index 119b9b32ce..bd083614ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7713,6 +7713,11 @@ marked@^0.4: resolved "https://registry.yarnpkg.com/marked/-/marked-0.4.0.tgz#9ad2c2a7a1791f10a852e0112f77b571dce10c66" integrity sha512-tMsdNBgOsrUophCAFQl0XPe6Zqk/uy9gnue+jIIKhykO51hxyu6uNx7zBPy0+y/WKYVZZMspV9YeXLNdKk+iYw== +marked@^15.0.6: + version "15.0.6" + resolved "https://registry.yarnpkg.com/marked/-/marked-15.0.6.tgz#8165f16afb6f4b30a35bdcee657c3b8415820a8f" + integrity sha512-Y07CUOE+HQXbVDCGl3LXggqJDbXDP2pArc2C1N1RRMN0ONiShoSsIInMd5Gsxupe7fKLpgimTV+HOJ9r7bA+pg== + marked@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3"