;; Copyright (c) 2008 Jeremiah LaRocco
;; All rights reserved.

;; Redistribution and use in source and binary forms, with or without
;; modification, are permitted provided that the following conditions
;; are met:
;; 1. Redistributions of source code must retain the above copyright
;;    notice, this list of conditions and the following disclaimer.
;; 2. Redistributions in binary form must reproduce the above copyright
;;    notice, this list of conditions and the following disclaimer in the
;;    documentation and/or other materials provided with the distribution.
;; 3. The name of the author may not be used to endorse or promote products
;;    derived from this software without specific prior written permission.

;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
;; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
;; OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
;; IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
;; INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
;; NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
;; THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

[BITS 16]
[ORG 0]

jmp start ; Skip data

	;; Reboot message
	rebooting	db 'Reboot',0

	;; Input prompt
	prompt		db 'Enter move.',0
	
	;; 9 empty spaces for the board
	game_moves	db '123456789'

	;; Lame, but simplifies the logic.
	;; Indexed by ecx, in reverse, for the next move character
	game_order	db 'XOXOXOXOX'

	win_msg		db '  has won!',13,10,0
	draw_msg	db 'Tie',13,10,0

	;; The game board
	game_line	db ' | | ',13,10,0
	sep_line	db '-+-+-',13,10,0

	;; Ask if they want to play again
	exit_msg	db 'Play again (y or n)?',13,10,0

	;; Winning bitmaps (see check_for_win for details)
  	matrix_of_win 	dw 0x0124, 0x01C0, 0x0111, 0x0092, 0x0054, 0x0049, 0x0038, 0x0007, 13, 10, 0

	;; Displays the message in si
        message:                        ; Dump ds:si to screen.
		push eax
		push ebx
inner_message:	
                lodsb                   ; load byte at ds:si into al
                or al,al                ; test if character is 0 (end)
                jz done
                mov ah,0eh              ; put character
                mov bx,0008             ; attribute
                int 0x10                ; call BIOS
		
                jmp inner_message
        done:
		pop ebx
		pop eax
                ret

        getkey:
                mov ah, 0               ; wait for key
                int 016h
                ret

        reboot:
                db 0EAh                 ; machine language to jump to FFFF:0000 (reboot)

                dw 0000h
                dw 0FFFFh

start:
        mov ax,0x7c0    ; BIOS puts us at 0:07C00h, so set DS accordinly
        mov ds,ax       ; Therefore, we don't have to add 07C00h to all our data

        cli             ; clear interrupts while we setup a stack
        mov ax,0x9000   ; this seems to be the typical place for a stack
        mov ss,ax
        mov sp,0xffff   ; let's use the whole segment.  Why not?  We can :)
        sti             ; put our interrupts back on
        
;;         call detect_cpu ; check if we've got a 386

.386
	;; Skip game functions
	jmp beginning
	
	;; Use int 0x10 to set video mode and clear screen
clear_screen:
 	mov ax, 3
	int 10h
	ret

start_game:
	;; Fill game_moves with '123456789'
	mov cl, '1'
clear:	
	mov [game_moves+ecx-'1'], cl
	inc ecx
	cmp cl, '9'
	jle clear

	;; Main game loop
	mov cl, 9
game_loop:
	call show_screen

	push edx
	call check_for_win
	;; dl == 'X' || dl=='O' means somebody won
	;; dl == 0 means no win
	cmp dl, 0
	jne win_found
	pop edx

	;; cx==0 means there are no more moves
	;; Since nobody's won, it's a draw
 	jecxz game_drawn

	;; Get user input
	call get_pos

	;; Convert user input into an offset
	sub dl, '1'
	
	;; Convert current move # into 'X' or 'O'
	mov bl, [game_order+ecx-1]
	
	;; Place the 'X' or 'O'
	mov [game_moves+edx], bl

	dec cl
	jmp game_loop

	
win_found:
	;; dl contains token of the winner,
	;; so fill in the "win message"
	mov [win_msg], dl
	pop edx
	mov si, win_msg
	;; Inform the user
	call message
	ret


game_drawn:
	;; Inform the user of the draw
	mov si, draw_msg
	call message
	ret

show_screen:
	;; Paint the TTT screen
	push ecx
	push eax
	push ebx

	call clear_screen
	
	xor ecx,ecx
	xor eax,eax

