/
num_286.asm
719 lines (579 loc) · 11.7 KB
/
num_286.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
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
.286
.model small
.stack
.data
; String messages
Welcome db "Welcome to NumGuess 286 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 dx, 0
jne limit_ok
cmp ax, 10
jae limit_ok
limit_fail:
; set default limit
mov dx, 0
mov ax, 10
limit_ok:
; store limit
mov [word ptr Limit], dx
mov [word ptr Limit+2], ax
; calculate maximum tries needed
; check if limit is dword
mov bx, 0
cmp dx, 0
je process_limit
; if dword, start result from 16 and process high word
mov bx, 16
mov ax, dx
process_limit:
inc bx
shr ax, 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 dx, [word ptr Limit]
mov ax, [word ptr Limit+2]
call randomLimit
mov [word ptr Number], dx
mov [word ptr Number+2], ax
; print start message
push offset StartMessage1
call print
mov dx, [word ptr Limit]
mov ax, [word ptr Limit+2]
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
mov bx, ax
or bx, dx
jz out_of_range
cmp dx, [word ptr Limit]
ja out_of_range
jb check_success
cmp ax, [word ptr Limit+2]
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 dx, [word ptr Number]
ja too_high
jb too_low
cmp ax, [word ptr Number+2]
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 dx, 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:
; ax 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 [word ptr Limit], 0
jne custom_over_10p
cmp ax, [word ptr Limit+2]
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 dx:ax
; cx is set to 1 on parse error
getNumber proc
push bx
push di
; set pointer to max length and read string
mov dx, offset IOBufferMax
mov ah, 0ah
int 21h
; clear dx:ax
mov dx, 0
mov ax, 0
; check if input is empty
mov ch, 0
mov cl, IOBufferLength
cmp cx, 0
je parse_error
; loop digits
mov di, offset IOBuffer
parse_loop:
; multiply dx:ax by 10
mov bx, 10
push ax
mov ax, dx
mul bx
mov dx, ax
pop ax
jc parse_error
push dx
mul bx
pop bx
add dx, bx
mov bl, [di]
; check for invalid character
cmp bl, '0'
jb parse_error
cmp bl, '9'
ja parse_error
; add to dx:ax
mov bh, 0
sub bl, '0'
add ax, bx
adc dx, 0
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 dx, 7fffh
; cx is zero, return success
pop di
pop bx
ret
parse_error:
mov cx, 1
pop di
pop bx
ret
getNumber endp
; Prints 32 bit number in dx:ax
printNumber proc
pusha
; set pointer to output buffer
mov di, offset IOBuffer
convert_loop:
; find mod 10 of dx:ax
mov bx, 10
cmp dx, 10
jae div_32bit
; div won't overflow, do 16 bit division
div bx
; add digit to buffer
add dl, '0'
mov [di], dl
inc di
; carry on converting if needed
mov dx, 0
cmp ax, 0
je print_digits
jmp convert_loop
div_32bit:
; simple div would overflow, do 32 bit division
; save low-order word and divide high-order word
mov cx, ax
mov ax, dx
mov dx, 0
div bx
; save result of high-order division
push ax
; leave remainder in dx, restore and divide low-order word
mov ax, cx
div bx
; add digit to buffer
add dl, '0'
mov [di], dl
inc di
; restore high-order result into dx
pop dx
; carry on converting if needed
cmp dx, 0
jne convert_loop
cmp ax, 0
jne convert_loop
print_digits:
; see how many chars we got
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
; unlikely, but re-generate if seed is zero
mov ax, dx
or ax, cx
jz generate_seed
; also re-generate if seed matches our random multiplier
mov ax, dx
sub ax, 48271
or ax, cx
jz generate_seed
; kill highest bit to prevent div overflow
and cx, 7fffh
mov di, offset RandomSeed
mov [di], cx
mov [di+2], dx
; check if number is a multiple of 48271
mov dx, cx
mov ax, dx
mov bx, 48271
div bx
cmp dx, 0
je generate_seed
popa
ret
randomize endp
; Generate a 31 bit random number into dx:ax
; Uses simple MINSTD implementation with multiplier 48271
random proc
push bx
push cx
push di
; load seed
mov di, offset RandomSeed
mov dx, [di]
mov ax, [di+2]
; multiply seed
mov bx, 48271
mov cx, dx ; save high order word of seed
mul bx ; multiply low order word of seed
push ax ; save low order word of result
push dx ; save high order word of result
mov ax, cx ; load high order word of seed
mul bx ; multiply high order word of seed
pop bx
add ax, bx ; add high order word of previous result to dx:ax
adc dx, 0
pop cx
; at this point, multiplied seed is 48 bit dx:ax:cx
; to mod it by 2^31-1, we'll do some shifting and adding
; shift highest bit of ax into dx
shl dx, 1
shl ax, 1
adc dx, 0
shr ax, 1
; add dx to ax:cx
add cx, dx
adc ax, 0
; put result into dx:ax
mov dx, ax
mov ax, cx
; final check is if result = 2^31-1, set to zero
cmp ax, 0ffffh
jne random_done
cmp dx, 7fffh
jne random_done
mov dx, 0
mov ax, 0
random_done:
; kill highest bit to be sure
and dx, 7fffh
; store seed
mov [di], dx
mov [di+2], ax
pop di
pop cx
pop bx
ret
random endp
; Generate a 31 bit random number into dx:ax
; Before calling this proc, pre-fill dx:ax with the limit
; The range is 1..limit, inclusive
randomLimit proc
push bx
push cx
push bp
mov bp, sp
; save limit
push dx
push ax
rlim_generate:
; generate 31-bit random number
call random
; mod dx:ax 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
; easier than 32bit mod 32bit, and prevents bias for lower numbers
; start with high-order bits
mov bx, [bp-2]
cmp bx, 0
jne rlim_32bit
rlim_16bit:
; limit is only 16 bits
mov dx, 0
mov bx, [bp-4]
mov cx, 1
rlim_16bit_mask_loop:
cmp cx, bx
jae rlim_16bit_mask_done
shl cx, 1
inc cx
jmp rlim_16bit_mask_loop
rlim_16bit_mask_done:
and ax, cx
jmp rlim_mask_finished
rlim_32bit:
; limit is 32 bits, process high-order only
; bx already has high-order bits of limit
mov cx, 1
rlim_32bit_mask_loop:
cmp cx, bx
jae rlim_32bit_mask_done
shl cx, 1
inc cx
jmp rlim_32bit_mask_loop
rlim_32bit_mask_done:
and dx, cx
jmp rlim_mask_finished
rlim_mask_finished:
; add 1
add ax, 1
adc dx, 0
; check if result is less than the limit
; if not, re-generate
cmp dx, [bp-2]
ja rlim_generate
jb rlim_return
cmp ax, [bp-4]
ja rlim_generate
rlim_return:
; discard two words from the stack
pop bx
pop bx
; return
pop bp
pop cx
pop bx
ret
randomLimit endp
end main