COMMENT ^
     One evening my 15 year-old son approached me and asked me about writing a
Tic-Tack-Toe game in assembly.  I gave him pointers along the way and gave
assistance in finding errors which caused his code to malfunction.  The result
is the code which follows this message.
     OK optimizers, here is a challenge for you.  See just how small you can
reduce this program to.  However, reducing calls to procedures is not
considered a valid means of reducing code or increasing program speed!  The
calls provide a structured environment and increasing calls to procedures is
considered valid, if justified.  Let's face it, no one can hit keys as fast as
assembly can process the instuctions, almost, no matter how sloppy the code is
written!
     Keyboard must be locked in caps mode, as only a capital X and a capital O
are recognized by the game(checking for lowercase could be added).
        The arrow keys are used to position cursor to the point of where
an X or O is entered (or appropiate characters can be used for program
movement).  Entering a B from the keyboard will erase any character being
displayed at the cursor.
     Ctrl-c and ctrl-break are suspended because of the interupt used to read
the keyboard.
     The spacebar can be used to halt the game at any point, and if a stalemate
occurs it must be used to halt the game.  A routine could be added to halt the
game and declare a stalemate.
     Game will recognize a horizontal, verticle or diagonal win, and will
declare the character (X or O) which has won.  The program also keeps
track of the last character entered(if it was an X or O).
     Tasm 2.1 compiles this code to a .com file of 885 bytes in length, as now
written.  Have fun!

END COMMENT ^

;TIC-TACK-TOE GAME
;PROGRAMMER:      David Z. Ostrom (with assistance from father, Brett
;L. Ostrom)
;DATE PROGRAMMED: 6/10/96
;REVISION HISTORY: NONE
;COPYRIGHT: NONE (FREEWARE!)
;WRITTEN IN TASM IDEAL MODE.
;TO ASSEMBLE AND LINK:
;     TASM TIC
;     TLINK/T TIC
;THIS PRODUCES THE FILE "TIC.COM"
;
IDEAL
SEGMENT CODE PUBLIC 'CODE'
ORG     100H
ASSUME CS:CODE, ES:CODE, DS:CODE, SS:CODE
GO:
       CALL    DRIVER
       MOV     AH, 4CH
       INT     21H
EGO_MSG     DB     'TIC-TAC-TOE by David Z.Ostrom', '+'
LAST_MSG    DB     'LAST MOVE MAKE BY: '
CHAR        DB     00H
            DB     '+'
WIN_MSG     DB     'WINNER IS: '
            DB     '+'
BOX_START   DW     1174
ROW         DB     10
COLUMN      DB     36
WIN_FLAG    DB     0
GEN_CTR     DB     0
TEMP1           DW      0000h
DATA_ADDR       DW      0000h
CTR             DB      00H
BOX_TOP         DB      0DAH, 0C4H, 0C2H, 0C4H, 0C2H, 0C4H, 0BFH
CELL_SIDE       DB      0B3H, 020H, 0B3H, 020H, 0B3H, 020H, 0B3H
DIV_LINE        DB      0C3H, 0C4H, 0C5H, 0C4H, 0C5H, 0C4H, 0B4H
BOX_BOTTOM      DB      0C0H, 0C4H, 0C1H, 0C4H, 0C1H, 0C4H, 0D9H
SCREEN_ADDR     DW      0000H

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     DRIVER      NEAR
         MOV     AX, 0B800H
         MOV     ES, AX
         MOV     [ROW], 23
         MOV     [COLUMN], 79
         CALL    CLS
         CALL    EGO_THINGY
         CALL    DRAW_BOX
         MOV     [ROW], 10
         MOV     [COLUMN], 36
         CALL    POSITION_CURSOR
         CALL    PROCESS
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     EGO_THINGY     NEAR
         MOV     DI, 102
         MOV     SI, OFFSET EGO_MSG
NEXT_EGO_CHAR:
         CMP     [BYTE CS:SI], '+'
         JE      DONE_EGO
         MOV     AL, [CS:SI]
         MOV     [ES:DI], AL
         INC     SI
         INC     DI
         INC     DI
         JMP     NEXT_EGO_CHAR
DONE_EGO:
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     PROCESS     NEAR
TOP:
         CALL    CHECK_HORZ_WIN
         CMP     [WIN_FLAG], 1
         JE      DONE
         CALL    CHECK_VERT_WIN
         CMP     [WIN_FLAG], 1
         CALL    CHECK_DIAG_WIN
         CMP     [WIN_FLAG], 1
         JE      DONE
         MOV     AH, 06H
         MOV     DL, 0FFH
         INT     21H
         CMP     AL, 20H
         JE      DONE
         CMP     AL, 'M'
         JE      _M
         CMP     AL, 'K'
         JE      _K
         CMP     AL, 'H'
         JE      _H
         CMP     AL, 'P'
         JE      _P
         CMP     AL, 'X'
         JE      PRINT_X_
         CMP     AL, 'O'
         JE      PRINT_O_
         CMP     AL, 'B'
         JE      PRINT_BLANK_
         CMP     AL, 'B'
         JMP     TOP
