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!

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();