Gamefic 0.1.1 (Beta)

Gamefic is an open-source adventure game engine developed in Ruby. This week I published the first demo, The Master Tape. Today I released version 0.1.1 of the engine, which includes some important updates.

The repo is available at http://github.com/castwide/gamefic.

How to Install

If you have Ruby, you can gem install gamefic.

So far I’ve tested it on CentOS using Ruby 1.8.7 and 2.0.0, and on Windows 7 using Ruby 1.9.3. Any version of Linux should handle it. The only platform issue I’ve encountered is with JRuby, which doesn’t support $SAFE levels.

How to Play

  1. Download the demo game at gamefic.com/the-master-tape.gfic
  2. Run it from the command line: gamefic the-master-tape.gfic

Making Games

Gamefic also provides commands for developing games and making distributable game files. The repo’s README has instructions and examples to help you get started. If you’d like to see the code for The Master Tape, you can download a zip of the source.

Things to Do

  • More documentation and examples.
  • A desktop client with graphical support.
  • A mobile-friendly version of the web client.
  • Save/restore/undo commands.
  • Lots and lots of testing and debugging!

Plans for the Future

I’m also working on libraries for real-time games, 2d graphics, and networked multiplayer. This will make it possible to use Gamefic for MUDs and graphical RPGs.

Questions, comments, and feedback are appreciated.

Runs with Ruby 1.9.1 in SailfishOS on my phone, except for the game or interprerer making assumptions about screen width instead of wrapping lines for how wide the screen/window.really is. Not that I played much. I don’t find touch keyboards very fun with parser-based games. But it was fun just to see it run.

Thanks, good to hear! You’re right, the client naively assumes that 80 columns is a safe width. I’ll make a note to add support for different screen sizes.

Hey, just wanted you to know that after doing a git pull things are a LOT slower.

Just doing an “init” and “test” on a new project, it takes like 10 seconds to get a response for “look”… the previous version (not entirely sure which I had) before the was pretty snappy, no noticeable lag there.

I’m on an otherwise ok MacBook Pro Retina with OS X Mavericks, using Ruby 2.1.0 (the current brew “ruby” distro).

Ouch. That’s some major lag.

I have a feeling it’s related to the new “sandbox” code. Methods that run game code wrap it in a Proc that sets the $SAFE level to 3. The purpose is to limit what games have permission to do, such as what they can access on the filesystem.

If you want to see if that’s the root cause, you could go into the Plot and Engine classes (lib/gamefic/plot.rb and lib/gamefic/engine.rb) and delete/comment the lines that set $SAFE.

I’ve been looking into other options for sandboxing code. Even the Ruby documentation says that $SAFE is less than perfect, but I figured it was better than nothing. It’s not a problem as long as you’re running your own games; I was concerned about the possibility of distributed games containing malicious code.

You mean just commenting out the lines that say $SAFE=3?
Nope, that did not do it. Still slow.

It looks like the window title of the terminal window is constantly alternating between “ruby” and “stty”. Can it be something with tty.rb that’s not working right on os x? Just guessing, I haven’t had time to look at it.

BTW, here’s a short comparison between the short sample in the README:

[code]import ‘basics’

apartment = make Room, :name => “apartment”, :description => “You are in a tiny one-room apartment.”

introduction do |player|
player.parent = apartment
end

pencil = make Item, :name => “pencil”, :description => “A plain old No. 2 yellow.”, :parent => apartment

player.tell “Welcome to your cozy little apartment!”

bed = make Fixture, :name => “bed”, :description => “A comfy little twin bed.”, :parent => apartment

cupboard = make Container, :name => “cupboard”, :description => “A small wooden cupboard.”, :parent => apartment

closet = make Room, :name => “closet”, :description => “This closet is surprisingly spacious for such a small apartment.”
closet.connect room, “east”

BTW, is this an error? shoudln’t be: closet.connect apartment, “east”

respond :lie_on, Query.new(:siblings, bed) do |actor, bed|
actor.tell “You take a short nap. That was refreshing!”
end