DONE_CHAR:
         CALL    READ_CHAR
         MOV     SI, OFFSET LAST_MSG
         MOV     DI, 0
NEXT_CHAR:
         CMP     [BYTE CS:SI], '+'
         JE      TOP
         MOV     AL, [CS:SI]
         MOV     [ES:DI], AL
         INC     SI
         INC     DI
         INC     DI
         JMP     NEXT_CHAR
         JMP     TOP
DONE:
         RET
_M:
         CALL     M_
         JMP      TOP
_K:
         CALL     K_
         JMP      TOP
_H:
         CALL     H_
         JMP      TOP
_P:
         CALL     P_
         JMP      TOP
PRINT_X_:
         CALL     PRINT_X
         JMP      DONE_CHAR
PRINT_O_:
         CALL     PRINT_O
         JMP      DONE_CHAR
PRINT_BLANK_:
         CALL     PRINT_BLANK
         JMP      DONE_CHAR
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     CHECK_HORZ_WIN     NEAR
         MOV     [GEN_CTR], 0
         MOV     SI, 1672
CHECK_NEXT_ROW:
         CMP     [BYTE ES:SI], 20H
         JE      HORZ_CHECK_DONE
         MOV     AL, [ES:SI]
         CMP     AL, [ES:SI+4]
         JNE     HORZ_CHECK_DONE
         MOV     AL, [ES:SI+4]
         CMP     AL, [ES:SI+8]
         JNE     HORZ_CHECK_DONE
         CALL    WINNER
HORZ_CHECK_DONE:
         INC     [GEN_CTR]
         CMP     [GEN_CTR], 3
         JE      FINAL_HORZ_CHECK_DONE
         ADD     SI, 320              ;INCREMENT ROW
         JMP     CHECK_NEXT_ROW
FINAL_HORZ_CHECK_DONE:
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     CHECK_VERT_WIN     NEAR
         MOV     [GEN_CTR], 0
         MOV     SI, 1672
CHECK_NEXT_COLUMN:
         CMP     [BYTE ES:SI], 20H
         JE      VERT_CHECK_DONE
         MOV     AL, [ES:SI]
         CMP     AL, [ES:SI+320]
         JNE     VERT_CHECK_DONE
         MOV     AL, [ES:SI+320]
         CMP     AL, [ES:SI+640]
         JNE     VERT_CHECK_DONE
         CALL    WINNER
VERT_CHECK_DONE:
         INC     [GEN_CTR]
         CMP     [GEN_CTR], 3
         JE      FINAL_VERT_CHECK_DONE
         ADD     SI, 4              ;INCREMENT COLUMN
         JMP     CHECK_NEXT_COLUMN
FINAL_VERT_CHECK_DONE:
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     CHECK_DIAG_WIN     NEAR
         MOV     SI, 1672
CHECK_NEXT_RDIAG_ROW:
         CMP     [BYTE ES:SI], 20H
         JE      RDIAG_CHECK_DONE
         MOV     AL, [ES:SI]
         CMP     AL, [ES:SI+324]
         JNE     RDIAG_CHECK_DONE
         MOV     AL, [ES:SI+324]
         CMP     AL, [ES:SI+648]
         JNE     RDIAG_CHECK_DONE
         CALL    WINNER
RDIAG_CHECK_DONE:
         MOV     SI, 1680
CHECK_NEXT_LDIAG_ROW:
         CMP     [BYTE ES:SI], 20H
         JE      LDIAG_CHECK_DONE
         MOV     AL, [ES:SI]
         CMP     AL, [ES:SI+316]
         JNE     LDIAG_CHECK_DONE
         MOV     AL, [ES:SI+316]
         CMP     AL, [ES:SI+632]
         JNE     LDIAG_CHECK_DONE
         CALL    WINNER
LDIAG_CHECK_DONE:
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     WINNER     NEAR
         MOV     [WIN_FLAG], 1
         MOV     SI, OFFSET WIN_MSG
         MOV     DI, 3680
WRITE_NEXT_CHAR:
         CMP    [BYTE SI], '+'
         JE     WINNER_DONE
         MOV    AL, [SI]
         MOV    [ES:DI], AL
         INC    SI
         INC    DI
         INC    DI
         JMP    WRITE_NEXT_CHAR
WINNER_DONE:
         MOV    SI, OFFSET CHAR
         MOV    AL, [SI]
         MOV    [ES:DI], AL
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     M_     NEAR
         CMP    [COLUMN], 40
         JB     INC_COLUMN
         MOV    [COLUMN], 36
BACK:
         CALL   POSITION_CURSOR
         RET
INC_COLUMN:
         CALL  _INC_COLUMN
         JMP    BACK
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     K_     NEAR
         CMP    [COLUMN], 36
         JA     DEC_COLUMN
         MOV    [COLUMN], 40
