Compare commits

...

5 commits

Author SHA1 Message Date
07442dd0f0 test(snippets): promote 152 hand-written docs examples into the gated suite
Extends the docs-as-tests pipeline to the hand-written pages. A multi-agent
workflow (tools/wf_docs_rewrite.js) fixed every cookbook/objects/tutorial/
features/templates page against the live 0.2.8-dev API and extracted every
complete runnable example into tests/snippets/131-282, each validated through
_harness.py and stamped by stamp_snippets.py.

run_tests.py already discovers tests/snippets/, so these 152 are now permanently
gated: a future API break that invalidates a documented example fails the suite.
Suite is 282/282; `make check-snippets` clean.
2026-07-15 00:26:38 -04:00
3a85dd818d fix(snippets): drop the commit hash from verified -- it churned all 130 every commit
The `# mcrf:` stamp recorded verified=<version>@<commit>, so every commit rewrote
the hash in all 130 snippet headers -- a guaranteed 130-file diff carrying no signal
git history doesn't already have, and one `--check` ignores anyway. The version alone
is stable within a dev cycle ("0.2.8-dev") and becomes the release marker at a tag
("0.2.9"), which is the whole point of the field: freezing a version into the docs
site's history, not pinning a commit.

verified is now version-only. The stamper is idempotent again: a second run on a
clean tree changes zero files. This commit is the one-time normalization that strips
the @hash suffix from the existing headers.

Found while cleaning up after a make release-docs run left all 130 snippets dirty
with nothing but hash churn.
2026-07-14 19:14:49 -04:00
50d6f1e5d2 build(docs): chain the release documentation pipeline into make release-docs
Every piece of this pipeline already existed -- generate_all_docs.sh,
generate_api_manifest.py, api_delta.py (with its --site-dir page resolver and its
--format gitea checklist), and the site's build_reference.py / build_library.py,
which already knew how to read this repo. Nothing chained them. So the site's
generated reference sat 26 engine commits behind, stamped verified_commit b6720f6,
and nothing noticed or could have noticed.

  make docs             regenerate man page, API reference, stubs, manifest
  make stamp-snippets   re-run the docs snippets; stamp what actually happened
  make check-snippets   CI gate: fail on a broken snippet or a stale stamp
  make api-delta        what changed, and which site pages that obligates
  make release-docs     all of it, for cutting a tag

release-docs deliberately does not commit and does not touch the hand-written
pages. It refreshes what is derived, proves the published samples still run,
rebuilds the site's generated half against this engine, and prints the checklist of
curated pages the API change obliges you to revisit. The judgment stays with a
human; only the mechanical parts are mechanical.

Two ref-shaped traps, both hit while building this:

  * BASE_REF and RELEASE_REF are different refs and I had conflated them. The delta
    is measured FROM the previous release; the site's source links are pinned TO the
    tag being cut. One variable could not be both.

  * api_delta now distinguishes "this ref predates the manifest" from "this ref is
    broken". 0.2.8 is older than the manifest infra (54624b3), so it has no baseline
    -- every object would read as "added", which is noise, not a delta. It says so
    and exits clean. An unknown ref is still an error.

stamp_snippets --check compares only the CLAIMS a header makes (status, objects),
never `verified`. That field records the engine a snippet was last confirmed
against -- provenance, not a freshness assertion. Had --check demanded it equal
HEAD, every commit would have invalidated all 130 stamps and the gate would have
cried wolf until people stopped listening.

Running it regenerated all 52 reference pages against the current engine, which
cleared every stale UIGrid / compute_astar / gridstate mention the docs audit found
in the generated tree -- the engine's docstrings had been right for weeks; the site
had simply never been rebuilt.
2026-07-14 18:33:31 -04:00
a46667df6f test(snippets): the docs site's 130 code samples are now part of the test suite
The samples published on mcrogueface.github.io were executed by nothing at all.
Each carried a machine-readable header -- objects=[...] verified=0.2.8@b6720f6
status=ok -- and every one of those fields was hand-typed. 130 snippets asserted
"status=ok" while no run had ever confirmed it. This is the same failure mode as
the 82 rotted tests in 112f357, at a larger scale and with no gate whatsoever.

The samples now live here, in the engine repo, so that breaking the API breaks the
build. The site pulls them from tests/snippets/ rather than keeping a copy to rot
in parallel (build_library.py, site-side).

They are display scripts: each builds a scene and stops. They deliberately do NOT
call sys.exit(), because they must stay copy-pasteable -- a reader who pastes one
into their game should get a running scene, not an interpreter that quits. Since
#350 that also makes them un-runnable on their own, so _harness.py is chained as a
second --exec to supply the ending the snippet must not have. Chaining rather than
exec()-ing the source is deliberate: the snippet travels the same path a real
user's script travels, and nothing has to read the file -- which is how the first
draft of this harness tripped over #378 and mistook three healthy snippets for
broken ones.

A snippet passes only if it runs clean AND leaves a scene with something in it.
Not raising is not enough: a sample whose body silently no-ops still renders as
code on the docs site, and would still be wrong.

tools/stamp_snippets.py makes the header a measurement instead of a claim: status
comes from an actual run, verified from the engine that ran it, and objects is
derived from the source intersected with what the engine exports -- so a snippet
that starts using a new type gets tagged for it without anyone remembering to. It
immediately found tags the humans had missed (062 uses Transition, Key and
InputState; the hand-written header listed none of them). --check is the CI gate.

The gate earned its keep on the first run, catching #379 (fixed here): configuring
default_transition before the first scene activation made the engine transition
FROM the internal "uitest" bootstrap scene, which is not a PyScene and has no
Python wrapper -- so mcrfpy.current_scene reported None for the transition's full
duration, immediately after the user had assigned it. A transition with nothing
meaningful to transition from now changes scene immediately. Scene-to-scene
transitions still report the outgoing scene until they complete.

Suite: 468/468 (336 + 130 snippets + 2 regressions).

closes #379
2026-07-14 18:29:37 -04:00
43a6cc376b fix(engine): UTF-8 filesystem encoding, --run-forever in --help, honest step() docs
Three documentation-adjacent defects, found while auditing what the July bugfix
batch obliged the docs to say.

#378 -- init_python_with_config(), the init path main.cpp actually uses, did no
pre-initialization at all. PyPreConfig.utf8_mode was never enabled, so the
filesystem encoding fell back to ASCII while sys.getdefaultencoding() and the
locale both reported UTF-8. open() with no explicit encoding= therefore could not
read a UTF-8 file:

    open("notes.py").read()
    # UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2

In any normal CPython 3, open() defaults to UTF-8. This broke any script reading a
data file, a save file, or its own source. init_python() -- the other init path --
had always set utf8_mode = 1; only the live path was missing it.

This also corrects the long-standing folklore that "--exec scripts must be
ASCII-only". They need not be, and never did: the C++ side reads the file and hands
the bytes to Python, which parses them as UTF-8 per PEP 3120. The folklore was
pointing at open(), via harnesses that read scripts themselves.

