diff --git a/snake/index.html b/snake/index.html new file mode 100644 index 0000000..be46955 --- /dev/null +++ b/snake/index.html @@ -0,0 +1,22 @@ + + + + + + + + Document + + +
+
+
+
+ + + + + diff --git a/snake/snake.js b/snake/snake.js new file mode 100644 index 0000000..e1c0da6 --- /dev/null +++ b/snake/snake.js @@ -0,0 +1,344 @@ +"use strict"; + +let snake = { + body: null, + direction: null, + lastStepDirection: null, + + init(startPoint, direction) { + this.body = [startPoint]; + this.direction = direction; + }, + + makeStep() { + + + this.lastStepDirection = this.direction; + this.body.unshift(this.getNextStepHeadPoint()); + this.body.pop(); + }, + + getNextStepHeadPoint() { + let firstPoint = this.body[0]; + + switch (this.direction) { + case 'up': + if (firstPoint.y != (settings.rowsCount - settings.rowsCount)) { + return { x: firstPoint.x, y: firstPoint.y - 1 }; + } else { + return { x: firstPoint.x, y: firstPoint.y + (settings.rowsCount - 1) }; + } + case 'down': + if (firstPoint.y != settings.rowsCount - 1) { + return { x: firstPoint.x, y: firstPoint.y + 1 }; + } else { + return { x: firstPoint.x, y: firstPoint.y - (settings.rowsCount - 1) }; + } + case 'right': + if (firstPoint.x != (settings.colsCount - 1)) { + return { x: firstPoint.x + 1, y: firstPoint.y }; + } else { + return { x: firstPoint.x - (settings.colsCount - 1), y: firstPoint.y }; + } + case 'left': + if (firstPoint.x != (settings.colsCount - settings.colsCount)) { + return { x: firstPoint.x - 1, y: firstPoint.y }; + } else { + return { x: firstPoint.x + (settings.colsCount - 1), y: firstPoint.y }; + } + } + }, + + isBodyPoint(point) { + return this.body.some(snakePoint => snakePoint.x === point.x && snakePoint.y === point.y); + }, + + setDirection(direction) { + this.direction = direction; + }, + + incrementBody() { + let lastBodyIdx = this.body.length - 1; + let lastBodyPoint = this.body[lastBodyIdx]; + let lastBodyPointClone = Object.assign({}, lastBodyPoint); + this.body.push(lastBodyPointClone); + } +}; + +let food = { + x: null, + y: null, + + setFoodCoordinates(point) { + this.x = point.x; + this.y = point.y; + }, + + getFoodCoordinates() { + return { + x: this.x, + y: this.y + } + }, + + isFoodPoint(point) { + return this.x === point.x && this.y === point.y; + } +}; + +let renderer = { + cells: {}, + renderMap(rowsCount, colsCount) { + let table = document.getElementById('game'); + table.innerHTML = ''; + + for (let row = 0; row < rowsCount; row++) { + let newRow = table.insertRow(row); + newRow.className = 'row'; + for (let col = 0; col < colsCount; col++) { + let newCell = newRow.insertCell(col); + newCell.className = 'cell'; + this.cells[`x${col}_y${row}`] = newCell; + } + } + }, + + render(snakePointArray, foodPoint) { + for (let key of Object.getOwnPropertyNames(this.cells)) { + this.cells[key].className = 'cell'; + } + + snakePointArray.forEach((point, idx) => { + this.cells[`x${point.x}_y${point.y}`].classList.add(idx === 0 ? 'snakeHead' : 'snakeBody'); + }); + + this.cells[`x${foodPoint.x}_y${foodPoint.y}`].classList.add('food'); + }, + + renderScore(initialScore) { + let scoreDiv = document.getElementById('score'); + scoreDiv.innerHTML = 'Ваш текущий счет: ' + initialScore; + } +}; + +let status = { + condition: null, + + setPlaying() { + this.condition = 'playing'; + }, + + setStopped() { + this.condition = 'stopped'; + }, + + setFinished() { + this.condition = 'finished'; + }, + + isPlaying() { + return this.condition === 'playing'; + }, + + isStopped() { + return this.condition === 'stopped'; + } +}; + +let settings = { + rowsCount: 21, + colsCount: 21, + speed: 2, + winLength: 10, + score: 0, + + validate() { + if (this.rowsCount < 10 || this.rowsCount > 30) { + console.error('Низя так'); + return false; + } + + if (this.colsCount < 10 || this.colsCount > 30) { + console.error('Низя так'); + return false; + } + + if (this.speed < 1 || this.speed > 10) { + console.error('Низя так'); + return false; + } + + if (this.winLength < 5 || this.winLength > 50) { + console.error('Низя так'); + return false; + } + return true; + }, +}; + +let game = { + settings, + status, + renderer, + food, + snake, + tickInterval: null, + + init(userSettings = {}) { + Object.assign(this.settings, userSettings); + if (!this.settings.validate()) { + return; + } + this.renderer.renderMap(this.settings.rowsCount, this.settings.colsCount); + this.renderer.renderScore(this.settings.score); + this.setEventHandlers(); + + this.reset(); + }, + + reset() { + this.snake.init(this.getStartSnakePoint(), 'up'); + this.food.setFoodCoordinates(this.getRandomCoordinates()); + + this.renderer.render(this.snake.body, this.food.getFoodCoordinates()); + }, + + setEventHandlers() { + document.getElementById('playButton').addEventListener('click', () => this.playClickHandler()); + document.getElementById('newGameButton').addEventListener('click', () => this.newGameClickHandler()); + document.addEventListener('keydown', () => this.keyDownHandler(event)); + }, + + playClickHandler() { + if (this.status.isPlaying()) { + this.stop(); + } else { + this.play(); + } + }, + + newGameClickHandler() { + this.settings.score = 0; + this.reset(); + }, + + keyDownHandler(event) { + if (!this.status.isPlaying()) { + return; + } + + let direction = this.getDirectionByCode(event.code); + if (this.canSetDirection(direction)) { + this.snake.setDirection(direction); + } + }, + + canSetDirection(direction) { + return direction === 'up' && this.snake.lastStepDirection !== 'down' || + direction === 'right' && this.snake.lastStepDirection !== 'left' || + direction === 'down' && this.snake.lastStepDirection !== 'up' || + direction === 'left' && this.snake.lastStepDirection !== 'right'; + }, + + getDirectionByCode(code) { + switch (code) { + case 'KeyW': + case 'ArrowUp': + return 'up'; + case 'KeyD': + case 'ArrowRight': + return 'right'; + case 'KeyS': + case 'ArrowDown': + return 'down'; + case 'KeyA': + case 'ArrowLeft': + return 'left'; + default: + return ''; + } + }, + + play() { + this.status.setPlaying(); + + this.tickInterval = setInterval(() => this.tickHandler(), 1000 / this.settings.speed); + + this.changePlayButton('Стоп'); + }, + + tickHandler() { + if (!this.canSnakeMakeStep()) { + this.finish(); + return; + } + + if (this.food.isFoodPoint(this.snake.getNextStepHeadPoint())) { + this.renderer.renderScore(++this.settings.score); + this.snake.incrementBody(); + this.food.setFoodCoordinates(this.getRandomCoordinates()); + if (this.isGameWon()) { + this.finish(); + } + } + + this.snake.makeStep(); + this.renderer.render(this.snake.body, this.food.getFoodCoordinates()); + }, + + isGameWon() { + return this.snake.body.length > this.settings.winLength; + }, + + stop() { + this.status.setStopped(); + clearInterval(this.tickInterval); + this.changePlayButton('Старт'); + }, + + finish() { + this.settings.score = 0; + this.status.setFinished(); + clearInterval(this.tickInterval); + this.changePlayButton('Игра закончена', true); + }, + + getStartSnakePoint() { + return { + x: Math.floor(this.settings.colsCount / 2), + y: Math.floor(this.settings.rowsCount / 2) + } + }, + + getRandomCoordinates() { + let exclude = [this.food.getFoodCoordinates(), ...this.snake.body]; + + while (true) { + let rndPoint = { + x: Math.floor(Math.random() * this.settings.colsCount), + y: Math.floor(Math.random() * this.settings.rowsCount) + } + + let excludeContainsRndPoint = exclude.some(function (exPoint) { + return exPoint.x === rndPoint.x && exPoint.y === rndPoint.y; + }); + + if (!excludeContainsRndPoint) return rndPoint; + } + }, + + changePlayButton(textContent, isDisabled = false) { + let playButton = document.getElementById('playButton'); + playButton.textContent = textContent; + isDisabled ? playButton.classList.add('disabled') : playButton.classList.remove('disabled'); + }, + + canSnakeMakeStep() { + let nextHeadPoint = this.snake.getNextStepHeadPoint(); + + return !this.snake.isBodyPoint(nextHeadPoint); + } + +}; + +window.onload = () => game.init({ speed: 5, winLength: 5 }); diff --git a/snake/style.css b/snake/style.css new file mode 100644 index 0000000..0c7b279 --- /dev/null +++ b/snake/style.css @@ -0,0 +1,58 @@ +* { + margin: 0; + padding: 0; + background: gray; +} + +.menuButton { + width: 150px; + padding: 15px; + margin: 10px; + color: white; + background: #007b32; + text-align: center; + cursor: pointer; +} + +.menuButton:hover { + background: #009641; +} + +#game { + border-collapse: collapse; + margin: 2% auto 0; +} + +#menu { + display: flex; + justify-content: center; +} + +td.cell { + width: 20px; + height: 20px; + border: 1px dotted #eee; + border-collapse: collapse; +} + +.food { + background: #00d021; +} + +.snakeBody { + background: #e5e5e5; + border-radius: 10px; +} + +.snakeHead { + background: #9b0013; + border-radius: 10px; +} + +.disabled { + background: #7c7c7c; +} + +.disabled:hover { + background: #7c7c7c; +}