Compare commits

..

4 Commits
0.6.0 ... main

  1. 7
      dict/compress.py
  2. 5
      dict/solver.py
  3. 8
      inc/constants.asm
  4. 137
      inc/dict.asm
  5. 30
      inc/guess.asm
  6. 33
      inc/hints.asm
  7. 97
      inc/init.asm
  8. 47
      inc/input.asm
  9. 17
      inc/interrupts.asm
  10. 8
      inc/math.asm
  11. 38
      inc/messages.asm
  12. 22
      inc/oam.asm
  13. 113
      inc/search.asm
  14. 22
      inc/wincondition.asm
  15. 1
      maps/window-game.asm
  16. 8
      maps/window-help.asm
  17. 17
      tiles/sign-arrow.asm
  18. 17
      tiles/sign-slash.asm
  19. 121
      wordle.asm
  20. BIN
      wordle.gb

7
dict/compress.py

@ -1,4 +1,11 @@
#!/usr/bin/env python3
"""
To fit the whole game into one memory bank, the dictionary is compressed.
Since only 2^5 combinations are needed to store 26 letters,
5 bits would be enough; for simplicity's sake, however, we take 6.
This way we can reduce the memory consumption by 20%.
"""
import struct
def compress(in_path: str, out_path:str):

5
dict/solver.py

@ -1,4 +1,9 @@
#!/usr/bin/env python3
"""
Even though I like Wordle, it becomes very exhausting to constantly
use your brain while developing. That's why this tool exists.
"""
import random
print("Welcome to this debug tool!")

8
inc/constants.asm

@ -1,4 +1,10 @@
;; Game Boy Constants
;; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
;; ██░▄▄░█░▄▄▀█░▄▀▄░█░▄▄████░▄▄▀█▀▄▄▀█░██░████░▄▄▀█▀▄▄▀█░▄▄▀█░▄▄█▄░▄█░▄▄▀█░▄▄▀█▄░▄█░▄▄██
;; ██░█▀▀█░▀▀░█░█▄█░█░▄▄████░▄▄▀█░██░█░▀▀░████░████░██░█░██░█▄▄▀██░██░▀▀░█░██░██░██▄▄▀██
;; ██░▀▀▄█▄██▄█▄███▄█▄▄▄████░▀▀░██▄▄██▀▀▀▄████░▀▀▄██▄▄██▄██▄█▄▄▄██▄██▄██▄█▄██▄██▄██▄▄▄██
;; ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
;; Hardware specific values and addresses
; Interrupt Flags
INTERRUPT_SETTINGS EQU $ffff
INT_VBLANK_ON EQU %00000001

137
inc/dict.asm

@ -1,15 +1,29 @@
; Selects a random word from the dictionary.
; Sets the to be guessed and the revealed letter indices.
;; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
;; ██░▄▄▀██▄██▀▄▀█▄░▄██▄██▀▄▄▀█░▄▄▀█░▄▄▀█░▄▄▀█░██░██
;; ██░██░██░▄█░█▀██░███░▄█░██░█░██░█░▀▀░█░▀▀▄█░▀▀░██
;; ██░▀▀░█▄▄▄██▄███▄██▄▄▄██▄▄██▄██▄█▄██▄█▄█▄▄█▀▀▀▄██
;; ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
;; This file contains all the functions to interact with the dictionary.
;; Therefore, a random word can be selected and it is possible to check if an entered word exists.
; Selects a random entry from the dictionary and saves it.
;
; Note: The dict saves the 5 chars of the words in 4 bytes.
; Note:
; In the dictionary, entries with a word length of five are stored with only four bytes.
; n+0: 11111122
; n+1: 22223333
; n+2: 33444444
; n+3: 55555500
; ~> 00111111 00222222 00333333 00444444 00555555
;
; -> [random_number]: depends on the current random number
; <- [currend_word]: five characters, index starts at one
select_word:
ld de, current_word
; determine the starting address of the dictionary entry
; (start address + random * word length)
ld a, [random_number+0]
and %00011111
ld h, a
@ -95,3 +109,120 @@ select_word:
ld [de], a
ret
; In order to check whether an entered word exists in the dictionary,
; it must first be compressed according to the dictionary.
; -> [current guess]: current rate attempt, stored in five bytes
; <- bc: first two bytes of the compression
; <- de: second two bytes of the compression
compress_guess:
call get_guess_offset
; first byte
ld a, [hl+] ; letter 1
and %00111111
sla a
sla a
ld b, a
ld a, [hl] ; letter 2
and %00110000
sra a
sra a
sra a
sra a
add a, b
ld d, a
; second byte
ld a, [hl+] ; letter 2
and %00001111
sla a
sla a
sla a
sla a
ld b, a
ld a, [hl] ; letter 3
and %00111100
sra a
sra a
add a, b
ld e, a
push de
; third byte
ld a, [hl+] ; letter 3
and %00000011
sla a
sla a
sla a
sla a
sla a
sla a
ld b, a
ld a, [hl+] ; letter 4
and %00111111
add a, b
ld d, a
; fourth byte
ld a, [hl] ; letter 5
and %00111111
sla a
sla a
ld e, a
pop bc
ret
; Try to find the current guess within the dictionary
; -> bc: first two bytes of the compression
; -> de: second two bytes of the compression
; <- a: whether the entry exists
find_guess:
ld hl, dictionary
.loop:
; check if the end of the dictionary is reached
push bc
ld bc, dictionary_end
ld a, h
cp a, b
jp nz, .not_eof
ld a, l
cp a, c
jp nz, .not_eof
pop bc
jp .return
.not_eof:
pop bc
ld a, [hl+]
cp a, b
jp nz, .add3
ld a, [hl+]
cp a, c
jp nz, .add2
ld a, [hl+]
cp a, d
jp nz, .add1
ld a, [hl+]
cp a, e
jp nz, .add0
ld a, 1
ret
.add3:
inc hl
.add2:
inc hl
.add1:
inc hl
.add0:
jp .loop
.return:
ld a, 0
ret

