Today I played
M-x dunnet for the first time in ten years. I'm surprised by how much I remembered. I remembered what the coconuts and the sauna do; I remembered where to look for the password to Pokey; I even remembered how to turn an essential item into a worthless pile of protoplasm.
I didn't remember the daze of thirsty little cabbages, all alike, but I remembered the problem it exhibits. Dunnet forces the player to guess far too much. In the maze, this is just tedious - you have to exhaustively try to move in every direction and draw a map. But there are also many deathtraps which are impossible to anticipate, and these inevitably force the player to restart the game, or to give up in frustration. (Once you die, there is no obvious way to restart the game without restarting the whole emacs!) Despite its cute bits, Dunnet is not much good as a game.
It is, however, good as a teaching tool. Ten years ago, I reacted to its frustration in the obvious way: I read the source for hints. (You might call that cheating, if you like text adventures more than I do.) I didn't know much Lisp at the time, so this was quite educational. I was well aware of this, and didn't mind, although the everything-is-a-list data structures were confusing. However, its effectiveness was limited for an ironic reason: the code is too clean.
Not that it's a shining example or anything - rooms are represented by a bunch of parallel lists in globals, for one thing - but data about rooms and such is mostly separated from code. So I was able to figure out most of what I wanted without having to understand much of the code. A similar game with less separation of concerns might be a better teacher, because it would force students to read more code. It would help to have more puzzles which depend on understanding the code, not just looking at literal strings for the answer. It would also be nice if early parts of the game had simpler, easier-to-read implementations, so the student could learn gradually.
By the way, here's an interesting use of
eval. Dunnet uses
eval to move object names from its data structures to its environment, so its code can refer to objects by name instead of by indexes into those parallel arrays:
(let (a) (setq a 0) (dolist (x dun-room-shorts) (eval (list 'defconst (intern x) a)) (setq a (+ a 1))))
It's not very clean, but it is convenient. It would be more convenient if every name didn't have to have a
obj- prefix, to avoid collisions. This is not a habit you want to be teaching students, but it's necessary, because Elisp doesn't have any kind of module system. That's something else to avoid in an educational version of Dunnet.
But keep the frustration, so players have a reason to look at the source. And by all means keep the unsettling leakage between different realities (in the game, I mean, not in the names of objects). That's what makes Dunnet fun. And it's definitely something you want to be teaching your students.