{"id":24559790,"url":"https://github.com/jsmolina/z88dk-tutorial-sp1","last_synced_at":"2025-08-03T04:09:55.046Z","repository":{"id":45304032,"uuid":"145724124","full_name":"jsmolina/z88dk-tutorial-sp1","owner":"jsmolina","description":"sp1 tutorial (ms nampac)","archived":false,"fork":false,"pushed_at":"2025-05-25T08:52:02.000Z","size":1440,"stargazers_count":14,"open_issues_count":9,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-25T09:36:18.814Z","etag":null,"topics":["gameengine","sp1","z88dk","zx-spectrum","zxspectrum"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jsmolina.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-08-22T14:55:11.000Z","updated_at":"2025-05-25T08:51:24.000Z","dependencies_parsed_at":"2025-05-25T09:28:30.125Z","dependency_job_id":"3d49145e-7ff1-486b-ae30-a7185677f3f5","html_url":"https://github.com/jsmolina/z88dk-tutorial-sp1","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/jsmolina/z88dk-tutorial-sp1","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsmolina%2Fz88dk-tutorial-sp1","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsmolina%2Fz88dk-tutorial-sp1/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsmolina%2Fz88dk-tutorial-sp1/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsmolina%2Fz88dk-tutorial-sp1/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jsmolina","download_url":"https://codeload.github.com/jsmolina/z88dk-tutorial-sp1/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsmolina%2Fz88dk-tutorial-sp1/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268492466,"owners_count":24258822,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-03T02:00:12.545Z","response_time":2577,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gameengine","sp1","z88dk","zx-spectrum","zxspectrum"],"created_at":"2025-01-23T06:18:42.698Z","updated_at":"2025-08-03T04:09:55.035Z","avatar_url":"https://github.com/jsmolina.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SP1 TUTORIAL\n\n## Building this project\nGraphic resources uses png2sp1sprite python scripts, which are available to be installed at: https://github.com/jsmolina/png2sp1sprite\nJust clone the repo and do a =\u003e python setup.py install \n\nEasiest way is to just use docker:\n* `docker-compose build \u0026\u0026 docker-compose up`\n\nBut for convenience, there are shell scripts:\n* `build.sh`generates msnampac.tap\n* `build-p3.sh` generates msnampac.dsk\n##  [plus3 docs](PLUS3-DISK-NOTES.md) \n\n## Considerations before start\n* Do you want to make a 48k game or a 128k game?\n* Do you know how ZX Spectrum paging works?\n* Do you know what's a sprite mask?\n* Do you have read about IM2 interruptions? (ISR z88dk)\n* Read about `SECTION` and how to define public vars from ASM code `PUBLIC`\n* You'll need z88dk, of course, so you could download it from here: http://nightly.z88dk.org/\n* Interesting to read as well is sp1 library tutorial: https://github.com/z88dk/z88dk/blob/master/doc/ZXSpectrumZSDCCnewlib_SP1_01_GettingStarted.md https://github.com/z88dk/z88dk/blob/master/doc/ZXSpectrumZSDCCnewlib_SP1_02_SimpleMaskedSprite.md\n* Interesting demo for z88dk as well \nhttps://github.com/z88dk/z88dk/tree/f6caa95eba4653ed79e0bd3460c4ebfa56820721/libsrc/_DEVELOPMENT/EXAMPLES/zx/demo_sp1/demo2\n* png2sp1sprite.py Quick and dirty png to sprite converter. Right now it only uses 1-bit sprites (no color), and ignores color info. Based on https://github.com/z88dk/z88dk/blob/master/doc/ZXSpectrumZSDCCnewlib_SP1_04_BiggerSprites.md\n\n## Saving memory\nI really enjoy structured code, and having functions and meaningful var names usually helps 'storytelling', but remember this system is very limited in memory, so you'll become dirty in your C programming:\n* mallocs should not be used\n* global vars are welcome (I will deny and request a lawyer if someone says I said this).\n* function calls should be only for code used on many places. You could keep them for readability, but then use `inline`.\n\n### Drawing on the screen with SP1\n\nGraphics are usually composed of pixel information (all pixels are stored as on or off) and color information that goes \nin 8x8 blocks. You'll see that Attribute clash is present everywhere everytime two sprites with different INK appear \non the same 8x8 box, and that's why lot of games made heroes to be black and white with transparent background \n(aka mask).\n\nThere are two types of entities:\n1. User-Defined Graphics (UDGs) (cheap on memory usage). Basically, kind of replacing an 8x8 ascii character by your own 'draw'.\n2. Sprites (more memory usage). A computer graphic which may be moved on-screen and otherwise manipulated as a single entity.\n\nUsually, UDGs are more intended for background items (even if they move, but SP1 won't help to move them), but it will help \nto repaint them if a sprite is on top of an UDG.\n\nSprites are more intended for moving overlapping items, like the game hero, enemies, ...\n\n### sprites \nThere are also a lot of ways to draw a sprite, main ones are:\n1. with SP1_DRAW_MASK2LB =\u003e Do you remember target:renegade? sprites were mostly with a border. But \nas a counterpart, it takes MORE memory (double, for sprite info and for mask info)\n2. with SP1_DRAW_XOR1LB =\u003e Just an XOR operation for drawing the sprite with the background.\n\nIn all cases SP1 will take care for you of the redrawing of background after sprite has passed.\n\n\nAlso, the spectrum has the 'border' and the 'paper', as defined in basic, thus border is only used for\ncolor effects usually.\n\n## First steps \n\n### A C program:\nWrite a simple program called `alley.c` with black border and black background\n```C\n#include \u003cz80.h\u003e\n#include \u003cstdlib.h\u003e\n#include \u003carch/zx.h\u003e\n#include \u003carch/zx/sp1.h\u003e\n\n// this is defining that screen will go from 0 - 32 in width and 0-24 in height\nstruct sp1_Rect full_screen = {0, 0, 32, 24};\n\nint main()\n{\n  zx_border(INK_WHITE);\n\n  sp1_Initialize( SP1_IFLAG_MAKE_ROTTBL | SP1_IFLAG_OVERWRITE_TILES | SP1_IFLAG_OVERWRITE_DFILE,\n                  INK_WHITE | PAPER_WHITE,\n                  ' ' );\n  sp1_Invalidate(\u0026full_screen);\n\n  sp1_UpdateNow();\n\n  while(1) {\n\n  }\n}\n```\n\nNow create two files: \n\nzproject.lst \n``` \nalley.c\n```\n\nand zpragma.inc (read z88dk documentation for more info about this). \nThe most important is CRT_ORG_CODE, it's where your code is placed in memory.\n```\n#pragma output CRT_ORG_CODE           = 25000      // org of compile todo this should be higher to allocate sprites\n#pragma output REGISTER_SP            = 0xd000     // typical stack location when using sp1\n\n#pragma output CRT_ENABLE_CLOSE       = 0          // don't bother closing files on exit\n#pragma output CRT_ENABLE_EIDI        = 0x1        // disable interrupts at start\n#pragma output CRT_ON_EXIT            = 0          // jump to 0 on exit\n\n#pragma output CLIB_STDIO_HEAP_SIZE   = 0          // no stdio heap (no files)\n\n#pragma output CLIB_EXIT_STACK_SIZE  = 0         // no atexit() functions\n\n#pragma output CLIB_FOPEN_MAX         = -1         // no FILE* list\n#pragma output CLIB_OPEN_MAX          = -1         // no fd table\n\n// CREATE A BLOCK MEMORY ALLOCATOR WITH ONE QUEUE\n\n#pragma output CLIB_BALLOC_TABLE_SIZE = 1\n\n#pragma output CRT_STACK_SIZE = 128\n```\n\n\nYou'll need of course to compile it\n```bash \nzcc +zx -v -startup=31 -DWFRAMES=3 -clib=sdcc_iy -SO3 --max-allocs-per-node200000 \\ \n   @zproject.lst -pragma-include:zpragma.inc -o alley -create-app\n```\nIf you had a loading screen you might be adding: `-Cz--screen=screen.scr `\n\nso boring isn't it?\n\n### Adding a sprite\nI would recommend using SevenUP (see other tutorials) \nor an automatic sprite to asm creator like mine:\n * cloning my repo: `$ git clone https://github.com/jsmolina/png2sp1sprite.git`\n * installing png2sp1sprite: `python setup.py install`\n\nNow  \n1. Create a `/build` directory.\n2. Add a png file (it needs to be size multiple of 8)\n3. Execute `png2sp1sprite ./build/cat_sprites.png -i sprite_protar -f 16 \u003e ./build/misifu.asm`. \n-f is the frame size, if your sprite has animations. If you don't specify more options the script will automatically \ngenerate a default mask.\n4. That will generate a `misifu.asm` file, that you can now add it.\n5. Create a text file named `binaries.lst` and add in first line just the filename so it can be \nfound by compiler: `misifu`.\n6. Add a link to binaries.lst in `zproject.lst`: `@build/binaries.lst`\n7. Now add this in alley.c:\n```C\n// brings the built binaries into C\nextern uint8_t sprite_protar1[];\nextern uint8_t sprite_protar2[];\nextern uint8_t sprite_protar3[];\nextern uint8_t sprite_protar4[];\n\n// allows to define colours for the sprite\nstatic void initialiseColour(unsigned int count, struct sp1_cs *c)\n{\n  (void)count;    /* Suppress compiler warning about unused parameter */\n\n  c-\u003eattr_mask = SP1_AMASK_INK;\n  c-\u003eattr      = INK_BLACK;\n}\n\n// adds a sprite with MASK (SP1_DRAW_MASK2LB)\nstruct sp1_ss * add_sprite_misifu() {\n  struct sp1_ss * sp;\n   sp = sp1_CreateSpr(SP1_DRAW_MASK2LB, SP1_TYPE_2BYTE, 4, 0, 1);\n  sp1_AddColSpr(sp, SP1_DRAW_MASK2,    SP1_TYPE_2BYTE, 640, 1);\n  sp1_AddColSpr(sp, SP1_DRAW_MASK2,    SP1_TYPE_2BYTE, 1280, 1);\n  sp1_AddColSpr(sp, SP1_DRAW_MASK2RB,  SP1_TYPE_2BYTE, 0, 1);\n\n  sp1_IterateSprChar(sp, initialiseColour);\n\n  return sp;\n}\n```\nsp1_CreateSpr. \nFirst param is Left bound of mask (if masked), second says that as it is masked it has 2 BYTES (that is mask,pixels). \nThird param is number_of_tiles + 1. Each tile is 8px, so if it is 24px heigh, third param will equal FOUR. \nLast param is the distance from background, the plane, (higher is nearer to the 'screen').\n\nsp1_AddColSpr. One per column. A 24px sprite will have three AddColSpr, two normal and one of right bound.\nFirst param is created sprite, second param is if it has mask or not, third param will be 2BYTE in case of mask.\nFourth param will be WHERE the column is located. For a masked 24px sprite (24 * 2 = 48 + 16 padding = 64). \nIn my png2sp1sprite (pending fix), I'm writing the animation frames together and columns far away, \nso `64*[NUMBER_OF_FRAMES e.g. 10]=640` will be the first AddColSpr, while `128*10=1280` will be this value for second call to AddColSpr.\nLast call will have just sprite, type of mask, number of bits, and lastly, the plane.\n\nBut I guess you want to see the sprite now, add this in MAIN\n```C\nint main()\n{\n  struct sp1_ss* sp = add_sprite_misifu();\n```\n\nI'd like to move it move it, ... sure!\n```C\n  while(1) {\n     // sprite, rectangle, offset (animations, use 1 instead of zero if animated), y, x, rotationy, rotationx\n     sp1_MoveSprAbs(sp, \u0026full_screen, (void*) 1, 10, 12, 0, 0);\n  }\n```\n### Moving a sprite\nNow we have the MoveSprAbs it's obvious that we'll need to set X, Y coordinates in vars and modify them from keyboard touches.\nIntroduced in branch example1: https://github.com/jsmolina/z88dk-tutorial-sp1/tree/example1\n`input.h` contains different functions `in_stick_keyboard` or `in_stick_kempston`. So the best is creating a function pointer to them,\nallowing the user to choose one using a menu or detecting fire button is pressed in one of the devices.\nThen function pointer `joy` can be called directly in game loop:\n`in = (joy)(\u0026joy_keys);`\nSo you can verify which one was pressed using binary checks like `in \u0026 IN_STICK_UP`.\nThe last two params, roty and rotx are a bit tricky, they are used for repositioning the sprite inside its box. Imagine the case of having a 24x24px sprite, but sometimes it is lying down or sometimes hanging. In that case it's possible that you need some readjustment to not drive you crazy with collisions.\n\nBut it moves so fast, right? It's time for the tricks... \n\n### Collisions between sprites\nAs any videogame sometimes there will be a collission. Best way to do them in anyvideogame are 'bubble collisions', which means calculating the absolute difference between x1 and x2, then y1 and y2. For complex sprites, consider that sprite x and y are the topmost and leftmost part of the sprite, NOT the center (unless you adjust it using hrot and vrot seen before).\n\n`has_collision` function in this file shows the easyest way to do it:\nhttps://github.com/jsmolina/z88dk-tutorial-sp1/blob/master/logic.c#L266\nIn my case, it returns a pointer to the 'eaten' ghost, so it's easy then to perform some actions over it (like changing ghost state or moving it home).\n\n### Slowdown games\nBad news, you don't have a RTC here. \n\nBut there are solutions:\n* IM2 https://chuntey.wordpress.com/2013/10/02/how-to-write-zx-spectrum-games-chapter-17/\n* Floating bus http://sky.relative-path.com/zx/floating_bus.html\n\nIM2 is an interruption that will run 50 times per second. It is useful for controlling a bit the speed of the game.\n\nSetting it is tricky:\n```\n   im2_init((void *)0xd000); // CRT_ORG = 25124\n   memset((void *)0xd000, 0xd1, 257);\n\n   z80_bpoke(0xd1d1, 0xc3);\n   z80_wpoke(0xd1d2, (unsigned int)isr);\n\n   intrinsic_ei();\n```\nThen you define the function this way in z88dk:\n```\nIM2_DEFINE_ISR(isr)\n{\n   // update the clock\n   ++tick;\n\n}\n```\nNow you have a tick that is incremented 50 times per second... so slowing it down is just controlling that number\nas any other timer.\nSee this change and a lot of other things in this pull request: https://github.com/jsmolina/z88dk-tutorial-sp1/pull/4\nBut basically take a look on int.c and the call to wait(); in main loop.\n\n# Tiles\nUse this pull request to see what was needed to change to use tiles: https://github.com/jsmolina/z88dk-tutorial-sp1/pull/3\n\nAs you know, spectrum has the User-Defined-Graphic (UDG). Splib offers them for backgrounds, and the sky is the limit... \nGenerate any 8x8 tile, and just execute png2udg. \nThen copy the var that the python script will generate somewhere in your code:\n```\n➭ png2udg ./build/tile1.png\nconst uint8_t tile1[] = {0x10, 0x18, 0x18, 0x3c, 0x7c, 0x7e, 0xfe, 0xff};\n```\n\nOr if you prefer to use banking, maybe asm will be easier:\n```\n➭ png2udg ./build/tile1.png -a\n\nSECTION rodata_user\nPUBLIC _tile1\n._tile1\ndefb @00010000, @00011000, @00011000, @00111100, @01111100, @01111110, @11111110, @11111111\n\n```\n\nand of course defining variable accessible in C code\n`extern uint8_t tile1[];`\n\nAnd then when painting level, just use:\n``` \nsp1_TileEntry('b', tile1);\nsp1_PrintAt(17, 15, INK_BLACK | PAPER_CYAN, 'b');\n```\nSP1 will take care for you of redrawing. \nThink on a game with pacman and 5 enemies. Everytime you draw the hero sprite, and the 5 enemies,\nsp1 invalidates the areas where you draw them. That allows SPLIB to only redraw that areas.\nIf an area is invalidated twice in one loop, it will only redraw it once.\n\nThat's not so useful for scrolling, so if you think on scrolling, then it's maybe \nbetter to do it yourself.\n\n### .AY music\nhttps://zxspectrumcoding.wordpress.com/2022/04/16/128k-programming-basics-using-z88dk-and-the-sccz80-compiler-lesson-13/#more-1150\n\n## Memory ##\nLess lines are better as memory is short. e.g. if you are going to assign four struct properties, don't do it repeating the lines again and again, do it.\n\n```\nfoo[0].x = 5;\nfoo[0].y = 6;\nfoo[1].x = 8;\nfoo[1].y = 9;\n...\n```\nthis one will be better:\n```\nvoid assign_foo(uint8_t i, uint8_t x, uint8_t y) {\n  foo[i].x = x;\n  foo[i].y = y;\n}\n\nassign_foo(0, 5, 6);\nassign_foo(1, 8, 9);\n```\n\n### How to do paging (128k)\nTo be able to page, normal compiling won't be enough so you'll need to create a custom loader.\n\nThis loader will be a BASIC PROGRAM, like this loader.bas:\n```\n10 BORDER 0: PAPER 0\n20 CLEAR VAL \"24499\"\n25 POKE VAL \"23739\",VAL \"111\"\n30 LOAD \"\"SCREEN$\n40 LOAD \"\"CODE\n45 POKE VAL \"23388\",VAL \"22\"\n50 OUT VAL \"32765\",PEEK VAL \"23388\"\n60 LOAD \"\"CODE\n65 POKE VAL \"23388\",VAL \"16\"\n70 OUT VAL \"32765\",PEEK VAL \"23388\"\n80 RANDOMIZE USR VAL \"25000\"\n\n```\n1. Skip that Bytes: name message that is printed up on loading the code block overwrite part of the screen: POKE VAL \"23739\",VAL \"111\"\n2. Load in page 0 both code and screen: `30 LOAD \"\"SCREEN$` and `40 LOAD \"\"CODE`\n3. By using POKE 23388,16+n'` where 'n' is a number between 0 and 7, you can make the\ncomputer switch in page 'n' of the RAM. You will then be able to use\nPEEK and POKE in the normal way to examine and change the page.\n4. Actual change of value using port: `OUT VAL \"32765\",PEEK VAL \"23388\"\n5. Run the game from CRT_ORG_CODE (defined in zpragma.inc): `RANDOMIZE USR VAL \"25000\"`\n\nAnd then you'll convert the .bin files to real TAP files, and concat them finally in one unique file:\n```\n    # linker:\n   \tzcc +zx -v -m -startup=31 -clib=sdcc_iy -SO3 --max-allocs-per-node200000 @zproject.lst -pragma-include:zpragma.inc -o alley\n\n\tappmake +zx -b screen.scr --org 16384 --noloader --blockname screen -o screen.tap\n\tappmake +zx -b alley_CODE.bin --org 25000 --noloader --blockname code -o code.tap\n\tappmake +zx -b alley_BANK_6.bin --org 49152 --noloader --blockname bank6 -o bank6.tap\n\ttouch alley.tap\n\trm alley.tap\n\tcat loader.tap screen.tap code.tap bank6.tap \u003e alley.tap\n\n```\n1. loader.tap is the previous BASIC code\n2. screen.tap is just the conversion of the screen to tap with first appmake\n3. code.tap is the actual game code.\n4. bank6 is the data/code for 6th bank.\n\n### How to load complex backgrounds and let SP1 manage them\n(Copied from z88dk forums)\n\nYes but I think I would use \"sp1_IterateUpdateRect(rect,func)\" for this instead.  If you pass it a rectangle that covers the full screen, this function will visit all the \"struct sp1_update\" in the screen in left to right, top to bottom order.  Each \"struct sp1_update\" is one character square.  So you could do something like this:\n\n\n```\nunsigned char screen[6144];\n\nunsigned char csx, csy;\nunsigned char *csdst;\n\nvoid copy_screen(struct sp1_update *u)\n{\n   unsigned char *p;\n\n   // locate character square on screen\n\n   p = zx_cxy2saddr(csx, csy);\n\n   // store graphic location and colour in struct update\n\n   u-\u003etile = (unsigned int)csdst;\n   u-\u003ecolour = *zx_saddr2aaddr(p);\n\n   // copy screen graphics to screen buffer\n\n   for (unsigned char i = 0; i != 8; ++i)\n   {\n      *csdst++ = *p;\n      p += 256;\n   }\n\n   // next character square\n\n   if (++csx == 32)\n   {\n      csx = 0;\n      ++csy;\n   }\n}\n\n...\n\n// copy screen from memory bank\n...\nmemcpy(16384,...);\n\n// create screen in buffer from screen$\n\ncsx = csy = 0;\ncsdst = screen;\n\nsp1_IterateUpdateRect(\u0026fs, copy_screen);   // fs = sp1_Rect covering full screen\n```\nYou need 6144 bytes to store the pixels of the screen$ if your display is full size (here I made an array \"screen\" but you probably want to control where that is placed).\n\nInstead of storing your screens full size you can compress them.  zx7 is included in z88dk so you can simply do \"zx7 -f intro.scr\" to compress the screen$ before including it as a binary.  In your program:\n\n\n```\n#include \u003ccompress/zx7.h\u003e\n\ndzx7_standard(cartoon0, 16384);  // the original author made this src,dst even though c is normally dst,src\n\n## Text Printing\n`Zx_print_int _chr _char _str`\n`Zx_print_ink _border for colours`\n```\n\n#### Automatic Banking\nThere's also a banking option that z88dk will automatically do it for you:\nhttps://github.com/z88dk/z88dk/blob/master/examples/banked/\n\n### Masks\nMasks should have at least one pixel bordering the sprite.\n* Misifu mask: https://github.com/jsmolina/speccy-misifu/blob/master/sprites/cat_sprites_mask.png\n* Misifu sprite: https://github.com/jsmolina/speccy-misifu/blob/master/sprites/cat_sprites.png\n* AND there you have a very simple example of using masks for drawing a circle with animations included:\nhttps://github.com/jsmolina/png2sp1sprite/tree/master/sample\n\n* And Some info about offsets: https://www.z88dk.org/forum/viewtopic.php?id=10277\n\n### Fonts\n(Thanks Timmy)\nfzx is pretty much incompatible with splib... one of them uses a buffer while the other prints directly on screen.\nusual way to go is to override the ascii table with a wider udg (8x multiple width)\n```\nsp1_TileEntry(33, font1);\n   for (i=1; i\u003c46; i++)\n   {\n        sp1_TileEntry(45+i, font1+i*8);\n   }\n ```\nsee for how we added font:\n\nhttps://github.com/jsmolina/z88dk-tutorial-sp1/pull/46   \n### Credits\nCredits to jarlaxe for the graphics.\n\n\n### Assembly\nCredits to ThePoPe for Assemby and AY code.\n\n```\nplaySirena:\n    ld hl, tabla_offsets\n    ld a, (current_siren)\n    dec a\n    add a,a\n    ld l,a\n    jr nc, no_acarrea\n    inc h\n    no_acarrea:\n        ld a,(hl)\n        inc hl\n        ld h,(hl)\n        ld l,a\n        ld a, 2\n        call FxStop\n        ld a, 2\n\t    call Load_Fx\n        ret\ntabla_offsets:\n    dw fxSirena1\n    dw fxSirena2\n    dw fxSirena3\n    dw fxSirena4\n    dw fxSirena5\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsmolina%2Fz88dk-tutorial-sp1","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjsmolina%2Fz88dk-tutorial-sp1","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsmolina%2Fz88dk-tutorial-sp1/lists"}