top_of_show:
	;; game_line contains the ' | | ' template
	;; game_moves contains 'X's, 'O's and ' 's for
	;; empty spots
	;; Fill game_line with 'X's, 'O's and ' 's,
	;; one spot at a time
	mov bl, [game_moves+eax]
	mov [game_line+ecx], bl
	inc al
	add cl, 2

	;; Loop for each spot
	cmp cl, 6
	jne top_of_show

	
	;; Now display game_line
	mov si, game_line
	call message

	xor cx, cx ; Zero ECX

	;; Get si ready in case we have to display '-+-+-',
	mov si, sep_line
	
	;; al contains the next index into game_moves
	;; sep_line gets displayed before the 4th and 7th
	;; game_moves pieces (i.e. it's 0 indexed)
	cmp al, 3
	je do_seperator
	cmp al, 6
 	je do_seperator

	;; No seperator this time
	jmp seperators_done

do_seperator:
	call message

seperators_done:
	;; Check if we're done
	cmp al, 9
	jne top_of_show

	;; Fin
	pop ebx
	pop eax
        pop ecx
	ret

get_pos:
	;; Get user's input
	;; Returns key press in dl
	push ax
	mov si, prompt
	call message
	
get_pos_get_key:
	call getkey
	
	;; Move key to dl
	mov dl, al
	
	;; Check for valid move
	;; game_moves contains 'X', 'O', or 1..9
	;; To detect a valid move use dl - '1' as 
	;; an index into game_moves
	;; This will result in an error if [game_moves+edx-'1']==dl
	;; Fortunately that's very unlikely
	cmp dl, byte [game_moves+edx-'1']
	
	;; Would be nice to display a message, but there's no space
	jne get_pos_get_key
	
	pop ax
	ret


check_for_win:
	;; Check for a win...
	;; Winning bit masks
	;; 111000000 - 0x01C0
	;; 000111000 - 0x0038
	;; 000000111 - 0x0007
	;; 100100100 - 0x0124
	;; 010010010 - 0x0092
	;; 001001001 - 0x0049
	;; 100010001 - 0x0111
	;; 001010100 - 0x0054
	
	push eax
	push ecx
	push ebx

	;; Check 'X' first
	mov ebx, 'X'

	;; game_moves contains 'X', 'O' and ' '
	;; Create a bitmap from it
	;; The first time through, each 'X' is converted to a 1
	;; The second time through, each 'O' is converted to a 1
	;; Everything else is 0
begin_bitmap:	
	xor edx, edx
	mov ecx, 8		; Test indexes 0-8
moves_to_num:
	shl edx, 1
	;; Compare to 'X' or '0'
	cmp bl, byte [game_moves+ecx]

	;; If it's not 'X' or 'O' don't add a bit
  	jne cont_mtn
	;; Add a bit
  	inc dx
	
cont_mtn
	;; Loop
	dec cl
	jnl moves_to_num

	;; At this point dx has a 9 bit bitmap indicating which spots
	;; were equal to bl
cont_wc:	
	mov ecx, 8
	;; Now compare the bitmap to the known winners
compare_to_winners:
	mov ax, word [matrix_of_win+2*ecx]
	mov di, dx

	;; Bitwise and to get rid of unnecessary bits
	and di, ax
	
	;; Then compare
	cmp ax, di
	;; Jump out early if there's a win
	je got_winner

	;; Loop
	dec cl
	jnl compare_to_winners

	;; Now do 'O'
	cmp bx, 'O'
	mov bx, 'O'
 	jne begin_bitmap
	
no_winner:
	;; Return 0 in dx if there's no winner
	xor dx, dx
	jmp win_check_cleanup	

got_winner:
	;; If there's a winner, return it
	mov dl, bl
win_check_cleanup:
	pop ebx
	pop ecx
	pop eax
	ret

beginning:
	call start_game

	;; Exit message
	mov si, exit_msg
	call message

	;; Check if they want to play again
exit_check:
        call getkey
	or al, 0x20 		; Convert to lower case
	
	cmp al, 'y'
	je beginning

	cmp al, 'n'
	jne exit_check
	
	;; Clear screen, display reboot message, and reboot
	call clear_screen
	
	mov si, rebooting
	call message

	call reboot
	
      	times 510-($-$$) db 0	; Fill out the bootsector
        dw 0xAA55
