Como resolver?

O ponto chave aqui é analisar os requisitos e entender o que a classe precisará conter para atendê-los. Para isso é preciso perceber o que será um atributo e o que será um método, além dos recursos que devem ser utilizados para criar as funcionalidades da classe.

Resolução

Podemos começar tentando identificar os atributos que serão necessários. Analisando os requisitos podemos perceber inicialmente os atributos nome, nível, experiência, planetas conhecidos, terrenos com especialidade e se o explorador está vivo ou não. Poderíamos ter um atributo para o ranque, mas como esse é um valor que muda bastante de acordo com o nível pode ser mais simples deixá-lo como um método. Também podemos definir os valores iniciais para esses atributos, visto que a maioria não será definida com parâmetros na instanciação. Teríamos algo assim:

class Explorer {  constructor(name) {
    this.name = name
    this.level = 1
    this.experience = 0
    this.knownPlanets = []
    this.terrainExpertise = {}
    this.alive = true
  }
}

Agora podemos lidar com o ranque do explorador. Como o ranque é obtido a partir do nível, podemos criar um método para nos retornar o ranque.

class Explorer {
  constructor(name) {
    this.name = name
    this.level = 1
    this.experience = 0
    this.knownPlanets = []
    this.terrainExpertise = {}
    this.alive = true
  }

	rank() {
    if (this.level < 10) return 'Newbie'
    if (this.level < 30) return 'Explorer'
    if (this.level < 50) return 'Veteran'
    if (this.level < 80) return 'Elite'
    if (this.level < 99) return 'Master'
    return 'Legend'
  }
}

O próximo passo pode ser lidar com o ganho de experiência. Para deixar os métodos de nossa classe mais curtos e objetivos podemos ter um método específico para lidar com isso que apenas recebe uma quantidade de pontos de experiência e adiciona estes pontos para o explorador. Além disso, precisaremos lidar com a subida de nível do explorador. Uma forma de fazer isso é adicionando um atributo que contém a experiência restante para subir de nível (que é diferente do total de pontos de experiência, que também deve ser armazenado). Vejamos como poderia ficar nosso método que aumenta os pontos de experiência e então, caso o explorador ainda não esteja no nível máximo, verifica se ele subiu de nível:

class Explorer {
  constructor(name) {
    this.name = name
    this.level = 1
    this.experience = 0
		this.expToNextLevel = 110
    this.knownPlanets = []
    this.terrainExpertise = {}
    this.alive = true
  }

	rank() {
    if (this.level < 10) return 'Newbie'
    if (this.level < 30) return 'Explorer'
    if (this.level < 50) return 'Veteran'
    if (this.level < 80) return 'Elite'
    if (this.level < 99) return 'Master'
    return 'Legend'
  }

	gainExperience(pts) {
    this.experience += pts

    // Max level
    if (this.level === 99) {
      return
    }

    // Level up
    if (pts >= this.expToNextLevel) {
      this.level++
      const newExpToNextLevel = (100 + (10 * this.level)) - (pts - this.expToNextLevel)
      this.expToNextLevel = this.level !== 99 ? newExpToNextLevel : 0
      console.log(`Você subiu de nível! Experiência para o próximo nível: ${this.expToNextLevel}`)
    } else {
      this.expToNextLevel -= pts
    }
  }
}

Agora podemos avançar para a ação de explorar, que será mais complexa, visto que possui muitas etapas e verificações. Primeiro de tudo precisamos garantir que o explorador não está morto:

class Explorer {
  constructor(name) {
    this.name = name
    this.level = 1
    this.experience = 0
		this.expToNextLevel = 110
    this.knownPlanets = []
    this.terrainExpertise = {}
    this.alive = true
  }

	rank() {
    if (this.level < 10) return 'Newbie'
    if (this.level < 30) return 'Explorer'
    if (this.level < 50) return 'Veteran'
    if (this.level < 80) return 'Elite'
    if (this.level < 99) return 'Master'
    return 'Legend'
  }

	gainExperience(pts) {
    this.experience += pts

    // Max level
    if (this.level === 99) {
      return
    }

    // Level up
    if (pts >= this.expToNextLevel) {
      this.level++
      const newExpToNextLevel = (100 + (10 * this.level)) - (pts - this.expToNextLevel)
      this.expToNextLevel = this.level !== 99 ? newExpToNextLevel : 0
      console.log(`Você subiu de nível! Experiência para o próximo nível: ${this.expToNextLevel}`)
    } else {
      this.expToNextLevel -= pts
    }
  }

