javascriptfunctionscopenestedthis

Is there a way to use a function which references an object that in turn references that function?


I'm working on a practice game that uses an array of objects (plants) that will be chosen randomly. Questions are asked about light and water requirements, winning or losing the plant and moving onto the next based on user answers.

My problem is that I can't use a function that hasn't been initialized, but I can't initialize the function without initializing the plant objects containing the functions first.

I tried putting the plants array inside the newPlant function and use "this" to reference the function within the plant objects but came up with undefined errors. Any help is appreciated!

Repo link: https://github.com/ChristinaBohn/botany-game

Code in js file so far:

let xp = 0;
let coins = 30;
let plantHealth = 100;
let collection = [];

// Player controls
const button1 = document.querySelector('#button1');
const button2 = document.querySelector('#button2');
const button3 = document.querySelector('#button3');

// Plant controls
const button4 = document.querySelector('#button4');
const button5 = document.querySelector('#button5');

// Text
const text = document.querySelector('#text');
const xpText = document.querySelector('#xpText');
const coinText = document.querySelector('#coinText');

// Plant collection
const plantTiles = document.querySelector('#plantTiles');
const plantName = document.querySelector('#plantName');
const healthText = document.querySelector('#healthText');

const plants = [
    {
        id: 0,
        name: "Snake Plant (easy care)",
        light: {
            "button text": ["Place plant in low light", "Place plant in medium light", "Place plant in bright light"],
            "button functions": [newPlant.askLight, newPlant.askLight, newPlant.askLight],
            text: "You have received a Snake Plant (easy care)! Where on your plant shelf will you place your plant?"
        },
        water: {
            "button text": ["Don't water at all", "Water a little", "Water a lot"],
            "button functions": [addPlant, addPlant, addPlant],
            text: "Good job! Snake plants are happy in any light. How much water do you want to give your plant?"
        },
        clippingCost: 5
    },
    {
        id: 1,
        name: "Hoya (medium care)",
        light: {
            "button text": ["Place plant in low light", "Place plant in medium light", "Place plant in bright light"],
            "button functions": [losePlant, newPlant.askWater, newPlant.askWater],
            text: "You have received a Hoya (medium care)! Where on your plant shelf will you place your plant?"
        },
        water: {
            "button text": ["Don't water at all", "Water a little", "Water a lot"],
            "button functions": [losePlant, addPlant, losePlant],
            text: "Good job! Hoyas are happy in medium to bright light. How much water do you want to give your plant?"
        },
        clippingCost: 10
    },
    {
        id: 2,
        name: "Calathea (difficult care)",
        light: {
            "button text": ["Place plant in low light", "Place plant in medium light", "Place plant in bright light"],
            "button functions": [losePlant, newPlant.askWater, losePlant],
            text: "You have received a Clathea (difficult care)! Where on your plant shelf will you place your plant?"
        },
        water: {
            "button text": ["Don't water at all", "Water a little", "Water a lot"],
            "button functions": [losePlant, addPlant, losePlant],
            text: "Good job! Calatheas are happy in medium light only. How much water do you want to give your plant?"
        },
        clippingCost: 15
    }
];

let plantShop = [...plants];

const locations = [
    {
        name: "welcome",
        "button text": ["Begin!", "Begin!", "Begin!"],
        "button functions": [newPlant.askLight, newPlant.askLight, newPlant.askLight],
        text: "Welcome to Botany Bliss. Take care of each new plant based on your plant knowledge and watch your plant collection grow!"
    },
    {
        name: "lose plant",
        "button text": ["Try again", "Try again", "Try again"],
        "button functions": [newPlant.askLight, newPlant.askLight, newPlant.askLight],
        text: "Oh no, your new plant didn't like that! You've lost this plant. Try again?"
    },
    {
        name: "lose game",
        "button text": ["Start over?", "Start over?", "Start over?"],
        "button functions": [welcome, welcome, welcome],
        text: "Oh no, your new plant didn't like that and you have no remaining plants in your collection! Game over. Would you like to play again?"
    },
    {
        name: "win game",
        "button text": ["Start over?", "Start over?", "Start over?"],
        "button functions": [welcome, welcome, welcome],
        text: "Congratulations, you have every available plant in your home collection! Would you like to play again?"
    }
];

