; Spaceship Shooter for Windows Console
; Developed by Boo Khan Ming (April 2024)
;
; X: @fliermate
;
; Public Domain
;
format PE console
entry start

include 'win32a.inc'

MOST_LEFT equ 1
MOST_RIGHT equ 70
START_COL equ 35
;START_ROW equ 20

section '.data' data readable writeable

_newpos           dd START_COL
_oldpos           dd START_COL
_spaceship        db '^'
_bullet           db '|'
_blank            db ' '
_ansi_cls         db 27,'[H',27,'[J'
_ansi_cls_len     = $ - _ansi_cls
_ansi_reset       db   27,'[0m'   ; ANSI escape code (reset color)
_ansi_reset_len   = $ - _ansi_reset
_instruction      db 'Press Left and Right arrow to move; ESC to quit'
_instruction_len  = $ - _instruction
_stdout           dd ?
_mode             dd ?
_number           rb 10
_ansi_goto_new    rb 9   ; 27,'[00;00H'
_ansi_goto_old    rb 9
_count            dd ?

;CSI n ; m H
;Moves the cursor to row n, column m. The values are 1-based, and default to 1 (top left corner) if omitted.

;VK_ESCAPE      0x1B    ESC key
;VK_LEFT        0x25    LEFT ARROW key
;VK_UP   0x26    UP ARROW key
;VK_RIGHT        0x27    RIGHT ARROW key
;VK_DOWN 0x28    DOWN ARROW key

section '.code' code readable executable

start:

         push    -11
         call    [GetStdHandle]
         mov     dword [_stdout], eax

         push    _mode
         push    [_stdout]
         call    [GetConsoleMode]
         or      [_mode], 4      ;ENABLE_VIRTUAL_TERMINAL_PROCESSING

         push    [_mode]
         push    [_stdout]
         call    [SetConsoleMode]

         push    0
         push    0
         push    _ansi_cls_len
         push    _ansi_cls
         push    dword [_stdout]
         call    [WriteConsole]  ;Clear entire screen

         push    0
         push    0
         push    _instruction_len
         push    _instruction
         push    dword [_stdout]
         call    [WriteConsole]  ;Print keyboard instruction

         mov     eax, dword [_newpos]
         call    itoa
         mov     [_count], ebx
         call    DrawSpaceship

.loop:
         push    100
         call    [Sleep]
         push    0x1B           ;ESC key
         call    [GetKeyState]
         bt      eax, 15        ; If the high-order bit is 1, the key is down; otherwise, it is up
         jc      .done
         push    0x25           ;LEFT ARROW key
         call    [GetKeyState]
         bt      eax, 15
         jc      .left
         push    0x27           ;RIGHT ARROW key
         call    [GetKeyState]
         bt      eax, 15
         jc      .right
         jmp     .loop

.left:
         call    SaveValue
         sub     [_newpos], 1
         cmp     [_newpos], MOST_LEFT
         jl      .keepleft
         mov     eax, dword [_newpos]
         call    itoa
         mov     [_count], ebx
         ;push    0
         ;push    0
         ;push    ebx
         ;push    _number
         ;push    dword [_stdout]
         ;call    [WriteConsole]

         call    DrawSpaceship
         jmp     .loop

.keepleft:
         mov     [_newpos], MOST_LEFT
         jmp     .loop

.right:
         call    SaveValue
         add     [_newpos], 1
         cmp     [_newpos], MOST_RIGHT
         jg      .keepright
         mov     eax, dword [_newpos]
         call    itoa
         mov     [_count], ebx
         ;push    0
         ;push    0
         ;push    ebx
         ;push    _number
         ;push    dword [_stdout]
         ;call    [WriteConsole]

         call    DrawSpaceship
         jmp     .loop

.keepright:
         mov     [_newpos], MOST_RIGHT
         jmp     .loop

.done:
         push    0
         call    [ExitProcess]


itoa:                      ;-) Nice code snippet by Pauli Lindgren  (https://pkl.paldex.fi/)
; INPUT:  EAX
; OUTPUT: _number
; LEN OF OUTPUT: EBX
         mov     ecx, 10
         xor     ebx, ebx

.divide:
         xor     edx, edx
         div     ecx
         push    edx
         inc     ebx
         test    eax, eax
         jnz     .divide

         mov     ecx, ebx
         lea     esi, [_number]
.next:
         pop     eax
         add     al, '0'
         mov     [esi], al
         inc     esi
         loop    .next
         ret

SaveValue:   ;Save old spaceship position
         mov     eax, [_newpos]
         mov     [_oldpos], eax
         mov     ecx, 8
         lea     edi, [_ansi_goto_old]
         lea     esi, [_ansi_goto_new]
         rep     movsb
         ret

DrawSpaceship:
         mov     ecx, [_count]
         lea     edi, [_ansi_goto_new]
         mov     byte [edi], 27
         mov     byte [edi+1], '['
         mov     byte [edi+2], '2'  ;START_ROW = 20
         mov     byte [edi+3], '0'
         mov     byte [edi+4], ';'
         add     edi,5
         lea     esi, [_number]
         rep     movsb
         mov     byte [edi], 'H'
         mov     byte [edi+1], 0

         push    0
         push    0
         push    8
         push    _ansi_goto_new          ;New position
         push    dword [_stdout]
         call    [WriteConsole]

         push    0
         push    0
         push    1
         push    _spaceship
         push    dword [_stdout]
         call    [WriteConsole]          ;Draw new spaceship

         push    0
         push    0
         push    8
         push    _ansi_goto_old          ;Old position
         push    dword [_stdout]
         call    [WriteConsole]

         push    0
         push    0
         push    1
         push    _blank
         push    dword [_stdout]
         call    [WriteConsole]          ;Delete old spaceship

         ret

section '.idata' import readable writeable

  library kernel32, 'KERNEL32.DLL', \
          user32, 'USER32.DLL'

  import kernel32,\
         GetStdHandle, 'GetStdHandle', \
         WriteConsole, 'WriteConsoleA', \
         ExitProcess,'ExitProcess', \
         GetConsoleMode, 'GetConsoleMode',\
         SetConsoleMode, 'SetConsoleMode',\
         Sleep, 'Sleep'

   import user32,\
         GetKeyState, 'GetKeyState'
