text:
# Go Gopher model
> A 3D model of the Go Gopher mascot. We ❤ Go.

## Why?

The conversation went something like this:

> Walter (Developer): Go uses a Gopher as a mascot. We just want to highlight the language in a project.
>
> David (Animator): Here you go.
>
> Walter (Developer): Whoa that's trick. Why the sweet 3D animation?
>
> David (Animator): Because I can.

This results in this render out of Maya:

[![Go Gopher](http://i.imgur.com/b8xYN39.png)](https://twitter.com/RobotSuperCzar/status/689321594796130305)

Which lead to a render we could use in a slidedeck:

![Go Gopher Render 2](http://i.imgur.com/HSyhawt.jpg)

Which then results in animated jibbily jelly go gophers uniting on Vine:

[![Jibbily jelly go gophers unite!](http://i.imgur.com/asymDsG.gif)](https://vine.co/v/ieumw3M2add)

Which then finally results in this CC0 repo of all the Go Gopher goodness. Enjoy!

Print all the things!
![czrqbrmw0aazbx3](https://user-images.githubusercontent.com/643503/28179069-b1b8a33c-67b5-11e7-9e13-dc32f24883aa.jpg)

mime_type: text/plain


text:
# This Python script requires Pillow to be installed.
# This can be done by writing the following in the command line (Windows) or terminal (Linux/Mac)
# assuming that Python is correctly installed: "python -m pip install Pillow" (without quotes)
# or "python3 -m pip install Pillow" or "pip install Pillow" or "pip3 install Pillow", depending on your setup.

from PIL import Image
from random import shuffle # everyday I'm shufflin'

def convert(im_path, outpath):
    im_path = 'examples/images/sgb_border.png'
    im = Image.open(im_path)
    im = im.convert('RGB')

    outpath = 'examples/images/sgb_border.inc'

    # Inserts element e into list l assuming list never goes beyond a length if m, and if so, returns False.
    def max_insert(l, e, m):
        if len(l) < m:
            l.append(e)
            return True
        else:
            return False

    colors = [] # all the colors in the image. Can be at most 64
    tile_data = [] # max 256 elements
    tile_map = [] # max 1024 elements
    palette_deps = [] # one per tile, which colors need to exist together in one palette
    palette_map = [] # one per tile, which palette each tile uses
    palettes = [] # Max 4 palettes, each containing 16 colors

    for i in range(4):
        palettes.append([0]) # All palettes should contain transparancy

    colors.append( (0,0,0) ) # This color should be first. It's used for transparency.
    tile_data.append( [0]*64 ) # This tile should be first. It's used for transparency.

    # Check dimensions of image
    assert(im.size == (256,256))

    # Loop through image to collect unique colors and palette dependencies
    for iy in range(0, 256, 8):
        for ix in range(0, 256, 8):
            palette_dep = set()

            for block_y in range(iy, iy+8):
                for block_x in range(ix, ix+8):
                    col = im.getpixel((block_x, block_y))
                    if not col in colors:
                        if max_insert(colors, col, 64):
                            pass
                        else:
                            raise ValueError('Too many unique colors in the image')

                    col_index = colors.index(col)
                    palette_dep.add(col_index)
            palette_deps.append(palette_dep)

    # Make this non-deterministic so that, if it fails to build good palettes when it should be possible,
    # you can just try again
    shuffle(palette_deps)

    for pd in palette_deps:
        # First check if any existing palette contains all or some of the colors of this tile
        best = -1
        best_i = -1
        for i,p in enumerate(palettes):
            this_one = 0
            remaining = 0
            for c in pd:
                if c in p:
                    this_one += 1
                else:
                    remaining += 1

            if this_one > best:
                # Check if this one has room for all the remaining colors
                if remaining + len(p) > 16:
                    # Yeah nah
                    continue

                best = this_one
                best_i = i

        if best > -1:
            palette_to_use = best_i
        else:
            # We need to create a new palette
            palette_to_use = len(palettes)
            new_palette = []
            if not max_insert(palettes, new_palette, 4):
                raise RuntimeError("Algorithm failed to fit all the colors into 4 palettes. Try running again or using fewer colors.")

        # Add any colors to the palette that aren't already in there
        for c in pd:
            if not c in palettes[palette_to_use]:
                if not max_insert(palettes[palette_to_use], c, 16):
                    raise RuntimeError("What is this I don't even")

    # Loop through image to collect blocks
    for iy in range(0, 256, 8):
        for ix in range(0, 256, 8):

            # Loop through 8x8 block
            tile = []

            # First find all the colors in the block and see which palette to use
            block_cols = []
            for block_y in range(iy, iy+8):
                for block_x in range(ix, ix+8):
                    col = im.getpixel((block_x, block_y))
                    col_index = colors.index(col)
                    if not col_index in block_cols:
                        block_cols.append(col_index)

            palette_index = -1
            for ip,p in enumerate(palettes):
                works = True
                for c in block_cols:
                    if not c in p:
                        works = False

                if works:
                    palette_index = ip

            assert(palette_index > -1)

            for block_y in range(iy, iy+8):
                for block_x in range(ix, ix+8):
                    col = im.getpixel((block_x, block_y))

                    col_index = colors.index(col)
                    col_index_2 = palettes[palette_index].index(col_index)
                    tile.append(col_index_2)

            if not tile in tile_data:
                if max_insert(tile_data, tile, 256):
                    pass
                else:
                    raise ValueError('Too many unique tiles in the image')

            tile_index = tile_data.index(tile)
            tile_map.append(tile_index)
            palette_map.append(palette_index)

    print("Unique colors used: {} / 64".format(len(colors)))
    print("Unique tiles used: {} / 256".format(len(tile_data)))

    assert(len(tile_map) == 32*32) # Otherwise the image is of incorrect size or something went wrong

    # Pad tile data with zeros
    while len(tile_data) < 256:
        tile_data.append( [0]*64 )

    # Pad colors with zeros
    for p in palettes:
        while len(p) < 16:
            p.append(0)

    s = ';This Super Game Boy border file is generated by sgb_border.py at https://github.com/ahrnbom/gingerbread'

    def newline(s):
        return s + '\n'

    s = newline(s)
    s = newline(s)
    s = newline(s + 'SECTION "SGB Border",ROMX')

    def strformat(p):

        s = "DB"
        for i in range(0,256,8):
            pp = p[i:i+8]
            if pp:
                s += " " + str(int("".join(pp), 2)) + ','

        s = s[:-1]
        return s

    # Converts a tile to the really strange SNES bitplane format
    def convert_to_bitplanes(tile):
        p01 = []
        p23 = []

        for ir in range(8):
            start = 8*ir

            row = [tile[x] for x in range(start, start+8)]
            bincols = []

            for col in row:
                bincol = "{0:b}".format(col)

                while len(bincol) < 4:
                    bincol = '0' + bincol

                if len(bincol) >= 5:
                    print(col, bincol)

                bincols.append(bincol)

            p0 = [x[3] for x in bincols]
            p1 = [x[2] for x in bincols]
            p2 = [x[1] for x in bincols]
            p3 = [x[0] for x in bincols]

            p01.extend(p0)
            p01.extend(p1)
            p23.extend(p2)
            p23.extend(p3)

        return p01, p23

    s = newline(s)
    s = newline(s+'SGB_VRAM_TILEDATA1:')

    for i_tile in range(128):
        tile = tile_data[i_tile]
        p01, p23 = convert_to_bitplanes(tile)
        s = newline(s+strformat(p01))
        s = newline(s+strformat(p23))

    s = newline(s)
    s = newline(s+'SGB_VRAM_TILEDATA2:')

    for i_tile in range(128,256):
        tile = tile_data[i_tile]
        p01, p23 = convert_to_bitplanes(tile)
        s = newline(s+strformat(p01))
        s = newline(s+strformat(p23))

    s = newline(s)
    s = newline(s+'SGB_VRAM_TILEMAP:')

    for t,p in zip(tile_map, palette_map):
        bint = "{0:b}".format(t)

        while len(bint) < 8:
            bint = '0' + bint

        if p == 0:
            s2 = "%00010000"
        elif p == 1:
            s2 = "%00010100"
        elif p == 2:
            s2 = "%00011000"
        elif p == 3:
            s2 = "%00011100"

        s = newline(s+'DB %' + bint + ', ' + s2)

    s = newline(s)
    s = newline(s)

    def col2bits(x):
        x = float(x)/256
        x = int(round(31*x))
        x = "{0:b}".format(x)
        while len(x) < 5:
            x = "0" + x
        return x

    for ip, palette in enumerate(palettes):
        s = newline(s+'; Palette '+str(4+ip))

        palette_colors = [colors[i] for i in palette]
        for c in palette_colors:
            r,g,b = c
            r = col2bits(r)
            g = col2bits(g)
            b = col2bits(b)

            s = newline(s + "DB %" + g[2:] + r + ', %0' + b + g[:2])

        s = newline(s)

    s = newline(s)
    s = newline(s + 'SGB_VRAMTRANS_GBTILEMAP:')

    for iy in range(32):
        line = 'DB '
        start = iy*20

        for ix in range(20):
            x = str(min(start+ix, 255))
            while len(x) < 3:
                x = ' '+x
            line += x + ','

        for ix in range(12):
            line += '  0,'

        line = line[:-1]

        s = newline(s+line)

    with open(outpath, 'w') as f:
        f.write(s)

if __name__ == "__main__":
    import sys

    if len(sys.argv) < 3:
        print("Usage: python sgb_border.py INFILE OUTFILE")
        print("INFILE should be a path to a .png image which fulfills the requirements for an SGB border")
        print("OUTFILE should be a path where a .inc file should be created, which can be included in a Game Boy game written with RGBDS and GingerBread")
    else:
        im_path = sys.argv[1]
        outpath = sys.argv[2]
        convert(im_path, outpath)

repo_name: ahrnbom/gingerbread

repo_language: Assembly


text:
; GingerBread is a kind of standard library for Game Boy games written in assembly
; using the RGBDS system. It intends to provide basic functionality that almost
; every game will need in one form or another. It is meant to be used alongside the
; book "Game Boy Assembly Programming for the Modern Game Developer", available
; here: https://teamlampoil.se/book/gbasmdev.pdf
; The book also works as a form of documentation for this library.

; --- ROM Header ---

; Before importing gingerbread.asm, you can specify the following options to affect the game header
IF !DEF(GAME_NAME)
GAME_NAME EQUS "GINGERBREAD"
ENDC

IF !DEF(GBC_SUPPORT)
H_GBC_CODE EQU $0
ELSE
H_GBC_CODE EQU $80
ENDC

IF !DEF(SGB_SUPPORT)
H_SGB_CODE EQU $0
ELSE
H_SGB_CODE EQU $3
ENDC

IF !DEF(ROM_SIZE)
ROM_SIZE EQU 0
ENDC

IF !DEF(RAM_SIZE)
RAM_SIZE EQU 1
ENDC

SECTION "header",ROM0[$0104]

    ; "Nintendo" logo. If this is modified, the game won't start on a real Gameboy.
    DB $CE,$ED,$66,$66,$CC,$0D,$00,$0B,$03,$73,$00,$83,$00,$0C,$00,$0D
    DB $00,$08,$11,$1F,$88,$89,$00,$0E,$DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
    DB $BB,$BB,$67,$63,$6E,$0E,$EC,$CC,$DD,$DC,$99,$9F,$BB,$B9,$33,$3E

    ; The header, specifying ROM details.
    DB "{GAME_NAME}"          ; $134 - Title of the game, in uppercase ASCII. Should be exactly 15 characters (padded with 0s if necessary)
REPT 15-STRLEN("{GAME_NAME}")
    DB 0
ENDR
    DB 	H_GBC_CODE          ; $143 - GBC functionality (0 for no, $80 for "black cart" and $C0 for GBC only)
    DB 	0,0                 ; $144 - Licensee code (not important)
    DB 	H_SGB_CODE          ; $146 - SGB Support indicator (0 means no support, 3 means there is SGB support in the game)
    DB 	$1B                 ; $147 - Cart type ($1B means MBC5 with RAM and battery save)
    DB 	ROM_SIZE            ; $148 - ROM Size, 0 means 32 kB, 1 means 64 kB and so on up to 2 MB
    DB	RAM_SIZE            ; $149 - RAM Size, 0 means no RAM, 1 means 2 kB, 2 -> 8 kB, 3 -> 32 kB, 4 -> 128 kB
    DB 	1                   ; $14a - Destination code (0 means Japan, 1 mean non-Japan, doesn't matter)
    DB 	$33                 ; $14b - Old licensee code, needs to be $33 for SGB to work
    DB 	0                   ; $14c - Mask ROM version
    DB 	0                   ; $14d - Complement check (important, RGBDS takes care of this)
    DW 	0                   ; $14e - Checksum (not important, RGBDS takes care of this)

; --- Hardware constants ---

; General
ROM_BANK_SWITCH     EQU $2000
SAVEDATA            EQU $0000
MBC5_RAMB           EQU $4000

; Key status
KEY_START   EQU %10000000
KEY_SELECT  EQU %01000000
KEY_B       EQU %00100000
KEY_A       EQU %00010000
KEY_DOWN    EQU %00001000
KEY_UP      EQU %00000100
KEY_LEFT    EQU %00000010
KEY_RIGHT   EQU %00000001

; Graphics palettes (monochrome)
BG_PALETTE       EQU $FF47
SPRITE_PALETTE_1 EQU $FF48
SPRITE_PALETTE_2 EQU $FF49

; GBC palettes
GBC_BG_PALETTE_INDEX      EQU $FF68
GBC_BG_PALETTE            EQU $FF69
GBC_SPRITE_PALETTE_INDEX  EQU $FF6A
GBC_SPRITE_PALETTE        EQU $FF6B

GBC_VRAM_BANK_SWITCH    EQU $FF4F

; Scrolling: Set these to nonzero values to scroll the screen across the 256x256 rendering surface
SCROLL_X         EQU $FF43
SCROLL_Y         EQU $FF42
; They see me scrollin'... They hatin'...

; Memory ranges
TILEDATA_START              EQU $8000 ; up to $97FF
BACKGROUND_MAPDATA_START    EQU $9800 ; up to $9BFF
WINDOW_MAPDATA_START        EQU $9C00 ; up to $9FFF

SAVEDATA_START     EQU $A000 ; up to $BFFF

RAM_START          EQU $C000 ; up to $E000, only write to data after USER_RAM_START as GingerBread uses some RAM before this for sprites etc.
SPRITES_START      EQU $C100 ; up to $C1A0
USER_RAM_START     EQU $C200 ; up to $E000

HRAM_START         EQU $F800 ; up to $FFFE
OAMRAM_START       EQU $FE00 ; up to $FE9F
AUD3WAVERAM_START  EQU $FF30 ; $FF30-$FF3F

DMACODE_START   EQU $FF80
SPRITES_LENGTH  EQU $A0

STATF_LYC     EQU  %01000000 ; LYCEQULY Coincidence (Selectable)
STATF_MODE10  EQU  %00100000 ; Mode 10
STATF_MODE01  EQU  %00010000 ; Mode 01 (V-Blank)
STATF_MODE00  EQU  %00001000 ; Mode 00 (H-Blank)
STATF_LYCF    EQU  %00000100 ; Coincidence Flag
STATF_HB      EQU  %00000000 ; H-Blank
STATF_VB      EQU  %00000001 ; V-Blank
STATF_OAM     EQU  %00000010 ; OAM-RAM is used by system
STATF_LCD     EQU  %00000011 ; Both OAM and VRAM used by system
STATF_BUSY    EQU  %00000010 ; When set, VRAM access is unsafe

rSTAT EQU $FF41

; Interrupts
rIF EQU $FF0F
rIE EQU $FFFF

IEF_HILO    EQU  %00010000 ; Transition from High to Low of Pin number P10-P13
IEF_SERIAL  EQU  %00001000 ; Serial I/O transfer end
IEF_TIMER   EQU  %00000100 ; Timer Overflow
IEF_LCDC    EQU  %00000010 ; LCDC
IEF_VBLANK  EQU  %00000001 ; V-Blank

; LCD stuff
rLCDC EQU $FF40

LCDCF_OFF     EQU  %00000000 ; LCD Control Operation
LCDCF_ON      EQU  %10000000 ; LCD Control Operation
LCDCF_WIN9800 EQU  %00000000 ; Window Tile Map Display Select
LCDCF_WIN9C00 EQU  %01000000 ; Window Tile Map Display Select
LCDCF_WINOFF  EQU  %00000000 ; Window Display
LCDCF_WINON   EQU  %00100000 ; Window Display
LCDCF_BG8800  EQU  %00000000 ; BG & Window Tile Data Select
LCDCF_BG8000  EQU  %00010000 ; BG & Window Tile Data Select
LCDCF_BG9800  EQU  %00000000 ; BG Tile Map Display Select
LCDCF_BG9C00  EQU  %00001000 ; BG Tile Map Display Select
LCDCF_OBJ8    EQU  %00000000 ; OBJ Construction
LCDCF_OBJ16   EQU  %00000100 ; OBJ Construction
LCDCF_OBJOFF  EQU  %00000000 ; OBJ Display
LCDCF_OBJON   EQU  %00000010 ; OBJ Display
LCDCF_BGOFF   EQU  %00000000 ; BG Display
LCDCF_BGON    EQU  %00000001 ; BG Display

; Sound stuff
SOUND_VOLUME    EQU $FF24
SOUND_OUTPUTS   EQU $FF25
SOUND_ONOFF     EQU $FF26

; Channel 1 (square with sweep and enevelope effects)
SOUND_CH1_START     EQU $FF10 ; bit 7: unused, bits 6-4: sweep time, bit 3: sweep frequency increase/decrease, bits 2-0: number of sweep shifts
SOUND_CH1_LENGTH    EQU $FF11 ; bits 7-6: wave duty, bits 5-0: length of sound data
SOUND_CH1_ENVELOPE  EQU $FF12 ; bits 7-4: start value for envelope, bit 3: envelope decrease/increase, bits 2-0: number of envelope sweeps
SOUND_CH1_LOWFREQ   EQU $FF13 ; bits 7-0: lower 8 bits of the sound frequency
SOUND_CH1_HIGHFREQ  EQU $FF14 ; bit 7: restart channel, bit 6: use length, bits 5-3: unused, bits 2-0: highest 3 bits of frequency

; Channel 2 (square with enevelope effect, with no sweep effect)
SOUND_CH2_START     EQU $FF15 ; Not used but you can write zeroes here
SOUND_CH2_LENGTH    EQU $FF16 ; bits 7-6: wave duty, bits 5-0: length of sound data
SOUND_CH2_ENVELOPE  EQU $FF17 ; bits 7-4: start value for envelope, bit 3: envelope decrease/increase, bits 2-0: number of envelope sweeps
SOUND_CH2_LOWFREQ   EQU $FF18 ; bits 7-0: lower 8 bits of the sound frequency
SOUND_CH2_HIGHFREQ  EQU $FF19 ; bit 7: restart channel, bit 6: use length, bits 5-3: unused, bits 2-0: highest 3 bits of frequency

; Channel 3 (custom wave)
SOUND_CH3_START     EQU $FF1A ; bit 7: on/off, bits 6-0: unused
SOUND_CH3_LENGTH    EQU $FF1B ; bits 7-0: length of sound
SOUND_CH3_VOLUME    EQU $FF1C ; bits 6-5: audio volume (%00 is mute, %01 is loudest, %10 is pretty quiet and %11 is very quiet)
SOUND_CH3_LOWFREQ   EQU $FF1D ; bits 7-0: lower 8 bits of the sound frequency
SOUND_CH3_HIGHFREQ  EQU $FF1E ; bit 7: restart channel, bit 6: use length, bits 5-3: unused, bits 2-0: highest 3 bits of frequency

; Channel 4 (noise)
SOUND_CH4_START     EQU $FF1F ; Not used but you can write zeroes here
SOUND_CH4_LENGTH    EQU $FF20 ; bits 5-0: length of sound
SOUND_CH4_ENVELOPE  EQU $FF21 ; bits 7-4: start value for envelope, bit 3: envelope decrease/increase, bits 2-0: number of envelope sweeps
SOUND_CH4_POLY      EQU $FF22 ; bits 7-4: polynomial counter, bit 3: number of steps (15 or 7), bits 2-0: ratio of frequency division (%000 gives highest frequency, %111 the lowest)
SOUND_CH4_OPTIONS   EQU $FF23 ; bit 7: restart channel, bit 6: use length

; Wave table for Channel 3
SOUND_WAVE_TABLE_START EQU $FF30
SOUND_WAVE_TABLE_STOP  EQU $FF3F

; O RLY?
rLY EQU $FF44

rDMA  EQU $FF46

; --- GingerBread RAM variables ---
; GingerBread writes a few variables in RAM between $C100 and $C1FF. Let your own RAM usage start at $C200 to make sure none of your code messes with GingerBread
; $C100 - $C1A0 are used for sprites, so these start after that. The space between $C000-$C1000 is used by GBT-Player.
SECTION "GingerBread RAM variables",WRAM0[$C1A1]
RUNNING_ON_SGB: DS 1
RUNNING_ON_GBC: DS 1

; --- Standard functions ---

SECTION "GingerBreadKeypad",ROM0
; Reads current keypad status, stores into A register, where each bit corresponds to one key being pressed or not
; Keys are in the following order: Start - Select - B - A - Down - Up - Left - Right
; The constants KEY_START etc. corresponds to the values obtained here if only that key is pressed.
; The code is copied from the Gameboy Programming Manual, http://www.chrisantonellis.com/files/gameboy/gb-programming-manual.pdf
ReadKeys:
    push bc

    ; Read D-pad
	ld a, $20
    ld [$FF00], a
    ld a, [$FF00]
    ld a, [$FF00]
    cpl
    and %00001111
    ld b, a

    ; Read buttons (Start, Select, B, A)
    ld a, $10
    ld [$FF00], a
    ld a, [$FF00]
    ld a, [$FF00]
    ld a, [$FF00]
    ld a, [$FF00]
    ld a, [$FF00]
    ld a, [$FF00]
    cpl
    and %00001111

    ; Combine D-pad with buttons, store in B
    swap a
    or b
    ld b, a

    ld a, $30
    ld [$FF00], a

    ; Return the stored result
    ld a, b

    pop bc
    ret

Section "GingerBreadSound",ROM0
; Enables audio on all channels, at maximum output volume.
; Overwrites AF
EnableAudio:
    ld a, %11111111
    ld [SOUND_VOLUME],  a ; Max out the audio volume
    ld [SOUND_OUTPUTS], a ; Output all channels to both left/right speakers (when using headphones)
    ld [SOUND_ONOFF],   a ; Turn audio on

    ret

; Use this if your game doesn't use audio, to save some battery life
; Overwrites AF
DisableAudio:
    xor a
    ld [SOUND_VOLUME],  a ; Turn off the audio volume
    ld [SOUND_OUTPUTS], a ; Output no channels to no left/right speakers (when using headphones)
    ld [SOUND_ONOFF],   a ; Turn audio off

    ret

; HL should point to a table which first contains a DW with either SOUND_CH1_START, SOUND_CH2_START, SOUND_CH3_START or SOUND_CH4_START
; followed by five values to be written to those addresses (see comments by the definitions of those constants)
; Overwrites AF and HL
PlaySoundHL:
    push de

    ; Read channel start into DE
    ld a, [hl+]
    ld e, a
    ld a, [hl+]
    ld d, a

    ; Read data from table and feed into the channel start
    ld a, [hl+]
    ld [de], a
    inc de

    ld a, [hl+]
    ld [de], a
    inc de

    ld a, [hl+]
    ld [de], a
    inc de

    ld a, [hl+]
    ld [de], a
    inc de

    ld a, [hl]
    ld [de], a

    pop de
    ret

Section "GingerBreadMemory",ROM0
WaitForNonBusyLCD: MACRO
    ld  a,[rSTAT]
    and STATF_BUSY
    jr  nz,@-4     ; Jumps up 4 bytes in the code (two lines in this case)
ENDM

WaitForNonBusyLCDSafeA: MACRO
    push af
    WaitForNonBusyLCD
    pop af
ENDM

; Copies data in a way that is safe to use when reading/writing to/from VRAM while LCD is on (but slower than mCopy)
; HL - memory position of the start of the copying source
; DE - memory position of the start of the copying destination
; BC - the number of bytes to be copied
mCopyVRAM:
    inc b
    inc c
    jr  .skip
.loop:
    di
        ; This "WaitForNonBusyLCD" here, along with the disabled interrupts, makes it safe to read/write to/from VRAM when LCD is on
        ; Essentially, we're waiting for the LCD to be non-busy before reading/writing. If we don't do this, we can
        ; read/write when the LCD is busy which results in corrupted data.
        WaitForNonBusyLCD
        ld a, [hl+]
        ld [de], a
    ei
    inc de
.skip:
    dec c
    jr  nz, .loop
    dec b
    jr nz, .loop
    ret

; Copies data in a way that is NOT safe to use when reading/writing to/from VRAM while LCD is on (but faster than mCopyVRAM)
; HL - memory position of the start of the copying source
; DE - memory position of the start of the copying destination
; BC - the number of bytes to be copied
mCopy:
    inc b
    inc c
    jr  .skip
.loop:
    ld a, [hl+]
    ld [de], a
    inc de
.skip:
    dec c
    jr  nz, .loop
    dec b
    jr nz, .loop
    ret

; Sets data to a constant value in a way that is safe to use when writing to VRAM while LCD is on (but slower than mSet)
; A  - constant value to set
; HL - memory position of the start of the copying destination
; BC - the number of bytes to be written
mSetVRAM:
    inc b
    inc c
    jr  .skip
.loop:
    di
        WaitForNonBusyLCDSafeA
        ld [hl+], a
    ei
.skip:
    dec c
    jr  nz, .loop
    dec b
    jr nz, .loop
    ret

; Sets data to a constant value in a way that is NOT safe to use when writing to VRAM while LCD is on (but faster than mSetVRAM)
; A  - constant value to set
; HL - memory position of the start of the copying destination
; BC - the number of bytes to be written
mSet:
    inc b
    inc c
    jr  .skip
.loop:
    ld [hl+], a
.skip:
    dec c
    jr  nz, .loop
    dec b
    jr nz, .loop
    ret

; --- Text and number display ---

; Draws text until a specific end character appears, using X/Y coordinates, which can be a bit slower than RenderTextToEndByPosition
; B - tile number of end character
; C - zero if drawn to background, non-zero if drawn to window
; D - X position
; E - Y position
; HL - address to start of the text to write, make sure it contains the end character somewhere
RenderTextToEnd:
    ; Convert position coordinates to position number
    push hl

    xor a
    ld h, a
    ld l, a
    call XYtoPosition

    ; Put position number at DE and then restore HL
    ld d, h
    ld e, l
    pop hl

    call RenderTextToEndByPosition
    ret

; Draws text until a specific end character appears, using a position number which is faster than RenderTextToEnd if the number is precomputed at compile time
; B - tile number of end character
; C - zero if drawn to background, non-zero if drawn to window
; DE - position number to start writing at
; HL - address to start of the text to write, make sure it contains the end character somewhere
RenderTextToEndByPosition:
    ; For now, HL will store the address to write to, which we'll have to compute
    push hl
    call InitializePositionForBackgroundOrWindow
    add hl, de

    ; Move this address onto DE so we can get the text address back
    ld d, h
    ld e, l
    pop hl

    di

    ; Start writing
    ld a, [hl]
.draw:
    WaitForNonBusyLCDSafeA
    ld [de], a
    inc de

    ; Check if the next character is the end character
    inc hl
    ld a, [hl]
    cp b
    jr nz, .draw

    reti

; Draws text until a certain number of characters have been written, with positions as X/Y coordinates. This might be a bit slow for repeated use every frame.
; B - number of characters to write
; C - drawing to background (0) or window (1)
; D - X position
; E - Y position
; HL - address to start of text to write
RenderTextToLength:
    ; Convert position coordinates to position number
    push hl

    xor a
    ld h, a
    ld l, a
    call XYtoPosition

    ; Put position number at DE and then restore HL
    ld d, h
    ld e, l
    pop hl

    call RenderTextToLengthByPosition
    ret

; Draws text until a certain number of characters have been writtens, with position numbers using the formula pos = x + y*32
; If position numbers are precomputed at compile time, this will execute faster than RenderTextToLength
; B - number of characters to write
; C - drawing to background (zero) or window (non-zero)
; DE - position number
; HL - address to start of text to write
RenderTextToLengthByPosition:
    push hl
    ; For now, HL will store the position to write to
    call InitializePositionForBackgroundOrWindow

    ; Add starting position onto background/window
    add hl, de

    ; Now store this onto DE so we can get the read address back again
    ld d, h
    ld e, l
    pop hl

    di
.draw:
    ; Write characters until B is zero, decreasing it every time
    ld a, [hl+]
    WaitForNonBusyLCDSafeA ; Writing to VRAM needs to be timed
    ld [de], a
    inc de

    dec b
    ; Is B zero? If so we should stop
    ld a, b
    cp 0
    jr z, .finish

    jr .draw

.finish:
    ei
    ret

; Internal function
; Converts X and Y coordinates to a single position number by the formula pos = x + 32*y
; D - X position
; E - Y position
; Output is added onto HL (which may be non-zero initially)
; Overwrites A
XYtoPosition:
    ; Addition of 16-bit numbers require a full other 16-bit number to add. So we use BC for that here
    push bc

    ; Add X-position
    ld c, d
    ld b, 0
    ; Now BC contains the X value as a 16-bit number

    add hl, bc

    ; Add Y-position if y>0
    ld a, e
    cp 0
    jr z, .end

    ld c, e
    ; Each line on the background/window is 32 tiles long, so to convert this to number of lines, we add the Y value 32 times)
    REPT 32
    add hl, bc
    ENDR

.end:
    pop bc
    ret

; Draws two decimal (base 10) numbers, stored in a single 8-bit number (for example $42 would represent 42)
; A - The two numbers
; B - Tile number of 0 (assuming that the rest of the digits follow, precisely in the order 0123456789)
; C - Zero if writing to background, non-zero if writing to window
; D - X position to write
; E - Y position to write
RenderTwoDecimalNumbers:
    push af
    push hl

    ; Reset HL
    xor a
    ld h, a
    ld l, a

    call XYtoPosition

    ; The ByPosition call below expects the position number (now on HL) to be on DE
    ld d, h
    ld e, l

    pop hl
    pop af

    call RenderTwoDecimalNumbersByPosition

    ret

; Internal function
; Sets HL to either the start of background map data or window map data, depending on C
; C - zero for background, non-zero for window
InitializePositionForBackgroundOrWindow:
    ld a, c
    cp 0
    jr nz, .useWindow

.useBackground:
    ld hl, BACKGROUND_MAPDATA_START
    ret

.useWindow:
    ld hl, WINDOW_MAPDATA_START
    ret

; Draws two decimal (base 10) numbers, stored in a single 8-bit number (for example $42 would represent 42)
; Unlike RenderTwoDecimalNumbers, the position input here is a position number. This executes faster if the number is precomputed
; and is thus recommended if the game displays lots of text and/or numbers every frame.
; A - The two numbers
; B - Tile number of 0 (assuming that the rest of the digits follow, precisely in the order 0123456789)
; C - Zero if writing to background, non-zero if writing to window
; DE - Position number
RenderTwoDecimalNumbersByPosition:
    push hl ; Use HL for temporary storage
    push af ; To store the original two numbers to write

    ; Set HL to base address for background/window depending on value in C
    call InitializePositionForBackgroundOrWindow

.draw:
    ; To get the correct position, we add the position number onto HL
    add hl, de

    pop af

    ; We don't need C anymore so we can use it to temporarily store the two numbers to write
    ld c, a

    ; Get the leftmost number first
    and %11110000
    swap a

    ; Convert to tile number
    add b

    di

    ; Write the number
    WaitForNonBusyLCDSafeA
    ld [hl+], a

    ; Get the rightmost number
    ld a, c
    and %00001111

    ; Convert to tile number
    add b

    ; Write the number
    WaitForNonBusyLCDSafeA
    ld [hl], a

    pop hl
    reti

; Draws four decimal (base 10) numbers, stored in a 16-bit number (for example $1234 would represent 1234)
; HL - The four numbers
; B - Tile number of 0 (assuming that the rest of the digits follow, precisely in the order 0123456789)
; C - Zero if writing to background, non-zero if writing to window
; D - X position to write
; E - Y position to write
RenderFourDecimalNumbers:
    ; Write the leftmost numbers first
    ld a, h

    push bc
    push de
    push hl

    call RenderTwoDecimalNumbers

    pop hl
    pop de
    pop bc

    ; Move "x" two steps to the right
    inc d
    inc d

    ; Then draw the rightmost numbers
    ld a, l
    call RenderTwoDecimalNumbers

    ret

; Draws four decimal (base 10) numbers, stored in a 16-bit number (for example $1234 would represent 1234)
; Unlike RenderFourDecimalNumbers, this function uses position numbers computed by pos = x + 32*y which will be
; faster if this number is precomputed.
; HL - The four numbers
; B - Tile number of 0 (assuming that the rest of the digits follow, precisely in the order 0123456789)
; C - Zero if writing to background, non-zero if writing to window
; DE - Position number of first number
RenderFourDecimalNumbersByPosition:
    ld a, h ; The leftmost two numbers

    push bc
    push de
    push hl

    call RenderTwoDecimalNumbersByPosition

    pop hl
    pop de
    pop bc

    ; Move position two steps to the right
    inc de
    inc de

    ld a, l ; The rightmost two numbers
    call RenderTwoDecimalNumbersByPosition

    ret

; --- Save data ---

; Allows save data to become accessible to read and write. Note that save data is disabled by default. It also must be supported by
; your cartidge and game header for this to work.
EnableSaveData:
    ld a, $0A
    ld [SAVEDATA], a

    ret

; Disables save data. Do this as soon as you are done using SRAM, to prevent data loss in case of a crash.
DisableSaveData:
    xor a
    ld [SAVEDATA], a

    ret

; Assuming your game uses MBC5, having different numbers on A (between $00 and $0F) will activate different
; save data banks. Do this before running EnableSaveData.
ChooseSaveDataBank:
    ld [MBC5_RAMB], a

    ret

; --- Game Boy Color functionality ---
IF DEF(GBC_SUPPORT)

SECTION "GBC commands",ROM0

GBCEarlyExit: MACRO
    ld a, [RUNNING_ON_GBC]
    cp 0
    ret z
ENDM

; HL - address pointing to a table of colors
; A - palette byte to start writing at (each palette is eight bytes, two for each of the four colors, and there are eight palettes)
; B - number of bytes to write
GBCApplyBackgroundPalettes:
    ; We use auto-increment for simplicity
    or %10000000

    ld [GBC_BG_PALETTE_INDEX], a

.writeByte:

    ld a, [hl]
    ld [GBC_BG_PALETTE], a
    inc hl

    ; Have we finished writing?
    dec b
    ld a, b
    cp 0 ; Is B equal to zero?
    ret z

    jr .writeByte ; keep going

; HL - address pointing to a table of colors
; A - palette byte to start writing at (each palette is eight bytes, two for each of the four colors, and there are eight palettes)
; B - number of bytes to write
GBCApplySpritePalettes:
    ; We use auto-increment for simplicity
    or %10000000

    ld [GBC_SPRITE_PALETTE_INDEX], a

    di ; To prevent an interrupt for interrupting while we try to write to VRAM in the limited time we have

.writeByte:
    WaitForNonBusyLCD ; Unlike background palettes, sprite palettes can only be written during H-Blank
    ld a, [hl]
    ld [GBC_SPRITE_PALETTE], a
    inc hl

    ; Have we finished writing?
    dec b
    ld a, b
    cp 0 ; Is B equal to zero?
    jr z, .finish

    jr .writeByte ; keep going

.finish:
    ei
    ret

ENDC ; End of GBC functionality

; --- Super Game Boy functionality ---
IF DEF(SGB_SUPPORT)

SECTION "SGB Messages",ROMX,BANK[1]
SGB_OUT_ADDRESS EQU $FF00

SGB_SEND_ZERO   EQU %00100000
SGB_SEND_ONE    EQU %00010000
SGB_SEND_RESET  EQU %00000000
SGB_SEND_NULL   EQU %00110000

SGB_FREEZE:
DB %10111001    ; MASK_EN command, length one
DB 1            ; Freeze current image
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0

SGB_UNFREEZE:
DB %10111001    ; MASK_EN command, length one
DB 0            ; Unfreeze
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0

SGB_MLTREQ1:
DB %10001001
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0

SGB_MLTREQ2:
DB %10001001
DB 1
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0

SGB_VRAMTRANS_TILEDATA1:
DB %10011001	; CHR_TRN, length one
DB 0 			; lower tiles (we can have another set of 128 tiles by setting this to one)
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0

SGB_VRAMTRANS_TILEDATA2:
DB %10011001	; CHR_TRN, length one
DB 1 			; upper tiles
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0

SGB_VRAMTRANS_TILEMAP:
DB %10100001	; PCT_TRN, length one
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0
DB 0

; According to some sources, these commands should be sent to the SGB as an initialization. It doesn't seem mandatory though?
SGB_INIT1:
DB $79,$5D,$08,$00,$0B,$8C,$D0,$F4,$60,$00,$00,$00,$00,$00,$00,$00
SGB_INIT2:
DB $79,$52,$08,$00,$0B,$A9,$E7,$9F,$01,$C0,$7E,$E8,$E8,$E8,$E8,$E0
SGB_INIT3:
DB $79,$47,$08,$00,$0B,$C4,$D0,$16,$A5,$CB,$C9,$05,$D0,$10,$A2,$28
SGB_INIT4:
DB $79,$3C,$08,$00,$0B,$F0,$12,$A5,$C9,$C9,$C8,$D0,$1C,$A5,$CA,$C9
SGB_INIT5:
DB $79,$31,$08,$00,$0B,$0C,$A5,$CA,$C9,$7E,$D0,$06,$A5,$CB,$C9,$7E
SGB_INIT6:
DB $79,$26,$08,$00,$0B,$39,$CD,$48,$0C,$D0,$34,$A5,$C9,$C9,$80,$D0
SGB_INIT7:
DB $79,$1B,$08,$00,$0B,$EA,$EA,$EA,$EA,$EA,$A9,$01,$CD,$4F,$0C,$D0
SGB_INIT8:
DB $79,$10,$08,$00,$0B,$4C,$20,$08,$EA,$EA,$EA,$EA,$EA,$60,$EA,$EA

SECTION "SGB commands on bank0",ROM0
SGBAbsolutelyFirstInit:
    call SGBStrangeInit
    ret

CheckIfSGB:
    call CheckSGB
    jr nc, .CISGB_notSGB

    ; If we get here, then we are running SGB
    ld a, 1
    ld [RUNNING_ON_SGB], a
    jr .CISGB_end

.CISGB_notSGB
    xor a
    ld [RUNNING_ON_SGB], a

    jr .CISGB_end

.CISGB_end
    ret

SGBEarlyExit: MACRO
    ld a, [RUNNING_ON_SGB]
    cp 0
    ret z
ENDM

SECTION "SGB commands on bank1",ROMX,BANK[1]

SGBStrangeInit:
    ld hl, SGB_INIT1
    call SGBSendData

    ld hl, SGB_INIT2
    call SGBSendData

    ld hl, SGB_INIT3
    call SGBSendData

    ld hl, SGB_INIT4
    call SGBSendData

    ld hl, SGB_INIT5
    call SGBSendData

    ld hl, SGB_INIT6
    call SGBSendData

    ld hl, SGB_INIT7
    call SGBSendData

    ld hl, SGB_INIT8
    call SGBSendData

    ret

SGBBorderTransferMacro: MACRO
    di
    call StopLCD

    ld hl, \1
    ld de, TILEDATA_START
    ld bc, 4096
    call mCopyVRAM

    ld hl, \3
    ld de, BACKGROUND_MAPDATA_START
    ld bc, 32*32
    call mCopyVRAM

    call StartLCD

    halt

    ld hl, \2
    call SGBSendData

    ei

REPT 5
    halt
ENDR

ENDM

SGBFreeze:
    ld hl, SGB_FREEZE
    call SGBSendData
    ret

SGBUnfreeze:
    ld hl, SGB_UNFREEZE
    call SGBSendData
    ret

; Input: HL - address to first byte to send
SGBSendData:
    di
    ; Register use:
    ; B - Byte currently sending
    ; C - Total number of bytes to send
    ; D - Number of bits sent of current byte

    ld a, [hl]
    ld b, a

    ; Each packet should send 16 bytes
    ld c, 16

    xor a
    ld d, a

    ; Prepare SGB for listening
    ld a, SGB_SEND_RESET
    ld [SGB_OUT_ADDRESS], a

    ld a, SGB_SEND_NULL
    ld [SGB_OUT_ADDRESS], a

SGBSendBit:
    inc d
    ld a, d
    cp 9
    jr z, SGBEndOfByte

    ld a, b
    and %00000001
    cp 0
    jr z, SGBSendZeroBit

    ; Send a ONE bit here
    ld a, SGB_SEND_ONE
    ld [SGB_OUT_ADDRESS], a
    jr SGBSendBitEnd

SGBSendZeroBit:
    ld a, SGB_SEND_ZERO
    ld [SGB_OUT_ADDRESS], a

SGBSendBitEnd:
    ; Both P14 and P15 should be HIGH in between sent bits
    ld a, SGB_SEND_NULL
    ld [SGB_OUT_ADDRESS], a

    ld a, b
    sra a
    and %01111111
    ld b, a

    jr SGBSendBit

SGBEndOfByte:
    dec c
    ld a, c
    cp 0
    jr z, SGBFinalEnd

    ; If there are still bytes to send, we get here
    inc hl
    ld a, [hl]
    ld b, a

    xor a
    ld d, a

    jr SGBSendBit

SGBFinalEnd:
    call SGBFinish
    ret

SGBFinish:
    ld a, SGB_SEND_ZERO
    ld [SGB_OUT_ADDRESS], a

    ld a, SGB_SEND_NULL
    ld [SGB_OUT_ADDRESS], a

    ei
    call Wait7000
    ret

Wait7000:
    ld de, 7000 ; Each loop takes 9 cycles so this routine actually waits 63000 cycles.
.loop
	nop
	nop
	nop
	dec de
	ld a, d
	or e
	jr nz, .loop
	ret

CheckSGB:
; Returns whether the game is running on an SGB in carry.
	ld hl, SGB_MLTREQ2
	call SGBSendData
	di
	ld a, 1
	ld [$FFF9], a
	ei
	call Wait7000
	ld a, [SGB_OUT_ADDRESS]
	and $3
	cp $3
	jr nz, .isSGB
	ld a, $20
	ld [SGB_OUT_ADDRESS], a
	ld a, [SGB_OUT_ADDRESS]
	ld a, [SGB_OUT_ADDRESS]
	call Wait7000
	call Wait7000
	ld a, $30
	ld [SGB_OUT_ADDRESS], a
	call Wait7000
	call Wait7000
	ld a, $10
	ld [SGB_OUT_ADDRESS], a
	ld a, [SGB_OUT_ADDRESS]
	ld a, [SGB_OUT_ADDRESS]
	ld a, [SGB_OUT_ADDRESS]
	ld a, [SGB_OUT_ADDRESS]
	ld a, [SGB_OUT_ADDRESS]
	ld a, [SGB_OUT_ADDRESS]
	call Wait7000
	call Wait7000
	ld a, $30
	ld [SGB_OUT_ADDRESS], a
	ld a, [SGB_OUT_ADDRESS]
	ld a, [SGB_OUT_ADDRESS]
	ld a, [SGB_OUT_ADDRESS]
	call Wait7000
	call Wait7000
	ld a, [SGB_OUT_ADDRESS]
	and $3
	cp $3
	jr nz, .isSGB
	call SendMltReq1Packet
	and a
	ret
.isSGB
	call SendMltReq1Packet
	scf
	ret

SendMltReq1Packet:
    ld hl, SGB_MLTREQ1
    call SGBSendData
    jp Wait7000

ENDC ; End of Super Game Boy functionality

; --- Boot process and interrupts ---
; Feel free to change interrupts if your game should use them

; Interrupts
SECTION	"vblank interrupt",ROM0[$0040]
    jp	DMACODE_START ; sprites should be updated on every vblank
SECTION	"LCDC interrupt",ROM0[$0048]
    reti
SECTION	"Timer overflow interrupt",ROM0[$0050]
    reti
SECTION	"Serial interrupt",ROM0[$0058]
    reti
SECTION	"p1234 interrupt",ROM0[$0060]
    reti

; These are the first lines the boot loader will run.
SECTION	"GingerBread start",ROM0[$0100]
    nop

IF DEF(GBC_SUPPORT)
    jp CheckIfGBC
    ; This jumps to GingerBreadBegin
ELSE
    jp GingerBreadBegin
ENDC

SECTION "GingerBread Technical stuff, DMA and stop/start LCD",ROM0
initdma:
	ld	de, DMACODE_START
	ld	hl, dmacode
	ld	bc, dmaend-dmacode
	call mCopyVRAM
	ret
dmacode:
	push	af
	ld	a, SPRITES_START/$100 ; When doing DMA, the address is given as an 8-bit number, to be multiplied by $100
	ldh	[rDMA], a
	ld	a, $28
dma_wait:
	dec	a
	jr	nz, dma_wait
	pop	af
	reti
dmaend:
    nop

StopLCD:
    ld a, [rLCDC]
    rlca
    ret nc ; In this case, the LCD is already off

.wait:
    ld a,[rLY]
    cp 145
    jr nz, .wait

    ld  a, [rLCDC]
    res 7, a
    ld  [rLCDC], a

    ret

StartLCD:
    ; Turns on LCD with reasonable settings (with 8x16 sprites!)
    ; It makes the background map be at $9800-$9BFF, while the window (which is off) be at $9C00-9FFF, which
    ; is consistent with the definitions of BACKGROUND_MAPDATA_START and WINDOW_MAPDATA_START
    ld	a, LCDCF_ON|LCDCF_BG8000|LCDCF_BG9800|LCDCF_BGON|LCDCF_OBJ16|LCDCF_OBJON|LCDCF_WIN9C00|LCDCF_WINOFF
	ld	[rLCDC], a
    ret

TurnOnWindow:
    ; Same as StartLCD except the window is on. Turn it off by calling StartLCD (which doesn't hurt calling even when the LCD is already on)
    ld	a, LCDCF_ON|LCDCF_BG8000|LCDCF_BG9800|LCDCF_BGON|LCDCF_OBJ16|LCDCF_OBJON|LCDCF_WIN9C00|LCDCF_WINON
	ld	[rLCDC], a
    ret

SECTION "GingerBread boot",ROMX

IF DEF(GBC_SUPPORT)
CheckIfGBC:
    cp $11
    jr nz, .notGBC

    ; If we get here, we are running on a GBC (or GBA)
.isGBC:
    ld a, 1
    ld [RUNNING_ON_GBC], a
    jr GingerBreadBegin

.notGBC:
    xor a
    ld [RUNNING_ON_GBC], a
    jr GingerBreadBegin
ENDC

; This function is called right at the start of the game. Calling or jumping to this function later should be equivalent to resetting the game.
; It resets RAM and various graphical settings.
GingerBreadBegin:
    nop
    di

    ; Initialize stack pointer
    ld	sp, $ffff

    ; Reset RAM
    ld hl, USER_RAM_START
    ld bc, $0FFF
    xor a
    call mSet

IF DEF(SGB_SUPPORT)
    call SGBAbsolutelyFirstInit
    call CheckIfSGB
ENDC

    ; Initialize display
    call StopLCD
    call initdma

    ld	a, IEF_VBLANK ; We only want vblank interrupts (for updating sprites)
    ld	[rIE], a

    ei

    ; Reset VRAM
    ld hl, TILEDATA_START
    ld bc, $1FFF
    xor a
    call mSet

    ; Set default palettes
    ld a, %11100100
    ld [BG_PALETTE], a
    ld [SPRITE_PALETTE_1], a
    ld [SPRITE_PALETTE_2], a

    ; Reset sprites
    ld   hl, SPRITES_START
    ld   bc, SPRITES_LENGTH
    xor a
    call mSet

    ; Set background position (no scrolling)
    xor a
    ld [SCROLL_X], a
    ld [SCROLL_Y], a

    jp begin ; GingerBread assumes that your game has this label somewhere where your own code should start


text:
# GingerBread
A library with some standard functions for making your own Game Boy games in Assembly, written from scratch. It was made alongside the book *Game Boy Assembly Programming for the Modern Game Developer* which [can be downloaded here](https://github.com/ahrnbom/gbapfomgd).

## Functionality
The GingerBread library attempts to contain a lot of basic functionality needed for most Game Boy games. It takes care of things like

1. Defining various constants that are easier to remember than random memory addresses
1. Defining several low-level function that most games would need, like
    * Reading key input
    * Displaying graphics
    * Writing text and numbers
    * Playing sound effects
    * Super Game Boy and Game Boy Color functionality
    * Managing save data
1. Taking care of the boot process
1. Defining the ROM header

The idea is to reduce the amount of boilerplate code needed to get started with Game Boy game development in Assembly. The book contains lots of examples and also serves as documentation for this library.

The library does not directly support playing background music. Instead, it is designed to be used alongside [GBT-Player](https://github.com/AntonioND/gbt-player) which is included as a git submodule. The library can also be used without GBT-Player.

## Requirements
[RGBDS](https://github.com/rednex/rgbds) is necessary to compile GingerBread and its example(s).

## To do
1. Make a stand-alone documentation document, specifying the constants and functions defined by GingerBread. Most of it is in the book, but having it available stand-alone is probably useful to many.

## Why make this?
During the development of [Rope & Bombs](http://teamlampoil.se), the lack of any cohesive and complete introduction to Game Boy development was a major hassle. To save others from having to figure out themselves like we did, [the book](https://github.com/ahrnbom/gbapfomgd) tries to explain things in a way that is (hopefully) easy to understand for people more used to modern programming.

Unfortunately, the amount of material needed to explain every necessary aspect of Game Boy development from the ground up would be so large that the book simply could not be finished. In order to relieve the book from having to explain absolutely *everything*, the GingerBread library was introduced which contains a bunch of boilerplate code. That way, new Game Boy developers can start coding their game much faster, while focusing on learning how to express their game logic, as opposed to starting with having to figure out how to write their ROM header (while simultaneously having to figure out what that is and why you need one).

We hope that, by introducing things in order of relevance, Game Boy development will be much more approachable. Once a developer has learned the basics, they may find that they no longer need, or no longer *want*, to use GingerBread and would rather code everything from scratch. If somebody reaches that point using our work, then we consider that a success.

### Legal stuff
The library is released under the The Unlicense. It basically means you are allowed to use it for whatever purpose.

This library is written with no association or cooperation with Nintendo. The Game Boy is a trademark of Nintendo.


text:
; This section is for including files that either need to be in the home section, or files where it doesn't matter
SECTION "Includes@home",ROM0

; Prior to importing GingerBread, some options can be specified

; Max 15 characters, should be uppercase ASCII
GAME_NAME EQUS "GBEXAMPLE "

; Include SGB support in GingerBread. This makes the GingerBread library take up a bit more space on ROM0. To remove support, comment out this line (don't set it to 0)
SGB_SUPPORT EQU 1

; Include GBC support in GingerBread. This makes the GingerBread library take up slightly more space on ROM0. To remove support, comment out this line (don't set it to 0)
GBC_SUPPORT EQU 1

; Set the size of the ROM file here. 0 means 32 kB, 1 means 64 kB, 2 means 128 kB and so on.
ROM_SIZE EQU 1

; Set the size of save RAM inside the cartridge.
; If printed to real carts, it needs to be small enough to fit.
; 0 means no RAM, 1 means 2 kB, 2 -> 8 kB, 3 -> 32 kB, 4 -> 128 kB
RAM_SIZE EQU 1

INCLUDE "gingerbread.asm"

; To compile this game without gbt-player, comment out this line (don't set it to 0)
USE_GBT_PLAYER EQU 1

; This section is for including files that need to be in data banks
SECTION "Include@banks",ROMX
INCLUDE "images/title.inc"
INCLUDE "images/pong.inc"
INCLUDE "images/sgb_border.inc"

IF DEF(USE_GBT_PLAYER)
INCLUDE "gbt_player.inc"
ENDC

; Macro for copying a rectangular region into VRAM
; Changes ALL registers
; Arguments:
; 1 - Height (number of rows)
; 2 - Width (number of columns)
; 3 - Source to copy from
; 4 - Destination to copy to
CopyRegionToVRAM: MACRO

I SET 0
REPT \1

    ld bc, \2
    ld hl, \3+(I*\2)
    ld de, \4+(I*32)

    call mCopyVRAM

I SET I+1
ENDR
ENDM

SECTION "StartOfGameCode",ROM0
begin: ; GingerBread assumes that the label "begin" is where the game should start

    call SetupSGB
    call SetupGBC

    ; Load title image into VRAM

    ; We need to switch bank to whatever bank contains the tile data
    ld a, BANK(title_tile_data)
    ld [ROM_BANK_SWITCH], a

    ld hl, title_tile_data
    ld de, TILEDATA_START
    ld bc, title_tile_data_size
    call mCopyVRAM

    ld a, BANK(title_map_data)
    ld [ROM_BANK_SWITCH], a

    CopyRegionToVRAM 18, 20, title_map_data, BACKGROUND_MAPDATA_START

    call StartLCD

    ; Everything up to this point is simply an example demo which shows a single image. If you want
    ; to use this as your starting point, remove all lines below.

    call EnableAudio

    call SetupHighScore

IF DEF(USE_GBT_PLAYER)
    ld      de,funkyforest_data
    ld      bc,BANK(funkyforest_data)
    ld      a,$05
    call    gbt_play ; Play song
ENDC

    ld a, 1
    ld [ROM_BANK_SWITCH], a

    jp TitleLoop

; Definition of some RAM variables
SECTION "RAM variables",WRAM0[USER_RAM_START]
BALL_POSITION: DS 2
BALL_DIRECTION: DS 2
LEFT_PADDLE_POSITION: DS 2
RIGHT_PADDLE_POSITION: DS 2
RIGHT_PADDLE_CHECK_TIME: DS 1
RIGHT_PADDLE_DIRECTION: DS 1
LEFT_SCORE: DS 1
RIGHT_SCORE: DS 1

SECTION "SRAM variables",SRAM[SAVEDATA_START]
SRAM_INTEGRITY_CHECK: DS 2 ; Two bytes that should read $1337; if they do not, the save is considered corrupt or unitialized
SRAM_HIGH_SCORE: DS 1

; Definition of some constants
PADDLE_SPEED                    equ 2 ; pixels per frame
RIGHT_PADDLE_CHECK_FREQUENCY    equ 15 ; how many frame should pass between each check if right paddle should move up/down

SECTION "Text definitions",ROM0
; Charmap definition (based on the pong.png image, and looking in the VRAM viewer after loading it in BGB helps finding the values for each character)
CHARMAP "A",$56
CHARMAP "B",$57
CHARMAP "C",$58
CHARMAP "D",$59
CHARMAP "E",$5A
CHARMAP "F",$5B
CHARMAP "G",$5C
CHARMAP "H",$5D
CHARMAP "I",$5E
CHARMAP "J",$5F
CHARMAP "K",$60
CHARMAP "L",$61
CHARMAP "M",$62
CHARMAP "N",$63
CHARMAP "O",$64
CHARMAP "P",$65
CHARMAP "Q",$66
CHARMAP "R",$67
CHARMAP "S",$68
CHARMAP "T",$69
CHARMAP "U",$6A
CHARMAP "V",$6B
CHARMAP "W",$6C
CHARMAP "X",$6D
CHARMAP "Y",$6E
CHARMAP "Z",$6F
CHARMAP "<happy>",$70
CHARMAP "<sad>",$71
CHARMAP "<heart>",$72
CHARMAP " ",$01
CHARMAP "<end>",$0 ; Choose some non-character tile that's easy to remember

; Text definitions
SupportiveText:
DB "WELL DONE <happy> <heart> <end>"

TauntingText:
DB "YOU SUCK LOL <sad><end>"

GameOverText:
DB "GAME OVER LOLOLOLO<end>"

SECTION "Sound effect definitions",ROM0
Sound_ball_bounce:
DW SOUND_CH4_START
DB %00000000 ; Data to be written to SOUND_CH4_START
DB %00000100 ; Data to be written to SOUND_CH4_LENGTH
DB %11110111 ; Data to be written to SOUND_CH4_ENVELOPE
DB %01010101 ; Data to be written to SOUND_CH4_POLY
DB %11000110 ; Data to be written to SOUND_CH4_OPTIONS

SECTION "SGB Palette data",ROMX,BANK[1]
SGBPalettes01: ; Specifies the colors of palettes 0 and 1
DB %00000001 ; PAL01 command (%00000), length one (%001)
DB %11111111 ; Color 0 (for all palettes), %gggrrrrr
DB %01111111 ; Color 0 (for all palettes), %0bbbbbgg
DB %11100001 ; Color 1, Palette 0, %gggrrrrr
DB %01111001 ; Color 1, Palette 0, %0bbbbbgg
DB %01100001 ; Color 2, Palette 0, %gggrrrrr
DB %00110100 ; Color 2, Palette 0, %0bbbbbgg
DB %00000000 ; Color 3, Palette 0, %gggrrrrr
DB %00000000 ; Color 3, Palette 0, %0bbbbbgg
DB %11100111 ; Color 1, Palette 1, %gggrrrrr
DB %01111001 ; Color 1, Palette 1, %0bbbbbgg
DB %11100001 ; Color 2, Palette 1, %gggrrrrr
DB %00011101 ; Color 2, Palette 1, %0bbbbbgg
DB %00000000 ; Color 3, Palette 1, %gggrrrrr
DB %00000000 ; Color 3, Palette 1, %0bbbbbgg
DB 0         ; Not used

SGBPalettes23: ; Specifies the colors of palettes 2 and 3
DB %00001001 ; PAL23 command (%00001), length one (%001)
DB %11111111 ; Color 0 (for all palettes), %gggrrrrr
DB %01111111 ; Color 0 (for all palettes), %0bbbbbgg
DB %10110101 ; Color 1, Palette 2, %gggrrrrr
DB %01010110 ; Color 1, Palette 2, %0bbbbbgg
DB %11100111 ; Color 2, Palette 2, %gggrrrrr
DB %00011100 ; Color 2, Palette 2, %0bbbbbgg
DB %00100001 ; Color 3, Palette 2, %gggrrrrr
DB %00000100 ; Color 3, Palette 2, %0bbbbbgg
DB 0,0,0,0,0,0 ; Colors 1-3, Palette 3 (which isn't used in this example game)

SGBPal2Everywhere: ; Tells the SGB to use Palette 2 everywhere (used for title and game over)
DB %00100001 ; ATTR_BLK (%00100), length one (%001)
DB 1         ; Number of blocks we send
DB %00000100 ; Set the value "outside" the block (doing this with a small block means setting the entire screen)
DB %00101010 ; Which palettes to set inside (%10), on the border (%10) and outside (%10)
DB 0         ; X1 coordinate
DB 0         ; Y1 coordinate
DB 0         ; X2 coordinate
DB 0         ; Y2 coordinate
DB 0,0,0,0,0,0,0,0 ; Zero-padding

SGBPal01Div: ; Tells the SGB to draw the top horizontal line with palette 1, and palette 0 everywhere else
DB %00110001 ; ATTR_DIV command (%00110), length one (%001)
DB %01010100 ; Zero-padding (%0), horizontal split (%1), palette on the line (%01), palette above the line (%01), palette below the line (%00)
DB 0         ; Y-coordinate
DB 0,0,0,0,0,0,0,0,0,0,0,0,0 ; Zero-padding

SECTION "GBC Palette data",ROM0
GBCBackgroundPalettes:
DB %11111111 ; Color 0, palette 0, %gggrrrrr
DB %01111111 ; Color 0, palette 0, %0bbbbbgg
DB %11100011 ; Color 1, palette 0, %gggrrrrr
DB %00011111 ; Color 1, palette 0, %0bbbbbgg
DB %11100001 ; Color 2, palette 0, %gggrrrrr
DB %00001001 ; Color 2, palette 0, %0bbbbbgg
DB %00000000 ; Color 3, palette 0, %gggrrrrr
DB %00000000 ; Color 3, palette 0, %0bbbbbgg
DB %11111111 ; Color 0, palette 1, %gggrrrrr
DB %01111111 ; Color 0, palette 1, %0bbbbbgg
DB %01111111 ; Color 1, palette 1, %gggrrrrr
DB %00001100 ; Color 1, palette 1, %0bbbbbgg
DB %01001111 ; Color 2, palette 1, %gggrrrrr
DB %00000100 ; Color 2, palette 1, %0bbbbbgg
DB %00000011 ; Color 3, palette 1, %gggrrrrr
DB %00000000 ; Color 3, palette 1, %0bbbbbgg

GBCBackgroundPalettesDark:
DB %11100111 ; Color 0, palette 0, %gggrrrrr
DB %00011100 ; Color 0, palette 0, %0bbbbbgg
DB %11100000 ; Color 1, palette 0, %gggrrrrr
DB %00000100 ; Color 1, palette 0, %0bbbbbgg
DB %01100000 ; Color 2, palette 0, %gggrrrrr
DB %00000100 ; Color 2, palette 0, %0bbbbbgg
DB %00000000 ; Color 3, palette 0, %gggrrrrr
DB %00000000 ; Color 3, palette 0, %0bbbbbgg
DB %11100111 ; Color 0, palette 1, %gggrrrrr
DB %00011100 ; Color 0, palette 1, %0bbbbbgg
DB %00000111 ; Color 1, palette 1, %gggrrrrr
DB %00000000 ; Color 1, palette 1, %0bbbbbgg
DB %00000011 ; Color 2, palette 1, %gggrrrrr
DB %00000000 ; Color 2, palette 1, %0bbbbbgg
DB %00000000 ; Color 3, palette 1, %gggrrrrr
DB %00000000 ; Color 3, palette 1, %0bbbbbgg

GBCBackgroundPalettesDarker:
DB %01000010 ; Color 0, palette 0, %gggrrrrr
DB %00001000 ; Color 0, palette 0, %0bbbbbgg
DB %01000000 ; Color 1, palette 0, %gggrrrrr
DB %00000000 ; Color 1, palette 0, %0bbbbbgg
DB %01000000 ; Color 2, palette 0, %gggrrrrr
DB %00000000 ; Color 2, palette 0, %0bbbbbgg
DB %00000000 ; Color 3, palette 0, %gggrrrrr
DB %00000000 ; Color 3, palette 0, %0bbbbbgg
DB %01000010 ; Color 0, palette 1, %gggrrrrr
DB %00001000 ; Color 0, palette 1, %0bbbbbgg
DB %00000010 ; Color 1, palette 1, %gggrrrrr
DB %00000000 ; Color 1, palette 1, %0bbbbbgg
DB %00000000 ; Color 2, palette 1, %gggrrrrr
DB %00000000 ; Color 2, palette 1, %0bbbbbgg
DB %00000000 ; Color 3, palette 1, %gggrrrrr
DB %00000000 ; Color 3, palette 1, %0bbbbbgg

GBCBackgroundPalettesDarkest:
DB %00000000 ; Color 0, palette 0, %gggrrrrr
DB %00000000 ; Color 0, palette 0, %0bbbbbgg
DB %00000000 ; Color 1, palette 0, %gggrrrrr
DB %00000000 ; Color 1, palette 0, %0bbbbbgg
DB %00000000 ; Color 2, palette 0, %gggrrrrr
DB %00000000 ; Color 2, palette 0, %0bbbbbgg
DB %00000000 ; Color 3, palette 0, %gggrrrrr
DB %00000000 ; Color 3, palette 0, %0bbbbbgg
DB %00000000 ; Color 0, palette 1, %gggrrrrr
DB %00000000 ; Color 0, palette 1, %0bbbbbgg
DB %00000000 ; Color 1, palette 1, %gggrrrrr
DB %00000000 ; Color 1, palette 1, %0bbbbbgg
DB %00000000 ; Color 2, palette 1, %gggrrrrr
DB %00000000 ; Color 2, palette 1, %0bbbbbgg
DB %00000000 ; Color 3, palette 1, %gggrrrrr
DB %00000000 ; Color 3, palette 1, %0bbbbbgg

GBCSpritePalettes:
DB %11111111 ; Color 0, palette 0, %gggrrrrr
DB %01111111 ; Color 0, palette 0, %0bbbbbgg
DB %11111111 ; Color 1, palette 0, %gggrrrrr
DB %00011111 ; Color 1, palette 0, %0bbbbbgg
DB %11111111 ; Color 2, palette 0, %gggrrrrr
DB %00001001 ; Color 2, palette 0, %0bbbbbgg
DB %00000000 ; Color 3, palette 0, %gggrrrrr
DB %00000000 ; Color 3, palette 0, %0bbbbbgg
DB %11111000 ; Color 0, palette 1, %gggrrrrr
DB %01111111 ; Color 0, palette 1, %0bbbbbgg
DB %01100000 ; Color 1, palette 1, %gggrrrrr
DB %01111100 ; Color 1, palette 1, %0bbbbbgg
DB %00000000 ; Color 2, palette 1, %gggrrrrr
DB %00011100 ; Color 2, palette 1, %0bbbbbgg
DB %00000000 ; Color 3, palette 1, %gggrrrrr
DB %00001100 ; Color 3, palette 1, %0bbbbbgg

GBCPaletteMap:
DB 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

SECTION "Pong game code",ROM0
SetupGBC:
    GBCEarlyExit ; Prevents garbage from being written to tile map on non-GBC by the palette map.

    ld hl, GBCBackgroundPalettes
    xor a ; Start at color 0, palette 0
    ld b, 16 ; We have 16 bytes to write
    call GBCApplyBackgroundPalettes

    ld hl, GBCSpritePalettes
    xor a
    ld b, 16
    call GBCApplySpritePalettes

    ; Switch to VRAM bank 1 to write the palette map (which palette to use at which tile)
    ld a, 1
    ld [GBC_VRAM_BANK_SWITCH], a

    ; Copy the palette map onto VRAM (where the tile data usually is)
    CopyRegionToVRAM 18, 20, GBCPaletteMap, BACKGROUND_MAPDATA_START

    ; Change the VRAM bank back
    xor a
    ld [GBC_VRAM_BANK_SWITCH], a

    ret

SetupSGB:
	SGBEarlyExit ; Without this, garbage would be visible on screen briefly when booting on a GB/GBC

    call InitSGBPalettes

	call SGBFreeze ; To prevent "garbage" from being visible on screen

    SGBBorderTransferMacro SGB_VRAM_TILEDATA1, SGB_VRAMTRANS_TILEDATA1, SGB_VRAMTRANS_GBTILEMAP
    SGBBorderTransferMacro SGB_VRAM_TILEDATA2, SGB_VRAMTRANS_TILEDATA2, SGB_VRAMTRANS_GBTILEMAP
    SGBBorderTransferMacro SGB_VRAM_TILEMAP, SGB_VRAMTRANS_TILEMAP, SGB_VRAMTRANS_GBTILEMAP

	call SGBUnfreeze
	ret

SetupSGBGameplay:
    ; Sets up palette usage which should be displayed during gameplay

    SGBEarlyExit
    ld hl, SGBPal01Div
    call SGBSendData

    ret

InitSGBPalettes:
    ld hl, SGBPalettes01
    call SGBSendData

    ld hl, SGBPalettes23
    call SGBSendData

    ld hl, SGBPal2Everywhere
    call SGBSendData

    ret

SetupHighScore:
    ; For this game, we only ever use one save data bank, the first one (0)
    xor a
    call ChooseSaveDataBank

    ; Activate save data so we can read and write it
    call EnableSaveData

    ; If the integrity check doesn't read $1337, we should initialize a default high score of 0 and then write $1337 to the integrity check position
    ld a, [SRAM_INTEGRITY_CHECK]
    cp $13
    jr nz, .initializeSRAM

    ld a, [SRAM_INTEGRITY_CHECK+1]
    cp $37
    jr nz, .initializeSRAM

    ; If we get here, no initialization is necessary
    jr .print

.initializeSRAM:
    ; Initialize high score to 0
    xor a
    ld [SRAM_HIGH_SCORE], a

    ; Intialize integrity check so that high score will not be overwritten on next boot
    ld a, $13
    ld [SRAM_INTEGRITY_CHECK], a

    ld a, $37
    ld [SRAM_INTEGRITY_CHECK+1], a

    jr .print

.print:
    ; Display current high score
    ld a, [SRAM_HIGH_SCORE]
    ld b, a

    call DisableSaveData ; Since we no longer need it. Always disable SRAM as quickly as possible.

    ld a, b
    ld b, $4F ; tile number of 0 character on the title screen
    ld c, 0   ; draw to background
    ld d, 8   ; X position
    ld e, 14  ; Y position
    call RenderTwoDecimalNumbers

    ret

; To keep music playing at all times, call this before every halt command
; A - ROM bank to switch to after updating music (GBT Player switches banks when updating the music)
UpdateMusic:
IF DEF(USE_GBT_PLAYER)
    push bc
    push af
    call gbt_update
    pop af
    pop bc
ENDC
    ld [ROM_BANK_SWITCH], a
    ret

TitleLoop:

    ld a, 1
    call UpdateMusic

    halt
    nop ; Always do a nop after a halt, because of a CPU bug

    call ReadKeys
    and KEY_A | KEY_START
    cp 0

    jp nz, TransitionToGame

    jr TitleLoop

ShortWait:
    ld b, 20

.loop:
    ld a, 1
    call UpdateMusic

    halt
    nop

    dec b
    ld a, b
    cp 0
    jr nz, .loop

    ret

; Modifies everything
DrawScore:
    ; We use the -ByPosition render calls because this is done every frame, so precomputing the position numbers at compile time
    ; will make the code run faster

    ld a, [LEFT_SCORE]
    ld b, $4C ; Tile number of 0
    ld c, 0 ; Write to background
    ld de, 1 + 32*0 ; Position number (the formula is x + 32*y)
    call RenderTwoDecimalNumbersByPosition

    ld a, [RIGHT_SCORE]
    ld b, $4C ; Tile number of 0
    ld c, 0 ; Write to background
    ld de, 17 + 32*0 ; Position number (the formula is x + 32*y)
    call RenderTwoDecimalNumbersByPosition

    ret

; Modifies AF
DrawBall:
    ; Left part of ball
    ld a, [BALL_POSITION+1] ; Y location
    ld [SPRITES_START], a
    ld a, [BALL_POSITION] ; X location
    ld [SPRITES_START+1], a
    ld a, $48 ; Tile number
    ld [SPRITES_START+2], a
    xor a ; Flags (including GBC sprite color palette)
    ld [SPRITES_START+3], a

    ; Right part of ball
    ld a, [BALL_POSITION+1] ; Y location
    ld [SPRITES_START+4], a
    ld a, [BALL_POSITION] ; X location
    add 8 ; right part of ball should be 8 pixels to the right
    ld [SPRITES_START+5], a
    ld a, $4A ; Tile number
    ld [SPRITES_START+6], a
    xor a ; Flags (including GBC sprite color palette)
    ld [SPRITES_START+7], a

    ret

; Modifies AF
DrawLeftPaddle:
    ; Top part of paddle
    ld a, [LEFT_PADDLE_POSITION+1] ; Y
    ld [SPRITES_START+8], a
    ld a, [LEFT_PADDLE_POSITION] ; X
    ld [SPRITES_START+9], a
    ld a, $44 ; Top and top-middle paddle tiles
    ld [SPRITES_START+10], a
    ld a, 1 ; Flags (including GBC sprite color palette)
    ld [SPRITES_START+11], a

    ; Bottom of paddle
    ld a, [LEFT_PADDLE_POSITION+1] ; Y
    add 16
    ld [SPRITES_START+12], a
    ld a, [LEFT_PADDLE_POSITION] ; X
    ld [SPRITES_START+13], a
    ld a, $46 ; Bottom-middle and bottom paddle tiles
    ld [SPRITES_START+14], a
    ld a, 1 ; Flags (including GBC sprite color palette)
    ld [SPRITES_START+15], a

    ret

; Modifies AF
DrawRightPaddle:
    ; Top part of paddle
    ld a, [RIGHT_PADDLE_POSITION+1] ; Y
    ld [SPRITES_START+16], a
    ld a, [RIGHT_PADDLE_POSITION] ; X
    ld [SPRITES_START+17], a
    ld a, $44 ; Top and top-middle paddle tiles
    ld [SPRITES_START+18], a
    ld a, 1 ; Flags (including GBC sprite color palette)
    ld [SPRITES_START+19], a

    ; Bottom of paddle
    ld a, [RIGHT_PADDLE_POSITION+1] ; Y
    add 16
    ld [SPRITES_START+20], a
    ld a, [RIGHT_PADDLE_POSITION] ; X
    ld [SPRITES_START+21], a
    ld a, $46 ; Bottom-middle and bottom paddle tiles
    ld [SPRITES_START+22], a
    ld a, 1 ; Flags (including GBC sprite color palette)
    ld [SPRITES_START+23], a

    ret

TransitionToGame:
    ld a, %11111001
    ld [BG_PALETTE], a

    ld hl, GBCBackgroundPalettesDark
    xor a ; Start at color 0, palette 0
    ld b, 16 ; We have 16 bytes to write
    call GBCApplyBackgroundPalettes

    call ShortWait

    ld a, %11111110
    ld [BG_PALETTE], a

    ld hl, GBCBackgroundPalettesDarker
    xor a ; Start at color 0, palette 0
    ld b, 16 ; We have 16 bytes to write
    call GBCApplyBackgroundPalettes

    call ShortWait

    ld a, %11111111
    ld [BG_PALETTE], a

    ld hl, GBCBackgroundPalettesDarkest
    xor a ; Start at color 0, palette 0
    ld b, 16 ; We have 16 bytes to write
    call GBCApplyBackgroundPalettes

    call ShortWait

    ; Now that the screen is completely black, load the game graphics

    ld a, BANK(pong_tile_data)
    ld [ROM_BANK_SWITCH], a

    ; Load pong tiles into VRAM
    ld hl, pong_tile_data
    ld de, TILEDATA_START
    ld bc, pong_tile_data_size
    call mCopyVRAM

    ; Clear out the background
    ld a, 1
    ld hl, BACKGROUND_MAPDATA_START
    ld bc, 32*32
    call mSetVRAM

    ld a, BANK(pong_map_data)
    ld [ROM_BANK_SWITCH], a

    ; Draw the pong map tiles
    CopyRegionToVRAM 18, 20, pong_map_data, BACKGROUND_MAPDATA_START

    ld a, 1
    ld [ROM_BANK_SWITCH], a

    ; On SGB, this is the right time to switch to the gameplay palettes
    call SetupSGBGameplay

    ; Now fade back to normal palette
    ld a, %11111110
    ld [BG_PALETTE], a

    ld hl, GBCBackgroundPalettesDarker
    xor a ; Start at color 0, palette 0
    ld b, 16 ; We have 16 bytes to write
    call GBCApplyBackgroundPalettes

    call ShortWait

    ld a, %11111001
    ld [BG_PALETTE], a

    ld hl, GBCBackgroundPalettesDark
    xor a ; Start at color 0, palette 0
    ld b, 16 ; We have 16 bytes to write
    call GBCApplyBackgroundPalettes

    call ShortWait

    ld a, %11100100
    ld [BG_PALETTE], a

    ld hl, GBCBackgroundPalettes
    xor a ; Start at color 0, palette 0
    ld b, 16 ; We have 16 bytes to write
    call GBCApplyBackgroundPalettes

    call ShortWait

    ; Let's put the sprites in place

    ld a, 100 ; X
    ld [BALL_POSITION], a
    ld a, 50 ; Y
    ld [BALL_POSITION+1], a
    call DrawBall

    ld a, 12 ; X
    ld [LEFT_PADDLE_POSITION], a
    ld a, 72 ; Y
    ld [LEFT_PADDLE_POSITION+1], a
    call DrawLeftPaddle

    ld a, 154 ; X
    ld [RIGHT_PADDLE_POSITION], a
    ld a, 72 ; Y
    ld [RIGHT_PADDLE_POSITION+1], a
    call DrawRightPaddle

    ; Set initial ball movement
    ld a, 2 ; dx
    ld [BALL_DIRECTION], a
    ld a, 1 ; dy
    ld [BALL_DIRECTION+1], a

    ; Initialize right paddle check counter and direction
    xor a
    ld [RIGHT_PADDLE_CHECK_TIME], a
    ld [RIGHT_PADDLE_DIRECTION], a

    ; Initialize score
    ld [LEFT_SCORE], a
    ld [RIGHT_SCORE], a

    jp GameLoop

; Modifies AF
; Moves the left paddle up, making sure not to move it outside the playing field
MoveLeftPaddleUp:
    ld a, [LEFT_PADDLE_POSITION+1]
    sub PADDLE_SPEED

    ; Check if too high up
    cp 24
    ret c

    ld [LEFT_PADDLE_POSITION+1], a
    ret

; Modifies AF
; Same as MoveLeftPaddleUp, except Down
MoveLeftPaddleDown:
    ld a, [LEFT_PADDLE_POSITION+1]
    add PADDLE_SPEED

    ; Check if too far down
    cp 130
    ret nc

    ld [LEFT_PADDLE_POSITION+1], a
    ret

; Modifies AF
; Moves the right paddle up, making sure not to move it outside the playing field
MoveRightPaddleUp:
    ld a, [RIGHT_PADDLE_POSITION+1]
    sub PADDLE_SPEED

    ; Check if too high up
    cp 24
    ret c

    ld [RIGHT_PADDLE_POSITION+1], a
    ret

; Modifies AF
; Same as MoveRightPaddleUp, except Down
MoveRightPaddleDown:
    ld a, [RIGHT_PADDLE_POSITION+1]
    add PADDLE_SPEED

    ; Check if too far down
    cp 130
    ret nc

    ld [RIGHT_PADDLE_POSITION+1], a
    ret

ReverseBallDY:
    ; 0 - dy will be a positive integer, but it still works because of overflow
    xor a
    sub c
    ld [BALL_DIRECTION+1], a
    ret

ReverseBallDX:
    xor a
    sub b
    ld [BALL_DIRECTION], a
    ret

CheckCollisionLeftPaddle:
    ; First check if the ball is far enough to the left
    ld a, [BALL_POSITION]
    cp 17
    ret nc

    ; Now check if it's far enough down
    ld a, [LEFT_PADDLE_POSITION+1]
    ld d, a
    ld a, [BALL_POSITION+1]
    add 8
    cp d
    ret c

    ; Now check if it's not too far down
    sub 40
    cp d
    ret nc

    call ReverseBallDX

    ld hl, Sound_ball_bounce
    call PlaySoundHL

    ; To prevent ball from getting stuck, make sure dx > 0 and it's a bit to the right
    ld a, [BALL_POSITION]
    add 2
    ld [BALL_POSITION], a

    ld a, [BALL_DIRECTION]
    cp 128
    ret c

    ; If we get here, it means ball direction was wrong
    call ReverseBallDX
    ret

CheckCollisionRightPaddle:
    ; First check if the ball is far enough to the right
    ld a, [BALL_POSITION]
    cp 142
    ret c

    ; Now check if it's far enough down
    ld a, [RIGHT_PADDLE_POSITION+1]
    ld d, a
    ld a, [BALL_POSITION+1]
    add 8
    cp d
    ret c

    ; Now check if it's not too far down
    sub 40
    cp d
    ret nc

    call ReverseBallDX

    ld hl, Sound_ball_bounce
    call PlaySoundHL

    ; To prevent ball from getting stuck, make sure dx < 0 and it's a bit to the left
    ld a, [BALL_POSITION]
    sub 2
    ld [BALL_POSITION], a

    ld a, [BALL_DIRECTION]
    cp 128
    ret nc

    ; If we get here, it means ball direction was wrong
    call ReverseBallDX
    ret

CheckBallOut:
    ld a, [BALL_POSITION]
    ; If the x position is larger than 160, the ball is outside the field. This is true regardless of
    ; if the ball went out to the left or right! 8-bit unsigned numbers, am I right? Or left?

    cp 160
    ret c

    ; If we get here, it means the ball was indeed outside
    ; Now we need to know which side. We can just look at the ball's direction

    ld a, [BALL_DIRECTION]
    cp 128
    jr nc, .leftSide

.rightSide:
    ; Increase left player's score
    ld a, [LEFT_SCORE]
    add a, 1 ; Since it's in decimal format, we can't use inc/dec and we need to remember to use daa
    daa
    ld [LEFT_SCORE], a

    ; Show some supportive text
    ld c, 0
    ld b, 0
    ld hl, SupportiveText
    ld d, 3
    ld e, 16
    call RenderTextToEnd

    jr .end

.leftSide:
    ; Increase right player's score
    ld a, [RIGHT_SCORE]
    add 1
    daa
    ld [RIGHT_SCORE], a

    ; Show taunting text
    ld c, 0
    ld b, 0
    ld hl, TauntingText
    ld d, 3
    ld e, 16
    call RenderTextToEnd

.end:
    ; Place ball at initial position

    ; X
    ld a, 80
    ld [BALL_POSITION], a

    ; Y
    ld a, 72
    ld [BALL_POSITION+1], a

    REPT 15
    ld a, 1
    call UpdateMusic

    halt
    nop
    ENDR

    ; Check if right player's score is 10. If so, the game is over
    ld a, [RIGHT_SCORE]
    cp $10
    jr z, .gameOver

    ret

.gameOver:
    call DrawScore

    ld hl, GameOverText
    ld b, 0 ; End character
    ld c, 0 ; Draw to background
    ld d, 1 ; X position
    ld e, 20 ; Y position
    call RenderTextToEnd

    ; Hide sprites
    ld   hl, SPRITES_START
    ld   bc, SPRITES_LENGTH
    xor a
    call mSetVRAM

    ; Scroll the screen down a bit
    ld c, 100
.scrollDown:
    ld a, [SCROLL_Y]
    inc a
    ld [SCROLL_Y], a

    call ShorterWait

    dec c
    ld a, c
    cp 0
    jr nz, .scrollDown

    ; Now remove the first few lines of tiles so that they don't become visible when we scroll too far down
    ld a, 1
    ld hl, BACKGROUND_MAPDATA_START
    ld bc, 32*4
    call mSetVRAM

    ; Scroll down a bit more
    ld c, 40
.scrollDown2:
    ld a, [SCROLL_Y]
    inc a
    ld [SCROLL_Y], a

    call ShorterWait

    dec c
    ld a, c
    cp 0
    jr nz, .scrollDown2

    call ShortWait
    call ShortWait
    call ShortWait

    ; Compare player's score with high score and save new high score if it's higher
    ld a, [LEFT_SCORE]
    ld b, a

    call EnableSaveData
    ld a, [SRAM_HIGH_SCORE]

    cp b
    call c, .newHighScore

    call DisableSaveData

    ; Resets the game
    jp GingerBreadBegin

; Local function for writing high score to SRAM
.newHighScore:
    ld a, b
    ld [SRAM_HIGH_SCORE], a
    ret

ShorterWait:
    ld b, 4
.wait:
    ld a, 1
    call UpdateMusic

    halt
    nop

    dec b
    ld a, b
    cp 0
    jr nz, .wait

    ret

; Modifies AF and BC and DE
UpdateBall:
    ; Store dx in B, and add to ball x
    ld a, [BALL_DIRECTION]
    ld b, a
    ld a, [BALL_POSITION]
    add b
    ld [BALL_POSITION], a

    ; Store dy in C, and add to ball y
    ld a, [BALL_DIRECTION+1]
    ld c, a
    ld a, [BALL_POSITION+1]
    add c
    ld [BALL_POSITION+1], a

    ; Check if ball collides with ceiling, if so reverse dy
    ld a, [BALL_POSITION+1]
    cp 24
    call c, ReverseBallDY

    ; Same but for the floor
    ld a, [BALL_POSITION+1]
    cp 144
    call nc, ReverseBallDY

    ; Check if ball is outside the playing field
    call CheckBallOut

    ; Check collision with paddles
    call CheckCollisionRightPaddle
    call CheckCollisionLeftPaddle

    ret

RightPaddleStartMovingDown:
    ld a, 1
    ld [RIGHT_PADDLE_DIRECTION], a
    ret

RightPaddleStartMovingUp:
    xor a
    ld [RIGHT_PADDLE_DIRECTION], a
    ret

UpdateRightPaddleDirection:
    ; Reset check counter
    xor a
    ld [RIGHT_PADDLE_CHECK_TIME], a

    ; Compare ball's and right paddle's y values and move paddle accordingly
    ld a, [BALL_POSITION+1]
    sub 8 ; because paddle is taller than ball
    ld b, a
    ld a, [RIGHT_PADDLE_POSITION+1]
    push af
    push bc
    cp b
    call nc, RightPaddleStartMovingUp
    pop bc
    pop af
    cp b
    call c, RightPaddleStartMovingDown

    ret

UpdateRightPaddle:
    ld a, [RIGHT_PADDLE_DIRECTION] ; 0 means up, 1 means down
    cp 0
    jr z, .moveUp

.moveDown:
    call MoveRightPaddleDown
    ret

.moveUp:
    call MoveRightPaddleUp
    ret

GameLoop:
    call DrawLeftPaddle
    call DrawRightPaddle
    call DrawBall
    call DrawScore

    ld a, 1
    call UpdateMusic

    halt
    nop

    call ReadKeys
    push af ; Store key status so it can be used twice
    and KEY_UP
    cp 0
    call nz, MoveLeftPaddleUp
    pop af
    and KEY_DOWN
    cp 0
    call nz, MoveLeftPaddleDown

    call UpdateBall

    ld a, [RIGHT_PADDLE_CHECK_TIME]
    inc a
    ld [RIGHT_PADDLE_CHECK_TIME], a
    cp RIGHT_PADDLE_CHECK_FREQUENCY
    call z, UpdateRightPaddleDirection

    call UpdateRightPaddle

    jp GameLoop


text:
#!/bin/bash

#To compile without GBT Player, change this line to 'include_gbt=0'
include_gbt=1

# To compile "Hello world" demo, change this to "hello-world"
name="example"

if [ -f images/sgb_border.inc ]; then
    echo "Not building SGB border since it exists..."
else
    echo "Building SGB border..."
    cd ..
    python sgb_border.py examples/images/sgb_border.png examples/images/sgb_border.inc
    cd examples
fi

cp ../gingerbread.asm .

# delete existing ROM, if it exists
if [ -f $name.gb ]; then
    rm $name.gb
fi

echo "Compilation step 1/4: Assembling..."

if [[ $include_gbt -eq 1 ]]; then
    cp ../gbt-player/rgbds_example/gbt_player.asm .
    cp ../gbt-player/rgbds_example/gbt_player_bank1.asm .
    cp ../gbt-player/rgbds_example/hardware.inc .
    cp ../gbt-player/rgbds_example/gbt_player.inc .
    rgbasm -ogbt_player.o gbt_player.asm
    rgbasm -ogbt_player_bank1.o gbt_player_bank1.asm
    rgbasm -ofunkyforest.o music/funkyforest.asm

    echo "Assembled GBT-Player related stuff"
fi

rgbasm -o$name.o $name.asm

echo "Compilation step 2/4: Linking..."
if [[ $include_gbt -eq 1 ]]; then
    rgblink -o $name.gb -m $name.map -n $name.sym $name.o gbt_player.o gbt_player_bank1.o funkyforest.o
else
    rgblink -o $name.gb -m $name.map -n $name.sym $name.o
fi

echo "Compilation step 3/4: Fixing..."
rgbfix -v -p 0x00 $name.gb

echo "Compilation step 4/4: Cleaning up..."
rm *.o
rm *.map
# del *.sym
# You probably want to leave the .sym files for debugging purposes

rm gingerbread.asm
if [[ $include_gbt -eq 1 ]]; then
    rm gbt_player.asm
    rm gbt_player_bank1.asm
    rm hardware.inc
    rm gbt_player.inc
fi


text:
; This section is for including files that either need to be in the home section, or files where it doesn't matter
SECTION "Includes@home",ROM0

; Prior to importing GingerBread, some options can be specified

; Max 15 characters, should be uppercase ASCII
GAME_NAME EQUS "HIWORLD "

; Include SGB support in GingerBread. This makes the GingerBread library take up a bit more space on ROM0. To remove support, comment out this line (don't set it to 0)
;SGB_SUPPORT EQU 1

; Include GBC support in GingerBread. This makes the GingerBread library take up slightly more space on ROM0. To remove support, comment out this line (don't set it to 0)
;GBC_SUPPORT EQU 1

; Set the size of the ROM file here. 0 means 32 kB, 1 means 64 kB, 2 means 128 kB and so on.
ROM_SIZE EQU 1

; Set the size of save RAM inside the cartridge.
; If printed to real carts, it needs to be small enough to fit.
; 0 means no RAM, 1 means 2 kB, 2 -> 8 kB, 3 -> 32 kB, 4 -> 128 kB
RAM_SIZE EQU 1

INCLUDE "gingerbread.asm"
INCLUDE "images/hello_world.inc"

SECTION "Text definitions",ROM0
; Charmap definition (based on the hello_world.png image, and looking in the VRAM viewer after loading it in BGB helps finding the values for each character)
CHARMAP "A",$14
CHARMAP "B",$15
CHARMAP "C",$16
CHARMAP "D",$17
CHARMAP "E",$18
CHARMAP "F",$19
CHARMAP "G",$1A
CHARMAP "H",$1B
CHARMAP "I",$1C
CHARMAP "J",$1D
CHARMAP "K",$1E
CHARMAP "L",$1F
CHARMAP "M",$20
CHARMAP "N",$21
CHARMAP "O",$22
CHARMAP "P",$23
CHARMAP "Q",$24
CHARMAP "R",$25
CHARMAP "S",$26
CHARMAP "T",$27
CHARMAP "U",$28
CHARMAP "V",$29
CHARMAP "W",$2A
CHARMAP "X",$2B
CHARMAP "Y",$2C
CHARMAP "Z",$2D
CHARMAP "<happy>",$02
CHARMAP "<sad>",$03
CHARMAP "<heart>",$04
CHARMAP "<up>",$07
CHARMAP "<down>",$08
CHARMAP "<left>",$06
CHARMAP "<right>",$05
CHARMAP " ",$00
CHARMAP "<end>",$30 ; Choose some non-character tile that's easy to remember

SomeText:
DB "HELLO WORLD <happy><end>"

SECTION "StartOfGameCode",ROM0
begin: ; GingerBread assumes that the label "begin" is where the game should start

    ld hl, hello_world_tile_data
    ld de, TILEDATA_START
    ld bc, hello_world_tile_data_size
    call mCopyVRAM

    ld b, $30 ; end character
    ld c, 0 ; draw to background
    ld d, 4 ; X start position (0-19)
    ld e, 8 ; Y start position (0-17)
    ld hl, SomeText ; text to write
    call RenderTextToEnd

    call StartLCD

main:
    halt
    nop

    jr main


text:
@echo off

rem To compile without GBT Player, change this line to 'set "include_gbt="'
rem and comment out USE_GBT_PLAYER in the %name%.asm if necessary
set "include_gbt=1"

rem To compile the Hello World demo, change this to "hello-world"
set name="example"

if exist images/sgb_border.inc (
echo Not building SGB border since it exists...
) else (
echo Building SGB border...
cd ..
python sgb_border.py examples/images/sgb_border.png examples/images/sgb_border.inc
cd examples
)

copy ..\gingerbread.asm .

REM delete existing ROM, if it exists
if exist %name%.gb del %name%.gb

echo Compilation step 1/4: Assembling...

if defined include_gbt (
copy ..\gbt-player\rgbds_example\gbt_player.asm .
copy ..\gbt-player\rgbds_example\gbt_player_bank1.asm .
copy ..\gbt-player\rgbds_example\hardware.inc .
copy ..\gbt-player\rgbds_example\gbt_player.inc .
rgbasm -ogbt_player.o gbt_player.asm
rgbasm -ogbt_player_bank1.o gbt_player_bank1.asm
rgbasm -ofunkyforest.o music\funkyforest.asm
)

rgbasm -o%name%.o %name%.asm

if errorlevel 1 goto cleanup

echo Compilation step 2/4: Linking...
if defined include_gbt (
rgblink -o %name%.gb -m %name%.map -n %name%.sym %name%.o gbt_player.o gbt_player_bank1.o funkyforest.o
) else (
rgblink -o %name%.gb -m %name%.map -n %name%.sym %name%.o
)

if errorlevel 1 goto cleanup

echo Compilation step 3/4: Fixing...
rgbfix -v -p 0x00 %name%.gb

:cleanup
echo Compilation step 4/4: Cleaning up...
del *.o
del *.map
rem del *.sym
rem You probably want to leave the .sym files for debugging purposes

del gingerbread.asm
if defined include_gbt (
del gbt_player.asm
del gbt_player_bank1.asm
del hardware.inc
del gbt_player.inc
)