<> <> <> <> <> DIRECTORY Commander USING [Handle, CommandProc, Register], Convert USING [RopeFromInt], FootballInternal, FootballMaster, FootballMasterRpcControl USING [ImportInterface], FS USING [ExpandName], Graphics USING [black, Box, Context, DrawBox, DrawChar, DrawRope, DrawTo, GetBounds, SetColor, SetCP, SetPaintMode, SetStipple, Translate, white], GraphicsColor USING [green], InputFocus USING [SetInputFocus], IO USING [Close, RopeFromROS, GetTokenRope, int, PutF, RIS, ROS, STREAM, EndOfStream], Menus USING [ClickProc, CreateEntry, CreateMenu, InsertMenuEntry, Menu], MessageWindow USING[Append, Blink, Clear], Process USING [Detach, SetTimeout], Real USING [RoundI], Rope USING [Cat, Find, FromChar, Length, ROPE, Substr], RPC USING [ImportFailed], TIPUser USING [InstantiateNewTIPTable, TIPScreenCoords, TIPTable], UserCredentials USING [Get], ViewerClasses USING [DestroyProc, ModifyProc, NotifyProc, PaintProc, Viewer, ViewerClass, ViewerClassRec], ViewerOps USING [CreateViewer, PaintViewer, RegisterViewerClass, SetMenu], ViewerTools USING [GetSelectionContents, MakeNewTextViewer]; FootballViewer: MONITOR IMPORTS Commander, Convert, FootballInternal, FootballMaster, FootballMasterRpcControl, FS, Graphics, InputFocus, IO, Menus, MessageWindow, Process, Real, Rope, RPC, TIPUser, UserCredentials, ViewerOps, ViewerTools EXPORTS FootballInternal = { OPEN FootballInternal, FootballMaster; ROPE: TYPE = Rope.ROPE; game: PUBLIC Game; myTeam: PUBLIC Team; displayLine: INTEGER _ 20; viewer: PUBLIC ViewerClasses.Viewer; shadow: ARRAY Position OF RECORD[oldX, oldY: REAL, char: CHAR]; docName: ROPE _ FS.ExpandName["FootballDoc.Tioga"].fullFName; menu: Menus.Menu; imported: BOOL _ FALSE; selected: PUBLIC Position _ none; setUp: BOOL _ FALSE; deltaX: REAL; deltaY: REAL = 50; lastState: State; lastTime: INTEGER _ 0; penaltyState: PenaltyState _ none; timeouts: ARRAY Team OF INTEGER _ [4,4]; penalties: ARRAY Team OF Penalty _ [none, none]; oldScore: ARRAY Team OF INTEGER _ [0, 0]; timer: CONDITION; yard: INTEGER _ 6; grey: CARDINAL _ 055132B; black: CARDINAL _ 177777B; statsX: REAL _ 37*yard; statsY: REAL _ 100 + fieldWidth*yard; scoreboard: Graphics.Box = [statsX - 30, statsY - 65, statsX + 300, statsY + 30]; old: ARRAY [0..6] OF Pattern; Pattern: TYPE = RECORD[x1, y1, x2, y2: REAL, command: Command]; d: REAL _ 4; block: REAL _ 2.5; zone: REAL _ 10; Initialize: PROC = { Process.SetTimeout[@timer, 1]; game _ NEW[GameRec _ []]; FOR i: Position IN Players DO game.player[i].position _ i; ENDLOOP; CreateMenu[]; MakeFootballClass[]; Commander.Register[key: "Football", proc: Create, doc: "Football arcade game."]; }; CreateMenu: PROC = { menu _ Menus.CreateMenu[4]; Menus.InsertMenuEntry[menu, Menus.CreateEntry["DisableDelayOfGame", MyCommand, $DisableDelayOfGame]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry["Decline", MyCommand, $Decline]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry["Accept", MyCommand, $Accept]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry["TimeOut", MyCommand, $TimeOut]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry["Kick", MyCommand, $Kick]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry["Pass", MyCommand, $Pass]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry["Hike", MyCommand, $Hike]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry[name: "WritePlays", proc: PlayBook, clientData: $Write, guarded: TRUE]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry["ReadPlays", PlayBook, $Read]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry["SavePlay", MySave]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry["SaveSetUp", MySave, $SetUp]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry["Help", MyCommand, $Help]]; }; MySave: Menus.ClickProc = TRUSTED {FootballInternal.SavePlay[ side: IF game.offense = myTeam THEN offense ELSE defense, name: ViewerTools.GetSelectionContents[], setUp: clientData = $SetUp]; }; MyCommand: Menus.ClickProc = TRUSTED { TipMe[NIL, LIST[clientData]]; }; PlayBook: Menus.ClickProc = TRUSTED { name: ROPE _ ViewerTools.GetSelectionContents[]; IF name.Length[] < 2 THEN { name _ UserCredentials.Get[].name; name _ Rope.Substr[name, 0, Rope.Find[name, "."]]; name _ Rope.Cat[name, ".plays"]}; IF clientData = $Read THEN FootballInternal.ReadPlayBook[name] ELSE FootballInternal.WritePlayBook[name]; }; Create: SAFE PROC [cmd: Commander.Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] --Commander.CommandProc-- = TRUSTED { self: ROPE; remote, local: ROPE _ NIL; failed: BOOL _ FALSE; commandLineStream: IO.STREAM _ IO.RIS[cmd.commandLine]; self _ UserCredentials.Get[].name; remote _ IO.GetTokenRope[commandLineStream !IO.EndOfStream => CONTINUE].token; IF remote.Length[] # 0 THEN local _ IO.GetTokenRope[commandLineStream !IO.EndOfStream => CONTINUE].token; IF remote.Length[] = 0 THEN remote _ self; IF local.Length[] = 0 THEN local _ self; IF ~imported THEN { myTeam _ visitors; FOR i: CARDINAL IN [0..2) DO failed _ FALSE; FootballMasterRpcControl.ImportInterface [ interfaceName: [instance: remote] ! RPC.ImportFailed => {failed _ TRUE; CONTINUE}]; IF failed AND Rope.Find[remote, "."] < 0 THEN remote _ Rope.Cat[remote, ".pa"] ELSE EXIT; ENDLOOP; IF failed THEN { failed _ FALSE; myTeam _ home; FootballInternal.StartServer[local]; FootballMasterRpcControl.ImportInterface [ interfaceName: [instance: local] ! RPC.ImportFailed => {failed _ TRUE; CONTINUE}]; IF failed THEN RETURN[$Failure, "couldn't import self!!!"]}; imported _ TRUE}; IF viewer = NIL OR viewer.destroyed THEN { newProcess: BOOL _ viewer = NIL; viewer _ ViewerOps.CreateViewer[ flavor: $Football, info: [ name: "Football", iconic: FALSE, data: NIL, scrollable: FALSE], paint: FALSE]; ViewerOps.SetMenu[viewer, menu]; ViewerOps.PaintViewer[viewer, all]; IF newProcess THEN Process.Detach[FORK FootballInternal.Control[]]}; RETURN[$Success]; }; <<>> <<******************************************************************>> <> <<******************************************************************>> MakeFootballClass: PROC = { tipTable: TIPUser.TIPTable _ TIPUser.InstantiateNewTIPTable["Football.tip"]; viewerClass: ViewerClasses.ViewerClass _ NEW[ ViewerClasses.ViewerClassRec _ [paint: PaintMe, -- called whenever the Viewer should repaint notify: TipMe, -- TIP input events modify: Noop, -- InputFocus changes reported through here destroy: DestroyMe, -- called before Viewer structures freed on destroy op scroll: NIL, -- document scrolling icon: document, -- picture to display when small tipTable: tipTable, -- could be moved into Viewer instance if needed <> cursor: crossHairsCircle -- standard cursor when mouse is in viewer ]]; ViewerOps.RegisterViewerClass[$Football, viewerClass]; }; Noop: ViewerClasses.ModifyProc = TRUSTED {}; DestroyMe: ViewerClasses.DestroyProc = TRUSTED { <> FootballInternal.StopServer[]; }; TipMe: ViewerClasses.NotifyProc = TRUSTED { <<[self: Viewer, input: LIST OF REF ANY]>> <> x, y: REAL _ 1000; InputFocus.SetInputFocus[self]; FOR input _ input, input.rest DO IF input = NIL THEN EXIT; <> SELECT input.first FROM $Help => [] _ ViewerTools.MakeNewTextViewer[[name: docName, file: docName, iconic: FALSE]]; $Accept => { FootballMaster.PenaltyResponse[myTeam, TRUE]; MessageWindow.Clear[]}; $Decline => { FootballMaster.PenaltyResponse[myTeam, FALSE]; MessageWindow.Clear[]}; $Hike => FootballMaster.Hike[myTeam]; $TimeOut => FootballMaster.TimeOut[myTeam]; $Pass => IF x = 1000 THEN SetCommand[ball, [pass, [null[]]]] -- defaults location ELSE SetCommand[ball, [pass, [absolute[x, y]]]]; $Kick => IF x = 1000 THEN SetCommand[ball, [kick, [goal[]]]] ELSE SetCommand[ball, [kick, [absolute[x, y]]]]; $DoIt => SELECT game.state FROM < FootballMaster.KickOff[myTeam];>> setUp => FootballMaster.Hike[myTeam] ENDCASE => SetCommand[ball, [pass, [null[]]]]; $Select => selected _ Select[x, y, myTeam, 10]; $Deselect => selected _ none; $SetUp => IF selected # none THEN SetCommand[selected, [run, [absolute[x, y]]], TRUE]; $Run => IF selected # none THEN SetCommand[selected, [run, [absolute[x, y]]]]; $Block => { target: Position _ Select[x, y, Opponent[myTeam], 1]; IF selected = none THEN RETURN; IF target = selected THEN target _ none; <> <> <> IF target = none THEN SetCommand[selected, [block, [absolute[x, y]]]] ELSE SetCommand[selected, [block, [player[target]]]]}; $BlockDefault => IF selected # none THEN SetCommand[selected, [block, [player[none]]]]; $Stop => IF selected # none THEN SetCommand[selected, [stop, [null[]]]]; $Freeze => IF selected # none THEN SetCommand[selected, [stop, [null[]]], TRUE]; $DisableDelayOfGame => FootballMaster.DisableDelayOfGame[]; ENDCASE; ENDLOOP; }; Translate: PROC[x, y: REAL] RETURNS[REAL, REAL] = { x _ x - deltaX; y _ y - deltaY; RETURN[x/yard, y/yard]; }; Select: PROC[x, y: REAL, team: Team, delta: REAL _ 1000] RETURNS[player: Position] = { min, temp: REAL _ 1000; FOR i: Position IN Players DO IF TeamOf[i] # team THEN LOOP; temp _ ABS[game.player[i].x - x] + ABS[game.player[i].y - y]; IF temp < min THEN {min _ temp; player _ i}; ENDLOOP; IF min > delta THEN player _ none; }; <<******************************************************************>> <> <<******************************************************************>> PaintMe: ViewerClasses.PaintProc = TRUSTED { <<[self: ViewerClasses.Viewer, context: Graphics.Context, whatChanged: REF ANY, clear: BOOL]>> new: INTEGER; clock: INTEGER _ MAX[0, game.clock]; fieldErased: BOOL _ FALSE; scoreboardErased: BOOL _ FALSE; IF self.iconic THEN RETURN; new _ GetDisplayLine[Real.RoundI[game.player[game.ballCarrier].x], displayLine, self.cw/yard]; deltaX _ self.cw/2 - new*yard; Graphics.Translate[context, deltaX, deltaY]; fieldErased _ (new # displayLine) OR whatChanged = NIL; IF fieldErased THEN DisplayField[context, whatChanged # NIL]; scoreboardErased _ (game.state = huddle AND lastState # huddle) OR (game.state = offField AND lastState # offField) OR game.timeouts # timeouts OR game.penalties # penalties OR game.score # oldScore OR game.penaltyState # penaltyState OR fieldErased; IF scoreboardErased THEN DisplayStats[context, ~fieldErased]; IF scoreboardErased OR (clock # lastTime) THEN DisplayClock[context, clock, ~scoreboardErased]; [] _ Graphics.SetPaintMode[context, invert]; IF fieldErased OR (game.state = option AND lastState # option) THEN ErasePlay[context, ~fieldErased]; IF game.state < option THEN DisplayPlay[context]; FOR i: Position IN Players DO DisplayPlayer[context, @game.player[i], ~fieldErased]; ENDLOOP; DisplayBall[context, ~fieldErased]; IF game.penalties # penalties AND game.penalties # [none, none] THEN Flash[context, Graphics.GetBounds[context], grey]; IF game.penaltyState # penaltyState AND game.penaltyState = asking AND game.penalties[Opponent[myTeam]] # none THEN { MessageWindow.Append["Please accept or decline penalty.", TRUE]; MessageWindow.Blink[]}; lastTime _ clock; displayLine _ new; lastState _ game.state; oldScore _ game.score; timeouts _ game.timeouts; penalties _ game.penalties; penaltyState _ game.penaltyState; }; Flash: ENTRY PROC[context: Graphics.Context, box: Graphics.Box, color: CARDINAL] = { Graphics.SetStipple[context, color]; Graphics.DrawBox[context, box]; WAIT timer; Graphics.DrawBox[context, box]; Graphics.SetStipple[context, black]; }; Erase: PROC[context: Graphics.Context, box: Graphics.Box] = { [] _ Graphics.SetPaintMode[context, opaque]; IF viewer.column = color THEN Graphics.SetColor[context, GraphicsColor.green] ELSE Graphics.SetColor[context, Graphics.white]; Graphics.DrawBox[context, box]; Graphics.SetColor[context, Graphics.black]; [] _ Graphics.SetPaintMode[context, transparent]; }; GetDisplayLine: PROC[ballX, displayLine, screenWidth: INTEGER] RETURNS[INTEGER] = { SELECT TRUE FROM displayLine - ballX > screenWidth/2 => displayLine _ MIN[MAX[ballX, 0], 100]; displayLine - ballX > screenWidth/2 - 5 => displayLine _ screenWidth/2 - 5 + ballX; ballX - displayLine > screenWidth/2 => displayLine _ MIN[MAX[ballX, 0], 100]; ballX - displayLine > screenWidth/2 - 5 => displayLine _ ballX - screenWidth/2 + 5; ENDCASE; IF displayLine + 10 < screenWidth/2 THEN displayLine _ - 10 + screenWidth/2; IF 100 - displayLine + 10 < screenWidth/2 THEN displayLine _ 100 + 10 - screenWidth/2; RETURN[displayLine]; }; <<>> <<******************************************************************>> <> <<******************************************************************>> DisplayField: PROC[context: Graphics.Context, erase: BOOL] = { x, y: REAL; rope: ROPE; IF erase THEN Erase[context, Graphics.GetBounds[context]]; IF viewer.column = color THEN { [] _ Graphics.SetPaintMode[context, opaque]; Graphics.SetColor[context, GraphicsColor.green]; Graphics.DrawBox[context, Graphics.GetBounds[context]]; Graphics.SetColor[context, Graphics.white]}; y _ fieldWidth*yard; DrawLine[context, -10*yard, 0, 110*yard, 0]; DrawLine[context, -10*yard, fieldWidth*yard, 110*yard, fieldWidth*yard]; DrawLine[context, -10*yard, 0, -10*yard, y]; DrawLine[context, 110*yard, 0, 110*yard, y]; FOR i: INTEGER IN [0..10] DO x _ i*10*yard; y _ fieldWidth*yard; DrawLine[context, x, 0, x, y]; DrawLine[context, x-3, y/3, x+3, y/3]; DrawLine[context, x-3, 2*y/3, x+3, 2*y/3]; IF i = 0 OR i = 10 THEN rope _ Rope.FromChar['G] ELSE rope _ Convert.RopeFromInt[MIN[100-i*10, i*10]]; DrawRope[context, rope, x-5, y+5]; DrawRope[context, rope, x-5, -15]; ENDLOOP; IF viewer.column # color THEN { Graphics.SetStipple[context, grey]; Graphics.DrawBox[context, [-10*yard, 0, 0, fieldWidth*yard]]; Graphics.DrawBox[context, [100*yard, 0, 110*yard, fieldWidth*yard]]; Graphics.SetStipple[context, black]}; }; DisplayStats: PROC[context: Graphics.Context, erase: BOOL] = { << (home)>> <> <<7 7:14 14>> <<4 - timeouts - 4>> <> number: ROPE; IF erase THEN Erase[context, scoreboard]; MessageWindow.Clear[]; -- remove any messages. <> IF myTeam = home THEN DrawRope[context, "(home)", statsX+82, statsY+15] ELSE DrawRope[context, "(visitors)", statsX+75, statsY+15]; context.SetCP[statsX, statsY]; IF game.offense = home THEN Graphics.DrawRope[context, "HOME* "] ELSE Graphics.DrawRope[context, "HOME "]; SELECT game.quarter FROM 1 => Graphics.DrawRope[context, " First Quarter "]; 2 => Graphics.DrawRope[context, " Second Quarter "]; 3 => Graphics.DrawRope[context, " Third Quarter "]; 4 => Graphics.DrawRope[context, " Fourth Quarter "]; 5 => Graphics.DrawRope[context, " Game Over "]; ENDCASE => ERROR; IF game.offense = visitors THEN Graphics.DrawRope[context, "VISITORS* "] ELSE Graphics.DrawRope[context, "VISITORS "]; <> number _ Convert.RopeFromInt[game.score[home]]; DrawRope[context, number, statsX+20, statsY-15]; <<(clock drawn separately)>> number _ Convert.RopeFromInt[game.score[visitors]]; DrawRope[context, number, statsX+185, statsY-15]; <> number _ Convert.RopeFromInt[game.timeouts[home]]; DrawRope[context, number, statsX+20, statsY-30]; DrawRope[context, " - timeouts - ", statsX+60, statsY-30]; number _ Convert.RopeFromInt[game.timeouts[visitors]]; DrawRope[context, number, statsX+185, statsY-30]; <> SELECT game.down FROM 1 => number _ "first down and "; 2 => number _ "second down and "; 3 => number _ "third down and "; 4 => number _ "fourth down and "; ENDCASE => number _ ">>>> down and "; DrawRope[context, number, statsX-20, statsY-45]; IF game.firstDownMarker <= 0 OR game.firstDownMarker >= 100 THEN number _ "goal" ELSE {toGo: INTEGER; toGo _ Real.RoundI[ABS[game.scrimmage-game.firstDownMarker]]; number _ IF toGo < 1 THEN "inches" ELSE Convert.RopeFromInt[toGo]}; Graphics.DrawRope[context, number]; Graphics.DrawRope[context, " to go on the "]; number _ Convert.RopeFromInt[ MIN[Real.RoundI[game.scrimmage], Real.RoundI[100-game.scrimmage]]]; Graphics.DrawRope[context, number]; Graphics.DrawRope[context, " yard line."]; <> context.SetCP[statsX - 20, statsY - 60]; IF game.penalties # [none, none] THEN {DisplayPenalties[context]; RETURN}; context.SetCP[statsX + 60, statsY - 60]; <> <> SELECT game.score[home] - oldScore[home] FROM 7 => FlashMessage["TOUCHDOWN! "]; 3 => FlashMessage["FIELD GOAL! "]; 2 => FlashMessage["SAFETY! "]; ENDCASE; SELECT game.score[visitors] - oldScore[visitors] FROM 7 => FlashMessage["TOUCHDOWN! "]; 3 => FlashMessage["FIELD GOAL! "]; 2 => FlashMessage["SAFETY! "]; ENDCASE; }; DisplayPenalties: PROC[context: Graphics.Context] = { DisplayPenalty[context, game.penalties[home], home]; IF game.penalties[visitors] # none THEN { IF game.penalties[home] # none THEN Graphics.DrawRope[context, " and "]; DisplayPenalty[context, game.penalties[visitors], visitors]}; Graphics.DrawRope[context, ": "]; SELECT game.penaltyState FROM accepted => Graphics.DrawRope[context, "ACCEPTED."]; declined => Graphics.DrawRope[context, "DECLINED."]; canceled => Graphics.DrawRope[context, "CANCELED."]; ENDCASE; }; DisplayPenalty: PROC[context: Graphics.Context, penalty: Penalty, team: Team] = { SELECT penalty FROM none => RETURN; offSides => Graphics.DrawRope[context, "offsides "]; forwardPass => Graphics.DrawRope[context, "illegal forward pass "]; grounding => Graphics.DrawRope[context, "intentional grounding "]; delayOfGame => Graphics.DrawRope[context, "delay of game "]; ENDCASE => Graphics.DrawRope[context, "illegal procedure "]; SELECT team FROM home => Graphics.DrawRope[context, "against home"]; visitors => Graphics.DrawRope[context, "against visitors"]; ENDCASE; }; DisplayClock: PROC[context: Graphics.Context, clock: INTEGER, erase: BOOL] = { stream: IO.STREAM _ IO.ROS[]; IF erase THEN Erase[context, [statsX+89, statsY-16, statsX + 150, statsY-3]]; stream.PutF["%2d:%02d", IO.int[clock/60], IO.int[clock MOD 60]]; DrawRope[context, IO.RopeFromROS[stream], statsX+90, statsY-15]; stream.Close[]; }; DisplayPlayer: PROC[context: Graphics.Context, player: Player, erase: BOOL] = { i: Position; x, y: REAL; same: BOOL; char: CHAR; i _ player.position; x _ Real.RoundI[player.x*yard]; y _ Real.RoundI[player.y*yard]; char _ IF TeamOf[i] = home THEN 'O ELSE 'X; same _ (x = shadow[i].oldX AND y = shadow[i].oldY AND char = shadow[i].char); IF erase AND same THEN RETURN; IF erase AND ~same THEN DrawChar[context, shadow[i].char, shadow[i].oldX, shadow[i].oldY]; DrawChar[context, char, x, y]; shadow[i].oldX _ x; shadow[i].oldY _ y; shadow[i].char _ char; }; DisplayBall: PROC[context: Graphics.Context, erase: BOOL] = { x, y: REAL; same: BOOL; x _ Real.RoundI[game.player[game.ballCarrier].x*yard]; y _ Real.RoundI[game.player[game.ballCarrier].y*yard]; same _ (x = shadow[ball].oldX AND y = shadow[ball].oldY); IF erase AND same THEN RETURN; IF erase AND ~same THEN DrawChar[context, '*, shadow[ball].oldX, shadow[ball].oldY]; DrawChar[context, '*, x, y]; shadow[ball].oldX _ x; shadow[ball].oldY _ y; }; FlashMessage: PROC[message: ROPE] = { MessageWindow.Append[message, TRUE]; MessageWindow.Blink[]; }; DrawLine: PROC[context: Graphics.Context, x1, y1, x2, y2: REAL] = INLINE {context.SetCP[x1, y1]; context.DrawTo[x2, y2]}; DrawChar: PROC[context: Graphics.Context, char: CHAR, x, y: REAL] = INLINE {context.SetCP[x, y]; context.DrawChar[char]}; DrawRope: PROC[context: Graphics.Context, rope: ROPE, x, y: REAL] = INLINE {context.SetCP[x, y]; Graphics.DrawRope[context, rope]}; <<>> <<******************************************************************>> <> <<******************************************************************>> ErasePlay: PROC[context: Graphics.Context, erase: BOOL] = { Graphics.SetStipple[context, grey]; FOR i: CARDINAL IN [0..6] DO IF erase THEN DrawPattern[context, old[i]]; old[i] _ [0, 0, 0, 0, [stop, [null[ ]]]]; ENDLOOP; Graphics.SetStipple[context, black]; }; DisplayPlay: PROC[context: Graphics.Context] = { new: Pattern; Graphics.SetStipple[context, grey]; FOR i: CARDINAL IN [0..6) DO new.command _ planned.commands[i]; new.x1 _ game.player[Add[IF myTeam = home THEN HQB ELSE VQB, i]].x; new.y1 _ game.player[Add[IF myTeam = home THEN HQB ELSE VQB, i]].y; [new.x2, new.y2] _ GetTarget[new.command.target, new.y1]; IF Equal[old[i], new] THEN LOOP; DrawPattern[context, old[i]]; DrawPattern[context, new]; old[i] _ new; ENDLOOP; Graphics.SetStipple[context, black]; new.command _ planned.ball; new.x1 _ new.y1 _ 0; [new.x2, new.y2] _ GetTarget[planned.ball.target, game.player[ball].y]; IF Equal[old[6], new] THEN RETURN; DrawPattern[context, old[6]]; DrawPattern[context, new]; old[6] _ new; }; DrawPattern: PROC[context: Graphics.Context, p: Pattern] = { <> r: REAL _ 1; IF p.command.target = [player[ball]] THEN RETURN; SELECT p.command.action FROM stop => RETURN; pass, kick => {DrawChar[context, '+, p.x2*yard, p.y2*yard]; RETURN}; ENDCASE => DrawLine[context, p.x1*yard+d, p.y1*yard+d, p.x2*yard+d, p.y2*yard+d]; SELECT p.command.target.type FROM player => { DrawLine[context, (p.x2-r)*yard+d, p.y2*yard+d, p.x2*yard+d, (p.y2+r)*yard+d]; DrawLine[context, (p.x2+r)*yard+d, p.y2*yard+d, p.x2*yard+d, (p.y2+r)*yard+d]; DrawLine[context, (p.x2-r)*yard+d, p.y2*yard+d, p.x2*yard+d, (p.y2-r)*yard+d]; DrawLine[context, (p.x2+r)*yard+d, p.y2*yard+d, p.x2*yard+d, (p.y2-r)*yard+d]}; relative, absolute => { IF p.command.action = run THEN { DrawChar[context, 'o, p.x2*yard+2, p.y2*yard+2]; RETURN}; IF game.offense # myTeam THEN r _ zone ELSE r _ block; DrawLine[context, (p.x2-r)*yard+d, (p.y2-r)*yard+d, (p.x2-r)*yard+d, (p.y2+r)*yard+d]; DrawLine[context, (p.x2-r)*yard+d, (p.y2+r)*yard+d, (p.x2+r)*yard+d, (p.y2+r)*yard+d]; DrawLine[context, (p.x2+r)*yard+d, (p.y2+r)*yard+d, (p.x2+r)*yard+d, (p.y2-r)*yard+d]; DrawLine[context, (p.x2+r)*yard+d, (p.y2-r)*yard+d, (p.x2-r)*yard+d, (p.y2-r)*yard+d]}; ENDCASE; }; GetTarget: PROC[target: Target, oldY: REAL] RETURNS[x, y: REAL] = { goal: INTEGER _ Goal[Opponent[myTeam], game.quarter]; WITH t: target SELECT FROM player => RETURN[game.player[t.position].x, game.player[t.position].y]; relative => RETURN[ game.scrimmage + (IF goal > 50 THEN t.x ELSE - t.x), fieldWidth/2 + t.y]; absolute => RETURN[t.x, t.y]; goal => RETURN[goal, oldY]; ENDCASE => RETURN[0, 0]; }; Equal: PROC[a, b: Pattern] RETURNS[BOOL] = INLINE { IF a.x1 # b.x1 THEN RETURN[FALSE]; IF a.x2 # b.x2 THEN RETURN[FALSE]; IF a.y1 # b.y1 THEN RETURN[FALSE]; IF a.y2 # b.y2 THEN RETURN[FALSE]; IF a.command.action # b.command.action THEN RETURN[FALSE]; WITH t: a.command.target SELECT FROM null => IF b.command.target # t THEN RETURN[FALSE]; goal => IF b.command.target # t THEN RETURN[FALSE]; player => IF b.command.target # t THEN RETURN[FALSE]; relative => IF b.command.target # t THEN RETURN[FALSE]; absolute => IF b.command.target # t THEN RETURN[FALSE]; ENDCASE => ERROR; RETURN[TRUE]; }; Add: PROC[p: Position, i: INTEGER] RETURNS[Position] = INLINE {RETURN[LOOPHOLE[LOOPHOLE[p, INTEGER]+i]]}; Initialize[]; } . . .