RobotTool.mesa
Created Tuesday, June 19, 1984 6:14 pm PDT
Last edited by Eric Nickell, July 11, 1985 11:28:27 pm PDT
DIRECTORY
Buttons USING [ButtonProc, Button, Create],
Commander USING [CommandProc, Handle, Register],
Containers USING [Container, Create],
IO USING [CreateStream, CreateStreamProcs, PutF, PutRope, STREAM, StreamProcs, int, rope],
Process USING [MsecToTicks, Pause, Ticks],
ProcessProps USING [GetProp],
Random USING [RandomStream, Create, ChooseInt],
Real USING [FixI, RoundLI],
RealFns USING [ArcTan, Cos, Sin],
RobotArena USING [Arena, Create, SetRobot, SetRobotColor],
RobotDefs USING [MemoryMap, Robot, RobotIndex, RobotState, RobotStateRec],
RobotIO USING [AssembleRobot, ReadRobot],
RobotHardware USING [direct, fire, hdmg, hx, hy, InitialMemory, mdmg, mx, my, ProcessRobot, speed],
Rope USING [Length, ROPE],
TypeScript USING [ChangeLooks, Create, PutChar, TS],
ViewerClasses USING [Viewer],
ViewerOps USING [OpenIcon, PaintViewer, SetOpenHeight],
ViewerTools USING [GetContents, MakeNewTextViewer];
RobotTool: CEDAR MONITOR
IMPORTS Buttons, Commander, Containers, IO, Process, ProcessProps, Random, Real, RealFns, RobotArena, RobotHardware, RobotIO, Rope, TypeScript, ViewerOps, ViewerTools
~ {
OPEN V: ViewerClasses, R: Real, RF: RealFns, RH: RobotHardware;
ROPE: TYPE ~ Rope.ROPE;
maxTurn: CARDINAL ← 5000;
waitTurn: CARDINAL ← 0;
hMargin: INT ~ 5;  --Spacing between buttons on a line
vMargin: INT ~ 5;  --Spacing between rows of buttons
vHeight: INT ~ 18;  --Height of each row
hTextWidth: INT ~ 100; --Default width of a text box
arenaHeight, arenaWidth: INT ~ 400;  --Size of the battle arena
robotDeath: INT ← 5;  --Death count for robots
robotRadius: INT ← 1;  --Robot fatness
failTemperature: INT ← 20; --Laser failure
delay: Process.Ticks ← Process.MsecToTicks[1000/10];
accuracy: REAL ← 100.0;  --Legal locations/reading along arena
MemoryMap: TYPE ~ RobotDefs.MemoryMap;
RobotIndex: TYPE ~ RobotDefs.RobotIndex;
RobotTool: TYPE ~ REF RobotToolRec;
RobotToolRec: TYPE ~ RECORD [
robots: ARRAY RobotIndex OF ViewerClasses.Viewer ← ALL[NIL],
arena: ViewerClasses.Viewer ← NIL,
ts: TypeScript.TS,
wDir: ROPE,
pleaseStop: BOOLEAN
];
MakeRobotTool: Commander.CommandProc ~ {
robotTool: RobotTool ← NEW[RobotToolRec];
viewer: Containers.Container ← Containers.Create[[
name: "Robot War",
iconic: TRUE,
scrollable: FALSE
]];
thisX: INT ← hMargin;
thisY: INT ← vMargin;
prev: ViewerClasses.Viewer;
Working Directory
WITH ProcessProps.GetProp[$WorkingDirectory] SELECT FROM
wd: ROPE => robotTool.wDir ← wd;
ENDCASE => ERROR;
Go button
prev ← Buttons.Create[
info: [
name: "Go! ",
wx: thisX,
wy: thisY,
wh: vHeight,
parent: viewer,
border: TRUE
],
proc: RobotBattle,
fork: TRUE,
clientData: robotTool
];
thisX ← thisX + prev.ww + hMargin;
Stop button
prev ← Buttons.Create[
info: [
name: "Stop! ",
wx: thisX,
wy: thisY,
wh: vHeight,
parent: viewer,
border: TRUE
],
proc: RobotStop,
fork: TRUE,
clientData: robotTool
];
thisX ← thisX + prev.ww + hMargin;
Robots
FOR i: RobotIndex IN RobotIndex DO
IF thisX+hTextWidth > arenaWidth THEN { --Go to next line
thisX ← hMargin;
thisY ← thisY + vHeight + vMargin;
};
prev ← robotTool.robots[i] ← ViewerTools.MakeNewTextViewer[
info: [
wx: thisX,
wy: thisY,
wh: vHeight,
parent: viewer,
ww: hTextWidth,
border: TRUE,
scrollable: FALSE
]
];
thisX ← thisX + prev.ww + hMargin;
ENDLOOP;
thisX ← hMargin;
thisY ← thisY + vHeight + vMargin;
Typescript
prev ← robotTool.ts ← TypeScript.Create[[
wx: thisX,
wy: thisY,
wh: 5*vHeight,
ww: arenaWidth,
parent: viewer,
border: TRUE
]];
thisY ← thisY + prev.wh + vMargin;
thisX ← vMargin;
Arena
prev ← robotTool.arena ← RobotArena.Create[[
parent: viewer,
wx: thisX,
wy: thisY,
ww: arenaWidth,
wh: arenaHeight,
border: TRUE,
scrollable: FALSE
]];
thisX ← hMargin;
thisY ← thisY + prev.wh + vMargin;
Set the height of the viewer as a whole
ViewerOps.SetOpenHeight[viewer, thisY + vMargin];
ViewerOps.OpenIcon[icon: viewer, bottom: FALSE];
result ← viewer;
};
RobotStop: Buttons.ButtonProc ~ {
robotTool: RobotTool ← NARROW[clientData];
robotTool.pleaseStop ← TRUE;  --He'll stop eventually
};
colorName: ARRAY RobotIndex OF ROPE ~ ["Black", "Gray", "Red", "Green", "Blue"];
deadRobot: INT ~ LAST[INT];   --Used to mark dead robots
RobotBattle: Buttons.ButtonProc ~ {
Alive: PROC [i: RobotIndex] RETURNS [BOOLEAN] ~ INLINE {
RETURN [state[i].damage # deadRobot AND robot[i] # NIL]
};
names: ARRAY RobotIndex OF ROPE;
robot: ARRAY RobotIndex OF RobotDefs.Robot;
state: ARRAY RobotIndex OF RobotDefs.RobotState;
robotTool: RobotTool ← NARROW[clientData];
tsLog: IO.STREAM ~ TSToLog[robotTool.ts];
active: CARDINAL ← 0;
valid, fight: BOOLEANTRUE;
robotTool.pleaseStop ← FALSE;  --So he can request a stop
TypeScript.ChangeLooks[robotTool.ts, 'b];
FOR i: RobotIndex IN RobotIndex DO
names[i] ← ViewerTools.GetContents[robotTool.robots[i]];
IF Rope.Length[names[i]]=0 THEN names[i] ← NIL ELSE {
active ← active+1;  --Keep track of the robots left in
IO.PutF[tsLog, IF active=1 THEN "%g (%g)" ELSE "vs. %g (%g) ", IO.rope[names[i]], IO.rope[colorName[i]]];
};
ENDLOOP;
IO.PutRope[tsLog, "\n"];
TypeScript.ChangeLooks[robotTool.ts, ' ];
FOR i: RobotIndex IN RobotIndex DO
IF names[i]=NIL THEN robot[i] ← NIL ELSE {
IF RobotIO.AssembleRobot[r: names[i], log: tsLog, wDir: robotTool.wDir] THEN {
robot[i] ← RobotIO.ReadRobot[names[i], robotTool.wDir];
}
ELSE fight ← FALSE;
};
ENDLOOP;
IF fight THEN {
FOR i: RobotIndex IN RobotIndex DO
Loc: PROC RETURNS [REAL] ~ INLINE {RETURN[Random.ChooseInt[rs, 1, 99]]};
state[i] ← NEW[RobotDefs.RobotStateRec];
IF Alive[i] THEN {
state[i].x ← Random.ChooseInt[rs, 1, 99];
state[i].y ← Random.ChooseInt[rs, 1, 99];
state[i].memory ← RobotHardware.InitialMemory[robot[i].code]; --Get ready to run
}
ELSE {
state[i].x ← state[i].y ← 200; --Out of sight
};
RobotArena.SetRobot[robotTool.arena, i, [
x: state[i].x,
y: state[i].y,
hit: FALSE,
fire: FALSE
]];
ENDLOOP;
ViewerOps.PaintViewer[robotTool.arena, client];
{
Let them all go at it!
FOR turn: CARDINAL IN [0 .. maxTurn] UNTIL active<2 OR robotTool.pleaseStop DO
fired: ARRAY RobotIndex OF BOOLEANALL[FALSE]; --Who fires this turn?
hit: ARRAY RobotIndex OF BOOLEANALL[FALSE]; --Who got hit?
Process.Pause[delay];
FOR i: RobotIndex IN RobotIndex DO
FindEnemy: PROC RETURNS [nme: RobotIndex] ~ {
offset: CARDINALFIRST[RobotIndex];
modulo: CARDINALLAST[RobotIndex]-FIRST[RobotIndex]+1;
nme ← i;
UNTIL Alive[nme ← ((nme+1-offset) MOD modulo) + offset] DO ENDLOOP;
};
map: MemoryMap;
nme: RobotIndex ← FindEnemy[]; --To have somebody to shoot at
Don't do anything if dead
IF ~Alive[i] THEN LOOP;
Set up the various "read-only" locations
state[i].memory[RH.mx] ← R.FixI[state[i].x];
state[i].memory[RH.my] ← R.FixI[state[i].y];
state[i].memory[RH.mdmg] ← R.FixI[state[i].damage];
state[i].memory[RH.hx] ← R.FixI[state[nme].x];
state[i].memory[RH.hy] ← R.FixI[state[nme].y];
state[i].memory[RH.hdmg] ← R.FixI[state[nme].damage];
Perform the instruction
map ← RobotHardware.ProcessRobot[state[i].memory];
Move the robot if appropriate...
IF state[i].memory[RH.speed] # 0 THEN {
mx: REAL ← state[i].x;
my: REAL ← state[i].y;
direction: REAL ← ToRad[state[i].memory[RH.direct]];
speed: INTEGERMIN[3, MAX[-3, state[i].memory[RH.speed]]];
mx ← Real.RoundLI[accuracy*(mx + speed*RF.Cos[direction])]/accuracy;
my ← Real.RoundLI[accuracy*(my + speed*RF.Sin[direction])]/accuracy;
If he goes out of bounds, penalize him...
IF mx # (MIN[100.0, MAX[0.0, mx]]) OR my # (MIN[100.0, MAX[0.0, my]]) THEN {
IO.PutF[stream: tsLog, format: "%g hits wall (%g)...", v1: IO.rope[names[i]], v2: IO.int[turn]];
state[i].damage ← state[i].damage+2;
hit[i] ← TRUE;
};
state[i].x ← MIN[100.0, MAX[0.0, mx]];
state[i].y ← MIN[100.0, MAX[0.0, my]];
};
Figure out and remember if he fires successfully...
state[i].temperature ← MAX[state[i].temperature-1, 0];
IF turn>waitTurn AND map[RH.fire] AND (state[i].temperature ← MAX[state[i].temperature-1, 0])<failTemperature THEN fired[i]←TRUE;
ENDLOOP;
FOR gunman: RobotIndex IN RobotIndex DO
IF Alive[gunman] AND fired[gunman] THEN {
mx: REAL ← state[gunman].x;
my: REAL ← state[gunman].y;
direction: REAL ← ToRad[state[gunman].memory[RH.fire]];
sinq: REALRF.Sin[direction];
cosq: REALRF.Cos[direction];
FOR target: RobotIndex IN RobotIndex DO
hx: REAL ← state[target].x;
hy: REAL ← state[target].y;
IF ~Alive[target] OR gunman=target THEN LOOP;
IF hx=mx AND hy=my THEN LOOP;
Here, will do a basic direction check. (This fairly well screens out the half of the screen defined by the firing angle and p/2 to either side.)
IF RF.Cos[direction - RF.ArcTan[hy-my, hx-mx]]<0 THEN LOOP;
Here, do the actual hit calculation. Assumes robot coords are given for its center, and that it has a certain radius.
IF ABS[(hx-mx)*sinq - (hy-my)*cosq] < robotRadius THEN { --Hit!!
IO.PutF[stream: tsLog, format: "%g hits %g (%g)...", v1: IO.rope[names[gunman]], v2: IO.rope[names[target]], v3: IO.int[turn]];
state[target].damage ← MIN[robotDeath, state[target].damage+1];
hit[target] ← TRUE;
};
ENDLOOP;
};
ENDLOOP;
Kill off the robots
FOR i: RobotIndex IN RobotIndex DO
IF Alive[i] AND state[i].damage>=robotDeath THEN {
TypeScript.ChangeLooks[robotTool.ts, 'b];
IO.PutF[stream: tsLog, format: "%g dies...", v1: IO.rope[names[i]]];
TypeScript.ChangeLooks[robotTool.ts, ' ];
active ← active - 1;
state[i].damage ← deadRobot;
};
ENDLOOP;
Here, display the robots on the screen...
FOR i: RobotIndex IN RobotIndex DO
IF robot[i]#NIL THEN RobotArena.SetRobot[robotTool.arena, i, [
x: state[i].x,
y: state[i].y,
hit: hit[i],
fire: fired[i],
fireAngle: state[i].memory[RH.fire]
]];
ENDLOOP;
ViewerOps.PaintViewer[robotTool.arena, client, FALSE, $MoveRobots];
ENDLOOP;
IO.PutRope[tsLog, "\nRound over.\n"];
};
};
};
ToRad: PROC [bytians: INT] RETURNS [radians: REAL] ~ {
p: REAL ← 3.14159265;
RETURN [p/128 * bytians]
};
rs: Random.RandomStream ~ Random.Create[];
TSToLog: PROC [ts: TypeScript.TS] RETURNS [s: IO.STREAM] ~ {
RETURN [IO.CreateStream[streamProcs: TSLogProcs, streamData: ts]];
};
PutTSChar: PROC [self: IO.STREAM, char: CHAR] ~ {
ts: TypeScript.TS ~ NARROW[self.streamData];
TypeScript.PutChar[ts, char];
};
TSLogProcs: REF IO.StreamProcs ~ IO.CreateStreamProcs[
variety: output,
class: $TSLog,
putChar: PutTSChar
];
Init: PROC ~ {
Commander.Register[key: "RobotTool", proc: MakeRobotTool, doc: "Make an instance of a Robot Tool" ];
FOR index: RobotIndex IN RobotIndex DO
RobotArena.SetRobotColor[index, colorName[index]];
ENDLOOP;
};
Init[];
}.