 org 0x7c00


; --- setup ---

start:

    ; hide that annoying blinking cursor

    mov ah, 0x01

    mov ch, 0x26

    mov cl, 0x07

    int 0x10


    xor ax, ax

    mov ds, ax                  ; abs addresses

    mov ax, 0xb800

    mov es, ax                  ; vram segment


    xor di, di

    mov cx, 2000                ; 80x25 screen

    mov ax, 0x0720              ; blank space

    rep stosw                   ; clear screen


    ; draw a wall of dashes on the top row

    xor di, di                  ; start at top left

    mov cx, 80                  ; 80 chars wide

    mov ax, 0x072d              ; 0x07 = grey, 0x2d = '-'

    rep stosw


    ; write "snakeboot2" at the top left

    mov si, title_str

    xor di, di                  ; offset 0 for top left

    mov ah, 0x0e                ; yellow text color

draw_title:

    lodsb                       ; load next char into al

    test al, al                 ; is it 0? (end of string)

    jz init_snake               ; if yes, move on

    stosw                       ; write char+color to screen and move di forward

    jmp draw_title              ; loop


init_snake:

    mov word [snake_head], 2000 ; head ring buffer ptr (0x7e00+)

    mov word [snake_tail], 2000 ; tail ptr

   

    ; init first snake segment in buffer (x=40, y=12 is offset 2000)

    mov word [0x7e00 + 2000], 2000


    call spawn_food

    call draw_score             ; show initial 000 score on top of the wall


; --- main loop ---

game_loop:

    mov ah, 0x86                

    mov cx, 1                  

    mov dx, 0x86a0              ; wait approx 100ms

    int 0x15


    mov ah, 0x01                ; check for keys

    int 0x16

    jz update_snake             ; no key, keep moving


    mov ah, 0x00                ; read the key

    int 0x16

   

    ; update direction (low byte = x dir, high byte = y dir)

    cmp ah, 0x48                

    je move_up

    cmp ah, 0x50                

    je move_down

    cmp ah, 0x4b                

    je move_left

    cmp ah, 0x4d                

    je move_right

    jmp update_snake


move_up:    mov word [dir], 0xff00  ; x=0, y=-1

            jmp update_snake

move_down:  mov word [dir], 0x0100  ; x=0, y=1

            jmp update_snake

move_left:  mov word [dir], 0x00ff  ; x=-1, y=0

            jmp update_snake

move_right: mov word [dir], 0x0001  ; x=1, y=0


; --- move the snake ---

update_snake:

    mov ax, [dir]

    add [head_x], al            ; update x

    add [head_y], ah            ; update y


    ; --- wrapping logic ---

    ; wrap x (left/right)

    cmp byte [head_x], 80

    jne x_not_over

    mov byte [head_x], 0        ; wrapped right to left

    x_not_over:

    cmp byte [head_x], 0xff

    jne x_not_under

    mov byte [head_x], 79       ; wrapped left to right

    x_not_under:


    ; wrap y (top/bottom - avoids row 0 where the wall is)

    cmp byte [head_y], 25

    jne y_not_over

    mov byte [head_y], 1        ; wrapped bottom to top (row 1)

    y_not_over:

    cmp byte [head_y], 0        ; hit the wall at row 0?

    jne y_not_under

    mov byte [head_y], 24       ; wrapped top to bottom

    y_not_under:


    ; calc new offset (di = (y * 80 + x) * 2)

    mov al, [head_y]

    mov bl, 80

    mul bl                      ; ax = y * 80

    mov bl, [head_x]

    xor bh, bh

    add ax, bx                  ; ax = (y * 80) + x

    shl ax, 1                   ; x2 for bytes

    mov di, ax


    mov ax, [es:di]             ; look at where we are going

    cmp al, '*'                 ; did we bite our tail?

    je game_over


    ; save new head to buffer

    mov bx, [snake_head]

    add bx, 2

    and bx, 0x0fff              ; wrap buffer so we dont overwrite code

    mov [snake_head], bx

    mov word [0x7e00 + bx], di  


    mov word [es:di], 0x0a2a    ; draw the new head (*)


    cmp di, [food_pos]          ; ate an apple?

    je eat_food                 ; skip erasing tail if we ate


    mov bx, [snake_tail]

    mov di, word [0x7e00 + bx]  ; find the tail end

    mov word [es:di], 0x0720    ; erase it

   

    add bx, 2

    and bx, 0x0fff              ; wrap tail ptr

    mov [snake_tail], bx

    jmp game_loop


eat_food:

    inc byte [score]

    call draw_score             ; update the score on screen

    call spawn_food

    jmp game_loop


; --- functions ---

draw_score:

    mov al, [score]

    xor ah, ah

    mov bl, 10

    mov cx, 3                   ; loop 3 times (for 3 digits)

    mov di, 158                 ; start at very top right of screen

.next_digit:

    div bl                      ; al = quotient, ah = remainder

    add ah, '0'                 ; turn remainder into ascii number

    mov dh, 0x0e                ; yellow text color

    mov dl, al                  ; save quotient

    mov al, ah                  

    mov ah, dh                  ; ax = char + color

    mov word [es:di], ax        ; draw it over the dashes

    mov al, dl                  ; get quotient back for next loop

    xor ah, ah

    sub di, 2                   ; move left one character on screen

    loop .next_digit            ; keep going until we've drawn 3 digits

    ret


spawn_food:

    mov ah, 0x00

    int 0x1a                    ; read clock for random numbers

   

    mov ax, dx

    xor dx, dx

    mov cx, 1920                ; modulo playable area (24 rows * 80 cols)

    div cx                      

   

    add dx, 80                  ; shift down 1 row so it never hits row 0

    shl dx, 1                   ; x2 for byte offset

    mov di, dx

   

    mov ax, [es:di]            

    cmp al, '*'                 ; make sure it didnt spawn on snake

    je spawn_food              


    mov [food_pos], di

    mov word [es:di], 0x0c40    ; draw food (@)

    ret


game_over:

    jmp $                       ; freeze forever


; --- vars & boot magic ---

title_str:  db 'snakeboot2', 0

score:      db 0

dir:        dw 0x00ff           ; starts moving left

head_x:     db 40               ; starting x

head_y:     db 12               ; starting y

snake_head: dw 2000

snake_tail: dw 2000

food_pos:   dw 0


times 510 - ($ - $$) db 0       ; pad boot sector

dw 0xaa55                       ; magic numbers 