	explore(planet) {
    // If dead exit early
    if (!this.alive) {
      console.log('You are dead.')
      return
    }

	}
}

Avançando, vamos agora obter as propriedades do planeta que precisamos através de desestruturação. Também vamos obter os resultados dos dados através do uso do Math.random() em conjunto com o Math.floor(). Outra coisa que já podemos adiantar é a verificação do bônus de especialidade no terreno, afinal ele deve estar incluído no resultado quando formos avaliá-lo.

class Explorer {
  constructor(name) {
    this.name = name
    this.level = 1
    this.experience = 0
		this.expToNextLevel = 110
    this.knownPlanets = []
    this.terrainExpertise = {}
    this.alive = true
  }

	rank() {
    if (this.level < 10) return 'Newbie'
    if (this.level < 30) return 'Explorer'
    if (this.level < 50) return 'Veteran'
    if (this.level < 80) return 'Elite'
    if (this.level < 99) return 'Master'
    return 'Legend'
  }

	gainExperience(pts) {
    this.experience += pts

    // Max level
    if (this.level === 99) {
      return
    }

    // Level up
    if (pts >= this.expToNextLevel) {
      this.level++
      const newExpToNextLevel = (100 + (10 * this.level)) - (pts - this.expToNextLevel)
      this.expToNextLevel = this.level !== 99 ? newExpToNextLevel : 0
      console.log(`Você subiu de nível! Experiência para o próximo nível: ${this.expToNextLevel}`)
    } else {
      this.expToNextLevel -= pts
    }
  }

	explore(planet) {
    // If dead exit early
    if (!this.alive) {
      console.log('You are dead.')
      return
    }

		const { id, hostility, terrain } = planet

    // Throw dices
    const dice1 = Math.floor(1 + Math.random() * 5)
    const dice2 = Math.floor(1 + Math.random() * 5)

    // Result with bonus (if applied)
    const bonus = this.terrainExpertise[terrain] > 2 ? 1 : 0
    const dices = dice1 + dice2 + bonus

    console.log(`Rolled ${dice1} and ${dice2} ${bonus ? '+1 bônus' : ''}`)
	}
}

Agora podemos tratar o resultado caso saia um acerto crítico ou falha crítica. Se for um acerto crítico o nosso objeto que contém os pontos de especialidade no terreno em questão devemos incrementar o seu valor em 1 (atenção para quando ainda não existe um valor para o terreno em questão, pois não é possível incrementar uma propriedade undefined). Se for uma falha crítica e o planeta for hostil o explorador deve morrer e o método deve ser encerrado imediatamente.

class Explorer {
  constructor(name) {
    this.name = name
    this.level = 1
    this.experience = 0
		this.expToNextLevel = 110
    this.knownPlanets = []
    this.terrainExpertise = {}
    this.alive = true
  }

	rank() {
    if (this.level < 10) return 'Newbie'
    if (this.level < 30) return 'Explorer'
    if (this.level < 50) return 'Veteran'
    if (this.level < 80) return 'Elite'
    if (this.level < 99) return 'Master'
    return 'Legend'
  }

	gainExperience(pts) {
    this.experience += pts

    // Max level
    if (this.level === 99) {
      return
    }

    // Level up
    if (pts >= this.expToNextLevel) {
      this.level++
      const newExpToNextLevel = (100 + (10 * this.level)) - (pts - this.expToNextLevel)
      this.expToNextLevel = this.level !== 99 ? newExpToNextLevel : 0
      console.log(`Você subiu de nível! Experiência para o próximo nível: ${this.expToNextLevel}`)
    } else {
      this.expToNextLevel -= pts
    }
  }

	explore(planet) {
    // If dead exit early
    if (!this.alive) {
      console.log('You are dead.')
      return
    }

		const { id, hostility, terrain } = planet

    // Throw dices
    const dice1 = Math.floor(1 + Math.random() * 5)
    const dice2 = Math.floor(1 + Math.random() * 5)

    // Result with bonus (if applied)
    const bonus = this.terrainExpertise[terrain] > 2 ? 1 : 0
    const dices = dice1 + dice2 + bonus

    console.log(`Rolled ${dice1} and ${dice2} ${bonus ? '+1 bônus' : ''}`)

		// Check for critical
    if (dices >= 12) {
      this.terrainExpertise[terrain] = this.terrainExpertise[terrain] ? this.terrainExpertise[terrain] + 1 : 1
    }

    // Check for critical lefailure
    if (dices === 2 && hostility === 'hostile') {
      console.log('You died.')
      this.alive = false
      return
    }
	}
}

