Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added example/scores/assets/eg_6x10.fff
Binary file not shown.
5 changes: 5 additions & 0 deletions example/scores/assets/eg_6x10.fff.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
SPDX-FileCopyrightText: 2020 James Waples

SPDX-License-Identifier: MIT

Obtained from: https://fonts.fireflyzero.com/ascii
14 changes: 14 additions & 0 deletions example/scores/firefly.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: 2026 Kalle Fagerberg
#
# SPDX-License-Identifier: CC0-1.0

author_id = "demo"
app_id = "moonbit-scores"
author_name = "Demo"
app_name = "Scores demo (MoonBit)"

[files]
font = { path = "assets/eg_6x10.fff" }

[boards]
1 = { name = "default" }
77 changes: 77 additions & 0 deletions example/scores/main.mbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
///|
using @firefly {type Point, type Peer, type Board}

///|
fn main {

}

///|
/// ID of a board set in `firefly.toml`
let board : Board = 1

///|
let font : Ref[@firefly.Font] = Ref::new(@firefly.Font::default())

///|
let players : Array[Player] = Array::new(capacity=4)

///|
struct Player {
peer : Peer
name : Bytes
mut curr : Int16
mut best : Int16
}

///|
/// boot is only called once, after all the memory is initialized.
pub fn boot() -> Unit {
font.val = @firefly.load_file("font").unwrap().as_font()
let peers = @firefly.get_peers().to_fixed_array()
for peer in peers {
players.push(Player::{
peer,
name: @firefly.get_name(peer),
curr: 0,
best: @firefly.get_score(peer, board),
})
}
}

///|
/// update is called ~60 times per second.
pub fn update() -> Unit {
for p in players {
let btns = @firefly.read_buttons(peer=p.peer)
if btns.south {
p.curr += 1
}
if btns.east {
p.best = @firefly.add_score(p.peer, board, p.curr)
p.curr = 0
}
}
}

///|
/// render is called before updating the image on the screen.
///
/// It might be called less often than `update` if the device sees that the game
/// is slow and needs more resources.
/// This is the best place to call all drawing functions.
pub fn render() -> Unit {
@firefly.clear_screen(White)
let c = @firefly.Color::DarkBlue
for i, p in players {
let y = 10 + 10 * i
@firefly.draw_text(p.name, font.val, Point::new(10, y), c)
@firefly.draw_text(format(p.curr), font.val, Point::new(120, y), c)
@firefly.draw_text(format(p.best), font.val, Point::new(150, y), c)
}
}

///|
fn[T : Show] format(v : T) -> Bytes {
@utf8.encode(v.to_string())
}
9 changes: 9 additions & 0 deletions example/scores/moon.mod.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "example-scores",
"deps": {
"firefly/firefly": {
"path": "../.."
}
},
"preferred-target": "wasm"
}
14 changes: 14 additions & 0 deletions example/scores/moon.pkg
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {
"moonbitlang/core/encoding/utf8",
"firefly/firefly",
}

options(
"is-main": true,
link: {
"wasm": {
"export-memory-name": "memory",
"exports": [ "boot", "update", "render" ],
},
},
)
23 changes: 23 additions & 0 deletions src/graphics_font.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,26 @@ pub fn Font::char_width(self : Font) -> Int {
pub fn Font::char_height(self : Font) -> Int {
self.0[3].to_int()
}

///|
/// Render text using the this font.
///
/// Unlike in the other drawing functions, here `point` does not represent
/// the top-left corner, but instead the text baseline position
/// (such as the bottom left pixel of an underscore "`_`").
///
/// The text must be valid UTF-8. To convert String into text,
/// use [@encoding/utf8]:
///
/// ```mbt nocheck
/// let utf8 = @encoding/utf8.encode("привет, мир!")
/// ```
#inline
pub fn Font::draw(
self : Font,
text : Bytes,
point : Point,
color : Color,
) -> Unit {
draw_text(text, self, point, color)
}
7 changes: 7 additions & 0 deletions src/internal/ffi/stats.mbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
///|
/// Add the given value to the progress for the badge.
pub fn add_progress(peerID : UInt, badgeID : UInt, val : Int) -> UInt = "stats" "add_progress"

///|
/// Add the given score to the board.
pub fn add_score(peerID : UInt, boardID : UInt, val : Int) -> Int = "stats" "add_score"
78 changes: 78 additions & 0 deletions src/stats.mbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
///|
/// A badge (aka achievement) ID.
pub(all) struct Badge(Byte) derive(Eq, Compare, Hash, Default, Show)

///|
/// A board (aka score board / leader board) ID.
pub(all) struct Board(Byte) derive(Eq, Compare, Hash, Default, Show)

///|
#valtype
struct Progress {
/// How many points the player has.
done : UInt16
/// How many points the player needs to earn the badge.
goal : UInt16
} derive(Show, Eq, Default)

///|
/// True if the player got enough points to unlock the badge.
pub fn Progress::is_earned(self : Progress) -> Bool {
self.done >= self.goal
}

///|
/// Get the progress of earning the badge.
pub fn Progress::get_progress(peer : Peer, badge : Badge) -> Progress {
add_progress(peer, badge, 0)
}

///|
/// Add the given value to the progress for the badge.
///
/// May be negative if you want to decrease the progress.
/// If zero, does not change the progress.
///
/// If using `Peer::combined()`, the progress is added to every peer
/// and the returned value is the lowest progress.
pub fn add_progress(peer : Peer, badge : Badge, value : Int16) -> Progress {
@ffi.add_progress(
peer.raw.reinterpret_as_uint(),
badge.0.to_uint(),
value.to_int(),
)
|> Progress::parse
}

///|
/// Get the personal best of the player.
pub fn get_score(peer : Peer, board : Board) -> Int16 {
add_score(peer, board, 0)
}

///|
fn Progress::parse(result : UInt) -> Progress {
Progress::{ done: (result >> 16).to_uint16(), goal: result.to_uint16() }
}

///|
test "parse" {
inspect(Progress::parse((87 << 16) + 391), content="{done: 87, goal: 391}")
}

///|
/// Add the given score to the board.
///
/// May be negative if you want to decrease the progress.
/// If zero, does not change the progress.
///
/// If using `Peer::combined()`, the progress is added to every peer
/// and the returned value is the lowest progress.
pub fn add_score(peer : Peer, board : Board, value : Int16) -> Int16 {
@ffi.add_score(
peer.raw.reinterpret_as_uint(),
board.0.to_uint(),
value.to_int(),
)
|> Int16::from_int
}