--run-forever (#350) parsed but was absent from print_help(), which lists every
other McRogueFace flag. The only place a user could learn it existed was the
runtime error printed after their script had already failed. CLI flags are not in
the API manifest, so nothing flagged the omission.

mcrfpy.step()'s docstring still read "Advance simulation time" -- accurate until
30abb0b made step() a full simulation frame (scene update, Python Scene.update(),
timers, animations, transition completion, frame metrics, currentFrame++). It now
says so, says why render and input are deliberately excluded, and states the
sys.exit() requirement that --run-forever exists to waive. The most-changed
semantic in the batch had the least-changed documentation.

closes #378
2026-07-14 18:21:19 -04:00
294 changed files with 26194 additions and 7 deletions

View file

@ -96,6 +96,78 @@ install-hooks:
echo "Linked $$hooks_dir/$$name -> $$hook"; \
done
# ---------------------------------------------------------------------------
# Documentation / release
#
# Every piece of the docs pipeline already existed; nothing chained them, so the
# site drifted 26 engine commits behind without anything noticing. These targets
# are the chain.
#
# SITE_DIR is the mcrogueface.github.io checkout. Override if yours lives elsewhere:
# make release-docs SITE_DIR=/path/to/mcrogueface.github.io
# ---------------------------------------------------------------------------
SITE_DIR ?= $(CURDIR)/../mcrogueface.github.io
# Two DIFFERENT refs, easily conflated:
#
# BASE_REF the PREVIOUS release. The API delta is measured against it, to answer
# "what changed, and which pages does that oblige us to revisit?"
# RELEASE_REF the tag being CUT. The site pins its source links to it, which is what
# freezes this version into the site's history.
#
# When cutting a release, set both:
# make release-docs BASE_REF=0.2.8 RELEASE_REF=0.2.9
#
# Note: BASE_REF must carry api/manifest.json. The manifest infrastructure landed in
# 54624b3, so tags older than that (0.2.8 included) have no baseline to diff against --
# api_delta will say so rather than inventing one.
BASE_REF ?= $(shell git describe --tags --abbrev=0 2>/dev/null)
RELEASE_REF ?= $(shell git describe --tags --always --abbrev=0 2>/dev/null)
# Regenerate everything derived from the compiled module: man page, API reference
# (HTML + Markdown), type stubs, and the tracked api/manifest.json.
docs: linux
@./tools/generate_all_docs.sh
@./build/mcrogueface --headless --exec ../tools/generate_api_manifest.py
# Re-run every docs snippet and stamp its `# mcrf:` header with what actually
# happened (status from the run, verified from the engine, objects from the source).
stamp-snippets: linux
@python3 tools/stamp_snippets.py
# CI gate: fail if any snippet is broken or its stamp is stale. Writes nothing.
check-snippets: linux
@python3 tools/stamp_snippets.py --check
# What changed in the API since the last release, and which site pages that
# obligates you to update (resolved via each page's `mcrf.objects` frontmatter).
api-delta:
@python3 tools/api_delta.py $(BASE_REF) . --site-dir $(SITE_DIR) --format md
# The full release documentation pass. Run this when cutting a tag: it refreshes
# everything the engine derives, proves the published samples still run, rebuilds
# the site's generated reference + snippet library against this engine, and prints
# the checklist of hand-written pages the API change obligates you to revisit.
#
# Deliberately NOT automatic: it does not commit, and it does not touch the
# hand-written pages. It tells you what it found and leaves the judgment to you.
release-docs: docs stamp-snippets
@echo ""
@echo "=== Regenerating the site against this engine (SITE_DIR=$(SITE_DIR)) ==="
@test -d "$(SITE_DIR)" || { echo "SITE_DIR not found: $(SITE_DIR)"; exit 1; }
@cd "$(SITE_DIR)" && python3 tools/build_reference.py
@cd "$(SITE_DIR)" && python3 tools/build_library.py --ref $(RELEASE_REF)
@echo ""
@echo "=== API delta $(BASE_REF) -> now, and the pages it obligates ==="
@python3 tools/api_delta.py $(BASE_REF) . --site-dir $(SITE_DIR) --format md
@echo ""
@echo "Site pinned to RELEASE_REF=$(RELEASE_REF); delta measured from BASE_REF=$(BASE_REF)."
@echo "Next: review the delta above, update the hand-written pages it names,"
@echo " then commit both repos. For a Gitea checklist issue instead, run:"
@echo " python3 tools/api_delta.py $(BASE_REF) . --site-dir $(SITE_DIR) --format gitea"
.PHONY: docs stamp-snippets check-snippets api-delta release-docs
# Debug and sanitizer targets
debug:
@echo "Building McRogueFace with debug Python (pydebug assertions)..."

File diff suppressed because one or more lines are too long

View file

@ -184,6 +184,8 @@ void CommandLineParser::print_help() {
<< "McRogueFace specific options:\n"
<< " --exec file : execute script before main program (can be used multiple times)\n"
<< " --headless : run without creating a window (implies --audio-off)\n"
<< " --run-forever : keep running after --exec scripts finish, instead of\n"
<< " exiting (for a long-lived headless process)\n"
<< " --audio-off : disable audio\n"
<< " --audio-on : enable audio (even in headless mode)\n"
<< " --screenshot [path] : take a screenshot in headless mode\n"

View file

@ -257,12 +257,24 @@ void GameEngine::changeScene(std::string sceneName, TransitionType transitionTyp
return;
}
if (transitionType == TransitionType::None || duration <= 0.0f)
// #379: a transition needs something to transition FROM. Until the first Python
// scene is activated the engine sits on the internal "uitest" bootstrap scene, which
// is not a PyScene and has no Python wrapper -- so mcrfpy.current_scene reports None
// for it. Because current_scene reports the OUTGOING scene until a transition
// completes, fading from the bootstrap left current_scene as None for the whole
// transition, right after the user had assigned it. Nothing meaningful to transition
// from means change now.
const bool have_outgoing_scene =
!scene.empty() &&
scenes.find(scene) != scenes.end() &&
dynamic_cast<PyScene*>(scenes[scene].get()) != nullptr;
if (transitionType == TransitionType::None || duration <= 0.0f || !have_outgoing_scene)
{
// Immediate scene change
std::string old_scene = scene;
scene = sceneName;
// Trigger Python scene lifecycle events
McRFPy_API::triggerSceneChange(old_scene, sceneName);
}

View file

@ -330,11 +330,22 @@ static PyMethodDef mcrfpyMethods[] = {
{"step", McRFPy_API::_step, METH_VARARGS,
MCRF_METHOD(mcrfpy, step,
MCRF_SIG("(dt: float = None)", "float"),
MCRF_DESC("Advance simulation time (headless mode only)."),
MCRF_DESC("Run one full simulation frame (headless mode only). Advances the scene "
"update, timers, Python Scene.update() callbacks, animations, and scene "
"transitions (including their completion), records frame-time metrics, and "
"increments the frame counter -- everything a windowed frame does except "
"render and input.")
MCRF_ARGS_START
MCRF_ARG("dt", "Time to advance in seconds. If None, advances to the next scheduled event (timer/animation).")
MCRF_RETURNS("float: Actual time advanced in seconds. Returns 0.0 in windowed mode.")
MCRF_NOTE("In windowed mode, this is a no-op and returns 0.0. Use this for deterministic simulation control in headless/testing scenarios.")
MCRF_NOTE("Rendering is deliberately excluded: it is orthogonal to the clock and costs "
"zero simulation time, so a screenshot draws arbitrary state without time "
"passing. In windowed mode this is a no-op returning 0.0. Timers run on "
"simulation time -- explicit, deterministic time is the point of driving a "
"headless test with step().")
MCRF_NOTE("A headless --exec script must end by calling sys.exit(), because step() is "
"the only headless clock: without it the engine cannot advance itself. Pass "
"--run-forever for a long-lived headless process that drives itself.")
)},
{"exit", McRFPy_API::_exit, METH_NOARGS,
MCRF_METHOD(mcrfpy, exit,
@ -1076,6 +1087,20 @@ PyStatus McRFPy_API::init_python_with_config(const McRogueFaceConfig& config)
}
PyStatus status;
// #378: pre-initialize in UTF-8 mode. Without this, the filesystem encoding falls
// back to ASCII -- so open() with no explicit encoding= cannot read a UTF-8 file,
// even though sys.getdefaultencoding() and the locale both say UTF-8. This is the
// init path main.cpp uses; init_python() above has always done this correctly.
PyPreConfig preconfig;
PyPreConfig_InitIsolatedConfig(&preconfig);
preconfig.utf8_mode = 1;
status = Py_PreInitialize(&preconfig);
if (PyStatus_Exception(status)) {
return status;
}
PyConfig pyconfig;
PyConfig_InitIsolatedConfig(&pyconfig);

View file

@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""
Issue #378: the embedded interpreter's filesystem encoding must be UTF-8.
init_python_with_config() -- the path main.cpp actually uses -- did no
pre-initialization at all, so PyPreConfig.utf8_mode was never enabled and the
filesystem encoding fell back to ASCII. sys.getdefaultencoding() said utf-8 and the
locale said UTF-8; only the filesystem encoding disagreed. (init_python(), the other
init path, had always set utf8_mode = 1 correctly.)
open() with no explicit encoding= defaults to the filesystem encoding, so it could not
read a UTF-8 file -- a degree sign was enough:
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 ...
In any normal CPython 3, open() defaults to UTF-8. This broke any script reading a data
file, a save file, or its own source.
NOT affected, despite the folklore that "--exec scripts must be ASCII-only": --exec on
a source file containing non-ASCII characters always worked. The C++ side reads the
file and hands the bytes to Python, which parses them as UTF-8 per PEP 3120. The
folklore was pointing at open(), via harnesses that read scripts themselves.
This file contains non-ASCII characters and reads itself back with a bare open(), so it
exercises both halves.
"""
import sys
failures = []
def check(cond, msg):
if not cond:
failures.append(msg)
print(f"FAIL: {msg}")
else:
print(f" ok: {msg}")
# Non-ASCII in a comment -- rotate the camera 90 degrees clockwise
# The characters that broke the docs snippets: degree, bullet
DEGREE = "\N{DEGREE SIGN}"
BULLET = "\N{BULLET}"
def test_filesystem_encoding():
enc = sys.getfilesystemencoding()
check(enc.lower().replace("-", "") == "utf8",
f"sys.getfilesystemencoding() is utf-8 (got {enc!r})")
check(sys.getdefaultencoding().lower().replace("-", "") == "utf8",
"sys.getdefaultencoding() is utf-8")
def test_source_literals_survived_the_parse():
"""If the loader had used ASCII, this file would not have parsed at all."""
check(DEGREE == "°", "a degree sign round-trips through the source")
check(BULLET == "", "a bullet round-trips through the source")
def test_open_defaults_to_utf8():
"""open() with no encoding= must read UTF-8, like any other Python 3."""
path = __file__
with open(path) as f:
text = f.read()
check("DEGREE SIGN" in text,
"open() with no encoding= read this file back without a UnicodeDecodeError")
def main():
test_filesystem_encoding()
test_source_literals_survived_the_parse()
test_open_defaults_to_utf8()
if failures:
print(f"\nFAILED ({len(failures)} checks)")
for f in failures:
print(f" - {f}")
sys.exit(1)
print("\nPASS")
sys.exit(0)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""
Issue #379: a transition into the FIRST scene must not leave current_scene as None.
While a transition runs, mcrfpy.current_scene reports the OUTGOING scene -- "current"
means "what is on screen". That is a sane convention for scene->scene. But at startup
there is no outgoing scene, so configuring default_transition before the first
activation made the engine fade from nothing, and current_scene read None for the
transition's entire duration:
mcrfpy.default_transition = mcrfpy.Transition.FADE
mcrfpy.default_transition_duration = 1.0
mcrfpy.current_scene = s
print(mcrfpy.current_scene) # None -- assigned one line ago
A transition with nothing to transition from is not a transition. changeScene() now
takes the immediate path when there is no outgoing scene.
Found by the tests/snippets/ gate: 062_scene_transition_fade.py and
063_scene_transition_slide.py both configure a transition and then activate their first
scene, which is the natural way to write that sample.
"""
import sys
import mcrfpy
failures = []
def check(cond, msg):
if not cond:
failures.append(msg)
print(f"FAIL: {msg}")
else:
print(f" ok: {msg}")
def frame():
return mcrfpy.Frame(pos=(0, 0), size=(10, 10))
def test_first_scene_with_transition_is_immediate():
"""The bug: no outgoing scene means nothing to fade from."""
s = mcrfpy.Scene("first")
s.children.append(frame())
mcrfpy.default_transition = mcrfpy.Transition.FADE
mcrfpy.default_transition_duration = 1.0
mcrfpy.current_scene = s
got = mcrfpy.current_scene
check(got is not None, "current_scene is not None right after the first activation")
check(got is not None and got.name == "first",
f"current_scene is the scene we activated (got {got!r})")
mcrfpy.step(0.016)
got = mcrfpy.current_scene
check(got is not None and got.name == "first",
"it is still the activated scene after a step")
def test_scene_to_scene_transition_still_defers():
"""The existing convention must not regress: mid-transition, current_scene is the
OUTGOING scene, and only becomes the incoming one on completion."""
a = mcrfpy.Scene("from_a")
a.children.append(frame())
b = mcrfpy.Scene("to_b")
b.children.append(frame())
mcrfpy.default_transition = mcrfpy.Transition.NONE
mcrfpy.default_transition_duration = 0.0
mcrfpy.current_scene = a
check(mcrfpy.current_scene.name == "from_a", "scene a is active with no transition")
mcrfpy.default_transition = mcrfpy.Transition.FADE
mcrfpy.default_transition_duration = 1.0
mcrfpy.current_scene = b
mcrfpy.step(0.1)
check(mcrfpy.current_scene.name == "from_a",
"mid-transition, current_scene is still the outgoing scene")
for _ in range(20):
mcrfpy.step(0.1)
check(mcrfpy.current_scene.name == "to_b",
"after the transition completes, current_scene is the incoming scene")
def main():
test_first_scene_with_transition_is_immediate()
test_scene_to_scene_transition_still_defers()
if failures:
print(f"\nFAILED ({len(failures)} checks)")
for f in failures:
print(f" - {f}")
sys.exit(1)
print("\nPASS")
sys.exit(0)
if __name__ == "__main__":
main()

View file

@ -41,7 +41,17 @@ SANITIZER_PATTERNS = [
]
# Test directories to run (in order)
TEST_DIRS = ['unit', 'integration', 'regression', 'demo']
TEST_DIRS = ['unit', 'integration', 'regression', 'demo', 'snippets']
# tests/snippets/ holds the code samples published on mcrogueface.github.io. They live
# here, in the engine repo, so that breaking the API breaks the build -- the docs site
# pulls them from here rather than keeping its own copy to rot in parallel. Before this,
# 130 snippets were executed by nothing at all and carried a hand-typed "status=ok"
# header that no run had ever verified.
#
# They are display scripts with no sys.exit(), because they must stay copy-pasteable, so
# _harness.py is chained as a second --exec to supply the ending (see its docstring).
SNIPPET_HARNESS = 'tests/snippets/_harness.py'
# #372: tests/demo/ was never run by anything, so the demo screens -- which CLAUDE.md
# points at as the canonical API-usage examples -- silently bitrotted until they could
@ -111,6 +121,10 @@ def run_test(test_path, verbose=False, timeout=DEFAULT_TIMEOUT,
cmd.extend([str(MCROGUEFACE), '--headless', '--exec', str(test_path)])
# A snippet has no ending of its own; chain the harness to supply one.
if test_path.parent.name == 'snippets':
cmd.extend(['--exec', str(TESTS_DIR / 'snippets' / '_harness.py')])
try:
result = subprocess.run(
cmd,
@ -171,7 +185,8 @@ def find_tests(directory):
if directory == 'demo':
# #372: allowlisted entry points only -- see DEMO_SMOKE_TESTS.
return [test_dir / name for name in DEMO_SMOKE_TESTS if (test_dir / name).exists()]
return sorted(test_dir.glob("*.py"))
# Leading underscore = support file, not a test (tests/snippets/_harness.py).
return sorted(p for p in test_dir.glob("*.py") if not p.name.startswith('_'))
def main():
verbose = '-v' in sys.argv or '--verbose' in sys.argv

View file

@ -0,0 +1,17 @@
# mcrf: objects=[Color,Frame,Scene] verified=0.2.8-dev status=ok
# Hello Frame - The simplest UI element
import mcrfpy
# Create a scene and make it active
scene = mcrfpy.Scene("hello")
mcrfpy.current_scene = scene
# Create a big, colorful frame in the center
frame = mcrfpy.Frame(
pos=(312, 234),
size=(400, 300),
fill_color=mcrfpy.Color(100, 150, 200),
outline_color=mcrfpy.Color(255, 255, 255),
outline=4.0
)
scene.children.append(frame)

View file

@ -0,0 +1,16 @@
# mcrf: objects=[Caption,Color,Scene] verified=0.2.8-dev status=ok
# Hello Caption - Display text on screen
import mcrfpy
scene = mcrfpy.Scene("hello")
mcrfpy.current_scene = scene
# Large centered text
caption = mcrfpy.Caption(
text="Hello, McRogueFace!",
pos=(512, 350)
)
caption.fill_color = mcrfpy.Color(255, 220, 100)
# Center the text by adjusting position after creation
caption.x -= caption.w / 2
scene.children.append(caption)

View file

@ -0,0 +1,19 @@
# mcrf: objects=[Caption,Scene,Sprite] verified=0.2.8-dev status=ok
# Hello Sprite - Display a textured sprite
import mcrfpy
scene = mcrfpy.Scene("hello")
mcrfpy.current_scene = scene
# Create a large sprite using default texture
sprite = mcrfpy.Sprite(
texture=mcrfpy.default_texture,
sprite_index=84, # Knight character
pos=(412, 284),
scale=12.0
)
scene.children.append(sprite)
# Add a label
label = mcrfpy.Caption(text="Sprite Index: 84", pos=(412, 550))
scene.children.append(label)

View file

@ -0,0 +1,25 @@
# mcrf: objects=[Grid,Scene] verified=0.2.8-dev status=ok
# Hello Grid - Create a tile-based grid
import mcrfpy
scene = mcrfpy.Scene("hello")
mcrfpy.current_scene = scene
# Create a grid that fills the screen
# zoom=4.0 with 16x16 texture means each tile is 64x64 pixels
# 16 tiles * 64 pixels = 1024, 12 tiles * 64 pixels = 768
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Fill with floor tiles
for y in range(12):
for x in range(16):
cell = grid.at(x, y)
cell.tilesprite = 48 # Floor tile
cell.walkable = True

View file

@ -0,0 +1,29 @@
# mcrf: objects=[Entity,Grid,Scene] verified=0.2.8-dev status=ok
# Hello Entity - Add entities to a grid
import mcrfpy
scene = mcrfpy.Scene("hello")
mcrfpy.current_scene = scene
# Create grid filling the screen
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Floor tiles
for y in range(12):
for x in range(16):
grid.at(x, y).tilesprite = 48
# Add a player entity
player = mcrfpy.Entity(
grid_pos=(8, 6),
texture=mcrfpy.default_texture,
sprite_index=84 # Knight
)
grid.entities.append(player)

View file

@ -0,0 +1,33 @@
# mcrf: objects=[Caption,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Frame Colors - Fill and outline colors
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Dark background
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
colors = [
(mcrfpy.Color(255, 100, 100), "Red"),
(mcrfpy.Color(100, 255, 100), "Green"),
(mcrfpy.Color(100, 100, 255), "Blue"),
(mcrfpy.Color(255, 255, 100), "Yellow"),
]
for i, (color, name) in enumerate(colors):
x = 112 + (i % 2) * 420
y = 134 + (i // 2) * 280
frame = mcrfpy.Frame(
pos=(x, y), size=(380, 240),
fill_color=color,
outline_color=mcrfpy.Color(255, 255, 255),
outline=3.0
)
scene.children.append(frame)
label = mcrfpy.Caption(text=name, pos=(x + 150, y + 100))
label.outline = 2
label.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(label)

View file

@ -0,0 +1,34 @@
# mcrf: objects=[Caption,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Frame Opacity - Transparency levels
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Background pattern
for i in range(20):
stripe = mcrfpy.Frame(
pos=(i * 52, 0), size=(26, 768),
fill_color=mcrfpy.Color(50, 50, 80)
)
scene.children.append(stripe)
# Frames with varying opacity
opacities = [1.0, 0.75, 0.5, 0.25]
for i, opacity in enumerate(opacities):
frame = mcrfpy.Frame(
pos=(112 + i * 220, 234), size=(180, 300),
fill_color=mcrfpy.Color(255, 150, 50),
outline=2.0,
outline_color=mcrfpy.Color(255, 255, 255),
opacity=opacity
)
scene.children.append(frame)
label = mcrfpy.Caption(
text=f"opacity={opacity}",
pos=(112 + i * 220 + 30, 550)
)
label.outline = 2
label.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(label)

View file

@ -0,0 +1,22 @@
# mcrf: objects=[Caption,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Frame Outline - Border thickness
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
outlines = [0, 2, 5, 10, 20]
for i, thickness in enumerate(outlines):
frame = mcrfpy.Frame(
pos=(92 + i * 180, 234), size=(160, 300),
fill_color=mcrfpy.Color(60, 80, 120),
outline_color=mcrfpy.Color(255, 200, 100),
outline=thickness
)
scene.children.append(frame)
label = mcrfpy.Caption(
text=f"outline={thickness}",
pos=(92 + i * 180 + 30, 550)
)
scene.children.append(label)

View file

@ -0,0 +1,41 @@
# mcrf: objects=[Caption,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Frame Children - Nested UI elements
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Parent container
parent = mcrfpy.Frame(
pos=(162, 134), size=(700, 500),
fill_color=mcrfpy.Color(40, 40, 60),
outline=3.0,
outline_color=mcrfpy.Color(100, 100, 150)
)
scene.children.append(parent)
# Child frames - positions relative to parent
child1 = mcrfpy.Frame(
pos=(20, 20), size=(200, 200),
fill_color=mcrfpy.Color(200, 80, 80)
)
parent.children.append(child1)
child2 = mcrfpy.Frame(
pos=(240, 20), size=(200, 200),
fill_color=mcrfpy.Color(80, 200, 80)
)
parent.children.append(child2)
child3 = mcrfpy.Frame(
pos=(460, 20), size=(200, 200),
fill_color=mcrfpy.Color(80, 80, 200)
)
parent.children.append(child3)
# Label
label = mcrfpy.Caption(
text="Children use parent-relative coordinates",
pos=(150, 450)
)
parent.children.append(label)

View file

@ -0,0 +1,42 @@
# mcrf: objects=[Caption,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Frame Clipping - clip_children property
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Frame WITHOUT clipping
no_clip = mcrfpy.Frame(
pos=(100, 200), size=(300, 300),
fill_color=mcrfpy.Color(60, 60, 100),
outline=2.0, outline_color=mcrfpy.Color(150, 150, 200),
clip_children=False
)
scene.children.append(no_clip)
overflow1 = mcrfpy.Frame(
pos=(150, 150), size=(200, 200),
fill_color=mcrfpy.Color(255, 100, 100)
)
no_clip.children.append(overflow1)
label1 = mcrfpy.Caption(text="clip_children=False", pos=(120, 520))
scene.children.append(label1)
# Frame WITH clipping
with_clip = mcrfpy.Frame(
pos=(524, 200), size=(300, 300),
fill_color=mcrfpy.Color(60, 60, 100),
outline=2.0, outline_color=mcrfpy.Color(150, 150, 200),
clip_children=True
)
scene.children.append(with_clip)
overflow2 = mcrfpy.Frame(
pos=(150, 150), size=(200, 200),
fill_color=mcrfpy.Color(100, 255, 100)
)
with_clip.children.append(overflow2)
label2 = mcrfpy.Caption(text="clip_children=True", pos=(544, 520))
scene.children.append(label2)

View file

@ -0,0 +1,48 @@
# mcrf: objects=[Caption,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Frame Z-Index - Control rendering order
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Dark background
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
# Create overlapping frames with different z_index
# Lower z_index = rendered first (behind)
back = mcrfpy.Frame(
pos=(262, 184), size=(300, 300),
fill_color=mcrfpy.Color(255, 100, 100),
z_index=0
)
scene.children.append(back)
middle = mcrfpy.Frame(
pos=(362, 234), size=(300, 300),
fill_color=mcrfpy.Color(100, 255, 100),
z_index=1
)
scene.children.append(middle)
front = mcrfpy.Frame(
pos=(462, 284), size=(300, 300),
fill_color=mcrfpy.Color(100, 100, 255),
z_index=2
)
scene.children.append(front)
# Labels with outlines for visibility
label1 = mcrfpy.Caption(text="z_index=0 (back)", pos=(262, 500))
label1.outline = 2
label1.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(label1)
label2 = mcrfpy.Caption(text="z_index=1 (middle)", pos=(362, 550))
label2.outline = 2
label2.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(label2)
label3 = mcrfpy.Caption(text="z_index=2 (front)", pos=(462, 600))
label3.outline = 2
label3.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(label3)

View file

@ -0,0 +1,32 @@
# mcrf: objects=[Caption,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Frame Visibility - Show and hide elements
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Create three frames
visible_frame = mcrfpy.Frame(
pos=(100, 234), size=(250, 300),
fill_color=mcrfpy.Color(100, 200, 100),
visible=True
)
scene.children.append(visible_frame)
scene.children.append(mcrfpy.Caption(text="visible=True", pos=(140, 550)))
hidden_frame = mcrfpy.Frame(
pos=(387, 234), size=(250, 300),
fill_color=mcrfpy.Color(200, 100, 100),
visible=False # This won't be displayed
)
scene.children.append(hidden_frame)
scene.children.append(mcrfpy.Caption(text="visible=False", pos=(427, 550)))
scene.children.append(mcrfpy.Caption(text="(frame exists but hidden)", pos=(397, 580)))
visible_frame2 = mcrfpy.Frame(
pos=(674, 234), size=(250, 300),
fill_color=mcrfpy.Color(100, 100, 200),
visible=True
)
scene.children.append(visible_frame2)
scene.children.append(mcrfpy.Caption(text="visible=True", pos=(714, 550)))

View file

@ -0,0 +1,41 @@
# mcrf: objects=[Caption,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Frame Name - Named elements for lookup
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Create named frames
header = mcrfpy.Frame(
pos=(112, 50), size=(800, 100),
fill_color=mcrfpy.Color(80, 80, 120),
name="header"
)
scene.children.append(header)
sidebar = mcrfpy.Frame(
pos=(112, 170), size=(200, 500),
fill_color=mcrfpy.Color(60, 100, 80),
name="sidebar"
)
scene.children.append(sidebar)
content = mcrfpy.Frame(
pos=(332, 170), size=(580, 500),
fill_color=mcrfpy.Color(100, 80, 60),
name="main_content"
)
scene.children.append(content)
# Find elements by name
found = mcrfpy.find("header")
if found:
label = mcrfpy.Caption(
text=f"Found: '{found.name}'",
pos=(20, 30)
)
found.children.append(label)
# Display all names
scene.children.append(mcrfpy.Caption(text="sidebar", pos=(170, 400)))
scene.children.append(mcrfpy.Caption(text="main_content", pos=(550, 400)))

View file

@ -0,0 +1,29 @@
# mcrf: objects=[Caption,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Frame Rotation - Rotate UI elements
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Dark background
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
rotations = [0, 15, 30, 45, 60]
for i, angle in enumerate(rotations):
frame = mcrfpy.Frame(
pos=(100 + i * 180, 300), size=(120, 120),
fill_color=mcrfpy.Color(150, 100, 200),
outline=3.0,
outline_color=mcrfpy.Color(255, 255, 255)
)
frame.rotation = angle
scene.children.append(frame)
label = mcrfpy.Caption(text=f"{angle}deg", pos=(130 + i * 180, 500))
label.outline = 2
label.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(label)
title = mcrfpy.Caption(text="Frame Rotation", pos=(420, 100))
title.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(title)

View file

@ -0,0 +1,45 @@
# mcrf: objects=[Caption,Color,Frame,Scene,Vector] verified=0.2.8-dev status=ok
# Frame Origin - Rotation pivot point
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Dark background
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
# All frames rotated 45 degrees, different origins
origins = [
((0, 0), "top-left"),
((60, 60), "center"),
((120, 0), "top-right"),
((0, 120), "bottom-left"),
]
for i, (origin, name) in enumerate(origins):
x = 150 + (i % 2) * 400
y = 180 + (i // 2) * 280
# Reference frame (no rotation)
ref = mcrfpy.Frame(
pos=(x, y), size=(120, 120),
fill_color=mcrfpy.Color(50, 50, 50),
outline=1.0,
outline_color=mcrfpy.Color(100, 100, 100)
)
scene.children.append(ref)
# Rotated frame
frame = mcrfpy.Frame(
pos=(x, y), size=(120, 120),
fill_color=mcrfpy.Color(200, 100, 150),
opacity=0.8
)
frame.rotation = 45
frame.origin = mcrfpy.Vector(origin[0], origin[1])
scene.children.append(frame)
label = mcrfpy.Caption(text=f"origin: {name}", pos=(x, y + 160))
label.outline = 2
label.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(label)

View file

@ -0,0 +1,43 @@
# mcrf: objects=[Alignment,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Frame Alignment - Automatic positioning
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Container to show alignment
container = mcrfpy.Frame(
pos=(112, 84), size=(800, 600),
fill_color=mcrfpy.Color(40, 40, 60),
outline=2.0
)
scene.children.append(container)
# Grid of alignment options (excluding CENTER to avoid margin error)
alignments = [
(mcrfpy.Alignment.TOP_LEFT, "TOP_LEFT"),
(mcrfpy.Alignment.TOP_CENTER, "TOP_CENTER"),
(mcrfpy.Alignment.TOP_RIGHT, "TOP_RIGHT"),
(mcrfpy.Alignment.CENTER_LEFT, "CENTER_LEFT"),
(mcrfpy.Alignment.CENTER_RIGHT, "CENTER_RIGHT"),
(mcrfpy.Alignment.BOTTOM_LEFT, "BOTTOM_LEFT"),
(mcrfpy.Alignment.BOTTOM_CENTER, "BOTTOM_CENTER"),
(mcrfpy.Alignment.BOTTOM_RIGHT, "BOTTOM_RIGHT"),
]
for alignment, name in alignments:
box = mcrfpy.Frame(
size=(80, 60),
fill_color=mcrfpy.Color(150, 100, 200),
align=alignment,
margin=10
)
container.children.append(box)
# Center box (no margin for CENTER)
center_box = mcrfpy.Frame(
size=(80, 60),
fill_color=mcrfpy.Color(100, 200, 150),
align=mcrfpy.Alignment.CENTER
)
container.children.append(center_box)

View file

@ -0,0 +1,26 @@
# mcrf: objects=[Caption,Color,Scene] verified=0.2.8-dev status=ok
# Caption Styling - Text colors and fonts
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Title
title = mcrfpy.Caption(text="Caption Styles", pos=(350, 80))
title.fill_color = mcrfpy.Color(255, 255, 255)
scene.children.append(title)
# Different colors
colors = [
(mcrfpy.Color(255, 100, 100), "Red Text"),
(mcrfpy.Color(100, 255, 100), "Green Text"),
(mcrfpy.Color(100, 100, 255), "Blue Text"),
(mcrfpy.Color(255, 255, 100), "Yellow Text"),
(mcrfpy.Color(255, 100, 255), "Magenta Text"),
(mcrfpy.Color(100, 255, 255), "Cyan Text"),
]
for i, (color, text) in enumerate(colors):
cap = mcrfpy.Caption(text=text, pos=(350, 180 + i * 80))
cap.fill_color = color
scene.children.append(cap)

View file

@ -0,0 +1,34 @@
# mcrf: objects=[Caption,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Caption in Frame - Text inside containers
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Card-style container with text
card = mcrfpy.Frame(
pos=(262, 184), size=(500, 400),
fill_color=mcrfpy.Color(50, 60, 80),
outline=3.0,
outline_color=mcrfpy.Color(100, 120, 160)
)
scene.children.append(card)
# Title in card
card_title = mcrfpy.Caption(text="Player Stats", pos=(180, 30))
card_title.fill_color = mcrfpy.Color(255, 220, 100)
card.children.append(card_title)
# Stats
stats = [
"Health: 100/100",
"Mana: 50/50",
"Strength: 15",
"Defense: 12",
"Speed: 18",
]
for i, stat in enumerate(stats):
line = mcrfpy.Caption(text=stat, pos=(50, 100 + i * 50))
line.fill_color = mcrfpy.Color(200, 200, 200)
card.children.append(line)

View file

@ -0,0 +1,36 @@
# mcrf: objects=[Caption,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Caption Dimensions - Text width and height
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
texts = [
"Short",
"A medium length text",
"This is a much longer piece of text to display",
]
y = 150
for text in texts:
# Create caption
cap = mcrfpy.Caption(text=text, pos=(100, y))
cap.fill_color = mcrfpy.Color(255, 255, 255)
scene.children.append(cap)
# Bounding box showing dimensions
box = mcrfpy.Frame(
pos=(100, y - 5),
size=(cap.w, cap.h + 10),
fill_color=mcrfpy.Color(0, 0, 0, 0),
outline=1.0,
outline_color=mcrfpy.Color(255, 100, 100)
)
scene.children.append(box)
# Show dimensions
dims = mcrfpy.Caption(text=f"w={cap.w:.0f}, h={cap.h:.0f}", pos=(100, y + 50))
dims.fill_color = mcrfpy.Color(150, 150, 150)
scene.children.append(dims)
y += 150

View file

@ -0,0 +1,28 @@
# mcrf: objects=[Caption,Color,Scene,Sprite] verified=0.2.8-dev status=ok
# Sprite Atlas - Browse texture atlas
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Display a grid of sprites from the default texture
title = mcrfpy.Caption(text="Default Texture Atlas (kenney_tinydungeon)", pos=(250, 30))
title.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(title)
# Show sprites 0-99 in a 10x10 grid
for row in range(10):
for col in range(10):
idx = row * 10 + col
sprite = mcrfpy.Sprite(
texture=mcrfpy.default_texture,
sprite_index=idx,
pos=(112 + col * 80, 80 + row * 65),
scale=3.0
)
scene.children.append(sprite)
# Index label
label = mcrfpy.Caption(text=str(idx), pos=(112 + col * 80 + 20, 80 + row * 65 + 48))
label.fill_color = mcrfpy.Color(120, 120, 120)
scene.children.append(label)

View file

@ -0,0 +1,24 @@
# mcrf: objects=[Caption,Color,Scene,Sprite] verified=0.2.8-dev status=ok
# Sprite Scale - Resize sprites
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scales = [1.0, 2.0, 4.0, 8.0, 16.0]
for i, scale in enumerate(scales):
sprite = mcrfpy.Sprite(
texture=mcrfpy.default_texture,
sprite_index=84, # Knight
pos=(100 + i * 180, 300),
scale=scale
)
scene.children.append(sprite)
label = mcrfpy.Caption(text=f"scale={scale}", pos=(100 + i * 180, 550))
label.fill_color = mcrfpy.Color(200, 200, 200)
scene.children.append(label)
title = mcrfpy.Caption(text="Sprite Scaling", pos=(420, 100))
title.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(title)

View file

@ -0,0 +1,32 @@
# mcrf: objects=[Caption,Color,Scene,Sprite] verified=0.2.8-dev status=ok
# Sprite Tint - Note: Sprite tinting requires shader
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Note: Direct sprite color tinting is not available
# Use opacity or shaders for color effects
opacities = [1.0, 0.8, 0.6, 0.4, 0.2]
labels = ["100%", "80%", "60%", "40%", "20%"]
for i, (opacity, label_text) in enumerate(zip(opacities, labels)):
x = 100 + (i % 3) * 300
y = 200 + (i // 3) * 280
sprite = mcrfpy.Sprite(
texture=mcrfpy.default_texture,
sprite_index=84,
pos=(x, y),
scale=8.0
)
sprite.opacity = opacity
scene.children.append(sprite)
label = mcrfpy.Caption(text=f"opacity={label_text}", pos=(x, y + 150))
scene.children.append(label)
title = mcrfpy.Caption(text="Sprite Opacity Levels", pos=(380, 80))
title.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(title)

View file

@ -0,0 +1,39 @@
# mcrf: objects=[Grid,Scene] verified=0.2.8-dev status=ok
# Grid Tiles - Different tile types
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Different terrain types
tile_types = [
(48, "Floor"), # Floor
(1, "Wall"), # Wall
(65, "Door"), # Door
(17, "Chest"), # Chest
(80, "Water"), # Water
]
# Fill grid with pattern showing different tiles
for y in range(12):
for x in range(16):
cell = grid.at(x, y)
# Border walls
if x == 0 or x == 15 or y == 0 or y == 11:
cell.tilesprite = 1
else:
# Show different tiles in sections
section = (x - 1) // 3
if section < len(tile_types):
cell.tilesprite = tile_types[section][0]
else:
cell.tilesprite = 48

View file

@ -0,0 +1,37 @@
# mcrf: objects=[Caption,Color,Grid,Scene] verified=0.2.8-dev status=ok
# Grid Walkable - Walkable vs blocked tiles
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Create maze-like pattern
for y in range(12):
for x in range(16):
cell = grid.at(x, y)
# Outer walls
if x == 0 or x == 15 or y == 0 or y == 11:
cell.tilesprite = 1 # Wall
cell.walkable = False
# Inner obstacles
elif (x + y) % 4 == 0 and x > 2 and x < 13:
cell.tilesprite = 1
cell.walkable = False
else:
cell.tilesprite = 48 # Floor
cell.walkable = True
# Legend
legend = mcrfpy.Caption(text="Walls are not walkable, floors are", pos=(350, 720))
legend.outline = 2
legend.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(legend)

View file

@ -0,0 +1,39 @@
# mcrf: objects=[Caption,Color,ColorLayer,Grid,Scene] verified=0.2.8-dev status=ok
# Grid Cell Colors - Use color layer for cell colors
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Set tiles
for y in range(12):
for x in range(16):
grid.at(x, y).tilesprite = 48
# Add a color layer for tinting
color_layer = mcrfpy.ColorLayer(z_index=1, name="tint")
grid.add_layer(color_layer)
# Create colorful gradient pattern using color layer
for y in range(12):
for x in range(16):
# Rainbow gradient based on position
r = int(255 * x / 16)
g = int(255 * y / 12)
b = int(255 * (16 - x) / 16)
color_layer.set((x, y), mcrfpy.Color(r, g, b, 180))
title = mcrfpy.Caption(text="Per-Cell Color Tinting via Layer", pos=(350, 720))
title.fill_color = mcrfpy.Color(255, 220, 100)
title.outline = 2
title.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(title)

View file

@ -0,0 +1,60 @@
# mcrf: objects=[Caption,Color,Entity,Grid,InputState,Key,Scene] verified=0.2.8-dev status=ok
# Grid Camera Center - Pan the view
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Large grid, smaller viewport
grid = mcrfpy.Grid(
grid_size=(30, 30),
texture=mcrfpy.default_texture,
pos=(212, 134),
size=(600, 500)
)
scene.children.append(grid)
# Fill with pattern to show scrolling
for y in range(30):
for x in range(30):
cell = grid.at(x, y)
if (x + y) % 2 == 0:
cell.tilesprite = 48
else:
cell.tilesprite = 49
# Center camera on specific tile (15, 15)
grid.center_camera((15, 15))
# Add marker at center
marker = mcrfpy.Entity(
grid_pos=(15, 15),
texture=mcrfpy.default_texture,
sprite_index=84
)
grid.entities.append(marker)
status = mcrfpy.Caption(text="WASD to pan camera - centered on (15, 15)", pos=(300, 660))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)
# Track camera position
camera_pos = [15, 15]
def on_key(key, action):
if action != mcrfpy.InputState.PRESSED:
return
dx, dy = 0, 0
if key == mcrfpy.Key.W: dy = -1
elif key == mcrfpy.Key.S: dy = 1
elif key == mcrfpy.Key.A: dx = -1
elif key == mcrfpy.Key.D: dx = 1
else: return
camera_pos[0] = max(0, min(29, camera_pos[0] + dx))
camera_pos[1] = max(0, min(29, camera_pos[1] + dy))
grid.center_camera((camera_pos[0], camera_pos[1]))
status.text = f"WASD to pan camera - centered on ({camera_pos[0]}, {camera_pos[1]})"
scene.on_key = on_key

View file

@ -0,0 +1,41 @@
# mcrf: objects=[Caption,Color,Entity,Frame,Grid,Scene] verified=0.2.8-dev status=ok
# Grid Zoom - Scale the grid view
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
zooms = [0.5, 1.0, 1.5, 2.0]
for i, zoom in enumerate(zooms):
x = 62 + (i % 2) * 480
y = 84 + (i // 2) * 340
grid = mcrfpy.Grid(
grid_size=(10, 8),
texture=mcrfpy.default_texture,
pos=(x, y),
size=(420, 280)
)
scene.children.append(grid)
# Fill with pattern
for gy in range(8):
for gx in range(10):
cell = grid.at(gx, gy)
cell.tilesprite = 48 if (gx + gy) % 2 == 0 else 49
# Add entity
ent = mcrfpy.Entity(grid_pos=(5, 4), texture=mcrfpy.default_texture, sprite_index=84)
grid.entities.append(ent)
# Apply zoom
grid.zoom = zoom
grid.center_camera((5, 4))
label = mcrfpy.Caption(text=f"zoom={zoom}", pos=(x + 170, y + 290))
label.outline = 2
label.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(label)
# Add background and title
scene.children.insert(0, mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))

View file

@ -0,0 +1,49 @@
# mcrf: objects=[Caption,Color,Entity,Grid,InputState,Key,Scene] verified=0.2.8-dev status=ok
# Entity Movement - Move entities on grid
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Floor tiles
for y in range(12):
for x in range(16):
grid.at(x, y).tilesprite = 48
# Create player entity
player = mcrfpy.Entity(
grid_pos=(8, 6),
texture=mcrfpy.default_texture,
sprite_index=84
)
grid.entities.append(player)
# Movement with keyboard
def on_key(key, action):
if action != mcrfpy.InputState.PRESSED:
return
x, y = player.grid_x, player.grid_y
if key == mcrfpy.Key.UP or key == mcrfpy.Key.W:
player.grid_pos = (x, y - 1)
elif key == mcrfpy.Key.DOWN or key == mcrfpy.Key.S:
player.grid_pos = (x, y + 1)
elif key == mcrfpy.Key.LEFT or key == mcrfpy.Key.A:
player.grid_pos = (x - 1, y)
elif key == mcrfpy.Key.RIGHT or key == mcrfpy.Key.D:
player.grid_pos = (x + 1, y)
scene.on_key = on_key
status = mcrfpy.Caption(text="WASD or Arrow keys to move", pos=(380, 720))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)

View file

@ -0,0 +1,44 @@
# mcrf: objects=[Caption,Color,Entity,Grid,Scene] verified=0.2.8-dev status=ok
# Multiple Entities - Several entities on one grid
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Floor tiles
for y in range(12):
for x in range(16):
grid.at(x, y).tilesprite = 48
# Different entity types
entities = [
(4, 3, 84, "Knight"), # Knight
(8, 3, 86, "Mage"), # Mage
(12, 3, 88, "Archer"), # Archer
(4, 8, 112, "Goblin"), # Goblin
(8, 8, 116, "Skeleton"),# Skeleton
(12, 8, 120, "Slime"), # Slime
]
for x, y, sprite_idx, name in entities:
entity = mcrfpy.Entity(
grid_pos=(x, y),
texture=mcrfpy.default_texture,
sprite_index=sprite_idx,
name=name
)
grid.entities.append(entity)
status = mcrfpy.Caption(text=f"Entity count: {len(grid.entities)}", pos=(420, 720))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)

View file

@ -0,0 +1,48 @@
# mcrf: objects=[Caption,Color,Entity,Grid,Scene,Timer,Vector] verified=0.2.8-dev status=ok
# Entity Draw Position - Smooth sub-tile positioning
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Floor tiles
for y in range(12):
for x in range(16):
grid.at(x, y).tilesprite = 48
# Entity at grid position with draw offset
entity = mcrfpy.Entity(
grid_pos=(8, 6),
texture=mcrfpy.default_texture,
sprite_index=84
)
grid.entities.append(entity)
# Animate the draw position for smooth movement
offset = 0.0
direction = 1
def animate_offset(timer, runtime):
global offset, direction
offset += 0.02 * direction
if offset > 0.5:
direction = -1
elif offset < -0.5:
direction = 1
entity.draw_pos = mcrfpy.Vector(8 + offset, 6)
timer = mcrfpy.Timer("move", animate_offset, 16)
status = mcrfpy.Caption(text="draw_pos allows sub-tile positioning", pos=(340, 720))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)

View file

@ -0,0 +1,38 @@
# mcrf: objects=[Caption,Color,Easing,Frame,InputState,Key,Scene,Timer] verified=0.2.8-dev status=ok
# Animation Basic - Animate position
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Dark background
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
# Create a frame to animate
frame = mcrfpy.Frame(
pos=(100, 334),
size=(150, 100),
fill_color=mcrfpy.Color(100, 150, 255)
)
scene.children.append(frame)
label = mcrfpy.Caption(text="SPACE to restart animation", pos=(380, 700))
label.outline = 2
label.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(label)
# Animate position when space pressed
def on_key(key, action):
if key == mcrfpy.Key.SPACE and action == mcrfpy.InputState.PRESSED:
frame.x = 100
frame.animate("x", 774, 2.0, mcrfpy.Easing.LINEAR)
scene.on_key = on_key
def restart_animation(timer, runtime):
frame.x = 100
frame.animate("x", 774, 2.0, mcrfpy.Easing.LINEAR)
# Auto-start animation with looping
frame.animate("x", 774, 2.0, mcrfpy.Easing.LINEAR)
loop_timer = mcrfpy.Timer("loop", restart_animation, 7000)

View file

@ -0,0 +1,42 @@
# mcrf: objects=[Caption,Color,Easing,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Easing: LINEAR - Constant speed
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Background
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
# Moving frame
frame = mcrfpy.Frame(
pos=(100, 334), size=(100, 100),
fill_color=mcrfpy.Color(255, 150, 100)
)
scene.children.append(frame)
# Start/end markers
scene.children.append(mcrfpy.Frame(
pos=(100, 300), size=(5, 170),
fill_color=mcrfpy.Color(100, 100, 100)
))
scene.children.append(mcrfpy.Frame(
pos=(824, 300), size=(5, 170),
fill_color=mcrfpy.Color(100, 100, 100)
))
_caption = mcrfpy.Caption(
text="LINEAR - Constant velocity",
pos=(380, 200))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)
def restart_animation(timer, runtime):
frame.x = 100
frame.animate("x", 824, 3.0, mcrfpy.Easing.LINEAR)
frame.animate("x", 824, 3.0, mcrfpy.Easing.LINEAR)
loop_timer = mcrfpy.Timer("loop", restart_animation, 8000) # 3s animation + 5s pause

View file

@ -0,0 +1,34 @@
# mcrf: objects=[Caption,Color,Easing,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Easing: EASE_IN - Slow start, fast end
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
frame = mcrfpy.Frame(
pos=(100, 334), size=(100, 100),
fill_color=mcrfpy.Color(100, 255, 150)
)
scene.children.append(frame)
# Markers
scene.children.append(mcrfpy.Frame(pos=(100, 300), size=(5, 170), fill_color=mcrfpy.Color(100, 100, 100)))
scene.children.append(mcrfpy.Frame(pos=(824, 300), size=(5, 170), fill_color=mcrfpy.Color(100, 100, 100)))
_caption = mcrfpy.Caption(
text="EASE_IN - Accelerates from rest",
pos=(350, 200))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)
def restart_animation(timer, runtime):
frame.x = 100
frame.animate("x", 824, 3.0, mcrfpy.Easing.EASE_IN)
frame.animate("x", 824, 3.0, mcrfpy.Easing.EASE_IN)
loop_timer = mcrfpy.Timer("loop", restart_animation, 8000)

View file

@ -0,0 +1,34 @@
# mcrf: objects=[Caption,Color,Easing,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Easing: EASE_OUT - Fast start, slow end
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
frame = mcrfpy.Frame(
pos=(100, 334), size=(100, 100),
fill_color=mcrfpy.Color(150, 100, 255)
)
scene.children.append(frame)
# Markers
scene.children.append(mcrfpy.Frame(pos=(100, 300), size=(5, 170), fill_color=mcrfpy.Color(100, 100, 100)))
scene.children.append(mcrfpy.Frame(pos=(824, 300), size=(5, 170), fill_color=mcrfpy.Color(100, 100, 100)))
_caption = mcrfpy.Caption(
text="EASE_OUT - Decelerates to rest",
pos=(350, 200))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)
def restart_animation(timer, runtime):
frame.x = 100
frame.animate("x", 824, 3.0, mcrfpy.Easing.EASE_OUT)
frame.animate("x", 824, 3.0, mcrfpy.Easing.EASE_OUT)
loop_timer = mcrfpy.Timer("loop", restart_animation, 8000)

View file

@ -0,0 +1,34 @@
# mcrf: objects=[Caption,Color,Easing,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Easing: EASE_IN_OUT - Smooth both ends
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
frame = mcrfpy.Frame(
pos=(100, 334), size=(100, 100),
fill_color=mcrfpy.Color(255, 200, 100)
)
scene.children.append(frame)
# Markers
scene.children.append(mcrfpy.Frame(pos=(100, 300), size=(5, 170), fill_color=mcrfpy.Color(100, 100, 100)))
scene.children.append(mcrfpy.Frame(pos=(824, 300), size=(5, 170), fill_color=mcrfpy.Color(100, 100, 100)))
_caption = mcrfpy.Caption(
text="EASE_IN_OUT - Smooth acceleration & deceleration",
pos=(300, 200))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)
def restart_animation(timer, runtime):
frame.x = 100
frame.animate("x", 824, 3.0, mcrfpy.Easing.EASE_IN_OUT)
frame.animate("x", 824, 3.0, mcrfpy.Easing.EASE_IN_OUT)
loop_timer = mcrfpy.Timer("loop", restart_animation, 8000)

View file

@ -0,0 +1,37 @@
# mcrf: objects=[Caption,Color,Easing,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Easing: BOUNCE - Bouncy effect
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
# Ball that bounces
ball = mcrfpy.Frame(
pos=(462, 100), size=(100, 100),
fill_color=mcrfpy.Color(255, 100, 100)
)
scene.children.append(ball)
# Ground line
scene.children.append(mcrfpy.Frame(
pos=(0, 600), size=(1024, 5),
fill_color=mcrfpy.Color(100, 100, 100)
))
_caption = mcrfpy.Caption(
text="EASE_OUT_BOUNCE - Ball drop effect",
pos=(340, 680))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)
def restart_animation(timer, runtime):
ball.y = 100
ball.animate("y", 500, 2.0, mcrfpy.Easing.EASE_OUT_BOUNCE)
ball.animate("y", 500, 2.0, mcrfpy.Easing.EASE_OUT_BOUNCE)
loop_timer = mcrfpy.Timer("loop", restart_animation, 7000) # 2s animation + 5s pause