Agora precisamos lidar com o resultado dos dados de acordo com a hostilidade do terreno para saber quantos pontos de experiência o explorador irá receber e então chamar o método de ganhar experiência.

class Explorer {
  constructor(name) {
    this.name = name
    this.level = 1
    this.experience = 0
		this.expToNextLevel = 110
    this.knownPlanets = []
    this.terrainExpertise = {}
    this.alive = true
  }

	rank() {
    if (this.level < 10) return 'Newbie'
    if (this.level < 30) return 'Explorer'
    if (this.level < 50) return 'Veteran'
    if (this.level < 80) return 'Elite'
    if (this.level < 99) return 'Master'
    return 'Legend'
  }

	gainExperience(pts) {
    this.experience += pts

    // Max level
    if (this.level === 99) {
      return
    }

    // Level up
    if (pts >= this.expToNextLevel) {
      this.level++
      const newExpToNextLevel = (100 + (10 * this.level)) - (pts - this.expToNextLevel)
      this.expToNextLevel = this.level !== 99 ? newExpToNextLevel : 0
      console.log(`Você subiu de nível! Experiência para o próximo nível: ${this.expToNextLevel}`)
    } else {
      this.expToNextLevel -= pts
    }
  }

	explore(planet) {
    // If dead exit early
    if (!this.alive) {
      console.log('You are dead.')
      return
    }

		const { id, hostility, terrain } = planet

    // Throw dices
    const dice1 = Math.floor(1 + Math.random() * 5)
    const dice2 = Math.floor(1 + Math.random() * 5)

    // Result with bonus (if applied)
    const bonus = this.terrainExpertise[terrain] > 2 ? 1 : 0
    const dices = dice1 + dice2 + bonus

    console.log(`Rolled ${dice1} and ${dice2} ${bonus ? '+1 bônus' : ''}`)

		// Check for critical
    if (dices >= 12) {
      this.terrainExpertise[terrain] = this.terrainExpertise[terrain] ? this.terrainExpertise[terrain] + 1 : 1
    }

    // Check for critical failure
    if (dices === 2 && hostility === 'hostile') {
      console.log('You died.')
      this.alive = false
      return
    }

		let obtainedExp = 0

		if (hostility === 'pacific') {
			obtainedExp = dices >= 5 ? 15 : 0
		} else if (hostility === 'neutral') {
			obtainedExp = dices >= 7 ? 25 : 0
		} else {
			obtainedExp = dices >= 9 ? 50 : 10
		}

		this.gainExperience(result)
	}
}

Finalmente, tudo o que precisamos fazer agora é lidar com o resultado em caso de sucesso ou falha. Como a menor quantidade de pontos de experiência possível de se obter em um resultado de sucesso é 15 podemos usar isso para determinar o sucesso da ação. Em caso de sucesso devemos adicionar o planeta ao array de planetas explorados caso ele já não esteja presente. Também podemos informar o resultado através de um console.log().

class Explorer {
  constructor(name) {
    this.name = name
    this.level = 1
    this.experience = 0
		this.expToNextLevel = 110
    this.knownPlanets = []
    this.terrainExpertise = {}
    this.alive = true
  }

	rank() {
    if (this.level < 10) return 'Newbie'
    if (this.level < 30) return 'Explorer'
    if (this.level < 50) return 'Veteran'
    if (this.level < 80) return 'Elite'
    if (this.level < 99) return 'Master'
    return 'Legend'
  }

	gainExperience(pts) {
    this.experience += pts

    // Max level
    if (this.level === 99) {
      return
    }

    // Level up
    if (pts >= this.expToNextLevel) {
      this.level++
      const newExpToNextLevel = (100 + (10 * this.level)) - (pts - this.expToNextLevel)
      this.expToNextLevel = this.level !== 99 ? newExpToNextLevel : 0
      console.log(`Você subiu de nível! Experiência para o próximo nível: ${this.expToNextLevel}`)
    } else {
      this.expToNextLevel -= pts
    }
  }

