Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

🐍 Snake

A classic Snake, rebuilt pixel by pixel to learn what a game loop actually is.

C# Library Status License


🔎 overview/

A classic Snake, written from scratch in C# on top of Aiv.Draw, a bare-bones educational graphics library that hands you a raw bitmap and nothing else.

Built in November 2022, during my first year at AIV (Accademia Italiana Videogiochi), as a way to actually use what I'd just learned instead of just reading about it: variables, loops, arrays, basic OOP, and how a game loop ties input, update, and draw together every frame.

The only twist on the classic formula: every 10 points, the screen gets a short shower of falling pixels as a little celebration/warning.

What I took away from it:

  • My first real "input → update → draw" loop, built without any engine underneath it
  • Working directly with a raw pixel buffer forced me to understand things like alpha blending and grid alignment instead of trusting a framework to handle them
  • A first, honest look at how quickly "quick and simple" code turns into something that's hard to extend — which shaped how I approach architecture today
  • Status: complete, not maintained — it is what it was: a two-month-in learning exercise.

🕹️ functional/

Tech stack: C#, .NET Framework 4.8, Aiv.Draw 1.0.1 (NuGet)

Requirements:

  • Windows (Aiv.Draw targets net48)
  • Visual Studio (or any IDE that can restore NuGet packages and build .NET Framework console apps)

Setup:

  1. Clone the repo: git clone https://github.com/Manu2402/Snake.git
  2. Open Snake/Snake.sln in Visual Studio
  3. Let NuGet restore Aiv.Draw (declared in packages.config)
  4. Build and run — a console window (instructions + final score) and a game window (855×855) will open side by side
  • Controls: W A S D for movement (Up, Left, Down, Right). No diagonal movement, no pause — this is Snake in its purest, least forgiving form.

Configuration: none exposed — window size, grid size, tick rate, and rain trigger (every 10 points) are all hardcoded constants in the source.

Gameplay preview The snake growing on a 30px grid, with the pixel-rain effect triggering after crossing a multiple of 10 points.

Playable build: none — source only, build it yourself.


⚙️ technical/

Architecture

Everything runs off a set of static "manager" classes that double as global state — Game, Gfx, Timer, Score, RandomGenerator, ColorsFactory. Game owns the loop: input → timer tick → update → collision checks → draw, all inside a single while (Gfx.Window.IsOpened) in Game.Play(). There's no scene graph, no component system, no abstraction layer between "game" and "rendering" — Gfx writes directly into Window.Bitmap, the raw RGB byte array Aiv.Draw exposes, and every drawable object (Pixel, SnakeRect, sprites) just calls into Gfx to stamp itself onto that buffer each frame.

Movement is grid-based rather than physics-based: everything snaps to a fixed 30px cell. The snake's head reads directional input (SnakeRect.CheckInput), and each following body piece simply inherits the previous piece's last direction (PrevCharPressed) on the next tick — a manual follow-the-leader chain instead of a position-history buffer, which is the classic naive way to implement a snake body.

Algorithms & data structures

  • Fixed-size snake body: SnakeBody.Snake is a pre-allocated SnakeRect[784] (a 29×29 grid's worth of cells) rather than a growable list — the array is sized for the theoretical maximum length up front and filled in as the snake eats.
  • Rejection-sampling apple spawn: Apple.Update() picks random coordinates in a do...while loop until they land on a valid grid intersection (multiples of 15 but not 30, to center the sprite inside a cell) and inside the play field.
  • Manual sprite alpha blending: Gfx.DrawSprite reads each sprite pixel's RGBA, computes the alpha percentage by hand, and blends it channel-by-channel against whatever is already in the window's bitmap — no built-in compositing, since Aiv.Draw doesn't provide any.
  • Grid-equality collision detection: collisions (snake ↔ apple, snake ↔ itself) are just float equality checks on grid-aligned coordinates. It works only because every position is snapped to the 30px grid — there's no tolerance or bounding-box math anywhere.
  • Custom decoupled timer: Timer runs on DeltaTime but only flips an alarm flag every 0.5s, so the snake's movement tick is decoupled from the render frame rate.
  • Particle-style rain effect: Rain recycles a pool of 420 pixels with an IsAlive flag — pixels fall with a randomized velocity and get reset to the top once they leave the screen, a small, self-contained particle system built without any pooling framework.

The hardest decision

Whether to give the snake body a proper dynamic structure (a List<SnakeRect>, or a linked list of positions) or a fixed, pre-sized array. I went with the fixed SnakeRect[784], sized for the maximum possible length on the grid, and grew a counter (counterPieces) into it instead of resizing anything at runtime. It's wasteful and inflexible — but at that point in the course I hadn't covered dynamic collections yet, and the goal was to practice arrays and manual indexing, not to write the "correct" solution. It's a decision I'd reverse immediately today, but it's an honest snapshot of where I was at the time.

No automated tests — this predates that habit for me. Debugging was done live with Visual Studio's debugger, stepping through the grid math and collision checks pixel by pixel.

About

Simple Snake written in C# with "Aiv.Draw" library

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages