bits 16 cpu 8086 org 0x100 args_length equ 0x80 args_area equ 0x81 section .code parse_arguments: xor ch, ch mov cl, [args_length] mov si, args_area ; Skip leading spaces .skip_leading_space: test cx, cx jz .leading_spaces_skipped cmp byte [si], ' ' jne .leading_spaces_skipped dec cx inc si jmp .skip_leading_space .leading_spaces_skipped: ; See whether we have /h, /u, /l, or /? cmp cx, 2 jb .not_mode_option cmp byte [si], '/' jne .not_mode_option cmp byte [si + 1], 'h' je .hard_mode cmp byte [si + 1], 'H' jne .not_hard_mode .hard_mode: mov byte [hard_mode], 1 add si, 2 sub cx, 2 jmp .after_option_space .not_hard_mode: cmp byte [si + 1], 'u' je .ultra_hard_mode cmp byte [si + 1], 'U' jne .not_ultra_hard_mode .ultra_hard_mode: mov byte [hard_mode], 1 mov byte [ultra_hard_mode], 1 add si, 2 sub cx, 2 .after_option_space: test cx, cx jz .option_done cmp byte [si], ' ' jne .option_done dec cx inc si jmp .after_option_space .option_done: .not_ultra_hard_mode: cmp byte [si + 1], 'l' je print_license cmp byte [si + 1], 'L' je print_license cmp byte [si + 2], '?' je print_help .not_mode_option: test cx, cx jz seed_rng_date seed_rng_argument: xor ax, ax xor dx, dx mov bp, mull32 call store32 .loop: xor dx, dx mov ax, 10 mov bp, mulr32 call store32 call mul32 mov bp, addl32 call store32 lodsb cmp al, '0' jb print_help cmp al, '9' ja print_help sub al, '0' xor ah, ah xor dx, dx mov bp, addr32 call store32 call add32 mov bp, mull32 call store32 loop .loop mov bp, rng_seed call store32 jmp select_target print_help: mov ah, 9 mov dx, help_str int 0x21 ret print_license: mov ah, 9 mov dx, license_str int 0x21 ret seed_rng_date: ; Get date mov ah, 0x2a int 0x21 mov [year], cx mov [month], dh %ifdef DEBUG mov cx, 2022 mov dh, 2 mov dl, 24 %endif mov [day], dl ; addl32 = year * 10000 mov ax, 10000 mul word [year] mov bp, addl32 call store32 ; addr32 = month * 100 + day mov ax, 100 mul word [month] add ax, [day] mov bp, addr32 call store32 ; rng_seed = addl32 + addr32 = year * 10000 + month * 100 + day call add32 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 target 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 %ifdef DEBUG call hexprint16 call newline %endif 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 print_greeting: mov ah, 9 mov dx, greeting_str int 0x21 read_guess: ; Number of thus far guessed letters stored in bx xor bx, bx .loop: mov ah, 8 int 0x21 cmp al, 3 je .ctrl_c 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 .ctrl_c: ret .done: check_hard_mode: cmp byte [hard_mode], 1 jne .done ; See whether exact hits are reused xor bp, bp mov cx, 5 .exact_hits: mov al, [exact_hits + bp] test al, al jz .not_exact_hit cmp al, [guess + bp] je .exact_hit_match mov dx, bp add dl, '1' mov [exact_hit_error_str.position], dl mov [exact_hit_error_str.letter], al mov si, exact_hit_error_str jmp print_error .exact_hit_match: .not_exact_hit: inc bp loop .exact_hits ; See whether wrongly placed letters are reused xor bp, bp .wrong_places: cmp bp, 5 je .done mov dl, [wrong_places + bp] test dl, dl jz .not_wrong_place ; Count how many times letter is used in exact_hits + wrong_places xor bx, bx mov cx, 5 mov si, exact_hits .count_exact_hits: lodsb cmp al, dl jne .not_count_exact_hit inc bh .not_count_exact_hit: loop .count_exact_hits mov cx, 5 mov si, wrong_places .count_wrong_places: lodsb cmp al, dl jne .not_count_wrong_places inc bh .not_count_wrong_places: loop .count_wrong_places ; Count how many times letter is used in guess mov cx, 5 mov si, guess .count_guess: lodsb cmp al, dl jne .not_count_guess inc bl .not_count_guess: loop .count_guess cmp bl, bh jae .used_enough mov [wrong_place_error_str.letter], dl mov ax, bp add ax, '1' mov [wrong_place_error_str.position], al mov si, wrong_place_error_str jmp print_error .used_enough: .not_wrong_place: inc bp jmp .wrong_places .done: check_ultra_hard_mode: cmp byte [ultra_hard_mode], 1 jne .done ; See whether any ruled-out letters were used xor bp, bp mov cx, 5 .ruled_out: xor bh, bh mov bl, [guess + bp] mov ax, bx mov al, [alphabet + bx - 'a'] cmp al, ' ' jne .not_ruled_out mov [ruled_out_error_str.letter], bl mov ax, bp add al, '1' mov [ruled_out_error_str.position], al mov si, ruled_out_error_str jmp print_error .not_ruled_out: inc bp loop .ruled_out ; See whether wrongly places letters were moved xor bp, bp mov cx, 5 .wrong_places: mov al, [wrong_places + bp] test al, al jz .not_wrong_place cmp al, [guess + bp] jne .moved mov [not_moved_error_str.letter], al mov ax, bp add ax, '1' mov [not_moved_error_str.position], al mov si, not_moved_error_str jmp print_error .moved: .not_wrong_place: inc bp loop .wrong_places .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 si, not_found_str print_error: mov ah, 9 mov dx, si 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 .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 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 not used in target at all, remove from list of possible letters test bh, bh jnz .used_in_target xor dh, dh mov di, dx mov byte [alphabet + di - 'a'], ' ' .used_in_target: ; 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 mov ah, 9 mov dx, space_dash_space_str int 0x21 mov cx, 26 mov si, alphabet .alphabet_loop: lodsb mov dl, al mov ah, 2 int 0x21 loop .alphabet_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 cmp byte [guesses], 1 je .singular .plural: mov dx, guesses_str jmp .print .singular: mov dx, guess_str .print: 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 %ifdef DEBUG 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 %endif section .data alphabet db 'abcdefghijklmnopqrstuvwxyz' 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 hard_mode db 0 ultra_hard_mode 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 year dw 0 month dw 0 day dw 0 exact_hit_error_str: db ' - letter at position ' .position db '#' db " must be '" .letter db '#' db "'$" wrong_place_error_str: db " - letter '" .letter db '#' db "' from position " .position db '#' db ' must be reused$' not_moved_error_str: db " - letter '" .letter db '#' db "' in position " .position db '#' db ' must be moved$' ruled_out_error_str: db " - letter '" .letter db '#' db "' in position " .position db '#' db ' has been ruled out$' section .rodata greeting_str db 'Welcome to hello DOSdl. Press Ctrl+C to exit. Run `dosdl /?` for help.', 13, 10, '$' space_dash_space_str db ' - $' 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.$' guess_str db ' guess.$' help_str: db 'Usage: dosdl [/h | /u | /l | /?] [seed]', 13, 10 db '/h Enable hard mode.', 13, 10 db '/u Enable ultra hard mode.', 13, 10 db '/l Display license info.', 13, 10 db '/? Display this help.', 13, 10 db 13, 10 db 'Hello DOSdl is a word guessing game. You have six tries to guess the correct', 13, 10 db 'English word. After a guess the game displays feedback under each letter:', 13, 10 db 13, 10 db "' ' Letter is in the correct place.", 13, 10 db "'^' Letter is in the wrong place.", 13, 10 db "'x' Letter is not in used in the word, or its uses are already covered by the", 13, 10 db ' above cases.', 13, 10 db 13, 10 db 'After the feedback, the game shows a list of letters that have not yet been', 13, 10 db 'ruled out.', 13, 10 db 13, 10 db 'In hard mode all letters marked as being in the correct place must stay fixed', 13, 10 db 'and those marked as being in the wrong place must be reused. In ultra hard mode', 13, 10 db 'letters marked as being in the wrong place must also be moved and letters that', 13, 10 db 'have been ruled out must not be played again.$' %include "dictionary.inc" %include "targets.inc" %include "license.inc"