30
inc/guess.asm

@ -1,7 +1,17 @@
;; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
;; ██░▄▄░█░██░█░▄▄█░▄▄█░▄▄█░███░█▀▄▄▀█░▄▄▀█░█▀██
;; ██░█▀▀█░██░█░▄▄█▄▄▀█▄▄▀█▄▀░▀▄█░██░█░▀▀▄█░▄▀██
;; ██░▀▀▄██▄▄▄█▄▄▄█▄▄▄█▄▄▄██▄█▄███▄▄██▄█▄▄█▄█▄██
;; ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
;; Manages and displays the guessing attempts
; Updates the objects of the entered characters
; -> [guess_attempts]
; <- [obj_guess_letters]
update_guess_objects:
ld hl, obj_guess_letters
ld de, guesses
ld de, guess_attempts
ld a, $14
call update_guess_row
@ -20,9 +30,10 @@ update_guess_objects:
; Updates one line of entered characters
; -> hl: position within obj data
; -> de: position within the guesses data
; -> hl: address of where to write obj data
; -> de: address of the current guess
; -> a: vertical screen position
; <- [obj_guess_letters]
update_guess_row:
push hl
push hl
@ -31,14 +42,14 @@ update_guess_row:
; distance between objects
ld bc, 4
; vertical positions
; write all five vertical positions
ld [hl], a
REPT 4
add hl, bc
ld [hl], a
ENDR
; horizontal positions
; write all five horizontal positions
pop hl
inc hl
ld a, $30
@ -49,7 +60,7 @@ REPT 4
ld [hl], a
ENDR
; tile indices
; write all five tile indices
pop hl
inc hl
inc hl
@ -62,7 +73,7 @@ REPT 4
ld [hl], a
ENDR
; palette info
; write all five palette info
pop hl
inc hl
inc hl
@ -80,7 +91,8 @@ ENDR
; Calculates the position of the current guess
; Calculates the address of the current guess attempt, using the number of the attempt
; -> [current_guess]: number of the current attempt
; <- hl
get_guess_offset:
ld a, [current_guess]
@ -88,7 +100,7 @@ get_guess_offset:
call multiply_ab
ld b, 0
ld c, a
ld hl, guesses
ld hl, guess_attempts
add hl, bc
ret

33
inc/hints.asm

@ -1,7 +1,19 @@
; Update the character hint objects
;; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
;; ██░██░██▄██░▄▄▀█▄░▄█░▄▄██
;; ██░▄▄░██░▄█░██░██░██▄▄▀██
;; ██░██░█▄▄▄█▄██▄██▄██▄▄▄██
;; ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
;; Manages the obtained hints
; Updates the background layer with the obtained hints.
; Note: a row within the background map consists of 32 tiles
; -> [guess_hints]
; <- [BKG_LOC_9800]
update_hint_markings:
ld hl, BKG_LOC_9800 + 20*32 + 6
ld de, guesses_hints
; calculate the
ld hl, BKG_LOC_9800 + 32*20 + 6
ld de, guess_hints
ld b, 6
.loop_outer:
@ -30,8 +42,13 @@ update_hint_markings:
; Update the hint data
; Saves the obtained hints.
; -> [current_word]
; -> [current_guess]
; -> [guess]
mark_hints:
; To get the beginning of the clues,
; you can use the end of the guessing attempts
call get_guess_offset
push hl
pop de
@ -40,6 +57,7 @@ mark_hints:
ld b, 0
.loop:
; update the hints char by char
ld a, [de]
call hint_for_char
ld [hl], a
@ -53,9 +71,10 @@ mark_hints:
; Determines the hint for one character
; <- a: character value to check
; <- b: index within the guess
; Determines the hint for one character.
; -> [current_word]
; -> a: character value to check
; -> b: position within the guess
hint_for_char:
push hl
push bc