BACK1:
         CALL   POSITION_CURSOR
         RET
DEC_COLUMN:
         CALL   _DEC_COLUMN
         JMP    BACK1
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     H_     NEAR
         CMP     [ROW], 10
         JA      DEC_ROW
         MOV     [ROW], 14
BACK2:
         CALL    POSITION_CURSOR
         RET
DEC_ROW:
         CALL    _DEC_ROW
         JMP     BACK2
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     P_     NEAR
         CMP    [ROW], 14
         JB     INC_ROW
         MOV    [ROW], 10
BACK3:
         CALL   POSITION_CURSOR
         RET
INC_ROW:
         CALL   _INC_ROW
         JMP    BACK3
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     PRINT_X     NEAR
         MOV     [CHAR], 'X'
         CALL    PRINT_CHAR2
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     PRINT_O     NEAR
         MOV     [CHAR], 'O'
         CALL    PRINT_CHAR2
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     PRINT_BLANK     NEAR
         MOV     [CHAR], 20H
         CALL    PRINT_CHAR2
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     _INC_COLUMN     NEAR
         INC     [COLUMN]
         INC     [COLUMN]
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     _DEC_COLUMN     NEAR
         DEC     [COLUMN]
         DEC     [COLUMN]
         RET
ENDP

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     _INC_ROW    NEAR
         INC    [ROW]
         INC    [ROW]
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     _DEC_ROW    NEAR
         DEC    [ROW]
         DEC    [ROW]
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     POSITION_CURSOR     NEAR
         MOV     AH, 02H
         MOV     BH, 0
         MOV     DH, [ROW]
         MOV     DL, [COLUMN]
         INT     10H
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     CLS      NEAR
         MOV      AH, 06
         MOV      AL, 00H
         MOV      BH, 07H
         MOV      CH, 0
         MOV      CL, 0
         MOV      DH, [ROW]
         MOV      DL, [COLUMN]
         INT      10H
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     PRINT_CHAR     NEAR
         MOV     AH, 02H
         MOV     DL, [CHAR]
         INT     21H
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     PRINT_CHAR2    NEAR
         MOV     AH, 0AH
         MOV     AL, [CHAR]
         MOV     BH, 0
         MOV     CX, 1
         INT     10H
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC    READ_CHAR     NEAR
        MOV     AH, 08H       ;FUNCTION NO. TO READ CHAR/ARRTIB AT CURSOR
        MOV     BH, 00H
        INT     10H
        MOV     [CHAR], AL    ;AL=CHAR, AH=ATTRIBUTE
        RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     DRAW_BOX     NEAR
         MOV     AX, [BOX_START]
         MOV     [SCREEN_ADDR], AX
         MOV     [DATA_ADDR], OFFSET BOX_TOP
         CALL    OUT7TOSCREEN
         ADD     [SCREEN_ADDR], 160
         MOV     [DATA_ADDR], OFFSET CELL_SIDE
         CALL    OUT7TOSCREEN
         ADD     [SCREEN_ADDR], 160
         MOV     [DATA_ADDR], OFFSET DIV_LINE
         CALL    OUT7TOSCREEN
         ADD     [SCREEN_ADDR], 160
         MOV     [DATA_ADDR], OFFSET CELL_SIDE
         CALL    OUT7TOSCREEN
         ADD     [SCREEN_ADDR], 160
         MOV     [DATA_ADDR], OFFSET DIV_LINE
         CALL    OUT7TOSCREEN
         ADD     [SCREEN_ADDR], 160
         MOV     [DATA_ADDR], OFFSET CELL_SIDE
         CALL    OUT7TOSCREEN
         ADD     [SCREEN_ADDR], 160
         MOV     [DATA_ADDR], OFFSET BOX_BOTTOM
         CALL    OUT7TOSCREEN
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PROC     OUT7TOSCREEN     NEAR
         MOV     DI, [SCREEN_ADDR]   ;ADDRESS TO BEGIN SCREEN WRITE
         MOV     SI, [DATA_ADDR]     ;DATA TO BE SENT TO SCREEN
         MOV     [CTR], 0            ;CTR WILL COUNT CHARS, ADDRESS SCREEN
OUT_ANOTHER:
         MOV     AL, [CS:SI]      ;SINCE THIS IS .COM CS=DS=SS
         MOV     [ES:DI+CTR], AL  ;CTR PROVIDES INCREMENTING OF SCREEN ADDR
         INC     SI               ;TO NEXT DATA BYTE
         INC     DI               ;INC DI PAST SCREEN ATTRIBUTE BYTE
         INC     DI
         INC     [CTR]        ;CTR IS SERVING A DUAL PURPOSE OF COUNTING
         INC     [CTR]        ;LINES AND IN ADDRESSING THE SCREEN
         CMP     [CTR], 14    ;NO. OF CHARS PRINTED = CTR/2
         JB      OUT_ANOTHER  ;UNTIL WE HAVE PRINTED 7 CHARS
         RET
ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
       ENDS
       END     GO
