fb new game && fb run game
Write it like BASIC. Ship it like C.
FutureBlitz is a language for making games and tools: plain words and a loop you own, compiled to one native executable through LLVM.
wave.fb
Import std.core
Struct Enemy
Field name: String
Field hp: Int
End Struct
Local wave = [Enemy("grunt", 30), Enemy("brute", 80),
Enemy("boss", 250)]
For i, e In wave
Print("{i + 1}. {e.name} with {e.hp} hp")
End For
$ fb run wave.fb
1. grunt with 30 hp
2. brute with 80 hp
3. boss with 250 hp What you get
Small programs, real machine code
-
Native code through LLVM
fb compiles each program to machine code with LLVM and links it with lld, which it ships. Debug builds panic on integer overflow with the line it happened on; release builds optimize.
fb build --release -
One file to ship
The runtime and the C libraries std uses link in statically, so a game is one executable. A crash's stack trace names your files and lines, in release builds too.
build/game.exe -
Threads the compiler checks
Handing another thread something it could race on doesn't compile: a Spawn's work takes only what's Sendable, and shared state goes through Mutex and Atomic.
Spawn · Parallel For · Channel · Mutex -
Coroutines
A cutscene, a door, or a boss's phases are a few lines of an Async Function that wait, resumed by the frame loop, with schedulers of your own for pausing and slow motion.
Await Wait(2.Seconds) -
The standard library
206 modules written in FutureBlitz: collections, text and regex, math, files, JSON, TOML, and YAML, SQLite, HTTP, images, compression, and crypto.
Import std.data -
Any C library
Declare a C library's functions in an Extern block and call them like your own, or have fb bindgen write the block from its header.
Extern "C" Lib "raylib"
From Blitz3D
The same program, both ways
Type Enemy
Field name$
Field hp
End Type
For i = 1 To 3
e.Enemy = New Enemy
e\name$ = "grunt " + i
e\hp = 10 * i
Next
For e.Enemy = Each Enemy
e\hp = e\hp - 15
If e\hp <= 0
Print e\name$ + " is gone"
Delete e
Else
Print e\name$ + " has " + e\hp + " hp"
EndIf
Next
Import std.core
Import std.collections
Class Enemy
Field name: String
Field hp: Int
New(name: String, hp: Int)
Self.name = name
Self.hp = hp
End New
End Class
Local enemies: Enemy[] = []
For i = 1 To 3
enemies.Add(Enemy("grunt {i}", 10 * i))
End For
For e In enemies
e.hp -= 15
If e.hp <= 0
Print("{e.name} is gone")
Else
Print("{e.name} has {e.hp} hp")
End If
End For
enemies.RemoveAll(Function(e: Enemy) => e.hp <= 0)
grunt 1 is gone grunt 2 has 5 hp grunt 3 has 15 hp
-
Types are words, not suffixes
name$ and hp% are name: String and hp: Int. A Local's type is inferred from its value, and every variable is declared, so a typo is an error, not a new variable.
-
Every block says how it ends
Next, Wend, and EndIf are End For, End While, and End If, the same End for every block.
-
Types are Classes
A Type's global list becomes a Class with a constructor and Methods, kept in an array you own, and freed when nothing refers to it, so there's no Delete.
-
Calls take parentheses
Print "hi" is Print("hi"), e\hp is e.hp, and strings take values inside braces: "{e.name} has {e.hp} hp".
The samples folder
Five small stories from making a game
Blitz3D came with a folder of samples that taught a generation. FutureBlitz has one too. Each output here was recorded by running the sample with fb when this site was built.
// A boss fight as a coroutine: each phase is a few lines that wait, and the game's
// frame loop ticks a Scheduler of its own, 60 times a second, so pausing it pauses the fight.
Import std.core
Import std.time
Class Boss
Field name: String
Field hp: Int = 300
New(name: String)
Self.name = name
End New
End Class
Global frame = 0
Function Say(text: String)
Print("frame {frame}: {text}")
End Function
Async Function Fight(boss: Boss)
Say("{boss.name} wakes up")
Await Wait(1.Seconds)
While boss.hp > 150
Say("{boss.name} swings its hammer ({boss.hp} hp)")
Await Wait(750.Milliseconds)
End While
Say("{boss.name} is angry, and twice as fast")
While boss.hp > 0
Await Wait(375.Milliseconds)
End While
Say("{boss.name} falls")
End Function
Local boss = Boss("The Warden")
Local gameplay = Scheduler()
gameplay.Run(Fight(boss))
While !gameplay.IsIdle()
frame += 1
If frame % 20 == 0 Then boss.hp -= 20 // the player lands a hit
gameplay.Tick(1.0 / 60.0)
End While
Say("the fight took {frame} frames")
frame 0: The Warden wakes up frame 60: The Warden swings its hammer (240 hp) frame 105: The Warden swings its hammer (200 hp) frame 150: The Warden swings its hammer (160 hp) frame 195: The Warden is angry, and twice as fast frame 310: The Warden falls frame 310: the fight took 310 frames
// A save file in JSON: Json.Write saves a Struct field by field, and Json.Read<T> loads
// it back, so the save format is the type itself.
Import std.core
Import std.data
Import std.io
Struct SaveGame
Field player: String
Field level: Int
Field gold: Int
Field items: String[]
End Struct
Local game = SaveGame("Ada", 3, 120, ["lamp", "rope", "old key"])
Try File.WriteText("save.json", Json.Write(game, 2))
Print(Try File.ReadText("save.json"))
Local loaded = Try Json.Read<SaveGame>(Try File.ReadText("save.json"))
Print("{loaded.player} is on level {loaded.level}, carrying {loaded.items.Count} things")
{
"player": "Ada",
"level": 3,
"gold": 120,
"items": [
"lamp",
"rope",
"old key"
]
}
Ada is on level 3, carrying 3 things
// Heavy work on another thread: Spawn runs a Function on the worker pool and gives a Task,
// and the main thread goes on until it needs the result.
Import std.core
Function CountPrimes(below: Int): Int
Local count = 0
For n = 2 Until below
Local d = 2
While d * d <= n && n % d != 0
d += 1
End While
If d * d > n Then count += 1
End For
Return count
End Function
Local task = Spawn(Function() => CountPrimes(300_000))
Print("the worker is counting; the main thread draws the loading screen")
Print("primes below 300,000: {task.Wait()}")
the worker is counting; the main thread draws the loading screen primes below 300,000: 25997
// Particles over every core: each frame, a Parallel For steps them in chunks on the worker
// pool. Its iterations touch only their own particle, which the compiler checks.
Import std.core
Import std.math
Struct Particle
Field pos: Vec2
Field vel: Vec2
ByRef Method Step(dt: Float)
vel = vel + Vec2(0, -9.8) * dt
pos = pos + vel * dt
End Method
End Struct
Local particles: Particle[] = []
For i = 0 Until 100_000
particles.Add(Particle(Vec2(0, 0), Vec2((i % 21 - 10) As Float * 0.5, 12)))
End For
For frame = 1 To 60
Parallel For i = 0 Until particles.Count
particles[i].Step(1.0 / 60.0)
End For
End For
Print("{particles.Count} particles after one second")
Print("the first is at {particles[0].pos}, the eleventh at {particles[10].pos}")
100000 particles after one second the first is at (-5, 7.01834), the eleventh at (0, 7.01834)
// A window from a C library: raylib's own functions, declared in an Extern block and called
// as FutureBlitz Functions. project.toml's [native] links raylib, which has to be installed.
Import std.core
@CLayout Struct Color
Field r: UInt8
Field g: UInt8
Field b: UInt8
Field a: UInt8
End Struct
Extern "C" Lib "raylib"
Function InitWindow(width: Int, height: Int, title: CString)
Function WindowShouldClose(): Bool
Function BeginDrawing()
Function EndDrawing()
Function ClearBackground(color: Color)
Function DrawText(text: CString, x: Int, y: Int, size: Int, color: Color)
Function SetTargetFPS(fps: Int)
Function CloseWindow()
End Extern
Const NIGHT = Color(8, 9, 13, 255)
Const AMBER = Color(242, 165, 58, 255)
InitWindow(800, 450, "FutureBlitz and raylib")
SetTargetFPS(60)
While !WindowShouldClose()
BeginDrawing()
ClearBackground(NIGHT)
DrawText("Hello from FutureBlitz", 190, 200, 40, AMBER)
EndDrawing()
End While
CloseWindow()
This one opens an 800 by 450 window, so the site's build checks it with fb check and doesn't run it. Running it needs raylib installed.
In the box
A standard library of 206 modules
- std.core 11 modules Print and Input, errors, the Interfaces, iterators, ranges, formatting and parsing, and Value
- std.collections 21 modules Map, Set, Stack, Queue, Deque, PriorityQueue, Pool, Trie, LRU caches, spatial grids, and arrays' methods
- std.text 14 modules Unicode, regex, Markdown, templates, diffs, fuzzy matching, glob patterns, and encodings
- std.math 22 modules vectors, matrices, Quat, colors, geometry, Random, noise, easing, stats, units, and BigInt and Decimal
- std.io 15 modules Path, File, Dir, streams, memory-mapped files, a virtual file system, file watching, and Preferences
- std.time 7 modules Duration, Timestamp, dates and times of day, calendars, timers, and time zones
- std.os 8 modules command-line Args, the environment, standard folders, processes, and signals
- std.concurrent 8 modules locks, ConcurrentMap, concurrent queues, Lazy, parallel algorithms, and cancellation
- std.log 5 modules Logger, with levels, filters, outputs, and log files
- std.events 1 module Event<T> and subscriptions that end with their owner
- std.data 12 modules JSON, MessagePack, TOML, CSV, INI, XML, YAML, and Protocol Buffers
- std.crypto 9 modules hashes, HMAC, ciphers, signatures, key derivation, password hashing, PEM, and secure random
- std.compress 11 modules Deflate, Zstd, Lz4, XZ, and Zip, tar, and 7z archives
- std.net 27 modules addresses, DNS, TCP and UDP, TLS, an HTTP client and server, WebSockets, ACME, LAN discovery, and game networking
- std.db 1 module SQLite databases, and Query<T> over their rows
- std.image 12 modules PNG, JPEG, GIF, WebP, SVG, and HDR images, drawing on a canvas, and fonts
- std.term 9 modules terminal styles, input, prompts, screens, and widgets
- std.algo 6 modules graph search, topological order, combinatorics, Delaunay triangulation, union-find, and grids
- std.uuid 1 module UUIDs, RFC 9562's
- std.debug 1 module whether a debugger is attached, and breakpoints
- std.test 1 module Expect, ExpectThrows, Fail, and Skip, which fb test runs
- std.ffi 4 modules CData, Buffer, Mem, and CStrings, for C libraries
The C libraries std links in
SQLite, libcurl, nghttp2, mbedTLS, libuv, PCRE2, utf8proc, zlib-ng, minizip-ng, zstd, lz4, liblzma, xxHash, BLAKE3, Argon2, Monocypher, crypt_blowfish, libtommath, mpdecimal, stb_image, libwebp, nanosvg, md4c. A program links each one only when it calls what's built on it, statically, so a program that uses them is still one file, and fb lists the ones a build links, with their licenses.
Get started
Install it, then fb new
# Linux and macOS
curl -fsSL https://github.com/hlpdev/FutureBlitz/releases/latest/download/install.sh | sh
# Windows, in PowerShell
irm https://github.com/hlpdev/FutureBlitz/releases/latest/download/install.ps1 | iex
# then
fb new hello
cd hello
fb run
Hello, world!
No release is published yet, so the install lines work once the first one is. Until then, build fb from source.