// Use same random plant for one iteration each of askLight and askWater
function useRandomIndex() {
    let plantIndex = Math.floor(Math.random() * 3);
    let currentPlant = plants[plantIndex];
        
    function askLight() {
        button1.innerText = currentPlant["light"]["button text"][0];
        button2.innerText = currentPlant["light"]["button text"][1];
        button3.innerText = currentPlant["light"]["button text"][2];
        button1.onclick = currentPlant["light"]["button functions"][0];
        button2.onclick = currentPlant["light"]["button functions"][1];
        button3.onclick = currentPlant["light"]["button functions"][2];
        text.innerHTML = currentPlant.light.text;
    };

    function askWater() {
        button1.innerText = currentPlant["water"]["button text"][0];
        button2.innerText = currentPlant["water"]["button text"][1];
        button3.innerText = currentPlant["water"]["button text"][2];
        button1.onclick = currentPlant["water"]["button functions"][0];
        button2.onclick = currentPlant["water"]["button functions"][1];
        button3.onclick = currentPlant["water"]["button functions"][2];
        text.innerHTML = currentPlant.water.text;
    };

    return { askLight, askWater };
};

const newPlant = useRandomIndex();

// Initialize buttons
button1.onclick = newPlant.askLight;
button2.onclick = newPlant.askLight;
button3.onclick = newPlant.askLight;


function update(location) {
    button1.innerText = location["button text"][0];
    button2.innerText = location["button text"][1];
    button3.innerText = location["button text"][2];
    button1.onclick = location["button functions"][0];
    button2.onclick = location["button functions"][1];
    button3.onclick = location["button functions"][2];
    text.innerHTML = location.text;
}

function welcome() {
    xp = 0;
    coins = 50;
    xpText.innerText = xp;
    coinText.innerText = coins;
    collection = [""];
    plantShop = [...plants];
    update(locations[0]);
    newPlant.askLight();
}

function addPlant() {
    alert("Congratulations! Your plant is happy and thriving. It has been added to your collection.")
    update(locations[2]);
    xp += 5;
    xpText.innerText = xp;
};

function losePlant() {
    update(locations[1])
    xp -= 5;
    xpText.innerText = xp;
};

function loseGame() {
    update(locations[2]);
};

welcome();

