├── .gitignore ├── LICENSE ├── README.md ├── demo.gif ├── makefile └── src ├── defs.h ├── font └── Inconsolata-Regular.ttf ├── graphics.c ├── graphics.h ├── init.c ├── init.h ├── input.c ├── input.h ├── main.c ├── main.h ├── sds.c ├── sds.h ├── shuffle.c ├── shuffle.h ├── tetris.c ├── tetris.h ├── utility.c └── utility.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Libraries 8 | *.lib 9 | *.a 10 | 11 | # Shared objects (inc. Windows DLLs) 12 | *.dll 13 | *.so 14 | *.so.* 15 | *.dylib 16 | 17 | # Executables 18 | *.exe 19 | *.out 20 | *.app 21 | *.i*86 22 | *.x86_64 23 | *.hex 24 | 25 | tetris_toy -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Alberto Leal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tetris-sdl-c 2 | ============ 3 | 4 | A simple tetris game implemented in C using SDL 2.x. 5 | This is just a toy project to play with and learn SDL 2.x. 6 | 7 | **Demo on Snow Leopard OSX:** 8 | 9 | ![](demo.gif) 10 | 11 | **Demo on El Capitan OSX:** 12 | 13 | [![](https://thumbs.gfycat.com/CraftyEntireCleanerwrasse-size_restricted.gif)](https://gfycat.com/gifs/detail/CraftyEntireCleanerwrasse) 14 | 15 | Install 16 | ======= 17 | 18 | 1. Install SDL 2.x ([www.libsdl.org](http://www.libsdl.org/)) in UNIX style; something like `./configure && make && make install`. 19 | 20 | Also you'll need: 21 | 22 | - `sdl2_ttf`: https://www.libsdl.org/projects/SDL_ttf/ 23 | - `sdl2_gfx`: http://cms.ferzkopp.net/index.php/software/13-sdl-gfx 24 | 25 | On OSX (or macOS), you may use [homebrew](http://brew.sh/): 26 | 27 | ```sh 28 | # As of Dec 2, 2017 29 | 30 | brew install sdl2 31 | 32 | # http://formulae.brew.sh/formula/sdl2_gfx 33 | brew install sdl2_gfx 34 | 35 | # http://formulae.brew.sh/formula/sdl2_ttf 36 | brew install sdl2_ttf 37 | ``` 38 | 39 | 2. `make` to create `tetris_toy` 40 | 41 | 3. `./tetris_toy` 42 | 43 | Usage 44 | ===== 45 | 46 | - Move tetromino with WASD keys or arrow keys. 47 | - Press `spacebar` for hard Tetromino drop. 48 | 49 | - Press `r` to reset. 50 | - Press `esc` to quit. 51 | 52 | 53 | To Do 54 | ===== 55 | 56 | - Add wall kick 57 | - Implement any other interesting mechanics listed in http://tetrisconcept.net/wiki/Main_Page 58 | 59 | License 60 | ======= 61 | 62 | MIT. 63 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashed/tetris-sdl-c/80e6bad084746663bbe51a8d54c8f028269ae653/demo.gif -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -g `sdl2-config --cflags` 2 | LDFLAGS = `sdl2-config --libs` -lSDL2_gfx -lSDL2_ttf -lm 3 | PROG = tetris_toy 4 | CXX = gcc 5 | 6 | OBJS = init.o input.o graphics.o tetris.o shuffle.o sds.o utility.o main.o 7 | 8 | # top-level rule to create the program. 9 | all: $(PROG) 10 | 11 | # compiling other source files. 12 | %.o: src/%.c src/%.h src/defs.h 13 | $(CXX) $(CFLAGS) -c $< 14 | 15 | # linking the program 16 | $(PROG): $(OBJS) 17 | $(CXX) $(OBJS) -o $(PROG) $(LDFLAGS) 18 | 19 | # cleaning everything that can be automatically recreated with "make" 20 | clean: 21 | rm $(PROG) *.o 22 | -------------------------------------------------------------------------------- /src/defs.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "sds.h" 9 | #include "SDL2/SDL.h" 10 | #include "SDL2/SDL_ttf.h" 11 | #include "SDL2_gfxPrimitives.h" 12 | 13 | #ifndef _GLOBAL_CONSTANTS 14 | #define _GLOBAL_CONSTANTS 15 | 16 | #define WINDOW_TITLE "tetris-sdl-c" 17 | 18 | // a block 'pixel' of a playing field is 15px by 15px in size 19 | #define BLOCK_SIZE 20 20 | 21 | // standard size of a tetris playing field 22 | #define PLAYFIELD_HEIGHT 22 23 | #define PLAYFIELD_WIDTH 10 24 | 25 | #define WINDOW_HEIGHT PLAYFIELD_HEIGHT * (BLOCK_SIZE + 1) + 1 26 | #define WINDOW_WIDTH PLAYFIELD_WIDTH * (BLOCK_SIZE + 1) + 1 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/font/Inconsolata-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashed/tetris-sdl-c/80e6bad084746663bbe51a8d54c8f028269ae653/src/font/Inconsolata-Regular.ttf -------------------------------------------------------------------------------- /src/graphics.c: -------------------------------------------------------------------------------- 1 | #include "graphics.h" 2 | 3 | 4 | void init_graphics() { 5 | 6 | render_changed = false; 7 | 8 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2"); 9 | 10 | window = SDL_CreateWindow( 11 | // title of window 12 | WINDOW_TITLE, 13 | 14 | // initial position of the window 15 | SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 16 | 17 | WINDOW_WIDTH, WINDOW_HEIGHT, 18 | SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI); 19 | 20 | if (window == NULL) { 21 | fprintf(stderr, 22 | "\nSDL_CreateWindow Error: %s\n", 23 | SDL_GetError()); 24 | exit(1); 25 | } 26 | 27 | // Create a renderer that will draw to the window, -1 specifies that we want to load whichever 28 | // video driver supports the flags we're passing 29 | // 30 | // Flags: 31 | // SDL_RENDERER_ACCELERATED: We want to use hardware accelerated rendering 32 | // SDL_RENDERER_PRESENTVSYNC: We want the renderer's present function (update screen) to be 33 | // synchornized with the monitor's refresh rate 34 | render = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE); 35 | 36 | if (render == NULL) { 37 | fprintf(stderr, 38 | "\nSDL_CreateRenderer Error: %s\n", 39 | SDL_GetError()); 40 | exit(1); 41 | } 42 | 43 | SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_BLEND); 44 | 45 | // texture for render context 46 | display = SDL_CreateTexture(render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, WINDOW_WIDTH, WINDOW_HEIGHT); 47 | 48 | SDL_SetRenderTarget(render, display); 49 | 50 | // Load font 51 | gFont = TTF_OpenFont("src/font/Inconsolata-Regular.ttf", 30); 52 | 53 | if (gFont == NULL) { 54 | fprintf(stderr, 55 | "\nTTF_OpenFont Error: %s\n", 56 | SDL_GetError()); 57 | exit(1); 58 | } 59 | 60 | TTF_SetFontHinting(gFont, TTF_HINTING_MONO); 61 | } 62 | 63 | void setRenderChanged() { 64 | render_changed = true; 65 | } 66 | 67 | void preRender() { 68 | 69 | 70 | SDL_SetRenderTarget(render, display); 71 | 72 | 73 | } 74 | 75 | void updateRender() { 76 | 77 | // lazily update the screen only if render operations are queued 78 | if(render_changed) { 79 | 80 | SDL_SetRenderTarget(render, NULL); 81 | SDL_RenderCopy(render, display, NULL, NULL); 82 | 83 | SDL_RenderPresent(render); 84 | render_changed = false; 85 | 86 | } 87 | } 88 | 89 | void draw_block(uint8_t x, uint8_t y, uint32_t color) { 90 | 91 | assert(x >= 0 && x < PLAYFIELD_WIDTH); 92 | assert(y >= 0 && y < PLAYFIELD_HEIGHT); 93 | 94 | // top-left coords of block 95 | uint16_t x_tl = x * (BLOCK_SIZE + 1) + 1; 96 | uint16_t y_tl = y * (BLOCK_SIZE + 1) + 1; 97 | 98 | // top-right coords of block 99 | uint16_t x_tr = x_tl + BLOCK_SIZE; 100 | uint16_t y_tr = y_tl; 101 | 102 | // bottom-right coords of block 103 | uint16_t x_br = x_tl + BLOCK_SIZE; 104 | uint16_t y_br = y_tl + BLOCK_SIZE; 105 | 106 | // bottom-left coords of block 107 | uint16_t x_bl = x_tl; 108 | uint16_t y_bl = y_tl + BLOCK_SIZE; 109 | 110 | boxColor(render, x_tl, y_tl, x_br, y_br, color); 111 | 112 | // draw grid lines 113 | 114 | if(y <= 0) { 115 | // draw top horizontal grid line (from top-left to top-right) 116 | aalineRGBA(render, x_tl, y_tl, x_tr, y_tr, 187, 173, 160, 255); 117 | } 118 | 119 | if(x <= 0) { 120 | // draw left vertical grid line (from top-left to bottom-left) 121 | aalineRGBA(render, x_tl, y_tl, x_bl, y_bl, 187, 173, 160, 255); 122 | } 123 | 124 | // draw bottom horizontal grid line (from bottom-left to bottom-right) 125 | aalineRGBA(render, x_bl, y_bl, x_br, y_br, 187, 173, 160, 255); 126 | 127 | // draw right vertical grid line (from top-right to bottom-right) 128 | aalineRGBA(render, x_tr, y_tr, x_br, y_br, 187, 173, 160, 255); 129 | 130 | setRenderChanged(); 131 | 132 | } 133 | 134 | void cleanup_graphics() { 135 | SDL_DestroyRenderer(render); 136 | SDL_DestroyWindow(window); 137 | } 138 | -------------------------------------------------------------------------------- /src/graphics.h: -------------------------------------------------------------------------------- 1 | #include "defs.h" 2 | 3 | extern SDL_Window *window; 4 | extern SDL_Renderer *render; 5 | extern SDL_Texture *display; 6 | extern TTF_Font *gFont; 7 | 8 | // This tracks whether to update the render. 9 | // Any procedure that has updated the render should set this to true. 10 | extern bool render_changed; 11 | 12 | void init_graphics(); 13 | void cleanup_graphics(); 14 | 15 | 16 | void draw_block(uint8_t x, uint8_t y, uint32_t color); 17 | 18 | void setRenderChanged(); 19 | void preRender(); 20 | void updateRender(); 21 | 22 | -------------------------------------------------------------------------------- /src/init.c: -------------------------------------------------------------------------------- 1 | #include "init.h" 2 | 3 | void init() { 4 | 5 | if(TTF_Init() == -1) { 6 | fprintf(stderr, 7 | "\nTTF_Init Error: %s\n", 8 | SDL_GetError()); 9 | exit(1); 10 | } 11 | 12 | init_graphics(); 13 | 14 | initTetris(); 15 | 16 | } 17 | 18 | void cleanup() { 19 | 20 | cleanup_graphics(); 21 | 22 | TTF_Quit(); 23 | 24 | // Shut down SDL 25 | SDL_Quit(); 26 | } 27 | -------------------------------------------------------------------------------- /src/init.h: -------------------------------------------------------------------------------- 1 | #include "defs.h" 2 | #include "graphics.h" 3 | #include "tetris.h" 4 | 5 | // extern vars from graphics.h 6 | SDL_Window *window; 7 | SDL_Renderer *render; 8 | 9 | void init(); 10 | 11 | void cleanup(); 12 | -------------------------------------------------------------------------------- /src/input.c: -------------------------------------------------------------------------------- 1 | #include "input.h" 2 | 3 | void getInput() { 4 | SDL_Event event; 5 | 6 | /* Loop through waiting messages and process them */ 7 | 8 | while (SDL_PollEvent(&event)) { 9 | switch (event.type) { 10 | 11 | /* Closing the Window or pressing Escape will exit the program */ 12 | case SDL_QUIT: 13 | exit(0); 14 | break; 15 | 16 | case SDL_KEYDOWN: 17 | switch (event.key.keysym.sym) { 18 | case SDLK_ESCAPE: 19 | exit(0); 20 | break; 21 | 22 | case SDLK_s: 23 | case SDLK_DOWN: 24 | TETROMINO_ACTION = DOWN; 25 | break; 26 | 27 | case SDLK_d: 28 | case SDLK_RIGHT: 29 | TETROMINO_ACTION = RIGHT; 30 | break; 31 | 32 | case SDLK_a: 33 | case SDLK_LEFT: 34 | TETROMINO_ACTION = LEFT; 35 | break; 36 | 37 | case SDLK_w: 38 | case SDLK_UP: 39 | TETROMINO_ACTION = ROTATE; 40 | break; 41 | 42 | case SDLK_r: 43 | TETROMINO_ACTION = RESTART; 44 | break; 45 | 46 | case SDLK_SPACE: 47 | TETROMINO_ACTION = DROP; 48 | break; 49 | 50 | default: 51 | break; 52 | } 53 | break; 54 | 55 | case SDL_KEYUP: 56 | TETROMINO_ACTION = NONE; 57 | break; 58 | 59 | case SDL_USEREVENT: 60 | TETROMINO_ACTION = AUTO_DROP; 61 | break; 62 | 63 | default: 64 | break; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/input.h: -------------------------------------------------------------------------------- 1 | #include "defs.h" 2 | #include "tetris.h" 3 | 4 | Tetris_Action TETROMINO_ACTION; 5 | 6 | void getInput(); 7 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | 3 | int main(int argc, const char *argv[]) { 4 | 5 | // Start up SDL, and make sure it went ok 6 | // 7 | uint32_t flags = SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_EVENTS; 8 | if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { 9 | 10 | fprintf(stderr, 11 | "\nUnable to initialize SDL: %s\n", 12 | SDL_GetError()); 13 | 14 | return 1; 15 | } 16 | 17 | atexit(cleanup); 18 | 19 | init(); 20 | 21 | bool quit = false; 22 | while(!quit) { 23 | 24 | preRender(); 25 | 26 | getInput(); 27 | 28 | updateTetris(); 29 | 30 | updateRender(); 31 | 32 | // Set to ~60 fps. 33 | // 1000 ms/ 60 fps = 1/16 s^2/frame 34 | SDL_Delay(16); 35 | } 36 | 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | #include "defs.h" 2 | 3 | #include "init.h" 4 | #include "graphics.h" 5 | #include "input.h" 6 | #include "tetris.h" 7 | 8 | // extern vars from graphics.h 9 | SDL_Window *window; 10 | SDL_Renderer *render; 11 | SDL_Texture *display; 12 | TTF_Font *gFont; 13 | 14 | bool render_changed; 15 | -------------------------------------------------------------------------------- /src/sds.c: -------------------------------------------------------------------------------- 1 | /* SDS (Simple Dynamic Strings), A C dynamic strings library. 2 | * 3 | * Copyright (c) 2006-2014, Salvatore Sanfilippo 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include "sds.h" 38 | 39 | /* Create a new sds string with the content specified by the 'init' pointer 40 | * and 'initlen'. 41 | * If NULL is used for 'init' the string is initialized with zero bytes. 42 | * 43 | * The string is always null-termined (all the sds strings are, always) so 44 | * even if you create an sds string with: 45 | * 46 | * mystring = sdsnewlen("abc",3"); 47 | * 48 | * You can print the string with printf() as there is an implicit \0 at the 49 | * end of the string. However the string is binary safe and can contain 50 | * \0 characters in the middle, as the length is stored in the sds header. */ 51 | sds sdsnewlen(const void *init, size_t initlen) { 52 | struct sdshdr *sh; 53 | 54 | if (init) { 55 | sh = malloc(sizeof *sh+initlen+1); 56 | } else { 57 | sh = calloc(sizeof *sh+initlen+1,1); 58 | } 59 | if (sh == NULL) return NULL; 60 | sh->len = initlen; 61 | sh->free = 0; 62 | if (initlen && init) 63 | memcpy(sh->buf, init, initlen); 64 | sh->buf[initlen] = '\0'; 65 | return (char*)sh->buf; 66 | } 67 | 68 | /* Create an empty (zero length) sds string. Even in this case the string 69 | * always has an implicit null term. */ 70 | sds sdsempty(void) { 71 | return sdsnewlen("",0); 72 | } 73 | 74 | /* Create a new sds string starting from a null termined C string. */ 75 | sds sdsnew(const char *init) { 76 | size_t initlen = (init == NULL) ? 0 : strlen(init); 77 | return sdsnewlen(init, initlen); 78 | } 79 | 80 | /* Duplicate an sds string. */ 81 | sds sdsdup(const sds s) { 82 | return sdsnewlen(s, sdslen(s)); 83 | } 84 | 85 | /* Free an sds string. No operation is performed if 's' is NULL. */ 86 | void sdsfree(sds s) { 87 | if (s == NULL) return; 88 | free(s-sizeof(struct sdshdr)); 89 | } 90 | 91 | /* Set the sds string length to the length as obtained with strlen(), so 92 | * considering as content only up to the first null term character. 93 | * 94 | * This function is useful when the sds string is hacked manually in some 95 | * way, like in the following example: 96 | * 97 | * s = sdsnew("foobar"); 98 | * s[2] = '\0'; 99 | * sdsupdatelen(s); 100 | * printf("%d\n", sdslen(s)); 101 | * 102 | * The output will be "2", but if we comment out the call to sdsupdatelen() 103 | * the output will be "6" as the string was modified but the logical length 104 | * remains 6 bytes. */ 105 | void sdsupdatelen(sds s) { 106 | struct sdshdr *sh = (void*) (s-sizeof *sh);; 107 | int reallen = strlen(s); 108 | sh->free += (sh->len-reallen); 109 | sh->len = reallen; 110 | } 111 | 112 | /* Modify an sds string on-place to make it empty (zero length). 113 | * However all the existing buffer is not discarded but set as free space 114 | * so that next append operations will not require allocations up to the 115 | * number of bytes previously available. */ 116 | void sdsclear(sds s) { 117 | struct sdshdr *sh = (void*) (s-sizeof *sh);; 118 | sh->free += sh->len; 119 | sh->len = 0; 120 | sh->buf[0] = '\0'; 121 | } 122 | 123 | /* Enlarge the free space at the end of the sds string so that the caller 124 | * is sure that after calling this function can overwrite up to addlen 125 | * bytes after the end of the string, plus one more byte for nul term. 126 | * 127 | * Note: this does not change the *length* of the sds string as returned 128 | * by sdslen(), but only the free buffer space we have. */ 129 | sds sdsMakeRoomFor(sds s, size_t addlen) { 130 | struct sdshdr *sh, *newsh; 131 | size_t free = sdsavail(s); 132 | size_t len, newlen; 133 | 134 | if (free >= addlen) return s; 135 | len = sdslen(s); 136 | sh = (void*) (s-sizeof *sh);; 137 | newlen = (len+addlen); 138 | if (newlen < SDS_MAX_PREALLOC) 139 | newlen *= 2; 140 | else 141 | newlen += SDS_MAX_PREALLOC; 142 | newsh = realloc(sh, sizeof *newsh+newlen+1); 143 | if (newsh == NULL) return NULL; 144 | 145 | newsh->free = newlen - len; 146 | return newsh->buf; 147 | } 148 | 149 | /* Reallocate the sds string so that it has no free space at the end. The 150 | * contained string remains not altered, but next concatenation operations 151 | * will require a reallocation. 152 | * 153 | * After the call, the passed sds string is no longer valid and all the 154 | * references must be substituted with the new pointer returned by the call. */ 155 | sds sdsRemoveFreeSpace(sds s) { 156 | struct sdshdr *sh; 157 | 158 | sh = (void*) (s-sizeof *sh);; 159 | sh = realloc(sh, sizeof *sh+sh->len+1); 160 | sh->free = 0; 161 | return sh->buf; 162 | } 163 | 164 | /* Return the total size of the allocation of the specifed sds string, 165 | * including: 166 | * 1) The sds header before the pointer. 167 | * 2) The string. 168 | * 3) The free buffer at the end if any. 169 | * 4) The implicit null term. 170 | */ 171 | size_t sdsAllocSize(sds s) { 172 | struct sdshdr *sh = (void*) (s-sizeof *sh);; 173 | 174 | return sizeof(*sh)+sh->len+sh->free+1; 175 | } 176 | 177 | /* Increment the sds length and decrements the left free space at the 178 | * end of the string according to 'incr'. Also set the null term 179 | * in the new end of the string. 180 | * 181 | * This function is used in order to fix the string length after the 182 | * user calls sdsMakeRoomFor(), writes something after the end of 183 | * the current string, and finally needs to set the new length. 184 | * 185 | * Note: it is possible to use a negative increment in order to 186 | * right-trim the string. 187 | * 188 | * Usage example: 189 | * 190 | * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the 191 | * following schema, to cat bytes coming from the kernel to the end of an 192 | * sds string without copying into an intermediate buffer: 193 | * 194 | * oldlen = sdslen(s); 195 | * s = sdsMakeRoomFor(s, BUFFER_SIZE); 196 | * nread = read(fd, s+oldlen, BUFFER_SIZE); 197 | * ... check for nread <= 0 and handle it ... 198 | * sdsIncrLen(s, nread); 199 | */ 200 | void sdsIncrLen(sds s, int incr) { 201 | struct sdshdr *sh = (void*) (s-sizeof *sh);; 202 | 203 | assert(sh->free >= incr); 204 | sh->len += incr; 205 | sh->free -= incr; 206 | assert(sh->free >= 0); 207 | s[sh->len] = '\0'; 208 | } 209 | 210 | /* Grow the sds to have the specified length. Bytes that were not part of 211 | * the original length of the sds will be set to zero. 212 | * 213 | * if the specified length is smaller than the current length, no operation 214 | * is performed. */ 215 | sds sdsgrowzero(sds s, size_t len) { 216 | struct sdshdr *sh = (void*) (s-sizeof *sh); 217 | size_t totlen, curlen = sh->len; 218 | 219 | if (len <= curlen) return s; 220 | s = sdsMakeRoomFor(s,len-curlen); 221 | if (s == NULL) return NULL; 222 | 223 | /* Make sure added region doesn't contain garbage */ 224 | sh = (void*)(s-sizeof *sh); 225 | memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ 226 | totlen = sh->len+sh->free; 227 | sh->len = len; 228 | sh->free = totlen-sh->len; 229 | return s; 230 | } 231 | 232 | /* Append the specified binary-safe string pointed by 't' of 'len' bytes to the 233 | * end of the specified sds string 's'. 234 | * 235 | * After the call, the passed sds string is no longer valid and all the 236 | * references must be substituted with the new pointer returned by the call. */ 237 | sds sdscatlen(sds s, const void *t, size_t len) { 238 | struct sdshdr *sh; 239 | size_t curlen = sdslen(s); 240 | 241 | s = sdsMakeRoomFor(s,len); 242 | if (s == NULL) return NULL; 243 | sh = (void*) (s-sizeof *sh);; 244 | memcpy(s+curlen, t, len); 245 | sh->len = curlen+len; 246 | sh->free = sh->free-len; 247 | s[curlen+len] = '\0'; 248 | return s; 249 | } 250 | 251 | /* Append the specified null termianted C string to the sds string 's'. 252 | * 253 | * After the call, the passed sds string is no longer valid and all the 254 | * references must be substituted with the new pointer returned by the call. */ 255 | sds sdscat(sds s, const char *t) { 256 | return sdscatlen(s, t, strlen(t)); 257 | } 258 | 259 | /* Append the specified sds 't' to the existing sds 's'. 260 | * 261 | * After the call, the modified sds string is no longer valid and all the 262 | * references must be substituted with the new pointer returned by the call. */ 263 | sds sdscatsds(sds s, const sds t) { 264 | return sdscatlen(s, t, sdslen(t)); 265 | } 266 | 267 | /* Destructively modify the sds string 's' to hold the specified binary 268 | * safe string pointed by 't' of length 'len' bytes. */ 269 | sds sdscpylen(sds s, const char *t, size_t len) { 270 | struct sdshdr *sh = (void*) (s-sizeof *sh);; 271 | size_t totlen = sh->free+sh->len; 272 | 273 | if (totlen < len) { 274 | s = sdsMakeRoomFor(s,len-sh->len); 275 | if (s == NULL) return NULL; 276 | sh = (void*) (s-sizeof *sh);; 277 | totlen = sh->free+sh->len; 278 | } 279 | memcpy(s, t, len); 280 | s[len] = '\0'; 281 | sh->len = len; 282 | sh->free = totlen-len; 283 | return s; 284 | } 285 | 286 | /* Like sdscpylen() but 't' must be a null-termined string so that the length 287 | * of the string is obtained with strlen(). */ 288 | sds sdscpy(sds s, const char *t) { 289 | return sdscpylen(s, t, strlen(t)); 290 | } 291 | 292 | /* Like sdscatpritf() but gets va_list instead of being variadic. */ 293 | sds sdscatvprintf(sds s, const char *fmt, va_list ap) { 294 | va_list cpy; 295 | char *buf, *t; 296 | size_t buflen = 16; 297 | 298 | while(1) { 299 | buf = malloc(buflen); 300 | if (buf == NULL) return NULL; 301 | buf[buflen-2] = '\0'; 302 | va_copy(cpy,ap); 303 | vsnprintf(buf, buflen, fmt, cpy); 304 | if (buf[buflen-2] != '\0') { 305 | free(buf); 306 | buflen *= 2; 307 | continue; 308 | } 309 | break; 310 | } 311 | t = sdscat(s, buf); 312 | free(buf); 313 | return t; 314 | } 315 | 316 | /* Append to the sds string 's' a string obtained using printf-alike format 317 | * specifier. 318 | * 319 | * After the call, the modified sds string is no longer valid and all the 320 | * references must be substituted with the new pointer returned by the call. 321 | * 322 | * Example: 323 | * 324 | * s = sdsempty("Sum is: "); 325 | * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b). 326 | * 327 | * Often you need to create a string from scratch with the printf-alike 328 | * format. When this is the need, just use sdsempty() as the target string: 329 | * 330 | * s = sdscatprintf(sdsempty(), "... your format ...", args); 331 | */ 332 | sds sdscatprintf(sds s, const char *fmt, ...) { 333 | va_list ap; 334 | char *t; 335 | va_start(ap, fmt); 336 | t = sdscatvprintf(s,fmt,ap); 337 | va_end(ap); 338 | return t; 339 | } 340 | 341 | /* Remove the part of the string from left and from right composed just of 342 | * contiguous characters found in 'cset', that is a null terminted C string. 343 | * 344 | * After the call, the modified sds string is no longer valid and all the 345 | * references must be substituted with the new pointer returned by the call. 346 | * 347 | * Example: 348 | * 349 | * s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); 350 | * s = sdstrim(s,"A. :"); 351 | * printf("%s\n", s); 352 | * 353 | * Output will be just "Hello World". 354 | */ 355 | void sdstrim(sds s, const char *cset) { 356 | struct sdshdr *sh = (void*) (s-sizeof *sh);; 357 | char *start, *end, *sp, *ep; 358 | size_t len; 359 | 360 | sp = start = s; 361 | ep = end = s+sdslen(s)-1; 362 | while(sp <= end && strchr(cset, *sp)) sp++; 363 | while(ep > start && strchr(cset, *ep)) ep--; 364 | len = (sp > ep) ? 0 : ((ep-sp)+1); 365 | if (sh->buf != sp) memmove(sh->buf, sp, len); 366 | sh->buf[len] = '\0'; 367 | sh->free = sh->free+(sh->len-len); 368 | sh->len = len; 369 | } 370 | 371 | /* Turn the string into a smaller (or equal) string containing only the 372 | * substring specified by the 'start' and 'end' indexes. 373 | * 374 | * start and end can be negative, where -1 means the last character of the 375 | * string, -2 the penultimate character, and so forth. 376 | * 377 | * The interval is inclusive, so the start and end characters will be part 378 | * of the resulting string. 379 | * 380 | * The string is modified in-place. 381 | * 382 | * Example: 383 | * 384 | * s = sdsnew("Hello World"); 385 | * sdsrange(s,1,-1); => "ello World" 386 | */ 387 | void sdsrange(sds s, int start, int end) { 388 | struct sdshdr *sh = (void*) (s-sizeof *sh);; 389 | size_t newlen, len = sdslen(s); 390 | 391 | if (len == 0) return; 392 | if (start < 0) { 393 | start = len+start; 394 | if (start < 0) start = 0; 395 | } 396 | if (end < 0) { 397 | end = len+end; 398 | if (end < 0) end = 0; 399 | } 400 | newlen = (start > end) ? 0 : (end-start)+1; 401 | if (newlen != 0) { 402 | if (start >= (signed)len) { 403 | newlen = 0; 404 | } else if (end >= (signed)len) { 405 | end = len-1; 406 | newlen = (start > end) ? 0 : (end-start)+1; 407 | } 408 | } else { 409 | start = 0; 410 | } 411 | if (start && newlen) memmove(sh->buf, sh->buf+start, newlen); 412 | sh->buf[newlen] = 0; 413 | sh->free = sh->free+(sh->len-newlen); 414 | sh->len = newlen; 415 | } 416 | 417 | /* Apply tolower() to every character of the sds string 's'. */ 418 | void sdstolower(sds s) { 419 | int len = sdslen(s), j; 420 | 421 | for (j = 0; j < len; j++) s[j] = tolower(s[j]); 422 | } 423 | 424 | /* Apply toupper() to every character of the sds string 's'. */ 425 | void sdstoupper(sds s) { 426 | int len = sdslen(s), j; 427 | 428 | for (j = 0; j < len; j++) s[j] = toupper(s[j]); 429 | } 430 | 431 | /* Compare two sds strings s1 and s2 with memcmp(). 432 | * 433 | * Return value: 434 | * 435 | * 1 if s1 > s2. 436 | * -1 if s1 < s2. 437 | * 0 if s1 and s2 are exactly the same binary string. 438 | * 439 | * If two strings share exactly the same prefix, but one of the two has 440 | * additional characters, the longer string is considered to be greater than 441 | * the smaller one. */ 442 | int sdscmp(const sds s1, const sds s2) { 443 | size_t l1, l2, minlen; 444 | int cmp; 445 | 446 | l1 = sdslen(s1); 447 | l2 = sdslen(s2); 448 | minlen = (l1 < l2) ? l1 : l2; 449 | cmp = memcmp(s1,s2,minlen); 450 | if (cmp == 0) return l1-l2; 451 | return cmp; 452 | } 453 | 454 | /* Split 's' with separator in 'sep'. An array 455 | * of sds strings is returned. *count will be set 456 | * by reference to the number of tokens returned. 457 | * 458 | * On out of memory, zero length string, zero length 459 | * separator, NULL is returned. 460 | * 461 | * Note that 'sep' is able to split a string using 462 | * a multi-character separator. For example 463 | * sdssplit("foo_-_bar","_-_"); will return two 464 | * elements "foo" and "bar". 465 | * 466 | * This version of the function is binary-safe but 467 | * requires length arguments. sdssplit() is just the 468 | * same function but for zero-terminated strings. 469 | */ 470 | sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { 471 | int elements = 0, slots = 5, start = 0, j; 472 | sds *tokens; 473 | 474 | if (seplen < 1 || len < 0) return NULL; 475 | 476 | tokens = malloc(sizeof(sds)*slots); 477 | if (tokens == NULL) return NULL; 478 | 479 | if (len == 0) { 480 | *count = 0; 481 | return tokens; 482 | } 483 | for (j = 0; j < (len-(seplen-1)); j++) { 484 | /* make sure there is room for the next element and the final one */ 485 | if (slots < elements+2) { 486 | sds *newtokens; 487 | 488 | slots *= 2; 489 | newtokens = realloc(tokens,sizeof(sds)*slots); 490 | if (newtokens == NULL) goto cleanup; 491 | tokens = newtokens; 492 | } 493 | /* search the separator */ 494 | if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { 495 | tokens[elements] = sdsnewlen(s+start,j-start); 496 | if (tokens[elements] == NULL) goto cleanup; 497 | elements++; 498 | start = j+seplen; 499 | j = j+seplen-1; /* skip the separator */ 500 | } 501 | } 502 | /* Add the final element. We are sure there is room in the tokens array. */ 503 | tokens[elements] = sdsnewlen(s+start,len-start); 504 | if (tokens[elements] == NULL) goto cleanup; 505 | elements++; 506 | *count = elements; 507 | return tokens; 508 | 509 | cleanup: 510 | { 511 | int i; 512 | for (i = 0; i < elements; i++) sdsfree(tokens[i]); 513 | free(tokens); 514 | *count = 0; 515 | return NULL; 516 | } 517 | } 518 | 519 | /* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */ 520 | void sdsfreesplitres(sds *tokens, int count) { 521 | if (!tokens) return; 522 | while(count--) 523 | sdsfree(tokens[count]); 524 | free(tokens); 525 | } 526 | 527 | /* Create an sds string from a long long value. It is much faster than: 528 | * 529 | * sdscatprintf(sdsempty(),"%lld\n", value); 530 | */ 531 | sds sdsfromlonglong(long long value) { 532 | char buf[32], *p; 533 | unsigned long long v; 534 | 535 | v = (value < 0) ? -value : value; 536 | p = buf+31; /* point to the last character */ 537 | do { 538 | *p-- = '0'+(v%10); 539 | v /= 10; 540 | } while(v); 541 | if (value < 0) *p-- = '-'; 542 | p++; 543 | return sdsnewlen(p,32-(p-buf)); 544 | } 545 | 546 | /* Append to the sds string "s" an escaped string representation where 547 | * all the non-printable characters (tested with isprint()) are turned into 548 | * escapes in the form "\n\r\a...." or "\x". 549 | * 550 | * After the call, the modified sds string is no longer valid and all the 551 | * references must be substituted with the new pointer returned by the call. */ 552 | sds sdscatrepr(sds s, const char *p, size_t len) { 553 | s = sdscatlen(s,"\"",1); 554 | while(len--) { 555 | switch(*p) { 556 | case '\\': 557 | case '"': 558 | s = sdscatprintf(s,"\\%c",*p); 559 | break; 560 | case '\n': s = sdscatlen(s,"\\n",2); break; 561 | case '\r': s = sdscatlen(s,"\\r",2); break; 562 | case '\t': s = sdscatlen(s,"\\t",2); break; 563 | case '\a': s = sdscatlen(s,"\\a",2); break; 564 | case '\b': s = sdscatlen(s,"\\b",2); break; 565 | default: 566 | if (isprint(*p)) 567 | s = sdscatprintf(s,"%c",*p); 568 | else 569 | s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); 570 | break; 571 | } 572 | p++; 573 | } 574 | return sdscatlen(s,"\"",1); 575 | } 576 | 577 | /* Helper function for sdssplitargs() that returns non zero if 'c' 578 | * is a valid hex digit. */ 579 | int is_hex_digit(char c) { 580 | return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || 581 | (c >= 'A' && c <= 'F'); 582 | } 583 | 584 | /* Helper function for sdssplitargs() that converts a hex digit into an 585 | * integer from 0 to 15 */ 586 | int hex_digit_to_int(char c) { 587 | switch(c) { 588 | case '0': return 0; 589 | case '1': return 1; 590 | case '2': return 2; 591 | case '3': return 3; 592 | case '4': return 4; 593 | case '5': return 5; 594 | case '6': return 6; 595 | case '7': return 7; 596 | case '8': return 8; 597 | case '9': return 9; 598 | case 'a': case 'A': return 10; 599 | case 'b': case 'B': return 11; 600 | case 'c': case 'C': return 12; 601 | case 'd': case 'D': return 13; 602 | case 'e': case 'E': return 14; 603 | case 'f': case 'F': return 15; 604 | default: return 0; 605 | } 606 | } 607 | 608 | /* Split a line into arguments, where every argument can be in the 609 | * following programming-language REPL-alike form: 610 | * 611 | * foo bar "newline are supported\n" and "\xff\x00otherstuff" 612 | * 613 | * The number of arguments is stored into *argc, and an array 614 | * of sds is returned. 615 | * 616 | * The caller should free the resulting array of sds strings with 617 | * sdsfreesplitres(). 618 | * 619 | * Note that sdscatrepr() is able to convert back a string into 620 | * a quoted string in the same format sdssplitargs() is able to parse. 621 | * 622 | * The function returns the allocated tokens on success, even when the 623 | * input string is empty, or NULL if the input contains unbalanced 624 | * quotes or closed quotes followed by non space characters 625 | * as in: "foo"bar or "foo' 626 | */ 627 | sds *sdssplitargs(const char *line, int *argc) { 628 | const char *p = line; 629 | char *current = NULL; 630 | char **vector = NULL; 631 | 632 | *argc = 0; 633 | while(1) { 634 | /* skip blanks */ 635 | while(*p && isspace(*p)) p++; 636 | if (*p) { 637 | /* get a token */ 638 | int inq=0; /* set to 1 if we are in "quotes" */ 639 | int insq=0; /* set to 1 if we are in 'single quotes' */ 640 | int done=0; 641 | 642 | if (current == NULL) current = sdsempty(); 643 | while(!done) { 644 | if (inq) { 645 | if (*p == '\\' && *(p+1) == 'x' && 646 | is_hex_digit(*(p+2)) && 647 | is_hex_digit(*(p+3))) 648 | { 649 | unsigned char byte; 650 | 651 | byte = (hex_digit_to_int(*(p+2))*16)+ 652 | hex_digit_to_int(*(p+3)); 653 | current = sdscatlen(current,(char*)&byte,1); 654 | p += 3; 655 | } else if (*p == '\\' && *(p+1)) { 656 | char c; 657 | 658 | p++; 659 | switch(*p) { 660 | case 'n': c = '\n'; break; 661 | case 'r': c = '\r'; break; 662 | case 't': c = '\t'; break; 663 | case 'b': c = '\b'; break; 664 | case 'a': c = '\a'; break; 665 | default: c = *p; break; 666 | } 667 | current = sdscatlen(current,&c,1); 668 | } else if (*p == '"') { 669 | /* closing quote must be followed by a space or 670 | * nothing at all. */ 671 | if (*(p+1) && !isspace(*(p+1))) goto err; 672 | done=1; 673 | } else if (!*p) { 674 | /* unterminated quotes */ 675 | goto err; 676 | } else { 677 | current = sdscatlen(current,p,1); 678 | } 679 | } else if (insq) { 680 | if (*p == '\\' && *(p+1) == '\'') { 681 | p++; 682 | current = sdscatlen(current,"'",1); 683 | } else if (*p == '\'') { 684 | /* closing quote must be followed by a space or 685 | * nothing at all. */ 686 | if (*(p+1) && !isspace(*(p+1))) goto err; 687 | done=1; 688 | } else if (!*p) { 689 | /* unterminated quotes */ 690 | goto err; 691 | } else { 692 | current = sdscatlen(current,p,1); 693 | } 694 | } else { 695 | switch(*p) { 696 | case ' ': 697 | case '\n': 698 | case '\r': 699 | case '\t': 700 | case '\0': 701 | done=1; 702 | break; 703 | case '"': 704 | inq=1; 705 | break; 706 | case '\'': 707 | insq=1; 708 | break; 709 | default: 710 | current = sdscatlen(current,p,1); 711 | break; 712 | } 713 | } 714 | if (*p) p++; 715 | } 716 | /* add the token to the vector */ 717 | vector = realloc(vector,((*argc)+1)*sizeof(char*)); 718 | vector[*argc] = current; 719 | (*argc)++; 720 | current = NULL; 721 | } else { 722 | /* Even on empty input string return something not NULL. */ 723 | if (vector == NULL) vector = malloc(sizeof(void*)); 724 | return vector; 725 | } 726 | } 727 | 728 | err: 729 | while((*argc)--) 730 | sdsfree(vector[*argc]); 731 | free(vector); 732 | if (current) sdsfree(current); 733 | *argc = 0; 734 | return NULL; 735 | } 736 | 737 | /* Modify the string substituting all the occurrences of the set of 738 | * characters specified in the 'from' string to the corresponding character 739 | * in the 'to' array. 740 | * 741 | * For instance: sdsmapchars(mystring, "ho", "01", 2) 742 | * will have the effect of turning the string "hello" into "0ell1". 743 | * 744 | * The function returns the sds string pointer, that is always the same 745 | * as the input pointer since no resize is needed. */ 746 | sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) { 747 | size_t j, i, l = sdslen(s); 748 | 749 | for (j = 0; j < l; j++) { 750 | for (i = 0; i < setlen; i++) { 751 | if (s[j] == from[i]) { 752 | s[j] = to[i]; 753 | break; 754 | } 755 | } 756 | } 757 | return s; 758 | } 759 | 760 | /* Join an array of C strings using the specified separator (also a C string). 761 | * Returns the result as an sds string. */ 762 | sds sdsjoin(char **argv, int argc, char *sep, size_t seplen) { 763 | sds join = sdsempty(); 764 | int j; 765 | 766 | for (j = 0; j < argc; j++) { 767 | join = sdscat(join, argv[j]); 768 | if (j != argc-1) join = sdscatlen(join,sep,seplen); 769 | } 770 | return join; 771 | } 772 | 773 | /* Like sdsjoin, but joins an array of SDS strings. */ 774 | sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) { 775 | sds join = sdsempty(); 776 | int j; 777 | 778 | for (j = 0; j < argc; j++) { 779 | join = sdscatsds(join, argv[j]); 780 | if (j != argc-1) join = sdscatlen(join,sep,seplen); 781 | } 782 | return join; 783 | } 784 | 785 | #ifdef SDS_TEST_MAIN 786 | #include 787 | #include "testhelp.h" 788 | 789 | int main(void) { 790 | { 791 | struct sdshdr *sh; 792 | sds x = sdsnew("foo"), y; 793 | 794 | test_cond("Create a string and obtain the length", 795 | sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) 796 | 797 | sdsfree(x); 798 | x = sdsnewlen("foo",2); 799 | test_cond("Create a string with specified length", 800 | sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) 801 | 802 | x = sdscat(x,"bar"); 803 | test_cond("Strings concatenation", 804 | sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); 805 | 806 | x = sdscpy(x,"a"); 807 | test_cond("sdscpy() against an originally longer string", 808 | sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) 809 | 810 | x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); 811 | test_cond("sdscpy() against an originally shorter string", 812 | sdslen(x) == 33 && 813 | memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) 814 | 815 | sdsfree(x); 816 | x = sdscatprintf(sdsempty(),"%d",123); 817 | test_cond("sdscatprintf() seems working in the base case", 818 | sdslen(x) == 3 && memcmp(x,"123\0",4) ==0) 819 | 820 | sdsfree(x); 821 | x = sdsnew("xxciaoyyy"); 822 | sdstrim(x,"xy"); 823 | test_cond("sdstrim() correctly trims characters", 824 | sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) 825 | 826 | y = sdsdup(x); 827 | sdsrange(y,1,1); 828 | test_cond("sdsrange(...,1,1)", 829 | sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) 830 | 831 | sdsfree(y); 832 | y = sdsdup(x); 833 | sdsrange(y,1,-1); 834 | test_cond("sdsrange(...,1,-1)", 835 | sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) 836 | 837 | sdsfree(y); 838 | y = sdsdup(x); 839 | sdsrange(y,-2,-1); 840 | test_cond("sdsrange(...,-2,-1)", 841 | sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) 842 | 843 | sdsfree(y); 844 | y = sdsdup(x); 845 | sdsrange(y,2,1); 846 | test_cond("sdsrange(...,2,1)", 847 | sdslen(y) == 0 && memcmp(y,"\0",1) == 0) 848 | 849 | sdsfree(y); 850 | y = sdsdup(x); 851 | sdsrange(y,1,100); 852 | test_cond("sdsrange(...,1,100)", 853 | sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) 854 | 855 | sdsfree(y); 856 | y = sdsdup(x); 857 | sdsrange(y,100,100); 858 | test_cond("sdsrange(...,100,100)", 859 | sdslen(y) == 0 && memcmp(y,"\0",1) == 0) 860 | 861 | sdsfree(y); 862 | sdsfree(x); 863 | x = sdsnew("foo"); 864 | y = sdsnew("foa"); 865 | test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0) 866 | 867 | sdsfree(y); 868 | sdsfree(x); 869 | x = sdsnew("bar"); 870 | y = sdsnew("bar"); 871 | test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0) 872 | 873 | sdsfree(y); 874 | sdsfree(x); 875 | x = sdsnew("aar"); 876 | y = sdsnew("bar"); 877 | test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0) 878 | 879 | sdsfree(y); 880 | sdsfree(x); 881 | x = sdsnewlen("\a\n\0foo\r",7); 882 | y = sdscatrepr(sdsempty(),x,sdslen(x)); 883 | test_cond("sdscatrepr(...data...)", 884 | memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) 885 | 886 | { 887 | int oldfree; 888 | 889 | sdsfree(x); 890 | x = sdsnew("0"); 891 | sh = (void*) (x-(sizeof(struct sdshdr))); 892 | test_cond("sdsnew() free/len buffers", sh->len == 1 && sh->free == 0); 893 | x = sdsMakeRoomFor(x,1); 894 | sh = (void*) (x-(sizeof(struct sdshdr))); 895 | test_cond("sdsMakeRoomFor()", sh->len == 1 && sh->free > 0); 896 | oldfree = sh->free; 897 | x[1] = '1'; 898 | sdsIncrLen(x,1); 899 | test_cond("sdsIncrLen() -- content", x[0] == '0' && x[1] == '1'); 900 | test_cond("sdsIncrLen() -- len", sh->len == 2); 901 | test_cond("sdsIncrLen() -- free", sh->free == oldfree-1); 902 | } 903 | } 904 | test_report() 905 | return 0; 906 | } 907 | #endif 908 | -------------------------------------------------------------------------------- /src/sds.h: -------------------------------------------------------------------------------- 1 | /* SDS (Simple Dynamic Strings), A C dynamic strings library. 2 | * 3 | * Copyright (c) 2006-2014, Salvatore Sanfilippo 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef __SDS_H 32 | #define __SDS_H 33 | 34 | #define SDS_MAX_PREALLOC (1024*1024) 35 | 36 | #include 37 | #include 38 | 39 | typedef char *sds; 40 | 41 | struct sdshdr { 42 | int len; 43 | int free; 44 | char buf[]; 45 | }; 46 | 47 | static inline size_t sdslen(const sds s) { 48 | struct sdshdr *sh = (void*)(s-sizeof *sh); 49 | return sh->len; 50 | } 51 | 52 | static inline size_t sdsavail(const sds s) { 53 | struct sdshdr *sh = (void*)(s-sizeof *sh); 54 | return sh->free; 55 | } 56 | 57 | sds sdsnewlen(const void *init, size_t initlen); 58 | sds sdsnew(const char *init); 59 | sds sdsempty(void); 60 | size_t sdslen(const sds s); 61 | sds sdsdup(const sds s); 62 | void sdsfree(sds s); 63 | size_t sdsavail(const sds s); 64 | sds sdsgrowzero(sds s, size_t len); 65 | sds sdscatlen(sds s, const void *t, size_t len); 66 | sds sdscat(sds s, const char *t); 67 | sds sdscatsds(sds s, const sds t); 68 | sds sdscpylen(sds s, const char *t, size_t len); 69 | sds sdscpy(sds s, const char *t); 70 | 71 | sds sdscatvprintf(sds s, const char *fmt, va_list ap); 72 | #ifdef __GNUC__ 73 | sds sdscatprintf(sds s, const char *fmt, ...) 74 | __attribute__((format(printf, 2, 3))); 75 | #else 76 | sds sdscatprintf(sds s, const char *fmt, ...); 77 | #endif 78 | 79 | void sdstrim(sds s, const char *cset); 80 | void sdsrange(sds s, int start, int end); 81 | void sdsupdatelen(sds s); 82 | void sdsclear(sds s); 83 | int sdscmp(const sds s1, const sds s2); 84 | sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); 85 | void sdsfreesplitres(sds *tokens, int count); 86 | void sdstolower(sds s); 87 | void sdstoupper(sds s); 88 | sds sdsfromlonglong(long long value); 89 | sds sdscatrepr(sds s, const char *p, size_t len); 90 | sds *sdssplitargs(const char *line, int *argc); 91 | sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); 92 | sds sdsjoin(char **argv, int argc, char *sep, size_t seplen); 93 | sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); 94 | 95 | /* Low level functions exposed to the user API */ 96 | sds sdsMakeRoomFor(sds s, size_t addlen); 97 | void sdsIncrLen(sds s, int incr); 98 | sds sdsRemoveFreeSpace(sds s); 99 | size_t sdsAllocSize(sds s); 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /src/shuffle.c: -------------------------------------------------------------------------------- 1 | #include "shuffle.h" 2 | 3 | int rrand(int m) { 4 | return (int)((double)m * ( rand() / (RAND_MAX+1.0) )); 5 | } 6 | 7 | #define BYTE(X) ((unsigned char *)(X)) 8 | void shuffle(void *obj, size_t nmemb, size_t size) { 9 | 10 | // random seed 11 | srand(time(NULL)); 12 | 13 | void *temp = malloc(size); 14 | size_t n = nmemb; 15 | while ( n > 1 ) { 16 | size_t k = rrand(n--); 17 | memcpy(temp, BYTE(obj) + n*size, size); 18 | memcpy(BYTE(obj) + n*size, BYTE(obj) + k*size, size); 19 | memcpy(BYTE(obj) + k*size, temp, size); 20 | } 21 | free(temp); 22 | } 23 | -------------------------------------------------------------------------------- /src/shuffle.h: -------------------------------------------------------------------------------- 1 | #include "defs.h" 2 | 3 | int rrand(int m); 4 | void shuffle(void *obj, size_t nmemb, size_t size); 5 | -------------------------------------------------------------------------------- /src/tetris.c: -------------------------------------------------------------------------------- 1 | #include "tetris.h" 2 | 3 | 4 | void draw_playing_field() { 5 | 6 | // loop var 7 | int i; 8 | 9 | // Set rendering clear color 10 | // This sets the 'background color' 11 | SDL_SetRenderDrawColor(render, 204, 192, 179, 255); 12 | 13 | // Clear the render 14 | // 'set' background color defined in SDL_SetRenderDrawColor(...) 15 | SDL_RenderClear(render); 16 | 17 | 18 | i = PLAYFIELD_HEIGHT * PLAYFIELD_WIDTH; 19 | while (i --> 0) 20 | set_playfield(i % PLAYFIELD_WIDTH, i / PLAYFIELD_WIDTH, playfield[i]); 21 | 22 | 23 | // Update the screen 24 | setRenderChanged(); 25 | } 26 | 27 | Uint32 auto_drop_timer(Uint32 interval, void *param) { 28 | 29 | SDL_Event event; 30 | SDL_UserEvent userevent; 31 | 32 | userevent.type = SDL_USEREVENT; 33 | userevent.code = 0; 34 | userevent.data1 = NULL; 35 | userevent.data2 = NULL; 36 | 37 | event.type = SDL_USEREVENT; 38 | event.user = userevent; 39 | 40 | SDL_PushEvent(&event); 41 | return interval; 42 | } 43 | 44 | void initTetris() { 45 | 46 | // set up SDL timer 47 | if(cb_timer != 0) { 48 | SDL_RemoveTimer(cb_timer); 49 | } 50 | cb_timer = 0; 51 | 52 | TETROMINO_ACTION = NONE; 53 | 54 | // Empty the playfield 55 | int i = PLAYFIELD_HEIGHT * PLAYFIELD_WIDTH; 56 | while (i --> 0) { 57 | playfield[i] = EMPTY; 58 | } 59 | 60 | // build tetromino queue 61 | current_queue_index = 0; 62 | i = tetromino_queue_size; 63 | int n = 0; 64 | while(i --> 0) { 65 | if((i + 1) % 4 == 0) { 66 | n++; 67 | } 68 | tetromino_queue[i] = n; 69 | } 70 | 71 | // apply shuffle algorithm 72 | shuffle(tetromino_queue, tetromino_queue_size, sizeof(uint8_t)); 73 | 74 | draw_playing_field(); 75 | 76 | spawn_tetromino(); 77 | 78 | } 79 | 80 | void lockTetromino() { 81 | 82 | lock_delay_count = 0; 83 | 84 | // lock tetromino in place 85 | int i = 4; 86 | while(i --> 0) { 87 | uint8_t x_coord = i * 2; 88 | uint8_t y_coord = x_coord + 1; 89 | 90 | uint8_t _x = CURRENT_TETROMINO_COORDS[x_coord]; 91 | uint8_t _y = CURRENT_TETROMINO_COORDS[y_coord]; 92 | 93 | CURRENT_TETROMINO_COORDS[x_coord] = 0; 94 | CURRENT_TETROMINO_COORDS[y_coord] = 0; 95 | 96 | set_playfield(_x, _y, CURRENT_TETROMINO.type.color); 97 | } 98 | 99 | // clear lines if any 100 | uint8_t row = PLAYFIELD_HEIGHT; 101 | int8_t row_to_copy_to = -1; 102 | 103 | uint8_t completed_lines = 0; 104 | 105 | while(row --> 0) { 106 | uint8_t col; 107 | bool complete_line = true; 108 | 109 | // check if line is complete 110 | for(col = 0; col < PLAYFIELD_WIDTH; col++) { 111 | if(get_playfield(col, row) == EMPTY) { 112 | 113 | complete_line = false; 114 | break; 115 | } 116 | } 117 | 118 | // clear line 119 | if(complete_line) { 120 | 121 | completed_lines++; 122 | 123 | if(row_to_copy_to < row) { 124 | row_to_copy_to = row; 125 | } 126 | 127 | for(col = 0; col < PLAYFIELD_WIDTH; col++) { 128 | set_playfield(col, row, EMPTY); 129 | } 130 | 131 | } else if(row_to_copy_to > row) { 132 | 133 | for(col = 0; col < PLAYFIELD_WIDTH; col++) { 134 | set_playfield(col, row_to_copy_to, get_playfield(col, row)); 135 | } 136 | 137 | row_to_copy_to--; 138 | } 139 | 140 | } 141 | 142 | // update score 143 | 144 | if(completed_lines > 0) { 145 | // tetris 146 | score += completed_lines/4 * 800; 147 | completed_lines = completed_lines % 4; 148 | 149 | // triple 150 | score += completed_lines/3 * 500; 151 | completed_lines = completed_lines % 3; 152 | 153 | // double 154 | score += completed_lines/2 * 300; 155 | completed_lines = completed_lines % 2; 156 | 157 | // single 158 | score += completed_lines * 100; 159 | } 160 | 161 | 162 | spawn_tetromino(); 163 | 164 | } 165 | 166 | void render_score() { 167 | // Show tetris score after all tetris operations are finished 168 | SDL_Color textColor = { 0x11, 0x1F, 0x3F }; 169 | 170 | sds string_score = printfcomma(score); 171 | 172 | SDL_Surface* textSurface = TTF_RenderText_Blended(gFont, string_score, textColor); 173 | 174 | sdsfree(string_score); 175 | 176 | if (textSurface == NULL) { 177 | fprintf(stderr, 178 | "\nTTF_RenderText_Solid Error: %s\n", 179 | SDL_GetError()); 180 | exit(1); 181 | } 182 | 183 | SDL_Texture* mTexture = SDL_CreateTextureFromSurface(render, textSurface); 184 | 185 | if (mTexture == NULL) { 186 | fprintf(stderr, 187 | "\nSDL_CreateTextureFromSurface Error: %s\n", 188 | SDL_GetError()); 189 | exit(1); 190 | } 191 | 192 | int mWidth = textSurface->w; 193 | int mHeight = textSurface->h; 194 | 195 | // render text 196 | SDL_Rect renderQuad = { WINDOW_WIDTH - mWidth - 10, 10, mWidth, mHeight }; 197 | 198 | SDL_RenderCopy(render, mTexture, NULL, &renderQuad); 199 | 200 | SDL_DestroyTexture(mTexture); 201 | SDL_FreeSurface(textSurface); 202 | } 203 | 204 | void updateTetris() { 205 | 206 | if (cb_timer == 0) { 207 | cb_timer = SDL_AddTimer(500, auto_drop_timer, NULL); 208 | } 209 | 210 | // draw the scoreboard as needed 211 | int i = 4; 212 | bool on_score_area = false; 213 | while(i --> 0) { 214 | uint8_t x_coord = i * 2; 215 | uint8_t y_coord = x_coord + 1; 216 | 217 | // uint8_t _x = CURRENT_TETROMINO_COORDS[x_coord]; 218 | uint8_t _y = CURRENT_TETROMINO_COORDS[y_coord]; 219 | 220 | // if a tetromino is within the top 3 rows of the playing field, redraw 221 | // that area of the playing field. 222 | // the third row is considered when the tetromino leaves the score area, 223 | // it will clear the previous position. 224 | if(_y <= 2) { 225 | on_score_area = true; 226 | break; 227 | } 228 | } 229 | 230 | if(on_score_area) { 231 | 232 | // re-draw playfield area where score is located in 233 | int n = PLAYFIELD_WIDTH * 2 - 1; 234 | while(n --> 0) { 235 | int x = n % PLAYFIELD_WIDTH; 236 | int y = n / PLAYFIELD_WIDTH; 237 | 238 | set_playfield(x, y, get_playfield(x, y)); 239 | } 240 | 241 | draw_playing_field(); 242 | 243 | // re-draw tetromino 244 | render_current_tetromino(CURRENT_TETROMINO); 245 | 246 | render_score(); 247 | } 248 | 249 | 250 | 251 | Tetromino_Movement request = CURRENT_TETROMINO; 252 | 253 | // action from keyboard 254 | switch(TETROMINO_ACTION) { 255 | case NONE: 256 | // do nothing - don't bother redrawing 257 | break; 258 | 259 | case ROTATE: 260 | request.rotation = (request.rotation + 1) % 4; 261 | render_current_tetromino(request); 262 | break; 263 | 264 | case LEFT: 265 | request.x -= 1; 266 | render_current_tetromino(request); 267 | break; 268 | 269 | case RIGHT: 270 | request.x += 1; 271 | render_current_tetromino(request); 272 | 273 | break; 274 | 275 | case DROP: 276 | 277 | request.y += 1; 278 | while(render_current_tetromino(request)) 279 | request.y += 1; 280 | 281 | lockTetromino(); 282 | 283 | break; 284 | 285 | case DOWN: 286 | request.y += 1; 287 | if(!render_current_tetromino(request)) { 288 | lock_delay_count++; 289 | } else { 290 | lock_delay_count = 0; 291 | } 292 | break; 293 | 294 | case RESTART: 295 | initTetris(); 296 | break; 297 | 298 | case AUTO_DROP: 299 | 300 | request.y += 1; 301 | if (!render_current_tetromino(request)) { 302 | lock_delay_count++; 303 | } else { 304 | lock_delay_count = 0; 305 | } 306 | 307 | if (lock_delay_count >= lock_delay_threshold) { 308 | lockTetromino(); 309 | } 310 | 311 | break; 312 | } 313 | TETROMINO_ACTION = NONE; 314 | 315 | } 316 | 317 | void spawn_tetromino() { 318 | 319 | current_queue_index++; 320 | if(current_queue_index >= tetromino_queue_size) { 321 | current_queue_index = 0; 322 | 323 | // apply shuffle algorithm 324 | shuffle(tetromino_queue, tetromino_queue_size, sizeof(uint8_t)); 325 | } 326 | 327 | Tetromino type; 328 | 329 | switch(tetromino_queue[current_queue_index]) { 330 | case 1: 331 | type = TETRA_I; 332 | break; 333 | case 2: 334 | type = TETRA_J; 335 | break; 336 | case 3: 337 | type = TETRA_L; 338 | break; 339 | case 4: 340 | type = TETRA_O; 341 | break; 342 | case 5: 343 | type = TETRA_S; 344 | break; 345 | case 6: 346 | type = TETRA_T; 347 | break; 348 | case 7: 349 | type = TETRA_Z; 350 | break; 351 | } 352 | 353 | Tetromino_Movement tetra_request = { 354 | type, 355 | 0, 356 | 3, 0 357 | }; 358 | 359 | if(!render_current_tetromino(tetra_request)) { 360 | 361 | // Reset the game 362 | initTetris(); 363 | } 364 | } 365 | 366 | bool can_render_tetromino(Tetromino_Movement tetra_request, uint8_t block_render_queue[]) { 367 | uint16_t bit, piece; 368 | uint8_t row = 0, col = 0; 369 | 370 | piece = tetra_request.type.rotation[tetra_request.rotation]; 371 | uint8_t x = tetra_request.x; 372 | uint8_t y = tetra_request.y; 373 | 374 | 375 | // loop through tetramino data 376 | int i = 0; 377 | for (bit = 0x8000; bit > 0 && i < 8; bit = bit >> 1) { 378 | 379 | if (piece & bit) { 380 | 381 | uint8_t _x = x + col; 382 | uint8_t _y = y + row; 383 | 384 | // bounds check 385 | if ((_x < 0) || (_x >= PLAYFIELD_WIDTH) 386 | || (_y < 0) || (_y >= PLAYFIELD_HEIGHT) 387 | || get_playfield(_x, _y) != EMPTY) { 388 | 389 | // unable to render tetramino block 390 | return false; 391 | break; 392 | } else { 393 | 394 | if(block_render_queue != NULL) { 395 | block_render_queue[i * 2] = _x; 396 | block_render_queue[i * 2 + 1] = _y; 397 | } 398 | 399 | i++; 400 | } 401 | } 402 | 403 | // cycle col between 0 to 3 404 | // if col is 0 then increment row 405 | col++; 406 | col = col % 4; 407 | if(col == 0) { 408 | row++; 409 | } 410 | 411 | } 412 | 413 | return true; 414 | 415 | } 416 | 417 | bool render_current_tetromino(Tetromino_Movement tetra_request) { 418 | 419 | // create ghost 420 | Tetromino ghost = tetra_request.type; 421 | 422 | // change alpha to ~50% 423 | ghost.color = ghost.color & 0x00FFFFFF; 424 | ghost.color = ghost.color | 0x66000000; 425 | 426 | Tetromino_Movement ghost_request = tetra_request; 427 | ghost_request.type = ghost; 428 | 429 | // render ghost tetromino 430 | while(render_tetromino(ghost_request, GHOST_TETROMINO_COORDS)) 431 | ghost_request.y += 1; 432 | 433 | // change alpha to 90% 434 | tetra_request.type.color = tetra_request.type.color & 0x00FFFFFF; 435 | tetra_request.type.color = tetra_request.type.color | 0xE5000000; 436 | 437 | if(render_tetromino(tetra_request, CURRENT_TETROMINO_COORDS)) { 438 | CURRENT_TETROMINO = tetra_request; 439 | 440 | return true; 441 | } 442 | 443 | return false; 444 | } 445 | 446 | // render tetromino movement request 447 | // returns true if tetromino is rendered succesfully; false otherwise 448 | bool render_tetromino(Tetromino_Movement tetra_request, uint8_t current_coords[]) { 449 | 450 | // simple 'queue' to store coords of blocks to render on playing field. 451 | // Each tetromino has 4 blocks with total of 4 coordinates. 452 | // 453 | // To access a coord, if 0 <= i < 4, then 454 | // x = i * 2, y = x + 1 455 | // 456 | uint8_t block_render_queue[8] = {0}; 457 | 458 | if(!can_render_tetromino(tetra_request, block_render_queue)) 459 | return false; 460 | 461 | // clear old tetromino position 462 | int i = 4; 463 | while(i --> 0) { 464 | uint8_t x_coord = i * 2; 465 | uint8_t y_coord = x_coord + 1; 466 | 467 | uint8_t _x = current_coords[x_coord]; 468 | uint8_t _y = current_coords[y_coord]; 469 | 470 | draw_block(_x, _y, EMPTY); 471 | } 472 | 473 | 474 | // render new tetromino blocks 475 | i = 4; 476 | while(i --> 0) { 477 | 478 | uint8_t x_coord = i * 2; 479 | uint8_t y_coord = x_coord + 1; 480 | 481 | // store and draw new tetromino position 482 | uint8_t _x = block_render_queue[x_coord]; 483 | uint8_t _y = block_render_queue[y_coord]; 484 | 485 | current_coords[x_coord] = _x; 486 | current_coords[y_coord] = _y; 487 | 488 | draw_block(_x, _y, tetra_request.type.color); 489 | 490 | } 491 | 492 | return true; 493 | } 494 | 495 | Color_Block get_playfield(uint8_t x, uint8_t y) { 496 | return playfield[(y * PLAYFIELD_WIDTH) + x]; 497 | } 498 | 499 | void set_playfield(uint8_t x, uint8_t y, Color_Block color) { 500 | playfield[(y * PLAYFIELD_WIDTH) + x] = color; 501 | 502 | draw_block(x, y, color); 503 | } 504 | -------------------------------------------------------------------------------- /src/tetris.h: -------------------------------------------------------------------------------- 1 | #include "defs.h" 2 | #include "utility.h" 3 | #include "shuffle.h" 4 | #include "graphics.h" 5 | 6 | 7 | #ifndef _TETRIS_CONSTANTS 8 | #define _TETRIS_CONSTANTS 9 | 10 | typedef struct { 11 | 12 | // an array of rotation schemes of a tetromino. 13 | // each rotation scheme is represented as 16 bits which form 4x4 matrix. 14 | // row-major order convention is used to interpret this matrix. 15 | uint16_t rotation[4]; 16 | 17 | // RGBA convention: 0xAABBGGRR 18 | uint32_t color; 19 | 20 | } Tetromino; 21 | 22 | typedef struct { 23 | 24 | Tetromino type; 25 | 26 | // expected values from 0 to 4 which are the indices of Tetromino.rotation 27 | uint8_t rotation; 28 | 29 | uint8_t x; 30 | uint8_t y; 31 | 32 | } Tetromino_Movement; 33 | 34 | typedef enum { 35 | NONE, 36 | DOWN, 37 | LEFT, 38 | RIGHT, 39 | DROP, 40 | ROTATE, 41 | // soft-drop tetrominos 42 | AUTO_DROP, 43 | RESTART 44 | } Tetris_Action; 45 | 46 | typedef enum { 47 | EMPTY = 0xFFB3C0CC, 48 | TEAL = 0xFFFFDB7F, 49 | BLUE = 0xFFD97400, 50 | ORANGE = 0XFF1B85FF, 51 | YELLOW = 0xFF00DCFF, 52 | GREEN = 0xFF40CC2E, 53 | PURPLE = 0xFF4B1485, 54 | RED = 0xFF4B59F2 55 | 56 | } Color_Block; 57 | 58 | // default tetris action 59 | // defines the action to apply to current tetromino 60 | extern Tetris_Action TETROMINO_ACTION; 61 | 62 | // tetromino data 63 | const static Tetromino TETRA_I = { 64 | {0x0F00, 0x2222, 0x00F0, 0x4444}, 65 | TEAL 66 | }; 67 | 68 | const static Tetromino TETRA_J = { 69 | {0x8E00, 0x6440, 0x0E20, 0x44C0}, 70 | BLUE 71 | }; 72 | 73 | const static Tetromino TETRA_L = { 74 | {0x2E00, 0x4460, 0x0E80, 0xC440}, 75 | ORANGE 76 | }; 77 | 78 | const static Tetromino TETRA_O = { 79 | {0x6600, 0x6600, 0x6600, 0x6600}, 80 | YELLOW 81 | }; 82 | 83 | const static Tetromino TETRA_S = { 84 | {0x6C00, 0x4620, 0x06C0, 0x8c40}, 85 | GREEN 86 | }; 87 | 88 | const static Tetromino TETRA_T = { 89 | {0x4E00, 0x4640, 0x0E40, 0x4C40}, 90 | PURPLE 91 | }; 92 | 93 | const static Tetromino TETRA_Z = { 94 | {0xC600, 0x2640, 0x0C60, 0x4C80}, 95 | RED 96 | }; 97 | 98 | // simple array to store coords of blocks rendered on playing field. 99 | // Each tetromino has 4 blocks with total of 4 coordinates. 100 | // 101 | // To access a coord, if 0 <= i < 4, then 102 | // x = i * 2, y = x + 1 103 | // 104 | static uint8_t CURRENT_TETROMINO_COORDS[8] = {0}; 105 | static uint8_t GHOST_TETROMINO_COORDS[8] = {0}; 106 | 107 | static Tetromino_Movement CURRENT_TETROMINO; 108 | 109 | 110 | // bool array of the playfield. 111 | // Use row-major order convention to access (x,y) coord. 112 | // Origin is 'top-left' -- like matrices. 113 | // Zero-based indexing. 114 | static Color_Block playfield[PLAYFIELD_HEIGHT * PLAYFIELD_WIDTH]; 115 | 116 | 117 | // Every time AUTO_DROP event is executed, the current tetromino will drop by one 118 | // block. If the drop is unsucessful equal to the number of times of lock_delay_threshold, 119 | // the tetromino freezes in place. 120 | // 121 | // Lock when ++lock_delay_count % lock_delay_threshold == 0 122 | const static uint8_t lock_delay_threshold = 2; 123 | static uint8_t lock_delay_count = 0; 124 | 125 | // Queue to determine the next tetromino. 126 | // Knuth shuffle algorithm is applied. 127 | static uint8_t tetromino_queue[7 * 4]; 128 | static uint8_t tetromino_queue_size = 7*4; 129 | static uint8_t current_queue_index = 0; 130 | 131 | 132 | static SDL_TimerID cb_timer = 0; 133 | 134 | static int score = 0; 135 | 136 | #endif 137 | 138 | 139 | void draw_playing_field(); 140 | Color_Block get_playfield(uint8_t x, uint8_t y); 141 | void set_playfield(uint8_t x, uint8_t y, Color_Block color); 142 | 143 | void initTetris(); 144 | void updateTetris(); 145 | void lockTetromino(); 146 | 147 | void spawn_tetromino(); 148 | bool render_tetromino(Tetromino_Movement tetra_request, uint8_t current_coords[]); 149 | bool render_current_tetromino(Tetromino_Movement tetra_request); 150 | -------------------------------------------------------------------------------- /src/utility.c: -------------------------------------------------------------------------------- 1 | #include "utility.h" 2 | 3 | // src: http://stackoverflow.com/a/1449859/412627 4 | sds printfcomma(int n) { 5 | 6 | sds num = sdsempty(); 7 | 8 | int n2 = 0; 9 | int scale = 1; 10 | if (n < 0) { 11 | // printf ("-"); 12 | num = sdscat(num, "-"); 13 | n = -n; 14 | } 15 | while (n >= 1000) { 16 | n2 = n2 + scale * (n % 1000); 17 | n /= 1000; 18 | scale *= 1000; 19 | } 20 | 21 | num = sdscatprintf(num, "%d", n); 22 | // printf ("%d", n); 23 | 24 | while (scale != 1) { 25 | scale /= 1000; 26 | n = n2 / scale; 27 | n2 = n2 % scale; 28 | // printf (",%03d", n); 29 | num = sdscatprintf(num, ",%03d", n); 30 | } 31 | 32 | return num; 33 | } 34 | -------------------------------------------------------------------------------- /src/utility.h: -------------------------------------------------------------------------------- 1 | #include "defs.h" 2 | 3 | sds printfcomma(int n); 4 | --------------------------------------------------------------------------------