Home  >  Article  >  Web Front-end  >  JavaScript Essentials: Part Mastermind in Javascript)

JavaScript Essentials: Part Mastermind in Javascript)

Barbara Streisand
Barbara StreisandOriginal
2024-10-30 17:09:02869browse

JavaScript Essentials: Part Mastermind in Javascript)

In this section, we will implement a game called Mastermind in JavaScript. This game development would cover a lot of the concepts that we have discussed so far. We will define functions, pass arguments to them, make use of variables, and make use of loops and if statements. We would briefly look at another concept around functions, known as IIFE, Immediately Invoked Function Expression. We will also look at how to take user input via the command line. At this point, it is just console applications.

You can reference a similar implementation here, Master mind in python

Mastermind is a simple board game that uses colours but We'd use numbers instead.

Summary: Behind a bar are four colours put up by one player. The other player can not see the first player's colours. The first player's colours are called the code maker and the other player's colours are the code breaker. The code breaker has, inclusively, between 2 to 12 attempts at guessing the code makers'. The number of attempts must be even.

Implementation

  • Create a folder called mastermind on your pc (or where you put your projects) and in mastermind, initialize a node project using npm init -y (on the command line). I am on a Linux machine so this is how I will set up my project.

    • Open my terminal, run, cd to move me to the user folder.
    • Then, cd ~/projects. projects is where I keep my projects.
    • Then mkdir mastermind and cd mastermind to create the mastermind folder and change into that folder.
    • Initialize a node project with npm init -y. A package.json file will be created.
    • Create app.js with touch app.js.
    • Write console.log("Mastermind") into app.js and run it with node app.js. I expect to see Mastermind else I have an issue with my setup.
  • The starting (entry) point of this game will be in App, a function. Let's create a function called App and add console.log("App"). We can then call App() and execute the code with node app.js. I won't be telling you to run your code but it is something you should be doing as you code along. This is the current content of my app.js file.

console.log("Mastermind");

function App() {
  console.log("App");
}

App();
  • When the game starts

    • user enters the number of rounds they want to play and the value entered must be validated
    • user chooses whether to allow duplicates or not
    • somewhere the code maker is randomly generated
    • user has entered the code breaker
    • the code breaker is compared to the code maker and a hint is given if it doesn't match
    • in the process, we make the number of rounds
    • and to make this more game-like we put the whole App into an infinite loop
  • Let's implement a function to generate random numbers for the code make, thereby setting random values to the code maker.

  • First, we need a way to generate random numbers. Not to interfere with the code in the app.js, let's create another file called scratch_pad.js and in this file we experiment.

  • JavaScript has a simple way to generate random numbers calling Math.random(). In the scratch pad, let's log 4 random numbers using a looping construct.

console.log("Mastermind");

function App() {
  console.log("App");
}

App();
  • what we want are integers (numbers like) 0, 1, 2, ..., 9 not decimals. We can multiply the value returned from the Math.random() by 10 and we would have x.something where x will now be in 1,2,3,...,9. Remember these experiments are all done on the scratch pad. Give it a try.

  • What we want is a number before the dot, the whole number part. We can write code to convert the number to a string and then split it by the "." and get the first element. However, there is a functionality for that called floor which we can use.

for (let i = 0; i < 4; i++) {
  console.log(Math.random());
}
// 0.10037268097853191
// 0.20981624777230534
// 0.47828165742292583
// 0.8160883929470153
  • The way this works is that, if we want to get random numbers between some number min and max, where max is greater than min, then we can do, min Math.floor(Math.random() * (max - min 1)). min is the minimum expected value and max is the maximum expected value. In our case, we have our minimum value to be 0 and maximum to be 9.
  • This is my snippet for generating the random number. I added parameters to the function because I don't want the function to have an internal state.
for (let i = 0; i < 4; i++) {
  console.log(Math.floor(Math.random() * 10));
}
// 4
// 7
// 3
// 4
  • At this point, we can now go back into our app.js and add the function above to generate the random numbers for the code maker. Put it above the App function.

  • From the summary the number of colours used is 4. So we need to generate 4 numbers for the code maker. We also have to handle if duplicates are allowed. Back to the scratchpad.

  • We have functions, if and else statements, the for and while loops, etc. These constructs all have a block or a body. Variables initialized in these blocks can be used within the block and not outside of it. This is known as the scope of a variable. So a variable can exist in the global scope, which means that that variable can be used or assessed everywhere. When we declare a variable in a block. The variable becomes internal or limited in that scope. Run this in the scratchpad.

