javascript – Snake game MVC (Vanilla JS) – Help separating concerns

I’m making a snake game for practicing the MVC pattern and also going out of my comfort zone (I’m in data processing / data science, mostly procedural coding)

The game is available in https://nabla-f.github.io/

This is a classical snake game, but with obstacles in the board (called blocks in the code)

This is my first time implementing MVC, so all comments are appreciated. Also, I’m having some trouble implementing the following things, cause I doesn’t know where to put this functions (model, view or controller):

  • A level class (to be able to create different stages)
  • UI messages doesn’t related to game (“this works better in landscape mode”, “welcome to the game”, etc)
  • All things related to a future “app-alike” workflow (loading screens, auth screen, functions controlling this, etc)

I’m ommiting implementation details because my question are centered about sw design rather than implementation of each function (e.g. I know that using canvas is better than render html elements in each cycle, etc). Here’s the code:

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...
}

If helps, here’s the repo: https://github.com/nabla-f/nabla-f.github.io

Many thanks in advance

machine learning – Separating objects into a constrained number of groups where data is known about all object pairs

In Frequency Planning, we may know the interference between one cell and another cell. You may be a able to operate a specific number of frequencies. How would one separate them into the best groups. I have been toying with Classify but am unable to see how to program it to perform the required grouping.

I have a program to produce sample data.

