TankViewer.mesa
last modified by: John Maxwell on: February 7, 1983 10:06 am
DIRECTORY
Convert USING [ValueToRope],
Graphics
USING [
black, Box, Context, DrawBox, DrawChar, DrawRope, FontRef,
GetBounds, MakeFont, SetColor, SetCP, SetPaintMode, Translate, white],
GraphicsOps USING [BitmapRef, DrawBitmap, NewBitmap, NewContextFromBitmap],
GVNames USING [GetConnect],
Icons USING [DrawIcon, IconFlavor, NewIconFromFile],
InputFocus USING [SetInputFocus],
IO
USING [
Close, CreateOutputStreamToRope, GetOutputStreamRope,
Handle, int, Put, PutF, rope, STREAM, Value],
Menus USING [AppendMenuEntry, ClickProc, CreateEntry, CreateMenu, Menu],
MessageWindow USING [Append, Blink],
Process USING [Detach, SetTimeout],
PupDefs USING [GetLocalPupAddress, PupAddress],
Real USING [RoundI],
RealFns USING [SinDeg, CosDeg],
Rope USING [Cat, ROPE],
RPC USING [CallFailed, ImportFailed],
TankInternal USING [StartServer, StopServer],
TankMaster
USING [
AddPlayer, GetPlayer, Machine, maxPlayers, RemovePlayer,
Tank, Torp, ScoreHit, Update, WorldState, WorldStateRec],
TankMasterRpcControl USING [ImportInterface, UnimportInterface],
TIPUser USING [InstantiateNewTIPTable, TIPTable],
UserExec USING [AcquireStreams, CommandProc, ExecHandle, GetExecHandle, GetNameAndPassword, RegisterCommand, ReleaseStreams],
ViewerClasses
USING [
DestroyProc, ModifyProc, NotifyProc, PaintProc,
ScrollProc, Viewer, ViewerClass, ViewerClassRec],
ViewerOps
USING [
BlinkIcon, CreateViewer, InvisiblePaint, PaintViewer, RegisterViewerClass, SetMenu],
VFonts USING [EstablishFont, GraphicsFont];
TankViewer:
CEDAR MONITOR
IMPORTS
Convert, Graphics, GraphicsOps, GVNames, InputFocus, Icons, IO, Menus, MessageWindow, Process, PupDefs, Real, RealFns, Rope, RPC, TankInternal, TankMasterRpcControl, TankMaster, TIPUser, UserExec, ViewerOps, VFonts
SHARES ViewerOps =
BEGIN
OPEN IO, Rope, TankMaster;
tank1, tank2: ViewerClasses.Viewer ← NIL;
menu: Menus.Menu;
tankFont: Graphics.FontRef;
timesRoman: Graphics.FontRef;
tankIcon, treadIcon: Icons.IconFlavor ← document;
Initialize:
PROCEDURE =
BEGIN
tankFont ← Graphics.MakeFont["tankFont.strike"];
timesRoman ← VFonts.GraphicsFont[VFonts.EstablishFont["TimesRoman", 10]];
icons
tankIcon ← Icons.NewIconFromFile["Tank.icons", 0];
FOR i:
CARDINAL
IN [1..turrets]
DO
[] ← Icons.NewIconFromFile["Tank.icons", i];
ENDLOOP;
treadIcon ← Icons.NewIconFromFile["Tank.icons", turrets+1];
FOR i:
CARDINAL
IN [turrets+2..turrets+3]
DO
[] ← Icons.NewIconFromFile["Tank.icons", i];
ENDLOOP;
menu ← Menus.CreateMenu[];
Menus.AppendMenuEntry[menu, Menus.CreateEntry["Help", Help]];
Menus.AppendMenuEntry[menu, Menus.CreateEntry["Players", Players]];
InitWalls[];
MakeTankViewerClass[];
UserExec.RegisterCommand["Tank", Create, "Tank arcade game."];
END;
******************************************************************
Viewers Interface
******************************************************************
MakeTankViewerClass:
PROCEDURE =
BEGIN
tipTable: TIPUser.TIPTable ← TIPUser.InstantiateNewTIPTable["Tank.tip"];
viewerClass: ViewerClasses.ViewerClass ←
NEW
[ViewerClasses.ViewerClassRec ←
[paint: PaintMe,
-- called whenever the Viewer should repaint
notify: TipMe, -- TIP input events
modify: Noop, -- InputFocus changes reported through here
destroy: DestroyMe, -- called before Viewer structures freed on destroy op
copy: NIL, -- copy data to new Viewer
set: NIL, -- set the viewer contents
get: NIL, -- get the viewer contents
init: NIL, -- called on creation or reset to init data
save: NIL, -- requests client to write contents to disk
scroll: ScrollMe, -- document scrolling
icon: document, -- picture to display when small
tipTable: tipTable, -- could be moved into Viewer instance if needed
cursor: crossHairsCircle -- standard cursor when mouse is in viewer
]];
ViewerOps.RegisterViewerClass[$TankViewer, viewerClass];
END;
Noop: ViewerClasses.ModifyProc = {};
TipMe: ViewerClasses.NotifyProc =
BEGIN
tankData: TankData ← NARROW[self.data, TankData];
[self: Viewer, input: LIST OF REF ANY]
N.B. Called at Process.priorityForeground!
InputFocus.SetInputFocus[self];
{
OPEN tankData;
FOR input ← input, input.rest
DO
IF input = NIL THEN EXIT;
SELECT input.first
FROM
motion
$Forward => me.move ← forward;
$HalfSpeed => me.move ← halfspeed; -- halfspeed
$Stop => me.move ← stop;
$Reverse => me.move ← reverse;
turning
$Left => me.turn ← left;
$SlowLeft => me.turn ← slowLeft;
$NotLeft => IF me.turn = left OR me.turn = slowLeft THEN me.turn ← straight;
$Right => me.turn ← right;
$SlowRight => me.turn ← slowRight;
$NotRight => IF me.turn = right OR me.turn = slowRight THEN me.turn ← straight;
firing
$Shoot =>
IF myTorp.state = 0
THEN {
myTorp.x ← me.x; myTorp.y ← me.y;
[myTorp.dx, myTorp.dy] ← Delta[me.angle, torpSpeed];
myTorp.state ← 50};
ENDCASE;
ENDLOOP};
END;
DestroyMe: ViewerClasses.DestroyProc =
TRUSTED {
Process.Detach[FORK Remove[NARROW[self.data, TankData]]]};
Remove:
PROC[tankData: TankData] =
BEGIN
tankData.quit ← TRUE;
WHILE tankData.quit DO Wait[tankData]; ENDLOOP;
TRUSTED {TankMaster.RemovePlayer[tankData.me.id ! RPC.CallFailed => CONTINUE]};
TankInternal.StopServer[];
END;
ScrollMe: ViewerClasses.ScrollProc =
BEGIN -- self: Viewer, amount: INTEGER, op: {up, down, thumb}
tankData: TankData ← NARROW[self.data, TankData];
{
OPEN tankData;
SELECT op
FROM
up => offset ← MIN[-vy1 + 8, offset + amount];
down => offset ← MAX[self.ch - vy2 + 8, offset - amount];
thumb => offset ← (amount*vy2)/self.wh;
ENDCASE => RETURN;
ViewerOps.PaintViewer[self, client];
};
END;
PaintMe: ViewerClasses.PaintProc =
BEGIN -- self: Viewer, context: Graphics.Context, whatChanged: REF ANY,
tankData: TankData ← NARROW[self.data, TankData];
{
OPEN tankData;
score: ROPE;
box: Graphics.Box;
IF self.iconic THEN {PaintIcon[tankData, context]; RETURN};
Graphics.Translate[context, 0, offset];
IF clear
THEN {
[] ← Graphics.SetPaintMode[context, opaque];
[] ← Graphics.SetColor[context, Graphics.white];
Graphics.DrawBox[context, Graphics.GetBounds[context]];
[] ← Graphics.SetColor[context, Graphics.black];
-- IF HitWall[Real.RoundI[me.x], Real.RoundI[me.y], 13, tankData]
-- THEN RandomPosition[tankData];
draw walls
FOR i:
CARDINAL
IN [0..wallLength)
DO
IF LastWall[i] THEN EXIT;
box.xmin ← wall[i].x1;
box.xmax ← wall[i].x2;
box.ymin ← wall[i].y1 - 8;
box.ymax ← wall[i].y2 - 8;
Graphics.DrawBox[context, box];
ENDLOOP;
draw mines
FOR i:
CARDINAL
IN [0..mineLength)
DO
IF mine[i] = [0,0] THEN EXIT;
Graphics.SetCP[context, mine[i].x, mine[i].y];
Graphics.DrawChar[context, 132C, tankFont];
ENDLOOP;
draw tanks
FOR i:
CARDINAL
IN [0..maxPlayers)
DO
IF enemy.tank[i].angle > 360 THEN LOOP;
DrawTank[context, enemy.tank[i], i = me.id];
ENDLOOP;
draw torps
FOR i:
CARDINAL
IN [0..maxPlayers)
DO
IF enemy.torp[i].state = 0 THEN LOOP;
DrawTorp[context, enemy.torp[i]];
ENDLOOP};
incremental update
IF
NOT clear
THEN {
FOR i:
CARDINAL
IN [0..maxPlayers)
DO
IF enemy.tank[i].angle = last.tank[i].angle
AND
enemy.tank[i].x = last.tank[i].x AND
enemy.tank[i].y = last.tank[i].y AND
enemy.tank[i].dead = last.tank[i].dead THEN LOOP;
[] ← Graphics.SetColor[context, Graphics.white];
DrawTank[context, last.tank[i], i = me.id];
IF enemy.tank[i].angle > 360 THEN LOOP;
[] ← Graphics.SetColor[context, Graphics.black];
DrawTank[context, enemy.tank[i], i = me.id];
ENDLOOP;
FOR i:
CARDINAL
IN [0..maxPlayers)
DO
IF enemy.torp[i].state = last.torp[i].state
AND
enemy.torp[i].x = last.torp[i].x AND
enemy.torp[i].y = last.torp[i].y THEN LOOP;
[] ← Graphics.SetColor[context, Graphics.white];
DrawTorp[context, last.torp[i]];
[] ← Graphics.SetColor[context, Graphics.black];
DrawTorp[context, enemy.torp[i]];
ENDLOOP};
IF repaint
THEN {
-- erase old score
box.xmin ← wall[14].x1;
box.xmax ← wall[14].x2;
box.ymin ← wall[14].y1 - 8;
box.ymax ← wall[14].y2 - 8;
Graphics.DrawBox[context, box]};
IF clear
OR repaint
THEN {
-- draw new
Graphics.SetColor[context, Graphics.white];
score ← PutFToRope["Self: %g, Enemy: %g",
IO.int[scored], IO.int[destroyed]];
Graphics.SetCP[context, wall[14].x1+5, wall[14].y1-4];
Graphics.DrawRope[context, score, , , timesRoman];
repaint ← FALSE}};
END;
turrets: CARDINAL = 12;
treadPause: CARDINAL ← 6;
turretPause: CARDINAL ← 3;
turretStop: CARDINAL ← 100;
PaintIcon:
PROCEDURE[data: TankData, context: Graphics.Context] =
BEGIN
oldTread: CARDINAL ← data.tread;
oldTurret: CARDINAL ← data.turret;
determine icons
IF data.treadPause # 0 THEN data.treadPause ← data.treadPause - 1;
IF data.treadPause = 0
THEN {
data.tread ← (data.tread + 1) MOD 3;
data.treadPause ← treadPause};
IF data.turretPause # 0 THEN data.turretPause ← data.turretPause - 1;
IF data.turretPause = 0
THEN
IF data.turretRight
THEN {
data.turretPause ← turretPause;
data.turret ← data.turret + 1;
IF data.turret = turrets THEN {data.turretPause ← turretStop; data.turretRight ← FALSE}}
ELSE {
data.turretPause ← turretPause;
data.turret ← data.turret - 1;
IF data.turret = 0 THEN {data.turretPause ← turretStop; data.turretRight ← TRUE}};
paint icons
IF data.tread = oldTread AND data.turret = oldTurret THEN RETURN;
[] ← Graphics.SetPaintMode[data.ctx, opaque];
Icons.DrawIcon[LOOPHOLE[LOOPHOLE[tankIcon, CARDINAL] + data.turret], data.ctx];
[] ← Graphics.SetPaintMode[data.ctx, transparent];
Icons.DrawIcon[LOOPHOLE[LOOPHOLE[treadIcon, CARDINAL] + data.tread], data.ctx];
Graphics.SetCP[context, 0, 64];
GraphicsOps.DrawBitmap[context, data.icon, 64, 64];
END;
PutFToRope:
PROC [format:
ROPE ←
NIL, v1, v2, v3, v4, v5: Value ← [null[]]]
RETURNS [r:
ROPE] =
INLINE
{h: Handle ← CreateOutputStreamToRope[];
h.PutF[format, v1, v2, v3, v4, v5];
r ← h.GetOutputStreamRope[]; h.Close[]};
DrawTank:
PROCEDURE[context: Graphics.Context, tank: Tank, me:
BOOLEAN] =
BEGIN
char: CHARACTER;
IF context = NIL THEN RETURN;
char ← LOOPHOLE[tank.angle/4];
IF me THEN char ← char + 96;
Graphics.SetCP[context, tank.x, tank.y];
Graphics.DrawChar[context, char, tankFont];
END;
DrawTorp:
PROCEDURE[context: Graphics.Context, torp: Torp] =
BEGIN
char: CHARACTER;
IF context = NIL THEN RETURN;
char ←
SELECT torp.state
FROM
4 => 134C, 3 => 135C, 2 => 136C,
1 => 137C, 0 => 000C, ENDCASE => 133C;
IF char = 000C THEN RETURN;
Graphics.SetCP[context, torp.x, torp.y];
Graphics.DrawChar[context, char, tankFont];
END;
******************************************************************
general procedures
******************************************************************
-- my state --
TankData: TYPE = REF TankDataRec;
TankDataRec:
TYPE =
RECORD[
user: CONDITION,
quit: BOOL ← FALSE,
offset: INTEGER ← 0,
destroyed: INTEGER ← 0,
scored: INTEGER ← 0,
icon: GraphicsOps.BitmapRef,
ctx: Graphics.Context,
turret: CARDINAL ← 0,
turretPause: CARDINAL ← 3,
tread: CARDINAL ← 0,
treadPause: CARDINAL ← 3,
repaint: BOOLEAN ← FALSE,
turretRight: BOOLEAN ← TRUE,
me: MyTank ← [],
myTorp: MyTorp ← [],
last: WorldState ← NIL,
enemy: WorldState ← NIL];
MyTank:
TYPE =
RECORD[
id: CARDINAL ← 0,
state: TankState ← alive,
time: CARDINAL ← 0, -- time remaining in current state
angle: INTEGER ← 0, -- current facing
x, y: REAL ← 0, -- current position
turn: Turn ← straight, -- turning direction
move: Move ← stop]; -- moving direction
MyTorp:
TYPE =
RECORD[
state: CARDINAL ← 0, -- [50..5] = fired, [4..1] = exploding, 0 = loaded
x, y: REAL ← 0, -- current position
dx, dy: REAL ← 0]; -- delta to new position
TankState: TYPE = {alive, dead};
Turn: TYPE = {left, slowLeft, straight, slowRight, right};
Move: TYPE = {forward, halfspeed, stop, reverse};
-- procedures --
Create: UserExec.CommandProc =
BEGIN
id: CARDINAL;
tankData: TankData;
success: BOOLEAN ← FALSE;
viewer: ViewerClasses.Viewer;
myAddr: PupDefs.PupAddress;
IF ~bound THEN Bind[];
TRUSTED {myAddr ← PupDefs.GetLocalPupAddress[[0,0], NIL]};
TRUSTED {[success, id] ← TankMaster.AddPlayer[
UserExec.GetNameAndPassword[].name,
[myAddr.net, myAddr.host]
! RPC.CallFailed => {Bind[]; RETRY}]};
IF ~success THEN RETURN[FALSE, "Sorry, game is already full."];
tankData ← NEW[TankDataRec ← []];
tankData.last ← NEW[WorldStateRec ← []];
tankData.enemy ← NEW[WorldStateRec ← []];
tankData.icon ← GraphicsOps.NewBitmap[64,64];
tankData.ctx ← GraphicsOps.NewContextFromBitmap[tankData.icon];
tankData.me.id ← id;
viewer ← ViewerOps.CreateViewer[
flavor: $TankViewer,
info: [
name: "Tank",
icon: private,
iconic: FALSE,
data: tankData,
scrollable: TRUE]];
ViewerOps.SetMenu[viewer, menu];
RandomPosition[tankData];
TRUSTED {Process.SetTimeout[@tankData.user, 1]};
TRUSTED {Process.Detach[FORK Main[viewer, tankData]]};
END;
bound, rebind: BOOL ← FALSE;
Bind:
PROC[wait:
INTEGER ← 1] =
BEGIN
InternalBind:
ENTRY
PROC =
BEGIN
IF bound
AND rebind
THEN {
-- only want to rebind once per error.
TankMasterRpcControl.UnimportInterface[];
MessageWindow.Append["Please wait-- binding to new Tank server.", TRUE];
MessageWindow.Blink[];
rebind ← bound ← FALSE};
IF bound THEN RETURN;
WHILE
TRUE
DO
bound ← TRUE;
IF wait <= 0 THEN TankInternal.StartServer[];
TankMasterRpcControl.ImportInterface [
interfaceName: [instance: "TankMaster.auto"]
! RPC.ImportFailed => {bound ← FALSE; CONTINUE}];
IF bound THEN RETURN;
IF wait > 0 THEN wait ← wait - 1 ELSE ERROR;
ENDLOOP;
END;
IF bound THEN rebind ← TRUE;
InternalBind[];
END;
Rank:
PROC[world: WorldState ←
NIL, me:
NAT]
RETURNS[rank:
INTEGER ← 0] =
BEGIN
myAddress: Machine;
IF world = NIL THEN RETURN[1];
myAddress ← world.address[me];
FOR i:
NAT
IN [0..world.players)
DO
IF ~world.tank[i].playing THEN LOOP;
IF world.address[i].net > myAddress.net THEN rank ← rank+1;
IF world.address[i].net = myAddress.net
THEN {
IF world.address[i].host > myAddress.host THEN rank ← rank+1;
IF world.address[i].host = myAddress.host AND i >= me THEN rank ← rank+1};
ENDLOOP;
END;
Help: Menus.ClickProc =
BEGIN
stream: IO.STREAM;
exec: UserExec.ExecHandle;
exec ← UserExec.GetExecHandle["A"];
stream ← UserExec.AcquireStreams[exec].out;
stream.Put[rope["\nTank commands: \n"]];
stream.Put[rope[" MouseLeft, S => Left.\n"]];
stream.Put[rope[" MouseMiddle, D => Forward.\n"]];
stream.Put[rope[" MouseRight, F => Right.\n"]];
stream.Put[rope[" Space => Fire.\n"]];
stream.Put[rope[" A => Reverse.\n"]];
stream.Put[rope[" SHIFT => Turn at quarter speed.\n"]];
UserExec.ReleaseStreams[exec];
END;
Players: Menus.ClickProc =
BEGIN
score: INTEGER;
address: Machine;
stream: IO.STREAM;
addressRope: ROPE;
name, server: ROPE;
exec: UserExec.ExecHandle;
Octal:
PROC[int:
INT]
RETURNS[rope: Rope.
ROPE] =
INLINE {
RETURN[Convert.ValueToRope[[signed[int, 8]]]]};
exec ← UserExec.GetExecHandle["A"];
stream ← UserExec.AcquireStreams[exec].out;
server ← GVNames.GetConnect["TankMaster.auto"].connect;
stream.Put[rope["\nCurrent Tank players: \n"]];
FOR i:
CARDINAL
IN [0..maxPlayers)
DO
TRUSTED {[name, address, score] ← TankMaster.GetPlayer[i
! RPC.CallFailed => {Bind[]; RETRY}]};
IF name = NIL THEN LOOP;
stream.Put[rope["\t"], rope[name], rope[" ("]];
addressRope ← Rope.Cat[Octal[address.net], "#", Octal[address.host], "#): "];
stream.Put[rope[addressRope], int[score], rope[".\n"]];
ENDLOOP;
stream.Put[rope["Server: "], rope[server], rope[".\n"]];
UserExec.ReleaseStreams[exec];
END;
******************************************************************
Tank control
******************************************************************
wait:
BOOLEAN ←
TRUE;
-- setting wait to FALSE lets the Spy measure things
Wait:
ENTRY
PROCEDURE[data: TankData] = {
IF wait
THEN
WAIT data.user};
Main:
PROCEDURE[viewer: ViewerClasses.Viewer, data: TankData] =
BEGIN
OPEN data;
ENABLE ViewerOps.InvisiblePaint => CONTINUE;
tank: Tank;
torp: Torp;
paintAll: BOOLEAN;
DO Wait[data];
IF data.quit THEN {data.quit ← FALSE; EXIT};
move me and myTorp
paintAll ← FALSE;
SELECT me.state
FROM
alive => MoveTank[data];
dead => {
me.time ← me.time - 1;
IF me.time = 0
THEN {
me.state ← alive;
RandomPosition[data];
paintAll ← TRUE}};
ENDCASE;
IF myTorp.state # 0 THEN MoveTorp[data];
update world state
tank ← [FALSE, TRUE, me.angle, Real.RoundI[me.x], Real.RoundI[me.y]];
torp ← [myTorp.state, Real.RoundI[myTorp.x], Real.RoundI[myTorp.y]];
last^ ← enemy^;
TRUSTED {enemy^ ← TankMaster.Update[me.id, tank, torp
! RPC.CallFailed => {Bind[Rank[last, me.id]-1]; RETRY}]};
IF enemy.tank[me.id].dead
THEN {
-- I must have been hit
destroyed ← destroyed + 1;
me.state ← dead; me.time ← 5};
IF viewer.iconic AND paintAll THEN ViewerOps.BlinkIcon[viewer];
ViewerOps.PaintViewer[
viewer: viewer,
hint: IF viewer.iconic THEN all ELSE client,
clearClient: paintAll];
ENDLOOP;
END;
-- general procedures --
tankSpeed: INTEGER = 6;
tankWidth: INTEGER = 6;
torpSpeed: INTEGER = 20;
torpWidth: INTEGER = 2;
mineWidth: INTEGER = 6;
MoveTank:
PROC[td: TankData] =
BEGIN
t, min: REAL ← 1;
dx, dy: REAL;
x, y, newX, newY: INTEGER;
hit: {tank, mine, wall};
determine the new position
SELECT td.me.turn
FROM
left => td.me.angle ← Mod[(td.me.angle + 4), 360];
slowLeft => td.me.angle ← Mod[(td.me.angle + 1), 360];
slowRight => td.me.angle ← Mod[(td.me.angle - 1), 360];
right => td.me.angle ← Mod[(td.me.angle - 4), 360];
ENDCASE;
IF td.me.move = stop THEN RETURN;
SELECT td.me.move
FROM
forward => [dx, dy] ← Delta[td.me.angle, tankSpeed];
halfspeed => [dx, dy] ← Delta[td.me.angle, (tankSpeed+1)/2];
reverse => [dx, dy] ← Delta[td.me.angle, -(tankSpeed+1)/2];
stop => RETURN;
ENDCASE;
x ← Real.RoundI[td.me.x + 8];
y ← Real.RoundI[td.me.y + 8];
newX ← Real.RoundI[td.me.x + 8 + dx];
newY ← Real.RoundI[td.me.y + 8 + dy];
did I hit a mine?
FOR i:
CARDINAL
IN [0..mineLength)
DO
IF mine[i] = [0,0] THEN EXIT;
t ← Intersect[x, y, newX, newY, tankWidth,
mine[i].x+8-mineWidth, mine[i].y+8-mineWidth,
mine[i].x+8+mineWidth, mine[i].y+8+mineWidth];
IF t < min THEN {min ← t; hit ← mine};
ENDLOOP;
did I hit a tank?
FOR i:
CARDINAL
IN [0..maxPlayers)
DO
IF i = td.me.id THEN LOOP; -- never hit yourself!
IF td.enemy.tank[i].angle > 360 THEN LOOP;
t ← Intersect[x, y, newX, newY, tankWidth,
td.enemy.tank[i].x+8-tankWidth, td.enemy.tank[i].y+8-tankWidth,
td.enemy.tank[i].x+8+tankWidth, td.enemy.tank[i].y+8+tankWidth];
IF t < min THEN {min ← t; hit ← tank};
ENDLOOP;
did I hit a wall?
FOR i:
CARDINAL
IN [0..wallLength)
DO
IF LastWall[i] THEN EXIT;
t ← Intersect[x, y, newX, newY, tankWidth,
wall[i].x1, wall[i].y1, wall[i].x2, wall[i].y2];
IF t < min THEN {min ← t; hit ← wall};
ENDLOOP;
update the tank
IF min < 1
THEN {
dx ← min*dx; dy ← min*dy;
IF hit = mine
THEN
TRUSTED {TankMaster.ScoreHit[maxPlayers, td.me.id
! RPC.CallFailed => {Bind[]; RETRY}]};
td.destroyed ← td.destroyed + 1; td.repaint ← TRUE;
td.me.state ← dead; td.me.time ← 4}
};
td.me.x ← td.me.x + dx;
td.me.y ← td.me.y + dy;
END;
MoveTorp:
PROCEDURE[td: TankData] =
BEGIN
tankID: INTEGER;
t, min: REAL ← 1;
hit: {tank, wall} ← wall;
dx, dy: REAL;
x, y, newX, newY: INTEGER;
td.myTorp.state ← td.myTorp.state - 1;
IF td.myTorp.state IN [0..4] THEN RETURN;
dx ← td.myTorp.dx;
dy ← td.myTorp.dy;
x ← Real.RoundI[td.myTorp.x + 8];
y ← Real.RoundI[td.myTorp.y + 8]; -- hot point is the lower right hand corner
newX ← Real.RoundI[td.myTorp.x + 8 + td.myTorp.dx];
newY ← Real.RoundI[td.myTorp.y + 8 + td.myTorp.dy];
did I hit a tank?
FOR i:
CARDINAL
IN [0..maxPlayers)
DO
IF i = td.me.id THEN LOOP; -- never hit yourself!
IF td.enemy.tank[i].angle > 360 THEN LOOP;
t ← Intersect[x, y, newX, newY, torpWidth,
td.enemy.tank[i].x+8-tankWidth, td.enemy.tank[i].y+8-tankWidth,
td.enemy.tank[i].x+8+tankWidth, td.enemy.tank[i].y+8+tankWidth];
IF t < min THEN {min ← t; hit ← tank; tankID ← i};
ENDLOOP;
did I hit a wall?
FOR i:
CARDINAL
IN [0..wallLength)
DO
IF LastWall[i] THEN EXIT;
t ← Intersect[x, y, newX, newY, torpWidth,
wall[i].x1, wall[i].y1, wall[i].x2, wall[i].y2];
IF t < min THEN {min ← t; hit ← wall};
ENDLOOP;
update the torp
IF min < 1
THEN {
dx ←min*dx; dy ← min*dy;
IF hit = tank
THEN {
TRUSTED {TankMaster.ScoreHit[td.me.id, tankID
! RPC.CallFailed => {Bind[]; RETRY}]};
td.scored ← td.scored + 1;
td.repaint ← TRUE};
td.myTorp.state ← 4};
td.myTorp.x ← td.myTorp.x + dx;
td.myTorp.y ← td.myTorp.y + dy;
END;
Intersect:
PROC[x1, y1, x2, y2, w:
INTEGER, tx1, ty1, tx2, ty2:
INTEGER]
RETURNS[nt: REAL] =
BEGIN
t, x, y: REAL;
bx1, by1, bx2, by2: REAL;
nt ← t ← 1;
do a gross check first
IF MIN[x1, x2]-w > tx2 THEN RETURN;
IF MIN[y1, y2]-w > ty2 THEN RETURN;
IF MAX[x1, x2]+w < tx1 THEN RETURN;
IF MAX[y1, y2]+w < ty1 THEN RETURN;
create a buffer around the box
bx1 ← tx1-w; bx2 ← tx2+w;
by1 ← ty1-w; by2 ← ty2+w;
intersect the horizontal lines
SELECT
TRUE
FROM
x1 = x2 => NULL;
x1 < x2
AND x1 <= bx1
AND bx1 <= x2 => {
t ← (bx1-x1)/(x2-x1); y ← y1 + t*(y2-y1);
IF by1 <= y AND y < by2 THEN nt ← MIN[nt, t]};
x1 > x2
AND x2 <= bx2
AND bx2 <= x1 => {
t ← (bx2-x1)/(x2-x1); y ← y1 + t*(y2-y1);
IF by1 <= y AND y < by2 THEN nt ← MIN[nt, t]};
ENDCASE;
intersect the vertical lines
SELECT
TRUE
FROM
y1 = y2 => NULL;
y1 < y2
AND y1 <= by1
AND by1 <= y2 => {
t ← (by1-y1)/(y2-y1); x ← x1 + t*(x2-x1);
IF bx1 <= x AND x < bx2 THEN nt ← MIN[nt, t]};
y1 > y2
AND y2 <= by2
AND by2 <= y1 => {
t ← (by2-y1)/(y2-y1); x ← x1 + t*(x2-x1);
IF bx1 <= x AND x < bx2 THEN nt ← MIN[nt, t]};
ENDCASE;
x ← 0; -- a place to set a break point
END;
Mod:
PROCEDURE[a, b:
INTEGER]
RETURNS[c:
INTEGER] =
INLINE {c ← a MOD b; IF c < 0 THEN c ← c + b};
RandomPosition:
PROCEDURE[data: TankData] =
BEGIN
OPEN data;
x, y: INTEGER;
DO [x, y] ← RandomPoint[];
IF x <= vx1+w OR x >= vx2-w-10 THEN LOOP;
IF y <= vy1+w OR y >= vy2-w-10 THEN LOOP;
IF HitWall[x, y, 13, data] THEN LOOP;
IF HitTank[x, y, 13, data] THEN LOOP;
IF HitMine[x, y, 13] THEN LOOP;
me.x ← x; me.y ← y;
EXIT; ENDLOOP;
END;
HitWall:
PROCEDURE[x, y:
INTEGER, width:
INTEGER, tankData: TankData]
RETURNS[BOOLEAN] =
BEGIN
x ← x + (15 - width)/2;
y ← y + (15 - width)/2;
IF x <= 0 OR x + width >= viewer.cw - 5 OR y <= tankData.offset
OR y + width >= viewer.cw - 30 + tankData.offset THEN RETURN[TRUE];
FOR i:
CARDINAL
IN [0..wallLength)
DO
IF LastWall[i] THEN EXIT;
IF x > wall[i].x2 THEN LOOP;
IF y > wall[i].y2 THEN LOOP;
IF x + width < wall[i].x1 THEN LOOP;
IF y + width < wall[i].y1 THEN LOOP;
RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
END;
HitMine:
PROCEDURE[x, y:
INTEGER, width:
INTEGER]
RETURNS[BOOLEAN] =
BEGIN
x ← x + (15 - width)/2;
y ← y + (15 - width)/2;
FOR i:
CARDINAL
IN [0..mineLength)
DO
IF mine[i] = [0,0] THEN EXIT;
IF x > mine[i].x+8+mineWidth THEN LOOP;
IF y > mine[i].y+8+mineWidth THEN LOOP;
IF x + width < mine[i].x+8-mineWidth THEN LOOP;
IF y + width < mine[i].y+8-mineWidth THEN LOOP;
RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
END;
HitTank:
PROCEDURE[x, y:
INTEGER, width:
INTEGER, data: TankData]
RETURNS[BOOLEAN] =
BEGIN OPEN data;
x ← x + (15 - width)/2;
y ← y + (15 - width)/2;
FOR i:
CARDINAL
IN [0..maxPlayers)
DO
IF i = me.id THEN LOOP; -- never hit yourself!
IF enemy.tank[i].angle > 360 THEN LOOP;
IF x > enemy.tank[i].x+8+tankWidth THEN LOOP;
IF y > enemy.tank[i].y+8+tankWidth THEN LOOP;
IF x + width < enemy.tank[i].x+8-tankWidth THEN LOOP;
IF y + width < enemy.tank[i].y+8-tankWidth THEN LOOP;
RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
END;
RandomPoint:
PUBLIC
PROCEDURE
RETURNS[x, y:
INTEGER] =
INLINE BEGIN
c: INTEGER ← 7;
randomX ← Mod[randomX*c, 606];
randomY ← Mod[randomY*c, 808];
RETURN[randomX, randomY];
END;
Delta:
PROCEDURE[angle, d:
INTEGER]
RETURNS[x, y:
REAL] =
--0 deg is =>, 90 is ^
INLINE BEGIN
x ← RealFns.CosDeg[angle]*d;
y ← RealFns.SinDeg[angle]*d;
END;
InitWalls:
PROCEDURE =
BEGIN
wall ← [[117, 292, 133, 404], [133, 86, 148, 140], [176, 371, 193, 518],
[178, 167, 192, 280], [314, 678, 333, 784], [453, 379, 470, 536],
[476, 169, 496, 237], [286, 87, 408, 105], [ 89, 124, 133, 140],
[192, 167, 292, 181], [391, 169, 476, 183], [483, 297, 590, 318],
[193, 500, 268, 518], [382, 520, 453, 536], [256, 583, 396, 600],
[106, 642, 221, 662], [430, 649, 556, 665], [vx1, vy1, vx1+w, vy2],
[vx1, vy1, vx2, vy1+w], [vx2-w, vy1, vx2, vy2], [vx1, vy2-w, vx2, vy2]];
END;
LastWall:
PROCEDURE[i:
CARDINAL]
RETURNS[
BOOLEAN] =
INLINE {RETURN[wall[i].x1=0 AND wall[i].y1=0 AND wall[i].x2=0 AND wall[i].y2=0]};
randomX, randomY: INTEGER ← 7;
vx1: INTEGER ← 0;
vx2: INTEGER ← 590;
vy1: INTEGER ← 35;
vy2: INTEGER ← 732;
w: INTEGER ← 10;
wallLength: CARDINAL = 21;
wall: ARRAY [0..wallLength) OF RECORD[x1, y1, x2, y2: INTEGER];
mineLength: CARDINAL = 15;
mine:
ARRAY [0..mineLength)
OF
RECORD[x, y:
INTEGER] ← [
[249, 237], [212, 385], [229, 324], [266, 407], [286, 385],
[290, 237], [307, 452], [311, 200], [318, 320], [350, 247],
[356, 398], [390, 427], [390, 306], [408, 351], [421, 250]];
Initialize[];
END...
pause1: CARDINAL ← 3;
pause2: CARDINAL ← 100;
TurnTurret:
PROCEDURE[v: ViewerClasses.Viewer, data: TankData] =
BEGIN
IF data.ticks # 0 THEN data.ticks ← data.ticks - 1;
IF data.ticks # 0 THEN RETURN;
IF data.turretRight
THEN {
data.ticks ← pause1;
v.icon ← LOOPHOLE[LOOPHOLE[v.icon, CARDINAL] + 1];
IF v.icon =
LOOPHOLE[
LOOPHOLE[tankIcon,
CARDINAL] + turrets]
THEN {data.ticks ← pause2; data.turretRight ← FALSE}}
ELSE {
data.ticks ← pause1;
v.icon ← LOOPHOLE[LOOPHOLE[v.icon, CARDINAL] - 1];
IF v.icon = tankIcon THEN {data.ticks ← pause2; data.turretRight ← TRUE}};
ViewerOps.PaintViewer[v, all];
END;