{"id":33916833,"url":"https://github.com/matheusrich/rich_engine","last_synced_at":"2025-12-12T07:32:36.141Z","repository":{"id":81191782,"uuid":"291869216","full_name":"MatheusRich/rich_engine","owner":"MatheusRich","description":"A terminal-based Ruby game engine","archived":false,"fork":false,"pushed_at":"2025-10-21T16:50:48.000Z","size":87,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-21T18:32:27.991Z","etag":null,"topics":["hacktoberfest","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MatheusRich.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-09-01T01:55:35.000Z","updated_at":"2025-10-21T16:50:52.000Z","dependencies_parsed_at":null,"dependency_job_id":"7452acc1-006c-4bf2-86d4-d5bc20c8ea8a","html_url":"https://github.com/MatheusRich/rich_engine","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/MatheusRich/rich_engine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatheusRich%2Frich_engine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatheusRich%2Frich_engine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatheusRich%2Frich_engine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatheusRich%2Frich_engine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MatheusRich","download_url":"https://codeload.github.com/MatheusRich/rich_engine/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatheusRich%2Frich_engine/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27678892,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-12T02:00:06.775Z","response_time":129,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hacktoberfest","ruby"],"created_at":"2025-12-12T07:32:34.602Z","updated_at":"2025-12-12T07:32:36.133Z","avatar_url":"https://github.com/MatheusRich.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# RichEngine\n\nRichEngine is a tiny terminal game engine for Ruby. It gives you a simple game\nloop, a 2D character canvas with colors, non-blocking keyboard input, and a\nhandful of helpers (timers, cooldowns, RNG, enums, matrices) so you can ship\nplayful ASCII games quickly.\n\nAt its core, you subclass `RichEngine::Game`, implement a few lifecycle hooks,\nand draw to a `Canvas` each frame.\n\n## Quick start: build a simple game\n\nBelow is a minimal, complete example showing how to:\n- create a game by subclassing `RichEngine::Game`\n- quit on a key press\n- draw text and shapes to the screen\n- use canvas slots to keep a bottom HUD separate from the playfield\n\n```ruby\nrequire \"rich_engine\"\n\nclass MyGame \u003c RichEngine::Game\n  using RichEngine::StringColors\n\n  TITLE = \"Catch the Star\"\n  PLAYER_CHAR = \"@\"\n  PLAYER_COLOR = :yellow\n  ITEM_COLORS = [:green, :magenta, :cyan]\n  ITEM_CHAR = \"*\"\n  HUD_HEIGHT = 3\n\n  def on_create\n    @score = 0\n    @player_x = 2\n    @player_y = field_height / 2\n    @timer = RichEngine::Cooldown.new(5.0)\n    @field = @canvas.slot(x: 0, y: 0, width: @width, height: field_height, bg: RichEngine::UI::Textures.solid.bright_white)\n    @hud   = @canvas.slot(x: 0, y: field_height, width: @width, height: HUD_HEIGHT)\n    spawn_item\n  end\n\n  # elapsed_time: seconds since last frame (Float)\n  # key: last key pressed (Symbol) or nil\n  def on_update(elapsed_time, key)\n    quit! if key == :q || key == :esc\n\n    # Move player with arrow keys\n    case key\n    when :left  then @player_x -= 1\n    when :right then @player_x += 1\n    when :up    then @player_y -= 1\n    when :down  then @player_y += 1\n    end\n\n    # Keep player inside the game field (above the HUD)\n    @player_x = @player_x.clamp(0, @width - 1)\n    @player_y = @player_y.clamp(0, field_height - 1)\n\n    # Game over if time runs out\n    @timer.update(elapsed_time)\n    if @timer.finished?\n      @game_over = true\n      quit!\n    end\n\n    # Pick up item\n    if @player_x == @item_x \u0026\u0026 @player_y == @item_y\n      @score += 1\n      spawn_item\n      @timer.reset!\n    end\n\n    # rendering the frame\n    @canvas.clear\n\n    @field.write_string(ITEM_CHAR, x: @item_x, y: @item_y, fg: @item_color)\n    @field.write_string(PLAYER_CHAR, x: @player_x, y: @player_y, fg: PLAYER_COLOR)\n\n    @hud.write_string(TITLE, x: 0, y: 0, fg: :bright_cyan)\n    @hud.write_string(\"Score: #{@score}\", x: 0, y: 1, fg: :bright_yellow)\n    @hud.write_string(\"Time: #{format('%.1f', @timer.get)}s\", x: 0, y: 2, fg: :bright_green)\n  end\n\n  def on_destroy\n    puts(@game_over ? \"Game over! Final score: #{@score}\" : \"Thanks for playing! Score: #{@score}\")\n  end\n\n  private\n\n  def field_height\n    @height - HUD_HEIGHT\n  end\n\n  def spawn_item\n    @item_x = rand(@width)\n    @item_y = rand(field_height)\n    @item_color = ITEM_COLORS.sample\n  end\nend\n\nMyGame.play(width: 50, height: 12)\n```\n\nNotes\n- Hooks:\n  - `on_create` runs once at start\n  - `on_update(elapsed_time, key)` runs every frame\n  - `on_destroy` runs when the game exits.\n- Keys: letters are symbols (e.g., `:q`), plus arrows (`:up`, `:down`, `:left`, `:right`), `:space`, `:enter`, `:esc`, `:pg_up`, `:pg_down`, `:home`, `:end`.\n- Drawing: all drawing happens on `@canvas`. Call `@canvas.clear` each frame if you want to redraw from scratch.\n- Rendering and frame pacing are handled for you:\n  - `Game` flushes the canvas after each frame\n  - `Game` auto-sleeps to hit your target FPS (60 by default, but configurable via `target_fps:` on `Game.play`)\n\n## Canvas essentials\n\n`@canvas` exposes a few handy methods:\n- `write_string(str, x:, y:, fg: :white, bg: :transparent)` — write colored text; pass a single color or an array (arrays will cycle per character)\n- `draw_rect(x:, y:, width:, height:, char: \"█\", color: :white)` — draw a filled rectangle\n- `draw_circle(x:, y:, radius:, char: \"█\", color: :white)` — draw a filled circle\n- `draw_sprite(sprite, x: 0, y: 0, fg: :white)` — draw a multi-line string as a sprite; spaces are transparent\n- `clear` — clear the entire canvas; `bg=` changes the background fill character and clears\n\n### Canvas slots (sub-canvases)\n\nSlots are sub-regions of a canvas that translate local coordinates and clip drawing automatically. Great for HUDs and side panels.\n\n```ruby\ncanvas = RichEngine::Canvas.new(100, 40)\nhud = canvas.slot(x: 0, y: 35, width: 100, height: 5, bg: \" \")\nhud.clear\nhud.write_string(\"Score: 10\", x: 2, y: 1, fg: :bright_yellow)\n\nlog = canvas.slot(x: 80, y: 0, width: 20, height: 35)\nlog.write_string(\"Hello\", x: 1, y: 1)  # writes to (81, 1) on the parent canvas\n```\n\nColors are provided via a refinement used internally by the canvas. For text, prefer the `fg:` and `bg:` options on `write_string`.\n\n## Animations\n\n`RichEngine::Animation` plays a sequence of string frames (sprites) at a fixed frames-per-second. Each frame is a multi-line string; spaces are treated as transparency by `Canvas#draw_sprite`.\n\n### Quick example\n\n```ruby\n# frozen_string_literal: true\n\nrequire \"rich_engine\"\n\nclass AnimationExample \u003c RichEngine::Game\n  def on_create\n    sprites = [\"(•‿•)\", \"(•‿-)\", \"(-‿-)\", \"(-‿•)\"]\n\n    @animation = RichEngine::Animation.new(\n      frames: sprites,\n      fps: 4,\n      loop: true,\n      fg: :bright_yellow\n    )\n  end\n\n  def on_update(elapsed_time, key)\n    quit! if key == :q\n\n    @animation.update(elapsed_time)\n\n    @canvas.clear\n    @animation.draw(@canvas, x: 0, y: 0)\n    @canvas.write_string(\"q: quit\", x: 0, y: @config[:screen_height] - 1, fg: :black)\n  end\nend\n\nAnimationExample.play(width: 10, height: 3, target_fps: 30)\n```\n\n### API\n\n- Initialize: `RichEngine::Animation.new(frames:, fps: 12, loop: true, fg: :white)`\n  - `frames`: Array\u003cString\u003e — each string is a frame; can be multi-line. Spaces are transparent.\n  - `fps`: Integer/Float — how many frames per second to advance.\n  - `loop`: Boolean — if true, wrap to the first frame after the last; otherwise stop at the last frame.\n  - `fg`: Symbol — default foreground color when drawing.\n- Methods:\n  - `update(dt)`: advance internal timer; call once per frame with `elapsed_time`.\n  - `draw(canvas, x:, y:, fg: default)`: render current frame to the canvas.\n  - `play!`, `pause!`, `stop!`, `reset!`, `playing?`: control playback.\n  - `current_frame`: returns the current frame string.\n  - `fps=`: change playback speed at runtime.\n\nNotes\n- Frames are advanced only when enough time has elapsed per the configured `fps`.\n- When `loop: false`, the animation stops at the last frame and `playing?` becomes false.\n\n## Helpers you can use\n\nAll helpers live under `RichEngine::...` and are independent utilities you can use inside your game code.\n\n### Timer\n\n- `Timer` accumulates elapsed time; you drive it by calling `update(dt)` with the `elapsed_time` from `on_update`.\n- `Timer.every(seconds:)` returns a small scheduler that fires a block at a fixed interval.\n\n```ruby\ntick = RichEngine::Timer.new\n\ndef on_update(dt, _key)\n  tick.update(dt)\n  if tick.get \u003e 2\n    # do something every ~2 seconds\n    tick.reset!\n  end\nend\n\n# Fixed interval\nspawn = RichEngine::Timer.every(seconds: 0.5)\ndef on_update(dt, _key)\n  spawn.update(dt)\n  spawn.when_ready { spawn_enemy! }\nend\n```\n\n### Cooldown\n\nTrack a fixed delay and check if it’s ready.\n\n```ruby\nshoot_cd = RichEngine::Cooldown.new(0.25) # seconds\n\ndef on_update(dt, key)\n  shoot_cd.update(dt)\n  if key == :space \u0026\u0026 shoot_cd.ready?\n    shoot!\n    shoot_cd.reset!\n  end\nend\n```\n\n### Chance (random helpers)\n\n```ruby\nRichEngine::Chance.of(0.2)   # 20% chance\nRichEngine::Chance.of(20)    # also 20% (percent form)\nRichEngine::Chance.of_one_in(10) # 1 in 10 chance\n```\n\n### Enum and Enum::Mixin\n\nCreate ergonomic, comparable enums with query methods.\n\n```ruby\n# Standalone enum\nSTATE = RichEngine::Enum.new(:state, {idle: 0, running: 1, paused: 2})\nSTATE.idle.value        #=\u003e 0\nSTATE.running \u003e STATE.idle #=\u003e true\n\n# In a class via Mixin\nclass Player\n  include RichEngine::Enum::Mixin\n  enum :state, {idle: 0, running: 1, paused: 2}\n\n  def initialize\n    @state = :idle\n  end\n\n  def update\n    if state.running?\n      # ...\n    end\n  end\nend\n```\n\n### Matrix (2D grid)\n\nA simple 2D matrix utility with convenience methods.\n\n```ruby\ngrid = RichEngine::Matrix.new(width: 10, height: 5, fill_with: 0)\ngrid[2, 3] = 1\n\ngrid.each { |cell| puts cell }\n\n# Fill regions\ngrid.fill(x: 0..2, y: 0..1, with: 9)\n\n# Zip two matrices into pairs\nother = RichEngine::Matrix.new(width: 10, height: 5, fill_with: :a)\npairs = grid.zip(other) # =\u003e matrix of [left, right]\n```\n\n### UI::Textures\n\nConvenience glyphs for shading and blocky fills.\n\n| Glyph | Name         |\n|-------|--------------|\n| ␠     | empty        |\n| █     | solid        |\n| ▓     | light_shade  |\n| ▒     | medium_shade |\n| ░     | dark_shade   |\n| ▀     | top_half     |\n| ▄     | bottom_half  |\n| ▌     | left_half    |\n| ▐     | right_half   |\n| ▞     | plaid        |\n\n```ruby\n@canvas.draw_rect(x: 10, y: 6, width: 8, height: 2, char: RichEngine::UI::Textures.solid, color: :magenta)\n```\n\n## Examples\n\nSee the `examples/` folder for more complete samples:\n- `timer.rb` — using timers and intervals\n- `noise.rb` — colorful random output\n- `background.rb` — background fill and drawing\n- `grains_of_sand.rb` — simple cellular-like simulation\n\n## Install and run locally (optional)\n\nAdd to a Gemfile, then bundle:\n\n```ruby\ngem \"rich_engine\"\n```\n\n```sh\nbundle install\n```\n\nOr install directly:\n\n```sh\ngem install rich_engine\n```\n\nThen run one of the examples:\n\n```sh\nruby examples/timer.rb\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatheusrich%2Frich_engine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmatheusrich%2Frich_engine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatheusrich%2Frich_engine/lists"}