View file

@ -0,0 +1,36 @@
# mcrf: objects=[Caption,Color,Easing,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Easing: ELASTIC - Springy overshoot
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
frame = mcrfpy.Frame(
pos=(100, 334), size=(100, 100),
fill_color=mcrfpy.Color(100, 200, 255)
)
scene.children.append(frame)
# Target marker
scene.children.append(mcrfpy.Frame(
pos=(824, 300), size=(5, 170),
fill_color=mcrfpy.Color(255, 100, 100)
))
_caption = mcrfpy.Caption(
text="EASE_OUT_ELASTIC - Spring overshoot",
pos=(340, 200))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)
def restart_animation(timer, runtime):
frame.x = 100
frame.animate("x", 824, 2.5, mcrfpy.Easing.EASE_OUT_ELASTIC)
frame.animate("x", 824, 2.5, mcrfpy.Easing.EASE_OUT_ELASTIC)
loop_timer = mcrfpy.Timer("loop", restart_animation, 7500) # 2.5s animation + 5s pause

View file

@ -0,0 +1,34 @@
# mcrf: objects=[Caption,Color,Easing,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Easing: BACK - Anticipation/overshoot
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
frame = mcrfpy.Frame(
pos=(100, 334), size=(100, 100),
fill_color=mcrfpy.Color(200, 150, 255)
)
scene.children.append(frame)
# Markers
scene.children.append(mcrfpy.Frame(pos=(100, 300), size=(5, 170), fill_color=mcrfpy.Color(100, 100, 100)))
scene.children.append(mcrfpy.Frame(pos=(824, 300), size=(5, 170), fill_color=mcrfpy.Color(100, 100, 100)))
_caption = mcrfpy.Caption(
text="EASE_IN_OUT_BACK - Pull back, then overshoot",
pos=(300, 200))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)
def restart_animation(timer, runtime):
frame.x = 100
frame.animate("x", 824, 2.0, mcrfpy.Easing.EASE_IN_OUT_BACK)
frame.animate("x", 824, 2.0, mcrfpy.Easing.EASE_IN_OUT_BACK)
loop_timer = mcrfpy.Timer("loop", restart_animation, 7000) # 2s animation + 5s pause

