Tao of the Machine

Programming, Python, my projects, card games, books, music, Zoids, bettas, manga, cool stuff, and whatever comes to mind.

You shouldn't judge a book by its cover...

...but what about a computer?

Posted by Hans Nowak on 2003-10-09 23:00:12   {link}
Categories: general, SGI

SGI

Today's topic is SGI. It would be Seriously Cool to have an SGI Indigo or Octane or whatever on my desktop. Not that I have any money to burn, but maybe it's possible to get an old one really cheap. (Sun Blades are interesting too, but they are *way* out of my price range, even second-hand.)

I suppose that, if you know your stuff, you can find some really good deals on Ebay. Assuming the seller is not tricking you. I saw some older system for $30, that "works but gives an error message when booting". If the error message is not caused by a missing harddisk or something like that, then all you have to do is slap a new OS on it and you're golden.

Unfortunately, many second-hand SGI systems do seem to come without a hard drive, or other crucial parts missing. It's still cheap, but what am I going to do with an incomplete system?

Anyway, here's a page that talks about buying old SGIs.

Sometimes I just get this feeling that it would be great to leave all my current stuff behind and start from scratch with a new (= different) computer and a new OS. A new box for hacking, with lots of things to learn and explore, etc. ... Unfortunately, earlier attempts to do this failed. Remember the Mac? Also, I tend to have a romantic and unrealistic view about systems I don't know. :-) What if I don't get Python to compile on IRIX? Hmm...

Posted by Hans Nowak on 2003-10-08 22:54:33   {link}
Categories: general, SGI

dyne:bolic

Another Linux that boots and runs from CD, without having to install anything on your harddisk: dyne:bolic. The other one I know of is Knoppix. Are there any other Linuxen that do this? BSDs, maybe?

Update: (See comments) Ludo points to a page with CD-based Linux distros. I tried Lonix, a distro that doesn't use a Windows manager. It apparently comes with a lot of programming languages:

Conjunto extra de compiladores para m&xuacute;ltiples lenguajes, incluyendo C y C++ (incluidos logicamente a traves de gcc), Pascal (con FreePascal), Perl, Python, Common LISP, Smalltalk, Java y alguno más

(http://lonix.sourceforge.net/es/especificaciones.html)

...but unfortunately its keyboard layout is Spanish... while I have fond memories of that layout, it's been at least 10 years that I used it, so I have a hard time finding certain keys.

Posted by Hans Nowak on 2003-10-07 23:52:54   {link}
Categories: general

Swimming against the stream

Check out this SICP section: 3.5.2. Infinite Streams.

Python's generators are a mechanism that can be used for similar purposes. For example, here's the "stream" that returns integers:

def integers_starting_from(n):
    while 1:
        yield n
        n = n + 1
        
g = integers_starting_from(42)
# test test...
for i in range(10):
    print g.next(),
print
# 42 43 44 45 46 47 48 49 50 51

The itertools module offers some functions that can be used to manipulate generators (iterators, really) in much the same way as the Scheme streams:

def divisible(x, y):
    return (x % y == 0)
no_sevens = itertools.ifilter(lambda x: not divisible(x, 7), 
            integers_starting_from(1))
for i in range(20):
    print no_sevens.next(),
print
# 1 2 3 4 5 6 8 9 10 11 12 13 15 16 17 18 19 20 22 23

And here's a version of the sieve generator. It works, but this recursive version isn't very Pythonic:

def sieve(stream):
    head = stream.next()
    yield head
    p = lambda x: not divisible(x, head)
    newstream = sieve(itertools.ifilter(p, stream))
    for x in newstream:
        yield x

primes = sieve(integers_starting_from(2))

for i in range(20):
    print primes.next(),
print
# 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71

Here's a different version:

def sieve2(stream):
    # a non-recursive version
    while 1:
        head = stream.next()
        yield head
        p = lambda a, b=head: not divisible(a, b)
        stream = itertools.ifilter(p, stream)
        
primes = sieve(integers_starting_from(2))
for i in range(20):
    print primes.next(),
print

A simple infinite stream of just ones can be defined easily as well (though not as easily as in Haskell, where you just write ones = 1:ones).

def ones():
    while 1:
        yield 1
    
def add_streams(s1, s2):
    return itertools.imap(lambda x, y: x+y, s1, s2)

twos = add_streams(ones(), ones())
for i in range(5): print twos.next(),
print
# 2 2 2 2 2

There are important differences between Scheme's streams and Python's generators, of course. Generators require special syntax (though not much), streams do not (though they require a special form to implement them). Streams can be elegantly implemented and augmented using recursion, while this is not a good match for generators. Streams are an actual data structure; the integers stream in the aforementioned SICP paragraph can be used and reused over and over again, or used multiple times simultaneously. A generator cannot be restarted, and the same generator shouldn't be used by multiple pieces of code at the same time, unless you are prepared to deal with the side effects.

I think the streams are more elegant, but not always easier to use or understand. Something like

(define double (cons-stream 1 (scale-stream double 2)))

; or:

(define s (cons-stream 1 (add-streams s s)))

just makes my brain explode. :-) Well, I understand it, sort of, but it's not something I would write myself. At least, not yet. It's funny how something can be blindingly simple and mind-bogglingly difficult at the same time. emoticon:bonk

