unity – How to animate body parts of the 3D snake on turning to the sides

Im currently learning Unity3D and I would like to recreate the old Nokia game “Snakes” (example video: https://www.youtube.com/watch?v=FXg3jVG-tXs&ab_channel=aantv, play it at 1:30 and notice the movement and even the animations when the snake is turning to the sides).

The game movement is grid based as you can see, so the snake can turn only, when the head is in the center of each floor slab. I have done this by using Coroutine and I am interpolating the position of the head between the distance of each 2 slabs. (example video: https://www.youtube.com/watch?v=_LWYQE1UC68&ab_channel=Chlebikpastika). Is this approach valid?

Now my question is, how to make the body of the snake. Should I use the List collection of body parts and update the position of each part with an offset? How to make the snake body parts looking connected to each other and even animate the body parts when turning to the side?

Here is my cody. If you have any recommendation, or event the code refactoring, give it to me please. Can I make the direction changing simpler?

using System.Collections;
using System.Collections.Generic;
using UnityEditor.Experimental.GraphView;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float moveSpeed = 10.0f;
    public GameObject cameraMount;


    public bool isMoving;
    private Vector3 originPos;
    private Vector3 targetPos;
    private float timeToMove = 0.5f;

    private bool keyPressed = false;

    private Vector3 forward = Vector3.forward;

    private float rotAngle = 0;

    // Start is called before the first frame update
    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
        if (!isMoving)
        {
            StartCoroutine(MovePlayer(forward));
        }

        if (Input.GetKeyDown(KeyCode.LeftArrow) && !keyPressed)
        {
            forward = TurnLeft(forward);
            keyPressed = true;
            rotAngle = -90;
        }
        else if (Input.GetKeyDown(KeyCode.RightArrow) && !keyPressed)
        {
            forward = TurnRight(forward);
            keyPressed = true;
            rotAngle = 90;
        }
    }

    private Vector3 TurnLeft(Vector3 direction)
    {
        if(direction == Vector3.forward)
        {
            return Vector3.left;
        }
        else if(direction == Vector3.left)
        {
            return Vector3.back;
        }
        else if (direction == Vector3.back)
        {
            return Vector3.right;
        }
        else 
        {
            return Vector3.forward;
        }
    }

    private Vector3 TurnRight(Vector3 direction)
    {
        if (direction == Vector3.forward)
        {
            return Vector3.right;
        }
        else if (direction == Vector3.right)
        {
            return Vector3.back;
        }
        else if (direction == Vector3.back)
        {
            return Vector3.left;
        }
        else
        {
            return Vector3.forward;
        }
    }

    private IEnumerator RotatePlayer(float angle)
    {
        Quaternion startRotation = transform.rotation;
        Quaternion endRotation = transform.rotation * Quaternion.Euler(new Vector3(0, 0, -angle));

        Quaternion startRotationCam = cameraMount.transform.rotation;
        Quaternion endRotationCam = cameraMount.transform.rotation * Quaternion.Euler(new Vector3(0, angle, 0));

        float i = 0;
        
        while (i < 1.0f)
        {
            transform.rotation = Quaternion.Slerp(startRotation, endRotation, i);
            cameraMount.transform.rotation = Quaternion.Slerp(startRotationCam, endRotationCam, i);

            i += Time.deltaTime * 3;
           
            yield return null;

        }

        transform.rotation = endRotation;
        cameraMount.transform.rotation = endRotationCam;


    }


    private IEnumerator MovePlayer(Vector3 direction)
    {

        isMoving = true;

        float elapsedTime = 0;

        originPos = transform.position;
        targetPos = originPos + direction;

        StartCoroutine(RotatePlayer(rotAngle));
        rotAngle = 0;

        while (elapsedTime < timeToMove)
        {
            transform.position = Vector3.Lerp(originPos, targetPos, (elapsedTime / timeToMove));
                
            elapsedTime += Time.deltaTime;
            yield return null;
        }
        transform.position = targetPos;
        isMoving = false;
        keyPressed = false;
    }
}

c++ – Smooth out movement of the snake in a 2d Snake game

(I can copy this over to your original question if you’d like to undelete that one and delete/close this one instead)

I’d solve this by storing an array of the grid points the snake head has run through, and the next point it’s on its way toward.

Then we’ll store a progress variable between 0 and 1, that represents how far the snake has progressed from its old position to its new position.

We’ll number our snake parts from 0 for the head up to snakeSize - 1 for the last tail segment. In our positionHistory array, 0 will be the point the head is moving toward now, 1 will be the point the head just left, and snakeSize will be the point the last tail segment just vacated.

We can update the state like so:

progress += deltaTime / secondsToCrossOneTile;

if (progress >= 1.0f) {
     // TODO: Handle activating a new tail segment here 
     //       when finishing the previous growth phase.

     // TODO: Handle eating a pellet here and beginning a new growth phase.

     for (int i = snakeSize; i > 0; --i) {
         positionHistory(i) = positionHistory(i - 1);
     }

     positionHistory(0) = PickNextPoint();

     progress -= 1.0f;
}

(You could also store your history as a circular buffer, and just increment the starting offset instead of copying it down one notch)

Now every frame we can run through each piece in the list, using this progress value as an interpolation weight between its old position and the new position.

for(int i = 0; i < snakeSize; i++) {
    snakePart(i).position = lerp(positionHistory(i+1), positionHistory(i), progress);
}

When you want to add a segment to the snake, you place it at the position of the last tail segment, and leave it stationary there until the next time we hit our progress >= 1.0f condition. Then you append it to the snakePart collection and extend the position history an extra notch.

c++ – Snake movement logic 2d Snake game

I am working on my snake game in C++ with SFML, now comes the part where I need to update the snakes body after the head has moved. Here’s the problem.

  • The distance the head moves is not the size of one part of a snake, because this doesn’t let the movement look smooth, so I instead move a smaller distance and update the direction when position % size == 0 This makes the movement so much smoother
  • Due to this, each body part should also move the “smaller distance” and not jump into the position of its next part because that will be basically moving each part the distance of the size

Now if I have to update the body I need to make a lot of checks to determine the correct change in position . How can I tackle this?

javascript – How to make a snake game where each snake tile follows it’s connected tile

I want to make a basic snake game. It has a head and a series of tails that occupy a tile in a grid. The first tail in the series is adjacent to the head. and each tail that comes after is adjacent to the previous tail. Head can move in four directions and the first tail moves to head’s previous position, and each other tail moves to the previous position of the previous tail, as they follow each other.

How can I model this as data, and update it for movement.

I’ve tried this but that’s a little doesn’t work.

p = {
  x=0,
  y=0,
  tails={}
}

function init_tail(dx,dy)
   return {
      x=0,
      y=0,
      dx=dx,
      dy=dy
   }
end
-- Initialization
add(p.tails, init_tail(0,1))
add(p.tails, init_tail(0,1))
add(p.tails, init_tail(0,1))

local last = p
for tail in all(p.tails) do
   local tx = last.x + tail.dx
   local ty = last.y + tail.dy
   tail.x = tx
   tail.y = ty
   last = tail
end
-- Movement
local last = p
for tail in all(p.tails) do
   tail.dx = -last.dx
   tail.dy = -last.dy
   local tx = last.x + last.dx + tail.dx
   local ty = last.y + last.dy + tail.dy
   tail.x = tx
   tail.y = ty
   last = tail
end

This is what I have so far: snake game

Console Snake Game C++ – Code Review Stack Exchange

I’ve tried to make a snake game. I think it works. But I really need your help to improve the code in terms of (readability – maintainability – efficiency, and so on). Is my code spaghetti? Any criticism is appreciated.

Here’s what’s done:

#include <iostream>
#include <vector>
#include <conio.h>
#include <cassert>


const int height = 25;
const int width = 90;
char board(height)(width);
bool snake_pos(height)(width);
std::vector<std::pair<int, int>> body; // body(0) head position
int cnt = 1;

void reset_board();
void draw_board();
void handle_input();

int foodX, foodY;

void reset_board() {
    for (int i = 0; i < width; i++)
        board(0)(i) = '#';
    for (int i = 1; i < height; i++) {
        for (int j = 0; j < width; j++) {
            // is border?
            if (j == 0 || j == width - 1) {
                board(i)(j) = '#';
            }
            else {
                board(i)(j) = ' ';
            }
        }
    }
    for (int i = 0; i < width; i++) {
        board(24)(i) = '#';
    }
    board(body(0).first)(body(0).second) = 'O';
    for (int i = 1; i < body.size(); i++)
        board(body(i).first)(body(i).second) = 'o';
    for (auto x : body)
        snake_pos(x.first)(x.second) = true;
        
    board(foodX)(foodY) = 'X';
}

void draw_board() {
    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {
            std::cout << board(i)(j);
        }
        std::cout << 'n';
    }
}

int rand(int a, int b) {
    return a + rand() % (b - a + 1);
}

void food() {
    int x = rand(1, 23);
    int y = rand(1, 88);
    // while food pos is part of the snake
    while (snake_pos(x)(y)) {
        x = rand(1, 23);
        y = rand(1, 88);
    }
    foodX = x;
    foodY = y;
}

#define KEY_UP 'w'
#define KEY_DOWN 's'
#define KEY_LEFT 'a'
#define KEY_RIGHT 'd'

bool game_over(int x, int y) {
    // check if head ate body
    for (int i = 1; i < body.size(); i++) {
        if (std::make_pair(x, y) == body(i))
            return true;
    }
    return false;
}

bool valid_move(char key) {
    // check if move is not in opposite direction 
    // 0, 1, 2, 3 (left, right, up, down) respectively
    if (cnt == 3 && key == KEY_UP)
        return false;
    if (cnt == 2 && key == KEY_DOWN)
        return false;
    if (cnt == 1 && key == KEY_LEFT)
        return false;
    if (cnt == 0 && key == KEY_RIGHT)
        return false;
    switch (key) {
    case KEY_LEFT:
        cnt = 0;
        break;
    case KEY_RIGHT:
        cnt = 1;
        break;
    case KEY_UP:
        cnt = 2;
        break;
    case KEY_DOWN:
        cnt = 3;
        break;
    default:
        assert(false);
    }
    return true;

}

bool food_eaten() {
    return body(0).first == foodX && body(0).second == foodY;
}

void handle_input() {
    char key = _getch();
    
    
    if (key == KEY_UP && valid_move(key)) {
        
        std::pair<int, int> prev = body(0);

        body(0).first--;
        if (body(0).first == 0)
            body(0).first = 23;
        if (game_over(body(0).first, body(0).second)) {
            std::cout << "Game is overn";
            exit(0);
        }
        if (food_eaten()) {
            board(foodX)(foodY) = ' ';
            // add tail 
            body.push_back({ body.back().first + 1, body.back().second });
            reset_board();
            // draw new food
            food();
        }

        
        for (int i = 1; i < body.size(); i++) {
            std::pair<int, int> tmp = body(i);
            body(i) = prev;
            prev = tmp;

        }
        
    }
            
        
    else if (key == KEY_DOWN && valid_move(key)) {
        
        std::pair<int, int> prev = body(0);
        body(0).first++;
        if (body(0).first == 24)
            body(0).first = 1;
        if (game_over(body(0).first, body(0).second)) {
            std::cout << "Game is overn";
            exit(0);
        }
        if (food_eaten()) {
            board(foodX)(foodY) = ' ';
            body.push_back({body.back().first - 1, body.back().second});
            reset_board();
            food();
        }
        
        for (int i = 1; i < body.size(); i++) {
            std::pair<int, int> tmp = body(i);
            body(i) = prev;
            prev = tmp;

        }
        
            
    }
    else if (key == KEY_LEFT && valid_move(key)) {
        if (cnt == 1) return;
        cnt = 0;
        std::pair<int, int> prev = body(0);
        body(0).second--;
        if (body(0).second == 0) {
            body(0).second = 88;
        }
        if (game_over(body(0).first, body(0).second)) {
            std::cout << "Game is overn";
            exit(0);
        }
        if (food_eaten()) {
            board(foodX)(foodY) = ' ';
            body.push_back({ body.back().first, body.back().second + 1 });
            reset_board();
            food();
        }
        
        for (int i = 1; i < body.size(); i++) {
            std::pair<int, int> tmp = body(i);
            body(i) = prev;
            prev = tmp;

        }
            
    }
    else if (key == KEY_RIGHT && valid_move(key)) {
        std::pair<int, int> prev = body(0);
        body(0).second++;
        if (body(0).second == 89) {
            body(0).second = 1;
        }
        if (game_over(body(0).first, body(0).second)) {
            std::cout << "Game is overn";
            exit(0);
        }
        if (food_eaten()) {
            board(foodX)(foodY) = ' ';
            body.push_back({ body.back().first, body.back().second - 1 });
            reset_board();
            food();
        }
        
        for (int i = 1; i < body.size(); i++) {
            std::pair<int, int> tmp = body(i);
            body(i) = prev;
            prev = tmp;

        }
            
    }
    system("cls");
    reset_board();
    draw_board();
    


        
}
int main()
{
    food();
    // initial snake 
    snake_pos(4)(81) = snake_pos(4)(80) = snake_pos(4)(79) = snake_pos(4)(82) = true;
    body.push_back({ 4, 82 });
    body.push_back({ 4, 81 });
    body.push_back({ 4, 80 });
    body.push_back({ 4, 79 });
    while (true) {
        handle_input();
    }
    

    return 0;

}

Snake game using C++ and FLTK

Description

I’ve written the snake game using C++ and FLTK. For simplifying the use of FLTK, a built-up library written by Bjarne Stroustrup was used. Bellow located main parts of the code written by me, a whole project can be found on GitHub: https://github.com/WumpusHunter/Snake-game.

Source.cpp

/*
    Snake game

    Revision history:
        Written by Oleg Kovalevskiy in August 2020
*/

//------------------------------------------------------------------------------------

#include "Game_window.h"
using namespace Graph_lib;

//------------------------------------------------------------------------------------

int main()
try {
    // Window with top-left angle at (100, 100), of size 600 * 400, labeled "Snake game"
    Snake_window win{ Point{ 100, 100 }, 600, 400, "Snake game" };
    
    return gui_main();
}
catch (const exception& e) {
    cerr << "Error message: " << e.what() << 'n';
    return 1;
}
catch (...) {
    cerr << "Unknown errorn";
    return 1;
}

//------------------------------------------------------------------------------------

Game_window.h

// Snake game's window

//------------------------------------------------------------------------------

#pragma once
#include "GraphicsLib/Window.h"
#include "GraphicsLib/GUI.h"
#include "GraphicsLib/Graph.h"
#include "Game_graph.h"

//------------------------------------------------------------------------------

namespace Graph_lib {

    //------------------------------------------------------------------------------

    // Invariant: w > 0, h > 0
    class Snake_window : public Window {        // Game window
    public:
        // Construction
        Snake_window(Point xy, int w, int h, const string& lab);

    private:
        // Callback functions
        int handle(int event) override;
        static void cb_game_loop(Address pw);
        static void cb_pause(Address, Address pw);
        static void cb_new_game(Address, Address pw);
        static void cb_quit(Address, Address pw);
        static void cb_game(Address, Address pw);
        static void cb_help(Address, Address pw);

        // Action functions
        void start();
        void game_loop();
        bool is_pause();
        void pause();
        void new_game();
        void quit();
        void game();
        void help();
        int current_score();
        void put_score();
        void show_graphics();
        void hide_graphics();

    private:
        // Graphics
        Grid field;
        Snake snake;
        Rectangle fruit;
        // GUI
        Menu game_menu;
        Button game_button;
        Button help_button;
        Text_box help_box;
        Out_box score_box;
        Out_box max_score_box;
    };

    //------------------------------------------------------------------------------

}   // End of Graph_lib namespace

//------------------------------------------------------------------------------

Game_window.cpp

// Snake game's window

//------------------------------------------------------------------------------

#include "Game_window.h"

//------------------------------------------------------------------------------

namespace Graph_lib {

    //------------------------------------------------------------------------------

    // Min possible size of window
    constexpr int min_w = 400;      // Window's min width
    constexpr int min_h = 300;      // Window's min height
    // Size of cells
    constexpr int cell_w = 50;      // Cell's width
    constexpr int cell_h = 50;      // Cell's height
    // Default parameters of snake
    constexpr int snake_sz = 3;     // Snake's length
    // Default location of graphics
    Point snake_xy = { 0, 0 };      // Snake's location
    Point fruit_xy = { 0, 0 };      // Fruit's location
    // Size of widgets
    constexpr int widget_h = 25;    // Widgets' height
    constexpr int out_box_w = 30;   // Output boxes' width
    constexpr int button_w = 100;   // Buttons' width
    // Indexes of game menu's buttons
    constexpr int new_game_ind = 0; // New game button's index
    constexpr int pause_ind = 1;    // Pause button's index
    constexpr int quit_ind = 2;     // Quit button's index

    // Constructs window with top-left angle at xy, of size w * h (if
    // it's not less than min, which is 400 * 300), labeled with lab
    Snake_window::Snake_window(Point xy, int w, int h, const string& lab)
        : Window{ xy, w >= min_w ? w - w % cell_w : min_w, h >= min_h ? h - h % cell_h : min_h, lab },
        field{ Point{ 0, cell_h }, cell_w, cell_h, x_max() / cell_w, (y_max() - cell_h) / cell_h },
        snake{ Point{ snake_sz * cell_w, y_max() / 2 }, cell_w, cell_h, snake_sz },
        fruit{ Point{ x_max() - cell_w * 2, y_max() / 2 }, cell_w, cell_h },
        game_menu{ Point{ 0, 0 }, button_w, widget_h, Menu::Kind::horizontal, "Game" },
        game_button{ Point{ 0, 0 }, button_w, widget_h, "&Game", cb_game },
        help_button{ Point{ button_w, 0 }, button_w, widget_h, "&Help", cb_help },
        help_box{ Point{ 0, cell_h }, x_max(), y_max() - cell_h, "" },
        score_box{ Point{ cell_w * 2, widget_h }, out_box_w, widget_h, "Current score: " },
        max_score_box{ Point{ cell_w * 4 + out_box_w, widget_h }, out_box_w, widget_h, "Max score: " }
    {
        if (w <= 0 || h <= 0)       // Error handling
            throw invalid_argument("Bad Snake_window: non-positive size");
        // Keep default location of graphics
        snake_xy = snake.point(0);
        fruit_xy = fruit.point(0);
        // Attach graphics to window
        attach(field);
        attach(snake);
        attach(fruit);
        // Attach widgets to window
        game_menu.attach(new Button{ Point{ 0, 0 }, 0, 0, "&New game", cb_new_game });
        game_menu.attach(new Button{ Point{ 0, 0 }, 0, 0, "&Pause", cb_pause });
        game_menu.attach(new Button{ Point{ 0, 0 }, 0, 0, "&Quit", cb_quit });
        attach(game_menu);
        attach(game_button);
        attach(help_button);
        attach(help_box);
        attach(score_box);
        attach(max_score_box);
        // Default value for graphics
        show_graphics();
        put_on_top(snake);
        // Default value for widgets
        game_menu.hide();
        help_box.put(" SNAKE GAMEn"
            " Snake is a video game concept where the player maneuvers a linen"
            "that grows in length, with the line itself being a primary obstacle.n"
            "The concept originated in the 1976 arcade game Blockade.n"
            " GAMEPLAYn"
            " The player controls an object on a bordered plane. As it moves for-n"
            "ward, it leaves a trail behind, resembling a moving snake. The snaken"
            "has a specific length, so there is a moving tail a fixed number of unitsn"
            "away from the head. The player loses when the snake runs into then"
            "screen border or itself.n"
            " A sole player attempts to eat items by running into them with the he-n"
            "ad of the snake. Each item eaten makes the snake longer, so con-n"
            "trolling is progressively more difficult.n"
            " CONTROLn"
            " The snake moves forward automatically, everything you need to don"
            "is to choose the direction of moving. To choose the direction of mov-n"
            "ing use arrow-buttons, that is,n"
            "1) Left-arrow - to move in the left direction;n"
            "2) Up-arrow - to move in the up direction;n"
            "3) Right-arrow - to move in the right direction;n"
            "4) Down-arrow - to move in the down direction.n"
            "Remember: you can't rotate the snake's head to the opposite direc-n"
            "tion, for instance, from the left to the right, or from the up to then"
            "down.n"
            " ADDITIONAL NOTESn"
            " Good luck on the game, try to eat as much as you can!n");
        help_box.hide();
        score_box.put(0);
        max_score_box.put(0);
    }

    // Handles passed to window event, for instance, pressed key
    int Snake_window::handle(int event)
    {
        switch (event) {
        case FL_FOCUS: case FL_UNFOCUS:     // Focuses are skipped (required by system)
            return 1;
        case FL_KEYBOARD: {                 // Keys, pressed using keyboard
            switch (Fl::event_key()) {
                // Arrow-keys used to change snake's direction
            case FL_Left:                   // Left-arrow
                snake.set_direction(Snake::Direction::left);
                cout << "Changed direction to the left (" << static_cast<int>(snake.direction()) << ")n";
                return 1;
            case FL_Up:                     // Up-arrow
                snake.set_direction(Snake::Direction::up);
                cout << "Changed direction to the up (" << static_cast<int>(snake.direction()) << ")n";
                return 1;
            case FL_Right:                  // Right-arrow
                snake.set_direction(Snake::Direction::right);
                cout << "Changed direction to the right (" << static_cast<int>(snake.direction()) << ")n";
                return 1;
            case FL_Down:                   // Down-arrow
                snake.set_direction(Snake::Direction::down);
                cout << "Changed direction to the down (" << static_cast<int>(snake.direction()) << ")n";
                return 1;
            }
        }
        }
        return Window::handle(event);       // Everything else is handled by base window
    }

