JavaScript OOD: 2048

Я написал игру 2048 года на JavaScript с объектно-ориентированной парадигмой. Игровое поле представлено двумерным массивом, и каждая плитка содержит целое число.

Вот реализация:

class Game {
  SIZE = 4
  constructor() {
    this.board = Array.from({ length: this.SIZE * this.SIZE }, () => 0).reduce(
      (arrays, curr) => {
        const lastArray = arrays[arrays.length - 1]
        if (lastArray.length < this.SIZE) lastArray.push(curr)
        else arrays.push([curr])
        return arrays
      },
      [[]]
    )
    this.isWin = false
    this._init()
  }

  _init() {
    const pickedTiles = this._randomlyPick(2)
    for (const [row, col] of pickedTiles) {
      this.board[row][col] = Game.generateTile()
    }
  }

  static generateTile() {
    if (Math.random() > 0.5) return 2
    return 4
  }

  _getEmptyTiles() {
    const emptyTiles = []
    for (let row = 0; row < this.SIZE; row++) {
      for (let col = 0; col < this.SIZE; col++) {
        if (this.board[row][col] === 0) emptyTiles.push([col, row])
      }
    }
    return emptyTiles
  }

  _randomlyPick(numOfItems) {
    const emptyTiles = this._getEmptyTiles()
    for (let i = 0; i < numOfItems; i++) {
      const toSwap = i + Math.floor(Math.random() * (emptyTiles.length - i))
      ;[emptyTiles[i], emptyTiles[toSwap]] = [emptyTiles[toSwap], emptyTiles[i]]
    }
    return emptyTiles.slice(0, numOfItems)
  }

  spawn() {
    // randomly spawn empty tiles with 2 or 4
    const [emtpyTile] = this._randomlyPick(1)
    this.board[emtpyTile[0]][emtpyTile[1]] = Game.generateTile()
  }

  play(dir) {
    if (this.canPlay()) {
      switch (dir) {
        case Game.moveUp:
          this._mergeUp()
          break
        case Game.moveRight:
          this._mergeRight()
          break
        case Game.moveLeft:
          this._mergeLeft()
          break
        case Game.moveDown:
          this._mergeDown()
          break
      }
      this.spawn()

      return true
    }
    return false
  }
  checkIsWin() {
    return this.isWin
  }

  static peek(array) {
    return array[array.length - 1]
  }

  static zip(arrays) {
    const result = []
    for (let i = 0; i < arrays[0].length; ++i) {
      result.push(arrays.map((array) => array[i]))
    }
    return result
  }

  _mergeRowRight(sparseRow) {
    const row = sparseRow.filter((x) => x !== 0)
    const result = []
    while (row.length) {
      let value = row.pop()
      if (Game.peek(row) === value) value += row.pop()
      result.unshift(value)
    }

    while (result.length < 4) result.unshift(0)
    return result
  }

  _mergeRowLeft(row) {
    return this._mergeRowRight([...row].reverse()).reverse()
  }

  _mergeUp() {
    this.board = Game.zip(Game.zip(this.board).map(row => this._mergeRowLeft(row)))
  }
  _mergeDown() {
    this.board = Game.zip(Game.zip(this.board).map(row => this._mergeRight(row)))
  }

  _mergeRight() {
    this.board = this.board.map((row) => this._mergeRowRight(row))
  }
  _mergeLeft() {
    this.board = this.board.map((row) => this._mergeRowLeft(row))
  }

  canPlay() {
    const dirs = [
      [0, 1],
      [1, 0],
      [-1, 0],
      [0, -1],
    ]
    const visited = new Set()
    for (let row = 0; row < this.SIZE; row++) {
      for (let col = 0; col < this.SIZE; col++) {
        if (visited.has([row, col].toString())) continue
        const tile = this.board[row][col]
        if (tile === 2048) {
          this.isWin = true
          return false
        }
        if (tile === 0) return true
        for (const [dx, dy] of dirs) {
          if (this.board[row + dx]?.[col + dy] === tile) return true
        }
        visited.add([row, col].toString())
      }
    }
    return false
  }
}

Game.moveUp = Symbol('moveUp')
Game.moveDown = Symbol('moveUp')
Game.moveLeft = Symbol('moveUp')
Game.moveRight = Symbol('moveUp')

const game = new Game()
console.log(game.board);
game.play(Game.moveUp)
console.log(game.board);
game.play(Game.moveRight)
console.log(game.board);

Любые отзывы приветствуются. В частности, хотелось бы знать:

  1. Идиоматично ли в объектно-ориентированном стиле использовать статические методы для хранения служебных функций, таких как zip перевернуть доску по диагонали.
  2. Есть ли лучший способ структурировать класс? Мне кажется, что я не очень хорошо разбираюсь в логике различных действий.
  3. Есть ли какой-то конкретный шаблон проектирования, который я могу использовать для улучшения класса?
  4. Наконец, я использую symbol чтобы представить направление движения, которое пользователь может сделать, и прикрепить их как переменную экземпляра к классу. Опять же, я не уверен, является ли это идиоматикой в ​​объектно ориентированном стиле, поскольку я новичок в этой парадигме.

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *