Skip to content

Commit

Permalink
fix snake example (#188)
Browse files Browse the repository at this point in the history
* fix: modernize snake example

* move: main logic

* cleanup

* reorganize: put into main

* update: internalize color definition

* simplify: make program more readable

* simplify: reduce game logic

* fix: ffi

* update: makefile
  • Loading branch information
peter-jerry-ye authored May 8, 2024
1 parent 1eedee5 commit ab871cc
Show file tree
Hide file tree
Showing 14 changed files with 320 additions and 334 deletions.
2 changes: 2 additions & 0 deletions examples/snake/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
target/
.mooncakes/
3 changes: 3 additions & 0 deletions examples/snake/.moonbit-lsp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"backend": "wasm-gc"
}
5 changes: 3 additions & 2 deletions examples/snake/Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
run:
moon build
sudo python3 -m http.server 8080
moon install
moon build --target wasm-gc
python3 -m http.server 8080
5 changes: 5 additions & 0 deletions examples/snake/extern/moon.pkg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"import": [
"peter-jerry-ye/memory"
]
}
33 changes: 33 additions & 0 deletions examples/snake/extern/string.mbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
type JS_String

fn string_load_ffi(offset : Int, length : Int) -> JS_String = "string" "load"

fn string_store_ffi(string : JS_String, offset : Int) = "string" "store"

pub fn length(self : JS_String) -> Int = "string" "length"

pub fn log(self : JS_String) = "string" "log"

pub fn JS_String::empty() -> JS_String = "string" "empty"

pub fn JS_String::from_string(str : String) -> Option[JS_String] {
if str.length() == 0 {
return Some(JS_String::empty())
}
let memory = @memory.allocate(str.length() * 2)?
memory.store_bytes(str.to_bytes())
let str = string_load_ffi(memory.offset, str.length())
memory.free()?
Some(str)
}

pub fn JS_String::to_string(str : JS_String) -> Option[String] {
if str.length() == 0 {
return Some("")
}
let memory = @memory.allocate(str.length() * 2)?
string_store_ffi(str, memory.offset)
let str = memory.load_bytes().to_string()
memory.free()?
Some(str)
}
136 changes: 16 additions & 120 deletions examples/snake/index.html
Original file line number Diff line number Diff line change
@@ -1,125 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<canvas id="canvas"></canvas>
<div class="information">
<div>
<p><kbd>up</kbd>: go up</p>
<p><kbd>left</kbd>: go left</p>
<p><kbd>right</kbd>: go right</p>
<p><kbd>down</kbd>: go down</p>
</div>
</div>
</body>
<script>

const canvas = document.getElementById("canvas")
const context = canvas.getContext("2d")
const WIDTH = 480
const HEIGHT = WIDTH

canvas.width = WIDTH
canvas.height = HEIGHT

context.scale(24, 24)
const GRID_COL = WIDTH/24
const GRID_ROW = HEIGHT/24

const colors = [
"#dcdcdc",
"#00263f",
"#ffb900",
"#2753f1",
"#f7ff00",
"#ff6728",
"#11c5bf",
"#ae81ff",
"#e94659",
]

let requestAnimationFrameId = null
let lastTime = 0
let dropCounter = 0
let dropInterval = 500
window.addEventListener("keydown", (e) => {
if (!requestAnimationFrameId) return
switch (e.key) {
case "ArrowLeft": {
snake_step(snake, 3)
snake_draw(context, snake)
break
}
case "ArrowRight": {

snake_step(snake, 4)
snake_draw(context, snake)
break
}
case "ArrowDown": {

snake_step(snake, 2)
snake_draw(context, snake)
break
}
case "ArrowUp": {

snake_step(snake, 1)
snake_draw(context, snake)
break
}

}
})
let snake_draw = null;
let snake_new = null;
let snake_step = null;
let snake = null;

const importObject = {
canvas: {
stroke_rect: (ctx, x, y, width, height) => ctx.strokeRect(x, y, width, height),
set_line_width: (ctx, width) => ctx.lineWidth = width,
fill_rect: (ctx, x, y, width, height) => ctx.fillRect(x, y, width, height),
set_stroke_color: (ctx, color) => ctx.strokeStyle = colors[color],
set_fill_style: (ctx, color) => ctx.fillStyle = colors[color],

},
spectest: {
print_i32: (x) => console.log(String(x)),
print_f64: (x) => console.log(String(x)),
print_char: (x) => console.log(String.fromCharCode(x)),
},
Math:{
random: Math.random,
floor: Math.floor,
}
};

function update(time = 0) {
const deltaTime = time - lastTime
dropCounter += deltaTime
if (dropCounter > dropInterval) {
snake_step(snake, 5);
dropCounter = 0
}
lastTime = time
snake_draw(context, snake)
requestAnimationFrameId = requestAnimationFrame(update)
}

<head>
<link rel="stylesheet" href="./style.css" />
</head>

<body>
<canvas id="canvas"></canvas>
<div class="information">
<div>
<p><kbd>up</kbd>: go up</p>
<p><kbd>left</kbd>: go left</p>
<p><kbd>right</kbd>: go right</p>
<p><kbd>down</kbd>: go down</p>
</div>
</div>
</body>
<script src="./main.mjs" type="module"></script>

WebAssembly.instantiateStreaming(fetch("target/wasm/release/build/main/main.wasm"), importObject).then(
(obj) => {
obj.instance.exports._start();
snake_draw = obj.instance.exports["snake/main::draw"];
snake_new = obj.instance.exports["snake/main::new"];
snake_step = obj.instance.exports["snake/main::step"];
snake = snake_new();
requestAnimationFrameId = requestAnimationFrame(update);
}
)
</script>
</html>
59 changes: 33 additions & 26 deletions examples/snake/lib/draw.mbt
Original file line number Diff line number Diff line change
@@ -1,52 +1,59 @@
// fn newCanvas(w: Int, h: Int) -> Int= "canvas""newCanvas"
pub type Canvas_ctx

// fn updateCanvas(id: Int, )
fn set_stroke_color_ffi(self : Canvas_ctx, color : @extern.JS_String) = "canvas" "set_stroke_color"

type Canvas_ctx

fn set_stroke_color(self : Canvas_ctx, color : Int) = "canvas" "set_stroke_color"
fn set_stroke_color(self : Canvas_ctx, color : String) -> Unit {
self.set_stroke_color_ffi(@extern.JS_String::from_string(color).unwrap())
}

fn set_line_width(self : Canvas_ctx, width : Double) = "canvas" "set_line_width"

fn stroke_rect(self : Canvas_ctx, x : Int, y : Int, width : Int, height : Int) = "canvas" "stroke_rect"

fn fill_rect(self : Canvas_ctx, x : Int, y : Int, width : Int, height : Int) = "canvas" "fill_rect"

fn set_fill_style(self : Canvas_ctx, color : Int) = "canvas" "set_fill_style"
fn set_fill_style_ffi(self : Canvas_ctx, color : @extern.JS_String) = "canvas" "set_fill_style"

fn set_fill_style(self : Canvas_ctx, color : String) -> Unit {
self.set_fill_style_ffi(@extern.JS_String::from_string(color).unwrap())
}

fn body_color(grid : GridType) -> String {
match grid {
Body => "#ffb900"
Food => "#2753f1"
Default => "#dcdcdc"
}
}

pub fn draw(canvas : Canvas_ctx, snake : GameState) {
let mut c = 0
let border_color = "#00263f"

pub fn draw(canvas : Canvas_ctx, snake : GameState) -> Unit {
// draw backgroud
while c < grid_col_count {
canvas.set_fill_style(0)
for c = 0; c < grid_col_count; c = c + 1 {
canvas.set_fill_style(body_color(Default))
canvas.fill_rect(c, 0, 1, grid_row_count)
c = c + 1
}

draw_piece(canvas, snake.grid, (0, 0))
}

pub fn draw_piece(canvas : Canvas_ctx, matrix : Array[Int],
offset : (Int, Int)) {

fn draw_piece(
canvas : Canvas_ctx,
matrix : Array[GridType],
offset : (Int, Int)
) -> Unit {
let mut r = 0
let mut c = 0
let mut c0 = 0
while c < matrix.length() {
if matrix[c] == 0 {
c = c + 1
continue
for c = 0; c < matrix.length(); c = c + 1 {
if matrix[c] == Default {
continue c + 1
}
c0 = c % grid_col_count
r = c / grid_col_count
canvas.set_fill_style(matrix[c] + 1)
canvas.fill_rect( offset.0 + c0, r, 1, 1)
canvas.set_stroke_color(1)
canvas.set_fill_style(body_color(matrix[c]))
canvas.fill_rect(offset.0 + c0, r, 1, 1)
canvas.set_stroke_color(border_color)
canvas.set_line_width(0.1)
canvas.stroke_rect( c0, r, 1, 1)
c = c + 1
canvas.stroke_rect(c0, r, 1, 1)
}
}

6 changes: 5 additions & 1 deletion examples/snake/lib/moon.pkg.json
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
{ }
{
"import": [
"snake/extern"
]
}
Loading

0 comments on commit ab871cc

Please sign in to comment.