    // Callback function for game_loop
    void Snake_window::cb_game_loop(Address pw)
    {
        constexpr double delay = 0.25;                  // Delay of game's loop
        reference_to<Snake_window>(pw).game_loop();     // Call of action function
        Fl::repeat_timeout(delay, cb_game_loop, pw);    // Execute delay of game's loop
    }

    // Callback function for pause
    void Snake_window::cb_pause(Address, Address pw)
    {
        reference_to<Snake_window>(pw).pause();
        reference_to<Snake_window>(pw).game();
    }

    // Callback function for new game
    void Snake_window::cb_new_game(Address, Address pw)
    {
        reference_to<Snake_window>(pw).new_game();
        reference_to<Snake_window>(pw).game();
    }

    // Callback function for quit
    void Snake_window::cb_quit(Address, Address pw)
    {
        reference_to<Snake_window>(pw).quit();
        reference_to<Snake_window>(pw).game();
    }

    // Callback function for game
    void Snake_window::cb_game(Address, Address pw)
    {
        reference_to<Snake_window>(pw).game();
    }

    // Callback function for help
    void Snake_window::cb_help(Address, Address pw)
    {
        reference_to<Snake_window>(pw).help();
    }

    // Starts game's loop
    void Snake_window::start()
    {
        constexpr double delay = 1.0;                   // Delay before first timeout
        Fl::add_timeout(delay, cb_game_loop, this);     // Start game's loop and delay proccess
        cout << "Started the gamen";
    }

    // Starts all proccesses of game's loop
    void Snake_window::game_loop()
    {
        // Snake's bumping (obstacle is snake's body or field's borders)
        if (snake.is_body_except_head(snake.body_head())) {     // Snake's body as obstacle
            cout << "Bumped into the snake's bodyn";
            // Pause after losed game
            return Fl::add_timeout(0.0, ()(Address pw) { cb_pause(nullptr, pw); }, this);;
        }
        if (!is_grid(field, snake.body_head())) {               // Grid's border as obstacle
            cout << "Bumped into the grid's bordern";
            // Pause after losed game
            return Fl::add_timeout(0.0, ()(Address pw) { cb_pause(nullptr, pw); }, this);
        }
        // Snake's eating
        if (snake.point(0) == fruit.point(0)) {
            snake.grow_length();
            put_score();                // Update score after eating
            cout << "Ate the fruit; the length becomes equal to " << snake.length() << 'n';
            // Randomly change location of fruit to everywhere, except snake's body
            while (snake.is_body(fruit))
                random_move(fruit, field.point(0), field.width() - fruit.width(), field.height() - fruit.height());
        }
        else snake.move_forward();      // Snake's moving
        cout << "Moved to (" << snake.point(0).x << ", " << snake.point(0).y << ")n";
        redraw();                       // Redraw window after made changes
    }

    // Determines either game is paused or not
    bool Snake_window::is_pause()
    {
        return Fl::has_timeout(cb_game_loop, this) ? false : true;
    }

    // Pauses game if it's playing, or starts if it's already
    // paused, that is, pause prevents snake's moves
    void Snake_window::pause()
    {
        if (!is_pause()) {
            Fl::remove_timeout(cb_game_loop, this);     // Stop timeout
            cout << "Paused the gamen";
        }
        else start();                                   // Start timeout
    }

    // Starts new game, that is, returns everything to initial state
    void Snake_window::new_game()
    {
        if (!is_pause()) pause();                   // Pause game
        snake.shrink_length(current_score());       // Shrink length to default length
        // Return graphics to default location
        snake.set_direction(Snake::Direction::up);
        snake.set_direction(Snake::Direction::right);
        for (int i = 0; i < snake_sz; ++i)
            snake.move_forward();
        snake.move(-snake.point(0).x, -snake.point(0).y);       // Top-left angle of window
        snake.move(snake_xy.x, snake_xy.y);
        fruit.move(-fruit.point(0).x, -fruit.point(0).y);       // Top-left angle of window
        fruit.move(fruit_xy.x, fruit_xy.y);
        cout << "Started the new game; shrank the length to " << snake.length() << 'n';
        put_score();                                // Update score after shrinking
        redraw();                                   // Redraw window after made changes
    }

    // Quits game, that is, closes window
    void Snake_window::quit()
    {
        Window::hide();         // Hide window to close it
        cout << "Quited the gamen";
    }

    // Hides game button and shows game menu, if game button is pressed,
    // or shows game button and hides game menu, if game menu is pressed
    void Snake_window::game()
    {
        // Hide game button and show game menu
        if (game_button.visible()) {        // Game button is pressed
            game_button.hide();
            game_menu.show();
            help_button.move(game_menu.selection.size() * game_menu.width - help_button.width, 0);
            cout << "Hid the game button and showed the game menun";
        }
        // Hide game menu and show game button
        else {                              // Game menu is pressed
            game_menu.hide();
            game_button.show();
            help_button.move(help_button.width - game_menu.selection.size() * game_menu.width, 0);
            cout << "Hid the game menu and showed the game buttonn";
        }
    }

    // Shows help box if it's invisible, or hides it if it's visible
    void Snake_window::help()
    {
        // Show help box
        if (!help_box.visible()) {      // Help box is invisible
            if (!is_pause()) pause();   // Pause game
            game_menu.selection(pause_ind).deactivate();
            hide_graphics();
            help_box.show();
            cout << "Showed the help boxn";
        }
        // Hide help box
        else {                          // Help box is visible
            game_menu.selection(pause_ind).activate();
            help_box.hide();
            show_graphics();
            cout << "Hid the help boxn";
        }
    }

    // Determines current score
    int Snake_window::current_score()
    {
        return snake.length() - snake_sz;
    }

    // Writes current score and max score into score boxes, if required
    void Snake_window::put_score()
    {
        int score = current_score();
        score_box.put(score);                   // Write current score
        if (score > max_score_box.get_int()) {  // New record
            max_score_box.put(score);           // Write max score
            cout << "Updated the max score to " << score << 'n';
        }
        cout << "Updated the current score to " << score << 'n';
    }

    // Shows game's graphics, that is, makes field, snake, and fruit visible
    void Snake_window::show_graphics()
    {
        // Modify color parameters of graphics
        field.set_color(Color::black);
        field.set_fill_color(Color::dark_green);
        snake.set_color(Color::black);
        snake.set_fill_color(Color::dark_yellow);
        snake.head_set_fill_color(Color::yellow);
        fruit.set_color(Color::black);
        fruit.set_fill_color(Color::red);
        cout << "Showed the graphicsn";
    }

