[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:
REF ←
NIL, msg:
ROPE ←
NIL]
--Commander.CommandProc-- =
BEGIN
MonitoredStart:
ENTRY
PROC =
BEGIN ENABLE UNWIND => {}; --not strictly right, but it helps debugging
name, machineName, userPicsSource, normdPicsSource, TIPTableName, mazeSource: ROPE ← NIL;
reread: BOOLEAN ← FALSE;
cls: IO.STREAM ← IO.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.STREAM ← IO.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 BOOLEAN ← FALSE;
blt: PUBLIC BOOLEAN ← TRUE;
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 BOOLEAN ← FALSE;
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: BOOL ← FALSE
];
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:
BOOLEAN ←
FALSE]
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: BOOLEAN ← TRUE;
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;
changed ← MoveSelfBegin[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: BOOLEAN ← TRUE;
shot: BOOLEAN ← FALSE;
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: BOOLEAN ← TRUE;
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:
BOOLEAN ←
FALSE] =
BEGIN
IF why = unbound
THEN
BEGIN
TRUSTED {RemovePlayer[callee.id]};
handled ← TRUE;
END;
IF debugFailures THEN whys ← CONS[why, whys];
END;
debugFailures: BOOLEAN ← FALSE;
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.