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"