    // Hides game's graphics, that is, makes field, snake, and fruit invisible
    void Snake_window::hide_graphics()
    {
        // Modify color parameters of graphics
        field.set_color(Color::invisible);
        field.set_fill_color(Color::invisible);
        snake.set_color(Color::invisible);
        snake.set_fill_color(Color::invisible);
        snake.head_set_fill_color(Color::invisible);
        fruit.set_color(Color::invisible);
        fruit.set_fill_color(Color::invisible);
        cout << "Hid the graphicsn";
    }

    //------------------------------------------------------------------------------

}   // End of Graph_lib namespace

//------------------------------------------------------------------------------

Game_graph.h

// Snake game's graphics

//------------------------------------------------------------------------------

#pragma once
#include "GraphicsLib/Graph.h"

//------------------------------------------------------------------------------

namespace Graph_lib {

    //------------------------------------------------------------------------------

    // Invariant: cell_w > 0, cell_h > 0, sz > 0
    class Snake : public Shape {
    public:
        enum class Direction {      // Possible directions of head
            left, up, right, down
        };

        // Construction
        Snake(Point xy, int cell_w, int cell_h, int sz);

        // Drawing
        void draw_lines() const override;
        void move(int dx, int dy) override;
        void move_forward();
        void grow_length();
        void shrink_length(int num);

        // Modification of parameters
        void set_color(Color c);
        void set_fill_color(Color c);
        void set_style(Line_style ls);
        void set_direction(Direction d);
        void head_set_fill_color(Color c);

        // Access to parameters
        const Rectangle& body_head() const;
        Direction direction() const { return head; }
        int length() const { return body.size(); }
        bool is_body(const Rectangle& cell) const;
        bool is_body_except_head(const Rectangle& cell) const;

    private:
        Vector_ref<Rectangle> body;
        Direction head;                 // Direction of head
    };

    //------------------------------------------------------------------------------

    // Helper function
    void random_move(Rectangle& rect, Point xy, int w, int h);

    //------------------------------------------------------------------------------

}   // End of Graph_lib namespace

//------------------------------------------------------------------------------

Game_graph.cpp

// Snake game's graphics

//------------------------------------------------------------------------------

#include "Game_graph.h"
#include "RandomNumber/Generator.h"

//------------------------------------------------------------------------------

namespace Graph_lib {

    //------------------------------------------------------------------------------

    // Indexes of snake's body
    constexpr int head_ind = 0;

    // Constructs snake with top left-angle of its head at xy, of sz
    // cells, and with size of each cell equal to cell_w * cell_h
    Snake::Snake(Point xy, int cell_w, int cell_h, int sz)
        : body{}, head{ Direction::right }
    {
        if (sz <= 0)        // Error handling
            throw invalid_argument("Bad Snake: non-positive length");
        // Fill of body
        for (int i = 0; i < sz; ++i)        // Horizontal line
            body.push_back(new Rectangle{ Point{ xy.x - i * cell_w, xy.y }, cell_w, cell_h });
        add(xy);            // Top-left angle of snake's head
    }

    // Draws snake and fills it with color if required
    void Snake::draw_lines() const
    {
        // Draw each cell of body
        for (int i = 0; i < body.size(); ++i)
            body(i).draw();
    }

    // Moves snake by dx at x-coordinate and dy at y-coordinate 
    void Snake::move(int dx, int dy)
    {
        Shape::move(dx, dy);
        // Move each cell of body
        for (int i = 0; i < body.size(); ++i)
            body(i).move(dx, dy);
    }

    // Moves snake forward, that is, moves each cell from tail to head
    // to its next neighbor, and moves head one cell in its direction
    void Snake::move_forward()
    {
        // Move each cell from tail to head to its next neighbour
        for (int i = body.size() - 1; i > 0; --i) {
            body(i).move(-body(i).point(0).x, -body(i).point(0).y);         // Move to initial point
            body(i).move(body(i - 1).point(0).x, body(i - 1).point(0).y);   // Move to neigbhour's point
        }
        // Move head one cell in its direction
        switch (head) {
        case Direction::left:       // Left-side
            body(head_ind).move(-body(head_ind).width(), 0);
            break;
        case Direction::up:         // Up-side
            body(head_ind).move(0, -body(head_ind).height());
            break;
        case Direction::right:      // Right-side
            body(head_ind).move(body(head_ind).width(), 0);
            break;
        case Direction::down:       // Down-side
            body(head_ind).move(0, body(head_ind).height());
            break;
        }
        set_point(0, body(head_ind).point(0));         // Update location of snake's head
    }

    // Grows snake in length, that is, adds one cell to its tail
    void Snake::grow_length()
    {
        const Point tail = body(body.size() - 1).point(0);      // Tail's coordinate
        move_forward();
        // Add new cell into body at previous tail's location
        body.push_back(new Rectangle{ tail, body(head_ind).width(), body(head_ind).height() });
        // Set same parameters for new tail as for all body
        body(body.size() - 1).set_color(color());
        body(body.size() - 1).set_fill_color(fill_color());
        body(body.size() - 1).set_style(style());
    }

    // Shrinks snake in length, that is, removes num cells from its body, starting with tail
    void Snake::shrink_length(int num)
    {
        if (num >= body.size())     // Error handling
            throw invalid_argument("Bad Snake: can't shrink to non-positive length");
        constexpr bool own = true;  // Cells are owned by body
        // Remove num cells from snake's body
        for (int i = 0; i < num; ++i)
            body.pop_back(own);
    }

    // Sets c as color of snake's lines
    void Snake::set_color(Color c)
    {
        Shape::set_color(c);
        // Set c as color of lines to each cell of body
        for (int i = 0; i < body.size(); ++i)
            body(i).set_color(c);
    }

    // Sets c as fill color of snake's body
    void Snake::set_fill_color(Color c)
    {
        Shape::set_fill_color(c);
        // Set c as fill color to each cell of body
        for (int i = 0; i < body.size(); ++i)
            body(i).set_fill_color(c);
    }

    // Sets c as fill color of snake's head
    void Snake::head_set_fill_color(Color c)
    {
        if (body.begin() == body.end())     // Error handling
            throw out_of_range("Bad Snake: can't set fill color to head of empty snake");
        body(head_ind).set_fill_color(c);
    }

    // Sets ls as line style of snake's body
    void Snake::set_style(Line_style ls)
    {
        Shape::set_style(ls);
        // Set ls as line style to each cell of body
        for (int i = 0; i < body.size(); ++i)
            body(i).set_style(ls);
    }

    // Sets d as direction of snake's head
    void Snake::set_direction(Direction d)
    {
        constexpr int opposite_diff = 2;     // Module of opposite direction's difference
        // Difference of directions
        const int diff = abs(static_cast<int>(head) - static_cast<int>(d));
        if (diff != opposite_diff)           // Set direction if it's not opposite
            head = d;
    }

    // Gets snake's head
    const Rectangle& Snake::body_head() const
    {
        if (body.cbegin() == body.cend())       // Error handling
            throw out_of_range("Bad Snake: can't get head of empty snake");
        return body(head_ind);
    }