View file

@ -0,0 +1,43 @@
# mcrf: objects=[Caption,Color,Easing,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Easing Comparison - Multiple easings side by side
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
easings = [
(mcrfpy.Easing.LINEAR, "LINEAR", mcrfpy.Color(255, 100, 100)),
(mcrfpy.Easing.EASE_IN_QUAD, "EASE_IN_QUAD", mcrfpy.Color(255, 200, 100)),
(mcrfpy.Easing.EASE_OUT_QUAD, "EASE_OUT_QUAD", mcrfpy.Color(200, 255, 100)),
(mcrfpy.Easing.EASE_IN_OUT_QUAD, "EASE_IN_OUT_QUAD", mcrfpy.Color(100, 255, 100)),
(mcrfpy.Easing.EASE_OUT_BOUNCE, "EASE_OUT_BOUNCE", mcrfpy.Color(100, 255, 200)),
(mcrfpy.Easing.EASE_OUT_ELASTIC, "EASE_OUT_ELASTIC", mcrfpy.Color(100, 200, 255)),
]
frames = []
for i, (easing, name, color) in enumerate(easings):
y = 80 + i * 110
label = mcrfpy.Caption(text=name, pos=(100, y + 15))
label.fill_color = mcrfpy.Color(180, 180, 180)
scene.children.append(label)
frame = mcrfpy.Frame(
pos=(350, y), size=(60, 50),
fill_color=color
)
scene.children.append(frame)
frames.append((frame, easing))
frame.animate("x", 900, 3.0, easing)
def restart_animation(timer, runtime):
for frame, easing in frames:
frame.x = 350
frame.animate("x", 900, 3.0, easing)
loop_timer = mcrfpy.Timer("loop", restart_animation, 8000)

View file

@ -0,0 +1,34 @@
# mcrf: objects=[Caption,Color,Easing,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Animation Color - Animate fill_color
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
# Large frame to show color change
frame = mcrfpy.Frame(
pos=(262, 184), size=(500, 400),
fill_color=mcrfpy.Color(255, 100, 100),
outline=3.0,
outline_color=mcrfpy.Color(255, 255, 255)
)
scene.children.append(frame)
_caption = mcrfpy.Caption(
text="Animating fill_color from red to blue",
pos=(340, 620))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)
def restart_animation(timer, runtime):
frame.fill_color = mcrfpy.Color(255, 100, 100)
frame.animate("fill_color", (100, 100, 255, 255), 3.0, mcrfpy.Easing.EASE_IN_OUT)
# Animate to blue (pass color as RGBA tuple)
frame.animate("fill_color", (100, 100, 255, 255), 3.0, mcrfpy.Easing.EASE_IN_OUT)
loop_timer = mcrfpy.Timer("loop", restart_animation, 8000)

View file

@ -0,0 +1,38 @@
# mcrf: objects=[Caption,Color,Easing,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Animation Opacity - Fade in/out
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
# Frame that fades out
frame = mcrfpy.Frame(
pos=(262, 184), size=(500, 400),
fill_color=mcrfpy.Color(200, 100, 255),
opacity=1.0
)
scene.children.append(frame)
label = mcrfpy.Caption(
text="I'm fading away...",
pos=(170, 180))
label.fill_color = mcrfpy.Color(255, 255, 255)
frame.children.append(label)
_caption = mcrfpy.Caption(
text="Animating opacity from 1.0 to 0.0",
pos=(360, 620))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)
def restart_animation(timer, runtime):
frame.opacity = 1.0
frame.animate("opacity", 0.0, 4.0, mcrfpy.Easing.EASE_IN)
frame.animate("opacity", 0.0, 4.0, mcrfpy.Easing.EASE_IN)
loop_timer = mcrfpy.Timer("loop", restart_animation, 9000) # 4s animation + 5s pause

View file

@ -0,0 +1,37 @@
# mcrf: objects=[Caption,Color,Easing,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Animation Size - Grow and shrink
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
# Frame that grows
frame = mcrfpy.Frame(
pos=(462, 334), size=(100, 100),
fill_color=mcrfpy.Color(100, 255, 150),
outline=2.0,
outline_color=mcrfpy.Color(255, 255, 255)
)
scene.children.append(frame)
_caption = mcrfpy.Caption(
text="Animating w and h properties",
pos=(380, 620))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)
def restart_animation(timer, runtime):
frame.w = 100
frame.h = 100
frame.animate("w", 400, 2.0, mcrfpy.Easing.EASE_OUT_ELASTIC)
frame.animate("h", 300, 2.0, mcrfpy.Easing.EASE_OUT_ELASTIC)
# Animate both dimensions
frame.animate("w", 400, 2.0, mcrfpy.Easing.EASE_OUT_ELASTIC)
frame.animate("h", 300, 2.0, mcrfpy.Easing.EASE_OUT_ELASTIC)
loop_timer = mcrfpy.Timer("loop", restart_animation, 7000) # 2s animation + 5s pause

View file

@ -0,0 +1,34 @@
# mcrf: objects=[Caption,Color,Easing,Frame,Scene,Sprite,Timer] verified=0.2.8-dev status=ok
# Animation Sprite Index - Sprite animation
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
# Large sprite for visibility
sprite = mcrfpy.Sprite(
texture=mcrfpy.default_texture,
sprite_index=84,
pos=(400, 250),
scale=12.0
)
scene.children.append(sprite)
_caption = mcrfpy.Caption(
text="Animating sprite_index (84 -> 120)",
pos=(360, 600))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)
def restart_animation(timer, runtime):
sprite.sprite_index = 84
sprite.animate("sprite_index", 120, 3.0, mcrfpy.Easing.LINEAR)
# Animate through sprite indices
sprite.animate("sprite_index", 120, 3.0, mcrfpy.Easing.LINEAR)
loop_timer = mcrfpy.Timer("loop", restart_animation, 8000)

View file

@ -0,0 +1,37 @@
# mcrf: objects=[Caption,Color,Easing,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Animation Callback - Run code when animation ends
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
frame = mcrfpy.Frame(
pos=(100, 334), size=(100, 100),
fill_color=mcrfpy.Color(255, 150, 100)
)
scene.children.append(frame)
status = mcrfpy.Caption(
text="Animation in progress...",
pos=(380, 500))
status.fill_color = mcrfpy.Color(200, 200, 200)
scene.children.append(status)
# Callback when animation completes
def on_complete(target, prop, value):
status.text = f"Animation complete! {prop}={value:.0f}"
target.fill_color = mcrfpy.Color(100, 255, 100)
def restart_animation(timer, runtime):
frame.x = 100
frame.fill_color = mcrfpy.Color(255, 150, 100)
status.text = "Animation in progress..."
frame.animate("x", 824, 2.0, mcrfpy.Easing.EASE_OUT, callback=on_complete)
frame.animate("x", 824, 2.0, mcrfpy.Easing.EASE_OUT, callback=on_complete)
loop_timer = mcrfpy.Timer("loop", restart_animation, 7000) # 2s animation + 5s pause

View file

