<> <> <> <> <> <> <> DIRECTORY Atom USING [ GetPropFromList, PropList, PutPropOnList ], Commander USING [Handle, CommandProc, Register], CommanderOps USING [ NextArgument ], Convert USING [RopeFromInt], FootballInternal, FootballMaster, <> FS USING [ExpandName], Imager USING [black, Context, MaskBox, MaskRectangle, MaskVector, Rectangle, SetColor, SetFont, SetGray, SetXY, ShowChar, TranslateT, white], ImagerBackdoor USING [GetBounds, invert], ImagerColor USING [ColorFromRGB], InputFocus USING [SetInputFocus], IO USING [Close, RopeFromROS, int, PutF, ROS, STREAM], Menus USING [ClickProc, CreateEntry, CreateMenu, InsertMenuEntry, Menu], MessageWindow USING[Append, Blink, Clear], Process USING [Detach, SetTimeout], Real USING [Round], Rope USING [Concat, Equal, Find, FromChar, Fetch, Length, ROPE, Substr], <> TIPUser USING [InstantiateNewTIPTable, TIPScreenCoords, TIPTable], <> VFonts USING [defaultFont], ViewerClasses USING [DestroyProc, ModifyProc, NotifyProc, PaintProc, Viewer, ViewerClass, ViewerClassRec], ViewerOps USING [BlinkViewer, CreateViewer, OpenIcon, RegisterViewerClass, SetMenu], ViewerTools USING [GetSelectionContents, MakeNewTextViewer]; FootballViewer: MONITOR IMPORTS Atom, Commander, CommanderOps, Convert, FootballInternal, FootballMaster, FS, Imager, ImagerBackdoor, ImagerColor, InputFocus, IO, Menus, MessageWindow, Process, Real, Rope, TIPUser, VFonts, ViewerOps, ViewerTools <<,FootballMasterRpcControl, RPC>> EXPORTS FootballInternal = { OPEN FootballInternal, FootballMaster; ROPE: TYPE = Rope.ROPE; menu: Menus.Menu _ NIL; docName: ROPE _ FS.ExpandName["FootballDoc.tioga"].fullFName; yard: INTEGER _ 6; d: REAL _ 4; block: REAL _ 2.5; zone: REAL _ 10; grey: CARDINAL _ 055132B; black: CARDINAL _ 177777B; statsX: REAL _ 37*yard; statsY: REAL _ 100 + fieldWidth*yard; scoreboard: Imager.Rectangle _ [x: statsX - 30, y: statsY - 65, w: 330, h: 95]; currentTeams: ARRAY Team OF T _ ALL[NIL]; <> Initialize: PROC = { 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 { t: T _ GetTFromViewer[parent]; FootballInternal.SavePlay[ t: t, side: IF t.game.offense = t.myTeam THEN offense ELSE defense, name: ViewerTools.GetSelectionContents[], playSetUp: clientData = $SetUp]; }; GetTFromViewer: PUBLIC PROC[v: ViewerClasses.Viewer] RETURNS [t: T_NIL] ~ { props: Atom.PropList _ NIL; IF v=NIL THEN RETURN; props _ v.props; IF props = NIL THEN RETURN; t _ NARROW[Atom.GetPropFromList[propList: props, prop: $TeamRec]]; }; GetTFromTeam: PUBLIC PROC[team: Team] RETURNS [t: T_NIL] ~ { RETURN[currentTeams[team]]; }; MyCommand: Menus.ClickProc = TRUSTED { TipMe[parent, LIST[clientData]]; -- supplying parent here is tentative. }; PlayBook: Menus.ClickProc = TRUSTED { name: ROPE _ ViewerTools.GetSelectionContents[]; t: T _ GetTFromViewer[parent]; IF name.Length[] < 2 THEN { name _ "Football.plays"; <> name _ Rope.Substr[name, 0, Rope.Find[name, "."]]; name _ Rope.Concat[name, ".plays"]}; IF clientData = $Read THEN FootballInternal.ReadPlayBook[t, name] ELSE FootballInternal.WritePlayBook[t, name]; }; Create: SAFE PROC [cmd: Commander.Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] --Commander.CommandProc-- = TRUSTED { t: T_NEW[TeamRec _ []]; viewer: ViewerClasses.Viewer; name: ROPE; <> <> <> <> FootballInternal.InitPlays[t]; t.myTeam _ home; Process.SetTimeout[@t.timer, 1]; t.game _ NEW[GameRec _ []]; FOR i: Position IN Players DO t.game.player[i].position _ i; ENDLOOP; <> < CONTINUE].token;>> < CONTINUE].token;>> <> <> IF CommanderOps.NextArgument[cmd].Equal["visitors", FALSE] THEN t.myTeam _ visitors; <> <> <> <> <> <> < {failed _ TRUE; CONTINUE}];>> <> <> <> <> <> <> <> <> <> < {failed _ TRUE; CONTINUE}];>> <> <<};>> <> <<};>> <> <> <> <> <> <> <> <> <> <> <> <> <> FootballInternal.StartServer[NIL]; -- Idempotent name _ Rope.Concat["Football ", IF t.myTeam = home THEN "(home)" ELSE "(visitors)"]; viewer _ ViewerOps.CreateViewer[ flavor: $Football, info: [name: name, iconic: TRUE, data: NIL, scrollable: FALSE], paint: FALSE]; t.viewer _ viewer; viewer.props _ Atom.PutPropOnList[viewer.props, $TeamRec, t]; ViewerOps.SetMenu[viewer, menu, FALSE]; ViewerOps.OpenIcon[viewer]; currentTeams[t.myTeam] _ t; Process.Detach[FORK FootballInternal.Control[t]]; 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 { <> t: T _ GetTFromViewer[self]; IF t#NIL THEN t.viewer _ NIL; FootballInternal.StopServer[]; }; TipMe: ViewerClasses.NotifyProc = TRUSTED { <<[self: Viewer, input: LIST OF REF ANY]>> <> t: T _ GetTFromViewer[self]; BEGIN OPEN t; 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[t, ball, [pass, [null[]]]] -- defaults location ELSE SetCommand[t, ball, [pass, [absolute[x, y]]]]; $Kick => IF x = 1000 THEN SetCommand[t, ball, [kick, [goal[]]]] ELSE SetCommand[t, ball, [kick, [absolute[x, y]]]]; $DoIt => SELECT game.state FROM < FootballMaster.KickOff[myTeam];>> setUp => FootballMaster.Hike[myTeam] ENDCASE => SetCommand[t, ball, [pass, [null[]]]]; $Select => selected _ Select[t, x, y, myTeam, 10]; $Deselect => selected _ none; $SetUp => IF selected # none THEN SetCommand[t, selected, [run, [absolute[x, y]]], TRUE]; $Run => IF selected # none THEN SetCommand[t, selected, [run, [absolute[x, y]]]]; $Block => { target: Position _ Select[t, x, y, Opponent[myTeam], 1]; IF selected = none THEN RETURN; IF target = selected THEN target _ none; <> <> <> IF target = none THEN SetCommand[t, selected, [block, [absolute[x, y]]]] ELSE SetCommand[t, selected, [block, [player[target]]]]}; $BlockDefault => IF selected # none THEN SetCommand[t, selected, [block, [player[none]]]]; $Stop => IF selected # none THEN SetCommand[t, selected, [stop, [null[]]]]; $Freeze => IF selected # none THEN SetCommand[t, selected, [stop, [null[]]], TRUE]; $DisableDelayOfGame => FootballMaster.DisableDelayOfGame[]; ENDCASE; ENDLOOP; END; -- OPEN t }; Translate: PROC[t: T, x, y: REAL] RETURNS[REAL, REAL] = { x _ x - t.deltaX; y _ y - t.deltaY; RETURN[x/yard, y/yard]; }; Select: PROC[t: T, 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[t.game.player[i].x - x] + ABS[t.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: Imager.Context, whatChanged: REF ANY, clear: BOOL]>> t: T _ GetTFromViewer[self]; BEGIN OPEN t; new: INTEGER; clock: INTEGER _ MAX[0, game.clock]; fieldErased: BOOL _ FALSE; scoreboardErased: BOOL _ FALSE; IF self.iconic OR self.destroyed THEN RETURN; IF self.wh # lastPaintedHeight THEN { whatChanged _ NIL; lastPaintedHeight _ self.wh; }; Imager.SetFont[context, VFonts.defaultFont]; new _ GetDisplayLine[Real.Round[game.player[game.ballCarrier].x], displayLine, self.cw/yard]; deltaX _ self.cw/2 - new*yard; Imager.TranslateT[context, [deltaX, deltaY]]; fieldErased _ (new # displayLine) OR whatChanged = NIL; IF fieldErased THEN DisplayField[t, 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[t, context, ~fieldErased]; IF scoreboardErased OR (clock # lastTime) THEN DisplayClock[t, context, clock, ~scoreboardErased]; Imager.SetColor[context, ImagerBackdoor.invert]; IF fieldErased OR (game.state = option AND lastState # option) THEN ErasePlay[t, context, ~fieldErased]; IF game.state < option THEN DisplayPlay[t, context]; FOR i: Position IN Players DO DisplayPlayer[t, context, @game.player[i], ~fieldErased]; ENDLOOP; DisplayBall[t, context, ~fieldErased]; [] _ Imager.SetColor[context, Imager.black]; IF game.penalties # penalties AND game.penalties # [none, none] THEN Flash[self]; 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; END; -- OPEN t; }; Flash: ENTRY PROC[viewer: ViewerClasses.Viewer] = { ViewerOps.BlinkViewer[viewer]; }; Erase: PROC [t: T, context: Imager.Context, rect: Imager.Rectangle] = { IF t.viewer.column = color THEN Imager.SetColor[context, ImagerColor.ColorFromRGB[[R: 0, G: 1, B: 0]]] ELSE Imager.SetColor[context, Imager.white]; Imager.MaskRectangle[context, rect]; Imager.SetColor[context, Imager.black]; }; 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[t: T, context: Imager.Context, erase: BOOL] = { x, y: REAL; rope: ROPE; IF erase THEN Erase[t, context, ImagerBackdoor.GetBounds[context]]; IF t.viewer.column = color THEN { Imager.SetColor[context, ImagerColor.ColorFromRGB[[R: 0, G: 1, B: 0]]]; Imager.MaskRectangle[context, ImagerBackdoor.GetBounds[context]]; Imager.SetColor[context, Imager.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 t.viewer.column # color THEN { Imager.SetGray[context, 0.5]; Imager.MaskBox[context, [-10*yard, 0, 0, fieldWidth*yard]]; Imager.MaskBox[context, [100*yard, 0, 110*yard, fieldWidth*yard]]; Imager.SetGray[context, black]; }; }; DisplayStats: PROC[t: T, context: Imager.Context, erase: BOOL] = { << (home)>> << HOME* First Quarter VISITORS>> << 7 7:14 14>> << 4 - timeouts - 4>> << first down and goal on the 4>> OPEN t; number: ROPE; IF erase THEN Erase[t, 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]; Imager.SetXY[context, [statsX, statsY]]; IF game.offense = home THEN MyShowRope[context, "HOME* "] ELSE MyShowRope[context, "HOME "]; SELECT game.quarter FROM 1 => MyShowRope[context, " First Quarter "]; 2 => MyShowRope[context, " Second Quarter "]; 3 => MyShowRope[context, " Third Quarter "]; 4 => MyShowRope[context, " Fourth Quarter "]; 5 => MyShowRope[context, " Game Over "]; ENDCASE => ERROR; IF game.offense = visitors THEN MyShowRope[context, "VISITORS* "] ELSE MyShowRope[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.Round[ABS[game.scrimmage-game.firstDownMarker]]; number _ IF toGo < 1 THEN "inches" ELSE Convert.RopeFromInt[toGo]}; MyShowRope[context, number]; MyShowRope[context, " to go on the "]; number _ Convert.RopeFromInt[ MIN[Real.Round[game.scrimmage], Real.Round[100-game.scrimmage]]]; MyShowRope[context, number]; MyShowRope[context, " yard line."]; <> Imager.SetXY[context, [statsX - 20, statsY - 60]]; IF game.penalties # [none, none] THEN {DisplayPenalties[t, context]; RETURN}; Imager.SetXY[context, [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[t: T, context: Imager.Context] = { DisplayPenalty[context, t.game.penalties[home], home]; IF t.game.penalties[visitors] # none THEN { IF t.game.penalties[home] # none THEN MyShowRope[context, " and "]; DisplayPenalty[context, t.game.penalties[visitors], visitors]}; MyShowRope[context, ": "]; SELECT t.game.penaltyState FROM accepted => MyShowRope[context, "ACCEPTED."]; declined => MyShowRope[context, "DECLINED."]; canceled => MyShowRope[context, "CANCELED."]; ENDCASE; }; DisplayPenalty: PROC[context: Imager.Context, penalty: Penalty, team: Team] = { SELECT penalty FROM none => RETURN; offSides => MyShowRope[context, "offsides "]; forwardPass => MyShowRope[context, "illegal forward pass "]; grounding => MyShowRope[context, "intentional grounding "]; delayOfGame => MyShowRope[context, "delay of game "]; ENDCASE => MyShowRope[context, "illegal procedure "]; SELECT team FROM home => MyShowRope[context, "against home"]; visitors => MyShowRope[context, "against visitors"]; ENDCASE; }; DisplayClock: PROC[t: T, context: Imager.Context, clock: INTEGER, erase: BOOL] = { stream: IO.STREAM _ IO.ROS[]; IF erase THEN Erase[t, context, [statsX+89, statsY-16, 60, 12]]; 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[t: T, context: Imager.Context, player: Player, erase: BOOL] = { i: Position; x, y: REAL; same: BOOL; char: CHAR; i _ player.position; x _ Real.Round[player.x*yard]; y _ Real.Round[player.y*yard]; char _ IF TeamOf[i] = home THEN 'O ELSE 'X; same _ (x = t.shadow[i].oldX AND y = t.shadow[i].oldY AND char = t.shadow[i].char); IF erase AND same THEN RETURN; IF erase AND ~same THEN DrawChar[context, t.shadow[i].char, t.shadow[i].oldX, t.shadow[i].oldY]; DrawChar[context, char, x, y]; t.shadow[i].oldX _ x; t.shadow[i].oldY _ y; t.shadow[i].char _ char; }; DisplayBall: PROC[t: T, context: Imager.Context, erase: BOOL] = { x, y: REAL; same: BOOL; x _ Real.Round[t.game.player[t.game.ballCarrier].x*yard]; y _ Real.Round[t.game.player[t.game.ballCarrier].y*yard]; same _ (x = t.shadow[ball].oldX AND y = t.shadow[ball].oldY); IF erase AND same THEN RETURN; IF erase AND ~same THEN DrawChar[context, '*, t.shadow[ball].oldX, t.shadow[ball].oldY]; DrawChar[context, '*, x, y]; t.shadow[ball].oldX _ x; t.shadow[ball].oldY _ y; }; FlashMessage: PROC[message: ROPE] = { MessageWindow.Append[message, TRUE]; MessageWindow.Blink[]; }; DrawLine: PROC[context: Imager.Context, x1, y1, x2, y2: REAL] = INLINE { Imager.MaskVector[context, [x1, y1], [x2, y2]]; }; DrawChar: PROC[context: Imager.Context, char: CHAR, x, y: REAL] = INLINE { Imager.SetXY[context, [x, y]]; Imager.ShowChar[context, char]; }; DrawRope: PROC[context: Imager.Context, rope: ROPE, x, y: REAL] = INLINE { Imager.SetXY[context, [x, y]]; MyShowRope[context, rope]; }; MyShowRope: PROC[context: Imager.Context, rope: ROPE] = { FOR i: INT IN [0..Rope.Length[rope]) DO Imager.ShowChar[context, Rope.Fetch[rope, i]]; ENDLOOP; }; <> ErasePlay: PROC[t: T, context: Imager.Context, erase: BOOL] = { <> FOR i: CARDINAL IN [0..6] DO IF erase THEN DrawPattern[t, context, t.old[i]]; t.old[i] _ [0, 0, 0, 0, [stop, [null[ ]]]]; ENDLOOP; }; DisplayPlay: PROC[t: T, context: Imager.Context] = { <> new: Pattern; FOR i: CARDINAL IN [0..6) DO new.command _ t.planned.commands[i]; new.x1 _ t.game.player[Add[IF t.myTeam = home THEN HQB ELSE VQB, i]].x; new.y1 _ t.game.player[Add[IF t.myTeam = home THEN HQB ELSE VQB, i]].y; [new.x2, new.y2] _ GetTarget[t, new.command.target, new.y1]; IF Equal[t.old[i], new] THEN LOOP; DrawPattern[t, context, t.old[i]]; DrawPattern[t, context, new]; t.old[i] _ new; ENDLOOP; new.command _ t.planned.ball; new.x1 _ new.y1 _ 0; [new.x2, new.y2] _ GetTarget[t, t.planned.ball.target, t.game.player[ball].y]; IF Equal[t.old[6], new] THEN RETURN; DrawPattern[t, context, t.old[6]]; DrawPattern[t, context, new]; t.old[6] _ new; }; DrawPattern: PROC[t: T, context: Imager.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 t.game.offense # t.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[t: T, target: Target, oldY: REAL] RETURNS[x, y: REAL] = { OPEN t; goal: INTEGER _ Goal[Opponent[myTeam], game.quarter]; WITH targ: target SELECT FROM player => RETURN[game.player[targ.position].x, game.player[targ.position].y]; relative => RETURN[ game.scrimmage + (IF goal > 50 THEN targ.x ELSE - targ.x), fieldWidth/2 + targ.y]; absolute => RETURN[targ.x, targ.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[]; } . . .