Find Your Hat Challenge Project (JavaScript)

Here is my solution. Time spent: 12 hrs.

const prompt = require('prompt-sync')({ sigint: true });

const hat = '^';
const hole = 'O';
const fieldCharacter = '░';
const pathCharacter = '*';

class Field {
    constructor(field = [[]]) {
        this._field = field

        this._xHat = 0
        this._yHat = 0

        this._xCursor = 0
        this._yCursor = 0

    }

    print() {
        const displayString = this._field.map(row => {
            return row.join('');
        }).join('\n');
        console.log(displayString);
    }

    isCorrectKeyPressed(key){
        let arrKey = ['a','s','d','w']
        return arrKey.includes(key) ? true : false
    }

    isWithinBoundary(){
        if (this._xCursor >= 0 && this._xCursor < height && this._yCursor >= 0 && this._yCursor < width) {
            return true
        } else {
            return false
        }    
    }

    foundHole() {
        let nextChar = this._field[this._xCursor][this._yCursor] 
        if (nextChar === hole) {
            return true
        } else {
            return false
        }
    }

    foundHat() {
        let nextChar = this._field[this._xCursor][this._yCursor] 
        if (nextChar === hat) {
            return true
        } else {
            return false
        }
    }

    start() {
        let hatHasBeenFound = false
        let loop = true
        let deathString = ''
        while (loop) {
            this.print()
            let prevPosX = this._xCursor
            let prevPosY = this._yCursor
            let key = prompt('press a-s-d-w key: ');
            
            // console.log(key)

            if (this.isCorrectKeyPressed(key)) {
                if (key === 's') {
                    this._xCursor += 1
                } else if (key === 'w') {
                    this._xCursor -= 1
                } else if (key === 'a') {
                    this._yCursor -= 1
                } else if (key === 'd') {
                    this._yCursor += 1
                }

                if (this.isWithinBoundary() == false) {
                    deathString = 'OUT OF BOUNDARY'
                    this._field[prevPosX][prevPosY] = fieldCharacter
                    loop = false
                } else if (this.foundHole()) {
                    this._field[this._xCursor][this._yCursor] = 'X'
                    this._field[prevPosX][prevPosY] = fieldCharacter
                    loop = false
                    deathString = 'FALLING INTO THE HOLE'
                } else if (this.foundHat()) {
                    this._field[this._xCursor][this._yCursor] = '8'
                    this._field[prevPosX][prevPosY] = fieldCharacter
                    loop = false
                    deathString = 'YESS, YOU FOUND THE HAT!'
                    hatHasBeenFound = true
                } else {
                    this._field[this._xCursor][this._yCursor] = pathCharacter
                    this._field[prevPosX][prevPosY] = fieldCharacter
                }
            } else {
                console.log('WRONG KEY PRESSED')
            }

        }
        this.print()
        if (hatHasBeenFound) {
            console.log('==== YOU WIN :) ====')
        } else {
            console.log(`YOU DIED due to ${deathString} (:`)
        }


    }


    static generateField(width, height, percentage) {
        const arr = new Array(height)
        arr.fill(new Array(width)) // fill array, otherwise it will be undefined
        for (let i = 0; i < height; i++) {
            let arr2 = []
            let countHole = 0
            for (let j = 0; j < width; j++) {
                const prob = Math.random();
                arr2[j] = prob > percentage ? fieldCharacter : hole;
            }
            arr[i] = arr2
        }


        // determine hat location at 0,0
        arr[0][0] = pathCharacter

        // randomly determine hat location
        const xHat = Math.round(Math.random() * width)
        const yHat = Math.round(Math.random() * height)
        // update the hat x,y property
        this._xHat = xHat
        this._yHat = yHat
        // update the area array for hat character
        arr[xHat][yHat] = hat
        return arr
    }
}

const height = 10
const width = 10
const percentage = 0.13
const mainArea = Field.generateField(width, height, percentage)
const game = new Field(mainArea)
game.start()


Hi all!!

Here I left my solution to the challenge:

Regards.

Wow, I agree this is a hard project. I’ve probably spent something like 16 hours writing code for it. and 3-4 hours of those was thinking about how I would even start and if this would even be possible for me to do xD
Well, I got a playable game at the moment, with a win and lose condition. I still have some work to do, for example solving how to not accept out of bounds movement, more user choices while creating the game ( right now you can only choose the game-map size), and some major refactoring of the code is needed :smiley:

I don’t know what to say, I was so close to just giving up and not doing this at all, but now it has made me feel like I can actually do something with javascript. I’ve made a game, from scratch. It’s a cool feeling. Can’t wait to see what the future has to offer :slight_smile:

