Remove camelCase module functions (setScale, findAll, getMetrics, setDevConsole), closes #304

Breaking API change: removes 4 camelCase function aliases from the mcrfpy
module. The snake_case equivalents (set_scale, find_all, get_metrics,
set_dev_console) remain and are the canonical API going forward.

- Removed setScale, findAll, getMetrics, setDevConsole from mcrfpyMethods[]
- Updated game scripts to use snake_case names
- Updated test scripts to use snake_case names
- Removed camelCase entries from type stubs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
John McCardle 2026-04-10 01:07:22 -04:00
commit e7462e37a3
7 changed files with 244 additions and 63 deletions

View file

@ -269,15 +269,6 @@ static PyMethodDef mcrfpyMethods[] = {
MCRF_RETURNS("None") MCRF_RETURNS("None")
MCRF_NOTE("This immediately closes the window and terminates the program.") MCRF_NOTE("This immediately closes the window and terminates the program.")
)}, )},
{"setScale", McRFPy_API::_setScale, METH_VARARGS,
MCRF_FUNCTION(setScale,
MCRF_SIG("(multiplier: float)", "None"),
MCRF_DESC("Deprecated: use Window.resolution instead. Scale the game window size."),
MCRF_ARGS_START
MCRF_ARG("multiplier", "Scale factor (e.g., 2.0 for double size)")
MCRF_RETURNS("None")
MCRF_NOTE("The internal resolution remains 1024x768, but the window is scaled. This is deprecated - use Window.resolution instead.")
)},
{"set_scale", McRFPy_API::_setScale, METH_VARARGS, {"set_scale", McRFPy_API::_setScale, METH_VARARGS,
MCRF_FUNCTION(set_scale, MCRF_FUNCTION(set_scale,
MCRF_SIG("(multiplier: float)", "None"), MCRF_SIG("(multiplier: float)", "None"),
@ -298,16 +289,6 @@ static PyMethodDef mcrfpyMethods[] = {
MCRF_RETURNS("Frame, Caption, Sprite, Grid, or Entity if found; None otherwise") MCRF_RETURNS("Frame, Caption, Sprite, Grid, or Entity if found; None otherwise")
MCRF_NOTE("Searches scene UI elements and entities within grids.") MCRF_NOTE("Searches scene UI elements and entities within grids.")
)}, )},
{"findAll", McRFPy_API::_findAll, METH_VARARGS,
MCRF_FUNCTION(findAll,
MCRF_SIG("(pattern: str, scene: str = None)", "list"),
MCRF_DESC("Find all UI elements matching a name pattern. Prefer find_all()."),
MCRF_ARGS_START
MCRF_ARG("pattern", "Name pattern with optional wildcards (* matches any characters)")
MCRF_ARG("scene", "Scene to search in (default: current scene)")
MCRF_RETURNS("list: All matching UI elements and entities")
MCRF_NOTE("Example: find_all('enemy*') finds all elements starting with 'enemy'")
)},
{"find_all", McRFPy_API::_findAll, METH_VARARGS, {"find_all", McRFPy_API::_findAll, METH_VARARGS,
MCRF_FUNCTION(find_all, MCRF_FUNCTION(find_all,
MCRF_SIG("(pattern: str, scene: str = None)", "list"), MCRF_SIG("(pattern: str, scene: str = None)", "list"),
@ -319,12 +300,6 @@ static PyMethodDef mcrfpyMethods[] = {
MCRF_NOTE("Example: find_all('enemy*') finds all elements starting with 'enemy', find_all('*_button') finds all elements ending with '_button'") MCRF_NOTE("Example: find_all('enemy*') finds all elements starting with 'enemy', find_all('*_button') finds all elements ending with '_button'")
)}, )},
{"getMetrics", McRFPy_API::_getMetrics, METH_NOARGS,
MCRF_FUNCTION(getMetrics,
MCRF_SIG("()", "dict"),
MCRF_DESC("Get current performance metrics. Prefer get_metrics()."),
MCRF_RETURNS("dict: Performance data with keys: frame_time, avg_frame_time, fps, draw_calls, ui_elements, visible_elements, current_frame, runtime")
)},
{"get_metrics", McRFPy_API::_getMetrics, METH_NOARGS, {"get_metrics", McRFPy_API::_getMetrics, METH_NOARGS,
MCRF_FUNCTION(get_metrics, MCRF_FUNCTION(get_metrics,
MCRF_SIG("()", "dict"), MCRF_SIG("()", "dict"),
@ -332,15 +307,6 @@ static PyMethodDef mcrfpyMethods[] = {
MCRF_RETURNS("dict: Performance data with keys: frame_time (last frame duration in seconds), avg_frame_time (average frame time), fps (frames per second), draw_calls (number of draw calls), ui_elements (total UI element count), visible_elements (visible element count), current_frame (frame counter), runtime (total runtime in seconds)") MCRF_RETURNS("dict: Performance data with keys: frame_time (last frame duration in seconds), avg_frame_time (average frame time), fps (frames per second), draw_calls (number of draw calls), ui_elements (total UI element count), visible_elements (visible element count), current_frame (frame counter), runtime (total runtime in seconds)")
)}, )},
{"setDevConsole", McRFPy_API::_setDevConsole, METH_VARARGS,
MCRF_FUNCTION(setDevConsole,
MCRF_SIG("(enabled: bool)", "None"),
MCRF_DESC("Enable or disable the developer console overlay. Prefer set_dev_console()."),
MCRF_ARGS_START
MCRF_ARG("enabled", "True to enable the console (default), False to disable")
MCRF_RETURNS("None")
MCRF_NOTE("When disabled, the grave/tilde key will not open the console. Use this to ship games without debug features.")
)},
{"set_dev_console", McRFPy_API::_setDevConsole, METH_VARARGS, {"set_dev_console", McRFPy_API::_setDevConsole, METH_VARARGS,
MCRF_FUNCTION(set_dev_console, MCRF_FUNCTION(set_dev_console,
MCRF_SIG("(enabled: bool)", "None"), MCRF_SIG("(enabled: bool)", "None"),

View file

@ -0,0 +1,239 @@
"""Main menu scene for Crypt of Sokoban.
Displays a title screen with a live demo grid, play button,
and settings buttons. Replaces the 7DRL's MainMenu class
that duplicated entity spawning code from the Crypt class.
"""
import random
import mcrfpy
from cos import Resources
from cos.constants import (
GRID_ZOOM, ENEMY_PRESETS, COLOR_TEXT,
)
from cos.level.generator import Level
from cos.entities.player import PlayerEntity
from cos.entities.enemies import EnemyEntity
from cos.entities.objects import BoulderEntity, ExitEntity
from cos.ui.widgets import SweetButton
class MenuScene:
"""Main menu with animated demo and navigation buttons.
The demo grid shows a small generated level with entities
that wander randomly, giving the menu visual interest.
"""
def __init__(self):
self.scene = mcrfpy.Scene("menu")
self.ui = self.scene.children
self.play_scene = None
res = Resources()
# -- Demo grid (background) ---------------------------------------
self.entities = [] # entity registry for demo animation
self._demo_level = Level(20, 20)
self.grid = self._demo_level.grid
self.grid.zoom = 1.75
gw = int(self.grid.grid_size.x)
gh = int(self.grid.grid_size.y)
self.grid.center_camera((gw / 2.0, gh / 2.0))
demo_plan = [
("boulder", "boulder", "rat", "cyclops", "boulder"),
("spawn",),
("rat", "big rat"),
("button", "boulder", "exit"),
]
coords = self._demo_level.generate(demo_plan)
self._spawn_demo_entities(coords)
# Wire up engine entities for rendering
for entity in self.entities:
entity.entity.grid = self.grid
self.demo_timer = mcrfpy.Timer("demo_motion", self._demo_tick, 100)
# -- Title text ---------------------------------------------------
drop_shadow = mcrfpy.Caption(
text="Crypt Of Sokoban", pos=(150, 10), font=res.font,
fill_color=(96, 96, 96), outline_color=(192, 0, 0),
)
drop_shadow.outline = 3
drop_shadow.font_size = 64
title = mcrfpy.Caption(
text="Crypt Of Sokoban", pos=(158, 18), font=res.font,
fill_color=COLOR_TEXT,
)
title.font_size = 64
# -- Toast notification -------------------------------------------
self.toast = mcrfpy.Caption(
text="", pos=(150, 400), font=res.font,
fill_color=(0, 0, 0),
)
self.toast.font_size = 28
self.toast.outline = 2
self.toast.outline_color = (255, 255, 255)
self._toast_remaining = None
self._toast_timer = None
# -- Buttons ------------------------------------------------------
play_btn = SweetButton(
(20, 248), "PLAY",
box_width=200, box_height=110,
icon=1, icon_scale=2.0, click=self._on_play,
)
config_btn = SweetButton(
(10, 678), "Settings", icon=2, click=self._on_config,
)
scale_btn = SweetButton(
(266, 678), "Scale up\nto 1080p", icon=15, click=self._on_scale,
)
self._scaled = False
music_btn = SweetButton(
(522, 678), "Music\nON", icon=12, click=self._on_music_toggle,
)
sfx_btn = SweetButton(
(778, 678), "SFX\nON", icon=0, click=self._on_sfx_toggle,
)
# -- Assemble scene -----------------------------------------------
for element in (
self.grid, drop_shadow, title, self.toast,
play_btn.base_frame, config_btn.base_frame,
scale_btn.base_frame, music_btn.base_frame,
sfx_btn.base_frame,
):
self.ui.append(element)
# -- Entity registry (minimal, for demo only) -------------------------
def register_entity(self, entity):
self.entities.append(entity)
def unregister_entity(self, entity):
if entity in self.entities:
self.entities.remove(entity)
# -- Demo animation ---------------------------------------------------
def _spawn_demo_entities(self, coords):
"""Spawn entities for the demo grid. Reuses the same entity
creation as PlayScene to avoid the 7DRL's duplicated spawning."""
buttons = []
for name, pos in sorted(coords, key=lambda c: c[0]):
if name == "spawn":
self.player = PlayerEntity(game=self)
self.player.draw_pos = pos
elif name == "boulder":
BoulderEntity(pos[0], pos[1], game=self)
elif name == "button":
buttons.append(pos)
elif name == "exit":
btn = buttons.pop(0)
ExitEntity(pos[0], pos[1], btn[0], btn[1], game=self)
elif name in ENEMY_PRESETS:
EnemyEntity.from_preset(name, pos[0], pos[1], game=self)
def _demo_tick(self, timer, runtime):
"""Timer callback: animate demo entities randomly."""
try:
dirs = ((1, 0), (-1, 0), (0, 1), (0, -1))
self.player.try_move(*random.choice(dirs))
for entity in self.entities:
entity.act()
except Exception:
pass # demo animation is cosmetic; don't crash the menu
# -- Navigation -------------------------------------------------------
def activate(self):
self.scene.activate()
def _on_play(self, btn, args):
if args[3] == mcrfpy.InputState.RELEASED:
return
self.demo_timer.stop()
from cos.scenes.play import PlayScene
self.play_scene = PlayScene()
self.play_scene.activate()
# -- Settings buttons -------------------------------------------------
def _on_config(self, btn, args):
if args[3] == mcrfpy.InputState.RELEASED:
return
self._show_toast("Settings will go here.")
def _on_scale(self, btn, args):
if args[3] == mcrfpy.InputState.RELEASED:
return
self._scaled = not self._scaled
btn.unpress()
if self._scaled:
self._show_toast("Windowed mode only.\nCheck Settings for fine-tuned controls.")
mcrfpy.set_scale(1.3)
btn.text = "Scale down\nto 1.0x"
else:
mcrfpy.set_scale(1.0)
btn.text = "Scale up\nto 1080p"
def _on_music_toggle(self, btn, args):
if args[3] == mcrfpy.InputState.RELEASED:
return
res = Resources()
res.music_enabled = not res.music_enabled
if res.music_enabled:
res.set_music_volume(res.music_volume)
btn.text = "Music\nON"
btn.sprite_number = 12
else:
self._show_toast("Use your volume keys or\nlook in Settings for a volume meter.")
res.set_music_volume(0)
btn.text = "Music\nOFF"
btn.sprite_number = 17
def _on_sfx_toggle(self, btn, args):
if args[3] == mcrfpy.InputState.RELEASED:
return
res = Resources()
res.sfx_enabled = not res.sfx_enabled
if res.sfx_enabled:
res.set_sfx_volume(res.sfx_volume)
btn.text = "SFX\nON"
btn.sprite_number = 0
else:
self._show_toast("Use your volume keys or\nlook in Settings for a volume meter.")
res.set_sfx_volume(0)
btn.text = "SFX\nOFF"
btn.sprite_number = 17
# -- Toast notification -----------------------------------------------
def _show_toast(self, text):
self.toast.text = text
self._toast_remaining = 350
self.toast.fill_color = (255, 255, 255, 255)
self.toast.outline_color = (0, 0, 0, 255)
if self._toast_timer:
self._toast_timer.stop()
self._toast_timer = mcrfpy.Timer("toast_timer", self._toast_tick, 100)
def _toast_tick(self, timer, runtime):
self._toast_remaining -= 5
if self._toast_remaining < 0:
self._toast_timer.stop()
self._toast_timer = None
self.toast.text = ""
return
alpha = min(self._toast_remaining, 255)
self.toast.fill_color = (255, 255, 255, alpha)
self.toast.outline_color = (0, 0, 0, alpha)

View file

@ -729,10 +729,10 @@ class MainMenu:
sweet_btn.unpress() sweet_btn.unpress()
if self.scaled: if self.scaled:
self.toast_say("Windowed mode only, sorry!\nCheck Settings for for fine-tuned controls.") self.toast_say("Windowed mode only, sorry!\nCheck Settings for for fine-tuned controls.")
mcrfpy.setScale(window_scale) mcrfpy.set_scale(window_scale)
sweet_btn.text = "Scale down\n to 1.0x" sweet_btn.text = "Scale down\n to 1.0x"
else: else:
mcrfpy.setScale(1.0) mcrfpy.set_scale(1.0)
sweet_btn.text = "Scale up\nto 1080p" sweet_btn.text = "Scale up\nto 1080p"
def music_toggle(self, sweet_btn, args): def music_toggle(self, sweet_btn, args):

View file

@ -1121,22 +1121,10 @@ def exit() -> None:
"""Cleanly shut down the game engine and exit the application.""" """Cleanly shut down the game engine and exit the application."""
... ...
def setScale(multiplier: float) -> None:
"""Scale the game window size (deprecated - use Window.resolution)."""
...
def find(name: str, scene: Optional[str] = None) -> Optional[UIElement]: def find(name: str, scene: Optional[str] = None) -> Optional[UIElement]:
"""Find the first UI element with the specified name.""" """Find the first UI element with the specified name."""
... ...
def findAll(pattern: str, scene: Optional[str] = None) -> List[UIElement]:
"""Find all UI elements matching a name pattern (supports * wildcards)."""
...
def getMetrics() -> Dict[str, Union[int, float]]:
"""Get current performance metrics."""
...
def step(dt: float) -> None: def step(dt: float) -> None:
"""Advance the game loop by dt seconds (headless mode only).""" """Advance the game loop by dt seconds (headless mode only)."""
... ...

View file

@ -1298,22 +1298,10 @@ def exit() -> None:
"""Cleanly shut down the game engine and exit the application.""" """Cleanly shut down the game engine and exit the application."""
... ...
def setScale(multiplier: float) -> None:
"""Scale the game window size (deprecated - use Window.resolution)."""
...
def find(name: str, scene: Optional[str] = None) -> Optional[UIElement]: def find(name: str, scene: Optional[str] = None) -> Optional[UIElement]:
"""Find the first UI element with the specified name.""" """Find the first UI element with the specified name."""
... ...
def findAll(pattern: str, scene: Optional[str] = None) -> List[UIElement]:
"""Find all UI elements matching a name pattern (supports * wildcards)."""
...
def getMetrics() -> Dict[str, Union[int, float]]:
"""Get current performance metrics."""
...
def step(dt: float) -> None: def step(dt: float) -> None:
"""Advance the game loop by dt seconds (headless mode only).""" """Advance the game loop by dt seconds (headless mode only)."""
... ...

View file

@ -42,7 +42,7 @@ def collect_metrics(timer, runtime):
return return
# Collect sample # Collect sample
m = mcrfpy.getMetrics() m = mcrfpy.get_metrics()
metrics_samples.append({ metrics_samples.append({
'frame_time': m['frame_time'], 'frame_time': m['frame_time'],
'avg_frame_time': m['avg_frame_time'], 'avg_frame_time': m['avg_frame_time'],

View file

@ -14,7 +14,7 @@ def test_metrics(timer, runtime):
print("\nRunning metrics test...") print("\nRunning metrics test...")
# Get metrics # Get metrics
metrics = mcrfpy.getMetrics() metrics = mcrfpy.get_metrics()
print("\nPerformance Metrics:") print("\nPerformance Metrics:")
print(f" Frame Time: {metrics['frame_time']:.2f} ms") print(f" Frame Time: {metrics['frame_time']:.2f} ms")
@ -81,7 +81,7 @@ def test_metrics(timer, runtime):
# Schedule another check after 100ms # Schedule another check after 100ms
def check_later(timer2, runtime2): def check_later(timer2, runtime2):
global success global success
metrics2 = mcrfpy.getMetrics() metrics2 = mcrfpy.get_metrics()
print(f"\nMetrics after 100ms:") print(f"\nMetrics after 100ms:")
print(f" Frame Time: {metrics2['frame_time']:.2f} ms") print(f" Frame Time: {metrics2['frame_time']:.2f} ms")