97
inc/init.asm

@ -1,4 +1,18 @@
; Initialise everything for the main game state
;; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
;; █▄░▄█░▄▄▀██▄██▄░▄██▄██░▄▄▀█░███▄██▄▄░█░▄▄▀█▄░▄██▄██▀▄▄▀█░▄▄▀██
;; ██░██░██░██░▄██░███░▄█░▀▀░█░███░▄█▀▄██░▀▀░██░███░▄█░██░█░██░██
;; █▀░▀█▄██▄█▄▄▄██▄██▄▄▄█▄██▄█▄▄█▄▄▄█▄▄▄█▄██▄██▄██▄▄▄██▄▄██▄██▄██
;; ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
;; Establish a defined initial state
; Initialize everything for the main game state
; <- [current_state]
; <- [sub_state]
; <- [BKG_POS_X_REGISTER]
; <- [BKG_POS_Y_REGISTER]
; <- [LCD_CONTROL_REGISTER]
; <- [message_objs]
init_state_menu:
ld a, STATE_MENU
ld [current_state], a
@ -22,7 +36,17 @@ init_state_menu:
; Initialise everything for help screen
; Initialize everything for the help screen
; <- [current_state]
; <- [BKG_POS_X_REGISTER]
; <- [BKG_POS_Y_REGISTER]
; <- [WND_POS_X_REGISTER]
; <- [WND_POS_Y_REGISTER]
; <- [LCD_CONTROL_REGISTER]
; <- [current_word]
; <- [guess]
; <- [guess_hints]
; <- [message_objs]
init_state_help:
ld a, STATE_HELP
ld [current_state], a
@ -45,24 +69,27 @@ init_state_help:
ld a, $70
ld [WND_POS_Y_REGISTER], a
; load the window data
ld hl, window_help
call load_window_map
; set a fixed game state
; Set a fixed and actually impossible game state.
; containing: [current_word], [guess] and [guess_hints]
;
; This is only possible because the memory space of these
; three variables is located directly after each other.
ld hl, current_word
ld de, help_word
ld c, 65
.set_gamestate:
.copy_gamestate:
ld a, [de]
ld [hl], a
inc de
inc hl
dec c
jp nz, .set_gamestate
; This is only possible because the memory space of these
; two variables is located directly after each other.
jp nz, .copy_gamestate
; for the rest we can use the default functions
call update_hint_markings
call update_guess_objects
@ -76,7 +103,21 @@ init_state_help:
; Initialise everything for the main game state
; Initialize everything for the main game state
; <- [current_state]
; <- [BKG_POS_X_REGISTER]
; <- [BKG_POS_Y_REGISTER]
; <- [WND_POS_X_REGISTER]
; <- [WND_POS_Y_REGISTER]
; <- [LCD_CONTROL_REGISTER]
; <- [current_word]
; <- [current_guess]
; <- [current_char]
; <- [guess]
; <- [guess_hints]
; <- [selected_letter_x]
; <- [selected_letter_y]
; <- [message_objs]
init_state_game:
; set the current state
ld a, STATE_GAME
@ -98,22 +139,22 @@ init_state_game:
; set the window position
ld a, 3
ld [WND_POS_X_REGISTER], a
ld a, $70
ld a, $68
ld [WND_POS_Y_REGISTER], a
; load the window data
ld hl, window_game
call load_window_map
; initialise some more variables
; initialize some more variables
ld a, 0
ld [selected_letter_x], a
ld [selected_letter_y], a
ld [current_guess], a
ld [current_char], a
.reset_guesses
ld hl, guesses
.reset_guess_attempts
ld hl, guess_attempts
ld a, NULL
ld d, 30
.loop1:
@ -122,7 +163,7 @@ init_state_game:
jp nz, .loop1
.reset_hints
ld hl, guesses_hints
ld hl, guess_hints
ld a, TILE_WHITE
ld d, 30
.loop2:
@ -140,7 +181,9 @@ init_state_game:
; Switches to the lost state
; Switches to the state when the player has lost
; <- [current_state]
; <- [message_objs]
init_state_lost:
ld a, STATE_LOST
ld [current_state], a
@ -149,7 +192,9 @@ init_state_lost:
; Switches to the won state
; Switches to the state when the player has won
; <- [current_state]
; <- [message_objs]
init_state_won:
ld a, STATE_WON
ld [current_state], a
@ -158,7 +203,10 @@ init_state_won:
; Initialise the DMG color palettes
; Initialize the DMG color palettes
; <- [PALETTE_BKG_REGISTER]
; <- [PALETTE_OBJ0_REGISTER]
; <- [PALETTE_OBJ1_REGISTER]
init_palettes:
ld a, %10010011
ld [PALETTE_BKG_REGISTER], a
@ -168,7 +216,9 @@ init_palettes:
; Load the tile data into the vram
; Copy the tile data into the vram
; -> [tiles]
; <- [TLS_LOC_8000]
load_tiles:
ld bc, tiles_start
ld hl, TLS_LOC_8000
@ -189,7 +239,9 @@ load_tiles:
; Load the background map into the vram
; Copy the background map into the vram
; -> [background]
; <- [BKG_LOC_9800]
load_background_map:
ld bc, background
ld hl, BKG_LOC_9800
@ -210,12 +262,13 @@ load_background_map:
; Load the window map into the vram
; -> hl
; Copy a window map into the vram
; -> hl: address of the desired window map
; <- [WND_LOC_9C00]
load_window_map:
ld bc, WND_LOC_9C00
ld d, 32
ld e, 4
ld e, 5
.loop:
ld a, [hl]

