My submission to Langjam Gamejam

Tags: gamedevtechnology

Contents

Langjam Gamejam seemed like a fun challenge. Game development and programming language design? What kind of game and language should I make? The possibilities are endless. My first idea was to make a simple state machine language and write a piece of interactive fiction. The focus would be in the game and storytelling, as the technical aspects of the language wouldn’t be interesting.

I thought it would be more challenging and fun to work with some sort of constraint; making a language with a familiar syntax wouldn’t be very interesting. Also, how would I handle the interaction between the language and the graphical and input elements? That was also something to consider. How to embed graphics, sound, color, and input capabilities to the game?

A thought came to me way before the jam started. I only started to write down the spec when the deadline countdown began.

The spark

My idea is: What if the screen contained the game’s state?

Each pixel of this hypothetical screen could be in one of many states. Each state would have an associated color. Pixels, or cells as I’m going to call them, would be able to interact with neighbors and react based on inputs.

For example, to simulate a falling pixel, I came up with this syntax:

# Define some states for the cells
CELL empty fill white
CELL particle fill black

GRID 10 10 empty # Fill a 10x10 grid with empty cells

ACTION particle fall # Define an action for particle cells, similar to methods in OOP
  # current cell will become "empty"
  SET empty 
  # the cell under it (+0 x, +1 y) will become a particle
  EXECUTE appear 0 1
END

ACTION empty appear
  SET particle
END

Now every time the fall action is triggered on particle cells, the particle will move down.

Particles should fall as time passes, so I added two new keywords.

TICK 100 ms # Run tick every 100 ms

INPUT tick
  ALL particle fall
END

Here’s the first interactive example.

CELL empty fill white
CELL particle fill black
TICK 100 ms

GRID 10 10 empty

ACTION particle fall
  SET empty 
  EXECUTE appear 0 1
END

ACTION empty appear
  SET particle
END

INPUT tick
  ALL particle fall
END

SET particle 2 2
SET particle 4 4

