/* ATC, an interactive aviation. */ /* Copyright 1999-2008 by Jesse McGrew. Based on the unix version by Ed James. */ /* 1999: Original version. */ /* July 2008: Updated for Glulx Snack. */ const DIRW = 0; const DIRE = 45; const DIRD = 90; const DIRC = 135; const DIRX = 180; const DIRZ = 225; const DIRA = 270; const DIRQ = 315; const NUMPLANES = 26; const MAXFUEL = 40; const FUELWARNING = 10; const MSPERTICK = 2500; // 3500; const CMD_NONE = 0; const CMD_TURN = 1; const CMD_CIRCLE = 2; const DELAY_NONE = 0; const DELAY_BEACON = 1; const DELAY_AIRPORT = 2; const DEST_EXIT = 0; const DEST_AIRPORT = 1; /* for command parsing only: */ const DEST_BEACON = 2; const DEST_NONE = 3; const STATE_HOLDING = 0; /* on the ground at an airport */ const STATE_MARKED = 1; /* normal */ const STATE_IGNORED = 2; /* not hilited */ const STATE_UNMARKED = 3; /* becomes hilited after running delayed cmd */ const GRID_TYPEMASK = (15 << 4); const GRID_ARGMASK = 15; const GRID_DOT = (0 << 4); const GRID_LINE = (1 << 4); const GRID_EDGE = (2 << 4); const GRID_BEACON = (3 << 4); const GRID_EXIT = (4 << 4); const GRID_AIRPORTW = (5 << 4); const GRID_AIRPORTD = (6 << 4); const GRID_AIRPORTX = (7 << 4); const GRID_AIRPORTA = (8 << 4); const REL_LEFT = 0; const REL_RIGHT = 1; const REL_UP = 2; const REL_DOWN = 3; function planename(n) { return chr('A' + n % NUMPLANES); } function isalpha(c) { c = asc(c); return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); } function isdigit(c) { c = asc(c); return (c >= '0' && c <= '9'); } function char2dir(c) { switch (c) { case 'q': return DIRQ; case 'w': return DIRW; case 'e': return DIRE; case 'd': return DIRD; case 'c': return DIRC; case 'x': return DIRX; case 'z': return DIRZ; case 'a': return DIRA; } } function dxdy2dir(dx, dy) { if (dx < 0) { /* left */ if (dy < 0) return DIRQ; else if (dy > 0) return DIRZ; else return DIRA; } else if (dx > 0) { /* right */ if (dy < 0) return DIRE; else if (dy > 0) return DIRC; else return DIRD; } else { /* straight up/dn */ if (dy < 0) return DIRW; else if (dy > 0) return DIRX; } /* whoops, dx and dy are zero. return DIRW to avoid screwing anything up. */ return DIRW; } function adjacent(x1, y1, a1, x2, y2, a2) { switch (x1 - x2) { case -1: case 0: case 1: if (y1 == y2 && a1 == a2) return true; } switch (y1 - y2) { case -1: case 0: case 1: if (x1 == x2 && a1 == a2) return true; } switch (a1 - a2) { case -1: case 0: case 1: if (x1 == x2 && y1 == y2) return true; } return false; } /* return a list with the last item removed */ function poplist(l) { local i, result = []; for (i = 0; i < #l - 1; i++) result += [ (l[i]) ]; return result; } class map { grid = []; /* height rows * width columns of type+arg numbers */ init { /* set up the grid from the descriptions */ local i, j, k, l; grid = []; for (i = 0; i < self.height; i++) { grid[i] = []; for (j = 0; j < self.width; j++) { if (i == 0 || i == self.height - 1 || j == 0 || j == self.width - 1) grid[i][j] = GRID_EDGE; else grid[i][j] = GRID_DOT; } } foreach i (self.lines) { if (i[0][0] == i[1][0]) { /* x values are the same, so the line is vertical */ k = i[0][0]; /* constant x value */ j = i[0][1], l = i[1][1]; /* begin and end y values */ if (j < l) for (; j <= l; j++) grid[j][k] = GRID_LINE; else for (; l <= j; l++) grid[l][k] = GRID_LINE; } else if (i[0][1] == i[1][1]) { /* y values are the same, so the line is horizontal */ k = i[0][1]; /* constant y value */ j = i[0][0], l = i[1][0]; /* begin and end x values */ if (j < l) for (; j <= l; j++) grid[k][j] = GRID_LINE; else for (; l <= j; l++) grid[k][l] = GRID_LINE; } else if ((i[0][0] < i[1][0] && i[0][1] < i[1][1]) || (i[0][0] > i[1][0] && i[0][1] > i[1][1])) { /* line is diagonal NW-SE */ if (i[0][0] < i[1][0]) /* first coord is top left */ for (j = i[0][0], k = i[1][0], l = i[0][1]; j <= k; j++, l++) grid[l][j] = GRID_LINE; else /* first coord is bottom right */ for (j = i[1][0], k = i[0][0], l = i[1][1]; j <= k; j++, l++) grid[l][j] = GRID_LINE; } else if ((i[0][0] < i[1][0] && i[0][1] > i[1][1]) || (i[0][0] > i[1][0] && i[0][1] < i[1][1])) { /* line is diagonal NE-SW */ if (i[0][0] < i[1][0]) /* first coord is bottom left */ for (j = i[0][0], k = i[1][0], l = i[0][1]; j <= k; j++, l--) grid[l][j] = GRID_LINE; else /* first coord is top right */ for (j = i[1][0], k = i[0][0], l = i[1][1]; j <= k; j++, l--) grid[l][j] = GRID_LINE; } } for (i = 0; i < #self.exits; i++) { j = self.exits[i]; grid[j[1]][j[0]] = GRID_EXIT | i; } for (i = 0; i < #self.beacons; i++) { j = self.beacons[i]; grid[j[1]][j[0]] = GRID_BEACON | i; } for (i = 0; i < #self.airports; i++) { j = self.airports[i]; switch (j[2]) { case DIRW: k = GRID_AIRPORTW; break; case DIRD: k = GRID_AIRPORTD; break; case DIRX: k = GRID_AIRPORTX; break; case DIRA: k = GRID_AIRPORTA; break; } grid[j[1]][j[0]] = k | i; } }; } defaultmap: map { update = 5; newplane = 10; width = 30; height = 21; exits = [[12 0 DIRX] [29 0 DIRZ] [29 7 DIRA] [29 17 DIRA] [9 20 DIRE] [0 13 DIRD] [0 7 DIRD] [0 0 DIRC]]; beacons = [[12 7] [12 17]]; airports = [[20 15 DIRW] [20 18 DIRD]]; lines = [[[1 1] [6 6]] [[12 1] [12 6]] [[13 7] [28 7]] [[28 1] [13 16]] [[1 13] [11 13]] [[12 8] [12 16]] [[11 18] [10 19]] [[13 17] [28 17]] [[1 7] [11 7]]]; } global themap = defaultmap; global nextnum = 0; global planes = [], time = 1, score = 0; //global planehash; class airplane { x = 0; y = 0; /* current position */ number = 0; /* number of the plane */ name = ""; /* name of the plane */ speed = 0; /* plane moves every 'speed' updates */ age = 0; /* number of updates the plane has been alive */ fuel = MAXFUEL; /* number of moves the plane can make before dying */ direction = 0; /* direction the plane is currently moving */ altitude = 0; /* thousands of feet off the ground */ targetalt = 0; /* target altitude */ cmd = CMD_NONE; /* current command */ cmdarg = 0; /* argument for command */ delay = DELAY_NONE; /* type of delayed command */ delayarg = 0; /* argument for delay */ dest = DEST_EXIT; /* type of destination */ destarg = 0; /* argument for dest */ state = STATE_MARKED; /* how it's displayed in the list */ drawplane() { /* draw the plane in the field */ if (state == STATE_MARKED) print "\(", name, altitude, "\)"; else print name, altitude; }; drawstatus() { /* draw the plane's description in the corner */ print name, altitude; print (fuel < FUELWARNING) ? "*" : " "; if (dest == DEST_EXIT) print "E", destarg; else print "A", destarg; /*print " ", x, ",", y; switch (state) { case STATE_HOLDING: print "h"; break; case STATE_MARKED: print "m"; break; case STATE_IGNORED: print "i"; break; case STATE_UNMARKED: print "u"; break; default: print state; break; }*/ print ": "; switch (cmd) { case CMD_NONE: if (state == STATE_HOLDING) print "Holding @ A", delayarg; /* airport no. is in here */ else if (state != STATE_MARKED) print "---------"; break; case CMD_TURN: print cmdarg; break; case CMD_CIRCLE: print "Circle"; break; } if (delay != DELAY_NONE) { print " @ "; switch (delay) { case DELAY_BEACON: print "B"; break; case DELAY_AIRPORT: print "A"; break; default: print delay; break; } print delayarg; } }; update() { local i, d; if (age++ % speed) return; if (altitude > targetalt) altitude--; else if (altitude < targetalt) altitude++; if (altitude && state == STATE_HOLDING) state = STATE_MARKED; if (state != STATE_HOLDING) { if (!delay) switch (cmd) { case CMD_TURN: d = cmdarg - direction; if (d > 180) d -= 360; else if (d < -180) d += 360; if (d > 90) direction += 90; else if (d < -90) direction -= 90; else direction += d; if (direction < 0) direction += 360; else if (direction >= 360) direction -= 360; if (direction == cmdarg) cmd = CMD_NONE; break; case CMD_CIRCLE: direction += 90; if (direction >= 360) direction -= 360; break; } switch (direction) { case DIRW: y--; break; case DIRE: x++, y--; break; case DIRD: x++; break; case DIRC: x++, y++; break; case DIRX: y++; break; case DIRZ: x--, y++; break; case DIRA: x--; break; case DIRQ: x--, y--; break; } if (delay) { switch (delay) { case DELAY_BEACON: if (x == themap.beacons[delayarg][0] && y == themap.beacons[delayarg][1]) delay = DELAY_NONE, i = true; break; case DELAY_AIRPORT: if (x == themap.airports[delayarg][0] && y == themap.airports[delayarg][1]) delay = DELAY_NONE, i = true; break; } if (!delay && state == STATE_UNMARKED) state = STATE_MARKED; } if (x <= 0 || x >= themap.width - 1 || y <= 0 || y >= themap.height - 1) return self.exitfield(x, y); if (altitude == 0) return self.landing(x, y); foreach i (planes) if (i != self && i.state != STATE_HOLDING && adjacent(x, y, altitude, i.x, i.y, i.altitude)) return self.collision(i); if (!--(fuel)) return self.outoffuel(); } }; lose(msg) { throw "Plane '" + name + "' " + msg; }; safe() { score++; destroy(self); }; exitfield(x, y) { if (dest == DEST_EXIT && x == themap.exits[destarg][0] && y == themap.exits[destarg][1]) if (altitude == 9) self.safe(); else self.lose("exited at the wrong altitude."); else if ((themap.grid[y][x] & GRID_TYPEMASK) != GRID_EXIT) self.lose("illegally left the flight arena."); else if (dest == DEST_AIRPORT) self.lose("exited instead of landed."); else self.lose("exited via the wrong exit."); }; landing(x, y) { if (dest == DEST_AIRPORT && x == themap.airports[destarg][0] && y == themap.airports[destarg][1]) if (direction == themap.airports[destarg][2]) self.safe(); else self.lose("landed in the wrong direction."); else if ((themap.grid[y][x] & GRID_TYPEMASK) < GRID_AIRPORTW) self.lose("crashed on the ground."); else if (dest == DEST_EXIT) self.lose("landed instead of exited."); else self.lose("landed at the wrong airport."); }; collision(p) { self.lose("collided with plane '" + p.name + "'."); }; outoffuel() { self.lose("ran out of fuel."); }; construct() { number = nextnum++; name = planename(number); //planehash.set(name.lower(), self); }; destruct() { //planehash.delete(name.lower()); planes = planes - self; }; } class jet: airplane { speed = 1; construct() { inherited construct; name = name.lower(); }; } class prop: airplane { speed = 2; /* don't need a constructor because the default name is uppercase */ } function drawfield() { local i, j, k; console.clear; //print themap.width, " x ", themap.height, "\n"; //print themap.grid; console.readKey; console.clear; for (i = 0; i < themap.height; i++) { console.position(0, i); for (j = 0; j < themap.width; j++) { switch ((k = themap.grid[i][j]) & GRID_TYPEMASK) { case GRID_DOT: print ". "; break; case GRID_LINE: print "+ "; break; case GRID_EDGE: if (i == 0 || i == themap.height - 1) if (j == themap.width - 1) print "- "; else print "--"; else print "| "; break; case GRID_BEACON: print "*", k & GRID_ARGMASK; break; case GRID_EXIT: print k & GRID_ARGMASK; if ((i == 0 || i == themap.height - 1) && j != themap.width - 1) print "-"; else print " "; break; case GRID_AIRPORTW: print "^", k & GRID_ARGMASK; break; case GRID_AIRPORTD: print ">", k & GRID_ARGMASK; break; case GRID_AIRPORTX: print "v", k & GRID_ARGMASK; break; case GRID_AIRPORTA: print "<", k & GRID_ARGMASK; break; default: print "?!"; break; } } } k = themap.width * 2; console.position(k, 0); print "Time: ", time, " Safe: ", score; console.position(k, 2); print "pl dt comm"; for (i = 0, j = 3; i < #planes; i++) if (planes[i].state != STATE_HOLDING) { console.position(planes[i].x * 2, planes[i].y); planes[i].drawplane(); console.position(k, j++); planes[i].drawstatus(); } if (j != 3) j++; for (i = 0; i < #planes; i++) if (planes[i].state == STATE_HOLDING) { console.position(k, j++); planes[i].drawstatus(); } drawcmdline(); } global cmdline = ""; function drawcmdline() { local i, w; console.position(0, themap.height); for (i = 0, w = console.width; i < w; i++) print " "; console.position(0, themap.height); print cmdline; } function newplane() { local x, y, n; if (random(100) < 50) x = create(jet); else x = create(prop); if (#themap.airports != 0 && random(100) < 50) { /* starts at airport */ y = themap.airports[x.delayarg = n = random(#themap.airports)]; print y, " - ", n, " - "; x.x = y[0]; x.y = y[1]; x.direction = y[2]; x.altitude = x.targetalt = 0; x.state = STATE_HOLDING; if (#themap.airports != 0 && random(100) < 50) { /* destination is airport */ x.dest = DEST_AIRPORT; do x.destarg = random(#themap.airports); while (x.destarg == n); } else { x.dest = DEST_EXIT; x.destarg = random(#themap.exits); } } else { y = themap.exits[n = random(#themap.exits)]; print y, " - ", n, " - "; x.x = y[0]; x.y = y[1]; x.direction = y[2]; switch (y[2]) { case DIRW: x.y--; break; case DIRE: x.x++, x.y--; break; case DIRD: x.x++; break; case DIRC: x.x++, x.y++; break; case DIRX: x.y++; break; case DIRZ: x.x--, x.y++; break; case DIRA: x.x--; break; case DIRQ: x.x--, x.y--; break; } x.altitude = x.targetalt = 7; x.state = STATE_MARKED; if (#themap.airports != 0 && random(100) < 50) { /* destination is airport */ x.dest = DEST_AIRPORT; x.destarg = random(#themap.airports); } else { x.dest = DEST_EXIT; do x.destarg = random(#themap.exits); while (x.destarg == n); } } planes = planes + x; } function updateall() { local i; time++; foreach i (planes) i.update(); //print "[pl="; //foreach i (planes) // print " ", i.name; //print "]\n"; //print "[hk=", planehash.keys, "]\n"; //console.readKey; /* add a new plane at random every other update */ if (time % 2 && random(themap.newplane) == 0) newplane(); drawfield(); } function makecmdline(stk) { local i; cmdline = ""; foreach i (stk) cmdline = cmdline + i[0]; } /* Keyboard input is handled using a finite state machine. Originally I had * special cases for each command type and each step along the path to a * completed command, but I ended up having far too many case statements * with nearly identical code. I looked through the unix atc source and * found that it used a finite state machine, so I decided to use one too. * The keyboard code is now much smaller and more easily extensible than it * would have been. */ /****** INPUT RULES ******/ const ALPHA = 256; const DIGIT = 257; /* Each rule is made up of four fields: * character: This is the ASCII value of the character that matches the rule. * If ALPHA or DIGIT is used instead, any letter or digit will match. * next state: This is the state that will be entered after the rule is * matched. Use -1 instead if this rule is the newline at the end of a * valid command. * description: This is the string that is displayed in the command line as * the command is built. It can also be a code reference which will be * called with one argument (a string containing the character typed) and * should return a string. * handler: This is the function that will be called (with one argument, the * ASCII code of the character typed) for this chunk of the command. When * the command is completed (i.e., state -1 is entered), the handlers * of each matched rule are called in the order in which they were * matched. If any of them return a true value (most likely a string), it * is treated as an error message and the command is aborted. */ global states = [ /* 0 */ [ [ ALPHA 1 {{ return args[0] + ":"; }} %setplane ] [ 10 (-1) "" undef ] ] /* 1 */ [ [ 't' 2 " turn" %turn ] [ 'a' 3 " altitude:" undef ] [ 'c' 4 " circle" %circle ] [ 'm' 7 " mark" %mark ] [ 'u' 7 " unmark" %unmark ] [ 'i' 7 " ignore" %ignore ] ] /* 2 */ [ [ 'l' 6 " left" %left ] [ 'r' 6 " right" %right ] [ 'L' 4 " left 90" %left90 ] [ 'R' 4 " right 90" %right90 ] [ 't' 11 " towards" undef ] [ 'w' 4 " to 0" %to_dir ] [ 'e' 4 " to 45" %to_dir ] [ 'd' 4 " to 90" %to_dir ] [ 'c' 4 " to 135" %to_dir ] [ 'x' 4 " to 180" %to_dir ] [ 'z' 4 " to 225" %to_dir ] [ 'a' 4 " to 270" %to_dir ] [ 'q' 4 " to 315" %to_dir ] ] /* 3 */ [ [ '+' 10 " climb" %climb ] [ 'c' 10 " climb" %climb ] [ '-' 10 " descend" %descend ] [ 'd' 10 " descend" %descend ] [ '0' 7 " 0 feet" %setalt ] [ DIGIT 7 {{ return " " + args[0] + "000 feet"; }} %setalt ] ] /* 4 */ [ [ '@' 9 " at" undef ] [ 'a' 9 " at" undef ] [ 10 (-1) "" undef ] ] /* 5 */ [ [ DIGIT 7 {{ return args[0]; }} %delayb ] ] /* 6 */ [ [ '@' 9 " at" undef ] [ 'w' 4 " 0" %rel_dir ] [ 'e' 4 " 45" %rel_dir ] [ 'd' 4 " 90" %rel_dir ] [ 'c' 4 " 135" %rel_dir ] [ 'x' 4 " 180" %rel_dir ] [ 'z' 4 " 225" %rel_dir ] [ 'a' 4 " 270" %rel_dir ] [ 'q' 4 " 315" %rel_dir ] [ 10 (-1) "" undef ] ] /* 7 */ [ [ 10 (-1) "" undef ] ] /* 8 */ [ [ DIGIT 4 {{ return args[0]; }} %benum ] ] /* 9 */ [ [ 'b' 5 " beacon #" undef ] [ '*' 5 " beacon #" undef ] [ 'a' 12 " airport #" undef ] ] /* 10 */ [ [ '0' 7 " 0 ft" %setrelalt ] [ DIGIT 7 {{ return " " + args[0] + "000 ft"; }} %setrelalt ] ] /* 11 */ [ [ 'b' 8 " beacon #" %beacon ] [ '*' 8 " beacon #" %beacon ] [ 'e' 8 " exit #" %exit ] [ 'a' 8 " airport #" %airport ] ] /* 12 */ [ [ DIGIT 7 {{ return args[0]; }} %delaya ] ] ]; global curplane, reldirtype, desttype; newplane {} /* for storing new direction, altitude, delay, etc. */ function findplane(name) { local i; name = name.lower(); foreach i (planes) if (i.name.lower() == name) return i; return undef; } function setplane(c) { //curplane = planehash.get(chr(c).lower()); curplane = findplane(chr(c)); if (!curplane) return "No such plane '" + chr(c) + "'"; newplane.targetalt = curplane.targetalt; newplane.cmd = curplane.cmd; newplane.cmdarg = curplane.cmdarg; newplane.delay = curplane.delay; newplane.delayarg = curplane.delayarg; newplane.state = curplane.state; desttype = DEST_NONE; } function turn() { if (curplane.state == STATE_HOLDING) return "Planes at airports may not change direction"; newplane.cmd = CMD_TURN; newplane.delay = DELAY_NONE; } function circle() { if (curplane.state == STATE_HOLDING) return "Planes cannot circle on the ground"; newplane.cmd = CMD_CIRCLE; newplane.delay = DELAY_NONE; } function mark() { switch (curplane.state) { case STATE_HOLDING: return "Cannot mark planes on the ground"; case STATE_MARKED: return "Already marked"; } newplane.state = STATE_MARKED; } function unmark() { switch (curplane.state) { case STATE_HOLDING: return "Cannot unmark planes on the ground"; case STATE_UNMARKED: return "Already unmarked"; } newplane.state = STATE_UNMARKED; } function ignore() { switch (curplane.state) { case STATE_HOLDING: return "Cannot ignore planes on the ground"; case STATE_IGNORED: return "Already ignored"; } newplane.state = STATE_IGNORED; return undef; } function left() { reldirtype = REL_LEFT; newplane.cmdarg = curplane.direction - 45; if (newplane.cmdarg < 0) newplane.cmdarg += 360; } function right() { reldirtype = REL_RIGHT; newplane.cmdarg = curplane.direction + 45; if (newplane.cmdarg >= 360) newplane.cmdarg -= 360; } function left90() { newplane.cmdarg = curplane.direction - 90; if (newplane.cmdarg < 0) newplane.cmdarg += 360; } function right90() { newplane.cmdarg = curplane.direction + 90; if (newplane.cmdarg >= 360) newplane.cmdarg -= 360; } function to_dir(c) { newplane.cmdarg = char2dir(c); } function climb() { reldirtype = REL_UP; } function descend() { reldirtype = REL_DOWN; } function setalt(c) { c -= '0'; if (curplane.altitude == c && curplane.targetalt == curplane.altitude) return "Already at that altitude"; newplane.targetalt = c; } function delayb(c) { local dx, dy; c -= '0'; if (c >= #themap.beacons) return "Unknown beacon"; newplane.delay = DELAY_BEACON; newplane.delayarg = c; if (desttype != DEST_NONE) { switch (desttype) { case DEST_BEACON: dx = themap.beacons[newplane.destarg][0] - themap.beacons[c][0]; dy = themap.beacons[newplane.destarg][1] - themap.beacons[c][1]; break; case DEST_EXIT: dx = themap.exits[newplane.destarg][0] - themap.beacons[c][0]; dy = themap.exits[newplane.destarg][1] - themap.beacons[c][1]; break; case DEST_AIRPORT: dx = themap.airports[newplane.destarg][0] - themap.beacons[c][0]; dy = themap.airports[newplane.destarg][1] - themap.beacons[c][1]; break; } if (dx == 0 && dy == 0) return "Would already be there"; newplane.cmdarg = dxdy2dir(dx, dy); } } function delaya(c) { local dx, dy; c -= '0'; if (c >= #themap.airports) return "Unknown airport"; newplane.delay = DELAY_AIRPORT; newplane.delayarg = c; if (desttype != DEST_NONE) { switch (desttype) { case DEST_BEACON: dx = themap.beacons[newplane.destarg][0] - themap.airports[c][0]; dy = themap.beacons[newplane.destarg][1] - themap.airports[c][1]; break; case DEST_EXIT: dx = themap.exits[newplane.destarg][0] - themap.airports[c][0]; dy = themap.exits[newplane.destarg][1] - themap.airports[c][1]; break; case DEST_AIRPORT: dx = themap.airports[newplane.destarg][0] - themap.airports[c][0]; dy = themap.airports[newplane.destarg][1] - themap.airports[c][1]; break; } if (dx == 0 && dy == 0) return "Would already be there"; newplane.cmdarg = dxdy2dir(dx, dy); } } function rel_dir(c) { local ang = char2dir(c); if (reldirtype == REL_LEFT) { newplane.cmdarg = curplane.direction - ang; if (newplane.cmdarg < 0) newplane.cmdarg += 360; } else { newplane.cmdarg = curplane.direction + ang; if (newplane.cmdarg >= 360) newplane.cmdarg -= 360; } } function benum(c) { c -= '0'; switch (desttype) { case DEST_BEACON: if (c >= #themap.beacons) return "Unknown beacon"; newplane.destarg = c; newplane.cmdarg = dxdy2dir(themap.beacons[c][0] - curplane.x, themap.beacons[c][1] - curplane.y); break; case DEST_EXIT: if (c >= #themap.exits) return "Unknown exit"; newplane.destarg = c; newplane.cmdarg = dxdy2dir(themap.exits[c][0] - curplane.x, themap.exits[c][1] - curplane.y); break; case DEST_AIRPORT: if (c >= #themap.airports) return "Unknown airport"; newplane.destarg = c; newplane.cmdarg = dxdy2dir(themap.airports[c][0] - curplane.x, themap.airports[c][1] - curplane.y); break; } } function setrelalt(c) { c -= '0'; if (c == 0) return "Altitude not changed"; if (reldirtype == REL_UP) newplane.targetalt = curplane.altitude + c; else newplane.targetalt = curplane.altitude - c; if (newplane.targetalt < 0) return "Altitude would be too low"; else if (newplane.targetalt > 9) return "Altitude would be too high"; } function beacon() { desttype = DEST_BEACON; } function exit() { desttype = DEST_EXIT; } function airport() { desttype = DEST_AIRPORT; } /****** INPUT LOOP ******/ function main() { local cw, ch, c, a, t, i, state = 0, inpstack = [], gotit, msg; console.statusline = false; print "Welcome to ATC. Do you need instructions? (y/n)"; c = console.readKey(); if (c == "y" || c == "Y") intro(); console.clear; console.fixed = true; console.showStatus = false; cw = console.width, ch = console.height; if (cw < themap.width * 2 + 19 || ch < themap.height + 1) { print "Sorry, your console is not big enough to play ATC.\n"; print "It must be at least ", themap.height + 1, " rows by ", themap.width * 2 + 19, " columns, but it's only ", ch, " by ", cw, "."; snack.halt; } //planehash = create(hash); themap.init(); newplane(); drawfield(); try { while (true) { c = console.readKey(MSPERTICK); if (c == "") updateall(); else if (c == "escape" || asc(c) == 3) { i = cmdline; cmdline = "Really quit? (y/n)"; drawcmdline(); c = console.readKey(); if (c == "y" || c == "Y") snack.halt; cmdline = i; drawcmdline(); } else if (c == "delete") { if (#inpstack) { inpstack = poplist(inpstack); makecmdline(inpstack); drawcmdline(); if (#inpstack) state = inpstack[#inpstack-1][3]; else state = 0; } else console.beep; } else { /* do the funky state machine dance */ if (isalpha(c)) t = ALPHA; else if (isdigit(c)) t = DIGIT; else t = 0; a = asc(c); /* look for a rule in the current state that matches the character */ gotit = false; foreach i (states[state]) if (i[0] == t || i[0] == a) { state = i[1]; inpstack = inpstack + [[((i[2])(c)) (i[3]) (a) (state)]]; gotit = true; break; } if (!gotit) console.beep; else if (state != -1) { makecmdline(inpstack); drawcmdline(); } else { /* -1 means a complete line is entered */ /* if #inpstack is 1, the return is the only entered key, so just * do an update. */ if (#inpstack == 1) { updateall(); inpstack = []; state = 0; continue; } cmdline = ""; gotit = true; /* stays set if the cmd succeeds */ foreach i (inpstack) { /* run the handler for this chunk of command */ if (c = (i[1])(i[2])) { /* failure! display the message and forget the command. */ cmdline = c; gotit = false; console.beep; break; } } if (gotit) { /*print "[cur=", curplane.name, "]\n"; print "[ta=", curplane.targetalt, "/", newplane.targetalt, "]\n"; print "[st=", curplane.state, "/", newplane.state, "]\n"; print "[c=", newplane.cmdarg, " ", newplane.cmd, " ", newplane.delay, " ", newplane.delayarg, "]\n"; console.readKey;*/ if (curplane.targetalt != newplane.targetalt) curplane.targetalt = newplane.targetalt; else if (curplane.state != newplane.state) curplane.state = newplane.state; else { curplane.cmdarg = newplane.cmdarg; curplane.cmd = newplane.cmd; curplane.delay = newplane.delay; curplane.delayarg = newplane.delayarg; } } drawcmdline(); /* start over for a new command */ inpstack = []; state = 0; drawfield(); } } } } catch (msg is type_String) { cmdline = "You lose: " + msg; drawcmdline(); } print "\n"; /* flush output buffer, maybe */ } function intro() { console.clear; print "\(RULES\)\n" + "In this game, you are an air traffic controller. There is no winning " + "state; the game continues until you lose, and you have only your " + "score to tell you how far you have progressed. You will have to " + "launch planes that are holding at airports (by ordering them to " + "increase their altitude), land planes at airports (by ordering them " + "to descend to 0 feet over the airport), and maneuver planes through " + "exit positions along the edges of the field.\b" + "There are many ways to lose the game: you can crash a plane on the " + "ground where there is no airport, send a plane off the edge of the " + "field where there is no exit, land a plane when you should have " + "sent it through an exit, send a plane through an exit when you " + "should have landed it, or crash two planes into each other.\b" + "Planes appear randomly at exits and airports. Each exit and airport " + "has an associated direction, which is the direction in which new " + "planes will be flying from them. For airports, the same direction " + "must also be used to land planes there. Planes start out at 7000 feet " + "from exits, and 0 feet at airports. To send a plane through an exit, " + "you must first bring it up to 9000 feet.\b" + "There are two kinds of planes: jets (shown with lowercase letters), " + "which move every update; and prop planes (uppercase), which move " + "every other update.\b" + "\(DISPLAY\)\n" + "The screen is divided into three areas: the field, which is a radar " + "display showing landmarks and aircraft; the status display (to the " + "right of the field), which shows the name, altitude, destination, " + "and current orders of each plane, as well as your score and the " + "current game time; and the command line (below the field), which " + "shows the current orders as you type them, as well as any errors " + "you may receive from giving invalid orders.\b" + "The field shows five things: lines, exits, beacons, airports, and " + "planes. Lines are shown with + signs and have no purpose other than " + "to show you how to guide the planes around. Exits are shown as " + "numbers around the edge of the field. Beacons are shown as asterisks " + "followed by numbers, and their only purpose is to provide an easy " + "reference point. Airports are shown as arrows (<, >, ^, or v) " + "followed by numbers; the arrows show the direction planes are " + "flying when they take off and land. Planes are shown as letters " + "followed by numbers; the numbers show the plane's altitude in " + "thousands of feet.\b" + "\(GIVING ORDERS\)\n" + "You control the planes by giving them orders. Commands you can give " + "are divided into two groups: immediate-only, which take effect on " + "the next update; or delayable, which can be delayed until the plane " + "reaches a specified landmark (explained below).\b" + "To give a command, press the letter of the plane's name, then the " + "letters of the command, then press return or enter. If you make a " + "mistake, press backspace or delete to back up. In the following " + "lists, \"