Completing The Order

📝 We continue in the Speedy Chef project folder throughout this section.

What we will be doing

When we have finished all the pizzas on an order, it would be a good idea to complete the order so we can move onto the next one.

Check a pizza is selected

Just before this, I want to fix a small issue. The issue is that we can start a game, click on an order, and we can add the ingredients before we even select a pizza. To fix this, all we need to do is check if the current pizza section is set when we click on any ingredient.

The place to do this is inside of the stepCompleted function:

function stepCompleted(e) {
  // 1.Check if a pizza has been selected to work on
  if (document.querySelector('#current_pizza').innerText === '') {
    // 2. Use existing error function to display message
    showErrorMessage('First, select a pizza you would like to work on...');
    // 3. return from function so code stops running
    return;
  }
  e.target.setAttribute('disabled', true);
  const stepName = e.target.innerText;
  completedSteps.push(stepName);
  makePizza(stepName);
}

Now onto completing the order.

Check player has completed enough pizzas for the order

Before we let the player complete it, it would be good to check they have created enough pizzas to complete the order.

So, step 1 will be to store the number of pizzas on each order. There are many ways we could approach this. One way is to store it as an attribute when we create the order.

In the createListOfPizzas function, we already have access to the number of pizzas for each order:

function createListOfPizzas(pizzas) {
  const pizzaList = document.createElement('ul');
  pizzas.forEach(function (pizza) {
    // quantity available in pizza.quantity:
    const orderQuantityEl = buildElement("span", `${pizza.quantity} - `);
    // ...
  });
  return pizzaList;
}

Now we can store and update the quantity of each order line to a variable:

// 1. initialise variable outside function:
let totalPizzas = 0;

function createListOfPizzas(pizzas) {
    // 3. reset for each order
    totalPizzas = 0;
    const pizzaList = document.createElement("ul");
    pizzas.forEach(function (pizza) {
        const orderQuantityEl = buildElement("span", `${pizza.quantity} - `);
        const pizzaNameElement = buildElement("span", pizza.name);
        // 2. update variable
        totalPizzas += pizza.quantity;
        pizzaNameElement.classList.add("pizza_name");
        const pizzaItem = document.createElement("li");
        pizzaItem.append(orderQuantityEl, pizzaNameElement);
        pizzaList.appendChild(pizzaItem);
    });
    return pizzaList;
}

Using += will add to the existing total for each pizza on our order. For example, order line 1 could have 3 pizzas, and line 2 could have 2 pizzas. This will result in the variable value of 5.

Setting the number of pizzas as an attribute

Then in the createSingleOrder function, add this totalPizzas variable as an attribute to the order wrapper:

function createSingleOrder(order) {
  const orderWrapper = document.createElement('div');
  orderWrapper.className = 'order_wrapper';
  orderWrapper.addEventListener('click', selectCurrentOrder);
  const orderNumberEl = buildElement('h4', `Order: ${order.id}`);
  orderWrapper.appendChild(orderNumberEl);
  const pizzaList = createListOfPizzas(order.pizzas);

  // add:
  orderWrapper.setAttribute('data-total-pizzas', totalPizzas);

  orderWrapper.appendChild(pizzaList);
  return orderWrapper;
}

HTML attributes such as class, id, and src etc are all standard attributes. When creating custom attributes as we just have, it is common to see them begin with the data- prefix.

This means we now have the number of pizzas on the order, and earlier we also created a variable to store the number we have completed:

let pizzasCompleteForOrder = 0;

If these match, we can complete the order.

Completing the order if correct

Next, we need a button to complete the order which will trigger a function. And we can set this in the selectCurrentOrder function. We do it in here since this is where we set the order we are currently working on:

function selectCurrentOrder(e) {
    const pizzas = document.querySelectorAll(".pizza_name");
    pizzas.forEach(function (pizza) {
        pizza.addEventListener("click", setCurrentPizza);
    });

    if (document.querySelector("#working_on").children.length > 1) {
        return;
    }
    let element = e.target;
    const orderWrapper = element.closest(".order_wrapper");
    if (orderWrapper !== null) {
        orderWrapper.removeEventListener("click", selectCurrentOrder);
        const orderDiv = document.querySelector("#working_on");
        orderDiv.appendChild(orderWrapper);
        // 1. create button:
        const completeButton = buildElement("button", "Complete");
        completeButton.className = "complete_btn";

        // 2. call a function we will create next
        completeButton.addEventListener("click", completeOrder);
        orderDiv.appendChild(completeButton);
    }
}

And create the function just below:

function completeOrder() {
  alert('complete');
}

Now in this new function, we need to check if enough pizzas have been made. Update the completeOrder function as follows:

function completeOrder() {

  // all orders in aside section have class of order_wrapper, so we use a
  // css selector to only get the one inside the working on section:

  const currentOrder = document.querySelector('#working_on > .order_wrapper');

  // get data-total-pizzas attribute we added earlier
  const totalPizzasOnOrder = currentOrder.getAttribute('data-total-pizzas');

  // we already have pizzasCompleteForOrder variable
  // use this toom compare with the total the player has completed
  if (pizzasCompleteForOrder < totalPizzasOnOrder) {
    showErrorMessage(
      'You have not made enough pizzas to complete this order...'
    );
    return
  }
}

You should see the error message when not enough pizzas have been made, and no message when we have made enough.

Removing complete orders

Next, we can move on to removing the order if complete. First, create a variable with the others:

let completedOrders = 0;

Then update the completeOrder function as follows:

function completeOrder(e) {
    const currentOrder = document.querySelector("#working_on > .order_wrapper");
    const totalPizzasOnOrder = currentOrder.getAttribute("data-total-pizzas");
    if (pizzasCompleteForOrder < totalPizzasOnOrder) {
        showErrorMessage(
            "You have not made enough pizzas to complete this order..."
        );
        return;
    }

    // remove from working on section
    currentOrder.remove();

    // also remove "complete button", need to pass in "e" to completeOrder function above
    let completeButton = e.target;
    completeButton.remove();

    // update a variable we have not yet created for stats
    completedOrders++;

    // reset completed pizzas
    pizzasCompleteForOrder = 0;
}

Try this out and you will now be able to complete the order if made correctly! Now the order and complete button should remove when we have made the correct number of pizzas.

There is still an issue you will notice. When we click on an order in the sidebar to work on, it re-appears when a new order generates. This is something we will resolve in the next lesson.

Completed code after this lesson

If needed, here is the completed code we added to the starter file (index.js):

let oven = [];
const ovenCapacity = 6;
let pizzasCompleteForOrder = 0;
let gameStarted = false;
const gameLength = 300;
let countdownTime = gameLength;
const cookingTime = 20;
let completedPizzas = 0;
let completedSteps = [];
let wastedPizzas = 0;
let completedOrders = 0;
document.querySelector(
    "#gameLength"
).innerText = `Game length is ${gameLength} seconds`;
document.querySelector("#endBtn").style.display = "none";

function buildElement(elementName, elementContent) {
    const element = document.createElement(elementName);
    const content = document.createTextNode(elementContent);
    element.appendChild(content);
    return element;
}

let totalPizzas = 0;

function createListOfPizzas(pizzas) {
    totalPizzas = 0;
    const pizzaList = document.createElement("ul");
    pizzas.forEach(function (pizza) {
        const orderQuantityEl = buildElement("span", `${pizza.quantity} - `);
        const pizzaNameElement = buildElement("span", pizza.name);
        totalPizzas += pizza.quantity;
        pizzaNameElement.classList.add("pizza_name");
        const pizzaItem = document.createElement("li");
        pizzaItem.append(orderQuantityEl, pizzaNameElement);
        pizzaList.appendChild(pizzaItem);
    });
    return pizzaList;
}

function createSingleOrder(order) {
    const orderWrapper = document.createElement("div");
    orderWrapper.className = "order_wrapper";
    orderWrapper.addEventListener("click", selectCurrentOrder);
    const orderNumberEl = buildElement("h4", `Order: ${order.id}`);
    orderWrapper.appendChild(orderNumberEl);
    const pizzaList = createListOfPizzas(order.pizzas);
    orderWrapper.setAttribute("data-total-pizzas", totalPizzas);
    orderWrapper.appendChild(pizzaList);
    return orderWrapper;
}

function createOrdersList() {
    document.querySelector("#orders").innerHTML = "";
    orders.forEach(function (order) {
        const singleOrder = createSingleOrder(order);
        document.querySelector("#orders").appendChild(singleOrder);
    });
}

function selectCurrentOrder(e) {
    const pizzas = document.querySelectorAll(".pizza_name");
    pizzas.forEach(function (pizza) {
        pizza.addEventListener("click", setCurrentPizza);
    });

    if (document.querySelector("#working_on").children.length > 1) {
        return;
    }
    let element = e.target;
    const orderWrapper = element.closest(".order_wrapper");
    if (orderWrapper !== null) {
        orderWrapper.removeEventListener("click", selectCurrentOrder);
        const orderDiv = document.querySelector("#working_on");
        orderDiv.appendChild(orderWrapper);
        const completeButton = buildElement("button", "Complete");
        completeButton.className = "complete_btn";
        completeButton.addEventListener("click", completeOrder);
        orderDiv.appendChild(completeButton);
    }
}