Posted by Hans Nowak on 2003-10-06 15:02:47   {link}
Categories: Python

1-day project: An adventure engine

(in Python of course)

The day isn't over yet, but I got most of the framework working. I don't have a name for it yet; suggestions are welcome. ^_^

Earlier attemps usually failed because I wanted to do too many things at once, and write a complete engine from scratch. I started out with different goals this time; I wanted to make something as simple as possible, yet Good Enough for most purposes (at least at first). I also resisted the temptation of abusing Python's OO capabilities to do all kinds of nifty stuff that covers most cases, but not all. :-) I wrote about this before, by the way; the current approach is different.

So, what does it look like? To use the engine, you simply import the core module, and start using some objects. There are objects and construct to make rooms, things, NPCs, and commands. First, start with rooms:

from core import *

kitchen = Room(name='Kitchen', description='You are in the kitchen.')
livingroom = Room(name='Living Room', 
             description='You are in the living room.')
# etc...

The current version isn't very sophisticated yet, but this is the gist of it... instantiate (rather than subclass) the Room class, and add some attributes. Note that Rooms, Objects and NPCs have properties. These are easily accessed with the obj[name] syntax, and can be used to stick all kinds of things into the instance... for Rooms, they are used for directions, for example:

kitchen['e'] = livingroom
livingroom['w'] = kitchen
livingroom['n'] = hall
hall['s'] = livingroom
# etc...

In other words, if you are in the kitchen and go north, you end up in the living room, etc.

Then, create objects and NPCs. These will usually start out in certain rooms:

knife = Object('knife', 'A knife is laying on the counter.', kitchen, 
 get=1)
# we can use a shorthand as well:
Object('faucet', 
 "A rusty old faucet sticks out of the wall. It's leaking.", 
 kitchen, 
 get_response="You don't dare pull the faucet. It might break off!")  

You don't *have* to name all the rooms and objects, but it's useful if you want to refer to them in functions later. Of course, that would create a lot of globals; for people who object to that, there will be functions to find objects/NPCs/etc by name.

Then, create an instance of the World object, which is basically what makes the engine run. This also creates a Player object, which can be accessed to give the player certain attributes, or inventory objects.

world = World()
# give player something to carry around
Object('wristwatch', world.player)

You now have a working "world" where you can walk around and look at things. To add more commands (in addition to N, S, etc), you use the world.registry object, which registers command templates.

