FootballControl.mesa
Last Edited by: Maxwell, March 15, 1983 10:21 am
DIRECTORY
FootballInternal,
FootballMaster,
FootballMasterRpcControl USING [ExportInterface, UnexportInterface],
Process USING [SetTimeout],
RealFns USING [SqRt],
Rope USING [ROPE],
RPC USING [MakeKey],
System USING [GetGreenwichMeanTime, GreenwichMeanTime, Pulses, GetClockPulses],
UserExec USING [GetNameAndPassword];
FootballControl: MONITOR
IMPORTS FootballMaster, FootballMasterRpcControl, Process, RealFns, RPC, System, UserExec
EXPORTS FootballInternal, FootballMaster =
BEGIN OPEN FootballMaster;
******************************************************************
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] ← UserExec.GetNameAndPassword[];
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
game ← NEW[GameRec ← []];
FOR i: Position IN Players DO
game.player[i].position ← i;
ENDLOOP;
END;
******************************************************************
Control procedures
******************************************************************
game: 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[game^];
END;
OffField: PROCEDURE =
BEGIN
game.state ← offField;
game.ballCarrier ← none;
game.player[ball].x ← -100;
game.player[ball].y ← -100;
game.ballTarget ← [absolute[-100, -100]];
game.clockStopped ← TRUE;
END;
KickOff: PUBLIC PROCEDURE[team: Team] =
BEGIN
IF game.quarter = 5 THEN {
game.quarter ← 1;
game.score ← [0, 0]};
IF game.quarter = 1 AND game.clock = 15*60 THEN team ← home;
IF game.quarter = 3 AND game.clock = 15*60 THEN team ← visitors;
game.offense ← game.possessor ← team;
IF Goal[team, game.quarter] < 50
THEN game.scrimmage ← 20
ELSE game.scrimmage ← 80;
FirstDown[];
Huddle[];
END;
Huddle: PROCEDURE =
BEGIN
FOR i: Position IN Players DO
game.player[i].dx ← 0;
game.player[i].dy ← 0;
ENDLOOP;
game.player[ball].x ← game.scrimmage;
game.player[ball].y ← fieldWidth/2;
game.ballCarrier ← none;
game.ballTarget ← [absolute[game.scrimmage, fieldWidth/2]];
game.state ← huddle;
game.pass ← FALSE;
game.lateral ← FALSE;
delay ← 1;
startHuddle ← game.clock;
END;
SetUp: PUBLIC PROCEDURE[team: Team] =
BEGIN
IF game.state = offField THEN KickOff[team];
game.state ← setUp;
END;
Hike: PUBLIC PROCEDURE[team: Team] =
BEGIN
hike: REAL;
count: NAT ← 0;
IF team # game.offense THEN RETURN;
IF ~Ready[home] OR ~Ready[visitors] THEN RETURN;
delay ← IF slow THEN 5 ELSE 1;
game.penalties ← [none, none];
game.penaltyState ← none;
game.clockStopped ← FALSE;
hike ← game.scrimmage - Direction[game.offense, game.quarter]*15;
move the ball back so that the center won't catch it
game.player[ball].x ← game.scrimmage - Direction[game.offense, game.quarter]*1.1;
game.ballTarget ← [absolute[hike, fieldWidth/2]];
game.ballCarrier ← ball;
check for offSides and illegal formations
FOR i: Position IN Players DO
game.player[i].blocking ← none; -- reset
IF TeamOf[i] = game.offense AND InMotion[@game.player[i]] THEN IF count = 0
THEN count ← count + 1
ELSE game.penalties[game.offense] ← formation;
IF CrossLine[game.player[i].x, game.scrimmage, TeamOf[i], game.quarter]
THEN game.penalties[TeamOf[i]] ← offSides;
ENDLOOP;
game.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[game.player[i].x - game.scrimmage] < 2 THEN count ← count + 1;
ENDLOOP;
RETURN[count > 2];
END;
FirstDown: PROC =
BEGIN
game.down ← 1;
IF Goal[game.offense, game.quarter] < 50
THEN game.firstDownMarker ← MIN[100, game.scrimmage + 10]
ELSE game.firstDownMarker ← MAX[0, game.scrimmage - 10];
END;
TimeOut: PUBLIC PROCEDURE[team: Team] =
BEGIN
IF game.timeouts[team] = 0 THEN RETURN;
IF game.state > option THEN RETURN;
IF game.clockStopped THEN RETURN;
game.clockStopped ← TRUE;
game.timeouts[team] ← game.timeouts[team] - 1;
END;
PenaltyResponse: PUBLIC PROCEDURE[team: Team, accepted: BOOLEAN] =
BEGIN
IF game.penalties[Opponent[team]] = none THEN RETURN;
IF game.penaltyState # asking THEN RETURN;
game.penaltyState ← IF accepted THEN accepted ELSE declined;
END;
DisableDelayOfGame: PUBLIC PROCEDURE = {delayOfGame ← FALSE};
******************************************************************
Main Loop
******************************************************************
timer: CONDITION;
delay: CARDINAL ← 1;
stopped: BOOLEANTRUE;
gmt: System.GreenwichMeanTime ← [0];
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 ~game.clockStopped AND game.quarter # 5 THEN
IF System.GetGreenwichMeanTime[] # gmt THEN {
game.clock ← game.clock - 1;
gmt ← System.GetGreenwichMeanTime[]};
IF game.clock <= 0 AND game.state < option THEN PlayOver[game.scrimmage];
IF delayOfGame AND game.state < option AND startHuddle - game.clock > 60 THEN {
SELECT TRUE FROM
~Ready[home] AND ~Ready[visitors] => {startHuddle ← game.clock; LOOP};
~Ready[home] => game.penalties[home] ← delayOfGame;
~Ready[visitors] => game.penalties[visitors] ← delayOfGame;
ENDCASE => game.penalties[game.offense] ← delayOfGame;
PlayOver[game.scrimmage, TRUE]};
IF (game.penalties[home] = offSides OR game.penalties[visitors] = offSides)
AND game.state > setUp THEN PlayOver[game.scrimmage, TRUE];
is the ball now out of play?
x ← game.player[ball].x;
y ← game.player[ball].y;
IF game.state > setUp AND
(x NOT IN [-10..110] OR y NOT IN [0..fieldWidth]) THEN PlayOver[x, TRUE];
IF game.state > setUp AND game.ballCarrier # ball AND
Direction[TeamOf[game.ballCarrier], game.quarter] = 1 AND x > 100
THEN PlayOver[101, TRUE];
IF game.state > setUp AND game.ballCarrier # ball AND
Direction[TeamOf[game.ballCarrier], game.quarter] = -1 AND x < 0
THEN PlayOver[-1, TRUE];
move the ball
IF game.state > setUp AND game.ballCarrier = none THEN {
[x, y] ← GetTarget[game.ballTarget, none];
MoveBall[@game.player[ball], x, y]; CatchBall[];
IF game.player[ball].x = x AND game.player[ball].y = y THEN -- ball has hit the ground
IF game.pass AND ~game.lateral
THEN PlayOver[game.scrimmage, TRUE]
ELSE game.state ← liveBall};
are we committed to a run?
IF game.state = option AND
CrossLine[x, game.scrimmage, game.offense, game.quarter]
THEN game.state ← run;
move home team
command ← commands[home];
FOR i: CARDINAL IN [0..6) DO
player ← @game.player[Add[HQB, i]];
IF game.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 ← @game.player[Add[VQB, i]];
IF game.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] = game.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 game.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 game.possessor # TeamOf[i] AND game.ballCarrier # none
AND Catch[player, game.ballCarrier, IF Blocked[player] THEN .4 ELSE .7] THEN {
PlayOver[game.player[game.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, game.ballCarrier = none]
ELSE IF ~possessor AND t.position # game.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] # game.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 game.state < option OR ax IN [-11..111] THEN player.x ← ax;
IF game.state < option OR ay IN [-1..fieldWidth+1] THEN player.y ← ay;
IF player.position = game.ballCarrier THEN {
game.player[ball].x ← player.x;
game.player[ball].y ← player.y;
game.player[ball].dx ← player.dx;
game.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, game.quarter, TRUE];
WITH t: target SELECT FROM
goal => RETURN[goal, game.player[pos].y];
player => IF t.position = ball AND game.ballCarrier = none AND game.state = pass
THEN WITH bt: game.ballCatchable SELECT FROM
absolute => RETURN[bt.x, bt.y]; ENDCASE => ERROR
ELSE {
m: REAL;
p1, p2: Player ← NIL;
p1 ← @game.player[pos];
p2 ← @game.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[game.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 game.state < option THEN RETURN;
FOR i: Position IN Players DO
IF i = player.position THEN LOOP;
IF i = game.ballCarrier THEN LOOP;
Block[player, @game.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 game.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] = game.offense AND
~CrossLine[other.x, game.scrimmage, game.offense, game.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[@game.player[i]]; ENDLOOP
ELSE FOR i: Position IN HomeTeam DO CheckNear[@game.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;
game.lateral ← FALSE;
IF pass AND game.state = run THEN -- run => ballCarrier crossed line of scrimmage
IF CrossLine[x, player.x, team, game.quarter]
THEN game.penalties[team] ← forwardPass -- illegal forward pass
ELSE game.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]
game.ballRange ← ballRange/delta;
x1 ← x + game.ballRange*(x - player.x)/(2*delta);
y1 ← y + game.ballRange*(y - player.y)/(2*delta);
game.ballTarget ← [absolute[x1, y1]];
the ball is catchable half of the range before [x,y]
x1 ← x - game.ballRange*(x - player.x)/(2*delta);
y1 ← y - game.ballRange*(y - player.y)/(2*delta);
IF game.ballRange < delta
THEN game.ballCatchable ← [absolute[x1, y1]]
ELSE game.ballCatchable ← [absolute[player.x, player.y]];
game.ballRange ← game.ballRange + 2;
game.ballCarrier ← none;
game.state ← pass;
game.pass ← pass;
END;
PossibleGround: PROCEDURE[player: Player, team: Team] RETURNS[grounded: BOOLEAN] =
BEGIN
grounded ← NOT CrossLine[player.x, game.scrimmage, team, game.quarter];
IF game.possessor # game.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[game.player[i].x - player.x] > 3 THEN LOOP;
IF ABS[game.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: System.Pulses;
game.state ← liveBall;
IF game.ballRange = 0 THEN RETURN;
select a random direction
time ← System.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 ← game.player[ball].x + 50*x/game.ballRange;
y ← game.player[ball].y + 50*y/game.ballRange;
game.ballRange ← 2*game.ballRange/3;
IF game.ballRange < 1 THEN game.ballRange ← 0;
game.ballTarget ← game.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 game.state = pass AND rt < game.ballRange THEN game.state ← catchable;
IF grounded AND game.state = catchable THEN
FOR i: Position IN Players DO
IF TeamOf[i] # game.possessor THEN LOOP;
IF ABS[game.player[i].x - game.player[ball].x] > 4 THEN LOOP;
IF ABS[game.player[i].y - game.player[ball].y] > 4 THEN LOOP;
grounded ← FALSE;
ENDLOOP;
END;
CatchBall: PROC =
BEGIN
delta: REAL ← 10;
receiver, ball: Player;
closest: Position ← none;
IF game.state = pass THEN RETURN;
ball ← @game.player[ball];
FOR i: Position IN Players DO
receiver ← @game.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 game.state # option AND ~game.pass AND TeamOf[closest] = game.offense
THEN {PlayOver[game.player[closest].x, TRUE]; RETURN}; -- grounding a punt
IF game.state # option THEN game.state ← run;
game.ballCarrier ← closest;
game.possessor ← TeamOf[closest];
IF game.possessor # game.offense THEN game.state ← turnover;
END;
Catch: PROCEDURE[catcher: Player, catchee: Position, delta: REAL ← 1] RETURNS[BOOLEAN] =
BEGIN
x, y: REAL;
IF catchee = ball THEN catchee ← game.ballCarrier;
IF ABS[catcher.y - game.player[catchee].y] > delta THEN RETURN[FALSE];
IF ABS[catcher.x - game.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[game.ballTarget, none];
IF x = game.player[ball].x AND y = game.player[ball].y THEN RETURN[TRUE];
IF x < catcher.x AND catcher.x < game.player[ball].x THEN RETURN[TRUE];
IF x > catcher.x AND catcher.x > game.player[ball].x THEN RETURN[TRUE];
IF y < catcher.y AND catcher.y < game.player[ball].y THEN RETURN[TRUE];
IF y > catcher.y AND catcher.y > game.player[ball].y THEN RETURN[TRUE];
RETURN[FALSE]};
RETURN[TRUE];
END;
******************************************************************
end of play computations
******************************************************************
PlayOver: PROCEDURE[scrimmage: REAL, stopClock: BOOLEANFALSE] =
BEGIN
game.clockStopped ← stopClock;
check for incomplete pass, penalties, and scoring
IF game.ballCarrier = none AND game.pass AND ~game.lateral
THEN scrimmage ← game.scrimmage;
scrimmage ← HandlePenalties[scrimmage];
IF EndZone[scrimmage] THEN Score[scrimmage];
did the gun go off?
IF game.clock <= 0 THEN {
game.clock ← 15*60;
game.clockStopped ← TRUE;
game.quarter ← game.quarter + 1;
IF game.quarter = 3 OR game.quarter = 5
THEN {OffField[]; game.timeouts ← [4, 4]; RETURN};
scrimmage ← 100 - scrimmage;
game.scrimmage ← 100 - game.scrimmage;
game.firstDownMarker ← 100 - game.firstDownMarker};
IF EndZone[scrimmage] THEN {Wait[60]; KickOff[Opponent[game.possessor]]; RETURN};
was there a turnover on the play?
game.scrimmage ← scrimmage;
IF game.possessor # game.offense THEN {
game.offense ← game.possessor;
FirstDown[]};
did they make the critical first down?
IF CrossLine[scrimmage, game.firstDownMarker,
game.offense, game.quarter] THEN FirstDown[];
IF game.down = 5 THEN {
game.offense ← Opponent[game.offense];
game.possessor ← game.offense;
FirstDown[]};
Wait[60]; Huddle[];
END;
HandlePenalties: PROCEDURE[scrimmage: REAL]
RETURNS[REAL] =
BEGIN
IF grounded AND game.possessor # game.offense THEN grounded ← FALSE; -- interception
IF grounded THEN {game.penalties[game.offense] ← grounding; grounded ← FALSE};
double penalties
IF game.penalties[home] = offSides AND game.penalties[visitors] = offSides THEN
{game.penaltyState ← canceled; RETURN[game.scrimmage]};
IF game.penalties[home] # none AND game.penalties[visitors] # none THEN
{game.down ← game.down + 1; game.penaltyState ← canceled; RETURN[game.scrimmage]};
single penalties
FOR team: Team IN Team DO
SELECT game.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;
game.down ← game.down + 1;
RETURN[scrimmage];
END;
Accepted: PROCEDURE RETURNS[BOOLEAN] =
BEGIN
game.penaltyState ← asking;
WHILE game.penaltyState = asking DO Wait[10]; ENDLOOP;
RETURN[game.penaltyState = accepted];
END;
SetPenalty: PROCEDURE[team: Team, yards: REAL] RETURNS[line: REAL] =
BEGIN
game.penaltyState ← accepted;
IF Direction[team, game.quarter] = 1
THEN IF game.scrimmage <= yards
THEN RETURN[(game.scrimmage+1)/2]
ELSE RETURN[game.scrimmage - yards]
ELSE IF (100 - game.scrimmage) <= yards
THEN RETURN[(100 + game.scrimmage)/2]
ELSE RETURN[game.scrimmage + yards];
END;
Score: PROCEDURE[scrimmage: REAL] =
BEGIN
goal: INTEGER;
opponent: Team;
game.clockStopped ← TRUE;
goal ← Goal[game.possessor, game.quarter];
opponent ← Opponent[game.possessor];
IF game.ballCarrier = none THEN { -- check for field goal
IF scrimmage IN [-9..109] THEN RETURN;
IF game.player[ball].y NOT IN [fieldWidth/3..2*fieldWidth/3] THEN RETURN};
SELECT TRUE FROM
game.ballCarrier = none AND goal > 50 AND scrimmage < 50 =>
game.score[game.possessor] ← game.score[game.possessor] + 3;
game.ballCarrier = none AND goal < 50 AND scrimmage > 50 =>
game.score[game.possessor] ← game.score[game.possessor] + 3;
goal > 50 AND scrimmage < 50 =>
game.score[game.possessor] ← game.score[game.possessor] + 7;
goal < 50 AND scrimmage > 50 =>
game.score[game.possessor] ← game.score[game.possessor] + 7;
goal > 50 AND scrimmage > 50 =>
game.score[opponent] ← game.score[opponent] + 2;
goal < 50 AND scrimmage < 50 =>
game.score[opponent] ← game.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 ← @game.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 ← @game.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 . . .