function completeOrder(e) {
    const currentOrder = document.querySelector("#working_on > .order_wrapper");
    const totalPizzasOnOrder = currentOrder.getAttribute("data-total-pizzas");
    if (pizzasCompleteForOrder < totalPizzasOnOrder) {
        showErrorMessage(
            "You have not made enough pizzas to complete this order..."
        );
        return;
    }
    currentOrder.remove();
    let completeButton = e.target;
    completeButton.remove();
    completedOrders++;
    pizzasCompleteForOrder = 0;
}

function setCurrentPizza(e) {
    const pizzaName = e.target.innerText;
    document.querySelector("#current_pizza").innerText = pizzaName;
    displayMethod(pizzaName);
}

function displayMethod(pizzaName) {
    document.querySelector("#pizza_name").innerText = pizzaName;
    const selectedPizza = pizzas.find((pizza) => pizza.name === pizzaName);
    const methodSteps = selectedPizza.method.split(".");
    document.querySelector("#pizza_method").innerHTML = "";
    methodSteps.forEach(function (method) {
        const element = buildElement("li", method);
        document.querySelector("#pizza_method").appendChild(element);
    });
}

function addToOven() {
    pizzasCompleteForOrder++;
    const pizzaName = document.querySelector("#current_pizza").innerText;
    if (pizzaName) {
        const canAddToOven = stepsCompleted(pizzaName);
        if (canAddToOven) {
            const pizzaForOven = {
                name: pizzaName,
                timeAdded: Date.now(),
            };
            oven.push(pizzaForOven);
            displayOvenItems();
            clearCanvas();
            completedSteps = [];
        }
    }
}

document.querySelector("#addToOven").addEventListener("click", addToOven);

function displayOvenItems() {
    document.querySelector("#oven").innerHTML = "";
    oven.forEach(function (pizza) {
        const pizzaDiv = document.createElement("div");
        pizzaDiv.className = "pizza_div";
        const image = document.createElement("img");
        image.src = "pizza.svg";
        const pizzaName = buildElement("p", `(${pizza.name})`);
        pizzaDiv.append(image, pizzaName);
        document.querySelector("#oven").appendChild(pizzaDiv);
    });
}

function startOfGame() {
    if (gameStarted) {
        return;
    }
    document.querySelector("#startBtn").style.display = "none";
    document.querySelector("#endBtn").style.display = "inline";

    gameStarted = true;
    const orders = document.getElementsByClassName("order_wrapper");
    Array.from(orders).forEach(function (order) {
        order.remove();
    });
    createOrdersList();
    ordersTimer();
    countdownTimerRef = setInterval(countdownTimer, 1000);
    gameTimer();

    document.querySelector("#message").innerText =
        "Chef, our first orders are coming in!!!";
    setTimeout(function () {
        document.querySelector("#message").innerText = "";
    }, 3000);
    checkOven();
    listIngredients();
}

function endOfGame() {
    gameStarted = false;
    clearInterval(orderTimerRef);
    clearInterval(countdownTimerRef);
    clearTimeout(gameTimerRef);
    document.querySelector("#endBtn").style.display = "none";
    document.querySelector("#startBtn").style.display = "inline";
}
document.querySelector("#startBtn").addEventListener("click", startOfGame);
document.querySelector("#endBtn").addEventListener("click", endOfGame);

let orderNumber = orders.length + 1;

function generateNewOrder() {
    let pizzas = [];
    const orderItem = Math.ceil(Math.random() * 5);
    for (i = 1; i <= orderItem; i++) {
        pizzas.push(generateNewPizza());
    }
    const newOrder = {
        id: orderNumber,
        pizzas,
    };
    orders.push(newOrder);
    orderNumber++;

    createOrdersList();
}

function generateNewPizza() {
    const quantity = Math.ceil(Math.random() * 3);
    const randomPizza = pizzas[Math.floor(Math.random() * pizzas.length)];
    const pizza = {
        quantity,
        name: randomPizza.name,
    };
    return pizza;
}
generateNewPizza();

let orderTimerRef = "";
function ordersTimer() {
    orderTimerRef = setInterval(generateNewOrder, 3000);
}

let countdownTimerRef = "";
function countdownTimer() {
    countdownTime -= 1;
    document.querySelector(
        "#gameLength"
    ).innerText = `Time left: ${countdownTime}`;
}

let gameTimerRef = "";
function gameTimer() {
    gameTimerRef = setTimeout(endOfGame, gameLength * 1000);
}

function checkOven() {
    setInterval(function () {
        oven.forEach(function (pizza) {
            if (Date.now() - cookingTime * 1000 > pizza.timeAdded) {
                oven.shift();
                displayOvenItems();
                completedPizzas++;
            }
        });
    }, 1000);
}

const canvas = document.querySelector("#pizza_area");
const ctx = canvas.getContext("2d");