world.registry.register('get $O')
world.registry.register('examine $')
world.registry.register('talk to $P about $')
world.registry.register_alias('take $O', 'get')
# etc...

Basically, you just type the command in the format you want it, with $O for an object, $P or $N for an NPC, and $ for anything. Aliases can be added as well.

Almost done; what we haven't integrated yet, is actual *actions*... functions or methods that will be executed when certain commands are entered. Adding these is easy too. Just write the function, then add it with world.actionregistry:

def get_default(world, obj):
    if obj['get']:
        print 'Maybe I will let you pick that up in the next version. :-)'
    else:
        print 'You cannot pick that up.'
        
world.actionregistry.register('get', get_default, None)
# called when you attempt to get any object
world.actionregistry.register('give_to', somefunction, lamp, troll)
# called when you give the lamp to the troll

This simple (and useless :-) code registers a handler for the get command. The "handler" is just a function; such functions should always accept a world argument first, and then zero or more other arguments (depending on the command; 'help' takes 0, 'get $O' takes one, 'give $O to $P' takes two). The call to register() works in a similar way; you can add actual objects as arguments, or None, in which case anything is matched.

You run the game by setting a start location and calling the run() method:

world.start = hall  # the player starts here
world.run()

This is all that is necessary for an adventure game framework where you can add objects, rooms, commands etc at will. Well, it's not done yet... I will add some more stuff, write a small demo game, and then maybe a real game, which will probably affect development as well. And I will need to write some documentation. Anyway, I plan to upload some code shortly.

In the meantime, here are some related Python projects that seem interesting:

  • pyzzy, a Z-machine implementation in Python
  • Viola, an interpreter for Z-machine games
  • PUB (Python Universe Builder)

Posted by Hans Nowak on 2003-10-05 20:18:47   {link}
Categories: Python

Ouwe koek

I uploaded a few old executables of Python-DX, and put a page together with some info. This is just about all that's left of it; unfortunately I don't have any source or documentation anymore.

Posted by Hans Nowak on 2003-10-04 23:48:42   {link}
Categories: Python

Confessions of an insecure string pickle

The Self-style objects seemed like a good tool to write text adventures. I haven't written an adventure since the middle 90s, and previous attempts to do this in Python failed because I usually got carried away trying to write a complete and generic adventure framework, rather than just a game. So, my idea was to write a few core objects, then write a simple GUI where you can add new objects (e.g. rooms, NPCs, things, etc) and add/edit attributes of existing ones (like a room's description, etc, but possibly also methods). This way, one should be able to create a "world" incrementally, and walk around in it while adding new stuff.

Those ideas are not lost yet, but I did run into a few problems. Most importantly, I'd like to store "the world" at a certain point, and restore it later. Pickling seems a natural fit. However, Self-style objects are different from regular classes and instances: methods can be added and passed around at will. 1) In other words, if you dynamically add a method to an object, it will not be pickled. Pickle, marshal and shelve don't work for functions, classes and methods. Normally this wouldn't be a real problem -- after all, code is defined in modules, and only the data differ, so that's all you need to pickle. But in this situation, it's a problem.

There are some ways around this, but they're not very satisfactory. For example, here's one way to pickle functions, but it's more of a hack... Another way would work if we had the source of methods. We could then just pickle that as a string, and recreate the method from source when unpickling.

What would be cool is if Python had a way to pickle arbitrary objects. I mean really arbitrary... classes, instances, functions, methods, modules, etc. Heck, even file objects if at all possible. You could store the interpreter in its current state, and restore that state later.

Maybe I need to look at Lisp or Smalltalk if I want to do these kinds of tricks. For the project, I could use regular classes. Or a different pickling mechanism.

1) Yes, you can do this with regular Python classes/instances as well, but it isn't all that common. But if you did, you might run into pickling problems as well.

Posted by Hans Nowak on 2003-10-04 18:31:24   {link}
Categories: Python

--
Generated by Firedrop2.