hello-dosdl/dosdl.asm

725 lines
9.9 KiB
NASM

bits 16
cpu 8086
org 0x100
section .code
seed equ 20220224
; Seed the rng
mov ax, seed & 0xffff
mov dx, seed >> 16
mov bp, rng_seed
call store32
select_target:
; Call rng to get a number
call rng
mov bp, rng_output
call store32
; Multiply rng output by number of targer words
xor si, si
xor dx, dx
xor ax, ax
mov cx, targets_len
.multiply_loop:
add ax, [rng_output + 0]
adc dx, [rng_output + 2]
adc si, 0
loop .multiply_loop
mov ax, si
call hexprint16
call newline
load_target:
shl si, 1
shl si, 1
add si, targets
mov di, target
call unpack_target
; Figure out whether this is a removed word
; If so, reroll target word
cmp byte [target], '{'
je select_target
read_guess:
; Number of thus far guessed letters stored in bx
xor bx, bx
.loop:
mov ah, 8
int 0x21
cmp al, 8
je .backspace
cmp bx, 5
je .full
cmp al, 'A'
jb .loop
cmp al, 'Z'
jbe .letter
cmp al, 'a'
jb .loop
cmp al, 'z'
jbe .letter
jmp .loop
.full:
cmp al, 13
je .done
jmp .loop
.letter:
; Lowercase
or al, 32
mov byte [guess + bx], al
inc bx
; Echo to screen
mov dl, al
mov ah, 2
int 0x21
jmp .loop
.backspace:
test bx, bx
jz .loop
; Rub out the letter
; PC-DOS 1.1 documentation says only 8 is needed, but DOSBox
; seems buggy here
mov ah, 2
mov dl, 8
int 0x21
mov dl, ' '
int 0x21
mov dl, 8
int 0x21
dec bx
jmp .loop
.done:
check_dictionary:
; Dictionary is split by initial letter
xor bx, bx
mov bl, [guess]
sub bl, 'a'
; Entry in the table is 4 bytes
shl bx, 1
shl bx, 1
mov si, [dictionaries + bx] ; Start address
mov cx, [dictionaries + bx + 2] ; Number of letters
.dictionary_loop:
push cx
mov di, dictionary_entry
; Entry is stored 5 bits per letter (first letter is implicit)
; 3332_2222 5444_4433 0000_5555
; Load 5444_4433_3332_2222 into dx and extract 3 first letters
mov dx, [si]
add si, 2
mov cl, 5
%rep 3
mov al, dl
and al, 0x1f
add al, 'a'
stosb
shr dx, cl
%endrep
; Load 0000_5555 into al, shift one over, or with dl which has
; 0000_0005
mov al, [si]
inc si
shl al, 1
or al, dl
add al, 'a'
stosb
sub di, 4
mov bp, 1
mov cx, 4
.compare_loop:
mov al, [di]
inc di
cmp al, [guess + bp]
jne .not_match
inc bp
loop .compare_loop
pop cx
jmp word_found
.not_match:
pop cx
loop .dictionary_loop
check_targets:
mov cx, targets_len
mov si, targets
.targets_loop:
mov di, dictionary_entry
call unpack_target
xor bp, bp
.compare_loop:
cmp bp, 5
je .match
mov al, [di + bp]
cmp al, [guess + bp]
jne .not_match
inc bp
jmp .compare_loop
.match:
jmp word_found
.not_match:
loop .targets_loop
word_not_found:
mov ah, 9
mov dx, not_found_str
int 0x21
; Wait for a keypress
mov ah, 8
int 0x21
; Clear line
mov ah, 2
mov dl, 13
int 0x21
mov ah, 9
mov dx, erase_word_str
int 0x21
mov si, not_found_str
.clear_loop:
lodsb
cmp al, '$'
je .done
mov ah, 2
mov dl, ' '
int 0x21
jmp .clear_loop
.done:
mov ah, 2
mov dl, 13
int 0x21
jmp read_guess
word_found:
find_exact_hits:
mov si, guess
mov di, exact_hits
mov cx, 5
.loop:
lodsb
cmp al, [si - guess + target - 1]
je .hit
; Zero out if not the right letter
xor al, al
.hit:
stosb
loop .loop
find_wrong_places:
; Zero out first
mov di, wrong_places
mov cx, 5
xor al, al
rep stosb
xor bp, bp
.loop:
cmp bp, 5
je .done
mov al, [exact_hits + bp]
inc bp
test al, al
jnz .loop
; What was the guessed letter?
mov dl, [guess + bp - 1]
; How many times does it appear in the target?
xor bh, bh
mov cx, 5
mov si, target
.count_target:
lodsb
cmp al, dl
jne .not_found_in_target
inc bh
.not_found_in_target:
loop .count_target
; How many times does it appear in the feedback already?
xor bl, bl
mov cx, 5
mov si, exact_hits
.count_exact_hits:
lodsb
cmp al, dl
jne .not_found_in_exact_hits
inc bl
.not_found_in_exact_hits:
loop .count_exact_hits
mov cx, 5
mov si, wrong_places
.count_wrong_places:
lodsb
cmp al, dl
jne .not_found_in_wrong_places
inc bl
.not_found_in_wrong_places:
loop .count_wrong_places
; If in target more than in feedback → wrong place
cmp bh, bl ; target <= feedback
jbe .loop
mov byte [wrong_places + bp - 1], dl
jmp .loop
.done:
print_feedback:
call newline
mov cx, 5
xor bp, bp
xor bx, bx ; How many exact hits?
.loop:
mov al, [exact_hits + bp]
test al, al
jnz .exact_hit
mov al, [wrong_places + bp]
test al, al
jnz .wrong_place
mov dl, 'x'
jmp .print
.exact_hit:
mov dl, ' '
inc bx
jmp .print
.wrong_place:
mov dl, '^'
.print:
mov ah, 2
int 0x21
inc bp
loop .loop
call newline
is_finished:
inc byte [guesses]
cmp bx, 5
je victory
cmp byte [guesses], 6
je loss
jmp read_guess
loss:
mov dx, word_was_str
mov ah, 9
int 0x21
mov cx, 5
mov si, target
.loop:
lodsb
mov dl, al
mov ah, 2
int 0x21
loop .loop
jmp exit
victory:
mov dx, correct_in_str
mov ah, 9
int 0x21
mov dl, [guesses]
add dl, '0'
mov ah, 2
int 0x21
mov dx, guesses_str
mov ah, 9
int 0x21
exit:
ret
newline:
push ax
push dx
mov ah, 2
mov dl, 13
int 0x21
mov dl, 10
int 0x21
pop dx
pop ax
ret
; in:
; si = packed target word
; di = 5 byte buffer for unpacking the target
; out:
; si = first byte after the packed target word
unpack_target:
push ax
push cx
push dx
push di
; Target word is stored packed 5 bits per letter into 32 bits
; 2221_1111 4333_3322 5555_4444 0000_0005
; Load 4333_3322_2221_1111 into dx and extracts first three letters
mov dx, [si]
add si, 2
mov cl, 5
%rep 3
mov al, dl
and al, 0x1f
add al, 'a'
stosb
shr dx, cl
%endrep
; Load 0000_0005_5555_4444 into ax, shift over by one, combine into dx
; which has 0000_0000_0000_0004, and extract last two letters
mov ax, [si]
add si, 2
shl ax, 1
or dx, ax
%rep 2
mov al, dl
and al, 0x1f
add al, 'a'
stosb
shr dx, cl
%endrep
pop di
pop dx
pop cx
pop ax
ret
; out:
; dx:ax = result
rng:
; https://github.com/lynn/hello-wordl/blob/7da40c1f067eb1ec157d4c5b7a9bd8257ed39342/src/util.ts#L14
; rng_t = rng_seed += rng_seed + 0x6d2b79f5
mov bp, rng_seed
call load32
add ax, 0x79f5
adc dx, 0x6d2b
call store32
mov bp, rng_t
call store32
; xorl32 = rng_t
mov bp, xorl32
call store32
; xorr32 = rng_t >> 15
mov cx, 15
call rshift32
mov bp, xorr32
call store32
; mull32 = xorl32 ^ xorr32 = rng_t ^ (rng_t >> 15)
call xor32
mov bp, mull32
call store32
; mulr32 = rng_t | 1
mov bp, rng_t
call load32
or ax, 1
mov bp, mulr32
call store32
; rng_t = mull32 * mulr32 = (rng_t ^ (rng_t >> 15)) * (rng_t | 1)
call mul32
mov bp, rng_t
call store32
; xorl32 = rng_t
mov bp, xorl32
call store32
; xorr32 = rng_t >> 7
mov cx, 7
call rshift32
mov bp, xorr32
call store32
; mull32 = xorl32 ^ xorr32 = rng_t ^ (rng_t >> 7)
call xor32
mov bp, mull32
call store32
; mulr32 = rng_t | 61
mov bp, rng_t
call load32
or ax, 61
mov bp, mulr32
call store32
; addl32 = rng_t
mov bp, rng_t
call load32
mov bp, addl32
call store32
; addr32 = mull32 * mulr32 = (rng_t ^ (rng_t >> 7)) * (rng_t | 61)
call mul32
mov bp, addr32
call store32
; xorl32 = rng_t
mov bp, rng_t
call load32
mov bp, xorl32
call store32
; xorr32 = addl32 + addr32 = rng_t + (rng_t ^ (rng_t >> 7)) * (rng_t | 61)
call add32
mov bp, xorr32
call store32
; rng_t = xorl32 ^ xorr32 = rng_t ^ (rng_t + (rng_t ^ (rng_t >> 7)) * (rng_t | 61))
call xor32
mov bp, rng_t
call store32
; xorl32 = rng_t
mov bp, xorl32
call store32
; xorr32 = rng_t >> 14
mov cx, 14
call rshift32
mov bp, xorr32
call store32
; result = xorl32 ^ xorr32 = rng_t ^ (rng_t >> 14)
call xor32
ret
; in:
; bp = address of uint32
; out:
; dx = high 16 bits
; ax = low 16 bits
load32:
mov ax, [bp + 0]
mov dx, [bp + 2]
ret
; in:
; bp = address of uint32
; dx = high 16 bits
; ax = low 16 bits
store32:
mov [bp + 0], ax
mov [bp + 2], dx
ret
; in:
; dx:ax = uint32 to be shifted
; cx = shift amount (must not be 0)
; out:
; dx:ax = result
rshift32:
push cx
.loop:
shr dx, 1
rcr ax, 1
loop .loop
pop cx
ret
; in:
; [xorl32], [xorr32] = operands
; out:
; dx:ax = result
xor32:
mov ax, [xorl32 + 0]
xor ax, [xorr32 + 0]
mov dx, [xorl32 + 2]
xor dx, [xorr32 + 2]
ret
; in:
; [mull32], [mulr32] = operands
; out:
; dx:ax = result
mul32:
push bx
push cx
; c = 1<<16
; l = l₁<<16 + l₀
; r = r₁<<16 + r₀
; l·r = (l₁<<16 + l₀)·(r₁<<16 + r₀)
; = l₁<<16·r₁<<16 + l₁<<16·r₀ + l₀·r₁<<16 + l₀·r₀
; = l₁·r₁<<32 + l₁·r₀<<16 + l₀·r₁<<16 + l₀·r₀ ||1<<32 = 0 (mod 1<<32)
; = l₁·r₀<<16 + l₀·r₁<<16 + l₀·r₀
; l₀·r₀
mov ax, [mull32 + 0]
mul word [mulr32 + 0]
mov bx, ax
mov cx, dx
; l₀·r₁<<16
mov ax, [mull32 + 0]
mul word [mulr32 + 2]
add cx, ax
; l₁·r₀<<16
mov ax, [mull32 + 2]
mul word [mulr32 + 0]
add cx, ax
mov dx, cx
mov ax, bx
pop cx
pop bx
ret
; in:
; [addl32], [addr32] = operands
; out:
; dx:ax = result
add32:
mov ax, [addl32 + 0]
mov dx, [addl32 + 2]
add ax, [addr32 + 0]
adc dx, [addr32 + 2]
ret
hexprint32:
xchg ax, dx
call hexprint16
xchg ax, dx
hexprint16:
xchg ah, al
call hexprint8
xchg ah, al
hexprint8:
%rep 4
rol al, 1
%endrep
call hexprint4
%rep 4
rol al, 1
%endrep
hexprint4:
push ax
and al, 0xf
cmp al, 10
jb .digit09
add al, 'a' - 10 - '0'
.digit09:
add al, '0'
mov ah, 0xe
int 0x10
pop ax
ret
section .data
target times 5 db 0
guess times 5 db 0
exact_hits times 5 db 0
wrong_places times 5 db 0
guesses db 0
dictionary_entry times 5 db 0
rng_seed dd 0
rng_t dd 0
rng_output dd 0
xorl32 dd 0
xorr32 dd 0
mull32 dd 0
mulr32 dd 0
addl32 dd 0
addr32 dd 0
section .rodata
not_found_str db ' - word not found$'
erase_word_str db ' $'
word_was_str db 'The word was: $'
correct_in_str db 'Correct in $'
guesses_str db ' guesses.$'
%include "dictionary.inc"
%include "targets.inc"