|  | 
|  | 1 | +const { Queue } = require('../../src/index'); | 
|  | 2 | + | 
|  | 3 | +// tag::description[] | 
|  | 4 | +/** | 
|  | 5 | + * The snake game stars with a snake of length 1 at postion 0,0. | 
|  | 6 | + * Only one food position is shown at a time. Once it's eaten the next one shows up. | 
|  | 7 | + * The snake can move in four directions up, down, left and right. | 
|  | 8 | + * If the snake go out of the boundaries (width x height) the game is over. | 
|  | 9 | + * If the snake hit itself the game is over. | 
|  | 10 | + * When the game is over, the `move` method returns -1 otherwise, return the current score. | 
|  | 11 | + * | 
|  | 12 | + * @example | 
|  | 13 | + *  const snakeGame = new SnakeGame(3, 2, [[1, 2], [0, 1]]); | 
|  | 14 | + *  snakeGame.move('R'); //  0 | 
|  | 15 | + *  snakeGame.move('D'); //  0 | 
|  | 16 | + *  snakeGame.move('R'); //  0 | 
|  | 17 | + *  snakeGame.move('U'); //  1 (ate the food1) | 
|  | 18 | + *  snakeGame.move('L'); //  2 (ate the food2) | 
|  | 19 | + *  snakeGame.move('U'); // -1 (hit the upper wall) | 
|  | 20 | + */ | 
|  | 21 | +class SnakeGame { | 
|  | 22 | +// end::description[] | 
|  | 23 | +// tag::solution[] | 
|  | 24 | + | 
|  | 25 | +  // end::solution[] | 
|  | 26 | +  // tag::description[] | 
|  | 27 | +  /** | 
|  | 28 | +   * Initialize game with grid's dimension and food order. | 
|  | 29 | +   * @param {number} width - The screen width (grid's columns) | 
|  | 30 | +   * @param {number} height - Screen height (grid's rows) | 
|  | 31 | +   * @param {number[]} food - Food locations. | 
|  | 32 | +   */ | 
|  | 33 | +  constructor(width, height, food) { | 
|  | 34 | +    // end::description[] | 
|  | 35 | +    // tag::solution[] | 
|  | 36 | +    this.width = width; | 
|  | 37 | +    this.height = height; | 
|  | 38 | +    this.food = new Queue(food); | 
|  | 39 | +    this.snake = new Queue([[0, 0]]); | 
|  | 40 | +    this.tail = new Set([[0, 0]]); | 
|  | 41 | +    this.dirs = { | 
|  | 42 | +      U: [-1, 0], D: [1, 0], R: [0, 1], L: [0, -1], | 
|  | 43 | +    }; | 
|  | 44 | +    // end::solution[] | 
|  | 45 | +    // tag::description[] | 
|  | 46 | +  } | 
|  | 47 | +  // end::description[] | 
|  | 48 | + | 
|  | 49 | +  // tag::description[] | 
|  | 50 | +  /** | 
|  | 51 | +   * Move snake 1 position into the given direction. | 
|  | 52 | +   * It returns the score or game over (-1) if the snake go out of bound or hit itself. | 
|  | 53 | +   * @param {string} direction - 'U' = Up, 'L' = Left, 'R' = Right, 'D' = Down. | 
|  | 54 | +   * @returns {number} - The current score (snake.length - 1). | 
|  | 55 | +   */ | 
|  | 56 | +  move(direction) { | 
|  | 57 | +    // end::description[] | 
|  | 58 | +    // tag::solution[] | 
|  | 59 | +    let [r, c] = this.snake.back(); // head of the snake | 
|  | 60 | +    [r, c] = [r + this.dirs[direction][0], c + this.dirs[direction][1]]; | 
|  | 61 | + | 
|  | 62 | +    // check wall collision | 
|  | 63 | +    if (r < 0 || c < 0 || r >= this.height || c >= this.width) return -1; | 
|  | 64 | + | 
|  | 65 | +    const [fr, fc] = this.food.front() || []; // peek | 
|  | 66 | +    if (r === fr && c === fc) { | 
|  | 67 | +      this.food.dequeue(); // remove eaten food. | 
|  | 68 | +    } else { | 
|  | 69 | +      this.snake.dequeue(); // remove snake's if not food was eaten | 
|  | 70 | +      this.tail.delete(this.tail.keys().next().value); | 
|  | 71 | +    } | 
|  | 72 | + | 
|  | 73 | +    // check collision with snake's tail | 
|  | 74 | +    if (this.tail.has(`${r},${c}`)) return -1; // O(1) | 
|  | 75 | + | 
|  | 76 | +    this.snake.enqueue([r, c]); // add new position | 
|  | 77 | +    this.tail.add(`${r},${c}`); | 
|  | 78 | + | 
|  | 79 | +    return this.snake.size - 1; // return score (length of the snake - 1) | 
|  | 80 | +    // end::solution[] | 
|  | 81 | +    // tag::description[] | 
|  | 82 | +  } | 
|  | 83 | +} | 
|  | 84 | +// end::description[] | 
|  | 85 | + | 
|  | 86 | +module.exports = { SnakeGame }; | 
0 commit comments