@ -0,0 +1,46 @@
# mcrf: objects=[Caption,Color,Easing,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Animation Chain - Sequential animations
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
frame = mcrfpy.Frame(
pos=(100, 100), size=(100, 100),
fill_color=mcrfpy.Color(255, 100, 100)
)
scene.children.append(frame)
status = mcrfpy.Caption(text="Step 1: Moving right", pos=(380, 620))
scene.children.append(status)
def step2(target, prop, value):
status.text = "Step 2: Moving down"
target.fill_color = mcrfpy.Color(100, 255, 100)
target.animate("y", 568, 1.0, mcrfpy.Easing.EASE_IN_OUT, callback=step3)
def step3(target, prop, value):
status.text = "Step 3: Moving left"
target.fill_color = mcrfpy.Color(100, 100, 255)
target.animate("x", 100, 1.0, mcrfpy.Easing.EASE_IN_OUT, callback=step4)
def step4(target, prop, value):
status.text = "Step 4: Moving up - Complete!"
target.fill_color = mcrfpy.Color(255, 255, 100)
target.animate("y", 100, 1.0, mcrfpy.Easing.EASE_IN_OUT)
def restart_animation(timer, runtime):
frame.x = 100
frame.y = 100
frame.fill_color = mcrfpy.Color(255, 100, 100)
status.text = "Step 1: Moving right"
frame.animate("x", 824, 1.0, mcrfpy.Easing.EASE_IN_OUT, callback=step2)
# Start chain
frame.animate("x", 824, 1.0, mcrfpy.Easing.EASE_IN_OUT, callback=step2)
loop_timer = mcrfpy.Timer("loop", restart_animation, 9000) # 4s animation + 5s pause

View file

@ -0,0 +1,32 @@
# mcrf: objects=[Caption,Color,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Timer Basic - Periodic callback
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
counter = mcrfpy.Caption(
text="Ticks: 0",
pos=(420, 350))
counter.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(counter)
count = 0
def on_tick(timer, runtime):
global count
count += 1
counter.text = f"Ticks: {count}"
# Timer fires every 500ms
timer = mcrfpy.Timer("ticker", on_tick, 500)
scene.children.append(mcrfpy.Caption(
text="Timer interval: 500ms",
pos=(410, 450)
))

View file

@ -0,0 +1,29 @@
# mcrf: objects=[Caption,Color,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Timer Once - Single-shot timer
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
status = mcrfpy.Caption(
text="Waiting for timer...",
pos=(380, 350))
status.fill_color = mcrfpy.Color(200, 200, 200)
scene.children.append(status)
def on_fire(timer, runtime):
status.text = "Timer fired! (once=True)"
status.fill_color = mcrfpy.Color(100, 255, 100)
# Timer fires once after 2 seconds
timer = mcrfpy.Timer("delayed", on_fire, 2000, once=True)
scene.children.append(mcrfpy.Caption(
text="Single-shot timer fires after 2 seconds",
pos=(320, 450)
))

View file

@ -0,0 +1,51 @@
# mcrf: objects=[Caption,Color,Frame,InputState,Key,Scene,Timer] verified=0.2.8-dev status=ok
# Timer Control - Pause, resume, stop
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
# Moving frame
frame = mcrfpy.Frame(
pos=(100, 334), size=(80, 80),
fill_color=mcrfpy.Color(255, 150, 100)
)
scene.children.append(frame)
status = mcrfpy.Caption(
text="Timer running - P=pause, R=resume, S=stop",
pos=(280, 500)
)
scene.children.append(status)
direction = 1
def animate(timer, runtime):
global direction
frame.x += 5 * direction
if frame.x > 844:
direction = -1
elif frame.x < 100:
direction = 1
timer = mcrfpy.Timer("mover", animate, 16)
def on_key(key, action):
if action != mcrfpy.InputState.PRESSED:
return
if key == mcrfpy.Key.P:
timer.pause()
status.text = "Timer PAUSED"
elif key == mcrfpy.Key.R:
timer.resume()
status.text = "Timer RESUMED"
elif key == mcrfpy.Key.S:
timer.stop()
status.text = "Timer STOPPED"
scene.on_key = on_key

View file

@ -0,0 +1,38 @@
# mcrf: objects=[Caption,Color,Frame,InputState,Scene] verified=0.2.8-dev status=ok
# Input Keyboard - Key press handling
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
key_display = mcrfpy.Caption(
text="Press any key...",
pos=(380, 300))
key_display.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(key_display)
state_display = mcrfpy.Caption(
text="State: waiting",
pos=(420, 400))
state_display.fill_color = mcrfpy.Color(200, 200, 200)
scene.children.append(state_display)
def on_key(key, action):
key_display.text = f"Key: {key}"
if action == mcrfpy.InputState.PRESSED:
state_display.text = "State: PRESSED"
state_display.fill_color = mcrfpy.Color(100, 255, 100)
elif action == mcrfpy.InputState.RELEASED:
state_display.text = "State: RELEASED"
state_display.fill_color = mcrfpy.Color(255, 100, 100)
elif action == mcrfpy.InputState.HELD:
state_display.text = "State: HELD"
state_display.fill_color = mcrfpy.Color(255, 255, 100)
scene.on_key = on_key

View file

@ -0,0 +1,41 @@
# mcrf: objects=[Caption,Color,Frame,InputState,Scene] verified=0.2.8-dev status=ok
# Input Mouse Click - Click on frames
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
# Clickable button
button = mcrfpy.Frame(
pos=(362, 284), size=(300, 200),
fill_color=mcrfpy.Color(100, 100, 200),
outline=3.0,
outline_color=mcrfpy.Color(150, 150, 255)
)
scene.children.append(button)
label = mcrfpy.Caption(
text="Click me!",
pos=(100, 80)
)
button.children.append(label)
click_count = 0
status = mcrfpy.Caption(text="Clicks: 0", pos=(450, 550))
scene.children.append(status)
def on_click(pos, button_type, action):
global click_count
if action == mcrfpy.InputState.PRESSED:
click_count += 1
status.text = f"Clicks: {click_count}"
button.fill_color = mcrfpy.Color(150, 150, 255)
elif action == mcrfpy.InputState.RELEASED:
button.fill_color = mcrfpy.Color(100, 100, 200)
button.on_click = on_click

View file

@ -0,0 +1,45 @@
# mcrf: objects=[Caption,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Input Mouse Hover - Enter/exit/move events
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
))
# Hoverable frame
frame = mcrfpy.Frame(
pos=(312, 234), size=(400, 300),
fill_color=mcrfpy.Color(80, 80, 120),
outline=3.0,
outline_color=mcrfpy.Color(120, 120, 180)
)
scene.children.append(frame)
status = mcrfpy.Caption(
text="Mouse outside",
pos=(140, 130)
)
frame.children.append(status)
coords = mcrfpy.Caption(text="", pos=(140, 180))
frame.children.append(coords)
def on_enter(pos):
frame.fill_color = mcrfpy.Color(100, 150, 100)
status.text = "Mouse ENTERED"
def on_exit(pos):
frame.fill_color = mcrfpy.Color(80, 80, 120)
status.text = "Mouse EXITED"
coords.text = ""
def on_move(pos):
coords.text = f"Position: ({pos.x:.0f}, {pos.y:.0f})"
frame.on_enter = on_enter
frame.on_exit = on_exit
frame.on_move = on_move

View file

@ -0,0 +1,47 @@
# mcrf: objects=[Caption,Color,ColorLayer,Grid,InputState,Scene] verified=0.2.8-dev status=ok
# Input Grid Cell Click - Click on grid cells
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Add color layer for click highlighting
click_layer = mcrfpy.ColorLayer(z_index=1, name="clicks")
grid.add_layer(click_layer)
# Fill with floor
for y in range(12):
for x in range(16):
grid.at(x, y).tilesprite = 48
status = mcrfpy.Caption(text="Click on cells to highlight", pos=(350, 720))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)
# Track clicked cells
clicked = set()
def on_cell_click(cell_pos, button, action):
if action == mcrfpy.InputState.PRESSED:
x, y = int(cell_pos.x), int(cell_pos.y)
if (x, y) in clicked:
# Clear highlight
click_layer.set((x, y), mcrfpy.Color(0, 0, 0, 0))
clicked.discard((x, y))
else:
# Add highlight
click_layer.set((x, y), mcrfpy.Color(255, 200, 100, 180))
clicked.add((x, y))
status.text = f"Clicked cell ({x}, {y})"
grid.on_cell_click = on_cell_click

View file

@ -0,0 +1,40 @@
# mcrf: objects=[Caption,Color,ColorLayer,Grid,Scene] verified=0.2.8-dev status=ok
# Input Grid Cell Hover - Hover over grid cells
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Add color layer for hover highlighting
hover_layer = mcrfpy.ColorLayer(z_index=1, name="hover")
grid.add_layer(hover_layer)
for y in range(12):
for x in range(16):
grid.at(x, y).tilesprite = 48
status = mcrfpy.Caption(text="Hover over cells", pos=(420, 720))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)
def on_cell_enter(cell_pos):
x, y = int(cell_pos.x), int(cell_pos.y)
hover_layer.set((x, y), mcrfpy.Color(100, 200, 255, 180))
status.text = f"Hovering cell ({x}, {y})"
def on_cell_exit(cell_pos):
x, y = int(cell_pos.x), int(cell_pos.y)
hover_layer.set((x, y), mcrfpy.Color(0, 0, 0, 0))
grid.on_cell_enter = on_cell_enter
grid.on_cell_exit = on_cell_exit

View file

@ -0,0 +1,56 @@
# mcrf: objects=[Caption,Color,ColorLayer,Grid,Scene] verified=0.2.8-dev status=ok
# Pathfinding A* - Find path between points
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Add color layer for path visualization
path_layer = mcrfpy.ColorLayer(z_index=1, name="path")
grid.add_layer(path_layer)
# Create maze
for y in range(12):
for x in range(16):
cell = grid.at(x, y)
# Border walls
if x == 0 or x == 15 or y == 0 or y == 11:
cell.tilesprite = 1
cell.walkable = False
# Some obstacles
elif x == 8 and y > 1 and y < 10:
cell.tilesprite = 1
cell.walkable = False
elif x == 4 and y > 3 and y < 11:
cell.tilesprite = 1
cell.walkable = False
else:
cell.tilesprite = 48
cell.walkable = True
# Find path
start = (2, 6)
end = (13, 6)
path = grid.find_path(start, end)
# Highlight path using color layer
for px, py in path:
path_layer.set((px, py), mcrfpy.Color(100, 255, 100, 180))
# Mark start/end
path_layer.set((start[0], start[1]), mcrfpy.Color(100, 100, 255, 200))
path_layer.set((end[0], end[1]), mcrfpy.Color(255, 100, 100, 200))
status = mcrfpy.Caption(text=f"A* Path length: {len(path)} - Blue=start, Red=end", pos=(320, 700))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)

View file

@ -0,0 +1,54 @@
# mcrf: objects=[Caption,Color,ColorLayer,Entity,Grid,Scene] verified=0.2.8-dev status=ok
# Entity Pathfinding - Entity path_to method
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Add color layer for path visualization
path_layer = mcrfpy.ColorLayer(z_index=1, name="path")
grid.add_layer(path_layer)
# Floor with some walls
for y in range(12):
for x in range(16):
cell = grid.at(x, y)
if x == 8 and y > 1 and y < 10:
cell.tilesprite = 1
cell.walkable = False
else:
cell.tilesprite = 48
cell.walkable = True
# Entity
player = mcrfpy.Entity(
grid_pos=(3, 6),
texture=mcrfpy.default_texture,
sprite_index=84
)
grid.entities.append(player)
# Find path to target
target = (13, 6)
path = player.path_to(target)
# Visualize path using color layer
for px, py in path:
path_layer.set((px, py), mcrfpy.Color(150, 200, 255, 180))
# Mark target
path_layer.set((target[0], target[1]), mcrfpy.Color(255, 200, 100, 200))
status = mcrfpy.Caption(text="Entity pathfinding around wall", pos=(360, 720))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)

View file

@ -0,0 +1,52 @@
# mcrf: objects=[Caption,Color,ColorLayer,Grid,Scene] verified=0.2.8-dev status=ok
# Dijkstra Map - Distance-based pathfinding
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Add color layer for visualization
dist_layer = mcrfpy.ColorLayer(z_index=1, name="distances")
grid.add_layer(dist_layer)
# Floor tiles
for y in range(12):
for x in range(16):
cell = grid.at(x, y)
cell.tilesprite = 48
cell.walkable = True
# Some walls
for i in range(5):
grid.at(8, 2 + i).tilesprite = 1
grid.at(8, 2 + i).walkable = False
# Compute dijkstra from center
center = (8, 6)
dijkstra = grid.get_dijkstra_map(center)
# Visualize distances with color gradient
for y in range(12):
for x in range(16):
if grid.at(x, y).walkable:
dist = dijkstra.distance((x, y))
if dist is not None and dist < 1000: # Valid distance
intensity = max(0, 255 - int(dist * 20))
dist_layer.set((x, y), mcrfpy.Color(intensity, intensity, 255, 180))
# Mark center
dist_layer.set((center[0], center[1]), mcrfpy.Color(255, 100, 100, 255))
status = mcrfpy.Caption(text="Dijkstra distance map (darker = farther)", pos=(320, 720))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)

View file

@ -0,0 +1,58 @@
# mcrf: objects=[Caption,Color,ColorLayer,Entity,Grid,Scene] verified=0.2.8-dev status=ok
# FOV Basic - Field of view computation
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Add fog layer for FOV visualization
fog_layer = mcrfpy.ColorLayer(z_index=1, name="fog")
grid.add_layer(fog_layer)
fog_layer.fill(mcrfpy.Color(0, 0, 0, 200)) # Start dark
# Create dungeon with walls
for y in range(12):
for x in range(16):
cell = grid.at(x, y)
# Border walls
if x == 0 or x == 15 or y == 0 or y == 11:
cell.tilesprite = 1
cell.walkable = False
cell.transparent = False
# Internal pillars
elif (x in [4, 8, 12]) and (y in [4, 8]):
cell.tilesprite = 1
cell.walkable = False
cell.transparent = False
else:
cell.tilesprite = 48
cell.walkable = True
cell.transparent = True
# Player position
player_pos = (8, 6)
player = mcrfpy.Entity(grid_pos=player_pos, texture=mcrfpy.default_texture, sprite_index=84)
grid.entities.append(player)
# Compute FOV from player position
grid.compute_fov(player_pos, radius=8)
# Clear fog for visible cells
for y in range(12):
for x in range(16):
if grid.is_in_fov((x, y)):
fog_layer.set((x, y), mcrfpy.Color(0, 0, 0, 0)) # Clear
status = mcrfpy.Caption(text="FOV radius=8, dark areas not visible", pos=(340, 700))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)

View file

@ -0,0 +1,60 @@
# mcrf: objects=[Caption,Color,ColorLayer,FOV,Frame,Grid,Scene] verified=0.2.8-dev status=ok
# FOV Algorithms - Compare different algorithms
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
algorithms = [
(mcrfpy.FOV.BASIC, "BASIC"),
(mcrfpy.FOV.SHADOW, "SHADOW"),
(mcrfpy.FOV.DIAMOND, "DIAMOND"),
(mcrfpy.FOV.PERMISSIVE_4, "PERMISSIVE4"),
]
for i, (algo, name) in enumerate(algorithms):
x = 62 + (i % 2) * 480
y = 60 + (i // 2) * 360
grid = mcrfpy.Grid(
grid_size=(12, 9),
texture=mcrfpy.default_texture,
pos=(x, y),
size=(420, 300)
)
scene.children.append(grid)
# Add fog layer
fog = mcrfpy.ColorLayer(z_index=1, name="fog")
grid.add_layer(fog)
fog.fill(mcrfpy.Color(0, 0, 0, 180))
# Setup dungeon
for gy in range(9):
for gx in range(12):
cell = grid.at(gx, gy)
if gx == 0 or gx == 11 or gy == 0 or gy == 8:
cell.tilesprite = 1
cell.transparent = False
elif gx == 6 and gy > 1 and gy < 7:
cell.tilesprite = 1
cell.transparent = False
else:
cell.tilesprite = 48
cell.transparent = True
# Compute FOV with this algorithm
grid.compute_fov((3, 4), radius=6, algorithm=algo)
# Clear fog for visible cells
for gy in range(9):
for gx in range(12):
if grid.is_in_fov((gx, gy)):
fog.set((gx, gy), mcrfpy.Color(0, 0, 0, 0))
label = mcrfpy.Caption(text=name, pos=(x + 170, y + 310))
label.outline = 2
label.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(label)

View file

@ -0,0 +1,53 @@
# mcrf: objects=[Caption,Color,Entity,Grid,InputState,Key,Scene] verified=0.2.8-dev status=ok
# FOV Perspective Entity - View from entity's perspective
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Dungeon layout
for y in range(12):
for x in range(16):
cell = grid.at(x, y)
if x == 0 or x == 15 or y == 0 or y == 11:
cell.tilesprite = 1
cell.transparent = False
elif (x == 4 or x == 12) and y > 2 and y < 9:
cell.tilesprite = 1
cell.transparent = False
else:
cell.tilesprite = 48
cell.transparent = True
# Player entity
player = mcrfpy.Entity(grid_pos=(8, 6), texture=mcrfpy.default_texture, sprite_index=84)
grid.entities.append(player)
# Set perspective entity - FOV follows this entity
grid.perspective = player
grid.fov_radius = 6
status = mcrfpy.Caption(text="WASD to move, FOV follows player", pos=(350, 700))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)
def on_key(key, action):
if action != mcrfpy.InputState.PRESSED:
return
x, y = player.grid_x, player.grid_y
if key == mcrfpy.Key.W: player.grid_pos = (x, y - 1)
elif key == mcrfpy.Key.S: player.grid_pos = (x, y + 1)
elif key == mcrfpy.Key.A: player.grid_pos = (x - 1, y)
elif key == mcrfpy.Key.D: player.grid_pos = (x + 1, y)
scene.on_key = on_key