function listIngredients() {
    ingredients.forEach(function (ingredient) {
        const ingredientElement = buildElement("button", ingredient);
        ingredientElement.className = "ingredient";
        ingredientElement.addEventListener("click", stepCompleted);
        document.querySelector("#ingredients").appendChild(ingredientElement);
    });
}

function stepCompleted(e) {
    if (document.querySelector("#current_pizza").innerText === "") {
        showErrorMessage("First, select a pizza you would like to work on...");
        return;
    }
    e.target.setAttribute("disabled", true);
    const stepName = e.target.innerText;
    completedSteps.push(stepName);
    makePizza(stepName);
}

function makePizza(ingredient) {
    console.log(completedSteps);
}

function makePizza(ingredient) {
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    switch (ingredient) {
        case "ROLL DOUGH":
            ctx.arc(canvas.width / 2, canvas.height / 2, 100, 0, 2 * Math.PI);
            ctx.lineWidth = 15;
            ctx.strokeStyle = "#f5cf89";
            ctx.stroke();
            ctx.fillStyle = "#f5d69d";
            ctx.fill();
            break;

        case "PIZZA SAUCE":
            ctx.beginPath();
            ctx.arc(canvas.width / 2, canvas.height / 2, 100, 0, 2 * Math.PI);
            ctx.fillStyle = "#ed4434";
            ctx.fill();
            break;

        case "CHEESE":
            ctx.beginPath();
            ctx.arc(canvas.width / 2, canvas.height / 2, 95, 0, 2 * Math.PI);
            ctx.fillStyle = "#f7bc4d";
            ctx.fill();
            break;
        case "PEPPERONI":
            const pepperoniPositions = [
                [78, 62],
                [118, 74],
                [147, 57],
                [116, 134],
                [125, 190],
                [162, 165],
                [190, 85],
                [192, 142],
                [150, 115],
                [76, 95],
                [80, 190],
                [61, 135],
            ];
            pepperoniPositions.forEach(function (piece) {
                ctx.beginPath();
                ctx.arc(piece[0], piece[1], 10, 0, Math.PI * 2);
                ctx.fillStyle = "#bd3611";
                ctx.fill();
            });
            break;
        case "HAM":
            const hamPositions = [
                [81, 62],
                [108, 74],
                [147, 47],
                [130, 124],
                [125, 160],
                [159, 145],
                [197, 82],
                [202, 132],
                [158, 90],
                [86, 95],
                [90, 140],
                [105, 135],
            ];
            hamPositions.forEach(function (piece) {
                ctx.fillStyle = "#f58c8c";
                ctx.rotate((Math.random() * 2 * Math.PI) / 180);
                ctx.fillRect(piece[0], piece[1], 8, 32);
            });
            break;
        case "PINEAPPLE":
            const pineapplePositions = [
                [81, 62],
                [108, 74],
                [147, 47],
                [130, 124],
                [125, 160],
                [159, 145],
                [197, 82],
                [202, 132],
                [158, 93],
                [86, 95],
                [90, 145],
                [105, 140],
            ];
            pineapplePositions.forEach(function (piece) {
                ctx.fillStyle = "#ebe534";
                ctx.rotate((Math.random() * 2 * Math.PI) / 180);
                ctx.fillRect(piece[0], piece[1], 12, 18);
            });
            break;
    }
}

function clearCanvas() {
    const steps = document.getElementsByClassName("ingredient");
    Array.from(steps).forEach(function (element) {
        element.removeAttribute("disabled");
    });
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.clearRect(0, 0, canvas.width, canvas.height);
}

function wastedPizza() {
    wastedPizzas++;
    completedSteps = [];
    clearCanvas();
}

document.querySelector("#waste").addEventListener("click", wastedPizza);

function stepsCompleted(pizzaName) {
    const pizzaObject = pizzas.find(function (pizza) {
        return pizza.name === pizzaName;
    });
    const stepsRequired = pizzaObject.requiredSteps;
    const checkSteps = stepsRequired.every(function (element, index) {
        return element === completedSteps[index];
    });
    if (completedSteps.length > stepsRequired.length) {
        showErrorMessage(
            "You have used too many ingredients :-(, please try again..."
        );
        wastedPizza();
        return;
    }
    if (completedSteps.length < stepsRequired.length) {
        showErrorMessage(
            "You have not used enough ingredients :-(, please try again..."
        );
        return;
    }
    if (completedSteps.length === stepsRequired.length && !checkSteps) {
        showErrorMessage(
            "You have used the wrong ingredients :-(, please try again..."
        );
        wastedPizza();
        return;
    }
    if (oven.length < ovenCapacity) {
        return true;
    }
    return false;
}

function showErrorMessage(message) {
    document.querySelector("#message").innerText = message;
    setTimeout(function () {
        document.querySelector("#message").innerText = "";
    }, 2000);
}