DIRECTORY Basics USING [BITAND, BITOR, BITXOR], BasicTime USING [GetClockPulses, Now, Pulses, PulsesToSeconds], CedarProcess USING [GetPriority, Priority, SetPriority], ChessDefs USING [Aliases, AliasesRep, Board, BoardIndex, ColoredPiece, CoverageRep, FullPosition, GameState, GameStateRep, Move, MoveHistory, MoveHistoryRep, MoveList, Pawn, Piece, PieceColor, Position, Positions, PositionsRep, Side, SpecialEffects, SquareCoverage, WhiteBlack], Commander USING [CommandProc, Register], FS USING [Error, ExpandName, StreamOpen], Imager USING [Color, DoSaveAll, MaskRectangleI, ScaleT, SetColor, SetFont, SetGray, SetXY, ShowChar, ShowRope, TranslateT], ImagerColor USING [Color, ColorFromGray], ImagerFont USING [BoundingBox, Extents, Find, Font, Scale], IO USING [Close, EndOfStream, Error, GetInt, GetToken, IDProc, PutChar, PutF, PutF1, PutFR1, PutRope, SkipWhitespace, STREAM], Menus USING [AppendMenuEntry, ClickProc, CreateEntry, CreateMenu, Menu], MessageWindow USING [Append], Process USING [Detach, MsecToTicks, Pause], ProcessProps USING [GetProp], Rope USING [Concat, Fetch, Length, ROPE], RuntimeError USING [UNCAUGHT], TIPUser USING [InstantiateNewTIPTable, TIPScreenCoords, TIPTable], ViewerClasses USING [DestroyProc, NotifyProc, PaintProc, Viewer, ViewerClass, ViewerClassRec], ViewerOps USING [CreateViewer, PaintViewer, RegisterViewerClass], ViewerSpecs USING [menuHeight], ViewerTools USING [GetSelectionContents]; ChessDisplayImpl: CEDAR MONITOR LOCKS data USING data: MyData IMPORTS Basics, BasicTime, CedarProcess, Commander, FS, Imager, ImagerColor, ImagerFont, IO, Menus, MessageWindow, Process, ProcessProps, Rope, RuntimeError, TIPUser, ViewerOps, ViewerSpecs, ViewerTools = BEGIN OPEN ChessDefs; LORA: TYPE = LIST OF REF ANY; ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; StopRequest: ERROR = CODE; AutoMove: PROC [data: MyData, level: NAT] RETURNS [MoveList] = { MoveFlags: TYPE = RECORD [ taking: BOOL _ FALSE, threat: BOOL _ FALSE, check: BOOL _ FALSE, inThreat: BOOL _ FALSE, inCover: BOOL _ FALSE, intoThreat: BOOL _ FALSE, intoCover: BOOL _ FALSE ]; ComputeCoverage: PROC [by: WhiteBlack, on: WhiteBlack] RETURNS [covers: ARRAY Piece OF NAT] = { other: WhiteBlack _ OtherColor[by]; state: GameState _ data.state; covers _ ALL[LAST[NAT]]; FOR piece: Piece IN Piece DO fPos: FullPosition _ data.state.positions[by][piece]; IF fPos.onOff # off THEN { pa: Piece _ IF piece IN Pawn THEN state.aliases[by][piece] ELSE piece; pv: NAT _ materialWeights[pa]; FOR victim: Piece IN Piece DO vPos: FullPosition _ data.state.positions[on][victim]; IF vPos.onOff # off AND PieceCovers[state, by, vPos.position, piece] THEN { IF pv < covers[victim] THEN covers[victim] _ pv; }; ENDLOOP; }; ENDLOOP; }; MinReply: PROC [by: WhiteBlack, victim: Piece] RETURNS [NAT] = { state: GameState _ data.state; min: NAT _ NoThreat; vPos: FullPosition _ state.positions[by][victim]; IF vPos.onOff # off THEN { FOR piece: Piece IN Piece DO IF victim # piece THEN { fPos: FullPosition _ state.positions[by][piece]; IF fPos.onOff # off THEN { pa: Piece _ IF piece IN Pawn THEN state.aliases[by][piece] ELSE piece; pv: NAT _ materialWeights[pa]; IF PieceCovers[state, by, vPos.position, piece] THEN { IF pv < min THEN min _ pv; }; }; }; ENDLOOP; }; RETURN [min]; }; NoThreat: NAT = LAST[NAT]; driver: PROC [level: NAT, fanOut: [0..MaxFanOut), firstCall: BOOL _ FALSE] RETURNS [dMove: Move, val: INTEGER _ 0, maxMoves: NAT _ 0] = { state: GameState _ data.state; goodMovesMax: NAT _ 0; goodValues: ARRAY [0..MaxFanOut) OF INTEGER; goodBonus: ARRAY [0..MaxFanOut) OF INTEGER; goodMoves: ARRAY [0..MaxFanOut) OF Move; goodFlags: ARRAY [0..MaxFanOut) OF MoveFlags; color: WhiteBlack _ state.toMove; other: WhiteBlack _ OtherColor[color]; history: MoveHistory _ state.history; lastValue: INTEGER _ 0; bestIndex: NAT _ 0; kW: INTEGER _ materialWeights[k]; wasInCheck: BOOL _ state.inCheck[color]; foundMate: BOOL _ FALSE; val _ state.material[color] - state.material[other]; { FOR piece: Piece IN Piece WHILE NOT foundMate DO fPos: FullPosition _ data.state.positions[color][piece]; IF fPos.onOff # off THEN { pass1: MoveAction = { IF data.stopRequested THEN ERROR StopRequest; IF MakeMove[state, move] THEN { ENABLE UNWIND => UnMakeMove[state]; value: INTEGER _ state.material[color] - state.material[other]; flags: MoveFlags _ []; pos: NAT _ goodMovesMax; dest: Position _ move.to; bonus: INTEGER _ 0; otherMoves: NAT _ 0; otherTakes: NAT _ 0; myWeight: NAT _ 0; forceEntry: BOOL _ wasInCheck; alias: Piece _ move.piece.piece; IF alias IN Pawn THEN alias _ state.aliases[color][alias]; myWeight _ materialWeights[alias]; IF state.inCheck[other] THEN { IF CheckMate[state] THEN GO TO absoluteBest; flags.check _ forceEntry _ TRUE; }; IF firstCall THEN { otherBest: MoveAction = { IF MakeMove[state, move] THEN { ENABLE UNWIND => UnMakeMove[state]; myBest2: MoveAction = { IF MakeMove[state, move] THEN { ENABLE UNWIND => UnMakeMove[state]; val2: INTEGER _ state.material[color] - state.material[other]; myMoves2 _ myMoves2 + 1; SELECT TRUE FROM state.inCheck[other] => { val2 _ val2 + 50; IF (quit _ CheckMate[state]) THEN val2 _ kW; }; ENDCASE; IF myMoves2 = 1 OR val2 > temp THEN temp _ val2; UnMakeMove[state]; }; }; inCheck: BOOL _ state.inCheck[color]; temp: INTEGER _ 0; myMoves2: NAT _ 0; otherMoves _ otherMoves + 1; FOR piece: Piece IN Piece DO fPos: FullPosition _ data.state.positions[color][piece]; IF fPos.onOff # off THEN { ProposeLegalMove[piece, color, state, myBest2]; }; ENDLOOP; IF myMoves2 = 0 AND state.inCheck[color] THEN { temp _ -kW; quit _ TRUE; }; IF otherMoves = 1 OR temp < value THEN value _ temp; UnMakeMove[state]; }; }; otherMoves: NAT _ 0; FOR piece: Piece IN Piece DO fPos: FullPosition _ data.state.positions[other][piece]; IF fPos.onOff # off THEN { ProposeLegalMove[piece, other, state, otherBest]; }; ENDLOOP; SELECT TRUE FROM otherMoves = 0 AND state.inCheck[other] => { value _ kW; quit _ TRUE; }; ENDCASE => { IF move.note.kind = remove THEN flags.taking _ TRUE; SELECT alias FROM p0, p1, p2, p3, p4, p5, p6, p7 => { toRow: [0..7] _ move.to.row; fwd: INTEGER _ IF color = white THEN 1 ELSE -1; bonus _ bonus + 5; SELECT move.from.row FROM 1 => IF toRow = 3 THEN bonus _ bonus + 5; 2 => IF toRow = 1 THEN bonus _ bonus + 20; 3 => IF toRow = 2 THEN bonus _ bonus + 5; 4 => IF toRow = 5 THEN bonus _ bonus + 5; 5 => IF toRow = 6 THEN bonus _ bonus + 20; 6 => IF toRow = 4 THEN bonus _ bonus + 5; ENDCASE => ERROR; SELECT move.to.col FROM 3, 4 => bonus _ bonus + 2; 1, 2 => bonus _ bonus + 1; ENDCASE; IF move.to.row IN [1..6] THEN { me: Piece _ move.piece.piece; nRow: [0..7] _ toRow+fwd; col: [0..7] _ move.to.col; IF col # 0 THEN { diag: Position _ [rowCol[nRow, col-1]]; cp: ColoredPiece _ state.board[diag.index]; SELECT cp.color FROM other => bonus _ bonus + 4; color => IF cp.piece IN Pawn THEN bonus _ bonus + 4; ENDCASE; }; IF col # 7 THEN { diag: Position _ [rowCol[nRow, col+1]]; cp: ColoredPiece _ state.board[diag.index]; SELECT cp.color FROM other => bonus _ bonus + 4; color => IF cp.piece IN Pawn THEN bonus _ bonus + 4; ENDCASE; }; IF col IN [1..7] THEN { facing: Position _ [rowCol[nRow, col]]; cp: ColoredPiece _ state.board[facing.index]; IF cp.color = other AND cp.piece IN Pawn THEN bonus _ bonus + 4; }; }; }; wn, bn => { bonus _ bonus + 4; SELECT move.from.row FROM 0, 7 => bonus _ bonus + 10; 1, 6 => bonus _ bonus + 5; ENDCASE => SELECT move.from.col FROM 0, 7 => bonus _ bonus + 10; 1, 6 => bonus _ bonus + 5; ENDCASE; IF wasInCheck THEN bonus _ bonus + 10; }; wb, bb => { bonus _ bonus + 5; IF wasInCheck THEN bonus _ bonus + 10; }; wr, br, q => { qPos: FullPosition _ state.positions[color][q]; brPos: FullPosition _ state.positions[color][br]; wrPos: FullPosition _ state.positions[color][wr]; SELECT TRUE FROM qPos.onOff = off AND wrPos.onOff = off => {}; qPos.onOff = off AND brPos.onOff = off => {}; wrPos.onOff = off AND brPos.onOff = off => {}; qPos.onOff = off AND (wrPos.position.row = brPos.position.row OR wrPos.position.col = brPos.position.col) => bonus _ bonus + 10; wrPos.onOff = off AND (qPos.position.row = brPos.position.row OR qPos.position.col = brPos.position.col) => value _ value + 5; brPos.onOff = off AND (wrPos.position.row = qPos.position.row OR wrPos.position.col = qPos.position.col) => bonus _ bonus + 5; ENDCASE; }; k => IF move.note.kind = castle THEN bonus _ bonus + 60 ELSE bonus _ bonus - 20; ENDCASE; }; } ELSE { IF flags.check THEN { bonus _ bonus + 50; IF (quit _ CheckMate[state]) THEN value _ kW; forceEntry _ TRUE; }; WITH move.note SELECT FROM castle: castle SpecialEffects => bonus _ bonus + 40; rem: remove SpecialEffects => { vp: Piece _ rem.victim.piece; va: Piece _ IF vp IN Pawn THEN state.aliases[other][vp] ELSE vp; vc: NAT _ materialWeights[va]; SELECT vc FROM < myWeight => bonus _ bonus - 20; > myWeight => bonus _ bonus + 20; ENDCASE; flags.taking _ TRUE; }; ENDCASE; }; UnMakeMove[state]; value _ value + bonus; WHILE pos > 0 DO np: NAT _ pos - 1; IF value <= goodValues[np] THEN EXIT; pos _ np; ENDLOOP; IF pos < fanOut OR (forceEntry AND pos < fanOut+4) THEN { IF goodMovesMax < fanOut OR flags.check OR goodFlags[goodMovesMax-1].check THEN IF goodMovesMax+1 < MaxFanOut THEN goodMovesMax _ goodMovesMax + 1; FOR j: NAT DECREASING IN (pos..goodMovesMax) DO goodValues[j] _ goodValues[j-1]; goodBonus[j] _ goodBonus[j-1]; goodMoves[j] _ goodMoves[j-1]; goodFlags[j] _ goodFlags[j-1]; ENDLOOP; goodMoves[pos] _ move; goodValues[pos] _ value; goodBonus[pos] _ bonus; goodFlags[pos] _ flags; IF goodMovesMax-1 > fanOut AND NOT goodFlags[fanOut].check THEN { FOR j: NAT IN (fanOut..goodMovesMax) DO goodValues[j-1] _ goodValues[j]; goodBonus[j-1] _ goodBonus[j]; goodMoves[j-1] _ goodMoves[j]; goodFlags[j-1] _ goodFlags[j]; ENDLOOP; goodMovesMax _ goodMovesMax-1; }; }; EXITS absoluteBest => { UnMakeMove[state]; dMove _ move; maxMoves _ 1; val _ kW; quit _ foundMate _ TRUE; }; }; }; ProposeLegalMove[piece, color, state, pass1]; }; ENDLOOP; }; IF foundMate THEN RETURN; IF goodMovesMax = 0 THEN { SELECT TRUE FROM state.inCheck[color] => val _ history.current - kW; val < -400 => val _ kW; ENDCASE => val _ -val; dMove _ move; RETURN; }; FOR i: [0..MaxFanOut) IN [0..goodMovesMax) DO move: Move _ goodMoves[i]; flags: MoveFlags _ goodFlags[i]; bonus: INTEGER _ goodBonus[i]; IF data.stopRequested THEN ERROR StopRequest; IF MakeMove[state, move] THEN { ENABLE UNWIND => UnMakeMove[state]; value: INTEGER _ goodValues[i]; current: NAT _ history.current; IF current IN [triggerLo..triggerHi] THEN { debug _ debug + 1; IF data.displayMoves THEN { UpdateViewer[data, FALSE]; Process.Pause[Process.MsecToTicks[500]]; }; }; SELECT TRUE FROM value >= kW => { value _ value; }; value < -900 AND level = 0 => { value _ value; }; current > 2 AND level # 0 => { value _ -driver[level-1, MAX[fanOut-1, 2]].val; }; current <= baseLevel + depthCutoff AND (flags.taking OR flags.check OR state.inCheck[color]) => { value _ -driver[0, MAX[fanOut-1, 2]].val; }; ENDCASE => { alias: Piece _ move.piece.piece; IF alias IN Pawn THEN alias _ state.aliases[color][alias]; value _ state.material[color] - state.material[other]; SELECT TRUE FROM flags.intoThreat => value _ value - materialWeights[alias]; flags.inThreat => IF alias # k THEN value _ value + materialWeights[alias]; move.note.kind = castle => value _ value + 50; flags.taking, flags.check => value _ value + 5; ENDCASE; IF flags.threat THEN value _ value + 20; IF current > 6 THEN { myLast: Move _ history[current-5]; IF myLast.piece = move.piece AND myLast.to = move.to AND myLast.from = move.from THEN value _ value - 50; }; }; IF current IN [triggerLo..triggerHi] THEN debug _ debug + 1; maxMoves _ maxMoves + 1; value _ value + bonus; IF maxMoves = 1 OR value > val THEN { val _ value; dMove _ move; bestIndex _ i; }; lastValue _ value; UnMakeMove[state]; }; ENDLOOP; IF history.current IN [triggerLo..triggerHi] THEN debug _ debug + 1; }; baseLevel: NAT _ data.state.history.current; triggerLo: NAT _ baseLevel+1; triggerHi: NAT _ baseLevel+debugLevel; debug: INT _ 0; move: Move; value: INTEGER; count: NAT; [move, value, count] _ driver[level, defaultFanOut, TRUE ! StopRequest => {count _ 0; CONTINUE}]; IF count # 0 THEN RETURN [LIST[move]] ELSE RETURN [NIL]; }; PaintWatcher: PROC [data: MyData, viewer: ViewerClasses.Viewer] = { WaitForPaint: ENTRY PROC [data: MyData] RETURNS [BOOL] = { ENABLE UNWIND => NULL; DO IF data.quit THEN RETURN [TRUE]; IF data.paintRequested THEN RETURN [FALSE]; WAIT data.paintCond; ENDLOOP; }; ShowPaintDone: ENTRY PROC [data: MyData] = { data.paintRequested _ FALSE; BROADCAST data.paintCond; }; DO IF WaitForPaint[data] THEN EXIT; ViewerOps.PaintViewer[viewer, client, FALSE, $Update]; ShowPaintDone[data]; ENDLOOP; }; PaintMe: ViewerClasses.PaintProc = { WITH self.data SELECT FROM data: MyData => { DrawPosition: PROC [position: Position, cPiece: ColoredPiece] = { char: CHAR _ 'P; color: REAL _ 0.0; alias: Piece _ cPiece.piece; IF cPiece.color # none AND alias IN Pawn THEN alias _ state.aliases[cPiece.color][alias]; SELECT alias FROM bn, wn => char _ 'N; bb, wb => char _ 'B; br, wr => char _ 'R; q => char _ 'Q; k => char _ 'K; ENDCASE; SELECT cPiece.color FROM white => color _ 0.0; black => color _ 1.0; ENDCASE => char _ 0C; DrawSquare[position, char, color]; }; DrawSquare: PROC [position: Position, char: CHAR, color: REAL] = { localX: NAT _ position.col*squareSize; localY: NAT _ position.row*squareSize; background: ImagerColor.Color _ IF (((position.col MOD 2) + (position.row MOD 2)) MOD 2) = 0 THEN blackBackground ELSE whiteBackground; quarter: NAT _ squareSize/4; half: NAT _ squareSize/2; IF inverted THEN { localY _ (7-position.row)*squareSize; localX _ (7-position.col)*squareSize; }; Imager.SetColor[context, background]; Imager.MaskRectangleI[context, localX, localY, squareSize, squareSize]; IF char # 0C THEN { extents: ImagerFont.Extents _ ImagerFont.BoundingBox[data.letterFont, [0, ORD[char]]]; width: REAL _ extents.rightExtent - extents.leftExtent; height: REAL _ extents.ascent; Imager.SetGray[context, color]; localY _ localY + letterOffset; Imager.SetXY[context, [localX+half-width*0.5, localY+half-height*0.5]]; Imager.SetFont[context, data.letterFont]; Imager.ShowChar[context, char]; }; }; DoFlashing: PROC [newColor: PieceColor] = { IF data.flashRequested # 0 THEN FOR i: NAT IN [0..data.flashRequested*2] DO FOR pos: BoardIndex IN BoardIndex DO old: ColoredPiece _ data.lastShown[pos]; new: ColoredPiece _ sample[pos]; IF old # new THEN IF newColor = new.color THEN { IF i MOD 2 = 0 THEN DrawPosition[ [index[pos]], new] ELSE DrawPosition[ [index[pos]], old ]; }; ENDLOOP; Process.Pause[Process.MsecToTicks[100]]; ENDLOOP; }; sample: Board _ data.state.board; inverted: BOOL _ data.invertDisplay; stackValid: BOOL _ data.stackValid; state: GameState _ data.state; toMove: WhiteBlack _ state.toMove; myHeight: INTEGER _ self.ch-ViewerSpecs.menuHeight; IF NOT data.screenValid THEN whatChanged _ NIL; Imager.TranslateT[context, [baseX, MAX[myHeight-(baseY + 8*squareSize), 0] + baseY]]; SELECT whatChanged FROM $Update => { DoFlashing[none]; DoFlashing[OtherColor[toMove]]; FOR pos: BoardIndex IN BoardIndex DO old: ColoredPiece _ data.lastShown[pos]; new: ColoredPiece _ sample[pos]; IF old # new THEN DrawPosition[ [index[pos]], new ]; ENDLOOP; }; ENDCASE => { stackValid _ FALSE; FOR pos: BoardIndex IN BoardIndex DO DrawPosition[ [index[pos]], sample[pos]]; ENDLOOP; }; data.flashRequested _ 0; IF NOT stackValid THEN { inner: PROC = { Imager.TranslateT[context, [8*squareSize+baseY, 5*squareSize]]; Imager.ScaleT[context, smallScale]; IF data.stack = NIL THEN { Imager.SetGray[context, 0.0]; Imager.MaskRectangleI[context, 0, 0, squareSize*8, squareSize*8]; } ELSE { state _ data.stack.first; FOR pos: BoardIndex IN BoardIndex DO DrawPosition[ [index[pos]], sample[pos]]; ENDLOOP; state _ data.state; }; }; Imager.DoSaveAll[context, inner]; data.stackValid _ TRUE; }; { localX: INTEGER _ 8*squareSize+8; localY: INTEGER _ 8; moves: NAT _ data.state.history.current; toMove: ROPE _ IF data.state.toMove = white THEN "white" ELSE "black"; Imager.SetGray[context, 0.0]; Imager.MaskRectangleI[context, localX, localY, textWidth, textHeight*5]; Imager.SetGray[context, 1.0]; Imager.SetFont[context, data.textFont]; IF data.message2 # NIL THEN { Imager.SetXY[context, [localX, localY+textHeight*0]]; Imager.ShowRope[context, data.message2]; data.message2 _ NIL; }; Imager.SetXY[context, [localX, localY+textHeight*1]]; Imager.ShowRope[context, IO.PutFR1["move: %g", [integer[(data.state.history.current/2)+1 ]]]]; Imager.SetXY[context, [localX, localY+textHeight*2]]; Imager.ShowRope[context, IO.PutFR1["to move: %g", [rope[toMove]]]]; IF data.message1 # NIL THEN { Imager.SetXY[context, [localX, localY+textHeight*3]]; Imager.ShowRope[context, data.message1]; }; }; data.lastShown _ data.state.board; data.screenValid _ TRUE; }; ENDCASE; }; NotifyMe: ViewerClasses.NotifyProc = { WITH self.data SELECT FROM data: MyData => IF MakeBusy[data] THEN { which: ATOM; x: INTEGER _ 0; y: INTEGER _ 0; inverted: BOOL _ data.invertDisplay; FOR each: LORA _ input, each.rest WHILE each # NIL DO WITH each.first SELECT FROM atom: ATOM => which _ atom; coords: TIPUser.TIPScreenCoords => { x _ coords.mouseX; y _ coords.mouseY; }; ENDCASE; ENDLOOP; SELECT which FROM $Select, $Move => { myHeight: INTEGER _ self.ch-ViewerSpecs.menuHeight; xLo: INTEGER _ baseX; xHi: INTEGER _ xLo+8*squareSize; yLo: INTEGER _ baseY+MAX[myHeight-(baseY + 8*squareSize), 0]; yHi: INTEGER _ yLo+8*squareSize; new: FullPosition _ [on, [index[0]]]; old: FullPosition _ data.selected; data.selected _ [off, [index[0]]]; IF x IN [xLo..xHi) AND y IN [yLo..yHi) THEN { state: GameState _ data.state; new.position.row _ (y-yLo) / squareSize; new.position.col _ (x-xLo) / squareSize; IF inverted THEN { new.position.row _ 7 - new.position.row; new.position.col _ 7 - new.position.col; }; SELECT which FROM $Select => IF state.board[new.position.index].color = state.toMove THEN data.selected _ new; $Move => IF old.onOff = on THEN { movePiece: ColoredPiece _ state.board[old.position.index]; color: WhiteBlack _ state.toMove; IF old # new AND movePiece.color = color THEN { taken: ColoredPiece _ state.board[new.position.index]; effects: SpecialEffects _ IF taken.color # none THEN [remove[taken, new.position]] ELSE [none[]]; pMove: Move _ [movePiece, old.position, new.position, p0, effects]; last: NAT _ state.history.last; legal: BOOL _ FALSE; CheckLegal: MoveAction = { SELECT TRUE FROM pMove.piece # move.piece => GO TO bogus; pMove.from # move.from => GO TO bogus; pMove.to # move.to => GO TO bogus; pMove.note.kind # move.note.kind => GO TO bogus; ENDCASE => legal _ TRUE; EXITS bogus => {}; }; alias: Piece _ movePiece.piece; IF alias IN Pawn THEN alias _ state.aliases[color][alias]; SELECT alias FROM IN Pawn => { IF pMove.from.row = pMove.to.row AND ABS[pMove.from.col- pMove.to.col] = 1 THEN { taken _ state.board[pMove.to.index]; SELECT pMove.from.row FROM 3 => pMove.to.row _ 2; 4 => pMove.to.row _ 5; ENDCASE; }; }; k => { IF pMove.from.col = 4 AND ABS[pMove.from.col- pMove.to.col] = 2 AND pMove.from.row = pMove.to.row THEN { side: Side _ IF pMove.to.col = 2 THEN queen ELSE king; ok: BOOL; [ok, pMove] _ CheckCastle[data.state, side]; IF NOT ok THEN GO TO bailOut; }; }; ENDCASE; ProposeLegalMove[movePiece.piece, color, state, CheckLegal]; IF legal THEN { data.message1 _ NIL; IF NOT MakeMove[state, pMove] THEN GO TO bailOut; data.flashRequested _ 2; last _ state.history.current; state.history.last _ last; UpdateViewer[data]; }; EXITS bailOut => {}; }; }; ENDCASE; }; }; ENDCASE; [] _ MakeBusy[data, FALSE]; }; ENDCASE; }; DestroyMe: ViewerClasses.DestroyProc = { IF self # NIL THEN { WITH self.data SELECT FROM data: MyData => { DropDead: ENTRY PROC [data: MyData] = { data.stopRequested _ TRUE; data.paintRequested _ TRUE; data.quit _ TRUE; BROADCAST data.paintCond; }; DropDead[data]; }; ENDCASE; }; }; StopButton: Menus.ClickProc = { WITH parent SELECT FROM viewer: ViewerClasses.Viewer => { WITH clientData SELECT FROM data: MyData => data.stopRequested _ TRUE; ENDCASE; }; ENDCASE; }; ResetButton: Menus.ClickProc = { WITH parent SELECT FROM viewer: ViewerClasses.Viewer => { WITH clientData SELECT FROM data: MyData => IF MakeBusy[data] THEN { history: MoveHistory _ data.state.history; current: NAT _ history.current; last: NAT _ history.last; NewBoard[data, history]; history.last _ last; UpdateViewer[data]; [] _ MakeBusy[data, FALSE]; }; ENDCASE; }; ENDCASE; }; RefreshButton: Menus.ClickProc = { WITH parent SELECT FROM viewer: ViewerClasses.Viewer => { WITH clientData SELECT FROM data: MyData => IF MakeBusy[data] THEN { UpdateViewer[data]; [] _ MakeBusy[data, FALSE]; } ELSE UpdateViewer[data, FALSE]; ENDCASE; }; ENDCASE; }; ReplayButton: Menus.ClickProc = { WITH parent SELECT FROM viewer: ViewerClasses.Viewer => { WITH clientData SELECT FROM data: MyData => IF MakeBusy[data] THEN { history: MoveHistory _ data.state.history; current: NAT _ history.current; last: NAT _ history.last; pause: NAT _ IF shift THEN 1000 ELSE 500; IF control THEN pause _ pause*4; NewBoard[data, history]; UpdateViewer[data, FALSE]; FOR i: NAT IN [0..current) DO play: MoveList _ LIST[history[i]]; Process.Pause[Process.MsecToTicks[pause]]; MakeMoves[data, play, viewer]; IF data.stopRequested THEN EXIT; ENDLOOP; history.last _ last; IF last = current THEN [] _ MakeBusy[data, FALSE]; }; ENDCASE; }; ENDCASE; }; StepButton: Menus.ClickProc = { WITH parent SELECT FROM viewer: ViewerClasses.Viewer => WITH clientData SELECT FROM data: MyData => IF MakeBusy[data] THEN { history: MoveHistory _ data.state.history; current: NAT _ history.current; last: NAT _ history.last; data.message1 _ NIL; IF mouseButton = red THEN { UnMakeMove[data.state]; UpdateViewer[data, FALSE]; } ELSE { IF current < last THEN { [] _ MakeMove[data.state, history[current]]; UpdateViewer[data]; }; }; [] _ MakeBusy[data, FALSE]; }; ENDCASE; ENDCASE; }; StackButton: Menus.ClickProc = { WITH parent SELECT FROM viewer: ViewerClasses.Viewer => WITH clientData SELECT FROM data: MyData => IF MakeBusy[data] THEN { state: GameState _ data.state; stack: GameStateList _ data.stack; history: MoveHistory _ state.history; IF mouseButton = red THEN { current: NAT _ history.current; IF current > 0 THEN { NewBoard[data]; data.stack _ CONS[state, stack]; FOR i: NAT IN [0..current) DO [] _ MakeMove[data.state, history[i]]; ENDLOOP; FOR i: NAT IN [current..history.last) DO data.state.history[i] _ history[i]; ENDLOOP; data.state.history.last _ history.last; }; } ELSE { stack: GameStateList _ data.stack; IF stack # NIL THEN {data.state _ stack.first; data.stack _ stack.rest}; }; data.stackValid _ FALSE; data.message1 _ NIL; UpdateViewer[data]; [] _ MakeBusy[data, FALSE]; }; ENDCASE; ENDCASE; }; AutoButton: Menus.ClickProc = { WITH parent SELECT FROM viewer: ViewerClasses.Viewer => WITH clientData SELECT FROM data: MyData => IF MakeBusy[data] THEN { level: NAT _ playLevel; moves: MoveList; old: CedarProcess.Priority _ CedarProcess.GetPriority[]; data.displayMoves _ shift; SELECT mouseButton FROM red => {}; yellow => level _ level + 1; blue => level _ level + 2; ENDCASE; DO pulses: BasicTime.Pulses _ BasicTime.GetClockPulses[]; seconds: REAL; CedarProcess.SetPriority[background]; moves _ AutoMove[data, level]; CedarProcess.SetPriority[old]; data.displayMoves _ FALSE; SELECT TRUE FROM data.stopRequested => data.message1 _ "stopped"; data.state.inCheck[data.state.toMove] => data.message1 _ NIL; moves = NIL => data.message1 _ "STALEMATE"; ENDCASE => data.message1 _ NIL; data.state.history.last _ data.state.history.current; seconds _ BasicTime.PulsesToSeconds[BasicTime.GetClockPulses[] - pulses]; data.message2 _ IO.PutFR1[ IF seconds <= 99.9 THEN "seconds: %4.1f" ELSE "seconds: %5.0f", [real[seconds]]]; IF moves = NIL THEN { UpdateViewer[data]; } ELSE { data.flashRequested _ 3; MakeMoves[data, moves, viewer]; }; IF moves = NIL OR NOT control OR data.stopRequested THEN EXIT; ENDLOOP; [] _ MakeBusy[data, FALSE]; }; ENDCASE; ENDCASE; }; InvertButton: Menus.ClickProc = { WITH parent SELECT FROM viewer: ViewerClasses.Viewer => WITH clientData SELECT FROM data: MyData => IF MakeBusy[data] THEN { data.invertDisplay _ NOT data.invertDisplay; data.screenValid _ FALSE; UpdateViewer[data]; [] _ MakeBusy[data, FALSE]; }; ENDCASE; ENDCASE; }; DumpButton: Menus.ClickProc = { WITH parent SELECT FROM viewer: ViewerClasses.Viewer => WITH clientData SELECT FROM data: MyData => IF MakeBusy[data] THEN { { ENABLE FS.Error => { MessageWindow.Append[error.explanation, TRUE]; GO TO failed; }; name: ROPE _ NameFromSelection[data]; out: STREAM _ FS.StreamOpen[fileName: name, accessOptions: $create, keep: 2]; MessageWindow.Append[Rope.Concat["Dumping game to ", name], TRUE]; DumpState[data, out]; IO.Close[out]; EXITS failed => {}; }; [] _ MakeBusy[data, FALSE]; }; ENDCASE; ENDCASE; }; LoadButton: Menus.ClickProc = { WITH parent SELECT FROM viewer: ViewerClasses.Viewer => WITH clientData SELECT FROM data: MyData => IF MakeBusy[data] THEN { state: GameState _ data.state; { ENABLE FS.Error => { MessageWindow.Append[error.explanation, TRUE]; GO TO failed; }; name: ROPE _ NameFromSelection[data]; in: STREAM _ FS.StreamOpen[name]; MessageWindow.Append[Rope.Concat["Loading game from ", name], TRUE]; IF NOT LoadState[data, in] THEN { MessageWindow.Append[Rope.Concat["Load failed from ", name], TRUE]; data.state _ state; }; IO.Close[in]; EXITS failed => {}; }; UpdateViewer[data]; [] _ MakeBusy[data, FALSE]; }; ENDCASE; ENDCASE; }; NameFromSelection: PROC [data: MyData] RETURNS [ROPE] = { name: ROPE _ NIL; len: INT; dot: INT; name _ ViewerTools.GetSelectionContents[ ! RuntimeError.UNCAUGHT => CONTINUE]; IF Rope.Length[name] < 2 THEN name _ "Dump$"; len _ dot _ Rope.Length[name]; FOR i: INT DECREASING IN [0..len) DO SELECT Rope.Fetch[name, i] FROM '. => {dot _ i; EXIT}; '>, '/, '] => EXIT; ENDCASE; ENDLOOP; IF dot = len THEN name _ Rope.Concat[name, ".chess"]; SELECT Rope.Fetch[name, 0] FROM '[, '/ => name _ FS.ExpandName[name, NIL].fullFName; ENDCASE => name _ FS.ExpandName[name, data.wd].fullFName; RETURN [name]; }; DumpState: PROC [data: MyData, out: STREAM] = { state: GameState _ data.state; history: MoveHistory _ state.history; IO.PutF1[out, "\n-- game dumped on %g", [time[BasicTime.Now[]]]]; FOR i: NAT IN [0..history.last) DO move: Move _ history[i]; enPassant: BOOL _ FALSE; IO.PutRope[out, IF i MOD 2 = 0 THEN "\n " ELSE " "]; IO.PutF[out, "%g%g", [cardinal[move.to.row]], [cardinal[move.to.col]] ]; SELECT move.note.kind FROM none => IO.PutChar[out, '_]; castle => IO.PutChar[out, '&]; remove => IO.PutChar[out, 'x]; ENDCASE; IO.PutF[out, "%g%g", [cardinal[move.from.row]], [cardinal[move.from.col]] ]; WITH move.note SELECT FROM rem: remove SpecialEffects => { IF move.to # rem.from THEN IO.PutF[out, "r%g%g", [cardinal[rem.from.row]], [cardinal[rem.from.col]] ]; }; ENDCASE; ENDLOOP; IO.PutF1[out, "\nCurrent %g \n\n", [cardinal[history.current]] ]; }; LoadState: PROC [data: MyData, in: STREAM] RETURNS [ok: BOOL _ TRUE] = { state: GameState; current: NAT _ 0; buffer: REF TEXT _ NEW[TEXT[100]]; NewBoard[data]; state _ data.state; { ENABLE IO.EndOfStream, IO.Error => GO TO bomb; ParseMove: PROC = { to: Position; from: Position; piece: ColoredPiece; to.row _ buffer[0]-'0; to.col _ buffer[1]-'0; from.row _ buffer[3]-'0; from.col _ buffer[4]-'0; piece _ state.board[from.index]; SELECT buffer[2] FROM '_ => { [] _ MakeMove[state, [piece: piece, from: from, to: to]]; }; '& => { side: Side _ SELECT to.col FROM 6 => king, 2 => queen, ENDCASE => ERROR; [] _ MakeMove[state, [piece: piece, from: from, to: to, note: [castle[side]]]]; }; 'x, 'X => { rfrom: Position _ to; IF buffer.length > 5 THEN { SELECT buffer[5] FROM 'r, 'R => { rfrom.row _ buffer[6]-'0; rfrom.col _ buffer[7]-'0; }; ENDCASE; }; [] _ MakeMove[state, [piece: piece, from: from, to: to, note: [remove[state.board[rfrom.index], rfrom]]]]; }; ENDCASE; }; DO [] _ IO.SkipWhitespace[in]; buffer _ IO.GetToken[in, IO.IDProc, buffer].token; IF buffer.length = 0 OR buffer[0] NOT IN ['0..'7] THEN EXIT; ParseMove[ ! IO.EndOfStream => EXIT; RuntimeError.UNCAUGHT => GO TO bomb ]; ENDLOOP; current _ IO.GetInt[in]; WHILE state.history.current > current DO UnMakeMove[state]; ENDLOOP; }; EXITS bomb => ok _ FALSE; }; MakeMoves: PROC [data: MyData, moves: MoveList, viewer: ViewerClasses.Viewer] = { FOR each: MoveList _ moves, each.rest WHILE each # NIL DO color: PieceColor _ data.state.toMove; IF NOT MakeMove[data.state, each.first] THEN EXIT; ENDLOOP; data.state.history.last _ data.state.history.current; data.message1 _ NIL; UpdateViewer[data]; }; UpdateViewer: ENTRY PROC [data: MyData, mateCheck: BOOL _ TRUE, waitForDone: BOOL _ TRUE] = { state: GameState _ data.state; data.paintRequested _ TRUE; IF data.message1 = NIL THEN IF state.inCheck[state.toMove] THEN IF mateCheck AND state.history.current = state.history.last AND CheckMate[data.state] THEN data.message1 _ "CHECKMATE" ELSE data.message1 _ "check"; BROADCAST data.paintCond; WHILE waitForDone AND data.paintRequested AND NOT data.quit DO WAIT data.paintCond; ENDLOOP; }; MakeBusy: ENTRY PROC [data: MyData, busy: BOOL _ TRUE] RETURNS [BOOL] = { IF busy AND data.busy THEN RETURN [FALSE]; data.busy _ busy; data.stopRequested _ FALSE; RETURN [TRUE]; }; NewBoard: PROC [data: MyData, oldHistory: MoveHistory _ NIL] = { state: GameState _ data.state _ NEW[GameStateRep _ [ coverage: [NEW[CoverageRep], NEW[CoverageRep]], positions: [NEW[PositionsRep], NEW[PositionsRep]], aliases: [NEW[AliasesRep], NEW[AliasesRep]] ]]; material: NAT _ 0; data.message1 _ NIL; state.aliases[white]^ _ [p0, p1, p2, p3, p4, p5, p6, p7]; state.aliases[black]^ _ [p0, p1, p2, p3, p4, p5, p6, p7]; IF oldHistory = NIL THEN { state.history _ NEW[MoveHistoryRep[maxHistoryLen]]; state.history.current _ state.history.last _ 0; } ELSE { state.history _ oldHistory; oldHistory.current _ 0; }; RenewPositions[data.state]; data.selected _ [off, [index[0]] ]; FOR piece: Piece IN Piece DO material _ material + materialWeights[piece]; ENDLOOP; state.material[black] _ state.material[white] _ material; }; RenewPositions: PROC [state: GameState] = { state.positions[white]^ _ ALL[ [off, [index[0]]] ]; state.positions[black]^ _ ALL[ [off, [index[0]]] ]; FOR index: BoardIndex IN BoardIndex DO cPiece: ColoredPiece _ state.board[index]; IF cPiece.color # none THEN state.positions[cPiece.color][cPiece.piece] _ [on, [index[index]]]; ENDLOOP; }; MakeMove: PROC [state: GameState, move: Move, check: BOOL _ TRUE] RETURNS [legal: BOOL _ TRUE] = { history: MoveHistory _ state.history; current: NAT _ history.current; last: NAT _ history.last; moving: ColoredPiece _ move.piece; color: WhiteBlack _ moving.color; other: WhiteBlack _ OtherColor[color]; alias: Piece _ moving.piece; move.material _ state.material[color]; move.wasCheck _ state.inCheck; IF alias IN Pawn THEN { alias _ state.aliases[color][alias]; SELECT move.to.row FROM 0, 7 => IF alias IN Pawn THEN { alias _ move.alias _ q; state.aliases[color][moving.piece] _ alias _ move.alias _ q; state.material[color] _ state.material[color] + (materialWeights[alias]-materialWeights[moving.piece]); }; ENDCASE; }; WITH move.note SELECT FROM castle: castle SpecialEffects => { row: [0..8) _ IF color = white THEN 0 ELSE 7; oldRookPos: Position _ [rowCol[row, IF castle.side = king THEN 7 ELSE 0]]; newRookPos: Position _ [rowCol[row, IF castle.side = king THEN 5 ELSE 3]]; rook: ColoredPiece _ state.board[oldRookPos.index]; state.positions[color][rook.piece] _ [on, newRookPos]; state.board[oldRookPos.index] _ [none, p0]; state.board[newRookPos.index] _ rook; }; remove: remove SpecialEffects => { vc: WhiteBlack _ remove.victim.color; vp: Piece _ remove.victim.piece; va: Piece _ vp; IF va IN Pawn THEN va _ state.aliases[vc][va]; state.positions[vc][vp] _ [off, [index[0]]]; state.board[remove.from.index] _ [none, p0]; state.material[vc] _ state.material[vc] - materialWeights[va]; }; ENDCASE; state.positions[moving.color][moving.piece] _ [on, move.to]; state.board[move.from.index] _ [none, p0]; state.board[move.to.index] _ moving; state.toMove _ IF color = white THEN black ELSE white; history[current] _ move; history.current _ current _ current + 1; IF last < current THEN history.last _ current; IF check THEN { legal _ CoveredWeight[state, other, state.positions[color][k].position, TRUE] = 0; IF NOT legal THEN {UnMakeMove[state]; history.last _ last; RETURN [FALSE]}; state.inCheck _ [ IF color = white THEN NOT legal ELSE CoveredWeight[state, black, state.positions[white][k].position, TRUE] # 0, IF color = black THEN NOT legal ELSE CoveredWeight[state, white, state.positions[black][k].position, TRUE] # 0 ]; }; RETURN [TRUE]; }; UnMakeMove: PROC [state: GameState] = { history: MoveHistory _ state.history; current: NAT _ history.current; IF current > 0 THEN { move: Move _ history[history.current _ current-1]; moving: ColoredPiece _ move.piece; color: WhiteBlack _ moving.color; state.positions[moving.color][moving.piece] _ [on, move.from]; state.board[move.to.index] _ [none, p0]; state.board[move.from.index] _ moving; state.toMove _ color; IF moving.piece IN Pawn AND move.alias # p0 THEN state.aliases[color][moving.piece] _ moving.piece; WITH move.note SELECT FROM castle: castle SpecialEffects => { row: [0..8) _ IF color = white THEN 0 ELSE 7; oldRookPos: Position _ [rowCol[row, IF castle.side = king THEN 7 ELSE 0]]; newRookPos: Position _ [rowCol[row, IF castle.side = king THEN 5 ELSE 3]]; rook: ColoredPiece _ state.board[newRookPos.index]; state.positions[color][rook.piece] _ [on, oldRookPos]; state.board[newRookPos.index] _ [none, p0]; state.board[oldRookPos.index] _ rook; }; remove: remove SpecialEffects => { vc: WhiteBlack _ remove.victim.color; vp: Piece _ remove.victim.piece; va: Piece _ vp; IF va IN Pawn THEN va _ state.aliases[vc][va]; state.positions[vc][vp] _ [on, remove.from]; state.board[remove.from.index] _ remove.victim; state.material[vc] _ state.material[vc] + materialWeights[va]; }; ENDCASE; state.material[color] _ move.material; state.inCheck _ move.wasCheck; }; }; PositionEvaluate: PROC [state: GameState, checkWeight: INTEGER] RETURNS [value: INTEGER _ 0] = INLINE { other: WhiteBlack _ state.toMove; color: WhiteBlack _ OtherColor[other]; value _ state.material[color] - state.material[other]; IF state.inCheck[other] THEN value _ value + checkWeight; }; CheckCastle: PROC [state: GameState, side: Side] RETURNS [ok: BOOL _ FALSE, move: Move] = { color: WhiteBlack _ state.toMove; other: WhiteBlack _ OtherColor[color]; row: [0..8) _ IF color = white THEN 0 ELSE 7; delta: INTEGER _ IF side = king THEN +1 ELSE -1; oldKingPos: Position _ [rowCol[row, 4]]; newKingPos: Position _ [rowCol[row, IF side = king THEN 6 ELSE 2]]; oldRookPos: Position _ [rowCol[row, IF side = king THEN 7 ELSE 0]]; newRookPos: Position _ [rowCol[row, IF side = king THEN 5 ELSE 3]]; rook: ColoredPiece _ state.board[oldRookPos.index]; king: ColoredPiece _ state.board[oldKingPos.index]; SELECT TRUE FROM rook.color # color, king.color # color, king.piece # k, state.inCheck[color] => {}; rook.piece = br, rook.piece = wr => { IF side = king THEN FOR pos: BoardIndex IN (oldKingPos.index..oldRookPos.index) DO IF state.board[pos].color # none THEN GO TO nope; ENDLOOP ELSE FOR pos: BoardIndex IN (oldRookPos.index..oldKingPos.index) DO IF state.board[pos].color # none THEN GO TO nope; ENDLOOP; FOR i: NAT IN [0..state.history.current) DO move: Move _ state.history[i]; IF move.piece.color = color THEN SELECT move.piece.piece FROM k, rook.piece => GO TO nope; ENDCASE; ENDLOOP; SELECT TRUE FROM CoveredWeight[state, other, newKingPos, TRUE] # 0 => GO TO nope; CoveredWeight[state, other, newRookPos, TRUE] # 0 => GO TO nope; ENDCASE; RETURN [TRUE, [king, oldKingPos, newKingPos, p0, [castle[side]] ]]; EXITS nope => {}; }; ENDCASE; }; CheckMate: PROC [state: ChessDefs.GameState] RETURNS [mated: BOOL _ FALSE] = { color: WhiteBlack _ state.toMove; IF state.inCheck[color] THEN { testMate: MoveAction = { IF MakeMove[state, move] THEN { quit _ TRUE; mated _ FALSE; UnMakeMove[state]; }; }; mated _ TRUE; FOR piece: Piece IN Piece WHILE mated DO fPos: FullPosition _ state.positions[color][piece]; IF fPos.onOff # off THEN ProposeLegalMove[piece, color, state, testMate]; ENDLOOP; }; }; nullCoverageEntry: SquareCoverage _ ALL[FALSE]; MoveAction: TYPE = PROC [state: ChessDefs.GameState, move: ChessDefs.Move] RETURNS [quit: BOOL _ FALSE]; ProposeLegalMove: PROC [piece: Piece, color: WhiteBlack, state: GameState, action: MoveAction, captureOnly: BOOL _ FALSE] = { myColor: PieceColor _ color; myPiece: ColoredPiece _ [myColor, piece]; other: PieceColor _ IF myColor = white THEN black ELSE white; forwards: INTEGER _ IF myColor = white THEN 1 ELSE -1; backwards: INTEGER _ -forwards; fullPos: FullPosition; curPos: Position; alias: Piece _ piece; currentRow: [0..8); currentCol: [0..8); quit: BOOL _ FALSE; Propose: PROC [dRow, dCol: INTEGER] RETURNS [final: BOOL _ FALSE] = { IF NOT quit THEN { oPos: Position _ [rowCol[row: currentRow, col: currentCol]]; nPos: Position _ [rowCol[row: dRow+currentRow, col: dCol+currentCol]]; cPiece: ColoredPiece _ state.board[nPos.index]; SELECT cPiece.color FROM none => { IF NOT captureOnly THEN quit _ action[state, [myPiece, oPos, nPos] ]; RETURN [quit]; }; other => quit _ action[state, [myPiece, oPos, nPos, p0, [remove[victim: cPiece, from: nPos]]]]; ENDCASE; }; RETURN [TRUE]; }; ProposePawn: PROC [dRow, dCol: INTEGER] RETURNS [final: BOOL _ FALSE] = { IF NOT quit THEN { nRow: [0..8) _ dRow+currentRow; nCol: [0..8) _ dCol+currentCol; oPos: Position _ [rowCol[row: currentRow, col: currentCol]]; nPos: Position _ [rowCol[row: nRow, col: nCol]]; cPiece: ColoredPiece _ state.board[nPos.index]; alias: Piece _ p0; IF myPiece.piece IN Pawn THEN IF nRow = 0 OR nRow = 7 THEN alias _ q; SELECT cPiece.color FROM none => { IF NOT captureOnly THEN quit _ action[state, [myPiece, oPos, nPos, alias] ]; RETURN [quit]; }; other => quit _ action[state, [myPiece, oPos, nPos, alias, [remove[victim: cPiece, from: nPos]]]]; ENDCASE; }; RETURN [TRUE]; }; fullPos _ state.positions[color][piece]; IF fullPos.onOff = off THEN RETURN; curPos.index _ fullPos.position.index; IF piece IN Pawn THEN alias _ state.aliases[color][piece]; currentRow _ curPos.row; currentCol _ curPos.col; SELECT alias FROM p0, p1, p2, p3, p4, p5, p6, p7 => { IF currentRow IN (0..7) THEN { aRow: [0..8) _ IF color = white THEN currentRow ELSE 7-currentRow; row: [0..8) _ currentRow+forwards; oPos: Position _ [rowCol[row: currentRow, col: currentCol]]; nPos: Position _ [rowCol[row: row, col: currentCol]]; cPiece: ColoredPiece _ state.board[nPos.index]; IF cPiece.color = none AND NOT captureOnly AND aRow # 7 THEN { IF action[state, [myPiece, oPos, nPos] ] THEN RETURN; IF aRow = 1 THEN { nPos.row _ row+forwards; cPiece _ state.board[nPos.index]; IF cPiece.color = none THEN { IF action[state, [myPiece, oPos, nPos] ] THEN RETURN; IF quit THEN RETURN; }; nPos.row _ row; }; }; IF currentCol # 0 THEN { nPos.col _ currentCol-1; cPiece _ state.board[nPos.index]; IF cPiece.color = other THEN { [] _ ProposePawn[forwards, -1]; IF quit THEN RETURN; }; }; IF currentCol # 7 THEN { nPos.col _ currentCol+1; cPiece _ state.board[nPos.index]; IF cPiece.color = other THEN { [] _ ProposePawn[forwards, 1]; IF quit THEN RETURN; }; }; IF aRow = 4 AND state.history.current > 0 THEN { lastMove: Move _ state.history[state.history.current-1]; p2: ColoredPiece _ lastMove.piece; a2: Piece _ p2.piece; IF a2 IN Pawn THEN { a2 _ state.aliases[other][a2]; IF a2 IN Pawn AND lastMove.to.row = currentRow AND ABS[lastMove.to.row - lastMove.from.row] = 2 THEN { col: INTEGER _ currentCol; SELECT lastMove.to.col FROM col+1, col-1 => { effects: SpecialEffects _ [remove[victim: lastMove.piece, from: lastMove.to]]; IF action[state, [myPiece, curPos, [rowCol[currentRow+forwards, lastMove.to.col]], p0, effects]] THEN RETURN; IF quit THEN RETURN; }; ENDCASE; }; }; }; }; }; br, wr => { FOR delta: INTEGER IN [1..7-currentRow] UNTIL quit OR Propose[delta, 0] DO ENDLOOP; FOR delta: INTEGER IN [1..currentRow] UNTIL quit OR Propose[-delta, 0] DO ENDLOOP; FOR delta: INTEGER IN [1..7-currentCol] UNTIL quit OR Propose[0, delta] DO ENDLOOP; FOR delta: INTEGER IN [1..currentCol] UNTIL quit OR Propose[0, -delta] DO ENDLOOP; }; bb, wb => { FOR delta: INTEGER IN [1..MIN[7-currentRow, 7-currentCol]] UNTIL quit OR Propose[delta, delta] DO ENDLOOP; FOR delta: INTEGER IN [1..MIN[7-currentRow, currentCol]] UNTIL quit OR Propose[delta, -delta] DO ENDLOOP; FOR delta: INTEGER IN [1..MIN[currentRow, 7-currentCol]] UNTIL quit OR Propose[-delta, delta] DO ENDLOOP; FOR delta: INTEGER IN [1..MIN[currentRow, currentCol]] UNTIL quit OR Propose[-delta, -delta] DO ENDLOOP; }; bn, wn => { IF currentRow < 7 THEN { IF currentCol < 6 THEN {[] _ Propose[1, 2]; IF quit THEN RETURN}; IF currentCol > 1 THEN {[] _ Propose[1, -2]; IF quit THEN RETURN}; IF currentRow < 6 THEN { IF currentCol < 7 THEN {[] _ Propose[2, 1]; IF quit THEN RETURN}; IF currentCol > 0 THEN {[] _ Propose[2, -1]; IF quit THEN RETURN}; }; }; IF currentRow > 0 THEN { IF currentCol < 6 THEN {[] _ Propose[-1, 2]; IF quit THEN RETURN}; IF currentCol > 1 THEN {[] _ Propose[-1, -2]; IF quit THEN RETURN}; IF currentRow > 1 THEN { IF currentCol < 7 THEN {[] _ Propose[-2, 1]; IF quit THEN RETURN}; IF currentCol > 0 THEN {[] _ Propose[-2, -1]; IF quit THEN RETURN}; }; }; }; q => { FOR delta: INTEGER IN [1..7-currentRow] UNTIL quit OR Propose[delta, 0] DO ENDLOOP; FOR delta: INTEGER IN [1..currentRow] UNTIL quit OR Propose[-delta, 0] DO ENDLOOP; FOR delta: INTEGER IN [1..7-currentCol] UNTIL quit OR Propose[0, delta] DO ENDLOOP; FOR delta: INTEGER IN [1..currentCol] UNTIL quit OR Propose[0, -delta] DO ENDLOOP; FOR delta: INTEGER IN [1..MIN[7-currentRow, 7-currentCol]] UNTIL quit OR Propose[delta, delta] DO ENDLOOP; FOR delta: INTEGER IN [1..MIN[7-currentRow, currentCol]] UNTIL quit OR Propose[delta, -delta] DO ENDLOOP; FOR delta: INTEGER IN [1..MIN[currentRow, 7-currentCol]] UNTIL quit OR Propose[-delta, delta] DO ENDLOOP; FOR delta: INTEGER IN [1..MIN[currentRow, currentCol]] UNTIL quit OR Propose[-delta, -delta] DO ENDLOOP; }; k => { IF currentRow < 7 THEN { IF currentCol < 7 THEN {[] _ Propose[1, 1]; IF quit THEN RETURN}; {[] _ Propose[1, 0]; IF quit THEN RETURN}; IF currentCol > 0 THEN {[] _ Propose[1, -1]; IF quit THEN RETURN}; }; IF currentCol < 7 THEN {[] _ Propose[0, 1]; IF quit THEN RETURN}; IF currentCol > 0 THEN {[] _ Propose[0, -1]; IF quit THEN RETURN}; IF currentRow > 0 THEN { IF currentCol < 7 THEN {[] _ Propose[-1, 1]; IF quit THEN RETURN}; {[] _ Propose[-1, 0]; IF quit THEN RETURN}; IF currentCol > 0 THEN {[] _ Propose[-1, -1]; IF quit THEN RETURN}; }; IF NOT captureOnly AND NOT quit THEN { IF NOT state.inCheck[color] THEN { ok: BOOL; move: Move; [ok, move] _ CheckCastle[state, king]; IF ok THEN IF action[state, move] THEN RETURN; [ok, move] _ CheckCastle[state, queen]; IF ok THEN IF action[state, move] THEN RETURN; }; }; }; ENDCASE => ERROR; }; CoveredWeight: PROC [state: GameState, color: WhiteBlack, pos: Position, stopOnFirst: BOOL _ FALSE] RETURNS [totalWeight: INTEGER _ 0] = { other: WhiteBlack _ OtherColor[color]; positions: Positions _ state.positions[color]; aliases: Aliases _ state.aliases[color]; FOR piece: Piece IN Piece DO myPos: FullPosition _ positions[piece]; IF myPos.onOff = on THEN { alias: Piece _ piece; kRow: [0..8) _ pos.row; dRow: INTEGER _ kRow-myPos.position.row; adr: NAT _ ABS[dRow]; kCol: [0..8) _ pos.col; dCol: INTEGER _ kCol-myPos.position.col; adc: NAT _ ABS[dCol]; IF Basics.BITOR[adc, adr] = 0 THEN GO TO notThisOne; IF alias IN Pawn THEN alias _ aliases[alias]; SELECT alias FROM p0, p1, p2, p3, p4, p5, p6, p7 => { IF adc = 1 AND adr = 1 THEN IF color = white THEN {IF dRow = 1 THEN GO TO addCoverage} ELSE {IF dRow = -1 THEN GO TO addCoverage}; }; wn, bn => { IF adr+adc = 3 AND adc IN [1..2] THEN GO TO addCoverage; }; wb, bb => { SELECT adr FROM # adc => GO TO notThisOne; 1 => GO TO addCoverage; ENDCASE => { stepR: INTEGER _ IF dRow > 0 THEN -1 ELSE 1; stepC: INTEGER _ IF dCol > 0 THEN -1 ELSE 1; THROUGH (0..adc) DO pos: Position _ [rowCol[kRow _ kRow + stepR, kCol _ kCol + stepC]]; IF state.board[pos.index].color # none THEN GO TO notThisOne; ENDLOOP; GO TO addCoverage; }; }; wr, br => { SELECT TRUE FROM adc = 0 => IF adr = 1 THEN GO TO addCoverage ELSE { r1: [0..8) _ MIN[kRow, myPos.position.row]; FOR r: [0..8) IN (r1..r1+adr) DO pos: Position _ [rowCol[r, kCol]]; IF state.board[pos.index].color # none THEN GO TO notThisOne; ENDLOOP; GO TO addCoverage; }; adr = 0 => IF adc = 1 THEN GO TO addCoverage ELSE { c1: [0..8) _ MIN[kCol, myPos.position.col]; FOR c: [0..8) IN (c1..c1+adc) DO pos: Position _ [rowCol[kRow, c]]; IF state.board[pos.index].color # none THEN GO TO notThisOne; ENDLOOP; GO TO addCoverage; }; ENDCASE; }; q => { SELECT adc FROM adr => { stepR: INTEGER _ IF dRow > 0 THEN -1 ELSE 1; stepC: INTEGER _ IF dCol > 0 THEN -1 ELSE 1; THROUGH (0..adc) DO pos: Position _ [rowCol[kRow _ kRow + stepR, kCol _ kCol + stepC]]; IF state.board[pos.index].color # none THEN GO TO notThisOne; ENDLOOP; GO TO addCoverage; }; 0 => { r1: [0..8) _ MIN[kRow, myPos.position.row]; FOR r: [0..8) IN (r1..r1+adr) DO pos: Position _ [rowCol[r, kCol]]; IF state.board[pos.index].color # none THEN GO TO notThisOne; ENDLOOP; GO TO addCoverage; }; ENDCASE => IF adr = 0 THEN { c1: [0..8) _ MIN[kCol, myPos.position.col]; FOR c: [0..8) IN (c1..c1+adc) DO pos: Position _ [rowCol[kRow, c]]; IF state.board[pos.index].color # none THEN GO TO notThisOne; ENDLOOP; GO TO addCoverage; }; }; k => { IF Basics.BITOR[adr, adc] = 1 THEN GO TO addCoverage; }; ENDCASE; EXITS notThisOne => {}; addCoverage => { totalWeight _ totalWeight + 1; IF stopOnFirst THEN EXIT; }; }; ENDLOOP; }; PieceCovers: PROC [state: GameState, color: WhiteBlack, pos: Position, piece: Piece] RETURNS [BOOL] = { myPos: FullPosition _ state.positions[color][piece]; IF myPos.onOff = on THEN { alias: Piece _ piece; kRow: [0..8) _ pos.row; dRow: INTEGER _ kRow-myPos.position.row; adr: NAT _ ABS[dRow]; kCol: [0..8) _ pos.col; dCol: INTEGER _ kCol-myPos.position.col; adc: NAT _ ABS[dCol]; IF Basics.BITOR[adc, adr] = 0 THEN RETURN [FALSE]; IF alias IN Pawn THEN alias _ state.aliases[color][alias]; SELECT alias FROM p0, p1, p2, p3, p4, p5, p6, p7 => { IF adc = 1 AND adr = 1 THEN IF color = white THEN {IF dRow = 1 THEN RETURN [TRUE]} ELSE {IF dRow = -1 THEN RETURN [TRUE]}; }; wn, bn => { IF adr+adc = 3 AND adc IN [1..2] THEN RETURN [TRUE]; }; wb, bb => { SELECT adr FROM # adc => RETURN [FALSE]; 1 => RETURN [TRUE]; ENDCASE => { stepR: INTEGER _ IF dRow > 0 THEN -1 ELSE 1; stepC: INTEGER _ IF dCol > 0 THEN -1 ELSE 1; THROUGH (0..adc) DO pos: Position _ [rowCol[kRow _ kRow + stepR, kCol _ kCol + stepC]]; IF state.board[pos.index].color # none THEN RETURN [FALSE]; ENDLOOP; RETURN [TRUE]; }; }; wr, br => { SELECT TRUE FROM adc = 0 => IF adr = 1 THEN RETURN [TRUE] ELSE { r1: [0..8) _ MIN[kRow, myPos.position.row]; FOR r: [0..8) IN (r1..r1+adr) DO pos: Position _ [rowCol[r, kCol]]; IF state.board[pos.index].color # none THEN RETURN [FALSE]; ENDLOOP; RETURN [TRUE]; }; adr = 0 => IF adc = 1 THEN RETURN [TRUE] ELSE { c1: [0..8) _ MIN[kCol, myPos.position.col]; FOR c: [0..8) IN (c1..c1+adc) DO pos: Position _ [rowCol[kRow, c]]; IF state.board[pos.index].color # none THEN RETURN [FALSE]; ENDLOOP; RETURN [TRUE]; }; ENDCASE; }; q => { SELECT adc FROM adr => { stepR: INTEGER _ IF dRow > 0 THEN -1 ELSE 1; stepC: INTEGER _ IF dCol > 0 THEN -1 ELSE 1; THROUGH (0..adc) DO pos: Position _ [rowCol[kRow _ kRow + stepR, kCol _ kCol + stepC]]; IF state.board[pos.index].color # none THEN RETURN [FALSE]; ENDLOOP; RETURN [TRUE]; }; 0 => { r1: [0..8) _ MIN[kRow, myPos.position.row]; FOR r: [0..8) IN (r1..r1+adr) DO pos: Position _ [rowCol[r, kCol]]; IF state.board[pos.index].color # none THEN RETURN [FALSE]; ENDLOOP; RETURN [TRUE]; }; ENDCASE => IF adr = 0 THEN { c1: [0..8) _ MIN[kCol, myPos.position.col]; FOR c: [0..8) IN (c1..c1+adc) DO pos: Position _ [rowCol[kRow, c]]; IF state.board[pos.index].color # none THEN RETURN [FALSE]; ENDLOOP; RETURN [TRUE]; }; }; k => { IF Basics.BITOR[adr, adc] = 1 THEN RETURN [TRUE]; }; ENDCASE; }; RETURN [FALSE]; }; materialWeights: REF PieceWeightArray _ NEW[PieceWeightArray _ [ 150, 150, 150, 150, 150, 150, 150, 150, 500, 320, 330, 900, 10000, 330, 320, 500 ]]; PieceWeightArray: TYPE = ARRAY Piece OF Weight; Weight: TYPE = NAT; CountBits: PROC [cover: SquareCoverage] RETURNS [w: CARDINAL] = { n: CARDINAL _ Basics.BITAND[0AAAAH, w _ LOOPHOLE[cover]]; w _ n/2 + (w-n); n _ Basics.BITAND[0CCCCH, w]; w _ n/4 + (w-n); n _ Basics.BITAND[0F0F0H, w]; w _ n/16 + (w-n); n _ Basics.BITAND[0FF00H, w]; w _ n/256 + (w-n); }; CountBitsInline: PROC [cover: SquareCoverage] RETURNS [w: CARDINAL] = INLINE { n: CARDINAL _ Basics.BITAND[0AAAAH, w _ LOOPHOLE[cover]]; w _ n/2 + (w-n); n _ Basics.BITAND[0CCCCH, w]; w _ n/4 + (w-n); n _ Basics.BITAND[0F0F0H, w]; w _ n/16 + (w-n); n _ Basics.BITAND[0FF00H, w]; w _ n/256 + (w-n); }; OtherColor: PROC [color: WhiteBlack] RETURNS [WhiteBlack] = INLINE { RETURN [LOOPHOLE[Basics.BITXOR[ORD[color], 1]]]; }; squareSize: NAT _ 40; whiteBackground: ImagerColor.Color _ ImagerColor.ColorFromGray[0.35]; blackBackground: ImagerColor.Color _ ImagerColor.ColorFromGray[0.65]; baseX: NAT _ 4; baseY: NAT _ 4; smallScale: REAL _ 3/8.0; letterScale: NAT _ 30; letterOffset: NAT _ 2; letterFontName: Rope.ROPE _ "Xerox/PressFonts/Cream-brr"; textFontName: Rope.ROPE _ "Xerox/TiogaFonts/Helvetica14"; textHeight: NAT _ 32; textWidth: NAT _ 256; minMaterial: INTEGER _ 5000; playLevel: NAT _ 3; debugLevel: NAT _ 1; defaultFanOut: NAT _ 8; depthCutoff: NAT _ 5; maxHistoryLen: NAT _ 400; MyData: TYPE = REF MyDataRep; MyDataRep: TYPE = MONITORED RECORD [ state: GameState _ NIL, stack: GameStateList _ NIL, lastShown: Board, paintCond: CONDITION, displayMoves: BOOL _ FALSE, busy: BOOL _ FALSE, stopRequested: BOOL _ FALSE, paintRequested: BOOL _ FALSE, flashRequested: NAT _ 0, autoAuto: BOOL _ FALSE, stackValid: BOOL _ FALSE, screenValid: BOOL _ FALSE, quit: BOOL _ FALSE, invertDisplay: BOOL _ FALSE, selected: FullPosition _ [off, [index[0]]], message1: ROPE _ NIL, message2: ROPE _ NIL, letterFont: ImagerFont.Font _ NIL, textFont: ImagerFont.Font _ NIL, wd: ROPE _ NIL ]; GameStateList: TYPE = LIST OF GameState; MaxFanOut: NAT = 20; ChessProcsClass: ViewerClasses.ViewerClass _ NEW[ViewerClasses.ViewerClassRec _ [ flavor: $Chess, notify: NotifyMe, destroy: DestroyMe, paint: PaintMe ]]; Init: Commander.CommandProc = { data: MyData _ NEW[MyDataRep]; menu: Menus.Menu _ Menus.CreateMenu[lines: 1]; tipTable: TIPUser.TIPTable _ NIL; NewBoard[data]; ViewerOps.RegisterViewerClass[$Chess, ChessProcsClass]; WITH ProcessProps.GetProp[$WorkingDirectory] SELECT FROM wd: ROPE => data.wd _ wd; ENDCASE; data.letterFont _ ImagerFont.Find[letterFontName]; data.textFont _ ImagerFont.Find[textFontName]; IF letterScale > 1 THEN data.letterFont _ ImagerFont.Scale[data.letterFont, letterScale]; tipTable _ TIPUser.InstantiateNewTIPTable["Chess.tip" ! FS.Error => CONTINUE]; IF tipTable = NIL THEN tipTable _ TIPUser.InstantiateNewTIPTable["///Chess.tip" ! FS.Error => CONTINUE]; IF tipTable = NIL THEN tipTable _ TIPUser.InstantiateNewTIPTable["///Chess/Chess.tip" ! FS.Error => CONTINUE]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["STOP!", StopButton, data]]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["Reset", ResetButton, data]]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["Refresh", RefreshButton, data]]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["Replay", ReplayButton, data]]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["Step", StepButton, data]]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["Stack", StackButton, data]]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["Auto", AutoButton, data]]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["Invert", InvertButton, data]]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["Dump", DumpButton, data]]; Menus.AppendMenuEntry[menu, Menus.CreateEntry["Load", LoadButton, data]]; TRUSTED { Process.Detach[FORK PaintWatcher[data, ViewerOps.CreateViewer[ flavor: $Chess, info: [ name: "ChessHack", openHeight: squareSize*8+baseY*2+ViewerSpecs.menuHeight*2, data: data, tipTable: tipTable, menu: menu ] ]]]; }; }; Commander.Register["ChessHack", Init]; END. 8copy -u /User/Atkinson/Chess/ _ ChessDisplayImpl.* Copyright c 1985, 1986 by Xerox Corporation. All rights reserved. Russ Atkinson (RRA) March 3, 1986 11:54:15 am PST Types Automatic player [state: GameState, move: Move] RETURNS [quit: BOOL _ FALSE] In Pass1 we try to make a rank ordering based on the "likely" best move. At the base level we try hard for pruning, since we don't get another chance at this level of move. We rather like moving the pawns forwards Slight advantage for advancing central pawns Slighter advantage for queen-side pawns Weight for coverage by pawns We are blocking the movement of an enemy pawn We rather like moving the knights off the edges We like to block checks with bishops It is a good idea to keep the rooks lined up The rooks are lined up A rook and a queen are lined up A rook and a queen are lined up Bias against moving the king, except via castle We like to castle But we don't really want to move the king At higher levels we try to be more approximate, which is faster. We can add a new move We don't even have to force out an old one (note that we add an extra entry beyond the fanout if we are trying to add or force out a check). Shift all of the moves up by one to make room for the new move in its appropriate slot Splice out a non-check move beyond the fanOut to keep our examinations down We have a mate, so forget all of the other moves. This is a quick stop for finding a mate There were no legal moves, so we are either checkmated or drawn In checkmate, but adjust value to prefer later checkmate We are behind, but we can draw The negative of the value is the worth of the draw We have a mate, guaranteed! Things are getting bad, so don't pursue this course any deeper We have a ways to go before getting out of our depth We are going to take a piece or place him in check, so check it out pretty closely. At this point we have to determine the best move based on the material difference and the flags. This is likely to be a problem, since we will lose the piece This gets the piece out of threat We like to castle Slight bias here towrds taking or checking We like to add threats to another piece Try not to repeat moves too much. Eventually we should avoid repetition that would force a draw (unless we want one, of course). Debug point after choice has been made Paint, Notify & Menu procedures [self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL _ FALSE] Our user wants the black squares on the bottom Erase the stacked state display Draw the stacked state (reduced) Attempt to show the current value [self: ViewerClasses.Viewer, input: LIST OF REF ANY] Trying for en passant capture, so modify the pMove a little Trying for castle, so check the legality [self: ViewerClasses.Viewer] [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] Make moves slowly [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] Push the current game state Pop the current game state [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] Utility procedures Special case, like en passant Get an initialized board A simple move, nothing more to do A castle, either king or queen side We are actually taking something A funny remove, as in en passant skip spaces & comments Get a move (it is a single token) Used to lock out input due to multiple input events. Input events are ignored if we are busy. Do this dynamically to allow experimentation with weights Moving stuff Check for interposing pieces Check for previous king or rook move Check for moving through check We could be in big trouble! This routine proposes legal moves, except that it does not check for moves that place the king in check. It can also propose only captuing moves, which is used in determining if the king really is in check. Propose a non-pawn move (no aliasing) It is possible to move, so propose such a move, then continue It is possible to capture, so propose such a move, then EXIT Propose a pawn move (aliasing possible) It is possible to move, so propose such a move, then continue It is possible to capture, so propose such a move, then EXIT Pawns It is possible to move forwards, so propose such a move It may be possible to move forwards two squares It is possible to move forwards two squares, so propose such a move It is possible to capture, so propose such a move It is possible to capture, so propose such a move Check for en passant capture. There was a two-space pawn move onto our row, so check for column Rooks Bishops kNights Queen Rook-type moves Bishop-type moves King We are not in check so we can try for a castle. Returns the number of pieces that color has covering the given position. It does not compensate for coverage by pieces pinned by discovered check, which makes this routine useful in determining check. A piece can never cover itself Pawns can be aliased due to reaching the farthest row Not on a diagonal, so skip it Fast test for adjacency On a column On a row On a diagonal On a column On a row Fast test for adjacency A piece can never cover itself Pawns can be aliased due to reaching the farthest row Not on a diagonal, so skip it Fast test for adjacency On a column On a row On a diagonal On a column On a row Fast test for adjacency An algorithm for counting bits due to Ed McCreight. Viewer stuff Goodies Minimum acceptable material Minimum acceptable play level Debugging display level Max fan out for moves Depth cutoff for following special cases Maximum # of moves [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] Build up the menu Registration ΚSΪ˜codešœ2™2Kšœ Οmœ7™BK™1—˜šΟk ˜ Kš œžœžœžœžœ˜%Kšœ žœ0˜?Kšœ žœ&˜8Kšœ žœ‡˜–Kšœ žœ˜(Kšžœžœ!˜)Kšœžœo˜{Kšœ žœ˜)Kšœ žœ+˜;Kšžœžœnžœ˜~Kšœžœ=˜HKšœžœ ˜Kšœžœ˜+Kšœ žœ ˜Kšœžœžœ˜)Kšœ žœžœ˜Kšœžœ5˜BKšœžœK˜^Kšœ žœ2˜AKšœ žœ˜Kšœ žœ˜)——headšœžœž˜Kšžœžœ ˜Kšžœ-žœ#žœo˜ΚKšœžœžœ ˜—head2•StartOfExpansiony -- [self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL _ FALSE]™Kš žœžœžœžœžœžœ˜Kšžœžœžœ˜Kšžœžœžœžœ˜—™Kšœ žœžœ˜K˜šΟnœžœžœžœ˜@šœ žœžœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœ žœžœ˜Kšœ žœžœ˜Kšœ žœžœ˜Kšœ žœž˜K˜—š Ÿœžœ"žœ žœžœžœ˜_Kšœ#˜#K˜Kšœ žœžœžœ˜šžœžœž˜Kšœ5˜5šžœžœ˜Kš œ žœžœžœžœ˜FKšœžœ˜šžœžœž˜Kšœ6˜6šžœžœ.žœ˜KKšžœžœ˜0K˜—Kšžœ˜—K˜—Kšžœ˜—K˜—šŸœžœ!žœžœ˜@K˜Kšœžœ ˜Kšœ1˜1šžœžœ˜šžœžœž˜šžœžœ˜Kšœ0˜0šžœžœ˜Kš œ žœžœžœžœ˜FKšœžœ˜šžœ.žœ˜6Kšžœ žœ ˜K˜—K˜—K˜—Kšžœ˜—K˜—Kšžœ˜ K˜—šœ žœžœžœ˜K˜—šœžœ žœ%žœžœžœžœžœ ˜‰K˜Kšœžœ˜Kšœ žœžœžœ˜,Kšœ žœžœžœ˜+Kšœ žœžœ˜(Kšœ žœžœ ˜-Kšœ!˜!Kšœ&˜&Kšœ%˜%Kšœ žœ˜Kšœ žœ˜Kšœžœ˜!Kšœ žœ˜(Kšœ žœžœ˜Kšœ4˜4˜š žœžœžœžœ ž˜0Kšœ8˜8šžœžœ˜šœ˜Kšœžœžœžœ™;K˜KšœH™HK˜Kšžœžœžœ ˜-šžœžœ˜Kšžœžœ˜#Kšœžœ1˜?Kšœ˜Kšœžœ˜Kšœ˜Kšœžœ˜Kšœ žœ˜Kšœ žœ˜Kšœ žœ˜Kšœ žœ˜Kšœ ˜ Kšžœžœžœ%˜:Kšœ"˜"šžœžœ˜Kšžœžœžœžœ˜,Kšœžœ˜ K˜—šžœ ˜ šžœ˜Kšœc™cšœ˜šžœžœ˜Kšžœžœ˜#šœ˜šžœžœ˜Kšžœžœ˜#Kšœžœ1˜>K˜šžœžœž˜šœ˜Kšœ˜Kšžœžœ ˜,K˜—Kšžœ˜—Kšžœžœ žœ ˜0Kšœ˜K˜—K˜—Kšœ žœ˜%Kšœžœ˜Kšœ žœ˜K˜šžœžœž˜Kšœ8˜8šžœžœ˜Kšœ/˜/K˜—Kšžœ˜—šžœžœžœ˜/Kšœ ˜ Kšœžœ˜ K˜—Kšžœžœžœ˜4Kšœ˜K˜—K˜—Kšœ žœ˜šžœžœž˜Kšœ8˜8šžœžœ˜Kšœ1˜1K˜—Kšžœ˜—šžœžœž˜šœžœ˜,Kšœ ˜ Kšœžœ˜ K˜—šžœ˜ Kšžœžœžœ˜4šžœž˜˜#Kšœ(™(Kšœ˜Kš œžœžœžœžœ˜/Kšœ˜šžœž˜Kšœžœ žœ˜)Kšœžœ žœ˜*Kšœžœ žœ˜)Kšœžœ žœ˜)Kšœžœ žœ˜*Kšœžœ žœ˜)Kšžœžœ˜—šžœ ž˜šœ˜Kšœ,™,—šœ˜Kšœ'™'—Kšžœ˜—šžœ žœžœ˜Kšœ™Kšœ˜Kšœ˜Kšœ˜šžœ žœ˜Kšœ'˜'Kšœ+˜+šžœ ž˜šœ˜Kšœ˜—šœ˜Kšžœ žœžœ˜+—Kšžœ˜—K˜—šžœ žœ˜Kšœ'˜'Kšœ+˜+šžœ ž˜šœ˜Kšœ˜—šœ˜Kšžœ žœžœ˜+—Kšžœ˜—K˜—šžœžœžœ˜Kšœ'˜'Kšœ-˜-šžœžœ žœž˜-Kšœ-™-Kšœ˜—K˜—K˜—K˜—˜ Kšœ/™/K˜šžœž˜Kšœ˜Kšœ˜šžœ˜ šžœž˜Kšœ˜Kšœ˜Kšžœ˜———Kšžœ žœ˜&K˜—˜ Kšœ$™$K˜Kšžœ žœ˜&K˜—˜K™,K˜/K˜1K˜1šžœžœž˜Kšœžœ˜-Kšœžœ˜-Kšœžœ˜.šœžœ*žœ,˜lKšœ™Kšœ˜—šœžœ)žœ+˜kKšœ™Kšœ˜—šœžœ)žœ+˜kKšœ™Kšœ˜—Kšžœ˜—K˜—˜Kšœ/™/šžœ˜šžœ˜Kšœ™—šžœ˜Kšœ)™)———Kšžœ˜—Kšœ˜——K˜—šžœ˜Kšœ@™@šžœ žœ˜Kšœ˜Kšžœžœ ˜-Kšœ žœ˜K˜—šžœ žœž˜Kšœ4˜4šœ˜Kšœ˜Kš œ žœžœžœžœ˜@Kšœžœ˜šžœž˜Kšœ!˜!Kšœ!˜!Kšžœ˜—Kšœžœ˜K˜—Kšžœ˜—K˜——Kšœ˜Kšœ˜šžœ ž˜Kšœžœ ˜Kšžœžœžœ˜%K˜ Kšžœ˜—šžœžœ žœžœ˜9Kšœ™šžœžœ žœ!ž˜OKšœŒ™ŒKšžœžœ!˜C—š žœžœž œžœž˜/KšœV™VKšœ ˜ Kšœ˜Kšœ˜Kšœ˜Kšžœ˜—Kšœ˜Kšœ˜Kšœ˜Kšœ˜šžœžœžœžœ˜AKšœK™Kšžœžœžœž˜'Kšœ ˜ Kšœ˜Kšœ˜Kšœ˜Kšžœ˜—Kšœ˜K˜—K˜—šžœ˜Kšœ1™1Kšœ˜Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœžœ˜Kšœ˜—K˜—Kšœ˜—Kšœ-˜-K˜—Kšžœ˜—K˜—šžœ žœžœ˜Kšœ'™'—šžœžœ˜Kšœ?™?šžœžœž˜šœ˜šœ˜Kšœ8™8——šœ˜Kšœ™—šžœ˜Kšœ2™2——Kšœ ˜ Kšžœ˜K˜—šžœžœž˜-Kšœ˜K˜ Kšœžœ˜Kšžœžœžœ ˜-šžœžœ˜Kšžœžœ˜#Kšœžœ˜Kšœ žœ˜šžœ žœžœ˜+K˜šžœžœ˜Kšœžœ˜Kšœ(˜(K˜—K˜—šžœžœž˜šœ˜K™Kšœ˜K˜—šœ žœ˜K™>Kšœ˜K˜—šœ žœ˜Kšœ4™4Kšœžœ˜/K˜—šœ#žœžœ žœ˜aKšœS™SKšœžœ˜)K˜—šžœ˜ Kšœ`™`K˜ Kšžœžœžœ%˜:Kšœ6˜6šžœžœž˜šœ˜Kšœ<™Kšžœ˜—Kšœžœ˜K˜—Kšžœ˜——Kšžœ˜—K˜K˜—–‚ -- [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]˜!Kš ~™~šžœžœž˜˜šžœ žœž˜šœžœžœ˜(Kšœžœ˜,Kšœžœ˜Kšœ˜Kšœžœ˜K˜—Kšžœ˜——Kšžœ˜—K˜K˜—–‚ -- [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]˜Kš ~™~šžœžœž˜˜šžœ žœž˜šœžœžœ˜(˜šžœžœ ˜Kšœ(žœ˜.Kšžœžœ˜ Kšœ˜—Kšœžœ˜%Kšœžœžœ=˜MKšœ<žœ˜BKšœ˜Kšžœ ˜Kšžœ˜K˜—Kšœžœ˜K˜—Kšžœ˜——Kšžœ˜—K˜K˜—–‚ -- [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]˜Kš ~™~šžœžœž˜˜šžœ žœž˜šœžœžœ˜(Kšœ˜˜šžœžœ ˜Kšœ(žœ˜.Kšžœžœ˜ Kšœ˜—Kšœžœ˜%Kšœžœžœ˜!Kšœ>žœ˜Dšžœžœžœ˜!Kšœ=žœ˜CKšœ˜K˜—Kšžœ ˜ Kšžœ˜K˜—Kšœ˜Kšœžœ˜K˜—Kšžœ˜——Kšžœ˜—K˜K˜——–y -- [self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL _ FALSE]™šŸœžœžœžœ˜9Kšœžœžœ˜Kšœžœ˜ Kšœžœ˜ šœ(˜(Kšœžœžœ˜%—Kšžœžœ˜-K˜š žœžœž œžœ ž˜$šžœž˜Kšœžœ˜Kšœžœ˜Kšžœ˜—Kšžœ˜—Kšžœ žœ$˜5šžœž˜Kšœžœžœ ˜4Kšžœ žœ%˜9—Kšžœ˜K˜K˜—šŸ œžœžœ˜/Kšœ˜Kšœ%˜%Kšžœ?˜Ašžœžœžœž˜"Kšœ˜Kšœ žœžœ˜Kš žœžœžœžœžœ˜6KšžœF˜Hšžœž˜Kšœžœ˜Kšœ žœ˜Kšœ žœ˜Kšžœ˜—KšžœJ˜Lšžœ žœž˜šœ˜šžœž˜Kšœ™KšžœI˜K—K˜—Kšžœ˜—Kšžœ˜—Kšžœ?˜AK˜K˜—š Ÿ œžœžœžœž œ˜HKšœ˜Kšœ žœ˜Kš œžœžœžœžœ˜"šœ˜Kšœ™—Kšœ˜˜Kš žœžœžœ žœžœ˜.šŸ œžœ˜K˜ K˜K˜K˜K˜K˜K˜Kšœ ˜ šžœ ž˜˜Kšœ!™!Kšœ9˜9K˜—˜Kšœ#™#Kš œ žœžœžœžœ˜IKšœO˜OK˜—˜ Kšœ ™ K˜šžœžœ˜Kšœ ™ šžœ ž˜˜ Kšœ˜Kšœ˜K˜—Kšžœ˜—K˜—Kšœj˜jK˜—Kšžœ˜—K˜—šž˜šœžœ˜Kšœ™—šœ žœžœ˜2K™!—Kš žœžœ žœžœ žœžœ˜<šœ ˜ Kš œžœžœžœžœžœ˜=Kšœ˜—Kšžœ˜—Kšœ žœ ˜Kšžœ!žœžœ˜DK˜—Kšžœžœ˜K˜K˜—šŸ œžœB˜Qšžœ#žœžœž˜9Kšœ&˜&Kšžœžœ"žœžœ˜2Kšžœ˜—Kšœ5˜5Kšœžœ˜Kšœ˜K˜K˜—šŸ œžœžœžœžœžœžœ˜]Kšœ˜Kšœžœ˜šžœžœž˜šžœž˜#šžœ žœ,žœ˜UKšžœ˜ Kšžœ˜———Kšž œ˜š žœ žœžœžœ ž˜>Kšžœ˜Kšžœ˜—K˜K˜—šŸœžœžœžœžœžœžœ˜IKšœ^™^Kš žœžœ žœžœžœ˜*Kšœ˜Kšœžœ˜Kšžœžœ˜K˜K˜—šŸœžœ*žœ˜@šœ žœ˜4Kšœ žœžœ˜/Kšœ žœžœ˜2Kšœ žœžœ ˜+K˜—Kšœ žœ˜Kšœžœ˜Kšœ9˜9Kšœ9˜9šžœž˜šžœ˜Kšœžœ ˜3Kšœ/˜/K˜—šžœ˜Kšœ˜Kšœ˜K˜——Kšœ˜K˜#šžœžœž˜Kšœ9™9Kšœ-˜-Kšžœ˜—Kšœ9˜9K˜K˜—šŸœžœ˜+Kšœžœ˜3Kšœžœ˜3šžœžœ ž˜&K˜*šžœž˜KšœC˜C—Kšžœ˜—K˜——šœ ™ šŸœžœ'žœžœžœ žœžœ˜bKšœ%˜%Kšœ žœ˜Kšœžœ˜Kšœ"˜"Kšœ!˜!Kšœ&˜&Kšœ˜Kšœ&˜&K˜šžœžœžœ˜Kšœ$˜$šžœ ž˜šœžœžœžœ˜Kšœ˜Kšœ<˜˜>K˜—Kšžœ˜—Kšœ<˜˜>Kšœ(˜(Kšœ&˜&Kšœ˜šžœžœžœž˜0Kšœ2˜2—šžœ žœž˜šœ"˜"Kšœžœžœžœ˜-Kšœ$žœžœžœ˜JKšœ$žœžœžœ˜JKšœ3˜3Kšœ6˜6Kšœ+˜+Kšœ%˜%K˜—šœ"˜"Kšœ%˜%Kšœ ˜ Kšœ˜Kšžœžœžœ˜.Kšœ,˜,Kšœ/˜/Kšœ>˜>K˜—Kšžœ˜—Kšœ&˜&K˜K˜—K˜K˜—š Ÿœžœ!žœžœ žœžœ˜gKšœ!˜!Kšœ&˜&Kšœ6˜6Kšžœžœ˜9K˜K˜—š Ÿ œžœ žœžœžœ˜[Kšœ!˜!Kšœ&˜&Kšœžœžœžœ˜-Kš œžœžœ žœžœ˜0Kšœ(˜(Kšœ$žœ žœžœ˜CKšœ$žœ žœžœ˜CKšœ$žœ žœžœ˜CKšœ3˜3Kšœ3˜3šžœžœž˜KšœS˜Sšœ%˜%Kšœ™šžœ ˜šžœžœžœ&ž˜CKšžœžœžœžœ˜1Kšž˜—šžœžœžœ&ž˜CKšžœžœžœžœ˜1Kšžœ˜——Kšœ$™$šžœžœžœž˜+Kšœ˜šžœž˜ šžœž˜Kšœžœžœ˜Kšžœ˜——Kšžœ˜—Kšœ™šžœžœž˜Kšœ(žœ žœžœ˜@Kšœ(žœ žœžœ˜@Kšžœ˜—Kšžœžœ7˜CKšžœ ˜K˜—Kšžœ˜—K˜K˜—š Ÿ œžœžœ žœžœ˜NKšœ!˜!šžœžœ˜Kšœ™šœ˜šžœžœ˜Kšœžœ˜ Kšœžœ˜K˜K˜—K˜—Kšœžœ˜ šžœžœžœž˜(Kšœ3˜3šžœž˜Kšœ0˜0—Kšžœ˜—K˜—K˜K˜—šœ$žœžœ˜/K˜—š Ÿ œžœžœ4žœžœžœ˜hK˜—šŸœžœVžœžœ˜}KšœΟ™ΟK˜Kšœ˜Kšœ)˜)Kšœžœžœžœ˜=Kš œ žœžœžœžœ˜6Kšœ žœ ˜Kšœ˜Kšœ˜K˜K˜K˜Kšœžœžœ˜K˜š Ÿœžœžœžœ žœžœ˜EKšœ%™%šžœžœžœ˜Kšœ<˜Kšœ7™7Kšžœ'žœžœ˜5šžœ žœ˜Kšœ/™/Kšœ˜Kšœ!˜!šžœžœ˜KšœC™CKšžœ'žœžœ˜5Kšžœžœžœ˜K˜—Kšœ˜K˜—K˜—šžœžœ˜Kšœ˜Kšœ!˜!šžœžœ˜Kšœ1™1Kšœ˜Kšžœžœžœ˜K˜—K˜—šžœžœ˜Kšœ˜Kšœ!˜!šžœžœ˜Kšœ1™1Kšœ˜Kšžœžœžœ˜K˜—K˜—šžœ žœžœ˜0Kšœ™Kšœ8˜8Kšœ"˜"Kšœ˜šžœžœžœ˜Kšœ˜š žœžœžœžœžœ*žœ˜fKšœA™AKšœžœ˜šžœž˜˜KšœN˜NKšžœ_žœžœ˜mKšžœžœžœ˜K˜—Kšžœ˜—K˜—K˜—K˜—K˜—K˜—˜ Kš‘™Kšžœžœžœžœžœžœžœ˜SKšžœžœžœžœžœžœžœ˜RKšžœžœžœžœžœžœžœ˜SKšžœžœžœžœžœžœžœ˜RK˜—˜ Kš‘™Kšžœžœžœžœžœžœžœžœ˜jKšžœžœžœžœžœžœžœžœ˜iKšžœžœžœžœžœžœžœžœ˜iKšžœžœžœžœžœžœžœžœ˜hK˜—˜ Kš‘™šžœžœ˜Kš žœžœžœžœžœ˜AKš žœžœžœžœžœ˜Bšžœžœ˜Kš žœžœžœžœžœ˜AKš žœžœžœžœžœ˜BKšœ˜—K˜—šžœžœ˜Kš žœžœžœžœžœ˜BKš žœžœžœžœžœ˜Cšžœžœ˜Kš žœžœžœžœžœ˜BKš žœžœžœžœžœ˜CKšœ˜—K˜—K˜—˜Kš‘™Kšœ™Kšžœžœžœžœžœžœžœ˜SKšžœžœžœžœžœžœžœ˜RKšžœžœžœžœžœžœžœ˜SKšžœžœžœžœžœžœžœ˜RK™Kšžœžœžœžœžœžœžœžœ˜jKšžœžœžœžœžœžœžœžœ˜iKšžœžœžœžœžœžœžœžœ˜iKšžœžœžœžœžœžœžœžœ˜hK˜—˜Kš‘™šžœžœ˜Kš žœžœžœžœžœ˜AKšœžœžœžœ˜*Kš žœžœžœžœžœ˜BKšœ˜—Kš žœžœžœžœžœ˜AKš žœžœžœžœžœ˜Bšžœžœ˜Kš žœžœžœžœžœ˜BKšœžœžœžœ˜+Kš žœžœžœžœžœ˜CKšœ˜—š žœžœ žœžœžœ˜&šžœžœžœ˜#Kšœ/™/Kšœžœ˜ K˜ Kšœ&˜&Kš žœžœžœžœžœ˜.Kšœ'˜'Kš žœžœžœžœžœ˜.K˜—K˜—K˜—Kšžœžœ˜—K˜K˜—š Ÿ œžœCžœžœžœžœ ˜ŠKšœΙ™ΙKšœ&˜&Kšœ.˜.Kšœ(˜(šžœžœž˜Kšœ'˜'šžœžœ˜K˜Kšœ˜Kšœžœ˜(Kšœžœžœ˜Kšœ˜Kšœžœ˜(Kšœžœžœ˜š žœžœžœžœžœ ˜4Kšœ™—šžœžœžœ˜-Kšœ5™5—šžœž˜šœ#˜#šžœ žœ ž˜šžœ˜Kš žœžœ žœžœžœ ˜)Kš žœžœ žœžœžœ˜+——K˜—šœ ˜ Kš žœ žœžœžœžœžœ ˜8K˜—šœ ˜ šžœž˜šœ žœžœ ˜Kšœ™—šœžœžœ ˜Kšœ™—šžœ˜ Kš œžœžœ žœžœ˜,Kš œžœžœ žœžœ˜,šžœ ž˜KšœC˜CKšžœ%žœžœžœ ˜=Kšžœ˜—Kšžœžœ ˜K˜——K˜—šœ ˜ šžœžœž˜š œ žœ žœžœžœ žœ˜3Kšœ ™ Kšœ žœ˜,šžœ žœž˜ Kšœ"˜"Kšžœ%žœžœžœ ˜=Kšžœ˜—Kšžœžœ ˜Kšœ˜—š œ žœ žœžœžœ žœ˜3Kšœ™Kšœ žœ˜+šžœ žœž˜ Kšœ"˜"Kšžœ%žœžœžœ ˜=Kšžœ˜—Kšžœžœ ˜Kšœ˜—Kšžœ˜—K˜—šœ˜šžœž˜šœ˜Kšœ ™ Kš œžœžœ žœžœ˜,Kš œžœžœ žœžœ˜,šžœ ž˜KšœC˜CKšžœ%žœžœžœ ˜=Kšžœ˜—Kšžœžœ ˜Kšœ˜—šœ˜Kšœ ™ Kšœ žœ˜+šžœ žœž˜ Kšœ"˜"Kšžœ%žœžœžœ ˜=Kšžœ˜—Kšžœžœ ˜Kšœ˜—šžœžœ žœ˜Kšœ™Kšœ žœ˜,šžœ žœž˜ Kšœ"˜"Kšžœ%žœžœžœ ˜=Kšžœ˜—Kšžœžœ ˜Kšœ˜——K˜—šœ˜š žœžœžœžœžœ ˜5Kšœ™—K˜—Kšžœ˜—šž˜Kšœ˜˜K˜Kšžœ žœžœ˜K˜——K˜—Kšžœ˜—K˜K˜—šŸ œžœDžœžœ˜gKšœ4˜4šžœžœ˜K˜Kšœ˜Kšœžœ˜(Kšœžœžœ˜Kšœ˜Kšœžœ˜(Kšœžœžœ˜š žœžœžœžœžœ˜2Kšœ™—šžœžœžœ%˜:Kšœ5™5—šžœž˜šœ#˜#šžœ žœ ž˜šžœ˜Kš žœžœ žœžœžœ˜%Kš žœžœ žœžœžœ˜'——K˜—šœ ˜ Kš žœ žœžœžœžœžœ˜4K˜—šœ ˜ šžœž˜šœ žœžœ˜Kšœ™—šœžœžœ˜Kšœ™—šžœ˜ Kš œžœžœ žœžœ˜,Kš œžœžœ žœžœ˜,šžœ ž˜KšœC˜CKšžœ%žœžœžœ˜;Kšžœ˜—Kšžœžœ˜K˜——K˜—šœ ˜ šžœžœž˜š œ žœ žœžœžœžœ˜/Kšœ ™ Kšœ žœ˜,šžœ žœž˜ Kšœ"˜"Kšžœ%žœžœžœ˜;Kšžœ˜—Kšžœžœ˜Kšœ˜—š œ žœ žœžœžœžœ˜/Kšœ™Kšœ žœ˜+šžœ žœž˜ Kšœ"˜"Kšžœ%žœžœžœ˜;Kšžœ˜—Kšžœžœ˜Kšœ˜—Kšžœ˜—K˜—šœ˜šžœž˜šœ˜Kšœ ™ Kš œžœžœ žœžœ˜,Kš œžœžœ žœžœ˜,šžœ ž˜KšœC˜CKšžœ%žœžœžœ˜;Kšžœ˜—Kšžœžœ˜Kšœ˜—šœ˜Kšœ ™ Kšœ žœ˜+šžœ žœž˜ Kšœ"˜"Kšžœ%žœžœžœ˜;Kšžœ˜—Kšžœžœ˜Kšœ˜—šžœžœ žœ˜Kšœ™Kšœ žœ˜,šžœ žœž˜ Kšœ"˜"Kšžœ%žœžœžœ˜;Kšžœ˜—Kšžœžœ˜Kšœ˜——K˜—šœ˜š žœžœžœžœžœ˜1Kšœ™—K˜—Kšžœ˜—K˜—Kšžœžœ˜K˜K˜—šœžœžœ˜@K˜'K˜(Kšœ˜—Kšœžœžœžœ˜/šœžœžœ˜K˜—šŸ œžœžœžœ˜AKšœ3™3Kšœžœ žœ žœ ˜9Kšœ˜Kšœ žœ ˜Kšœ˜Kšœ žœ ˜Kšœ˜Kšœ žœ ˜Kšœ˜K˜K˜—š Ÿœžœžœžœžœ˜NKšœžœ žœ žœ ˜9Kšœ˜Kšœ žœ ˜Kšœ˜Kšœ žœ ˜Kšœ˜Kšœ žœ ˜Kšœ˜K˜K˜—šŸ œžœžœžœ˜DKšžœžœžœžœ˜0K˜——–y -- [self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL _ FALSE]™ –y -- [self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL _ FALSE]šœ™Kšœ žœ˜KšœE˜EKšœE˜EKšœžœ˜Kšœžœ˜Kšœ žœ ˜Kšœ žœ˜Kšœžœ˜Kšœžœ ˜9Kšœžœ"˜9Kšœ žœ˜Kšœ žœ˜šœ žœ˜Kšœ™—šœ žœ˜Kšœ™—šœ žœ˜K™—šœžœ˜K™—šœ žœ˜Kšœ(™(—šœžœ˜K™—K˜—Kšœžœžœ ˜šœ žœž œžœ˜$Kšœžœ˜Kšœžœ˜Kšœ˜Kšœ ž œ˜Kšœžœžœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœžœ˜Kšœ žœžœ˜Kšœ žœžœ˜Kšœ žœžœ˜Kšœžœžœ˜Kšœžœžœ˜K˜+Kšœ žœžœ˜Kšœ žœžœ˜Kšœžœ˜"Kšœžœ˜ Kšœžœž˜K˜—šœžœžœžœ ˜(Kšœ žœ˜K˜—šœ-žœ!˜QKšœ˜Kšœ˜Kšœ˜Kšœ˜K˜K˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]šœ˜Kš H™HKšœžœ ˜Kšœ.˜.Kšœžœ˜!Kšœ˜Kšœ7˜7šžœ)žœž˜8Kšœžœ˜Kšžœ˜—Kšœ2˜2Kšœ.˜.KšžœžœB˜YKšœ8žœ žœ˜Nšžœ žœž˜Kšœ;žœ žœ˜Q—šžœ žœž˜KšœAžœ žœ˜W—K˜Kšœ™KšœJ˜JKšœK˜KKšœO˜OKšœM˜MKšœI˜IKšœK˜KKšœI˜IKšœM˜MKšœI˜IKšœI˜IK˜šžœ˜ šœžœ+˜>Kšœ˜šœ˜Kšœ˜Kšœ:˜:Kšœ ˜ Kšœ˜Kšœ ˜ Kšœ˜—Kšœ˜—K˜—K˜——šœ ™ K˜&K˜—Kšžœ˜K˜—…—Ν;