View file

@ -0,0 +1,56 @@
# mcrf: objects=[BSP,Caption,Color,ColorLayer,Grid,Scene] verified=0.2.8-dev status=ok
# BSP Basic - Binary space partition for dungeon generation
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Add color layer for room visualization
room_layer = mcrfpy.ColorLayer(z_index=1, name="rooms")
grid.add_layer(room_layer)
# Fill with walls
for y in range(12):
for x in range(16):
grid.at(x, y).tilesprite = 1
grid.at(x, y).walkable = False
# Create BSP tree (pos and size as tuples)
bsp = mcrfpy.BSP(pos=(0, 0), size=(16, 12))
bsp.split_recursive(depth=3, min_size=(3, 3))
# Room colors
colors = [
mcrfpy.Color(255, 150, 150, 150),
mcrfpy.Color(150, 255, 150, 150),
mcrfpy.Color(150, 150, 255, 150),
mcrfpy.Color(255, 255, 150, 150),
mcrfpy.Color(255, 150, 255, 150),
mcrfpy.Color(150, 255, 255, 150),
]
# Carve rooms from leaves
for i, leaf in enumerate(bsp):
x, y = leaf.pos
w, h = leaf.size
# Shrink room slightly
for ry in range(y + 1, y + h - 1):
for rx in range(x + 1, x + w - 1):
if 0 <= rx < 16 and 0 <= ry < 12:
grid.at(rx, ry).tilesprite = 48
grid.at(rx, ry).walkable = True
room_layer.set((rx, ry), colors[i % len(colors)])
status = mcrfpy.Caption(text="BSP dungeon - each color is a leaf node", pos=(320, 720))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)

View file

@ -0,0 +1,34 @@
# mcrf: objects=[Caption,Color,Frame,InputState,Key,Scene] verified=0.2.8-dev status=ok
# Scene Switching - Multiple scenes
import mcrfpy
# Create two scenes
scene1 = mcrfpy.Scene("menu")
scene2 = mcrfpy.Scene("game")
# Menu scene
scene1.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(50, 50, 80)))
_caption = mcrfpy.Caption(text="MENU SCENE", pos=(420, 300))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene1.children.append(_caption)
scene1.children.append(mcrfpy.Caption(text="Press SPACE to go to game", pos=(360, 400)))
# Game scene
scene2.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(80, 50, 50)))
_caption = mcrfpy.Caption(text="GAME SCENE", pos=(420, 300))
_caption.fill_color = mcrfpy.Color(100, 255, 100)
scene2.children.append(_caption)
scene2.children.append(mcrfpy.Caption(text="Press SPACE to go to menu", pos=(360, 400)))
def menu_key(key, action):
if key == mcrfpy.Key.SPACE and action == mcrfpy.InputState.PRESSED:
scene2.activate()
def game_key(key, action):
if key == mcrfpy.Key.SPACE and action == mcrfpy.InputState.PRESSED:
scene1.activate()
scene1.on_key = menu_key
scene2.on_key = game_key
mcrfpy.current_scene = scene1

View file

@ -0,0 +1,36 @@
# mcrf: objects=[Caption,Color,Frame,InputState,Key,Scene,Transition] verified=0.2.8-dev status=ok
# Scene Transition Fade - Smooth scene transitions
import mcrfpy
scene1 = mcrfpy.Scene("scene1")
scene2 = mcrfpy.Scene("scene2")
scene1.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(100, 50, 50)))
_caption = mcrfpy.Caption(text="Scene 1 - Red", pos=(420, 350))
_caption.fill_color = mcrfpy.Color(255, 255, 255)
scene1.children.append(_caption)
scene2.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(50, 50, 100)))
_caption = mcrfpy.Caption(text="Scene 2 - Blue", pos=(420, 350))
_caption.fill_color = mcrfpy.Color(255, 255, 255)
scene2.children.append(_caption)
# Set default transition
mcrfpy.default_transition = mcrfpy.Transition.FADE
mcrfpy.default_transition_duration = 1.0
scene1.children.append(mcrfpy.Caption(text="Press SPACE - Fade transition", pos=(340, 450)))
scene2.children.append(mcrfpy.Caption(text="Press SPACE - Fade transition", pos=(340, 450)))
def key1(key, action):
if key == mcrfpy.Key.SPACE and action == mcrfpy.InputState.PRESSED:
scene2.activate()
def key2(key, action):
if key == mcrfpy.Key.SPACE and action == mcrfpy.InputState.PRESSED:
scene1.activate()
scene1.on_key = key1
scene2.on_key = key2
mcrfpy.current_scene = scene1

View file

@ -0,0 +1,35 @@
# mcrf: objects=[Caption,Color,Frame,InputState,Key,Scene,Transition] verified=0.2.8-dev status=ok
# Scene Transition Slide - Sliding transitions
import mcrfpy
scene1 = mcrfpy.Scene("left")
scene2 = mcrfpy.Scene("right")
scene1.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(50, 100, 50)))
_caption = mcrfpy.Caption(text="LEFT Scene", pos=(420, 350))
_caption.fill_color = mcrfpy.Color(255, 255, 255)
scene1.children.append(_caption)
scene2.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(100, 50, 100)))
_caption = mcrfpy.Caption(text="RIGHT Scene", pos=(420, 350))
_caption.fill_color = mcrfpy.Color(255, 255, 255)
scene2.children.append(_caption)
mcrfpy.default_transition = mcrfpy.Transition.SLIDE_LEFT
mcrfpy.default_transition_duration = 0.5
scene1.children.append(mcrfpy.Caption(text="Press SPACE - Slide left", pos=(360, 450)))
scene2.children.append(mcrfpy.Caption(text="Press SPACE - Slide left", pos=(360, 450)))
def key1(key, action):
if key == mcrfpy.Key.SPACE and action == mcrfpy.InputState.PRESSED:
scene2.activate()
def key2(key, action):
if key == mcrfpy.Key.SPACE and action == mcrfpy.InputState.PRESSED:
scene1.activate()
scene1.on_key = key1
scene2.on_key = key2
mcrfpy.current_scene = scene1

View file

@ -0,0 +1,50 @@
# mcrf: objects=[Caption,Color,Frame,Scene,Vector] verified=0.2.8-dev status=ok
# Vector Basics - 2D vector operations
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
# Create vectors
v1 = mcrfpy.Vector(100, 50)
v2 = mcrfpy.Vector(30, 80)
# Display vector info
info = [
f"v1 = Vector({v1.x}, {v1.y})",
f"v2 = Vector({v2.x}, {v2.y})",
f"",
f"v1.magnitude() = {v1.magnitude():.2f}",
f"v1.angle() = {v1.angle():.2f} radians",
f"",
f"Distance v1 to v2 = {v1.distance_to(v2):.2f}",
]
y = 150
for line in info:
cap = mcrfpy.Caption(text=line, pos=(300, y))
cap.fill_color = mcrfpy.Color(200, 200, 200)
scene.children.append(cap)
y += 60
# Visual representation
origin = mcrfpy.Frame(pos=(500, 500), size=(10, 10), fill_color=mcrfpy.Color(255, 255, 255))
scene.children.append(origin)
# v1 endpoint
p1 = mcrfpy.Frame(pos=(500 + v1.x * 2, 500 - v1.y * 2), size=(15, 15), fill_color=mcrfpy.Color(255, 100, 100))
scene.children.append(p1)
v1_label = mcrfpy.Caption(text="v1", pos=(500 + v1.x * 2 + 20, 500 - v1.y * 2))
v1_label.outline = 2
v1_label.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(v1_label)
# v2 endpoint
p2 = mcrfpy.Frame(pos=(500 + v2.x * 2, 500 - v2.y * 2), size=(15, 15), fill_color=mcrfpy.Color(100, 100, 255))
scene.children.append(p2)
v2_label = mcrfpy.Caption(text="v2", pos=(500 + v2.x * 2 + 20, 500 - v2.y * 2))
v2_label.outline = 2
v2_label.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(v2_label)

View file

@ -0,0 +1,40 @@
# mcrf: objects=[Caption,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Color Components - RGBA color manipulation
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
# Show color components
colors = [
(mcrfpy.Color(255, 0, 0), "Pure Red"),
(mcrfpy.Color(0, 255, 0), "Pure Green"),
(mcrfpy.Color(0, 0, 255), "Pure Blue"),
(mcrfpy.Color(255, 255, 0), "Yellow (R+G)"),
(mcrfpy.Color(255, 0, 255), "Magenta (R+B)"),
(mcrfpy.Color(0, 255, 255), "Cyan (G+B)"),
(mcrfpy.Color(128, 128, 128), "Gray"),
(mcrfpy.Color(255, 255, 255, 128), "White 50% Alpha"),
]
for i, (color, name) in enumerate(colors):
x = 100 + (i % 4) * 230
y = 150 + (i // 4) * 280
frame = mcrfpy.Frame(pos=(x, y), size=(180, 100), fill_color=color)
scene.children.append(frame)
label = mcrfpy.Caption(text=name, pos=(x, y + 110))
scene.children.append(label)
rgba = mcrfpy.Caption(
text=f"R:{color.r} G:{color.g} B:{color.b} A:{color.a}",
pos=(x, y + 140))
rgba.fill_color = mcrfpy.Color(150, 150, 150)
scene.children.append(rgba)
_caption = mcrfpy.Caption(text="Color RGBA Components", pos=(400, 60))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)

View file

@ -0,0 +1,36 @@
# mcrf: objects=[Caption,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Find Elements - Locate UI by name
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
# Create named elements
elements = [
("btn_play", 200, 200, mcrfpy.Color(100, 150, 100)),
("btn_options", 200, 320, mcrfpy.Color(100, 100, 150)),
("btn_quit", 200, 440, mcrfpy.Color(150, 100, 100)),
("header", 500, 100, mcrfpy.Color(150, 150, 100)),
]
for name, x, y, color in elements:
frame = mcrfpy.Frame(pos=(x, y), size=(200, 80), fill_color=color, name=name)
scene.children.append(frame)
label = mcrfpy.Caption(text=name, pos=(20, 30))
frame.children.append(label)
# Find specific element
found = mcrfpy.find("btn_options")
if found:
found.outline = 4.0
found.outline_color = mcrfpy.Color(255, 255, 255)
# Find all buttons
buttons = mcrfpy.find_all("btn_*")
status = mcrfpy.Caption(
text=f"Found {len(buttons)} elements matching 'btn_*'",
pos=(350, 600))
status.fill_color = mcrfpy.Color(200, 200, 200)
scene.children.append(status)

View file

@ -0,0 +1,40 @@
# mcrf: objects=[Caption,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Collection Iteration - Loop through UI children
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
# Container with multiple children
container = mcrfpy.Frame(pos=(162, 134), size=(700, 500), fill_color=mcrfpy.Color(50, 50, 70))
scene.children.append(container)
# Add various elements
for i in range(5):
frame = mcrfpy.Frame(
pos=(20 + i * 130, 50),
size=(110, 80),
fill_color=mcrfpy.Color(100 + i * 30, 100, 200 - i * 30)
)
container.children.append(frame)
for i in range(3):
cap = mcrfpy.Caption(text=f"Label {i}", pos=(50 + i * 200, 200))
container.children.append(cap)
# Iterate and count by type
frames = 0
captions = 0
for child in container.children:
if hasattr(child, 'fill_color'):
frames += 1
if hasattr(child, 'text'):
captions += 1
status = mcrfpy.Caption(
text=f"Container has {len(container.children)} children: {frames} frames, {captions} captions",
pos=(100, 400)
)
container.children.append(status)

View file

@ -0,0 +1,41 @@
# mcrf: objects=[Caption,Color,Entity,Grid,Scene] verified=0.2.8-dev status=ok
# Entity Collection - Manage grid entities
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
for y in range(12):
for x in range(16):
grid.at(x, y).tilesprite = 48
# Add multiple entities
sprites = [84, 86, 88, 112, 116, 120]
for i, sprite_idx in enumerate(sprites):
entity = mcrfpy.Entity(
grid_pos=(2 + i * 2, 6),
texture=mcrfpy.default_texture,
sprite_index=sprite_idx,
name=f"entity_{i}"
)
grid.entities.append(entity)
# Count and iterate
status = mcrfpy.Caption(text=f"Grid has {len(grid.entities)} entities", pos=(400, 720))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)
# Highlight alternating entities
for i, entity in enumerate(grid.entities):
if i % 2 == 0:
entity.opacity = 0.5

View file

@ -0,0 +1,33 @@
# mcrf: objects=[Caption,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Window Properties - Access window settings
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
# Display window info
window = mcrfpy.window
info = [
f"Resolution: {window.resolution}",
f"Game Resolution: {window.game_resolution}",
f"Title: {window.title}",
f"Fullscreen: {window.fullscreen}",
f"VSync: {window.vsync}",
f"Framerate Limit: {window.framerate_limit}",
f"Scaling Mode: {window.scaling_mode}",
]
_caption = mcrfpy.Caption(
text="Window Properties",
pos=(400, 100))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)
y = 200
for line in info:
cap = mcrfpy.Caption(text=line, pos=(300, y))
cap.fill_color = mcrfpy.Color(200, 200, 200)
scene.children.append(cap)
y += 60

View file

@ -0,0 +1,46 @@
# mcrf: objects=[Caption,Color,Frame,InputState,Scene] verified=0.2.8-dev status=ok
# Keyboard State - Track key presses
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
_caption = mcrfpy.Caption(text="Keyboard State", pos=(420, 100))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)
last_key_label = mcrfpy.Caption(text="Last key: none", pos=(200, 250))
scene.children.append(last_key_label)
state_label = mcrfpy.Caption(text="State: waiting", pos=(200, 350))
scene.children.append(state_label)
modifier_label = mcrfpy.Caption(text="Modifiers: none", pos=(200, 450))
scene.children.append(modifier_label)
scene.children.append(mcrfpy.Caption(text="Press keys to see state", pos=(350, 550)))
def on_key(key, action):
last_key_label.text = f"Last key: {key}"
if action == mcrfpy.InputState.PRESSED:
state_label.text = "State: PRESSED"
state_label.fill_color = mcrfpy.Color(100, 255, 100)
elif action == mcrfpy.InputState.RELEASED:
state_label.text = "State: RELEASED"
state_label.fill_color = mcrfpy.Color(255, 100, 100)
elif action == mcrfpy.InputState.HELD:
state_label.text = "State: HELD"
state_label.fill_color = mcrfpy.Color(255, 255, 100)
# Show modifiers
kb = mcrfpy.keyboard
mods = []
if kb.ctrl: mods.append("CTRL")
if kb.shift: mods.append("SHIFT")
if kb.alt: mods.append("ALT")
modifier_label.text = f"Modifiers: {', '.join(mods) if mods else 'none'}"
scene.on_key = on_key

View file

@ -0,0 +1,39 @@
# mcrf: objects=[Caption,Color,Frame,Scene,Timer,Vector] verified=0.2.8-dev status=ok
# Mouse State - Track mouse position and buttons
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
_caption = mcrfpy.Caption(text="Mouse State", pos=(430, 100))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)
pos_label = mcrfpy.Caption(text="Position: (0, 0)", pos=(300, 250))
scene.children.append(pos_label)
button_label = mcrfpy.Caption(text="Buttons: none", pos=(300, 350))
scene.children.append(button_label)
# Cursor follower
cursor = mcrfpy.Frame(pos=(0, 0), size=(20, 20), fill_color=mcrfpy.Color(255, 100, 100))
scene.children.append(cursor)
def update_display(timer, runtime):
mouse = mcrfpy.mouse
pos = mouse.pos
pos_label.text = f"Position: ({pos.x:.0f}, {pos.y:.0f})"
# Check button states
buttons = []
if mouse.left: buttons.append("LEFT")
if mouse.middle: buttons.append("MIDDLE")
if mouse.right: buttons.append("RIGHT")
button_label.text = f"Buttons: {', '.join(buttons) if buttons else 'none'}"
cursor.pos = mcrfpy.Vector(pos.x - 10, pos.y - 10)
timer = mcrfpy.Timer("mouse_poll", update_display, 16)

View file