Combinations(all___) := Flatten(Outer(List, all, 1), Length({all}) - 1);
all=ToString/@Range(6);
groups=ToString(#)&/@Range(2);
contents=Association();
(contents(#)=groups((1)))&/@all((;;3));
(contents(#)=groups((2)))&/@all((4;;));
contents;
reverseContents=contents//Normal//GroupBy(Last->First);
makeValues({a_,b_}):=Module({},
If(a==b,Missing(),
c=contents(a)==contents(b);
If(c,
{a,b,RandomReal({-28,-50}),{a,b}->"same group"},
{a,b,RandomReal({0,-30}),{a,b}->"different group"}
)));
training=Select(makeValues/@Combinations(all,all),Not(MissingQ(#))&);

The data as I have programmed gives the following dataset where the fourth column is the recommendation. Either the nodes should be in the same group or they should be in different groups.

Grid(training)
1   2   -36.0319    {1,2}->same group
1   3   -33.4696    {1,3}->same group
1   4   -26.8633    {1,4}->different group
1   5   -18.9969    {1,5}->different group
1   6   -14.5339    {1,6}->different group
2   1   -42.4544    {2,1}->same group
2   3   -32.0968    {2,3}->same group
2   4   -25.3401    {2,4}->different group
2   5   -19.9827    {2,5}->different group
2   6   -7.73741    {2,6}->different group
3   1   -49.5421    {3,1}->same group
3   2   -41.9788    {3,2}->same group
3   4   -13.1989    {3,4}->different group
3   5   -18.0183    {3,5}->different group
3   6   -10.5025    {3,6}->different group
4   1   -17.6852    {4,1}->different group
4   2   -0.997097   {4,2}->different group
4   3   -5.0447     {4,3}->different group
4   5   -47.2881    {4,5}->same group
4   6   -43.6139    {4,6}->same group
5   1   -20.8662    {5,1}->different group
5   2   -29.4012    {5,2}->different group
5   3   -4.96949    {5,3}->different group
5   4   -31.5497    {5,4}->same group
5   6   -29.7002    {5,6}->same group
6   1   -3.60692    {6,1}->different group
6   2   -28.6723    {6,2}->different group
6   3   -12.8056    {6,3}->different group
6   4   -44.3089    {6,4}->same group
6   5   -48.3195    {6,5}->same group

This is a simple example I plan to expand to hundreds of cells and up to 10 groups. Anybody’s help would be much appreciated.

Separating the even and odd numbers in an array in C

I wrote a working program in C that takes an array with size 2^n, and separates all the even numbers from the odd numbers.
For example: Input: {1,2,3,4,5,6,7,8}, Output: {8,2,6,4,5,3,7,1}
I want my program to be as effecient as possible and to be with space complexity O(log n) and time complexity of O(n log n)

This is the code in C:

#include <stdio.h>

void swap(int*, int*);
void arrange(int arr(), int n);

int main() {
    int arr() = {1,2,3,4,5,6,7,8};
    int size = sizeof(arr)/sizeof(arr(0));
    arrange(arr,size);
    
    return 0;
}

void arrange(int arr(), int n) {
    
    if (n==1) return;
    
    int istart = 0, iend = n-1;
    while (istart <= iend) {
    if (arr(istart) % 2 == 0) {
        istart++;
        return arrange(arr+istart,n-1);
    }
    else if (arr(iend) % 2 == 1) {
        iend--;
        return arrange(arr,n-1);
    }
    else {
        swap(&arr(istart++),&arr(iend--));
        return arrange(arr+istart,n-1);
    }
    }
}

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

How can I improve my code? Maybe make it run faster with a better complexity?

Separating a Graph into 2 sets with crossing edges only

I’m trying to think of an algorithm that checks the existence of partition $(V_1,V_2)$ in undirected $G$ where all the edges are crossing edges between $V_1$ and $V_2$. For example, if $e=(a,b)$ then $a in V_1$, $b in V_2$. Oh and $V_1+V_2=V$.

I’ve been thinking to come up with an algorithm but I simply cannot. I’ve checked some previous posts and they weren’t that helpful. Can someone give me a hint?

How are VLANS logically separating a switch?

Some attacks are prevented by VLANs regardless of what the router allows. Arp poisoning, for example, or broadcast-based attacks like DHCP hijacking. Two hosts segregated by a VLAN cannot attack each other in those manners.

However, for security the presumption is that the router will not allow everything, but will apply some access controls or NAT hiding between the two VLANs. In that event the router should be able to provide all the security that it could between two physical VLANs.

differential – Separating the variables possible?

Given the differential equation $$ frac{dy}{dx} = y+x$$

I am told this differential equation is separable. Meaning I need to rewrite the RHS into a product of two variables depending on y and x.

I’ve tried for some time now but I simply cannot figure out how this is separable. I’m able to solve it using the method of “integrating factor” and so I know the solution should be $$ x = Ce^x-x-1 $$

Any ideas?

image processing – Separating boundaries from WatershedComponents

I am trying to look for local minimum on the boundaries between watershed regions.

Typically the boundaries are not connected and spanning across the whole image so it is possible to do it through

MorphologicalComponents[
  1 - ImageData[Binarize[Colorize[WatershedComponents[image]], 0.1]]
]

The problem is when there are more than two watershed components are intersecting at a point and the boundary collides. The boundaries will then considered as one entity. This is illustrated as the green line in the picture where the watershed components are treated as the foregrounds.

boundary merged

Is there an efficient way to get unique pixels corresponding to the boundaries between two watershed components?

Which is a good way of separating and organizing layers and subdomains code, one that highlights and facilitates a clean architecture?

As I understand, when following the Low Coupling High Cohesion Principle, I should keep together the code that is related to same theme, splitting the code into modules/submodules by its “domain” or, in other words by what “topic” it is about, not by its “type”.

  • E.g. it can be considered a bad idea to keep all interfaces together just because they’re interfaces and keep all classes in other place. Or to keep all controllers in one place just because they’re controllers, and all adapters in other. Etc.

  • A better option is to keep all code relating to shopping cart in one place, and all code related to goods in other. – So as they’re are kinda modules. The Shopping cart folder will contain all code related to it: all controllers, adapters, entities, repositories etc – because they’re all about the same thing.

On the other hand, a good clean architecture implies a clear division onto separate “layers”, so that upper layers know nothing about lower ones.

  • first of all, there is a separate ‘library’ level – like web server, input-output, or data structures (e.g. map or deque) – they should “know” nothing about other layers.
  • then, at the top level there are some domain classes modelling domain entities and their relations, e.g. shopping cart and goods, maybe some business rules – they also “know” nothing about other layers,
  • next, there is some application logic, that describes the way in which your application allows to use those domain objects
  • and there is an infrastructure layer – the classes that use system layer and implement some interfaces required by upper layers.
  • finally, “on top of that all” (actually, at the bottom) – there is also a composition root level, which wires everything together, it may be familiar with every class and their dependencies and supplies them, maybe DI container, main() function etc.

It’d be cool if there’d be a way to highlight that e.g. all objects from one layer are somehow related and should use one layer but not use another.

– How to organize the code? What are best practices?

As a very simplified example – should it be like

- bookstore
    - customers
        - customer entity
        - customers repository interface
        - customers repository implementation
    - books
        - book entity
        - books repository interface
        - books repository implementation
        - book list view
        - book details view
        - amazon book adapter

or like

- domain
    - customers
        - customer entity
        - customer repository interface
    - books
        - book entity
        - books repository interface
- infrastructure
    - repositories
        - books repository implementation
        - customers repository implementation
- views
    - book list view
    - book details view
- adapters
    - amazon book adapter

But I’m not sure if this example illustrates the question well.