Solution

  • Well of course you can't refer to an undefined function or variable. But you can refer to it as string, later to retrieve the item (function) by that key. For example, obj["my-function"] = function () {} where the key is "my-function".

    I made this change for your code from the Repo.

    let xp = 0;
    let coins = 30;
    let plantHealth = 100;
    let collection = [];
    const functions = {}
    
    
    
    // Player controls
    const button1 = document.querySelector('#button1');
    const button2 = document.querySelector('#button2');
    const button3 = document.querySelector('#button3');
    
    // Plant controls
    const button4 = document.querySelector('#button4');
    const button5 = document.querySelector('#button5');
    
    // Text
    const text = document.querySelector('#text');
    const xpText = document.querySelector('#xpText');
    const coinText = document.querySelector('#coinText');
    
    // Plant collection
    const plantTiles = document.querySelector('#plantTiles');
    const plantName = document.querySelector('#plantName');
    const healthText = document.querySelector('#healthText');
    
    const plants = [{
        id: 0,
        name: "Snake Plant (easy care)",
        light: {
          "button text": ["Place plant in low light", "Place plant in medium light", "Place plant in bright light"],
          "button functions": ['newPlant.askLight', 'newPlant.askLight', 'newPlant.askLight'],
          text: "You have received a Snake Plant (easy care)! Where on your plant shelf will you place your plant?"
        },
        water: {
          "button text": ["Don't water at all", "Water a little", "Water a lot"],
          "button functions": ['addPlant', 'addPlant', 'addPlant'],
          text: "Good job! Snake plants are happy in any light. How much water do you want to give your plant?"
        },
        clippingCost: 5
      },
      {
        id: 1,
        name: "Hoya (medium care)",
        light: {
          "button text": ["Place plant in low light", "Place plant in medium light", "Place plant in bright light"],
          "button functions": ['losePlant', 'newPlant.askWater', 'newPlant.askWater'],
          text: "You have received a Hoya (medium care)! Where on your plant shelf will you place your plant?"
        },
        water: {
          "button text": ["Don't water at all", "Water a little", "Water a lot"],
          "button functions": ['losePlant', 'addPlant', 'losePlant'],
          text: "Good job! Hoyas are happy in medium to bright light. How much water do you want to give your plant?"
        },
        clippingCost: 10
      },
      {
        id: 2,
        name: "Calathea (difficult care)",
        light: {
          "button text": ["Place plant in low light", "Place plant in medium light", "Place plant in bright light"],
          "button functions": ['losePlant', 'newPlant.askWater', 'losePlant'],
          text: "You have received a Clathea (difficult care)! Where on your plant shelf will you place your plant?"
        },
        water: {
          "button text": ["Don't water at all", "Water a little", "Water a lot"],
          "button functions": ['losePlant', 'addPlant', 'losePlant'],
          text: "Good job! Calatheas are happy in medium light only. How much water do you want to give your plant?"
        },
        clippingCost: 15
      }
    ];
    
    let plantShop = [...plants];
    
    const locations = [{
        name: "welcome",
        "button text": ["Begin!", "Begin!", "Begin!"],
        "button functions": ['newPlant.askLight', 'newPlant.askLight', 'newPlant.askLight'],
        text: "Welcome to Botany Bliss. Take care of each new plant based on your plant knowledge and watch your plant collection grow!"
      },
      {
        name: "lose plant",
        "button text": ["Try again", "Try again", "Try again"],
        "button functions": ['newPlant.askLight', 'newPlant.askLight', 'newPlant.askLight'],
        text: "Oh no, your new plant didn't like that! You've lost this plant. Try again?"
      },
      {
        name: "lose game",
        "button text": ["Start over?", "Start over?", "Start over?"],
        "button functions": ['welcome', 'welcome', 'welcome'],
        text: "Oh no, your new plant didn't like that and you have no remaining plants in your collection! Game over. Would you like to play again?"
      },
      {
        name: "win game",
        "button text": ["Start over?", "Start over?", "Start over?"],
        "button functions": ['welcome', 'welcome', 'welcome'],
        text: "Congratulations, you have every available plant in your home collection! Look at those green thumbs. Would you like to play again?"
      }
    ];
    
    // Use same random plant for one iteration each of askLight and askWater
    function useRandomIndex() {
      let plantIndex = Math.floor(Math.random() * 3);
      let currentPlant = plants[plantIndex];
    
      function askLight() {
        button1.innerText = currentPlant["light"]["button text"][0];
        button2.innerText = currentPlant["light"]["button text"][1];
        button3.innerText = currentPlant["light"]["button text"][2];
        button1.onclick = functions[currentPlant["light"]["button functions"][0]];
        button2.onclick = functions[currentPlant["light"]["button functions"][1]];
        button3.onclick = functions[currentPlant["light"]["button functions"][2]];
        text.innerHTML = currentPlant.light.text;
      };
    
      function askWater() {
        button1.innerText = currentPlant["water"]["button text"][0];
        button2.innerText = currentPlant["water"]["button text"][1];
        button3.innerText = currentPlant["water"]["button text"][2];
        button1.onclick = functions[currentPlant["water"]["button functions"][0]];
        button2.onclick = functions[currentPlant["water"]["button functions"][1]];
        button3.onclick = functions[currentPlant["water"]["button functions"][2]];
        text.innerHTML = currentPlant.water.text;
      };
    
      return {
        askLight,
        askWater
      };
    };
    
    const newPlant = useRandomIndex();
    
    // Initialize buttons
    button1.onclick = newPlant.askLight;
    button2.onclick = newPlant.askLight;
    button3.onclick = newPlant.askLight;
    
    
    function update(location) {
      button1.innerText = location["button text"][0];
      button2.innerText = location["button text"][1];
      button3.innerText = location["button text"][2];
      button1.onclick = functions[location["button functions"][0]];
      button2.onclick = functions[location["button functions"][1]];
      button3.onclick = functions[location["button functions"][2]];
      text.innerHTML = location.text;
    }
    
    function welcome() {
      xp = 0;
      coins = 50;
      xpText.innerText = xp;
      coinText.innerText = coins;
      collection = [""];
      plantShop = [...plants];
      update(locations[0]);
      newPlant.askLight();
    }
    
    function addPlant() {
      alert("Congratulations! Your plant is happy and thriving. It has been added to your collection.")
      update(locations[2]);
      xp += 5;
      xpText.innerText = xp;
    };
    
    function losePlant() {
      update(locations[1])
      xp -= 5;
      xpText.innerText = xp;
    };
    
    function loseGame() {
      update(locations[2]);
    };
    
    
    functions['newPlant.askLight'] = newPlant.askLight;
    functions['update'] = update
    functions['welcome'] = welcome
    functions['addPlant'] = addPlant
    functions['losePlant'] = losePlant
    functions['loseGame'] = loseGame
    
    welcome();
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
      font-size: 16px;
    }
    
    body {
      display: flex;
      justify-content: center;
      align-items: center;
      width: 100vw;
      height: 100vh;
      background-color: #98C9A3;
    }
    
    .intro {
      text-align: center;
      padding: 2rem;
    }
    
    h1 {
      font-family: "Monoton";
      font-size: 4rem;
      font-weight: 200;
      line-height: 4rem;
      color: white;
    }
    
    .intro p {
      font-family: "Shadows Into Light";
      font-size: 1.5rem;
      color: #023436;
      padding-top: 1rem;
    }
    
    #text {
      font-family: "Shadows Into Light";
      font-size: 1.4rem;
      color: #023436;
    }
    
    .game {
      margin: 0 auto;
      width: 80%;
      max-width: 850px;
      padding: 40px;
      border: 8px solid #BFD8BD;
      border-radius: 20px;
      background-color: #DDE7C7;
      box-shadow: 5px 10px 10px #77BFA3;
    }
    
    .controls button {
      display: block;
      padding: 10px;
      border-radius: 50px;
      margin: 10px auto;
      background-color: #BFD8BD;
      border: 4px solid #edeec9;
      box-shadow: 2px 2px #abc1a9,
        inset 2px 2px #abc1a9;
      font-family: "Ubuntu";
      color: #023436;
    }
    
    .controls button:hover {
      background-color: #77BFA3;
    }
    
    .controls button:active {
      box-shadow: 1px 1px #abc1a9,
        inset 3px 3px #788777;
      background-color: #6bad93;
    }
    
    /* Hide plant tiles */
    #plantTiles {
      display: none;
    }
    
    .plantControls button {
      padding: 6px;
      border-radius: 20px;
    }
    
    .stats,
    .plantStats {
      padding: 10px;
      color: #483d3f;
      font-family: "Ubuntu";
    }
    
    #text {
      padding: 10px;
    }
    
    /* Github Icon */
    footer {
      display: flex;
      justify-content: center;
      padding-top: 40px;
    }
    
    footer p {
      font-family: "Shadows Into Light";
      color: white;
    }
    
    footer i {
      padding: 10px;
      transition: 0.5s;
    }
    
    footer i:hover {
      transform: scale(1.35);
    }
    
    #gh-link {
      display: flex;
      align-items: center;
    }
    
    /* Medium Screens or Larger */
    @media (min-width: 760px) {
      h1 {
        font-size: 4.8rem;
      }
    
      .intro p {
        font-size: 1.8rem;
      }
    
      #text {
        padding: 20px;
      }
    
      .controls {
        display: flex;
        justify-content: space-around;
      }
    
      .controls button {
        margin: 5px;
      }
    }
    <!DOCTYPE html>
    <hmtl lang="en">
    
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Botany Bliss</title>
        <link rel="icon" type="image/x-icon" href="./assets/images/fern.png">
        <!-- Google Fonts -->
        <link rel="preconnect" href="https://fonts.googleapis.com">
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
        <link href="https://fonts.googleapis.com/css2?family=Monoton&family=Shadows+Into+Light&family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&family=Varela+Round&display=swap" rel="stylesheet">
        <!-- Github Icon -->
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
      </head>
    
      <body>
    
        <!-- Intro -->
        <div class="intro">
          <h1>Botany Bliss</h1>
          <p>Try out your green thumb without killing plants at home!</p>
        </div>
    
        <div class="game">
    
          <!-- Player Stats -->
          <div class="stats">
            <span class="stat">XP: <strong><span id="xpText">0</span></strong></span>
            <span class="stat">Coins: <strong><span id="coinText">50</span></strong></span>
          </div>
    
          <!-- Text -->
          <div id="text">
            Welcome to Botany Bliss! You've just been gifted a Hoya (care level: medium). You bring the plant home, what is your next move?
          </div>
    
          <!-- Control Buttons -->
          <div class="controls">
            <button id="button1">Place plant in low light</button>
            <button id="button2">Place plant in medium light</button>
            <button id="button3">Place plant in bright light</button>
          </div>
    
          <!-- Plant Tiles -->
          <div id="plantTiles">
    
            <div>
              <!-- Plant Stats TODO: append each plant in inventory with buttons below, text changes color on health, use insert adjacent html? -->
              <div class="plantStats">
                <span class="stat">Plant Name: <strong><span id="plantName"></span></strong></span>
                <span class="stat">Health: <strong><span id="healthText"></span></strong></span>
              </div>
    
              <!-- Plant Image -->
    
    
              <!-- Plant Buttons TODO: Place plants in 3 different locations visually on page for low, medium, and bright light or display brightness -->
              <div class="plantControls">
                <button id="button4">Water plant</button>
                <button id="button5">Sell clippings</button>
              </div>
            </div>
          </div>
        </div>
    
        <!-- Github Link -->
        <footer>
          <div id="gh-link">
            <p>This game was created by me:</p>
            <a href="https://github.com/ChristinaBohn" target="_blank"><i class="fa fa-github" style="font-size:48px;color:white"></i></a>
          </div>
        </footer>
    
    
      </body>
    </hmtl>