Author: Tony Worsman
Date: 10:41:47 02/24/03
Go up one level in this thread
'******************************************************************** ' File: MINIMAXE.BAS (1.0a /Me) ' Purpose: A Didactic (Instructional) Chess Program ' Project: MiniMAX in BASIC ' Compiler: The program compiles with ' Visual Basic for DOS ' Authors: D.Steinwender, Ch.Donninger ' Date: May 1st, 1995 '******************************************************************** DEFINT A-Z ' Declaration of subroutines DECLARE FUNCTION AlphaBeta% (Alpha%, Beta%, Distance%) DECLARE FUNCTION AssessPosition% (Alpha%, Beta%, Side%) DECLARE FUNCTION BPAssessment% (Square%, Row%, Column%, developed%) DECLARE SUB CommandLoop () DECLARE SUB ComputerMove () DECLARE SUB ComputingDepth () DECLARE SUB CopyMainVariant (CurrMove%) DECLARE SUB DisplayBoard (BoardOnly%) DECLARE SUB DisplayMainVariant () DECLARE SUB DisplayMove (CurrMove%) DECLARE SUB FlipBoard () DECLARE SUB GameOver () DECLARE SUB GenerateMoves (AllMoves%) DECLARE SUB InitAssessment () DECLARE SUB InitGameTree () DECLARE SUB Initialize () DECLARE FUNCTION InputMove% (Move$) DECLARE SUB InputPosition () DECLARE FUNCTION IsAttackingSquare% (Square%, Side%) DECLARE SUB MoveBack () DECLARE SUB MoveList () DECLARE FUNCTION NextBestMove% () DECLARE SUB PerformMove (CurrMove%) DECLARE SUB PrintBack () DECLARE SUB PrintLogo () DECLARE SUB PrintMove (CurrMove%) DECLARE SUB PrintPosition () DECLARE SUB ReadPieces (Side%) DECLARE SUB RegisterCaptureMove (Source%, Dest%) DECLARE SUB RegisterEPMove (Source%, Dest%, ep%) DECLARE SUB RegisterMove (Source%, Dest%) DECLARE SUB RegisterPromotion (Source%, Dest%) DECLARE FUNCTION SquareNotation$ (SquareNum%) DECLARE FUNCTION SquareNumber% (SquareNot$) DECLARE SUB TakeBackMove (CurrMove%) DECLARE FUNCTION WPAssessment% (Square%, Row%, Column%, developed%) '-------------------------------------------------------------------- ' Definition of symbolic constants. '-------------------------------------------------------------------- CONST BoardDim = 119 ' Dimension of expanded chess board CONST MaxDepth = 19 ' Maximum search depth CONST MoveDirections = 15 ' No. of move directions for all pieces CONST PieceTypes = 6 ' No. of piece types - considering ' MoveDirections (wQueen = bQueen) CONST MoveStackDim = 1000 ' Dimension of the move stack 'CONST MinDepth = 4 ' Minimum full ply search depth ' Pieces <color><piece> CONST BK = -6 ' Black pieces CONST BQ = -5 CONST BN = -4 ' Knight CONST BB = -3 CONST BR = -2 CONST BP = -1 CONST Empty = 0 ' Empty Square CONST WP = 1 ' White pieces CONST WR = 2 CONST WB = 3 CONST WN = 4 CONST WQ = 5 CONST WK = 6 CONST Edge = 100 ' Frame of the chess program ' Material value of the pieces CONST ValP = 100 CONST ValR = 500 CONST ValB = 350 CONST ValN = 325 CONST ValQ = 900 CONST ValK = 0 ' As both sides have just one king ' the value can be set to zero ' Check mate assessment CONST CheckMateVal = 32000 CONST MaxPos = ValB ' Maximum of the position assessment ' Bonus for main variants and killer moves; ' used for the sorting of moves CONST MainVarBonus = 500 CONST Killer1Bonus = 250 CONST Killer2Bonus = 150 ' Total material value in the initial position CONST MaterialTotal = 4 * (ValR + ValB + ValN) + (2 * ValQ) CONST EndgameMaterial = 4 * ValR + 2 * ValB ' Square numbers of frequently used Squares ' ("if Board(E1)=WK" means "if Board(25)=6") CONST A1 = 21 CONST B1 = 22 CONST C1 = 23 CONST D1 = 24 CONST E1 = 25 CONST F1 = 26 CONST G1 = 27 CONST H1 = 28 CONST C2 = 33 CONST H2 = 38 CONST A3 = 41 CONST C3 = 43 CONST D3 = 44 CONST E3 = 45 CONST A6 = 71 CONST C6 = 73 CONST D6 = 74 CONST E6 = 75 CONST H6 = 78 CONST A7 = 81 CONST C7 = 83 CONST H7 = 88 CONST A8 = 91 CONST B8 = 92 CONST C8 = 93 CONST D8 = 94 CONST E8 = 95 CONST F8 = 96 CONST G8 = 97 CONST H8 = 98 ' Values of rows and columns CONST AColumn = 1 CONST BColumn = 2 CONST CColumn = 3 CONST DColumn = 4 CONST EColumn = 5 CONST FColumn = 6 CONST GColumn = 7 CONST HColumn = 8 CONST Row1 = 2 CONST Row2 = 3 CONST Row3 = 4 CONST Row4 = 5 CONST Row5 = 6 CONST Row6 = 7 CONST Row7 = 8 CONST Row8 = 9 ' Castling numbering (index into castling array) ' or move is not a castling move CONST NoCastlingMove = 0 CONST ShortCastlingMove = 1 CONST LongCastlingMove = 2 ' Player who is to make a move CONST White = 1 CONST Black = -1 ' Symbolic logical constants CONST True = 1 CONST False = 0 CONST Legal = 1 CONST Illegal = 0 '------------------------------------------------------------------- ' Definition of data types. '------------------------------------------------------------------- ' Information for one move, the data type of the move stack TYPE MoveType FromSqr AS INTEGER ' From Square ToSqr AS INTEGER ' To Square CapturedPiece AS INTEGER ' captured piece PromotedPc AS INTEGER ' transformed/promoted piece CastlingNr AS INTEGER ' type of castling move EpSquare AS INTEGER ' Enpassant Square Value AS INTEGER ' Assessment for the sorting of moves END TYPE ' Index of the pieces into the offset list and ' fast/slow pace indication (used by the move generator) TYPE PcOffsetType Begin AS INTEGER Ende AS INTEGER Fastpaced AS INTEGER END TYPE ' Information on pawn/pieces constellations TYPE BothColorsType White AS INTEGER Black AS INTEGER END TYPE ' Information on From/To Square (moves without additional information) ' Used for storing promising moves (main variants, ' killer moves) TYPE FromToType FromSqr AS INTEGER ToSqr AS INTEGER END TYPE ' Data structure for storing killer moves. TYPE KillerType Killer1 AS FromToType Killer2 AS FromToType END TYPE '-------------------------------------------------------------------- ' Definition of global variables and tables '-------------------------------------------------------------------- DIM SHARED Board(BoardDim) AS INTEGER DIM SHARED EpSquare(MaxDepth) AS INTEGER DIM SHARED MoveStack(MoveStackDim) AS MoveType DIM SHARED MovedCnt(H8) AS INTEGER ' Counts how often a piece has moved from ' a Square. Used to determine castling ' rights (also useable for ' assessment) DIM SHARED Castling(2) AS INTEGER ' Has white/black already castled? DIM SHARED Index AS INTEGER ' Index into MoveStack ' Registers the position in MoveStack. Moves of depth 'n' are stored in range ' (StackLimit(n), StackLimit(n+1)) in MoveStack. DIM SHARED StackLimit(MaxDepth) AS INTEGER DIM SHARED MVar(MaxDepth, MaxDepth) AS FromToType ' Main variants table DIM SHARED KillerTab(MaxDepth) AS KillerType ' Killer moves table ' Tables for assessment function DIM SHARED PawnControlled(BoardDim) AS BothColorsType ' Squares that are ' controlled by pawns DIM SHARED Pawns(HColumn + 1) AS BothColorsType ' No. of pawns per column DIM SHARED Rooks(HColumn + 1) AS BothColorsType ' No. of rooks per column DIM SHARED Mobility(MaxDepth) AS INTEGER ' Mobility of bishop and rook DIM SHARED ToSquare(MaxDepth) AS INTEGER ' ToSquare of the moves, used for ' sorting of moves and for extension ' of searches DIM SHARED wKing AS INTEGER ' Position of the white king DIM SHARED bKing AS INTEGER ' Position of the black king DIM SHARED MaterialBalance(MaxDepth) AS INTEGER ' Mat. balance betw. white and black DIM SHARED MaterialSum(MaxDepth) AS INTEGER ' Total material value at the board DIM SHARED MoveColor AS INTEGER ' Who is to make a move DIM SHARED PlayerPlayer AS INTEGER ' Player vs. player (memo) mode off/on DIM SHARED Printing AS INTEGER ' Printing moves off/on DIM SHARED MinDepth AS INTEGER ' Generally, searches are performed ' until MinDepth DIM SHARED MaxExtension AS INTEGER ' Extensions in the search tree because of ' checks or captures are only carried out ' until MaxExtension (otherwise ' the search can explode) DIM SHARED Depth AS INTEGER ' Search depth = no. of half moves from the ' initial position DIM SHARED NodeCount AS LONG ' No. of examined positions/nodes DIM SHARED LastMove AS INTEGER ' Last performed move DIM SHARED InCheck AS INTEGER ' Player is being checked DIM SHARED MoveCount AS INTEGER ' No. of half-moves performed so far DIM SHARED isWhiteLast AS INTEGER ' For printing control ' Initial position of 10-by-12 board DIM SHARED InitialPosition(BoardDim) AS INTEGER FOR i = 0 TO BoardDim READ InitialPosition(i) NEXT i DATA 100,100,100,100,100,100,100,100,100,100 DATA 100,100,100,100,100,100,100,100,100,100 DATA 100, 2 , 4 , 3 , 5 , 6 , 3 , 4 , 2 ,100 DATA 100, 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,100 DATA 100, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,100 DATA 100, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,100 DATA 100, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,100 DATA 100, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,100 DATA 100,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,100 DATA 100,-2 ,-4 ,-3 ,-5 ,-6 ,-3 ,-4 ,-2 ,100 DATA 100,100,100,100,100,100,100,100,100,100 DATA 100,100,100,100,100,100,100,100,100,100 ' Move generator tables DIM SHARED Offset(MoveDirections) AS INTEGER Offset(0) = -9 ' Diagonal steps Offset(1) = -11 Offset(2) = 9 Offset(3) = 11 Offset(4) = -1 ' Straight steps Offset(5) = 10 Offset(6) = 1 Offset(7) = -10 Offset(8) = 19 ' Knight steps Offset(9) = 21 Offset(10) = 12 Offset(11) = -8 Offset(12) = -19 Offset(13) = -21 Offset(14) = -12 Offset(15) = 8 DIM SHARED PcOffset(PieceTypes) AS PcOffsetType PcOffset(Empty).Begin = 0 ' Empty Square PcOffset(Empty).Ende = 0 PcOffset(Empty).Fastpaced = False PcOffset(WP).Begin = -1 ' Pawn moves are produced separately PcOffset(WP).Ende = -1 PcOffset(WP).Fastpaced = False PcOffset(WR).Begin = 4 ' Rook PcOffset(WR).Ende = 7 PcOffset(WR).Fastpaced = True PcOffset(WB).Begin = 0 ' Bishop PcOffset(WB).Ende = 3 PcOffset(WB).Fastpaced = True PcOffset(WN).Begin = 8 ' Knight PcOffset(WN).Ende = 15 PcOffset(WN).Fastpaced = False PcOffset(WQ).Begin = 0 ' Queen PcOffset(WQ).Ende = 7 PcOffset(WQ).Fastpaced = True PcOffset(WK).Begin = 0 ' King PcOffset(WK).Ende = 7 PcOffset(WK).Fastpaced = False ' Centralization tables. We only need Squares 0..H8 as ' pieces can't stand on a Square outside H8. ' The lower edge is preserved as we would otherwise have to ' transform board coordinates into centrality coordinates. ' H1 is further away from the center as G1. In spite of this, H1 has a better ' center value than G1. This table is used, a.o., for assessing ' the kings. The values of G1,H1 imply that the king remains on G1 ' after castling and doesn't perform the unnecessary move G1-H1. ' (The knight is neither very well placed on G1 nor on H1). DIM SHARED CenterTable(H8) AS INTEGER FOR i = 0 TO H8 READ CenterTable(i) NEXT i ' --- A B C D E F G H --- DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 DATA 0, 4, 0, 8, 12, 12, 8, 0, 4, 0 DATA 0, 4, 8, 12, 16, 16, 12, 8, 4, 0 DATA 0, 8, 12, 16, 20, 20, 16, 12, 8, 0 DATA 0, 12, 16, 20, 24, 24, 20, 16, 12, 0 DATA 0, 12, 16, 20, 24, 24, 20, 16, 12, 0 DATA 0, 8, 12, 16, 20, 20, 16, 12, 8, 0 DATA 0, 4, 8, 12, 16, 16, 12, 8, 4, 0 DATA 0, 4, 0, 8, 12, 12, 8, 0, 4 ' Assessment of the Squares for the pawns. ' Is used in the position assessment. ' Center pawns on 2nd row is bad (they belong in the front). ' F-H pawns should stay behind for protection of the king. DIM SHARED wPSquareValue(H7) AS INTEGER ' White pawns FOR i = 0 TO H7 READ wPSquareValue(i) NEXT i ' --- A B C D E F G H --- DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 DATA 0, 4, 4, 0, 0, 0, 6, 6, 6, 0 DATA 0, 6, 6, 8, 8, 8, 4, 6, 6, 0 DATA 0, 8, 8, 16, 22, 22, 4, 4, 4, 0 DATA 0, 10, 10, 20, 26, 26, 10, 10, 10, 0 DATA 0, 12, 12, 22, 28, 28, 14, 14, 14, 0 DATA 0, 18, 18, 28, 32, 32, 20, 20, 20 ' No pawn can stay on the 8th row DIM SHARED bPSquareValue(H7) AS INTEGER ' Black pawns FOR i = 0 TO H7 READ bPSquareValue(i) NEXT i ' --- A B C D E F G H --- DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 DATA 0, 18, 18, 28, 32, 32, 20, 20, 20, 0 DATA 0, 12, 12, 22, 28, 28, 14, 14, 14, 0 DATA 0, 10, 10, 20, 26, 26, 10, 10, 10, 0 DATA 0, 8, 8, 16, 22, 22, 4, 4, 4, 0 DATA 0, 6, 6, 8, 8, 8, 4, 6, 6, 0 DATA 0, 4, 4, 0, 0, 0, 6, 6, 6, 0 ' No pawn can stay on the 8th row ' Material value of the pieces DIM SHARED PcMaterial(PieceTypes) AS INTEGER PcMaterial(Empty) = 0 ' Empty Square PcMaterial(WP) = ValP ' Pawn PcMaterial(WR) = ValR ' Rook PcMaterial(WB) = ValB ' Bishop PcMaterial(WN) = ValN ' Knight PcMaterial(WQ) = ValQ ' Queen PcMaterial(WK) = ValK ' King ' Symbolic representation of the pieces DIM SHARED PcSymbol(PieceTypes) AS STRING * 1 PcSymbol(Empty) = "." ' Empty Square PcSymbol(WP) = "P" ' Pawn PcSymbol(WR) = "R" ' Rook PcSymbol(WB) = "B" ' Bishop PcSymbol(WN) = "N" ' Knight PcSymbol(WQ) = "Q" ' Queen PcSymbol(WK) = "K" ' King ' Symbolic representation of the pieces for printing DIM SHARED PrintSymbol(PieceTypes) AS STRING * 1 PrintSymbol(Empty) = " " ' Empty Square PrintSymbol(WP) = " " ' Pawn PrintSymbol(WR) = "R" ' Rook PrintSymbol(WB) = "B" ' Bishop PrintSymbol(WN) = "N" ' Knight PrintSymbol(WQ) = "Q" ' Queen PrintSymbol(WK) = "K" ' King ' Color symbols DIM SHARED ColorSymbol(2) AS STRING * 1 ColorSymbol(0) = "." ' Black ColorSymbol(1) = "." ' Empty Square ColorSymbol(2) = "*" ' White '-------------------------------------------------------------------- ' The actual program begins here '-------------------------------------------------------------------- CALL PrintLogo CALL Initialize CALL CommandLoop '-------------------------------------------------------------------- ' The program ends here '-------------------------------------------------------------------- '-------------------------------------------------------------------- ' Alpha-Beta-Tree search ' Returns assessment from the viewpoint of the player who is to make ' a move. "Alpha" is the lower limit, "Beta" upper limit and "Distance" ' the number of half-moves until horizon. ' If "Distance" is positive, a normal Alpha-Beta search is performed, ' if less than 0 the quiescence search. ' Returns the NegaMax value from the point of view of the player who is ' to make a move. ' This procedure is called recursively. ' ' Local variables: i, Value, BestValue, Check '--------------------------------------------------------------------- FUNCTION AlphaBeta (Alpha, Beta, Distance) NodeCount = NodeCount + 1 ' Additional position examined MVar(Depth, Depth).FromSqr = 0 ' Delete current main variant ' Position is always assessed, i.e. also inside the tree. ' This is necessary to recognize checkmate and stalemate. Also, ' the assessment is used to control search/extension. ' The number of nodes inside the tree is much smaller than that at ' the horizon. I.e. the program does not become significantly slower ' because of that. ' Assessment from the viewpoint of the player who is to make a move. Value = AssessPosition(Alpha, Beta, MoveColor) ' In the case of a check, the search is extended, by up to four ' half-moves total. Otherwise it may happen that the search tree ' becomes extremely large thru mutual checks and capture sequences. ' As a rule, these move sequences are completely meaningless. Check = InCheck ' If being checked, then extend the search Condition1 = (Check = True AND Depth + Distance < MaxExtension + 1) ' By capture and re-capture on the same Square, the search is ' extended if the material balance remains approximately ' the same AND we didn't make too many extensions ' so far. Condition2 = (Depth >= 2 AND Depth + Distance < MaxExtension) Condition2 = Condition2 AND ToSquare(Depth) = ToSquare(Depth - 1) Condition2 = Condition2 AND Value >= Alpha - 150 AND Value <= Beta + 150 IF Condition1 OR Condition2 THEN Distance = Distance + 1 ' If more than 5 moves were already performed in the quiescence search ' or the opponent is checkmate or we have reached maximum search depth ' imposed by data structures, end the search. IF Distance < -5 OR Value = CheckMateVal - Depth OR Depth >= MaxDepth THEN AlphaBeta = Value EXIT FUNCTION END IF ' If - during the quiescence search - the player who is to move has a ' good position, the search is aborted since by definition the value ' can only become better during the quiescence search. ' Warning: Aborts already at Distance 1, i.e. a half-move before the ' horizon, in case the player who is to move is not ' being checked. This is a selective deviation from ' the brute-force-alpha-beta scheme. IF Value >= Beta AND Distance + Check <= 1 THEN AlphaBeta = Value EXIT FUNCTION END IF ' Compute moves. If Distance is <= 0 (quiescence search) only capture ' moves and promotion moves are computed. CALL GenerateMoves(Distance) ' Examining if any moves at all are available IF Distance > 0 THEN ' is indirectly done by determining BestValue = -CheckMateVal ' BestValue. ELSE ' In quiescence search, the current position assessment BestValue = Value ' is the lower limit of the search value. END IF ' Examine all moves in the sorted sequence. i = NextBestMove DO WHILE i >= 0 ' As long as any moves are left CALL PerformMove(i) ' NegaMax principle: the sign is reversed, ' the roles of alpha and beta exchanged. Value = -AlphaBeta(-Beta, -Alpha, Distance - 1) CALL TakeBackMove(i) IF Value > BestValue THEN ' New best value found BestValue = Value IF Value >= Beta THEN ' Cutoff found ' Inside the tree, new main variants are still registered. IF Distance > 0 THEN CALL CopyMainVariant(i) GOTO done END IF IF Value > Alpha THEN ' Value is the improved lower limit IF Distance > 0 THEN CALL CopyMainVariant(i) ' Register main var. Alpha = Value ' Improved alpha value END IF END IF i = NextBestMove LOOP done: ' A good move showing cutoff is entered into the killer table. ' Keep the best killer so far as the 2nd best killer. IF Value >= Beta AND i >= 0 THEN KillerTab(Depth).Killer2 = KillerTab(Depth).Killer1 KillerTab(Depth).Killer1.FromSqr = MoveStack(i).FromSqr KillerTab(Depth).Killer1.ToSqr = MoveStack(i).ToSqr END IF ' If the player has no more legal moves ... IF BestValue = -(CheckMateVal - (Depth + 1)) THEN IF Check = False THEN ' ... but isn't being checked, AlphaBeta = 0 ' it's stalemate EXIT FUNCTION END IF END IF AlphaBeta = BestValue END FUNCTION '-------------------------------------------------------------------- ' Position assessment. ' Returns value from the viewpoint of "Side". ' If material value deviates too far from the Alpha-Beta window, ' only material is assessed. ' If "Side" is checkmating, returns (CheckMateVal - Depth). ' If "Side" is being checked, the variable InCheck is changed ' to "True". ' Warning: The function assumes, both for check/checkmate and for the ' king opposition, that "Side" is the player who is to make a move. ' ' Local variables: ' Value, PosValue, i, j, k, Square, wBishop, bBishop ' PawnCount, MatSum, WRon7, BRon2 ' wDeveloped, bDeveloped '-------------------------------------------------------------------- FUNCTION AssessPosition (Alpha, Beta, Side) ' First examine if opponent is checkmate ' or "Side" is being checked IF Side = White THEN IF IsAttackingSquare(bKing, White) = True THEN AssessPosition = CheckMateVal - Depth EXIT FUNCTION END IF InCheck = IsAttackingSquare(wKing, Black) ' Is white being checked? ELSE IF IsAttackingSquare(wKing, Black) = True THEN AssessPosition = CheckMateVal - Depth EXIT FUNCTION END IF InCheck = IsAttackingSquare(bKing, White) ' Is black being checked? END IF ' Positional assessment factors do not outweigh a heavy material ' imbalance. Hence, we omit the position assessment in this case. ' Exception: The late endgame. Free pawns have a high value. ' A junior officer without pawns is without effect. Value = MaterialBalance(Depth) MatSum = MaterialSum(Depth) IF MatSum > ValB + ValB THEN IF Value < Alpha - MaxPos OR Value > Beta + MaxPos THEN AssessPosition = Value EXIT FUNCTION END IF END IF ' Initialize the lines of rooks and pawns as well as the pawn controls. ' This could be computed incrementally significantly faster when ' performing (and taking back) the moves. However, this incremental ' computation is difficult and error-prone due to the special cases ' castling, En Passant, and promotion. ' You could also build a list of pieces in 'InitAssessment' and ' in the second turn go thru this list (and no longer the entire ' board). ' The fastest solution consists of computing this list of pieces ' incrementally, too. This complicates, however, the functions ' "PerformMove" and "TakeBackMove". ' Following the KISS principle (Keep It Simple and Stupid), this ' solution was chosen in MiniMAX. CALL InitAssessment PosValue = 0 ' Used for assessing the bishop pair. bBishop = 0 wBishop = 0 ' Used for determining insufficient material. PawnCount = 0 ' White rooks on 7/8th row, black rooks on 1/2nd WRon7 = 0 BRon2 = 0 ' Development state: Castled and junior officers developed. wDeveloped = Castling(White + 1) bDeveloped = Castling(Black + 1) ' Knight on B1 developed? IF MovedCnt(B1) > 0 THEN wDeveloped = wDeveloped + 1 ' Bishop on C1 developed? IF MovedCnt(C1) > 0 THEN wDeveloped = wDeveloped + 1 ' Bishop on F1 developed? IF MovedCnt(F1) > 0 THEN wDeveloped = wDeveloped + 1 ' Knight on G1 developed? IF MovedCnt(G1) > 0 THEN wDeveloped = wDeveloped + 1 ' Knight on B8 developed? IF MovedCnt(B8) > 0 THEN bDeveloped = bDeveloped + 1 ' Bishop on C8 developed? IF MovedCnt(C8) > 0 THEN bDeveloped = bDeveloped + 1 ' Bishop on F8 developed? IF MovedCnt(F8) > 0 THEN bDeveloped = bDeveloped + 1 ' Knight on G8 developed? IF MovedCnt(G8) > 0 THEN bDeveloped = bDeveloped + 1 ' Read the entire board and assess each piece. ' The asssessment takes white's point of view. For the black ' pieces, a positive assessment means that this evaluation ' is unfavorable for black. FOR i = Row1 TO Row8 Square = i * 10 FOR j = AColumn TO HColumn Square = Square + 1 SELECT CASE Board(Square) CASE BK IF MatSum < EndgameMaterial THEN ' Endgame assessment for king ' Centralize the king in the endgame PosValue = PosValue - CenterTable(Square) ELSE ' Not castled yet, but castling rights lost IF Castling(Black + 1) = False THEN IF MovedCnt(E8) > 0 OR (MovedCnt(H8) > 0 AND MovedCnt(A8) > 0) THEN PosValue = PosValue + 35 END IF END IF ' King preferably not in the center PosValue = PosValue + 4 * CenterTable(Square) FOR k = -1 TO 1 ' Bonus for pawn shield before the king IF Board(Square - 10 + k) = BP THEN PosValue = PosValue - 15 ' Pawn shield 2 rows before the king IF Board(Square - 20 + k) = BP THEN PosValue = PosValue - 6 ' Deduct for half-open line occupied by ' enemy rook. IF Pawns(j + k).White = 0 AND Rooks(j + k).White > 0 THEN PosValue = PosValue + 12 END IF NEXT k END IF CASE BQ ' Avoid queen outings in the opening of the game IF bDeveloped < 4 THEN IF Square < A8 THEN PosValue = PosValue + 15 ELSE ' If development is completed, place the queen near ' the enemy king. Row and column distance ' between queen and enemy king should be small. RowDiff = ABS(wKing \ 10 - Square \ 10) ColDiff = ABS(wKing MOD 10 - Square MOD 10) PosValue = PosValue + 2 * (RowDiff + ColDiff) END IF CASE BN ' Black knight PosValue = PosValue - CenterTable(Square) / 2 ' Centralize knight CASE BB ' Bishop should not impede black d7/e7 pawns. ' Bishop is also assessed by variable mobility ' in the move generator IF (Square = D6 OR Square = E6) AND Board(Square + 10) = BP THEN PosValue = PosValue + 20 END IF bBishop = bBishop + 1 ' No. of bishops for the bishop pair CASE BR ' Rook influences the king assessment ' Black rook has penetrated row 1 or 2 IF Square <= H2 THEN BRon2 = BRon2 + 1 ' Bring rooks from a and h columns into the center IF j >= CColumn AND j <= EColumn THEN PosValue = PosValue - 4 ' Rooks on half-open and open lines IF Pawns(j).White = 0 THEN PosValue = PosValue - 8 ' Rook on half-open line ' Rook on open line IF Pawns(j).Black = 0 THEN PosValue = PosValue - 5 END IF CASE BP ' The pawn assessment is relatively complex. Consequently, ' it is accomplished in a separate routine. PosValue = PosValue - BPAssessment((Square), (i), (j), (bDeveloped)) PawnCount = PawnCount + 1 CASE Empty ' Do nothing CASE WP ' White assessment is analogous to the black PosValue = PosValue + WPAssessment((Square), (i), (j), (wDeveloped)) PawnCount = PawnCount + 1 CASE WR ' White rook on 7th or 8th row IF Square >= A7 THEN WRon7 = WRon7 + 1 ' Bring rooks from a and h columns into the center IF j >= CColumn AND j <= EColumn THEN PosValue = PosValue + 4 ' Rooks on half-open and open lines IF Pawns(j).Black = 0 THEN PosValue = PosValue + 8 ' Rook on half-open line ' Rook on open line IF Pawns(j).White = 0 THEN PosValue = PosValue + 5 END IF CASE WB ' Bishop should not block pawns on D3/E3. IF (Square = D3 OR Square = E3) AND Board(Square - 10) = WP THEN PosValue = PosValue - 20 END IF wBishop = wBishop + 1 CASE WN PosValue = PosValue + CenterTable(Square) \ 2 CASE WQ ' Avoid queen outings in the beginning of the game IF wDeveloped < 4 THEN IF Square > H1 THEN PosValue = PosValue - 15 ELSE ' Place the queen near the enemy king. ' Row and column distance ' between queen and enemy king should be small. RowDiff = ABS(bKing \ 10 - Square \ 10) ColumnDiff = ABS(bKing MOD 10 - Square MOD 10) PosValue = PosValue - 2 * (RowDiff + ColumnDiff) END IF CASE WK IF MatSum < EndgameMaterial THEN ' Endgame assessment for the king ' Centralize the king in the endgame PosValue = PosValue + CenterTable(Square) ' Near opposition of the kings IF ABS(Square - bKing) = 20 OR ABS(Square - bKing) = 2 THEN k = 10 ' Opposition in the pawn endgame IF MatSum = 0 THEN k = 30 IF MoveColor = White THEN PosValue = PosValue - k ELSE PosValue = PosValue + k END IF END IF ELSE ' Not castled yet, but castling rights lost IF Castling(White + 1) = False THEN IF MovedCnt(E1) > 0 OR (MovedCnt(H1) > 0 AND MovedCnt(A1) > 0) THEN PosValue = PosValue - 35 END IF END IF ' King preferably not in the center PosValue = PosValue - 4 * CenterTable(Square) FOR k = -1 TO 1 ' Bonus for pawn shield before the king IF Board(Square + 10 + k) = WP THEN PosValue = PosValue + 15 ' Pawn shield 2 rows before the king IF Board(Square + 20 + k) = WP THEN PosValue = PosValue + 6 ' Deduct for half-open lines occupied by ' enemy rook. IF Pawns(j + k).Black = 0 AND Rooks(j + k).Black > 0 THEN PosValue = PosValue - 12 END IF NEXT k END IF END SELECT NEXT j NEXT i ' No pawns left on the board and insufficient material ' Recognizes all elementary draw situations ' KK, KBK, KNK, KNNK, KBKB, KNKB. IF PawnCount = 0 THEN Bed1 = MatSum <= ValB ' Less than a bishop Bed2 = MatSum = 2 * ValN ' Two knights ' Two bishops, but material difference less than a pawn Bed3 = MatSum <= 2 * ValB AND ABS(MaterialBalance(Depth)) < ValP IF Bed1 OR Bed2 OR Bed3 THEN AssessPosition = 0 EXIT FUNCTION END IF END IF ' Bishop pair bonus for white IF wBishop >= 2 THEN PosValue = PosValue + 15 ' Bishop pair bonus for black IF bBishop >= 2 THEN PosValue = PosValue - 15 ' White rooks on 7/8th row and black king also ' on these rows IF WRon7 > 0 AND bKing >= A7 THEN PosValue = PosValue + 10 ' Double rooks extra dangerous IF WRon7 > 1 THEN PosValue = PosValue + 25 END IF ' Black rooks on 1/2nd rows and white king also ' on these rows IF BRon2 > 0 AND wKing <= H2 THEN PosValue = PosValue - 10 IF BRon2 > 1 THEN PosValue = PosValue - 25 END IF IF Side = Black THEN ' Assessment was from white's point of view PosValue = -PosValue ' Change sign for black END IF ' Consider the mobility of bishops and rooks. This is done ' by the move generator. Mobility(Depth) is the ' mobility of the opponent, mobility(Depth-1) that of ' "Side" (before the opponent has made a move). IF Depth >= 1 THEN PosValue = PosValue - ((Mobility(Depth) - Mobility(Depth - 1)) / 16) END IF AssessPosition = Value + PosValue END FUNCTION '-------------------------------------------------------------------- ' Assessment of one black pawn. Besides passed parameters, the ' pawn controls, pawn lines, and rook lines must be correctly ' engaged. ' Returns the assessment from black's viewpoint. '-------------------------------------------------------------------- FUNCTION BPAssessment (Square, Row, Column, developed) Row = (Row8 + Row1) - Row ' Flip row. This makes higher row = better, ' as for white. IF MaterialSum(Depth) > EndgameMaterial THEN ' Opening or midgame Value = bPSquareValue(Square) ' If development incomplete, don't push edge pawns forward IF developed < 4 THEN IF (Column >= FColumn OR Column <= BColumn) AND Row > Row3 THEN Value = Value - 15 END IF END IF ELSE ' In the endgame, all lines are equally good. ' Bring pawns forward. Value = Row * 4 END IF ' Is the pawn isolated? ' Edge pawns don't require extra treatment. Pawns(AColumn-1) is ' the left edge, Pawns(HColumn+1) the right edge. No pawn is ' placed on these edges. IF Pawns(Column - 1).Black = 0 AND Pawns(Column + 1).Black = 0 THEN Value = Value - 12 ' Isolated pawn ' Isolated double pawn IF Pawns(Column).Black > 1 THEN Value = Value - 12 END IF ' Double pawn IF Pawns(Column).Black > 1 THEN Value = Value - 15 ' Duo or guarded pawn gets a Bonus ' e.g.. e5,d5 is a Duo, d6 guards e5 IF PawnControlled(Square).Black > 0 OR PawnControlled(Square - 10).Black > 0 THEN Value = Value + Row END IF IF Pawns(Column).White = 0 THEN ' Half-open column ' Pawn left behind on half-open column: ' Left-behind pawn is not guarded by its fellow pawns... Condition1 = PawnControlled(Square).Black = 0 ' ... and can't advance because enemy pawns ' control the Square in front of him. Condition2 = PawnControlled(Square - 10).White > PawnControlled(Square - 10).Black IF Condition1 AND Condition2 THEN Value = Value - 10 ' Rook impeded by left-behind pawn IF Rooks(Column).White > 0 THEN Value = Value - 8 ELSE ' Pawn is a free pawn, on a half-open column and the ' Squares ahead on his column are not controlled by ' enemy pawns. FOR j = Square TO A3 STEP -10 ' Until 3rd row IF PawnControlled(j).White > 0 THEN BPAssessment = Value EXIT FUNCTION END IF NEXT j ' Found a free pawn. In the endgame, a free pawn is more important ' than in the midgame. IF MaterialSum(Depth) < EndgameMaterial THEN Value = Value + Row * 16 ' The more advanced, the better ' Rook guards free pawn on the same column IF Rooks(Column).Black > 0 THEN Value = Value + Row * 2 ' Enemy rook on the same column IF Rooks(Column).White > 0 THEN Value = Value - Row * 2 ' Pure pawn endgame. Free pawn particularly valuable. IF MaterialSum(Depth) = 0 THEN Value = Value + Row * 8 ' Guarded free pawn IF PawnControlled(Square).Black > 0 OR PawnControlled(Square - 10).Black > 0 THEN Value = Value + Row * 4 END IF ' Free pawn blocked by a white piece. This piece is not ' threatened by fellow pawns. IF Board(Square - 10) < 0 AND PawnControlled(Square - 10).Black = 0 THEN Value = Value - Row * 4 END IF ELSE ' Free pawn in the midgame Value = Value + Row * 8 ' Guarded free pawn IF PawnControlled(Square).Black > 0 OR PawnControlled(Square - 10).Black > 0 THEN Value = Value + Row * 2 END IF END IF END IF END IF BPAssessment = Value END FUNCTION '-------------------------------------------------------------------- ' Reads the player's commands in a loop and calls ' the appropriate functions. The loop is terminated by ' the "EN" command. ' If the input is not a command it is interpreted as a ' move (on the form "e2e4" (from-Square, to-Square)), ' and ignored as a command. See also: PrintLogo '-------------------------------------------------------------------- SUB CommandLoop Ende = False DO PRINT INPUT " Your input: ", Cmd$ Cmd$ = UCASE$(Cmd$) ' Change to uppercase SELECT CASE Cmd$ CASE "EX" Ende = True CALL GameOver CASE "NG" PRINT " New game" CALL Initialize CASE "DB" CALL DisplayBoard(False) CASE "CP" CALL InputPosition CASE "PL" CALL ComputerMove CASE "FB" CALL FlipBoard CASE "PR" PRINT " Printing "; IF Printing = False THEN Printing = True PRINT "on " ELSE Printing = False PRINT "off" END IF CASE "MM" PRINT " Player-Player "; IF PlayerPlayer = False THEN PlayerPlayer = True PRINT "on " ELSE PlayerPlayer = False PRINT "off" END IF CASE "DL" CALL MoveList CASE "TB" CALL MoveBack CASE "SD" CALL ComputingDepth CASE "DA" CALL InitGameTree PRINT " Assessment= "; AssessPosition(-CheckMateVal, CheckMateVal, MoveColor) CASE "?", "HELP" CALL PrintLogo CASE ELSE IF InputMove(Cmd$) = False THEN PRINT " Illegal move or unknown command" ELSEIF PlayerPlayer = False THEN CALL ComputerMove END IF END SELECT LOOP WHILE Ende = False END SUB '-------------------------------------------------------------------- ' Computes the next computer move. ' The search is iteratively deepened until MinDepth. ' The search uses "Aspiration"-Alpha-Beta. ' The search process can be interrupted by ' a keypress. ' If the search wasn't interrupted and no checkmate/stalemate ' exists, the best move is performed. '-------------------------------------------------------------------- SUB ComputerMove DIM tmp AS MoveType ' Temporary MoveType variable CALL InitGameTree ' Assess the initial position. End search if opponent is already checkmate. Value = AssessPosition(-CheckMateVal, CheckMateVal, MoveColor) IF Value = CheckMateVal THEN PRINT " Checkmate!" EXIT SUB END IF ' Store "checked state". Required to recognize ' stalemate at the end of the search. Schach = InCheck NodeCount = 0 ' Start time of the computation. Used for displaying nodes/second. StartTimer& = TIMER ' Generate all pseudo-legal moves CALL GenerateMoves(1) ' You could/should remove all illegal moves from the MoveStack ' here and only keep computing with legal moves. ' (Has only an optical effect, however, as the search is always aborted ' immediately after performing an illegal move). ' Iterative deepening: Distance is the number of half-moves until the ' horizon. Is not equal to the depth, however, as the distance can increase ' during the search process (e.g. by checks). FOR Distance = 1 TO MinDepth IF Distance = 1 THEN ' On Depth 1, we compute with open windows Alpha = -CheckMateVal ' We have no good assessment value for Beta = CheckMateVal ' the position yet. ELSE ' On the higher levels, the result should not Beta = Alpha + 100 ' differ significantly from the result of the Alpha = Alpha - 100 ' previous Depth. END IF ' For capture moves and checks, the search is extended. ' This variable limits the extensions. MaxExtension = Distance + 3 PRINT PRINT PRINT " ComputingDepth="; Distance; ","; PRINT " Alpha-Beta window= ["; Alpha; ","; Beta; "]" MovesInLine = 0 PRINT " "; ' Compute the value of each move FOR i = 0 TO StackLimit(1) - 1 IF INKEY$ <> "" THEN ' Stop the calculation if a key is pressed PRINT " Computation interrupted!" EXIT SUB END IF MovesInLine = MovesInLine + 1 ' Initialize the main variant and display ' the move just examined. MVar(Depth, Depth).FromSqr = 0 CALL DisplayMove(i) IF MovesInLine MOD 9 = 8 THEN ' Eight moves per line PRINT MovesInLine = 0 PRINT " "; END IF ' Perform move, compute value, take back move. CALL PerformMove((i)) Value = -AlphaBeta(-Beta, -Alpha, Distance - 1) CALL TakeBackMove((i)) IF i = 0 THEN ' Was it the first move (the best yet)? ' This move requires an exact value. IF Value < Alpha THEN ' Search for the best move until now 'fails low' out the ' window (the program understands the mishap). Requires ' a renewed search with windows opened 'below'. Alpha = -CheckMateVal Beta = Value PRINT "? ["; Alpha; ","; Beta; "]" MovesInLine = 0 PRINT " "; CALL PerformMove((i)) Value = -AlphaBeta(-Beta, -Alpha, Distance - 1) CALL TakeBackMove((i)) ELSEIF Value >= Beta THEN ' Fails high Alpha = Value Beta = CheckMateVal PRINT "! ["; Alpha; ","; Beta; "]" MovesInLine = 0 PRINT " "; CALL PerformMove((i)) Value = -AlphaBeta(-Beta, -Alpha, Distance - 1) CALL TakeBackMove((i)) END IF ' There is just a slim chance that a subsequent move is ' even better. We continue calculating with a null window ' as this expedites the search. Alpha = Value Beta = Alpha + 1 PRINT PRINT " Best move: "; CALL DisplayMove(i) PRINT "Value ="; Value CALL CopyMainVariant(i) CALL DisplayMainVariant MovesInLine = 0 PRINT " "; ELSE ' Already computed the best move yet to SearchDepth IF Value > Alpha THEN ' New best move found. Currently, it is only known ' that it is better. The exact value must be computed ' again with an open window. BestValue = Alpha Alpha = Value Beta = CheckMateVal CALL PerformMove((i)) Value = -AlphaBeta(-Beta, -Alpha, Distance - 1) CALL TakeBackMove((i)) ' Is it also better with the open window? ' Solely applying alpha-beta, the move must always ' be better with the open window. Since the window is ' considered by the extensions and in the selectivity, ' the outcome may be different in our case. IF Value > BestValue THEN Alpha = Value Beta = Alpha + 1 PRINT PRINT " Best move: "; CALL DisplayMove(i) PRINT "Value ="; Value CALL CopyMainVariant(i) CALL DisplayMainVariant MovesInLine = 0 PRINT " "; ' Place the best move at the start of the MoveList. ' Push the other moves one position up. tmp = MoveStack(i) FOR j = i TO 1 STEP -1 MoveStack(j) = MoveStack(j - 1) NEXT j MoveStack(0) = tmp END IF END IF END IF NEXT i NEXT Distance EndTimer& = TIMER IF Alpha > -(CheckMateVal - 1) THEN PRINT PRINT PRINT " Computer plays: "; CALL DisplayMove(0) ' Best move is always sorted into position ' zero of the MoveStack PRINT PRINT " Value ="; Alpha; ", positions ="; NodeCount; ETime = EndTimer& - StartTimer& ' Prevent division by zero on nodes/second IF ETime = 0 THEN ETime = 1 PRINT ", time="; ETime; "sec., positions/sec. ="; NodeCount \ ETime CALL PerformMove(0) CALL PrintMove(0) IF Alpha >= CheckMateVal - 10 THEN PRINT PRINT " I checkmate in "; (CheckMateVal - 2 - Alpha) \ 2; " moves" ELSE IF Alpha <= -CheckMateVal + 10 THEN PRINT PRINT " I'm checkmated in "; (Alpha + CheckMateVal - 1) \ 2; " moves" END IF END IF ELSE IF Schach = True THEN PRINT " Congratulations: MiniMAX is checkmated!" ELSE PRINT " Stalemate!" END IF END IF END SUB ' ----------------------------------------------------------------- ' Input the minimum ComputingDepth ' ----------------------------------------------------------------- SUB ComputingDepth PRINT " ComputingDepth is"; MinDepth INPUT " New ComputingDepth: ", LInput tmp = LInput IF tmp > 0 AND tmp < MaxDepth - 9 THEN MinDepth = tmp ELSE PRINT " Invalid ComputingDepth" END IF END SUB '-------------------------------------------------------------------- ' Registers the current move in the main variant and copies ' the continuation that was found on the next depth. '-------------------------------------------------------------------- SUB CopyMainVariant (CurrMove) ' New main variant is a continuation of this variant MVar(Depth, Depth).FromSqr = MoveStack(CurrMove).FromSqr MVar(Depth, Depth).ToSqr = MoveStack(CurrMove).ToSqr i = 0 DO i = i + 1 MVar(Depth, Depth + i) = MVar(Depth + 1, Depth + i) LOOP UNTIL MVar(Depth + 1, Depth + i).FromSqr = 0 END SUB '-------------------------------------------------------------------- ' Display of the game board and the game/board state. ' Only displays game/board state if "BoardOnly" is false. ' ' The SGN function (Signum) returns the sign, i.e. -1 or +1 ' The ABS function returns the absolute value (without sign) '-------------------------------------------------------------------- SUB DisplayBoard (BoardOnly) ' Display board FOR i = Row8 TO Row1 STEP -1 ' For all rows PRINT PRINT i - 1; " "; ' Row coordinates FOR j = AColumn TO HColumn ' For all lines Piece = Board(i * 10 + j) Side = SGN(Piece) ' Compute color from piece ' Empty Square has color 0 Piece = ABS(Piece) ' Type of piece PRINT ColorSymbol(Side + 1); PcSymbol(Piece); " "; NEXT j NEXT i PRINT PRINT " "; FOR j = AColumn TO HColumn ' Line coordinates 'a'...'h' PRINT " "; CHR$(ASC("a") - 1 + j); NEXT j PRINT ' Empty line PRINT ' Empty line IF BoardOnly THEN EXIT SUB ' Remaining board/game state IF MoveColor = White THEN PRINT " White"; ELSE PRINT " Black"; END IF PRINT " is to make a move" PRINT " Material balance = "; MaterialBalance(Depth) PRINT " En Passant Square = "; SquareNotation$(EpSquare(Depth)) ' Castling is in principle possible if king and the appropriate ' rook haven't moved. PRINT " Castling state black = "; IF MovedCnt(E8) + MovedCnt(H8) = 0 THEN PRINT "0-0 "; IF MovedCnt(E8) + MovedCnt(A8) = 0 THEN PRINT "0-0-0"; PRINT PRINT " Castling state white = "; IF MovedCnt(E1) + MovedCnt(H1) = 0 THEN PRINT "0-0 "; IF MovedCnt(E1) + MovedCnt(A1) = 0 THEN PRINT "0-0-0"; PRINT END SUB '-------------------------------------------------------------------- ' Display the current main variant. Only the from-to Squares ' are output. '-------------------------------------------------------------------- SUB DisplayMainVariant PRINT " Main variant: "; i = 0 DO WHILE MVar(0, i).FromSqr <> 0 PRINT SquareNotation$(MVar(0, i).FromSqr); "-"; PRINT SquareNotation$(MVar(0, i).ToSqr); " "; i = i + 1 LOOP PRINT END SUB '-------------------------------------------------------------------- ' Display the current move in chess notation. ' Castling is 'E1-G1' and not O-O ' CurrMove is the index of the move into MoveStack. '-------------------------------------------------------------------- SUB DisplayMove (CurrMove) Source = MoveStack(CurrMove).FromSqr Dest = MoveStack(CurrMove).ToSqr PRINT PcSymbol(ABS(Board(Source))); ' Type of piece PRINT SquareNotation$(Source); ' Initial Square IF MoveStack(CurrMove).CapturedPiece = Empty THEN PRINT "-"; ' Normal move ELSE PRINT "x"; ' Capture move END IF PRINT SquareNotation$(Dest); ' Target Square ' If promoted, add promotion piece IF MoveStack(CurrMove).PromotedPc <> Empty THEN PRINT PcSymbol(MoveStack(CurrMove).PromotedPc); END IF PRINT " "; END SUB '--------------------------------------------------------------------- ' Flips the representation of the board on the monitor. ' Note: Not implemented in version 1.0 '--------------------------------------------------------------------- SUB FlipBoard END SUB '-------------------------------------------------------------------- ' Stores the game and game parameters on the harddisk. ' Note: Not implemented in version 1.0 '-------------------------------------------------------------------- SUB GameOver END SUB '-------------------------------------------------------------------- ' Generates moves and places them on the MoveStack. ' Returns the number of moves. ' If "AllMoves" is greater than 0, all pseudo-legal ' moves are produced, otherwise all pseudo-legal capture moves, ' promotions, En Passant, and castling moves. '-------------------------------------------------------------------- SUB GenerateMoves (AllMoves) Index = StackLimit(Depth) ' Start of MoveList on current depth Mobility(Depth) = 0 ' Search the board for pieces FOR Source = A1 TO H8 Piece = Board(Source) ' Empty and edge Squares make no moves IF Piece = Empty OR Piece = Edge THEN GOTO NextSquare ' Piece must also be of correct color IF MoveColor = White AND Piece < 0 THEN GOTO NextSquare IF MoveColor = Black AND Piece > 0 THEN GOTO NextSquare Piece = ABS(Piece) ' Type of piece. Color doesn't influence ' (except for pawns) the move direction. IF Piece = WP THEN ' Pawn moves IF MoveColor = White THEN IF Board(Source + 10) = Empty THEN IF Source >= A7 THEN CALL RegisterPromotion(Source, Source + 10) ELSEIF AllMoves > 0 THEN CALL RegisterMove(Source, Source + 10) ' Double-step possible? IF Source <= H2 AND Board(Source + 20) = Empty THEN CALL RegisterMove(Source, Source + 20) ' Move has already increased Index MoveStack(Index - 1).EpSquare = Source + 10 END IF END IF END IF IF Board(Source + 11) < 0 THEN ' Pawn can capture black piece IF Source >= A7 THEN CALL RegisterPromotion(Source, Source + 11) ELSE CALL RegisterCaptureMove(Source, Source + 11) END IF END IF IF Board(Source + 9) < 0 THEN ' Likewise in other capture direction IF Source >= A7 THEN CALL RegisterPromotion(Source, Source + 9) ELSE CALL RegisterCaptureMove(Source, Source + 9) END IF END IF ELSEIF MoveColor = Black THEN ' Same for black pawns IF Board(Source - 10) = Empty THEN IF Source <= H2 THEN CALL RegisterPromotion(Source, Source - 10) ELSEIF AllMoves > 0 THEN CALL RegisterMove(Source, Source - 10) ' Double-step possible? IF Source >= A7 AND Board(Source - 20) = Empty THEN CALL RegisterMove(Source, Source - 20) ' Move has already increased Index MoveStack(Index - 1).EpSquare = Source - 10 END IF END IF END IF ' For black pawns, also examine the edge, ' not for white as the edge is > 0. IF Board(Source - 11) > 0 AND Board(Source - 11) <> Edge THEN IF Source <= H2 THEN CALL RegisterPromotion(Source, Source - 11) ELSE CALL RegisterCaptureMove(Source, Source - 11) END IF END IF IF Board(Source - 9) > 0 AND Board(Source - 9) <> Edge THEN IF Source <= H2 THEN CALL RegisterPromotion(Source, Source - 9) ELSE CALL RegisterCaptureMove(Source, Source - 9) END IF END IF END IF GOTO NextSquare ' Examine next Square END IF ' Moves for all other pieces are computed ' by way of move offset. Fastpaced = PcOffset(Piece).Fastpaced FOR i = PcOffset(Piece).Begin TO PcOffset(Piece).Ende Direction = Offset(i) Dest = Source slideon2: Dest = Dest + Direction IF Board(Dest) = Empty THEN IF AllMoves > 0 THEN CALL RegisterMove(Source, Dest) END IF IF Fastpaced THEN ' Bishop, rook, and queen GOTO slideon2 ELSE ' Knight and king GOTO NextDirection END IF END IF IF Board(Dest) = Edge THEN ' Hit the edge, keep searching GOTO NextDirection ' in another direction END IF ' Hit a piece. Must be of the correct color. CaptureMove = MoveColor = White AND Board(Dest) < 0 CaptureMove = CaptureMove OR (MoveColor = Black AND Board(Dest) > 0) IF CaptureMove THEN CALL RegisterCaptureMove(Source, Dest) NextDirection: NEXT i NextSquare: NEXT Source ' Enpassant move IF EpSquare(Depth) <> Illegal THEN ep = EpSquare(Depth) IF MoveColor = White THEN IF Board(ep - 9) = WP THEN CALL RegisterEPMove(ep - 9, ep, ep - 10) END IF IF Board(ep - 11) = WP THEN CALL RegisterEPMove(ep - 11, ep, ep - 10) END IF ELSE IF Board(ep + 9) = BP THEN CALL RegisterEPMove(ep + 9, ep, ep + 10) END IF IF Board(ep + 11) = BP THEN CALL RegisterEPMove(ep + 11, ep, ep + 10) END IF END IF END IF ' Castling is also performed in the quiescence search because it has a ' strong influence on the assessment. (Whether this is appropriate, ' is a matter of dispute even among leading programmers). ' Compute castling IF MoveColor = White THEN IF wKing = E1 AND MovedCnt(E1) = 0 THEN ' Is short castling permitted? OK = Board(H1) = WR AND MovedCnt(H1) = 0 OK = OK AND Board(F1) = Empty AND Board(G1) = Empty OK = OK AND IsAttackingSquare(E1, Black) = False OK = OK AND IsAttackingSquare(F1, Black) = False OK = OK AND IsAttackingSquare(G1, Black) = False IF OK THEN CALL RegisterMove(E1, G1) ' Register king's move MoveStack(Index - 1).CastlingNr = ShortCastlingMove END IF ' Is long castling permitted? OK = Board(A1) = WR AND MovedCnt(A1) = 0 OK = OK AND Board(D1) = Empty OK = OK AND Board(C1) = Empty OK = OK AND Board(B1) = Empty OK = OK AND IsAttackingSquare(E1, Black) = False OK = OK AND IsAttackingSquare(D1, Black) = False OK = OK AND IsAttackingSquare(C1, Black) = False IF OK THEN CALL RegisterMove(E1, C1) ' Register king's move ' Register type of castling MoveStack(Index - 1).CastlingNr = LongCastlingMove END IF END IF ELSE ' Black is to make a move IF bKing = E8 AND MovedCnt(E8) = 0 THEN ' Is short castling permitted? OK = Board(H8) = BR AND MovedCnt(H8) = 0 OK = OK AND Board(F8) = Empty AND Board(G8) = Empty OK = OK AND IsAttackingSquare(E8, White) = False OK = OK AND IsAttackingSquare(F8, White) = False OK = OK AND IsAttackingSquare(G8, White) = False IF OK THEN CALL RegisterMove(E8, G8) ' Register king's move MoveStack(Index - 1).CastlingNr = ShortCastlingMove END IF ' Is long castling permitted? OK = Board(A8) = BR AND MovedCnt(A8) = 0 OK = OK AND Board(D8) = Empty OK = OK AND Board(C8) = Empty OK = OK AND Board(B8) = Empty OK = OK AND IsAttackingSquare(E8, White) = False OK = OK AND IsAttackingSquare(D8, White) = False OK = OK AND IsAttackingSquare(C8, White) = False IF OK THEN CALL RegisterMove(E8, C8) ' Register king's move ' Register type of castling MoveStack(Index - 1).CastlingNr = LongCastlingMove END IF END IF END IF StackLimit(Depth + 1) = Index ' Mark end of MoveList END SUB '-------------------------------------------------------------------- ' Compute the pawn controls and the columns on which pawns and ' rooks are placed. Called by the assessment function ' for initialization. '-------------------------------------------------------------------- SUB InitAssessment ' Delete pawn controls FOR i = A1 TO H8 PawnControlled(i).White = 0 PawnControlled(i).Black = 0 NEXT i ' Also initialize edges. This eliminates the ' need to examine edge columns. FOR i = AColumn - 1 TO HColumn + 1 Pawns(i).White = 0 Pawns(i).Black = 0 Rooks(i).White = 0 Rooks(i).Black = 0 NEXT i FOR i = A1 TO H8 IF Board(i) = Empty OR Board(i) = Edge THEN GOTO NextSquare2 SELECT CASE Board(i) CASE WP PawnControlled(i + 9).White = PawnControlled(i + 9).White + 1 PawnControlled(i + 11).White = PawnControlled(i + 11).White + 1 Pawns(i MOD 10).White = Pawns(i MOD 10).White + 1 CASE BP PawnControlled(i - 9).Black = PawnControlled(i - 9).Black + 1 PawnControlled(i - 11).Black = PawnControlled(i - 11).Black + 1 Pawns(i MOD 10).Black = Pawns(i MOD 10).Black + 1 CASE BR Rooks(i MOD 10).Black = Rooks(i MOD 10).Black + 1 CASE WR Rooks(i MOD 10).White = Rooks(i MOD 10).White + 1 CASE ELSE END SELECT NextSquare2: NEXT i END SUB '-------------------------------------------------------------------- ' Initialize the game tree '-------------------------------------------------------------------- SUB InitGameTree ' In Depth 0 nothing has been computed, game tree already initialized IF Depth = 0 THEN EXIT SUB EpSquare(0) = EpSquare(1) MaterialBalance(0) = MaterialBalance(1) MaterialSum(0) = MaterialSum(1) Depth = 0 END SUB '-------------------------------------------------------------------- ' Initializes the board and the game status '-------------------------------------------------------------------- SUB Initialize ' Board initialization, build InitialPosition MoveCount = 0 ' Counts the half-moves in the game FOR i = 0 TO BoardDim Board(i) = InitialPosition(i) NEXT i ' Kings's positions in the InitialPosition wKing = E1 bKing = E8 ' No castling yet FOR i = 0 TO 2 Castling(i) = False NEXT i FOR i = A1 TO H8 MovedCnt(i) = 0 ' Initially, no piece has moved NEXT i EpSquare(0) = Illegal ' Enpassant status MaterialSum(0) = MaterialTotal ' Material value (of pieces) in InitialPosition MaterialBalance(0) = 0 ' Material balance even PlayerPlayer = False StackLimit(0) = 0 ' Limit of MoveStack MinDepth = 4 ' Default ComputingDepth Depth = 0 ' Current depth in the game tree MoveColor = White ' White has the first move END SUB '-------------------------------------------------------------------- ' Attempts to interpret the passed string as a move. ' If it's a legal move, that move is performed and the function ' returns the value "True". If no (legal) move can be identified ' the function returns the value "False". '-------------------------------------------------------------------- FUNCTION InputMove (Move$) IF LEN(Move$) < 4 THEN ' Only from-to representation allowed InputMove = False EXIT FUNCTION END IF Source = SquareNumber(Move$) Dest = SquareNumber(MID$(Move$, 3, 2)) CALL GenerateMoves(1) FOR i = StackLimit(Depth) TO StackLimit(Depth + 1) - 1 IF MoveStack(i).FromSqr = Source AND MoveStack(i).ToSqr = Dest THEN IF MoveStack(i).PromotedPc <> Empty THEN ' Promotions IF MID$(Move$, 5, 1) = "N" THEN ' in the sequence queen, knight, i = i + 1 ' bishop, and rook ELSEIF MID$(Move$, 5, 1) = "B" THEN i = i + 2 ELSEIF MID$(Move$, 5, 1) = "R" THEN i = i + 3 END IF END IF CALL InitGameTree PRINT " Your move: "; CALL DisplayMove(i) tmp = LastMove ' Temp. storage for last move so far. CALL PerformMove(i) ' Warning: PerformMove changes ' Color. Next inquiry of color must ' compensate for this. IF MoveColor = Black THEN IF IsAttackingSquare(wKing, Black) = True THEN PRINT " White king on "; SquareNotation$(wKing); " being checked" CALL TakeBackMove((i)) LastMove = tmp ' No new move made. Restore InputMove = False ' last move. EXIT FUNCTION END IF ELSEIF IsAttackingSquare(bKing, White) = True THEN PRINT " Black king on "; SquareNotation$(bKing); " being checked" CALL TakeBackMove((i)) LastMove = tmp InputMove = False EXIT FUNCTION END IF PRINT CALL PrintMove(i) InputMove = True EXIT FUNCTION END IF NEXT i InputMove = False ' The input move was not found in MoveList END FUNCTION '-------------------------------------------------------------------- ' Input of any position '-------------------------------------------------------------------- SUB InputPosition Depth = 0 ' Position becomes root of the search tree PieceInput: INPUT " Delete board (Y/N) ", LInput$ LInput$ = UCASE$(LInput$) ' Change to uppercase IF LInput$ = "Y" THEN FOR i = Row1 TO Row8 FOR j = AColumn TO HColumn Board(i * 10 + j) = Empty NEXT j NEXT i END IF ' Interpret not "Y" as no PRINT " White:" ReadPieces (White) PRINT " Black:" ReadPieces (Black) ' Compute material balance and examine if each side ' has just one king. MaterialBalance(0) = 0 MaterialSum(0) = 0 wKings = 0 bKings = 0 FOR i = A1 TO H8 ' Read each Square IF Board(i) = Empty OR Board(i) = Edge THEN GOTO Continue ' Found empty or edge Square, go on to next Square END IF ' New material balance ' White piece positively affects the balance, black negatively MaterialBalance(0) = MaterialBalance(0) + SGN(Board(i)) * PcMaterial(ABS(Board(i))) IF ABS(Board(i)) <> WP THEN MaterialSum(0) = MaterialSum(0) + PcMaterial(ABS(Board(i))) END IF IF Board(i) = WK THEN wKings = wKings + 1 ' Number and position of the white kings wKing = i END IF IF Board(i) = BK THEN bKings = bKings + 1 ' Black kings bKing = i END IF Continue: NEXT i IF bKings <> 1 OR wKings <> 1 THEN PRINT "Illegal position, each side must have exactly one king" CALL DisplayBoard(True) GOTO PieceInput END IF Repeat: ' The entry must be complete with a legal position ' otherwise the MoveGenerator doesn't work. INPUT " Who is to make a move (W/B): "; LInput$ LInput$ = UCASE$(LInput$) IF LInput$ = "W" THEN MoveColor = White ELSEIF LInput$ = "B" THEN MoveColor = Black ' Material balance was computed from white's MaterialBalance(0) = -MaterialBalance(0) ' viewpoint until now. ELSE GOTO Repeat END IF FOR i = A1 TO H8 ' To simplify, we assume here that MovedCnt(i) = 1 ' all pieces have already moved once. NEXT i ' Otherwise, the assessment function ' believes this is an ' initial position. MovedCnt(E1) = 0 MovedCnt(A1) = 0 ' Single exception: The king and rook MovedCnt(H1) = 0 ' Squares represent the castling state MovedCnt(E8) = 0 ' and must therefore be reset MovedCnt(A8) = 0 ' to zero. MovedCnt(H8) = 0 EpSquare(0) = Illegal INPUT " Change the status (Y/N): "; LInput$ LInput$ = UCASE$(LInput$) IF LInput$ = "Y" THEN ' Input the Enpassant Square. If following input isn't correct, ' Enpassant is not possible. INPUT " En Passant column: "; LInput$ LInput$ = UCASE$(LInput$) ep$ = LEFT$(LInput$, 1) IF ep$ >= "A" AND ep$ <= "H" THEN IF MoveColor = White THEN EpSquare(0) = A6 + ASC(ep$) - ASC("A") ELSE EpSquare(0) = A3 + ASC(ep$) - ASC("A") END IF END IF ' Black short castling. By default, castling is possible. INPUT " Black 0-0 legal (Y/N) : "; LInput$ LInput$ = UCASE$(LInput$) IF LInput$ = "N" THEN MovedCnt(H8) = 1 ' 'Move' the rook. This eliminates ' the castling. END IF INPUT " Black 0-0-0 legal (Y/N): "; LInput$ LInput$ = UCASE$(LInput$) IF LInput$ = "N" THEN MovedCnt(A8) = 1 END IF INPUT " White 0-0 legal (Y/N) : "; LInput$ LInput$ = UCASE$(LInput$) IF LInput$ = "N" THEN MovedCnt(H1) = 1 END IF INPUT " White 0-0-0 legal (Y/N) : "; LInput$ LInput$ = UCASE$(LInput$) IF LInput$ = "N" THEN MovedCnt(A1) = 1 END IF END IF MoveCount = 0 ' Reset the move count CALL DisplayBoard(False) ' Display the new board CALL PrintPosition END SUB '-------------------------------------------------------------------- ' Examine whether player "Side" is attacking the Square "Square". ' Returns "True" if Square is attacked by "Side", otherwise "False". ' Algorithm: Imagine "Square" occupied by a super officer that can move ' in any direction. If this super officer 'captures' e.g. ' a rook belonging to "Side" then the rook is actually ' attacking the Square. ' ' Local variables: i, Direction, Dest, Piece, slide '-------------------------------------------------------------------- FUNCTION IsAttackingSquare (Square, Side) ' First test the special case pawns. They have the same direction ' as bishops but don't slide (?? /Me). IF Side = White THEN ' Must go in the opposite direction of pawns. D5 is attacked ' by pawn E4. IF Board(Square - 9) = WP OR Board(Square - 11) = WP THEN IsAttackingSquare = True EXIT FUNCTION END IF END IF IF Side = Black THEN IF Board(Square + 9) = BP OR Board(Square + 11) = BP THEN IsAttackingSquare = True EXIT FUNCTION END IF END IF ' Examine the knight FOR i = 8 TO 15 ' Knight directions Dest = Square + Offset(i) IF Board(Dest) = Empty OR Board(Dest) = Edge THEN GOTO w1 IF Side = White THEN IF Board(Dest) = WN THEN IsAttackingSquare = True EXIT FUNCTION END IF ELSEIF Board(Dest) = BN THEN IsAttackingSquare = True EXIT FUNCTION END IF w1: NEXT i ' Examine sliding pieces and king. FOR i = 0 TO 7 Dest = Square Direction = Offset(i) slide = 0 slideon1: slide = slide + 1 Dest = Dest + Direction IF Board(Dest) = Empty THEN GOTO slideon1 END IF ' If edge reached then next direction IF Board(Dest) = Edge THEN GOTO w2 ' Hit a piece. Piece must be of color "Side". ' Also, the current direction must be a possible move direction ' of the piece. The king can only do one step. Piece = Board(Dest) IF Side = White THEN IF Piece > 0 THEN ' White piece IF Piece = WK THEN IF slide <= 1 THEN ' King is slow-paced IsAttackingSquare = True EXIT FUNCTION END IF ELSE ' As far as sliding pieces are concerned, the current direction ' must be a possible move direction of the piece. IF PcOffset(Piece).Begin <= i THEN IF PcOffset(Piece).Ende >= i THEN IsAttackingSquare = True EXIT FUNCTION END IF END IF END IF END IF ELSE IF Piece < 0 THEN ' Black piece IF Piece = BK THEN IF slide <= 1 THEN IsAttackingSquare = True EXIT FUNCTION END IF ELSE IF PcOffset(-Piece).Begin <= i THEN IF PcOffset(-Piece).Ende >= i THEN IsAttackingSquare = True EXIT FUNCTION END IF END IF END IF END IF END IF w2: NEXT i ' All directions exhausted, didn't hit a piece. ' I.e. "Side" is not attacking the Square. IsAttackingSquare = False END FUNCTION ' ----------------------------------------------------------------- ' Takes back a move. ' Since the played moves are not stored, a maximum of ' one move can be taken back. ' ----------------------------------------------------------------- SUB MoveBack IF Depth <> 1 THEN PRINT " Unfortunately not possible." EXIT SUB END IF CALL TakeBackMove(LastMove) CALL DisplayBoard(False) CALL PrintBack END SUB ' ----------------------------------------------------------------- ' Generates all moves and displays them on the monitor ' ----------------------------------------------------------------- SUB MoveList CALL GenerateMoves(1) IF MoveColor = White THEN CheckMated = IsAttackingSquare(bKing, White) ELSE CheckMated = IsAttackingSquare(wKing, Black) END IF IF CheckMated THEN PRINT " The king cannot be captured" EXIT SUB END IF PRINT " "; Index - StackLimit(Depth); "pseudo-legal moves" FOR i = StackLimit(Depth) TO Index - 1 CALL DisplayMove(i) IF (i - StackLimit(Depth)) MOD 9 = 8 THEN ' After 8 moves, start PRINT ' a new line. END IF NEXT i PRINT ' Carriage return END SUB '-------------------------------------------------------------------- ' From the possible moves of a certain depth the best, ' not-yet-played move is selected. Returns the index of the move ' into MoveStack. If all moves were already played, an ' impossible index (-1) is returned. ' The value of a move is determined by the move generator. ' This function finishes the move sorting in the search. '-------------------------------------------------------------------- FUNCTION NextBestMove BestMove = -1 BestValue = -CheckMateVal FOR i = StackLimit(Depth) TO StackLimit(Depth + 1) - 1 IF MoveStack(i).Value > BestValue THEN ' Found new best move BestMove = i BestValue = MoveStack(i).Value END IF NEXT i ' Mark the selected move so it isn't selected again ' on the next call. IF BestMove >= 0 THEN MoveStack(BestMove).Value = -CheckMateVal NextBestMove = BestMove END FUNCTION '-------------------------------------------------------------------- ' Performs a move at the board and updates the status and ' the search depth. ' CurrMove is the index of the move into MoveStack. '-------------------------------------------------------------------- SUB PerformMove (CurrMove) MoveCount = MoveCount + 1 ' Increase move count by one half-move Source = MoveStack(CurrMove).FromSqr Dest = MoveStack(CurrMove).ToSqr ep = MoveStack(CurrMove).EpSquare LastMove = CurrMove Depth = Depth + 1 ' One step deeper in the tree ToSquare(Depth) = Dest ' Used for move sorting and extension ' of the search. EpSquare(Depth) = Illegal ' Material balance is always seen from the viewpoint of the player who is ' to make a move. Therefore, flip the sign. MaterialBalance(Depth) = -MaterialBalance(Depth - 1) MaterialSum(Depth) = MaterialSum(Depth - 1) ' The piece is moving from the 'from' Square to the 'to' Square MovedCnt(Source) = MovedCnt(Source) + 1 MovedCnt(Dest) = MovedCnt(Dest) + 1 IF ep <> Illegal THEN IF Board(ep) = Empty THEN ' Pawn move from 2nd to 4th row EpSquare(Depth) = ep ELSE ' Enemy pawn is captured enpassant Board(ep) = Empty ' Remove captured pawn MaterialBalance(Depth) = MaterialBalance(Depth) - ValP END IF ELSE ' If a piece is captured, change the material balance IF MoveStack(CurrMove).CapturedPiece <> Empty THEN ' Piece was captured MatChange = PcMaterial(MoveStack(CurrMove).CapturedPiece) MaterialBalance(Depth) = MaterialBalance(Depth) - MatChange ' Sum up only the officers's material value IF MatChange <> ValP THEN MaterialSum(Depth) = MaterialSum(Depth) - MatChange END IF END IF END IF Board(Dest) = Board(Source) ' Place onto board Board(Source) = Empty ' Now the special cases promotion and castling IF MoveStack(CurrMove).PromotedPc <> Empty THEN ' Pawn promotion Board(Dest) = MoveColor * MoveStack(CurrMove).PromotedPc MatChange = PcMaterial(MoveStack(CurrMove).PromotedPc) - ValP MaterialBalance(Depth) = MaterialBalance(Depth) - MatChange ' Pawns are not included in MaterialSum. MaterialSum(Depth) = MaterialSum(Depth) + MatChange + ValP ELSE IF MoveStack(CurrMove).CastlingNr = ShortCastlingMove THEN Board(Dest + 1) = Empty ' 'to' is G1 or G8 (depending on Color) Board(Dest - 1) = MoveColor * WR ' Put white/black rook on F1/F8 Castling(MoveColor + 1) = True ELSEIF MoveStack(CurrMove).CastlingNr = LongCastlingMove THEN Board(Dest - 2) = Empty ' 'to' is C1 or C8 Board(Dest + 1) = MoveColor * WR Castling(MoveColor + 1) = True END IF END IF ' If king has moved, update the king's position IF Board(Dest) = WK THEN wKing = Dest ELSEIF Board(Dest) = BK THEN bKing = Dest END IF ' Flip the Color (the player who is to make a move) MoveColor = -MoveColor END SUB '-------------------------------------------------------------------- ' Print the take-back command '-------------------------------------------------------------------- SUB PrintBack IF Printing = False THEN EXIT SUB ' Only if printing is on IF MoveColor = White THEN LPRINT " BACK" ' "ZURUECK" isWhiteLast = False ELSE LPRINT USING "###. BACK!"; MoveCount \ 2 + 1; CHR$(9); isWhiteLast = True END IF END SUB '-------------------------------------------------------------------- ' Displays the program logo/menu on the monitor (see CommandLoop) '-------------------------------------------------------------------- SUB PrintLogo CLS PRINT "***********************************************************" PRINT "* MiniMAX 1.0 (Basic) *" PRINT "* *" PRINT "* by Dieter Steinwender *" PRINT "* and Chrilly Donninger *" PRINT "* *" PRINT "* Input a move (e.g. G1F3) *" PRINT "* or one of the following commands: *" PRINT "* *" PRINT "* NG --> New game *" PRINT "* EX --> Exit the program *" PRINT "* DB --> Display board on the monitor *" PRINT "* CP --> Input position (chess problem) *" PRINT "* PL --> Play, computer move *" PRINT "* PR --> Printing on/off *" PRINT "* MM --> Multi-move input (player-player) *" PRINT "* DL --> Display move list *" PRINT "* TB --> Take back one move *" PRINT "* SD --> Set computing depth *" PRINT "* DA --> Display assessment *" PRINT "***********************************************************" END SUB '-------------------------------------------------------------------- ' Prints the current move. ' WARNING: Don't change the format of this output as it will cause ' malfunction of the Noname driver for the CHESS232 board ' and the Autoplayer AUTO232. ' ' Notes: ' CHR$(9) is the tab character '-------------------------------------------------------------------- SUB PrintMove (CurrMove) IF Printing = False THEN EXIT SUB ' Only if printing is on IF MoveColor = Black THEN ' If black is to make a move ' the last move was by white. LPRINT USING "###. "; MoveCount \ 2 + 1; isWhiteLast = True ELSE ' Black move IF isWhiteLast = True THEN LPRINT " "; ELSE LPRINT USING "###. ... ! "; MoveCount \ 2 + 1; CHR$(9); END IF isWhiteLast = False END IF IF MoveStack(CurrMove).CastlingNr = NoCastlingMove THEN LPRINT PrintSymbol(ABS(Board(MoveStack(CurrMove).ToSqr))); Source$ = LCASE$(SquareNotation$(MoveStack(CurrMove).FromSqr)) LPRINT Source$; IF MoveStack(CurrMove).CapturedPiece <> Empty THEN LPRINT "x"; ELSE LPRINT "-"; END IF Dest$ = LCASE$(SquareNotation$(MoveStack(CurrMove).ToSqr)) LPRINT Dest$; IF MoveStack(CurrMove).PromotedPc <> Empty THEN LPRINT PrintSymbol(MoveStack(CurrMove).PromotedPc); END IF ELSEIF MoveStack(CurrMove).CastlingNr = ShortCastlingMove THEN LPRINT " 0-0 "; ELSE LPRINT " 0-0-0" END IF ' Finish with a tab character for a white move ' or a carriage return for a black move IF MoveColor = Black THEN LPRINT CHR$(9); ELSE LPRINT END IF END SUB '-------------------------------------------------------------------- ' Prints the current position in ChessBase / Fritz format. ' WARNING: Don't change the format of this output as it will ' cause malfunction of the Chess332 driver. '-------------------------------------------------------------------- SUB PrintPosition IF Printing = False THEN EXIT SUB IF isWhiteLast = True THEN LPRINT isWhiteLast = False END IF LPRINT "(wK"; LPRINT SquareNotation$(wKing); ' First the king FOR i = A1 TO H8 ' Remaining white pieces IF Board(i) > 0 AND Board(i) < WK THEN LPRINT ","; PcSymbol(Board(i)); LPRINT SquareNotation$(i); END IF NEXT i LPRINT "; sK"; LPRINT SquareNotation$(bKing); ' First the king FOR i = A1 TO H8 ' Remaining black pieces IF Board(i) < 0 AND Board(i) > BK THEN LPRINT ","; PcSymbol(ABS(Board(i))); LPRINT SquareNotation$(i); END IF NEXT i LPRINT ")" END SUB '-------------------------------------------------------------------- ' Reads the pieces for "Side". ' Format is: <piece><Square> e.g. "Ke1". ' "." is an empty Square, i.e. removes any piece from that Square. '-------------------------------------------------------------------- SUB ReadPieces (Side) NextPiece: INPUT LInput$ IF LInput$ = "" THEN EXIT SUB ' Exit if input Empty IF LEN(LInput$) < 3 THEN GOTO BadInput ' Input too short LInput$ = UCASE$(LInput$) ' Uppercase Piece$ = LEFT$(LInput$, 1) Square$ = MID$(LInput$, 2, 2) FOR i = 0 TO PieceTypes ' From empty Square to king IF Piece$ = PcSymbol(i) THEN ' Convert chess notation into Square value ' First character of input was already used for the piece Square = SquareNumber(Square$) IF Square = Illegal THEN GOTO BadInput IF i = WP THEN ' Pawns only legal on 2nd thru 7th row IF Square <= H1 OR Square >= A8 THEN GOTO BadInput END IF Piece = i * Side ' If color is black the sign ' of the piece is reversed. Board(Square) = Piece ' Place piece onto board GOTO NextPiece END IF NEXT i BadInput: PRINT " Bad input " GOTO NextPiece END SUB '-------------------------------------------------------------------- ' Register a capture move in MoveStack. '-------------------------------------------------------------------- SUB RegisterCaptureMove (Source, Dest) ' King cannot be captured IF Board(Dest) = WK OR Board(Dest) = BK THEN EXIT SUB PcValue = PcMaterial(ABS(Board(Dest))) MoveStack(Index).FromSqr = Source MoveStack(Index).ToSqr = Dest MoveStack(Index).CapturedPiece = ABS(Board(Dest)) ' Rule for move sorting: Capture the most valuable enemy piece ' using the least valuable piece MoveStack(Index).Value = PcValue - (PcMaterial(ABS(Board(Source))) \ 8) ' Extra bonus for capturing the piece just moved IF Depth > 0 THEN IF Dest = ToSquare(Depth - 1) THEN MoveStack(Index).Value = MoveStack(Index).Value + 300 END IF END IF ' Bonus for main variant moves and "killer" moves Killer1 = KillerTab(Depth).Killer1.FromSqr = Source Killer1 = Killer1 AND KillerTab(Depth).Killer1.ToSqr = Dest Killer2 = KillerTab(Depth).Killer2.FromSqr = Source Killer2 = Killer2 AND KillerTab(Depth).Killer2.ToSqr = Dest MvarMove = MVar(0, Depth).FromSqr = Source AND MVar(0, Depth).ToSqr = Dest IF MvarMove THEN MoveStack(Index).Value = MoveStack(Index).Value + MainVarBonus ELSEIF Killer1 THEN MoveStack(Index).Value = MoveStack(Index).Value + Killer1Bonus ELSEIF Killer2 THEN MoveStack(Index).Value = MoveStack(Index).Value + Killer2Bonus END IF MoveStack(Index).PromotedPc = Empty MoveStack(Index).CastlingNr = NoCastlingMove MoveStack(Index).EpSquare = Illegal IF Index < MoveStackDim THEN ' Prevent MoveStack overflow Index = Index + 1 ELSE PRINT " ERROR: Move stack overflow" END ' Exit to DOS END IF END SUB '-------------------------------------------------------------------- ' Register an Enpassant move in MoveStack. '-------------------------------------------------------------------- SUB RegisterEPMove (Source, Dest, ep) ' King cannot be captured IF Board(Dest) = WK OR Board(Dest) = BK THEN EXIT SUB MoveStack(Index).FromSqr = Source MoveStack(Index).ToSqr = Dest MoveStack(Index).CapturedPiece = WP MoveStack(Index).PromotedPc = Empty MoveStack(Index).CastlingNr = NoCastlingMove MoveStack(Index).EpSquare = ep MoveStack(Index).Value = ValP IF Index < MoveStackDim THEN ' Prevent MoveStack overflow Index = Index + 1 ELSE PRINT " ERROR: Move stack overflow" END ' Exit to DOS END IF END SUB '--------------------------------------------------------------------- ' Register a normal move in MoveStack. ' As a side effect, this procedure provides the mobility of bishop ' and rook, as well as the value of the move for the pre-sorting. '--------------------------------------------------------------------- SUB RegisterMove (Source, Dest) ' Increase the mobility of bishop and rook. ' Mobility in the center is rated higher ' than mobility at the edge. IF MoveColor = White THEN IF Board(Source) = WB OR Board(Source) = WR THEN Mobility(Depth) = Mobility(Depth) + CenterTable(Dest) END IF ELSE IF Board(Source) = BB OR Board(Source) = BR THEN Mobility(Depth) = Mobility(Depth) + CenterTable(Dest) END IF END IF ' Assess the move for move sorting. Bonus for main variant or "killer". Killer1 = KillerTab(Depth).Killer1.FromSqr = Source Killer1 = Killer1 AND KillerTab(Depth).Killer1.ToSqr = Dest Killer2 = KillerTab(Depth).Killer2.FromSqr = Source Killer2 = Killer2 AND KillerTab(Depth).Killer2.ToSqr = Dest MvarMove = MVar(0, Depth).FromSqr = Source AND MVar(0, Depth).ToSqr = Dest IF MvarMove THEN MoveStack(Index).Value = MainVarBonus ELSEIF Killer1 THEN MoveStack(Index).Value = Killer1Bonus ELSEIF Killer2 THEN MoveStack(Index).Value = Killer2Bonus ELSE MoveStack(Index).Value = Empty END IF MoveStack(Index).FromSqr = Source MoveStack(Index).ToSqr = Dest MoveStack(Index).CapturedPiece = Empty MoveStack(Index).PromotedPc = Empty MoveStack(Index).CastlingNr = NoCastlingMove MoveStack(Index).EpSquare = Illegal IF Index < MoveStackDim THEN ' Prevent MoveStack overflow Index = Index + 1 ELSE PRINT " ERROR: Move stack overflow" END ' In this case best to "ease out" to DOS END IF END SUB '-------------------------------------------------------------------- ' Produce all possible pawn promotions '-------------------------------------------------------------------- SUB RegisterPromotion (Source, Dest) IF Board(Dest) = Empty THEN FOR i = WQ TO WR STEP -1 ' Sequence queen, knight, bishop, rook CALL RegisterMove(Source, Dest) MoveStack(Index - 1).PromotedPc = i NEXT i ELSE ' Promotion with capture FOR i = WQ TO WR STEP -1 CALL RegisterCaptureMove(Source, Dest) MoveStack(Index - 1).PromotedPc = i NEXT i END IF END SUB '----------------------------------------------------------------------- ' Converts internal SquareNumber to SquareNotation. ' Returns '--' if the number is not on the board. ' ' Notes: ' The \ operator is integer division. ' The mod (Modulo) operator returns the remainder of an integer division. '----------------------------------------------------------------------- FUNCTION SquareNotation$ (SquareNum) ' See if correct IF SquareNum < A1 OR SquareNum > H8 OR Board(SquareNum) = Edge THEN SquareNotation$ = "--" ELSE s$ = CHR$(ASC("A") - 1 + SquareNum MOD 10) ' Line s$ = s$ + CHR$(ASC("1") - 2 + SquareNum \ 10) ' Row SquareNotation$ = LCASE$(s$) END IF END FUNCTION '-------------------------------------------------------------------- ' Converts SquareNotation (e.g. "A1") to internal SquareNumber. ' Returns "Illegal" if input is incorrect. ' Line coordinates must be passed as uppercase letters. '-------------------------------------------------------------------- FUNCTION SquareNumber (SquareNot$) Column$ = LEFT$(SquareNot$, 1) Row$ = MID$(SquareNot$, 2, 1) ' See if correct IF Column$ < "A" OR Column$ > "H" OR Row$ < "1" OR Row$ > "8" THEN SquareNumber = Illegal EXIT FUNCTION END IF SquareNumber = (ASC(Column$) - ASC("A") + 1) + 10 * (ASC(Row$) - ASC("1") + 2) END FUNCTION '-------------------------------------------------------------------- ' Takes back a move in the tree. ' CurrMove is the index of the move in MoveStack. '-------------------------------------------------------------------- SUB TakeBackMove (CurrMove) MoveCount = MoveCount - 1 Source = MoveStack(CurrMove).FromSqr Dest = MoveStack(CurrMove).ToSqr ep = MoveStack(CurrMove).EpSquare MoveColor = -MoveColor ' Other side to move Depth = Depth - 1 ' One level higher in tree Board(Source) = Board(Dest) ' Put back the piece Board(Dest) = Empty IF ep <> Illegal AND MoveStack(CurrMove).CapturedPiece = WP THEN Board(ep) = -MoveColor ' WP=White, BP=Black ' Put back captured piece ELSEIF MoveStack(CurrMove).CapturedPiece <> Empty THEN Board(Dest) = (-MoveColor) * MoveStack(CurrMove).CapturedPiece END IF ' Adjust move counter MovedCnt(Source) = MovedCnt(Source) - 1 MovedCnt(Dest) = MovedCnt(Dest) - 1 ' If castling put back rook IF MoveStack(CurrMove).CastlingNr = ShortCastlingMove THEN Board(Dest + 1) = MoveColor * WR Board(Dest - 1) = Empty Castling(MoveColor + 1) = False ELSEIF MoveStack(CurrMove).CastlingNr = LongCastlingMove THEN Board(Dest - 2) = MoveColor * WR Board(Dest + 1) = Empty Castling(MoveColor + 1) = False END IF IF MoveStack(CurrMove).PromotedPc <> Empty THEN Board(Source) = MoveColor ' Take back pawn promotion END IF ' If king has moved, update the king's position IF Board(Source) = WK THEN wKing = Source ELSEIF Board(Source) = BK THEN bKing = Source END IF END SUB '-------------------------------------------------------------------- ' Assessment of one white pawn. ' Analogous to the assessment of black pawns. ' Returns the assessment from white's viewpoint. '-------------------------------------------------------------------- FUNCTION WPAssessment (Square, Row, Column, developed) IF MaterialSum(Depth) > EndgameMaterial THEN ' Opening or midgame Value = wPSquareValue(Square) ' If development incomplete, don't push edge pawns forward IF developed < 4 THEN IF (Column >= FColumn OR Column <= BColumn) AND Row > Row3 THEN Value = Value - 15 END IF END IF ELSE ' In the endgame, all lines are equally good. Value = Row * 4 ' Bring pawns forward. END IF ' Is the pawn isolated? ' Edge pawns don't require extra treatment. Pawns(AColumn-1) is ' the left edge, Pawns(HColumn+1) the right edge. No pawn is ' placed on these edges. IF Pawns(Column - 1).White = 0 AND Pawns(Column + 1).White = 0 THEN Value = Value - 12 ' Isolated ' Isolated double pawn IF Pawns(Column).White > 1 THEN Value = Value - 12 END IF ' Double pawn IF Pawns(Column).White > 1 THEN Value = Value - 15 ' Duo or guarded pawn gets a Bonus IF PawnControlled(Square).White > 0 OR PawnControlled(Square + 10).White > 0 THEN Value = Value + Row END IF IF Pawns(Column).Black = 0 THEN ' Half-open column ' Pawn left behind on half-open column: ' Left-behind pawn is not guarded by its fellow pawns... Condition1 = PawnControlled(Square).White = 0 ' ... and can't advance because enemy pawns ' control the Square in front of him. Condition2 = PawnControlled(Square + 10).Black > PawnControlled(Square + 10).White IF Condition1 AND Condition2 THEN Value = Value - 10 ' Rook impeded by left-behind pawn IF Rooks(Column).Black > 0 THEN Value = Value - 8 ELSE ' Pawn is a free pawn, on a half-open column and the ' Squares ahead on his column are not controlled by ' enemy pawns. FOR j = Square TO H6 STEP 10 ' Until 6th row IF PawnControlled(j).Black > 0 THEN WPAssessment = Value EXIT FUNCTION END IF NEXT j ' Found a free pawn. In the endgame, a free pawn is more important ' than in the midgame. IF MaterialSum(Depth) < EndgameMaterial THEN Value = Value + Row * 16 ' The more advanced, the better ' Rook guards free pawn on the same column IF Rooks(Column).White > 0 THEN Value = Value + Row * 2 ' Enemy rook on the same column IF Rooks(Column).Black > 0 THEN Value = Value - Row * 2 ' Pure pawn endgame. Free pawn particularly valuable. IF MaterialSum(Depth) = 0 THEN Value = Value + Row * 8 ' Guarded free pawn IF PawnControlled(Square).White > 0 OR PawnControlled(Square + 10).White > 0 THEN Value = Value + Row * 4 END IF ' Free pawn blocked by a black piece. This piece is not ' threatened by fellow pawns. IF Board(Square + 10) < 0 AND PawnControlled(Square + 10).White = 0 THEN Value = Value - Row * 4 END IF ELSE ' Free pawn in the midgame Value = Value + Row * 8 ' Guarded free pawn IF PawnControlled(Square).White > 0 OR PawnControlled(Square + 10).White > 0 THEN Value = Value + Row * 2 END IF END IF END IF END IF WPAssessment = Value END FUNCTION
This page took 0.03 seconds to execute
Last modified: Thu, 15 Apr 21 08:11:13 -0700
Current Computer Chess Club Forums at Talkchess. This site by Sean Mintz.