/
num_386.asm
578 lines (465 loc) · 9.22 KB
/
num_386.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
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
.model small
.386 ; Specify this after model to force 16 bit segments for simple DOS mode
.stack
.data
; String messages
Welcome db "Welcome to NumGuess 386 Assembly version!",0dh,0ah,0dh,0ah,"Enter your name: $"
LimitPrompt1 db 0dh,0ah,0dh,0ah,"Welcome $"
LimitPrompt2 db ", enter upper limit: $"
StartMessage1 db 0dh,0ah,0dh,0ah,"Guess my number between 1 and $"
StartMessage2 db "!",0dh,0ah,0dh,0ah,"$"
GuessPrompt db "Guess: $"
OutOfRange db 0dh,0ah,"Out of range.",0dh,0ah,"$"
Wrong db 0dh,0ah,"That's just plain wrong.",0dh,0ah,"$"
TooHigh db 0dh,0ah,"Too high!",0dh,0ah,"$"
TooLow db 0dh,0ah,"Too low!",0dh,0ah,"$"
WellDone1 db 0dh,0ah,0dh,0ah,"Well done $"
WellDone2 db ", you guessed my number from $"
TryWord db " try!",0dh,0ah,"$"
TriesWord db " tries!",0dh,0ah,"$"
PlayAgain db 0dh,0ah,"Play again [y/N]? $"
OkayBye db 0dh,0ah,0dh,0ah,"Okay, bye.$"
; End of game custom messages
Custom1 db "You're one lucky bastard!$"
CustomLessMax db "You are the master of this game!$"
CustomMax db "You are a machine!$"
CustomWithin10p db "Very good result!$"
CustomOver10p db "Try harder, you can do better!$"
CustomOverLimit db "I find your lack of skill disturbing!$"
; Input buffer for player name
PlayerMax db 255
PlayerLength db 0
PlayerName db 256 dup (0)
DefaultName db "Player$"
; Temporary I/O buffer for subroutines
IOBufferMax db 255
IOBufferLength db 0
IOBuffer db 256 dup (0)
; Random number generator state
RandomSeed dd 65535
; Game variables
Limit dd 0
MaxTries dw 0
MaxTriesPlus10p dw 0
Tries dw 0
Number dd 0
.code
assume ds:@data
;;
;; Main program
;;
main proc
mov ax, @data
mov ds, ax
; initialise random seed
call randomize
; print welcome message and name prompt
push offset Welcome
call print
; get player name
push offset PlayerName
call input
; check if player name is empty
cmp PlayerLength, 0
jne got_name
; copy default name to player name buffer
mov di, offset PlayerName
mov si, offset DefaultName
copy_loop:
mov al, [si]
mov [di], al
inc si
inc di
cmp al, '$'
jne copy_loop
got_name:
; print limit prompt
push offset LimitPrompt1
call print
push offset PlayerName
call print
push offset LimitPrompt2
call print
; get input and check if valid
call getNumber
cmp cx, 0
jne limit_fail
cmp eax, 10
jae limit_ok
limit_fail:
; set default limit
mov eax, 10
limit_ok:
; store limit
mov Limit, eax
; calculate maximum tries needed
mov bx, 0
process_limit:
inc bx
shr eax, 1
jnz process_limit
; save max tries
mov MaxTries, bx
; calculate max tries plus 10%
mov ax, MaxTries
mov dx, 0
mov bx, 10
div bx
add ax, MaxTries
mov MaxTriesPlus10p, ax
start_game:
; initialise game variables
mov Tries, 0
mov eax, Limit
call randomLimit
mov Number, eax
; print start message
push offset StartMessage1
call print
mov eax, Limit
call printNumber
push offset StartMessage2
call print
guess_loop:
push offset GuessPrompt
call print
call getNumber
; check if valid input
cmp cx, 0
jne invalid_input
; check if out of range
cmp eax, 0
je out_of_range
cmp eax, Limit
jbe check_success
out_of_range:
push offset OutOfRange
call print
jmp guess_loop
invalid_input:
push offset Wrong
call print
jmp guess_loop
check_success:
; counts as a try
inc Tries
cmp eax, Number
ja too_high
jb too_low
jmp success
too_high:
push offset TooHigh
call print
jmp guess_loop
too_low:
push offset TooLow
call print
jmp guess_loop
success:
; number guessed, print well done message
push offset WellDone1
call print
push offset PlayerName
call print
push offset WellDone2
call print
mov eax, 0
mov ax, Tries
call printNumber
cmp ax, 1
jne plural
push offset TryWord
call print
jmp custom_message
plural:
push offset TriesWord
call print
custom_message:
; eax has number of tries
cmp ax, 1
je custom_1
cmp ax, MaxTries
jb custom_less_max
je custom_max
cmp ax, MaxTriesPlus10p
jbe custom_within_10p
; compare tries to limit (tries is only 16 bits)
cmp eax, Limit
ja custom_over_limit
jmp custom_over_10p
custom_1:
push offset Custom1
call print
jmp play_again
custom_less_max:
push offset CustomLessMax
call print
jmp play_again
custom_max:
push offset CustomMax
call print
jmp play_again
custom_within_10p:
push offset CustomWithin10p
call print
jmp play_again
custom_over_10p:
push offset CustomOver10p
call print
jmp play_again
custom_over_limit:
push offset CustomOverLimit
call print
play_again:
push offset PlayAgain
call print
push offset IOBuffer
call input
cmp IOBufferLength, 1
jne exit_program
cmp IOBuffer, 'y'
je restart_game
cmp IOBuffer, 'Y'
je restart_game
jmp exit_program
restart_game:
; inverted logic needed for long jump
jmp start_game
exit_program:
push offset OkayBye
call print
; exit
mov ax,4c00h
int 21h
main endp
;;
;; Subroutines
;;
; Prints a $ terminated string
; takes 2 byte buffer offset on the stack
print proc
push bp
mov bp, sp
push ax
push dx
mov dx, [bp+4]
mov ah, 9
int 21h
pop dx
pop ax
pop bp
ret 2
print endp
; Gets string input into buffer
; takes 2 byte buffer offset on the stack
; the two bytes before the buffer is used for max and actual length
input proc
push bp
mov bp, sp
push ax
push dx
push di
; set pointer back by 2 bytes to max length and read string
mov dx, [bp+4]
sub dx, 2
mov ah, 0ah
int 21h
; add $ to the end of the string
mov di, [bp+4]
dec di
mov al, [di]
mov ah, 0
inc di
add di, ax
mov al, '$'
mov [di], al
pop di
pop dx
pop ax
pop bp
ret 2
input endp
; Gets user input and returns:
; 31 bit positive integer in eax
; cx is set to 1 on parse error
getNumber proc
push ebx
push edx
push di
; set pointer to max length and read string
mov dx, offset IOBufferMax
mov ah, 0ah
int 21h
; clear eax
mov eax, 0
; check if input is empty
mov ecx, 0
mov cl, IOBufferLength
cmp ecx, 0
je parse_error
; loop digits
mov di, offset IOBuffer
parse_loop:
; multiply eax by 10
mov ebx, 10
mul ebx
jc parse_error
mov ebx, 0
mov bl, [di]
; check for invalid character
cmp bl, '0'
jb parse_error
cmp bl, '9'
ja parse_error
; add to eax
sub bl, '0'
add eax, ebx
jc parse_error
inc di
loop parse_loop
; due to the limitation of our random function, limit numbers to 31 bits
; it is the same positive limit of a signed 32 bit number
and eax, 7fffffffh
; cx is zero, return success
pop di
pop edx
pop ebx
ret
parse_error:
mov cx, 1
pop di
pop edx
pop ebx
ret
getNumber endp
; Prints 32 bit number in eax
printNumber proc
pusha
; set pointer to output buffer
mov di, offset IOBuffer
convert_loop:
; find mod 10 of eax
mov edx, 0
mov ebx, 10
div ebx
; add digit to buffer
add dl, '0'
mov [di], dl
inc di
; carry on converting if needed
cmp eax, 0
jne convert_loop
print_digits:
; see how many chars we got
mov ecx, 0
mov cx, di
sub cx, offset IOBuffer
print_loop:
dec di
mov dl, [di]
mov ah, 2
int 21h
loop print_loop
popa
ret
printNumber endp
; Generate random seed and initialise RNG
; Makes sure seed is coprime to 48271
randomize proc
pusha
generate_seed:
; get system time in cx:dx
mov ah, 0
int 1ah
; could be too small number, xor it up and set second highest bit
xor cx, dx
or cx, 4000h
; move seed to eax
mov eax, 0
mov ax, dx
shl eax, 16
mov ax, cx
; unlikely, but re-generate if seed is zero
cmp eax, 0
je generate_seed
; also re-generate if seed matches our random multiplier
cmp eax, 48271
je generate_seed
; kill highest bit to prevent div overflow
and eax, 7fffffffh
mov di, offset RandomSeed
mov [di], eax
; check if number is a multiple of 48271
mov edx, 0
mov ebx, 48271
div ebx
cmp edx, 0
je generate_seed
popa
ret
randomize endp
; Generate a 31 bit random number into eax
; Uses simple MINSTD implementation with multiplier 48271
random proc
push ebx
push edx
push di
; load seed
mov di, offset RandomSeed
mov eax, [di]
; multiply seed
mov ebx, 48271
mul ebx
; at this point, multiplied seed is 48 bit edx:eax
; mod it by 2^31-1
mov ebx, 7fffffffh
div ebx
; put result into eax
mov eax, edx
; store seed
mov [di], eax
pop di
pop edx
pop ebx
ret
random endp
; Generate a 31 bit random number into eax
; Before calling this proc, pre-fill eax with the limit
; The range is 1..limit, inclusive
randomLimit proc
push ecx
push bp
mov bp, sp
; save limit
push eax
rlim_generate:
; generate 31-bit random number
call random
; mod eax with nearest power of two
; if still over the limit, regenerate
; there is at most 50% chance of re-iteration, unlikely to cause an infinite loop
; this prevents bias for lower numbers
mov ecx, 1
rlim_mask_loop:
cmp ecx, [bp-4]
jae rlim_mask_done
shl ecx, 1
inc ecx
jmp rlim_mask_loop
rlim_mask_done:
; apply mask
and eax, ecx
; add 1
add eax, 1
; check if result is less than the limit
; if not, re-generate
cmp eax, [bp-4]
ja rlim_generate
; discard dword from the stack
pop ecx
; return
pop bp
pop ecx
ret
randomLimit endp
end main