'=========================================================================== ' Subject: INTELLIGENT OTHELLO Date: 12-10-97 (23:59) ' Author: Antoni Gual Code: QB, PDS ' Origin: agual@eic.ictnet.es Packet: GAMES.ABC '=========================================================================== '-------------------------------------------------------------------------- 'OTHELLO.BAS 'More or less inteligent othello player for Quick Basic (not QBasic) 'By Antoni Gual agual@eic.ictnet.es ' 'I spent all my time with the evaluating routine, so forgive my graphics 'It Will not work without a mouse 'Any suggestions about evaluating routine or user interface will be welcome 'Any suggestions about graphics will be politely answered. (I hate doing 'computer graphics) 'Just click with the mouse the place whwre you want to move 'As i have a 486 i never used the program at a level higher than 5, so i can't 'guarantee the higher levels. 'Enjoy it! '---------------------------------------------------------------------------- DECLARE FUNCTION MouseInt% (func%, fil%, col%) DECLARE SUB PointCount (niv%, clr%, ptosyo%, ptoscont%) DECLARE SUB DebugRank (niv%, a() AS ANY, curs%) DECLARE SUB EvaluatePos (niv%, punt%, clr%) DECLARE SUB PCThinks (posi%, punt%, niv%, clr%) DECLARE SUB DisplayMove (clr%, niv%) DECLARE SUB UserInput (a$, x%, y%) DECLARE SUB HumanMoves (posicion%) DECLARE SUB NextMove (clr%, punt%) DECLARE SUB DisplMessage (clr%, txt$, pulse%) DECLARE SUB Presentacion () DECLARE SUB FichaVer (posc%, niv%) DECLARE SUB AskUser (in%, min%, max%, lloc%, txt$) DECLARE SUB CopyGame (de%, a%) DECLARE SUB LegalMove (posicion%, puntos%, LEV%, clr%, opcion%) DECLARE SUB SrchForValidMove (LEV%, clr%, posi%) DECLARE SUB InitDisplay () DEFINT A-Z TYPE elrank ' ranking jugadas en PCThinks posi AS INTEGER punt AS INTEGER END TYPE TYPE RegTypeX 'for mouse interrupt. ax AS INTEGER bx AS INTEGER cx AS INTEGER dx AS INTEGER bp AS INTEGER si AS INTEGER di AS INTEGER flags AS INTEGER ds AS INTEGER es AS INTEGER END TYPE DECLARE SUB INTERRUPTX (intnum AS INTEGER, inreg AS RegTypeX, outreg AS RegTypeX) CONST true = -1, false = NOT true CONST numrank = 5 ' n§ jugadas que considera PCThinks CONST hjEsLegal = 1, hjJuega = 3 ' opciones LegalMove CONST maxnivel = 10 ' Profundidad maxima PCThinks 'Valores validos en el array Game CONST Humano = -1, PeCe = 1 ' Fichas de los jugadores. PC=PeCe, Humano=Humano CONST muro = 9, libre = 0 ' Limites tablero y casilla vac¡a 'constantes para dibujo tablero y fichas CONST anchorej = 32 CONST dsplvert = 24 CONST dsplhori = 28 'constantes raton CONST minit = 0, mshow = 1, mhide = 2, mget = 3, mset = 4, mpres = 5 CONST mrele = 6, mxlim = 7, mylim = 8, mattr = 10 CONST mbleft = 1, mbright = 2, mbcenter = 4 'main Array--------------------------------------------------------- DIM SHARED Game(0 TO 99, -2 TO maxnivel) AS INTEGER ' levels ' 0 : real game ' 1 a maxniv,: levels of evaluation '-1 : pieces captured, blinking '-2 : position before last human move 'A game is a 1dimensional array, by rows 'Game(12,x) = Piece at 1st row, 2nd column 'j(0,x),(X0,x),(9X,x),(X9,x) limits of playground '-------------------------------------------------------------------------- 'This comes straigth from a game for the ZX-Spectrum, it used this curious 'representation to speed up the calculation of the correctness of the moves, 'the captures and the posibility of a move.It was supposed to be faster 'to work with an one dimensional array than with a two dimension, i suppose 'the diffference in a PC is not so big. However, as i added levels to 'evaluate moves, replays,etc, Game() became two dimensional again. ' 'I can't credit anyone for the idea, it was from a game in a magazine 'i lost many years ago. The game also played by itself, but 'only putted his pieces in the first legal place it found, so it was too 'easy to win. There started all..Aah, the old times.... '------------------------------------------------------------------------- DIM SHARED Score(Humano TO PeCe) AS INTEGER ' puntuacion DIM SHARED PlayLevel AS INTEGER ' PlayLevel juego elegido DIM SHARED anterior AS INTEGER, HayMouse AS INTEGER 'Array that preserves the 8 directions the moves can be tested 'In the strange representation of Game() you can follow a line in the 'playground by adding a constant to the current position ' +/- 1 is rowwise +/-10 is columnwise and +/-9, and +/-11 are the two 'diagonals DIM SHARED direcc(7) AS INTEGER ' FOR i = 0 TO 7: READ direcc(i): NEXT' 'inicializa direcciones DATA 1,-1,10,-10,11,-11,9,-9 PlayLevel = 1 IF MouseInt(minit, 0, 0) THEN HayMouse = true ELSE PRINT "No mouse, No game" END END IF 'main loop--------------------------------------------------------------- DO Presentacion AskUser PlayLevel, 1, maxnivel, 25, "Evaluation Level" ' dibuja InitDisplay ERASE Game FOR i = 0 TO 9 Game(i, 0) = muro Game(90 + i, 0) = muro Game(10 * i, 0) = muro Game(10 * i + 9, 0) = muro NEXT 'Do the 4 initial moves Game(44, 0) = Humano Game(55, 0) = Humano Game(45, 0) = PeCe Game(54, 0) = PeCe CopyGame 0, -2 anterior = 1 InitDisplay DisplayMove 0, 0 ' display tablas = 0: Player = PeCe ' bucle jugadas DO NextMove Player, pt IF pt = 0 THEN tablas = tablas + 1 ELSE tablas = 0 END IF IF pt > 0 THEN DisplayMove Player, 0 Player = -Player LOOP UNTIL Score(Player) + Score(-Player) = 64 OR tablas = 2 OR Score(Player) * Score(-Player) = 0 ' final partida SELECT CASE Score(Humano) CASE IS < Score(PeCe) DisplMessage PeCe, "You Won!", 1 CASE IS > Score(PeCe) DisplMessage Humano, "You Lost!", 1 CASE IS = Score(PeCe) DisplMessage Humano, "Equals!", 1 END SELECT COLOR 7: LOCATE 28, 1: INPUT "Another Game? [Y]/N "; a$ LOOP UNTIL UCASE$(a$) = "N" END SUB AskUser (in, min, max, lloc, txt$) 'Displays a string, waits for numeric user entry, looks if the entry is 'between limits max and min .If not, re-asks, if yes, erases all and returns 'with value in in IF lloc < 0 THEN CLS lloc = ABS(lloc) DO IF lloc > 0 THEN LOCATE lloc, 1 txt1$ = txt$ + ":" + STR$(min) + "<" + STR$(max) + " (" + STR$(in) + ") " PRINT txt1$; SPACE$(60 - LEN(txt1$)); ":"; : INPUT Entrada$ IF Entrada$ = "" THEN EXIT DO IF VAL(Entrada$) <> 0 THEN in = VAL(Entrada$) ELSEIF Entrada$ = "0" THEN in = VAL(Entrada$) END IF LOOP UNTIL in >= min AND in <= max END SUB SUB CopyGame (de, a) 'copies the game from one level of the array Game() to another FOR i = 0 TO 99: Game(i, a) = Game(i, de): NEXT END SUB SUB DebugRank (Level, a() AS elrank, curs) 'only debugging. Displays renking of best moves being calculated by PC vertpos = (Level - 1) * 10 + 1 IF curs > 0 THEN LOCATE 21, Level * 10 + 1: PRINT curs END IF IF Level MOD 2 THEN COLOR 12 ELSE COLOR 11 FOR i = 1 TO numrank LOCATE 21 + i, vertpos: PRINT USING "###### ##"; a(i).punt; a(i).posi NEXT 'nb$ = INPUT$(1) END SUB SUB DisplayMove (Player, Level) 'Displays new move, blinking the pieces captured and the score IF Player <> 0 THEN a = 7: B = Player FOR i = 1 TO 10 'Blink tim1! = TIMER FOR j = 1 TO 88 IF Game(j, -1) <> 0 THEN Game(j, -1) = a FichaVer j, -1 END IF NEXT DO LOOP UNTIL TIMER > tim1! + .07 'FOR K = 1 TO 3200: K1 = K: NEXT SWAP a, B NEXT END IF 'displays score PointCount Level, Humano, Score(Humano), Score(PeCe) DisplMessage 0, "", 0 total = Score(Humano) + Score(PeCe) LOCATE 4, 50: PRINT SPACE$(14); : LOCATE , 50 COLOR 11: PRINT USING "###.#% "; Score(Humano) / total * 100; COLOR 12: PRINT USING "###.#%"; Score(PeCe) / total * 100; LOCATE 5, 50: PRINT SPACE$(14); : LOCATE , 51 COLOR 11: PRINT USING "## "; Score(Humano); COLOR 12: PRINT USING "##"; Score(PeCe); 'resets the blinking level of Game FOR i = 1 TO 88 Game(i, -1) = 0 FichaVer i, 0 NEXT END SUB SUB DisplMessage (Player, txt$, pulse) 'Displays message,waits for key and erases it CONST ltxt = 79 CONST filatxt = 28 txt$ = LEFT$(txt$, ltxt) IF pulse = 1 THEN txt$ = txt$ + " Key or Click." txt$ = txt$ + SPACE$(79 - LEN(txt$)) SELECT CASE Player CASE Humano COLOR 11 CASE PeCe COLOR 12 CASE ELSE COLOR Player END SELECT LOCATE filatxt, 1: PRINT txt$; IF pulse = 1 THEN DO a$ = INKEY$ B = MouseInt(mget, dummy, dummy) LOOP UNTIL LEN(a$) OR B LOCATE filatxt, 1: PRINT SPACE$(ltxt); END IF END SUB SUB EvaluatePos (Level, punt, Player) STATIC 'Evaluates strategic value of the move, returns points 'Level In Level in Game() to evaluate 'punt Out Points obtained 'Jugador In Player DIM contador(1 TO 88, 1 TO 4) AS INTEGER 'resets counter array ERASE contador 'evaluate lines in 4 directions capa = 1: dir = 10 FOR i = 1 TO 8: GOSUB valora: NEXT capa = 2: dir = 1 FOR i = 10 TO 80 STEP 10: GOSUB valora: NEXT capa = 3: dir = 11 FOR i = 0 TO 70 STEP 10: GOSUB valora: NEXT FOR i = 1 TO 7: GOSUB valora: NEXT capa = 4: dir = 9 FOR i = 19 TO 79 STEP 10: GOSUB valora: NEXT FOR i = 2 TO 9: GOSUB valora: NEXT 'at the end it adds up all values in contador punt = 0 FOR i2 = 1 TO 8 FOR i1 = 10 TO 80 STEP 10 i = i1 + i2 a = SGN(contador(i, 1)) * contador(i, 1) * contador(i, 2) * contador(i, 3) * contador(i, 4) punt = punt + a ' LOCATE i1 / 10 + 10, i2 * 4 + 36: PRINT USING "+###"; a; NEXT NEXT punt = punt * Player EXIT SUB 'evauates one line 'Tests for the ends of alignments of user's pieces. 'end in a limit BETTER THAN end by other players's pieces BETTER THAN free end 'i don't remember exactly how it works!! valora: ini = i: prim = i: ult = i: sig = i vini = muro: vult = muro DO sig = sig + dir vsig = Game(sig, Level) IF vult <> vsig THEN IF vult <> muro THEN IF vult <> 0 THEN punts = 1 * vult 'assigns ponts IF vini = muro OR vsig = muro THEN punts = 4 * vult IF vini = 0 AND vsig = 0 THEN punts = 2 * vult IF vini = -Player AND vsig = -Player THEN punts = 3 * vult FOR j = prim TO ult STEP dir contador(j, capa) = punts NEXT END IF ini = ult: vini = vult END IF prim = sig END IF ult = sig: vult = vsig LOOP UNTIL vsig = muro RETURN END SUB SUB FichaVer (posc, Level) 'Puts a piece in the screen (can display levels other than 0, never used) ' it must decode the one-dimensional position posc to row-column Player = Game(posc, Level) IF Player = 0 OR Player > 8 THEN EXIT SUB columna = (posc \ 10) * anchorej + dsplvert fila = (posc MOD 10) * anchorej + dsplhori SELECT CASE Player CASE Humano CLOR = 9 CASE PeCe CLOR = 12 CASE ELSE CLOR = Player END SELECT CIRCLE (fila, columna), 15, 16: PAINT (fila, columna), CLOR, 16 END SUB SUB HumanMoves (Donde) 'Human move, asks for input and does move, replay or exit as needed 'Donde Output position of the move DisplMessage 0, "", 0 DO a$ = INKEY$ DisplMessage Humano, "You Play: Click position (Also R=Replay Q=Quit)", 0 UserInput a$, x, y SELECT CASE UCASE$(a$) CASE "T" i = 0: a = 0: B = 0 CLS : COLOR 4 DEF SEG = VARSEG(otlotx) a = VARPTR(otlotx): B = a + 1500 FOR i = a TO B PRINT CHR$(PEEK(i)); NEXT CASE "R" IF anterior = 1 THEN DisplMessage Humano, "To replay your last move", 1 CopyGame -2, 0 anterior = 0 InitDisplay DisplayMove 0, 0 pts = 0 ELSE DisplMessage Humano, "No more saved moves", 1 END IF CASE ELSE Donde = 10 * y + x LegalMove Donde, pts, 0, Humano, hjEsLegal IF pts = 0 THEN dummy = MouseInt(mshow, 0, 0) DisplMessage Humano, "Not a Legal Move:", 1 dummy = MouseInt(mhide, 0, 0) END IF LOCATE 1, 51: COLOR 0: PRINT SPACE$(29) END SELECT LOOP UNTIL pts <> 0 CopyGame 0, -2 anterior = 1 END SUB DEFSNG A-Z SUB InitDisplay 'displays playground. No comment SCREEN 12 COLOR 7 CLS 2 COLOR 2 'lineas horiz FOR i = anchorej * 1 + dsplvert TO anchorej * 9 STEP anchorej LINE (anchorej, i)-STEP(anchorej * 8 + dsplvert, 0) NEXT 'lineas vert FOR i = anchorej * 1 + dsplhori TO anchorej * 9 STEP anchorej LINE (i, anchorej)-STEP(0, anchorej * 8 + dsplhori) NEXT 'bordes LINE (390, 20)-(520, 90), 7, B LOCATE 2, 55: PRINT "SCORE:" LOCATE 3, 52: COLOR 11: PRINT "YOU"; : LOCATE , 61: COLOR 12: PRINT "PC"; END SUB DEFINT A-Z SUB LegalMove (Donde, Ptosjugada, Profund, Player, LaHago) STATIC 'Tests for validity of a move or does it 'Donde Input Place to move 'PtosJugada Output Nr Of captures 0 if its not a legal move 'Profund Input Level of Game() where move is done/tested 'Player Input Who moves 'LaHago Input hjEsLegal: only test for validity hjJuega: do the move Ptosjugada = 0 'If position occupied, exit IF Game(Donde, Profund) <> 0 THEN EXIT SUB 'para cada una de las 8 direcciones, comprobamos las fichas que se capturan FOR i = 0 TO 7 j = Donde j1 = direcc(i) PtosLinea = 0 'acumula capturas en esta direccion DO j = j + j1 K = Game(j, Profund) PtosLinea = PtosLinea + 1 LOOP UNTIL K <> -Player 'hasta que no hay fichas contrarias 'si terminamos en ficha propia no contigua a la inicial IF K = Player AND j <> Donde + j1 THEN 'los puntos calculados son validos Ptosjugada = Ptosjugada + PtosLinea - 1 'si puntua es valida IF Ptosjugada THEN IF LaHago = hjEsLegal THEN EXIT SUB 'si solo EsLegal? salir 'si pretendemos hacer la jugada, la hacemos ELSEIF LaHago = hjJuega THEN FOR i1 = Donde TO j - j1 STEP j1 Game(i1, Profund) = Player IF Profund = 0 THEN Game(i1, -1) = Player NEXT END IF END IF END IF NEXT END SUB SUB monitor (Level) 'for debugging only, displays one level of Game() CLS FOR i = 1 TO 8 FOR j = 10 TO 80 STEP 10 LOCATE i, (j \ 10) * 3: PRINT Game(i + j, Level) NEXT NEXT END SUB FUNCTION MouseInt (func, fil, col) 'Mouse Interrupt rutines 'col,fil entrada Donde raton en mset 'col,fil salida Donde raton en mget,mpres,mrele 'col,fil entrada minx, maxx en mxlim 'col,fil entrada miny,maxy en mylim 'col entrada AND mask fil entrada XOR mask en mattr 'MouseInt devuelve: 'minit n§ botones.0 si no hay rat¢n 'mshow,mhide,mxlim,mylim,mset nada 'mpres,mrel byte alto n§ pulsaciones, byte bajo botones 'mget botones CONST mouStepH = 32 CONST mouStepV = 32 CONST MouOffH = 15 CONST MouOffV = 15 DIM regs AS RegTypeX SELECT CASE func CASE minit GOSUB MouseInt IF regs.ax THEN MouseInt = regs.bx CASE mshow, mhide GOSUB MouseInt CASE mget GOSUB MouseInt col = (regs.cx - MouOffV) \ mouStepV fil = (regs.dx - MouOffH) \ mouStepH MouseInt = regs.bx CASE mset regs.cx = col * mouStepV + MouOffV + 12 regs.dx = fil * mouStepH + MouOffH + 12 GOSUB MouseInt CASE mpres, mrele regs.bx = col GOSUB MouseInt col = regs.cx \ 8 + 1 fil = regs.dx \ 8 + 1 MouseInt = regs.bx * 256 + regs.ax CASE mxlim regs.cx = (col - 1) * 8 regs.dx = (fil - 1) * 8 GOSUB MouseInt CASE mylim regs.cx = (col - 1) * 8 regs.dx = (fil - 1) * 8 GOSUB MouseInt CASE mattr regs.bx = 0 regs.cx = col regs.dx = fil GOSUB MouseInt END SELECT EXIT FUNCTION MouseInt: regs.ax = func CALL INTERRUPTX(&H33, regs, regs) RETURN END FUNCTION SUB NextMove (Player, punt) 'looks if the Player's next move can be done 'if not, displays a message and asks for a key 'does the next move 'returns with the points obtained by the player (if 0, player has passed) SrchForValidMove 0, Player, posi 'Puede jugarse? IF posi = 0 THEN SELECT CASE Player CASE PeCe DisplMessage Player, "PC passes", 1 CASE Humano DisplMessage Player, "Human passes", 1 END SELECT punt = 0 ELSE 'permite la jugada por turnos SELECT CASE Player CASE PeCe PCThinks pos1, pt, 1, PeCe IF pos1 = 0 THEN pos1 = posi CASE Humano HumanMoves pos1 END SELECT LegalMove pos1, punt, 0, Player, hjJuega END IF END SUB SUB PCThinks (MejorPosic, ValorEstrtg, NivelCalculo, Player) ' Recursive routine that ends up with the best move possible ' MejorPosic Out Best move found ' ValorEstrtg Out Value of the best move ' NivelCalculoIn Level in Game() and level of recursion ' Player In Player that moves 'I'm not dead... IF NivelCalculo < PlayLevel THEN DisplMessage Player, "Evaluating,Level " + STR$(NivelCalculo), 0 'reset ranking of moves REDIM rank(1 TO numrank) AS elrank FOR i = 1 TO numrank: rank(i).punt = -32000: NEXT 'Fore each place in the playground FOR i = 11 TO 88 ' 'barre todo el tablero LegalMove i, Score, NivelCalculo - 1, Player, hjEsLegal 'if i'ts a legal position IF Score <> 0 THEN CopyGame NivelCalculo - 1, NivelCalculo LegalMove i, Score, NivelCalculo, Player, hjJuega PointCount NivelCalculo, Player, ptyo, ptcont 'if other player without pieces, we won IF ptcont = 0 THEN MejorPosic = i: ValorEstrtg = 32000: EXIT SUB 'evaluate position EvaluatePos NivelCalculo, ValorEstrtg, Player 'if position best than worse saved, swap and sort rank() IF ValorEstrtg > rank(numrank).punt THEN rank(numrank).punt = ValorEstrtg rank(numrank).posi = i posj = numrank GOSUB bblsort END IF END IF NEXT ' 'if under max level, copy game in next level and recalls itself for each move 'in rank() IF NivelCalculo < PlayLevel THEN FOR i = 1 TO numrank 'para cada una de las mejores jugadas IF rank(i).punt = -32000 AND PlayLevel <> 1 AND i <> 1 THEN EXIT FOR 'DebugRank NivelCalculo, rank(), rank(i).posi 'visualiza jugadas(debug) CopyGame NivelCalculo - 1, NivelCalculo LegalMove rank(i).posi, dummy, NivelCalculo, Player, hjJuega PCThinks MejorPosic, ValorEstrtg, NivelCalculo + 1, -Player 'final value of a move is the reverse of the other player's 'best next move rank(i).punt = -ValorEstrtg NEXT 'resort rank, the better move is the one that allows a worse 'next move for the other player FOR posj = 2 TO numrank GOSUB bblsort NEXT END IF 'DebugRank NivelCalculo, rank(), 0 'returns first entry in rank() MejorPosic = rank(1).posi ValorEstrtg = rank(1).punt EXIT SUB 'small bubblesort routine to sort best moves bblsort: IF posj = 1 THEN EXIT SUB FOR temp = posj TO 2 STEP -1 IF rank(temp).punt > rank(temp - 1).punt THEN SWAP rank(temp), rank(temp - 1) END IF NEXT RETURN END SUB SUB PointCount (Level, Player, ptosyo, ptoscont) ptosyo = 0: ptoscont = 0 FOR i = 10 TO 80 STEP 10 FOR j = 1 TO 8 SELECT CASE Game(i + j, Level) CASE Player ptosyo = ptosyo + 1 CASE -Player ptoscont = ptoscont + 1 END SELECT NEXT NEXT END SUB SUB Presentacion SCREEN 12 CLS LOCATE 2, 28: COLOR 14: PRINT "O T H E L L O " COLOR 7 LOCATE 3, 25: PRINT "Antoni Gual Via 1997 " COLOR 15 END SUB SUB SrchForValidMove (Profund, Player, posi) 'devuelve 1¦ tirada valida, 0 si no hay posi = 10 DO posi = posi + 1 LegalMove posi, pts, Profund, Player, hjEsLegal LOOP UNTIL pts <> 0 OR posi = 89 IF posi = 89 THEN posi = 0 END SUB SUB UserInput (a$, x, y) 'Takes input from user:cursor keys or mouse 'A$ Output LastKey Pressed 'X Output Column Selected 'Y Output Row Selected STATIC cur1 AS INTEGER, cur2 AS INTEGER dummy = MouseInt(mset, cur2, cur1) dummy = MouseInt(mshow, 0, 0) a$ = "" DO a$ = INKEY$ SELECT CASE RIGHT$(a$, 1) CASE "q", "Q" 'quit END CASE "r", "R" dummy = MouseInt(mhide, 0, 0) 'repeat EXIT SUB CASE "K" cur1 = cur1 - 1 IF cur1 < 1 THEN cur1 = 1 dummy = MouseInt(mset, cur2, cur1) CASE "H" cur2 = cur2 - 1 IF cur2 < 1 THEN cur2 = 1 dummy = MouseInt(mset, cur2, cur1) CASE "M" cur1 = cur1 + 1 IF cur1 > 8 THEN cur1 = 8 dummy = MouseInt(mset, cur2, cur1) CASE "P" cur2 = cur2 + 1 IF cur2 > 8 THEN cur2 = 8 dummy = MouseInt(mset, cur2, cur1) END SELECT B = MouseInt(mget, mf, mc) IF B THEN x = mc: y = mf LOOP UNTIL B OR a$ = CHR$(13) dummy = MouseInt(mhide, 0, 0) IF B = 0 THEN x = cur1: y = cur2 IF x > 8 THEN x = 8 IF y > 8 THEN y = 8 cur1 = x: cur2 = y END SUB