FootballControl.mesa
Last Edited by: Maxwell, March 15, 1983 10:21 am
Last Edited by: Spreitzer, May 1, 1984 10:58:28 pm PDT
DIRECTORY
BasicTime USING [GMT, Now, nullGMT, Pulses, GetClockPulses],
FootballInternal,
FootballMaster,
FootballMasterRpcControl USING [ExportInterface, UnexportInterface],
Process USING [SetTimeout],
RealFns USING [SqRt],
Rope USING [ROPE],
RPC USING [MakeKey],
UserCredentials USING [Get];
FootballControl: MONITOR
IMPORTS BasicTime, FootballMaster, FootballMasterRpcControl, Process, RealFns, RPC, UserCredentials
EXPORTS FootballInternal, FootballMaster =
BEGIN OPEN FootballMaster;
StupidFuckingWarnings: PUBLIC SIGNAL [itsGottaHaveAGoddamName: ATOM] = CODE;
******************************************************************
RPC procedure
******************************************************************
process: PROCESSNIL;
StartServer: PUBLIC ENTRY PROCEDURE[name: Rope.ROPE] =
BEGIN
user, password: Rope.ROPE;
IF stopped = FALSE THEN RETURN ELSE stopped ← FALSE;
process ← FORK Main[];
[user, password] ← UserCredentials.Get[];
FootballMasterRpcControl.ExportInterface[
interfaceName: [instance: name],
user: user,
password: RPC.MakeKey[password]];
END;
StopServer: PUBLIC ENTRY PROCEDURE =
BEGIN
IF stopped = TRUE THEN RETURN ELSE stopped ← TRUE;
WHILE process # NIL DO WAIT timer; ENDLOOP;
FootballMasterRpcControl.UnexportInterface[];
END;
Init: PROC =
BEGIN
controlGame ← NEW[GameRec ← []];
FOR i: Position IN Players DO
controlGame.player[i].position ← i;
ENDLOOP;
END;
******************************************************************
Control procedures
******************************************************************
controlGame: Game;
slow: BOOLEANFALSE;
commands: ARRAY Team OF Commands ← [ALL[[stop, [null[]]]], ALL[[stop, [null[]]]]];
SetCommands: PUBLIC PROC[team: Team, c: Commands]
RETURNS[GameRec] =
BEGIN
commands[team] ← c;
RETURN[controlGame^];
END;
OffField: PROCEDURE =
BEGIN
controlGame.state ← offField;
controlGame.ballCarrier ← none;
controlGame.player[ball].x ← -100;
controlGame.player[ball].y ← -100;
controlGame.ballTarget ← [absolute[-100, -100]];
controlGame.clockStopped ← TRUE;
END;
KickOff: PUBLIC PROCEDURE[team: Team] =
BEGIN
IF controlGame.quarter = 5 THEN {
controlGame.quarter ← 1;
controlGame.score ← [0, 0]};
IF controlGame.quarter = 1 AND controlGame.clock = 15*60 THEN team ← home;
IF controlGame.quarter = 3 AND controlGame.clock = 15*60 THEN team ← visitors;
controlGame.offense ← controlGame.possessor ← team;
IF Goal[team, controlGame.quarter] < 50
THEN controlGame.scrimmage ← 20
ELSE controlGame.scrimmage ← 80;
FirstDown[];
Huddle[];
END;
Huddle: PROCEDURE =
BEGIN
FOR i: Position IN Players DO
controlGame.player[i].dx ← 0;
controlGame.player[i].dy ← 0;
ENDLOOP;
controlGame.player[ball].x ← controlGame.scrimmage;
controlGame.player[ball].y ← fieldWidth/2;
controlGame.ballCarrier ← none;
controlGame.ballTarget ← [absolute[controlGame.scrimmage, fieldWidth/2]];
controlGame.state ← huddle;
controlGame.pass ← FALSE;
controlGame.lateral ← FALSE;
delay ← 1;
startHuddle ← controlGame.clock;
END;
SetUp: PUBLIC PROCEDURE[team: Team] =
BEGIN
IF controlGame.state = offField THEN KickOff[team];
controlGame.state ← setUp;
END;
Hike: PUBLIC PROCEDURE[team: Team] =
BEGIN
hike: REAL;
count: NAT ← 0;
IF team # controlGame.offense THEN RETURN;
IF ~Ready[home] OR ~Ready[visitors] THEN RETURN;
delay ← IF slow THEN 5 ELSE 1;
controlGame.penalties ← [none, none];
controlGame.penaltyState ← none;
controlGame.clockStopped ← FALSE;
hike ← controlGame.scrimmage - Direction[controlGame.offense, controlGame.quarter]*15;
move the ball back so that the center won't catch it
controlGame.player[ball].x ← controlGame.scrimmage - Direction[controlGame.offense, controlGame.quarter]*1.1;
controlGame.ballTarget ← [absolute[hike, fieldWidth/2]];
controlGame.ballCarrier ← ball;
check for offSides and illegal formations
FOR i: Position IN Players DO
controlGame.player[i].blocking ← none; -- reset
IF TeamOf[i] = controlGame.offense AND InMotion[@controlGame.player[i]] THEN IF count = 0
THEN count ← count + 1
ELSE controlGame.penalties[controlGame.offense] ← formation;
IF CrossLine[controlGame.player[i].x, controlGame.scrimmage, TeamOf[i], controlGame.quarter]
THEN controlGame.penalties[TeamOf[i]] ← offSides;
ENDLOOP;
controlGame.state ← option;
END;
InMotion: PROC[player: Player] RETURNS[BOOLEAN] =
INLINE {RETURN[ABS[player.dx] > .5 OR ABS[player.dy] > .5]};
Ready: PROC[team: Team] RETURNS[BOOLEAN] = INLINE
BEGIN
count: NAT ← 0;
FOR i: Position IN Players DO
IF TeamOf[i] # team THEN LOOP;
IF ABS[controlGame.player[i].x - controlGame.scrimmage] < 2 THEN count ← count + 1;
ENDLOOP;
RETURN[count > 2];
END;
FirstDown: PROC =
BEGIN
controlGame.down ← 1;
IF Goal[controlGame.offense, controlGame.quarter] < 50
THEN controlGame.firstDownMarker ← MIN[100, controlGame.scrimmage + 10]
ELSE controlGame.firstDownMarker ← MAX[0, controlGame.scrimmage - 10];
END;
TimeOut: PUBLIC PROCEDURE[team: Team] =
BEGIN
IF controlGame.timeouts[team] = 0 THEN RETURN;
IF controlGame.state > option THEN RETURN;
IF controlGame.clockStopped THEN RETURN;
controlGame.clockStopped ← TRUE;
controlGame.timeouts[team] ← controlGame.timeouts[team] - 1;
END;
PenaltyResponse: PUBLIC PROCEDURE[team: Team, accepted: BOOLEAN] =
BEGIN
IF controlGame.penalties[Opponent[team]] = none THEN RETURN;
IF controlGame.penaltyState # asking THEN RETURN;
controlGame.penaltyState ← IF accepted THEN accepted ELSE declined;
END;
DisableDelayOfGame: PUBLIC PROCEDURE = {delayOfGame ← FALSE};
******************************************************************
Main Loop
******************************************************************
timer: CONDITION;
delay: CARDINAL ← 1;
stopped: BOOLEANTRUE;
gmt: BasicTime.GMT ← BasicTime.nullGMT;
startHuddle: INTEGER ← 0;
delayOfGame: BOOLEANTRUE;
Wait: ENTRY PROCEDURE[i: CARDINAL] =
BEGIN
DO IF i = 0 THEN EXIT ELSE i ← i - 1; WAIT timer; ENDLOOP;
END;
Main: PROCEDURE =
BEGIN
x, y: REAL;
player: Player;
command: Commands;
Process.SetTimeout[@timer, 1];
SetOffField[];
DO Wait[delay]; IF stopped THEN {process ← NIL; EXIT};
check clock and penalties
IF ~controlGame.clockStopped AND controlGame.quarter # 5 THEN
IF BasicTime.Now[] # gmt THEN {
controlGame.clock ← controlGame.clock - 1;
gmt ← BasicTime.Now[]};
IF controlGame.clock <= 0 AND controlGame.state < option THEN PlayOver[controlGame.scrimmage];
IF delayOfGame AND controlGame.state < option AND startHuddle - controlGame.clock > 60 THEN {
SELECT TRUE FROM
~Ready[home] AND ~Ready[visitors] => {startHuddle ← controlGame.clock; LOOP};
~Ready[home] => controlGame.penalties[home] ← delayOfGame;
~Ready[visitors] => controlGame.penalties[visitors] ← delayOfGame;
ENDCASE => controlGame.penalties[controlGame.offense] ← delayOfGame;
PlayOver[controlGame.scrimmage, TRUE]};
IF (controlGame.penalties[home] = offSides OR controlGame.penalties[visitors] = offSides)
AND controlGame.state > setUp THEN PlayOver[controlGame.scrimmage, TRUE];
is the ball now out of play?
x ← controlGame.player[ball].x;
y ← controlGame.player[ball].y;
IF controlGame.state > setUp AND
(x NOT IN [-10..110] OR y NOT IN [0..fieldWidth]) THEN PlayOver[x, TRUE];
IF controlGame.state > setUp AND controlGame.ballCarrier # ball AND
Direction[TeamOf[controlGame.ballCarrier], controlGame.quarter] = 1 AND x > 100
THEN PlayOver[101, TRUE];
IF controlGame.state > setUp AND controlGame.ballCarrier # ball AND
Direction[TeamOf[controlGame.ballCarrier], controlGame.quarter] = -1 AND x < 0
THEN PlayOver[-1, TRUE];
move the ball
IF controlGame.state > setUp AND controlGame.ballCarrier = none THEN {
[x, y] ← GetTarget[controlGame.ballTarget, none];
MoveBall[@controlGame.player[ball], x, y]; CatchBall[];
IF controlGame.player[ball].x = x AND controlGame.player[ball].y = y THEN -- ball has hit the ground
IF controlGame.pass AND ~controlGame.lateral
THEN PlayOver[controlGame.scrimmage, TRUE]
ELSE controlGame.state ← liveBall};
are we committed to a run?
IF controlGame.state = option AND
CrossLine[x, controlGame.scrimmage, controlGame.offense, controlGame.quarter]
THEN controlGame.state ← run;
move home team
command ← commands[home];
FOR i: CARDINAL IN [0..6) DO
player ← @controlGame.player[Add[HQB, i]];
IF controlGame.state IN [offField..setUp] THEN {
IF command[i].action # run THEN LOOP;
[x, y] ← GetTarget[command[i].target, player.position];
Move[player, x, y, TRUE]; LOOP};
PlayAction[player, command[i]];
ENDLOOP;
move visitors team
command ← commands[visitors];
FOR i: CARDINAL IN [0..6) DO
player ← @controlGame.player[Add[VQB, i]];
IF controlGame.state IN [offField..setUp] THEN {
IF command[i].action # run THEN LOOP;
[x, y] ← GetTarget[command[i].target, player.position];
Move[player, x, y, TRUE]; LOOP};
PlayAction[player, command[i]];
ENDLOOP;
ENDLOOP;
END;
ballRange: REAL ← 100;
tackle: REAL ← .7;
block: REAL ← 2.5;
zone: REAL ← 10;
PlayAction: PROC[player: Player, command: Command] =
BEGIN
x, y: REAL;
target: Target;
action: Action;
possessor: BOOLEAN;
nearest: Position ← none;
i: Position ← player.position;
possessor ← (TeamOf[i] = controlGame.possessor);
target ← command.target;
action ← command.action;
IF action # block THEN player.blocking ← none;
check for passing or kicking
IF action = pass OR action = kick THEN {
IF controlGame.ballCarrier # i THEN RETURN;
IF action = kick THEN target ← [goal[]];
[x, y] ← GetTarget[target, i];
Pass[player, x, y, action = pass];
RETURN};
try to tackle the ball carrier
IF controlGame.possessor # TeamOf[i] AND controlGame.ballCarrier # none
AND Catch[player, controlGame.ballCarrier, IF Blocked[player] THEN .4 ELSE .7] THEN {
PlayOver[controlGame.player[controlGame.ballCarrier].x];
RETURN};
IF action = stop THEN RETURN;
look for someone to guard or block
IF action = block THEN
WITH t: target SELECT FROM
player => IF possessor THEN {
IF t.position = none THEN t.position ← Nearest[player, player.x, player.y, block];
IF t.position = none THEN RETURN;
IF Catch[player, t.position, 2] THEN player.blocking ← t.position};
relative, absolute, goal => {
nearest: Position ← none;
[x, y] ← GetTarget[target, i];
IF possessor THEN IF ABS[player.x - x] < block AND ABS[player.y - y] < block
THEN nearest ← Nearest[player, x, y, block]
ELSE nearest ← Nearest[player, player.x, player.y, 1];
IF ~possessor THEN nearest ← Nearest[player, x, y, zone];
target ← IF nearest = none THEN [absolute[x, y]] ELSE [player[nearest]];
IF possessor AND nearest # none AND
Catch[player, nearest, 2] THEN player.blocking ← nearest};
ENDCASE;
move the player
[x, y] ← GetTarget[target, i];
WITH t: target SELECT FROM
player => IF t.position = ball
THEN Move[player, x, y, controlGame.ballCarrier = none]
ELSE IF ~possessor AND t.position # controlGame.ballCarrier
THEN Move[player, x, y, FALSE, t.position]
ELSE Move[player, x, y];
goal => Move[player, x, y];
relative, absolute => Move[player, x, y, TRUE];
ENDCASE;
END;
******************************************************************
utility procedures
******************************************************************
inertia: REAL ← 4;
Move: PROC[player: Player, x, y: REAL,
stopping: BOOLEANFALSE, receiver: Position ← none] =
BEGIN
ax, ay: REAL;
rt, speed: REAL;
ax ← x - player.x;
ay ← y - player.y;
speed ← IF TeamOf[player.position] # controlGame.possessor THEN .22 ELSE .2;
accelerate towards the target
rt ← RealFns.SqRt[ax*ax + ay*ay];
stopping ← stopping AND rt < .5;
IF stopping THEN {player.dx ← 0; player.dy ← 0};
IF rt # 0 THEN IF stopping AND rt < speed
THEN {player.dx ← player.dx + ax; player.dy ← player.dy + ay}
ELSE {player.dx ← player.dx + speed*ax/rt; player.dy ← player.dy + speed*ay/rt};
IF receiver # none AND rt < 2 THEN {-- decelerate
player.dx ← player.dx/2; player.dy ← player.dy/2};
there is an upper bound on the velocity
rt ← RealFns.SqRt[player.dx*player.dx + player.dy*player.dy];
IF rt > inertia*speed THEN {
player.dx ← player.dx*inertia*speed/rt;
player.dy ← player.dy*inertia*speed/rt};
ax ← player.x + player.dx/inertia;
ay ← player.y + player.dy/inertia;
IF controlGame.state < option OR ax IN [-11..111] THEN player.x ← ax;
IF controlGame.state < option OR ay IN [-1..fieldWidth+1] THEN player.y ← ay;
IF player.position = controlGame.ballCarrier THEN {
controlGame.player[ball].x ← player.x;
controlGame.player[ball].y ← player.y;
controlGame.player[ball].dx ← player.dx;
controlGame.player[ball].dy ← player.dy};
CheckBlocks[player];
END;
GetTarget: PROCEDURE[target: Target, pos: Position] RETURNS[tx, ty: REAL] =
BEGIN
otherTeam: Team = IF TeamOf[pos] = home THEN visitors ELSE home;
goal: INTEGER = Goal[otherTeam, controlGame.quarter, TRUE];
WITH t: target SELECT FROM
goal => RETURN[goal, controlGame.player[pos].y];
player => IF t.position = ball AND controlGame.ballCarrier = none AND controlGame.state = pass
THEN WITH bt: controlGame.ballCatchable SELECT FROM
absolute => RETURN[bt.x, bt.y]; ENDCASE => ERROR
ELSE {
m: REAL;
p1, p2: Player ← NIL;
p1 ← @controlGame.player[pos];
p2 ← @controlGame.player[t.position];
IF p2.blocking = pos THEN RETURN[p2.x, p2.y];
IF ABS[p2.dx] < .1 AND ABS[p2.dy] < .1 THEN RETURN[p2.x, p2.y];
m ← 2*(p2.dx*(p1.x - p2.x) + p2.dy*(p1.y - p2.y));
IF m <= 0 THEN RETURN[p2.x, p2.y];
m ← ((p1.x - p2.x)*(p1.x - p2.x) + (p1.y - p2.y)*(p1.y - p2.y))/m;
RETURN[p2.x + m*p2.dx, p2.y + m*p2.dy]};
relative => RETURN[controlGame.scrimmage + (IF goal > 50 THEN t.x ELSE -t.x),
fieldWidth/2 + t.y];
absolute => RETURN[t.x, t.y];
ENDCASE => ERROR;
END;
CheckBlocks: PROCEDURE[player: Player] =
INLINE BEGIN -- takes blocking into account
IF controlGame.state < option THEN RETURN;
FOR i: Position IN Players DO
IF i = player.position THEN LOOP;
IF i = controlGame.ballCarrier THEN LOOP;
Block[player, @controlGame.player[i]]
ENDLOOP;
END;
Block: PROCEDURE[player1, player2: Player] =
BEGIN
delta: INTEGER ← 1;
blocked: BOOLEAN ← TRUE;
IF player1.blocking = player2.position THEN delta ← 2;
IF player2.blocking = player1.position THEN delta ← 2;
IF ABS[player1.x - player2.x] > delta THEN RETURN;
IF ABS[player1.y - player2.y] > delta THEN RETURN;
IF (player2.x - player1.x > 0) = (player1.dx < 0) THEN blocked ← FALSE;
IF (player2.y - player1.y > 0) = (player1.dy < 0) THEN blocked ← FALSE;
IF blocked THEN {
IF (player1.x - player2.x > 0) = (player2.dx < 0) THEN blocked ← FALSE;
IF (player1.y - player2.y > 0) = (player2.dy < 0) THEN blocked ← FALSE};
IF ~blocked THEN RETURN;
player1.dx ← player2.dx ← (player1.dx + player2.dx)/2;
player1.dy ← player2.dy ← (player1.dy + player2.dy)/2;
END;
Blocked: PROCEDURE[player: Player] RETURNS[BOOLEAN] =
BEGIN
FOR i: Position IN Players DO
IF TeamOf[i] = TeamOf[player.position] THEN LOOP;
IF controlGame.player[i].blocking = player.position THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
END;
Nearest: PROC[player: Player, x, y: REAL, delta: REAL ← 15]
RETURNS[nearest: Position ← none] =
BEGIN
blocked: Position ← none;
bx, by: REAL ← 100;
nx, ny: REAL ← 100;
CheckNear: PROCEDURE[other: Player] = {
dx, dy: REAL;
IF ABS[other.x - x] > delta THEN RETURN;
IF ABS[other.y - y] > delta THEN RETURN;
IF TeamOf[other.position] = controlGame.offense AND
~CrossLine[other.x, controlGame.scrimmage, controlGame.offense, controlGame.quarter] THEN RETURN;
dx ← ABS[other.x - player.x];
dy ← ABS[other.y - player.y];
IF Blocked[other]
THEN {IF dx < bx AND dy < by THEN {bx ← dx; by ← dy; blocked ← other.position}}
ELSE {IF dx < nx AND dy < ny THEN {nx ← dx; ny ← dy; nearest ← other.position}}};
IF player.position IN HomeTeam
THEN FOR i: Position IN VisitorsTeam DO CheckNear[@controlGame.player[i]]; ENDLOOP
ELSE FOR i: Position IN HomeTeam DO CheckNear[@controlGame.player[i]]; ENDLOOP;
IF nearest = none THEN nearest ← blocked;
END;
Add: PROCEDURE[p: Position, i: CARDINAL] RETURNS[Position] =
INLINE {RETURN[LOOPHOLE[LOOPHOLE[p, CARDINAL]+i]]};
******************************************************************
dealing with the ball
******************************************************************
Pass: PROCEDURE[player: Player, x, y: REAL, pass: BOOLEAN] =
BEGIN
team: Team;
delta, x1, y1: REAL;
check for penalties
team ← TeamOf[player.position];
grounded ← IF pass THEN PossibleGround[player, team] ELSE FALSE;
IF grounded THEN passerX ← player.x;
controlGame.lateral ← FALSE;
IF pass AND controlGame.state = run THEN -- run => ballCarrier crossed line of scrimmage
IF CrossLine[x, player.x, team, controlGame.quarter]
THEN controlGame.penalties[team] ← forwardPass -- illegal forward pass
ELSE controlGame.lateral ← TRUE;
throw the ball
delta ← RealFns.SqRt[(x - player.x)*(x - player.x) + (y - player.y)*(y - player.y)];
IF delta > 50 THEN {
x ← player.x + 50*(x-player.x)/delta;
y ← player.y + 50*(y-player.y)/delta;
delta ← 50};
IF delta < .01 THEN RETURN;
the ball's target is half of the range after [x,y]
controlGame.ballRange ← ballRange/delta;
x1 ← x + controlGame.ballRange*(x - player.x)/(2*delta);
y1 ← y + controlGame.ballRange*(y - player.y)/(2*delta);
controlGame.ballTarget ← [absolute[x1, y1]];
the ball is catchable half of the range before [x,y]
x1 ← x - controlGame.ballRange*(x - player.x)/(2*delta);
y1 ← y - controlGame.ballRange*(y - player.y)/(2*delta);
IF controlGame.ballRange < delta
THEN controlGame.ballCatchable ← [absolute[x1, y1]]
ELSE controlGame.ballCatchable ← [absolute[player.x, player.y]];
controlGame.ballRange ← controlGame.ballRange + 2;
controlGame.ballCarrier ← none;
controlGame.state ← pass;
controlGame.pass ← pass;
END;
PossibleGround: PROCEDURE[player: Player, team: Team] RETURNS[grounded: BOOLEAN] =
BEGIN
grounded ← NOT CrossLine[player.x, controlGame.scrimmage, team, controlGame.quarter];
IF controlGame.possessor # controlGame.offense THEN grounded ← FALSE;
passerX ← player.x;
IF grounded THEN FOR i: Position IN Players DO
IF TeamOf[i] = TeamOf[player.position] THEN LOOP;
IF ABS[controlGame.player[i].x - player.x] > 3 THEN LOOP;
IF ABS[controlGame.player[i].y - player.y] > 3 THEN LOOP;
RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
END;
passerX: REAL;
ballSpeed: REAL ← 1;
grounded: BOOLEANFALSE;
LiveBall: PROC =
BEGIN
x, y: REAL;
time: BasicTime.Pulses;
controlGame.state ← liveBall;
IF controlGame.ballRange = 0 THEN RETURN;
select a random direction
time ← BasicTime.GetClockPulses[];
SELECT time MOD 3 FROM
0 => x ← -1; 1 => x ← 0; ENDCASE => x ← 1;
SELECT time/3 MOD 3 FROM
0 => y ← -1; 1 => y ← 0; ENDCASE => y ← 1;
move the ball in that direction
x ← controlGame.player[ball].x + 50*x/controlGame.ballRange;
y ← controlGame.player[ball].y + 50*y/controlGame.ballRange;
controlGame.ballRange ← 2*controlGame.ballRange/3;
IF controlGame.ballRange < 1 THEN controlGame.ballRange ← 0;
controlGame.ballTarget ← controlGame.ballCatchable ← [absolute[x, y]];
END;
MoveBall: PROC[player: Player, x, y: REAL] =
BEGIN
rt, dx, dy: REAL;
dx ← x - player.x;
dy ← y - player.y;
IF dx = 0 AND dy = 0 THEN RETURN;
rt ← RealFns.SqRt[dx*dx + dy*dy];
player.x ← player.x + (IF rt <= ballSpeed THEN dx ELSE dx*ballSpeed/rt);
player.y ← player.y + (IF rt <= ballSpeed THEN dy ELSE dy*ballSpeed/rt);
IF controlGame.state = pass AND rt < controlGame.ballRange THEN controlGame.state ← catchable;
IF grounded AND controlGame.state = catchable THEN
FOR i: Position IN Players DO
IF TeamOf[i] # controlGame.possessor THEN LOOP;
IF ABS[controlGame.player[i].x - controlGame.player[ball].x] > 4 THEN LOOP;
IF ABS[controlGame.player[i].y - controlGame.player[ball].y] > 4 THEN LOOP;
grounded ← FALSE;
ENDLOOP;
END;
CatchBall: PROC =
BEGIN
delta: REAL ← 10;
receiver, ball: Player;
closest: Position ← none;
IF controlGame.state = pass THEN RETURN;
ball ← @controlGame.player[ball];
FOR i: Position IN Players DO
receiver ← @controlGame.player[i];
IF ~Catch[receiver, ball, 1.1] THEN LOOP;
IF ABS[receiver.x - ball.x] + ABS[receiver.y - ball.y] < delta THEN {
delta ← ABS[receiver.x - ball.x] + ABS[receiver.y - ball.y]; closest ← i};
ENDLOOP;
IF closest = none THEN RETURN;
IF controlGame.state # option AND ~controlGame.pass AND TeamOf[closest] = controlGame.offense
THEN {PlayOver[controlGame.player[closest].x, TRUE]; RETURN}; -- grounding a punt
IF controlGame.state # option THEN controlGame.state ← run;
controlGame.ballCarrier ← closest;
controlGame.possessor ← TeamOf[closest];
IF controlGame.possessor # controlGame.offense THEN controlGame.state ← turnover;
END;
Catch: PROCEDURE[catcher: Player, catchee: Position, delta: REAL ← 1] RETURNS[BOOLEAN] =
BEGIN
x, y: REAL;
IF catchee = ball THEN catchee ← controlGame.ballCarrier;
IF ABS[catcher.y - controlGame.player[catchee].y] > delta THEN RETURN[FALSE];
IF ABS[catcher.x - controlGame.player[catchee].x] > delta THEN RETURN[FALSE];
IF catchee = ball AND Blocked[catcher] THEN RETURN[FALSE];
IF catchee = ball THEN { -- has to be moving toward the receiver
[x, y] ← GetTarget[controlGame.ballTarget, none];
IF x = controlGame.player[ball].x AND y = controlGame.player[ball].y THEN RETURN[TRUE];
IF x < catcher.x AND catcher.x < controlGame.player[ball].x THEN RETURN[TRUE];
IF x > catcher.x AND catcher.x > controlGame.player[ball].x THEN RETURN[TRUE];
IF y < catcher.y AND catcher.y < controlGame.player[ball].y THEN RETURN[TRUE];
IF y > catcher.y AND catcher.y > controlGame.player[ball].y THEN RETURN[TRUE];
RETURN[FALSE]};
RETURN[TRUE];
END;
******************************************************************
end of play computations
******************************************************************
PlayOver: PROCEDURE[scrimmage: REAL, stopClock: BOOLEANFALSE] =
BEGIN
controlGame.clockStopped ← stopClock;
check for incomplete pass, penalties, and scoring
IF controlGame.ballCarrier = none AND controlGame.pass AND ~controlGame.lateral
THEN scrimmage ← controlGame.scrimmage;
scrimmage ← HandlePenalties[scrimmage];
IF EndZone[scrimmage] THEN Score[scrimmage];
did the gun go off?
IF controlGame.clock <= 0 THEN {
controlGame.clock ← 15*60;
controlGame.clockStopped ← TRUE;
controlGame.quarter ← controlGame.quarter + 1;
IF controlGame.quarter = 3 OR controlGame.quarter = 5
THEN {OffField[]; controlGame.timeouts ← [4, 4]; RETURN};
scrimmage ← 100 - scrimmage;
controlGame.scrimmage ← 100 - controlGame.scrimmage;
controlGame.firstDownMarker ← 100 - controlGame.firstDownMarker};
IF EndZone[scrimmage] THEN {Wait[60]; KickOff[Opponent[controlGame.possessor]]; RETURN};
was there a turnover on the play?
controlGame.scrimmage ← scrimmage;
IF controlGame.possessor # controlGame.offense THEN {
controlGame.offense ← controlGame.possessor;
FirstDown[]};
did they make the critical first down?
IF CrossLine[scrimmage, controlGame.firstDownMarker,
controlGame.offense, controlGame.quarter] THEN FirstDown[];
IF controlGame.down = 5 THEN {
controlGame.offense ← Opponent[controlGame.offense];
controlGame.possessor ← controlGame.offense;
FirstDown[]};
Wait[60]; Huddle[];
END;
HandlePenalties: PROCEDURE[scrimmage: REAL]
RETURNS[REAL] =
BEGIN
IF grounded AND controlGame.possessor # controlGame.offense THEN grounded ← FALSE; -- interception
IF grounded THEN {controlGame.penalties[controlGame.offense] ← grounding; grounded ← FALSE};
double penalties
IF controlGame.penalties[home] = offSides AND controlGame.penalties[visitors] = offSides THEN
{controlGame.penaltyState ← canceled; RETURN[controlGame.scrimmage]};
IF controlGame.penalties[home] # none AND controlGame.penalties[visitors] # none THEN
{controlGame.down ← controlGame.down + 1; controlGame.penaltyState ← canceled; RETURN[controlGame.scrimmage]};
single penalties
FOR team: Team IN Team DO
SELECT controlGame.penalties[team] FROM
none => NULL;
offSides => RETURN[SetPenalty[team, 5]];
delayOfGame => RETURN[IF Accepted[] THEN SetPenalty[team, 5] ELSE scrimmage];
forwardPass => IF Accepted[] THEN RETURN[SetPenalty[team, 15]];
grounding => IF Accepted[] THEN RETURN[SetPenalty[team, 15]];
ENDCASE => IF Accepted[] THEN RETURN[SetPenalty[team, 5]];
ENDLOOP;
controlGame.down ← controlGame.down + 1;
RETURN[scrimmage];
END;
Accepted: PROCEDURE RETURNS[BOOLEAN] =
BEGIN
controlGame.penaltyState ← asking;
WHILE controlGame.penaltyState = asking DO Wait[10]; ENDLOOP;
RETURN[controlGame.penaltyState = accepted];
END;
SetPenalty: PROCEDURE[team: Team, yards: REAL] RETURNS[line: REAL] =
BEGIN
controlGame.penaltyState ← accepted;
IF Direction[team, controlGame.quarter] = 1
THEN IF controlGame.scrimmage <= yards
THEN RETURN[(controlGame.scrimmage+1)/2]
ELSE RETURN[controlGame.scrimmage - yards]
ELSE IF (100 - controlGame.scrimmage) <= yards
THEN RETURN[(100 + controlGame.scrimmage)/2]
ELSE RETURN[controlGame.scrimmage + yards];
END;
Score: PROCEDURE[scrimmage: REAL] =
BEGIN
goal: INTEGER;
opponent: Team;
controlGame.clockStopped ← TRUE;
goal ← Goal[controlGame.possessor, controlGame.quarter];
opponent ← Opponent[controlGame.possessor];
IF controlGame.ballCarrier = none THEN { -- check for field goal
IF scrimmage IN [-9..109] THEN RETURN;
IF controlGame.player[ball].y NOT IN [fieldWidth/3..2*fieldWidth/3] THEN RETURN};
SELECT TRUE FROM
controlGame.ballCarrier = none AND goal > 50 AND scrimmage < 50 =>
controlGame.score[controlGame.possessor] ← controlGame.score[controlGame.possessor] + 3;
controlGame.ballCarrier = none AND goal < 50 AND scrimmage > 50 =>
controlGame.score[controlGame.possessor] ← controlGame.score[controlGame.possessor] + 3;
goal > 50 AND scrimmage < 50 =>
controlGame.score[controlGame.possessor] ← controlGame.score[controlGame.possessor] + 7;
goal < 50 AND scrimmage > 50 =>
controlGame.score[controlGame.possessor] ← controlGame.score[controlGame.possessor] + 7;
goal > 50 AND scrimmage > 50 =>
controlGame.score[opponent] ← controlGame.score[opponent] + 2;
goal < 50 AND scrimmage < 50 =>
controlGame.score[opponent] ← controlGame.score[opponent] + 2;
ENDCASE;
END;
******************************************************************
simple plays
******************************************************************
SetOffField: PROC =
BEGIN
player: Player;
command: ARRAY [0..6) OF Command;
command ← offField1;
FOR i: CARDINAL IN [0..6) DO
player ← @controlGame.player[Add[HQB, i]];
WITH t: command[i].target SELECT FROM
absolute => {player.x ← t.x; player.y ← t.y};
ENDCASE => ERROR;
ENDLOOP;
command ← offField2;
FOR i: CARDINAL IN [0..6) DO
player ← @controlGame.player[Add[VQB, i]];
WITH t: command[i].target SELECT FROM
absolute => {player.x ← t.x; player.y ← t.y};
ENDCASE => ERROR;
ENDLOOP;
OffField[];
END;
offField1: Commands = [
[run, [absolute[23, 55]]], [run, [absolute[26, 55]]], [run, [absolute[29, 55]]],
[run, [absolute[32, 55]]], [run, [absolute[35, 55]]], [run, [absolute[38, 55]]]];
offField2: Commands = [
[run, [absolute[77, -5]]], [run, [absolute[74, -5]]], [run, [absolute[71, -5]]],
[run, [absolute[68, -5]]], [run, [absolute[65, -5]]], [run, [absolute[62, -5]]]];
Init[];
END . . .