Every tick, the fall action will be run on all cells (Only particle cells will be affected. Other input directives can be added, e.g. to detect key presses.

With this, we have the base of a programming language:

Is this enough to make a game?

Making Snake

Snake was my first thought. After all, the entire game’s state is present on the screen:

The only thing missing is its current direction, but that can be represented as four different states, all having the same color.

CELL food fill red
CELL empty fill white

CELL head_up fill green
CELL head_down fill green
CELL head_left fill green
CELL head_right fill green

Adding movement

Every tick, the head should move in the current direction it’s pointing at. Additionally, it can react to key presses to change directions.

CELL food fill red
CELL empty fill white

CELL head_up fill green
CELL head_down fill green
CELL head_left fill green
CELL head_right fill green

GRID 30 30 empty
SET head_up 15 15
SET food 20 20

TICK 100

INPUT tick
  ALL head_up move
  ALL head_down move
  ALL head_left move
  ALL head_right move
END

ACTION head_up move
  SET empty
  SET head_up 0 -1
END

ACTION head_down move
  SET empty
  SET head_down 0 1
END

ACTION head_left move
  SET empty
  SET head_left -1 0
END

ACTION head_right move
  SET empty
  SET head_right 1 0
END

ACTION head_left go_up
  SET head_up
END

ACTION head_right go_up
  SET head_up
END

ACTION head_left go_down
  SET head_down
END

ACTION head_right go_down
  SET head_down
END


INPUT ArrowUp
  ALL head_left go_up
  ALL head_right go_up
END

INPUT ArrowDown
  ALL head_left go_down
  ALL head_right go_down
END

ACTION head_up go_left
  SET head_left
END

ACTION head_down go_left
  SET head_left
END

ACTION head_up go_right
  SET head_right
END

ACTION head_down go_right
  SET head_right
END


INPUT ArrowLeft
  ALL head_up go_left
  ALL head_down go_left
END

INPUT ArrowRight
  ALL head_up go_right
  ALL head_down go_right
END

Eating food

When the snake eats a piece of food, it grows. This new segment will keep moving in the same direction that the head was moving.

In the move action, instead of simply changing the state, additional behavior must be added. Also, the RAND keyword is introduced, to turn a random pixel into a piece of food.

CELL food fill red
CELL empty fill white

CELL head_up fill green
CELL head_down fill green
CELL head_left fill green
CELL head_right fill green

# Tail cells
CELL tail_up fill yellow
CELL tail_down fill yellow
CELL tail_left fill yellow
CELL tail_right fill yellow

GRID 30 30 empty
SET head_up 15 15
SET food 20 20
SET food 20 10

TICK 100

INPUT tick
  ALL head_up move
  ALL head_down move
  ALL head_left move
  ALL head_right move
END

ACTION empty become_food
  SET food
END

ACTION empty move_up # Snake moves up into an empty space
  SET head_up
  SET empty 0 1
  EXECUTE move_tail_up 0 2  # Move the tail segment thats closest to the head
END

ACTION empty move_down
  SET head_down
  SET empty 0 -1
  EXECUTE move_tail_down 0 -2
END

ACTION empty move_left
  SET head_left
  SET empty 1 0
  EXECUTE move_tail_left 2 0
END

ACTION empty move_right
  SET head_right
  SET empty -1 0
  EXECUTE move_tail_right -2 0
END

ACTION food move_up # Snake moves up into a piece of food
  SET head_up       # The head takes the place of the food
  SET tail_up 0 1   # New tail segment
  RAND empty become_food
END

ACTION food move_down
  SET head_down
  SET tail_down 0 -1
  RAND empty become_food
END

ACTION food move_right
  SET head_right
  SET tail_right -1 0
  RAND empty become_food
END

ACTION food move_left
  SET head_left
  SET tail_left 1 0
  RAND empty become_food
END

ACTION head_up move
  EXECUTE move_up 0 -1
END

ACTION head_down move
  EXECUTE move_down 0 1
END

ACTION head_left move
  EXECUTE move_left -1 0
END

ACTION head_right move
  EXECUTE move_right 1 0
END

ACTION head_left go_up
  SET head_up
END

ACTION head_right go_up
  SET head_up
END

ACTION head_left go_down
  SET head_down
END

ACTION head_right go_down
  SET head_down
END

ACTION tail_up move_tail_up
  SET tail_up 0 -1
  SET empty
  EXECUTE move_tail_up 0 1
END

ACTION tail_down move_tail_down
  SET tail_down 0 1
  SET empty
  EXECUTE move_tail_down 0 -1
END

ACTION tail_left move_tail_left
  SET tail_left -1 0
  SET empty
  EXECUTE move_tail_left 1 0
END

ACTION tail_right move_tail_right
  SET tail_right 1 0
  SET empty
  EXECUTE move_tail_right -1 0
END


INPUT ArrowUp
  ALL head_left go_up
  ALL head_right go_up
END

INPUT ArrowDown
  ALL head_left go_down
  ALL head_right go_down
END

ACTION head_up go_left
  SET head_left
END

ACTION head_down go_left
  SET head_left
END

ACTION head_up go_right
  SET head_right
END

ACTION head_down go_right
  SET head_right
END


INPUT ArrowLeft
  ALL head_up go_left
  ALL head_down go_left
END

INPUT ArrowRight
  ALL head_up go_right
  ALL head_down go_right
END

There’s some behavior missing, when turning, the tail segments don’t follow the head. More states need to be created to properly make the snake work.

A failed attempt

It was at this point that I realized that implementing Snake was proving to be more complicated than expected. The biggest challenge is making sure the tail segments “know” where the previous segment is, so they can move together every tick.

I didn’t want to further enhance the language so implementing Snake would become easier (or possible, I still don’t know if it is!)

Let’s make another game

I opted for implementing an easier game. A TRON clone for two players.

Use WASD and the arrow keys to move.

CELL player1_up fill red
CELL player1_down fill red
CELL player1_left fill red
CELL player1_right fill red
CELL player1_trail fill pink

CELL player2_up fill blue
CELL player2_down fill blue
CELL player2_left fill blue
CELL player2_right fill blue
CELL player2_trail fill cyan 
CELL empty fill black

GRID 40 40 empty
SET player1_right 2 20
SET player2_left 37 20

TICK 50

INPUT tick
  ALL player1_up move
  ALL player1_down move
  ALL player1_left move
  ALL player1_right move

  ALL player2_up move
  ALL player2_down move
  ALL player2_left move
  ALL player2_right move
END

ACTION player1_up move
  SET player1_trail
  EXECUTE collision 0 -1
  SET player1_up 0 -1
END

ACTION player1_down move
  SET player1_trail
  EXECUTE collision 0 1
  SET player1_down 0 1
END

ACTION player1_left move
  SET player1_trail
  EXECUTE collision -1 0
  SET player1_left -1 0
END

ACTION player1_right move
  SET player1_trail
  EXECUTE collision 1 0
  SET player1_right 1 0
END

ACTION player2_up move
  SET player2_trail
  EXECUTE collision 0 -1
  SET player2_up 0 -1
END

ACTION player2_down move
  SET player2_trail
  EXECUTE collision 0 1
  SET player2_down 0 1
END

ACTION player2_left move
  SET player2_trail
  EXECUTE collision -1 0
  SET player2_left -1 0
END

ACTION player2_right move
  SET player2_trail
  EXECUTE collision 1 0
  SET player2_right 1 0
END

ACTION player1_trail collision
  HALT
END

ACTION player2_trail collision
  HALT
END

INPUT w
  ALL player1_left go_up
  ALL player1_right go_up
END

INPUT a
  ALL player1_up go_left
  ALL player1_down go_left
END

INPUT d
  ALL player1_up go_right
  ALL player1_down go_right
END

INPUT s
  ALL player1_left go_down
  ALL player1_right go_down
END

INPUT ArrowUp
  ALL player2_left go_up
  ALL player2_right go_up
END

INPUT ArrowLeft
  ALL player2_up go_left
  ALL player2_down go_left
END

INPUT ArrowRight
  ALL player2_up go_right
  ALL player2_down go_right
END

INPUT ArrowDown
  ALL player2_left go_down
  ALL player2_right go_down
END

ACTION player1_up go_left
  SET player1_left
END

ACTION player1_up go_right
  SET player1_right
END

ACTION player1_down go_left
  SET player1_left
END

ACTION player1_down go_right
  SET player1_right
END

ACTION player1_left go_up
  SET player1_up
END

ACTION player1_left go_down
  SET player1_down
END

ACTION player1_right go_up
  SET player1_up
END

ACTION player1_right go_down
  SET player1_down
END

ACTION player2_up go_left
  SET player2_left
END

ACTION player2_up go_right
  SET player2_right
END

ACTION player2_down go_left
  SET player2_left
END

ACTION player2_down go_right
  SET player2_right
END

ACTION player2_left go_up
  SET player2_up
END

ACTION player2_left go_down
  SET player2_down
END

ACTION player2_right go_up
  SET player2_up
END

ACTION player2_right go_down
  SET player2_down
END

# Walls
CELL wall fill gray
SET wall 0 0
SET wall 0 39

ACTION wall collision
  HALT
END

# Draw top wall
ACTION wall grow_right
  SET wall 1 0
  EXECUTE grow_right 1 0
END

ACTION wall grow_down
  SET wall 0 1
  EXECUTE grow_down 0 1
END

ALL wall grow_right
EXECUTE grow_down 0 0
EXECUTE grow_down 39 0

Conclusion

The language is very verbose, lots of similar code must be repeated to handle the user inputs and all the possible movement directions. But hey! It works. Forget about making a game with more advanced math. Proving if this language is even Turing complete seems like a fun challenge.

I think I achieved my goal of making a very simple language (easy to parse and implement) that could show interesting behaviors. Perhaps adding stuff such as variables and functions can enable making more complex games.

Next time I should stick with a more traditional language…

Other fun things

Binary counter

CELL zero fill white
CELL one fill black

GRID 8 1 zero

ACTION zero count
  SET one
END

ACTION one count
  SET zero
  EXECUTE count -1 0
END

TICK 100
INPUT tick
  EXECUTE count 7 0
END

Sokoban

To implement a simple sokoban game the following behaviors must be implemented:

Handle when the player…

Use the arrow keys to move.

CELL player fill red
CELL wall fill black
CELL box fill gray
CELL empty fill white

TICK 100

GRID 20 20 empty
SET player 10 10
SET box 8 8
SET box 4 2
SET wall 5 5
SET wall 5 6
SET wall 5 7
SET wall 16 16
SET wall 17 16

INPUT ArrowUp
  ALL player move_up
END

INPUT ArrowDown
  ALL player move_down
END

INPUT ArrowLeft
  ALL player move_left
END

INPUT ArrowRight
  ALL player move_right
END


ACTION player move_up
  EXECUTE player_move_up 0 -1
END

ACTION player move_down
  EXECUTE player_move_down 0 1
END

ACTION player move_left
  EXECUTE player_move_left -1 0
END

ACTION player move_right
  EXECUTE player_move_right 1 0
END


ACTION empty player_move_up
  SET player
  SET empty 0 1
END

ACTION empty player_move_down
  SET player
  SET empty 0 -1
END

ACTION empty player_move_left
  SET player
  SET empty 1 0
END

ACTION empty player_move_right
  SET player
  SET empty -1 0
END


ACTION box player_move_up
  EXECUTE box_move_up 0 -1
END

ACTION box player_move_down
  EXECUTE box_move_down 0 1
END

ACTION box player_move_left
  EXECUTE box_move_left -1 0
END

ACTION box player_move_right
  EXECUTE box_move_right 1 0
END


ACTION empty box_move_up
  SET box
  SET empty 0 2
  SET player 0 1
END

ACTION empty box_move_down
  SET box
  SET empty 0 -2
  SET player 0 -1
END

ACTION empty box_move_left
  SET box
  SET empty 2 0
  SET player 1 0
END

ACTION empty box_move_right
  SET box
  SET empty -2 0
  SET player -1 0
END

When the player attempts an invalid move (e.g. Moving into a wall) An action is called on the cell to which the player or the box wants to move. If the action is not defined, nothing happens.

Syntax

I decided to call the language InterState (Interacting States)

# Comment

GRID <width> <height> <default state>

# Cell definition
CELL <name> <shape> <color>

# Tick time definition
TICK <duration in milliseconds>

# Action definition
ACTION <cell> <action>
  SET <cell> [dx] [dy] # Defauly X Y values are 0, 0
  EXECUTE <action> [dx] [dy]
  ALL <cell> <action>
END

# Input definition
INPUT <input name>
  # When outside an action or inside an input, coordinates are absolute
  SET <cell> <x> <y>
  EXECUTE <action> <x> <y>
END

# Execute <action> on a random <cell>
RAND <cell> <action>
# Stop the program
HALT

Improving the language

I only implement a single shape type for the cells: fill. However, SVG allows to use more than pixels.

ALL executes actions on cells in a very specific order (row by row, left to right, up to down). This can cause problems in some cases. Adding an option to specify the order in which cells are updated is a good idea.