[Cedar]<CedarChest®>Top>MazeWar.df=>MazeWarPlayerImpl1.Mesa
Mike Spreitzer July 19, 1986 1:44:10 pm PDT
Hal Murray, March 16, 1986 7:23:12 pm PST
DIRECTORY BasicTime, Commander, CommandTool, GenSym, Icons, InputFocus, IO, Labels, MazeWarFinder, MazeWarPlayer, MazeWarPlayerInsides, MazeWarPlayerRpcControl, MessageWindow, Process, Random, Rope, RPC, ThisMachine, TIPUser, UserCredentials, UserProfile, ViewerClasses, ViewerLocks, ViewerOps;
MazeWarPlayerImpl1: CEDAR MONITOR
IMPORTS BasicTime, CommandTool, GenSym, Icons, InputFocus, IO, Labels, MazeWarPlayerInsides, MazeWarPlayerRpcControl, MessageWindow, Process, Random, Rope, RPC, ThisMachine, TIPUser, UserCredentials, UserProfile, ViewerLocks, ViewerOps
EXPORTS MazeWarFinder, MazeWarPlayer, MazeWarPlayerInsides =
INVARIANT
p.srow, p.scol, p.sangle, and p.speek describe:
If p is me: the topview and the hallview,
Otherwise: no guarantees.
p.distance > 0 <=> p appears in hallview.
p.srow, p.scol, and p.sangle describe p's appearance.
The "occupants" fields of the Squares are redundant with the p.rows and p.cols.
p.distance is redundant with current positions.
Video inversness of opponent names is redundant with current positions.
All Players are "fully formed".
No more than one user being served.
myself # NIL <=> exactly one user being served.
BEGIN OPEN MazeWarPlayerInsides;
mazeViewerFlavor: PUBLIC ATOM ← GenSym.Gen[];
mazeViewerClass: PUBLIC ViewerClasses.ViewerClass ← NEW [ViewerClasses.ViewerClassRec ← [
paint: PaintMaze,
notify: NotifyMaze,
destroy: DestroyMaze,
tipTable: --TIPUser.InstantiateNewTIPTable[UserProfile.Token[key: "MazeWar.TIPTable", default: "/Ivy/Spreitzer/Games/MazeWar.TIP"]]-- NIL
]];
installationDirectory: ROPE ← CommandTool.CurrentWorkingDirectory[];
mazeWarriorIcon: PUBLIC Icons.IconFlavor ← Icons.NewIconFromFile[file: "MazeWar.icons", n: 0];
currentTIPTableName: ROPE ← " ";
rs: Random.RandomStream ← Random.Create[seed: -1];
quiverTime: PUBLIC BasicTime.Pulses ← BasicTime.MicrosecondsToPulses[2000000];
CallUnderMonitor1: PUBLIC ENTRY PROC [p: PROC] =
BEGIN ENABLE UNWIND => {};
p[];
END;
StartPlayer: PUBLIC PROC [cmd: Commander.Handle] RETURNS [result: REFNIL, msg: ROPENIL] --Commander.CommandProc-- =
BEGIN
MonitoredStart: ENTRY PROC =
BEGIN ENABLE UNWIND => {}; --not strictly right, but it helps debugging
name, machineName, userPicsSource, normdPicsSource, TIPTableName, mazeSource: ROPENIL;
reread: BOOLEANFALSE;
cls: IO.STREAMIO.RIS[cmd.commandLine];
self: Player ← myself;
position: INTEGER;
IF self # NIL THEN {cmd.err.PutRope[IF self.destroyed THEN "Still dying... wait 1.5 - 4.5 minutes and try again.\n" ELSE "Already playing.\n"]; RETURN};
paints ← 0;
FOR position ← 1, position + 1 DO
token: ROPE;
GetArg: PROC RETURNS [arg: ROPE] = {
arg ← cls.GetTokenRope[TokenBreak].token;
IF arg.Equal["="] THEN arg ← cls.GetTokenRope[TokenBreak].token ELSE cmd.err.PutRope["Missing \"=\""]};
ToBool: PROC [r: ROPE] RETURNS [b: BOOL] = {
s: IO.STREAMIO.RIS[r]; b ← s.GetBool[]; s.Close[]};
[] ← cls.SkipWhitespace[flushComments: FALSE];
IF cls.EndOf[] THEN EXIT;
token ← cls.GetTokenRope[TokenBreak].token;
IF token.Equal["="] THEN {cmd.err.PutRope["Syntax error"]; RETURN};
IF token.Equal["name", FALSE] THEN name ← GetArg[]
ELSE IF token.Equal["pics", FALSE] THEN userPicsSource ← GetArg[]
ELSE IF token.Equal["TIPTable", FALSE] THEN TIPTableName ← GetArg[]
ELSE IF token.Equal["MazeSource", FALSE] THEN mazeSource ← GetArg[]
ELSE IF token.Equal["reread", FALSE] THEN reread ← ToBool[GetArg[]]
ELSE SELECT position FROM
1 => name ← token;
2 => userPicsSource ← token;
3 => TIPTableName ← token;
4 => mazeSource ← token;
5 => reread ← ToBool[token];
ENDCASE => {
cmd.err.PutF["I won't take a %th argument\n", IO.int[position]];
RETURN};
ENDLOOP;
machineName ← ThisMachine.Name[$Pup];
IF playerNumber # 1 THEN machineName ← IO.PutFR["%g#%g", IO.rope[machineName], IO.card[playerNumber]];
THROUGH [1 .. playerNumber] DO instance ← instance.Cat["1"] ENDLOOP;
IF userPicsSource.Length[] < 1 THEN userPicsSource ← UserProfile.Token[key: "MazeWar.Pics", default: installationDirectory.Cat["Eyeball"]];
IF (normdPicsSource ← NormalizePlayerPicsRoot[userPicsSource]) = NIL THEN {
result ← $Failure;
msg ← IO.PutFR["Pics %g don't exist or are local with no attachment", IO.rope[userPicsSource]];
RETURN;
};
IF name.Length[] < 1 THEN name ← UserProfile.Token[key: "MazeWar.Name", default: NIL];
IF name.Length[] < 1 THEN name ← UserCredentials.Get[].name.Cat["@", machineName];
IF TIPTableName.Length[] < 1 THEN TIPTableName ← UserProfile.Token[key: "MazeWar.TIPTable", default: installationDirectory.Cat["MazeWar.TIP"]];
IF mazeSource.Length[] < 1 THEN mazeSource ← UserProfile.Token[key: "MazeWar.MazeSource", default: installationDirectory.Cat["MazeWar.Maze"]];
IF reread OR NOT TIPTableName.Equal[currentTIPTableName]
THEN mazeViewerClass.tipTable ←
TIPUser.InstantiateNewTIPTable[currentTIPTableName ← TIPTableName];
IF reread THEN LosePlayerPics[normdPicsSource];
cmd.err.PutRope[MakeNewPlayer[name: name, instance: myInstance, picsSource: normdPicsSource, mazeSource: mazeSource]];
END;
MonitoredStart[];
IF myself # NIL THEN ViewerOps.OpenIcon[icon: myself.mv.container, closeOthers: TRUE];
END;
TokenBreak: IO.BreakProc = {RETURN [SELECT char FROM
IO.SP, IO.CR, IO.ESC, IO.LF, IO.TAB => sepr,
'= => break,
ENDCASE => other]};
StopPlayer: PUBLIC Commander.CommandProc--PROC [cmd: Handle]-- =
BEGIN
self: Player ← myself;
IF self = NIL THEN {cmd.err.PutRope["Not playing!"]; RETURN};
IF self.destroyed THEN {cmd.err.PutRope["I'm busy dying... should be done in half a minute"]; RETURN};
ViewerOps.DestroyViewer[self.mv.container];
END;
UnMakePlayer: ENTRY PROC [p: Player] =
BEGIN ENABLE UNWIND => {}; --not strictly right, but it helps debugging
IF p.destroyed THEN RETURN;
FOR pl: PlayerList ← p.mv.players, pl.rest WHILE pl # NIL DO
IF pl.first = myself THEN LOOP;
TRUSTED {pl.first.ir.RemovePlayer[p.id !RPC.CallFailed => CONTINUE]};
pl.first.ir ← NIL; --unimports dynamic interface
ENDLOOP;
IF exportedPlayer THEN MazeWarPlayerRpcControl.UnexportInterface[];
exportedPlayer ← FALSE;
p.destroyed ← TRUE;
END;
paints: PUBLIC INTEGER ← 0;
clippable: PUBLIC BOOLEANFALSE;
blt: PUBLIC BOOLEANTRUE;
Peek: PUBLIC PROC [peek, row, col, angle: INTEGER] RETURNS [prow, pcol, pdrow, pdcol, pangle: INTEGER] =
BEGIN
[pdrow, pdcol] ← aToD[angle];
IF peek = 0 THEN RETURN [row, col, pdrow, pdcol, angle];
prow ← row + pdrow;
pcol ← col + pdcol;
[pdrow, pdcol] ← aToD[pangle ← (angle + peek) MOD 4];
END;
Visible: PUBLIC PROC [mv: MazeView, frow, fcol, fdrow, fdcol, trow, tcol: INTEGER] RETURNS [distance: INTEGER] =
BEGIN
drow, dcol: INTEGER;
drow ← trow - frow;
dcol ← tcol - fcol;
IF SGN[drow] # fdrow THEN RETURN [-1];
IF SGN[dcol] # fdcol THEN RETURN [-1];
distance ← 0;
DO
IF frow = trow AND fcol = tcol THEN RETURN;
IF NOT mv.squares[Index[mv, frow, fcol]].open THEN RETURN [-1];
frow ← frow + fdrow;
fcol ← fcol + fdcol;
distance ← distance + 1;
ENDLOOP;
END;
SGN: PROC [x: INTEGER] RETURNS [sgn: INTEGER] =
{sgn ← IF x < 0 THEN -1 ELSE IF x > 0 THEN 1 ELSE 0};
forkNotify: PUBLIC BOOLEANFALSE;
NotifyMaze: ViewerClasses.NotifyProc--PROC [self: Viewer, input: LIST OF REF ANY]-- =
BEGIN
IF NOT forkNotify THEN ReallyNotifyMaze[self, input]
ELSE TRUSTED {Process.Detach[FORK ReallyNotifyMaze[self, input]]};
END;
ReallyNotifyMaze: ViewerClasses.NotifyProc--PROC [self: Viewer, input: LIST OF REF ANY]-- =
BEGIN
p: Player ← NARROW[self.data];
IF p = NIL THEN RETURN;
IF p.pausing AND input.first # $Grab THEN {
MessageWindow.Append[" patience!", FALSE];
RETURN};
SELECT input.first FROM
$Grab => {InputFocus.SetInputFocus[self]; RETURN};
$MoveFwd => {MoveSelf[p, p.angle]; RETURN};
$MoveBack => {MoveSelf[p, (p.angle+2) MOD 4]; RETURN};
$Shoot => TRUSTED {Process.Detach[FORK RawShoot[p]]; RETURN};
$TurnLeft => SetDirection[p, (p.angle+3) MOD 4];
$TurnRight => SetDirection[p, (p.angle+1) MOD 4];
$TurnBack => SetDirection[p, (p.angle+2) MOD 4];
$PeekLeft => SetPeek[p, peekLeft];
$PeekRight => SetPeek[p, peekRight];
$UnPeek => SetPeek[p, 0];
$Auto => IF ((auto ← auto.SUCC) MOD 2) # 0 THEN TRUSTED {Process.Detach[FORK Auto[p, auto]]};
$Aggressive => MessageWindow.Append[
message: IF autoParms.aggressive ← NOT autoParms.aggressive
THEN "Getting aggressive"
ELSE "Becoming mediocre",
clearFirst: TRUE];
ENDCASE => ERROR;
IncrPaint[p];
END;
DestroyMaze: ViewerClasses.DestroyProc--PROC [self: Viewer]-- =
BEGIN
p: Player ← NARROW[self.data];
IF p = myself THEN UnMakePlayer[p];
END;
auto: INT ← 0;
autoPeriod: INT;
atuoParmsRep: TYPE ~ RECORD [
forwardRange: INT ← 2,
forwardChooseAhead: INT ← 0,
forwardChooseLeft: INT ← 1,
forwardChooseRight: INT ← 2,
backRange: INT ← 1,
backChooseLeft: INT ← 0,
backChooseRight: INT ← 1,
lookBehindRange: INT ← 3,
lookBehindChoose: INT ← 0,
aggressive: BOOLFALSE
];
autoParms: REF atuoParmsRep ~ NEW [atuoParmsRep];
Auto: PROC [p: Player, myAutoId: INT] =
BEGIN
defaultAutoPeriod: INT ~ 400;
minAutoPeriod: INT ~ 50;
maxAutoPeriod: INT ~ 1000;
myLastScore: INT ← p.score;
TRUSTED {Process.SetPriority[Process.priorityNormal]};
autoPeriod ← defaultAutoPeriod;
DO
Do: PROC [action: ATOM] ~ {
IF p.destroyed OR p.mv.container.destroyed OR auto # myAutoId THEN
ERROR ABORTED;
p.mv.maze.class.notify[p.mv.maze, LIST[action]];
Process.Pause[Process.MsecToTicks[autoPeriod]];
};
ahead: Angle ~ 0; left: Angle ~ 3; right: Angle ~ 1; behind: Angle ~ 2;
Compose: PROC [d1, d2: Angle] RETURNS [Angle] ~ {
RETURN [(d1+d2) MOD 4]
};
IsSpace: PROC [dirn: Angle ← ahead, whenPeeking: BOOLEANFALSE] RETURNS [BOOLEAN] ~ {
drow, dcol: INT;
[drow, dcol] ← aToD[(p.angle+dirn) MOD 4];
IF whenPeeking THEN {
fdrow, fdcol: INT;
[fdrow, fdcol] ← aToD[p.angle];
drow ← drow+fdrow;
dcol ← dcol+fdcol
};
RETURN [p.mv.squares[Index[p.mv, p.row, p.col]+Direction[p.mv, drow, dcol]].open];
};
TargetPresent: PROC RETURNS [BOOLEAN] ~ {
FOR pl: PlayerList ← p.mv.players, pl.rest WHILE pl # NIL DO
IF pl.first.distance >= 0 THEN RETURN [TRUE]
ENDLOOP;
RETURN [FALSE]
};
IF myLastScore # p.score THEN {
better: INT ← 0;
worse: INT ← 0;
FOR pl: PlayerList ← p.mv.players, pl.rest WHILE pl # NIL DO
IF pl.first.score > p.score THEN better ← better.SUCC;
IF pl.first.score < p.score THEN worse ← worse.SUCC;
ENDLOOP;
IF better # worse OR autoParms.aggressive THEN {
I'm not the middle player.
SELECT TRUE FROM
(myLastScore > p.score) AND (better > (IF NOT autoParms.aggressive THEN worse ELSE 0)) =>
SpeedUp.
autoPeriod ← MAX[(autoPeriod*4)/5, minAutoPeriod];
(myLastScore < p.score) AND (better < (IF NOT autoParms.aggressive THEN worse ELSE 1)) =>
SlowDown.
autoPeriod ← MIN[(autoPeriod*5)/4, maxAutoPeriod];
ENDCASE => NULL;
};
myLastScore ← p.score
};
SELECT TRUE FROM
~IsSpace[] => {
turn: Angle ← SELECT rs.ChooseInt[0, autoParms.backRange] FROM <= autoParms.backChooseLeft => left, <= autoParms.backChooseRight => right, ENDCASE => behind;
inc: Angle ~ SELECT turn FROM left => right, right => left, ENDCASE => (SELECT rs.ChooseInt[0, 1] FROM 0=> right, 1=> left, ENDCASE => ERROR);
WHILE ~IsSpace[turn] DO turn ← Compose[turn, inc] ENDLOOP;
Do[SELECT turn FROM left => $TurnLeft, right => $TurnRight, ENDCASE => $TurnBack];
IF turn = left OR turn = right THEN Do[$MoveFwd]
};
TargetPresent[] => {
Do[$Shoot];
DO
side: Angle ← SELECT rs.ChooseInt[0, 1] FROM 0=> left, 1=> right, ENDCASE => ERROR;
IF IsSpace[Compose[ side, behind]] THEN side ← Compose[ side, behind];
IF IsSpace[side] THEN {
Do[SELECT side FROM left => $TurnLeft, right => $TurnRight, ENDCASE => ERROR];
Do[$MoveFwd];
EXIT
};
IF ~IsSpace[] THEN EXIT;
Do[$MoveFwd]
ENDLOOP
};
ENDCASE => {
PeekAndShoot: PROC [dirn: Angle] RETURNS [didShoot: BOOLEAN] ~ {
IF ~IsSpace[ dirn, TRUE] THEN RETURN [didShoot ← FALSE];
Do[SELECT dirn FROM left => $PeekLeft, right => $PeekRight, ENDCASE => ERROR];
didShoot ← TargetPresent[];
Do[$UnPeek];
IF didShoot THEN {
Do[$MoveFwd];
Do[SELECT dirn FROM left => $TurnLeft, right => $TurnRight, ENDCASE => ERROR];
Do[$Shoot];
Do[SELECT dirn FROM left => $TurnLeft, right => $TurnRight, ENDCASE => ERROR];
Do[$MoveFwd];
}
};
peekDirn: Angle;
IF rs.ChooseInt[0, autoParms.lookBehindRange] <= autoParms.lookBehindChoose AND IsSpace[behind] THEN {
Do[$TurnBack];
IF TargetPresent[] THEN LOOP;
Do[$TurnBack]
};
peekDirn ← SELECT rs.ChooseInt[0, 1] FROM 0=> left, 1=> right, ENDCASE => ERROR;
IF ~PeekAndShoot[peekDirn] THEN
IF ~PeekAndShoot[Compose[peekDirn, behind]] THEN {
turn: Angle ← SELECT rs.ChooseInt[0, autoParms.forwardRange] FROM <= autoParms.forwardChooseAhead => ahead, <= autoParms.forwardChooseLeft => left, <= autoParms.forwardChooseRight => right, ENDCASE => behind;
inc: Angle ~ SELECT turn FROM left => right, right => left, ENDCASE => (SELECT rs.ChooseInt[0, 1] FROM 0=> right, 1=> left, ENDCASE => ERROR);
WHILE ~IsSpace[turn] DO turn ← Compose[turn, inc] ENDLOOP;
Do[SELECT turn FROM ahead => $MoveFwd, left => $TurnLeft, right => $TurnRight, ENDCASE => $TurnBack];
IF turn # ahead THEN Do[$MoveFwd]
}
};
ENDLOOP;
END;
ListPlayers: PUBLIC UNSAFE PROC RETURNS [players: BasicPlayerList] =
BEGIN
IF myself = NIL THEN RETURN [NIL];
FOR pl: PlayerList ← myself.mv.players, pl.rest WHILE pl # NIL DO
players ← CONS[[pl.first.instanceName, pl.first.id], players];
ENDLOOP;
FOR pl: PlayerList ← myself.mv.differentMazePlayers, pl.rest WHILE pl # NIL DO
players ← CONS[[pl.first.instanceName, pl.first.id], players];
ENDLOOP;
END;
TakePlayer: PUBLIC UNSAFE PROC [bp: BasicPlayer] = UNCHECKED {KnowPlayer[bp]};
GetFinderID: PUBLIC UNSAFE PROC RETURNS [bp: BasicPlayer] =
{self: Player ← myself;
bp ← IF self # NIL THEN [self.instanceName, self.id] ELSE [myInstance, 0]};
GetState: PUBLIC UNSAFE PROC RETURNS [id: PlayerId, name, pics, maze: ROPE, row, col, score: INTEGER, angle: Angle] =
BEGIN
RETURN [myself.id, myself.name, myself.pics.name, myself.mv.asRope, myself.row, myself.col, myself.score, myself.angle];
END;
KnowPlayer: PUBLIC UNSAFE PROC [bp: BasicPlayer] =
BEGIN
q: Player;
IF myself = NIL THEN RETURN;
q ← EnsurePlayer[bp];
IF q # NIL AND NOT q.instanceName.Equal[bp.instance] THEN
MessageWindow.Append["Random number collision detected", TRUE];
END;
EnsurePlayer: PUBLIC PROC [bp: BasicPlayer] RETURNS [q: Player] =
BEGIN
ir: PlayerInterface ← NIL;
alreadyKnown, differentMaze: BOOLEAN;
id: PlayerId;
row, col, score: INTEGER;
angle: Angle;
name, picsSource, mazeRope: ROPE;
pp: PlayerPics;
Internals1: PROC =
BEGIN ENABLE UNWIND => NULL;
IF alreadyKnown ← (q ← FindPlayer[myself.mv.players, bp.id]) # NIL THEN RETURN;
IF alreadyKnown ← (q ← FindPlayer[myself.mv.differentMazePlayers, bp.id]) # NIL THEN RETURN;
ir ← MazeWarPlayerRpcControl.ImportNewInterface[interfaceName: [instance: bp.instance] !RPC.ImportFailed => {ir ← NIL; CONTINUE}];
IF ir = NIL THEN {ok ← FALSE; RETURN};
TRUSTED {[id, name, picsSource, mazeRope, row, col, score, angle] ← ir.GetState[ !RPC.CallFailed => {ok ← FALSE; CONTINUE}]};
IF NOT ok THEN RETURN;
IF id # bp.id THEN {ok ← FALSE; RETURN};
differentMaze ← NOT mazeRope.Equal[myself.mv.asRope, FALSE];
END;
Internals2: ENTRY PROC =
BEGIN ENABLE UNWIND => NULL;
IF alreadyKnown ← (q ← FindPlayer[myself.mv.players, bp.id]) # NIL THEN RETURN;
IF alreadyKnown ← (q ← FindPlayer[myself.mv.differentMazePlayers, bp.id]) # NIL THEN RETURN;
IF differentMaze THEN
BEGIN
myself.mv.differentMazePlayers ← CONS[NEW [PlayerRep ← [id: bp.id, instanceName: bp.instance, quiverTill: BasicTime.GetClockPulses[]-1]], myself.mv.differentMazePlayers];
END
ELSE BEGIN
q ← AddPlayerAt[myself.mv, ir, bp.instance, id, name, row, col, angle, score, pp];
AddScoring[myself, q];
END;
END;
ok: BOOLEANTRUE;
me: Player ← myself; --'cause the debugger can't.
q ← NIL;
Internals1[];
IF NOT ok THEN RETURN [NIL];
IF q # NIL THEN RETURN;
IF NOT differentMaze THEN pp ← GetPlayerPics[picsSource];
ViewerLocks.CallUnderWriteLock[Internals2, myself.mv.container];
IF NOT ok THEN RETURN [NIL];
IF alreadyKnown THEN RETURN;
IF differentMaze THEN
BEGIN
--nothing to do here!?--
END
ELSE BEGIN
ViewerOps.PaintViewer[viewer: myself.mv.container, hint: client];
TRUSTED {ir.KnowPlayer[[myself.instanceName, myself.id] !RPC.CallFailed => {ok ← HandleCallFailure[q, why]; CONTINUE}]};
NoticeCall[q, ok];
END;
END;
TakeStatus: PUBLIC UNSAFE PROC [id: PlayerId, row, col, score: INTEGER, angle: Angle, shot: BOOLEAN] =
BEGIN
q: Player;
IF (q ← FindPlayer[myself.mv.players, id]) = NIL THEN RETURN;
IF row # q.row OR col # q.col OR angle # q.angle THEN {
q.shot ← shot;
MoveOther[myself, q, row, col, angle]};
IF score # q.score THEN Labels.Set[q.scoreLabel, IO.PutFR["%6g", IO.int[q.score ← score]]];
END;
RemovePlayer: PUBLIC UNSAFE PROC [id: PlayerId] =
BEGIN
MonitoredRemove: ENTRY PROC =
BEGIN
goner: Player;
index: INTEGER;
goner ← FindPlayer[myself.mv.players, id];
IF goner = NIL THEN RETURN;
myself.mv.players ← Filter[myself.mv.players, goner];
ViewerOps.DestroyViewer[goner.nameLabel, FALSE];
ViewerOps.DestroyViewer[goner.scoreLabel, FALSE];
index ← Index[myself.mv, goner.row, goner.col];
myself.mv.squares[index].occupants ← Filter[myself.mv.squares[index].occupants, goner];
goner.ir ← NIL; --Unimports dynamic interface
goner.mv ← NIL;
END;
IF myself = NIL THEN RETURN;
ViewerLocks.CallUnderWriteLock[MonitoredRemove, myself.mv.container];
ViewerOps.PaintViewer[myself.mv.container, client];
END;
FindPlayer: PUBLIC PROC [pl: PlayerList, id: PlayerId] RETURNS [p: Player] =
BEGIN
FOR pl ← pl, pl.rest WHILE pl # NIL DO
IF pl.first.id = id THEN RETURN [pl.first];
ENDLOOP;
p ← NIL;
END;
MoveSelf: PROC [p: Player, angle: Angle] =
BEGIN
moving: BOOLEAN;
MonitoredMove: ENTRY PROC =
BEGIN
di, me, nu, drow, dcol: INTEGER;
[drow, dcol] ← aToD[angle];
di ← Direction[p.mv, drow, dcol];
me ← Index[p.mv, p.row, p.col];
nu ← me+di;
IF moving ← p.mv.squares[nu].open THEN moving ← MoveSelfBegin[p, p.row+drow, p.col+dcol, p.angle];
END;
ViewerLocks.CallUnderWriteLock[MonitoredMove, myself.mv.container];
IF moving THEN MoveSelfEnd[p, FALSE];
END;
MoveSelfWork: PROC [p: Player, row, col: INTEGER, angle: Angle, shot: BOOLEAN] =
BEGIN
changed: BOOL;
EntryWork: ENTRY PROC = {
p.shot ← shot;
changedMoveSelfBegin[p, row, col, angle]};
ViewerLocks.CallUnderWriteLock[EntryWork, myself.mv.container];
IF changed THEN MoveSelfEnd[p, shot];
END;
MoveSelfBegin: INTERNAL PROC [p: Player, row, col: INTEGER, angle: Angle] RETURNS [changed: BOOLEAN] =
BEGIN
old, new: INTEGER;
IF row # p.row OR col # p.col THEN {
old ← Index[p.mv, p.row, p.col];
new ← Index[p.mv, row, col];
p.mv.squares[old].occupants ← Filter[p.mv.squares[old].occupants, p];
p.mv.squares[new].occupants ← CONS[p, p.mv.squares[new].occupants]}
ELSE IF angle = p.angle THEN RETURN [FALSE];
changed ← TRUE;
p.row ← row;
p.col ← col;
p.angle ← angle;
RecomputeVisibilities[p];
END;
MoveSelfEnd: PROC [p: Player, shot: BOOLEAN] =
BEGIN
AnnounceStatus[shot];
IF shot THEN TRUSTED {Process.SetPriority[Process.priorityNormal]};
IncrPaint[p];
END;
RecomputeVisibilities: PROC [from: Player] =
BEGIN
now: BasicTime.Pulses = BasicTime.GetClockPulses[];
row, col, drow, dcol: INTEGER;
[row, col, drow, dcol, ] ← Peek[from.peek, from.row, from.col, from.angle];
FOR pl: PlayerList ← from.mv.players, pl.rest WHILE pl # NIL DO
quiver: BOOL = pl.first.quiverTill - now <= quiverTime;
wasVisible: BOOLEAN ← pl.first.distance > 0;
nowVisible: BOOLEAN;
IF pl.first = from THEN LOOP;
nowVisible ← (pl.first.distance ← Visible[from.mv, row, col, drow, dcol, pl.first.row, pl.first.col]) > 0;
IF wasVisible # nowVisible OR quiver # pl.first.quivering THEN {
Labels.SetDisplayStyle[pl.first.nameLabel, IF quiver THEN $BlackOnGrey ELSE IF nowVisible THEN $WhiteOnBlack ELSE $BlackOnWhite];
pl.first.quivering ← quiver;
};
ENDLOOP;
END;
MoveOther: PROC [p, q: Player, row, col: INTEGER, angle: Angle] =
BEGIN
EntryWork: ENTRY PROC =
BEGIN
now: BasicTime.Pulses = BasicTime.GetClockPulses[];
quiver: BOOL = q.quiverTill - now <= quiverTime;
index: INTEGER ← Index[q.mv, q.row, q.col];
wasShown ← q.sdistance > 0;
wasVisible ← q.distance > 0;
q.mv.squares[index].occupants ← Filter[q.mv.squares[index].occupants, q];
q.row ← row;
q.col ← col;
q.angle ← angle;
index ← Index[q.mv, q.row, q.col];
q.mv.squares[index].occupants ← CONS[q, q.mv.squares[index].occupants];
nowVisible ← (q.distance ← Visibility[p, q]) > 0;
IF wasVisible # nowVisible OR quiver # q.quivering THEN
BEGIN
Labels.SetDisplayStyle[label: q.nameLabel, style: IF quiver THEN $BlackOnGrey ELSE IF nowVisible THEN $WhiteOnBlack ELSE $BlackOnWhite];
q.quivering ← quiver;
END;
END;
wasShown, wasVisible, nowVisible: BOOLEAN;
ViewerLocks.CallUnderWriteLock[EntryWork, myself.mv.container];
IF wasShown OR nowVisible THEN IncrPaint[q];
END;
Visibility: PUBLIC PROC [from, to: Player] RETURNS [distance: INTEGER] =
BEGIN
frow, fcol, drow, dcol: INTEGER;
[frow, fcol, drow, dcol, ] ← Peek[from.peek, from.row, from.col, from.angle];
distance ← Visible[from.mv, frow, fcol, drow, dcol, to.row, to.col];
END;
ShownDistance: PROC [from, to: Player] RETURNS [distance: INTEGER] =
BEGIN
frow, fcol, drow, dcol: INTEGER;
[frow, fcol, drow, dcol, ] ← Peek[from.speek, from.srow, from.scol, from.sangle];
distance ← Visible[from.mv, frow, fcol, drow, dcol, to.srow, to.scol];
END;
SetDirection: PROC [p: Player, angle: Angle] =
{MonitoredSet: ENTRY PROC = {RecomputeVisibilities[p]};
p.angle ← angle;
ViewerLocks.CallUnderWriteLock[MonitoredSet, myself.mv.container];
AnnounceStatus[FALSE];
};
SetPeek: PROC [p: Player, peek: INTEGER] =
{ReallySetPeek: ENTRY PROC = {
p.peek ← peek;
RecomputeVisibilities[p];
};
ViewerLocks.CallUnderWriteLock[ReallySetPeek, myself.mv.container];
};
shootDelay: Process.Ticks ← Process.MsecToTicks[700];
ProcessList: TYPE = LIST OF PROCESS;
RawShoot: PROC [p: Player] =
BEGIN
row, col, drow, dcol: INTEGER;
victims: PlayerList ← NIL;
processes: ProcessList ← NIL;
EntryShoot: ENTRY PROC =
BEGIN ENABLE UNWIND => NULL;
row ← p.row; col ← p.col;
[drow, dcol] ← aToD[p.angle];
FOR pl: PlayerList ← p.mv.players, pl.rest WHILE pl # NIL DO
IF pl.first = p THEN LOOP;
IF pl.first.distance > 0 THEN victims ← CONS[pl.first, victims];
ENDLOOP;
END;
EntryShoot[];
IF victims = NIL THEN RETURN;
Process.Pause[shootDelay];
FOR pl: PlayerList ← victims.rest, pl.rest WHILE pl # NIL DO
processes ← CONS[FORK FinishShoot[p, pl.first, row, col, drow, dcol], processes];
ENDLOOP;
FinishShoot[p, victims.first, row, col, drow, dcol];
FOR processes ← processes, processes.rest WHILE processes # NIL DO
TRUSTED {[] ← JOIN processes.first};
ENDLOOP;
Labels.Set[p.scoreLabel, IO.PutFR["%6g", IO.int[p.score]]];
AnnounceStatus[FALSE];
END;
FinishShoot: PROC [p, q: Player, row, col, drow, dcol: INTEGER] = {
ok: BOOLEANTRUE;
shot: BOOLEANFALSE;
TRUSTED {shot ← q.ir.Shoot[p.id, row, col, drow, dcol !RPC.CallFailed => {ok ← HandleCallFailure[q, why]; CONTINUE}]};
NoticeCall[q, ok];
IF ok AND shot THEN
BEGIN
Quiver: ENTRY PROC = {
ENABLE UNWIND => NULL;
q.quiverTill ← BasicTime.GetClockPulses[] + quiverTime;
IF NOT q.quivering THEN {
q.quivering ← TRUE;
Labels.SetDisplayStyle[q.nameLabel, $BlackOnGrey];
};
p.score ← p.score + 10;
};
ViewerLocks.CallUnderWriteLock[Quiver, myself.mv.container];
END;
};
Shoot: PUBLIC UNSAFE PROC [shooterId: PlayerId, row, col, drow, dcol: INTEGER] RETURNS [shot: BOOLEAN] =
BEGIN
shooter: Player ← FindPlayer[myself.mv.players, shooterId];
IF shooter = NIL THEN RETURN;
DO
row ← row + drow;
col ← col + dcol;
IF NOT myself.mv.squares[Index[myself.mv, row, col]].open THEN EXIT;
IF row = myself.row AND col = myself.col THEN {HitPlayer[victor: shooter, victim: myself]; RETURN [TRUE]};
ENDLOOP;
shot ← FALSE;
END;
HitPlayer: PROC [victor, victim: Player] =
BEGIN
row, col: INTEGER;
angle: Angle;
ok: BOOLEANTRUE;
IF victim # myself THEN ERROR;
[row, col] ← PickAPlace[victim.mv];
angle ← rs.ChooseInt[0, 3];
Labels.Set[victim.scoreLabel, IO.PutFR["%6g", IO.int[victim.score ← victim.score - 5]]];
MoveSelfWork[victim, row, col, angle, TRUE];
END;
Filter: PUBLIC PROC [org: PlayerList, rem: Player] RETURNS [ohne: PlayerList] =
BEGIN
last: PlayerList ← NIL;
ohne ← org;
FOR org ← org, org.rest WHILE org # NIL DO
IF org.first = rem THEN {IF last = NIL THEN ohne ← org.rest ELSE last.rest ← org.rest; RETURN};
last ← org;
ENDLOOP;
ERROR;
END;
HandleCallFailure: PUBLIC PROC [callee: Player, why: RPC.CallFailure] RETURNS [handled: BOOLEANFALSE] =
BEGIN
IF why = unbound THEN
BEGIN
TRUSTED {RemovePlayer[callee.id]};
handled ← TRUE;
END;
IF debugFailures THEN whys ← CONS[why, whys];
END;
debugFailures: BOOLEANFALSE;
whys: LIST OF RPC.CallFailure ← NIL;
maxAcceptableCallFailures: NAT ← 0;
NoticeCall: PUBLIC PROC [p: Player, ok: BOOLEAN] =
BEGIN
IF p.destroyed THEN RETURN;
p.callFailures ← MAX[0, (p.callFailures + (IF ok THEN -1 ELSE 1))];
IF p.callFailures > maxAcceptableCallFailures THEN TRUSTED {RemovePlayer[p.id]};
END;
StupidFuckingWarnings: PUBLIC SIGNAL [atom: ATOM] = CODE;
END.