// src/wasm_bindings.mbt
// WASM exports and rendering layer using Canvas FFI
let global_game : Ref[Option[Game]] = { val: None }
pub fn wasm_new_game() -> Unit {
global_game.val = Some(Game::new())
}
pub fn wasm_tick() -> Unit {
match global_game.val {
Some(g) => g.tick()
None => ()
}
}
pub fn wasm_move_left() -> Unit {
match global_game.val {
Some(g) => { let _ = g.move_left(); }
None => ()
}
}
pub fn wasm_move_right() -> Unit {
match global_game.val {
Some(g) => { let _ = g.move_right(); }
None => ()
}
}
pub fn wasm_rotate() -> Unit {
match global_game.val {
Some(g) => { let _ = g.rotate(); }
None => ()
}
}
pub fn wasm_soft_drop() -> Unit {
match global_game.val {
Some(g) => { let _ = g.soft_drop(); }
None => ()
}
}
pub fn wasm_hard_drop() -> Unit {
match global_game.val {
Some(g) => g.hard_drop()
None => ()
}
}
pub fn wasm_hold() -> Unit {
match global_game.val {
Some(g) => g.hold()
None => ()
}
}
pub fn wasm_is_game_over() -> Bool {
match global_game.val {
Some(g) => g.game_over
None => true
}
}
pub fn wasm_get_score() -> Int {
match global_game.val {
Some(g) => g.score
None => 0
}
}
pub fn wasm_get_lines() -> Int {
match global_game.val {
Some(g) => g.lines
None => 0
}
}
pub fn wasm_get_level() -> Int {
match global_game.val {
Some(g) => g.level
None => 1
}
}
fn draw_block(x : Double, y : Double, kind : Int, is_ghost : Bool) -> Unit {
let size = 28.0
let (r, g, b) = match kind {
1 => (0, 243, 255) // Cyber Cyan
2 => (255, 235, 59) // Neon Yellow
3 => (188, 79, 255) // Cyber Purple
4 => (0, 230, 118) // Neon Green
5 => (255, 23, 68) // Neon Red
6 => (41, 121, 255) // Neon Blue
7 => (255, 145, 0) // Neon Orange
_ => (20, 20, 25)
}
if kind > 0 {
if is_ghost {
set_stroke_color(r, g, b)
set_line_width(1.5)
stroke_rect(x + 2.0, y + 2.0, size - 4.0, size - 4.0)
} else {
set_fill_color(r, g, b)
fill_rect(x + 1.0, y + 1.0, size - 2.0, size - 2.0)
set_fill_color(255, 255, 255)
fill_rect(x + 2.0, y + 2.0, size - 4.0, 3.0)
fill_rect(x + 2.0, y + 2.0, 3.0, size - 4.0)
}
} else {
set_fill_color(24, 24, 30)
fill_rect(x, y, size, size)
set_stroke_color(36, 36, 45)
set_line_width(0.5)
stroke_rect(x, y, size, size)
}
}
fn draw_small_piece(center_x : Double, center_y : Double, kind : Int) -> Unit {
if kind == 0 { return }
let n = if kind == 1 { 4 } else if kind == 2 { 2 } else { 3 }
let block_size = 18.0
let offset_x = center_x - (n.to_double() * block_size) / 2.0
let offset_y = center_y - (n.to_double() * block_size) / 2.0
let (r, g, b) = match kind {
1 => (0, 243, 255)
2 => (255, 235, 59)
3 => (188, 79, 255)
4 => (0, 230, 118)
5 => (255, 23, 68)
6 => (41, 121, 255)
7 => (255, 145, 0)
_ => (0, 0, 0)
}
set_fill_color(r, g, b)
for row = 0; row < n; row = row + 1 {
for col = 0; col < n; col = col + 1 {
if get_block(kind, 0, row, col) {
let x = offset_x + col.to_double() * block_size
let y = offset_y + row.to_double() * block_size
fill_rect(x + 1.0, y + 1.0, block_size - 2.0, block_size - 2.0)
}
}
}
}
pub fn wasm_render() -> Unit {
match global_game.val {
Some(g) => {
clear_rect(0.0, 0.0, 600.0, 600.0)
set_fill_color(30, 30, 40)
fill_rect(20.0, 20.0, 110.0, 110.0)
set_stroke_color(70, 70, 90)
set_line_width(2.0)
stroke_rect(20.0, 20.0, 110.0, 110.0)
set_fill_color(180, 180, 200)
set_font("bold 14px Outfit")
fill_text("HOLD", 55.0, 42.0)
set_fill_color(30, 30, 40)
fill_rect(470.0, 20.0, 110.0, 110.0)
set_stroke_color(70, 70, 90)
stroke_rect(470.0, 20.0, 110.0, 110.0)
set_fill_color(180, 180, 200)
fill_text("NEXT", 505.0, 42.0)
draw_small_piece(75.0, 85.0, g.hold_kind)
draw_small_piece(525.0, 85.0, g.next_kind)
set_fill_color(30, 30, 40)
fill_rect(470.0, 150.0, 110.0, 150.0)
stroke_rect(470.0, 150.0, 110.0, 150.0)
set_fill_color(140, 140, 160)
set_font("12px Outfit")
fill_text("SCORE", 480.0, 180.0)
set_fill_color(255, 255, 255)
set_font("bold 18px JetBrains Mono")
fill_text(g.score.to_string(), 480.0, 202.0)
set_fill_color(140, 140, 160)
set_font("12px Outfit")
fill_text("LEVEL", 480.0, 230.0)
set_fill_color(0, 243, 255)
set_font("bold 18px JetBrains Mono")
fill_text(g.level.to_string(), 480.0, 252.0)
set_fill_color(140, 140, 160)
set_font("12px Outfit")
fill_text("LINES", 480.0, 280.0)
set_fill_color(255, 255, 255)
set_font("bold 18px JetBrains Mono")
fill_text(g.lines.to_string(), 480.0, 302.0)
let board_x = 160.0
let board_y = 20.0
set_stroke_color(100, 100, 130)
stroke_rect(board_x - 2.0, board_y - 2.0, 284.0, 564.0)
for row = 0; row < 20; row = row + 1 {
for col = 0; col < 10; col = col + 1 {
let x = board_x + col.to_double() * 28.0
let y = board_y + row.to_double() * 28.0
draw_block(x, y, g.board[row][col], false)
}
}
if !g.game_over {
let mut ghost_y = g.current_y
while !g.check_collision(g.current_x, ghost_y + 1, g.current_rotation) {
ghost_y = ghost_y + 1
}
let n = if g.current_kind == 1 { 4 } else if g.current_kind == 2 { 2 } else { 3 }
for row = 0; row < n; row = row + 1 {
for col = 0; col < n; col = col + 1 {
if get_block(g.current_kind, g.current_rotation, row, col) {
let by = ghost_y + row
if by >= 0 {
let bx = g.current_x + col
let x = board_x + bx.to_double() * 28.0
let y = board_y + by.to_double() * 28.0
draw_block(x, y, g.current_kind, true)
}
}
}
}
}
if !g.game_over {
let n = if g.current_kind == 1 { 4 } else if g.current_kind == 2 { 2 } else { 3 }
for row = 0; row < n; row = row + 1 {
for col = 0; col < n; col = col + 1 {
if get_block(g.current_kind, g.current_rotation, row, col) {
let by = g.current_y + row
if by >= 0 {
let bx = g.current_x + col
let x = board_x + bx.to_double() * 28.0
let y = board_y + by.to_double() * 28.0
draw_block(x, y, g.current_kind, false)
}
}
}
}
}
if g.game_over {
set_fill_color(0, 0, 0)
fill_rect(board_x, board_y, 280.0, 560.0)
set_fill_color(255, 23, 68)
set_font("bold 24px Outfit")
fill_text("GAME OVER", board_x + 65.0, board_y + 260.0)
set_fill_color(180, 180, 200)
set_font("14px Outfit")
fill_text("Press Enter to Restart", board_x + 63.0, board_y + 295.0)
}
}
None => ()
}
}