Skip to main content
Code Review

Return to Question

Notice removed Draw attention by Community Bot
Bounty Ended with Blindman67's answer chosen by Community Bot
added 1 character in body; edited tags
Source Link

I wrote a 2048 game in JavaScript with aan object-oriented paradigm. The game board is presented with a two-dimensional array and each tile holds an integer.

  1. Is it idiomatic in OO to use static methods to store util functions such as zip to flip the board along its diagonal.?
  2. Is there a better way to structure the class? I feel like I am not doing a great job at dividing some of the logic of different actions.
  3. Is there any specific design pattern that I can use to improve the class?
  4. Lastly, I am using symbol to present the direction of the move the user can make and attach them as the instance variable to the class. Again I am not sure if this is idiomatic in OO since I am kinda new to this paradigm.

I wrote a 2048 game in JavaScript with a object-oriented paradigm. The game board is presented with a two-dimensional array and each tile holds an integer.

  1. Is it idiomatic in OO to use static methods to store util functions such as zip to flip the board along its diagonal.
  2. Is there a better way to structure the class? I feel like I am not doing a great job at dividing some of the logic of different actions.
  3. Is there any specific design pattern that I can use to improve the class?
  4. Lastly, I am using symbol to present the direction of the move the user can make and attach them as the instance variable to the class. Again I am not sure if this is idiomatic in OO since I am kinda new to this paradigm.

I wrote a 2048 game in JavaScript with an object-oriented paradigm. The game board is presented with a two-dimensional array and each tile holds an integer.

  1. Is it idiomatic in OO to use static methods to store util functions such as zip to flip the board along its diagonal?
  2. Is there a better way to structure the class? I feel like I am not doing a great job at dividing some of the logic of different actions.
  3. Is there any specific design pattern that I can use to improve the class?
  4. Lastly, I am using symbol to present the direction of the move the user can make and attach them as the instance variable to the class. Again I am not sure if this is idiomatic in OO since I am kinda new to this paradigm.
Tweeted twitter.com/StackCodeReview/status/1367943018574213123
Notice added Draw attention by Joji
Bounty Started worth 50 reputation by Joji
Source Link
Joji
  • 335
  • 2
  • 15

JavaScript OOD: 2048

I wrote a 2048 game in JavaScript with a object-oriented paradigm. The game board is presented with a two-dimensional array and each tile holds an integer.

Here is the implementation:

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

Any feedback is welcomed. Specifically, I would like to know:

  1. Is it idiomatic in OO to use static methods to store util functions such as zip to flip the board along its diagonal.
  2. Is there a better way to structure the class? I feel like I am not doing a great job at dividing some of the logic of different actions.
  3. Is there any specific design pattern that I can use to improve the class?
  4. Lastly, I am using symbol to present the direction of the move the user can make and attach them as the instance variable to the class. Again I am not sure if this is idiomatic in OO since I am kinda new to this paradigm.
default

AltStyle によって変換されたページ (->オリジナル) /