console.log("Mastermind");

function App() {
  console.log("App");
}

App();
  • Now update this by initializing a variable,x, in the if statement, console.log(x) outside the if block and run your scratch pad. You should get an error similar to this.
for (let i = 0; i < 4; i++) {
  console.log(Math.random());
}
// 0.10037268097853191
// 0.20981624777230534
// 0.47828165742292583
// 0.8160883929470153

At this point I want to bring to your attention the idea about scopes.

  • When generating the code maker, we want to know if duplicates are allowed and at this point, we know that the code maker is an array of numbers (or numeric strings). Let's start with the scratchpad. We want to implement a function that takes in a boolean argument indicating if duplicates are allowed. The function will add (push) four numbers into the code maker but before that, we have to check if duplicates are allowed and handle when not.
for (let i = 0; i < 4; i++) {
  console.log(Math.floor(Math.random() * 10));
}
// 4
// 7
// 3
// 4
  • we also have written our code in such a way that the code maker isn't accessed globally in the code maker function. So will return the code maker instead.
function generateRandomNumbersBetween(min, max) {
  return min + Math.floor(Math.random() * (max - min + 1));
}

for (let i = 0; i < 4; i++) {
  console.log(generateRandomNumbersBetween(0, 9));
}
  • In app.js we can now add the code maker function and a variable for the code make.
  • Now back to the scratchpad. We want to take input from the user from the terminal. Javascript has a way to do that too. Try this snippet.
const HP = 100;

if (true) {
  console.log("IF BLOCK::", HP);
}

console.log("End::", HP);

// IF BLOCK:: 100
// End:: 100
  • There is no issue with this approach of taking user input. It is just that we have to use a callback function and there is no way to pass the entered input to the outer scope of the callback function of readlineOInstance.question.

  • What are you thinking? Try it out in the "scratch pad". If you are thinking about declaring a variable in the outer scope of readlineOInstance.question the assigning the input entered to it, then it is a good approach but ... Still try it.

  • Do you remember the concept of Promises? We can use promise here and resolve the input. However, we have to wrap the whole process in function. There are a few parts of the readlineOInstance.question has a header similar to question(query: string, callback: (answer: string) => void. The query is the query (or prompt) to the user and the callback is how we handle the input collection. Since we might reuse the same function somewhere later, we'd pass the query as an argument.

    console.log("Mastermind");
    
    function App() {
      console.log("App");
    }
    
    App();
    
    • Now we can add the getInput function to the app.js. Do not forget the import, const readline = require("readline"). The content of the app.js should be similar to the snippet below.
    for (let i = 0; i < 4; i++) {
      console.log(Math.random());
    }
    // 0.10037268097853191
    // 0.20981624777230534
    // 0.47828165742292583
    // 0.8160883929470153
    
    • Now we ask the user to enter the number of rounds and if a duplicate is allowed. We know that the number of rounds must be even and between 2 to 12. We will implement a function to validate a value (number) to be even and between 2 and 12. It will return a boolean. A number is even when the number modulo 2 is zero. (ie. number % 2 == 0).
    for (let i = 0; i < 4; i++) {
      console.log(Math.floor(Math.random() * 10));
    }
    // 4
    // 7
    // 3
    // 4
    
    • In the body of the App function, we can ask for the inputs and validate them. We will continuously ask for the proper input for the number of rounds. For the duplicate values in the code, when the user enters anything other than the expected we'd assume that the user doesn't want duplicates. We will use a while loop and set the condition to be true and only break when the rounds are valid however using a try and a catch (for error handling), when the user enters an invalid value we log a message indicating that the value entered is invalid. Try it out.
    function generateRandomNumbersBetween(min, max) {
      return min + Math.floor(Math.random() * (max - min + 1));
    }
    
    for (let i = 0; i < 4; i++) {
      console.log(generateRandomNumbersBetween(0, 9));
    }
    

    Run the app.js and interact with it. This is a similar output during the interaction.

    const HP = 100;
    
    if (true) {
      console.log("IF BLOCK::", HP);
    }
    
    console.log("End::", HP);
    
    // IF BLOCK:: 100
    // End:: 100
    
    • We have taken the number of rounds and the value for duplication. Now we can generate the code maker. To do this we can call the generateCodeMaker function and pass the duplication option value to it (or leave it since it's by default false).
     IF BLOCK:: 100
     /home/Projects/mastermind/scratch_pad.js:8
     console.log(x)
        ^
    
     ReferenceError: x is not defined
        at Object.<anonymous> (/home/Projects/mastermind/scratch_pad.js:8:13)
        at Module._compile (node:internal/modules/cjs/loader:1469:14)
        at Module._extensions..js (node:internal/modules/cjs/loader:1548:10)
        at Module.load (node:internal/modules/cjs/loader:1288:32)
        at Module._load (node:internal/modules/cjs/loader:1104:12)
        at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:174:12)
        at node:internal/main/run_main_module:28:49
    
     Node.js v20.17.0
    
    • Now we can ask the user for the code breaker and compare it to the code maker. The code breaker is also an array of numbers. We will also add a hint for the user to know how far they are from a particular code. So if the code for the code breaker is greater than the code of the code maker, we say more. We say equal when they are equal and else we say less when the code from the code breaker is less than the code of the code breaker. Let's head into the scratchpad.
    • We will create a function that will take a numeric array of 4 elements and then compare the user's input (code breaker).
    // a global code maker that is accessible inside any other scope
    let CODE_MAKER = [];
    
    function generateRandomNumbersBetween(min, max) {
      return min + Math.floor(Math.random() * (max - min + 1));
    }
    
    function generateCodeMaker(isDuplicatesAllowed = false) {
      let counter = 0;
    
      while (counter < 4) {
        let code = generateRandomNumbersBetween(0, 9);
    
        if (isDuplicatesAllowed) {
          CODE_MAKER.push(code);
          counter += 1;
        } else if (!CODE_MAKER.includes(code)) {
          CODE_MAKER.push(code);
          counter += 1;
        }
      }
    }
    
    console.log(CODE_MAKER);
    generateCodeMaker(true);
    console.log(CODE_MAKER);
    
    // reset the code maker
    CODE_MAKER = [];
    generateCodeMaker(false);
    console.log(CODE_MAKER);
    // []
    // [ 6, 6, 0, 9 ]
    // [ 2, 5, 0, 8 ]
    
    • We have a variable to handle the hints and a value for each code related to the code maker and breaker.
    • We pass the code maker to the function to compare it against the input from the user.
    • We update the hints to let the user know how to update the values in the code breaker
    • Now we can add the HINTS and compareCode function to the app.js. It is a great time to run your app.js, above the App function.

    • Now that we implemented a function to compare the code maker and the code breaker, we can now put this in a loop to account for the rounds (rounds = number of times to play the game). So if the number of rounds is 6, then the game would be played 6 times but we'd have to terminate the game when the user guesses all the codes correctly, that is when the values in the HINTS are all 0s. So when we count the number of 0s in HINTS and it is 4, we can terminate the game and say the user won.

    console.log("Mastermind");
    
    function App() {
      console.log("App");
    }
    
    App();
    
    • The number of rounds is reduced and we'd know whether the user won or not if the number of rounds is not 0.
    for (let i = 0; i < 4; i++) {
      console.log(Math.random());
    }
    // 0.10037268097853191
    // 0.20981624777230534
    // 0.47828165742292583
    // 0.8160883929470153
    
    • Some outputs when you run the program
    for (let i = 0; i < 4; i++) {
      console.log(Math.floor(Math.random() * 10));
    }
    // 4
    // 7
    // 3
    // 4
    
    • When I hit enter
    function generateRandomNumbersBetween(min, max) {
      return min + Math.floor(Math.random() * (max - min + 1));
    }
    
    for (let i = 0; i < 4; i++) {
      console.log(generateRandomNumbersBetween(0, 9));
    }
    
    • I guess we can enjoy our hard work so far. I have about 130 lines. How many do you have?

    • This is the full code

    const HP = 100;
    
    if (true) {
      console.log("IF BLOCK::", HP);
    }
    
    console.log("End::", HP);
    
    // IF BLOCK:: 100
    // End:: 100
    

    Is there room for improvement?

    Even though this is a simple console/terminal/text-based app, there is more we can do about it.

    • We can replace all constants such as strings and numbers.
    • We could pull out (refactor) the code breaker input and splitting of it, out of the compare code and then pass the code breaker and code maker as arguments. We could even let the function return the hints rather than accessing the hints globally. We will create a new hints variable and return it. So compareCode will return hints assigned to the hints variable.
     IF BLOCK:: 100
     /home/Projects/mastermind/scratch_pad.js:8
     console.log(x)
        ^
    
     ReferenceError: x is not defined
        at Object.<anonymous> (/home/Projects/mastermind/scratch_pad.js:8:13)
        at Module._compile (node:internal/modules/cjs/loader:1469:14)
        at Module._extensions..js (node:internal/modules/cjs/loader:1548:10)
        at Module.load (node:internal/modules/cjs/loader:1288:32)
        at Module._load (node:internal/modules/cjs/loader:1104:12)
        at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:174:12)
        at node:internal/main/run_main_module:28:49
    
     Node.js v20.17.0
    
    • we can also wrap the console.clear() into a function.
    • we can let the program slow down before the next game
    • we can pull out HINTS.filter((value) => 0 === value).length === 4 as a function. The purpose of it is to check if the code breaker has guessed correctly the code maker.
    • we can also do the same for declaring who won the game
    // a global code maker that is accessible inside any other scope
    let CODE_MAKER = [];
    
    function generateRandomNumbersBetween(min, max) {
      return min + Math.floor(Math.random() * (max - min + 1));
    }
    
    function generateCodeMaker(isDuplicatesAllowed = false) {
      let counter = 0;
    
      while (counter < 4) {
        let code = generateRandomNumbersBetween(0, 9);
    
        if (isDuplicatesAllowed) {
          CODE_MAKER.push(code);
          counter += 1;
        } else if (!CODE_MAKER.includes(code)) {
          CODE_MAKER.push(code);
          counter += 1;
        }
      }
    }
    
    console.log(CODE_MAKER);
    generateCodeMaker(true);
    console.log(CODE_MAKER);
    
    // reset the code maker
    CODE_MAKER = [];
    generateCodeMaker(false);
    console.log(CODE_MAKER);
    // []
    // [ 6, 6, 0, 9 ]
    // [ 2, 5, 0, 8 ]
    
    • Put all functions that can stand alone into their own file, functions.js and export them. We can then refactor standalone functions that depend on a global variable and then pass that data as an argument to the function using a parameter.
    • We can even have a separate file for
    // a global code maker that is accessible inside any other scope
    let CODE_MAKER = [];
    
    function generateRandomNumbersBetween(min, max) {
      return min + Math.floor(Math.random() * (max - min + 1));
    }
    
    function generateCodeMaker(isDuplicatesAllowed = false) {
      let counter = 0;
      let codeMaker = [];
    
      while (counter < 4) {
        let code = generateRandomNumbersBetween(0, 9);
    
        if (isDuplicatesAllowed) {
          codeMaker.push(code);
          counter += 1;
        } else if (!codeMaker.includes(code)) {
          codeMaker.push(code);
          counter += 1;
        }
      }
    
      return codeMaker;
    }
    
    console.log(CODE_MAKER);
    CODE_MAKER = generateCodeMaker(true);
    console.log(CODE_MAKER);
    
    CODE_MAKER = generateCodeMaker(false);
    console.log(CODE_MAKER);
    
    // []
    // [ 6, 6, 0, 9 ]
    // [ 2, 5, 0, 8 ]
    

    Conclusion

    We have used all that we have learnt in this project and there is more. I mentioned that we could group some functions and export them. For this, we will discuss how to import and export in Javascript. I will provide another project that I think will be useful to you. This is the end of the mastermind game and I hope you will also do some refactoring since there are a lot of places that need to be refactored. Best of luck...

    const readline = require("readline");
    
    const readlineOInstance = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });
    
    readlineOInstance.question("Enter code maker: ", (userInput) => {
      console.clear();
      console.log(`INPUT: ${userInput}`);
      readlineOInstance.close();
    });
    

    Sources

    • wiki-play-mastermind
    • wikipedia-mastermind
    • Master mind in python

    The above is the detailed content of JavaScript Essentials: Part Mastermind in Javascript). For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn