Doo-cr: DOOM in Crystal (Not just the renderer)


Doo-cr is DOOM in Crystal Lang. Not just the renderer. The whole game engine.

It features full DOOM, DOOM II, and Final Doom compatibility as well as compatibility with vanilla pwads.
It also has functioning networked multiplayer, see the latest release on how to do that.

Also -timedemo, demo benchmarking, finishes quicker than chocolate-doom. Meaning this source port is somehow faster than that one

Right now Doo-cr is very unsafe and low level. I want to eventually Crystalize more and more of the code. I’ve already done this with loading and saving for example. One day Doo-cr shouldn’t have a null pointer in sight!

The only thing still in C is variable declarations. I just haven’t pulled them out yet
Every method in Doom is in Crystal though.

The way I went about doing this, to me, is a little more interesting than the project itself.
I took PureDoom, made bindings to its functions and variables, then one by one rewrote every function in crystal, made that function an extern declaration in C, made my crystal method a fun at the top level so it didn’t just act as an extern in Crystal, how fun worked when inside of lib, and then bridged the gap going from the Lib binding, to the top level fun which just called the crystal def function.

Here’s an example of this

my_example.c

#include <stdio.h>

int MyInt = 0;

extern void CrystalFunc();

void CRun() {
  printf("Printing from C!\n");
  printf("Adding + 1 to MyInt in C!\n");
  MyInt++;
  printf("Calling Crystal function from C\n\n");
  CrystalFunc();
}

my_example.cr

@[Link(ldflags: "-L#{__DIR__} -lexample")]
lib LibExample
  fun run = CRun()

  $my_int = MyInt : LibC::Int
end

fun crystal_func = CrystalFunc
  puts "crystal_func Called from C, my_int is #{LibExample.my_int}"
  puts "Adding 1 to my_int in crystal"
  puts
  LibExample.my_int += 1
end

puts "my_int is initialized in C to be #{LibExample.my_int}"
puts "Calling CRun from Crystal"
puts
LibExample.run()
puts "Back into top level of Crystal, my_int is now #{LibExample.my_int}"

makefile

.PHONY: all
all: libexample.dll
    shards install
    crystal build ./my_example.cr -o ./
    ./my_example

libexample.dll:
    cc -shared -fPIC -x c \
        -Wl,-undefined,dynamic_lookup \
            my_example.c -o libexample.dll

This shows how you can have what I call a two-way-binding between C and Crystal which I used to bridge the gap between PureDoom and Doo-cr as I rewrote the functions instead of doing it blind.

Long story short, you can play Doom lol.
This project has taken me about 4 years of trying to figure out how to do it, but I think I’m finally here.
Check out the latest release for a windows build. (which has shareware DOOM)
Any feedback or bug reporting will be greatly appreciated!

Wow, that’s quite a feat to achieve! Congratulations :tada:
And cudos on the determination to keep working on this project for 4 years.

I guess we can insert Crystal now on https://canitrundoom.org/ ? :thinking:

Thank you! I might have to slap it on canitrundoom lol. I totally forgot about that site funny enough