Nimbin[12]?SDL & Graphics / sdl_runtime_compiler / src/levels/level/tutorial.cpp

sdl_runtime_compiler git · main

SDL3 game for running and compiling code at runtime

sdl3 c++ compiler dlopen cmake · first commit 2026-04-19 · last commit 2026-07-03 (3 months ago) · synced 3 days ago · upstream: git.ide3.de/hsnr/sdl-runtime-compiler

C++ 72.3% C 26.2%
git clone https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler.gitwget https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler/archive/sdl_runtime_compiler.tar.gz
src/levels/level/tutorial.cpp 13 KB · 334 lines raw
#include "entities/world.hpp"
#include "levels/level.hpp"
#include "utils/config.hpp"

namespace
Nimbin
{

Level::Data
Level::build_Tutorial()
{
    return {
        .steps = {

            {
                .grants  = {},
                .scene   = Scene::Data{
                    .ai_seeds  = {},
                    .plates = {
                        { .tag = "", .pos = { 8.6, World::get_FloorY<double>() + 2.2, 0.0 } },
                    },
                    .platforms = {
                        { .pos = { 5.0, World::get_FloorY<double>() + 0.4, 0.0 }, .half_extents = { 0.9, 0.2, 0.9 } },
                        { .pos = { 6.8, World::get_FloorY<double>() + 1.1, 0.0 }, .half_extents = { 0.9, 0.2, 0.9 } },
                        { .pos = { 8.6, World::get_FloorY<double>() + 1.8, 0.0 }, .half_extents = { 0.9, 0.2, 0.9 } },
                    },
                    .words = {},
                },
                .goal    = Goal::ReachPlate{ "" },
                .hint    = "Jump on top of the green hovering plate",
                .details = R"(Press SPACE to jump.

Use [wasd] to walk.
Move the mouse to turn.

Jump with SPACE.

Jump on the plate on top of the platform. )",
                .reset_carry  = true,
                .max_ai_words = 0,
            },

            {
                .grants  = {},
                .scene   = Scene::Data{
                    .ai_seeds  = {},
                    .plates = {
                        { .tag = "", .pos = { 0.0, World::get_FloorY<double>() + 1.2, 6.0 } },
                    },
                    .platforms = {
                        { .pos = { 0.0, World::get_FloorY<double>() + 0.6, 6.0 }, .half_extents = { 1.0, 0.4, 1.0 } },
                    },
                    .words  = {},
                },
                .goal         = Goal::ReachPlate{ "" },
                .hint    = "Look at each wall, then jump onto the plate",
                .details = R"(Move the mouse to look around.

Four walls surround you:
  - Editor:   where you build code (the scroll and clear pults face it)
  - Compiler: shows compile messages
  - Program:  shows your program's output
  - Info:     this wall, with hints
)",
                .reset_carry  = true,
                .max_ai_words = 0,
            },

            {
                .grants  = {},
                .scene   = Scene::Data{
                    .ai_seeds  = {},
                    .plates    = {},
                    .platforms = {},
                    .words = {
                        { .text = "hello", .pos = { 5.0, World::get_FloorY<double>() + 1.5, 0.0 } },
                    },
                },
                .goal    = Goal::CollectWord{ "hello" },
                .hint    = "Walk to the floating word and press 'e'",
                .details = R"(Walk next to "hello" and press 'e' to pick it up.
Picked up words will align in front of you.

> hold SHIFT to free-look without turning your body.")
)",
                .reset_carry  = true,
                .max_ai_words = 0,
            },

            {
                .grants  = {},
                .scene   = Scene::Data{
                    .ai_seeds  = {},
                    .plates    = {},
                    .platforms = {},
                    .words = {
                        { .text = "keep"    , .pos = {  5.0, World::get_FloorY<double>() + 1.5, -3.0 } },
                        { .text = "e"       , .pos = {  0.0, World::get_FloorY<double>() + 1.5, -3.0 } },
                        { .text = "pressed" , .pos = { -5.0, World::get_FloorY<double>() + 1.5, -3.0 } },
                    },
                },
                .goal = Goal::AllOf{{
                    Goal::CollectWord{ "keep" },
                    Goal::CollectWord{ "e"    },
                    Goal::CollectWord{ "pressed" },
                }},
                .hint    = "Keep 'e' pressed to pick up all words.",
                .details = R"(You can carry several words at once.

Collect "press", "any" and "key" with 'e'.
They queue up in the order you grab them.)",
                .reset_carry  = false,
                .max_ai_words = 0,
            },

            {
                .grants  = {},
                .scene   = {},
                .goal    = Goal::DroppedWord{},
                .hint    = "Press 'q' to drop a word",
                .details = R"('q' drops the most recently picked word.

The dropped word falls to the floor — handy for
putting back something you grabbed by mistake.)",
                .reset_carry  = false,
                .max_ai_words = 0,
            },

            {
                .grants  = {},
                .scene   = {
                    .words = {
                        {  .text = "keep"   , .pos = {  5.0, World::get_FloorY<double>() + 1.5, -3.0 }, },
                        {  .text = "left"   , .pos = {  3.0, World::get_FloorY<double>() + 1.5, -3.0 }, },
                        {  .text = "click"  , .pos = {  0.0, World::get_FloorY<double>() + 1.5, -3.0 }, },
                        {  .text = "pressed", .pos = {  2.0, World::get_FloorY<double>() + 1.5, -3.0 }, },
                    },
                },
                .goal    = Goal::ShotWord{},
                .hint    = "LEFT CLICK to throw a word (you can keep it pressed)",
                .details = R"(Left click throws the word at the front of your line.

Aim with the crosshair and throw — any direction is fine.

Carrying several? Tap F to throw the WHOLE line at once.)",
                .reset_carry  = false,
                .max_ai_words = 0,
            },
            {
                .grants  = {},
                .scene   = {
                    .words = {
                        { .text = "f"    , .pos = {  1.0, World::get_FloorY<double>() + 1.5, -3.0 }, },
                        { .text = "for"  , .pos = {  2.0, World::get_FloorY<double>() + 1.5, -2.0 }, },
                        { .text = "flush", .pos = {  0.0, World::get_FloorY<double>() + 1.5, -3.0 }, },
                    },
                },
                .goal    = Goal::ShotWord{},
                .hint    = "Press 'f' to flush all carried words",
                .details = R"(Bla blub
...)",
                .reset_carry  = false,
                .max_ai_words = 0,
            },

            {
                .grants  = {},
                .scene   = Scene::Data{
                    .ai_seeds  = {
                        {
                            .text           = "danger",
                            .pos            = { -6.0, World::get_FloorY<double>() + 2.0, -6.0 },
                            .initial_state  = Text::Token::Ai::State::Attack,
                            .aggressiveness = 1.0,
                        },
                    },
                    .plates    = {},
                    .platforms = {},
                    .words = {
                        { .text = "ammo", .pos = { 6.0, World::get_FloorY<double>() + 1.5, 6.0 }, },
                        { .text = "ammo", .pos = { 3.0, World::get_FloorY<double>() + 1.5, 2.0 }, },
                        { .text = "ammo", .pos = { 6.0, World::get_FloorY<double>() + 1.5, 3.0 }, },
                        { .text = "ammo", .pos = { 4.0, World::get_FloorY<double>() + 1.5, 6.0 }, },
                        { .text = "ammo", .pos = { 6.0, World::get_FloorY<double>() + 1.5, 1.0 }, },
                    },
                },
                .goal    = Goal::AllOf{{ Goal::CollectWord{ "ammo" }, Goal::AiWordDefeated{ "danger" } }},
                .hint    = "Grab the ammo, then shoot the red word",
                .details = R"(Some words turn red and chase you to steal what
you carry.

Pick up "ammo", then knock out the red "danger"
word by throwing at it with LEFT CLICK before it
reaches you.)",
                .reset_carry = true,
                .max_ai_words = 0,
            },

            {
                .grants = {
                    Level::TokenGrant::give("int"),
                    Level::TokenGrant::give("main"),
                    Level::TokenGrant::give("("),
                    Level::TokenGrant::give("void"),
                    Level::TokenGrant::give(")"),
                    Level::TokenGrant::give("{"),
                    Level::TokenGrant::give("return"),
                    Level::TokenGrant::give("0"),
                    Level::TokenGrant::give(";"),
                    Level::TokenGrant::give("}"),
                },
                .scene   = {},
                .goal    = Goal::CompilePattern{ "int main ( void ) { return 0 ; }" },
                .hint    = "Build int main(void) { return 0; } and press BUILD",
                .details = R"(Now make code.

Face the Editor wall (the pults point at it). Throw
tokens at the wall to place them:
  - LEFT CLICK throws one word
  - F throws your whole carried line at once

Words snap into rows. Aim between two words to drop
a token between them. Mis-placed a token? Walk up and
press E to pluck it back off the wall.

Build exactly:
  int main(void) { return 0; }

Then walk to the Compile pult (labelled BUILD) and
press it (or press 'c' to compile).)",
                .reset_carry  = true,
                .max_ai_words = 0,
            },

            {
                .grants = {
                    Level::TokenGrant::give("#include"),
                    Level::TokenGrant::give("<stdio.h>"),
                    Level::TokenGrant::give("printf"),
                    Level::TokenGrant::give("("),
                    Level::TokenGrant::give("\"hello\""),
                    Level::TokenGrant::give(")"),
                    Level::TokenGrant::give(";"),
                },
                .scene = {},
                .goal  = Goal::AllOf{{
                    Goal::CompilePattern{ "int main ( void ) { printf ( \"hello\" ) ; return 0 ; }" },
                    Goal::PressButton{ PultAction::RunProgram },
                }},
                .hint    = "Add printf, BUILD, then press RUN",
                .details = R"(A program that does nothing is dull. Make it print.

Add a printf line inside main, before return:
  printf("hello");

So the whole thing reads:
  int main(void) { printf("hello"); return 0; }

Press BUILD to compile, then walk to the RUN pult and
press it. Your program runs and its output appears on
the Program wall — and that's what finishes this step.)",
                .reset_carry  = false,
                .max_ai_words = 0,
            },

            {
                .grants  = {
                    Level::TokenGrant::give("scanf(\"%d\", &x)"),
                    Level::TokenGrant::give("int"),
                    Level::TokenGrant::give("x"),
                    Level::TokenGrant::give("="),
                    Level::TokenGrant::give("0"),
                    Level::TokenGrant::give(";"),
                    Level::TokenGrant::give(";"),
                    Level::TokenGrant::give(";"),
                    Level::TokenGrant::give("\"Your Number is : %d\""),
                    Level::TokenGrant::give(","),
                    Level::TokenGrant::give("x"),
                    Level::TokenGrant::give("42"),
                    Level::TokenGrant::give("printf(\"Enter a Number:\\n\\t\")"),
                },
                .scene   = Scene::Data{
                    .ai_seeds  = {},
                    .plates = {
                        { .tag = "", .pos = { 0.0, World::get_FloorY<double>() + 1.2, -6.0 }, },
                    },
                    .platforms = {
                        { .pos = { 0.0, World::get_FloorY<double>() + 0.6, -6.0 }, .half_extents = { 1.0, 0.4, 1.0 } },
                    },
                    .words  = {},
                },
                .goal    = Goal::ReachPlate{ "" },
                .hint    = "Try the editor pults, then jump onto the plate",
                .details = R"(A few tools for managing the Editor wall:

  - The UP / DOWN pults scroll the code.
  - Each line is numbered on both sides. Shoot a line
    number to scroll that line to your eye level.
  - The CLEAR pult wipes the wall.
  - Press E on a placed word to pull it back off.

Have a play, then jump onto the plate to continue.)",
                .reset_carry  = false,
                .max_ai_words = 0,
            },

            {
                .grants  = {},
                .scene   = Scene::Data{
                    .ai_seeds = {},
                    .plates = {
                        { .tag = "", .pos = { 0.0, World::get_FloorY<double>() + 1.2, 0.0 }, },
                    },
                    .platforms = {
                        { .pos = { 0.0, World::get_FloorY<double>() + 0.6, 0.0 }, .half_extents = { 1.0, 0.4, 1.0 } },
                    },
                    .words  = {},
                },
                .goal    = Goal::ReachPlate{ "" },
                .hint    = "Jump onto the plate to begin \"Char Copy\"",
                .details = R"(You're ready.

Jump onto the plate to start the first real level:
"Char Copy".)",
                .reset_carry  = true,
                .max_ai_words = 0,
            },
        },
        .name = "Tutorial",
        .desc = "Learn the controls and the code-building loop.",
    };
}

} // namespace Nimbin