Я делаю змейку для отработки паттерна MVC, а также выхожу из зоны комфорта (я занимаюсь обработкой данных / наукой о данных, в основном процедурным кодированием)
Игра доступна в https://nabla-f.github.io/
Это классическая игра в змейку, но с препятствиями на доске (в коде они называются блоками).
Я впервые использую MVC, поэтому все комментарии приветствуются. Кроме того, у меня возникли проблемы с реализацией следующих вещей, потому что я не знаю, где разместить эти функции (модель, представление или контроллер):
- Класс уровня (чтобы иметь возможность создавать разные этапы)
- Сообщения пользовательского интерфейса не связаны с игрой («лучше работает в ландшафтном режиме», «добро пожаловать в игру» и т. Д.)
- Все, что связано с будущим рабочим процессом, похожим на приложение (экраны загрузки, экран авторизации, функции, управляющие этим и т. Д.)
Я опускаю детали реализации, потому что мой вопрос касается дизайна SW, а не реализации каждой функции (например, я знаю, что использование холста лучше, чем отображение элементов html в каждом цикле и т. Д.). Вот код:
models.js
const DIRECTIONS = {
// an object representing directions (left, right, etc)
}
class Snake {
constructor() {
this.body = // Stores coordinates (an array) that represent snake position (i.e. an array of arrays)
this.len = this.body.length
this.head = this.body[0]
this.last = this.body[this.len-1]
this.direction = // Direction in which snake moves
this.toDigest = [] // Stores coordinates that snake should add in the following cycle
}
updateDirection(direction) {
this.direction = direction
}
updatePosition() {
// Updates snakes body to a new position according to his direction
}
updateData() {
this.len = this.body.length
this.head = this.body[0]
this.last = this.body[this.len-1]
}
eatFruit(fruitCoord) {
// Eats a fruit
}
checkToDigest() {
//
}
grow() {
this.body.push(Array.from(this.toDigest[0]))
this.toDigest.shift()
}
}
class Board {
constructor(width, height) {
this.width = width
this.height = height
this.fruits = new Fruits()
this.coords = // Stores coordinates of board. Each coordinate is an array
this.blocks = // Stores coordinates of blocking elements
}
generateFruits(forbiddenCoords) {
this.fruits.generateFruits()
}
removeFruits() {
this.fruits.removeFruits()
}
}
class Fruits {
constructor() {
this.fruits = [] // Stores a list of fruits
}
generateFruits(maxWidth, maxHeight, forbiddenCoords) {
// Generate a fruit coordinante that:
// - Isn't the same as a block coordinate
// - Isn't the same as a snake body coordinate
}
checkFruits() {
if (this.fruits) {return true}
else {return false}
}
removeFruits() {
// Remove a fruit for the list of fruits
}
}
export { Snake, Board, DIRECTIONS }
views.js
class BoardDrawer {
constructor(HTMLelem, snake, board) {
this.main = HTMLelem
this.board = board
this.snake = snake
}
drawCells() {
// draw cells of the board
}
drawSnake() {
// draws snake in board
}
drawBlocks() {
// draws blocks in board
}
drawFruits() {
// draws fruits in board
}
eraseSnake() {
// erase the snake in board
}
eraseFruits() {
// erase the fruits in board
}
}
export { BoardDrawer }
controllers.js
import { DIRECTIONS } from './models.js'
class InputHandler {
constructor(deniedKey) {
this.deniedKey = deniedKey // last key pressed
}
userInput = (key) => {
// translates an arrow key into direction coordinate
}
}
class ObstaclesChecker {
constructor(snake, board) {
this.snake = snake.body
this.boundaries = {
left: -1,
right: board.width,
up: -1,
down: board.height,
}
this.snakeWithoutHead = []
this.blocks = board.blocks
this.fruits = board.fruits.fruits
}
checkBoundaryCollision(snakeHead) {
// Checks if snake collides with boundaries
}
checkSelfCollision(snakeHead) {
// Checks if snake collides with itself
}
checkBlockCollision(snakeHead) {
// Checks if snake collides with a block
}
checkFruitCollision(snakeHead) {
// Checks if snake collides with a fruit
}
}
export { InputHandler, ObstaclesChecker }
app.js
import { Snake, Board } from './js/models.js'
import { BoardDrawer } from './js/views.js'
import { InputHandler, ObstaclesChecker } from './js/controllers.js'
// VARIABLE INIT
let boardHTMLelement = document.querySelector('#board');
let snake = new Snake()
let board = new Board(10, 10)
let obstacles = new ObstaclesChecker(snake, board)
let boardDrawer = new BoardDrawer(boardHTMLelement, snake, board)
let inputHandler = new InputHandler('ArrowLeft')
window.onload = () => {
// INITIALIZE GAME
boardDrawer.drawCells()
boardDrawer.drawBlocks()
board.generateFruits()
boardDrawer.drawFruits()
boardDrawer.drawSnake()
startGame()
};
/// GAME LOOP
function gameLoop(timeStamp){
// SNAKE AND FRUITS LOGIC BLOCK
boardDrawer.eraseSnake()
boardDrawer.eraseFruits()
// check digestion
if (snake.checkToDigest()) {
console.log('The snake has a new block')
snake.updatePosition()
snake.grow()
snake.updateData()
} else {
snake.updatePosition()
snake.updateData()
}
// check new fruit
if (obstacles.checkFruitCollision(snake.head)) {
console.log('The snake found a fruit')
snake.eatFruit(snake.head)
board.removeFruits()
board.generateFruits([snake.toDigest, board.blocks])
}
// COLLISION CHECKING BLOCK
if (
obstacles.checkSelfCollision(snake.head) ||
obstacles.checkBoundaryCollision(snake.head) ||
obstacles.checkBlockCollision(snake.head)
)
{
console.log('u lose')
loserFunction()
}
// DRAWING BLOCK
boardDrawer.drawFruits()
boardDrawer.drawSnake()
// Keep requesting new frames
setTimeout(window.requestAnimationFrame, 250, gameLoop);
}
/// Things that I doesn't know where to put in :(
let startGame = (event) => {
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
window.requestAnimationFrame(gameLoop)
document.removeEventListener('keydown', startGame)
}
};
let startGameListener = () => {
document.addEventListener('keydown', startGame)
}
const loserFunction = () => {
// Delete the screen when user loses, show "you lose" message and so on...
}
Если поможет, вот репо: https://github.com/nabla-f/nabla-f.github.io
Спасибо заранее