PS: Of course I will upload my code as soon as I feel it’s readable by other people :smiley: Right now it’s a big mess, but hey. It works!

1 Like

So I have created a function for moving and “rendering” my player movement, which looks like this.
I’m trying to refactor my code to make it easier to read and follow. I’m thinking there must be a way to replace the way I’m repeating “this._field[curx][cury]” everywhere. But I can’t quite figure out exactly how I would do that. Anyone got any ideas? (btw, renderPreviousSquare() only replaces the square the player previously was on to a field character).

const moveAndRender = (axis, operator) => {
      renderPreviousSquare();
      if (axis === "y") {
        if (operator === "+") {
          nextElement = this._field[currentY + 1][currentX];
          this._field[currentY + 1][currentX] = player;
          currentY += 1;
        } else if (operator === "-") {
          nextElement = this._field[currentY - 1][currentX];
          this._field[currentY - 1][currentX] = player;
          currentY -= 1;
        }
      } else if (axis === "x") {
        if (operator === "+") {
          nextElement = this._field[currentY][currentX + 1];
          this._field[currentY][currentX + 1] = player;
          currentX += 1;
        } else if (operator === "-") {
          nextElement = this._field[currentY][currentX - 1];
          this._field[currentY][currentX - 1] = player;
          currentX -= 1;
        }
      }
    };

Well, that was a log one that made me think. I started this at 3 pm it’s now 9:27 pm. So, 6.5 hours. Here is my code. I am all ears of what I did and didn’t do correctly, so please comment.

const prompt = require('prompt-sync')({sigint: true});
class Field {
  constructor(field =[[]]){
    this._field = field;
    this._background = '░';
    this._hole = 'O';
    this._hat = '^';
    this._player = '*';
    this._position = [0,0];
    this._gamestats = true;
    this._message = '';
  }

  print(){
    this._field.forEach(f => {
      console.log(f.join(''));
    });
  }

  screenRefresh(){
    const readline = require('readline')
    const blank = '\n'.repeat(process.stdout.rows)
    console.log(blank)
    readline.cursorTo(process.stdout, 0, 0)
    readline.clearScreenDown(process.stdout)
  }
  endGame(msg){
    this._message = msg;
    this._gamestats = false;
  }
  newGame(){
    this._gamestats = true;
    this._position = [0,0];
    this._gameMessage = '';
    this.screenRefresh();
  }
  updatePosition(){
    if(this._field[this._position[0]][this._position[1]] === this._hole){
      this.endGame('You fell in a hole and broke your leg!');      
    }else if(this._field[this._position[0]][this._position[1]] === this._hat){
      this.endGame("Ya! You found your hat. Let's celebrate!");
    }else{
      this._field[this._position[0]][this._position[1]] = this._player;
    }
  }

