; 8086 version of app that proves you can't win at tic-tac-toe ; build with masm 5.0 like this: ; ntvdm -h masm /Zi /Zd /z /l %1,,,; ; ntvdm -h link %1,,%1,,nul.def ; ; board position: ; 0 1 2 ; 3 4 5 ; 6 7 8 dosseg .model small .stack 100h ; DOS constants dos_write_char equ 2h dos_realtime_clock equ 1ah dos_exit equ 4ch default_iterations equ 1 ; # of times to run (max 32767) max_score equ 9 ; maximum score min_score equ 2 ; minimum score win_score equ 6 ; winning score tie_score equ 5 ; tie score lose_score equ 4 ; losing score x_piece equ 1 o_piece equ 2 blank_piece equ 0 ; arguments to minmax relative to bp/sp ; space between locals and arguments: ; 0-1 2 or 4 bytes for return pc if minmax is near or far (it's near here) ; 2-3 2 bytes to save BP alpha_offset equ 4 beta_offset equ 6 dataseg segment para public 'data' assume ds: dataseg crlfmsg db 13,10,0 secondsmsg db ' seconds',13,10,0 iterationsmsg db 'iterations: ',0 zeroitersmsg db 'iterations argument must be 1..32767',13,10,0 movesmsg db 'moves: ',0 commaspmsg db ', ',0 align 16 board db 0,0,0,0,0,0,0,0,0 align 2 iters dw 0 ; iterations of running the app so far totaliters dw 0 ; # of iterations to run in total align 4 scratchpad dd 0 starttime dd 0 result dd 0 align 2 winprocs dw proc0, proc1, proc2, proc3, proc4, proc5, proc6, proc7, proc8 dataseg ends .code start: mov ax, dataseg mov ds, ax mov di, 0 mov [ di + totaliters ], default_iterations xor ax, ax cmp al, byte ptr es: [ di + 128 ] ; command tail length is 128 bytes into the PSP jz done_with_arguments mov cx, 129 ; string is guaranteed to be 0x0d terminated by DOS push ds mov ax, es ; temporarily make ds point to the psp mov ds, ax call atou ; pointer to string in cx, unsigned integer result in ax pop ds mov [ di + totaliters ], ax cmp ax, 0 jnz done_with_arguments mov dx, offset zeroitersmsg ; the argument isn't valid; show error and exit call printstring mov al, 1 mov ah, dos_exit int 21h done_with_arguments: xor ax, ax int dos_realtime_clock mov word ptr [ starttime ], dx mov word ptr [ starttime + 2 ], cx lea bx, [ board ] ; global board pointer xor ax, ax ; make sure ah is 0 mov dx, ax ; make sure dh is 0 again: xor si, si ; zero the global move count so it doesn't overflow ; run for the 3 unique first moves mov ax, 0 call runmm mov ax, 1 call runmm mov ax, 4 call runmm inc word ptr [ iters ] mov ax, [ totaliters ] cmp ax, [ iters ] jne again push si ; save the global move count for later call printelap mov dx, offset secondsmsg call printstring mov dx, offset movesmsg call printstring pop ax ; restore the global move count for printing call printint call printcrlf mov dx, offset iterationsmsg call printstring mov ax, [iters] call printint call printcrlf mov al, 0 mov ah, dos_exit int 21h ;start endp runmm proc near ; make the first move mov di, ax push di mov byte ptr [ bx + di ], x_piece ; alpha and beta passed on the stack ; current move in di ; current value in dl ; current depth in cx ; global move count in si ; global board pointer in bx ; always 0 in ah and dh xor cx, cx ; depth in cx mov al, max_score ; pushing constants didn't start until the 80186 push ax ; beta mov al, min_score push ax ; alpha call minmax_min ; restore the board at the first move position pop di mov byte ptr [ bx + di ], blank_piece ret runmm endp minmax_max proc near inc si ; increment global move count cmp cl, 4 jl short _max_no_winner_check shl di, 1 mov al, o_piece call word ptr [ offset winprocs + di ] cmp al, o_piece mov al, lose_score je short _max_just_return_al _max_no_winner_check: push bp mov bp, sp ; set bp to the stack location push dx ; save caller's value mov dl, min_score ; dx has value mov di, -1 ; di has the move 0..8 inc cx ; increment global depth _max_loop: cmp di, 8 je short _max_load_value_return inc di cmp byte ptr [ bx + di ], 0 jnz short _max_loop push di mov byte ptr [ bx + di ], x_piece push [ bp + beta_offset ] push [ bp + alpha_offset ] call minmax_min pop di mov byte ptr [ bx + di ], 0 cmp al, win_score ; can't do better than winning je short _max_restore_value cmp ax, dx ; compare score with value jle short _max_loop cmp al, [ bp + beta_offset ] ; compare value with beta jge short _max_restore_value ; beta pruning mov dx, ax ; update value with score cmp al, [ bp + alpha_offset ] ; compare value with alpha jle short _max_loop mov [ bp + alpha_offset ], al ; update alpha with value jmp short _max_loop _max_load_value_return: mov ax, dx _max_restore_value: dec cx pop dx ; restore caller's value pop bp _max_just_return_al: ret 4 ; cleanup stack space used for arguments minmax_max endp minmax_min proc near inc si ; increment global move count cmp cl, 4 jl short _min_no_winner_check shl di, 1 mov al, x_piece call word ptr [ offset winprocs + di ] cmp al, x_piece mov al, win_score je short _min_just_return_al cmp cl, 8 mov al, tie_score je short _min_just_return_al _min_no_winner_check: push bp mov bp, sp ; set bp to the stack location push dx ; save caller's value mov dl, max_score ; dx has value mov di, -1 ; di has the move 0..8 inc cx ; increment global depth _min_loop: cmp di, 8 je short _min_load_value_return inc di cmp byte ptr [ bx + di ], 0 jne short _min_loop push di mov byte ptr [ bx + di ], o_piece push [ bp + beta_offset ] push [ bp + alpha_offset ] call minmax_max pop di mov byte ptr [ bx + di ], 0 cmp al, lose_score ; can't do better than losing je short _min_restore_value cmp al, dl ; compare score with value jge short _min_loop cmp al, [ bp + alpha_offset ] ; compare value with alpha jle short _min_restore_value ; alpha pruning mov dx, ax ; update value with score cmp al, [ bp + beta_offset ] ; compare value with beta jge short _min_loop mov [ bp + beta_offset ], al ; update beta with value jmp short _min_loop _min_load_value_return: mov ax, dx _min_restore_value: dec cx pop dx ; restore caller's value pop bp _min_just_return_al: ret 4 ; cleanup stack space used for arguments minmax_min endp IFDEF WinnerProc ; winner is no longer used since function pointers with the most recent move in ax are much faster winner proc near xor al, al mov al, [ bx ] cmp al, 0 je _win_check_3 cmp al, [ bx + 1 ] jne _win_check_0_b cmp al, [ bx + 2 ] jne _win_check_0_b ret _win_check_0_b: cmp al, [ bx + 3 ] jne _win_check_3 cmp al, [ bx + 6 ] jne _win_check_3 ret _win_check_3: mov al, [ bx + 3 ] cmp al, 0 je _win_check_6 cmp al, [ bx + 4 ] jne _win_check_6 cmp al, [ bx + 5 ] jne _win_check_6 ret _win_check_6: mov al, [ bx + 6 ] cmp al, 0 je _win_check_1 cmp al, [ bx + 7 ] jne _win_check_1 cmp al, [ bx + 8 ] jne _win_check_1 ret _win_check_1: mov al, [ bx + 1 ] cmp al, 0 je _win_check_2 cmp al, [ bx + 4 ] jne _win_check_2 cmp al, [ bx + 7 ] jne _win_check_2 ret _win_check_2: mov al, [ bx + 2 ] cmp al, 0 je _win_check_4 cmp al, [ bx + 5 ] jne _win_check_4 cmp al, [ bx + 8 ] jne _win_check_4 ret _win_check_4: mov al, [ bx + 4 ] cmp al, 0 je _win_return cmp al, [ bx ] jne _win_check_4_b cmp al, [ bx + 8 ] jne _win_check_4_b ret _win_check_4_b: cmp al, [ bx + 2 ] jne _win_return_blank cmp al, [ bx + 6 ] je _win_return _win_return_blank: xor al, al _win_return: ret winner endp ENDIF ; WinnerProc atou proc near ; string input in cx. unsigned 16-bit integer result in ax push di push bx push cx mov bx, 0 ; running total is in bx mov di, cx mov cx, 10 _skipspaces: cmp byte ptr [di ], ' ' jne _atouNext inc di jmp _skipspaces _atouNext: cmp byte ptr [ di ], '0' ; if not a digit, we're done. Works with null and 0x0d terminated strings jb _atouDone cmp byte ptr [ di ], '9' + 1 jge _atouDone mov ax, bx mul cx mov bx, ax xor ah, ah mov al, byte ptr [ di ] sub ax, '0' add bx, ax inc di jmp _atouNext _atouDone: mov ax, bx pop cx pop bx pop di ret atou endp ; print the integer in ax printint proc near test ah, 80h push ax push bx push cx push dx push di push si jz _prpositive neg ax ; just one instruction for complement + 1 push ax mov dx, '-' mov ah, dos_write_char int 21h pop ax _prpositive: xor cx, cx xor dx, dx cmp ax, 0 je _przero _prlabel1: cmp ax, 0 je _prprint1 mov bx, 10 div bx push dx inc cx xor dx, dx jmp _prlabel1 _prprint1: cmp cx, 0 je _prexit pop dx add dx, 48 mov ah, dos_write_char int 21h dec cx jmp _prprint1 _przero: mov dx, '0' mov ah, dos_write_char int 21h _prexit: pop si pop di pop dx pop cx pop bx pop ax ret printint endp printcrlf proc near push ax push bx push cx push dx push di push si mov dx, offset crlfmsg call printstring pop si pop di pop dx pop cx pop bx pop ax ret printcrlf endp printcommasp proc near push ax push bx push cx push dx push di push si mov dx, offset commaspmsg call printstring pop si pop di pop dx pop cx pop bx pop ax ret printcommasp endp prperiod proc near push ax push bx push cx push dx push di push si mov dx, '.' mov ah, dos_write_char int 21h pop si pop di pop dx pop cx pop bx pop ax ret prperiod endp printelap proc near push ax push bx push cx push dx push di push si xor ax, ax int dos_realtime_clock mov word ptr [ scratchpad ], dx mov word ptr [ scratchpad + 2 ], cx mov dl, 0 mov ax, word ptr [ scratchpad ] mov bx, word ptr [ starttime ] sub ax, bx mov word ptr [ result ], ax mov ax, word ptr [ scratchpad + 2 ] mov bx, word ptr [ starttime + 2 ] sbb ax, bx mov word ptr [ result + 2 ], ax mov dx, word ptr [ result + 2 ] mov ax, word ptr [ result ] mov bx, 10000 mul bx mov bx, 18206 div bx xor dx, dx mov bx, 10 div bx push dx call printint call prperiod pop ax call printint pop si pop di pop dx pop cx pop bx pop ax ret printelap endp printstring proc near push ax push bx push cx push dx push di push si mov di, dx _psnext: mov al, byte ptr [ di ] cmp al, 0 je _psdone mov dx, ax mov ah, dos_write_char int 21h inc di jmp _psnext _psdone: pop si pop di pop dx pop cx pop bx pop ax ret printstring endp align 2 proc0 proc near cmp al, [bx + 1] jne short proc0_next_win cmp al, [bx + 2] je short proc0_yes proc0_next_win: cmp al, [bx + 3] jne short proc0_next_win2 cmp al, [bx + 6] je short proc0_yes proc0_next_win2: cmp al, [bx + 4] jne short proc0_no cmp al, [bx + 8] je short proc0_yes proc0_no: mov al, ah ; ah is always 0. mov al, ah is 2 cycles. xor ax and xor al are both 3 cycles. proc0_yes: ret proc0 endp align 2 proc1 proc near cmp al, [bx + 0] jne short proc1_next_win cmp al, [bx + 2] je short proc1_yes proc1_next_win: cmp al, [bx + 4] jne short proc1_no cmp al, [bx + 7] je short proc1_yes proc1_no: mov al, ah proc1_yes: ret proc1 endp align 2 proc2 proc near cmp al, [bx + 0] jne short proc2_next_win cmp al, [bx + 1] je short proc2_yes proc2_next_win: cmp al, [bx + 5] jne short proc2_next_win2 cmp al, [bx + 8] je short proc2_yes proc2_next_win2: cmp al, [bx + 4] jne short proc2_no cmp al, [bx + 6] je short proc2_yes proc2_no: mov al, ah proc2_yes: ret proc2 endp align 2 proc3 proc near cmp al, [bx + 0] jne short proc3_next_win cmp al, [bx + 6] je short proc3_yes proc3_next_win: cmp al, [bx + 4] jne short proc3_no cmp al, [bx + 5] je short proc3_yes proc3_no: mov al, ah proc3_yes: ret proc3 endp align 2 proc4 proc near cmp al, [bx + 0] jne short proc4_next_win cmp al, [bx + 8] je short proc4_yes proc4_next_win: cmp al, [bx + 2] jne short proc4_next_win2 cmp al, [bx + 6] je short proc4_yes proc4_next_win2: cmp al, [bx + 1] jne short proc4_next_win3 cmp al, [bx + 7] je short proc4_yes proc4_next_win3: cmp al, [bx + 3] jne short proc4_no cmp al, [bx + 5] je short proc4_yes proc4_no: mov al, ah proc4_yes: ret proc4 endp align 2 proc5 proc near cmp al, [bx + 3] jne short proc5_next_win cmp al, [bx + 4] je short proc5_yes proc5_next_win: cmp al, [bx + 2] jne short proc5_no cmp al, [bx + 8] je short proc5_yes proc5_no: mov al, ah proc5_yes: ret proc5 endp align 2 proc6 proc near cmp al, [bx + 2] jne short proc6_next_win cmp al, [bx + 4] je short proc6_yes proc6_next_win: cmp al, [bx + 0] jne short proc6_next_win2 cmp al, [bx + 3] je short proc6_yes proc6_next_win2: cmp al, [bx + 7] jne short proc6_no cmp al, [bx + 8] je short proc6_yes proc6_no: mov al, ah proc6_yes: ret proc6 endp align 2 proc7 proc near cmp al, [bx + 1] jne short proc7_next_win cmp al, [bx + 4] je short proc7_yes proc7_next_win: cmp al, [bx + 6] jne short proc7_no cmp al, [bx + 8] je short proc7_yes proc7_no: mov al, ah proc7_yes: ret proc7 endp align 2 proc8 proc near cmp al, [bx + 0] jne short proc8_next_win cmp al, [bx + 4] je short proc8_yes proc8_next_win: cmp al, [bx + 2] jne short proc8_next_win2 cmp al, [bx + 5] je short proc8_yes proc8_next_win2: cmp al, [bx + 6] jne short proc8_no cmp al, [bx + 7] je short proc8_yes proc8_no: mov al, ah proc8_yes: ret proc8 endp end