1 ! --------------------------------------------------------------------------
2 ! "ROBOTS": Another abuse of the Z-machine, Copied Right in 1995-1997
3 ! Torbjörn Andersson, d91tan@Update.UU.SE
5 ! This program is free. By all means, do fold, spindle and mutilate.
6 ! Be aware, however, that removing my name from it may cause
7 ! irreparable damage to your karma. If you do redistribute it in any
8 ! form, please drop me a note. I'd love to hear about it! If you
9 ! think you can make money from it, you are more optimistic than I
12 ! I got the idea of writing this when seeing Andrew Plotkin's much
13 ! more interesting game "Freefall". I used his code for reference
14 ! about some technical details.
16 ! I don't know who originally came up with this game idea. I have
17 ! seen it under the name "DALEKS", but that version was a bit
18 ! different. This one uses (almost) both the layout and the key
19 ! configuration of the version which can, at least usually, be found
20 ! in /usr/games on Unix systems.
22 ! Release 2 makes some slight optimizations (I hope) to the code
23 ! which detects collisions between robots, and makes a few cosmetic
26 ! Release 3 cleans up some of the code a bit, makes some further
27 ! optimizations to the collision-detection and allows the user to
28 ! keep playing even when the maximum number of robots have been
29 ! reached. (It just won't increase the number of robots any further.)
30 ! For this reason, I've lowered the maximum number of robots from 500
31 ! to 300, which should still be more than enough.
33 ! Release 4 changes @read_char 1 0 0 key; to @read_char 1 key; since
34 ! I have been informed (no pun intended) that the former is
35 ! considered illegal by some intepreters. Of course, I then felt
36 ! obliged to test the limits of portability again by changing it to
37 ! use @@156 for the non-standard character in my name. To make the
38 ! new release a bit more worthwhile, I've cleaned up MoveRobots() a
39 ! bit (I hope), and added a variable to keep track of bonus earned
42 ! Release 5; I was told that @beep without argument crashed an
43 ! interpreter (I don't know which one), so I changed it to use
44 ! @sound_effect 1 instead, to comply with the most recent version of
45 ! the Z-machine specification.
47 ! Release 6; nothing specific, but I have made a few minor updates to
48 ! make it compile under Inform 6. Some day, I should write a nice,
49 ! object-oriented version. Or maybe not...
50 ! --------------------------------------------------------------------------
53 Message fatalerror "This program must be compiled as a version 4 (or
63 Constant PrefLines 24; ! This is the screen size for which
64 Constant PrefCols 80; ! the game is designed.
66 Constant FieldRows 22; ! Size of the playing field.
67 Constant FieldColumns 59;
68 Constant FieldSize FieldRows * FieldColumns;
70 Constant RobotScore 10; ! Points for killing one robot
71 Constant BonusScore 11; ! Ditto while 'W'aiting.
72 Constant Robot '+'; ! Symbols used on the game field
73 Constant Player 64; ! '@'
74 Constant JunkHeap '*';
77 Constant IncRobots 10; ! Robots added for each level
78 Constant MaxRobots 300; ! Max number of robots
82 Global score = 0; ! Current score
83 Global high_score = 0; ! Highest score this session
84 Global waiting = 0; ! Set when 'W'aiting
85 Global wait_bonus = 0; ! Bonus while waiting
86 Global beep_flag = 1; ! Sound on/off
87 Global player_x = 0; ! Player's current position
88 Global player_y = 0; ! - " -
89 Global num_robots = IncRobots; ! Number of robots on level
90 Global active_robots = IncRobots; ! Number of live robots on level
92 ! The PlayingField contains information about robots and junkheaps (though not
93 ! about the player). It is used for fast lookup when moving the player or a
94 ! robot. An alternative solution would be to keep an array of the junkheaps,
95 ! similar to RobotList, which would save memory but which would also be much
98 Array PlayingField -> FieldSize;
100 ! The RobotList encodes the individual robots' positions in words (two bytes),
101 ! and is used to speed up the operations which work on all robots. It would be
102 ! possible to search PlayingField, but that would be impractical. It is assumed
103 ! that no player will survive long enough for the array to overflow.
105 Array RobotList --> MaxRobots;
107 ! --------------------------------------------------------------------------
109 ! --------------------------------------------------------------------------
111 [ Main screen_height screen_width i;
112 screen_height = 0->32;
113 screen_width = 0->33;
115 if (screen_height < PrefLines || screen_width < PrefCols) {
117 print "^^[The interpreter thinks your screen is ", screen_width,
118 (char) 'x', screen_height, ". It is recommended that you \
119 use at least ", PrefCols, (char) 'x', PrefLines, ".]";
123 print "^^", (Strong) "ROBOTS", " - Another abuse of the ",
124 (Emphasize) "Z-Machine", "^A nostalgic diversion by Torbj@:orn
125 Andersson^Release ", (0-->1) & $03ff, " / Serial number ";
127 for (i = 18 : i < 24 : i++)
133 print "^^~You can't miss it,~ they said. ~A white house in a
134 clearing with a small mailbox outside; just open the kitchen
135 window and the entrance to the Great Underground Empire isn't
137 print "You found the house and the window all right, and a
138 trapdoor leading down. But as the trapdoor crashed shut behind
139 you, you realized that something was very wrong. Surely the
140 GUE shouldn't look like a large square room with bare walls,
141 and what about those menacing shapes advancing towards
143 print "[Press any key to continue.]^";
150 ! This magic incantation should restore the screen to something
151 ! more normal (for a text adventure). Actually, I'm not 100% sure
152 ! how much of this is really needed.
158 print "^^The idea of writing something like this came from seeing
159 Andrew Plotkin's much more interesting game ",
160 (Emphasize) "Freefall", ". It's really quite amusing to see
161 what the Z-Machine can do with a little persuasion.^^";
162 print "[Press any key to exit.]^";
168 ! --------------------------------------------------------------------------
170 ! --------------------------------------------------------------------------
172 ! This function plays a game of "robots"
174 [ PlayGame x y n key got_keypress meta old_score;
175 ! Clear the screen, initialize the game board and draw it on screen.
183 num_robots = IncRobots;
184 active_robots = IncRobots;
189 ! "Infinite" loop (there are 'return' statements to terminate it) which
190 ! waits for keypresses and moves the robots. The 'meta' variable is used
191 ! to keep track of whether or not anything game-related really happened.
195 ! Remember the player's old position.
199 ! Wait for a valid keypress. If the player is 'W'aiting, it is the
200 ! same as if he or she is constantly pressing the '.' key, except the
201 ! robots will actually be allowed to walk into the player.
202 for (got_keypress = 0 : ~~got_keypress :) {
206 key = ReadKeyPress();
210 if (wait_bonus == -1) {
212 n = FieldColumns + 4;
219 'Y': player_x--; player_y--;
221 'U': player_x++, player_y--;
224 'B': player_x--; player_y++;
226 'N': player_x++; player_y++;
234 return AnotherGame();
251 ! If the command was a movement command, check if the player is moving
252 ! to a safe spot or not. (Exception: Teleports are inherently risky,
253 ! but will always put you in an empty spot on the game board, so don't
256 ! If the player has moved, redraw that part of the game board.
258 ! If the move is not accepted, make sure the player remains at the
259 ! original location, warn him or her, and make sure the robots don't
263 (InsideField(player_x, player_y) &&
264 SafeSpot(player_x, player_y))) {
265 if (x ~= player_x || y ~= player_y) {
266 DrawObject(x, y, ' ');
267 DrawObject(player_x, player_y, Player);
278 ! If the player made a valid move, move the robots.
282 ! The robots have moved and dead robots have been handled by
283 ! MoveRobots(). Now it's time to see if the player survived, and
284 ! maybe even won the game.
285 if (GetPiece(player_x, player_y) == Empty) {
286 if (~~active_robots) {
291 num_robots = num_robots + IncRobots;
293 if (num_robots > MaxRobots)
294 num_robots = MaxRobots;
299 DrawObject(player_x, player_y, 0);
301 DrawObject(player_x, player_y, 0);
302 print "AARRrrgghhhh....";
310 return AnotherGame();
316 ! This function moves the robots and handles collisions between robots and
317 ! other robots or junkheaps.
319 [ MoveRobots i j robot_x robot_y hit;
320 ! Traverse the list of active robots. At this point there should be no
321 ! 'dead' robots in the list.
322 for (i = 0, hit = 0 : i < active_robots : i++) {
326 ! Remove the robot from the playing field and the game board (though
327 ! not from the robot list.
328 DrawObject(robot_x, robot_y, ' ');
329 PutPiece(robot_x, robot_y, Empty);
331 ! The robot will always try to move towards the player, regardless of
333 if (robot_x ~= player_x) {
334 if (robot_x < player_x)
340 if (robot_y ~= player_y) {
341 if (robot_y < player_y)
347 ! Any robot moving onto a junk heap is destroyed. Otherwise, the robot
348 ! is inserted on the playing field at its new location.
349 if (GetPiece(robot_x, robot_y) == JunkHeap) {
354 ! Draw the robot on screen to reduce the flicker. The final
355 ! drawing is done in the next loop, as some robots may have
356 ! been erased by other moving robots.
357 DrawObject(robot_x, robot_y, Robot);
358 PutRobot(robot_x, robot_y, i);
362 ! If a robot was removed, clean up the robot list.
366 ! To make sure that no robot is accidentally 'removed' from the board
367 ! (which could happen if a robot onto another robot before the other
368 ! robot moves, since the other robot will 'blank' its old position on
369 ! the board) we draw all the robots again.
370 for (i = 0, hit = 0 : i < active_robots : i++) {
374 ! If two robots ended up in the same position, there was a
375 ! collision. I don't know if it's a good idea or not, but I
376 ! don't want to do the robot-removal yet, so just set a flag
377 ! that there are collisions to detect.
378 if (GetPiece(robot_x, robot_y) == Robot)
381 DrawObject(robot_x, robot_y, Robot);
382 PutPiece(robot_x, robot_y, Robot);
385 ! If no robots collided, all is done.
391 ! At least one collision occured. It's time to find out which robots
392 ! collided. This code is the game's major cause of slowdown.
393 for (i = 0, hit = 0 : i < active_robots - 1 : i++) {
394 for (j = i + 1 : j < active_robots : j++) {
395 if (RobotList-->i ~= -1 && RobotList-->i == RobotList-->j) {
399 PutPiece(robot_x, robot_y, JunkHeap);
400 DrawObject(robot_x, robot_y, JunkHeap);
405 ! Don't give the player any points for robots killing him/her
406 if (robot_x ~= player_x || robot_y ~= player_y)
409 ! Since RobotList-->i now is -1, we won't find any other
410 ! robots on the same position, so terminate the inner loop.
411 ! I don't know if it'd be better to save the position of
412 ! robot i, and follow the loop to its very end.
418 ! I know at least one collision occured, and therefore I know that robots
422 ! And even now we are not done: What if three robots went to the same
423 ! square? In that case, there should be a robot sitting on a junkheap
424 ! now. This can only happen if the previous loop detected a collision
425 ! between two robots.
426 for (i = 0, hit = 0 : i < active_robots : i++) {
430 if (GetPiece(robot_x, robot_y) == JunkHeap) {
434 if (robot_x ~= player_x || robot_y ~= player_y)
443 ! --------------------------------------------------------------------------
445 ! --------------------------------------------------------------------------
447 ! These two functions are used for printing the game board. This is done both
448 ! when starting on a level and when using the 'R'edraw command.
450 [ DrawPlayingField i x y;
453 ! Draw the border around the game board.
454 DrawHorizontalLine(1);
455 DrawHorizontalLine(FieldRows + 2);
457 x = FieldColumns + 2;
459 for (i = 2 : i <= FieldRows + 1 : i++) {
460 @set_cursor i 1; print (char) '|';
461 @set_cursor i x; print (char) '|';
464 ! Draw the robots on the game board.
465 for (i = 0 : i < active_robots : i++)
466 DrawObject(RobotX(i), RobotY(i), Robot);
468 ! If some robots have died, we have to traverse the entire PlayingField
469 ! looking for junkheaps. Fortunately, this only happens when 'R'edrawing
470 ! the screen, which shouldn't be very often.
471 if (active_robots < num_robots) {
472 for (x = 0 : x < FieldColumns : x++) {
473 for (y = 0 : y < FieldRows : y++) {
474 if (GetPiece(x, y) == JunkHeap) {
475 DrawObject(x, y, JunkHeap);
481 ! Put some help text to the right of the game board.
482 x = FieldColumns + 4;
484 @set_cursor 1 x; print "Directions:";
486 @set_cursor 3 x; print "y k u";
487 @set_cursor 4 x; print " @@92|/ ";
488 @set_cursor 5 x; print "h-.-l";
489 @set_cursor 6 x; print " /|@@92 ";
490 @set_cursor 7 x; print "b j n";
492 @set_cursor 9 x; print "Commands:";
494 @set_cursor 11 x; print "w: wait for end";
495 @set_cursor 12 x; print "t: teleport";
496 @set_cursor 13 x; print "q: quit";
497 @set_cursor 14 x; print "r: redraw screen";
499 @set_cursor 16 x; print "Legend:";
501 @set_cursor 18 x; print (char) Robot, ": robot";
502 @set_cursor 19 x; print (char) JunkHeap, ": junk heap";
503 @set_cursor 20 x; print (char) Player, ": you";
505 if (wait_bonus > 0) {
506 @set_cursor 24 x; print "Bonus: ", wait_bonus;
510 @set_cursor 22 x; print "Score: ", score;
511 @set_cursor 23 x; print "High: ", high_score;
513 ! Finally, draw the player on the game board.
514 DrawObject(player_x, player_y, Player);
515 DrawObject(player_x, player_y, 0);
518 [ DrawHorizontalLine row i;
523 for (i = 0 : i < FieldColumns : i++)
529 ! --------------------------------------------------------------------------
531 ! --------------------------------------------------------------------------
545 ! Test is a coordinate is safe to move it, ie that
547 ! a) There is no junkheap on it
548 ! b) There are no robots on any adjacent coordinate
550 [ SafeSpot xpos ypos x y;
551 if (GetPiece(xpos, ypos) == JunkHeap)
554 for (x = xpos - 1 : x <= xpos + 1 : x++) {
555 for (y = ypos - 1 : y <= ypos + 1 : y++) {
556 if (InsideField(x, y) && GetPiece(x, y) == Robot)
564 ! Update the score after killing 'n' robots. If 'n' is 0 it will simply
565 ! redraw the score. If we are 'W'aiting, the score is not written since it
566 ! is not known whether or not the player will actually get points until he
567 ! or she has survived the entire level.
572 wait_bonus = wait_bonus + n * (BonusScore - RobotScore);
573 score = score + (n * BonusScore);
575 score = score + (n * RobotScore);
579 x = FieldColumns + 11;
581 @set_cursor 22 x; print score;
583 if (score > high_score) {
585 @set_cursor 23 x; print high_score;
590 ! Ask the user if he or she wants to play another game
593 x = FieldColumns + 4;
595 print "Another game? ";
598 switch (ReadKeyPress()) {
605 ! Get a new position for the player. This is used both when 'T'eleporting and
606 ! when starting on a new level, and ensures that the player will not land on
607 ! any robot or junkpile. The player may, however, land right next to a robot,
608 ! which is fatal when 'T'eleporting, and uncomfortable when starting on a new
613 player_x = random(FieldColumns) - 1;
614 player_y = random(FieldRows) - 1;
616 if (GetPiece(player_x, player_y) == Empty)
621 ! The code which checks for robots colliding is horrendously inefficient, so
622 ! in order to speed it up as the game proceeds, remove 'dead' robots from the
623 ! list and keep a counter of 'active' robots.
625 [ CleanRobotList i j;
626 for (i = 0, j = 0 : i < active_robots : i++) {
627 if (RobotList-->i ~= -1) {
628 RobotList-->j = RobotList-->i;
636 ! --------------------------------------------------------------------------
638 ! --------------------------------------------------------------------------
640 ! Initialize the PlayingField and RobotList
642 [ InitPlayingField i x y;
643 active_robots = num_robots;
645 for (i = 0 : i < FieldSize : i++)
646 PlayingField->i = Empty;
648 for (i = 0 : i < num_robots : i++) {
650 x = random(FieldColumns) - 1;
651 y = random(FieldRows) - 1;
653 if (GetPiece(x, y) == Empty) {
654 PutPiece(x, y, Robot);
664 ! --------------------------------------------------------------------------
666 ! --------------------------------------------------------------------------
668 ! Produce an annoying 'beep', if the sound is turned on. The sound is toggled
669 ! with 'S', which, since it isn't properly documented, must surely be a bug
670 ! rather than a feature. :-)
677 ! Read a single character from stream 1 (the keyboard) and return it. If the
678 ! character is lower-case, it is translated to upper-case first.
683 if (x >= 'a' && x <= 'z')
689 ! These two primitives are used for reading the PlayingField and inserting new
690 ! values in it respectively.
693 return PlayingField->(y * FieldColumns + x);
697 PlayingField->(y * FieldColumns + x) = type;
700 ! These three primitives are used for getting and setting the coordinates of
701 ! a robot respectively. A dead robot is marked as -1 in RobotList, and it is
702 ! up to the calling functions to test this if necessary.
705 return (RobotList-->n) / 256;
709 return (RobotList-->n) % 256;
713 RobotList-->n = x * 256 + y;
716 ! Print a character on the game board. Note that it is up to the calling
717 ! function to make sure that this bears any resemblance to what is actually
718 ! stored in the PlayingField.
730 ! Primitive for testing if a coordinate is inside the game board.
733 if (x >= 0 && y >= 0 && x < FieldColumns && y < FieldRows)