title Othello name ('OTHELLO') ; This game based upon a BASIC game written by Richard O. Duda, October 1977 ; ; From the original source: ; ############################################## ; PLAYS THE GAME "OTHELLO" WITH TWO STRATEGIES: ; 1. TAKE THE MAXIMUM NUMBER OF PIECES ; 2. ADD A BONUS FOR OUTSIDE POSITION ; BOARD IS THE ARRAY A, BOUNDED BY 0'S (BLANKS) ; A = 0 FOR EMPTY SQUARE ; A = B FOR BLACK SQUARE -- X (INTERNALLY -1) ; A = W FOR WHITE SQUARE -- 0 (INTERNALLY +1) ; I AND J ALWAYS USED FOR ROW/COLUMN INDICES ; I4 AND J4 STORE INCREMENTS TO THE 8 NEIGHBORS ; ############################################## ; ; Written for Z80 CP/M by W.Cirsovius ; ; This version a bit optimized and less line number labels OS equ 0000h BDOS equ 0005h TPATOP equ BDOS+1 .condir equ 6 .string equ 9 _get equ 0fdh lf equ 0ah cr equ 0dh eot equ '$' BordSiz equ 8 ; Line of board BordDim equ 1+BordSiz+1 ; Dtto including edges BordFul equ BordSiz*BordSiz ; Filled board count ; ; Board pieces ; Piece1 equ 'X' Piece2 equ 'O' ; ; Winner types ; Squeak equ 11 Hot equ 25 Fighter equ 39 Walk equ 53 print macro msg ld de,msg call string endm dseg me: db '',cr,eot stopit: db cr,lf db '^C detected - really abort?',eot bybye: db cr,lf,'Bye bye',cr,lf,lf,eot left: db ' ',eot head: db cr,lf db ' A B C D E F G H',cr,lf,eot thatwas: db 'THAT WAS A ',eot squeaker: db 'SQUEAKER ! ! ',eot hotgame: db 'HOT GAME !',eot fight: db 'FIGHT.',eot walkaway: db 'WALKAWAY.',eot perfect: db 'PERFECT GAME.',eot anotherone: db cr,lf db 'DO YOU WANT TO PLAY ANOTHER GAME',eot thankyou: db 'THANKS FOR PLAYING.',cr,lf,eot end1: db 'YOU HAVE ',eot end2: db ' PIECES AND I HAVE ',eot end3: db ' PIECES -- ',eot atie: db 'A TIE !!',cr,lf,eot youwon: db 'YOU WON !',cr,lf,eot iwon: db 'SORRY, I WON THAT ONE.',cr,lf,eot foryou: db 'THAT GIVES YOU ',eot foryou2: db ' OF MY PIECES',cr,lf,eot notflank: db 'SORRY, THAT DOESN''T FLANK A ROW; TRY AGAIN',cr,lf,eot notnext: db 'SORRY, YOU ARE NOT NEXT TO ONE OF MY PIECES;' db cr,lf db 'TRY AGAIN',cr,lf,eot occupied: db 'SORRY, THAT SQUARE IS OCCUPIED; TRY AGAIN',cr,lf,eot youforfeit: db 'ARE YOU FORFEITING YOUR TURN',eot yourmove: db 'YOUR MOVE -- ',eot inrow: db 'ROW: ',eot incol: db 'COL: ',eot Thatgives: db 'THAT GIVES ME ',eot Thatgives2: db ' OF YOUR PIECES',cr,lf,eot Imove: db 'I WILL MOVE TO ',eot MoveDelim: db ' , ',eot forfeit: db 'I HAVE TO FORFEIT MY MOVE',cr,lf XorO: db 'DO YOU WANT TO HAVE X OR O ',eot gofirst: db 'DO YOU WANT TO GO FIRST',eot BestStrategy: db 'SHOULD I PLAY MY BEST STRATEGY',eot Waitformove: db 'SHOULD I WAIT BEFORE MAKING MY MOVES',eot Greeting: db 'GREETINGS FROM OTHELLO' db cr,lf db 'DO YOU WANT INSTRUCTIONS',eot YorN: db ' (Y OR N) ',eot okletgo: db 'OK. TYPING ANY CHARACTER WILL LET ME GO.' db cr,lf,eot Instruction: db cr,lf db 'OTHELLO IS PLAYED ON AN 8 X 8 CHECKER BOARD,' db cr,lf db 'ROWS NUMBERED 1 TO 8 AND COLUMNS A TO H.' db cr,lf db 'THE INITIAL CONFIGURATION IS ALL BLANK, EXCEPT' db cr,lf db 'FOR THE CENTER FOUR SQUARES, WHICH FORM THE' db cr,lf db 'PATTERN' db cr,lf db ' ',Piece2,' ',Piece1 db cr,lf db ' ',Piece1,' ',Piece2 db cr,lf,lf db 'TRY TO PLACE YOUR PIECE SO THAT IT ''OUTFLANKS''' db cr,lf db 'MINE, CREATING A HORIZONTAL, VERTICAL, OR' db cr,lf db 'DIAGONAL RUN OF MY PIECES BOUNDED AT EACH END' db cr,lf db 'BY AT LEAST ONE OF YOURS. THIS WILL ''FLIP'' MY' db cr,lf db 'PIECES, TURNING THEM INTO YOURS.' db cr,lf db 'NOTE: YOU MUST CAPTURE AT LEAST ONE OF MY' db cr,lf db 'PIECES IN THIS WAY IF IT IS AT ALL POSSIBLE.' db cr,lf db 'IF IT IS NOT POSSIBLE, YOU FORFEIT YOUR TURN BY' db cr,lf db 'ENTERING 0,0 FOR YOUR (ROW,COL) MOVE.' db cr,lf,lf,eot ; XO: db Piece1,Piece2-Piece1 YN: db 'Y','N'-'Y' ; FlipFlag: ds 1 White: ; \ ds 1 ; | Black: ; | ds 1 ; / CompColor: ; \ ds 1 ; | HumanColor: ; | ds 1 ; / MoveSum: ds 1 CompScore: ds 1 HumanScore: ds 1 Score: ds 1 FlipCount: ; \ ds 1 ; | Best: ; | db 0 ; / Wait: db 0 ScoreSum: ds 1 MyPiece: ds 1 YourPiece: ds 1 CompRow: ; \ ds 1 ; | CompCol: ; | ds 1 ; / isForfeit: ds 1 RowPos: ; \ ds 1 ; | ColPos: ; | ds 1 ; / CurVerFix: ; \ ds 1 ; | CurHorFix: ; | ds 1 ; / CurVerPos: ; \ ds 1 ; | CurHorPos: ; | ds 1 ; / ; Pieces: db Piece1,'.',Piece2 VerFix: db 0,-1,-1,-1, 0, 1, 1, 1 HorFix: db 1, 1, 0,-1,-1,-1, 0, 1 HeadLine: db 0,'ABCDEFGH' ; ; Game board ; Board: rept BordDim ds BordDim endm A.SIZE equ $-Board cseg ; ; Print decimal Accu - Max is 64 ; prdec: ld c,-1 div10: inc c ; Get result sub 10 jr nc,div10 add a,'0'+10 push af ld a,'0' add a,c ; Build tens cp '0' ; Test leading zero call nz,COT ; Nope, print it pop af jr COT ; ; Echo character and close line ; COTnl: call COT ; Echo it NL: ld a,cr call COT ; Close line ld a,lf ; ; Do direct console call ; COT: push bc push de push hl ld e,a ; Get code or character ld c,.condir call BDOS ; Get or put character pop hl pop de pop bc ret ; ; Get character from keyboard as upper case ; UPIN: ld a,_get call COT ; Get character cp 'C'-'@' ; Test abort jr z,Abort cp 'a' ; Test lower case ret c cp 'z'+1 ret nc add a,'A'-'a' ; Convert it ret Abort: print stopit call Y$N ; Test real abort jr nz,UPIN ; Nope print bybye jp OS ; ; Put string ^DE to console ; string: push bc push de push hl ld c,.string call BDOS ; Print pop hl pop de pop bc ret ; ; Get X or O -> Z or NZ ; X$O: ld iy,XO jr WtYN ; ; Get Y.ES or N.O -> Z or NZ ; Y$N: print YorN ; Tell what we are expecting ld iy,YN WtYN: call UPIN ; Get response ld b,a sub (iy+0) ; Test valid response jr z,isY cp (iy+1) jr nz,WtYN isY: or a push af ld a,b call COTnl ; Echo response pop af ret ; ; Tell instructions ; Help: print Instruction ret ; ; ######## ; # MAIN # ; ######## ; OTHELLO: ld sp,(TPATOP) ; Init stack print Greeting call Y$N ; Get response call z,Help ; Print instructions print Waitformove ; Ask for waiting before moving call Y$N jr nz,NoWait ld a,1 ld (Wait),a ; Set wait flag print okletgo NoWait: print BestStrategy ; Ask for playing best strategy call Y$N jr nz,NotBest ld a,2 ld (Best),a ; Mark best strategy NotBest: ld a,-1 ld (Black),a ; Init piece colours ld a,+1 ld (White),a StartOTHELLO: ld hl,Board ; Point to game board ld de,Board+1 ld bc,A.SIZE-1 ld (hl),0 ldir ; Clear board ld a,(White) ; Init start position ld (Board+4*BordDim+4),a ld (Board+5*BordDim+5),a ld a,(Black) ld (Board+4*BordDim+5),a ld (Board+5*BordDim+4),a ld a,2 ld (CompScore),a ; Init scores ld (HumanScore),a add a,a ld (Score),a xor a ld (isForfeit),a ; No forfeit yet print XorO ; Ask for X or O call X$O ld hl,(White) jr z,HumanX ; Human selected X ld a,l ; Swap selection ld l,h ld h,a HumanX: ld (CompColor),hl ; Set colours print gofirst ; Ask for start call Y$N jr nz,CompGo ; Nope, computer starts call PrintBoard ; Print initial board jp HumanMove ; Human's move ; ; Computer's move ; CompMove: ld a,(Wait) or a ; Test wait for move jr z,CompGo ; Nope print me ; Yeap, push key call UPIN CompGo: xor a ld (CompRow),a ; Init coordinates ld (CompCol),a dec a ld (MoveSum),a ; Set no sum yet ld a,(CompColor) ld (MyPiece),a ld a,(HumanColor) ld (YourPiece),a ld b,BordSiz ; Init row loop CompLook: ld c,BordSiz ; Init column loop CompCheck: push bc call GetMove ; Do the job pop bc ; Next column dec c jr nz,CompCheck djnz CompLook ; Next row ld a,(MoveSum) add a,a ; Could we do something? ld hl,isForfeit jr nc,CompExec ; Yeap print forfeit ld a,(hl) or a ; Test previous forfeit jp nz,EndGame ; Yeap, exit ld (hl),1 ; Indicate forfeit jr HumanMove ; Let the human do CompExec: ld (hl),0 ; Reset forfeit flag print Imove ld a,(CompRow) call prdec ; Tell row print MoveDelim ld hl,(CompCol) ld h,0 ld bc,HeadLine add hl,bc ; Position in header line ld a,(hl) ; Get character call COTnl ; Print column ld hl,(CompRow) ld (RowPos),hl ld a,1 call ScoreUpdate ; Flip pieces ld hl,CompScore ld a,(FlipCount) ld c,a add a,(hl) ; Calculate new counts inc a ld (hl),a ld a,(HumanScore) sub c ld (HumanScore),a ld hl,Score inc (hl) print Thatgives ; Tell result ld a,c call prdec print Thatgives2 call PrintBoard ; Print new board ld a,(HumanScore) or a ; Test end jp z,EndGame ld a,(Score) cp BordFul jp z,EndGame ; ; Human's move ; HumanMove: ld a,(HumanColor) ld (MyPiece),a ; Unpack color ld a,(CompColor) ld (YourPiece),a GetInput: print yourmove IllInput: print inrow call UPIN ; Get row sub '0' ; Make decimal jr c,IllInput jr z,WantForfeit ; Test forfeiting cp BordSiz+1 ; Verify correct input jr nc,IllInput ld (RowPos),a ; Save position jr GetColumn WantForfeit: print youforfeit ; Test forfeiting call Y$N jr nz,GetInput ; Nope, get next ld hl,isForfeit ld a,(hl) or a ; Test previous forfeit jp nz,EndGame ; Yeap, exit ld (hl),1 ; Indicate forfeit jp CompMove ; Let the machine do GetColumn: add a,'0' call COT ; Echo row ld a,' ' call COT print incol ; Get column call UPIN push af call COT ; Echo column call NL pop af sub 'A'-1 ; Map A..H to 1..8 jr c,IllInput ; Verify valid input cp BordSiz+1 jr nc,IllInput ld (ColPos),a ; Save position ld ix,RowPos call BoardLoc ; Get pointer to board location jr z,FreePos ; Test free print occupied ; Tell occupied jr IllInput FreePos: call TestNeighbor ; Test pieces next jr z,CompPiece print notnext ; Tell no computer piece jr IllInput CompPiece: xor a call ScoreUpdate ; Test opponent ld a,(FlipCount) ; Get count or a ; Test any jr nz,FlipHuman ; Yeap print notflank ; Tell no opponent jp IllInput FlipHuman: xor a ld (isForfeit),a ; Clear forfeiting flag print foryou ld a,(FlipCount) call prdec ; Tell number print foryou2 ld a,1 call ScoreUpdate ; Flip pieces ld hl,HumanScore ld a,(FlipCount) ld c,a add a,(hl) ; Calculate new counts inc a ld (hl),a ld a,(CompScore) sub c ld (CompScore),a ld hl,Score inc (hl) call PrintBoard ; Print board ld a,(CompScore) or a ; Test end jr z,EndGame ld a,(Score) cp BordFul jp nz,CompMove ; ; End of game wrap up ; EndGame: print end1 ; Tell piece count ld a,(HumanScore) call prdec print end2 ld a,(CompScore) call prdec print end3 ld de,atie ld hl,HumanScore ld a,(CompScore) cp (hl) ; Test same jr z,TellTie ; Yeap, tell it ld de,youwon jr c,TellTie ld de,iwon TellTie: push af call string pop af call nz,TellType ; Tell type of game if not a tie print anotherone call Y$N ; Tell another game to play jp z,StartOTHELLO print thankyou ; Tell bye jp OS ; Exit to CP/M again ; ; Tell some game characters ; TellType: ld hl,HumanScore ld de,CompScore ld a,(de) sub (hl) ; Get difference jr nc,DiffPos neg ; Make grater 0 DiffPos: ld (de),a ld l,a ld h,0 add hl,hl ; * 2 add hl,hl ; * 4 add hl,hl ; * 8 add hl,hl ; *16 add hl,hl ; *32 add hl,hl ; *64 ld a,(Score) ld c,a call div ; Divide ld a,l ; Test class ld de,squeaker cp Squeak jr c,GotType ld de,hotgame cp Hot jr c,GotType ld de,fight cp Fighter jr c,GotType ld de,walkaway cp Walk jr c,GotType ld de,perfect GotType: push de print thatwas pop de call string ; Tell it call NL ret ; ; Loop for computer's move - enters with reg B as row (8..1 -> 1..8) ; and reg C as column (8..1 -> 1..8) ; GetMove: ld a,BordSiz+1 sub b ; Map to 1..8 ld (RowPos),a ld a,BordSiz+1 sub c ld (ColPos),a ld ix,RowPos call BoardLoc ; Get pointer to board location ret nz ; Not blank, try next call TestNeighbor ; Test opponent ret nz ; Nope xor a call ScoreUpdate ; Test opponent ld a,(FlipCount) ; Get count or a ; Test any ret z ; Nope ld a,(RowPos) ; Test boundary position call boundary ld a,(ColPos) call boundary ld a,(MoveSum) ld c,a add a,a ; Test any set jr c,BetterMove ; Nope, take sum ld a,(FlipCount) cp c ; Ist this move better found so far? ret c ; Nope jr nz,BetterMove ld a,i ; The random decision add a,r add a,a ret c BetterMove: ld a,(FlipCount) ld (MoveSum),a ; Change count ld hl,(RowPos) ld (CompRow),hl ; Save position ret ; ; Calculate board location ; BoardLoc: ld hl,Board-BordDim ld de,BordDim ld b,(ix+0) ; Get row=multiplier inc b MultLoc: add hl,de ; Calculate row djnz MultLoc ld c,(ix+1) ; Get column add hl,bc ; Get position ld a,(hl) ; Get piece or a ; Or empty if Z set ret ; ; Test boundary position of board ; boundary: cp 1 ; Test it jr z,inccnt ; Yeap cp BordSiz ret nz inccnt: ld hl,Best ld a,(hl) dec hl add a,(hl) ; Update count ld (hl),a ret ; ; Subroutine test-for-proper-neighbor ; Assumes: ; IX points to I,J locating a blank square ; You hope to see an adjacent YourPiece (= -MyPiece) ; ; Success on F1=1 -> Z ; TestNeighbor: ld ix,RowPos call BoardLoc ; Get pointer to current location ld de,BordDim or a sbc hl,de ; Set start position dec hl ld a,(YourPiece) ; Get what we are searching ld b,3 ; Three lines NextLine: push hl call isNeighbor ; Find neighbor call nz,isNeighbor ; In horizontal line call nz,isNeighbor pop hl ret z ; Got it add hl,de ; Next line djnz NextLine xor a dec a ; Set nosuccess ret ; ; Test neighbour - Z set says found ; isNeighbor: cp (hl) ; Compare inc hl ; Set next ret ; ; Subroutine score-and-update ; Assumes: ; (I,J) is a tentative place for a piece MyPiece. ; want runs of YourPiece = -MyPiece, terminated by a MyPiece. ; if FlipFlag (in Accu) is true (1), mark those runs as MyPiece's. ; Return sum of all runs (YourPiece's only) in FlipCount. ; Main program contains the following arrays: ; VerFix: 0 -1 -1 -1 0 1 1 1 ; HorFix: 1 1 0 -1 -1 -1 0 1 ; ScoreUpdate: ld (FlipFlag),a xor a ld (FlipCount),a ; Clear count ld b,BordSiz ; Set count ScoreLoop: push bc call DoScoreUpdate ; Do the run pop bc djnz ScoreLoop ret ; ; Do score-and-update ; DoScoreUpdate: ld a,BordSiz sub b ; Map 8..1 -> 0..7 ld c,a ld b,0 ld hl,VerFix add hl,bc ; Position in fix table ld a,(hl) ; Calculate position ld (CurVerFix),a ld hl,RowPos add a,(hl) ld (CurVerPos),a ; Set current board location ld hl,HorFix add hl,bc ld a,(hl) ld (CurHorFix),a ld hl,ColPos add a,(hl) ld (CurHorPos),a xor a ld (ScoreSum),a ; Init sum for this run ld ix,CurVerPos call BoardLoc ; Get pointer to location ld a,(YourPiece) cp (hl) ; Test piece ret nz ; Nope ScoreScan: ld hl,ScoreSum inc (hl) ; Update count call NextPos ld ix,CurVerPos call BoardLoc ; Get pointer to location ret z ; Is empty ld a,(MyPiece) cp (hl) ; Test piece jr nz,ScoreScan ; Nope ld hl,FlipCount ld a,(ScoreSum) add a,(hl) ; Build sum ld (hl),a ld a,(FlipFlag) ; Test update dec a ret nz ; Nope ld hl,(RowPos) ld (CurVerPos),hl ; Init coordinate ld a,(ScoreSum) ; Get count ld b,a inc b UpdateIt: push bc ld ix,CurVerPos call BoardLoc ; Get pointer to location ld a,(MyPiece) ld (hl),a ; Update call NextPos ; Build new location pop bc djnz UpdateIt ret ; ; Build new positions ; NextPos: ld hl,CurVerPos ld de,CurVerFix ld a,(de) add a,(hl) ; Build new location ld (hl),a inc hl inc de ld a,(de) add a,(hl) ld (hl),a ret ; ; Print game board ; PrintBoard: print head ld ix,RowPos ld (ix+0),1 ld b,BordSiz PrintIt: ld a,(ix+0) ; Get row add a,'0' ; Make ASCII call COT ; Print it print left ld (ix+1),1 ; Init column position ld c,BordSiz PrintCol: push bc ld a,' ' call COT call BoardLoc ; Get pointer to location inc a ld e,a ld d,0 ld hl,Pieces add hl,de ld a,(hl) ; Get piece call COT ; Print it pop bc inc (ix+1) dec c ; Go thru this row jr nz,PrintCol call NL inc (ix+0) ; Next row djnz PrintIt call NL ret ; ; HL:=HL DIV C ; div: xor a ld b,16 divloop: add hl,hl rla cp c jr c,divskp sub c inc l divskp: djnz divloop ret end OTHELLO