moongridgym

A lightweight Gym-style environment suite for MoonBit.

moonbit
gym
gridworld
maze
rl
moon add zhaoyuanjuns/moongridgym@0.1.1
Download zip
Version
0.1.1
License
MIT
Last updated
19 hours ago
Downloads
3
README

#MoonGridGym

MoonGridGym is a lightweight Gym-style environment suite written in MoonBit. It bundles a small set of classic grid environments behind one consistent API:

  • GridWorld
  • CliffWalking
  • Maze
  • FrozenLakeLike
  • RandomMaze
  • EmptyRoom
  • FourRooms

Each environment exposes the same three operations:

  • reset()
  • step(action)
  • render()

The suite also ships with a built-in shortest-path solver and rollout helpers for quick demos and evaluation.

The goal is to provide a reusable, deterministic foundation for simulation, planning, search, offline trajectory collection, and reinforcement-learning experiments—not a one-off visual demo.

#Why this is a good OSC2026 topic

  • It is useful on its own, not just as a demo.
  • It sits in a mature area, so the scope can grow without getting narrow.
  • It includes executable benchmark, validation, replay, and dataset tooling.
  • It has fixed and seeded scenarios so results can be reproduced in CI.
  • It exposes numeric observation encoding for downstream agents.

#Quick Start

Requires MoonBit Toolchain v0.10.3 or later.

To obtain the source and run the project locally:

git clone https://github.com/zhaoyuanjuns/MoonGridGym.git cd MoonGridGym moon check moon test moon run cmd/main

To use the published module from another MoonBit project after the release is available:

moon add zhaoyuanjuns/moongridgym

The module name above matches the authenticated MoonBit account and the GitHub repository owner: zhaoyuanjuns/moongridgym.

moon check moon test moon run cmd/main

For the strict local gate, also run:

moon fmt --check moon info moon check --deny-warn moon test --deny-warn

#API

Create a scenario with one of the convenience constructors:

  • new_grid_world(seed)
  • new_cliff_walking(seed)
  • new_maze(seed)
  • new_frozen_lake_like(seed)
  • new_random_maze(seed)
  • new_empty_room(seed)
  • new_four_rooms(seed)

Then use the same interface everywhere:

let env = @moongridgym.new_grid_world(7)
let obs = env.reset()
println(env.summary())
println(obs.ascii)
let result = env.step(@moongridgym.Action::Right)
println(result.info)
println(result.observation.ascii)

#Actions

  • Up
  • Down
  • Left
  • Right
  • Stay

#Scenarios

  • GridWorld focuses on pathfinding with a simple obstacle layout.
  • CliffWalking mirrors the classic control task with a harsh failure state and a positive terminal reward when the goal is reached safely.
  • Maze is a fixed benchmark maze for deterministic examples.
  • FrozenLakeLike adds slipping behavior and holes.
  • RandomMaze generates a seeded maze for replayable experiments.
  • EmptyRoom is a completely empty 7x7 room, ideal for basic random-walk agents.
  • FourRooms is the classic Sutton's 4-rooms environment for hierarchical RL tests.

#Solver Helpers

  • shortest_path() returns a BFS plan from the current agent position.
  • route_string() formats that plan as a readable action sequence.
  • rollout(actions) runs a batch of actions and summarizes the episode.
  • auto_solve() runs the shortest-path plan when one exists.

#Evaluation and data helpers

  • benchmark(seed, episodes) evaluates planner, greedy, and seeded-random policies across all seven scenarios.
  • validate_all(seed) checks reset determinism, reachability, and solvability.
  • collect_episode(...) returns replayable transitions and to_csv() exports an offline-RL-friendly dataset without external dependencies.
  • encode() provides a numeric board representation and checksum.
  • action_checks() and legal_actions() expose boundary and hazard behavior.
  • replay_check(...) detects hidden-state or random-seed regressions.
  • quality_report, contracts_report, and release_gate produce CI-friendly acceptance evidence.

The command-line example prints a scenario summary, a route, a reference rollout, and the local release gate. The library itself remains dependency free and can be embedded in another MoonBit package.

#Reproducible benchmark

let rows = @moongridgym.benchmark(2026, 5)
let report = @moongridgym.benchmark_report(2026, 5)
let data = @moongridgym.collect_episode(
@moongridgym.ScenarioKind::RandomMaze,
2026,
@moongridgym.PolicyKind::ShortestPath,
512,
)
println(data.summary())
println(data.to_csv())

