legacy/outils/sokoban/Mobile.js

/**
 * @typedef AnimationOptions
 * @property {number} dureeDeplacement
 * @property {number} dureeAnimation
 * @property {number} delai
 */

/**
 * @typedef ImagePosition
 * @property {number} x
 * @property {number} y
 * @property {number} direction
 */
/**
 * Pour les objets mobilesCoords
 * @param {Sokoban} sokoban
 * @param {ImagePosition} position
 * @param {AnimationOptions} animation
 * @param {boolean} isAvatar
 * @constructor
 */
function Mobile (sokoban, position, animation, isAvatar) {
  const image = isAvatar ? sokoban.persoElt : sokoban.chatElt
  if (!(image instanceof HTMLImageElement)) throw Error('sokoban non initialisé')
  this.sokoban = sokoban
  this.image = image
  this.x = position.x
  this.y = position.y
  this.direction = position.direction
  this.etat = -1// immobile
  this.surplace = false
  this.decalageY = position.decalageY
  this.largeur = this.image.width / 4
  this.hauteur = this.image.height / 4
  this.animation = animation
}

Mobile.prototype.dessine = function (context) {
  if (!this.image) throw Error('Aucune image à dessiner')
  const c = this.sokoban.constantes
  let frame = 0
  // Numéro de l’image à prendre pour l’animation

  let decalageX = 0
  let decalageY = this.decalageY
  // Décalage à appliquer à la position du personnage
  if (this.etat >= this.animation.dureeDeplacement) {
    // Si le déplacement a atteint ou dépassé le temps nécessaire pour s’effectuer, on le termine
    this.etat = -1
  } else if (this.etat >= 0) {
    // On calcule l’image (frame) de l’animation à afficher
    frame = Math.floor(this.etat / this.animation.dureeAnimation) % 4

    // Nombre de pixels restant à parcourir entre les deux cases
    const pixelsAParcourir = 48 - (48 * (this.etat / this.animation.dureeDeplacement))

    // À partir de ce nombre, on définit le décalage en x et y.
    if (this.direction === c.haut) {
      decalageY = this.surplace ? 0 : pixelsAParcourir
    } else if (this.direction === c.bas) {
      decalageY = this.surplace ? 0 : -pixelsAParcourir
    } else if (this.direction === c.gauche) {
      decalageX = this.surplace ? 0 : pixelsAParcourir
    } else if (this.direction === c.droite) {
      decalageX = this.surplace ? 0 : -pixelsAParcourir
    }
    // On incrémente d’une frame
    this.etat++
  }

  // @todo voir pourquoi ça plante de temps en temps avec firefox
  // IndexSizeError: Index or size is negative or greater than the allowed amount
  // cf https://app.bugsnag.com/sesamath/j3p/errors/616550934100b500078554ae
  try {
    context.drawImage(
      this.image,
      frame * this.largeur, // sx
      this.direction * this.hauteur, // sy
      this.largeur, // swidth
      this.hauteur, // sheight
      // Point de destination (dépend de la taille du personnage)
      (this.x * 48) + decalageX + 8, // x
      Math.max(((this.y + 1) * 48) - this.hauteur + decalageY, 0), // y
      this.largeur,
      this.hauteur
    )
  } catch (error) {
    console.error(error)
  }
}

Mobile.prototype.getCoordonneesAdjacentes = function (direction) {
  const c = this.sokoban.constantes
  const coord = { x: this.x, y: this.y }
  switch (direction) {
    case c.bas :
      coord.y++
      break
    case c.gauche:
      coord.x--
      break
    case c.droite :
      coord.x++
      break
    case c.haut :
      coord.y--
      break
  }
  return coord
}

/**
 *
 * @param direction
 * @param {Sokoban} sokoban
 * @return {boolean}
 */
Mobile.prototype.deplacer = function (direction, sokoban) {
  // On ne peut pas se déplacer si un mouvement est déjà en cours !
  if (this.etat >= 0) {
    return false
  }

  // On change la direction du personnage
  this.direction = direction

  // On vérifie que la case demandée est bien située dans la carte
  const prochaineCase = this.getCoordonneesAdjacentes(direction)

  if (!sokoban.libre(prochaineCase.x, prochaineCase.y, direction)) {
    // if (prochaineCase.x < 0 || prochaineCase.y < 0 || prochaineCase.x >= 10 || prochaineCase.y >= 10) {
    this.surplace = true
    this.etat = 1
    // pour bouger sur place
    return false
  }
  this.surplace = false
  // On commence l’animation
  this.etat = 1
  sokoban.scene[this.y][this.x] = '-'
  // On effectue le déplacement
  this.x = prochaineCase.x
  this.y = prochaineCase.y

  if (sokoban.correction()) {
    sokoban.feedbackElt.innerText = 'Bravo  !'
    const hasNext = (sokoban.tableauCourant + 1) < sokoban.tableaux.length
    if (hasNext) {
      if (sokoban.tableauMax < sokoban.tableauCourant + 1) sokoban.tableauMax = sokoban.tableauCourant + 1
      if (sokoban.nextBtn) sokoban.resetNav()
      // sinon on passe au suivant d’office
      else setTimeout(sokoban.change.bind(sokoban, true), 3000)
    }
  }

  return true
}

export default Mobile