Examples
Learn GX by example. Each snippet is a complete, runnable program.
Basics
Hello World 01_hello_world.gx
// 01_hello_world.ec - The simplest EC program
// Build: ec examples/01_hello_world.ec -o build/01_hello_world.exe
fn main() {
var a:i32 = 100;
var b:i32 = 200;
var name:str = "Afan";
print("Hello, GXers!! {a} : {b} : {name}\n");
}
Variables 02_variables.gx
// 02_variables.ec - Variable declarations, types, and string interpolation
// Build: ec examples/02_variables.ec -o build/02_variables.exe
fn main() {
// Type inference with var
var a = 10;
var b = 3.14;
var name = "GX";
// Explicit types
var x:i32 = 100;
var y:f32 = 2.718;
var big:i64 = 9999999999;
var precise:f64 = 3.14159265358979;
// String interpolation
print("a={a}\n");
print("b={b}\n");
print("name={name}\n");
print("x={x} y={y}\n");
print("big={big}\n");
print("precise={precise}\n");
print("i32 as string: " + x.str() + "\n");
print("f64 as string: " + precise.str() + "\n");
// Assignment
x = x + 1;
print("x after +1: {x}\n");
}
Print And Strings 03_print_and_strings.gx
// 03_print_and_strings.ec - Print, string concatenation, interpolation, UTF-8
// Build: ec examples/03_print_and_strings.ec -o build/03_print_and_strings.exe
fn main() {
do_some()
do_some1()
// Basic print
print("Hello GXers!\n");
var s:str = "Family";
// String concatenation with +
var greeting = "Hello" + ", " + "GX {s}!!! \n";
print(greeting)
// String interpolation with {expr}
var x:i32 = 42;
var f:f32 = 3.14;
print("int={x} float={f}\n");
// .str() converts any type to string
var v = vec3{1.0, 2.0, 3.0};
print("vec3={v}\n");
print("vec3 via concat: " + v.str() + "\n");
// UTF-8 strings
var utf = "Hello UTF-8: cafe\n";
print(utf);
// Escape sequences
print("Tab:\there\n");
print("Quote: \"hello\"\n");
// String + number auto-converts
print("result=" + (10 + 20).str() + "\n");
}
Data Types
Control Flow 04_control_flow.gx
// 04_control_flow.ec - If/elif/else, while loops, for-range, for-each, collect
// Build: ec examples/04_control_flow.ec -o build/04_control_flow.exe
fn main() {
// --- If / elif / else ---
var score:i32 = 75;
var x:i32 = 10;
print("Helloo\n");
if (score >= 90) {
print("Grade: A\n");
} elif (score >= 80) {
print("Grade: B\n");
} elif (score >= 70) {
print("Grade: C\n");
} else {
print("Grade: F\n");
}
// --- While loop ---
var count:i32 = 0;
while (count < 5) {
print("count={count}\n");
count = count + 1;
}
// --- For-range (inclusive) ---
print("\nFor-range 1 to 5:\n");
for (i = 1:5) {
print(" i={i}\n");
}
// --- For-each over array ---
print("\nFor-each:\n");
var arr:i32[5] = {10, 20, 30, 40, 50};
for (var x in arr) {
print(" x={x}\n");
}
// --- For-each with where filter ---
print("\nFiltered (even only):\n");
var nums:i32[8] = {1, 2, 3, 4, 5, 6, 7, 8};
for (var n in nums) where (n % 2 == 0) {
print(" even={n}\n");
}
// --- Collect: build array from loop ---
var evens = for (n in nums) where (n % 2 == 0) collect n;
print("\nCollected evens ({evens.len()}):");
for (i = 0:3) {
print(" {evens.at(i)}");
}
print("\n");
// Collect with transform
var doubled = for (n in nums) where (n <= 4) collect n * 10;
print("Doubled small ({doubled.len()}):");
for (i = 0:3) {
print(" {doubled.at(i)}");
}
print("\n");
evens.free();
doubled.free();
}
Arrays 05_arrays.gx
// 05_arrays.ec - Fixed-size arrays: declaration, init, iteration
// Build: ec examples/05_arrays.ec -o build/05_arrays.exe
fn main() {
// Fixed-size array with initializer
var scores:i32[5] = {95, 82, 74, 88, 91};
var win:[]i32 = scores[0:3];
print("Winners:\n");
for (var s in win) {
print(" win {s}\n");
}
// Array with inferred size
var colors:f32[3] = {1.0, 0.0, 0.0};
print("\nRGB: {colors[0]}, {colors[1]}, {colors[2]}\n");
// Modify by index
scores[0] = 100;
print("\nUpdated first score: {scores[0]}\n");
// Iterate with range
print("\nAll scores after update:\n");
for (i = 0:4) {
print(" scores[{i}] = {scores[i]}\n");
}
}
Dynamic Arrays 06_dynamic_arrays.gx
// 06_dynamic_arrays.ec - array<T> dynamic container: init, push, sort, remove, for-each
// Build: ec examples/06_dynamic_arrays.ec -o build/06_dynamic_arrays.exe
fn main() {
// --- Static array for-each ---
print("Static array\n");
var week_days:str[7] = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
for (item:str in week_days) {
print("Day: {item}\n");
}
// --- Dynamic array with initializer syntax ---
print("\nDynamic array (initializer syntax)\n");
var darr:array<str> = {"Afan", "Sanela", "Ajdin"};
defer darr.free();
// For-each works on dynamic arrays
for (var item in darr) {
print(" item: {item}\n");
}
// Multiple iterations work fine
print("\nSecond iteration with index:\n");
var cnt = 0;
for (var item in darr) {
print(" [{cnt}] {item}\n");
cnt = cnt + 1;
}
// Index-based loop also works
print("\nIndex-based loop:\n");
for (j = 0:darr.len()-1) {
print(" {darr.at(j)}\n");
}
// --- Dynamic array without initializer (auto-init) ---
var a:array<i32>;
defer a.free();
// Push elements
a.push(30);
a.push(10);
a.push(50);
a.push(20);
a.push(40);
print("\nAfter push: len={a.len()} cap={a.cap()}\n");
// Access elements
print("a[0]={a.at(0)} a[1]={a.at(1)} a[2]={a.at(2)}\n");
// For-each over i32 dynamic array
print("\nFor-each over i32 array:\n");
for (var n in a) {
print(" {n}\n");
}
// Sort
a.sort();
print("\nAfter sort:\n");
for (var n in a) {
print(" {n}\n");
}
// Remove (swap with last - O(1))
a.remove_swap(0);
print("\nAfter remove_swap(0): len={a.len()}\n");
for (var n in a) {
print(" {n}\n");
}
// Clear
a.clear();
print("\nAfter clear: len={a.len()}\n");
// --- Collect: create dynamic array from loop ---
var data:i32[6] = {5, 12, 3, 18, 7, 20};
var big = for (x in data) where (x >= 10) collect x;
print("\nCollected (>= 10): ");
for (var n in big) {
print("{n} ");
}
print("(len={big.len()})\n");
big.free();
print("\nDone.\n");
}
Structs 07_structs.gx
// 07_structs.ec - Structs, field access, extension methods
// Build: ec examples/07_structs.ec -o build/07_structs.exe
struct Player {
id:i32
name:str
score:f32
}
ex Player {
fn greet() {
print("Player {name} (id={id}) score={score}\n");
}
fn add_score(points:f32) {
score = score + points;
}
}
struct Vec2i {
x:i32
y:i32
}
ex Vec2i {
fn to_str:str() {
return "({x}, {y})";
}
}
fn main() {
// Create with initializer
var p = Player{1, "Alice", 0.0};
p.greet();
// Modify via extension method
p.add_score(100.5);
p.add_score(50.0);
p.greet();
// Direct field access
p.name = "Bob";
print("Renamed to: {p.name}\n");
// Custom struct with str conversion
var pos = Vec2i{10, 20};
print("Position: " + pos.to_str() + "\n");
}
Enums 08_enums.gx
// 08_enums.ec - Enums: declaration, comparison, branching
// Build: ec examples/08_enums.ec -o build/08_enums.exe
enum Direction {
North
South
East
West
}
enum Color {
Red = 0
Green = 1
Blue = 2
}
fn main() {
// Declare enum variables
var dir:Direction = East;
// Compare enum values
if (dir == North) {
print("Heading North\n");
} elif (dir == East) {
print("Heading East\n");
} elif (dir == South) {
print("Heading South\n");
} else {
print("Heading West\n");
}
// Enum to string (shows integer value)
print("East = {dir}\n");
// Another enum
var c:Color = Blue;
print("Blue = {c}\n");
}
Map 39_map.gx
// 39_map.gx - Hash map: map<K,V>
// Build: gx examples/39_map.gx -o build/39_map.exe
// Test 1: String-keyed map
fn test_str_map() {
var scores:map<str, i32>
var names:map<str,str>
names.set("Afan","OLOVCIC")
names.set("Sanela","GLUHOVIC")
names.set("Ajdin","OLOVCIC")
for (var k, v in names) {
print("name: {k} {v}\n")
}
for (var item in names) {
print("name: {item.key} {item.value}\n")
}
scores.set("alice", 95)
scores.set("bob", 82)
scores.set("charlie", 91)
var a:str = "alice"
var b:str = "bob"
var c:str = "charlie"
var d:str = "dave"
print("alice: ")
print("{scores.get(a)}\n")
print("bob: ")
print("{scores.get(b)}\n")
print("has charlie: {scores.has(c)}\n")
print("has dave: {scores.has(d)}\n")
print("len: {scores.len()}\n")
// Update existing key
scores.set("bob", 88)
print("bob updated: {scores.get(b)}\n")
// Remove
scores.remove(c)
print("after remove, has charlie: {scores.has(c)}\n")
print("len after remove: {scores.len()}\n")
scores.free()
}
// Test 2: Integer-keyed map
fn test_int_map() {
var names:map<i32, str>
names.set(1, "one")
names.set(2, "two")
names.set(3, "three")
print("1 -> ")
print(names.get(1))
print("\n")
print("2 -> ")
print(names.get(2))
print("\n")
print("3 -> ")
print(names.get(3))
print("\n")
print("has 4: {names.has(4)}\n")
names.free()
}
// Test 3: Many entries (tests grow/rehash)
fn test_grow() {
var m:map<i32, i32>
for (i = 0:99) {
m.set(i, i * i)
}
print("len after 100 inserts: {m.len()}\n")
print("m[50] = {m.get(50)}\n")
print("m[99] = {m.get(99)}\n")
// Clear and verify
m.clear()
print("len after clear: {m.len()}\n")
m.free()
}
// Test 4: Map iteration — destructured (var k, v)
fn test_iter_kv() {
var scores:map<str, i32>
scores.set("alice", 95)
scores.set("bob", 62)
scores.set("charlie", 91)
scores.set("dave", 55)
print("All scores (k, v):\n")
for (var name, score in scores) {
print(" {name}: {score}\n")
}
print("High scores (>= 80):\n")
for (var name, score in scores) where (score >= 80) {
print(" {name}: {score}\n")
}
scores.free()
}
// Test 5: Map iteration — entry-based (var item → item.key, item.value)
fn test_iter_entry() {
var scores:map<str, i32>
scores.set("alice", 95)
scores.set("bob", 62)
scores.set("charlie", 91)
scores.set("dave", 55)
print("All scores (entry):\n")
for (var entry in scores) {
print(" {entry.key}: {entry.value}\n")
}
print("High scores (>= 80):\n")
for (var entry in scores) where (entry.value >= 80) {
print(" {entry.key}: {entry.value}\n")
}
// Integer-keyed iteration
var m:map<i32, i32>
for (i = 0:9) {
m.set(i, i * i)
}
print("Squares:\n")
for (var item in m) {
print(" {item.key}^2 = {item.value}\n")
}
scores.free()
m.free()
}
fn main() {
print("=== String-keyed map ===\n")
test_str_map()
print("\n=== Integer-keyed map ===\n")
test_int_map()
print("\n=== Grow/rehash test ===\n")
test_grow()
print("\n=== Map iteration (k, v) ===\n")
test_iter_kv()
print("\n=== Map iteration (entry) ===\n")
test_iter_entry()
}
Functions & Control
Vec Mat Math 09_vec_mat_math.gx
// 09_vec_mat_math.ec - Built-in vector and matrix types
// Build: ec examples/09_vec_mat_math.ec -o build/09_vec_mat_math.exe
fn main() {
// --- Vectors ---
var a = vec3{1.0, 2.0, 3.0};
var b = vec3{4.0, 5.0, 6.0};
// Vector addition
var c = a + b;
print("a + b = {c}\n");
// Scalar multiply
var d = a * 2.0;
print("a * 2 = {d}\n");
// Negate
var e = -a;
var len = 32;
for (i=0:len) {
print("i = {i}\n");
}
print("-a = {e}\n");
// Field access
print("a.x={a.x} a.y={a.y} a.z={a.z}\n");
// --- vec2 and vec4 ---
var v2 = vec2{10.0, 20.0};
print("vec2: {v2}\n");
var v4 = vec4{1.0, 0.0, 0.0, 1.0};
print("vec4: {v4}\n");
// --- Swizzle ---
var sw1 = a.xy;
print("a.xy = {sw1}\n");
var sw2 = a.zyx;
print("a.zyx = {sw2}\n");
var sw3 = v4.wz;
print("v4.wz = {sw3}\n");
var sw4 = v2.yx;
print("v2.yx = {sw4}\n");
// Repeat components
var sw5 = a.xx;
print("a.xx = {sw5}\n");
// --- Matrix ---
var M = mat4{
vec4{1,0,0,0},
vec4{0,1,0,0},
vec4{0,0,1,0},
vec4{0,0,0,1},
};
print("Identity: {M}\n");
}
Functions 10_functions.gx
// 10_functions.ec - Functions, return types, recursion
// Build: ec examples/10_functions.ec -o build/10_functions.exe
fn add:i32(a:i32, b:i32) {
return a + b;
}
fn larger:i32(a:i32, b:i32) {
if (a > b) {
return a;
}
return b;
}
fn factorial:i32(n:i32) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
fn greet(name:str) {
print("Hello, {name}!\n");
}
fn main() {
// Basic function calls
var sum = add(10, 20);
print("add(10, 20) = {sum}\n");
var m = larger(42, 17);
print("larger(42, 17) = {m}\n");
// Recursion
for (i = 1:8) {
print("{i}! = {factorial(i)}\n");
}
// Void function
greet("EC");
}
Bool 11_bool.gx
// 11_bool.ec - Boolean literals: true and false
// Build: ec examples/11_bool.ec -o build/11_bool.exe
fn main() {
var a:bool = true;
var b:bool = false;
print("a = {a}\n");
print("b = {b}\n");
if (a) {
print("a is true\n");
}
if (!b) {
print("b is false\n");
}
// Boolean logic
var c = a && b;
var d = a || b;
print("true && false = {c}\n");
print("true || false = {d}\n");
// Comparison produces bool
var x = 5;
var gt = x > 3;
print("5 > 3 = {gt}\n");
// Use in conditions directly
var done = false;
var count = 0;
while (!done) {
count = count + 1;
if (count == 3) {
done = true;
}
}
print("counted to {count}\n");
}
Input 12_input.gx
// Example: input() built-in function
// Reads a line from stdin, with an optional prompt string.
fn main() {
var name:str = input("What is your name? ");
print("Hello, {name}!\n");
var color:str = input("Favorite color? ");
print("{name} likes {color}\n");
// input() with no prompt
print("Type something: ");
var line:str = input();
print("You said: {line}\n");
}
Bitwise 13_bitwise.gx
// 13_bitwise.ec - Bitwise operators and hex literals
// Build: ec examples/13_bitwise.ec -o build/13_bitwise.exe
fn main() {
// --- Hex literals ---
var a:i32 = 0xFF;
var b:i32 = 0x0F;
var c:i32 = 0x0A;
print("Hex literals: a=0xFF={a}, b=0x0F={b}\n");
// --- Bitwise AND ---
var and_result:i32 = a & b & c;
print("\nBitwise AND: 0xFF & 0x0F = {and_result}\n");
// --- Bitwise OR ---
var or_result:i32 = 0xF0 | 0x0F;
print("Bitwise OR: 0xF0 | 0x0F = {or_result}\n");
// --- Bitwise XOR ---
var xor_result:i32 = 0xFF ^ 0x0F;
print("Bitwise XOR: 0xFF ^ 0x0F = {xor_result}\n");
// --- Bitwise NOT ---
var not_result:i32 = ~0;
print("Bitwise NOT: ~0 = {not_result}\n");
// --- Left shift ---
var lshift:i32 = 1 << 4;
print("Left shift: 1 << 4 = {lshift}\n");
// --- Right shift ---
var rshift:i32 = 256 >> 3;
print("Right shift: 256 >> 3 = {rshift}\n");
// --- Flags pattern ---
print("\n--- Flags pattern ---\n");
var FLAG_READ:u32 = 1 << 0;
var FLAG_WRITE:u32 = 1 << 1;
var FLAG_EXEC:u32 = 1 << 2;
var perms:u32 = FLAG_READ | FLAG_EXEC;
print("Permissions: {perms}\n");
print("Has READ: {perms & FLAG_READ}\n");
print("Has WRITE: {perms & FLAG_WRITE}\n");
print("Has EXEC: {perms & FLAG_EXEC}\n");
// Add write permission
perms = perms | FLAG_WRITE;
print("\nAfter adding WRITE: {perms}\n");
print("Has WRITE: {perms & FLAG_WRITE}\n");
// Remove exec permission (AND with inverted flag)
perms = perms & ~FLAG_EXEC;
print("\nAfter removing EXEC: {perms}\n");
print("Has EXEC: {perms & FLAG_EXEC}\n");
// Toggle read with XOR
perms = perms ^ FLAG_READ;
print("\nAfter toggling READ: {perms}\n");
print("Has READ: {perms & FLAG_READ}\n");
// --- Byte extraction ---
print("\n--- Byte extraction ---\n");
var val:i32 = 0xABCD;
var low:i32 = val & 0xFF;
var high:i32 = (val >> 8) & 0xFF;
print("0xABCD low byte: {low}\n");
print("0xABCD high byte: {high}\n");
// --- Bit counting (popcount) ---
print("\n--- Bit counting ---\n");
var num:i32 = 0xFF;
var count:i32 = 0;
var tmp:i32 = num;
for (i = 0:31) {
if (tmp & 1) {
count = count + 1;
}
tmp = tmp >> 1;
}
print("Bits set in 0xFF: {count}\n");
// --- Power of two check ---
print("\n--- Power of two check ---\n");
var vals:i32[6] = {1, 2, 3, 4, 16, 255};
for (var v in vals) {
var is_pow2:i32 = 0;
if (v & (v - 1)) {
is_pow2 = 0;
} else {
is_pow2 = 1;
}
print("{v} is power of 2: {is_pow2}\n");
}
print("\nDone.\n");
}
Advanced Features
Break Continue 21_break_continue.gx
// Break and Continue example
fn main() {
// Break: exit loop early
print("Break test: ");
for (i = 0:20) {
if (i > 4) {
break;
}
print("{i} ");
}
print("\n");
// Continue: skip even numbers
print("Continue test: ");
for (i = 0:10) {
if (i % 2 == 0) {
continue;
}
print("{i} ");
}
print("\n");
var a:i32 = 10;
a++;
a--;
++a;
--a;
print("a={a}\n");
// Break in while loop
print("While break: ");
var n:i32 = 0;
while (n < 100) {
if (n > 5) {
break;
}
print("{n} ");
n = n + 1;
}
print("\n");
// Continue in while loop
print("While continue: ");
var m:i32 = 0;
while (m < 10) {
m = m + 1;
if (m % 3 == 0) {
continue;
}
print("{m} ");
}
print("\n");
} Match 21_match.gx
// 21_match.ec - Match statement: clean multi-way branching
// Build: ec examples/21_match.ec -o build/21_match.exe
// Test 1: Match on integer with single values
fn test_int() {
var x:i32 = 2;
match (x) {
1: print("one\n")
2: print("two\n")
3: print("three\n")
default: print("other\n")
}
}
// Test 2: Match with comma-separated patterns
fn test_comma() {
var code:i32 = 5;
match (code) {
1, 2, 3: print("low\n")
4, 5, 6: print("mid\n")
7, 8, 9: print("high\n")
default: print("out of range\n")
}
}
// Test 3: Match with range
fn test_range() {
var score:i32 = 85;
match (score) {
0..59: print("F\n")
60..69: print("D\n")
70..79: print("C\n")
80..89: print("B\n")
90..100: print("A\n")
default: print("invalid\n")
}
}
// Test 4: Match on enum
enum Color { Red Green Blue }
fn test_enum() {
var c:Color = Green;
match (c) {
Red: print("red\n")
Green: print("green\n")
Blue: print("blue\n")
}
}
// Test 5: Match with block body
fn test_block() {
var n:i32 = 3;
match (n) {
1: {
print("one ");
print("(single)\n");
}
2, 3: {
print("two or three ");
print("(group)\n");
}
default: print("other\n")
}
}
fn main() {
print("--- int match ---\n");
test_int();
print("--- comma match ---\n");
test_comma();
print("--- range match ---\n");
test_range();
print("--- enum match ---\n");
test_enum();
print("--- block match ---\n");
test_block();
}
Func Ptr 22_func_ptr.gx
// Function pointer validation test
fn add:i32(a:i32, b:i32) {
return a + b;
}
fn negate:i32(x:i32) {
return 0 - x;
}
fn apply:i32(f:fn(i32):i32, val:i32) {
return f(val);
}
fn main() {
// Valid: assign function matching signature
var op:fn(i32, i32):i32 = add;
print("add(3,4) = {op(3, 4)}\n");
// Valid: pass function pointer as argument
var result:i32 = apply(negate, 42);
print("negate(42) = {result}\n");
// Call through function pointer
var f:fn(i32):i32 = negate;
print("f(10) = {f(10)}\n");
}
Increment 23_increment.gx
// Increment and Decrement operators
fn main() {
// Postfix
var a:i32 = 5;
a++;
print("a++ => {a}\n");
var b:i32 = 10;
b--;
print("b-- => {b}\n");
var h1:i32 = b<11?10:20;
print("h={h1}\n");
// Prefix
var c:i32 = 0;
++c;
++c;
++c;
c++;
--c;
c--;
print("++c x3 => {c}\n");
// Prefix vs postfix in expressions
var e:i32 = 5;
var f:i32 = e++;
print("postfix: e={e}, f={f}\n");
var g:i32 = 5;
var h:i32 = ++g;
print("prefix: g={g}, h={h}\n");
} Union 24_union.gx
// Example 24: Union Types
// Unions allow multiple fields to share the same memory — only one is valid at a time.
// Use cases: type punning, variant storage, memory-efficient tagged unions.
// --- Basic union: type punning ---
union IntFloat {
i: i32
f: f32
}
// --- Tagged union pattern: manual tag + union ---
enum ValueKind {
IntVal
FloatVal
StrVal
}
union ValueData {
i: i32
f: f32
s: str
}
union Actor {
id:i32
name:str
}
struct Value {
kind: ValueKind
data: ValueData
}
fn print_value(v: Value) {
match (v.kind) {
IntVal: print("int: {v.data.i}\n")
FloatVal: print("float: {v.data.f}\n")
StrVal: print("str: {v.data.s}\n")
}
}
// --- Color as packed bytes ---
union Color {
rgba: u32
bytes: u8[4]
}
fn main() {
// Type punning: reinterpret i32 bits as f32
var pun: IntFloat;
pun.i = 1065353216; // IEEE 754 for 1.0f
print("int bits: {pun.i}\n");
print("as float: {pun.f}\n");
// Tagged union: variant storage
var v1: Value;
v1.kind = IntVal;
v1.data.i = 42;
print_value(v1);
var v2: Value;
v2.kind = FloatVal;
v2.data.f = 3.14;
print_value(v2);
var v3: Value;
v3.kind = StrVal;
v3.data.s = "hello";
print_value(v3);
// Packed color: write as u32, read bytes
var c: Color;
c.rgba = 0xFF8040FF;
print("color rgba: {c.rgba}\n");
}
Fat Str 25_fat_str.gx
// Example 25: Fat Strings (ec_str)
// str is now { ptr, len } — O(1) length, proper comparison, safe iteration.
fn main() {
// Basic str with .len and .cstr
var s: str = "hello";
print("s = {s}\n");
print("s.len = {s.len}\n");
// String comparison (uses ec_str_eq, not pointer comparison!)
var a: str = "hello";
var b: str = "hello";
var c: str = "world";
if (a == b) {
print("a == b: true (correct!)\n");
}
if (a != c) {
print("a != c: true (correct!)\n");
}
// String concatenation
var greeting: str = "Hello, " + "EC!";
print("greeting = {greeting}\n");
print("greeting.len = {greeting.len}\n");
// String interpolation
var x: i32 = 42;
var msg: str = "The answer is {x}";
print("{msg}\n");
// Empty string
var empty: str = "";
print("empty.len = {empty.len}\n");
// Escape sequences — sizeof handles them correctly
var newline: str = "a\nb";
print("newline.len = {newline.len}\n");
print("\nAll fat string tests passed!\n");
}
Sort 25_sort.gx
// 25_sort.ec - Sort on all container types: fixed array, slice, array<T>
// Build: ec examples/25_sort.ec -o build/25_sort.exe
fn print_arr(label: str, s: []i32) {
print("{label}: ");
for (var x in s) {
print("{x} ");
}
print("\n");
}
fn main() {
// === Fixed array sort ===
var fixed:i32[6] = {42, 7, 99, 3, 55, 1};
print_arr("before fixed", fixed);
fixed.sort();
print_arr("after fixed", fixed);
// === Slice sort ===
var data:i32[8] = {80, 20, 60, 10, 90, 40, 70, 30};
var sl:[]i32 = data[2:6];
print_arr("before slice", sl);
sl.sort();
print_arr("after slice", sl);
print_arr("full data ", data);
// === Dynamic array sort ===
var dyn:array<i32>;
dyn.init();
dyn.push(33);
dyn.push(11);
dyn.push(77);
dyn.push(22);
dyn.push(55);
// print via slice
var ds:[]i32 = dyn;
print_arr("before dyn ", ds);
dyn.sort();
var ds2:[]i32 = dyn;
print_arr("after dyn ", ds2);
dyn.free();
} Sizeof 26_sizeof.gx
// sizeof expression examples
struct Point {
x: f32
y: f32
}
struct Color {
r: u8
g: u8
b: u8
a: u8
}
fn main() {
// sizeof with built-in types
print("sizeof(i8) = {sizeof(i8)}\n");
print("sizeof(i16) = {sizeof(i16)}\n");
print("sizeof(i32) = {sizeof(i32)}\n");
print("sizeof(i64) = {sizeof(i64)}\n");
print("sizeof(f32) = {sizeof(f32)}\n");
print("sizeof(f64) = {sizeof(f64)}\n");
print("sizeof(bool) = {sizeof(bool)}\n");
// sizeof with user-defined structs (via expression)
var p:Point = Point{1.0, 2.0};
print("sizeof(Point) = {sizeof(p)}\n");
var c:Color = Color{255, 128, 0, 255};
print("sizeof(Color) = {sizeof(c)}\n");
// sizeof with vec types
print("sizeof(vec2) = {sizeof(vec2)}\n");
print("sizeof(vec3) = {sizeof(vec3)}\n");
print("sizeof(vec4) = {sizeof(vec4)}\n");
// sizeof in expressions
var arr_size:i64 = 10 * sizeof(i32);
print("10 * sizeof(i32) = {arr_size}\n");
} Slice 26_slice.gx
// Example 26: Slices and Safe Arrays
// Arrays automatically have .len and work like slices.
//
// Slice ranges are INCLUSIVE on both ends, matching the loop convention
// `for (i = a:b)`:
// arr[1:4] → indices 1, 2, 3, 4 (4 elements)
// arr[0:0] → 1 element (just index 0)
// arr[0:arr.len-1] → the whole array
fn sum:i32(data: []i32) {
var total: i32 = 0;
for (var x in data) {
total += x;
}
return total;
}
fn print_slice(data: []i32) {
print("[");
for (var x in data) {
print("{x} ");
}
print("]\n");
}
fn main() {
var arr: i32[5] = {10, 20, 30, 40, 50};
// Arrays have .len — compile-time constant, no struct needed
print("arr.len = {arr.len}\n");
// Arrays implicitly convert to slices at function boundaries
print("sum(arr) = {sum(arr)}\n");
print("arr = ");
print_slice(arr);
// Subslicing works directly on arrays. Inclusive ranges:
// arr[1:4] takes indices 1, 2, 3, 4 → 4 elements.
var mid: []i32 = arr[1:4];
print("arr[1:4] = ");
print_slice(mid);
print("mid.len = {mid.len}\n");
// Slices work the same way
var s: []i32 = arr;
print("s[0] = {s[0]}\n");
print("s.len = {s.len}\n");
// Subslice of subslice — mid[0:2] takes indices 0, 1, 2 → 3 elements
var inner: []i32 = mid[0:2];
print("inner = ");
print_slice(inner);
print("\nAll tests passed!\n");
}
Bounds Check 27_bounds_check.gx
// Example 27: Bounds Checking
// Out-of-bounds access panics in debug mode (default).
// Use -O1 or higher to disable checks for release builds.
fn main() {
// Fixed array bounds check
var arr: i32[3] = {10, 20, 30};
print("arr[0] = {arr[0]}\n");
print("arr[2] = {arr[2]}\n");
print("arr.len = {arr.len}\n");
// Slice bounds check
var s: []i32 = arr;
print("s[0] = {s[0]}\n");
print("s[2] = {s[2]}\n");
// This will PANIC:
print("About to access s[10]...\n");
print("s[10] = {s[10]}\n");
print("This should never print!\n");
}
Const 28_const.gx
// Example 28: Const declarations and constant folding
// Global constants (no type annotation - inferred)
const PI = 3.14159265358979;
const TAU = PI * 2.0;
const SCREEN_WIDTH = 1920;
const SCREEN_HEIGHT = 1080;
const ASPECT_RATIO = 16.0 / 9.0;
// Global constants with type annotation
const MAX_ENTITIES:i32 = 1024;
const BUFFER_SIZE:i64 = MAX_ENTITIES * 64;
// Const expressions with bitwise ops
const FLAG_READ = 1;
const FLAG_WRITE = 2;
const FLAG_EXEC = 4;
const FLAG_ALL = FLAG_READ | FLAG_WRITE | FLAG_EXEC;
// Boolean consts
const DEBUG_MODE = false;
const IS_WIDE = SCREEN_WIDTH > 1000;
// Const arrays — explicit size or inferred with []
const PRIMES:i32[5] = { 2, 3, 5, 7, 11 };
const COLORS:str[] = { "red", "green", "blue" }; // size inferred to 3
const FIB:i64[] = { 1, 1, 2, 3, 5, 8, 13, 21 };
fn main() {
print("=== Const Declarations ===");
// Using global consts
print("PI = {PI}");
print("TAU = {TAU}");
print("Screen: {SCREEN_WIDTH}x{SCREEN_HEIGHT}");
print("Aspect ratio: {ASPECT_RATIO}");
print("Max entities: {MAX_ENTITIES}");
print("Buffer size: {BUFFER_SIZE}");
print("");
print("=== Const Folding ===");
// Const folding: these should be computed at compile time
const AREA = SCREEN_WIDTH * SCREEN_HEIGHT;
print("Screen area: {AREA}");
const HALF_PI = PI / 2.0;
print("PI/2 = {HALF_PI}");
// Bitwise const
print("FLAG_ALL = {FLAG_ALL}");
// Boolean const
print("Debug mode: {DEBUG_MODE}");
print("Is wide: {IS_WIDE}");
print("");
print("=== Const in Expressions ===");
// Const used in regular expressions
var x = SCREEN_WIDTH / 2;
var y = SCREEN_HEIGHT / 2;
print("Center: ({x}, {y})");
// Const in loop bounds
const LOOP_COUNT = 5;
for (i=0:LOOP_COUNT) {
print(" i = {i}");
}
print("");
print("=== Negative and Unary ===");
const NEG_ONE = -1;
const NOT_FLAG = ~FLAG_READ;
print("NEG_ONE = {NEG_ONE}");
print("~FLAG_READ = {NOT_FLAG}");
print("");
print("=== Const Arrays ===");
print("First prime: {PRIMES[0]}");
print("Last color: {COLORS[2]}");
print("FIB[7] = {FIB[7]}");
print("Done!");
}
Compile-Time
Comptime If 29_comptime_if.gx
// Example 29: Compile-time conditional compilation (#if)
// Platform detection with @os
#if (@os == "windows") {
const PLATFORM = "Windows";
}
#else {
const PLATFORM = "Other";
}
fn main() {
print("Platform: {PLATFORM}");
// Statement-level #if with @os
#if (@os == "windows") {
print("Running on Windows");
}
#else {
print("Running on non-Windows");
}
// #if with user const
const ENABLE_FEATURE = true;
#if (ENABLE_FEATURE) {
print("Feature enabled");
}
// @debug check (false unless --debug flag)
#if (@debug) {
print("Debug mode ON");
}
#else {
print("Debug mode OFF");
}
// @opt level check
#if (@opt > 0) {
print("Optimized build");
}
#else {
print("Unoptimized build");
}
print("Done!");
}
Comptime Fn 30_comptime_fn.gx
// Example 30: Compile-time functions (#fn)
// Align a size up to the given alignment
#fn align_up:i32(size:i32, align:i32) {
return (size + align - 1) & ~(align - 1);
}
// Create a bitmask with N bits set
#fn make_bitmask:i32(bits:i32) {
if (bits <= 0) { return 0; }
if (bits >= 32) { return -1; }
return (1 << bits) - 1;
}
// Compile-time fibonacci
#fn fib:i32(n:i32) {
if (n <= 1) { return n; }
return fib(n - 1) + fib(n - 2);
}
// Compile-time max
#fn max:i32(a:i32, b:i32) {
if (a > b) { return a; }
return b;
}
// Compile-time power
#fn pow:i32(base:i32, exp:i32) {
var result:i32 = 1;
var i:i32 = 0;
while (i < exp) {
result = result * base;
i = i + 1;
}
return result;
}
// All evaluated at compile time — no runtime computation!
const BUF_SIZE = align_up(100, 64);
const CHANNEL_MASK = make_bitmask(8);
const PIXEL_MASK = make_bitmask(24);
const FIB_10 = fib(10);
const MAX_DIM = max(1920, 1080);
const TWO_TO_10 = pow(2, 10);
const COMBINED = align_up(FIB_10, 16);
fn main() {
print("=== Compile-Time Functions (#fn) ===");
print("align_up(100, 64) = {BUF_SIZE}");
print("make_bitmask(8) = {CHANNEL_MASK}");
print("make_bitmask(24) = {PIXEL_MASK}");
print("fib(10) = {FIB_10}");
print("max(1920, 1080) = {MAX_DIM}");
print("pow(2, 10) = {TWO_TO_10}");
print("align_up(fib(10), 16) = {COMBINED}");
print("Done!");
}
Comptime For 31_comptime_for.gx
// Example 31: Compile-time loop unrolling with #for
// #for unrolls the loop at compile time, substituting the iteration
// variable with literal integer values. No runtime loop overhead.
fn main() {
// Basic #for — unrolls to 5 print calls
print("=== Compile-Time Loop Unrolling (#for) ===");
#for (i=0:5) {
print("iteration {i}");
}
// #for with expressions using the loop variable
var sum:i32 = 0;
#for (i=1:6) {
sum += i * i;
}
print("sum of squares 1..5 = {sum}");
// Nested #for — generates 3x3 = 9 iterations
#for (row=0:3) {
#for (col=0:3) {
var val:i32 = row * 3 + col;
print(" ({row},{col}) = {val}");
}
}
// #for with const bounds
const N = 4;
#for (i=0:N) {
print("channel {i}");
}
print("Done!");
}
Struct Comptime 32_struct_comptime.gx
// Example 32: Struct and Enum level comptime expansion (Phase 5)
// Struct with conditional fields based on platform.
//
// PlatformInfo has different fields depending on @os: on Windows it
// gets (handle, registry_key); elsewhere it gets (fd, path). The
// expansion happens at compile time during the comptime pre-pass,
// before the resolver runs.
//
// Note: comptime statement-level #if inside fn bodies runs in the
// post-pass (after type checking), so main() below only touches the
// unconditional fields (name, version). Reading or writing the
// platform-specific fields directly would force the type checker to
// see both branches of an #if and reject the inactive one. The struct
// expansion itself works fine — it's just the access pattern in
// fn-body code that has to stay platform-agnostic.
struct PlatformInfo {
name: str
#if (@os == "windows") {
handle: i64
registry_key: str
} #else {
fd: i32
path: str
}
version: i32
}
// Struct with generated fields via #for
struct Pixel {
#for (i=0:4) {
channel_i: f32
}
}
// Enum with conditional members
enum LogLevel {
Error
Warning
Info
#if (@debug) {
Debug
Trace
}
}
fn main() {
print("=== Struct/Enum Comptime Expansion (Phase 5) ===");
// PlatformInfo: only access fields that are unconditional.
var info: PlatformInfo;
info.name = "test";
info.version = 1;
print("name = {info.name}");
print("version = {info.version.str()}");
// Pixel has channel_0 through channel_3 (generated by #for)
var px: Pixel;
px.channel_0 = 1.0;
px.channel_1 = 0.5;
px.channel_2 = 0.25;
px.channel_3 = 1.0;
print("pixel = ({px.channel_0.str()}, {px.channel_1.str()}, {px.channel_2.str()}, {px.channel_3.str()})");
// LogLevel enum — Debug/Trace only exist in debug builds
var level: LogLevel = Error;
print("log level = {level.str()}");
print("Done!");
}
Reflection 33_reflection.gx
// Example 33: Compile-time reflection (Phase 6)
struct Point {
x: f32
y: f32
z: f32
}
struct Node {
value: i32
next: *Node
}
enum Color { Red Green Blue }
fn main() {
print("=== Compile-Time Reflection (Phase 6) ===\n");
// --- Struct field reflection ---
print("Point has {@fields(Point)} fields:\n");
#for (i=0:@fields(Point)) {
print(" {@field(Point, i)}: {@field_type(Point, i)}\n");
}
// --- Field detail intrinsics ---
print("Node has {@fields(Node)} fields:");
#for (i=0:@fields(Node)) {
print(" {@field(Node, i)}: ptr={@field_is_ptr(Node, i)}\n");
}
// --- Enum member reflection ---
print("Color has {@members(Color)} members:\n");
#for (i=0:@members(Color)) {
print(" {@member(Color, i)}");
}
// --- Type meta ---
print("@type_kind(Point) = {@type_kind(Point)}\n");
print("@type_kind(Color) = {@type_kind(Color)}\n");
print("@type_name(Point) = {@type_name(Point)}\n");
// Note: Function reflection (@params, @param, @return_type) works
// but requires multi-file module setup (single-file function
// declarations have a pre-existing resolver limitation)
print("Done!");
}
Build Directives 34_build_directives.gx
// Example 34: Build directives (Phase 7)
// @link, @cflags, @ldflags make .ec files self-contained.
// Build directives — these inject linker/compiler flags without CLI args
@link("ucrtbase")
// Platform-conditional linking via #if
#if (@os == "windows") {
@link("kernel32")
}
fn main() {
print("=== Build Directives (Phase 7) ===");
print("@link and @cflags are collected from source");
print("No CLI flags needed for linking!");
print("Done!");
}
Templates 35_templates.gx
// Example 35: Compile-time templates (Phase 8)
// #type T declares a type parameter — monomorphized at compile time.
#type T
struct Pair {
first: T
second: T
}
fn main() {
print("=== Compile-Time Templates (Phase 8) ===");
// Pair<i32> — generates struct Pair_i32 { int32_t first; int32_t second; }
var pi: Pair<i32>;
pi.first = 10;
pi.second = 20;
print("Pair<i32>: ({pi.first.str()}, {pi.second.str()})");
// Pair<i64> — generates struct Pair_i64 { int64_t first; int64_t second; }
var pl: Pair<i64>;
pl.first = 100;
pl.second = 200;
print("Pair<i64>: ({pl.first.str()}, {pl.second.str()})");
var pf: Pair<f32>;
pf.first = 100.32;
pf.second = 200.32;
print("Pair<f32>: ({pf.first.str()}, {pf.second.str()})");
var ps: Pair<str>;
ps.first = "Afan";
ps.second = "Olovcic";
print("Pair<f32>: ({ps.first.str()}, {ps.second.str()})");
{
var ps:Pair< str >;
ps.first = "Sanela";
ps.second = "Gluhovic";
print("Pair<f32>: ({ps.first.str()}, {ps.second.str()})");
}
print("Done!");
}
Standard Library
Pointers 36_pointers.gx
// Example 36: Pointer arithmetic
fn main() {
print("=== Pointer Arithmetic ===");
// Basic dereference
var x:i32 = 42;
var p:*i32 = &x;
print("*p = {(*p).str()}");
*p = 99;
print("after *p=99: x = {x.str()}#\n");
// Pointer arithmetic: take address, add offset
var a:i32 = 10;
var b:i32 = 20;
var c:i32 = 30;
a+=10;
a++;
a+=0xFF;
var pa:*i32 = &a;
// Test pointer + int type
var pb:*i32 = pa + 1;
print("pa+1 deref = {(*pb).str()}");
// Pointer difference
var diff:i64 = pb - pa;
print("pb - pa = {diff.str()}");
// Pointer indexing
print("pa[0] = {pa[0].str()}");
print("Done!");
}
Main Args 37_main_args.gx
// Example 37: Main with string arguments
// Usage: gx 37_main_args.gx -o args.exe && args.exe hello world
fn main(args: str[]) {
print("Got {args.len} arguments:\n");
for (var arg in args) {
print(" - {arg}\n");
}
}
Par For 37_par_for.gx
// Example 37: Parallel For (par for)
// Uses OpenMP under the hood — requires -O1+ (clang/gcc) for actual parallelism
// With TCC (default), runs sequentially (graceful degradation)
fn main() {
var data:i32[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Parallel for-range: each iteration is independent
par for (i = 0:9) {
data[i] = data[i] * 2;
print("{data[i]}\n");
}
// Print results
/*for (i = 0:9) {
print("{data[i]}\n");
}*/
}
Strings 38_strings.gx
// 38_strings.gx — String method tests
fn main() {
var s = " Hello, World! "
// ── trim ─────────────────────────────────
var trimmed = s.trim()
var trim_l = s.trim_left()
var trim_r = s.trim_right()
print("trim: '" + trimmed + "'\n")
print("trim_left: '" + trim_l + "'\n")
print("trim_right: '" + trim_r + "'\n")
var hw = s.trim()
// ── find / contains ──────────────────────
var needle1 = "World"
var needle2 = "xyz"
var needle3 = "l"
print("find World: {hw.find(needle1)}\n")
print("find xyz: {hw.find(needle2)}\n")
print("find_last l: {hw.find_last(needle3)}\n")
print("contains Hello: {hw.contains(needle1)}\n")
print("contains xyz: {hw.contains(needle2)}\n")
// ── starts_with / ends_with ──────────────
var prefix = "Hello"
var suffix = "!"
print("starts_with Hello: {hw.starts_with(prefix)}\n")
print("ends_with !: {hw.ends_with(suffix)}\n")
// ── sub ──────────────────────────────────
var sub1 = hw.sub(0, 5)
var sub2 = hw.sub(7)
print("sub(0,5): '" + sub1 + "'\n")
print("sub(7): '" + sub2 + "'\n")
// ── upper / lower ────────────────────────
var up = hw.upper()
var lo = hw.lower()
print("upper: '" + up + "'\n")
print("lower: '" + lo + "'\n")
// ── replace ──────────────────────────────
var old_s = "World"
var new_s = "GX"
var rep = hw.replace(old_s, new_s)
print("replace: '" + rep + "'\n")
// ── count ────────────────────────────────
var cnt = hw.count(needle3)
print("count l: {cnt}\n")
// ── repeat ───────────────────────────────
var dash_str = "-"
var dash = dash_str.repeat(20)
print(dash + "\n")
// ── split ────────────────────────────────
var csv = "one,two,three,four"
var delim = ","
var parts = csv.split(delim)
print("split count: {parts.len}\n")
for (var p in parts) {
print(" part: '" + p + "'\n")
}
// ── chaining ─────────────────────────────
var input_str = " HELLO world "
var hello = "hello"
var hi = "hi"
var result = input_str.trim().lower().replace(hello, hi)
print("chained: '" + result + "'\n")
// ── string interpolation to var ──────────
var name = "GX"
var greeting:str = "Hello {name}!"
print(greeting + "\n")
}
Io 39_io.gx
// 39_io.gx - Test the io module
import io.io
import io.file
import io.fs
import io.path
fn main() {
// --- Console I/O ---
println("=== Console I/O ===")
println("Hello from println!")
eprintln("This goes to stderr")
// --- Path utilities ---
println("\n=== Path Utilities ===")
var p = "src/frontend/parser/parser.cpp"
print(" path: ") println(p)
print(" dirname: ") println(dirname(p))
print(" basename: ") println(basename(p))
print(" extension: ") println(extension(p))
print(" stem: ") println(stem(p))
var win = "C:\\Users\\test\\file.txt"
print(" win path: ") println(win)
print(" dirname: ") println(dirname(win))
print(" basename: ") println(basename(win))
print(" is_absolute('/usr/bin'): ")
if (is_absolute("/usr/bin")) { println("true") } else { println("false") }
print(" is_absolute('C:\\foo'): ")
if (is_absolute("C:\\foo")) { println("true") } else { println("false") }
print(" is_absolute('relative'): ")
if (is_absolute("relative")) { println("true") } else { println("false") }
print(" join('foo','bar'): ") println(join("foo", "bar"))
print(" join('foo/','bar'): ") println(join("foo/", "bar"))
// --- Filesystem ---
println("\n=== Filesystem ===")
print(" exists('examples/39_io.gx'): ")
if (exists("examples/39_io.gx")) { println("true") } else { println("false") }
print(" is_file('examples/39_io.gx'): ")
if (is_file("examples/39_io.gx")) { println("true") } else { println("false") }
print(" is_dir('examples'): ")
if (is_dir("examples")) { println("true") } else { println("false") }
print(" is_file('nonexistent'): ")
if (is_file("nonexistent")) { println("true") } else { println("false") }
var sz = file_size("examples/39_io.gx")
print(" file_size: ") print("{sz}") println(" bytes")
// --- File I/O ---
println("\n=== File I/O ===")
// Write a test file
var ok = write_all("_test_io.txt", "Hello from GX io module!\nLine 2\n")
print(" write_all: ") if (ok) { println("ok") } else { println("failed") }
// Read it back
var content = read_all("_test_io.txt")
print(" read_all: ") print(content)
// Append to it
ok = append_all("_test_io.txt", "Line 3 appended\n")
print(" append: ") if (ok) { println("ok") } else { println("failed") }
// Read again
content = read_all("_test_io.txt")
print(" after append:\n") print(content)
// Low-level file API
var f = file_open("_test_io.txt", "r")
if (f != 0) {
var pos = file_tell(f)
print(" tell at start: ") println("{pos}")
file_close(f)
}
// Clean up test file
ok = remove_file("_test_io.txt")
print(" remove: ") if (ok) { println("ok") } else { println("failed") }
print(" exists after remove: ")
if (exists("_test_io.txt")) { println("true") } else { println("false") }
// make_dir / remove_dir
ok = make_dir("_test_dir")
print("\n make_dir: ") if (ok) { println("ok") } else { println("failed") }
print(" is_dir: ") if (is_dir("_test_dir")) { println("true") } else { println("false") }
ok = remove_dir("_test_dir")
print(" remove_dir:") if (ok) { println("ok") } else { println("failed") }
println("\n=== Done ===")
}
Random 40_random.gx
// 40_random.gx - Test random and noise modules
import random.random
import random.noise
fn main() {
// --- Random numbers ---
print("=== Random (default seed) ===\n")
print(" rand_int(100): ") print("{rand_int(100)}") print("\n")
print(" rand_int(100): ") print("{rand_int(100)}") print("\n")
print(" rand_range(10,20): ") print("{rand_range(10, 20)}") print("\n")
print(" rand_f32(): ") print("{rand_f32()}") print("\n")
print(" rand_bool(): ")
if (rand_bool()) { print("true\n") } else { print("false\n") }
// --- Seeded (deterministic) ---
print("\n=== Seeded (42) ===\n")
seed(42)
print(" rand_int(100): ") print("{rand_int(100)}") print("\n")
print(" rand_int(100): ") print("{rand_int(100)}") print("\n")
// Same seed = same sequence
print("\n=== Seeded (42) again ===\n")
seed(42)
print(" rand_int(100): ") print("{rand_int(100)}") print("\n")
print(" rand_int(100): ") print("{rand_int(100)}") print("\n")
// --- Float range ---
print("\n=== Float range ===\n")
seed(123)
var i:i32 = 0
while (i < 5) {
var v:f32 = rand_f32_range(-1.0, 1.0)
print(" rand_f32_range(-1,1): ") print("{v}") print("\n")
i = i + 1
}
// --- Simplex noise ---
print("\n=== Simplex Noise ===\n")
print(" simplex1(0.5): ") print("{simplex1(0.5)}") print("\n")
print(" simplex1(1.0): ") print("{simplex1(1.0)}") print("\n")
print(" simplex1(1.5): ") print("{simplex1(1.5)}") print("\n")
print(" simplex2(0.5, 0.5): ") print("{simplex2(0.5, 0.5)}") print("\n")
print(" simplex2(1.0, 2.0): ") print("{simplex2(1.0, 2.0)}") print("\n")
print(" simplex3(0.5, 0.5, 0.5): ") print("{simplex3(0.5, 0.5, 0.5)}") print("\n")
print(" simplex4(1,2,3,4): ") print("{simplex4(1.0, 2.0, 3.0, 4.0)}") print("\n")
// --- fBm ---
print("\n=== Fractal Brownian Motion ===\n")
print(" fbm2(0.5, 0.5, 4, 2.0, 0.5): ") print("{fbm2(0.5, 0.5, 4, 2.0, 0.5)}") print("\n")
print(" fbm3(1, 2, 3, 6, 2.0, 0.5): ") print("{fbm3(1.0, 2.0, 3.0, 6, 2.0, 0.5)}") print("\n")
// --- 2D noise map (ASCII) ---
print("\n=== 2D Noise Map (10x5) ===\n")
var y:i32 = 0
while (y < 5) {
var x:i32 = 0
while (x < 20) {
var n:f32 = simplex2(x * 0.2, y * 0.2)
if (n > 0.3) { print("#") }
else if (n > 0.0) { print("+") }
else if (n > -0.3) { print(".") }
else { print(" ") }
x = x + 1
}
print("\n")
y = y + 1
}
print("\n=== Done ===\n")
}
Out Params 41_out_params.gx
// Example 41: Out Parameters & Const-by-Default Pointers
//
// GX pointer parameters are read-only (const) by default.
// Use 'out' to mark parameters that the function will modify.
// GX functions cannot return pointers — use 'out' params instead.
struct Point {
x: f32
y: f32
}
struct Result {
value: f32
ok: bool
}
// 'out' pointer param: function CAN modify what the pointer points to
fn init_point(out p: *Point, x: f32, y: f32) {
p.x = x
p.y = y
}
fn scale_point(out p: *Point, factor: f32) {
p.x = p.x * factor
p.y = p.y * factor
}
// No 'out': pointer is read-only (const Point* in C)
fn print_point(p: *Point) {
print("({p.x.str()}, {p.y.str()})\n")
// p.x = 99.0 ← compile error: Cannot write through read-only pointer
}
// out value param: multi-return pattern
// 'out result:f32' becomes 'float*' in C
fn divide:bool(a: f32, b: f32, out result: f32) {
if (b == 0.0) { return false }
*result = a / b
return true
}
// out struct param: fill a result struct
fn compute(a: f32, b: f32, out r: *Result) {
if (b == 0.0) {
r.ok = false
r.value = 0.0
} else {
r.ok = true
r.value = a / b
}
}
fn main() {
// === Out pointer param: init and modify ===
var pt = Point{0.0, 0.0}
init_point(&pt, 3.0, 4.0)
print("After init: ")
print_point(&pt)
scale_point(&pt, 2.0)
print("After scale: ")
print_point(&pt)
// === Out value param (multi-return) ===
var result: f32 = 0.0
if (divide(10.0, 3.0, &result)) {
print("10 / 3 = {result.str()}\n")
}
if (!divide(5.0, 0.0, &result)) {
print("Division by zero caught!\n")
}
// === Out struct param ===
var r = Result{0.0, false}
compute(10.0, 4.0, &r)
if (r.ok) {
print("10 / 4 = {r.value.str()}\n")
}
}
Typedef 42_typedef.gx
// Example 42: Type Aliases
//
// `type NewName:ExistingType` creates a type alias.
// The alias is interchangeable with the original type.
// Emits a C `typedef` — zero runtime cost.
// Alias to built-in types
type byte:u8
type size:u64
type real:f32
// Alias to pointer type
type cstring:*c_char
struct Point {
x: real
y: real
}
type Vec2D:Point
fn distance:real(a: *Vec2D, b: *Vec2D) {
var dx:real = b.x - a.x
var dy:real = b.y - a.y
return dx * dx + dy * dy
}
fn main() {
var b:byte = 255
var s:size = 1024
var r:real = 3.14
print("byte: {b.str()}\n")
print("size: {s.str()}\n")
print("real: {r.str()}\n")
var p1 = Vec2D{1.0, 2.0}
var p2 = Vec2D{4.0, 6.0}
var d = distance(&p1, &p2)
print("distance^2: {d.str()}\n")
} Microaudio 44_microaudio.gx
// Example 44: Microaudio
//
// Minimal audio playback using the microaudio module.
// Loads and plays WAV files.
//
// Run: gx examples/44_microaudio.gx -I modules -o build/audio_test.exe
import microaudio.audio
fn main() {
audio_init()
defer audio_shutdown()
// Load a WAV file (place a .wav file next to the executable)
var snd = audio_load("test.wav")
if (snd < 0) {
print("Could not load test.wav\n")
print("Place a WAV file named test.wav in the working directory.\n")
return
}
print("Sound loaded (id={snd.str()})\n")
// Set volume to 80%
audio_set_volume(snd, 0.8)
// Play the sound
audio_play(snd)
print("Playing... press Enter to stop.\n")
input()
audio_stop(snd)
audio_free(snd)
print("Done.\n")
}
Json 45_json.gx
// 45_json.gx - JSON module example
// Demonstrates parsing, querying, building, and serializing JSON
import json
fn main() {
// ---- Parsing JSON ----
var text = "{\"name\": \"Alice\", \"age\": 30, \"scores\": [95, 87, 92], \"active\": true}"
var doc = json.gx_json_parse(text.cstr, text.len, 0)
if (doc == 0) {
print("Failed to parse JSON\n")
return
}
defer json.gx_json_free(doc)
var r = json.gx_json_root(doc)
// ---- Reading values ----
var name = json.gx_json_get_str(json.gx_json_obj_get(r, "name"))
var age = json.gx_json_get_int(json.gx_json_obj_get(r, "age"))
var active = json.gx_json_get_bool(json.gx_json_obj_get(r, "active"))
print("Name: {name}\n")
print("Age: {age}\n")
print("Active: {active}\n")
// ---- Array access ----
var scores = json.gx_json_obj_get(r, "scores")
print("Scores:\n")
// Array access by index
var s0 = json.gx_json_get_int(json.gx_json_arr_get(scores, 0))
var s1 = json.gx_json_get_int(json.gx_json_arr_get(scores, 1))
var s2 = json.gx_json_get_int(json.gx_json_arr_get(scores, 2))
print(" [0] = {s0}\n")
print(" [1] = {s1}\n")
print(" [2] = {s2}\n")
// ---- Type inspection ----
var name_type = json.gx_json_type_desc(json.gx_json_obj_get(r, "name"))
var age_type = json.gx_json_type_desc(json.gx_json_obj_get(r, "age"))
var scores_type = json.gx_json_type_desc(scores)
print("Type of name: {name_type}\n")
print("Type of age: {age_type}\n")
print("Type of scores: {scores_type}\n")
// ---- Serialize back (pretty) ----
var pretty = json.gx_json_write(doc, json.WRITE_PRETTY)
print("\nPretty printed:\n{pretty}\n")
// ---- Build JSON from scratch ----
print("--- Building JSON ---\n")
var mdoc = json.gx_json_new_doc()
defer json.gx_json_mut_free(mdoc)
var obj = json.gx_json_new_obj(mdoc)
json.gx_json_set_root(mdoc, obj)
json.gx_json_obj_add_str(mdoc, obj, "language", "GX")
json.gx_json_obj_add_int(mdoc, obj, "version", 2)
json.gx_json_obj_add_bool(mdoc, obj, "fast", true)
json.gx_json_obj_add_float(mdoc, obj, "pi", 3.14159)
// Nested array
var arr = json.gx_json_obj_add_arr(mdoc, obj, "features")
json.gx_json_arr_add_str(mdoc, arr, "transpiles to C")
json.gx_json_arr_add_str(mdoc, arr, "zero-cost abstractions")
json.gx_json_arr_add_str(mdoc, arr, "yyjson powered")
// Nested object
var meta = json.gx_json_obj_add_obj(mdoc, obj, "meta")
json.gx_json_obj_add_str(mdoc, meta, "author", "GX Team")
json.gx_json_obj_add_int(mdoc, meta, "year", 2026)
var result = json.gx_json_mut_write(mdoc, json.WRITE_PRETTY)
print("{result}\n")
}
Time 46_time.gx
// 46_time.gx - Time module example
// Demonstrates clock, date/time, formatting, benchmarking, and sleep
import time
fn main() {
// ---- Current date and time ----
var now = time.gx_time_unix()
var y = time.gx_time_year(now)
var mo = time.gx_time_month(now)
var d = time.gx_time_day(now)
var h = time.gx_time_hour(now)
var mi = time.gx_time_minute(now)
var s = time.gx_time_second(now)
print("Current local time: {y}-{mo}-{d} {h}:{mi}:{s}\n")
// UTC equivalent
var uy = time.gx_time_utc_year(now)
var umo = time.gx_time_utc_month(now)
var ud = time.gx_time_utc_day(now)
var uh = time.gx_time_utc_hour(now)
print("Current UTC time: {uy}-{umo}-{ud} {uh}:{mi}:{s}\n")
// ---- Formatting ----
var formatted = time.gx_time_format(now, "%Y-%m-%d %H:%M:%S")
print("Formatted (local): {formatted}\n")
var formatted_utc = time.gx_time_format_utc(now, "%Y-%m-%d %H:%M:%S")
print("Formatted (UTC): {formatted_utc}\n")
// Day of week
var wd = time.gx_time_weekday(now)
var day_name = time.gx_time_format(now, "%A")
print("Weekday: {wd} ({day_name})\n")
// ---- Benchmarking ----
print("\n--- Benchmark ---\n")
var t0 = time.gx_time_now_ns()
// Do some work
var sum = 0
for (i = 0 : 999999) {
sum = sum + i
}
var t1 = time.gx_time_now_ns()
var ms = time.gx_time_elapsed_ms(t0, t1)
var us = time.gx_time_elapsed_us(t0, t1)
print("Sum of 0..999999 = {sum}\n")
print("Elapsed: {ms} ms ({us} us)\n")
// ---- Sleep ----
print("\n--- Sleep test ---\n")
var s0 = time.gx_time_now_ns()
time.gx_time_sleep_ms(100)
var s1 = time.gx_time_now_ns()
var slept = time.gx_time_elapsed_ms(s0, s1)
print("Slept ~100ms, measured: {slept} ms\n")
// ---- Build timestamp ----
var epoch = time.gx_time_make(2026, 1, 1, 0, 0, 0)
var new_year = time.gx_time_format(epoch, "%Y-%m-%d %H:%M:%S (%A)")
print("\nNew Year 2026: {new_year}\n")
}
Xml 47_xml.gx
// 47_xml.gx - XML parsing, navigation, and building example
// Build: gx 47_xml.gx -I modules -o build/47_xml.exe
import xml
fn main() {
// ---- Parse XML from string ----
var data = "<library name='City Library'><book id='1'><title>The Rust Programming Language</title><author>Steve Klabnik</author><year>2019</year></book><book id='2'><title>The C Programming Language</title><author>Kernighan and Ritchie</author><year>1978</year></book><book id='3'><title>Structure and Interpretation</title><author>Abelson and Sussman</author><year>1996</year></book></library>"
var doc = xml.gx_xml_parse(data.cstr, data.len)
if (xml.gx_xml_has_error(doc)) {
print("Parse error: {xml.gx_xml_error(doc)}\n")
xml.gx_xml_free(doc)
return
}
// Root element
print("Root tag: {xml.gx_xml_name(doc)}\n")
var lib_name = xml.gx_xml_attr(doc, "name")
print("Library: {lib_name}\n")
var num_books = xml.gx_xml_child_count(doc, "book")
print("Book count: {num_books}\n\n")
// Iterate children with same tag name
var book = xml.gx_xml_child(doc, "book")
while (book != 0) {
var id = xml.gx_xml_attr(book, "id")
var title = xml.gx_xml_txt(xml.gx_xml_child(book, "title"))
var author = xml.gx_xml_txt(xml.gx_xml_child(book, "author"))
var year = xml.gx_xml_txt(xml.gx_xml_child(book, "year"))
print("Book {id}: {title} by {author} ({year})\n")
book = xml.gx_xml_next(book)
}
// ---- Build XML from scratch ----
print("\n--- Building XML ---\n")
var root = xml.gx_xml_new("config")
xml.gx_xml_set_attr(root, "version", "1.0")
var window = xml.gx_xml_add_child(root, "window")
xml.gx_xml_set_attr(window, "width", "1280")
xml.gx_xml_set_attr(window, "height", "720")
xml.gx_xml_set_txt(window, "Main Window")
var gfx = xml.gx_xml_add_child(root, "graphics")
xml.gx_xml_set_attr(gfx, "backend", "opengl")
var res = xml.gx_xml_add_child(gfx, "resolution")
xml.gx_xml_set_txt(res, "1920x1080")
var aa = xml.gx_xml_add_child(gfx, "antialiasing")
xml.gx_xml_set_txt(aa, "4x MSAA")
// Serialize to string
var xml_str = xml.gx_xml_to_str(root)
print("{xml_str}\n")
xml.gx_xml_free_str(xml_str)
xml.gx_xml_free(root)
xml.gx_xml_free(doc)
}
External Libraries
Enet 50_enet.gx
// 01_hello.gx — minimal ENet host/client smoke test in one process.
//
// Spins up a server and a client on localhost:7777, exchanges a single
// reliable packet, then tears down.
//
// Build: gx examples/enet/01_hello.gx -I modules -o build/enet_hello.exe
// Run: ./build/enet_hello.exe
//
// FRICTION LOG (this file):
// #3: RESOLVED — pointer cast syntax `(*void)expr` now lets us pass
// cstr buffers to const *void parameters directly.
// #4: RESOLVED — c_int and i32 are now mutually coercible at every site
// (assignment, comparison, arithmetic), so binding sigs can use
// either; the i32 declarations here remain for clarity.
// #7: GX's `fn main()` still lowers to non-void C main; bare `return`
// is still a clang error under -Wreturn-mismatch. Workaround
// (single-bottom-exit, no early returns from main) preserved.
// #8: RESOLVED — `null` literal is now a first-class language token.
import enet
fn main() {
var ok: bool = true
// ---- Init ----
if (enet_initialize() != 0) {
print("ENet init FAILED\n")
ok = false
}
if (ok) { print("ENet init OK\n") }
// Friction #8 RESOLVED: `null` literal is now in the language.
var server: *void = null
var client: *void = null
var peer: *void = null
if (ok) {
var server_addr: ENetAddress
server_addr.host = ENET_HOST_ANY
server_addr.port = 7777
server = enet_host_create(&server_addr, 4, 2, 0, 0)
if (server == null) {
print("server create FAILED\n")
ok = false
}
}
if (ok) { print("server listening on port 7777\n") }
if (ok) {
client = enet_host_create(0, 1, 2, 0, 0)
if (client == null) {
print("client create FAILED\n")
ok = false
}
}
if (ok) {
var connect_addr: ENetAddress
enet_address_set_host(&connect_addr, "127.0.0.1")
connect_addr.port = 7777
peer = enet_host_connect(client, &connect_addr, 2, 0)
if (peer == null) {
print("client connect FAILED\n")
ok = false
}
}
if (ok) { print("client dialing 127.0.0.1:7777...\n") }
// ---- Pump until handshake completes ----
var ev: ENetEvent
var connected: bool = false
var iters: i32 = 0
while (ok && !connected) {
if (iters > 500) {
print("handshake TIMEOUT\n")
ok = false
} else {
var sr: i32 = enet_host_service(server, &ev, 5)
if (sr > 0) {
if (ev.type == ENET_EVENT_TYPE_CONNECT) {
print("[server] peer connected\n")
}
}
var cr: i32 = enet_host_service(client, &ev, 5)
if (cr > 0) {
if (ev.type == ENET_EVENT_TYPE_CONNECT) {
print("[client] connected to server\n")
connected = true
}
}
iters = iters + 1
}
}
// ---- Send one reliable packet ----
if (ok) {
var msg: cstr = "hello from gx!"
// Friction #3 RESOLVED: pointer cast syntax `(*void)expr` lets us
// pass a cstr buffer directly. The C-glue gx_enet_packet_create_bytes
// shim is no longer needed and will be removed in a follow-up.
var pkt: *void = enet_packet_create((*void)msg, 14, ENET_PACKET_FLAG_RELIABLE)
enet_peer_send(peer, 0, pkt)
enet_host_flush(client)
print("[client] sent packet\n")
}
// ---- Wait for receive ----
var got: bool = false
iters = 0
while (ok && !got) {
if (iters > 200) {
print("receive TIMEOUT\n")
ok = false
} else {
var sr: i32 = enet_host_service(server, &ev, 5)
if (sr > 0) {
if (ev.type == ENET_EVENT_TYPE_RECEIVE) {
print("[server] got packet, channel={ev.channelID}\n")
enet_packet_destroy(ev.packet)
got = true
}
}
enet_host_service(client, &ev, 0)
iters = iters + 1
}
}
// ---- Disconnect cleanly ----
if (ok && peer != null) {
enet_peer_disconnect(peer, 0)
iters = 0
var done: bool = false
while (!done && iters < 50) {
var sr: i32 = enet_host_service(server, &ev, 5)
if (sr > 0) {
if (ev.type == ENET_EVENT_TYPE_DISCONNECT) {
print("[server] peer disconnected\n")
done = true
}
}
enet_host_service(client, &ev, 5)
iters = iters + 1
}
}
// ---- Tear down ----
if (client != null) { enet_host_destroy(client) }
if (server != null) { enet_host_destroy(server) }
enet_deinitialize()
if (ok) {
print("smoke test PASSED\n")
} else {
print("smoke test FAILED\n")
}
}
Threading 51_threading.gx
// 01_hello.gx — threading + chan<T> smoke test.
//
// 8 worker threads each push 100,000 ints into a shared channel.
// Main drains the channel until all expected values arrive, then
// joins every worker. Verifies count == 800,000.
//
// What this demonstrates:
// - Thread[N] typed handles instead of *void[N]
// - chan<T> for typed cross-thread communication (no atomics,
// no shared globals via raw memory, no manual *void casts)
// - Bounded channel back-pressure: capacity is small relative to
// total throughput, so producers block on full and resume as
// main drains. That's the load-bearing path of ec_chan.
//
// Build: gx examples/threading/01_hello.gx -I modules -o build/threading_hello.exe
// Run: ./build/threading_hello.exe
import threading
const NUM_THREADS = 8
const ITERS_PER_THREAD = 100000
var g_ch: chan<i32>(capacity = 64)
fn worker:i32(arg: *void) {
var i: i32 = 0
while (i < ITERS_PER_THREAD) {
g_ch.send(i)
i = i + 1
}
return 0
}
fn main() {
// Spawn N workers — typed handle array, no *void in user code.
var threads: Thread[8]
var i: i32 = 0
while (i < NUM_THREADS) {
gx_thread_start(&threads[i], worker, 0)
i = i + 1
}
// Drain in parallel with workers — required for back-pressure
// to release. Producers block on full sends until we recv.
var got_count: i32 = 0
var expected: i32 = NUM_THREADS * ITERS_PER_THREAD
var v: i32
while (got_count < expected) {
if (g_ch.recv(&v)) { got_count = got_count + 1 }
}
// All values delivered — workers are done. Join.
i = 0
while (i < NUM_THREADS) {
gx_thread_join(threads[i])
i = i + 1
}
g_ch.close()
print("received = {got_count}, expected = {expected}\n")
if (got_count == expected) {
print("smoke test PASSED\n")
} else {
print("smoke test FAILED\n")
}
}
Sdl3 52_sdl3.gx
// 01_hello.gx — Breakout-ish game in ~140 lines of GX, rendered with SDL3.
//
// Bricks on the LEFT, player paddle on the RIGHT. Ball bounces off the
// brick wall, the top/bottom edges, and the right paddle. If the ball
// gets past the paddle (off the right edge), miss count increments and
// the ball respawns. Wave clears → new wall.
//
// Controls:
// Up / Down — paddle up / down
// ESC — quit
//
// Build: gx examples/sdl3/01_hello.gx -I modules -o build/sdl3_hello.exe
// Run: ./build/sdl3_hello.exe (needs SDL3.dll on PATH or beside exe)
import sdl3
const SCREEN_W: f32 = 800.0
const SCREEN_H: f32 = 600.0
const PADDLE_W: f32 = 14.0
const PADDLE_H: f32 = 100.0
const PADDLE_SPEED: f32 = 6.0
const BALL_SIZE: f32 = 14.0
// Brick wall — 5 columns × 8 rows on the left third of the screen.
const BRICK_COLS: i32 = 5
const BRICK_ROWS: i32 = 8
const BRICK_W: f32 = 40.0
const BRICK_H: f32 = 50.0
const BRICK_GAP: f32 = 4.0
const BRICK_X0: f32 = 20.0
const BRICK_Y0: f32 = 80.0
const BRICK_COUNT: i32 = 40 // BRICK_COLS * BRICK_ROWS
fn clamp_paddle:f32(y: f32) {
if (y < 0.0) { return 0.0 }
var max_y: f32 = SCREEN_H - PADDLE_H
if (y > max_y) { return max_y }
return y
}
fn main() {
var ok: bool = true
var window: *void = null
var renderer: *void = null
if (!SDL_Init(SDL_INIT_VIDEO)) {
print("SDL_Init failed: {SDL_GetError()}\n")
ok = false
}
if (ok) {
window = SDL_CreateWindow("GX Breakout", 800, 600, 0)
if (window == null) { print("CreateWindow failed: {SDL_GetError()}\n"); ok = false }
}
if (ok) {
renderer = SDL_CreateRenderer(window, null)
if (renderer == null) { print("CreateRenderer failed: {SDL_GetError()}\n"); ok = false }
}
if (ok) {
// vsync removes the 15-ms granularity jitter SDL_Delay would
// otherwise introduce on Windows. Caps to monitor refresh.
SDL_SetRenderVSync(renderer, 1)
}
// ---- Game state ----
var paddle_y: f32 = (SCREEN_H - PADDLE_H) * 0.5
var ball_x: f32 = SCREEN_W * 0.5
var ball_y: f32 = SCREEN_H * 0.5
var ball_vx: f32 = 0.0 - 4.0 // start heading toward bricks
var ball_vy: f32 = 3.0
var miss: i32 = 0
var cleared: i32 = 0
// Brick aliveness — flat array indexed by row*COLS + col.
var bricks: bool[40]
var bi: i32 = 0
while (bi < BRICK_COUNT) { bricks[bi] = true; bi = bi + 1 }
var bricks_left: i32 = BRICK_COUNT
var quit: bool = !ok
var ev: SDL_Event
while (!quit) {
while (SDL_PollEvent(&ev)) {
if (ev.type == SDL_EVENT_QUIT) { quit = true }
if (ev.type == SDL_EVENT_KEY_DOWN) {
if (ev.key.scancode == SDL_SCANCODE_ESCAPE) { quit = true }
}
}
var keys: *c_bool = SDL_GetKeyboardState(null)
if (keys[SDL_SCANCODE_UP]) { paddle_y = paddle_y - PADDLE_SPEED }
if (keys[SDL_SCANCODE_DOWN]) { paddle_y = paddle_y + PADDLE_SPEED }
paddle_y = clamp_paddle(paddle_y)
// ---- Ball physics ----
ball_x = ball_x + ball_vx
ball_y = ball_y + ball_vy
// Top/bottom wall bounce
if (ball_y < 0.0) { ball_y = 0.0; ball_vy = 0.0 - ball_vy }
if (ball_y + BALL_SIZE > SCREEN_H) { ball_y = SCREEN_H - BALL_SIZE; ball_vy = 0.0 - ball_vy }
// Left wall bounce (in case the ball passes all the bricks)
if (ball_x < 0.0) { ball_x = 0.0; ball_vx = 0.0 - ball_vx }
// Brick collision — scan the alive bricks; first AABB overlap wins.
// To pick the right axis to reverse, we look at where the ball was
// BEFORE this frame's step. Whichever axis it was already overlapping
// on is the axis along which it entered → reverse the OTHER axis's
// velocity. This correctly handles top/bottom hits (reverse vy) vs
// side hits (reverse vx).
var prev_x: f32 = ball_x - ball_vx
var r: i32 = 0
var hit: bool = false
while (r < BRICK_ROWS && !hit) {
var c: i32 = 0
while (c < BRICK_COLS && !hit) {
var idx: i32 = r * BRICK_COLS + c
if (bricks[idx]) {
var bx: f32 = BRICK_X0 + c * (BRICK_W + BRICK_GAP)
var by: f32 = BRICK_Y0 + r * (BRICK_H + BRICK_GAP)
if (ball_x + BALL_SIZE > bx) {
if (ball_x < bx + BRICK_W) {
if (ball_y + BALL_SIZE > by) {
if (ball_y < by + BRICK_H) {
bricks[idx] = false
bricks_left = bricks_left - 1
// Was the ball overlapping this brick's
// x-range last frame? If so, it entered
// vertically — reverse vy. Otherwise it
// entered horizontally — reverse vx.
var was_in_x: bool = (prev_x + BALL_SIZE > bx) && (prev_x < bx + BRICK_W)
if (was_in_x) { ball_vy = 0.0 - ball_vy }
else { ball_vx = 0.0 - ball_vx }
hit = true
}
}
}
}
}
c = c + 1
}
r = r + 1
}
// Right paddle collision
if (ball_x + BALL_SIZE > SCREEN_W - PADDLE_W) {
if (ball_y + BALL_SIZE >= paddle_y) {
if (ball_y <= paddle_y + PADDLE_H) {
ball_x = SCREEN_W - PADDLE_W - BALL_SIZE
ball_vx = 0.0 - ball_vx
var hit_pos: f32 = (ball_y + BALL_SIZE * 0.5) - (paddle_y + PADDLE_H * 0.5)
ball_vy = ball_vy + hit_pos * 0.05
}
}
}
// Ball off the right edge → miss; respawn ball center, heading left.
if (ball_x > SCREEN_W) {
miss = miss + 1
print("miss total:{miss} cleared waves:{cleared}\n")
ball_x = SCREEN_W * 0.5; ball_y = SCREEN_H * 0.5
ball_vx = 0.0 - 4.0; ball_vy = 3.0
}
// All bricks down — reset wave.
if (bricks_left <= 0) {
cleared = cleared + 1
print("WAVE CLEARED total cleared:{cleared} misses:{miss}\n")
var i: i32 = 0
while (i < BRICK_COUNT) { bricks[i] = true; i = i + 1 }
bricks_left = BRICK_COUNT
ball_x = SCREEN_W * 0.5; ball_y = SCREEN_H * 0.5
ball_vx = 0.0 - 4.0; ball_vy = 3.0
}
// ---- Render ----
SDL_SetRenderDrawColor(renderer, 12, 14, 22, 255)
SDL_RenderClear(renderer)
// Bricks — alternating row tints, simple but readable
var rr: i32 = 0
while (rr < BRICK_ROWS) {
var cc: i32 = 0
while (cc < BRICK_COLS) {
var idx2: i32 = rr * BRICK_COLS + cc
if (bricks[idx2]) {
var rect: SDL_FRect
rect.x = BRICK_X0 + cc * (BRICK_W + BRICK_GAP)
rect.y = BRICK_Y0 + rr * (BRICK_H + BRICK_GAP)
rect.w = BRICK_W; rect.h = BRICK_H
if (rr % 2 == 0) { SDL_SetRenderDrawColor(renderer, 220, 80, 80, 255) }
else { SDL_SetRenderDrawColor(renderer, 220, 160, 60, 255) }
SDL_RenderFillRect(renderer, &rect)
}
cc = cc + 1
}
rr = rr + 1
}
// Paddle + ball in white
SDL_SetRenderDrawColor(renderer, 220, 220, 230, 255)
var rp: SDL_FRect; rp.x = SCREEN_W - PADDLE_W; rp.y = paddle_y; rp.w = PADDLE_W; rp.h = PADDLE_H
var ba: SDL_FRect; ba.x = ball_x; ba.y = ball_y; ba.w = BALL_SIZE; ba.h = BALL_SIZE
SDL_RenderFillRect(renderer, &rp)
SDL_RenderFillRect(renderer, &ba)
SDL_RenderPresent(renderer)
// No SDL_Delay — vsync (set above) paces the loop to monitor refresh.
}
if (renderer != null) { SDL_DestroyRenderer(renderer) }
if (window != null) { SDL_DestroyWindow(window) }
SDL_Quit()
}
Cimgui 53_cimgui.gx
// 01_hello.gx — minimum cimgui smoke test.
//
// Validates that the @cppfile build pipeline can pull in Dear ImGui
// (5 .cpp files of C++ source) + cimgui's C wrapper, link with the
// C++ runtime, and call into the resulting library from GX.
//
// Doesn't render anything — just creates an ImGui context, asks it
// for the version string, destroys the context. That's enough to
// prove the C++ → C → GX chain is working end-to-end.
//
// Build: gx examples/cimgui/01_hello.gx -I modules -o build/cimgui_hello.exe
// Run: ./build/cimgui_hello.exe
import cimgui
fn main() {
var ver: cstr = igGetVersion()
print("imgui version = {ver}\n")
var ctx: *void = igCreateContext(null)
if (ctx == null) {
print("CreateContext FAILED\n")
} else {
print("created ImGuiContext OK\n")
igDestroyContext(ctx)
print("smoke test PASSED\n")
}
}
F16 54_f16.gx
// Example 54: f16 — half-precision float (shader `half`)
//
// `f16` is a 16-bit IEEE-754 binary16 float. It mirrors the `half` type
// every shading language exposes, so it is the storage type you reach for
// when preparing data for the GPU backend: vertex attributes, weights,
// and large buffers where 2-byte elements halve bandwidth and footprint.
//
// On the C backend `f16` lowers to the C compiler's native `_Float16`
// when available (clang, gcc 12+); otherwise it falls back to `float`.
// On the LLVM/GPU backend it always lowers to true `half`.
// f16 globals — the literal is f32, narrowed into the annotated f16.
const HALF_ONE:f16 = 1.0
const HALF_PI:f16 = 3.140625 // nearest binary16 value to pi
// f16 fields pack tightly — a Vertex here is 6 bytes with native f16.
struct Vertex {
x: f16
y: f16
z: f16
}
// f16 arguments and return type. Arithmetic on f16 promotes to f32
// (matching C's usual conversions), and narrows back on the typed return.
fn midpoint:f16(a: f16, b: f16) {
var m: f16 = (a + b) * 0.5
return m
}
fn main() {
print("=== f16 / half precision ===\n")
var h: f16 = 2.5
print("h = {h}\n")
print("HALF_ONE = {HALF_ONE}\n")
print("HALF_PI = {HALF_PI}\n")
// f16 arithmetic — the result of a + b is f32, stored back into f16.
var a: f16 = 1.0
var b: f16 = 4.0
var mid: f16 = midpoint(a, b)
print("midpoint(1.0, 4.0) = {mid}\n")
// f16 in a struct, ready to hand to a GPU vertex buffer.
var v: Vertex
v.x = 0.5
v.y = -0.5
v.z = 1.0
print("Vertex = ({v.x}, {v.y}, {v.z})\n")
// f16 fixed-size array — a tightly packed weight table.
var weights: f16[4]
weights[0] = 0.1
weights[1] = 0.2
weights[2] = 0.3
weights[3] = 0.4
var sum: f16 = 0.0
for (i = 0:3) {
sum = sum + weights[i]
}
print("weight sum = {sum}\n")
}
Test Math test_math.gx
// test_math.ec - Verify vec and mat math modules compile and produce correct results
import math.scalar
import math.vec
import math.mat
fn main() {
// --- scalar tests ---
print("=== Scalar ===\n");
var s:f32 = sinf(PI() / 2.0);
print("sin(PI/2) = {s}\n");
var c:f32 = cosf(0.0);
print("cos(0) = {c}\n");
var sq:f32 = sqrtf(9.0);
print("sqrt(9) = {sq}\n");
var sw_pos:vec3 = {1,2,3};
sw_pos.xy += 10;
print("sw_pos after .xy += 10: {sw_pos}\n");
var cl:f32 = clamp_f(5.0, 0.0, 1.0);
print("clamp(5, 0, 1) = {cl}\n");
var lr:f32 = lerp_f(0.0, 10.0, 0.25);
print("lerp(0, 10, 0.25) = {lr}\n");
var rad:f32 = radians(180.0);
print("radians(180) = {rad}\n");
// --- vec3 tests ---
print("\n=== Vec3 ===\n");
var a:vec3;
a.x = 3.0;
a.y = 0.0;
a.z = 4.0;
var len:f32 = length_v3(a);
print("length(3,0,4) = {len}\n");
var n:vec3 = normalize_v3(a);
print("normalize(3,0,4) = {n}\n");
var b:vec3;
b.x = 1.0;
b.y = 2.0;
b.z = 3.0;
var d:f32 = distance_v3(a, b);
print("distance = {d}\n");
var mid:vec3 = lerp_v3(a, b, 0.5);
print("lerp(a, b, 0.5) = {mid}\n");
// --- vec2 tests ---
print("\n=== Vec2 ===\n");
var v2:vec2;
v2.x = 3.0;
v2.y = 4.0;
var len2:f32 = length_v2(v2);
print("length(3,4) = {len2}\n");
var n2:vec2 = normalize_v2(v2);
print("normalize(3,4) = {n2}\n");
// --- mat4 tests ---
print("\n=== Mat4 ===\n");
var id:mat4 = identity_mat4();
print("identity = {id}\n");
// Test translation
var pos:vec3;
pos.x = 10.0;
pos.y = 20.0;
pos.z = 30.0;
var tm:mat4 = translation(pos);
var tc:vec4 = tm.col[3];
print("translation(10,20,30) col3 = {tc}\n");
// Test mul_m4 (identity * translation = translation)
var result:mat4 = mul_m4(id, tm);
var rc:vec4 = result.col[3];
print("I * T col3 = {rc}\n");
// Test mul_m4v4
var v:vec4;
v.x = 1.0;
v.y = 0.0;
v.z = 0.0;
v.w = 1.0;
var tv:vec4 = mul_m4v4(tm, v);
print("T * (1,0,0,1) = {tv}\n");
// Test perspective
var proj:mat4 = perspective(radians(60.0), 1.333, 0.1, 100.0);
var p00:f32 = proj.col[0].x;
print("perspective[0][0] = {p00}\n");
// Test look_at
var eye:vec3;
eye.x = 0.0;
eye.y = 0.0;
eye.z = 5.0;
var target:vec3;
target.x = 0.0;
target.y = 0.0;
target.z = 0.0;
var up:vec3;
up.x = 0.0;
up.y = 1.0;
up.z = 0.0;
var view:mat4 = look_at(eye, target, up);
var vc:vec4 = view.col[3];
print("look_at col3 = {vc}\n");
print("\nAll math tests done.\n");
}
Graphics — Raylib
Ball Physics raylib/ball_physics.gx
// ball_physics.gx - Raylib ball physics sandbox
// Port of raylib [shapes] example - ball physics
// Build: gx ball_physics.gx -I modules -o ball_physics.exe
// Web: gx ball_physics.gx -I modules --target web -o ball_physics.html
//
// Controls:
// Left click (on ball) — grab and throw
// Right click — create new random ball at cursor
// Left Ctrl + Right hold — spawn many balls
// Middle click — shake all balls
// Mouse wheel — adjust gravity
import raylib
const MAX_BALLS = 200
const SCREEN_W = 800
const SCREEN_H = 600
struct Ball {
position: Vector2
speed: Vector2
prev_position: Vector2
radius: f32
friction: f32
elasticity: f32
color: Color
grabbed: bool
}
// Global ball pool (fixed size, no dynamic allocation)
var g_balls: Ball[200]
var g_ball_count: i32 = 0
var g_grabbed_idx: i32 = 0 - 1 // -1 = none grabbed
var g_press_offset_x: f32 = 0.0
var g_press_offset_y: f32 = 0.0
fn spawn_ball(px: f32, py: f32, vx: f32, vy: f32, radius: f32, r: u8, g: u8, b: u8) {
if (g_ball_count >= MAX_BALLS) { return }
var i: i32 = g_ball_count
g_balls[i].position.x = px
g_balls[i].position.y = py
g_balls[i].speed.x = vx
g_balls[i].speed.y = vy
g_balls[i].prev_position.x = px
g_balls[i].prev_position.y = py
g_balls[i].radius = radius
g_balls[i].friction = 0.985
g_balls[i].elasticity = 0.75
g_balls[i].color.r = r
g_balls[i].color.g = g
g_balls[i].color.b = b
g_balls[i].color.a = 255
g_balls[i].grabbed = false
g_ball_count = g_ball_count + 1
}
fn main() {
InitWindow(SCREEN_W, SCREEN_H, "raylib [shapes] example - ball physics")
SetTargetFPS(60)
// Initial ball: big blue one in the center
spawn_ball(SCREEN_W * 0.5, SCREEN_H * 0.5, 200.0, 200.0, 40.0, 0, 121, 241)
var gravity: f32 = 100.0
var sw: f32 = SCREEN_W * 1.0
var sh: f32 = SCREEN_H * 1.0
while (!WindowShouldClose()) {
var delta: f32 = GetFrameTime()
var mouse: Vector2 = GetMousePosition()
// ---- Grab a ball on left-click ----
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
var i: i32 = g_ball_count - 1
while (i >= 0) {
var dx: f32 = mouse.x - g_balls[i].position.x
var dy: f32 = mouse.y - g_balls[i].position.y
var dist_sq: f32 = dx * dx + dy * dy
var r_sq: f32 = g_balls[i].radius * g_balls[i].radius
if (dist_sq <= r_sq) {
g_balls[i].grabbed = true
g_grabbed_idx = i
g_press_offset_x = dx
g_press_offset_y = dy
i = 0 - 1 // break
}
i = i - 1
}
}
// ---- Release grabbed ball ----
if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) {
if (g_grabbed_idx >= 0) {
g_balls[g_grabbed_idx].grabbed = false
g_grabbed_idx = 0 - 1
}
}
// ---- Spawn new random ball on right-click ----
var spawn_new: bool = false
if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) { spawn_new = true }
if (IsKeyDown(KEY_LEFT_CONTROL)) {
if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) { spawn_new = true }
}
if (spawn_new) {
var vx: f32 = (f32)GetRandomValue(0 - 300, 300)
var vy: f32 = (f32)GetRandomValue(0 - 300, 300)
var rad: f32 = 20.0 + (f32)GetRandomValue(0, 30)
var cr: c_int = GetRandomValue(0, 255)
var cg: c_int = GetRandomValue(0, 255)
var cb: c_int = GetRandomValue(0, 255)
spawn_ball(mouse.x, mouse.y, vx, vy, rad, (u8)cr, (u8)cg, (u8)cb)
}
// ---- Shake all balls on middle-click ----
if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) {
for (i = 0 : g_ball_count - 1) {
if (!g_balls[i].grabbed) {
g_balls[i].speed.x = (f32)GetRandomValue(0 - 2000, 2000)
g_balls[i].speed.y = (f32)GetRandomValue(0 - 2000, 2000)
}
}
}
// ---- Adjust gravity with mouse wheel ----
gravity = gravity + GetMouseWheelMove() * 5.0
// ---- Update ball physics ----
for (i = 0 : g_ball_count - 1) {
if (!g_balls[i].grabbed) {
g_balls[i].position.x = g_balls[i].position.x + g_balls[i].speed.x * delta
g_balls[i].position.y = g_balls[i].position.y + g_balls[i].speed.y * delta
// Wall collision: right
if ((g_balls[i].position.x + g_balls[i].radius) >= sw) {
g_balls[i].position.x = sw - g_balls[i].radius
g_balls[i].speed.x = 0.0 - g_balls[i].speed.x * g_balls[i].elasticity
}
// Wall collision: left
if ((g_balls[i].position.x - g_balls[i].radius) <= 0.0) {
g_balls[i].position.x = g_balls[i].radius
g_balls[i].speed.x = 0.0 - g_balls[i].speed.x * g_balls[i].elasticity
}
// Wall collision: bottom
if ((g_balls[i].position.y + g_balls[i].radius) >= sh) {
g_balls[i].position.y = sh - g_balls[i].radius
g_balls[i].speed.y = 0.0 - g_balls[i].speed.y * g_balls[i].elasticity
}
// Wall collision: top
if ((g_balls[i].position.y - g_balls[i].radius) <= 0.0) {
g_balls[i].position.y = g_balls[i].radius
g_balls[i].speed.y = 0.0 - g_balls[i].speed.y * g_balls[i].elasticity
}
// Friction + gravity
g_balls[i].speed.x = g_balls[i].speed.x * g_balls[i].friction
g_balls[i].speed.y = g_balls[i].speed.y * g_balls[i].friction + gravity
} else {
// Ball is being dragged
var new_x: f32 = mouse.x - g_press_offset_x
var new_y: f32 = mouse.y - g_press_offset_y
g_balls[i].speed.x = (new_x - g_balls[i].prev_position.x) / delta
g_balls[i].speed.y = (new_y - g_balls[i].prev_position.y) / delta
g_balls[i].prev_position.x = new_x
g_balls[i].prev_position.y = new_y
g_balls[i].position.x = new_x
g_balls[i].position.y = new_y
}
}
// ---- Ball-to-ball collision (O(n^2) pairwise) ----
// Uses equal-mass elastic collision along the collision normal.
// Mass is approximated as radius (bigger balls push harder).
for (a = 0 : g_ball_count - 1) {
for (b = a + 1 : g_ball_count - 1) {
var dx: f32 = g_balls[b].position.x - g_balls[a].position.x
var dy: f32 = g_balls[b].position.y - g_balls[a].position.y
var min_dist: f32 = g_balls[a].radius + g_balls[b].radius
var dist_sq: f32 = dx * dx + dy * dy
var min_sq: f32 = min_dist * min_dist
if (dist_sq < min_sq) {
if (dist_sq > 0.0001) {
// Collision normal (unit vector from a to b)
// sqrt approximation: Newton's method would be more accurate,
// but for stability we use inverse sqrt via division.
var dist: f32 = dist_sq
// Fast approximate sqrt — just use raylib-provided one via math
// Simple approach: iterative refinement
var guess: f32 = min_dist
guess = (guess + dist / guess) * 0.5
guess = (guess + dist / guess) * 0.5
guess = (guess + dist / guess) * 0.5
dist = guess
var nx: f32 = dx / dist
var ny: f32 = dy / dist
// Separate overlapping balls (split the overlap proportionally)
var overlap: f32 = (min_dist - dist) * 0.5
if (!g_balls[a].grabbed) {
g_balls[a].position.x = g_balls[a].position.x - nx * overlap
g_balls[a].position.y = g_balls[a].position.y - ny * overlap
}
if (!g_balls[b].grabbed) {
g_balls[b].position.x = g_balls[b].position.x + nx * overlap
g_balls[b].position.y = g_balls[b].position.y + ny * overlap
}
// Relative velocity along the normal
var dvx: f32 = g_balls[b].speed.x - g_balls[a].speed.x
var dvy: f32 = g_balls[b].speed.y - g_balls[a].speed.y
var vel_along: f32 = dvx * nx + dvy * ny
// Only resolve if balls are moving toward each other
if (vel_along < 0.0) {
// Elastic collision with mass = radius
var ma: f32 = g_balls[a].radius
var mb: f32 = g_balls[b].radius
var e: f32 = 0.55 // elasticity (lower = more damping)
var j: f32 = (0.0 - (1.0 + e)) * vel_along / (1.0 / ma + 1.0 / mb)
var impulse_x: f32 = j * nx
var impulse_y: f32 = j * ny
if (!g_balls[a].grabbed) {
g_balls[a].speed.x = g_balls[a].speed.x - impulse_x / ma
g_balls[a].speed.y = g_balls[a].speed.y - impulse_y / ma
}
if (!g_balls[b].grabbed) {
g_balls[b].speed.x = g_balls[b].speed.x + impulse_x / mb
g_balls[b].speed.y = g_balls[b].speed.y + impulse_y / mb
}
}
}
}
}
}
// ---- Rest clamp: kill tiny velocities to let balls settle ----
// Without this, balls near the floor vibrate forever due to gravity
// fighting the elastic response.
for (k = 0 : g_ball_count - 1) {
if (!g_balls[k].grabbed) {
var vx: f32 = g_balls[k].speed.x
var vy: f32 = g_balls[k].speed.y
var speed_sq: f32 = vx * vx + vy * vy
// If ball is touching the floor and moving slowly, clamp to rest
var on_floor: bool = (g_balls[k].position.y + g_balls[k].radius) >= (sh - 1.0)
if (on_floor) {
if (speed_sq < 400.0) {
g_balls[k].speed.x = vx * 0.8 // horizontal damping
g_balls[k].speed.y = 0.0 // kill vertical bounce
}
}
// Also kill horizontal micro-movement to prevent sliding forever
if (vx < 1.5) {
if (vx > 0.0 - 1.5) {
g_balls[k].speed.x = 0.0
}
}
}
}
// ---- Draw ----
BeginDrawing()
ClearBackground(RAYWHITE)
for (i = 0 : g_ball_count - 1) {
DrawCircleV(g_balls[i].position, g_balls[i].radius, g_balls[i].color)
DrawCircleLinesV(g_balls[i].position, g_balls[i].radius, BLACK)
}
DrawText("Left-click + drag a ball to grab and throw", 10, 10, 14, DARKGRAY)
DrawText("Right-click to spawn (hold Left Ctrl for rapid-fire)", 10, 30, 14, DARKGRAY)
DrawText("Mouse wheel: gravity Middle-click: shake", 10, 50, 14, DARKGRAY)
var count = g_ball_count
var grav_i = (i32)gravity
var info1 = "BALL COUNT: {count}"
var info2 = "GRAVITY: {grav_i}"
DrawText(info1.cstr, 10, SCREEN_H - 60, 20, BLACK)
DrawText(info2.cstr, 10, SCREEN_H - 30, 20, BLACK)
EndDrawing()
}
CloseWindow()
}
Boids raylib/boids.gx
// boids.gx - Classic boids flocking simulation
// Based on Craig Reynolds' 1987 "Flocks, Herds, and Schools" paper.
// Three steering rules:
// 1. Separation — avoid crowding local flockmates
// 2. Alignment — steer toward average heading of neighbors
// 3. Cohesion — move toward average position of neighbors
//
// Build: gx boids.gx -I modules -o boids.exe
// Web: gx boids.gx -I modules --target web -o boids.html
//
// Controls:
// Left click — attract boids to cursor
// Right click — scatter boids from cursor
// Mouse wheel — adjust boid count (adds/removes 10)
import raylib
import math.scalar
const MAX_BOIDS = 300
const SCREEN_W = 800
const SCREEN_H = 600
struct Boid {
position: Vector2
velocity: Vector2
color: Color
sprint_timer: f32 // >0 = sprinting at 2x speed (escaping predator)
}
struct Predator {
position: Vector2
velocity: Vector2
attack_timer: f32 // >0 = chasing (increased speed)
scan_timer: f32 // countdown between prey-scans
active: bool
}
var g_boids: Boid[300]
var g_boid_count: i32 = 0
var g_predator: Predator
var g_spawn_timer: f32 = 0.0 // counts up until next predator spawn
// Flocking parameters
const VIEW_RADIUS: f32 = 60.0
const SEPARATION_RADIUS: f32 = 25.0
const MAX_SPEED: f32 = 90.0
const MAX_FORCE: f32 = 3.5
const SEP_WEIGHT: f32 = 1.8
const ALI_WEIGHT: f32 = 1.0
const COH_WEIGHT: f32 = 0.8
// Predator parameters
const PRED_SIZE: f32 = 21.0 // 3x the boid triangle size (7)
const PRED_CRUISE_SPEED: f32 = 45.0 // slow patrol speed
const PRED_ATTACK_SPEED: f32 = 150.0 // sprint when hunting
const PRED_DETECT_RADIUS: f32 = 140.0 // prey detection range
const PRED_FLEE_RADIUS: f32 = 120.0 // how close predator is before prey flee
const PRED_SPAWN_INTERVAL: f32 = 12.0 // seconds between spawns
// --- Simple LCG PRNG ---
var g_rng: i32 = 12345
fn rand_next:i32() {
g_rng = g_rng * 1103515245 + 12345
var v: i32 = (g_rng / 65536) & 32767
return v
}
fn rand_range_f:f32(lo: f32, hi: f32) {
var v: f32 = (rand_next() % 10000) / 10000.0
return lo + v * (hi - lo)
}
fn rand_range_i:i32(lo: i32, hi: i32) {
var range: i32 = hi - lo + 1
return lo + (rand_next() % range)
}
fn spawn_boid() {
if (g_boid_count >= MAX_BOIDS) { return }
var i: i32 = g_boid_count
g_boids[i].position.x = rand_range_f(0.0, SCREEN_W * 1.0)
g_boids[i].position.y = rand_range_f(0.0, SCREEN_H * 1.0)
g_boids[i].velocity.x = rand_range_f(0.0 - MAX_SPEED, MAX_SPEED)
g_boids[i].velocity.y = rand_range_f(0.0 - MAX_SPEED, MAX_SPEED)
// Color gradient based on position — cool blues/purples
var hue: f32 = rand_range_f(140.0, 255.0)
g_boids[i].color.r = (u8)(40 + rand_range_i(0, 60))
g_boids[i].color.g = (u8)(120 + rand_range_i(0, 80))
g_boids[i].color.b = (u8)hue
g_boids[i].color.a = 255
g_boid_count = g_boid_count + 1
}
// Clamp a velocity magnitude in-place (via ball index since GX doesn't deref pointers easily)
fn limit_boid_speed(i: i32, max: f32) {
var vx: f32 = g_boids[i].velocity.x
var vy: f32 = g_boids[i].velocity.y
var mag_sq: f32 = vx * vx + vy * vy
var max_sq: f32 = max * max
if (mag_sq > max_sq) {
var mag: f32 = sqrtf(mag_sq)
g_boids[i].velocity.x = (vx / mag) * max
g_boids[i].velocity.y = (vy / mag) * max
}
}
// Spawn a predator from a random edge, heading toward screen center
fn spawn_predator() {
var edge: i32 = rand_next() % 4
var cx: f32 = SCREEN_W * 0.5
var cy: f32 = SCREEN_H * 0.5
if (edge == 0) {
// Top edge
g_predator.position.x = rand_range_f(0.0, SCREEN_W * 1.0)
g_predator.position.y = 0.0 - PRED_SIZE
}
if (edge == 1) {
// Right edge
g_predator.position.x = SCREEN_W + PRED_SIZE
g_predator.position.y = rand_range_f(0.0, SCREEN_H * 1.0)
}
if (edge == 2) {
// Bottom edge
g_predator.position.x = rand_range_f(0.0, SCREEN_W * 1.0)
g_predator.position.y = SCREEN_H + PRED_SIZE
}
if (edge == 3) {
// Left edge
g_predator.position.x = 0.0 - PRED_SIZE
g_predator.position.y = rand_range_f(0.0, SCREEN_H * 1.0)
}
// Initial heading: toward screen center
var dx: f32 = cx - g_predator.position.x
var dy: f32 = cy - g_predator.position.y
var mag: f32 = sqrtf(dx * dx + dy * dy)
if (mag < 0.001) { mag = 0.001 }
g_predator.velocity.x = (dx / mag) * PRED_CRUISE_SPEED
g_predator.velocity.y = (dy / mag) * PRED_CRUISE_SPEED
g_predator.attack_timer = 0.0
g_predator.scan_timer = 2.0
g_predator.active = true
}
fn main() {
InitWindow(SCREEN_W, SCREEN_H, "GX + Raylib - Classic Boids")
SetTargetFPS(60)
// Spawn initial flock
for (i = 0 : 99) {
spawn_boid()
}
// Predator starts inactive; will spawn after interval
g_predator.active = false
var bg: Color
bg.r = 12
bg.g = 18
bg.b = 32
bg.a = 255
while (!WindowShouldClose()) {
var delta: f32 = GetFrameTime()
var mouse: Vector2 = GetMousePosition()
var mouse_attract: bool = IsMouseButtonDown(MOUSE_BUTTON_LEFT)
var mouse_repel: bool = IsMouseButtonDown(MOUSE_BUTTON_RIGHT)
// Adjust count with mouse wheel
var wheel: f32 = GetMouseWheelMove()
if (wheel > 0.0) {
for (k = 0 : 9) { spawn_boid() }
}
if (wheel < 0.0) {
if (g_boid_count >= 10) { g_boid_count = g_boid_count - 10 }
}
// ---- Predator spawn timer ----
if (!g_predator.active) {
g_spawn_timer = g_spawn_timer + delta
if (g_spawn_timer >= PRED_SPAWN_INTERVAL) {
g_spawn_timer = 0.0
spawn_predator()
}
}
// ---- Predator update ----
if (g_predator.active) {
// Periodic target scan — enter attack mode if prey is close enough
g_predator.scan_timer = g_predator.scan_timer - delta
if (g_predator.scan_timer <= 0.0) {
g_predator.scan_timer = 3.0 // scan every 3 seconds
// Find the nearest boid within detection radius
var best_idx: i32 = 0 - 1
var best_dist_sq: f32 = PRED_DETECT_RADIUS * PRED_DETECT_RADIUS
for (i = 0 : g_boid_count - 1) {
var bdx: f32 = g_boids[i].position.x - g_predator.position.x
var bdy: f32 = g_boids[i].position.y - g_predator.position.y
var bsq: f32 = bdx * bdx + bdy * bdy
if (bsq < best_dist_sq) {
best_dist_sq = bsq
best_idx = i
}
}
if (best_idx >= 0) {
// Attack mode for 2.5 seconds — steer toward target
g_predator.attack_timer = 2.5
var tdx: f32 = g_boids[best_idx].position.x - g_predator.position.x
var tdy: f32 = g_boids[best_idx].position.y - g_predator.position.y
var tmag: f32 = sqrtf(tdx * tdx + tdy * tdy)
if (tmag < 0.001) { tmag = 0.001 }
g_predator.velocity.x = (tdx / tmag) * PRED_ATTACK_SPEED
g_predator.velocity.y = (tdy / tmag) * PRED_ATTACK_SPEED
}
}
// Decay attack timer and slow back to cruise when it expires
if (g_predator.attack_timer > 0.0) {
g_predator.attack_timer = g_predator.attack_timer - delta
if (g_predator.attack_timer <= 0.0) {
// Return to cruise speed
var cvx: f32 = g_predator.velocity.x
var cvy: f32 = g_predator.velocity.y
var cmag: f32 = sqrtf(cvx * cvx + cvy * cvy)
if (cmag > 0.001) {
g_predator.velocity.x = (cvx / cmag) * PRED_CRUISE_SPEED
g_predator.velocity.y = (cvy / cmag) * PRED_CRUISE_SPEED
}
}
}
// Move predator
g_predator.position.x = g_predator.position.x + g_predator.velocity.x * delta
g_predator.position.y = g_predator.position.y + g_predator.velocity.y * delta
// Kill predator when it exits the screen
var off: f32 = PRED_SIZE * 2.0
if (g_predator.position.x < (0.0 - off)) { g_predator.active = false }
if (g_predator.position.x > (SCREEN_W + off)) { g_predator.active = false }
if (g_predator.position.y < (0.0 - off)) { g_predator.active = false }
if (g_predator.position.y > (SCREEN_H + off)) { g_predator.active = false }
}
// ---- Boid flocking update ----
for (i = 0 : g_boid_count - 1) {
// Accumulators for the three rules
var sep_x: f32 = 0.0
var sep_y: f32 = 0.0
var ali_x: f32 = 0.0
var ali_y: f32 = 0.0
var coh_x: f32 = 0.0
var coh_y: f32 = 0.0
var neighbors: i32 = 0
var sep_count: i32 = 0
// Scan all other boids
for (j = 0 : g_boid_count - 1) {
if (i != j) {
var dx: f32 = g_boids[j].position.x - g_boids[i].position.x
var dy: f32 = g_boids[j].position.y - g_boids[i].position.y
var dist_sq: f32 = dx * dx + dy * dy
if (dist_sq < VIEW_RADIUS * VIEW_RADIUS) {
if (dist_sq > 0.01) {
// Alignment & cohesion: all boids in view radius
ali_x = ali_x + g_boids[j].velocity.x
ali_y = ali_y + g_boids[j].velocity.y
coh_x = coh_x + g_boids[j].position.x
coh_y = coh_y + g_boids[j].position.y
neighbors = neighbors + 1
// Separation: only very close boids
if (dist_sq < SEPARATION_RADIUS * SEPARATION_RADIUS) {
// Push away, weighted by inverse distance
var dist: f32 = sqrtf(dist_sq)
sep_x = sep_x - (dx / dist) / dist
sep_y = sep_y - (dy / dist) / dist
sep_count = sep_count + 1
}
}
}
}
}
// Compute steering forces
var steer_x: f32 = 0.0
var steer_y: f32 = 0.0
if (sep_count > 0) {
steer_x = steer_x + sep_x * SEP_WEIGHT * 200.0
steer_y = steer_y + sep_y * SEP_WEIGHT * 200.0
}
if (neighbors > 0) {
var inv_n: f32 = 1.0 / (neighbors * 1.0)
// Alignment: steer toward average velocity of neighbors
var avg_vx: f32 = ali_x * inv_n
var avg_vy: f32 = ali_y * inv_n
steer_x = steer_x + (avg_vx - g_boids[i].velocity.x) * ALI_WEIGHT * 0.05
steer_y = steer_y + (avg_vy - g_boids[i].velocity.y) * ALI_WEIGHT * 0.05
// Cohesion: steer toward average position of neighbors
var center_x: f32 = coh_x * inv_n
var center_y: f32 = coh_y * inv_n
steer_x = steer_x + (center_x - g_boids[i].position.x) * COH_WEIGHT * 0.02
steer_y = steer_y + (center_y - g_boids[i].position.y) * COH_WEIGHT * 0.02
}
// Mouse interaction
if (mouse_attract) {
var mdx: f32 = mouse.x - g_boids[i].position.x
var mdy: f32 = mouse.y - g_boids[i].position.y
steer_x = steer_x + mdx * 0.8
steer_y = steer_y + mdy * 0.8
}
if (mouse_repel) {
var mdx: f32 = g_boids[i].position.x - mouse.x
var mdy: f32 = g_boids[i].position.y - mouse.y
var mdist_sq: f32 = mdx * mdx + mdy * mdy
if (mdist_sq < 40000.0) {
steer_x = steer_x + mdx * 3.0
steer_y = steer_y + mdy * 3.0
}
}
// Predator flee: if predator is active and close, steer away hard + sprint
if (g_predator.active) {
var pdx: f32 = g_boids[i].position.x - g_predator.position.x
var pdy: f32 = g_boids[i].position.y - g_predator.position.y
var pdist_sq: f32 = pdx * pdx + pdy * pdy
var flee_sq: f32 = PRED_FLEE_RADIUS * PRED_FLEE_RADIUS
if (pdist_sq < flee_sq) {
// Stronger flee the closer the predator is
var pdist: f32 = sqrtf(pdist_sq)
if (pdist < 0.001) { pdist = 0.001 }
var flee_force: f32 = (PRED_FLEE_RADIUS - pdist) * 4.0
steer_x = steer_x + (pdx / pdist) * flee_force
steer_y = steer_y + (pdy / pdist) * flee_force
// Activate sprint for 1.2 seconds
g_boids[i].sprint_timer = 1.2
}
}
// Decay sprint timer
if (g_boids[i].sprint_timer > 0.0) {
g_boids[i].sprint_timer = g_boids[i].sprint_timer - delta
}
// Apply steering force
g_boids[i].velocity.x = g_boids[i].velocity.x + steer_x * delta
g_boids[i].velocity.y = g_boids[i].velocity.y + steer_y * delta
// Limit speed (2x while sprinting to escape predator)
var speed_cap: f32 = MAX_SPEED
if (g_boids[i].sprint_timer > 0.0) {
speed_cap = MAX_SPEED * 2.0
}
limit_boid_speed(i, speed_cap)
// Update position
g_boids[i].position.x = g_boids[i].position.x + g_boids[i].velocity.x * delta
g_boids[i].position.y = g_boids[i].position.y + g_boids[i].velocity.y * delta
// Wrap around screen edges
if (g_boids[i].position.x < 0.0) { g_boids[i].position.x = g_boids[i].position.x + SCREEN_W }
if (g_boids[i].position.x > SCREEN_W) { g_boids[i].position.x = g_boids[i].position.x - SCREEN_W }
if (g_boids[i].position.y < 0.0) { g_boids[i].position.y = g_boids[i].position.y + SCREEN_H }
if (g_boids[i].position.y > SCREEN_H) { g_boids[i].position.y = g_boids[i].position.y - SCREEN_H }
}
// ---- Draw ----
BeginDrawing()
ClearBackground(bg)
// Draw each boid as a triangle pointing in its velocity direction
for (i = 0 : g_boid_count - 1) {
var vx: f32 = g_boids[i].velocity.x
var vy: f32 = g_boids[i].velocity.y
var vmag: f32 = sqrtf(vx * vx + vy * vy)
if (vmag < 0.001) { vmag = 0.001 }
// Unit direction
var dx: f32 = vx / vmag
var dy: f32 = vy / vmag
// Perpendicular
var px: f32 = 0.0 - dy
var py: f32 = dx
var cx: f32 = g_boids[i].position.x
var cy: f32 = g_boids[i].position.y
var size: f32 = 7.0
// Triangle: tip forward, two base points behind
// Winding MUST be counter-clockwise for raylib (otherwise back-face culled)
var p1: Vector2
p1.x = cx + dx * size
p1.y = cy + dy * size
// Back-right (CCW from tip in screen space where y goes down)
var p2: Vector2
p2.x = cx - dx * size - px * size * 0.6
p2.y = cy - dy * size - py * size * 0.6
// Back-left
var p3: Vector2
p3.x = cx - dx * size + px * size * 0.6
p3.y = cy - dy * size + py * size * 0.6
DrawTriangle(p1, p2, p3, g_boids[i].color)
}
// Draw predator (big red triangle)
if (g_predator.active) {
var pvx: f32 = g_predator.velocity.x
var pvy: f32 = g_predator.velocity.y
var pvmag: f32 = sqrtf(pvx * pvx + pvy * pvy)
if (pvmag < 0.001) { pvmag = 0.001 }
var pdx: f32 = pvx / pvmag
var pdy: f32 = pvy / pvmag
var ppx: f32 = 0.0 - pdy
var ppy: f32 = pdx
var pcx: f32 = g_predator.position.x
var pcy: f32 = g_predator.position.y
var psize: f32 = PRED_SIZE
var pred_color: Color
if (g_predator.attack_timer > 0.0) {
// Brighter red while attacking
pred_color.r = 255
pred_color.g = 40
pred_color.b = 40
} else {
pred_color.r = 200
pred_color.g = 30
pred_color.b = 30
}
pred_color.a = 255
var pp1: Vector2
pp1.x = pcx + pdx * psize
pp1.y = pcy + pdy * psize
var pp2: Vector2
pp2.x = pcx - pdx * psize - ppx * psize * 0.6
pp2.y = pcy - pdy * psize - ppy * psize * 0.6
var pp3: Vector2
pp3.x = pcx - pdx * psize + ppx * psize * 0.6
pp3.y = pcy - pdy * psize + ppy * psize * 0.6
DrawTriangle(pp1, pp2, pp3, pred_color)
}
DrawText("Left-click: attract Right-click: scatter Wheel: +/- 10 boids", 10, 10, 14, RAYWHITE)
var count = g_boid_count
var info = "BOIDS: {count}"
DrawText(info.cstr, 10, SCREEN_H - 30, 20, RAYWHITE)
DrawFPS(SCREEN_W - 90, 10)
EndDrawing()
}
CloseWindow()
}
Bullet Hell raylib/bullet_hell.gx
// bullet_hell.gx - Raylib bullet hell shmup demo
// Port of raylib [shapes] example - bullet hell
// Build: gx bullet_hell.gx -I modules -o bullet_hell.exe
// Web: gx bullet_hell.gx -I modules --target web -o bullet_hell.html
//
// Controls:
// Right/Left or A/D — Change number of bullet rows
// Up/Down or W/S — Change bullet speed
// Z / X — Change spawn cooldown
// Space (hold) — Change angle increment per frame
// Enter — Switch draw method (texture vs DrawCircle)
// C — Clear bullets
import raylib
import math.scalar
const MAX_BULLETS = 100000
struct Bullet {
position: Vector2
acceleration: Vector2
disabled: bool
color: Color
}
// Global bullet pool — fixed size array (no dynamic allocation)
var g_bullets: Bullet[100000]
fn main() {
var screen_width: i32 = 800
var screen_height: i32 = 600
InitWindow(screen_width, screen_height, "raylib [shapes] example - bullet hell")
var bullet_count: i32 = 0
var bullet_disabled_count: i32 = 0
var bullet_radius: i32 = 10
var bullet_speed: f32 = 3.0
var bullet_rows: i32 = 6
// Spawner state
var base_direction: f32 = 0.0
var angle_increment: i32 = 5
var spawn_cooldown: f32 = 2.0
var spawn_cooldown_timer: f32 = 2.0
// Magic circle animation
var magic_circle_rotation: f32 = 0.0
// Pre-rendered bullet texture for performance
var bullet_texture = LoadRenderTexture(24, 24)
BeginTextureMode(bullet_texture)
DrawCircle(12, 12, 10.0, WHITE)
DrawCircleLines(12, 12, 10.0, BLACK)
EndTextureMode()
var draw_perf_mode: bool = true
// Colors array (can't easily use const arrays in GX, use locals)
var color_a = RED
var color_b = BLUE
SetTargetFPS(60)
while (!WindowShouldClose()) {
// ----- Update -----
// Reset ring buffer when full
if (bullet_count >= MAX_BULLETS) {
bullet_count = 0
bullet_disabled_count = 0
}
spawn_cooldown_timer = spawn_cooldown_timer - 1.0
if (spawn_cooldown_timer < 0.0) {
spawn_cooldown_timer = spawn_cooldown
// Spawn one bullet per row, distributed around 360 degrees
var degrees_per_row: f32 = 360.0 / (bullet_rows * 1.0)
var row: i32 = 0
while (row < bullet_rows) {
if (bullet_count < MAX_BULLETS) {
var pos: Vector2
pos.x = screen_width * 0.5
pos.y = screen_height * 0.5
g_bullets[bullet_count].position = pos
g_bullets[bullet_count].disabled = false
if ((row % 2) == 0) {
g_bullets[bullet_count].color = color_a
} else {
g_bullets[bullet_count].color = color_b
}
var bullet_direction: f32 = base_direction + (degrees_per_row * (row * 1.0))
var rad: f32 = radians(bullet_direction)
var acc: Vector2
acc.x = bullet_speed * cosf(rad)
acc.y = bullet_speed * sinf(rad)
g_bullets[bullet_count].acceleration = acc
bullet_count = bullet_count + 1
}
row = row + 1
}
base_direction = base_direction + (angle_increment * 1.0)
}
// Update bullet positions
var i: i32 = 0
while (i < bullet_count) {
if (!g_bullets[i].disabled) {
g_bullets[i].position.x = g_bullets[i].position.x + g_bullets[i].acceleration.x
g_bullets[i].position.y = g_bullets[i].position.y + g_bullets[i].acceleration.y
var off: f32 = bullet_radius * 2.0
if (g_bullets[i].position.x < (0.0 - off)) {
g_bullets[i].disabled = true
bullet_disabled_count = bullet_disabled_count + 1
}
if (g_bullets[i].position.x > (screen_width + off)) {
if (!g_bullets[i].disabled) {
g_bullets[i].disabled = true
bullet_disabled_count = bullet_disabled_count + 1
}
}
if (g_bullets[i].position.y < (0.0 - off)) {
if (!g_bullets[i].disabled) {
g_bullets[i].disabled = true
bullet_disabled_count = bullet_disabled_count + 1
}
}
if (g_bullets[i].position.y > (screen_height + off)) {
if (!g_bullets[i].disabled) {
g_bullets[i].disabled = true
bullet_disabled_count = bullet_disabled_count + 1
}
}
}
i = i + 1
}
// ----- Input -----
if (IsKeyPressed(KEY_RIGHT)) {
if (bullet_rows < 359) { bullet_rows = bullet_rows + 1 }
}
if (IsKeyPressed(KEY_D)) {
if (bullet_rows < 359) { bullet_rows = bullet_rows + 1 }
}
if (IsKeyPressed(KEY_LEFT)) {
if (bullet_rows > 1) { bullet_rows = bullet_rows - 1 }
}
if (IsKeyPressed(KEY_A)) {
if (bullet_rows > 1) { bullet_rows = bullet_rows - 1 }
}
if (IsKeyPressed(KEY_UP)) { bullet_speed = bullet_speed + 0.25 }
if (IsKeyPressed(KEY_W)) { bullet_speed = bullet_speed + 0.25 }
if (IsKeyPressed(KEY_DOWN)) {
if (bullet_speed > 0.5) { bullet_speed = bullet_speed - 0.25 }
}
if (IsKeyPressed(KEY_S)) {
if (bullet_speed > 0.5) { bullet_speed = bullet_speed - 0.25 }
}
if (IsKeyPressed(KEY_Z)) {
if (spawn_cooldown > 1.0) { spawn_cooldown = spawn_cooldown - 1.0 }
}
if (IsKeyPressed(KEY_X)) { spawn_cooldown = spawn_cooldown + 1.0 }
if (IsKeyPressed(KEY_ENTER)) { draw_perf_mode = !draw_perf_mode }
if (IsKeyDown(KEY_SPACE)) {
angle_increment = angle_increment + 1
angle_increment = angle_increment % 360
}
if (IsKeyPressed(KEY_C)) {
bullet_count = 0
bullet_disabled_count = 0
}
// ----- Draw -----
BeginDrawing()
ClearBackground(RAYWHITE)
// Magic circle
magic_circle_rotation = magic_circle_rotation + 1.0
var rect: Rectangle
rect.x = screen_width * 0.5
rect.y = screen_height * 0.5
rect.width = 120.0
rect.height = 120.0
var origin: Vector2
origin.x = 60.0
origin.y = 60.0
DrawRectanglePro(rect, origin, magic_circle_rotation, PURPLE)
DrawRectanglePro(rect, origin, magic_circle_rotation + 45.0, PURPLE)
DrawCircleLines(screen_width / 2, screen_height / 2, 70.0, BLACK)
DrawCircleLines(screen_width / 2, screen_height / 2, 50.0, BLACK)
DrawCircleLines(screen_width / 2, screen_height / 2, 30.0, BLACK)
// Draw bullets
if (draw_perf_mode) {
var tw: f32 = 12.0
var th: f32 = 12.0
var j: i32 = 0
while (j < bullet_count) {
if (!g_bullets[j].disabled) {
var fx: f32 = g_bullets[j].position.x - tw
var fy: f32 = g_bullets[j].position.y - th
DrawTextureV(bullet_texture.texture, vec2{fx, fy}, g_bullets[j].color)
}
j = j + 1
}
} else {
var j: i32 = 0
while (j < bullet_count) {
if (!g_bullets[j].disabled) {
DrawCircleV(g_bullets[j].position, bullet_radius * 1.0, g_bullets[j].color)
DrawCircleLinesV(g_bullets[j].position, bullet_radius * 1.0, BLACK)
}
j = j + 1
}
}
// UI panel background
var panel_bg: Color
panel_bg.r = 0
panel_bg.g = 0
panel_bg.b = 0
panel_bg.a = 200
DrawRectangle(10, 10, 280, 150, panel_bg)
DrawText("Controls:", 20, 20, 10, LIGHTGRAY)
DrawText("- Right/Left or A/D: Change rows number", 40, 40, 10, LIGHTGRAY)
DrawText("- Up/Down or W/S: Change bullet speed", 40, 60, 10, LIGHTGRAY)
DrawText("- Z or X: Change spawn cooldown", 40, 80, 10, LIGHTGRAY)
DrawText("- Space (Hold): Change the angle increment", 40, 100, 10, LIGHTGRAY)
DrawText("- Enter: Switch draw method (Performance)", 40, 120, 10, LIGHTGRAY)
DrawText("- C: Clear bullets", 40, 140, 10, LIGHTGRAY)
DrawRectangle(610, 10, 170, 30, panel_bg)
if (draw_perf_mode) {
DrawText("Draw method: DrawTexture(*)", 620, 20, 10, GREEN)
} else {
DrawText("Draw method: DrawCircle(*)", 620, 20, 10, RED)
}
DrawRectangle(135, 560, 530, 30, panel_bg)
DrawText("[ FPS stats ]", 155, 570, 10, GREEN)
EndDrawing()
}
UnloadRenderTexture(bullet_texture)
CloseWindow()
}
Hello raylib/hello.gx
// hello.gx - GX + Raylib with built-in math types
// Build: gx hello.gx -I modules -o hello.exe
//
// Vector2/3/4 are aliases to GX's vec2/vec3/vec4 — you get
// operator overloading, swizzle ops, and GX math for free.
// Colors are uppercase constants: RED, BLUE, WHITE, etc.
import raylib
fn main() {
InitWindow(800, 450, "GX + Raylib")
SetTargetFPS(60)
while (!WindowShouldClose()) {
if (IsKeyPressed(KEY_F)) {
ToggleFullscreen()
}
BeginDrawing()
ClearBackground(RAYWHITE)
DrawText("Hello from GX + Raylib!", 240, 40, 30, DARKGRAY)
// GX vec2 literals work directly as Vector2
DrawRectangle(50, 120, 200, 100, RED)
DrawCircleV(vec2{450.0, 170.0}, 60.0, BLUE)
// vec2 math: operator overloading
var a = vec2{600.0, 120.0}
var b = vec2{550.0, 220.0}
var c = vec2{650.0, 220.0}
DrawTriangle(a, b, c, GREEN)
// vec2 arithmetic works naturally
var center = vec2{400.0, 350.0}
var offset = vec2{50.0, 0.0}
DrawCircleV(center - offset, 20.0, ORANGE)
DrawCircleV(center, 20.0, PURPLE)
DrawCircleV(center + offset, 20.0, SKYBLUE)
DrawFPS(10, 10)
DrawText("Press F for fullscreen", 10, 420, 16, GRAY)
DrawCircleV(center, 20.0, PURPLE)
EndDrawing()
}
CloseWindow()
}
import raylib.colors Image Generation raylib/image_generation.gx
// image_generation.gx - Raylib procedural texture generation
// Port of raylib [textures] example — image generation
// Build: gx image_generation.gx -I modules -o image_generation.exe
// Web: gx image_generation.gx -I modules --target web -o image_generation.html
//
// Demonstrates raylib's procedural image generators:
// - Linear gradients (vertical, horizontal, diagonal)
// - Radial / Square gradients
// - Checkerboard
// - White noise / Perlin noise / Cellular noise
//
// Controls: Left click or RIGHT arrow to cycle textures
import raylib
const NUM_TEXTURES = 9
fn main() {
var screen_width: c_int = 800
var screen_height: c_int = 600
InitWindow(screen_width, screen_height, "raylib [textures] example - image generation")
// Generate 9 procedural images
var vertical_gradient = GenImageGradientLinear(screen_width, screen_height, 0, RED, BLUE)
var horizontal_gradient = GenImageGradientLinear(screen_width, screen_height, 90, RED, BLUE)
var diagonal_gradient = GenImageGradientLinear(screen_width, screen_height, 45, RED, BLUE)
var radial_gradient = GenImageGradientRadial(screen_width, screen_height, 0.0, WHITE, BLACK)
var square_gradient = GenImageGradientSquare(screen_width, screen_height, 0.0, WHITE, BLACK)
var checked = GenImageChecked(screen_width, screen_height, 32, 32, RED, BLUE)
var white_noise = GenImageWhiteNoise(screen_width, screen_height, 0.5)
var perlin_noise = GenImagePerlinNoise(screen_width, screen_height, 50, 50, 4.0)
var cellular = GenImageCellular(screen_width, screen_height, 32)
// Upload all images to GPU as textures
var textures: Texture[9]
textures[0] = LoadTextureFromImage(vertical_gradient)
textures[1] = LoadTextureFromImage(horizontal_gradient)
textures[2] = LoadTextureFromImage(diagonal_gradient)
textures[3] = LoadTextureFromImage(radial_gradient)
textures[4] = LoadTextureFromImage(square_gradient)
textures[5] = LoadTextureFromImage(checked)
textures[6] = LoadTextureFromImage(white_noise)
textures[7] = LoadTextureFromImage(perlin_noise)
textures[8] = LoadTextureFromImage(cellular)
// Free CPU image data (textures live on GPU now)
UnloadImage(vertical_gradient)
UnloadImage(horizontal_gradient)
UnloadImage(diagonal_gradient)
UnloadImage(radial_gradient)
UnloadImage(square_gradient)
UnloadImage(checked)
UnloadImage(white_noise)
UnloadImage(perlin_noise)
UnloadImage(cellular)
var current_texture: i32 = 0
var auto_timer: i32 = 0
SetTargetFPS(60)
while (!WindowShouldClose()) {
// Auto-cycle every 120 frames (~2 seconds at 60 FPS)
auto_timer = auto_timer + 1
if (auto_timer >= 120) {
current_texture = (current_texture + 1) % NUM_TEXTURES
auto_timer = 0
}
// Manual cycle on mouse click or right arrow (resets timer)
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
current_texture = (current_texture + 1) % NUM_TEXTURES
auto_timer = 0
}
if (IsKeyPressed(KEY_RIGHT)) {
current_texture = (current_texture + 1) % NUM_TEXTURES
auto_timer = 0
}
BeginDrawing()
ClearBackground(RAYWHITE)
DrawTexture(textures[current_texture], 0, 0, WHITE)
// Info panel with faded background
DrawRectangle(30, 550, 325, 30, Fade(SKYBLUE, 0.5))
DrawRectangleLines(30, 550, 325, 30, Fade(WHITE, 0.5))
DrawText("MOUSE LEFT BUTTON to CYCLE PROCEDURAL TEXTURES", 40, 560, 10, WHITE)
// Texture label
if (current_texture == 0) { DrawText("VERTICAL GRADIENT", 560, 10, 20, RAYWHITE) }
if (current_texture == 1) { DrawText("HORIZONTAL GRADIENT", 540, 10, 20, RAYWHITE) }
if (current_texture == 2) { DrawText("DIAGONAL GRADIENT", 540, 10, 20, RAYWHITE) }
if (current_texture == 3) { DrawText("RADIAL GRADIENT", 580, 10, 20, LIGHTGRAY) }
if (current_texture == 4) { DrawText("SQUARE GRADIENT", 580, 10, 20, LIGHTGRAY) }
if (current_texture == 5) { DrawText("CHECKED", 680, 10, 20, RAYWHITE) }
if (current_texture == 6) { DrawText("WHITE NOISE", 640, 10, 20, RED) }
if (current_texture == 7) { DrawText("PERLIN NOISE", 640, 10, 20, RED) }
if (current_texture == 8) { DrawText("CELLULAR", 670, 10, 20, RAYWHITE) }
EndDrawing()
}
// Cleanup — unload GPU textures
for (i = 0 : 8) {
UnloadTexture(textures[i])
}
CloseWindow()
}
Logo Gx Anim raylib/logo_gx_anim.gx
// logo_gx_anim.gx - Animated GX logo loaded from LogoLow.png
// Each pixel in the low-res logo becomes a quad that falls from above.
// Preserves the exact shape of the brand logo.
//
// Build: gx logo_gx_anim.gx -I modules -o logo_gx_anim.exe
// Web: gx logo_gx_anim.gx -I modules --target web -o logo_gx_anim.html --preload-file website/resources/LogoLow.png
import raylib
const BLOCK_SIZE = 10 // pixel size of each quad on screen
const MAX_QUADS = 4000
struct Quad {
target_x: f32
target_y: f32
current_y: f32
velocity: f32
delay: i32 // frames to wait before starting to fall
landed: bool
r: u8
g: u8
b: u8
}
// Simple LCG for randomized delays
var g_rng: i32 = 12345
fn rand_range:i32(max: i32) {
g_rng = g_rng * 1103515245 + 12345
var v: i32 = (g_rng / 65536) & 32767
return v % max
}
var g_quads: Quad[4000]
var g_quad_count: i32 = 0
// Load logo image, sample every pixel, spawn a quad for each non-transparent one
fn build_logo_from_image(img: Image, center_x: i32, center_y: i32) {
g_quad_count = 0
var img_w: i32 = (i32)img.width
var img_h: i32 = (i32)img.height
var total_w: i32 = img_w * BLOCK_SIZE
var total_h: i32 = img_h * BLOCK_SIZE
var start_x: i32 = center_x - total_w / 2
var start_y: i32 = center_y - total_h / 2
// Old-school chroma key: top-left pixel is the transparent color
var bg_color: Color = GetImageColor(img, 0, 0)
for (y = 0 : img_h - 1) {
for (x = 0 : img_w - 1) {
var c: Color = GetImageColor(img, x, y)
// Skip pixels matching the top-left (background) color
var is_bg: bool = false
if (c.r == bg_color.r) {
if (c.g == bg_color.g) {
if (c.b == bg_color.b) {
is_bg = true
}
}
}
if (!is_bg) {
if (g_quad_count < MAX_QUADS) {
g_quads[g_quad_count].target_x = (start_x + x * BLOCK_SIZE) * 1.0
g_quads[g_quad_count].target_y = (start_y + y * BLOCK_SIZE) * 1.0
// Start well above the screen
g_quads[g_quad_count].current_y = 0.0 - 200.0 - (rand_range(300) * 1.0)
g_quads[g_quad_count].velocity = 0.0
// Random delay (0-90 frames) so quads fall in scattered order
g_quads[g_quad_count].delay = rand_range(90)
g_quads[g_quad_count].landed = false
g_quads[g_quad_count].r = c.r
g_quads[g_quad_count].g = c.g
g_quads[g_quad_count].b = c.b
g_quad_count = g_quad_count + 1
}
}
}
}
}
fn update_quads:bool() {
var all_landed: bool = true
for (i = 0 : g_quad_count - 1) {
if (!g_quads[i].landed) {
if (g_quads[i].delay > 0) {
g_quads[i].delay = g_quads[i].delay - 1
all_landed = false
} else {
g_quads[i].velocity = g_quads[i].velocity + 0.55
g_quads[i].current_y = g_quads[i].current_y + g_quads[i].velocity
if (g_quads[i].current_y >= g_quads[i].target_y) {
g_quads[i].current_y = g_quads[i].target_y
g_quads[i].landed = true
} else {
all_landed = false
}
}
}
}
return all_landed
}
fn draw_quads(alpha: f32) {
for (i = 0 : g_quad_count - 1) {
// Skip quads still waiting for their delay to expire
if (g_quads[i].delay > 0) {
continue
}
var col: Color
col.r = g_quads[i].r
col.g = g_quads[i].g
col.b = g_quads[i].b
col.a = 255
var tint: Color = Fade(col, alpha)
var rec: Rectangle
rec.x = g_quads[i].target_x
rec.y = g_quads[i].current_y
rec.width = BLOCK_SIZE * 1.0
rec.height = BLOCK_SIZE * 1.0
DrawRectangleRec(rec, tint)
}
}
fn main() {
var screen_width: i32 = 800
var screen_height: i32 = 600
InitWindow(screen_width, screen_height, "GX logo animation")
SetTargetFPS(60)
// Load the low-res logo
var logo_img: Image = LoadImage("website/resources/LogoLow.png")
var bg: Color
bg.r = 8
bg.g = 15
bg.b = 35
bg.a = 255
var state: i32 = 0
var alpha: f32 = 1.0
var hold_timer: i32 = 0
build_logo_from_image(logo_img, screen_width / 2, screen_height / 2)
while (!WindowShouldClose()) {
if (state == 0) {
var landed: bool = update_quads()
if (landed) {
state = 1
hold_timer = 0
}
}
if (state == 1) {
hold_timer = hold_timer + 1
if (hold_timer > 120) { state = 2 }
}
if (state == 2) {
alpha = alpha - 0.015
if (alpha <= 0.0) {
alpha = 0.0
state = 3
}
}
if (state == 3) {
build_logo_from_image(logo_img, screen_width / 2, screen_height / 2)
alpha = 1.0
hold_timer = 0
state = 0
}
BeginDrawing()
ClearBackground(bg)
draw_quads(alpha)
EndDrawing()
}
UnloadImage(logo_img)
CloseWindow()
}
Graphics — Sokol
Input sokol/input.gx
// input.ec - Handling input events with Sokol
// Build: ec examples/sokol/input.ec -I modules --cfile modules/sokol/c/sokol_impl.c --cflags "-Imodules/sokol/c" --ldflags "-lopengl32 -lgdi32 -luser32 -lshell32" -o build/input.exe
import sokol.gfx
import sokol.app
import sokol.glue
// Global state for background color
var g_r:f32 = 0.2;
var g_g:f32 = 0.2;
var g_b:f32 = 0.2;
@c_abi
fn init() {
var gfx_desc:sg_desc;
gfx_desc.environment = sglue_environment();
sg_setup(&gfx_desc);
print("Input demo initialized. Press keys or click mouse!\n");
}
@c_abi
fn frame() {
var pass:sg_pass;
pass.action.colors[0].load_action = 1; // SG_LOADACTION_CLEAR
pass.action.colors[0].clear_value.r = g_r;
pass.action.colors[0].clear_value.g = g_g;
pass.action.colors[0].clear_value.b = g_b;
pass.action.colors[0].clear_value.a = 1.0;
pass.swapchain = sglue_swapchain();
sg_begin_pass(&pass);
sg_end_pass();
sg_commit();
}
@c_abi
fn event(ev: const *sapp_event) {
if (ev.type == EVENTTYPE_KEY_DOWN) {
if (ev.key_code == KEYCODE_ESCAPE) {
sapp_quit();
}
print("Key pressed!\n");
}
elif (ev.type == EVENTTYPE_MOUSE_DOWN) {
if (ev.mouse_button == MOUSEBUTTON_LEFT) {
g_r = 0.8; g_g = 0.2; g_b = 0.2;
}
elif (ev.mouse_button == MOUSEBUTTON_RIGHT) {
g_r = 0.2; g_g = 0.8; g_b = 0.2;
}
print("Mouse down!\n");
}
elif (ev.type == EVENTTYPE_MOUSE_MOVE) {
// print("Mouse move: " + ev.mouse_x.str() + ", " + ev.mouse_y.str() + "\n");
}
}
@c_abi
fn cleanup() {
sg_shutdown();
}
fn main() {
var desc:sapp_desc;
desc.init_cb = init;
desc.frame_cb = frame;
desc.event_cb = event;
desc.cleanup_cb = cleanup;
desc.width = 640;
desc.height = 480;
desc.sample_count = 1;
desc.swap_interval = 1;
desc.window_title = "EC Sokol Input Demo";
sapp_run(&desc);
}
Shapes sokol/shapes.gx
// shapes.ec - Drawing 2D shapes with Sokol GP
// Build: ec examples/sokol/shapes.ec -I modules --cfile modules/sokol/c/sokol_impl.c --cflags "-Imodules/sokol/c" --ldflags "-lopengl32 -lgdi32 -luser32 -lshell32" -o build/shapes.exe
import sokol.gfx
import sokol.app
import sokol.glue
import sokol.gp
import math.scalar
// Animation state
var g_time:f32 = 0.0;
var g_frame:i32 = 0;
@c_abi
fn init() {
var gfx_desc:sg_desc;
gfx_desc.environment = sglue_environment();
sg_setup(&gfx_desc);
var gp_desc:sgp_desc;
sgp_setup(&gp_desc);
var err:sgp_error = sgp_get_last_error();
var msg:cstr = sgp_get_error_message(err);
print("SGP status: ");
print(msg);
print("\n");
}
@c_abi
fn frame() {
var w:c_int = sapp_width();
var h:c_int = sapp_height();
var fw:f32 = 800.0;
var fh:f32 = 600.0;
g_time = g_time + 0.016;
g_frame = g_frame + 1;
sgp_begin(w, h);
sgp_viewport(0, 0, w, h);
sgp_project(0.0, fw, 0.0, fh);
// Dark background
sgp_set_color(0.08, 0.08, 0.12, 1.0);
sgp_clear();
// --- Grid of small dots ---
var gx:i32 = 0;
while (gx < 20) {
var gy:i32 = 0;
while (gy < 15) {
sgp_set_color(0.15, 0.15, 0.2, 1.0);
sgp_draw_filled_rect(gx * 40 + 19, gy * 40 + 19, 2.0, 2.0);
gy = gy + 1;
}
gx = gx + 1;
}
// --- Animated rectangles (color wheel) ---
var i:i32 = 0;
while (i < 6) {
var fi:f32 = i * 1.0;
var angle:f32 = fi * 1.0472 + g_time; // 60 degrees apart + rotation
var cx:f32 = 200.0 + cosf(angle) * 80.0;
var cy:f32 = 150.0 + sinf(angle) * 80.0;
var r:f32 = sinf(g_time + fi) * 0.5 + 0.5;
var g:f32 = sinf(g_time + fi + 2.094) * 0.5 + 0.5;
var b:f32 = sinf(g_time + fi + 4.189) * 0.5 + 0.5;
sgp_set_blend_mode(BLENDMODE_BLEND);
sgp_set_color(r, g, b, 0.7);
sgp_draw_filled_rect(cx - 20.0, cy - 20.0, 40.0, 40.0);
i = i + 1;
}
// --- Spinning triangles ---
sgp_set_blend_mode(BLENDMODE_BLEND);
var t:i32 = 0;
while (t < 4) {
var ft:f32 = t * 1.0;
var ta:f32 = g_time * 1.5 + ft * 1.5708;
var tcx:f32 = 600.0;
var tcy:f32 = 150.0;
var tr:f32 = 60.0;
var ax:f32 = tcx + cosf(ta) * tr;
var ay:f32 = tcy + sinf(ta) * tr;
var bx:f32 = tcx + cosf(ta + 2.094) * tr;
var by:f32 = tcy + sinf(ta + 2.094) * tr;
var ccx:f32 = tcx + cosf(ta + 4.189) * tr;
var ccy:f32 = tcy + sinf(ta + 4.189) * tr;
var cr:f32 = 0.2;
var cg:f32 = 0.6;
var cb:f32 = 0.9;
if (t == 1) { cr = 0.9; cg = 0.3; cb = 0.2; }
elif (t == 2) { cr = 0.2; cg = 0.9; cb = 0.3; }
elif (t == 3) { cr = 0.9; cg = 0.9; cb = 0.2; }
sgp_set_color(cr, cg, cb, 0.6);
sgp_draw_filled_triangle(ax, ay, bx, by, ccx, ccy);
t = t + 1;
}
// --- Pulsing center rectangle ---
var pulse:f32 = sinf(g_time * 3.0) * 0.3 + 0.7;
sgp_set_color(1.0, 1.0, 1.0, pulse);
var pw:f32 = 60.0 + sinf(g_time * 2.0) * 20.0;
var ph:f32 = 60.0 + cosf(g_time * 2.0) * 20.0;
sgp_draw_filled_rect(400.0 - pw / 2.0, 300.0 - ph / 2.0, pw, ph);
// --- Line star pattern ---
sgp_set_color(0.5, 0.8, 1.0, 0.5);
var li:i32 = 0;
while (li < 12) {
var la:f32 = li * 0.5236 + g_time * 0.3; // 30 degrees apart
var lx:f32 = 400.0 + cosf(la) * 120.0;
var ly:f32 = 450.0 + sinf(la) * 80.0;
sgp_draw_line(400.0, 450.0, lx, ly);
li = li + 1;
}
// --- Bouncing row of rectangles ---
var bi:i32 = 0;
while (bi < 8) {
var bf:f32 = bi * 1.0;
var bx:f32 = 50.0 + bf * 90.0;
var bounce:f32 = sinf(g_time * 4.0 + bf * 0.8);
if (bounce < 0.0) { bounce = 0.0 - bounce; }
var by:f32 = 520.0 - bounce * 40.0;
var br:f32 = sinf(bf * 0.7) * 0.5 + 0.5;
var bg:f32 = sinf(bf * 0.7 + 2.0) * 0.5 + 0.5;
var bb:f32 = sinf(bf * 0.7 + 4.0) * 0.5 + 0.5;
sgp_set_color(br, bg, bb, 0.9);
sgp_draw_filled_rect(bx, by, 50.0, 20.0);
bi = bi + 1;
}
// --- Concentric rectangles (top right) ---
var ci:i32 = 0;
while (ci < 5) {
var cf:f32 = ci * 1.0;
var cs:f32 = 20.0 + cf * 20.0;
var ca:f32 = 1.0 - cf * 0.18;
sgp_set_color(sinf(g_time + cf) * 0.5 + 0.5, 0.3, 0.8, ca);
sgp_draw_filled_rect(400.0 - cs / 2.0, 100.0 - cs / 2.0, cs, cs);
ci = ci + 1;
}
// Flush SGP into Sokol GFX pass
var pass:sg_pass;
pass.action.colors[0].load_action = 1; // SG_LOADACTION_CLEAR
pass.action.colors[0].clear_value.r = 0.0;
pass.action.colors[0].clear_value.g = 0.0;
pass.action.colors[0].clear_value.b = 0.0;
pass.action.colors[0].clear_value.a = 1.0;
pass.swapchain = sglue_swapchain();
sg_begin_pass(&pass);
sgp_flush();
sgp_end();
sg_end_pass();
sg_commit();
}
@c_abi
fn event(ev: const *sapp_event) {
if (ev.type == EVENTTYPE_KEY_DOWN) {
if (ev.key_code == KEYCODE_ESCAPE) {
sapp_quit();
}
}
}
@c_abi
fn cleanup() {
sgp_shutdown();
sg_shutdown();
}
fn main() {
var desc:sapp_desc;
desc.init_cb = init;
desc.frame_cb = frame;
desc.event_cb = event;
desc.cleanup_cb = cleanup;
desc.width = 800;
desc.height = 600;
desc.sample_count = 1;
desc.swap_interval = 1;
desc.window_title = "EC Sokol GP - Shapes Demo";
sapp_run(&desc);
}
Snake sokol/snake.gx
// snake.ec - Classic Snake game in EC using Sokol
// Build: ec examples/sokol/snake.ec -I modules --cfile modules/sokol/c/sokol_impl.c --cflags "-Imodules/sokol/c" --ldflags "-lopengl32 -lgdi32 -luser32 -lshell32" -o build/snake.exe
import sokol.app
import sokol.gfx
import sokol.glue
// ============================================================================
// Constants
// ============================================================================
// Grid: 20x20 cells
// Each cell in NDC: 2.0/20 = 0.1 units
// Max snake length: 400 (full grid)
// Vertex format: (x, y, r, g, b) = 5 floats per vertex
// Each cell quad: 2 triangles = 6 vertices = 30 floats
// Max cells rendered: ~410 (snake + food + border)
// Max floats: 410 * 30 = 12300
// ============================================================================
// Game State
// ============================================================================
var g_sx:i32[400]; // snake X positions (grid coords)
var g_sy:i32[400]; // snake Y positions (grid coords)
var g_slen:i32 = 3; // current snake length
var g_dir:i32 = 1; // direction: 0=up, 1=right, 2=down, 3=left
var g_next_dir:i32 = 1; // buffered direction (applied on next tick)
var g_food_x:i32 = 10;
var g_food_y:i32 = 10;
var g_score:i32 = 0;
var g_alive:i32 = 1; // 1=playing, 0=game over
var g_tick:i32 = 0; // frame counter for movement timing
var g_speed:i32 = 8; // frames between moves (lower = faster)
var g_rng:i32 = 42; // simple PRNG state
var g_started:i32 = 0; // 0=title screen, 1=playing
// ============================================================================
// Vertex buffer (dynamic, rebuilt each frame)
// ============================================================================
var g_verts:f32[12600]; // max 420 quads * 30 floats
var g_vert_count:i32 = 0; // number of floats written
var g_num_verts:i32 = 0; // number of vertices (vert_count / 5)
// ============================================================================
// Sokol resources
// ============================================================================
var g_pip:sg_pipeline;
var g_bind:sg_bindings;
var g_vbuf:sg_buffer;
// Shaders
var g_vs:cstr = "#version 330\nin vec2 position;\nin vec3 color0;\nout vec3 color;\nvoid main() { gl_Position = vec4(position, 0.0, 1.0); color = color0; }\n";
var g_fs:cstr = "#version 330\nin vec3 color;\nout vec4 frag_color;\nvoid main() { frag_color = vec4(color, 1.0); }\n";
// ============================================================================
// PRNG (simple LCG)
// ============================================================================
fn rng_next:i32() {
g_rng = g_rng * 1103515245 + 12345;
var v:i32 = g_rng / 65536;
if (v < 0) { v = 0 - v; }
return v;
}
fn rng_range:i32(max:i32) {
var v:i32 = rng_next();
return v % max;
}
// ============================================================================
// Vertex helpers: push a colored quad at grid position
// ============================================================================
fn push_vert(x:f32, y:f32, r:f32, g:f32, b:f32) {
g_verts[g_vert_count] = x; g_vert_count = g_vert_count + 1;
g_verts[g_vert_count] = y; g_vert_count = g_vert_count + 1;
g_verts[g_vert_count] = r; g_vert_count = g_vert_count + 1;
g_verts[g_vert_count] = g; g_vert_count = g_vert_count + 1;
g_verts[g_vert_count] = b; g_vert_count = g_vert_count + 1;
}
fn push_quad(gx:i32, gy:i32, r:f32, g:f32, b:f32) {
// Convert grid coords to NDC
// Grid is 20x20, mapped to [-0.95, 0.95] to leave a small border
var cell:f32 = 1.9 / 20.0;
var gap:f32 = 0.01;
var x0:f32 = -0.95 + gx * cell + gap;
var y0:f32 = 0.95 - gy * cell - gap;
var x1:f32 = x0 + cell - gap * 2.0;
var y1:f32 = y0 - cell + gap * 2.0;
// Triangle 1: top-left, top-right, bottom-left
push_vert(x0, y0, r, g, b);
push_vert(x1, y0, r, g, b);
push_vert(x0, y1, r, g, b);
// Triangle 2: top-right, bottom-right, bottom-left
push_vert(x1, y0, r, g, b);
push_vert(x1, y1, r, g, b);
push_vert(x0, y1, r, g, b);
}
// ============================================================================
// Game Logic
// ============================================================================
fn game_reset() {
g_slen = 3;
g_dir = 1;
g_next_dir = 1;
g_score = 0;
g_alive = 1;
g_tick = 0;
g_speed = 8;
g_started = 1;
// Start snake in center, going right
g_sx[0] = 10; g_sy[0] = 10; // head
g_sx[1] = 9; g_sy[1] = 10;
g_sx[2] = 8; g_sy[2] = 10;
spawn_food();
print("Snake game started! Score: 0\n");
}
fn spawn_food() {
var valid:i32 = 0;
while (valid == 0) {
g_food_x = rng_range(20);
g_food_y = rng_range(20);
// Make sure food doesn't overlap snake
valid = 1;
var i:i32 = 0;
while (i < g_slen) {
if (g_sx[i] == g_food_x) {
if (g_sy[i] == g_food_y) {
valid = 0;
}
}
i = i + 1;
}
}
}
fn game_tick() {
if (g_alive == 0) { return; }
if (g_started == 0) { return; }
g_tick = g_tick + 1;
if (g_tick < g_speed) { return; }
g_tick = 0;
// Apply buffered direction
g_dir = g_next_dir;
// Calculate new head position
var hx:i32 = g_sx[0];
var hy:i32 = g_sy[0];
if (g_dir == 0) { hy = hy - 1; }
if (g_dir == 1) { hx = hx + 1; }
if (g_dir == 2) { hy = hy + 1; }
if (g_dir == 3) { hx = hx - 1; }
// Wall collision
if (hx < 0) { g_alive = 0; print("Game Over! Hit wall. Score: {g_score}\n"); return; }
if (hx > 19) { g_alive = 0; print("Game Over! Hit wall. Score: {g_score}\n"); return; }
if (hy < 0) { g_alive = 0; print("Game Over! Hit wall. Score: {g_score}\n"); return; }
if (hy > 19) { g_alive = 0; print("Game Over! Hit wall. Score: {g_score}\n"); return; }
// Self collision
var i:i32 = 0;
while (i < g_slen) {
if (g_sx[i] == hx) {
if (g_sy[i] == hy) {
g_alive = 0;
print("Game Over! Hit self. Score: {g_score}\n");
return;
}
}
i = i + 1;
}
// Check food
var ate:i32 = 0;
if (hx == g_food_x) {
if (hy == g_food_y) {
ate = 1;
g_score = g_score + 1;
print("Score: {g_score}\n");
// Speed up every 5 points
if (g_score % 5 == 0) {
if (g_speed > 3) {
g_speed = g_speed - 1;
}
}
}
}
// Move body: shift all segments down
if (ate == 1) {
// Grow: don't remove tail
var j:i32 = g_slen;
while (j > 0) {
g_sx[j] = g_sx[j - 1];
g_sy[j] = g_sy[j - 1];
j = j - 1;
}
g_slen = g_slen + 1;
} else {
// No growth: shift body, tail disappears
var j:i32 = g_slen - 1;
while (j > 0) {
g_sx[j] = g_sx[j - 1];
g_sy[j] = g_sy[j - 1];
j = j - 1;
}
}
// Place new head
g_sx[0] = hx;
g_sy[0] = hy;
// Spawn new food if eaten
if (ate == 1) {
spawn_food();
}
}
// ============================================================================
// Rendering
// ============================================================================
fn build_vertices() {
g_vert_count = 0;
if (g_started == 0) {
// Title screen: draw "S N A K E" pattern using cells
// Letter S (col 2-5, row 6-12)
push_quad(2, 6, 0.2, 0.8, 0.2); push_quad(3, 6, 0.2, 0.8, 0.2); push_quad(4, 6, 0.2, 0.8, 0.2);
push_quad(2, 7, 0.2, 0.8, 0.2);
push_quad(2, 8, 0.2, 0.8, 0.2); push_quad(3, 8, 0.2, 0.8, 0.2); push_quad(4, 8, 0.2, 0.8, 0.2);
push_quad(4, 9, 0.2, 0.8, 0.2);
push_quad(2, 10, 0.2, 0.8, 0.2); push_quad(3, 10, 0.2, 0.8, 0.2); push_quad(4, 10, 0.2, 0.8, 0.2);
// Letter N (col 6-9, row 6-10)
push_quad(6, 6, 0.2, 0.8, 0.2); push_quad(6, 7, 0.2, 0.8, 0.2); push_quad(6, 8, 0.2, 0.8, 0.2);
push_quad(6, 9, 0.2, 0.8, 0.2); push_quad(6, 10, 0.2, 0.8, 0.2);
push_quad(7, 7, 0.2, 0.8, 0.2);
push_quad(8, 8, 0.2, 0.8, 0.2);
push_quad(9, 6, 0.2, 0.8, 0.2); push_quad(9, 7, 0.2, 0.8, 0.2); push_quad(9, 8, 0.2, 0.8, 0.2);
push_quad(9, 9, 0.2, 0.8, 0.2); push_quad(9, 10, 0.2, 0.8, 0.2);
// Letter A (col 11-14, row 6-10)
push_quad(12, 6, 0.2, 0.8, 0.2); push_quad(13, 6, 0.2, 0.8, 0.2);
push_quad(11, 7, 0.2, 0.8, 0.2); push_quad(14, 7, 0.2, 0.8, 0.2);
push_quad(11, 8, 0.2, 0.8, 0.2); push_quad(12, 8, 0.2, 0.8, 0.2);
push_quad(13, 8, 0.2, 0.8, 0.2); push_quad(14, 8, 0.2, 0.8, 0.2);
push_quad(11, 9, 0.2, 0.8, 0.2); push_quad(14, 9, 0.2, 0.8, 0.2);
push_quad(11, 10, 0.2, 0.8, 0.2); push_quad(14, 10, 0.2, 0.8, 0.2);
// Letter K (col 16-19, row 6-10)
push_quad(16, 6, 0.2, 0.8, 0.2); push_quad(16, 7, 0.2, 0.8, 0.2);
push_quad(16, 8, 0.2, 0.8, 0.2); push_quad(16, 9, 0.2, 0.8, 0.2);
push_quad(16, 10, 0.2, 0.8, 0.2);
push_quad(18, 6, 0.2, 0.8, 0.2); push_quad(17, 7, 0.2, 0.8, 0.2);
push_quad(17, 8, 0.2, 0.8, 0.2);
push_quad(17, 9, 0.2, 0.8, 0.2); push_quad(18, 10, 0.2, 0.8, 0.2);
// "Press SPACE" hint - small dots
push_quad(6, 14, 0.5, 0.5, 0.5);
push_quad(8, 14, 0.5, 0.5, 0.5);
push_quad(10, 14, 0.5, 0.5, 0.5);
push_quad(12, 14, 0.5, 0.5, 0.5);
push_quad(14, 14, 0.5, 0.5, 0.5);
g_num_verts = g_vert_count / 5;
return;
}
// Draw food (pulsing red — alternate brightness)
var food_r:f32 = 0.9;
var food_g:f32 = 0.15;
var food_b:f32 = 0.15;
if (g_tick % 4 < 2) {
food_r = 1.0;
food_g = 0.25;
food_b = 0.2;
}
push_quad(g_food_x, g_food_y, food_r, food_g, food_b);
// Draw snake body (gradient from bright to dark green)
var i:i32 = g_slen - 1;
while (i >= 0) {
var t:f32 = 0.3;
if (g_slen > 1) {
// Gradient: head is bright, tail is darker
t = 0.3 + 0.6 * (1.0 - i * 1.0 / g_slen);
}
var sr:f32 = 0.1;
var sg:f32 = t;
var sb:f32 = 0.1;
if (i == 0) {
// Head is brightest
sr = 0.2;
sg = 0.95;
sb = 0.2;
}
// Game over: snake turns red
if (g_alive == 0) {
sr = 0.7;
sg = 0.15;
sb = 0.1;
if (i == 0) {
sr = 1.0;
sg = 0.2;
sb = 0.15;
}
}
push_quad(g_sx[i], g_sy[i], sr, sg, sb);
i = i - 1;
}
// Draw border (subtle gray outline around play area)
var bx:i32 = 0;
while (bx < 20) {
// Top and bottom border markers (just outside play area — we'll draw at grid edge)
// Since we can't draw outside the grid, draw a subtle border at the edges
if (g_alive == 1) {
// Dim grid dots at corners only to keep it subtle
}
bx = bx + 1;
}
g_num_verts = g_vert_count / 5;
}
// ============================================================================
// Sokol Callbacks
// ============================================================================
@c_abi
fn init() {
// Setup sokol_gfx
var gfx_desc:sg_desc;
gfx_desc.environment = sglue_environment();
sg_setup(&gfx_desc);
// Create a STREAM vertex buffer (updated every frame)
var buf_desc:sg_buffer_desc;
buf_desc.size = 50400; // 12600 floats * 4 bytes = 50400 bytes
buf_desc.usage.stream_update = 1; // enable streaming
g_vbuf = sg_make_buffer(&buf_desc);
g_bind.vertex_buffers[0] = g_vbuf;
// Create shader
var shd_desc:sg_shader_desc;
shd_desc.vertex_func.source = g_vs;
shd_desc.fragment_func.source = g_fs;
shd_desc.attrs[0].glsl_name = "position";
shd_desc.attrs[1].glsl_name = "color0";
var shd:sg_shader = sg_make_shader(&shd_desc);
// Create pipeline
var pip_desc:sg_pipeline_desc;
pip_desc.shader = shd;
pip_desc.layout.attrs[0].format = 2; // SG_VERTEXFORMAT_FLOAT2
pip_desc.layout.attrs[1].format = 3; // SG_VERTEXFORMAT_FLOAT3
g_pip = sg_make_pipeline(&pip_desc);
// Seed PRNG with something
g_rng = 7919;
print("=== EC Snake ===\n");
print("Arrow keys or WASD to move\n");
print("Press SPACE to start\n");
print("ESC to quit\n\n");
}
@c_abi
fn frame() {
// Game logic
game_tick();
// Build vertices
build_vertices();
// Update vertex buffer
var range:sg_range;
range.ptr = &g_verts[0];
range.size = g_vert_count * 4; // 4 bytes per float
if (g_vert_count > 0) {
sg_update_buffer(g_vbuf, &range);
}
// Background color
var bg_r:f32 = 0.08;
var bg_g:f32 = 0.08;
var bg_b:f32 = 0.1;
if (g_alive == 0) {
// Reddish tint on game over
bg_r = 0.15;
bg_g = 0.05;
bg_b = 0.05;
}
// Render pass
var pass:sg_pass;
pass.action.colors[0].load_action = 1; // SG_LOADACTION_CLEAR
pass.action.colors[0].clear_value.r = bg_r;
pass.action.colors[0].clear_value.g = bg_g;
pass.action.colors[0].clear_value.b = bg_b;
pass.action.colors[0].clear_value.a = 1.0;
pass.swapchain = sglue_swapchain();
sg_begin_pass(&pass);
if (g_num_verts > 0) {
sg_apply_pipeline(g_pip);
sg_apply_bindings(&g_bind);
sg_draw(0, g_num_verts, 1);
}
sg_end_pass();
sg_commit();
}
@c_abi
fn event(ev: const *sapp_event) {
if (ev.type == EVENTTYPE_KEY_DOWN) {
// Quit
if (ev.key_code == KEYCODE_ESCAPE) {
sapp_quit();
}
// Start / restart
if (ev.key_code == KEYCODE_SPACE) {
if (g_started == 0) {
game_reset();
}
elif (g_alive == 0) {
game_reset();
}
}
if (ev.key_code == KEYCODE_ENTER) {
if (g_started == 0) {
game_reset();
}
elif (g_alive == 0) {
game_reset();
}
}
// Direction input (prevent 180-degree turns)
if (g_alive == 1) {
if (ev.key_code == KEYCODE_UP) {
if (g_dir != 2) { g_next_dir = 0; }
}
if (ev.key_code == KEYCODE_W) {
if (g_dir != 2) { g_next_dir = 0; }
}
if (ev.key_code == KEYCODE_RIGHT) {
if (g_dir != 3) { g_next_dir = 1; }
}
if (ev.key_code == KEYCODE_D) {
if (g_dir != 3) { g_next_dir = 1; }
}
if (ev.key_code == KEYCODE_DOWN) {
if (g_dir != 0) { g_next_dir = 2; }
}
if (ev.key_code == KEYCODE_S) {
if (g_dir != 0) { g_next_dir = 2; }
}
if (ev.key_code == KEYCODE_LEFT) {
if (g_dir != 1) { g_next_dir = 3; }
}
if (ev.key_code == KEYCODE_A) {
if (g_dir != 1) { g_next_dir = 3; }
}
}
}
}
@c_abi
fn cleanup() {
sg_shutdown();
}
fn main() {
var desc:sapp_desc;
desc.init_cb = init;
desc.frame_cb = frame;
desc.event_cb = event;
desc.cleanup_cb = cleanup;
desc.width = 640;
desc.height = 640;
desc.sample_count = 1;
desc.swap_interval = 1;
desc.window_title = "EC Snake";
sapp_run(&desc);
}
Triangle sokol/triangle.gx
// triangle.ec - 100% EC triangle demo using sokol wrappers
// Build: ec examples/sokol/triangle.ec -I modules --cfile modules/sokol/c/sokol_impl.c --cflags "-Imodules/sokol/c" --ldflags "-lopengl32 -lgdi32 -luser32 -lshell32" -o build/triangle.exe
import sokol.app
import sokol.gfx
import sokol.glue
// ============================================================================
// Vertex data: 3 vertices * (2 pos + 3 color) = 15 floats
// ============================================================================
var g_vertices:f32[15];
// Global state
var g_pip:sg_pipeline;
var g_bind:sg_bindings;
// ============================================================================
// Shader sources (GLSL 330)
// ============================================================================
var g_vs_source:cstr = "#version 330\nin vec2 position;\nin vec3 color0;\nout vec3 color;\nvoid main() { gl_Position = vec4(position, 0.0, 1.0); color = color0; }\n";
var g_fs_source:cstr = "#version 330\nin vec3 color;\nout vec4 frag_color;\nvoid main() { frag_color = vec4(color, 1.0); }\n";
@c_abi
fn init() {
print("Initializing...\n");
// Setup sokol_gfx
var gfx_desc:sg_desc;
gfx_desc.environment = sglue_environment();
sg_setup(&gfx_desc);
// Initialize vertex data: positions + colors
// Top vertex (red)
g_vertices[0] = 0.0; g_vertices[1] = 0.5;
g_vertices[2] = 1.0; g_vertices[3] = 0.0; g_vertices[4] = 0.0;
// Right vertex (green)
g_vertices[5] = 0.5; g_vertices[6] = -0.5;
g_vertices[7] = 0.0; g_vertices[8] = 1.0; g_vertices[9] = 0.0;
// Left vertex (blue)
g_vertices[10] = -0.5; g_vertices[11] = -0.5;
g_vertices[12] = 0.0; g_vertices[13] = 0.0; g_vertices[14] = 1.0;
// Create vertex buffer
var buf_desc:sg_buffer_desc;
buf_desc.data.ptr = &g_vertices[0];
buf_desc.data.size = 60; // 15 floats * 4 bytes
var vbuf:sg_buffer = sg_make_buffer(&buf_desc);
g_bind.vertex_buffers[0] = vbuf;
// Create shader
var shd_desc:sg_shader_desc;
shd_desc.vertex_func.source = g_vs_source;
shd_desc.fragment_func.source = g_fs_source;
shd_desc.attrs[0].glsl_name = "position";
shd_desc.attrs[1].glsl_name = "color0";
var shd:sg_shader = sg_make_shader(&shd_desc);
// Create pipeline
var pip_desc:sg_pipeline_desc;
pip_desc.shader = shd;
pip_desc.layout.attrs[0].format = 2; // SG_VERTEXFORMAT_FLOAT2
pip_desc.layout.attrs[1].format = 3; // SG_VERTEXFORMAT_FLOAT3
g_pip = sg_make_pipeline(&pip_desc);
print("Ready!\n");
}
@c_abi
fn frame() {
var pass:sg_pass;
pass.action.colors[0].load_action = 1; // SG_LOADACTION_CLEAR
pass.action.colors[0].clear_value.r = 0.2;
pass.action.colors[0].clear_value.g = 0.2;
pass.action.colors[0].clear_value.b = 0.2;
pass.action.colors[0].clear_value.a = 1.0;
pass.swapchain = sglue_swapchain();
sg_begin_pass(&pass);
sg_apply_pipeline(g_pip);
sg_apply_bindings(&g_bind);
sg_draw(0, 3, 1);
sg_end_pass();
sg_commit();
}
@c_abi
fn cleanup() {
print("Cleanup...\n");
sg_shutdown();
}
fn main() {
print("EC Sokol Triangle (100% EC)\n");
var desc:sapp_desc;
desc.init_cb = init;
desc.frame_cb = frame;
desc.cleanup_cb = cleanup;
desc.width = 640;
desc.height = 480;
desc.sample_count = 1;
desc.swap_interval = 1;
desc.window_title = "EC Triangle (Pure EC)";
sapp_run(&desc);
}