Skip to main content

Placing the Fruit

A freely moving snake is nice. But it get's a bit dull if that's all there is. To make it a bit more of a challenge, you'd need to add something to change the snake. The classic approach is to let the snake "eat" fruits. That's a good place to start.

To place (and eat) a fruit, you first need to make a variable for this. Since it's simply a point on the grid, `Point` will do. Set it to undefined for now:
const w4 = @import("wasm4.zig");const Snake = @import("snake.zig").Snake;const Point = @import("snake.zig").Point;
var snake = Snake.init();var fruit: Point = undefined;var frame_count: u32 = 0;var prev_state: u8 = 0;

Random Numbers#

Zig's standard library has a couple of different random number generators, including some that are meant to be cryptographically secure. We don't need anything our snake game to be cryptographically secure, so we'll just use std.rand.DefaultPrng, where Prng means pseudo-random number generator. To start, we'll need to import std and initialize the prng:

const w4 = @import("wasm4.zig");const std = @import("std");const Snake = @import("snake.zig").Snake;const Point = @import("snake.zig").Point;
var snake = Snake.init();var fruit: Point = undefined;var frame_count: u32 = 0;var prev_state: u8 = 0;var prng: std.rand.DefaultPrng = undefined;var random: std.rand.Random = undefined;
export fn start() void {    w4.PALETTE.* = .{        0xfbf7f3,        0xe5b083,        0x426e5d,        0x20283d,    };    prng = std.rand.DefaultPrng.init(0);    random = prng.random();}

We can call random.intRangeLessThan(T, at_least, less_than) to get a number between at_least and less_than, with the type of T. You can wrap that in a helper function if you'd like:

fn rnd(max: i32) i32 {    return random.intRangeLessThan(i32, 0, max);}

Now use it for the location of the fruit:

export fn start() void {    w4.PALETTE.* = .{        0xfbf7f3,        0xe5b083,        0x426e5d,        0x20283d,    };    prng = std.rand.DefaultPrng.init(0);    random = prng.random();    fruit = Point.init(rnd(20), rnd(20));}

Importing PNG Files#

Importing images in WASM-4 works a bit different compared to other game engines and Fantasy Consoles. Images have to meet certain criteria:

  • PNG only
  • Index only
  • 4 colors max

Indexed PNG files can be created by several image apps like Aseprite or GIMP.

The image we import is a 8x8 PNG file with exactly 4 colors:

Zoomed Fruit This image is zoomed by 800%.

Zoomed Fruit This is the original image. You can download it to proceed.

Now you need to import the image. For this, the WASM-4 CLI tool w4 comes with another tool: png2src. You can use it like this:

w4 png2src --zig fruit.png

This will output the following content in the terminal:

const fruit_width = 8;const fruit_height = 8;const fruit_flags = 1; // BLIT_2BPPconst fruit = [16]u8{ 0x00,0xa0,0x02,0x00,0x0e,0xf0,0x36,0x5c,0xd6,0x57,0xd5,0x57,0x35,0x5c,0x0f,0xf0 };

To get it into a an existing file, use the >> operator (or copy and paste). Like this:

w4 png2src --zig fruit.png >> main.zig

This will add the previous lines to your main.zig and causes an error because "fruit" already exists. Just rename the new fruit to fruitSprite and move it somewhere else. Also: You can remove the other stuff added, you won't need it for this project:

var snake = Snake.init();var fruit: Point = undefined;var frame_count: u32 = 0;var prev_state: u8 = 0;var random: std.rand.Random = undefined;const fruit_sprite = [16]u8{ 0x00,0xa0,0x02,0x00,0x0e,0xf0,0x36,0x5c,0xd6,0x57,0xd5,0x57,0x35,0x5c,0x0f,0xf0 };

With that out of the way, it's time to actually render the newly imported sprite.

Rendering a PNG File#

Rendering the sprite is rather simple. Just call the blit function of w4:

/// Copies pixels to the framebuffer.pub extern fn blit(sprite: [*]const u8, x: i32, y: i32, width: i32, height: i32, flags: u32) void;

In practice it looks like this:

export fn update() void {    frame_count += 1;
    input();
    if (frame_count % 15 == 0) {        snake.update();    }    snake.draw();
    w4.blit(&fruit_sprite, fruit.x * 8, fruit.y * 8, 8, 8, w4.BLIT_2BPP);}

But since you set the drawing colors, you need to change the drawing colors too:

    snake.draw();
    w4.DRAW_COLORS.* = 0x4320;    w4.blit(&fruit_sprite, fruit.x * 8, fruit.y * 8, 8, 8, w4.BLIT_2BPP);

This way, w4 uses the color palette in its default configuration. Except for one thing: The background will be transparent.