@ -0,0 +1,47 @@
# mcrf: objects=[Caption,Color,Frame,InputState,Scene] verified=0.2.8-dev status=ok
# UI Button - Interactive button with hover
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
def make_button(x, y, text, callback):
btn = mcrfpy.Frame(
pos=(x, y), size=(200, 60),
fill_color=mcrfpy.Color(80, 80, 120),
outline=2.0,
outline_color=mcrfpy.Color(120, 120, 180)
)
label = mcrfpy.Caption(text=text, pos=(60, 18))
btn.children.append(label)
def on_enter(pos):
btn.fill_color = mcrfpy.Color(100, 100, 150)
def on_exit(pos):
btn.fill_color = mcrfpy.Color(80, 80, 120)
def on_click(pos, button, action):
if action == mcrfpy.InputState.PRESSED:
btn.fill_color = mcrfpy.Color(60, 60, 100)
callback()
elif action == mcrfpy.InputState.RELEASED:
btn.fill_color = mcrfpy.Color(100, 100, 150)
btn.on_enter = on_enter
btn.on_exit = on_exit
btn.on_click = on_click
return btn
status = mcrfpy.Caption(text="Click a button", pos=(420, 600))
scene.children.append(status)
def click1(): status.text = "Button 1 clicked!"
def click2(): status.text = "Button 2 clicked!"
def click3(): status.text = "Button 3 clicked!"
scene.children.append(make_button(412, 200, "Button 1", click1))
scene.children.append(make_button(412, 300, "Button 2", click2))
scene.children.append(make_button(412, 400, "Button 3", click3))

View file

@ -0,0 +1,64 @@
# mcrf: objects=[Caption,Color,Easing,Frame,InputState,Key,Scene] verified=0.2.8-dev status=ok
# UI Health Bar - Animated health display
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
class HealthBar:
def __init__(self, x, y, width, height, max_health):
self.max_health = max_health
self.health = max_health
self.width = width
# Background
self.bg = mcrfpy.Frame(
pos=(x, y), size=(width, height),
fill_color=mcrfpy.Color(60, 20, 20),
outline=2.0, outline_color=mcrfpy.Color(100, 100, 100)
)
# Fill bar
self.fill = mcrfpy.Frame(
pos=(2, 2), size=(width - 4, height - 4),
fill_color=mcrfpy.Color(200, 50, 50)
)
self.bg.children.append(self.fill)
# Label
self.label = mcrfpy.Caption(
text=f"{max_health}/{max_health}",
pos=(width // 2 - 30, height // 2 - 10)
)
self.bg.children.append(self.label)
def set_health(self, value):
self.health = max(0, min(value, self.max_health))
ratio = self.health / self.max_health
self.fill.animate("w", (self.width - 4) * ratio, 0.3, mcrfpy.Easing.EASE_OUT)
self.label.text = f"{self.health}/{self.max_health}"
# Color based on health
if ratio > 0.5:
self.fill.fill_color = mcrfpy.Color(100, 200, 100)
elif ratio > 0.25:
self.fill.fill_color = mcrfpy.Color(200, 200, 50)
else:
self.fill.fill_color = mcrfpy.Color(200, 50, 50)
hp_bar = HealthBar(312, 300, 400, 40, 100)
scene.children.append(hp_bar.bg)
scene.children.append(mcrfpy.Caption(text="Press 1-5 to set health", pos=(380, 400)))
def on_key(key, action):
if action != mcrfpy.InputState.PRESSED: return
if key == mcrfpy.Key.NUM_1: hp_bar.set_health(100)
elif key == mcrfpy.Key.NUM_2: hp_bar.set_health(75)
elif key == mcrfpy.Key.NUM_3: hp_bar.set_health(50)
elif key == mcrfpy.Key.NUM_4: hp_bar.set_health(25)
elif key == mcrfpy.Key.NUM_5: hp_bar.set_health(0)
scene.on_key = on_key

View file

@ -0,0 +1,52 @@
# mcrf: objects=[Caption,Color,Frame,Scene,Vector] verified=0.2.8-dev status=ok
# UI Tooltip - Hover tooltip display
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
# Tooltip (hidden by default)
tooltip = mcrfpy.Frame(
pos=(0, 0), size=(200, 60),
fill_color=mcrfpy.Color(40, 40, 50),
outline=1.0, outline_color=mcrfpy.Color(150, 150, 150),
visible=False
)
tooltip_text = mcrfpy.Caption(text="", pos=(10, 10))
tooltip.children.append(tooltip_text)
def make_item(x, y, name, description):
item = mcrfpy.Frame(
pos=(x, y), size=(150, 150),
fill_color=mcrfpy.Color(80, 80, 120)
)
label = mcrfpy.Caption(text=name, pos=(30, 60))
item.children.append(label)
def on_enter(pos):
tooltip.visible = True
tooltip_text.text = description
tooltip.pos = mcrfpy.Vector(pos.x + 20, pos.y + 20)
def on_exit(pos):
tooltip.visible = False
def on_move(pos):
tooltip.pos = mcrfpy.Vector(pos.x + 20, pos.y + 20)
item.on_enter = on_enter
item.on_exit = on_exit
item.on_move = on_move
return item
scene.children.append(make_item(200, 300, "Sword", "A sharp blade"))
scene.children.append(make_item(400, 300, "Shield", "Blocks attacks"))
scene.children.append(make_item(600, 300, "Potion", "Heals 50 HP"))
scene.children.append(tooltip) # Add last for z-order
status = mcrfpy.Caption(text="Hover over items to see tooltips", pos=(350, 550))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)

View file

@ -0,0 +1,62 @@
# mcrf: objects=[Caption,Color,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# UI Message Log - Scrolling text messages
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
class MessageLog:
def __init__(self, x, y, width, height, max_messages=8):
self.max_messages = max_messages
self.messages = []
self.container = mcrfpy.Frame(
pos=(x, y), size=(width, height),
fill_color=mcrfpy.Color(20, 20, 30),
outline=2.0, outline_color=mcrfpy.Color(80, 80, 100),
clip_children=True
)
def add_message(self, text, color):
self.messages.append((text, color))
if len(self.messages) > self.max_messages:
self.messages.pop(0)
self._refresh()
def _refresh(self):
# Clear existing
while len(self.container.children) > 0:
self.container.children.remove(self.container.children[0])
# Add messages
for i, (text, color) in enumerate(self.messages):
cap = mcrfpy.Caption(text=text, pos=(10, 10 + i * 25))
cap.fill_color = color
self.container.children.append(cap)
log = MessageLog(262, 200, 500, 250)
scene.children.append(log.container)
messages = [
("You enter the dungeon.", mcrfpy.Color(200, 200, 200)),
("A goblin appears!", mcrfpy.Color(255, 100, 100)),
("You attack the goblin.", mcrfpy.Color(255, 200, 100)),
("The goblin is defeated!", mcrfpy.Color(100, 255, 100)),
("You found gold!", mcrfpy.Color(255, 255, 100)),
("You descend deeper...", mcrfpy.Color(150, 150, 255)),
]
msg_idx = 0
def add_msg(timer, runtime):
global msg_idx
if msg_idx < len(messages):
log.add_message(*messages[msg_idx])
msg_idx += 1
timer = mcrfpy.Timer("msg", add_msg, 1000)
_caption = mcrfpy.Caption(text="Message Log Demo", pos=(420, 100))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)

View file

@ -0,0 +1,55 @@
# mcrf: objects=[Caption,Color,Frame,InputState,Key,Scene,Timer] verified=0.2.8-dev status=ok
# Effect Screen Shake - Impact feedback
import mcrfpy
import random
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Container for all game elements
game_container = mcrfpy.Frame(
pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(30, 30, 40)
)
scene.children.append(game_container)
# Some game content
for i in range(5):
frame = mcrfpy.Frame(
pos=(150 + i * 150, 300), size=(100, 100),
fill_color=mcrfpy.Color(100 + i * 30, 100, 200 - i * 30)
)
game_container.children.append(frame)
original_pos = (0, 0)
shake_timer = None
def do_shake(timer, runtime):
offset_x = random.randint(-10, 10)
offset_y = random.randint(-10, 10)
game_container.x = offset_x
game_container.y = offset_y
def end_shake(timer, runtime):
global shake_timer
if shake_timer:
shake_timer.stop()
shake_timer = None
game_container.x = original_pos[0]
game_container.y = original_pos[1]
def trigger_shake():
global shake_timer
shake_timer = mcrfpy.Timer("shake", do_shake, 16)
mcrfpy.Timer("shake_end", end_shake, 300, once=True)
game_container.children.append(mcrfpy.Caption(
text="Press SPACE for screen shake",
pos=(350, 550)
))
def on_key(key, action):
if key == mcrfpy.Key.SPACE and action == mcrfpy.InputState.PRESSED:
trigger_shake()
scene.on_key = on_key

View file

@ -0,0 +1,55 @@
# mcrf: objects=[Caption,Color,Easing,Frame,InputState,Key,Scene,Sprite,Timer] verified=0.2.8-dev status=ok
# Effect Damage Flash - Hit feedback on frame
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
# Character frame (use Frame since Sprite doesn't support color tinting)
character = mcrfpy.Frame(
pos=(400, 250), size=(200, 200),
fill_color=mcrfpy.Color(100, 150, 255)
)
scene.children.append(character)
# Add sprite inside frame
sprite = mcrfpy.Sprite(
texture=mcrfpy.default_texture,
sprite_index=84,
pos=(50, 50),
scale=6.0
)
character.children.append(sprite)
scene.children.append(mcrfpy.Caption(
text="Press SPACE to damage",
pos=(400, 550)
))
hp_label = mcrfpy.Caption(text="HP: 100", pos=(450, 500))
hp_label.fill_color = mcrfpy.Color(100, 255, 100)
scene.children.append(hp_label)
hp = 100
def flash_damage():
global hp
hp = max(0, hp - 10)
hp_label.text = f"HP: {hp}"
# Flash white then red then back
character.fill_color = mcrfpy.Color(255, 255, 255)
character.animate("fill_color", (255, 100, 100, 255), 0.1, mcrfpy.Easing.LINEAR)
def restore(timer, runtime):
character.animate("fill_color", (100, 150, 255, 255), 0.2, mcrfpy.Easing.EASE_OUT)
mcrfpy.Timer("flash_restore", restore, 150, once=True)
def on_key(key, action):
if key == mcrfpy.Key.SPACE and action == mcrfpy.InputState.PRESSED:
flash_damage()
scene.on_key = on_key

View file

@ -0,0 +1,50 @@
# mcrf: objects=[Caption,Color,Easing,Frame,InputState,Scene,Timer] verified=0.2.8-dev status=ok
# Effect Floating Text - Damage numbers
import mcrfpy
import random
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
# Target
target = mcrfpy.Frame(
pos=(412, 284), size=(200, 200),
fill_color=mcrfpy.Color(100, 80, 80)
)
scene.children.append(target)
scene.children.append(mcrfpy.Caption(text="Click target to deal damage", pos=(360, 550)))
text_id = 0
def spawn_damage_text(x, y, damage):
global text_id
text_id += 1
text = mcrfpy.Caption(text=f"-{damage}", pos=(x, y))
text.fill_color = mcrfpy.Color(255, 100, 100)
text.font_size = 32
scene.children.append(text)
# Float up and fade
text.animate("y", y - 100, 1.0, mcrfpy.Easing.EASE_OUT)
text.animate("opacity", 0.0, 1.0, mcrfpy.Easing.EASE_IN)
# Remove after animation using closure to capture text reference
current_text = text
def remove(timer, runtime):
try:
scene.children.remove(current_text)
except:
pass
mcrfpy.Timer(f"remove_{text_id}", remove, 1100, once=True)
def on_click(pos, button, action):
if action == mcrfpy.InputState.PRESSED:
damage = random.randint(10, 99)
# pos is relative to target, add target position
spawn_damage_text(pos.x + 412 + 50, pos.y + 284, damage)
target.on_click = on_click

View file

@ -0,0 +1,39 @@
# mcrf: objects=[Caption,Color,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Effect Pulse - Pulsing highlight
import mcrfpy
import math
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
# Pulsing frame
frame = mcrfpy.Frame(
pos=(362, 284), size=(300, 200),
fill_color=mcrfpy.Color(100, 100, 200),
outline=4.0,
outline_color=mcrfpy.Color(150, 150, 255)
)
scene.children.append(frame)
label = mcrfpy.Caption(text="Pulsing Effect", pos=(80, 80))
frame.children.append(label)
time = 0
def pulse(timer, runtime):
global time
time += 0.1
# Oscillate opacity
opacity = 0.5 + 0.5 * math.sin(time)
frame.opacity = opacity
# Oscillate outline
outline = 2 + 4 * abs(math.sin(time))
frame.outline = outline
timer = mcrfpy.Timer("pulse", pulse, 50)
scene.children.append(mcrfpy.Caption(text="Continuous pulse animation", pos=(380, 550)))

View file

@ -0,0 +1,67 @@
# mcrf: objects=[Caption,Color,Entity,Grid,InputState,Key,Scene,Timer] verified=0.2.8-dev status=ok
# Game Turn-Based - Simple turn system
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
for y in range(12):
for x in range(16):
grid.at(x, y).tilesprite = 48
grid.at(x, y).walkable = True
# Player
player = mcrfpy.Entity(grid_pos=(3, 6), texture=mcrfpy.default_texture, sprite_index=84)
grid.entities.append(player)
# Enemy
enemy = mcrfpy.Entity(grid_pos=(12, 6), texture=mcrfpy.default_texture, sprite_index=112)
grid.entities.append(enemy)
turn = "player"
status = mcrfpy.Caption(text="Player turn - WASD to move", pos=(320, 720))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)
def enemy_turn(timer, runtime):
global turn
# Simple AI: move toward player
dx = 1 if player.grid_x > enemy.grid_x else -1 if player.grid_x < enemy.grid_x else 0
dy = 1 if player.grid_y > enemy.grid_y else -1 if player.grid_y < enemy.grid_y else 0
new_x = enemy.grid_x + dx
new_y = enemy.grid_y + dy
if grid.at(new_x, new_y).walkable:
enemy.grid_pos = (new_x, new_y)
turn = "player"
status.text = "Player turn - WASD to move"
def on_key(key, action):
global turn
if action != mcrfpy.InputState.PRESSED or turn != "player":
return
x, y = player.grid_x, player.grid_y
nx, ny = x, y
if key == mcrfpy.Key.W: ny = y - 1
elif key == mcrfpy.Key.S: ny = y + 1
elif key == mcrfpy.Key.A: nx = x - 1
elif key == mcrfpy.Key.D: nx = x + 1
else: return
if 0 <= nx < 16 and 0 <= ny < 12 and grid.at(nx, ny).walkable:
player.grid_pos = (nx, ny)
turn = "enemy"
status.text = "Enemy turn..."
mcrfpy.Timer("enemy", enemy_turn, 500, once=True)
scene.on_key = on_key

View file

@ -0,0 +1,57 @@
# mcrf: objects=[Caption,Color,Frame,Scene,Sprite] verified=0.2.8-dev status=ok
# Game Inventory - Grid-based inventory UI
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
# Inventory grid
inv_frame = mcrfpy.Frame(
pos=(262, 184), size=(500, 400),
fill_color=mcrfpy.Color(40, 40, 60),
outline=3.0, outline_color=mcrfpy.Color(100, 100, 140)
)
scene.children.append(inv_frame)
inv_frame.children.append(mcrfpy.Caption(text="Inventory", pos=(200, 20)))
# Create inventory slots
slots = []
items = [84, 17, 65, 0, 0, 0, 112, 0, 88, 0, 0, 0] # Sprite indices, 0 = empty
for i in range(12):
x = 30 + (i % 4) * 115
y = 70 + (i // 4) * 100
slot = mcrfpy.Frame(
pos=(x, y), size=(90, 80),
fill_color=mcrfpy.Color(60, 60, 80),
outline=2.0, outline_color=mcrfpy.Color(80, 80, 100)
)
inv_frame.children.append(slot)
if items[i] > 0:
sprite = mcrfpy.Sprite(
texture=mcrfpy.default_texture,
sprite_index=items[i],
pos=(25, 15),
scale=3.0
)
slot.children.append(sprite)
# Hover effect
def make_hover(s):
def enter(pos): s.fill_color = mcrfpy.Color(80, 80, 100)
def exit(pos): s.fill_color = mcrfpy.Color(60, 60, 80)
return enter, exit
enter, exit = make_hover(slot)
slot.on_enter = enter
slot.on_exit = exit
status = mcrfpy.Caption(text="Hover over slots to highlight", pos=(400, 620))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)

View file

@ -0,0 +1,66 @@
# mcrf: objects=[Caption,Color,Entity,Grid,InputState,Key,Scene] verified=0.2.8-dev status=ok
# Game Minimap - Small overview map
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
# Main game grid - fills most of the screen
main_grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(800, 600),
zoom=3.0
)
scene.children.append(main_grid)
# Create dungeon
for y in range(12):
for x in range(16):
cell = main_grid.at(x, y)
if x == 0 or x == 15 or y == 0 or y == 11:
cell.tilesprite = 1
elif x == 8 and y > 2 and y < 9:
cell.tilesprite = 1
else:
cell.tilesprite = 48
player = mcrfpy.Entity(grid_pos=(4, 6), texture=mcrfpy.default_texture, sprite_index=84)
main_grid.entities.append(player)
# Minimap - small overview in corner
minimap = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(820, 20),
size=(180, 135),
zoom=0.7
)
scene.children.append(minimap)
# Copy layout to minimap
for y in range(12):
for x in range(16):
minimap.at(x, y).tilesprite = main_grid.at(x, y).tilesprite
# Minimap player marker
mini_player = mcrfpy.Entity(grid_pos=(4, 6), texture=mcrfpy.default_texture, sprite_index=84)
minimap.entities.append(mini_player)
def on_key(key, action):
if action != mcrfpy.InputState.PRESSED: return
x, y = player.grid_x, player.grid_y
if key == mcrfpy.Key.W and main_grid.at(x, y-1).tilesprite == 48: y -= 1
elif key == mcrfpy.Key.S and main_grid.at(x, y+1).tilesprite == 48: y += 1
elif key == mcrfpy.Key.A and main_grid.at(x-1, y).tilesprite == 48: x -= 1
elif key == mcrfpy.Key.D and main_grid.at(x+1, y).tilesprite == 48: x += 1
player.grid_pos = (x, y)
mini_player.grid_pos = (x, y)
scene.on_key = on_key
status = mcrfpy.Caption(text="WASD to move, watch minimap", pos=(320, 720))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)

View file

@ -0,0 +1,43 @@
# mcrf: objects=[Caption,Color,ColorLayer,Grid,Scene,TileLayer] verified=0.2.8-dev status=ok
# Grid Layers - Multiple tile layers
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Base layer - floor
for y in range(12):
for x in range(16):
grid.at(x, y).tilesprite = 48
# Add a color overlay layer
color_layer = mcrfpy.ColorLayer(z_index=1, name="highlights")
grid.add_layer(color_layer)
# Highlight some cells with color
for x in range(4, 12):
color_layer.set((x, 5), mcrfpy.Color(100, 200, 255, 100))
color_layer.set((x, 6), mcrfpy.Color(100, 200, 255, 100))
# Add a tile layer for decorations
tile_layer = mcrfpy.TileLayer(z_index=2, name="decorations", texture=mcrfpy.default_texture)
grid.add_layer(tile_layer)
# Add some decoration tiles
tile_layer.set((5, 3), 17) # Chest
tile_layer.set((10, 3), 65) # Door
tile_layer.set((7, 8), 80) # Water
status = mcrfpy.Caption(text="Grid with multiple layers: base + color + decoration", pos=(280, 720))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)

View file

@ -0,0 +1,46 @@
# mcrf: objects=[Caption,Color,ColorLayer,Grid,Scene] verified=0.2.8-dev status=ok
# Bresenham Line - Draw lines on grid
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(20, 15),
texture=mcrfpy.default_texture,
pos=(112, 84),
size=(800, 600)
)
scene.children.append(grid)
# Add color layer for line visualization
line_layer = mcrfpy.ColorLayer(z_index=1, name="line")
grid.add_layer(line_layer)
# Fill with floor
for y in range(15):
for x in range(20):
grid.at(x, y).tilesprite = 48
# Draw lines using bresenham
start = (2, 2)
end = (17, 12)
# Get all cells along the line
line_cells = mcrfpy.bresenham(start, end)
# Highlight the line
for x, y in line_cells:
line_layer.set((x, y), mcrfpy.Color(255, 200, 100, 200))
# Mark start and end
line_layer.set((start[0], start[1]), mcrfpy.Color(100, 255, 100, 255))
line_layer.set((end[0], end[1]), mcrfpy.Color(255, 100, 100, 255))
status = mcrfpy.Caption(
text=f"Bresenham line: {len(line_cells)} cells - Green=start, Red=end",
pos=(280, 700)
)
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)

View file

@ -0,0 +1,50 @@
# mcrf: objects=[Caption,Color,Entity,Grid,Scene,Timer] verified=0.2.8-dev status=ok
# Entity Visibility - Check what entities can see
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Create dungeon
for y in range(12):
for x in range(16):
cell = grid.at(x, y)
if x == 8 and y > 1 and y < 10:
cell.tilesprite = 1
cell.transparent = False
else:
cell.tilesprite = 48
cell.transparent = True
# Player
player = mcrfpy.Entity(grid_pos=(4, 6), texture=mcrfpy.default_texture, sprite_index=84, name="player")
grid.entities.append(player)
# Enemies
for i, pos in enumerate([(12, 3), (12, 6), (12, 9)]):
enemy = mcrfpy.Entity(grid_pos=pos, texture=mcrfpy.default_texture, sprite_index=112, name=f"enemy_{i}")
grid.entities.append(enemy)
grid.perspective = player
grid.fov_radius = 10
status = mcrfpy.Caption(text="Wall blocks view of some enemies", pos=(320, 700))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)
def update_status(timer, runtime):
visible = player.visible_entities()
names = [e.name for e in visible if e.name != "player"]
status.text = f"Visible entities: {names if names else 'none'}"
timer = mcrfpy.Timer("check", update_status, 100)

View file

@ -0,0 +1,59 @@
# mcrf: objects=[Caption,Grid,Scene] verified=0.2.8-dev status=ok
# Procgen Cellular - Cave generation
import mcrfpy
import random
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(40, 30),
texture=mcrfpy.default_texture,
pos=(112, 84),
size=(800, 600)
)
scene.children.append(grid)
# Initialize with random walls
for y in range(30):
for x in range(40):
cell = grid.at(x, y)
if x == 0 or x == 39 or y == 0 or y == 29:
cell.tilesprite = 1
elif random.random() < 0.45:
cell.tilesprite = 1
else:
cell.tilesprite = 48
# Cellular automata iterations
def count_neighbors(x, y):
count = 0
for dy in range(-1, 2):
for dx in range(-1, 2):
if dx == 0 and dy == 0:
continue
nx, ny = x + dx, y + dy
if 0 <= nx < 40 and 0 <= ny < 30:
if grid.at(nx, ny).tilesprite == 1:
count += 1
else:
count += 1
return count
def iterate():
changes = []
for y in range(1, 29):
for x in range(1, 39):
neighbors = count_neighbors(x, y)
if neighbors > 4:
changes.append((x, y, 1))
elif neighbors < 4:
changes.append((x, y, 48))
for x, y, tile in changes:
grid.at(x, y).tilesprite = tile
# Run 4 iterations
for _ in range(4):
iterate()
scene.children.append(mcrfpy.Caption(text="Cellular automata cave generation", pos=(340, 700)))

View file

@ -0,0 +1,54 @@
# mcrf: objects=[Caption,Color,ColorLayer,Grid,InputState,Scene] verified=0.2.8-dev status=ok
# Grid Point State - Cell properties
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(16, 12),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=4.0
)
scene.children.append(grid)
# Add visual layer for highlighting
highlight_layer = mcrfpy.ColorLayer(z_index=1, name="highlight")
grid.add_layer(highlight_layer)
# Setup cells with different properties
for y in range(12):
for x in range(16):
cell = grid.at(x, y)
cell.tilesprite = 48
cell.walkable = True
cell.transparent = True
# Mark some cells as walls
for i in range(5):
cell = grid.at(8, 3 + i)
cell.tilesprite = 1
cell.walkable = False
cell.transparent = False
# Highlight left half as "explored"
for x in range(8):
for y in range(12):
highlight_layer.set((x, y), mcrfpy.Color(100, 200, 100, 80))
# Show cell info on click
status = mcrfpy.Caption(text="Click a cell to see properties", pos=(350, 720))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)
def on_cell_click(pos, button, action):
if action == mcrfpy.InputState.PRESSED:
x, y = int(pos.x), int(pos.y)
if 0 <= x < 16 and 0 <= y < 12:
cell = grid.at(x, y)
status.text = f"Cell ({x},{y}): walkable={cell.walkable}, transparent={cell.transparent}"
grid.on_cell_click = on_cell_click

View file

@ -0,0 +1,49 @@
# mcrf: objects=[Caption,Color,Easing,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Animation Multiple - Several animations at once
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
# Create a frame
frame = mcrfpy.Frame(
pos=(100, 300), size=(100, 100),
fill_color=mcrfpy.Color(255, 100, 100),
outline=2.0,
outline_color=mcrfpy.Color(255, 255, 255),
opacity=1.0
)
scene.children.append(frame)
scene.children.append(mcrfpy.Caption(
text="Multiple animations running simultaneously",
pos=(300, 600)
))
def restart_animation(timer, runtime):
frame.x = 100
frame.y = 300
frame.w = 100
frame.h = 100
frame.fill_color = mcrfpy.Color(255, 100, 100)
frame.opacity = 1.0
frame.outline = 2.0
frame.animate("x", 800, 3.0, mcrfpy.Easing.EASE_IN_OUT)
frame.animate("y", 500, 3.0, mcrfpy.Easing.EASE_IN_OUT_BACK)
frame.animate("w", 200, 3.0, mcrfpy.Easing.EASE_OUT_ELASTIC)
frame.animate("h", 150, 3.0, mcrfpy.Easing.EASE_OUT_ELASTIC)
frame.animate("fill_color", (100, 100, 255, 255), 3.0, mcrfpy.Easing.LINEAR)
frame.animate("opacity", 0.5, 3.0, mcrfpy.Easing.EASE_IN)
frame.animate("outline", 10.0, 3.0, mcrfpy.Easing.LINEAR)
# Start multiple animations at the same time
frame.animate("x", 800, 3.0, mcrfpy.Easing.EASE_IN_OUT)
frame.animate("y", 500, 3.0, mcrfpy.Easing.EASE_IN_OUT_BACK)
frame.animate("w", 200, 3.0, mcrfpy.Easing.EASE_OUT_ELASTIC)
frame.animate("h", 150, 3.0, mcrfpy.Easing.EASE_OUT_ELASTIC)
frame.animate("fill_color", (100, 100, 255, 255), 3.0, mcrfpy.Easing.LINEAR)
frame.animate("opacity", 0.5, 3.0, mcrfpy.Easing.EASE_IN)
frame.animate("outline", 10.0, 3.0, mcrfpy.Easing.LINEAR)
loop_timer = mcrfpy.Timer("loop", restart_animation, 8000)

View file

@ -0,0 +1,60 @@
# mcrf: objects=[Caption,Color,Easing,Entity,Grid,InputState,Key,Scene] verified=0.2.8-dev status=ok
# Camera Follow - Camera tracks entity
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
grid = mcrfpy.Grid(
grid_size=(30, 30),
texture=mcrfpy.default_texture,
pos=(0, 0),
size=(1024, 768),
zoom=2.0
)
scene.children.append(grid)
# Create large map
for y in range(30):
for x in range(30):
cell = grid.at(x, y)
if x == 0 or x == 29 or y == 0 or y == 29:
cell.tilesprite = 1
cell.walkable = False
elif (x + y) % 7 == 0 and x > 1 and x < 28 and y > 1 and y < 28:
cell.tilesprite = 1
cell.walkable = False
else:
cell.tilesprite = 48
cell.walkable = True
# Player in center
player = mcrfpy.Entity(grid_pos=(15, 15), texture=mcrfpy.default_texture, sprite_index=84)
grid.entities.append(player)
# Center camera on player
grid.center_camera((15, 15))
def on_key(key, action):
if action != mcrfpy.InputState.PRESSED:
return
x, y = player.grid_x, player.grid_y
nx, ny = x, y
if key == mcrfpy.Key.W: ny -= 1
elif key == mcrfpy.Key.S: ny += 1
elif key == mcrfpy.Key.A: nx -= 1
elif key == mcrfpy.Key.D: nx += 1
else: return
if grid.at(nx, ny).walkable:
player.grid_pos = (nx, ny)
# Animate camera to follow (center uses pixel coords: tile * 16 for 16x16 sprites)
grid.animate("center_x", nx * 16 + 8, 0.2, mcrfpy.Easing.EASE_OUT)
grid.animate("center_y", ny * 16 + 8, 0.2, mcrfpy.Easing.EASE_OUT)
scene.on_key = on_key
status = mcrfpy.Caption(text="WASD - Camera follows player", pos=(380, 720))
status.outline = 2
status.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(status)

View file

@ -0,0 +1,45 @@
# mcrf: objects=[Caption,Color,Frame,InputState,Key,Scene] verified=0.2.8-dev status=ok
# Scene Lifecycle - on_enter/on_exit callbacks
import mcrfpy
# Create scenes with lifecycle callbacks
class MenuScene(mcrfpy.Scene):
def __init__(self):
super().__init__("menu")
self.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(50, 50, 80)))
self.status = mcrfpy.Caption(text="Menu Scene", pos=(420, 300))
self.status.fill_color = mcrfpy.Color(255, 220, 100)
self.children.append(self.status)
self.children.append(mcrfpy.Caption(text="Press SPACE for game", pos=(380, 400)))
self.on_key = self.handle_key
def on_enter(self):
self.status.text = "Menu Scene (entered!)"
def on_exit(self):
print("Leaving menu scene")
def handle_key(self, key, action):
if key == mcrfpy.Key.SPACE and action == mcrfpy.InputState.PRESSED:
game_scene.activate()
class GameScene(mcrfpy.Scene):
def __init__(self):
super().__init__("game")
self.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(80, 50, 50)))
self.status = mcrfpy.Caption(text="Game Scene", pos=(420, 300))
self.status.fill_color = mcrfpy.Color(100, 255, 100)
self.children.append(self.status)
self.children.append(mcrfpy.Caption(text="Press SPACE for menu", pos=(380, 400)))
self.on_key = self.handle_key
def on_enter(self):
self.status.text = "Game Scene (entered!)"
def handle_key(self, key, action):
if key == mcrfpy.Key.SPACE and action == mcrfpy.InputState.PRESSED:
menu_scene.activate()
menu_scene = MenuScene()
game_scene = GameScene()
mcrfpy.current_scene = menu_scene

View file

@ -0,0 +1,44 @@
# mcrf: objects=[Caption,Color,Frame,Scene,Timer] verified=0.2.8-dev status=ok
# Metrics Display - Performance monitoring
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
_caption = mcrfpy.Caption(
text="Performance Metrics",
pos=(400, 80))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)
# Create metric displays
labels = {}
metrics_list = ["frame_time", "fps", "draw_calls", "ui_elements", "visible_elements"]
for i, name in enumerate(metrics_list):
label = mcrfpy.Caption(text=f"{name}: --", pos=(300, 180 + i * 60))
scene.children.append(label)
labels[name] = label
# Add some elements to track
for i in range(50):
frame = mcrfpy.Frame(
pos=(100 + (i % 10) * 80, 500 + (i // 10) * 50),
size=(60, 40),
fill_color=mcrfpy.Color(80 + i * 3, 80, 150 - i * 2)
)
scene.children.append(frame)
def update_metrics(timer, runtime):
metrics = mcrfpy.getMetrics()
for name in metrics_list:
if name in metrics:
value = metrics[name]
if isinstance(value, float):
labels[name].text = f"{name}: {value:.2f}"
else:
labels[name].text = f"{name}: {value}"
timer = mcrfpy.Timer("metrics", update_metrics, 100)

View file

@ -0,0 +1,48 @@
# mcrf: objects=[Caption,Color,Frame,Scene] verified=0.2.8-dev status=ok
# Global Position - World coordinates
import mcrfpy
scene = mcrfpy.Scene("demo")
mcrfpy.current_scene = scene
scene.children.append(mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(30, 30, 40)))
# Nested containers
outer = mcrfpy.Frame(
pos=(200, 200), size=(500, 400),
fill_color=mcrfpy.Color(60, 60, 80),
outline=2.0
)
scene.children.append(outer)
inner = mcrfpy.Frame(
pos=(100, 100), size=(300, 200),
fill_color=mcrfpy.Color(80, 80, 100),
outline=2.0
)
outer.children.append(inner)
deepest = mcrfpy.Frame(
pos=(50, 50), size=(100, 80),
fill_color=mcrfpy.Color(150, 100, 100),
outline=2.0
)
inner.children.append(deepest)
# Show positions
outer.children.append(mcrfpy.Caption(text="Outer: pos=(200,200)", pos=(10, 10)))
inner.children.append(mcrfpy.Caption(text="Inner: pos=(100,100)", pos=(10, 10)))
# Global position
gpos = deepest.global_bounds
deepest.children.append(mcrfpy.Caption(
text=f"local=(50,50)",
pos=(5, 5)
))
_caption = mcrfpy.Caption(
text=f"Deepest global_pos = ({gpos[0].x}, {gpos[0].y})",
pos=(300, 650))
_caption.fill_color = mcrfpy.Color(255, 220, 100)
scene.children.append(_caption)
scene.children.append(mcrfpy.Caption(text="(200 + 100 + 50 = 350, 200 + 100 + 50 = 350)", pos=(300, 690)))

Some files were not shown because too many files have changed in this diff Show more