FootballControl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Maxwell, March 15, 1983 10:21 am
Spreitzer, May 1, 1984 10:58:28 pm PDT
Russ Atkinson (RRA) August 14, 1985 4:28:01 pm PDT
DIRECTORY
BasicTime USING [GMT, Now, nullGMT],
FootballInternal,
FootballMaster,
FootballMasterRpcControl USING [ExportInterface, UnexportInterface],
Process USING [SetTimeout],
Random USING [Create, ChooseInt, RandomStream],
RealFns USING [SqRt],
Rope USING [ROPE],
RPC USING [MakeKey],
UserCredentials USING [Get];
FootballControl: MONITOR
IMPORTS BasicTime, FootballMaster, FootballMasterRpcControl, Process, Random, RealFns, RPC, UserCredentials
EXPORTS FootballInternal, FootballMaster = {
OPEN FootballMaster;
StupidFuckingWarnings: PUBLIC SIGNAL [itsGottaHaveAGoddamName: ATOM] = CODE;
RPC procedure
process: PROCESSNIL;
StartServer: PUBLIC ENTRY PROC [name: Rope.ROPE] = {
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]];
};
StopServer: PUBLIC ENTRY PROC = {
IF stopped = TRUE OR process = NIL THEN RETURN ELSE stopped ← TRUE;
WHILE process # NIL DO WAIT timer; ENDLOOP;
FootballMasterRpcControl.UnexportInterface[];
};
Init: PROC = {
controlGame ← NEW[GameRec ← []];
FOR i: Position IN Players DO
controlGame.player[i].position ← i;
ENDLOOP;
};
Control procedures
controlGame: Game;
slow: BOOLFALSE;
commands: ARRAY Team OF Commands ← [ALL[[stop, [null[]]]], ALL[[stop, [null[]]]]];
SetCommands: PUBLIC PROC [team: Team, c: Commands] RETURNS [GameRec] = {
commands[team] ← c;
RETURN [controlGame^];
};
OffField: PROC = {
controlGame.state ← offField;
controlGame.ballCarrier ← none;
controlGame.player[ball].x ← -100;
controlGame.player[ball].y ← -100;
controlGame.ballTarget ← [absolute[-100, -100]];
controlGame.clockStopped ← TRUE;
};
KickOff: PUBLIC PROC [team: Team] = {
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[];
};
Huddle: PROC = {
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;
};
SetUp: PUBLIC PROC [team: Team] = {
IF controlGame.state = offField THEN KickOff[team];
controlGame.state ← setUp;
};
Hike: PUBLIC PROC [team: Team] = {
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
player: Player = @controlGame.player[i];
team: Team = TeamOf[i];
player.blocking ← none; -- reset
SELECT TRUE FROM
team # controlGame.offense => {
no check for defense in motion
};
ABS[player.dx]+ABS[player.dy] < 1.0 => {
Not really in motion
};
count = 0 => count ← 1;
ENDCASE => controlGame.penalties[controlGame.offense] ← formation;
IF CrossLine[player.x, controlGame.scrimmage, team, controlGame.quarter]
THEN controlGame.penalties[team] ← offSides;
ENDLOOP;
controlGame.state ← option;
};
Ready: PROC [team: Team] RETURNS [BOOL] = {
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];
};
FirstDown: PROC = {
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];
};
TimeOut: PUBLIC PROC [team: Team] = {
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;
};
PenaltyResponse: PUBLIC PROC [team: Team, accepted: BOOL] = {
IF controlGame.penalties[Opponent[team]] = none THEN RETURN;
IF controlGame.penaltyState # asking THEN RETURN;
controlGame.penaltyState ← IF accepted THEN accepted ELSE declined;
};
DisableDelayOfGame: PUBLIC PROC = {delayOfGame ← FALSE};
Main Loop
timer: CONDITION;
delay: CARDINAL ← 1;
stopped: BOOLTRUE;
gmt: BasicTime.GMT ← BasicTime.nullGMT;
startHuddle: INTEGER ← 0;
delayOfGame: BOOLTRUE;
Wait: ENTRY PROC [i: CARDINAL] ={
DO IF i = 0 THEN EXIT ELSE i ← i - 1; WAIT timer; ENDLOOP;
};
Main: PROC = {
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 {
readyHome: BOOL = Ready[home];
readyVisitors: BOOL = Ready[visitors];
SELECT TRUE FROM
~readyHome AND ~readyVisitors => {startHuddle ← controlGame.clock; LOOP};
~readyHome => controlGame.penalties[home] ← delayOfGame;
~readyVisitors => 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;
};
ballRange: REAL ← 100;
tackle: REAL ← .7;
block: REAL ← 2.5;
zone: REAL ← 10;
PlayAction: PROC [player: Player, command: Command] = {
x, y: REAL;
nearest: Position ← none;
i: Position ← player.position;
possessor: BOOL ← (TeamOf[i] = controlGame.possessor);
target: Target ← command.target;
action: 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;
};
Utility procedures
inertia: REAL ← 4;
tipsiness: REAL ← 0.01;
Move: PROC [player: Player, x, y: REAL, stopping: BOOLFALSE, receiver: Position ← none] = {
ax: REAL ← x - player.x;
ay: REAL ← y - player.y;
teamHasBall: BOOL ← TeamOf[player.position] = controlGame.possessor;
speed: REALIF teamHasBall THEN .20 ELSE .22;
speedProd: REAL ← inertia*speed;
rt: REAL ← RealFns.SqRt[ax*ax + ay*ay];
accelerate towards the target
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 NOT teamHasBall THEN {
If one is being blocked then one cannot move as fast
FOR i: Position IN Players DO
otherGuy: Player ← @controlGame.player[i];
IF TeamOf[i] = TeamOf[player.position] THEN LOOP;
IF otherGuy.blocking = player.position THEN {
IF DistSquared[otherGuy, player] <= 1.0 THEN {
We are close enough, but this player only can be blocked if blocker is closer to the ball than we are.
ball: Player ← @controlGame.player[ball];
IF DistSquared[otherGuy, ball] < DistSquared[player, ball] THEN {
speedProd ← speedProd * 0.5;
EXIT;
};
};
};
ENDLOOP;
};
IF rt > speedProd THEN {
temp: REAL = speedProd/rt;
player.dx ← player.dx*temp;
player.dy ← player.dy*temp;
};
{
Now update the position, allowing for a little tipsiness
tip: REALIF controlGame.state < option THEN 0.0 ELSE tipsiness;
cx: REAL ← (Random.ChooseInt[rs, 0, 10] - 5) * tip;
cy: REAL ← (Random.ChooseInt[rs, 0, 10] - 5) * tip;
ax ← player.x + player.dx/inertia + cx;
ay ← player.y + player.dy/inertia + cy;
};
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};
IF controlGame.state >= option THEN
take blocking into account
FOR i: Position IN Players DO
IF i = player.position THEN LOOP;
IF i = controlGame.ballCarrier THEN LOOP;
Block[player, @controlGame.player[i]]
ENDLOOP;
};
DistSquared: PROC [player1, player2: Player] RETURNS [REAL] = {
dx: REAL ← player2.x-player1.x;
dy: REAL ← player2.y-player1.y;
RETURN [dx*dx + dy*dy];
};
GetTarget: PROC [target: Target, pos: Position] RETURNS [tx, ty: REAL] = {
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;
};
Block: PROC [player1, player2: Player] = {
delta: INTEGER ← 1;
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;
player1.dx ← player2.dx ← (player1.dx + player2.dx)/2;
player1.dy ← player2.dy ← (player1.dy + player2.dy)/2;
};
Blocked: PROC [player: Player] RETURNS [BOOL] = {
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];
};
Nearest: PROC [player: Player, x, y: REAL, delta: REAL] RETURNS [nearest: Position ← none] = {
blocked: Position ← none;
bx, by: REAL ← 100;
nx, ny: REAL ← 100;
CheckNear: PROC [other: Player] = {
dx: REALABS[other.x - x];
dy: REALABS[other.y - y];
IF dx > delta THEN RETURN;
IF dy > 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;
};
Add: PROC [p: Position, i: CARDINAL] RETURNS [Position] = INLINE {
RETURN [LOOPHOLE[LOOPHOLE[p, CARDINAL]+i]];
};
Dealing with the ball
Pass: PROC [player: Player, x, y: REAL, pass: BOOL] = {
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
x1 ← x - player.x;
y1 ← y - player.y;
delta ← RealFns.SqRt[x1*x1 + y1*y1];
IF delta > 50 THEN {
d50: REAL = 50/delta;
x ← player.x + x1*d50;
y ← player.y + y1*d50;
delta ← 50;
};
IF delta > .01 THEN {
the ball's target is half of the range after [x,y] (but allow a little error)
temp: REAL = controlGame.ballRange/(2*delta);
rem: REAL ← delta;
add in random errors based on the range (more range, more error)
WHILE rem > 10 DO
nx,ny: REAL;
[nx,ny] ← RandomDelta[ballRandom];
x ← x + nx;
y ← y + ny;
rem ← rem - 10;
ENDLOOP;
controlGame.ballRange ← ballRange/delta;
x1 ← x + temp*(x - player.x);
y1 ← y + temp*(y - player.y);
controlGame.ballTarget ← [absolute[x1, y1]];
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;
};
};
PossibleGround: PROC [player: Player, team: Team] RETURNS [grounded: BOOL] = {
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];
};
passerX: REAL;
ballSpeed: REAL ← 1;
ballRandom: REAL ← 0.1;
grounded: BOOLFALSE;
RandomDelta: PROC [scale: REAL] RETURNS [x,y: REAL ← 0.0] = {
Now update the position, allowing for a little tipsiness
x ← (Random.ChooseInt[rs, 0, 10] - 5) * scale;
y ← (Random.ChooseInt[rs, 0, 10] - 5) * scale;
};
LiveBall: PROC = {
controlGame.state ← liveBall;
IF controlGame.ballRange # 0 THEN {
x, y: REAL;
scale: REAL = 5/controlGame.ballRange;
select a random direction
[x,y] ← RandomDelta[scale];
move the ball in that direction
x ← controlGame.player[ball].x + scale*x;
y ← controlGame.player[ball].y + scale*y;
controlGame.ballRange ← 2*controlGame.ballRange/3;
IF controlGame.ballRange < 1 THEN controlGame.ballRange ← 0;
controlGame.ballTarget ← controlGame.ballCatchable ← [absolute[x, y]];
};
};
MoveBall: PROC [player: Player, x, y: REAL] = {
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;
};
CatchBall: PROC = {
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
controlGame.ballCarrier ← closest;
controlGame.possessor ← TeamOf[closest];
IF controlGame.possessor # controlGame.offense THEN controlGame.state ← turnover;
};
Catch: PROC [catcher: Player, catchee: Position, delta: REAL ← 1] RETURNS [BOOL] = {
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 of play computations
PlayOver: PROC [scrimmage: REAL, stopClock: BOOLFALSE] = {
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[];
};
HandlePenalties: PROC [scrimmage: REAL] RETURNS [REAL] = {
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];
};
Accepted: PROC RETURNS [BOOL] = {
controlGame.penaltyState ← asking;
WHILE controlGame.penaltyState = asking DO Wait[10]; ENDLOOP;
RETURN [controlGame.penaltyState = accepted];
};
SetPenalty: PROC [team: Team, yards: REAL] RETURNS [line: REAL] = {
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];
};
Score: PROC [scrimmage: REAL] = {
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;
};
Simple plays
SetOffField: PROC = {
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[];
};
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]]]];
rs: Random.RandomStream ← Random.Create[];
Init[];
} . . .