AnotherWorld game written + playable in R (with {nara} and {eventloop})

AnotherWorld playable game in R

This is the full code for a playable R version of the 16-bit game AnotherWorld

The game plays in realtime using 2 key packages:

  • {nara} for manipulation of nativeRaster images as a fast in-memory drawing canvas
  • {eventloop} for event-driven interaction - allowing for keyboard feedback while rendering the game with sound.

You can play this game today in R!!

However, there are still some graphics bugs etc to work out.

Please file an issue on github if you think something is definitely wrong!

Gameplay in R

A live capture of playing the game in R

Installation

  1. Install the dependencies
  2. Retrieve the anotherworld game code from github
    • Note that this is not a package, but just a collection of R scripts and code. This should make it easier to hack on, extend and adapt.
library(remotes)

# Install dependencies
remotes::install_github('coolbutuseless/eventloop')
remotes::install_github('coolbutuseless/nara')

# Install packages
install.packages('audio')
install.packages('beepr')

# Grab a copy of the code (this is not distributed as a package)
system("git clone https://github.com/coolbutuseless/anotherworld")
setwd('anotherworld')

Controls

After your experiment on Earth goes awry, you are transported to another world where you must survive and, hopefully, escape!

  • Run game with source('game.R')
    • Set PART variable first to the section you want to play.
  • Control game with arrow keys and space bar (for action)
  • Press ‘p’ to pause
  • Press ‘ESC’ to quit
  • When you finish a section the game does not automatically advance to the next section.

Play the game

  • 16001 “Introduction”: cinematic introduction. No user interactivity
  • 16002 “Water”: If you want to get to playing the game, this is the section to run.
    • Hint: Press “Up” at the start when it looks like you’re underwater.
  • 16003 “Jail”
  • 16004 “City”
  • 16005 “Arena”
  • 16006 “Luxe”
  • 16007 “Final”
PART <- 16001  # <------- select the part of the game to play
source('game.R')

GamePlay

Game walkthrough on the Amiga

This looks pretty similar to the R version as it’s running on the same in-game virtual machine.

Click the image below to watch it on youtube.

AnotherWorld gameplay

Screenshots of the R version


ToDo

  • Controls still feel a bit “sticky”. This feels like a bug in my code.
  • Incorporate display of pre-rendered bitmaps which are part of the in-game data
    • You’ll notice some scenes are a little blank because these bitmaps aren’t drawn yet.
  • Still some rendering anomalies
  • See if I can get sound playback with {audio} package working correctly
  • While there are sound effects in the R version, there is currently no music as it hasn’t been looked into yet.

Requirements

macOS

Running this on macOS requires XQuartz to have been installed in order to support the x11() device.

Linux

R needs to have been compiled with x11() and Cairo support.

Limitation - WindowsOS

The WindowsOS version of R does not currently have support for the onIdle callback and therefore does not support event-driven interactive graphics as implemented in the {eventloop} package.

If you are a windows developer capable of adding support for an onIdle callback to R itself, please get in touch!

Limitation - Flickering Cursor

The cursor icon in an {eventloop} window will flicker because of some hard-coded behaviour in R’s double-buffered x11() graphics device.

{eventloop} makes use of the x11() graphics device with a double-buffered backend based on Cairo (x11(type = 'dbcairo')).

The double-buffering within the graphics device is coordinated through use of dev.hold() and dev.flush() calls.

Whenever dev.hold() is called, the cursor will be set to the busy cursor (usually a stopwatch on macOS), and when dev.flush() is called the cursor reverts to a normal pointer arrow.

Since dev.hold() and dev.flush() are called when drawing every single frame, the cursor will flicker between these two images.

The interim solution is to not have the cursor over the window when interacting with the app. This only really works when the app solely relies on keyboard feedback (like this game of AnotherWorld).

The more complicated solution will involve crafting a patch to R itself to make the cursor change behaviour user-configurable rather than hard-coded.

If you are are an R developer capable of crafting such a patch to R , please get in touch with me!

Reference Source Code

The reference code for this port was the JavaScript code from github/cyxx/another_js.

Further details have been extracted from the C code from Fabien’s C++ Interpreter.

Challenges

0-based vs 1-based indexing

Javascript and C code used for reference both use 0-based indexing, whereas R uses 1-based indexing.

To try and avoid editing all references to array indexint to +1, I created a new class cindex which has 1-based indexing in R.

There still a lot of refacoring to do, and many structures are still indexed by a character string representation of the hexadecimal version of the index or address i.e. ugly.

bitwise operations

JS and C both use & and | for bitwise operations, whereas R uses them for logical operations.

I created a set of infix operators (%|% etc) to mimic the bitwise operators in C.

‘Bytekiller’ compression

Data in the original game is compressed with ‘bytekiller’ which is a very very old skool data compression algorithm.