  move(pos){
    switch(pos){
      case 'left':
        this._position[1]--
        if(this._position[1] < 0){
          this.endGame('Now you did it, you gone and fell off the map!');
        }else{
          this.updatePosition()
        }
      break;
      case 'down':
        this._position[0]++
        if(this._position[0] > 14){
          this.endGame('Now you did it, you gone and fell off the map!');
        }else{
          this.updatePosition()
        }
      break;
      case 'right':
        this._position[1]++
        if(this._position[1] > 19){
          this.endGame('Now you did it, you gone and fell off the map!');
        }else{
          this.updatePosition()
        }
      break;
      case 'up':
        this._position[0]--
        if(this._position[0] < 0){
          this.endGame('Now you did it, you gone and fell off the map!');
        }else{
          this.updatePosition()
        }
      break;
    }
    this.print();
  }
  getInput(string){
    return prompt(`${string}`)
  }
  getDirections(){
        let direction
    while(this._gamestats){
      this.print();
      direction = this.getInput('What Direction would you like to go?');
      if(direction.toLowerCase() === 's' || direction.toLowerCase() === 'l'){
        this.move('left');
        this._gameMessage = '';
      }else if(direction.toLowerCase() === 'd'){
        this.move('down');
        this._gameMessage = '';
      }else if(direction.toLowerCase() === 'f' || direction.toLowerCase() === 'r'){
        this.move('right');
        this._gameMessage = '';
      }else if(direction.toLowerCase() === 'e' || direction.toLowerCase() === 'u'){
        this.move('up');
        this._gameMessage = '';
      }else if(direction.toLowerCase() === 'q' || direction.toLowerCase() === 'quit'){
        break;
      }else{ this._gameMessage = 'Please use the following keys:\nFor up: U or E\nFor down: D\nFor left: S or L\nFor right: R or F';
      }
      this.screenRefresh();     
    }
  }
  game(){
    this.screenRefresh();
    this.getDirections();
    if(!this._gamestats){
      console.log(this._message)
      let play = this.getInput('Would you like to play again? (Y/N)');
      if(play === 'y'){
        this.newGame();
        this._field = Field.generateField();
        this.game()
      }
    }
  }
  static generateField(){
    this._background = '░';
    this._hole = 'O';
    this._hat = '^';
    this._player = '*';
    let createField;
    const randomPlacement = (row, col, holes) => {
      if(!Math.floor((Math.random() * holes))){
        if(holes > 0){
          return  this._hole
        }else{
          return this._background
        }
      }else{
        return this._background
      }
    }
    const creator = () => {
      let field = [];
      const rows = 15
      const cols = 20
      const hatPlacement = [Math.floor(Math.random() * cols),Math.floor(Math.random() * (rows - 5) + 5)]
      for(let i =0; i < rows; i++){
        let holes = Math.floor(Math.random() * cols);
        let path = [];
        for(let ii = 0; ii < cols; ii++){
          if(i === 0 && ii === 0){
            path.push(this._player);
          }else{
            if(hatPlacement[1] == i && hatPlacement[0] == ii){
              path.push(this._hat);
            }else{
              path.push(randomPlacement(i, ii, holes))
              //holes--
            }        
          }
        }
        field.push(path)
      }
      return field;
    }
    const validator = fieldObj => {
        let validator = false;
        fieldObj.forEach(obj => {
          if((obj.includes(this._hole))){
            let c = 0;
            obj.forEach(o =>{
              if(o === this._hole){
                c++;
              }
            });
            if(obj.length === c){
              validator = true;
            }
        }
      });
      return validator;
    }
    do{
      createField = creator();
    }while(validator(createField))
    return createField;
  }
}

const field = Field.generateField();
const myField = new Field(field);

