It doesn't come with engine stuff, like complicated collision detection
or physics. If you need such thing, you need to build it yourself. Or
find something already written by someone else that is ready to work
with Raylib.

Raylib PHP Extension

Recently a PHP extension caught my attention. Developed by
@joseph-montanez quite a while
ago, the raylib-php
extension got its first alpha release less than a month ago.

If you need to learn how to compile and run it please head to the
repository's README.md file. On MacOS the following steps worked fine
for me:

It only compiled fine with PHP 7.4 for me. So make sure you have the
appropriate php version.

This extension aims to provide bindings to the C library so we can
write same games in PHP.

Of course as the C library won't provide game-specific features like
physics and others. So such things would have to be developed on PHP side.

This extension is not complete yet. You can check the MAPPING.md file
from the official repository to understand what was already accomplished.

Even though it is not complete, I decided to play a bit with it and, as
far as I can see, it is already pretty functional.

A Simple Snake Game

Even though "Snake" is a simple game I decided not to implement it
completely. My main goal here was to have a good enough running engine
that would test some basic features from the extension.

I picked then a couple of requirements to implement:

The snake must be constantly moving, but may change direction

There shall be one fruit placed in a random spot on the screen

When the head of the snake hits a fruit five things must happen: the fruit must be destroyed, the snake's body must grow, another fruit must be created, the score counter must increase by one and Snake's speed also increases.

When Snake hits a window edge it should warp to the opposite edge

Should be clear, but is also required that the player would change Snake's
direction using an input device, like a keyboard.

Two extremely important requirements I chose not to implement were 1)
that the Snake should not bite itself. Meaning that if by any reason
Snake hits its own body, the game should be over. And 2) that Snake
cannot change immediately to its opposite direction. So when you're heading
right, switching to left direction would require to first go up or down.

Those two requirements were not implemented as they as purely algorithmic
and wouldn't add much to the experimentation of the extension itself.

Implementation

The implementation of this game has two components: a Game Loop and a
Game State.

The game loop is responsible for updating the game state based on user
inputs and calculations and later on painting this state on the screen.
For this I've created a class named "GameLoop".

The game state holds a snapshot of the game. It holds things like the
player's score, the fruit's x,y coordinates, the x,y coordinates from
Snake and all squares composing its body. For this one a “GameState”
class was created.

Here's how they look like.

Game Loop

The GameLoop class initializes the system, and creates a loop that
executes two steps on each iteration: update state and draw state.

So in the constructor I just initialize the canvas width and height
and instantiate the GameState.

As parameters to the GameState I passed width and height divided by
a desired cell size (30 pixels in my case). Such values represent
the max X and Y coordinates the GameState can work with. We'll
check them later.

Later on, a public method named start() will then spawn a Window,
set the frame rate and create an infinite loop - yes, a sort of
while (true) - that will first trigger a private method update()
and later on a method draw().

Last comes the draw() method. It will read properties on
GameState and just print them. Applying all proportions and scales.

The way I've built this, it expects that X coordinates will range
from 0 to (width divided by cell size) and Y coordinates will range
from 0 to (height divided by cell size). By multiplying each coordinate
by "cell size" we get a good enough scaled drawing without mixing up
our state manipulation and drawing.

There are some other things I've added for debugging but I'd rather
leave them out from this article.

After this, comes the state management. This is GameState's
responsibility. Check it out!

Game State

The GameState represents everything that exists in the game.
Scores, objects like the player and the fruits.

This means that whenever the player must move or a fruit must be
eaten, this will happen inside GameState.

For the Snake's body I decided to have an array with (x, y)
coordinates inside. And I consider the first element of the
array (index zero) to be the Snake's head. Adding more (x, y)
elements to this array would then increase the Snake's body size.

The fruit, though, is a single (x, y) coordinates pair. As I
expect to have only one fruit on screen each time.

The constructor of the GameState class will initialize such
objects with random coordinates. It looks like this:

To increase the Snake's body size, I created a private method
named incrementBody() which should add a new head to the
Snake's body. This new head shall consider the current direction
the Snake is heading to. (left, right, up or down)

To add a new head, I just create copy the current Head, update
its coordinates based on current direction and merge it to
Snake's body occupying the zero index.

Having the incrementBody() method in place makes it very
simple to implement the score() method, which just increments
the score counter and the snake's body. Also score() will
place a new fruit in a random coordinate.

The interesting one is the step() method, which is responsible
for moving the snake.

If you remember well, the way Snake moves is that its head will
constantly step towards one direction and the body will then
follow it in a delayed fashion. So if the snake has 3 blocks
as body size and is moving downwards, it takes three steps to
make it face left completely.

The way I've done this was to basically increment the Snake's
body again (which adds a new head in the new direction) and
remove the last element from the snake's body. This way the
size remains the same, the head has the new direction and on
each step the old coordinates will be deleted.

I've also added some logic for warping from one edge of the
screen to another, you can read it (I hope).

PHP is still blocking by default, so heavy I/O should be smartly
handled. It is possible to use this library alongside an Event Loop
or using threads from the Parallel extension. Probably you'll have
to develop something yourself to achieve this.

What bugs me the most so far is how portable games written in php
can be. There's no simple way to package such games into binaries.
So players would have to install PHP and compile the Raylib extension
in order to play.

But as I mentioned, the first steps were already taken. So technically
speaking, it is already easier to achieve this than it was before.

Big thanks to Joseph Montanez. Your extension really inspired me and
I hope this post reaches and triggers more developers to support its
development.