47
inc/input.asm

@ -1,6 +1,14 @@
;; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
;; █▄░▄█░▄▄▀█▀▄▄▀█░██░█▄░▄██
;; ██░██░██░█░▀▀░█░██░██░███
;; █▀░▀█▄██▄█░█████▄▄▄██▄███
;; ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
;; Handles the player inputs
; Read the current input state
; -> b: current keystates
; -> c: changed keys since last read
; <- b: current keystates
; <- c: changed keys since last read
read_input:
di
@ -42,7 +50,8 @@ read_input:
; React on input within the menu
; React to input within the menu
; -> c: the changed keystate
handle_input_menu:
.check_movement:
ld a, c
@ -54,9 +63,11 @@ handle_input_menu:
ld [sub_state], a
and a, STATE_MENU_START
jp z, .switch_to_help
.switch_to_start
call show_message_menu_start
jp .check_confirm
.switch_to_help
call show_message_menu_help
jp .check_confirm
@ -79,6 +90,8 @@ handle_input_menu:
; React to input within the help screen
; -> c: the changed keystate
handle_input_help:
ld a, c
and a, INPUT_START
@ -89,8 +102,10 @@ handle_input_help:
; React on input within the main game
; <- c: changed keys since last read
; React to input within the main game
; -> c: the changed keystate
; <- [selected_letter_x]
; <- [selected_letter_y]
handle_input_game:
ld a, [selected_letter_x]
ld d, a
@ -178,22 +193,26 @@ handle_input_game:
; React on input after a game round
; React to input after a game round,
; no matter if won or lost
; -> c: the changed keystate
handle_input_after:
ld a, c
and a, INPUT_START
jp z, .nothing
call init_state_game
call clear_message
.nothing
ret
; Add a character to the guess
; Select a character and add it to the current guess
; -> d: x position of the cursor
; -> e: y position of the cursor
; <- [current_guess]
; <- [current_char]
; <- [guess_attempts]
select_letter:
push hl
push af
@ -216,7 +235,7 @@ select_letter:
jp .return
.normal_letter:
ld hl, guesses
ld hl, guess_attempts
ld a, [current_guess]
ld b, 5
call multiply_ab
@ -243,14 +262,17 @@ select_letter:
; Delete the last letter
; Delete the last entered letter
; <- [current_guess]
; <- [current_char]
; <- [guess_attempts]
delete_letter:
push hl
push af
push bc
push de
ld hl, guesses
ld hl, guess_attempts
ld a, [current_guess]
ld b, 5
call multiply_ab
@ -280,6 +302,9 @@ delete_letter:
; Update the object data for the alphabet cursor
; -> [selected_letter_x]
; -> [selected_letter_y]
; <- [obj_selected_letter]
update_cursor_objects:
ld hl, obj_selected_letter

17
inc/interrupts.asm

@ -1,4 +1,12 @@
; Interrupt handler for the vertical blanking
;; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
;; █▄░▄█░▄▄▀█▄░▄█░▄▄█░▄▄▀█░▄▄▀█░██░█▀▄▄▀█▄░▄█░▄▄██
;; ██░██░██░██░██░▄▄█░▀▀▄█░▀▀▄█░██░█░▀▀░██░██▄▄▀██
;; █▀░▀█▄██▄██▄██▄▄▄█▄█▄▄█▄█▄▄██▄▄▄█░█████▄██▄▄▄██
;; ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
;; Handle hardware interrupts
; Interrupt handler for the vertical blanking period
int_vblank:
push af
ld a, 1
@ -11,7 +19,10 @@ int_vblank:
; Interrupt handler for the timer
;
; Getting random numbers is hard, here we read the vertical position of the scanline,
; save them periodically and use them to get something.
; save them periodically and use them to get something. This approach works,
; but is not unbalanced because the y position only goes from 0 to 153.
; To get around this, two values are stored as in a shift register
; and not all bits of this are used when determining the random number.
int_timer:
push hl
push af
@ -26,7 +37,7 @@ int_timer:
ld hl, LCD_POSITION_REGISTER
ld a, [hl]
; save the new number
; save the old and new number
ld hl, random_number
ld [hl+], a
ld a, d