    // Determines either cell is one of snake's body's cells
    bool Snake::is_body(const Rectangle& cell) const
    {
        // Search for cell in snake's body, located same as cell, and compare parameters
        return find_if(body.cbegin(), body.cend(), (&cell)(const Rectangle* rect)
            { return rect->point(0) == cell.point(0); }) != body.cend()
            && body(0).width() == cell.width() && body(0).height() == cell.height();
    }

    // Determines either cell is one of snake's body's cells, except its head
    bool Snake::is_body_except_head(const Rectangle& cell) const
    {
        // Search for cell in snake's body, located same as cell, except snake's head, and compare parameters
        return body.cbegin() != body.cend() ? find_if(next(body.cbegin()), body.cend(),
            (&cell)(const Rectangle* rect) { return rect->point(0) == cell.point(0); }) != body.cend()
            && body(0).width() == cell.width() && body(0).height() == cell.height() : false;
    }

    //------------------------------------------------------------------------------

    // Moves rect randomly in range (xy.x; xy.x + w) for x-coordinate and (xy.y; xy.y + h) for
    // y-coordinate, with xy as original point, w as width of range and h as height of range
    void random_move(Rectangle& rect, Point xy, int w, int h)
    {
        if (w < 0 || h < 0)     // Error handling
            throw invalid_argument("Bad random_move: invalid range for coordinates");
        // Move to original location, that is, xy
        rect.move(-(rect.point(0).x - xy.x), -(rect.point(0).y - xy.y));
        rect.move(rect.width() * randint(0, w / rect.width()),      // Random x-coordinate
            rect.height() * randint(0, h / rect.height()));         // Random y-coordinate
    }

    //------------------------------------------------------------------------------

}   // End of Graph_lib namespace

//------------------------------------------------------------------------------

Question

How can I improve my code in the future? Any tips are appreciated, but especially hope
to see your thoughts on the structuring of the code, its flexibility, and readability.

Credits

Thanks for your time and efforts.

windows – Basic snake game in C

I wanted to learn C and Win32 so I thought the best way to start would be to create a simple game to get familiar with the language, so I made Snake. The games works perfectly except for sometimes seeing the game window flash for a split second, don’t know what causes it.

Code:

#include <stdio.h>
#include <windows.h>

#define TIMER_MAIN 42069

HWND gameWindow;
HFONT gameFont;

int timeElapsed = 0;
int direction = 0;
int length = 0;
int width, height, bSize, snakeThinness;

POINT* playerHistory;
POINT playerLocation = { 0 };
POINT fruitLocation = { 0 };

void Die(int errorCode)
{
    if (gameFont)
        DeleteObject(gameFont);
    char error(6)(64) = {   "Finished program execution", 
                            "Could not register window class", 
                            "Could not create window",
                            "Win32 Error",
                            "Player direction is outside 2D space",
                            "" };
    LPVOID lpMsgBuf;
    DWORD dw = GetLastError(); 
    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL);
    printf("Halting: (0x%08x) %srnWIN 0x%08x / %s", errorCode, error(errorCode), dw, (LPCSTR)lpMsgBuf);
    exit(errorCode);
}

void GameInit(int w, int h, int bS)
{
    printf("%sn", "Initiating game variables...");
    width = w;
    height = h;
    bSize = bS;
    snakeThinness = bSize / 10;
    timeElapsed = 0;
    direction = 0;
    length = 0;
    playerLocation.x = 4;
    playerLocation.y = 4;
    while (1)
    {
        fruitLocation.x = rand() % width;
        fruitLocation.y = rand() % height;
        if (fruitLocation.x == playerLocation.x && fruitLocation.y == playerLocation.y)
            continue;
        else
            break;
    }
    RECT rc, rcClient;
    GetWindowRect(gameWindow, &rc);
    GetClientRect(gameWindow, &rcClient);
    int xExtra = rc.right - rc.left - rcClient.right - rcClient.left;
    int yExtra = rc.bottom - rc.top - rcClient.bottom - rcClient.top;
    SetWindowPos(gameWindow, NULL, NULL, NULL, (width * bSize) + xExtra, (height * bSize) + yExtra, SWP_NOMOVE | SWP_NOZORDER | SWP_NOCOPYBITS);
}

