Snake
Snake is a sophisticated 3D implementation of the classic Snake game, adapted for LED Matrix Cube displays. It supports multiple simultaneous players, joystick control, and features advanced 3D navigation mechanics that allow snakes to move seamlessly across cube faces.
Game Features
Core Gameplay
3D Snake Movement: Snakes navigate through 3D space and across cube surfaces
Multi-Player Support: Up to 8 simultaneous players with different colors
Collision Detection: Snake-to-snake collisions and self-collision detection
Food System: Randomly placed food items that cause snakes to grow
Score Persistence: High score tracking with file-based storage
Dynamic Difficulty: Snake speed increases as players eat food
Technical Architecture
Class Structure
The Snake application consists of multiple interconnected classes:
class Snake : public CubeApplication {
public:
Snake(); // Constructor with 40 FPS
bool loop() override; // Main game loop
private:
std::vector<Player*> players; // Active players
std::vector<Food*> food; // Food items
int currentHighScore; // Current high score
};
Player Class
Each snake is represented by a Player
object with complex behavior:
class Snake::Player {
public:
Player(CubeApplication* renderCube, int joysticknumber,
Vector3f setPosition, Vector3f setVelocity,
Color setColor, unsigned int length);
void step(); // Update player state
void handleJoystick(); // Process input
void render(); // Draw snake
void turnLeft(); // Left turn logic
void turnRight(); // Right turn logic
private:
std::vector<Vector3f> tail; // Snake body segments
Joystick joystick; // Controller interface
};
Food Class
Food items are simple objects that provide growth when consumed:
class Snake::Food {
public:
Food(CubeApplication* renderCube, Vector3i setPosition, Color setColor);
void render(); // Draw food item
void eat(); // Mark as consumed
private:
Vector3i position; // 3D position
Color color; // Display color
bool isEaten; // Consumption state
};
Game Mechanics
Player Movement
Snake movement in 3D space involves complex coordinate transformations:
Position Updates:
void Player::step() {
accelerate(); // Apply acceleration
move(); // Update position
warp(); // Handle edge transitions
// Position clamping and rounding
for (unsigned int i = 0; i < 3; i++) {
if (position[i] < 0.01 && position[i] > 0)
position[i] = 0;
if (position[i] > VIRTUALCUBEMAXINDEX - 0.01)
position[i] = VIRTUALCUBEMAXINDEX;
}
}
Edge Navigation:
The warp()
function handles transitions between cube faces:
void Player::warp() {
Vector3i currentPosition = iPosition();
EdgeNumber currentEdge = ca->getEdgeNumber(currentPosition);
if (currentEdge != anyEdge) {
// Transform velocity vectors based on edge type
switch (currentEdge) {
case topLeft:
case topRight:
case bottomRight:
case bottomLeft:
std::swap(velocity[2], velocity[0]);
break;
// ... additional edge cases
}
// Reflect velocity if hitting boundaries
if ((currentPosition[0] == 0 && velocity[0] < 0) ||
(currentPosition[0] == VIRTUALCUBEMAXINDEX && velocity[0] > 0))
velocity[0] *= -1;
}
}
Turning Mechanics
The turning system adapts to the snake’s current position on the cube:
void Player::turnLeft() {
if (!isDying && !ca->isOnEdge(iPosition())) {
if (position[2] == 0) { // Bottom face
std::swap(velocity[0], velocity[1]);
velocity[0] = -velocity[0];
} else if (position[2] == VIRTUALCUBEMAXINDEX) { // Top face
std::swap(velocity[0], velocity[1]);
velocity[1] = -velocity[1];
}
// ... additional face-specific turning logic
}
}
Input System
Joystick Integration
Each player can use a dedicated joystick controller:
void Player::handleJoystick() {
if (joystick.isFound()) {
float newAxis0 = joystick.getAxis(0);
if (newAxis0 < 0 && lastAxis0 == 0) {
turnLeft();
} else if (newAxis0 > 0 && lastAxis0 == 0) {
turnRight();
}
lastAxis0 = newAxis0;
} else {
doKiMove(); // AI fallback
}
}
AI Fallback
Players without joysticks use simple AI:
void Player::doKiMove() {
int random = rand() % 512;
if (random == 55) {
turnLeft();
} else if (random == 66) {
turnRight();
}
}
Collision System
Multi-layered Collision Detection
The game implements several collision types:
Snake-to-Snake Collisions:
for (auto player : players) {
for (auto player2 : players) {
if (player->collidesWith(player2->iPosition()) &&
player != player2 && !player->getIsDying() &&
!player2->getIsDying()) {
player2->die();
player->grow(player2->getSnakeLength() / 4);
player->speedUp(1.10);
}
}
}
Self-Collision Detection:
Vector3i head = iPosition();
int colCounter = 0;
for (auto t : tail)
if (head == t.cast<int>())
colCounter++;
if (colCounter > 1)
die();
Food Consumption:
for (auto f : food) {
for (auto p : players) {
if (p->iPosition() == f->getPosition()) {
p->grow(2);
p->speedUp(1.05);
f->eat();
food.push_back(new Food(this, getRandomPointOnScreen(anyScreen),
Color::randomBlue() * 2));
}
}
}
Score Management
High Score System
The game maintains persistent high scores using file I/O:
bool Snake::updateHighScoreFromToFile(int score, std::string filename) {
std::ifstream highScoreFile(filename);
if (highScoreFile) {
highScoreFile >> currentHighScore;
} else {
// Create new high score file
std::ofstream newFile(filename);
newFile << 0;
currentHighScore = 0;
}
if (score > currentHighScore) {
currentHighScore = score;
std::ofstream updateFile(filename);
updateFile << currentHighScore;
return true; // New high score achieved
}
return false;
}
High Score Display
New high scores trigger a celebration animation:
if (highScoreTime) {
Color fontColor = highScoreColor;
if (highScoreTimer/5%2 == 0)
fontColor = Color::black(); // Blinking effect
else
fontColor = highScoreColor;
// Display on all faces
drawText(top, Vector2i(centered, centered), fontColor,
"HIGHSCORE " + std::to_string(currentHighScore));
}
Visual Effects
Death Animation
When snakes die, they flash between colors:
if (isDying) {
if (dieCounter / 40 % 2) {
color = Color::black();
} else {
color = Color::white();
}
dieCounter++;
if (dieCounter >= 200) {
isDead = true;
}
}
Snake Rendering
Each snake segment is rendered as a 3D pixel:
void Player::render() {
for (auto t : tail) {
ca->setPixel3D(t[0], t[1], t[2], color);
}
}
Performance Optimization
Oversampling
The game uses oversampling to ensure smooth collision detection:
for (int oversampling = 8; oversampling > 0; oversampling--) {
for (auto player : players) {
player->step();
// ... collision detection and rendering
}
}
Memory Management
Efficient food management using STL algorithms:
food.erase(std::remove_if(food.begin(), food.end(),
[](Food *f) { return (f->getIsEaten()); }),
food.end());
Build and Execution
The Snake application requires uncommenting in the main CMakeLists.txt:
add_subdirectory(Snake)
Compilation and execution:
make snake
./snake
Game Controls
Default controller mappings:
Axis 0 Left/Right: Turn left/right
Multiple Controllers: Support for up to 8 simultaneous players
AI Fallback: Non-controller players use random AI
This implementation demonstrates advanced 3D game development techniques and provides an engaging multi-player experience on LED Matrix Cube displays.