8
inc/math.asm

@ -1,3 +1,11 @@
;; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
;; ██░▄▀▄░█░▄▄▀█▄░▄█░█████
;; ██░█░█░█░▀▀░██░██░▄▄░██
;; ██░███░█▄██▄██▄██▄██▄██
;; ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
;; Mathematical functions
; Multiply a and b
; -> a, b
; <- a

38
inc/messages.asm

@ -1,4 +1,16 @@
;; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
;; ██░▄▀▄░█░▄▄█░▄▄█░▄▄█░▄▄▀█░▄▄▄█░▄▄█░▄▄██
;; ██░█░█░█░▄▄█▄▄▀█▄▄▀█░▀▀░█░█▄▀█░▄▄█▄▄▀██
;; ██░███░█▄▄▄█▄▄▄█▄▄▄█▄██▄█▄▄▄▄█▄▄▄█▄▄▄██
;; ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
;; To give the user specific information, short messages could be displayed.
;; These are either permanent or can be provided with a timer.
;; Some of these messages are also used for highlighting in the menu.
; Highlights the menu entry "start game"
; <- de: message address
; <- b: message timeout
show_message_menu_start:
ld de, message_menu_start
ld b, 0
@ -8,6 +20,8 @@ show_message_menu_start:
; Highlights the menu entry "how it works"
; <- de: message address
; <- b: message timeout
show_message_menu_help:
ld de, message_menu_help
ld b, 0
@ -17,15 +31,19 @@ show_message_menu_help:
; Inform that the current guess is not in the dictionary
; <- de: message address
; <- b: message timeout
show_message_unknown:
ld de, message_unknown
ld b, 180
ld b, 180 ; three seconds
call show_message
ret
; Inform about the users victory
; <- de: message address
; <- b: message timeout
show_message_won:
ld de, message_won
ld b, 0
@ -35,6 +53,8 @@ show_message_won:
; Inform about the users loss
; <- de: message address
; <- b: message timeout
show_message_lost:
ld de, message_lost
ld b, 0
@ -57,9 +77,11 @@ ENDR
; Displays a message to the user
; <- de
; <- b
; Generic function to present a message to the user
; -> de: message address
; -> b: message timeout
; <- [obj_message_letters]
; <- [message_timeout]
show_message:
push hl
push bc
@ -85,7 +107,10 @@ show_message:
; Checks if the current message has expired
; Checks if the current message has expired and removes it when necessary
; -> [message_timeout]
; <- [message_timeout]
; <- [obj_message_letters]
check_message_timeout:
ld a, [message_timeout]
cp a, 0
@ -103,7 +128,8 @@ check_message_timeout:
; Clears the current message by overwriting with nothing
; Clears the current message by overwriting it with NULL values
; <- [obj_message_letters]
clear_message:
ld de, message_clear
ld b, 0

22
inc/oam.asm

@ -1,18 +1,32 @@
; Fill the whole oam copy with zero to prevent artifacts
;; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
;; ██░▄▄▄░█░▄▄▀███▄█░▄▄█▀▄▀█▄░▄███░▄▄▀█▄░▄█▄░▄█░▄▄▀██▄██░▄▄▀█░██░█▄░▄█░▄▄████░▄▀▄░█░▄▄█░▄▀▄░█▀▄▄▀█░▄▄▀█░██░██
;; ██░███░█░▄▄▀███░█░▄▄█░█▀██░████░▀▀░██░███░██░▀▀▄██░▄█░▄▄▀█░██░██░██░▄▄████░█░█░█░▄▄█░█▄█░█░██░█░▀▀▄█░▀▀░██
;; ██░▀▀▀░█▄▄▄▄█░▀░█▄▄▄██▄███▄████░██░██▄███▄██▄█▄▄█▄▄▄█▄▄▄▄██▄▄▄██▄██▄▄▄████░███░█▄▄▄█▄███▄██▄▄██▄█▄▄█▀▀▀▄██
;; ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
;; Contains functions to manage the object attribute memory (OAM).
;; We do not write to this area directly, but use a copy in the working memory.
;; During a v-blank, the OAM is updated using direct memory access (DMA).
;; Note: the Game Boy can only handle a total of 40 objects, and only 10 can be displayed on a line.
; Fill the whole OAM copy with zero to prevent artifacts
; <- [objects]
init_oam_copy:
ld b, 160
ld a, 0
ld hl, obj_start
.zero_loop
.copy_loop
ld [hl+], a
dec b
jp nz, .zero_loop
jp nz, .copy_loop
ret
; Write into OAM via DMA
; Update the OAM via DMA
; -> [objects]
; <- [OAM]
update_oam:
; start DMA
ld a, $c0

113
inc/search.asm