The seed is part of every benchmark and dataset record. RandomMaze is generated from the seed, while FrozenLakeLike uses the seed for its slip sequence. A repeated seed and action sequence must produce identical replay results; this is covered by the test suite.

#Project boundaries

In scope: discrete 2-D grid environments, deterministic and seeded stochastic transitions, rendering, shortest-path planning, baseline policy evaluation, trajectory capture, numeric encoding, and validation utilities.

Out of scope: neural-network training, external simulators, network services, and a claim of Gymnasium API binary compatibility. These boundaries keep the package portable while leaving clear extension points for future MoonBit agents.

#Current engineering evidence

  • MoonBit is the primary implementation language.
  • The repository contains more than 3,000 lines of maintained .mbt source and tests, including the environment core, planning, benchmark harness, dataset export, replay checks, and acceptance contracts.
  • Seven scenarios, four policy families, seeded benchmark runs, boundary checks, deterministic replay checks, and 36 automated tests are included.
  • CI runs formatting, package info, warning-denied checks, tests, and the runnable example.

#Repository policy

  • MoonBit is the primary implementation language.
  • The code in this repository is original and written from scratch.
  • No upstream source code is copied into this project.
  • If you later port or reference another project, list the upstream source, license, and scope of reuse here before submission.

#Official Mirrors

#Contribution Rules

  • The default branch is master.
  • The commit history is authored by a single contributor account.
  • No virtual or secondary contributors are included.

#Contest readiness checklist

  • Public repository
  • Clear README
  • MIT license
  • Runnable example
  • Automated tests
  • GitHub Actions CI
  • Mooncakes.io publication before final submission

#Mooncakes.io release checklist

Publishing is intentionally not performed by local development commands. After the repository owner authorizes the external release, verify the default branch, confirm the final GitHub/GitLink mirrors contain the same commit, and then publish the module named zhaoyuanjuns/moongridgym from moon.mod. The acceptance package should retain the published version, package page, and the exact commit used for publication as evidence.

#Notes for submission

The OSC2026 guide requires the repository to be public, readable, and actively maintained. It also expects the acceptance materials to show the repository link, README, CI, tests, and Mooncakes publication.

The repository field in moon.mod is already set to the GitHub project URL. Do not publish to GitHub, GitLink, or Mooncakes.io from an unreviewed local working tree.

#
Action

pub(all) enum Action {
Up
Down
Left
Right
Stay
}

Actions shared by every environment.

#
ActionCheck

pub struct ActionCheck {
action : String
legal : Bool
moves : Bool
reaches_goal : Bool
lands_in_hazard : Bool
reason : String
}

Summary of an action's immediate safety without changing the environment.

#
ActionHistogram

pub struct ActionHistogram {
up : Int
down : Int
left : Int
right : Int
stay : Int
total : Int
}

Counted action distribution for a collected trajectory.

#
BenchmarkRow

pub struct BenchmarkRow {
scenario : String
policy : String
episodes : Int
successes : Int
total_steps : Int
total_reward : Int
min_steps : Int
max_steps : Int
reachable : Int
planned_steps : Int
}

Aggregate result for one scenario and policy pair.

#
BoundaryScore

pub struct BoundaryScore {
scenario : String
tested_actions : Int
blocked_actions : Int
hazard_actions : Int
goal_actions : Int
stable : Bool
}

Boundary and regression counters used by release automation.

#
ContractResult

pub struct ContractResult {
name : String
passed : Bool
detail : String
}

A public contract result for downstream packages and release scripts.

#
EncodedObservation

pub struct EncodedObservation {
width : Int
height : Int
agent_x : Int
agent_y : Int
goal_x : Int
goal_y : Int
step : Int
done : Bool
cells : Array[Int]
}

A rectangular observation encoding for agents that prefer numeric input.

#
EncodedObservation::checksum

fn EncodedObservation::checksum(self : EncodedObservation) -> Int

#
EpisodeDataset

pub struct EpisodeDataset {
scenario : String
seed : Int
transitions : Array[Transition]
total_reward : Int
success : Bool
}

An in-memory episode dataset. It intentionally uses plain arrays so it is easy to export, inspect, and feed into another MoonBit package.

#
EpisodeDataset::length

fn EpisodeDataset::length(self : EpisodeDataset) -> Int

#
EpisodeDataset::summary

fn EpisodeDataset::summary(self : EpisodeDataset) -> String

#
EpisodeDataset::to_csv

fn EpisodeDataset::to_csv(self : EpisodeDataset) -> String

#
EpisodeScore

pub struct EpisodeScore {
scenario : String
policy : String
seed : Int
steps : Int
reward : Int
success : Bool
terminated : Bool
truncated : Bool
reachable : Int
planned_steps : Int
}

One measured episode. The structure is deliberately small so callers can store many records without depending on a logging framework.

#
EpisodeStats

pub struct EpisodeStats {
steps : Int
reward_sum : Int
terminated : Bool
truncated : Bool
final_render : String
info : String
}

Summary information for a rollout or evaluation run.

#
GridGym

pub struct GridGym {
kind : ScenarioKind
initial_seed : Int
seed : Int
width : Int
height : Int
start_x : Int
start_y : Int
goal_x : Int
goal_y : Int
agent_x : Int
agent_y : Int
step_count : Int
step_limit : Int
done : Bool
board : Array[Int]
}

#
GridGym::action_checks

fn GridGym::action_checks(self : GridGym) -> Array[ActionCheck]

#
GridGym::auto_solve

fn GridGym::auto_solve(self : GridGym) -> EpisodeStats

#
GridGym::encode

fn GridGym::encode(self : GridGym) -> EncodedObservation

#
GridGym::legal_actions

fn GridGym::legal_actions(self : GridGym) -> Array[Action]

#
GridGym::reachable_cells

fn GridGym::reachable_cells(self : GridGym) -> Int

#
GridGym::render

fn GridGym::render(self : GridGym) -> String

#
GridGym::reset

fn GridGym::reset(self : GridGym) -> Observation

#
GridGym::rollout

fn GridGym::rollout(self : GridGym, actions : Array[Action]) -> EpisodeStats

#
GridGym::route_string

fn GridGym::route_string(self : GridGym) -> String

#
GridGym::shortest_path

fn GridGym::shortest_path(self : GridGym) -> PathPlan

#
GridGym::step

fn GridGym::step(self : GridGym, action : Action) -> StepResult

#
GridGym::summary

fn GridGym::summary(self : GridGym) -> String

#
Observation

pub struct Observation {
kind : String
ascii : String
agent_x : Int
agent_y : Int
step_count : Int
done : Bool
}

#
PathPlan

pub struct PathPlan {
found : Bool
steps : Int
actions : Array[Action]
}

A path plan returned by the built-in grid solver.

#
PlanExecution

pub struct PlanExecution {
found : Bool
planned_steps : Int
executed_steps : Int
reached_goal : Bool
reward : Int
exact : Bool
}

Compare planner length with an executed trajectory.

#
PolicyComparison

pub struct PolicyComparison {
scenario : String
seed : Int
best_policy : String
best_reward : Int
planner_reward : Int
greedy_reward : Int
random_reward : Int
stay_reward : Int
planner_success : Bool
greedy_success : Bool
random_success : Bool
stay_success : Bool
}

Per-policy comparison used to choose a baseline before adding a learner.

#
PolicyKind

pub(all) enum PolicyKind {
ShortestPath
GreedyGoal
SeededRandom
Stay
}

Policy families used by the local benchmark harness.

#
QualityScore

pub struct QualityScore {
scenarios : Int
valid_scenarios : Int
solvable_scenarios : Int
deterministic_scenarios : Int
total_reachable : Int
total_planned_steps : Int
passed : Bool
}

A simple scorecard for release readiness. These checks are intentionally executable so CI and maintainers can reuse the same acceptance evidence.

#
ReplayComparison

pub struct ReplayComparison {
same_observation : Bool
same_reward : Bool
same_terminal_flags : Bool
compared_steps : Int
mismatch_count : Int
}

Difference between two deterministic runs of the same environment.

#
ScenarioKind

pub(all) enum ScenarioKind {
GridWorld
CliffWalking
Maze
FrozenLakeLike
RandomMaze
EmptyRoom
FourRooms
}

The bundled scenario kinds.

#
ScenarioMetadata

