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;
+}