@ -1,113 +0,0 @@
; Compresses the current guess to the same format as the dictionary
; <- bc
; <- de
compress_guess:
call get_guess_offset
; first byte
ld a, [hl+] ; letter 1
and %00111111
sla a
sla a
ld b, a
ld a, [hl] ; letter 2
and %00110000
sra a
sra a
sra a
sra a
add a, b
ld d, a
; second byte
ld a, [hl+] ; letter 2
and %00001111
sla a
sla a
sla a
sla a
ld b, a
ld a, [hl] ; letter 3
and %00111100
sra a
sra a
add a, b
ld e, a
push de
; third byte
ld a, [hl+] ; letter 3
and %00000011
sla a
sla a
sla a
sla a
sla a
sla a
ld b, a
ld a, [hl+] ; letter 4
and %00111111
add a, b
ld d, a
; fourth byte
ld a, [hl] ; letter 5
and %00111111
sla a
sla a
ld e, a
pop bc
ret
; Try to find the guess within the dictionary
; -> bc
; -> de
; <- a
find_guess:
ld hl, dictionary
.loop:
; check if the end of the dictionary is reached
push bc
ld bc, dictionary_end
ld a, h
cp a, b
jp nz, .not_eof
ld a, l
cp a, c
jp nz, .not_eof
pop bc
jp .return
.not_eof:
pop bc
ld a, [hl+]
cp a, b
jp nz, .add3
ld a, [hl+]
cp a, c
jp nz, .add2
ld a, [hl+]
cp a, d
jp nz, .add1
ld a, [hl+]
cp a, e
jp nz, .add0
ld a, 1
ret
.add3:
inc hl
.add2:
inc hl
.add1:
inc hl
.add0:
jp .loop
.return:
ld a, 0
ret

22
inc/wincondition.asm

@ -1,4 +1,17 @@
; Check if the guess is valid
;; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
;; ██░███░██▄██░▄▄▀███▀▄▀█▀▄▄▀█░▄▄▀█░▄▀██▄██▄░▄██▄██▀▄▄▀█░▄▄▀██
;; ██░█░█░██░▄█░██░███░█▀█░██░█░██░█░█░██░▄██░███░▄█░██░█░██░██
;; ██▄▀▄▀▄█▄▄▄█▄██▄████▄███▄▄██▄██▄█▄▄██▄▄▄██▄██▄▄▄██▄▄██▄██▄██
;; ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
;; Functions to be able to detect the end of the game.
; Check if the guess attempt is valid
; -> [guess]
; -> [current_guess]
; -> [current_char]
; -> [obj_message]
; <- a: whether the test is valid or not
check_guess:
push hl
push af
@ -14,7 +27,6 @@ check_guess:
; and then search after it.
call compress_guess
call find_guess
cp a, 1
jp nz, .is_invalid
@ -56,8 +68,10 @@ check_guess:
; Check the win conditions
; <- a
; Check if guessed correctly and therefore won
; -> [current_word]
; -> [current_guess]
; <- a: whether it is correct or not
check_win:
call get_guess_offset
ld bc, current_word

1
maps/window-game.asm

@ -1,4 +1,5 @@
; Window map while in game
DB $1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b
DB $1b,$1b,$01,$1b,$02,$1b,$03,$1b,$04,$1b,$05,$1b,$06,$1b,$07,$1b,$08,$1b,$09,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b
DB $1b,$1b,$0a,$1b,$0b,$1b,$0c,$1b,$0d,$00,$0e,$1b,$0f,$1b,$10,$1b,$11,$1b,$12,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b
DB $1b,$1b,$13,$1b,$14,$1b,$15,$1b,$16,$1b,$17,$1b,$18,$1b,$19,$1b,$1a,$1b,$1e,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b

8
maps/window-help.asm

@ -1,6 +1,6 @@
; Window map while within help
DB $1b,$1b,$13,$14,$01,$12,$14,$1b,$1b,$0e,$05,$17,$1b,$07,$01,$0d,$05,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b
DB $1b,$1b,$13,$05,$0c,$05,$03,$14,$1b,$03,$08,$05,$03,$0b,$1b,$17,$0f,$12,$04,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b
DB $1b,$1b,$02,$22,$01,$1b,$1b,$1b,$1b,$04,$05,$22,$13,$05,$0c,$05,$03,$14,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b
DB $1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b
DB $1b,$1b,$13,$14,$01,$12,$14,$1b,$22,$0e,$05,$17,$1b,$07,$01,$0d,$05,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b
DB $1b,$1b,$13,$05,$0c,$05,$03,$14,$22,$03,$08,$05,$03,$0b,$1b,$17,$0f,$12,$04,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b
DB $1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b
DB $1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b,$1b

17
tiles/sign-arrow.asm

@ -0,0 +1,17 @@
; Slash
DB %00000000, \
%00000000, \
%00000000, \
%00000000, \
%00001000, \
%00001000, \
%00000100, \
%00000100, \
%01111110, \
%01111110, \
%00000100, \
%00000100, \
%00001000, \
%00001000, \
%00000000, \
%00000000