	explore(planet) {
    // If dead exit early
    if (!this.alive) {
      console.log('You are dead.')
      return
    }

		const { id, hostility, terrain } = planet

    // Throw dices
    const dice1 = Math.floor(1 + Math.random() * 5)
    const dice2 = Math.floor(1 + Math.random() * 5)

    // Result with bonus (if applied)
    const bonus = this.terrainExpertise[terrain] > 2 ? 1 : 0
    const dices = dice1 + dice2 + bonus

    console.log(`Rolled ${dice1} and ${dice2} ${bonus ? '+1 bônus' : ''}`)

		// Check for critical
    if (dices >= 12) {
      this.terrainExpertise[terrain] = this.terrainExpertise[terrain] ? this.terrainExpertise[terrain] + 1 : 1
    }

    // Check for critical failure
    if (dices === 2 && hostility === 'hostile') {
      console.log('You died.')
      this.alive = false
      return
    }

		let obtainedExp = 0

		if (hostility === 'pacific') {
			obtainedExp = dices >= 5 ? 15 : 0
		} else if (hostility === 'neutral') {
			obtainedExp = dices >= 7 ? 25 : 0
		} else {
			obtainedExp = dices >= 9 ? 50 : 10
		}

		this.gainExperience(result)

		// Handle result
    if (obtainedExp > 10) {
      const planetAlreadyExplored = this.knownPlanets.find(planet => planet.id === id)
  
      if (! planetAlreadyExplored) {
        this.knownPlanets.push(planet)
      }
  
      console.log(`Success! Earned ${obtainedExp} exp.`)
    } else {
      console.log(`Failure. Earned ${obtainedExp} exp.`)
    }
	}
}

Refatorando

Podemos melhorar nosso código acima em alguns pontos. Os pontos são:

Vejamos como fica nosso código após as modificações:

class Explorer {
  constructor(name) {
    this.name = name
    this.level = 1
    this.experience = 0
    this.expToNextLevel = 110
    this.knownPlanets = []
    this.terrainExpertise = {}
    this.alive = true
  }

  get rank() {
    if (this.level < 10) return 'Newbie'
    if (this.level < 30) return 'Explorer'
    if (this.level < 50) return 'Veteran'
    if (this.level < 80) return 'Elite'
    if (this.level < 99) return 'Master'
    return 'Legend'
  }

  static explorationHandler = {
    pacific: (diceResult) => diceResult >= 5 ? 15 : 0,
    neutral: (diceResult) => diceResult >= 7 ? 25 : 0,
    hostile: (diceResult) => diceResult >= 9 ? 50 : 10
  }

  gainExperience(pts) {
    this.experience += pts

    // Max level
    if (this.level === 99) {
      return
    }

    if (pts < this.expToNextLevel) {
      this.expToNextLevel -= pts
      return
    }

    // Level up
    this.level++

    const newExpToNextLevel = 100 + (10 * this.level) - (pts - this.expToNextLevel)

    // Reached Max level
    this.expToNextLevel = this.level !== 99 ? newExpToNextLevel : 0

    console.log(`Level up! You're now level ${this.level} Experience to next level: ${this.expToNextLevel}`)
  }

  explore(planet) {
    // Check if is alive
    if (!this.alive) {
      console.log('You are dead.')
      return
    }

    const { id, hostility, terrain } = planet

    // Throw dices
    const dice1 = Math.floor(1 + (Math.random() * 6))
    const dice2 = Math.floor(1 + (Math.random() * 6))

    // Result with bonus (if applied)
    const bonus = this.terrainExpertise[terrain] > 2 ? 1 : 0
    const dices = dice1 + dice2 + bonus

    console.log(`Rolled ${dice1} and ${dice2} ${bonus ? '+1 bonus' : ''}`)

    // Check for critical
    if (dices >= 12) {
      this.terrainExpertise[terrain] = this.terrainExpertise[terrain] + 1 || 1
    }

    // Check for critical failure
    if (dices === 2 && hostility === 'hostile') {
      console.log('You died.')
      this.alive = false
      return
    }

    // Handle exploration
    const handler = Explorer.explorationHandler[hostility]
    const obtainedExp = handler(dices)

    this.gainExperience(obtainedExp)

    // Handle result
    if (obtainedExp > 10) {
      const planetAlreadyExplored = this.knownPlanets.find(planet => planet.id === id)

      if (!planetAlreadyExplored) {
        this.knownPlanets.push(planet)
      }

      console.log(`Success! Earned ${obtainedExp} exp.`)
    } else {
      console.log(`Failure. Earned ${obtainedExp} exp.`)
    }
  }
}