myField.game();
const prompt = require('prompt-sync')({sigint: true}); class Field { constructor(field =[[]]){ this._field = field; this._background = '░'; this._hole = 'O'; this._hat = '^'; this._player = '*'; this._position = [0,0]; this._gamestats = true; this._message = ''; } print(){ this._field.forEach(f => { console.log(f.join('')); }); } screenRefresh(){ const readline = require('readline') const blank = '\n'.repeat(process.stdout.rows) console.log(blank) readline.cursorTo(process.stdout, 0, 0) readline.clearScreenDown(process.stdout) } endGame(msg){ this._message = msg; this._gamestats = false; } newGame(){ this._gamestats = true; this._position = [0,0]; this._gameMessage = ''; this.screenRefresh(); } updatePosition(){ if(this._field[this._position[0]][this._position[1]] === this._hole){ this.endGame('You fell in a hole and broke your leg!'); }else if(this._field[this._position[0]][this._position[1]] === this._hat){ this.endGame("Ya! You found your hat. Let's celebrate!"); }else{ this._field[this._position[0]][this._position[1]] = this._player; } } move(pos){ switch(pos){ case 'left': this._position[1]-- if(this._position[1] < 0){ this.endGame('Now you did it, you gone and fell off the map!'); }else{ this.updatePosition() } break; case 'down': this._position[0]++ if(this._position[0] > 14){ this.endGame('Now you did it, you gone and fell off the map!'); }else{ this.updatePosition() } break; case 'right': this._position[1]++ if(this._position[1] > 19){ this.endGame('Now you did it, you gone and fell off the map!'); }else{ this.updatePosition() } break; case 'up': this._position[0]-- if(this._position[0] < 0){ this.endGame('Now you did it, you gone and fell off the map!'); }else{ this.updatePosition() } break; } this.print(); } getInput(string){ return prompt(`${string}`) } getDirections(){ let direction while(this._gamestats){ this.print(); direction = this.getInput('What Direction would you like to go?'); if(direction.toLowerCase() === 's' || direction.toLowerCase() === 'l'){ this.move('left'); this._gameMessage = ''; }else if(direction.toLowerCase() === 'd'){ this.move('down'); this._gameMessage = ''; }else if(direction.toLowerCase() === 'f' || direction.toLowerCase() === 'r'){ this.move('right'); this._gameMessage = ''; }else if(direction.toLowerCase() === 'e' || direction.toLowerCase() === 'u'){ this.move('up'); this._gameMessage = ''; }else if(direction.toLowerCase() === 'q' || direction.toLowerCase() === 'quit'){ break; }else{ this._gameMessage = 'Please use the following keys:\nFor up: U or E\nFor down: D\nFor left: S or L\nFor right: R or F'; } this.screenRefresh(); } } game(){ this.screenRefresh(); this.getDirections(); if(!this._gamestats){ console.log(this._message) let play = this.getInput('Would you like to play again? (Y/N)'); if(play === 'y'){ this.newGame(); this._field = Field.generateField(); this.game() } } } static generateField(){ this._background = '░'; this._hole = 'O'; this._hat = '^'; this._player = '*'; let createField; const randomPlacement = (row, col, holes) => { if(!Math.floor((Math.random() * holes))){ if(holes > 0){ return this._hole }else{ return this._background } }else{ return this._background } } const creator = () => { let field = []; const rows = 15 const cols = 20 const hatPlacement = [Math.floor(Math.random() * cols),Math.floor(Math.random() * (rows - 5) + 5)] for(let i =0; i < rows; i++){ let holes = Math.floor(Math.random() * cols); let path = []; for(let ii = 0; ii < cols; ii++){ if(i === 0 && ii === 0){ path.push(this._player); }else{ if(hatPlacement[1] == i && hatPlacement[0] == ii){ path.push(this._hat); }else{ path.push(randomPlacement(i, ii, holes)) //holes-- } } } field.push(path) } return field; } const validator = fieldObj => { let validator = false; fieldObj.forEach(obj => { if((obj.includes(this._hole))){ let c = 0; obj.forEach(o =>{ if(o === this._hole){ c++; } }); if(obj.length === c){ validator = true; } } }); return validator; } do{ createField = creator(); }while(validator(createField)) return createField; } } const field = Field.generateField(); const myField = new Field(field); myField.game();

This took me a week :sob:

Hi all,

This is my solution to Find Your Hat Project.

This was a challenging project but I did enjoy working through it. It took me about 1 week! let me know what you think.

Link to my code on github

Or you can view it on here.

const prompt = require('prompt-sync')({sigint: true});

const hat = '^';
const hole = 'O';
const fieldCharacter = '░';
const pathCharacter = '*';
const lost = '!'
const win = '@'
let gameStatus = true;
let fallen = false;
let found = false;
let outOfBounds = false;

class Field {
    constructor(field) {
    this._field = field;
    this._xPosition = 0;
    this._yPosition = 0;
    this._direction = '';
  }

  /* 

  The move() method all increment the xPosition or yPosition by +1 or -1.
  However, before doing so the following methods will be executed first:

  direction() : This will find out the which direction the user wants to move.
  insideBoundaryCheck() : This will check if the direction the user wants to move into is inside the boundary.

  If the direction is within the boundary, the next method, foundHat(), will see if the user would find the hat by moving in their chosen direction.
  If true, the user's chosen direction  would be executed and the game completed.
  
  If false, insideHole() method is run. This checks to see if the chosen direction would move inside a hole. If true, the lost symbol "!" is printed on the co-ordinates and the game ends.

  If false, then the pathCharacter '*' is printed on the co-ordinates and the game continues.
  
  */
    move() {
        // updates the xPosition according to the user's input
        if(this._direction === 'R') {
            this._xPosition += 1
        } 
        else if (this._direction === 'L') {
            this._xPosition -= 1
        } 
        else if (this._direction === 'U') {
            this._yPosition -= 1
        } 
        else if (this._direction === 'D') {
            this._yPosition += 1
        }
        
        // Checks to see if the variable 'found' is true. If true, it will print the win symbol '@' on the current co-ordinates.
        if (found === true) {
            this._field[this._yPosition][this._xPosition] = win;
            this.print()

            console.log('\nYou Win!\nYou found your hat.')
            gameStatus = false

        }
        
        // Checks to see if the variable 'fallen' is true. If True, it will print the lost symbol '!' on the current co-ordinates.
        else if (fallen === true) {
            this._field[this._yPosition][this._xPosition] = lost;
            this.print()
        } 
        
        // Checks to see if the variable 'outOfBounds' is false. If outOfBounds is false, it will print the pathCharacter symbol '░' on the current co-ordinates.
        else if(outOfBounds === false) {
            this._field[this._yPosition][this._xPosition] = pathCharacter;
            this.print()
        }        
  }


    // Each element of the Outter array is joined together and logged  to the console
    print() {
        this._field.forEach( outerArray => {
        console.log(outerArray.join(''))
        })
    }

    // logs the insturctions of how to move to the console.
    instructions () {
        console.log(
        '\n\nTo move up one tile enter "U"'+
        '\nTo move down one tile enter "D"' +
        '\nTo move left once tile enter "L"'+
        '\nTo move right one tile enter "R"')
    }

    currentPosition() {
        console.log(`x co-ordinate: ${this._xPosition}\ny co-ordinate: ${this._yPosition}`)
    }

    // Checks to see if we were to move according to the user's input would we fall inside a hole?
    insideHole() {
        if(this._direction === 'D') {
            if(this._field[this._yPosition +1 ][this._xPosition] === hole) 
                fallen = true;

        }

        else if(this._direction === 'R') {
            if(this._field[this._yPosition ][this._xPosition +1] === hole)
                fallen = true;
        }

        else if(this._direction === 'U') {
            if(this._field[this._yPosition -1 ][this._xPosition] === hole)
                fallen = true;
        }

        else if(this._direction === 'L') {
            if(this._field[this._yPosition ][this._xPosition -1] === hole)
                fallen = true; 
        }
        
        // if the user's intended move was a hole then the variable 'fallen' would be set to true and the below code would be executed.
        if (fallen === true) {
            this.move()

            console.log('You Lose!\nYou fell in the hole.')
            gameStatus = false;

        }
        // If it was not a hole then the variable 'fallen' would be false.
        else {
            this.move()
        } 

    }
    
    // Checks to see if the intended move is the co-ordinates of the hat.
    foundHat() {
        if (this._direction === 'D') {

            if(this._field[this._yPosition +1][this._xPosition] === hat) {
                found = true;
                this.move()

            }
        } 
        
        else if (this._direction === 'R') {
            if(this._field[this._yPosition][this._xPosition +1] === hat) {
                found = true;
                this.move()

            }
        }

        else if (this._direction === 'U') {
            if(this._field[this._yPosition -1][this._xPosition] === hat) {
                found = true;
                this.move()
            }
        } 
            
        else if (this._direction === 'L') {
            if(this._field[this._yPosition][this._xPosition -1] === hat) {
                found = true;
                this.move()
            }
        }
    }

// This first check is to see if the intended move is within the field Boundary.
    insideBoundaryCheck() {
        if (this._direction === 'D') {
            if((this._yPosition +1) < this._field.length) {
                outOfBounds = false
        } 
        
        else {
            outOfBounds = true
        }

        } 
        else if(this._direction === 'R') {
            
            
            if((this._xPosition +1) < this._field[0].length) {
                outOfBounds = false
            } 

            else {
                outOfBounds = true
            }

        } 

        else if(this._direction === 'U') {
            if((this._yPosition -1) > -1) {
                outOfBounds = false
            } 
            else {
                outOfBounds = true
            }

        }

        else if(this._direction === 'L') {
            if((this._xPosition -1) > -1) {
                outOfBounds = false
            }
            else {
                outOfBounds = true
            }
        }

        //If the variable 'outOfBounds' equals false then code will run the foundHat() method.
        if (outOfBounds === false) {
            this.foundHat()

            //If the variable 'found' is false, the hat was not found, then the method insideHole() will run.
            // If the hat was found, then there is no need to run insideHole() method.
            if(found === false) {
                this.insideHole()
            }
        }
        
        // If the variable 'outOfBounds' is true, then the user loses.
        else if(outOfBounds === true) {
            
            //The x or Y co-ordinates are still updates to simulate the move.
            this.move();
            console.log('You Lose:\nYou moved outside of the field')

            // The variable gameStatus will be set to false so that the game will stop.
            //The while loop in the directions() method will now stop.
            gameStatus = false;

            return false
        }

    }


    // This method asks the user to input the diretion they intend to move.
    direction() {

        // Before the game starts it will print the field.
        this.print()

        // The variable 'gameStatus' is intially set to true. 
        //So this loop will run and will continue to run unless 'gameStatus' becomes false.'
        while (gameStatus) {

            // Asks the user for their input
            let whichWay = prompt('\nWhich way would you like to move? ')
            
            // this.direction variable was initially stored as an empty string in the constructor of the Field class.
            // It know will store the direction the user want to move.
            this._direction = whichWay
            
            // If this.direction is any of the valid inputs it will be used as an arguement for the insideBoundaryCheck() method.
            if (this._direction === 'U' || this._direction === 'D' || this._direction === 'L' || this._direction === 'R') {
                console.log(this._direction)
                this.insideBoundaryCheck(this._direction)
            } 
            else {
                console.log('Invalid Input')   
            }

        }
    }

    // This method will automatically generate a 2 dimensional field based on the arguement provided.
    static generateField (height, width, percentage) {

        const numOfTiles = height * width;
        const field = []

        
        
        let firstIndex = true
        let widthIndex = width - width
        let heightIndex = height - height
        let randomX = undefined
        let randomY = undefined
        let hatX = undefined
        let hatY = undefined
        let hatPlaced = false

        // creates empty arrays inside the empty 'field' array. This will be the Y-axis
        // The number of empty arrays it creates will be the same as the height arguement. 
        for( let i=0 ; i<height ; i++) {
            field.push([])
        }
        
        // Inside each element (the empty arrays created) we need to input the 'fieldCharacter' variable.
        // The total numner of times we will need to do this depends on the variable 'numOfTiles' (height * width)

        for (let i = 0  ; i < numOfTiles  ; i++) {
            
            // Because we want the upper left conner of the field to be the pathCharacter variable, field[0][0] will be set as pathCharacter.
            // firstIndex variable is intitalized as true, so this if block will run.
            if(firstIndex) {
                
                // Inside the block we change firstIndex to false so that this block will not run after its first execution.
                firstIndex = false;
                // The variables heightIndex and width index are both intialised as 0.  
                field[heightIndex][widthIndex] = pathCharacter

                widthIndex += 1;
            }
            
            // After the first for loop this else block will run for the rest of the for loops.
            else {
                // we intend to input the fieldCharacter symbol into an array until it reaches its desired width.
                if ( widthIndex < width) {
                    field[heightIndex][widthIndex] = fieldCharacter;

                    // the widthIndex is incremented by 1 each time so that we can move along the array in each for loop.
                    widthIndex +=1;

                }

                // when the widthIndex has been incremented to equal the width, we want to now move along to the next array.
                else if (widthIndex === width){
                    //widthIndex is set back to 0 and the heightIndex is set to 1.
                    // This is so that we move on to the beginning of the next array.
                    widthIndex = 0
                    heightIndex += 1;

                    field[heightIndex][widthIndex] = fieldCharacter;
                    widthIndex += 1;
                }
            }

        }

        // Now that we have generated the field, we want to randomly place the hat within that field.
        // We only need to execute this code while hatPlaced is false.
        while (hatPlaced === false) {
            
            // Generates and random x and y co-ordinate using the width and height arguement to makesure it is within the field.
            hatX = Math.floor(Math.random() * width)
            hatY = Math.floor(Math.random() * height)

            // We check to see the random co-ordinates are NOT field[0][0] as this is where we want the pathCharacter to be intialised.
            // If it is equal to [0][0] then the while loop will run again as hatPlaced will still be false.
            if (field[hatY][hatX] !== field[0][0]) {
                field[hatY][hatX] = hat
                hatPlaced = true
            }
        }

        // numOfHole variable stores the number of holes to be printed in the field.
        // It find the percentage of numOftiles to be holes and rounds it up as we need to have a whole number.
        const numOfHoles = Math.ceil((percentage/100) * numOfTiles)
        let remainingHoles = numOfHoles



        // while there are holes remaining to be printed onto the field this code will run. 
        while (remainingHoles > 0) {

            // Generates and random x and y co-ordinate using the width and height arguement to makesure it is within the field.
            randomX = Math.floor(Math.random() * width)
            randomY = Math.floor(Math.random() * height)

            // checks to see the random co-ordinates are not:
            // 1) not the co-ordinates of the hat that was placed
            // 2) not field[0][0] were the pathCharacter is intitally placed.
            // 3) not were we have randomly placed another hole. 
            if(field[randomY][randomX] !== hat && field[randomY][randomX] !== field[0][0] && field[randomY][randomX] !== hole) {
                field[randomY][randomX] = hole;

                remainingHoles -= 1
            } 
        }
        return field

    }

}


const field1 = new Field(Field.generateField(5,5,10));

field1.direction()


I do agree with you as the hat project is a bit more difficult ,can we get the video version of this project
I did saw the solution and able to run it but not able to understand the code.

My solution:
Github link for Find Your Hat

I had an unexpectedly rough time of this one! But I’ve got a minimum viable product, so I’m posting it now!

It turns out the big issue I was stuck on was that I had hatLoc and usrLoc parameters (both as 2 element vectors) I was trying to compare with hatLoc === usrLoc but it turns out I had to compare individual elements of each vector, not the full vectors.

What I’ve got now is a bit messy, but it works!

I completed my project within a week, But finally, it has been done.

Hello everyone,
Below is my solution to the Find Your Hat Challenge Project.
Will appreciate any feedback. Thanks! :slight_smile:

findyourhat/main.js at main · teoabaza/findyourhat (github.com)

1 Like

Hello Everybody,

Here is my take of the Find Your Hat challenge.

Comments are welcome.

Thanks,

Daniel

Here is my code. I’ve created validator from scratch with my own idea for it. Took me few hours to make it work, but it’s great and I’m very proud :smiley:

const prompt = require('prompt-sync')({sigint: true}); const hat = '^'; const hole = 'O'; const fieldCharacter = '░'; const pathCharacter = '*'; class Field { constructor(field) { this.field = field; this.y = 0; this.x = 0; this.end = false; } print() { for (let i=0; i<this.field.length; i++) { console.log(this.field[i].join('')); } } playGame() { this.print(); while (!this.end) { let direction = prompt('Which way?'); direction = direction.toLowerCase(); switch (direction) { case 'u': this.x -= 1; this.updateGame(); break; case 'd': this.x += 1; this.updateGame(); break; case 'r': this.y += 1; this.updateGame(); break; case 'l': this.y -= 1; this.updateGame(); break; default: console.log('Pick correct direction (u, d, l, r)'); break; } } } updateGame() { if (this.notOut()) { if (this.isHole()) { console.log('Oops You fell into the hole'); this.end = true; } else if (this.isHat()) { console.log('Congratulations, you have found the hat'); this.end = true; } else { this.field[this.x][this.y] = pathCharacter; myField.print(); } } else { console.log('Oops Your out of bounds'); this.end = true; } } notOut() { return (this.x < this.field.length && this.x >= 0 && this.y < this.field[this.x].length && this.y >= 0); } isHole() { return (this.field[this.x][this.y] === hole); } isHat() { return (this.field[this.x][this.y] === hat); } static generateField(height, width, percentage = 10) { if (percentage >= 0 && percentage <= 100) { let field = []; for (let i=0; i < height; i++) { field.push([]); for (let i2=0; i2 < width;i2++) { if (i === 0 && i2 === 0) { field[i].push(pathCharacter); } else { let isHole = Math.floor(Math.random() * percentage); let isField = Math.floor(Math.random() * (100 - percentage)); if (isHole >= isField) { field[i].push(hole); } else { field[i].push(fieldCharacter); } } } } const hatX = Math.floor(Math.random() * height); const hatY = Math.floor(Math.random() * width); while (hatX === 0 && hatY === 0) { hatX = Math.floor(Math.random() * height); hatY = Math.floor(Math.random() * width); } field[hatX][hatY] = hat; return field; } else { console.log('Please pick percentage between 0 and 100'); } } isSolvable() { const allEmpty = []; const locHat = []; let outOfMoves = false; for (let i=0; i<this.field.length; i++) { for (let i2=0; i2<this.field[i].length; i2++) { if (this.field[i][i2] === fieldCharacter) { allEmpty.push('x' + i + 'y' +i2); } else if (this.field[i][i2] === hat) { locHat.push(i, i2); } } } let x = locHat[0]; let y = locHat[1]; let i3 = 0; let marks = []; while (!outOfMoves && i3 < 5){ if (allEmpty.includes('x' + (x-1) + 'y' +y) && !marks.includes('x' + (x-1) + 'y' +y + i3)) { x--; marks.push('x' + x + 'y' +y + i3) } else if (allEmpty.includes('x' + (x+1) + 'y' +y) && !marks.includes('x' + (x+1) + 'y' + y + i3)) { x++; marks.push('x' + x + 'y' +y + i3) } else if (allEmpty.includes('x' + x + 'y' +(y-1)) && !marks.includes('x' + x + 'y' +(y-1) +i3)) { y--; marks.push('x' + x + 'y' +y + i3) } else if (allEmpty.includes('x' + x + 'y' +(y+1)) && !marks.includes('x' + x + 'y' +(y+1) + i3)) { y++; marks.push('x' + x + 'y' +y + i3) } else if (i3 < 5){ i3++; } else { outOfMoves = true; } } if (x === 0 & y === 1) { return true; } else if (y === 0 & x == 1) { return true; } else { return false; } } } let myField = new Field(Field.generateField(7, 7, 40)); while (!myField.isSolvable()) { myField = new Field(Field.generateField(7, 7, 40)); } myField.playGame();
1 Like

Hi there,

Here’s my solution. Trained modules and found unexpected bug that took 40 minuts to find and squash

Please , enjoy :grinning:

My thoughts exactly. I don’t think this project is appropriate for this stage of the course. I’ve been on it for about 2weeks now. I’ve made some headway in figuring things out but it’s been quite frustrating. Also, I haven’t found any solution out here that actually works exactly as expected.
I hope the organizers take notice of this and proffer a solution to solve this problem. Like you said, a walkthrough , step-by-step video would be a life saver.

1 Like

The project is in my opinion out of place at this stage of the learning path, above all because the lerners are asked to build a game in the command line, that’s something completely new. After I skipped the game months ago: I came back today to fill this hole. I managed to complete it in one whole day, about 8 hours: it was a nice and compelling experience.

I think a better explanation of the project - i.e. the “spatial” relation between the field array as displayed in the command line, as a field with a width and a length, and the field array as array of arrays could have avoided a lot of frustration.

Great project though!

1 Like

Hello everyone!
This is my solution for this project:

I skipped this project a few weeks ago and finally it took me the eternity to finish it.
The method to check if the field can be solved was a pure nightmare for me. And I couldn’t build this algorithm without the help of my husband, the experienced developer. It turns out this solution is based on the Hoshen–Kopelman algorithm. I couldn’t fully understand it at first but it was a very interesting experience and it works!
Also after running the game you will be asked if you would like to play the hard mode. In the hard mode one hole will be added after every three turns. In the beginning and after adding every hole the field will be checked for passability.

Happy coding! :heart:

2 Likes

Find My Hat Project - Github
Above is the link to my Find My Hat Project - just click the heading to be taken to the project page. There you will find my code. Command to execute is node findhat.js
I found the Find My Hat project an interesting challenge. I posted on Github and even found the Ci javascript node modules actions workflow. I passed two but failed one. I did not have a test script. Therefore it did not pass. I need to read more about how the workflows work, as I think they are a good tool for helping you to successfully build your future projects. Do others agree? Here is the workflow run I used but it has not passed Node.js

By GitHub Actions

![Node.js logo]test a Node.js project with npm. I may be ahead of myself here but if anyone knows how I can get it to pass, that would be good. thanks :evergreen_tree: :christmas_tree:

Hi all,
here is my solution.
With a little help from the Codeacademy Community, it took me two evening to code this project.

const prompt = require('prompt-sync')({sigint: true});

const hat = '^';
const hole = 'O';
const fieldCharacter = '░';
const pathCharacter = '*';

class Field {
 constructor (field){
    this.field = field;
    }
 
 print() {
        for(let row of this.field) 
          {
            console.log(row.join(''))
          }
        }

  play() {
    let start = this.field;
    let xpos = 0;
    let ypos = 0;
    

    const prompt = require('prompt-sync')();

      while ([xpos][ypos] != hat) {
            this.print();
            const move = prompt('What is your move?');

            if (move === 'w') { if (ypos === 0) 
              { return (console.log(`You LOSE, Try Again!!!`)); 
              } else {ypos--;} } 
            else if (move === 'a'){if (xpos === 0) 
              { return (console.log(`You LOSE, Try Again!!!`)); 
              }                       
              else {xpos--;}  }
            else if (move === 's'){ if (ypos === (this.field.length)-2) 
              { return (console.log(`You LOSE, Try Again!!!`)); 
              }                       
              else {ypos++;}  }
            else if (move === 'd'){ if (xpos === 50-1) 
              { return (console.log(`You LOSE, Try Again!!!`)); 
              }                             
              else {xpos++;}   };

             if (this.field [ypos][xpos] === hole) 
             {return (console.log(`You LOSE, Try    Again!!!`));}   
             if (this.field [ypos][xpos] === hat) 
             {return (console.log(`You Won !!!`));}       

     
        start[ypos][xpos] = pathCharacter;

      }

  }



  static generateField() {
    var background = [];
    let iMax = 15;
    let jMax = 50;
    let count = 0;
    let counter = iMax*jMax;
    let HatXPosition = Math.floor((Math.random()*counter)/iMax)
    let HatYPosition = Math.floor((Math.random()*counter)/jMax)

    for (let i = 0; i < iMax; i++) {
      background[i] = [];
        background.push([]);
      for (let j = 0; j < jMax; j++) {
        background[i].push(fieldCharacter);
        count++;
      }   
    }
      
        background[0][0] = pathCharacter;
        
        if((HatYPosition===0)&&(HatXPosition===0)) {
            background[iMax-1][jMax-1] = hat;
        } else 
        {
            background[HatYPosition][HatXPosition] =  hat;
        }

      let percenthole = 25;
      let uMax = (counter/100) * percenthole;   
        for (let u = 0; u < uMax; u++) 
        {
          let holeXPosition = Math.floor((Math.random()*counter)/iMax)
          let holeYPositon = Math.floor((Math.random()*counter)/jMax)
          background[holeYPositon][holeXPosition] =  hole;
        }   
    return background
    
  };

}


let generatedfield = Field.generateField();
const myField = new Field(generatedfield);


myField.play()