17
tiles/sign-slash.asm

@ -1,17 +0,0 @@
; Slash
DB %00000000, \
%00000000, \
%00000010, \
%00000010, \
%00000100, \
%00000100, \
%00001000, \
%00001000, \
%00010000, \
%00010000, \
%00100000, \
%00100000, \
%01000000, \
%01000000, \
%10000000, \
%10000000, \

121
wordle.asm

@ -1,5 +1,15 @@
;; ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
;; ██░███░█▀▄▄▀█░▄▄▀█░▄▀█░██░▄▄██
;; ██░█░█░█░██░█░▀▀▄█░█░█░██░▄▄██
;; ██▄▀▄▀▄██▄▄██▄█▄▄█▄▄██▄▄█▄▄▄██
;; ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
;; The famous word puzzle
; Include hardware-specific constants
; used all over the code.
include "inc/constants.asm"
;; Game specific constants
; Game states
STATE_MENU EQU %00000001
@ -22,28 +32,28 @@ TILE_ENTER EQU $1e
TILE_RIGHT EQU $1f
TILE_MISPLACED EQU $20
TILE_WRONG EQU $21
TILE_SLASH EQU $22
TILE_ARROW EQU $22
; Vertical blanking interrupt starting address
SECTION "ENTRY_VBLANK", ROM0[$0040]
jp int_vblank
jp int_vblank ; used for game logic updates
; LCDC status interrupt starting address
SECTION "ENTRY_LCDCS", ROM0[$0048]
reti
reti ; we don't use this interrupt
; Timer overflow interrupt starting address
SECTION "ENTRY_TIMER", ROM0[$0050]
jp int_timer
jp int_timer ; used for generating randomness
; Serial transfer completion interrupt starting address
SECTION "ENTRY_SERIAL", ROM0[$0058]
reti
reti ; we don't use this interrupt
; Program starting address
@ -54,7 +64,8 @@ SECTION "ENTRY_START", ROM0[$0100]
SECTION "MAIN", ROM0[$0150]
main:
; turn the screen off until everything is initialised
; turn the screen off until everything is initialized,
; or wild glitches will appear
ld a, DISPLAY_OFF
ld [LCD_CONTROL_REGISTER], a
ld [LCD_STATUS_REGISTER], a
@ -99,7 +110,6 @@ main_loop:
.within_vblank:
call update_oam
call read_input
ld a, [current_state]
.in_menu:
@ -156,7 +166,6 @@ include "inc/dict.asm"
include "inc/input.asm"
include "inc/guess.asm"
include "inc/hints.asm"
include "inc/search.asm"
include "inc/messages.asm"
include "inc/wincondition.asm"
include "inc/interrupts.asm"
@ -165,7 +174,10 @@ include "inc/interrupts.asm"
;; Game data
SECTION "DATA0", ROM0[$1000]
; Tiles
;; Tiles
; Small 8x8 pixel images, which are addressable
; and will be used for sprite maps and objects.
tiles_start:
tile_null:
include "tiles/plain-null.asm"
@ -185,15 +197,16 @@ tile_misplaced:
include "tiles/sign-misplaced.asm"
tiles_wrong:
include "tiles/sign-wrong.asm"
tiles_slash:
include "tiles/sign-slash.asm"
tiles_arrow:
include "tiles/sign-arrow.asm"
tiles_logo:
include "tiles/logo.asm"
tiles_end:
; Maps
;; Maps
; Each map defines a grid of tile addresses,
; which combined will form the desired image.
maps_start:
background:
include "maps/background.asm"
@ -203,13 +216,20 @@ window_game:
include "maps/window-game.asm"
maps_end:
;; Dictionary
; A list of known words to choose from.
dictionary:
incbin "dict/en.dat"
dictionary_end:
DB
; Help data
;; Help data
; The help screen will reuse the normal
; game mechanics to provide a manual
; how to play this game.
; Now following is hard coded-data
; to provide a set of information.
help_word:
DB $12, $09, $07, $08, $14 ; right
@ -230,7 +250,11 @@ help_guess_hints:
DB TILE_WHITE, TILE_WHITE, TILE_WHITE, TILE_WHITE, TILE_WHITE
; Messages
;; Messages
; This game uses a message system to
; provide feedback to the user.
; For this, nine objects are reserved
; and can be used at will.
message_clear:
DB $00, $00, $00, $00
DB $00, $00, $00, $00
@ -265,41 +289,41 @@ message_menu_help:
DB $90, $80, 25, OBJ_ATTR_PALETTE1 ; Y
message_unknown:
DB $74, $38, 21, OBJ_ATTR_PALETTE1 ; U
DB $74, $40, 14, OBJ_ATTR_PALETTE1 ; N
DB $74, $48, 11, OBJ_ATTR_PALETTE1 ; k
DB $74, $50, 14, OBJ_ATTR_PALETTE1 ; N
DB $74, $58, 15, OBJ_ATTR_PALETTE1 ; O
DB $74, $60, 23, OBJ_ATTR_PALETTE1 ; W
DB $74, $68, 14, OBJ_ATTR_PALETTE1 ; N
DB $78, $38, 21, OBJ_ATTR_PALETTE1 ; U
DB $78, $40, 14, OBJ_ATTR_PALETTE1 ; N
DB $78, $48, 11, OBJ_ATTR_PALETTE1 ; k
DB $78, $50, 14, OBJ_ATTR_PALETTE1 ; N
DB $78, $58, 15, OBJ_ATTR_PALETTE1 ; O
DB $78, $60, 23, OBJ_ATTR_PALETTE1 ; W
DB $78, $68, 14, OBJ_ATTR_PALETTE1 ; N
DB $00, $00, 0, 0
DB $00, $00, 0, 0
message_won:
DB $74, $38, 25, OBJ_ATTR_PALETTE1 ; Y
DB $74, $40, 15, OBJ_ATTR_PALETTE1 ; O
DB $74, $48, 21, OBJ_ATTR_PALETTE1 ; U
DB $74, $58, 23, OBJ_ATTR_PALETTE1 ; W
DB $74, $60, 15, OBJ_ATTR_PALETTE1 ; O
DB $74, $68, 14, OBJ_ATTR_PALETTE1 ; N
DB $78, $38, 25, OBJ_ATTR_PALETTE1 ; Y
DB $78, $40, 15, OBJ_ATTR_PALETTE1 ; O
DB $78, $48, 21, OBJ_ATTR_PALETTE1 ; U
DB $78, $58, 23, OBJ_ATTR_PALETTE1 ; W
DB $78, $60, 15, OBJ_ATTR_PALETTE1 ; O
DB $78, $68, 14, OBJ_ATTR_PALETTE1 ; N
DB $00, $00, 0, 0
DB $00, $00, 0, 0
DB $00, $00, 0, 0
message_lost:
DB $74, $30, 9, OBJ_ATTR_PALETTE1 ; I
DB $74, $38, 20, OBJ_ATTR_PALETTE1 ; T
DB $74, $40, 19, OBJ_ATTR_PALETTE1 ; S
DB $74, $50, 0, OBJ_ATTR_PALETTE1 ; guess[0]
DB $74, $58, 0, OBJ_ATTR_PALETTE1 ; guess[1]
DB $74, $60, 0, OBJ_ATTR_PALETTE1 ; guess[2]
DB $74, $68, 0, OBJ_ATTR_PALETTE1 ; guess[3]
DB $74, $70, 0, OBJ_ATTR_PALETTE1 ; guess[4]
DB $78, $30, 9, OBJ_ATTR_PALETTE1 ; I
DB $78, $38, 20, OBJ_ATTR_PALETTE1 ; T
DB $78, $40, 19, OBJ_ATTR_PALETTE1 ; S
DB $78, $50, 0, OBJ_ATTR_PALETTE1 ; guess[0]
DB $78, $58, 0, OBJ_ATTR_PALETTE1 ; guess[1]
DB $78, $60, 0, OBJ_ATTR_PALETTE1 ; guess[2]
DB $78, $68, 0, OBJ_ATTR_PALETTE1 ; guess[3]
DB $78, $70, 0, OBJ_ATTR_PALETTE1 ; guess[4]
DB $00, $00, 0, 0
; Address reservations within the working ram
;; Address reservations within the working ram
SECTION "RAM", WRAM0
; The first 160 Bytes are reserved for a copy
; of the OAM data, which will be updated via DMA.
@ -319,7 +343,7 @@ obj_dma_padding:
vblank_flag:
DB
; Saves the current 16bit random number
; Saves the current random number consisting of 16 bits
random_number:
DS 2
@ -327,19 +351,19 @@ random_number:
input_state:
DB
; Saves the current game state
; Saves the current game state (see STATE constants)
current_state:
DB
; Saves the state in the submenu
; Saves the subordinate state (see STATE constants)
sub_state:
DB
; Number of the current guess
; Number of the current guess (0..5)
current_guess:
DB
; Position within the current guess
; Position within the current guess (0..4)
current_char:
DB
@ -348,21 +372,22 @@ current_word:
DS 5
; The guess attempts
guesses:
guess_attempts:
DS 30
; The corresponding hints
guesses_hints:
guess_hints:
DS 30
; Message timeout
; Message timeout (remaining number of frames)
message_timeout:
DB
; Horizontal position of the letter selection
; Horizontal position of the letter selection within the alphabet grid
selected_letter_x:
DB
; Vertical position of the letter selection
; Vertical position of the letter selection within the alphabet grid
selected_letter_y:
DB

BIN
wordle.gb

Binary file not shown.
Loading…
Cancel
Save