pub struct ScenarioMetadata {
kind : String
description : String
width : Int
height : Int
step_limit : Int
supports_stochasticity : Bool
recommended_use : String
}

Static metadata helps applications build scenario pickers without hard-coding descriptions outside the library.

#
SnapshotBatch

pub struct SnapshotBatch {
seed : Int
values : Array[String]
checksum : Int
}

A batch of deterministic snapshots for golden-file style testing.

#
StepResult

pub struct StepResult {
observation : Observation
reward : Int
terminated : Bool
truncated : Bool
info : String
}

#
TrainingCurve

pub struct TrainingCurve {
scenario : String
policy : String
rewards : Array[Int]
successes : Array[Bool]
cumulative_reward : Int
}

A compact training curve with reward and success observations per episode.

#
TrainingCurve::report

fn TrainingCurve::report(self : TrainingCurve) -> String

#
TrainingCurve::successes

fn TrainingCurve::successes(self : TrainingCurve) -> Int

#
Transition

pub struct Transition {
scenario : String
seed : Int
index : Int
x : Int
y : Int
action : String
reward : Int
next_x : Int
next_y : Int
terminated : Bool
truncated : Bool
info : String
}

A compact transition record suitable for offline RL and regression data.

#
ValidationReport

pub struct ValidationReport {
scenario : String
valid : Bool
reachable : Int
planned_steps : Int
checks : Int
failures : Int
message : String
}

Validation result used by CI and release checks.

#
action_name

fn action_name(action : Action) -> String

#
action_roundtrip_count

fn action_roundtrip_count() -> Int

Verify that all action names have a stable, documented representation.

#
action_table

fn action_table() -> Array[String]

Return a stable action table for consumers that serialize actions by code.

#
all_contracts_pass

fn all_contracts_pass(seed : Int) -> Bool

#
all_planner_regrets

fn all_planner_regrets(seed : Int, policy : PolicyKind) -> Array[Int]

#
all_plans_valid

fn all_plans_valid(seed : Int) -> Bool

#
available_actions

fn available_actions() -> Array[String]

#
available_policies

fn available_policies() -> Array[String]

Return a compact list of all supported policy names for clients building a command-line or UI selector.

#
available_scenarios

fn available_scenarios() -> Array[String]

#
benchmark

fn benchmark(seed : Int, episodes : Int) -> Array[BenchmarkRow]

Evaluate every bundled scenario using the deterministic planner and two intentionally weaker baselines. Seeds make the output reproducible.

#
benchmark_report

fn benchmark_report(seed : Int, episodes : Int) -> String

#
boundary_report

fn boundary_report(seed : Int) -> String

Exercise all five actions from the initial state and return the outcome counters. This explicitly covers boundary, wall, stay, and hazard paths.

#
boundary_score

fn boundary_score(kind : ScenarioKind, seed : Int) -> BoundaryScore

#
collect_episode

fn collect_episode(kind : ScenarioKind, seed : Int, policy : PolicyKind, max_steps : Int) -> EpisodeDataset

Collect a replayable trajectory using any action policy.

#
collect_reference_dataset

fn collect_reference_dataset(seed : Int) -> Array[EpisodeDataset]

Collect one deterministic planner trajectory for every bundled scenario.

#
compare_policies

fn compare_policies(kind : ScenarioKind, seed : Int) -> PolicyComparison

#
contract_dataset

fn contract_dataset(kind : ScenarioKind, seed : Int) -> ContractResult

#
contract_render

fn contract_render(kind : ScenarioKind, seed : Int) -> ContractResult

#
contract_replay

fn contract_replay(kind : ScenarioKind, seed : Int) -> ContractResult

#
contract_reset

fn contract_reset(kind : ScenarioKind, seed : Int) -> ContractResult

#
contract_solver

fn contract_solver(kind : ScenarioKind, seed : Int) -> ContractResult

#
contract_step_progress

fn contract_step_progress(kind : ScenarioKind, seed : Int) -> ContractResult

#
contracts_for

fn contracts_for(kind : ScenarioKind, seed : Int) -> Array[ContractResult]

#
contracts_passed

fn contracts_passed(kind : ScenarioKind, seed : Int) -> Bool

#
contracts_report

fn contracts_report(seed : Int) -> String

#
execute_all_plans

fn execute_all_plans(seed : Int) -> Array[PlanExecution]

#
execute_plan

fn execute_plan(kind : ScenarioKind, seed : Int) -> PlanExecution

#
histogram

fn histogram(dataset : EpisodeDataset) -> ActionHistogram

#
histogram_report

fn histogram_report(dataset : EpisodeDataset) -> String

#
mean_reward

fn mean_reward(kind : ScenarioKind, policy : PolicyKind, seed : Int, episodes : Int) -> Int

Compute the mean reward using integer hundredths, avoiding floating-point dependencies in small embedded targets.

#
new

fn new(kind : ScenarioKind, seed : Int) -> GridGym

#
new_cliff_walking

fn new_cliff_walking(seed : Int) -> GridGym

#
new_empty_room

fn new_empty_room(seed : Int) -> GridGym

#
new_four_rooms

fn new_four_rooms(seed : Int) -> GridGym

#
new_frozen_lake_like

fn new_frozen_lake_like(seed : Int) -> GridGym

#
new_grid_world

fn new_grid_world(seed : Int) -> GridGym

#
new_maze

fn new_maze(seed : Int) -> GridGym

#
new_random_maze

fn new_random_maze(seed : Int) -> GridGym

#
normalized_episode_count

fn normalized_episode_count(episodes : Int) -> Int

Reject unsafe benchmark arguments while retaining a total, predictable API.

#
normalized_step_limit

fn normalized_step_limit(limit : Int) -> Int

#
plan_is_valid

fn plan_is_valid(kind : ScenarioKind, seed : Int) -> Bool

Verify a plan remains valid when executed one action at a time.

#
planner_regret

fn planner_regret(kind : ScenarioKind, policy : PolicyKind, seed : Int) -> Int

A conservative regret estimate relative to the built-in planner.

#
policy_comparison_matrix

fn policy_comparison_matrix(seed : Int) -> Array[PolicyComparison]

Produce a comparison matrix with one row per scenario.

#
policy_comparison_report

fn policy_comparison_report(seed : Int) -> String

#
project_status

fn project_status(seed : Int) -> String

#
quality_report

fn quality_report(seed : Int) -> String

#
quality_score

fn quality_score(seed : Int) -> QualityScore

Run structural and behavioral checks across the reference scenario set.

#
release_gate

fn release_gate(seed : Int) -> String

A short release gate message consumed by the example program and CI logs.

#
replay_check

fn replay_check(kind : ScenarioKind, seed : Int, actions : Array[Action]) -> ReplayComparison

Replay an action sequence twice from the same seed and compare every result. This catches accidental hidden state and stochastic reset bugs.

#
run_policy

fn run_policy(env : GridGym, policy : PolicyKind, seed : Int, max_steps : Int) -> EpisodeScore

Run a policy with a hard safety cap. The cap prevents a faulty policy from hanging a benchmark runner even when an environment is misconfigured.

#
scenario_catalog

fn scenario_catalog(seed : Int) -> Array[ScenarioMetadata]

#
scenario_catalog_report

fn scenario_catalog_report(seed : Int) -> String

#
scenario_name

fn scenario_name(kind : ScenarioKind) -> String

#
scenario_table

fn scenario_table() -> Array[String]

Return a stable scenario table for metadata and dataset headers.

#
snapshot

fn snapshot(kind : ScenarioKind, seed : Int) -> String

A compact benchmark row for a single seed, useful for snapshot tests.

#
snapshot_batch

fn snapshot_batch(seed : Int) -> SnapshotBatch

#
snapshot_report

fn snapshot_report(seed : Int) -> String

#
success_rate

fn success_rate(kind : ScenarioKind, policy : PolicyKind, seed : Int, episodes : Int) -> Int

Measure how often a baseline reaches the target within its safety cap.

#
total_regret

fn total_regret(seed : Int, policy : PolicyKind) -> Int

#
train_curve

fn train_curve(kind : ScenarioKind, policy : PolicyKind, seed : Int, episodes : Int) -> TrainingCurve

#
validate

fn validate(kind : ScenarioKind, seed : Int) -> ValidationReport

Validate invariants that matter before a scenario is used as benchmark data: reset determinism, non-empty reachability, and a solvable goal.

#
validate_all

fn validate_all(seed : Int) -> Array[ValidationReport]

#
validation_report

fn validation_report(seed : Int) -> String

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

© 2026 mooncakes.io