-
Notifications
You must be signed in to change notification settings - Fork 0
/
bootsnake.asm
292 lines (246 loc) · 5.63 KB
/
bootsnake.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
bits 16
org 0x7C00
%include "bios.inc"
bootsect_sz equ 512
boot_sign_sz equ 2
video_mem_segment equ 0xB800
screen_rows equ 25
screen_cols equ 80
kbd_int equ 0x09
ioport_kbd equ 0x60
ioport_pic equ 0x20
pic_eoi equ 0x20; end of interrupt command code
scancode_cursor equ 0xE0
scancode_cursor_up_pressed equ 0x48
scancode_cursor_down_pressed equ 0x50
scancode_cursor_left_pressed equ 0x4B
scancode_cursor_right_pressed equ 0x4D
; snake segment is 2-byte long: byte 1 - segment row, byte 2 - segment column
snake_segments equ 0x0500; 0x0500-0x7BFF guaranteed to be free
snake_head_init_row equ screen_rows / 2
snake_head_init_col equ screen_cols / 2
snake_direction_up equ scancode_cursor_up_pressed
snake_direction_down equ scancode_cursor_down_pressed
snake_direction_left equ scancode_cursor_left_pressed
snake_direction_right equ scancode_cursor_right_pressed
main:
; hide cursor
mov ah, bios_video_cursor_shape_fn
mov ch, 0x20; bits 6:5 == 01 - cursor invisible
int bios_video_int
; setup video memory
mov ax, video_mem_segment
mov es, ax
; setup keyboard interrupt service routine
cli; disable interrupts
mov word [kbd_int * 4], kbd_isr; write ISR offset to IVT
mov word [kbd_int * 4 + 2], cs; write segment containing ISR
sti; enable interrupts
call snake_init
push '#'
call snake_print
add sp, 2
.loop:
call sleep_nop
call snake_move
jmp .loop
jmp $
kbd_isr:
push ax
in al, ioport_kbd; read from keyboard io port
cmp al, scancode_cursor_up_pressed
je .set_direction_up
cmp al, scancode_cursor_down_pressed
je .set_direction_down
cmp al, scancode_cursor_left_pressed
je .set_direction_left
cmp al, scancode_cursor_right_pressed
je .set_direction_right
jmp .out
.set_direction_up:
cmp byte [snake_current_direction], snake_direction_down
je .out
jmp .set_direction
.set_direction_down:
cmp byte [snake_current_direction], snake_direction_up
je .out
jmp .set_direction
.set_direction_left:
cmp byte [snake_current_direction], snake_direction_right
je .out
jmp .set_direction
.set_direction_right:
cmp byte [snake_current_direction], snake_direction_left
je .out
jmp .set_direction
.set_direction:
mov [snake_current_direction], al
.out:
mov al, pic_eoi
out ioport_pic, al; acknowledge interrupt to PIC
pop ax
iret
snake_init:
mov dl, snake_head_init_col; column number
mov bx, 0; offset from "snake_segments" label
mov ax, [snake_segment_count]
shl ax, 1; bx is incremented by 2 each time in loop, so multiply by two
.loop:
mov byte [snake_segments + bx + 0], snake_head_init_row
mov byte [snake_segments + bx + 1], dl
inc dx
add bx, 2
cmp bx, ax
jne .loop
ret
print_char: ; argument push order: row, col, char
push bp
mov bp, sp
push ax
push bx
; desired char position = (row * screen_cols + col)
mov ax, screen_cols
mul byte [bp + 8]
add ax, [bp + 6]
; Multiplication by 2, needed because the video memory consists of words
; where the first byte is character attribute and the second is the char
; itself. We're not interested in any attributes, just place the character
; in desired place.
shl ax, 1
mov bx, ax
mov al, [bp + 4]
mov [es:bx], al
pop bx
pop ax
pop bp
ret
snake_print:
push bp
mov bp, sp
pusha
mov bx, 0
.loop:
mov al, [snake_segments + bx + 0]
mov dl, [snake_segments + bx + 1]
mov cx, [snake_segment_count]
shl cx, 1
push ax
push dx
push word [bp + 4]
call print_char
add sp, 6
add bx, 2
cmp bx, cx
jne .loop
popa
pop bp
ret
snake_move:
push ' '
call snake_print
add sp, 2
call snake_tail_update
call snake_head_update
push '#'
call snake_print
add sp, 2
ret
snake_tail_update:
pusha
mov ax, [snake_segment_count]
dec ax
shl ax, 1
mov bx, snake_segments
add bx, ax
.loop:
mov ax, [bx - 2]
mov [bx], ax
sub bx, 2
cmp bx, snake_segments
jne .loop
popa
ret
snake_head_update:
cmp byte [snake_current_direction], snake_direction_up
je .move_up
cmp byte [snake_current_direction], snake_direction_down
je .move_down
cmp byte [snake_current_direction], snake_direction_left
je .move_left
cmp byte [snake_current_direction], snake_direction_right
je .move_right
ret
.move_up:
dec byte [snake_segments + 0]
cmp byte [snake_segments + 0], 0
je game_over
call check_self_hit
ret
.move_down:
inc byte [snake_segments + 0]
cmp byte [snake_segments + 0], screen_rows - 1
je game_over
call check_self_hit
ret
.move_left:
dec byte [snake_segments + 1]
cmp byte [snake_segments + 1], 0
je game_over
call check_self_hit
ret
.move_right:
inc byte [snake_segments + 1]
cmp byte [snake_segments + 1], screen_cols - 1
je game_over
call check_self_hit
ret
check_self_hit:
pusha
mov ah, [snake_segments + 0]; head row
mov al, [snake_segments + 1]; head col
mov bx, 2; current snake segment offset
mov dx, [snake_segment_count]
shl dx, 1; multiply by two because we add 2 to bx each time
.loop:
cmp ah, [snake_segments + bx + 0]
jne .not_hit
cmp al, [snake_segments + bx + 1]
jne .not_hit
jmp game_over
.not_hit:
add bx, 2
cmp bx, dx
jne .loop
.out:
popa
ret
; This is a temporary replacement for a proper sleep/delay routine. I have to
; use it because I don't know yet how to interrupt BIOS sleep routine (int 0x15,
; ah = 0x86) when a key is pressed.
sleep_nop:
push eax
xor eax, eax
.loop:
nop
inc eax
cmp eax, 0xFFFF * 128
je .out
jmp .loop
.out:
pop eax
ret
game_over:
push '#'
call snake_print
mov al, [snake_segments + 0]
mov bl, [snake_segments + 1]
push ax
push bx
push 'X'
call print_char
jmp $
snake_segment_count: dw 4
snake_current_direction: db snake_direction_left
; Make executable be 512 bytes exactly. Essential for making it bootable.
times (bootsect_sz - boot_sign_sz) - ($ - $$) db 0
dw 0xAA55; boot signature