void GameLoop()
{
    int lastX = playerLocation.x;
    int lastY = playerLocation.y;
    switch (direction)
    {   
        //0 == right
        //1 == bottom
        //2 == left
        //3 == top
        case 0:
            playerLocation.x++;
            if (playerLocation.x > width)
                playerLocation.x = 0;
            break;
        case 1:
            playerLocation.y++;
            if (playerLocation.y > height)
                playerLocation.y = 0;
            break;
        case 2:
            playerLocation.x--;
            if (playerLocation.x < 0)
                playerLocation.x = width;
            break;
        case 3:
            playerLocation.y--;
            if (playerLocation.y < 0)
                playerLocation.y = height;
            break;
        default:
            Die(4);
    }
    POINT p;
    p.x = lastX;
    p.y = lastY;
    if (fruitLocation.x == playerLocation.x && fruitLocation.y == playerLocation.y)
    {
        length++;
        if (playerHistory != 0)
        {
            POINT* playerHistory2 = (POINT*) malloc(length * sizeof(POINT));
            //memcpy(playerHistory2 + sizeof(POINT), playerHistory, (length - 1)  * sizeof(POINT));
            for (int i = 0; i < length - 1; i++)
                playerHistory2(i + 1) = playerHistory(i);
            playerHistory2(0) = p;
            free(playerHistory);
            playerHistory = playerHistory2;
        }
        else
        {
            playerHistory = (POINT*) malloc(length * sizeof(POINT));
        }
        if (length == 1)
        {
            playerHistory(0) = p;
        }
        while (1)
        {
            fruitLocation.x = rand() % width;
            fruitLocation.y = rand() % height;
            if (fruitLocation.x == playerLocation.x && fruitLocation.y == playerLocation.y)
                continue;
            int t = 0;
            for (int i = 0; i < length; i++)
                if (fruitLocation.x == playerHistory(i).x && fruitLocation.y == playerHistory(i).y)
                    t = 1;
            if (t == 0)
                break;
        }
    }
    else if (playerHistory != 0)
    {
        POINT* playerHistory2 = (POINT*) malloc(length * sizeof(POINT));
        //memcpy(playerHistory2 + sizeof(POINT), playerHistory, (length - 1) * sizeof(POINT));
        for (int i = 0; i < length - 1; i++)
            playerHistory2(i + 1) = playerHistory(i);
        playerHistory2(0) = p;
        free(playerHistory);
        playerHistory = playerHistory2;
    }
    int willDie = 0;
    for (int i = 0; i < length; i++)
        if (playerLocation.x == playerHistory(i).x && playerLocation.y == playerHistory(i).y)
            willDie = 1;
    if (willDie == 1)
        GameInit(width, height, bSize);
}

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{ 
    switch (uMsg) 
    { 
        case WM_CREATE:
            MessageBox(hwnd, "Window succesfully created", "Succes", MB_OK);
            ShowWindow(hwnd, SW_SHOW);
            SetTimer(hwnd, TIMER_MAIN, 375, (TIMERPROC) NULL);
            return 0; 
        case WM_TIMER:
            switch (wParam) 
            { 
                case TIMER_MAIN: 
                    timeElapsed++;
                    GameLoop();
                    RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE);
                    return 0;
            } 
            return 0;
        case WM_PAINT: 
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            RECT rect;
            //Clear screen
            rect.left = 0;
            rect.right = width * bSize;
            rect.top = 0;
            rect.bottom = height * bSize;
            FillRect(hdc, &rect, GetStockObject(BLACK_BRUSH));
            //Draw fruit
            rect.left = fruitLocation.x * bSize;
            rect.right = (fruitLocation.x + 1) * bSize;
            rect.top = fruitLocation.y * bSize;
            rect.bottom = (fruitLocation.y + 1) * bSize;
            FillRect(hdc, &rect, GetStockObject(GRAY_BRUSH));
            //Draw player tail
            int lastX = playerLocation.x;
            int lastY = playerLocation.y;
            for (int i = 0; i < length; i++)
            {
                int doNormal = 0;
                int compare1, compare2;
                if (lastY == playerHistory(i).y)
                {
                    compare1 = playerHistory(i).x;
                    compare2 = lastX;
                }
                else
                {
                    compare1 = playerHistory(i).y;
                    compare2 = lastY;
                }
                if (abs(compare1 - compare2) == 1)
                {
                    if (compare1 < compare2)
                    {
                        rect.left = (playerHistory(i).x * bSize) + snakeThinness;
                        rect.right = ((lastX + 1) * bSize) - snakeThinness;
                        rect.top = (playerHistory(i).y * bSize) + snakeThinness;
                        rect.bottom = ((lastY + 1) * bSize) - snakeThinness;
                    }
                    else
                    {
                        rect.left = (lastX * bSize) + snakeThinness;
                        rect.right = ((playerHistory(i).x + 1) * bSize) - snakeThinness;
                        rect.top = (lastY * bSize) + snakeThinness;
                        rect.bottom = ((playerHistory(i).y + 1) * bSize) - snakeThinness;
                    }
                }
                else
                    doNormal = 1;
                if (doNormal)
                {
                    rect.left = (playerHistory(i).x * bSize) + snakeThinness;
                    rect.right = ((playerHistory(i).x + 1) * bSize) - snakeThinness;
                    rect.top = (playerHistory(i).y * bSize) + snakeThinness;
                    rect.bottom = ((playerHistory(i).y + 1) * bSize) - snakeThinness;
                }
                lastX = playerHistory(i).x;
                lastY = playerHistory(i).y;
                FillRect(hdc, &rect, CreateSolidBrush(RGB(0,255,0)));
            }
            //Draw player
            rect.left = playerLocation.x * bSize;
            rect.right = (playerLocation.x + 1) * bSize;
            rect.top = playerLocation.y * bSize;
            rect.bottom = (playerLocation.y + 1) * bSize;
            FillRect(hdc, &rect, GetStockObject(WHITE_BRUSH));
            rect.left = 0;
            rect.right = width * bSize;
            rect.top = 0;
            rect.bottom = 30;
            SelectObject(hdc, gameFont);
            SetTextColor(hdc, RGB(128, 128, 128));
            SetBkMode(hdc, TRANSPARENT);
            int strLen = snprintf(NULL, 0, "%i", length);
            char* str = malloc(sizeof(char) * (strLen + 1 + 7));
            sprintf(str, "Score: %i", length);
            DrawText(hdc, str, -1, &rect, DT_CENTER);
            EndPaint(hwnd, &ps);
            return 0; 
        case WM_SIZE: 
            return 0; 
        case WM_DESTROY: 
            Die(0);
            return 0;
        case WM_KEYDOWN:
            if ((lParam & 0x40000000) == 0)
            {
                switch (wParam)
                {
                    case VK_RIGHT:
                        if (direction != 2)
                            direction = 0;
                        return;
                    case VK_DOWN:
                        if (direction != 3)
                            direction = 1;
                        return;
                    case VK_LEFT:
                        if (direction != 0)
                            direction = 2;
                        return;
                    case VK_UP:
                        if (direction != 1)
                            direction = 3;
                        return;
                }
            }
            return 0;
        default: 
            return DefWindowProc(hwnd, uMsg, wParam, lParam); 
    } 
    return 0; 
} 

