JSFiddle - React, Tailwind, and code Playground

by Gwyn Milcote

HTML

<div id='cyoa_container'>
 
    <div id='cyoa_topbar'>
        <div><i>Scene #<span id='scene_id'></span></i></div>
        <div><b>CYOA Example Game</b></div>
        <div id='cyoa_restart'><i>Restart?</i></div>
    </div>
    
    <hr>
    
    <div id='scene_image'></div>
    <div id='scene_text'></div>
    <div id='scene_choices'></div>
    
    <hr>
    
    <div id='scene_stats'>
        <div>
            <b>Name:</b> <span id='stat_name'></span>
        </div>
        <div>
            <b>Role:</b> <span id='stat_role'></span>
        </div>
        <div>
            <b>Colour:</b> <span id='stat_colour'></span>
        </div>
        <div>
            <b>Strength:</b> <span id='stat_strength'></span>
        </div>
        <div>
            <b>Flight:</b> <span id='stat_flight'></span>
        </div>
        <div>
            <b>Magic:</b> <span id='stat_magic'></span>
        </div>
    </div>
    
</div>

<p>Simple CYOA game, in vanilla JS. The <b>db</b> object could be an actual database (add AJAX requests to load from server) or included as own .js file, for each game. The <b>CYOA</b> class contains the logic, and can be shared across many games.</p>
<p><b>Tip:</b> use a free flowchart tool like <a href='https://app.diagrams.net/'>this</a> to map out scenes, where each choice goes, and which stats get checked or updated between scenes. Or you might find it hard to keep track of ID numbers!</p>

<hr>

<p>A <b>scene</b> must have some text, and at least 1 choice leading away from it. Optionally, you can set an URL for an image to display.</p>

<p>The only exceptions are 'game over' scenes. They don't need any choices.</p>

<ul>
    <li><b>id</b> - The ID number, for looking up this scene.</li>
    <li><b>text</b> - The text that player sees.</li>
    <li><b>image</b> - (optional) URL of an image to display in the #scene_image div.</li>
    <li><b>result</b> - (optional) What to do with the value that was passed here from the previous choice. See...

CSS

div, img, span, button, hr {
    box-sizing: border-box;
}

/* Variables */
:root {
    --gold_vdark: #635B4D;
    --gold_dark: #BAA47D;
    --gold_medium: #DEC497;
    --gold_light: #F5E8D2;
}

#cyoa_container {
    background-color: var(--gold_light);
    padding: 1.5em;
}

#cyoa_topbar {
    display: flex;
    justify-content: space-between;
}

#cyoa_restart {
    cursor: pointer;
}

#cyoa_container hr {
    border: 1px solid var(--gold_dark);
    margin: 20px auto;
}

#scene_image {
    display: none;
    border: 1px solid var(--gold_dark);
    border-radius: 2px;
    background-size: cover;
    background-position: center;
    margin: 20px auto;
}

#scene_text {
    margin: 20px auto;
}

#scene_choices .choice {
    padding: 5px 10px;
    border: 1px solid var(--gold_dark);
    background-color: var(--gold_medium);
    transition: 0.2s;
    margin: 5px auto;
    border-radius: 2px;
}

#scene_choices .choice:hover {
    cursor: pointer;
    background-color: white;
    transition: 0.2s;
}

#scene_stats {
    display: flex;
    flex-flow: row wrap;
    justify-content: space-between;
}
#scene_stats div {
    width: calc(33.33% - 5px);
    margin: 5px 0px;
    border: 1px solid var(--gold_dark);
    background-color: var(--gold_medium);
    padding: 5px 10px;
    border-radius: 2px;
}

JavaScript

let db = {
    
    scenes: [
        {
            id: 1,
            text: "You're a griffin. What colour are your feathers?"
        },{
            id: 2,
            text: "Of course! Your feathers are [RESULT]. Can you fly?",
            result: {stat: "colour", type: "set"},
        },{
            id: 3,
            text: "[RESULT] Now, how about magic?",
            result: {stat: "flight", type: "set"},
        },{
            id: 4,
            text: "[RESULT] So that's your magic skill.",
            result: {stat: "magic", type: "set"}
        },{
            id: 5,
            text: "Magic isn't everyone's cup of tea. It's fine. Did you know that a lack of magic makes you very physically strong instead?",
            effects: [
                {stat: "magic", type: "set", value: 3},
                {stat: "strength", type: "set", value: 15}
            ]
        },{
            id: 6,
            text: "Let's decide your role. What do you do in your flock?",
        },{
            id: 7,
            text: "Oh dear, a solitary griffin! That's unusual. May I ask the reason...?",
            result: {stat: "role", type: "set"}
        },{
            id: 8,
            text: "[RESULT]<br>Let's move on.",
        },{
            id: 9,
            text: "I see! [RESULT] Very cool!<p>This scene also gives you +5 flight and +2 magic, just because. Did you notice that in the stats below?</p>Now let's try a status check. You'll go to scene 10 if successful, or scene 11 if it fails.",
            result: {stat: "role", type: "set"},
            effects: [
                {stat: "magic", type: "increase", value: 2},
                {stat: "flight", type: "increase", value: 5}
            ]
        },{
            id: 10,
            text: "You have at least 10 strength! Yay! You might have slayed a dragon or something."
        },{
            id: 11,
            text: "Oh dear, your strength is less than 10. Maybe a dragon is now eating you?"
        },{
  ...