JS13kGames: a game jam under 13kB

alexmbeasley

Alex Beasley

Posted on November 26, 2023

JS13kGames: a game jam under 13kB

In the world of gaming, size has always been an ever expanding issue. The original Super Mario Bros for NES had a file size of 40kB and this was considered revolutionary in size reduction for at home use. Call of Duty: Warzone (as of November 2023) now has a requirement of 175 GB of space to play as the game is always expanding and adding on new features. But what if we wanted to make a smaller, yet equally as fun game.......

Image description

js13kGames is a competition where developers are required to have their game come in at under 13kB of space when compressed using ZIP. Contributors are not allowed to use external services or libraries, must utilize JavaScript with HTML5, and all aspects need to be under the required size limit.

Some common file sizes:

A typical email - 75kB
'Spacewar!'(1962 https://en.wikipedia.org/wiki/Spacewar!) - 18kB
High Res image in 75 PPI - 250 to 500kB
Average Webpage - 800kB for desktop and 400kb for mobile
Amazon data storage - 171,798,691,840,000kB
This blog post - 13kB

Created in 2012 by Andrzej Mazur, this yearly event is held between August 13th to September 13th. The size restrictions force developers to use different strategies for code optimization and asset compression in order to meet the size requirements.

Minification and Compression

One of the key strategies to reduce the size of your code is minification with compression. Some tools developers can use is the Terser compressor or gzip. Terser will reduce the file size by removing unnecessary characters, whitespace and renaming variables. Gzip will remove any redundancies or repeated strings with binary representation references.

//original code
function getScore(player) {
return player.currPoints + player.level * 2;
}

//terser mini code 
const c = (x) => x.currPoints + x.level * 2;
Enter fullscreen mode Exit fullscreen mode

Kontra.js

https://straker.github.io/kontra/
Kontra is a lightweight JavaScript gaming micro-library specially optimized to be used for the js13kGame competition. Kontra aims to implement basic game components like asset loading, user inputs, loading loops, and sprites. This allows developers to spend less time worrying about smaller components and only focus on their personal game designs.

Examples of using the kontra library can be found in most of the games, but can be seen here with the only-one game:
https://github.com/cemalgnlts/only-one-js13k
https://js13kgames.com/entries/only-one

In the code section below we will use:

Sprite - a way to create your player object; can handle rectangles, images, and sheet animations
Vector - a 2d vector object that takes in x and y coordinates
keyPressed - checks if a key is currently pressed and an update() performs an action for each frame

//import the needed dependences from the kontra library
import { init, initPointer, initKeys, Sprite, Vector, Scene, keyPressed, getWorldRect, track, clamp, GameLoop } from "/kontra.mjs";

//create a player, we will use the Kontra features Sprite, Vector and keyPressed
const player = Sprite({
//set the initial position of the player
    x: WIDTH / 2 - 10,
    y: HEIGHT / 2 - 10,
    radius: 20,
    halfRadius: 20 / 2,

 //create the anchor point for the player, center of sprite
    anchor: Vector(0.5, 0.5),
    color: colors.player,

  //this is called every frame to update the players current state

    update() {
  //moves player a certain distance, DELTA
        this.advance(DELTA);

        let pos = Vector(0, 0);

 //check for key movements, either arrows or wsad
        if(keyPressed("w") || keyPressed("arrowup")) pos.y = -1;
        else if(keyPressed("s") || keyPressed("arrowdown")) pos.y = 1;

        if(keyPressed("a") || keyPressed("arrowleft")) pos.x = -1;
        else if(keyPressed("d") || keyPressed("arrowright")) pos.x = 1;

        if(pos.x !== 0 && pos.y !== 0) pos = pos.normalize();

        pos = pos.scale(0.15);

        this.dx = pos.x;
        this.dy = pos.y;

        if(this.x < this.radius) this.x = this.radius;
        else if(this.x > WIDTH - this.radius) this.x = WIDTH - this.radius;

        if(this.y < this.radius) this.y = this.radius;
        else if(this.y > HEIGHT - this.radius) this.y = HEIGHT - this.radius;
    },
//now that the player mechanics are laid out, this will render the sprite

    render() {
        context.fillStyle = this.color;

        context.beginPath();
        context.arc(0, 0, this.radius, 0, Math.PI * 2);
        context.fill();
    }
});

Enter fullscreen mode Exit fullscreen mode

Lets look at how you create a new enemy that can shoot an arrow.

function newArcher(x, y) {
    return Sprite({
        x,
        y,
        speed: 3,
        width: 40,
        height: 40,
        color: colors.enemy,
        anchor: Vector(0.5, 0.5),
        time: 0,
        arrowDelay: 1,
Enter fullscreen mode Exit fullscreen mode

First, we create a function that will take in an x and y position that will represent the archers location. We will use the Kontra Sprite feature to initialize some properties such as location, movement speed, size, color, anchor point, time, and arrow delay.

throwArrow() {
    if(this.time <= this.arrowDelay) return;        
    this.time = 0;
    const arrow = newArrow(this.x, this.y, player);
    arrows.push(arrow);
        },
Enter fullscreen mode Exit fullscreen mode

Now we can create the functionality to shoot the arrows. We check if enough time has passed in-between the last arrow. If it has, we reset the time and create a new arrow function(included in the code) and add an arrow to the already initialized empty arrow array.

update() {
    const dist = this.position.subtract(player.position);

    if(dist.length() <= this.width * 7) {
        this.time += MS;
        this.throwArrow();
        return;
    }

    this.rotation += 0.05;

    const dir = dist.normalize()
        .scale(this.speed);

    this.x -= dir.x;
    this.y -= dir.y;
},
Enter fullscreen mode Exit fullscreen mode

The update method will be called for each frame in the game loop. It calculates the distance between the player, and in this case, the archer. If the current player is within 7 times the width of the archer, it will increment the time and throwArrow function to shoot more arrows at the player. It then updates the archers position.

render() {
    context.fillStyle = this.color;

    context.beginPath();
    context.roundRect(0, 0, this.width, this.height, 10);
    context.fill();
}
Enter fullscreen mode Exit fullscreen mode

Finally, we render the archer sprite. It sets the fill style of the archer, creates a rounded rectangle path for it to travel on using the roundRect method of Canvas 2D API.

In conclusion, JS13kGames not only challenges modern game development, but also helps create new methods for streamlining efficient code designs.

Image description

💖 💪 🙅 🚩
alexmbeasley
Alex Beasley

Posted on November 26, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

JS13kGames: a game jam under 13kB
javascript JS13kGames: a game jam under 13kB

November 26, 2023