#ifndef SAF_H #define SAF_H /** @file saf.h Small Abstract Fish (SAF) [] [][][][][] [][][] [][] [][] [] [] XX XX[] [] XXXX [] [][] [] [][][] [][] [] [][][][][] Simple interface for programming small portable games, especially for open consoles but also the PC and other platforms. Some attributes of the SAF console are: - 64 x 64 pixels display - framebuffer - 256 colors, RGB332 palette - 25 FPS - 7 buttons - simple speaker - Von Neumann architecture (single memory space for program and data) - no limit on resource usage (RAM, cycles, cores, ...) - behavior such as rasterization should be the same on all platforms (i.e. rasterization or circle is implemented internally rather than relying on the platform's circle rasterization) - without extensions, SAF programs should be deterministic by drummyfish, 2021 Released under CC0 1.0 (https://creativecommons.org/publicdomain/zero/1.0/) plus a waiver of all other intellectual property. The goal of this work is be and remain completely in the public domain forever, available for any use whatsoever. */ /* user settings, these can be redefined before including the library (platform specific settings are listed under each platform implementation later): */ #ifndef SAF_SETTING_FORCE_1BIT /** Forces monochrome (1 bit) graphics even on platforms that can display more than 2 colors. This can be good for testing how a color game would look like on 1 bit displays. */ #define SAF_SETTING_FORCE_1BIT 0 #endif #ifndef SAF_SETTING_1BIT_DITHER /** Says if dithering should be used for monochrome (1 bit) platforms. Whether to use dithering or not depends on each program, some look better with it, some don't. Dithering consumes significantly more CPU power.*/ #define SAF_SETTING_1BIT_DITHER 0 #endif #ifndef SAF_SETTING_FASTER_1BIT /** If non-zero, the conversion of color to 1bit (monochromatic) will be done with an approximation that is faster but gives a slightly different (incorrect) result. 1 will set a mild approximation, 2 will set a faster one, 3 a fastest one. This may be good for slow platforms. */ #define SAF_SETTING_FASTER_1BIT 1 #endif #ifndef SAF_SETTING_ENABLE_SOUND /** Can be used to disable sound at compile time. This is good to do if your game doesn't use any sounds so that the frontend doesn't have to unnecessarily manage sound libraries. Disabling sound may increase performance. */ #define SAF_SETTING_ENABLE_SOUND 1 #endif #ifndef SAF_SETTING_ENABLE_SAVES /** If 0, persistent memory for saves will be disabled so that saved data will only last during the program run. Disabling saves for games that don't use them may help the compiler optimize the program and not include libraries it won't need. */ #define SAF_SETTING_ENABLE_SAVES 1 #endif #ifndef SAF_SETTING_BACKGROUND_COLOR /** Specifies the color that should be used as a background, e.g. on platforms that have regions on screen where the game isn't drawn due to non-square resolution. */ #define SAF_SETTING_BACKGROUND_COLOR 0 #endif #include /* ============================= FOR PROGRAMS ================================== These are resources (functions, macros, ...) that are to be used by SAF client programs. If you are creating a program (a game etc.), only use these. A program is REQUIRED to implement: - SAF_PROGRAM_NAME macro: this must be set to a string with the program's name (e.g. #define SAF_PROGRAM_NAME "My game"). All version of the program should keep the same name as this name may be used e.g. to compute a hash that will determine its save address in EEPROM. - SAF_init function: in this function program should be initialized - SAF_loop function: this function handles the main loop Before including saf.h a platform also needs to be specified by defining one of the possible SAF_PLATFORM_* macros. The program must NOT implement the main() function. */ // do NOT redefine these macros, they're read-only: #define SAF_SCREEN_WIDTH 64 #define SAF_SCREEN_HEIGHT 64 #define SAF_FPS 25 ///< A divisor of 1000 prevents desync with RT. #define SAF_SAVE_SIZE 32 #define SAF_MS_PER_FRAME (1000 / SAF_FPS) #define SAF_VERSION_STRING "1.0d" #define SAF_BUTTON_UP 0x00 #define SAF_BUTTON_DOWN 0x01 #define SAF_BUTTON_LEFT 0x02 #define SAF_BUTTON_RIGHT 0x03 #define SAF_BUTTON_A 0x04 #define SAF_BUTTON_B 0x05 #define SAF_BUTTON_C 0x06 #define SAF_BUTTONS 7 ///< number of buttons #define SAF_COLOR_BLACK 0x00 #define SAF_COLOR_WHITE 0xff #define SAF_COLOR_GRAY 0x92 #define SAF_COLOR_GRAY_DARK 0x49 #define SAF_COLOR_RED 0xe0 #define SAF_COLOR_RED_DARK 0x80 #define SAF_COLOR_GREEN 0x1c #define SAF_COLOR_GREEN_DARK 0x10 #define SAF_COLOR_BLUE 0x03 #define SAF_COLOR_BLUE_DARK 0x01 #define SAF_COLOR_YELLOW 0xf8 #define SAF_COLOR_ORANGE 0xf0 #define SAF_COLOR_BROWN 0x8d #define SAF_COLOR_RGB(r,g,b) (((r / 32) << 5) | ((g / 32) << 2) | (b / 64)) #define SAF_SOUND_BEEP 0x00 ///< beep sound for special events #define SAF_SOUND_CLICK 0x01 ///< click sound, e.g. for menu #define SAF_SOUND_BOOM 0x02 ///< boom sound, e.g. for shooting #define SAF_SOUND_BUMP 0x03 ///< bump sound, e.g. for hitting walls #define SAF_SOUNDS 4 ///< number of sounds #define SAF_TRANSFORM_NONE 0x00 #define SAF_TRANSFORM_ROTATE_90 0x01 #define SAF_TRANSFORM_ROTATE_180 0x02 #define SAF_TRANSFORM_ROTATE_270 0x03 #define SAF_TRANSFORM_FLIP 0x04 ///< horizontal flip before rotation #define SAF_TRANSFORM_SCALE_2 0x08 #define SAF_TRANSFORM_SCALE_3 0x10 #define SAF_TRANSFORM_SCALE_4 0x18 #define SAF_TRANSFORM_INVERT 0x20 ///< invert colors #define SAF_INFO_STRING \ "made with SAF (SmallAbstractFish) library v. " SAF_VERSION_STRING // these will potentially be redefined by each platform #define SAF_PLATFORM_NAME "platform" #define SAF_PLATFORM_COLOR_COUNT 256 #define SAF_PLATFORM_BUTTON_COUNT 7 #define SAF_PLATFORM_RAM 0 #define SAF_PLATFORM_FREQUENCY 0 #define SAF_PLATFORM_HAS_SAVES 1 #define SAF_PLATFORM_HAS_SOUND 1 #define SAF_PLATFORM_HARWARD 0 ///< Harward architecture #define SAF_LOGO_IMAGE 0xbee3c1938dc1e3be ///< 8x8 1b logo as 64 bit int #ifndef SAF_PROGRAM_NAME #error SAF_PROGRAM_NAME has to be defined before including the library. #endif /** Implement this function in your program and put initialization code in it. Frontend will call this when the program starts to initialize it. */ void SAF_init(void); /** Implement this function in your program and put main loop code inside it. This function will be called periodically SAF_FPS times per second. When the function finishes, the framebuffer is presented to screen. The frame buffer is NOT cleared before this function is called. The function should return non-zero if the program continues or 0 if the program has ended. */ uint8_t SAF_loop(void); /** Returns the number of frames for which a button has been continuously held, up to 255. If button >= SAF_BUTTONS, 0 will be returned. */ uint8_t SAF_buttonPressed(uint8_t button); /** Checks if the button has been pressed exactly in the current frame. */ static inline uint8_t SAF_buttonJustPressed(uint8_t button); /** Plays given sound. */ void SAF_playSound(uint8_t sound); /** Saves a byte of data to persistent storage (e.g. a file, cookie etc.). If index >= SAF_SAVE_SIZE, nothing happens. WARNING: it may potentially be bad to call this function extremely often as on some platforms the save memory may be slow (disk) or prone to wearing off (EEPROM). The function tries to eliminate the writes, but you should also try to reduce the calls if possible. */ void SAF_save(uint8_t index, uint8_t data); /** Loads a byte from persistent storage (saved with SAF_save). If no data were ever saved with SAF_save at the index, 0 is returned. 0 is always returned for index >= SAF_SAVE_SIZE. WARNING: The function keeps a cache of loaded values so that loading from the actual save memory only happens at most once per program run. */ uint8_t SAF_load(uint8_t index); /** Gets the number of frames from start of the program. */ static inline uint32_t SAF_frame(void); /** Gets the time from start of the program in milliseconds. */ static inline uint32_t SAF_time(void); /** Returns a simple pseudorandom number. The number sequence will be the same in each program run and will repeat after 256 calls. SAF_randomSeed() can be called to seed this pseudorandom generator. */ uint8_t SAF_random(void); /** Seeds the pseudorandom generator with an initial number. Numbers returned by SAF_random depend on this value. The generator is automatically seeded with 0 at the start of a program. */ static inline void SAF_randomSeed(uint8_t seed); /** Computes sin function of the argument (255 corresponds to 2*pi, i.e. the full angle). Returns a value between -127 to 127 (including). */ int8_t SAF_sin(uint8_t phase); int8_t SAF_cos(uint8_t phase); /** Computes integer square root of a number. */ uint16_t SAF_sqrt(uint32_t number); /** Returns a 332 color closest to given RGB values. */ uint8_t SAF_colorFromRGB(uint8_t red, uint8_t green, uint8_t blue); /** Converts given 332 color to amount of red, green and blue. This conversion aligns the blue levels with red/green levels so that it is possible to get true gray. */ void SAF_colorToRGB(uint8_t colorIndex, uint8_t *red, uint8_t *green, uint8_t *blue); /** Converts given 332 color to an approximate 8bit grayscale value. Note that doing this per-pixel can negatively affect performance, in which case you may consider creating a lookup table using this function. */ static inline uint8_t SAF_colorToGrayscale(uint8_t colorIndex); /** Converts given 332 color to a 1 bit value (black&white). The result returned will either be 0 (black) or non-zero (white). The conversion performed by this function is affected by SAF_SETTING_FASTER_1BIT. */ static inline uint8_t SAF_colorTo1Bit(uint8_t colorIndex); /** Returns an "opposite" color of given 332 color. */ static inline uint8_t SAF_colorInvert(uint8_t color); /** Sets a single pixel of the frame buffer to given color. */ void SAF_drawPixel(int8_t x, int8_t y, uint8_t color); /** Draws a rectangle. */ void SAF_drawRect(int8_t x, int8_t y, int8_t width, int8_t height, uint8_t color, uint8_t filled); /** Draws a line using the DDA algorithm. */ void SAF_drawLine(int8_t x1, int8_t y1, int8_t x2, int8_t y2, uint8_t color); /** Draws a circle. */ void SAF_drawCircle(int8_t x, int8_t y, uint8_t radius, uint8_t color, uint8_t filled); /** Clears the screen with given color, typically called before rendering a new frame. */ static inline void SAF_clearScreen(uint8_t color); /** Draws given text with the built-in 4x4 font. */ int8_t SAF_drawText(const char *text, int8_t x, int8_t y, uint8_t color, uint8_t size); /** Gets the built-in character mask, in case you want to draw the font character yourself. The 4x4 character is returned as a 2 byte binary image. */ void SAF_getFontCharacter(uint8_t asciiIndex, uint8_t result[2]); /** Draws an uncompressed 332 color image. Transformation can be applies by passing a bitwise or value or SAF_TRANSFORM_* values. The image format is following: 1st byte unsigned width, 2nd byte is unsigned height, following bytes each hold the 332 color of pixels starting from top left of the image and going right and down. */ void SAF_drawImage(const uint8_t *image, int8_t x, int8_t y, uint8_t transform, uint8_t transparentColor); /** Same as SAF_drawImage but takes a compressed image as an input. This will most likely be slower than SAF_drawImage but will save ~2/3 memory on images. The compressed format is both lossy (palette reduction) and lossless (RLE). It is following: 1st byte is unsigned width, 2nd byte is unsigned height, the following 16 bytes are the image palette, then RLE bytes follow: each RLE byte specifies the palette color index in its lower 4 bits and the number of repetitions of that color in upper 4 bits (so e.g. value 0 means 1 pixel will be drawn). Images can be compressed to this format by tools that come with SAF. */ void SAF_drawImageCompressed(const uint8_t *image, int8_t x, int8_t y, uint8_t transform, uint8_t transparentColor); /** Same as SAF_drawImage but for 1bit (monochrome) images. Transparency mask (in the same format as the drawn image) can be used. The 1bit image format is following: 1st byte is unsigned width, 2nd byte is unsigned height, following bytes encode bits if the image, each byte holds 8 bits, MSB coming as 1st bit of the byte, going from top left of the image to the right and down. */ void SAF_drawImage1Bit(const uint8_t *image, int8_t x, int8_t y, const uint8_t *mask, uint8_t color1, uint8_t color2, uint8_t transform); /** Converts integer to string. The string must have enough space allocated (safe size is 12). The string will be zero terminated by the function. Pointer identical to "string" will be returned. */ char *SAF_intToStr(int32_t number, char *string); /** Same as SAF_intToStr but for floats. The safe allocated size for the string is 23. Maximum decimals in the result will be 10. */ char *SAF_floatToStr(float number, char *string, uint8_t decimals); /** RESERVED for possible future implementation of extension via a text protocol, at the moment this function does nothing. The extension should work like this: the client program will send a string (call this function) and receive a string from the frontend (the return value). This may be exploited e.g. for network communication or file system operations. Empty string means an empty answer and will returned if the the extension is unsupported or similar cases. The string passed to the function can be dealocated or changed after the call, the frontend should make a copy if it needs it. */ const char *SAF_extension(const char *string); /* ============================ FOR FRONTENDS ================================== These functions are NOT to be used by SAF client programs, they are for frontend implementations, i.e. if you're adding a new platform support. A frontend is normally required to do the following (but see below): - Redefine some or all of SAF_PLATFORM_* macros (e.g. the platform name, button count etc.). First undefine the macro and define again (to prevent warnings). - Implement the specific SAF_FE_* functions that are required to be implemented (see below); The functions mostly correspond to the client functions (e.g. SAF_drawPixel vs SAF_FE_drawPixel), but don't have to preform parameter checks. Call SAF_FE_init and SAF_FE_loop in the right places. The remaining SAF_FE_* functions are for the convenience of frontends that you may or may not use as you wish. - The frame buffer should be initialized to all zeros at the beginning of your frontend program. - Implement the main program body (e.g. the main() function or setup/loop Arduino functions) and call SAF_FE_init and SAF_FE_loop. Frame buffer should be presented to the screen after SAF_FE_loop finishes. Do NOT clear the frame buffer in main loop, the program is supposed to do this. - Init the console's state, i.e. clear screen to all black, buttons states to 0 etc. - Try to respect and take into account SAF_SETTING_* macros. - Try to make your global identifiers unlikely to collide with the user program, i.e. you may e.g. prefix them with '_'. When implementing a new frontend take a look at already implemented ones to see how it's done. Another way to implement a frontend is to use a partly preimplemented PC stdlib generic frontend with advanced. For details look at SAF_FE_GENERIC_FRONTEND. */ //~~~ IMPLEMENT THE FOLLOWING ~~~ /** DO NOT USE IN PROGRAMS, this is for frontends only! Programs should use SAF_drawPixel instead. Implement this function in your platform. Draws pixel to the screen back buffer, i.e. this shouldn't be visible on the display right after calling this function, but only when the screen is updated by the frontend at the end of the frame. The coordinates passed are guaranteed to be from 0 to 63, therefore no bound check is needed to be performed by this function. */ static inline void SAF_FE_drawPixel(uint8_t x, uint8_t y, uint8_t color); /** DO NOT USE IN PROGRAMS, this is for frontends only! Programs should use SAF_playSound instead. Implement this function in your platform. */ static inline void SAF_FE_playSound(uint8_t sound); /** DO NOT USE IN PROGRAMS, this is for frontends only! Programs should use SAF_save instead. Implement this function in your platform. This function should save given data byte to a specified address (index) in the persistent storage to last between HW resets. You can use e.g. files, cookies or EEPROM to implement this memory. Index passed to this function will always be < SAF_SAVE_SIZE. You don't have to implement any optimizations (e.g. buffers, ignoring overwrites of same values etc.) as SAF already does them internally. */ static inline void SAF_FE_save(uint8_t index, uint8_t data); /** DO NOT USE IN PROGRAMS, this is for frontends only! Programs should use SAF_load instead. Implement this function in your platform. This function should load and return data byte from specified address (index) in the persistent storage. This data was saved with SAF_FE_save. If no data have ever been written to that memory address, 0 should be returned. Index passed to this function will always be < SAF_SAVE_SIZE. You don't have to implement any optimizations (e.g. buffers) as SAF already does this internally. */ static inline uint8_t SAF_FE_load(uint8_t index); /** DO NOT USE IN PROGRAMS, this is for frontends only! Programs should use SAF_buttonPressed instead. Implement this function in your platform. This function should return a non-zero value if given button is pressed, or 0 if the button is not pressed. The function will be called for each button exactly one per frame (so there is no need to worry about returning a consistent value during a frame). Button number passed to this function will always be < SAF_BUTTONS. */ static inline uint8_t SAF_FE_buttonPressed(uint8_t button); /* RESERVED, at this moment this function should always return an empty string (a pointer to value 0). */ static inline const char *SAF_FE_extension(const char *string); // ~~~ CALL THE FOLLOWING IN RIGHT PLACES ~~~ /** DO NOT USE IN PROGRAMS, this is for frontends only! In your platform implementation call this function once in SAF_MS_PER_FRAME. This function calls the client program's SAF_loop (don't call this directly). If this function returns 0, halt the program, otherwise continue. */ static inline uint8_t SAF_FE_loop(void); /** DO NOT USE IN PROGRAMS, this is for frontends only! In your platform implementation call this function at the start of the program. */ static inline void SAF_FE_init(void); // ~~~ FOR FRONTEND CONVENIENCE ~~~ /* The following macros can optinally be defined by your frontend: SAF_FE_GENERIC_FRONTEND enables a partly preimplemented generic frontend that uses stdio functions and has advanced features (screenshot taking, volume control, ...). If this is defined, you don't have to implement SAF_FE_save, SAF_FE_load, SAF_FE_drawPixel, SAF_FE_buttonPressed, SAF_FE_extension and the main function, but you need to implement some other functions: see SAF_FE_GF_* functions. This frontend also handles emscripten integration. SAF_FE_STDIO_SAVE_LOAD includes the stdio.h library and implements SAF_FE_save and SAF_FE_load using stdio files (so you don't have to implement these). With emscripten cookies are used instead of stdio files. */ char SAF_FE_emptyString[1] = {0}; #define _SAF_UNUSED(identifier) (void)(identifier) ///< for suppressing warnings /** Returns a simple 16bit hash of given string, useful for e.g. determining the save location in EEPROM based on the program's name. If you need an 8bit hash, just take the lower 8 bits of this hash. */ uint16_t SAF_FE_hashStr(const char *str); /** Parses CLI arguments of form '-x' or '-xN' (x and N being chars). After calling, paramValues will hold the value of corresponding flags, e.g. if -s2 was passed, paramValues['s'] will hold value '2'. If parameter wasn't present, the value will be 0. If the parameter was present without a value (-x), the value will be 1. */ void SAF_FE_paramParse(int argc, char **argv, uint8_t paramValues[128]); /** Converts a 332 color to monochrome (1bit) color, taking into account potential dithering. This function should be used by monochromatic platforms. */ static inline uint8_t SAF_FE_colorTo1Bit(uint8_t color, uint8_t x, uint8_t y); /** Uses the pixel art scaling algorithm "scale2x" to expand a single pixel into four pixels depending on its neighbouring pixels. To scale whole screen use SAF_FE_scale2xScreen. */ void SAF_FE_scale2xPixel(uint8_t middle, uint8_t top, uint8_t right, uint8_t bottom, uint8_t left, uint8_t result[4]); /** Quickly Scales the whole SAF screen 4 times (twice in each dimention) using the smart "scale2x" pixel art scaling algorithm. */ void SAF_FE_scale2xScreen( const uint8_t screen[SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT], uint8_t result[SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT * 4]); #define SAF_FE_SOUND_SAMPLE_COUNT 1024 /** The function returns an 8 bit 8 KHz sample of a default built-in sound of this library, for implementing SAF_FE_playSound. If you're using custom external sounds or the platform's built-in beeps, don't use this. Each sound has SAF_FE_SOUND_SAMPLE_COUNT samples. */ int8_t SAF_FE_getSoundSample(uint8_t sound, uint16_t sampleNumber); /** If SAF_FE_GENERIC_FRONTEND is defined, you need to implement this function in which you initialize your frontend. CLIParameters holds values of parsed command line arguments of format as returned by SAF_FE_paramParse. Certain flags (see generic frontend's help) are used by the generic frontend and these are guaranteed to hold only valid values when accessed (e.g. 's' will always have values '1' to '8'). Your frontend can use the rest of the flags as it wishes (to include these into help define SAF_FE_GF_EXTRA_HELP). */ void SAF_FE_GF_init(uint8_t CLIParameters[128]); /** If SAF_FE_GENERIC_FRONTEND is defined, you need to implement this function in which you free your allocated resources. This function will be called before the program exit. */ void SAF_FE_GF_end(); /** If SAF_FE_GENERIC_FRONTEND is defined, you need to implement this function in which you handle the main loop (only things that your frontend needs, the rest us handled by the generic frontend). CLIParameters is the same array as CLIParameters in SAF_FE_GF_init. The function should return a non-zero value if the program keeps running and 0 if the program has been exited (e.g. by closing the window). */ uint8_t SAF_FE_GF_loop(uint8_t params[128]); const uint8_t *SAF_FE_GF_getScreenPointer(); /** If SAF_FE_GENERIC_FRONTEND is defined, you need to implement this function in which you copy the passed screen data into your frontend's screen. */ void SAF_FE_GF_present( const uint8_t screen[SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT]); /** If SAF_FE_GENERIC_FRONTEND is defined, you need to implement this function which should return a bool value indicating whether specific keyboard key is pressed or not. Lowercase letters ('a', 'b', 'c', ...) represent letter keys, 'U', 'D', 'L', 'R' represent arrow keys, 'E' is escape, 'X', 'Y', 'Z' are controller buttons. */ uint8_t SAF_FE_GF_keyPressed(char keyChar); /** If SAF_FE_GENERIC_FRONTEND is defined, you need to implement this function which should do two things: sleep (yield CPU) for sleepMs milliseconds, then return the current number of milliseconds (after the sleep) from the start of the program. */ uint32_t SAF_FE_GF_sleep(uint16_t sleepMs); /** If SAF_FE_GENERIC_FRONTEND is defined, you need to implement this function which should behave the same as SAF_FE_extension (at this moment should only return an empty string pointer).*/ const char *SAF_FE_GF_extension(const char *string); /// SAF palette as 565 values. #define SAF_FE_PALETTE_565 \ 0,9,18,31,288,297,306,319,576,585,594,607,864,873,882,895,1152,1161,1170,1183,\ 1440,1449,1458,1471,1728,1737,1746,1759,2016,2025,2034,2047,8192,8201,8210,\ 8223,8480,8489,8498,8511,8768,8777,8786,8799,9056,9065,9074,9087,9344,9353,\ 9362,9375,9632,9641,9650,9663,9920,9929,9938,9951,10208,10217,10226,10239,\ 18432,18441,18450,18463,18720,18729,18738,18751,19008,19017,19026,19039,19296,\ 19305,19314,19327,19584,19593,19602,19615,19872,19881,19890,19903,20160,20169,\ 20178,20191,20448,20457,20466,20479,26624,26633,26642,26655,26912,26921,26930,\ 26943,27200,27209,27218,27231,27488,27497,27506,27519,27776,27785,27794,27807,\ 28064,28073,28082,28095,28352,28361,28370,28383,28640,28649,28658,28671,36864,\ 36873,36882,36895,37152,37161,37170,37183,37440,37449,37458,37471,37728,37737,\ 37746,37759,38016,38025,38034,38047,38304,38313,38322,38335,38592,38601,38610,\ 38623,38880,38889,38898,38911,45056,45065,45074,45087,45344,45353,45362,45375,\ 45632,45641,45650,45663,45920,45929,45938,45951,46208,46217,46226,46239,46496,\ 46505,46514,46527,46784,46793,46802,46815,47072,47081,47090,47103,55296,55305,\ 55314,55327,55584,55593,55602,55615,55872,55881,55890,55903,56160,56169,56178,\ 56191,56448,56457,56466,56479,56736,56745,56754,56767,57024,57033,57042,57055,\ 57312,57321,57330,57343,63488,63497,63506,63519,63776,63785,63794,63807,64064,\ 64073,64082,64095,64352,64361,64370,64383,64640,64649,64658,64671,64928,64937,\ 64946,64959,65216,65225,65234,65247,65504,65513,65522,65535 //======================= PLATFORM FRONTENDS =================================== #if defined(SAF_PLATFORM_SDL2) || defined(SAF_PLATFORM_SDL2_TINY) #include // code common to all SDL frontends uint8_t _SDL_volume = 0; int8_t _SDL_currentSound = -1; uint16_t _SDL_soundPosition = 0; #if SAF_SETTING_ENABLE_SOUND void SAF_SDL_playSound(uint8_t sound) { _SDL_currentSound = sound; _SDL_soundPosition = 0; } void SAF_SDL_audioFillCallback(void *userdata, uint8_t *s, int l) { _SAF_UNUSED(userdata); int16_t *s16 = (int16_t *) s; l /= 2; for (int i = 0; i < l; ++i) { if (_SDL_currentSound < 0) *s16 = 0; else { *s16 = SAF_FE_getSoundSample(_SDL_currentSound,_SDL_soundPosition); _SDL_soundPosition++; if (_SDL_soundPosition >= SAF_FE_SOUND_SAMPLE_COUNT) { _SDL_currentSound = -1; _SDL_soundPosition = 0; } } s16++; } } uint8_t SAF_SDL_initAudio(void) { SDL_AudioSpec audioSpec; SDL_memset(&audioSpec, 0, sizeof(audioSpec)); audioSpec.callback = SAF_SDL_audioFillCallback; audioSpec.freq = 8000; audioSpec.format = AUDIO_S16; audioSpec.channels = 1; #ifdef __EMSCRIPTEN__ audioSpec.samples = 1024; #else audioSpec.samples = 256; #endif if (SDL_OpenAudio(&audioSpec,NULL) < 0) return 0; SDL_PauseAudio(0); return 1; } #endif // SAF_SETTING_ENABLE_SOUND #endif // SAF_PLATFORM_SDL2 || SAF_PLATFORM_SDL2_TINY #ifdef SAF_PLATFORM_NULL /* Null frontend, has no I/O implemented and runs at highest reachable FPS instead of SAF_FPS. This can be useful for testing, e.g. if you want to see the compiled size or performance of just the game without any frontend. ------------------------------------------------------------------------------*/ void SAF_FE_drawPixel(uint8_t x, uint8_t y, uint8_t color) { _SAF_UNUSED(x); _SAF_UNUSED(y); _SAF_UNUSED(color); } void SAF_FE_playSound(uint8_t sound) { _SAF_UNUSED(sound); } void SAF_FE_save(uint8_t index, uint8_t data) { _SAF_UNUSED(index); _SAF_UNUSED(data); } uint8_t SAF_FE_load(uint8_t index) { _SAF_UNUSED(index); return 0; } uint8_t SAF_FE_buttonPressed(uint8_t button) { _SAF_UNUSED(button); return 0; } const char *SAF_FE_extension(const char *string) { _SAF_UNUSED(string); return SAF_FE_emptyString; } int main(void) { SAF_FE_init(); while (SAF_FE_loop()); return 0; } #elif defined(SAF_PLATFORM_SDL2) /* SDL2 platform using the SAF_FE_GENERIC_FRONTEND, also usable with emscripten (browser JavaScript). requirements: libsdl2-dev, stdio.h, unistd.h, stdlib.h compiling: link SDL2, e.g. -lSDL2 (emscripten: -s USE_SDL=2) ------------------------------------------------------------------------------*/ #undef SAF_PLATFORM_NAME #define SAF_PLATFORM_NAME "SDL2" #define SAF_FE_GENERIC_FRONTEND #include #include // for malloc/free #include // for usleep const uint8_t *_SDL_keyboardState; SDL_Window *_SDL_window; SDL_Renderer *_SDL_renderer; SDL_Texture *_SDL_texture; uint8_t _SDL_pixelArtUpscale = 0; uint8_t *_SDL_upscaleScreen = 0; SDL_GameController *_SDL_controller = 0; void SAF_FE_playSound(uint8_t sound) { SAF_SDL_playSound(sound); } void SAF_FE_GF_init(uint8_t CLIParameters[128]) { SDL_Init( SDL_INIT_EVENTS | #if SAF_SETTING_ENABLE_SOUND SDL_INIT_AUDIO | #endif SDL_INIT_JOYSTICK); _SDL_volume = CLIParameters['v'] - '0'; if (CLIParameters['u'] != 0) { _SDL_pixelArtUpscale = 1; _SDL_upscaleScreen = malloc(SAF_SCREEN_WIDTH * SAF_SCREEN_WIDTH * 4); } uint16_t screenScale = CLIParameters['s'] - '0'; uint8_t fullscreen = screenScale == 0; if (screenScale == 0) screenScale = 1; _SDL_window = SDL_CreateWindow(SAF_PROGRAM_NAME, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SAF_SCREEN_WIDTH * screenScale, SAF_SCREEN_HEIGHT * screenScale,SDL_WINDOW_SHOWN); if (fullscreen) SDL_SetWindowFullscreen(_SDL_window,SDL_WINDOW_FULLSCREEN_DESKTOP); _SDL_renderer = SDL_CreateRenderer(_SDL_window,-1,0); _SDL_texture = SDL_CreateTexture(_SDL_renderer, SDL_PIXELFORMAT_RGB332,SDL_TEXTUREACCESS_STATIC, SAF_SCREEN_WIDTH * (_SDL_pixelArtUpscale ? 2 : 1), SAF_SCREEN_HEIGHT * (_SDL_pixelArtUpscale ? 2 : 1)); _SDL_keyboardState = SDL_GetKeyboardState(NULL); _SDL_controller = SDL_GameControllerOpen(0); SDL_PumpEvents(); SDL_GameControllerUpdate(); #if SAF_SETTING_ENABLE_SOUND if (!SAF_SDL_initAudio()) puts("SDL: could not initialize audio"); #endif } void SAF_FE_GF_end() { #if SAF_SETTING_ENABLE_SOUND SDL_PauseAudio(1); SDL_CloseAudio(); #endif if (_SDL_controller != 0) SDL_GameControllerClose(_SDL_controller); SDL_DestroyTexture(_SDL_texture); SDL_DestroyRenderer(_SDL_renderer); SDL_DestroyWindow(_SDL_window); if (_SDL_pixelArtUpscale) free(_SDL_upscaleScreen); } uint8_t SAF_FE_GF_loop(uint8_t params[128]) { _SDL_volume = params['v'] - '0'; SDL_Event event; while (SDL_PollEvent(&event)) if (event.type == SDL_QUIT) return 0; return 1; } void SAF_FE_GF_present(const uint8_t screen[SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT]) { if (_SDL_pixelArtUpscale) { SAF_FE_scale2xScreen(screen,_SDL_upscaleScreen); SDL_UpdateTexture(_SDL_texture,NULL,_SDL_upscaleScreen,SAF_SCREEN_WIDTH * 2); } else SDL_UpdateTexture(_SDL_texture,NULL,screen,SAF_SCREEN_WIDTH); SDL_RenderClear(_SDL_renderer); SDL_RenderCopy(_SDL_renderer,_SDL_texture,NULL,NULL); SDL_RenderPresent(_SDL_renderer); } uint8_t SAF_FE_GF_keyPressed(char keyChar) { if (keyChar <= 'z' && keyChar >= 'a') return _SDL_keyboardState[SDL_SCANCODE_A + (keyChar - 'a')]; #define b(x) ((_SDL_controller != NULL) && \ SDL_GameControllerGetButton(_SDL_controller,SDL_CONTROLLER_BUTTON_ ## x)) switch (keyChar) { case 'U': return _SDL_keyboardState[SDL_SCANCODE_UP] || b(DPAD_UP); break; case 'R': return _SDL_keyboardState[SDL_SCANCODE_RIGHT] || b(DPAD_RIGHT); break; case 'D': return _SDL_keyboardState[SDL_SCANCODE_DOWN] || b(DPAD_DOWN); break; case 'L': return _SDL_keyboardState[SDL_SCANCODE_LEFT] || b(DPAD_LEFT); break; case 'E': return _SDL_keyboardState[SDL_SCANCODE_ESCAPE]; break; case 'X': return b(X); break; case 'Y': return b(Y) || b(B); break; case 'Z': return b(A) || b(START); break; default: return 0; break; } #undef b } uint32_t SAF_FE_GF_sleep(uint16_t sleepMs) { if (sleepMs != 0) usleep(sleepMs * 1000); return SDL_GetTicks(); } const char *SAF_FE_GF_extension(const char *string) { _SAF_UNUSED(string); return SAF_FE_emptyString; } #elif defined(SAF_PLATFORM_SDL2_TINY) /* Minimal SDL2 frontend, this frontend does NOT work with Emscripten (use normal SDL2 platform for that). requirements: libsdl2-dev, stdio.h, unistd.h, stdlib.h compiling: link SDL2, e.g. -lSDL2 ------------------------------------------------------------------------------*/ #undef SAF_PLATFORM_NAME #define SAF_PLATFORM_NAME "SDL2 tiny" #define SAF_FE_STDIO_SAVE_LOAD #include #include // for usleep #ifndef SAF_SETTING_SDL2_TINY_SCALE #define SAF_SETTING_SDL2_TINY_SCALE 4 #endif #define SDL_UPSCALE #define SDL_SCREEN_WIDTH \ (SAF_SCREEN_WIDTH * SAF_SETTING_SDL2_TINY_SCALE) #define SDL_SCREEN_HEIGHT \ (SAF_SCREEN_HEIGHT * SAF_SETTING_SDL2_TINY_SCALE) const uint8_t *_SDL_keyboardState; uint8_t _SDL_screen[SDL_SCREEN_WIDTH * SDL_SCREEN_HEIGHT]; void SAF_FE_drawPixel(uint8_t x, uint8_t y, uint8_t color) { #if SAF_SETTING_SDL2_TINY_SCALE == 1 _SDL_screen[y * SDL_SCREEN_WIDTH + x] = color; #else uint8_t *pixel = _SDL_screen + y * (SDL_SCREEN_WIDTH * SAF_SETTING_SDL2_TINY_SCALE) + x * SAF_SETTING_SDL2_TINY_SCALE; for (int y = 0; y < SAF_SETTING_SDL2_TINY_SCALE; ++y) { for (int x = 0; x < SAF_SETTING_SDL2_TINY_SCALE; ++x) { *pixel = color; pixel++; } pixel += SDL_SCREEN_WIDTH - SAF_SETTING_SDL2_TINY_SCALE; } #endif } void SAF_FE_playSound(uint8_t sound) { SAF_SDL_playSound(sound); } uint8_t SAF_FE_buttonPressed(uint8_t button) { switch (button) { case SAF_BUTTON_UP: return _SDL_keyboardState[SDL_SCANCODE_W] || _SDL_keyboardState[SDL_SCANCODE_UP]; break; case SAF_BUTTON_DOWN: return _SDL_keyboardState[SDL_SCANCODE_S] || _SDL_keyboardState[SDL_SCANCODE_DOWN]; break; case SAF_BUTTON_LEFT: return _SDL_keyboardState[SDL_SCANCODE_A] || _SDL_keyboardState[SDL_SCANCODE_LEFT]; break; case SAF_BUTTON_RIGHT: return _SDL_keyboardState[SDL_SCANCODE_D] || _SDL_keyboardState[SDL_SCANCODE_RIGHT]; break; case SAF_BUTTON_A: return _SDL_keyboardState[SDL_SCANCODE_Y] || _SDL_keyboardState[SDL_SCANCODE_Z] || _SDL_keyboardState[SDL_SCANCODE_J] || _SDL_keyboardState[SDL_SCANCODE_SPACE]; break; case SAF_BUTTON_B: return _SDL_keyboardState[SDL_SCANCODE_X] || _SDL_keyboardState[SDL_SCANCODE_K] || _SDL_keyboardState[SDL_SCANCODE_RETURN]; break; case SAF_BUTTON_C: return _SDL_keyboardState[SDL_SCANCODE_C] || _SDL_keyboardState[SDL_SCANCODE_L]; break; default: return 0; break; } } static inline const char *SAF_FE_extension(const char *string) { _SAF_UNUSED(string); return SAF_FE_emptyString; } int main(int argc, char **argv) { uint8_t fullscreen = 0; _SDL_volume = 8; if (argc > 1 && argv[1][0] == '-' && argv[1][2] == 0) { if (argv[1][1] == 'f') fullscreen = 1; else if (argv[1][1] == 'm') _SDL_volume = 0; else if (argv[1][1] == 'h') { puts(SAF_PROGRAM_NAME ", " SAF_INFO_STRING " (" SAF_PLATFORM_NAME ")"); puts("-f = fullscreen, -m = mute"); puts("controls: WSAD (arrows), JKL (XYZC), ESC = exit"); return 0; } } SDL_Init( SDL_INIT_EVENTS | #if SAF_SETTING_ENABLE_SOUND SDL_INIT_AUDIO | #endif SDL_INIT_JOYSTICK); SDL_Window *window = SDL_CreateWindow(SAF_PROGRAM_NAME, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SDL_SCREEN_WIDTH,SDL_SCREEN_HEIGHT,SDL_WINDOW_SHOWN); if (fullscreen) SDL_SetWindowFullscreen(window,SDL_WINDOW_FULLSCREEN_DESKTOP); SDL_Renderer *renderer = SDL_CreateRenderer(window,-1,0); SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB332,SDL_TEXTUREACCESS_STATIC, SDL_SCREEN_WIDTH, SDL_SCREEN_HEIGHT); _SDL_keyboardState = SDL_GetKeyboardState(NULL); for (int i = 0; i < SDL_SCREEN_WIDTH * SDL_SCREEN_HEIGHT; ++i) _SDL_screen[i] = 0; SDL_PumpEvents(); SDL_GameControllerUpdate(); #if SAF_SETTING_ENABLE_SOUND if (!SAF_SDL_initAudio()) puts("SDL: could not initialize audio"); #endif SAF_FE_init(); uint32_t SAF_FE_GF_nextFrameTime = 0; while (1) { // SDL_PumpEvents(); SDL_Event event; while (SDL_PollEvent(&event)) if (event.type == SDL_QUIT) return 0; if (_SDL_keyboardState[SDL_SCANCODE_ESCAPE]) break; uint32_t time = SDL_GetTicks(); while (time >= SAF_FE_GF_nextFrameTime) { if (!SAF_FE_loop()) break; SAF_FE_GF_nextFrameTime += SAF_MS_PER_FRAME; } SDL_UpdateTexture(texture,NULL,_SDL_screen,SDL_SCREEN_WIDTH); SDL_RenderClear(renderer); SDL_RenderCopy(renderer,texture,NULL,NULL); SDL_RenderPresent(renderer); usleep(((SAF_FE_GF_nextFrameTime - time) * 3 / 4) * 1000); // relieve CPU } #if SAF_SETTING_ENABLE_SOUND SDL_PauseAudio(1); SDL_CloseAudio(); #endif SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); return 0; } #elif defined(SAF_PLATFORM_CSFML) /* CSFML (C binding for SFML) platform with SAF_FE_GENERIC_FRONTEND. requirements: libscfml-dev compiling: link CSFML, usually -lcsfml-graphics -lcsfml-window -lcsfml-system -lcsfml-audio ------------------------------------------------------------------------------*/ #undef SAF_PLATFORM_NAME #define SAF_PLATFORM_NAME "CSFML" #define SAF_FE_GENERIC_FRONTEND #include #include #include #include #include #include // for malloc/free sfClock *_CSFML_clock; sfRenderWindow* _CSFML_window; sfTexture* _CSFML_windowTexture; sfSprite* _CSFML_windowSprite; uint8_t _CSFML_screenSize = SAF_SCREEN_WIDTH; uint8_t _CSFML_SDL_pixelArtUpscale = 0; uint8_t *_CSFML_upscaledScreen = 0; #if SAF_SETTING_ENABLE_SOUND sfSound *_CSFML_sound; sfSoundBuffer *_CSFML_sounds[SAF_SOUNDS]; uint8_t _CSFML_previousVolume = 0; #endif uint32_t _CSFML_windowPixels[SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT * 4]; uint32_t _CSFML_paletteRGB32[256]; // SFML can't do 332, so precompute RGB here void SAF_FE_playSound(uint8_t sound) { #if SAF_SETTING_ENABLE_SOUND sfSound_setBuffer(_CSFML_sound,_CSFML_sounds[sound]); sfSound_play(_CSFML_sound); #else _SAF_UNUSED(sound); #endif } void SAF_FE_GF_init(uint8_t CLIParameters[128]) { for (int i = 0; i < 256; ++i) // precompute RGB palette { uint8_t r,g,b; SAF_colorToRGB(i,&r,&g,&b); _CSFML_paletteRGB32[i] = 0xff000000 | (((uint32_t) b) << 16) | (((uint32_t) g) << 8) | r; } if (CLIParameters['u'] != 0) { _CSFML_SDL_pixelArtUpscale = 1; _CSFML_upscaledScreen = malloc(SAF_SCREEN_WIDTH * SAF_SCREEN_WIDTH * 4); _CSFML_screenSize *= 2; } for (int i = 0; i < _CSFML_screenSize * _CSFML_screenSize; ++i) _CSFML_windowPixels[i] = 0; _CSFML_clock = sfClock_create(); sfClock_restart(_CSFML_clock); uint16_t screenScale = CLIParameters['s'] - '0'; uint8_t fullscreen = screenScale == 0; _CSFML_windowTexture = sfTexture_create(_CSFML_screenSize, _CSFML_screenSize); sfTexture_setSmooth(_CSFML_windowTexture,sfFalse); _CSFML_windowSprite = sfSprite_create(); sfVideoMode mode = {_CSFML_screenSize, _CSFML_screenSize, 32}; _CSFML_window = sfRenderWindow_create(mode, SAF_PROGRAM_NAME, fullscreen ? sfFullscreen : (sfResize | sfClose ), NULL); sfVector2i winPos; winPos.x = 1; winPos.y = 1; sfWindow_setPosition(_CSFML_window,winPos); sfSprite_setTexture(_CSFML_windowSprite,_CSFML_windowTexture,sfTrue); sfWindow_setVerticalSyncEnabled((sfWindow *) _CSFML_window,sfFalse); if (screenScale != 0) { sfVector2u winSize; winSize.x = _CSFML_screenSize * screenScale; winSize.y = winSize.x; sfWindow_setSize(_CSFML_window,winSize); } #if SAF_SETTING_ENABLE_SOUND _CSFML_sound = sfSound_create(); { int16_t samples[SAF_FE_SOUND_SAMPLE_COUNT]; for (int j = 0; j < SAF_SOUNDS; ++j) { for (int i = 0; i < SAF_FE_SOUND_SAMPLE_COUNT; ++i) samples[i] = ((int16_t) SAF_FE_getSoundSample(j,i)) << 7; _CSFML_sounds[j] = sfSoundBuffer_createFromSamples( samples,SAF_FE_SOUND_SAMPLE_COUNT,1,8000); } } #endif } void SAF_FE_GF_end() { #if SAF_SETTING_ENABLE_SOUND sfSound_destroy(_CSFML_sound); for (int i = 0; i < SAF_SOUNDS; ++i) sfSoundBuffer_destroy(_CSFML_sounds[i]); #endif sfClock_destroy(_CSFML_clock); sfRenderWindow_destroy(_CSFML_window); sfSprite_destroy(_CSFML_windowSprite); sfTexture_destroy(_CSFML_windowTexture); if (_CSFML_SDL_pixelArtUpscale) free(_CSFML_upscaledScreen); } uint8_t SAF_FE_GF_loop(uint8_t params[128]) { sfEvent event; while (sfRenderWindow_pollEvent(_CSFML_window,&event)); if (!sfRenderWindow_isOpen(_CSFML_window)) return 0; #if SAF_SETTING_ENABLE_SOUND int v = params['v'] - '0'; if (_CSFML_previousVolume != v) { sfSound_setVolume(_CSFML_sound,(v * 100) / 8); _CSFML_previousVolume = v; } #endif return 1; } void SAF_FE_GF_present( const uint8_t screen[SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT]) { const uint8_t *pixel = screen; uint32_t *pixel2 = _CSFML_windowPixels; if (_CSFML_SDL_pixelArtUpscale) { SAF_FE_scale2xScreen(screen,_CSFML_upscaledScreen); pixel = _CSFML_upscaledScreen; } for (int i = 0; i < _CSFML_screenSize * _CSFML_screenSize; ++i) { *pixel2 = _CSFML_paletteRGB32[*pixel]; pixel++; pixel2++; } sfTexture_updateFromPixels(_CSFML_windowTexture, (const sfUint8 *) _CSFML_windowPixels, _CSFML_screenSize,_CSFML_screenSize,0,0); sfRenderWindow_drawSprite(_CSFML_window,_CSFML_windowSprite,NULL); sfRenderWindow_display(_CSFML_window); } uint8_t SAF_FE_GF_keyPressed(char keyChar) { if (keyChar <= 'z' && keyChar >= 'a') return sfKeyboard_isKeyPressed(sfKeyA + (keyChar - 'a')); #define k(x) sfKeyboard_isKeyPressed(sfKey ## x) switch (keyChar) { case 'U': return k(Up) || (sfJoystick_getAxisPosition(0,sfJoystickY) <= -50); break; case 'D': return k(Down) || (sfJoystick_getAxisPosition(0,sfJoystickY) >= 50); break; case 'L': return k(Left) || (sfJoystick_getAxisPosition(0,sfJoystickX) <= -50); break; case 'R': return k(Right) || (sfJoystick_getAxisPosition(0,sfJoystickX) >= 50); break; case 'E': return k(Escape); break; case 'X': return sfJoystick_isButtonPressed(0,0) || sfJoystick_isButtonPressed(0,3); break; case 'Y': return sfJoystick_isButtonPressed(0,1); break; case 'Z': return sfJoystick_isButtonPressed(0,2); break; default: break; } #undef k return 0; } uint32_t SAF_FE_GF_sleep(uint16_t sleepMs) { sfTime t; t.microseconds = sleepMs * 1000; sfSleep(t); return sfClock_getElapsedTime(_CSFML_clock).microseconds / 1000; } const char *SAF_FE_GF_extension(const char *string) { _SAF_UNUSED(string); return SAF_FE_emptyString; } #elif defined(SAF_PLATFORM_POKITTO) /* Pokitto platform using the official PokittoLib. requirements: PokittoLib compiling: as any other pokitto program, leave My_settings.h empty ------------------------------------------------------------------------------*/ #ifndef SAF_SETTING_POKITTO_SCALE #define SAF_SETTING_POKITTO_SCALE 0 /**< type of screen scale for Pokitto, possible values: 0 (176x176), 1 (128x128), 2 (220x176) */ #endif #ifndef SAF_SETTING_POKITTO_VOLUME #define SAF_SETTING_POKITTO_VOLUME 4 //< 1 to 8 volume (if sound is on) #endif #ifndef SAF_SETTING_POKITTO_JOYHAT #define SAF_SETTING_POKITTO_JOYHAT 0 #endif #undef SAF_PLATFORM_NAME #define SAF_PLATFORM_NAME "Pokitto" #undef SAF_PLATFORM_RAM #define SAF_PLATFORM_RAM 36000 #undef SAF_PLATFORM_FREQUENCY #define SAF_PLATFORM_FREQUENCY ((_OSCT != 2) ? 48000000 : 72000000) #include "Pokitto.h" #include "POKITTO_HW/HWLCD.h" #include "POKITTO_CORE/PokittoCookie.h" #if SAF_SETTING_ENABLE_SOUND #include "POKITTO_HW/HWSound.h" #include "POKITTO_HW/clock_11u6x.h" #include "POKITTO_HW/timer_11u6x.h" #endif #if SAF_SETTING_POKITTO_JOYHAT #include "JoyHat/JoyHat.h" JoyHat pokittoJoy; uint16_t pokittoAxisThreshold1, pokittoAxisThreshold2; uint16_t pokittoRumbleCooldown = 0; #endif uint8_t *pokittoScreen; static const uint16_t pokittoPalette[256] = // 332 palette in 565 format { SAF_FE_PALETTE_565 }; #define CUSTOM_SCREEN_BUFFER \ (PROJ_SCREENBUFFERSIZE < SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT) #if CUSTOM_SCREEN_BUFFER // if PokittoLib doesn't have large enough screen buffer, we create our own: uint8_t pokittoScreenBuffer[SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT]; #endif #if SAF_SETTING_ENABLE_SAVES class SaveCookie: public Pokitto::Cookie { public: uint8_t data[SAF_SAVE_SIZE]; }; SaveCookie pokittoSave; #endif #if SAF_SETTING_ENABLE_SOUND int8_t pokittoCurrentSound = -1; uint16_t pokittoSoundPos = 0; void pokittoOnTimer() // for sound { if (Chip_TIMER_MatchPending(LPC_TIMER32_0,1)) { Chip_TIMER_ClearMatch(LPC_TIMER32_0, 1); if (pokittoCurrentSound >= 0) { Pokitto::dac_write((SAF_FE_getSoundSample(pokittoCurrentSound, pokittoSoundPos) >> (8 - SAF_SETTING_POKITTO_VOLUME) & (0xff >> (8 - SAF_SETTING_POKITTO_VOLUME)))); pokittoSoundPos++; if (pokittoSoundPos >= SAF_FE_SOUND_SAMPLE_COUNT) { pokittoCurrentSound = -1; pokittoSoundPos = 0; } } } } void pokittoTimerInit(uint32_t samplingRate) { Chip_TIMER_Init(LPC_TIMER32_0); Chip_TIMER_Reset(LPC_TIMER32_0); Chip_TIMER_MatchEnableInt(LPC_TIMER32_0, 1); Chip_TIMER_SetMatch(LPC_TIMER32_0, 1, (Chip_Clock_GetSystemClockRate() / samplingRate)); Chip_TIMER_ResetOnMatchEnable(LPC_TIMER32_0, 1); Chip_TIMER_Enable(LPC_TIMER32_0); #define weirdNumber ((IRQn_Type) 18) NVIC_ClearPendingIRQ(weirdNumber); NVIC_SetVector(weirdNumber,(uint32_t) &pokittoOnTimer); NVIC_EnableIRQ(weirdNumber); #undef weirdNumber } #endif // if SAF_SETTING_ENABLE_SOUND void SAF_FE_drawPixel(uint8_t x, uint8_t y, uint8_t color) { #if SAF_SETTING_POKITTO_JOYHAT pokittoScreen[x * SAF_SCREEN_WIDTH + (SAF_SCREEN_HEIGHT - 1 - y)] = color; #else pokittoScreen[y * SAF_SCREEN_WIDTH + x] = color; #endif } void SAF_FE_playSound(uint8_t sound) { #if SAF_SETTING_ENABLE_SOUND pokittoCurrentSound = sound; #if SAF_SETTING_POKITTO_JOYHAT if (sound == SAF_SOUND_BOOM && pokittoRumbleCooldown == 0) { pokittoJoy.Rumble(0.025); pokittoRumbleCooldown = 32; } #endif #else _SAF_UNUSED(sound); #endif } void SAF_FE_save(uint8_t index, uint8_t data) { #if SAF_SETTING_ENABLE_SAVES pokittoSave.data[index] = data; pokittoSave.saveCookie(); #if SAF_SETTING_ENABLE_SOUND // PokittoLib bug: saving disables timer, so re-enable it: pokittoTimerInit(8000); #endif #else _SAF_UNUSED(index); _SAF_UNUSED(data); #endif } uint8_t SAF_FE_load(uint8_t index) { #if SAF_SETTING_ENABLE_SAVES return pokittoSave.data[index]; #else _SAF_UNUSED(index); return 0; #endif } uint8_t SAF_FE_buttonPressed(uint8_t button) { switch (button) { #if !SAF_SETTING_POKITTO_JOYHAT case SAF_BUTTON_UP: return Pokitto::Core::upBtn(); break; case SAF_BUTTON_RIGHT: return Pokitto::Core::rightBtn(); break; case SAF_BUTTON_DOWN: return Pokitto::Core::downBtn(); break; case SAF_BUTTON_LEFT: return Pokitto::Core::leftBtn(); break; case SAF_BUTTON_A: return Pokitto::Core::aBtn(); break; case SAF_BUTTON_B: return Pokitto::Core::bBtn(); break; case SAF_BUTTON_C: return Pokitto::Core::cBtn(); break; #else case SAF_BUTTON_UP: return Pokitto::Core::rightBtn() || (pokittoJoy.JoyX() < pokittoAxisThreshold1); break; case SAF_BUTTON_RIGHT: return Pokitto::Core::downBtn() || (pokittoJoy.JoyY() > pokittoAxisThreshold2); break; case SAF_BUTTON_DOWN: return Pokitto::Core::leftBtn() || (pokittoJoy.JoyX() > pokittoAxisThreshold2); break; case SAF_BUTTON_LEFT: return Pokitto::Core::upBtn() || (pokittoJoy.JoyY() < pokittoAxisThreshold1); break; case SAF_BUTTON_A: return Pokitto::Core::aBtn() || pokittoJoy.Button1(); break; case SAF_BUTTON_B: return Pokitto::Core::bBtn() || pokittoJoy.Button2(); break; case SAF_BUTTON_C: return Pokitto::Core::cBtn(); break; #endif default: return 0; break; } #undef AXIS_THRES } static inline const char *SAF_FE_extension(const char *string) { return SAF_FE_emptyString; } #if SAF_SETTING_POKITTO_SCALE == 0 || SAF_SETTING_POKITTO_SCALE == 2 static const uint8_t upscaleMap[176] = { 0,0,0,1,1,1,2,2,2,3,3,3,4,4,5,5,5,6,6,6,7,7,7,8,8,9,9,9,10,10,10,11,11,11,12, 12,13,13,13,14,14,14,15,15,15,16,16,17,17,17,18,18,18,19,19,19,20,20,21,21,21, 22,22,22,23,23,23,24,24,25,25,25,26,26,26,27,27,27,28,28,29,29,29,30,30,30,31, 31,31,32,32,33,33,33,34,34,34,35,35,35,36,36,37,37,37,38,38,38,39,39,39,40,40, 41,41,41,42,42,42,43,43,43,44,44,45,45,45,46,46,46,47,47,47,48,48,49,49,49,50, 50,50,51,51,51,52,52,53,53,53,54,54,54,55,55,55,56,56,57,57,57,58,58,58,59,59, 59,60,60,61,61,61,62,62,62,63,63 }; #endif #if SAF_SETTING_POKITTO_SCALE == 2 static const uint8_t upscaleMap2[220] = { 0,0,0,0,1,1,1,2,2,2,2,3,3,3,4,4,4,4,5,5,5,6,6,6,6,7,7,7,8,8,8,9,9,9,9,10,10, 10,11,11,11,11,12,12,12,13,13,13,13,14,14,14,15,15,15,16,16,16,16,17,17,17,18, 18,18,18,19,19,19,20,20,20,20,21,21,21,22,22,22,22,23,23,23,24,24,24,25,25,25, 25,26,26,26,27,27,27,27,28,28,28,29,29,29,29,30,30,30,31,31,31,32,32,32,32,33, 33,33,34,34,34,34,35,35,35,36,36,36,36,37,37,37,38,38,38,38,39,39,39,40,40,40, 41,41,41,41,42,42,42,43,43,43,43,44,44,44,45,45,45,45,46,46,46,47,47,47,48,48, 48,48,49,49,49,50,50,50,50,51,51,51,52,52,52,52,53,53,53,54,54,54,54,55,55,55, 56,56,56,57,57,57,57,58,58,58,59,59,59,59,60,60,60,61,61,61,61,62,62,62,63,63, 63 }; #endif int main() { #if SAF_SETTING_ENABLE_SAVES pokittoSave.begin( "SAF" SAF_PROGRAM_NAME,sizeof(pokittoSave),(char*) &pokittoSave); #endif Pokitto::Core::begin(); #if SAF_SETTING_ENABLE_SOUND pokittoTimerInit(8000); #endif #if CUSTOM_SCREEN_BUFFER pokittoScreen = pokittoScreenBuffer; #else pokittoScreen = Pokitto::Display::screenbuffer; #endif Pokitto::Core::setFrameRate(SAF_FPS); Pokitto::Display::persistence = 1; Pokitto::Display::setInvisibleColor(-1); for (uint16_t y = 0; y < 176; ++y) for (uint16_t x = 0; x < 220; ++x) Pokitto::Display::directPixel(x,y, pokittoPalette[SAF_SETTING_BACKGROUND_COLOR]); #if SAF_SETTING_POKITTO_JOYHAT pokittoAxisThreshold1 = pokittoJoy.joyScale / 4; pokittoAxisThreshold2 = pokittoJoy.joyScale - pokittoAxisThreshold1; #endif SAF_FE_init(); uint32_t nextFrame = 0; while (Pokitto::Core::isRunning()) { Pokitto::Core::update(true); #if SAF_SETTING_POKITTO_JOYHAT if (pokittoRumbleCooldown > 0) pokittoRumbleCooldown--; #endif uint32_t time = Pokitto::Core::getTime(); // we handle FPS ourselves as Pokittolib has a bug if (time >= nextFrame) { while (time >= nextFrame) { SAF_FE_loop(); nextFrame += SAF_MS_PER_FRAME; } const uint8_t *p = pokittoScreen; #if SAF_SETTING_POKITTO_SCALE == 0 || SAF_SETTING_POKITTO_SCALE == 2 // 176x176, 220x176 #if SAF_SETTING_POKITTO_SCALE == 0 #define SCR_W 176 #define SCR_X 22 #else #define SCR_W 220 #define SCR_X 0 #endif uint16_t line[SCR_W]; uint8_t previousLine = 255; for (int16_t j = 0; j < 176; ++j) { int16_t upscaleMapRow = upscaleMap[j]; if (previousLine != upscaleMapRow) { const uint8_t *l = pokittoScreen + SAF_SCREEN_WIDTH * upscaleMapRow; uint16_t *ll = line; const uint8_t *m = #if SAF_SETTING_POKITTO_SCALE == 0 upscaleMap; #else upscaleMap2; #endif for (int16_t i = 0; i < SCR_W; ++i) { uint16_t c = pokittoPalette[*(l + *m)]; *ll = c; ll++; m++; p++; } } previousLine = upscaleMapRow; Pokitto::setDRAMpoint(SCR_X,j); Pokitto::pumpDRAMdata(line,SCR_W); } #elif SAF_SETTING_POKITTO_SCALE == 1 // 128x128 uint16_t line[SAF_SCREEN_WIDTH * 2]; int16_t yPos = 24; for (int16_t j = 0; j < SAF_SCREEN_WIDTH * 2; j += 2) { uint16_t *l = line; for (int16_t i = 0; i < SAF_SCREEN_WIDTH; ++i) { uint16_t c = pokittoPalette[*p]; *l = c; l++; *l = c; l++; p++; } Pokitto::setDRAMpoint(46,yPos); Pokitto::pumpDRAMdata(line,SAF_SCREEN_WIDTH * 2); yPos++; Pokitto::setDRAMpoint(46,yPos); Pokitto::pumpDRAMdata(line,SAF_SCREEN_WIDTH * 2); yPos++; } #endif } } return 0; } #elif defined(SAF_PLATFORM_NCURSES) /* ncuses (terminal, text-based) platform, this does not offer a "full" experience as terminal real time I/O handling and image displaying are limited, but some games are playable requirements: libncurses-dev, sys/time.h, stdio compiling: link ncurses, e.g. -lncurses ------------------------------------------------------------------------------*/ #define SAF_FE_STDIO_SAVE_LOAD #undef SAF_PLATFORM_NAME #define SAF_PLATFORM_NAME "ncurses" #undef SAF_PLATFORM_COLOR_COUNT #define SAF_PLATFORM_COLOR_COUNT 2 #include #include #include // for files #define OFFSET_X 1 #define OFFSET_Y 1 uint8_t ncScreen[SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT]; uint8_t ncButtonStates[SAF_BUTTONS]; uint8_t ncCurrentSound = 0; uint32_t ncSoundEnd = 0; uint32_t ncGetTime() { struct timeval now; gettimeofday(&now, NULL); return now.tv_sec * 1000 + now.tv_usec / 1000; } void SAF_FE_drawPixel(uint8_t x, uint8_t y, uint8_t color) { ncScreen[y * SAF_SCREEN_WIDTH + x] = color; } void SAF_FE_playSound(uint8_t sound) { ncCurrentSound = sound; ncSoundEnd = ncGetTime() + 1000; } uint8_t SAF_FE_buttonPressed(uint8_t button) { return ncButtonStates[button]; } const char *SAF_FE_extension(const char *string) { return SAF_FE_emptyString; } void printHelp(void) { puts(SAF_PROGRAM_NAME "\n " SAF_INFO_STRING " (" SAF_PLATFORM_NAME ")" "\n controls: WSAD arrows, JKL YZXC space, Q = quit" "\n possible arguments: -h (help), -i (invert colors)" ); } int main(int argc, char **argv) { int invert = 0; for (int i = 0; i < argc; ++i) if (argv[i][0] == '-' && argv[i][1] != 0 && argv[i][2] == 0) { switch (argv[i][1]) { case 'h': printHelp(); return 0; break; case 'i': invert = 1; default: break; } } for (int i = 0; i < SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT; ++i) ncScreen[i] = 0; initscr(); halfdelay(1); keypad(stdscr,TRUE); noecho(); curs_set(0); SAF_FE_init(); uint32_t nextFrame = ncGetTime(); char c00 = ' ', c01 = ',', c10 = '\'', c11 = ';'; if (invert) { c00 = ';'; c01 = '\''; c10 = ','; c11 = ' '; } while (1) { for (int i = 0; i < SAF_BUTTONS; ++i) ncButtonStates[i] = 0; int c = getch(); int goOn = 1; switch (c) { case KEY_UP: case 'w': ncButtonStates[SAF_BUTTON_UP] = 1; break; case KEY_LEFT: case 'a': ncButtonStates[SAF_BUTTON_LEFT] = 1; break; case KEY_RIGHT: case 'd': ncButtonStates[SAF_BUTTON_RIGHT] = 1; break; case KEY_DOWN: case 's': ncButtonStates[SAF_BUTTON_DOWN] = 1; break; case ' ': case 'y': case 'z': case 'j': ncButtonStates[SAF_BUTTON_A] = 1; break; case 'x': case 'k': ncButtonStates[SAF_BUTTON_B] = 1; break; case 'c': case 'l': ncButtonStates[SAF_BUTTON_C] = 1; break; case 'q': goOn = 0; break; default: break; } uint32_t time = ncGetTime(); while (time >= nextFrame) { if (!SAF_FE_loop()) { goOn = 0; break; } nextFrame += SAF_MS_PER_FRAME; } if (!goOn) break; /* One terminal character will correspong to two pixels vertically nexto to each other, so we'll be scanning two display lines at once. */ const uint8_t* scr = ncScreen; const uint8_t* scr2 = ncScreen + SAF_SCREEN_WIDTH; erase(); move(OFFSET_Y,OFFSET_X + 1); for (int i = 0; i < SAF_SCREEN_WIDTH; ++i) addch('_'); for (int y = 0; y < SAF_SCREEN_HEIGHT / 2; ++y) { move(y + OFFSET_Y + 1,1); addch('|'); for (int x = 0; x < SAF_SCREEN_WIDTH; ++x) { uint8_t pixels = ((SAF_colorTo1Bit(*scr) != 0) << 1) | (SAF_colorTo1Bit(*scr2) != 0); char p; switch (pixels) { case 0: p = c00; break; case 1: p = c01; break; case 2: p = c10; break; case 3: p = c11; break; default: p = ' '; break; } addch(p); scr++; scr2++; } addch('|'); scr += SAF_SCREEN_WIDTH; scr2 += SAF_SCREEN_WIDTH; } move(OFFSET_Y + 1 + SAF_SCREEN_HEIGHT / 2,2); for (int i = 0; i < SAF_SCREEN_WIDTH; ++i) addch('-'); move(0,1); printw(SAF_PROGRAM_NAME); if (time < ncSoundEnd) { switch (ncCurrentSound) { case SAF_SOUND_BEEP: printw(" (BEEP)"); break; case SAF_SOUND_CLICK: printw(" (click)"); break; case SAF_SOUND_BOOM: printw(" (BOOM!)"); break; case SAF_SOUND_BUMP: printw(" (bump!)"); break; default: break; } } refresh(); } endwin(); return 0; } #elif defined(SAF_PLATFORM_X11) /* X11 (xwindow, XLib) frontend requirements: XLib, stdio.h, unistd.h, sys/time.h compiling: link XLib, e.g. -lX11 ------------------------------------------------------------------------------*/ #include #include #include #include #include // for usleep #undef SAF_PLATFORM_NAME #define SAF_PLATFORM_NAME "X11" #define SAF_FE_STDIO_SAVE_LOAD #define SOUND_BOOM "BOOM!" #define SOUND_CLICK "click" #define SOUND_BEEP "Beep" #define SOUND_BUMP "bump!" uint8_t _x11Scr[SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT]; uint8_t _x11Buttons[SAF_BUTTONS]; uint8_t _x11CurrentSound = 0; uint32_t _x11SoundEnd = 0; uint32_t _x11GetTime() { struct timeval now; gettimeofday(&now,NULL); return now.tv_sec * 1000 + now.tv_usec / 1000; } void SAF_FE_drawPixel(uint8_t x, uint8_t y, uint8_t color) { _x11Scr[y * SAF_SCREEN_WIDTH + x] = color; } void SAF_FE_playSound(uint8_t sound) { _x11CurrentSound = sound; _x11SoundEnd = _x11GetTime() + 1000; switch (sound) { case SAF_SOUND_CLICK: puts(SOUND_CLICK); break; case SAF_SOUND_BEEP: puts(SOUND_BEEP); break; case SAF_SOUND_BOOM: puts(SOUND_BOOM); break; case SAF_SOUND_BUMP: puts(SOUND_BUMP); break; default: break; } } uint8_t SAF_FE_buttonPressed(uint8_t button) { return _x11Buttons[button]; } const char *SAF_FE_extension(const char *string) { _SAF_UNUSED(string); return SAF_FE_emptyString; } void printHelp(void) { puts(SAF_PROGRAM_NAME "\n " SAF_INFO_STRING " (" SAF_PLATFORM_NAME ")" "\n controls: WASD arrows, JKL YZXC space return, Esc = quit" "\n possible arguments: -h (print help), -N (N = 1..8, scale)" ); } unsigned long _palette[256]; int main(int argc, char **argv) { int scale = 4; for (int i = 0; i < argc; ++i) { char *arg = argv[i]; if (arg[0] != 0 && arg[1] != 0 && arg[2] == 0) { if (arg[1] == 'h') { printHelp(); return 0; } if (arg[1] >= '1' && arg[1] <= '8') scale = arg[1] - '0'; } } for (int i = 0; i < SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT; ++i) _x11Scr[i] = 0; for (int i = 0; i < SAF_BUTTONS; ++i) _x11Buttons[i] = 0; SAF_FE_init(); Display *display = XOpenDisplay(0); if (display == 0) { puts("could not open a display"); return 0; } int screen = DefaultScreen(display); Window window = XCreateSimpleWindow(display,RootWindow(display,screen),10,10, SAF_SCREEN_WIDTH * scale,SAF_SCREEN_HEIGHT * scale,1, BlackPixel(display,screen),WhitePixel(display,screen)); XMapWindow(display,window); XSelectInput(display,window,KeyPressMask | KeyReleaseMask); // create the palette: for (int i = 0; i < 256; ++i) { XColor color; uint8_t r, g, b; SAF_colorToRGB(i,&r,&g,&b); color.red = ((uint16_t) r) * 256; color.green = ((uint16_t) g) * 256; color.blue = ((uint16_t) b) * 256; color.flags = DoRed | DoGreen | DoBlue; XAllocColor(display,DefaultColormap(display,0),&color); _palette[i] = color.pixel; } GContext context = DefaultGC(display,screen); uint32_t nextFrame = _x11GetTime(); int goOn = 1; uint8_t previousSound = 255; while (1) // main loop { uint32_t time = _x11GetTime(); while (time >= nextFrame) { if (!SAF_FE_loop()) { goOn = 0; break; } nextFrame += SAF_MS_PER_FRAME; } usleep(((nextFrame - time) * 3 / 4) * 1000); // relieve CPU if (!goOn) break; const uint8_t *pixel = _x11Scr; int drawX = 0, drawY = 0; for (int y = 0; y < SAF_SCREEN_HEIGHT; ++y) { drawX = 0; for (int x = 0; x < SAF_SCREEN_WIDTH; ++x) { XSetForeground(display,context,_palette[*pixel]); XFillRectangle(display,window,context,drawX,drawY, scale,scale); pixel++; drawX += scale; } drawY += scale; } if (time >= _x11SoundEnd) _x11CurrentSound = 255; if (_x11CurrentSound != previousSound) { switch (_x11CurrentSound) { case SAF_SOUND_BEEP: XStoreName(display, window, SAF_PROGRAM_NAME " " SOUND_BEEP); break; case SAF_SOUND_CLICK: XStoreName(display, window, SAF_PROGRAM_NAME " " SOUND_CLICK); break; case SAF_SOUND_BOOM: XStoreName(display, window, SAF_PROGRAM_NAME " " SOUND_BOOM); break; case SAF_SOUND_BUMP: XStoreName(display, window, SAF_PROGRAM_NAME " " SOUND_BUMP); break; default: XStoreName(display, window, SAF_PROGRAM_NAME); break; } } previousSound = _x11CurrentSound; XEvent event; while (XCheckWindowEvent(display,window,KeyPressMask | KeyReleaseMask,&event) != False) { uint8_t state = event.xkey.type == KeyPress; switch (XKeycodeToKeysym(display,event.xkey.keycode,0)) { case XK_Escape: goOn = 0; break; case XK_Up: case XK_w: _x11Buttons[SAF_BUTTON_UP] = state; break; case XK_Left: case XK_a: _x11Buttons[SAF_BUTTON_LEFT] = state; break; case XK_Right: case XK_d: _x11Buttons[SAF_BUTTON_RIGHT] = state; break; case XK_Down: case XK_s: _x11Buttons[SAF_BUTTON_DOWN] = state; break; case XK_y: case XK_z: case XK_j: case XK_space: _x11Buttons[SAF_BUTTON_A] = state; break; case XK_x: case XK_k: case XK_Return: _x11Buttons[SAF_BUTTON_B] = state; break; case XK_c: case XK_l: _x11Buttons[SAF_BUTTON_C] = state; break; default: break; } } if (!goOn) break; } XCloseDisplay(display); return 0; } #elif defined(SAF_PLATFORM_ARDUBOY) /* Arduboy platform using the official Arduboy2 library. requirements: Arduino environment, Arduboy2 library compiling: compile with Arduino IDE ------------------------------------------------------------------------------*/ #undef SAF_PLATFORM_NAME #define SAF_PLATFORM_NAME "Arduboy" #undef SAF_PLATFORM_COLOR_COUNT #define SAF_PLATFORM_COLOR_COUNT 2 #undef SAF_PLATFORM_BUTTON_COUNT #define SAF_PLATFORM_BUTTON_COUNT 6 #undef SAF_PLATFORM_HARWARD #define SAF_PLATFORM_HARWARD 1 #undef SAF_PLATFORM_RAM #define SAF_PLATFORM_RAM 2500 #undef SAF_PLATFORM_FREQUENCY #define SAF_PLATFORM_FREQUENCY 16000000 #include Arduboy2 arduboy; #if SAF_SETTING_ENABLE_SOUND BeepPin1 arduboyBeep; #endif void SAF_FE_drawPixel(uint8_t x, uint8_t y, uint8_t color) { arduboy.drawPixel(32 + x,y,color ? WHITE : BLACK); } void SAF_FE_playSound(uint8_t sound) { #if SAF_SETTING_ENABLE_SOUND uint16_t f = 0; uint8_t t = 0; switch (sound) { case SAF_SOUND_BEEP: f = 300; t = 10; break; case SAF_SOUND_CLICK: f = 800; t = 1; break; case SAF_SOUND_BOOM: f = 50; t = 8; break; case SAF_SOUND_BUMP: f = 100; t = 2; break; default: break; } arduboyBeep.tone(arduboyBeep.freq(f),t); #endif } #define SAVE_VALID_VALUE 133 uint16_t saveAddress = 0; uint8_t saveLoaded = 0; void arduboyLoadSave(void) { if (!saveLoaded) { if (EEPROM.read(saveAddress) != SAVE_VALID_VALUE) { EEPROM.update(saveAddress,SAVE_VALID_VALUE); for (uint8_t i = 0; i < SAF_SAVE_SIZE; ++i) EEPROM.update(saveAddress + 1 + i,0); } saveLoaded = 1; } } void SAF_FE_save(uint8_t index, uint8_t data) { arduboyLoadSave(); EEPROM.update(saveAddress + 1 + index,data); } uint8_t SAF_FE_load(uint8_t index) { arduboyLoadSave(); return EEPROM.read(saveAddress + 1 + index); } uint8_t SAF_FE_buttonPressed(uint8_t button) { switch (button) { case SAF_BUTTON_UP: button = UP_BUTTON; break; case SAF_BUTTON_RIGHT: button = RIGHT_BUTTON; break; case SAF_BUTTON_DOWN: button = DOWN_BUTTON; break; case SAF_BUTTON_LEFT: button = LEFT_BUTTON; break; case SAF_BUTTON_A: button = A_BUTTON; break; case SAF_BUTTON_B: button = B_BUTTON; break; default: return 0; break; } return arduboy.pressed(button); } const char *SAF_FE_extension(const char *string) { _SAF_UNUSED(string); return SAF_FE_emptyString; } void setup() { arduboy.begin(); arduboy.clear(); #if SAF_SETTING_ENABLE_SOUND arduboyBeep.begin(); arduboy.audio.on(); #endif uint8_t c = SAF_colorTo1Bit(SAF_SETTING_BACKGROUND_COLOR) ? WHITE : BLACK; for (uint8_t y = 0; y < 64; ++y) for (uint8_t x = 0; x < 128; ++x) arduboy.drawPixel(x,y,c); arduboy.setFrameRate(SAF_FPS); SAF_FE_init(); saveAddress = SAF_FE_hashStr(SAF_PROGRAM_NAME) & 0x03ff; if (saveAddress > (1024 - SAF_SAVE_SIZE - 1)) // -1 for valid value saveAddress = 1024 - SAF_SAVE_SIZE - 1; } void loop() { if (!(arduboy.nextFrame())) return; #if SAF_SETTING_ENABLE_SOUND arduboyBeep.timer(); #endif arduboy.pollButtons(); SAF_FE_loop(); arduboy.display(); } #elif defined(SAF_PLATFORM_ESPBOY) /* ESPBoy platform. requirements: Arduino environment and required libraries compiling: compile with Arduino IDE ------------------------------------------------------------------------------*/ #undef SAF_PLATFORM_NAME #define SAF_PLATFORM_NAME "ESPBoy" #undef SAF_PLATFORM_RAM #define SAF_PLATFORM_RAM 4000000 #undef SAF_PLATFORM_FREQUENCY #define SAF_PLATFORM_FREQUENCY 80000000 #undef SAF_PLATFORM_HARWARD #define SAF_PLATFORM_HARWARD 1 #include #include #include #include #if SAF_SETTING_ENABLE_SAVES #include uint8_t espboySaveValidValue = 0; EEPROMClass espboyEeprom; #endif #define MCP23017address 0 #define MCP4725address 0x60 Adafruit_MCP23017 espboyMcp; Adafruit_MCP4725 espboyDac; TFT_eSPI espboyTft; uint8_t espboyScreen[SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT]; uint8_t espboyKeys = 0; PROGMEM uint16_t espboyPalette[256] = { SAF_FE_PALETTE_565 }; void SAF_FE_drawPixel(uint8_t x, uint8_t y, uint8_t color) { espboyScreen[y * SAF_SCREEN_WIDTH + x] = color; } void SAF_FE_playSound(uint8_t sound) { #if SAF_SETTING_ENABLE_SOUND int freq = 400; int dur = 75; switch (sound) { case SAF_SOUND_CLICK: freq = 400; dur = 20; break; case SAF_SOUND_BEEP: freq = 300; dur = 150; break; case SAF_SOUND_BOOM: freq = 70; dur = 200; break; case SAF_SOUND_BUMP: freq = 130; dur = 50; break; default: break; } tone(D3,freq,dur); #else _SAF_UNUSED(sound); #endif } #if SAF_SETTING_ENABLE_SAVES void espboyCheckEeprom(void) { if (espboyEeprom.read(0) == espboySaveValidValue) return; espboyEeprom.write(0,espboySaveValidValue); for (uint8_t i = 0; i < SAF_SAVE_SIZE; ++i) espboyEeprom.write(i + 1,0); espboyEeprom.commit(); } #endif void SAF_FE_save(uint8_t index, uint8_t data) { #if SAF_SETTING_ENABLE_SAVES espboyCheckEeprom(); espboyEeprom.write(index + 1,data); espboyEeprom.commit(); #else _SAF_UNUSED(index); _SAF_UNUSED(data); #endif } uint8_t SAF_FE_load(uint8_t index) { #if SAF_SETTING_ENABLE_SAVES espboyCheckEeprom(); return espboyEeprom.read(index + 1); #else _SAF_UNUSED(index); #endif } uint8_t SAF_FE_buttonPressed(uint8_t button) { switch (button) { case SAF_BUTTON_UP: return espboyKeys & 0x02; break; case SAF_BUTTON_DOWN: return espboyKeys & 0x04; break; case SAF_BUTTON_RIGHT: return espboyKeys & 0x08; break; case SAF_BUTTON_LEFT: return espboyKeys & 0x01; break; case SAF_BUTTON_A: return espboyKeys & 0x10; break; case SAF_BUTTON_B: return espboyKeys & 0x20; break; case SAF_BUTTON_C: return espboyKeys & 0x80; break; default: return 0; break; } } const char *SAF_FE_extension(const char *string) { _SAF_UNUSED(string); return SAF_FE_emptyString; } void setup() { espboyDac.begin(MCP4725address); delay(100); espboyDac.setVoltage(0,false); espboyMcp.begin(MCP23017address); delay(100); #if SAF_SETTING_ENABLE_SAVES espboySaveValidValue = SAF_FE_hashStr(SAF_PROGRAM_NAME); #endif // buttons for (uint8_t i = 0; i < 8; i++) { espboyMcp.pinMode(i,INPUT); espboyMcp.pullUp(i,HIGH); } espboyMcp.pinMode(8,OUTPUT); espboyMcp.digitalWrite(8,LOW); espboyTft.begin(); delay(100); espboyTft.setRotation(0); espboyTft.setAddrWindow(0,128,0,128); espboyTft.fillScreen(TFT_BLACK); espboyDac.setVoltage(4095,true); // backlight #if SAF_SETTING_ENABLE_SOUND pinMode(D3,OUTPUT); #endif #if SAF_SETTING_ENABLE_SAVES espboyEeprom.begin(SAF_SAVE_SIZE + 1); #endif for (uint16_t i = 0; i < SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT; ++i) espboyScreen[i] = 0; SAF_FE_init(); } uint32_t espboyNextFrame = 0; void loop() { uint32_t time = millis(); if (time < espboyNextFrame) return; while (time >= espboyNextFrame) { SAF_FE_loop(); espboyNextFrame += SAF_MS_PER_FRAME; } espboyKeys = ~espboyMcp.readGPIOAB() & 255; const uint8_t *pixel = espboyScreen; uint16_t line[SAF_SCREEN_WIDTH * 2]; for (int y = 0; y < SAF_SCREEN_HEIGHT; ++y) { uint16_t *p = line; for (int x = 0; x < SAF_SCREEN_WIDTH; ++x) { uint16_t c = pgm_read_word(espboyPalette + *pixel); *p = c; p++; *p = c; p++; pixel++; } espboyTft.pushColors(line,SAF_SCREEN_WIDTH * 2); espboyTft.pushColors(line,SAF_SCREEN_WIDTH * 2); } } #elif defined(SAF_PLATFORM_NIBBLE) /* Circuitmess Nibble platform. Persistent save/load isn't supported (EEPROM somehow doesn't work). requirements: Arduino environment and required libraries compiling: compile with Arduino IDE ------------------------------------------------------------------------------*/ #include #include #include #undef SAF_PLATFORM_HARWARD #define SAF_PLATFORM_HARWARD 1 #undef SAF_PLATFORM_NAME #define SAF_PLATFORM_NAME "Nibble" #undef SAF_PLATFORM_FREQUENCY #define SAF_PLATFORM_FREQUENCY 160000000 #undef SAF_PLATFORM_RAM #define SAF_PLATFORM_RAM 80000 #undef SAF_PLATFORM_HAS_SAVES #define SAF_PLATFORM_HAS_SAVES 0 Display *nibbleDisplay; uint16_t *nibbleFrameBuffer; uint8_t nibbleButtons[SAF_BUTTONS]; PROGMEM uint16_t nibblePalette[256] = { SAF_FE_PALETTE_565 }; void SAF_FE_drawPixel(uint8_t x, uint8_t y, uint8_t color) { uint16_t c = pgm_read_word(nibblePalette + color); c = ((c << 8) | (c >> 8)); // endien, TODO: could rather switch in the palette uint16_t *p = nibbleFrameBuffer + y * SAF_SCREEN_WIDTH * 4 + x * 2; *p = c; p++; *p = c; p += SAF_SCREEN_WIDTH * 2 - 1; *p = c; p++; *p = c; } void SAF_FE_playSound(uint8_t sound) { #if SAF_SETTING_ENABLE_SOUND switch (sound) { case SAF_SOUND_CLICK: Piezo.tone(400,20); break; case SAF_SOUND_BEEP: Piezo.tone(300,150); break; case SAF_SOUND_BOOM: Piezo.tone(70,200); break; case SAF_SOUND_BUMP: Piezo.tone(130,50); break; default: break; } #else _SAF_UNUSED(sound); #endif } void SAF_FE_save(uint8_t index, uint8_t data) { _SAF_UNUSED(index); _SAF_UNUSED(data); } uint8_t SAF_FE_load(uint8_t index) { _SAF_UNUSED(index); return 0; } uint8_t SAF_FE_buttonPressed(uint8_t button) { return nibbleButtons[button]; } const char *SAF_FE_extension(const char *string) { _SAF_UNUSED(string); return SAF_FE_emptyString; } // create button callbacks: #define cbf(b,n)\ void b ## _down() { nibbleButtons[n] = 255; }\ void b ## _up() { nibbleButtons[n] = 0; } cbf(BTN_UP,SAF_BUTTON_UP) cbf(BTN_RIGHT,SAF_BUTTON_RIGHT) cbf(BTN_DOWN,SAF_BUTTON_DOWN) cbf(BTN_LEFT,SAF_BUTTON_LEFT) cbf(BTN_A,SAF_BUTTON_A) cbf(BTN_B,SAF_BUTTON_B) cbf(BTN_C,SAF_BUTTON_C) #undef cbf void setup() { Nibble.begin(); nibbleDisplay = Nibble.getDisplay(); nibbleFrameBuffer = (uint16_t *) nibbleDisplay->getBaseSprite()->frameBuffer(0); for (uint8_t i = 0; i < SAF_BUTTONS; ++i) nibbleButtons[i] = 0; // register button callbacks: #define cb(b) \ Input::getInstance()->setBtnPressCallback(b,b ## _down); \ Input::getInstance()->setBtnReleaseCallback(b,b ## _up); cb(BTN_UP) cb(BTN_DOWN) cb(BTN_LEFT) cb(BTN_RIGHT) cb(BTN_A) cb(BTN_B) cb(BTN_C) #undef cb SAF_FE_init(); } uint32_t nibbleNextFrame = 0; void loop() { uint32_t time = millis(); Input::getInstance()->loop(0); if (time < nibbleNextFrame) return; while (time >= nibbleNextFrame) { SAF_FE_loop(); nibbleNextFrame += SAF_MS_PER_FRAME; } nibbleDisplay->commit(); } #elif defined(SAF_PLATFORM_GAMEBUINO_META) /* Gamebuino META frontend. requirements: Arduino environment, Gamebuino-Meta.h compiling: compile with Arduino IDE ------------------------------------------------------------------------------*/ #undef SAF_PLATFORM_NAME #define SAF_PLATFORM_NAME "GB META" #undef SAF_PLATFORM_RAM #define SAF_PLATFORM_RAM 32000 #undef SAF_PLATFORM_FREQUENCY #define SAF_PLATFORM_FREQUENCY 48000000 #undef SAF_PLATFORM_HARWARD #define SAF_PLATFORM_HARWARD 1 #include #if SAF_SETTING_ENABLE_SAVES const Gamebuino_Meta::SaveDefault metaSaveDefault[] = { { 0, SAVETYPE_BLOB, SAF_SAVE_SIZE, 0 } }; #endif uint8_t metaLEDCountdown = 0; PROGMEM uint16_t gbmPalette[256] = { SAF_FE_PALETTE_565 }; void SAF_FE_drawPixel(uint8_t x, uint8_t y, uint8_t color) { gb.display.drawPixel(8 + x,y,(Color) pgm_read_word(gbmPalette + color)); } void SAF_FE_playSound(uint8_t sound) { #if SAF_SETTING_ENABLE_SOUND switch (sound) { case SAF_SOUND_CLICK: gb.sound.tone(800,60); break; case SAF_SOUND_BEEP: gb.sound.tone(300,150); break; case SAF_SOUND_BUMP: gb.sound.tone(130,50); break; case SAF_SOUND_BOOM: gb.sound.playCancel(); gb.lights.fill(RED); metaLEDCountdown = 5; break; default: break; } #else _SAF_UNUSED(sound); #endif } uint8_t SAF_FE_buttonPressed(uint8_t button) { Gamebuino_Meta::Button b; switch (button) { case SAF_BUTTON_UP: b = BUTTON_UP; break; case SAF_BUTTON_RIGHT: b = BUTTON_RIGHT; break; case SAF_BUTTON_DOWN: b = BUTTON_DOWN; break; case SAF_BUTTON_LEFT: b = BUTTON_LEFT; break; case SAF_BUTTON_A: b = BUTTON_A; break; case SAF_BUTTON_B: b = BUTTON_B; break; case SAF_BUTTON_C: b = BUTTON_MENU; break; default: return 0; break; } return gb.buttons.timeHeld(b) > 0; } const char *SAF_FE_extension(const char *string) { _SAF_UNUSED(string); return SAF_FE_emptyString; } uint8_t SAF_FE_load(uint8_t index) { #if SAF_SETTING_ENABLE_SAVES uint8_t data[SAF_SAVE_SIZE]; gb.save.get(0,data,SAF_SAVE_SIZE); return data[index]; #else _SAF_UNUSED(index); #endif } void SAF_FE_save(uint8_t index, uint8_t data) { #if SAF_SETTING_ENABLE_SAVES uint8_t d[SAF_SAVE_SIZE]; gb.save.get(0,d,SAF_SAVE_SIZE); d[index] = data; gb.save.set(0,d,SAF_SAVE_SIZE); #else _SAF_UNUSED(index); _SAF_UNUSED(data); #endif } void setup() { gb.begin(); gb.setFrameRate(SAF_FPS); for (uint8_t y = 0; y < 64; ++y) for (uint8_t x = 0; x < 80; ++x) gb.display.drawPixel(x,y,(Color) pgm_read_word(gbmPalette + SAF_SETTING_BACKGROUND_COLOR)); #if SAF_SETTING_ENABLE_SAVES gb.save.config(metaSaveDefault); #endif SAF_FE_init(); } void loop() { if (!gb.update()) return; if (metaLEDCountdown > 0) { metaLEDCountdown--; if (metaLEDCountdown == 0) gb.lights.clear(); } SAF_FE_loop(); } #elif defined(SAF_PLATFORM_RINGO) /* Circuitmess Ringo (MAKERphone) frontend. requirements: Arduino environment, required libraries compiling: compile with Arduino IDE ------------------------------------------------------------------------------*/ #include #include //#include #undef SAF_PLATFORM_NAME #define SAF_PLATFORM_NAME "GB META" #undef SAF_PLATFORM_RAM #define SAF_PLATFORM_RAM 520000 #undef SAF_PLATFORM_FREQUENCY #define SAF_PLATFORM_FREQUENCY 160000000 #define RINGO_SAVE_FILE_NAME ("/" SAF_PROGRAM_NAME ".sav") uint16_t ringoPalette[256] = { SAF_FE_PALETTE_565 }; MAKERphone mp; #if SAF_SETTING_ENABLE_SOUND Oscillator *ringoOsc; #endif uint8_t ringoArrows; //uint8_t ringoScreen[SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT]; void SAF_FE_drawPixel(uint8_t x, uint8_t y, uint8_t color) { mp.display.fillRect(16 + x * 2,y * 2,2,2,ringoPalette[color]); } void SAF_FE_playSound(uint8_t sound) { #if SAF_SETTING_ENABLE_SOUND uint8_t wave = SINE; uint16_t freq = 1000; float dur = 0.2; switch (sound) { case SAF_SOUND_BEEP: freq = 500; dur = 0.08; wave = SINE; break; // plasma case SAF_SOUND_CLICK: freq = 900; dur = 0.003; wave = SAW; break; // click case SAF_SOUND_BOOM: freq = 100; dur = 0.06; wave = SAW; break; // explosion case SAF_SOUND_BUMP: freq = 200; dur = 0.02; wave = SQUARE; break; // plasma default: break; } ringoOsc->setWaveform(wave); ringoOsc->beep(freq,dur); #else _SAF_UNUSED(sound); #endif } uint8_t SAF_FE_buttonPressed(uint8_t button) { switch (button) { #define b(but) (mp.buttons.timeHeld(but) > 0) #define r(but) return b(but); break; case SAF_BUTTON_UP: return (ringoArrows & 0x04) || b(BTN_2); break; case SAF_BUTTON_DOWN: return (ringoArrows & 0x08) || b(BTN_5); break; case SAF_BUTTON_RIGHT: return (ringoArrows & 0x01) || b(BTN_6); break; case SAF_BUTTON_LEFT: return (ringoArrows & 0x02) || b(BTN_4); break; case SAF_BUTTON_A: return b(BTN_A) || b(BTN_8); break; case SAF_BUTTON_B: return b(BTN_B) || b(BTN_9); break; case SAF_BUTTON_C: r(BTN_7); default: return 0; break; #undef b #undef r } return 0; return 0; } const char *SAF_FE_extension(const char *string) { _SAF_UNUSED(string); return SAF_FE_emptyString; } #if 1//SAF_SETTING_ENABLE_SAVES uint8_t ringoSaveLoaded = 0; uint8_t ringoSaveData[SAF_SAVE_SIZE]; void ringoCheckSave() { if (!ringoSaveLoaded) { if (SD.exists(RINGO_SAVE_FILE_NAME)) { File f = SD.open(RINGO_SAVE_FILE_NAME,FILE_READ); f.read(ringoSaveData,SAF_SAVE_SIZE); f.close(); } else for (uint8_t i = 0; i < SAF_SAVE_SIZE; ++i) ringoSaveData[i] = 0; ringoSaveLoaded = 1; } } #endif uint8_t SAF_FE_load(uint8_t index) { #if SAF_SETTING_ENABLE_SAVES ringoCheckSave(); return ringoSaveData[index]; #else _SAF_UNUSED(index); return 0; #endif } void SAF_FE_save(uint8_t index, uint8_t data) { #if SAF_SETTING_ENABLE_SAVES ringoSaveData[index] = data; ringoCheckSave(); SD.remove(RINGO_SAVE_FILE_NAME); File f = SD.open(RINGO_SAVE_FILE_NAME,FILE_WRITE); f.write(ringoSaveData,SAF_SAVE_SIZE); f.close(); #else _SAF_UNUSED(index); _SAF_UNUSED(data); #endif } void setup() { mp.begin(); #if SAF_SETTING_ENABLE_SOUND ringoOsc = new Oscillator(SINE); addOscillator(ringoOsc); ringoOsc->setVolume(64); #endif SAF_FE_init(); } uint32_t ringoNextFrame = 0; void loop() { uint32_t time = millis(); if (time < ringoNextFrame) return; mp.display.fillRect( 0,0,LCDWIDTH,LCDHEIGHT,ringoPalette[SAF_SETTING_BACKGROUND_COLOR]); while (time >= ringoNextFrame) { SAF_FE_loop(); ringoNextFrame += SAF_MS_PER_FRAME; } ringoArrows = 0x00 | ((mp.buttons.getJoystickX() < 200)) << 0 | ((mp.buttons.getJoystickX() > 900)) << 1 | ((mp.buttons.getJoystickY() < 200)) << 2 | ((mp.buttons.getJoystickY() > 900)) << 3; mp.update(); } #else #error No known SAF frontend specified. #endif // platform frontends //===================== FUNCTION IMPLEMENTATIONS =============================== #ifdef SAF_FE_GENERIC_FRONTEND #ifndef SAF_FE_STDIO_SAVE_LOAD #define SAF_FE_STDIO_SAVE_LOAD #endif #include #define SAF_FE_GF_SAVE_FILE_NAME (SAF_PROGRAM_NAME ".rec") uint8_t SAF_FE_GF_running = 1; uint8_t SAF_FE_GF_volume = 4; uint8_t SAF_FE_GF_screen[SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT]; uint8_t SAF_FE_GF_parameters[128]; // CLI parameters uint32_t SAF_FE_GF_keyStates = 0; uint8_t SAF_FE_GF_buttonStates = 0; uint8_t SAF_FE_GF_paused = 0; uint32_t SAF_FE_GF_demoNextFrame = 0; // when to load the next record item uint8_t SAF_FE_GF_demoNextButtons = 0; int32_t SAF_FE_GF_frameTimes = 0; // for debug, measures time spent in a frame uint32_t SAF_FE_GF_nextFrameTime = 0; FILE *SAF_FE_GF_recordFile; /** Records a specific key state and returns 0 if the key is not pressed, 1 if it was just pressed or 2 if it's been pressed for multiple frames. */ uint8_t SAF_FE_GF_handleKey(char key, uint8_t position) { position = 0x01 << position; uint8_t previous = (SAF_FE_GF_keyStates & position) != 0; if (SAF_FE_GF_keyPressed(key)) { SAF_FE_GF_keyStates |= position; return previous ? 2 : 1; } else { SAF_FE_GF_keyStates &= ~position; return 0; } } uint8_t SAF_FE_buttonPressed(uint8_t button) { char key = 0, key2 = 0, key3 = 0, key4 = 0; uint8_t bit = 255; switch (button) { case SAF_BUTTON_UP: key = 'w'; key2 = 'U'; bit = 0; break; case SAF_BUTTON_RIGHT: key = 'd'; key2 = 'R'; bit = 1; break; case SAF_BUTTON_DOWN: key = 's'; key2 = 'D'; bit = 2; break; case SAF_BUTTON_LEFT: key = 'a'; key2 = 'L'; bit = 3; break; case SAF_BUTTON_A: key = 'j'; key2 = 'y'; key3 = 'z'; key4 = 'X'; bit = 4; break; case SAF_BUTTON_B: key = 'k'; key2 = 'x'; key3 = 'Y'; bit = 5; break; case SAF_BUTTON_C: key = 'l'; key2 = 'c'; key3 = 'Z'; bit = 6; break; default: break; } return SAF_FE_GF_keyPressed(key) || (key2 && SAF_FE_GF_keyPressed(key2)) || (key3 && SAF_FE_GF_keyPressed(key3)) || (key4 && SAF_FE_GF_keyPressed(key4)) || (SAF_FE_GF_parameters['p'] && bit != 255 && (SAF_FE_GF_buttonStates & (0x01 << bit))); } void SAF_FE_drawPixel(uint8_t x, uint8_t y, uint8_t color) { SAF_FE_GF_screen[y * SAF_SCREEN_WIDTH + x] = color; } void SAF_FE_GF_saveScreenshot(const char *file) { FILE *f = fopen(file,"wb"); if (!f) return; fwrite("P6 64 64 255\n",13,1,f); for (uint16_t i = 0; i < SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT; ++i) { uint8_t t[3]; SAF_colorToRGB(SAF_FE_GF_screen[i],t,t + 1,t + 2); fwrite(t,3,1,f); } fclose(f); puts("screenshot taken"); } void SAF_FE_GF_printHelp() { puts( SAF_PROGRAM_NAME "\n " SAF_INFO_STRING "\n " SAF_PLATFORM_NAME " (generic frontend)\n" "controls:\n" " WSAD, arrows direction buttons\n" " JKL A, B and C buttons\n" " P pause/resume\n" " escape quit\n" " T take screenshot\n" " U/I volume -/+\n" " N/M speed -/+\n" "possible arguments (if supported):\n" " -h print help and quit\n" " -sX scale X times (0 = fullscreen)\n" " -vX set volume to X (0 to 8)\n" " -bX boost speed (1 to 5, 3 = normal)\n" " -d show debug info\n" " -u use pixel art scaling (scale2x)\n" " -r record inputs (demo, can be used for saves)\n" " -p play recorded inputs (demo)\n" " -P like -p but rewind to end (\"load state\")\n" " -l turns on -P and -r (save/load via demos)\n" " -S start paused"); #ifdef SAF_FE_GF_EXTRA_HELP puts(SAF_FE_GF_EXTRA_HELP); #endif } char *SAF_FE_GF_byteToHex(uint8_t b, char s[3]) { for (uint8_t i = 0; i < 2; ++i) { uint8_t r = b % 16; s[1 - i] = r + ((r < 10) ? '0' : ('a' - 10)); b /= 16; } s[2] = 0; return s; } uint8_t SAF_FE_GF_byteFromHex(const char *hex) { uint8_t result = 0; for (int8_t i = 0; i < 2; ++i) result = result * 16 + hex[i] - ((hex[i] >= '0' && hex [i] <= '9') ? '0' : ('a' - 10)); return result; } void readNextDemoRecord(void) { char line[64]; SAF_FE_GF_buttonStates = SAF_FE_GF_demoNextButtons; if (fgets(line,64,SAF_FE_GF_recordFile) != 0) { SAF_FE_GF_demoNextFrame = (((uint32_t) SAF_FE_GF_byteFromHex(line)) << 24) + (((uint32_t) SAF_FE_GF_byteFromHex(line + 2)) << 16) + (((uint32_t) SAF_FE_GF_byteFromHex(line + 4)) << 8) + ((uint32_t) SAF_FE_GF_byteFromHex(line + 6)); SAF_FE_GF_demoNextButtons = SAF_FE_GF_byteFromHex(line + 9); } else { puts("replaying inputs finished"); SAF_FE_GF_demoNextFrame = 0xffffffff; fclose(SAF_FE_GF_recordFile); if (SAF_FE_GF_parameters['r']) { // after "loading" the state open again for appending SAF_FE_GF_parameters['p'] = 0; SAF_FE_GF_parameters['P'] = 0; SAF_FE_GF_recordFile = fopen(SAF_FE_GF_SAVE_FILE_NAME,"a"); } if (SAF_FE_GF_parameters['S']) SAF_FE_GF_paused = 1; } } void _SAF_FE_GF_mainLoopIteration(void) { if (!SAF_FE_GF_loop(SAF_FE_GF_parameters)) { SAF_FE_GF_running = 0; return; } uint32_t time = #ifdef __EMSCRIPTEN__ 0; #else SAF_FE_GF_sleep(0); #endif #ifndef __EMSCRIPTEN__ if (time >= SAF_FE_GF_nextFrameTime) #endif { if (SAF_FE_GF_handleKey('t',0) == 1) { char fileName[64] = SAF_PROGRAM_NAME "_"; char *c = fileName; while (*c != 0) c++; SAF_FE_GF_byteToHex((SAF_frame() / 256) % 256,c); c += 2; SAF_FE_GF_byteToHex(SAF_frame() % 256,c); c += 2; *c = '.'; c++; *c = 'p'; c++; *c = 'p'; c++; *c = 'm'; c++; *c = 0; SAF_FE_GF_saveScreenshot(fileName); } if (SAF_FE_GF_handleKey('i',1) == 1 && SAF_FE_GF_parameters['v'] < '8') { SAF_FE_GF_parameters['v']++; puts("volume +"); } else if (SAF_FE_GF_handleKey('u',2) == 1 && SAF_FE_GF_parameters['v'] > '0') { SAF_FE_GF_parameters['v']--; puts("volume -"); } if (SAF_FE_GF_handleKey('m',3) == 1 && SAF_FE_GF_parameters['b'] < '5') { SAF_FE_GF_parameters['b']++; puts("speed +"); } else if (SAF_FE_GF_handleKey('n',4) == 1 && SAF_FE_GF_parameters['b'] > '1') { SAF_FE_GF_parameters['b']--; puts("speed -"); } if (SAF_FE_GF_handleKey('p',5) == 1) { SAF_FE_GF_paused = !SAF_FE_GF_paused; puts(SAF_FE_GF_paused ? "paused" : "resumed"); } uint16_t mult = 2; for (uint8_t i = 0; i < '5' - SAF_FE_GF_parameters['b']; ++i) mult *= 2; uint8_t frames = 0; uint8_t rewind = 0; #ifndef __EMSCRIPTEN__ while (time >= SAF_FE_GF_nextFrameTime || rewind) #endif { rewind = SAF_FE_GF_parameters['P'] && SAF_FE_GF_demoNextFrame != 0xffffffff; if (!SAF_FE_GF_paused) { #ifndef __EMSCRIPTEN__ SAF_FE_GF_frameTimes -= SAF_FE_GF_sleep(0); #endif // demo recording/playing: if (SAF_FE_GF_parameters['r'] && !SAF_FE_GF_parameters['p']) { uint8_t previousButtonStates = SAF_FE_GF_buttonStates; SAF_FE_GF_buttonStates = ((SAF_buttonPressed(SAF_BUTTON_UP) != 0)) | ((SAF_buttonPressed(SAF_BUTTON_RIGHT) != 0) << 1) | ((SAF_buttonPressed(SAF_BUTTON_DOWN) != 0) << 2) | ((SAF_buttonPressed(SAF_BUTTON_LEFT) != 0) << 3) | ((SAF_buttonPressed(SAF_BUTTON_A) != 0) << 4) | ((SAF_buttonPressed(SAF_BUTTON_B) != 0) << 5) | ((SAF_buttonPressed(SAF_BUTTON_C) != 0) << 6); if (SAF_FE_GF_buttonStates != previousButtonStates) { char s[3]; uint32_t f = SAF_frame() - 1; fprintf(SAF_FE_GF_recordFile,"%s",SAF_FE_GF_byteToHex((f >> 24) & 0xff,s)); fprintf(SAF_FE_GF_recordFile,"%s",SAF_FE_GF_byteToHex((f >> 16) & 0xff,s)); fprintf(SAF_FE_GF_recordFile,"%s",SAF_FE_GF_byteToHex((f >> 8) & 0xff,s)); fprintf(SAF_FE_GF_recordFile,"%s ",SAF_FE_GF_byteToHex(f & 0xff,s)); fprintf(SAF_FE_GF_recordFile,"%s\n",SAF_FE_GF_byteToHex(SAF_FE_GF_buttonStates,s)); } } else if (SAF_FE_GF_parameters['p'] && SAF_frame() >= SAF_FE_GF_demoNextFrame) { readNextDemoRecord(); } // demo handling SAF_FE_GF_running = SAF_FE_loop(); #ifndef __EMSCRIPTEN__ SAF_FE_GF_frameTimes += SAF_FE_GF_sleep(0); #endif if (SAF_FE_GF_parameters['d'] && SAF_frame() % 64 == 0) { char debugStr[] = "frame us/frame "; SAF_intToStr(SAF_frame(),debugStr + 6)[0] = ' '; SAF_intToStr((SAF_FE_GF_frameTimes * 1000) / 64,debugStr + 23); puts(debugStr); SAF_FE_GF_frameTimes = 0; } } // if (!paused) if (!rewind) SAF_FE_GF_nextFrameTime += (SAF_MS_PER_FRAME * mult) / 8; frames++; } if (SAF_FE_GF_parameters['d'] && frames > 1) { char debugStr[] = "skipped x frames"; debugStr[8] = '0' + frames - 1; puts(debugStr); } SAF_FE_GF_present(SAF_FE_GF_screen); } // if (time > nextFrameTime) #ifndef __EMSCRIPTEN__ SAF_FE_GF_sleep((SAF_FE_GF_nextFrameTime - time) * 3 / 4); // relieve CPU #endif if (SAF_FE_GF_keyPressed('E')) SAF_FE_GF_running = 0; } #ifdef __EMSCRIPTEN__ typedef void (*em_callback_func)(void); void emscripten_set_main_loop( em_callback_func func, int fps, int simulate_infinite_loop); #endif int main(int argc, char **argv) { SAF_FE_paramParse(argc,argv,SAF_FE_GF_parameters); if (SAF_FE_GF_parameters['h']) { SAF_FE_GF_printHelp(); return 0; } if (SAF_FE_GF_parameters['s'] < '0' || SAF_FE_GF_parameters['s'] > '8') SAF_FE_GF_parameters['s'] = '4'; if (SAF_FE_GF_parameters['v'] < '0' || SAF_FE_GF_parameters['v'] > '8') SAF_FE_GF_parameters['v'] = '4'; if (SAF_FE_GF_parameters['b'] < '1' || SAF_FE_GF_parameters['b'] > '5') SAF_FE_GF_parameters['b'] = '3'; if (SAF_FE_GF_parameters['l']) { SAF_FE_GF_parameters['P'] = 1; SAF_FE_GF_parameters['r'] = 1; } if (SAF_FE_GF_parameters['P']) SAF_FE_GF_parameters['p'] = 1; // automatically turn this on as well if (SAF_FE_GF_parameters['S'] && !SAF_FE_GF_parameters['p']) SAF_FE_GF_paused = 1; for (uint16_t i = 0; i < SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT; ++i) SAF_FE_GF_screen[i] = 0; puts("starting " SAF_PROGRAM_NAME); puts("run with -h for help"); if (SAF_FE_GF_parameters['d']) puts("initializing frontend"); SAF_FE_GF_init(SAF_FE_GF_parameters); if (SAF_FE_GF_parameters['d']) puts("initializing client program"); SAF_FE_init(); if (SAF_FE_GF_parameters['p']) { SAF_FE_GF_recordFile = fopen(SAF_FE_GF_SAVE_FILE_NAME,"r"); if (!SAF_FE_GF_recordFile) { puts("couldn't open demo file for reading"); SAF_FE_GF_parameters['p'] = 0; SAF_FE_GF_parameters['P'] = 0; } else readNextDemoRecord(); } if (!SAF_FE_GF_parameters['p'] && SAF_FE_GF_parameters['r']) { SAF_FE_GF_recordFile = fopen(SAF_FE_GF_SAVE_FILE_NAME,"w"); if (!SAF_FE_GF_recordFile) { puts("couldn't open demo file for writing"); SAF_FE_GF_parameters['r'] = 0; } } #ifdef __EMSCRIPTEN__ emscripten_set_main_loop(_SAF_FE_GF_mainLoopIteration,SAF_FPS,1); #else while (SAF_FE_GF_running) // main loop _SAF_FE_GF_mainLoopIteration(); #endif if (SAF_FE_GF_recordFile) fclose(SAF_FE_GF_recordFile); printf("ending %s\n",SAF_PROGRAM_NAME); SAF_FE_GF_end(); return 0; } const uint8_t *SAF_FE_GF_getScreenPointer() { return SAF_FE_GF_screen; } const char *SAF_FE_extension(const char *string) { return SAF_FE_GF_extension(string); } #endif // SAF_FE_GENERIC_FRONTEND #ifdef SAF_FE_STDIO_SAVE_LOAD #if SAF_SETTING_ENABLE_SAVES #include uint8_t _SAF_FE_saveMemory[SAF_SAVE_SIZE]; uint8_t _SAF_FE_saveMemoryLoaded = 0; #ifdef __EMSCRIPTEN__ char *emscripten_run_script_string(const char *script); int emscripten_run_script_int(const char *script); void emscripten_run_script(const char *script); char emscriptenCookie[17]; #endif void _SAF_FE_loadSaveMemory() { #ifdef __EMSCRIPTEN__ // generate the cookie name char *progName = SAF_PROGRAM_NAME; emscriptenCookie[0] = 'S'; emscriptenCookie[1] = 'A'; emscriptenCookie[2] = 'F'; for (uint8_t i = 3; i < 16; ++i) emscriptenCookie[i] = 'x'; uint8_t p = 0; while (progName[p] != 0 && p < 16 - 3) { emscriptenCookie[p + 3] = progName[p]; p++; } emscriptenCookie[16] = 0; char script[] = "document.cookie.search('................=')"; for (uint8_t i = 0; i < 16; ++i) script[24 + i] = emscriptenCookie[i]; int cookieIndex = emscripten_run_script_int(script); if (cookieIndex >= 0) { char *cookie = emscripten_run_script_string("document.cookie") + cookieIndex + 17; for (uint8_t i = 0; i < SAF_SAVE_SIZE; ++i) _SAF_FE_saveMemory[i] = (cookie[2 * i] - 'a') * 16 + (cookie[2 * i + 1] - 'a'); } else for (uint16_t i = 0; i < SAF_SAVE_SIZE; ++i) _SAF_FE_saveMemory[i] = 0; #else FILE *f = fopen(SAF_PROGRAM_NAME ".sav","rb"); if (f) { fread(_SAF_FE_saveMemory,SAF_SAVE_SIZE,1,f); fclose(f); } else for (uint16_t i = 0; i < SAF_SAVE_SIZE; ++i) _SAF_FE_saveMemory[i] = 0; #endif // emscripten _SAF_FE_saveMemoryLoaded = 1; } #endif // if SAF_SETTING_ENABLE_SAVES void SAF_FE_save(uint8_t index, uint8_t data) { #if SAF_SETTING_ENABLE_SAVES if (!_SAF_FE_saveMemoryLoaded) _SAF_FE_loadSaveMemory(); _SAF_FE_saveMemory[index] = data; #ifdef __EMSCRIPTEN__ char str[] = "document.cookie = '................=" " '"; for (uint8_t i = 0; i < 16; ++i) str[19 + i] = emscriptenCookie[i]; uint8_t p = 36; for (uint8_t i = 0; i < SAF_SAVE_SIZE; ++i) { char c1 = 'a' + _SAF_FE_saveMemory[i] / 16; char c2 = 'a' + _SAF_FE_saveMemory[i] % 16; str[p] = c1; str[p + 1] = c2; p += 2; } str[p] = '\''; str[p + 1] = ';'; str[p + 2] = 0; emscripten_run_script(str); #else FILE *f = fopen(SAF_PROGRAM_NAME ".sav","wb"); fwrite(_SAF_FE_saveMemory,SAF_SAVE_SIZE,1,f); fclose(f); #endif // emscripten #else _SAF_UNUSED(index); _SAF_UNUSED(data); #endif } uint8_t SAF_FE_load(uint8_t index) { #if SAF_SETTING_ENABLE_SAVES if (!_SAF_FE_saveMemoryLoaded) _SAF_FE_loadSaveMemory(); return _SAF_FE_saveMemory[index]; #else return 0; #endif } #endif // ifded SAF_FE_STDIO_SAVE_LOAD void SAF_FE_paramParse(int argc, char **argv, uint8_t paramValues[128]) { for (uint8_t i = 0; i < 128; ++i) paramValues[i] = 0; for (uint16_t i = 0; i < argc; ++i) { const char *a = *argv; if (*a == '-') { a++; if (a != 0) { if (a[1] == 0) // -p paramValues[(uint8_t) a[0]] = 1; else if (a[2] == 0) //-pX paramValues[(uint8_t) a[0]] = a[1]; } } argv++; } } #if SAF_SETTING_FORCE_1BIT #undef SAF_PLATFORM_COLOR_COUNT #define SAF_PLATFORM_COLOR_COUNT 2 #endif #if SAF_PLATFORM_HARWARD #include #define _SAF_CONST PROGMEM const #define _SAF_READ_CONST(addr) ((uint8_t) pgm_read_byte(addr)) #else #define _SAF_CONST static const #define _SAF_READ_CONST(addr) *(addr) #endif uint32_t _SAF_frame = 0; uint8_t _SAF_currentRandom = 0; uint8_t _SAF_buttonStates[SAF_BUTTONS] = {0,0,0,0,0,0,0}; uint8_t _SAF_saveMemory[SAF_SAVE_SIZE] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; uint8_t _SAF_saveMemoryLoaded = 0; _SAF_CONST uint8_t _SAF_font[] = { 0x00,0x00, // 32 ' ' 0x22,0x20, // 33 '!' 0x55,0x00, // 34 '"' 0xea,0x57, // 35 '#' 0x36,0x36, // 36 '$' 0x49,0x92, // 37 '%' 0x52,0xb6, // 38 '&' 0x22,0x00, // 39 ''' 0x24,0x42, // 40 '(' 0x42,0x24, // 41 ')' 0x25,0x05, // 42 '*' 0x20,0x27, // 43 '+' 0x00,0x22, // 44 ',' 0x00,0x07, // 45 '-' 0x00,0x20, // 46 '.' 0x24,0x12, // 47 '/' 0x57,0x75, // 48 '0' 0x46,0xe4, // 49 '1' 0x67,0x71, // 50 '2' 0x27,0x34, // 51 '3' 0x55,0x47, // 52 '4' 0x17,0x76, // 53 '5' 0x17,0x77, // 54 '6' 0x47,0x22, // 55 '7' 0x77,0x75, // 56 '8' 0x77,0x74, // 57 '9' 0x20,0x20, // 58 ':' 0x02,0x22, // 59 ';' 0x40,0x42, // 60 '<' 0x70,0x70, // 61 '=' 0x20,0x24, // 62 '>' 0x96,0x44, // 63 '?' 0xde,0x61, // 64 '@' 0x57,0x57, // 65 'A' 0x73,0x75, // 66 'B' 0x16,0x61, // 67 'C' 0x53,0x35, // 68 'D' 0x37,0x71, // 69 'E' 0x17,0x13, // 70 'F' 0x16,0x65, // 71 'G' 0x75,0x55, // 72 'H' 0x27,0x72, // 73 'I' 0x44,0x75, // 74 'J' 0x35,0x55, // 75 'K' 0x11,0x71, // 76 'L' 0xfb,0x99, // 77 'M' 0xb9,0x9d, // 78 'N' 0x96,0x69, // 79 'O' 0x57,0x17, // 80 'P' 0x96,0xed, // 81 'Q' 0x57,0x53, // 82 'R' 0x36,0x34, // 83 'S' 0x27,0x22, // 84 'T' 0x55,0x75, // 85 'U' 0x55,0x25, // 86 'V' 0x99,0xbf, // 87 'W' 0x25,0x52, // 88 'X' 0x75,0x22, // 89 'Y' 0x27,0x71, // 90 'Z' 0x26,0x62, // 91 '[' 0x21,0x42, // 92 '\' 0x46,0x64, // 93 ']' 0x52,0x00, // 94 '^' 0x00,0x70, // 95 '_' 0x42,0x00, // 96 '`' 0x60,0x75, // 97 'a' 0x31,0x35, // 98 'b' 0x60,0x61, // 99 'c' 0x64,0x65, // 100 'd' 0xd6,0x63, // 101 'e' 0x26,0x27, // 102 'f' 0x76,0x34, // 103 'g' 0x31,0x55, // 104 'h' 0x02,0x22, // 105 'i' 0x04,0x64, // 106 'j' 0x51,0x53, // 107 'k' 0x22,0x42, // 108 'l' 0xf0,0x9d, // 109 'm' 0x30,0x55, // 110 'n' 0x70,0x75, // 111 'o' 0x70,0x17, // 112 'p' 0x70,0x47, // 113 'q' 0x60,0x22, // 114 'r' 0x60,0x32, // 115 's' 0x72,0x62, // 116 't' 0x50,0x75, // 117 'u' 0x50,0x25, // 118 'v' 0x90,0xfb, // 119 'w' 0x90,0x96, // 120 'x' 0xa0,0x36, // 121 'y' 0x30,0x62, // 122 'z' 0x36,0x62, // 123 '{' 0x22,0x22, // 124 '|' 0x63,0x32, // 125 '}' 0xa0,0x05, // 126 '~' 0x00,0x00 // 127 ' ' }; _SAF_CONST int8_t _SAF_cosTable[64] = { 127,127,127,127,127,127,126,126,125,124,124,123,122,121,120,119,118,117,115, 114,112,111,109,108,106,104,102,100,98,96,94,92,90,88,85,83,81,78,76,73,71,68, 65,63,60,57,54,51,48,46,43,40,37,34,31,28,24,21,18,15,12,9,6,3 }; int8_t SAF_FE_getSoundSample(uint8_t sound, uint16_t sampleNumber) { switch (sound) { case SAF_SOUND_BEEP: return SAF_sin(sampleNumber * 16); break; case SAF_SOUND_CLICK: return (sampleNumber / 2) >> + (sampleNumber & 0x02); break; case SAF_SOUND_BOOM: return sampleNumber * 2; break; case SAF_SOUND_BUMP: return ((sampleNumber >> 3) + sampleNumber) ^ 0x24; break; default: return 0; break; } } void SAF_FE_scale2xScreen( const uint8_t screen[SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT], uint8_t result[SAF_SCREEN_WIDTH * SAF_SCREEN_HEIGHT * 4]) { /* Here we try to optimize by handling the border cases separately which should considerably reduce amount of branching per pixel (but code size will be greater). */ uint8_t p[4] = {0,0,0,0}; const uint8_t *sCurr = screen; const uint8_t *sBott = sCurr + SAF_SCREEN_WIDTH; const uint8_t *sTop = sCurr - SAF_SCREEN_WIDTH; uint8_t *rCurr = result; uint8_t *rBott = rCurr + SAF_SCREEN_WIDTH * 2; #define step(t,r,b,l)\ {SAF_FE_scale2xPixel(*sCurr,t,r,b,l,p);\ *rCurr = p[0]; rCurr++; *rCurr = p[1]; rCurr++;\ *rBott = p[2]; rBott++; *rBott = p[3]; rBott++;\ sCurr++; sBott++; sTop++;} #define nextLine\ {rCurr += SAF_SCREEN_WIDTH * 2;\ rBott += SAF_SCREEN_WIDTH * 2;} #define T *sTop #define R *(sCurr + 1) #define B *sBott #define L *(sCurr - 1) step(0,R,B,0) // first pixel (top-left) // first row from second to second to last (top edge) for (uint8_t i = 1; i < SAF_SCREEN_WIDTH - 1; ++i) step(0,R,B,L) step(0,0,B,L) // lest pixel of first row (top-right) nextLine // rows from second to second to last for (uint8_t j = 1; j < SAF_SCREEN_HEIGHT - 1; ++j) { step(T,R,B,0) // first pixel of the row (left edge) // middle pixels (not touching any edge) for (uint8_t i = 1; i < SAF_SCREEN_WIDTH - 1; ++i) step(T,R,B,L) step(T,0,B,L) // last pixel of the row (right edge) nextLine } step(T,R,0,0) // first pixel of the last row (top-bottom) // last row from second to second to last (bottom edge) for (uint8_t i = 1; i < SAF_SCREEN_WIDTH - 1; ++i) step(T,R,0,L) step(T,0,0,L) // last pixel (bottom-right) nextLine #undef step #undef nextLine #undef T #undef R #undef B #undef L } void SAF_FE_scale2xPixel(uint8_t middle, uint8_t top, uint8_t right, uint8_t bottom, uint8_t left, uint8_t result[4]) { uint8_t rightBottom = right == bottom; if (top == left) { result[1] = middle; result[2] = middle; result[0] = (bottom == left || top == right) ? middle : top; } else { result[0] = middle; if (rightBottom) { result[1] = middle; result[2] = middle; } else { result[1] = (top != right) ? middle : right; result[2] = (bottom != left) ? middle : left; } } result[3] = (!rightBottom || top == right || bottom == left) ? middle : bottom; } int8_t SAF_cos(uint8_t phase) { uint8_t index = phase % 64; uint8_t part = phase / 64; if (part % 2) index = 63 - index; int8_t result = _SAF_READ_CONST(_SAF_cosTable + index); if (part == 1 || part == 2) result *= -1; return result; } int8_t SAF_sin(uint8_t phase) { return SAF_cos(phase - 64); } void SAF_getFontCharacter(uint8_t asciiIndex, uint8_t result[2]) { asciiIndex = (asciiIndex >= ' ' && asciiIndex < '~') ? asciiIndex : ' '; asciiIndex = (asciiIndex - ' ') * 2; result[0] = _SAF_READ_CONST(_SAF_font + asciiIndex); result[1] = _SAF_READ_CONST(_SAF_font + asciiIndex + 1); } void _SAF_preprocessPosSize(int8_t *x, int8_t *y, int8_t *w, int8_t *h) { #define p(s,c)\ if (*s >= 0)\ {\ if (*c + *s < *c) /* overflow? */\ *s = 127 - *s;\ }\ else\ {\ if (*c + *s > *c)\ *c = -1 * (*c + 128);\ *c += *s;\ *s *= -1;\ } p(w,x) p(h,y) #undef p } void SAF_drawRect(int8_t x, int8_t y, int8_t width, int8_t height, uint8_t color, uint8_t filled) { if (width == 0 || height == 0) return; _SAF_preprocessPosSize(&x,&y,&width,&height); int8_t x2 = x + width; if (filled) { while (height > 0) { int8_t x2 = x; for (uint8_t i = 0; i < width; ++i) { SAF_drawPixel(x2,y,color); x2++; } height--; y++; } } else { int8_t y2 = y; while (height > 0) { SAF_drawPixel(x,y,color); SAF_drawPixel(x2 - 1,y,color); height--; y++; } y--; while (width > 0) { SAF_drawPixel(x,y,color); SAF_drawPixel(x,y2,color); width--; x++; } } } int8_t SAF_drawText(const char *text, int8_t x, int8_t y, uint8_t color, uint8_t size) { if (size == 0) return x; int8_t originalX = x; while (1) // for each string character { char c = *text; if (c == 0) break; uint8_t character[2]; uint8_t width = 4 * size; if (c == '\n') { x = originalX; y += 5; text++; continue; } SAF_getFontCharacter(c,character); for (int8_t i = 0; i < 2; ++i) // for both bytes { uint8_t byte = character[i]; for (uint8_t j = 0; j < 8; ++j) { if (byte & 0x01) { int8_t x2, y2 = y; /* we have to iterate this way sa to prevent infinite for loops */ for (int8_t k = 0; k < size; ++k) { x2 = x; for (int8_t l = 0; l < size; ++l) { SAF_drawPixel(x2,y2,color); x2++; } y2++; } } byte >>= 1; x += size; if (j == 3 || j == 7) { x -= width; y += size; } } } x += 6 * size; y -= width; text++; } return x; } void SAF_drawLine(int8_t x1, int8_t y1, int8_t x2, int8_t y2, uint8_t color) { if (x1 > x2) { uint8_t tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; } uint8_t dx = x2 - x1; uint8_t dy; int8_t *drawX = &x1; int8_t *drawY = &y1; int8_t stepX = 1; int8_t stepY = 1; uint8_t length = dx; uint8_t add; uint8_t compare = dx; if (y2 > y1) { dy = y2 - y1; add = dy; if (dy > dx) { drawX = &y1; drawY = &x1; length = dy; add = dx; compare = dy; } } else { dy = y1 - y2; add = dy; if (dy < dx) { stepY = -1; } else { drawX = &y1; drawY = &x1; length = dy; stepX = -1; add = dx; compare = dy; } } uint8_t accumulator = compare / 2; length++; while (length > 0) { SAF_drawPixel(x1,y1,color); *drawX += stepX; length--; accumulator += add; if (accumulator >= compare) { accumulator -= compare; *drawY += stepY; } } } uint32_t SAF_frame(void) { return _SAF_frame; } uint32_t SAF_time(void) { return _SAF_frame * SAF_MS_PER_FRAME; } void SAF_FE_init(void) { SAF_init(); } uint8_t SAF_FE_loop(void) { for (uint8_t i = 0; i < SAF_BUTTONS; ++i) { uint8_t *b = _SAF_buttonStates + i; uint8_t state = *b; *b = SAF_FE_buttonPressed(i) ? (state < 255 ? state + 1 : state) : 0; } uint8_t result = SAF_loop(); _SAF_frame++; return result; } static inline void SAF_clearScreen(uint8_t color) { SAF_drawRect(0,0,SAF_SCREEN_WIDTH,SAF_SCREEN_HEIGHT,color,1); } uint8_t SAF_random() { /* We reorder each sequence of 8 values to give some variety to specific bit patterns. */ uint8_t remap[8] = {5,7,2,0,3,6,4,1}; _SAF_currentRandom *= 13; _SAF_currentRandom += 7; uint8_t remainder = _SAF_currentRandom % 8; return _SAF_currentRandom - remainder + remap[remainder]; } uint8_t SAF_buttonJustPressed(uint8_t button) { return SAF_buttonPressed(button) == 1; } uint8_t SAF_buttonPressed(uint8_t button) { return (button < SAF_BUTTONS) ? _SAF_buttonStates[button] : 0; } uint8_t SAF_colorFromRGB(uint8_t red, uint8_t green, uint8_t blue) { return SAF_COLOR_RGB(red,green,blue); } void SAF_colorToRGB(uint8_t colorIndex, uint8_t *red, uint8_t *green, uint8_t *blue) { uint8_t value = (colorIndex >> 5) & 0x07; *red = value != 7 ? value * 36 : 255; value = (colorIndex >> 2) & 0x07; *green = value != 7 ? value * 36 : 255; value = colorIndex & 0x03; *blue = (value != 3) ? value * 72 : 255; } uint8_t SAF_colorInvert(uint8_t color) { return ~color; } uint16_t SAF_sqrt(uint32_t number) { uint32_t result = 0; uint32_t a = number; uint32_t b = 1u << 30; while (b > a) b >>= 2; while (b != 0) { if (a >= result + b) { a -= result + b; result = result + 2 * b; } b >>= 2; result >>= 1; } return result; } #define _SAF_IMAGE_MODE_NORMAL 0 #define _SAF_IMAGE_MODE_COMPRESSED 1 #define _SAF_IMAGE_MODE_1BIT 2 struct { uint8_t mode; const uint8_t *image; uint8_t transparentColor; const uint8_t *binaryMask; uint8_t binaryLine; uint8_t binaryMaskLine; uint8_t binaryPosition; uint8_t binaryColor1; uint8_t binaryColor2; uint8_t rleCount; uint8_t rleLastColor; const uint8_t *palette; } _SAF_drawnImage; uint8_t _SAF_getNextImagePixel() { uint8_t result = 0; switch (_SAF_drawnImage.mode) { case _SAF_IMAGE_MODE_NORMAL: result = *_SAF_drawnImage.image; _SAF_drawnImage.image++; break; case _SAF_IMAGE_MODE_1BIT: if (_SAF_drawnImage.binaryPosition == 0) { _SAF_drawnImage.binaryLine = *_SAF_drawnImage.image; _SAF_drawnImage.image++; _SAF_drawnImage.binaryPosition = 8; if (_SAF_drawnImage.binaryMask != 0) { _SAF_drawnImage.binaryMaskLine = ~(*_SAF_drawnImage.binaryMask); /* We negate the mask because we want 1 to mean transparency; this allows to avoid an if-check before mask line shift later on. */ _SAF_drawnImage.binaryMask++; } } result = ((_SAF_drawnImage.binaryMaskLine & 0x80) == 0) ? ( (_SAF_drawnImage.binaryLine & 0x80) ? _SAF_drawnImage.binaryColor1 : _SAF_drawnImage.binaryColor2 ) : _SAF_drawnImage.transparentColor; _SAF_drawnImage.binaryPosition--; _SAF_drawnImage.binaryLine <<= 1; _SAF_drawnImage.binaryMaskLine <<= 1; break; case _SAF_IMAGE_MODE_COMPRESSED: if (_SAF_drawnImage.rleCount == 0) { uint8_t b = *_SAF_drawnImage.image; _SAF_drawnImage.rleLastColor = _SAF_drawnImage.palette[b & 0x0f]; _SAF_drawnImage.rleCount = (b >> 4) + 1; _SAF_drawnImage.image++; } result = _SAF_drawnImage.rleLastColor; _SAF_drawnImage.rleCount--; break; default: break; } return result; } void _SAF_drawImageGeneral(int8_t x, int8_t y, uint8_t transform) { int8_t stepX = 1; int8_t stepY = 1; uint8_t invert = transform & SAF_TRANSFORM_INVERT; uint8_t scale = 1; switch (transform & 0x18) { case SAF_TRANSFORM_SCALE_2: scale = 2; break; case SAF_TRANSFORM_SCALE_3: scale = 3; break; case SAF_TRANSFORM_SCALE_4: scale = 4; break; default: break; } uint8_t width = *(_SAF_drawnImage.image); _SAF_drawnImage.image++; uint8_t h = *(_SAF_drawnImage.image); _SAF_drawnImage.image++; if (_SAF_drawnImage.mode == _SAF_IMAGE_MODE_COMPRESSED) _SAF_drawnImage.image += 16; // skip the palette uint8_t scaledWidth = (width - 1) * scale; uint8_t scaledHeight = (h - 1) * scale; int8_t *drawX = &x; int8_t *drawY = &y; switch (transform & 0x07) { case SAF_TRANSFORM_ROTATE_90: drawY = &x; drawX = &y; stepY = -1; x += scaledHeight; break; case SAF_TRANSFORM_ROTATE_180: stepX = -1; stepY = -1; x += scaledWidth; y += scaledHeight; break; case SAF_TRANSFORM_ROTATE_270: drawY = &x; drawX = &y; stepX = -1; y += scaledWidth; break; case SAF_TRANSFORM_FLIP: stepX = -1; x += scaledWidth; break; case (SAF_TRANSFORM_ROTATE_90 | SAF_TRANSFORM_FLIP): drawY = &x; drawX = &y; stepX = -1; stepY = -1; x += scaledHeight; y += scaledWidth; break; case (SAF_TRANSFORM_ROTATE_180 | SAF_TRANSFORM_FLIP): stepY = -1; y += scaledHeight; break; case (SAF_TRANSFORM_ROTATE_270 | SAF_TRANSFORM_FLIP): drawY = &x; drawX = &y; break; default: break; } stepX *= scale; stepY *= scale; int8_t lineBack = -1 * stepX * width; while (h > 0) { uint8_t w = width; while (w > 0) { uint8_t pixel = _SAF_getNextImagePixel(); if (pixel != _SAF_drawnImage.transparentColor) for (int8_t x2 = x; x2 < x + scale; ++x2) for (int8_t y2 = y; y2 < y + scale; ++y2) SAF_drawPixel(x2,y2,invert ? ~(pixel) : pixel); *drawX += stepX; w--; } *drawX += lineBack; h--; *drawY += stepY; } } /** Performs the most common cases of image drawing faster than the general function. */ void _SAF_drawImageFast(const uint8_t *image, int8_t x, int8_t y, uint8_t flip, uint8_t transparentColor, uint8_t is1Bit, const uint8_t *mask, uint8_t color1, uint8_t color2) { uint8_t width = *image; image++; uint8_t height = *image; image ++; int8_t x0 = x; int8_t xPlus = 1; if (flip) { x0 = x + width - 1; xPlus = -1; } #define loopStart\ while (height != 0) {\ x = x0;\ for (uint8_t w = width; w != 0; --w, x += xPlus) { #define loopEnd\ }\ height--;\ y++; } if (is1Bit) { uint8_t bitCount = 0, imgLine = 0, maskLine = 0; mask += mask != 0 ? 2 : 0; // skip width and height loopStart if (bitCount == 0) { imgLine = *image; bitCount = 8; image++; if (mask != 0) { maskLine = ~(*mask); // negation helps avoid branching later mask++; } } if ((maskLine & 0x80) == 0) SAF_drawPixel(x,y,(imgLine & 0x80) ? color1 : color2); bitCount--; imgLine <<= 1; maskLine <<= 1; loopEnd } else { loopStart uint8_t color = *image; if (color != transparentColor) SAF_drawPixel(x,y,*image); image++; loopEnd } #undef loopStart #undef loopEnd } void SAF_drawImage(const uint8_t *image, int8_t x, int8_t y, uint8_t transform, uint8_t transparentColor) { if ((transform & ~SAF_TRANSFORM_FLIP) == 0) { _SAF_drawImageFast(image,x,y,transform & SAF_TRANSFORM_FLIP, transparentColor,0,0,0,0); return; } _SAF_drawnImage.image = image; _SAF_drawnImage.mode = _SAF_IMAGE_MODE_NORMAL; _SAF_drawnImage.transparentColor = transparentColor; _SAF_drawImageGeneral(x,y,transform); } void SAF_drawImageCompressed(const uint8_t *image, int8_t x, int8_t y, uint8_t transform, uint8_t transparentColor) { _SAF_drawnImage.image = image; _SAF_drawnImage.mode = _SAF_IMAGE_MODE_COMPRESSED; _SAF_drawnImage.transparentColor = transparentColor; _SAF_drawnImage.rleCount = 0; _SAF_drawnImage.rleLastColor = 0; _SAF_drawnImage.palette = image + 2; _SAF_drawImageGeneral(x,y,transform); } void SAF_drawImage1Bit(const uint8_t *image, int8_t x, int8_t y, const uint8_t *mask, uint8_t color1, uint8_t color2, uint8_t transform) { if ((transform & ~SAF_TRANSFORM_FLIP) == 0) { _SAF_drawImageFast(image,x,y,transform & SAF_TRANSFORM_FLIP,0,1,mask,color1, color2); return; } _SAF_drawnImage.image = image; _SAF_drawnImage.binaryMask = (mask != 0) ? mask + 2 : 0; // skip width/height _SAF_drawnImage.binaryLine = 0; _SAF_drawnImage.binaryMaskLine = 0; // 0 will be negated _SAF_drawnImage.binaryPosition = 0; for (int i = 0; i < 3; ++i) if (i != color1 && i != color2) { _SAF_drawnImage.transparentColor = i; break; } _SAF_drawnImage.mode = _SAF_IMAGE_MODE_1BIT; _SAF_drawnImage.binaryColor1 = color1; _SAF_drawnImage.binaryColor2 = color2; _SAF_drawImageGeneral(x,y,transform); } void SAF_drawPixel(int8_t x, int8_t y, uint8_t color) { if ((x & 0xc0) == 0 && (y & 0xc0) == 0) #if SAF_PLATFORM_COLOR_COUNT <= 2 SAF_FE_drawPixel(x,y,SAF_FE_colorTo1Bit(color,x,y) ? SAF_COLOR_WHITE : SAF_COLOR_BLACK); #else SAF_FE_drawPixel(x,y,color); #endif } void SAF_playSound(uint8_t sound) { #if SAF_SETTING_ENABLE_SOUND if (sound < SAF_SOUNDS) SAF_FE_playSound(sound); #else _SAF_UNUSED(sound); #endif } void _SAF_reloadSaveMemory() { if (!_SAF_saveMemoryLoaded) { for (uint8_t i = 0; i < SAF_SAVE_SIZE; ++i) _SAF_saveMemory[i] = #if SAF_SETTING_ENABLE_SAVES SAF_FE_load(i); #else 0; #endif _SAF_saveMemoryLoaded = 1; } } void SAF_save(uint8_t index, uint8_t data) { if (index >= SAF_SAVE_SIZE) return; _SAF_reloadSaveMemory(); if (_SAF_saveMemory[index] != data) { _SAF_saveMemory[index] = data; #if SAF_SETTING_ENABLE_SAVES SAF_FE_save(index,data); #endif } } uint8_t SAF_load(uint8_t index) { if (index >= SAF_SAVE_SIZE) return 0; _SAF_reloadSaveMemory(); return _SAF_saveMemory[index]; } void SAF_randomSeed(uint8_t seed) { _SAF_currentRandom = seed; } char *SAF_intToStr(int32_t number, char *string) { if (number == 0) { *string = '0'; *(string + 1) = 0; return string; } char *start = string; if (number < 0) { *string = '-'; string++; number *= -1; start++; } while (number > 0) { *string = '0' + number % 10; number /= 10; string++; } *string = 0; // terminate char *end = string - 1; string = start; while (end > start) { char tmp = *start; *start = *end; *end = tmp; start++; end--; } return string; } char *SAF_floatToStr(float number, char *string, uint8_t decimals) { int32_t whole = number; SAF_intToStr(whole,string); if (decimals == 0) { *string = 0; return string; } char *c = string; while (*c != 0) c++; *c = '.'; c++; if (number < 0) { number *= -1; whole *= -1; } if (decimals > 10) decimals = 10; int32_t factor = 1; while (decimals > 0) { factor *= 10; decimals--; } SAF_intToStr((number - whole) * factor,c); return string; } void SAF_drawCircle(int8_t x, int8_t y, uint8_t radius, uint8_t color, uint8_t filled) { int8_t drawX = 0; int8_t drawY = radius; int16_t d = 3 - 2 * radius; if (!filled) { SAF_drawPixel(x,y + radius,color); SAF_drawPixel(x,y - radius,color); SAF_drawPixel(x + radius,y,color); SAF_drawPixel(x - radius,y,color); } else for (int8_t i = x - radius; i <= x + radius; ++i) SAF_drawPixel(i,y,color); while (drawY >= drawX) { if (d < 0) { d += 4 * drawX + 6; } else { d += 4 * (drawX - drawY) + 10; drawY--; } drawX++; int8_t xPlus = x + drawX; int8_t xMinus = x - drawX; int8_t yPlus = y + drawY; int8_t yMinus = y - drawY; int8_t x2Plus = x + drawY; int8_t x2Minus = x - drawY; int8_t y2Plus = y + drawX; int8_t y2Minus = y - drawX; if (!filled) { SAF_drawPixel(xPlus,yPlus,color); SAF_drawPixel(xPlus,yMinus,color); SAF_drawPixel(xMinus,yPlus,color); SAF_drawPixel(xMinus,yMinus,color); SAF_drawPixel(x2Plus,y2Plus,color); SAF_drawPixel(x2Plus,y2Minus,color); SAF_drawPixel(x2Minus,y2Plus,color); SAF_drawPixel(x2Minus,y2Minus,color); } else { for (int8_t i = xMinus; i <= xPlus; ++i) { SAF_drawPixel(i,yPlus,color); SAF_drawPixel(i,yMinus,color); } for (int8_t i = x2Minus; i <= x2Plus; ++i) { SAF_drawPixel(i,y2Plus,color); SAF_drawPixel(i,y2Minus,color); } } } } const char *SAF_extension(const char *string) { return SAF_FE_extension(string); } uint8_t SAF_colorTo1Bit(uint8_t colorIndex) { return #if SAF_SETTING_FASTER_1BIT == 0 // more accurate: 7 operations ((colorIndex & 0x03) + ((colorIndex >> 2) & 0x07) + (colorIndex >> 5)) > 8; #elif SAF_SETTING_FASTER_1BIT == 1 // faster: 4 operations ((colorIndex >> 3) + (colorIndex & 0x1f)) >= ((0x1f + 0x1c) / 2); #elif SAF_SETTING_FASTER_1BIT == 2 // fastest: 1 operation colorIndex & 0x90; #else // fastester: 0 operations colorIndex; #endif } uint8_t SAF_colorToGrayscale(uint8_t colorIndex) { uint8_t tmp = colorIndex >> 2; return (((colorIndex << 2) & 0x7f) + tmp + ((colorIndex << 4) & 0x3f)) | tmp; } uint16_t SAF_FE_hashStr(const char *str) { uint16_t r = 7621; while (*str != 0) { r = (r << 4) ^ (r + ((uint16_t) *str)); str++; } return r; } uint8_t SAF_FE_colorTo1Bit(uint8_t color, uint8_t x, uint8_t y) { #if SAF_SETTING_1BIT_DITHER color = SAF_colorToGrayscale(color); if (color < 85) return 0; else if (color > 170) return 1; else return (x % 2) == (y % 2); #else _SAF_UNUSED(x); _SAF_UNUSED(y); return SAF_colorTo1Bit(color); #endif } #endif // guard