int main()
{
    printf("%sn", "Starting...");
    HINSTANCE module = GetModuleHandle(NULL);
    WNDCLASSEX wndClass = { 0 };
    wndClass.cbSize = sizeof(WNDCLASSEX);
    wndClass.style = NULL;
    wndClass.lpfnWndProc = (WNDPROC) MainWndProc;
    wndClass.cbClsExtra = NULL;
    wndClass.cbWndExtra = NULL;
    wndClass.hInstance = module;
    wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndClass.hbrBackground = GetStockObject(LTGRAY_BRUSH);
    wndClass.lpszMenuName =  ""; 
    wndClass.lpszClassName = "MainWindowClass"; 

    if (!RegisterClassEx(&wndClass))
        Die(1);

    gameWindow = CreateWindowEx(WS_EX_CLIENTEDGE, "MainWindowClass", "CSnake", WS_CAPTION | WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT, 460, 460, NULL, NULL, module, NULL);
    if (gameWindow == NULL)
        Die(2);

    gameFont = CreateFont(28, 0, 0, 0, 400, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, OUT_DEFAULT_PRECIS, DEFAULT_PITCH, "Comic Sans MS");

    GameInit(20, 20, 20);

    MSG msg;
    BOOL bRet;

    while (1)
    {
        bRet = GetMessage(&msg, NULL, 0, 0);

        if (bRet > 0)
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else if (bRet < 0)
        {
            Die(3);
        }
        else
        {
            Die(0);
        }
    }
    return msg.wParam;
}
```

dnd 5e – Has anyone had to DM the Giant Coral Snake from 5e SaltMarsh dealing stun damage to undead?

Last night I was DMing my group. They were fighting some undead and the Wizard polymorphed the ranger’s animal companion into the Giant Coral Snake from Ghosts of Saltmarsh. Upon a hit by the coral snake, the target must make a DC 12 CON save or be stunned until it’s next turn. Furthermore, on a failed save the target begins to hallucinate and is affected by short term madness. This lasts for 10 minutes. Ok, so that’s pretty clear. However, since the coral snake was attacking an undead creature I ruled the stun feature, as well as the hallucination, wouldn’t take affect due Undead creatures being immune to poison (or, in this case venom from a coral snake). The stat block didn’t call it poison damage but….well, it had to have been right? My players seemed to accept this line of reasoning but I am curious, has anyone else encountered this?

logic – AI for 1v1 snake

I’ve got a task for job application to create a snake AI that can defeat their basic AI. Searching in this topic I’ve found that it’s very similar to Tron and it’s solved mostly by minimax algorithm. But my problem is that I’ve got an interface defined, and I can only acces these from the AI class:

public interface Data
{
    Point getFoodPosition();
    Body getSnake(int i);
    int getSnakePiece();
    BoardElements getBoardElement(int x, int y);
    Size getBoardSize();
}

public interface Body
{
    Point getHead();
    int getLength();
    Directions getDirection();
    Point? getBodyPiece(int i);
    Point getEnd();
    void setSnakeColor(Color color);
}

I can’t see their AI’s code, because it’s in a DLL. I can’t access the whole map to then pass it. I could get each element from the map, but that would be a dirty solution in my opinion. But without it I can only do ineffective solutions. I’ve tried to create a heuristic based on distance of the food, but it seems not complex enough. What do you think, how should I approach this problem?

python 3.x – Snake game implemented in PyGame and Python3

I know there’s already a whole bunch of similar attempts at this and I’ve looked at a few of them to get some tips. My main aim with this was not to write anything fancy. I wanted to create a simple game with the most basic functionality but write it really well. In other words I’m more interested in making my code professional, efficient and of high quality. One way I’ve tried to do this is to keep the main function simple and use it mostly for initialisation and the game loop, whilst implementing most of the functionality in separate classes and functions. Also, I would appreciate any advice on comments and docstrings because I’m sure mine are far from perfect. I also noticed the clock speed is around 4.5 GHz while playing this, so I think this could definitely do with an efficiency boost.

#!/usr/bin/env python3

import pygame as pg
from random import randint

# Define window parameters
BLOCK_SIZE = 20
WIN_SIZE = 500


class Head():
    blue = (0, 0, 255) # Colour of snake
    start_params = (BLOCK_SIZE * 0.05, BLOCK_SIZE * 0.05, BLOCK_SIZE * 0.9, BLOCK_SIZE * 0.9)
    def __init__(self, pos):
        """Head of snake"""
        self.x = pos(0)
        self.y = pos(1)
        self.last_x = self.x
        self.last_y = self.y
        self.direction = (0, -BLOCK_SIZE)
        self.square = None

    def make_block(self):
        """Create a surface to contain a square and draw a square Rect object onto said surface"""
        self.square = pg.Surface((BLOCK_SIZE, BLOCK_SIZE), pg.SRCALPHA)
        # Draw a square onto the "square" surface
        pg.draw.rect(self.square, self.blue, self.start_params)

    def update_pos(self):
        """Last coords are used to update next block in the snake"""
        self.last_x = self.x
        self.last_y = self.y
        self.x += self.direction(0)
        self.y += self.direction(1)

    def change_direction(self, new_dir):
        """Change direction of snake without allowing it to go back in itself"""
        if new_dir == 'u' and self.direction != (0, BLOCK_SIZE):
            self.direction = (0, -BLOCK_SIZE)
        elif new_dir == 'd' and self.direction != (0, -BLOCK_SIZE):
            self.direction = (0, BLOCK_SIZE)
        elif new_dir == 'l' and self.direction != (BLOCK_SIZE, 0):
            self.direction = (-BLOCK_SIZE, 0)
        elif new_dir == 'r' and self.direction != (-BLOCK_SIZE, 0):
            self.direction = (BLOCK_SIZE, 0)

    def check_collision(self, pos_list):
        """Check if snake collides with wall or itself"""
        if self.x in (0, WIN_SIZE) or self.y in (0, WIN_SIZE):
            return True

        if (self.x, self.y) in pos_list(3:):
            return True

        return False

    def get_pos(self):
        return (self.last_x, self.last_y)


class Block(Head):
    def __init__(self, next_block):
        """Body of snake"""
        self.next = next_block
        pos = next_block.get_pos()
        self.x = pos(0)
        self.y = pos(1)
        self.last_x = self.x
        self.last_y = self.y
        self.ready = 0

    def update_pos(self):
        """Use position of next block in snake to update current position"""
        self.last_x = self.x
        self.last_y = self.y
        next_pos = self.next.get_pos()
        self.x = next_pos(0)
        self.y = next_pos(1)


def add_block(snake_arr):
    """Extend snake by adding a snake block to the snake array"""
    snake_arr.append(Block(snake_arr(-1)))
    snake_arr(-1).make_block()

    return snake_arr


def check_keypress(input_event, block_object):
    """Take input event and change direction if arrow key or quit game if esc key or other exit signal"""
    if input_event.type == pg.QUIT:
        return True
    elif input_event.type == pg.KEYDOWN:
        if input_event.key == pg.K_ESCAPE:
            return True
        elif input_event.key == pg.K_UP:
            block_object.change_direction('u')
        elif input_event.key == pg.K_DOWN:
            block_object.change_direction('d')
        elif input_event.key == pg.K_LEFT:
            block_object.change_direction('l')
        elif input_event.key == pg.K_RIGHT:
            block_object.change_direction('r')

    return False


class Food():
    def __init__(self):
        """Food block, created in the same way as a snake block"""
        self.exists = False
        self.x = None
        self.y = None
        self.square = None

    def add_food(self):
        """If no food on window, create a new food block with random position on window"""
        if self.exists == False:
             # Create a surface to contain a square
            self.square = pg.Surface((BLOCK_SIZE, BLOCK_SIZE), pg.SRCALPHA)
            # Draw a square onto the "square" surface
            pg.draw.rect(self.square, (255, 0, 0), (BLOCK_SIZE * 0.05, BLOCK_SIZE * 0.05 , BLOCK_SIZE * 0.9, BLOCK_SIZE * 0.9))

            self.x = randint(1, (WIN_SIZE - BLOCK_SIZE)/BLOCK_SIZE) * BLOCK_SIZE
            self.y = randint(1, (WIN_SIZE - BLOCK_SIZE)/BLOCK_SIZE) * BLOCK_SIZE
            self.exists = True

    def check_if_eaten(self, snake):
        """If snake head is in food block, food is eaten"""
        snake_x, snake_y = snake(0)
        if (self.x <= snake_x <= self.x + BLOCK_SIZE * 0.9) and (self.y <= snake_y <= self.y + BLOCK_SIZE * 0.9):
            self.exists = False
            return True

        return False


def main():
    # Initialise PyGame
    pg.init()

    clock = pg.time.Clock()

    size = (WIN_SIZE, WIN_SIZE) # Size of window, (width, height)
    black = (0, 0, 0) # Background colour of window

    # Place head of snake in centre of window
    start_coord = (WIN_SIZE / 2) - (BLOCK_SIZE / 2)

    # Create window
    screen = pg.display.set_mode(size)

    head = Head((start_coord, start_coord))
    head.make_block()

    # Make first three blocks of snake
    snake = ()
    snake.append(head)
    snake = add_block(snake)
    snake = add_block(snake)

    ticker = 0
    game_over = False
    food = Food()
    # Game loop
    while game_over == False:
        # Run game at 60 FPS
        clock.tick(60)
        # Monitor events and check for keypresses
        for event in pg.event.get():
            game_over = check_keypress(event, head)
        if game_over == True:
            continue

        snake_pos = (block.get_pos() for block in snake)
        game_over = head.check_collision(snake_pos)

        # Update snake position every 4 frames
        if ticker == 3:
            for s in snake:
                s.update_pos()
            ticker = 0
        ticker += 1

        food.add_food()
        eaten = food.check_if_eaten(snake_pos)
        if eaten == True:
            snake = add_block(snake)

        # Clear the window before the next frame
        screen.fill(black)
        # Draw block to window
        screen.blit(food.square, (food.x, food.y))
        for s in snake:
            screen.blit(s.square, (s.x, s.y))
        # Swap buffers
        pg.display.flip()

    pg.quit()


if __name__ == "__main__":
    main()
```