[Cedar]<CedarChest®>Top>MazeWar.df=>MazeWarPlayerImpl2.Mesa
Mike Spreitzer July 19, 1986 1:44:42 pm PDT
Hal Murray, March 16, 1986 7:26:54 pm PST
DIRECTORY Atom, Basics, BasicTime, Commander, Containers, FS, Imager, ImagerBackdoor, ImagerPath, ImagerPixelMap, IO, IOClasses, Labels, MazeWarFinder, MazeWarFinderRpcControl, MazeWarPlayer, MazeWarPlayerInsides, MazeWarPlayerRpcControl, MessageWindow, Process, Random, Rope, RPC, ThisMachine, UserCredentials, ViewerBlast, ViewerClasses, ViewerOps;
MazeWarPlayerImpl2: CEDAR MONITOR
LOCKS lock USING lock: REF MONITORLOCK
IMPORTS Atom, BasicTime, Commander, Containers, FS, IO, Imager, ImagerBackdoor, ImagerPixelMap, IOClasses, Labels, MazeWarFinder, MazeWarFinderRpcControl, MazeWarPlayerInsides, MazeWarPlayerRpcControl, MessageWindow, Process, Random, Rope, RPC, ThisMachine, UserCredentials, ViewerBlast, ViewerOps
EXPORTS MazeWarPlayer, MazeWarPlayerInsides =
INVARIANT
No clippable paints are going on.
BEGIN OPEN MazeWarPlayerInsides;
myself: PUBLIC Player ← NIL;
playerNumber: PUBLIC CARDINAL ← 47;
myInstance: PUBLIC ROPE ← "??";
topviews: PUBLIC ARRAY Angle OF Bitmap ← ALL[nilBitmap];
exportedPlayer: PUBLIC BOOLEANFALSE;
pieces: PUBLIC ARRAY [0 .. MaxDistance] OF ARRAY Piece OF LocatedBitmap ← ALL[ALL[[]]];
shownPlayers: PUBLIC PlayerList ← NIL;
rs: Random.RandomStream ← Random.Create[seed: -1];
MakeNewPlayer: PUBLIC PROC [name, instance, picsSource, mazeSource: ROPE] RETURNS [errmsg: ROPENIL] =
BEGIN
mv: MazeView;
id: PlayerId ← rs.ChooseInt[FIRST[INTEGER], LAST[INTEGER]];
userName, password, mazeRope: ROPE;
failure: BOOLEANFALSE;
pp: PlayerPics;
mazeStreamIn, mazeStreamOut: IO.STREAM;
mazeStreamIn ← FS.StreamOpen[fileName: mazeSource ! FS.Error => {errmsg ← IO.PutFR["Unable to open maze source because %g", IO.rope[error.explanation]]; failure ← TRUE; CONTINUE}];
IF failure THEN RETURN;
[] ← mazeStreamIn.SkipWhitespace[flushComments: FALSE];
mazeStreamOut ← IO.ROS[];
IOClasses.Copy[from: mazeStreamIn, to: mazeStreamOut, closeFrom: TRUE, closeTo: FALSE];
mazeRope ← mazeStreamOut.RopeFromROS[];
pp ← GetPlayerPics[picsSource !
FS.Error => {errmsg ← error.explanation; failure ← TRUE; CONTINUE}];
IF failure THEN RETURN;
[mv, errmsg] ← MazeFromRope[mazeRope];
IF errmsg.Length[] > 0 THEN RETURN;
myself ← Plop[mv, id, name, instance, pp];
ViewMaze[myself, [iconic: TRUE, name: name]];
AddScoring[myself, myself];
[userName, password] ← UserCredentials.Get[];
TRUSTED {MazeWarPlayerRpcControl.ExportInterface[
interfaceName: [instance: instance],
user: userName,
password: RPC.MakeKey[password]]};
exportedPlayer ← TRUE;
TRUSTED {
Process.Detach[FORK StayAlive[myself]];
Process.Detach[FORK StatusBroadcaster[myself]];
};
END;
debugImport: BOOLEANFALSE;
importFailures: LIST OF CARDINALNIL;
exportedFinder: BOOLEANFALSE;
disabled: BOOLFALSE;
finderID: BasicPlayer;
StayAlive: PROC [p: Player] =
BEGIN ENABLE UNWIND => {IF p.destroyed THEN myself ← NIL};
ExportFinder: PROC = {
IF exportedFinder THEN
{MazeWarFinderRpcControl.UnexportInterface[]; exportedFinder ← FALSE};
exportedFinder ← TRUE;
TRUSTED {MazeWarFinderRpcControl.ExportInterface[
interfaceName: [instance: "MazeWar.Auto"],
user: "MazeWar.Auto",
password: RPC.MakeKey["MazeWar"]
! RPC.ExportFailed => {
MessageWindow.Append[
IO.PutFR["Tried to become Finder and failed! (at %g)", [time[BasicTime.Now[]]]],
TRUE];
exportedFinder ← FALSE;
CONTINUE};
]};
};
TRUSTED {Process.SetPriority[Process.priorityBackground]};
ExportFinder[];
work around half-day timeout for binding to unfindable machine
FOR age: CARDINAL ← 0, age + 1 WHILE NOT p.destroyed DO
ok: BOOLEANTRUE;
IF NOT disabled THEN {
MazeWarFinderRpcControl.ImportInterface[interfaceName: [instance: "MazeWar.Auto"] !RPC.ImportFailed => {ok ← FALSE; CONTINUE}];
IF debugImport THEN {
MessageWindow.Append[IO.PutFR["Import %g at age %g", IO.bool[ok], IO.card[age]], TRUE];
IF NOT ok THEN importFailures ← CONS[age, importFailures]};
IF ok THEN {
players: BasicPlayerList;
TRUSTED {
finderID ← MazeWarFinder.GetFinderID[!RPC.CallFailed => {ok ← FALSE; CONTINUE}];
IF ok THEN MazeWarFinder.TakePlayer[[myself.instanceName, myself.id] !RPC.CallFailed => {ok ← FALSE; CONTINUE}];
IF ok THEN players ← MazeWarFinder.ListPlayers[!RPC.CallFailed => {ok ← FALSE; CONTINUE}]};
IF ok THEN {
FOR players ← players, players.rest WHILE players # NIL DO
q: Player ← EnsurePlayer[players.first];
IF q # NIL AND NOT q.instanceName.Equal[players.first.instance] THEN
MessageWindow.Append["Random number collision", TRUE];
ENDLOOP;
};
TRUSTED {MazeWarFinderRpcControl.UnimportInterface[]};
}
ELSE ExportFinder[];
};
Delay[age];
ENDLOOP;
IF exportedFinder THEN
{MazeWarFinderRpcControl.UnexportInterface[]; exportedFinder ← FALSE};
myself ← NIL;
END;
StatusBroadcaster: PROC [p: Player] =
BEGIN
TRUSTED {Process.SetPriority[Process.priorityForeground]};
DO
row, col, score, angle: INTEGER;
shot: BOOL;
Wait: ENTRY PROC [lock: REF MONITORLOCK] RETURNS [exit: BOOL] = {
ENABLE UNWIND => NULL;
WHILE NOT (p.announce OR p.destroyed) DO WAIT p.announceChange ENDLOOP;
p.announce ← FALSE;
row ← p.row;
col ← p.col;
score ← p.score;
angle ← p.angle;
shot ← p.announceShot;
p.announceShot ← FALSE;
exit ← p.destroyed;
};
IF Wait[p.announceLock] THEN EXIT;
FOR pl: PlayerList ← p.mv.players, pl.rest WHILE pl # NIL DO
ok: BOOLEANTRUE;
IF pl.first = p OR pl.first.ir = NIL THEN LOOP;
TRUSTED {pl.first.ir.TakeStatus[id: p.id, row: row, col: col, score: score, angle: angle, shot: shot !RPC.CallFailed => {ok ← HandleCallFailure[pl.first, why]; CONTINUE}]};
NoticeCall[pl.first, ok];
ENDLOOP;
ENDLOOP;
END;
AnnounceStatus: PUBLIC PROC [shot: BOOLEAN] =
BEGIN
Enter: ENTRY PROC [lock: REF MONITORLOCK] = {
ENABLE UNWIND => NULL;
myself.announceShot ← myself.announceShot OR shot;
myself.announce ← TRUE;
BROADCAST myself.announceChange;
};
Enter[myself.announceLock];
END;
WhoAmI: PROC [cmd: Commander.Handle] RETURNS [result: REFNIL, msg: ROPENIL] --Commander.CommandProc-- =
BEGIN
p: Player ← myself;
IF p = NIL THEN RETURN [$Failure, "Not playing"] ELSE cmd.out.PutF["\"%q\" %g\n", IO.rope[p.instanceName], IO.int[p.id]];
END;
Contact: PROC [cmd: Commander.Handle] RETURNS [result: REFNIL, msg: ROPENIL] --Commander.CommandProc-- =
BEGIN
Parse: PROC = {
bp.instance ← in.GetRopeLiteral[];
bp.id ← in.GetInt[];
};
bp: BasicPlayer;
in: IO.STREAMIO.RIS[cmd.commandLine];
ok: BOOLTRUE;
IF myself = NIL THEN RETURN [$Failure, "Not playing"];
Parse[!IO.Error, IO.EndOfStream => {
ok ← FALSE;
CONTINUE}];
IF ok THEN [] ← EnsurePlayer[bp] ELSE RETURN [$Failure, "Syntax error; say: MazeWarContact \"nn#mmm#ss\" iiiii\n"];
END;
Delay: PROC [age: CARDINAL] =
BEGIN
center: CARDINAL ← Process.SecondsToTicks[IF age < 5 THEN 4 ELSE IF age < 18 THEN 16 ELSE 180];
wait: CARDINAL ← rs.ChooseInt[center/2, 3*center/2];
Process.Pause[wait];
END;
MazeFromRope: PROC [rope: ROPE] RETURNS [mv: MazeView, errmsg: ROPENIL] =
BEGIN
Check: PROC [row, col, drow, dcol: INTEGER] RETURNS [bad: BOOLEANFALSE] =
BEGIN
index: INTEGER ← Index[mv, row-drow, col-dcol];
di: INTEGER ← Direction[mv, drow, dcol];
IF NOT mv.squares[index].open THEN
BEGIN
d: INTEGER ← 0;
FOR index ← index+di, index+di WHILE mv.squares[index].open DO d ← d + 1 ENDLOOP;
IF d > MaxDistance+1 THEN {errmsg ← IO.PutFR["Maze has open distance too long (> %g)", IO.int[MaxDistance+1]]; RETURN[TRUE]};
END;
END;
rl: INT ← rope.Length[];
cols: INTEGER ← rope.Find["\n"];
rows: INTEGER ← rl/(cols+1);
IF rows*(cols+1) # rl THEN RETURN [NIL, "Maze description not properly punctuated with newlines"];
mv ← NEW [MazeViewRep[rows*cols]];
mv.asRope ← rope;
mv.rows ← rows;
mv.cols ← cols;
FOR row: INTEGER IN [0 .. rows) DO
FOR col: INTEGER IN [0 .. cols) DO
char: CHAR ← rope.Fetch[row*(cols+1)+col];
index: INTEGER ← Index[mv, row, col];
mv.squares[index] ← [];
SELECT char FROM
'X => mv.squares[index].open ← FALSE;
'. => mv.squares[index].open ← TRUE;
ENDCASE => RETURN [NIL, IO.PutFR["Bad character in maze: 0%bC", IO.char[char]]];
IF mv.squares[index].open AND (row = 0 OR row = rows-1 OR col = 0 OR col = cols-1) THEN RETURN [NIL, IO.PutFR["Gap in border"]];
ENDLOOP;
ENDLOOP;
FOR row: INTEGER IN (0 .. rows-1) DO
FOR col: INTEGER IN (0 .. cols-1) DO
IF Check[row, col, 0, 1] THEN RETURN;
IF Check[row, col, 1, 0] THEN RETURN;
ENDLOOP;
ENDLOOP;
mv.topX ← innerBorder + w - tvd*cols/2;
mv.topY ← innerBorder + tvd*rows;
mv.inY ← w + hallTopSep;
mv.inX ← tvd*cols/2;
END;
hallTopSep: INTEGER ← 3;
scoreTopSep: INTEGER ← 5;
scoreSep: INTEGER ← 1;
innerBorder: INTEGER ← 2;
Plop: PROC [mv: MazeView, id: PlayerId, name, instance: ROPE, pp: PlayerPics] RETURNS [p: Player] =
BEGIN
row, col: INTEGER;
[row, col] ← PickAPlace[mv];
p ← AddPlayerAt[mv, NIL, instance, id, name, row, col, 0, 0, pp];
END;
PickAPlace: PUBLIC PROC [mv: MazeView] RETURNS [row, col: INTEGER] =
BEGIN
me: INTEGER;
DO
row ← rs.ChooseInt[0, mv.rows-1];
col ← rs.ChooseInt[0, mv.cols-1];
me ← Index[mv, row, col];
IF mv.squares[me].open THEN RETURN;
ENDLOOP;
END;
ViewMaze: PROC [p: Player, viewerInit: ViewerClasses.ViewerRec] =
BEGIN
IF viewerInit.icon = unInit THEN viewerInit.icon ← mazeWarriorIcon;
p.mv.container ← Containers.Create[info: viewerInit, paint: FALSE];
p.mv.maze ← ViewerOps.CreateViewer[flavor: mazeViewerFlavor, info: [parent: p.mv.container, wx: 10, wy: 10, ww: 100, wh: 100, data: p, border: FALSE], paint: FALSE];
ViewerOps.MoveViewer[
viewer: p.mv.maze,
x: p.mv.maze.wx,
y: p.mv.maze.wy,
w: 2*(w+innerBorder) + p.mv.maze.ww - p.mv.maze.cw,
h: 2*(w+innerBorder) + hallTopSep + tvd*p.mv.rows + p.mv.maze.wh - p.mv.maze.ch,
paint: FALSE];
END;
IncrPaint: PUBLIC PROC [p: Player] = {
IF p.shot THEN TRUSTED {Process.Detach[FORK ReallyIncrPaint[p]]}
ELSE ReallyIncrPaint[p]};
paintlock: REF MONITORLOCK = NEW [MONITORLOCK];
ReallyIncrPaint: PROC [p: Player] = {
InnerPaint: ENTRY PROC [lock: REF MONITORLOCK] = {
ViewerOps.PaintViewer[viewer: p.mv.maze, hint: client, clearClient: FALSE, whatChanged: p];
};
IF p # myself OR NOT clippable THEN
ViewerOps.PaintViewer[viewer: p.mv.maze, hint: client, clearClient: FALSE, whatChanged: p]
ELSE {
paints ← paints + 1;
InnerPaint[paintlock !UNWIND => paints ← paints - 1];
paints ← paints - 1};
};
AddScoring: PUBLIC PROC [vp, p: Player] =
BEGIN
mv: MazeView ← p.mv;
y: INTEGER ← vp.mv.maze.wy + vp.mv.maze.wh + scoreTopSep;
p.nameLabel ← Labels.Create[info: [parent: mv.container, name: p.name, border: FALSE], paint: FALSE];
p.scoreLabel ← Labels.Create[info: [parent: mv.container, name: "-65,535", border: FALSE], paint: FALSE];
Labels.Set[p.scoreLabel, IO.PutFR["%6g", IO.int[p.score]], FALSE];
Labels.SetDisplayStyle[p.nameLabel, IF Visibility[vp, p] > 0 THEN $WhiteOnBlack ELSE $BlackOnWhite, FALSE];
FOR pl: PlayerList ← mv.players, pl.rest WHILE pl # NIL DO
n: Viewer ← pl.first.nameLabel;
v: Viewer ← pl.first.scoreLabel;
ViewerOps.MoveViewer[viewer: n, x: mv.maze.wx, y: y, w: n.ww, h: n.wh, paint: FALSE];
ViewerOps.MoveViewer[viewer: v, x: mv.maze.wx + mv.maze.ww - v.ww, y: y, w: v.ww, h: v.wh, paint: FALSE];
y ← MAX[n.wy+n.wh, v.wy+v.wh] + scoreSep;
ENDLOOP;
END;
DrawHall: PUBLIC PROC [context: Context, mv: MazeView, row, col, drow, dcol: INTEGER, angle: Angle] =
BEGIN
Line: PROC [a, b: Pt3] = INLINE {context.MaskVector[[a.x/a.z, a.y/a.z], [b.x/b.z, b.y/b.z]]};
DoIt: PROC = {
Blt: PROC [piece: Piece] = {
lb: LocatedBitmap ← pieces[d][piece];
ViewerBlast.DrawBitmap[context: context, bitmap: lb.bitmap, sOrigin: lb.sOrigin, fOrigin: lb.fOrigin];
};
DrawPlayers: PROC = {
context.SetColor[ImagerBackdoor.invert];
FOR pl: PlayerList ← shownPlayers, pl.rest WHILE pl # NIL DO
b: Bitmap ← pl.first.pics.pics[pl.first.sdistance][(pl.first.sangle+4-angle) MOD 4];
ViewerBlast.DrawBitmap[context: context, bitmap: b, fOrigin: b.fSize/2, sOrigin: b.sSize/2];
ENDLOOP;
};
DrawBorder: ImagerPath.PathProc = {
moveTo[[w, w]];
lineTo[[w, -w]];
lineTo[[-w, -w]];
lineTo[[-w, w]];
lineTo[[ w, w]];
};
me, di, left, right, d, crow, ccol: INTEGER;
f, b: Number;
mes, lefts, rights: Square;
di ← Direction[mv, drow, dcol];
left ← Direction[mv, -dcol, drow];
right ← Direction[mv, dcol, -drow];
crow ← row;
ccol ← col;
mes ← mv.squares[me ← Index[mv, row, col]];
f ← z1;
b ← hither;
context.SetColor[Imager.white];
context.MaskRectangle[[-w, -w, 2*w, 2*w]];
context.SetColor[Imager.black];
context.MaskStroke[DrawBorder];
IF NOT mes.open THEN RETURN;
lefts ← mv.squares[me + left];
rights ← mv.squares[me + right];
d ← 0;
WHILE mes.open DO
nu: INTEGER ← me + di;
fls: Square ← mv.squares[nu + left];
frs: Square ← mv.squares[nu + right];
fs: Square ← mv.squares[nu];
r2: Number ← f*r/b;
IF paints > 1 AND clippable THEN RETURN;
IF NOT lefts.open THEN {
IF blt THEN Blt[LeftFilled]
ELSE {
Line[[-r, r, f], [-r, r, b]];
Line[[-r, -r, f], [-r, -r, b]]}}
ELSE IF NOT fls.open THEN {
IF blt THEN Blt[LeftGap]
ELSE {
Line[[-r2, r, f], [-r, r, f]];
Line[[-r2, -r, f], [-r, -r, f]]}};
IF (IF fs.open THEN lefts.open # fls.open ELSE IF lefts.open THEN fls.open ELSE TRUE) THEN {
IF blt THEN Blt[LeftEdge] ELSE Line[[-r, r, f], [-r, -r, f]]};
IF NOT rights.open THEN {
IF blt THEN Blt[RightFilled]
ELSE {
Line[[ r, r, f], [ r, r, b]];
Line[[ r, -r, f], [ r, -r, b]]}}
ELSE IF NOT frs.open THEN {
IF blt THEN Blt[RightGap]
ELSE {
Line[[ r2, r, f], [ r, r, f]];
Line[[ r2, -r, f], [ r, -r, f]]}};
IF (IF fs.open THEN rights.open # frs.open ELSE IF rights.open THEN frs.open ELSE TRUE) THEN {
IF blt THEN Blt[RightEdge] ELSE Line[[r, r, f], [r, -r, f]]};
b ← f;
f ← f + dz;
d ← d + 1;
me ← nu;
mes ← fs;
lefts ← fls;
rights ← frs;
crow ← crow + drow;
ccol ← ccol + dcol;
ENDLOOP;
d ← d - 1;
IF blt THEN Blt[HorEdges]
ELSE {
Line[[-r, r, b], [r, r, b]];
Line[[-r, -r, b], [r, -r, b]]};
CallUnderMonitor1[DrawPlayers];
};
mark: BasicTime.Pulses;
IF hallHist # NIL THEN mark ← BasicTime.GetClockPulses[];
DoIt[];
IF hallHist # NIL THEN hallHist.Increment[ BasicTime.PulsesToMicroseconds[BasicTime.GetClockPulses[] - mark]/5000];
END;
hallHist: Histograms.Histogram;
hallHistViewer: Viewer;
HistHall: PROC =
BEGIN
hallHist ← Histograms.NewHistogram[];
hallHistViewer ← hallHist.ShowIn[name: "Hall Paint Times/(.005 sec)"];
END;
AddPlayerAt: PUBLIC PROC [mv: MazeView, ir: PlayerInterface, instanceName: ROPE, id: PlayerId, name: ROPE, row, col: INTEGER, angle: Angle, score: INTEGER, pp: PlayerPics] RETURNS [p: Player] =
BEGIN
me: INTEGER ← Index[mv, row, col];
p ← NEW [PlayerRep ← [mv: mv, ir: ir, instanceName: instanceName, id: id, quiverTill: BasicTime.GetClockPulses[]-1, row: row, col: col, angle: angle, peek: 0, score: score, name: name, pics: pp, announceLock: NEW [MONITORLOCK]]];
mv.squares[me].occupants ← CONS[p, mv.squares[me].occupants];
mv.players ← CONS[p, mv.players];
TRUSTED {Process.InitializeCondition[@p.announceChange, Process.SecondsToTicks[5]]};
END;
Setup: PROC =
BEGIN
f, b: Number;
count: REF CARDINALNARROW[Atom.GetProp[atom: $MazeWar, prop: $PlayerCount]];
playerRope: ROPENIL;
ArrowPath: ImagerPath.PathProc = {
q: REAL = 2; q2: REAL = 5;
moveTo[ [tvd/q, 0]];
lineTo[ [0, tvd/q]];
lineTo[ [0, tvd/q2]];
lineTo[ [-tvd/q, tvd/q2]];
lineTo[ [-tvd/q, -tvd/q2]];
lineTo[ [0, -tvd/q2]];
lineTo[ [0, -tvd/q]];
lineTo[ [tvd/q, 0]];
};
IF count = NIL THEN Atom.PutProp[atom: $MazeWar, prop: $PlayerCount, val: count ← NEW [CARDINAL ← 0]];
count^ ← playerNumber ← count^ + 1;
myInstance ← IO.PutFR["%g%b", IO.rope[ThisMachine.Address[$Pup]], IO.card[playerNumber]];
IF playerNumber # 1 THEN playerRope ← IO.PutFR["%g", IO.card[playerNumber]];
FOR a: Angle IN Angle DO
context: Context;
topviews[a] ← ImagerPixelMap.Create[0, [0, 0, tvd, tvd]];
context ← ViewerBlast.PixelMapContext[topviews[a]];
context.SetColor[Imager.white];
context.MaskRectangle[[0, 0, tvd, tvd]];
context.SetColor[Imager.black];
context.TranslateT[[tvd/2, tvd/2]];
context.RotateT[a*-90];
context.MaskFill[ArrowPath];
ENDLOOP;
f ← z1;
b ← hither;
FOR d: INTEGER IN [0 .. MaxDistance] DO
context: Context;
Start: PROC [piece: Piece] = {
rmin: Number ← r/f;
rmax: Number ← r/b;
bounds: Box;
bounds ← SELECT piece FROM
LeftFilled => [-rmax, -rmax, -rmin, rmax],
LeftGap => [-rmax, -rmin, -rmin, rmin],
LeftEdge => [-rmin, -rmin, -rmin, rmin],
RightFilled => [rmin, -rmax, rmax, rmax],
RightGap => [rmin, -rmin, rmax, rmin],
RightEdge => [rmin, -rmin, rmin, rmin],
HorEdges => [-rmin, -rmin, rmin, rmin],
ENDCASE => ERROR;
bounds.xmin ← bounds.xmin - 1;
bounds.ymin ← bounds.ymin - 1;
bounds.xmax ← bounds.xmax + 1;
bounds.ymax ← bounds.ymax + 1;
pieces[d][piece] ← [
bitmap: ImagerPixelMap.Create[0, [
sMin: 0,
fMin: 0,
sSize: bounds.ymax-bounds.ymin,
fSize: bounds.xmax-bounds.xmin]],
fOrigin: -bounds.xmin,
sOrigin: bounds.ymax];
context ← ViewerBlast.PixelMapContext[pieces[d][piece].bitmap];
context.TranslateT[[-bounds.xmin, -bounds.ymin]];
context.SetColor[Imager.white];
context.MaskBox[[bounds.xmin, bounds.ymin, bounds.xmax, bounds.ymax]];
context.SetColor[Imager.black];
};
Line: PROC [a, b: Pt3] = INLINE {context.MaskVector[[a.x/a.z, a.y/a.z], [b.x/b.z, b.y/b.z]]};
r2: Number ← f*r/b;
Start[LeftFilled]; Line[[-r, r, f], [-r, r, b]]; Line[[-r, -r, f], [-r, -r, b]];
Start[LeftGap]; Line[[-r2, r, f], [-r, r, f]]; Line[[-r2, -r, f], [-r, -r, f]];
Start[LeftEdge]; Line[[-r, r, f], [-r, -r, f]];
Start[RightFilled]; Line[[ r, r, f], [ r, r, b]]; Line[[ r, -r, f], [ r, -r, b]];
Start[RightGap]; Line[[ r2, r, f], [ r, r, f]]; Line[[ r2, -r, f], [ r, -r, f]];
Start[RightEdge]; Line[[r, r, f], [r, -r, f]];
Start[HorEdges]; Line[[-r, r, f], [r, r, f]]; Line[[-r, -r, f], [r, -r, f]];
b ← f;
f ← f + dz;
ENDLOOP;
ViewerOps.RegisterViewerClass[flavor: mazeViewerFlavor, class: mazeViewerClass];
Commander.Register[key: Rope.Cat["MazeWar", playerRope], proc: StartPlayer, doc: "starts playing maze war; see MazeWarDoc.Tioga for possible command line arguments"];
Commander.Register[key: Rope.Cat["MazeWarStop", playerRope], proc: StopPlayer, doc: "exits maze war game"];
Commander.Register[key: Rope.Cat["WhoAmI", playerRope], proc: WhoAmI, doc: "gives your MazeWar identification"];
Commander.Register[key: Rope.Cat["MazeWarContact", playerRope], proc: Contact, doc: "MazeWarContact \"nn#mmm#ss\" iiiii makes contact with that MazeWar player"];
END;
Box: TYPE = RECORD [xmin, ymin, xmax, ymax: Number];
StupidFuckingWarnings: PUBLIC SIGNAL [ATOM] = CODE;
Setup[];
END.