xlate “lie down on :thing”, :lie_on, :thing
xlate “sleep on :thing”, :lie_on, :thing
xlate “sleep”, :lie_on, “bed”
[/code]

and in i7:

[code]Apartment is a room. “You are in a tiny one-room apartment. A closet opens west.”

A pencil is here. The description is “A plain old No. 2 yellow.”

When play begins, say “Welcome to your cozy little apartment!”

An enterable supporter called a bed is here. The description is “A comfy little twin bed.”

An openable closed container called a cupboard is here. The description is “A small wooden cupboard.”

The Closet is west of Apartment. “This closet is surprisingly spacious for such a small apartment. An exit is east.”

Understand “lie on [something]”, “lie down on [something]”, “sleep on [something]” as entering.
Instead of sleeping, try entering the bed.

Instead of entering the bed, say “You take a short nap. That was refreshing!”
[/code]

it’s nice, uncluttered and refreshing writing in i7, specially after a day of looking at buttugly java code all day. Yeah, I know ruby is way nicer than java, but it’s still yet another programming language. Seems i7 also subtly incorporated some of the best bits from Python, Perl and functional programming.

[code]import ‘basics’

apartment = make Room, “apartment”, “You are in a tiny one-room apartment.”

introduction do |player|
player.parent = apartment
end

pencil = make Item, “pencil”, “A plain old No. 2 yellow.”, apartment

player.tell “Welcome to your cozy little apartment!”

bed = make Fixture, “bed”, “A comfy little twin bed.”, apartment

cupboard = make Container, “cupboard”, “A small wooden cupboard.”, apartment

closet = make Room, “closet”, “This closet is surprisingly spacious for such a small apartment.”
closet.connect room, “east”

BTW, is this an error? shoudln’t be: closet.connect apartment, “east”

respond :lie_on, Query.new(:siblings, bed) do |actor, bed|
actor.tell “You take a short nap. That was refreshing!”
end

xlate “lie down on :thing”, :lie_on, :thing
xlate “sleep on :thing”, :lie_on, :thing
xlate “sleep”, :lie_on, “bed”
[/code]

would this still work? much better with positional arguments…

Ah, I think you’re right. The problem is most likely the TerminalThread class in tty.rb, which runs a process that constantly checks for changes in the screen size. I’ll give it a closer look tonight.

My goal wasn’t specifically to replace i7 for authoring, but to develop a highly extensible engine. For example, one of my other projects extends the Gamefic engine into a graphical MUD server.

I’d love the ability to interpret and/or compile i7 syntax (or something similar), but that’s a decidedly non-trivial task.

Also, thanks for catching the error in my example code. I fixed it in the repo.

I went with the :property => value syntax because it seemed like the easier way to deal with extended entities. Say, for example, you create a class Cavern that extends Room and adds a few more properties, you can reference the new properties by name instead of needing to memorize the order in which the initialize method expects them.

Another alternative that works:

apartment = make Room
apartment.name = "apartment"
apartment.description = "You are in a tiny one-room apartment."

So, regarding the TTY thing - yes, it’s definitely that stuff that’s taking time.

My hunch is it’s this:

  `stty size`.scan(/\d+/).map { |s| s.to_i }.reverse

I just moved the detect_terminal_size out of the loop in Gamefic::Tty::TerminalThread, changing initialize to this:

 def initialize
        @output = Array.new
        @semaphore = Mutex.new
        @size = detect_terminal_size 
        @thread = Thread.new {
          while true
            @semaphore.synchronize {
              # @size = detect_terminal_size
              while @output.length > 0
                data = @output.shift
                case data[0]
                  when :html
                    # TODO: Support HTML and/or Markdown
                    print data[1]
                  else
                    print data[1]
                end
              end
            }
          end
        }
      end

Now the lag is gone. It takes a few seconds to load things up initially, but after that each command responds quick enough.

I guess this means line breaks are a little random but I can live with that… handling dynamic window resizing comes pretty far down on the feature list for me.

Cool, thanks for catching that. I created an issue on GitHub to determine whether there’s a better approach to size detection or if TerminalThread should just be disabled altogether.