├── .deps.lock ├── .gitignore ├── Projectfile ├── README.md ├── gameplay.png ├── orig ├── LICENSE ├── README.md ├── demo.gif ├── makefile └── src │ ├── defs.h │ ├── font │ └── Ubuntu-M.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 ├── src ├── Ubuntu-M.ttf ├── fps.cr ├── game.cr ├── graphics.cr ├── lib_sdl2_gfx.cr ├── lib_sdl2_ttf.cr ├── main.cr └── tetromino.cr └── tetris.cr /.deps.lock: -------------------------------------------------------------------------------- 1 | { 2 | "sdl2": "81defc25399294fd1063ad0e9ec85487defe893b" 3 | } 4 | -------------------------------------------------------------------------------- /.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 26 | *.swp 27 | .swp 28 | .crystal/ 29 | libs/ 30 | .deps/ 31 | -------------------------------------------------------------------------------- /Projectfile: -------------------------------------------------------------------------------- 1 | deps do 2 | github "weskinner/crystal-sdl2" 3 | end 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # crystal-tetris 2 | port of Dashed/tetris-sdl-c to crystal-lang 3 | 4 | ![](gameplay.png) 5 | ## Prerequisites 6 | * libsdl2 7 | * libsdl2-gfx 8 | * libsdl2-ttf 9 | 10 | ``` 11 | sudo apt-get install libsdl2-dev libsdl2-gfx-dev libsdl2-ttf-dev 12 | ``` 13 | ``` 14 | brew install sdl2 sdl2_gfx sdl2_ttf 15 | ``` 16 | 17 | ## How to Build 18 | ``` 19 | crystal deps 20 | crystal tetris.cr 21 | ``` 22 | -------------------------------------------------------------------------------- /gameplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weskinner/crystal-tetris/6d92f211c88a7a9049976c9f2d38e3cc75e48519/gameplay.png -------------------------------------------------------------------------------- /orig/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 | -------------------------------------------------------------------------------- /orig/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.gif) 8 | 9 | Install 10 | ======= 11 | 12 | 1. Install SDL 2.x ([www.libsdl.org](http://www.libsdl.org/)) in UNIX style; something like `./configure && make && make install`. 13 | 14 | On OSX, you may use [homebrew](http://brew.sh/). 15 | 16 | Also install: 17 | * [SDL2_gfx](http://cms.ferzkopp.net/index.php/software/13-sdl-gfx) 18 | * [SDL2_ttf](https://www.libsdl.org/projects/SDL_ttf/) 19 | 20 | 2. `make` to create `tetris_toy` 21 | 22 | 3. `./tetris_toy` 23 | 24 | Usage 25 | ===== 26 | 27 | - Move tetromino with WASD keys or arrow keys. 28 | - Press `spacebar` for hard Tetromino drop. 29 | 30 | - Press `r` to reset. 31 | - Press `esc` to quit. 32 | 33 | 34 | To Do 35 | ===== 36 | 37 | - Add wall kick 38 | - Implement any other interesting mechanics listed in http://tetrisconcept.net/wiki/Main_Page 39 | 40 | License 41 | ======= 42 | 43 | MIT. 44 | -------------------------------------------------------------------------------- /orig/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weskinner/crystal-tetris/6d92f211c88a7a9049976c9f2d38e3cc75e48519/orig/demo.gif -------------------------------------------------------------------------------- /orig/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 | -------------------------------------------------------------------------------- /orig/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 | static const char * WINDOW_TITLE = "tetris-sdl-c"; 17 | 18 | // a block 'pixel' of a playing field is 15px by 15px in size 19 | static const short int BLOCK_SIZE = 20; 20 | 21 | // standard size of a tetris playing field 22 | static const short int PLAYFIELD_HEIGHT = 22; 23 | static const short int PLAYFIELD_WIDTH = 10; 24 | 25 | static const int WINDOW_HEIGHT = 22 * ( 20 + 1) + 1; 26 | static const int WINDOW_WIDTH = 22 * ( 20 + 1) + 1; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /orig/src/font/Ubuntu-M.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weskinner/crystal-tetris/6d92f211c88a7a9049976c9f2d38e3cc75e48519/orig/src/font/Ubuntu-M.ttf -------------------------------------------------------------------------------- /orig/src/graphics.c: -------------------------------------------------------------------------------- 1 | #include "graphics.h" 2 | 3 | 4 | void init_graphics() { 5 | 6 | render_changed = false; 7 | 8 | window = SDL_CreateWindow( 9 | // title of window 10 | WINDOW_TITLE, 11 | 12 | // initial position of the window 13 | SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 14 | 15 | WINDOW_WIDTH, WINDOW_HEIGHT, 16 | SDL_WINDOW_SHOWN); 17 | 18 | if (window == NULL) { 19 | fprintf(stderr, 20 | "\nSDL_CreateWindow Error: %s\n", 21 | SDL_GetError()); 22 | exit(1); 23 | } 24 | 25 | // Create a renderer that will draw to the window, -1 specifies that we want to load whichever 26 | // video driver supports the flags we're passing 27 | // 28 | // Flags: 29 | // SDL_RENDERER_ACCELERATED: We want to use hardware accelerated rendering 30 | // SDL_RENDERER_PRESENTVSYNC: We want the renderer's present function (update screen) to be 31 | // synchornized with the monitor's refresh rate 32 | render = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE); 33 | 34 | if (render == NULL) { 35 | fprintf(stderr, 36 | "\nSDL_CreateRenderer Error: %s\n", 37 | SDL_GetError()); 38 | exit(1); 39 | } 40 | 41 | SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_BLEND); 42 | 43 | // texture for render context 44 | display = SDL_CreateTexture(render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, WINDOW_WIDTH, WINDOW_HEIGHT); 45 | 46 | SDL_SetRenderTarget(render, display); 47 | 48 | // Load font 49 | gFont = TTF_OpenFont("src/font/Ubuntu-M.ttf", 20); 50 | if (gFont == NULL) { 51 | fprintf(stderr, 52 | "\nTTF_OpenFont Error: %s\n", 53 | SDL_GetError()); 54 | exit(1); 55 | } 56 | 57 | } 58 | 59 | void setRenderChanged() { 60 | render_changed = true; 61 | } 62 | 63 | void preRender() { 64 | 65 | 66 | SDL_SetRenderTarget(render, display); 67 | 68 | 69 | } 70 | 71 | void updateRender() { 72 | 73 | // lazily update the screen only if render operations are queued 74 | if(render_changed) { 75 | 76 | SDL_SetRenderTarget(render, NULL); 77 | SDL_RenderCopy(render, display, NULL, NULL); 78 | 79 | SDL_RenderPresent(render); 80 | render_changed = false; 81 | 82 | } 83 | } 84 | 85 | void draw_block(uint8_t x, uint8_t y, uint32_t color) { 86 | 87 | assert(x >= 0 && x < PLAYFIELD_WIDTH); 88 | assert(y >= 0 && y < PLAYFIELD_HEIGHT); 89 | 90 | // top-left coords of block 91 | uint16_t x_tl = x * (BLOCK_SIZE + 1) + 1; 92 | uint16_t y_tl = y * (BLOCK_SIZE + 1) + 1; 93 | 94 | // bottom-right coords of block 95 | uint16_t x_br = x_tl + BLOCK_SIZE; 96 | uint16_t y_br = y_tl + BLOCK_SIZE; 97 | 98 | boxColor(render, x_tl, y_tl, x_br, y_br, color); 99 | 100 | setRenderChanged(); 101 | 102 | } 103 | 104 | void cleanup_graphics() { 105 | SDL_DestroyRenderer(render); 106 | SDL_DestroyWindow(window); 107 | } 108 | -------------------------------------------------------------------------------- /orig/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 | -------------------------------------------------------------------------------- /orig/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 | -------------------------------------------------------------------------------- /orig/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 | -------------------------------------------------------------------------------- /orig/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 | -------------------------------------------------------------------------------- /orig/src/input.h: -------------------------------------------------------------------------------- 1 | #include "defs.h" 2 | #include "tetris.h" 3 | 4 | Tetris_Action TETROMINO_ACTION; 5 | 6 | void getInput(); 7 | -------------------------------------------------------------------------------- /orig/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 | -------------------------------------------------------------------------------- /orig/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 | -------------------------------------------------------------------------------- /orig/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 | -------------------------------------------------------------------------------- /orig/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 | -------------------------------------------------------------------------------- /orig/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 | -------------------------------------------------------------------------------- /orig/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 | -------------------------------------------------------------------------------- /orig/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 | // Draw tetris playing field 18 | // Vertical lines for playing field 19 | // i = PLAYFIELD_WIDTH * (BLOCK_SIZE + 1); 20 | // for (; i >= 0; i -= BLOCK_SIZE + 1) 21 | // aalineRGBA(render, i, 0, i, WINDOW_HEIGHT, 187, 173, 160, 0); 22 | 23 | // Horizontal lines for playing field 24 | // i = PLAYFIELD_HEIGHT * (BLOCK_SIZE + 1); 25 | // for (; i >= 0; i -= BLOCK_SIZE + 1) 26 | // aalineRGBA(render, 0, i, WINDOW_WIDTH, i, 187, 173, 160, 0); 27 | 28 | 29 | i = PLAYFIELD_HEIGHT * PLAYFIELD_WIDTH; 30 | while (i --> 0) 31 | set_playfield(i % PLAYFIELD_WIDTH, i / PLAYFIELD_WIDTH, playfield[i]); 32 | 33 | 34 | // Update the screen 35 | setRenderChanged(); 36 | } 37 | 38 | Uint32 auto_drop_timer(Uint32 interval, void *param) { 39 | 40 | SDL_Event event; 41 | SDL_UserEvent userevent; 42 | 43 | userevent.type = SDL_USEREVENT; 44 | userevent.code = 0; 45 | userevent.data1 = NULL; 46 | userevent.data2 = NULL; 47 | 48 | event.type = SDL_USEREVENT; 49 | event.user = userevent; 50 | 51 | SDL_PushEvent(&event); 52 | return interval; 53 | } 54 | 55 | void initTetris() { 56 | 57 | // set up SDL timer 58 | if(cb_timer != 0) { 59 | SDL_RemoveTimer(cb_timer); 60 | } 61 | cb_timer = 0; 62 | 63 | TETROMINO_ACTION = NONE; 64 | 65 | // Empty the playfield 66 | int i = PLAYFIELD_HEIGHT * PLAYFIELD_WIDTH; 67 | while (i --> 0) { 68 | playfield[i] = EMPTY; 69 | } 70 | 71 | // build tetromino queue 72 | current_queue_index = 0; 73 | i = tetromino_queue_size; 74 | int n = 0; 75 | while(i --> 0) { 76 | if((i + 1) % 4 == 0) { 77 | n++; 78 | } 79 | tetromino_queue[i] = n; 80 | } 81 | 82 | // apply shuffle algorithm 83 | shuffle(tetromino_queue, tetromino_queue_size, sizeof(uint8_t)); 84 | 85 | draw_playing_field(); 86 | 87 | spawn_tetromino(); 88 | 89 | } 90 | 91 | void lockTetromino() { 92 | 93 | lock_delay_count = 0; 94 | 95 | // lock tetromino in place 96 | int i = 4; 97 | while(i --> 0) { 98 | uint8_t x_coord = i * 2; 99 | uint8_t y_coord = x_coord + 1; 100 | 101 | uint8_t _x = CURRENT_TETROMINO_COORDS[x_coord]; 102 | uint8_t _y = CURRENT_TETROMINO_COORDS[y_coord]; 103 | 104 | CURRENT_TETROMINO_COORDS[x_coord] = 0; 105 | CURRENT_TETROMINO_COORDS[y_coord] = 0; 106 | 107 | set_playfield(_x, _y, CURRENT_TETROMINO.type.color); 108 | } 109 | 110 | // clear lines if any 111 | uint8_t row = PLAYFIELD_HEIGHT; 112 | int8_t row_to_copy_to = -1; 113 | 114 | uint8_t completed_lines = 0; 115 | 116 | while(row --> 0) { 117 | uint8_t col; 118 | bool complete_line = true; 119 | 120 | // check if line is complete 121 | for(col = 0; col < PLAYFIELD_WIDTH; col++) { 122 | if(get_playfield(col, row) == EMPTY) { 123 | 124 | complete_line = false; 125 | break; 126 | } 127 | } 128 | 129 | // clear line 130 | if(complete_line) { 131 | 132 | completed_lines++; 133 | 134 | if(row_to_copy_to < row) { 135 | row_to_copy_to = row; 136 | } 137 | 138 | for(col = 0; col < PLAYFIELD_WIDTH; col++) { 139 | set_playfield(col, row, EMPTY); 140 | } 141 | 142 | } else if(row_to_copy_to > row) { 143 | 144 | for(col = 0; col < PLAYFIELD_WIDTH; col++) { 145 | set_playfield(col, row_to_copy_to, get_playfield(col, row)); 146 | } 147 | 148 | row_to_copy_to--; 149 | } 150 | 151 | } 152 | 153 | // update score 154 | 155 | if(completed_lines > 0) { 156 | // tetris 157 | score += completed_lines/4 * 800; 158 | completed_lines = completed_lines % 4; 159 | 160 | // triple 161 | score += completed_lines/3 * 500; 162 | completed_lines = completed_lines % 3; 163 | 164 | // double 165 | score += completed_lines/2 * 300; 166 | completed_lines = completed_lines % 2; 167 | 168 | // single 169 | score += completed_lines * 100; 170 | } 171 | 172 | 173 | spawn_tetromino(); 174 | 175 | } 176 | 177 | 178 | 179 | void render_score() { 180 | // Show tetris score after all tetris operations are finished 181 | SDL_Color textColor = { 0x11, 0x1F, 0x3F }; 182 | 183 | sds string_score = printfcomma(score); 184 | 185 | SDL_Surface* textSurface = TTF_RenderText_Blended(gFont, string_score, textColor); 186 | 187 | sdsfree(string_score); 188 | 189 | if (textSurface == NULL) { 190 | fprintf(stderr, 191 | "\nTTF_RenderText_Solid Error: %s\n", 192 | SDL_GetError()); 193 | exit(1); 194 | } 195 | 196 | SDL_Texture* mTexture = SDL_CreateTextureFromSurface(render, textSurface); 197 | 198 | if (mTexture == NULL) { 199 | fprintf(stderr, 200 | "\nSDL_CreateTextureFromSurface Error: %s\n", 201 | SDL_GetError()); 202 | exit(1); 203 | } 204 | 205 | int mWidth = textSurface->w; 206 | int mHeight = textSurface->h; 207 | 208 | SDL_FreeSurface(textSurface); 209 | 210 | // render text 211 | SDL_Rect renderQuad = { WINDOW_WIDTH - mWidth - 10, 10, mWidth, mHeight }; 212 | 213 | SDL_RenderCopyEx(render, mTexture, NULL, &renderQuad, 0, NULL, SDL_FLIP_NONE); 214 | 215 | SDL_DestroyTexture(mTexture); 216 | } 217 | 218 | void updateTetris() { 219 | 220 | if (cb_timer == 0) { 221 | cb_timer = SDL_AddTimer(500, auto_drop_timer, NULL); 222 | } 223 | 224 | // draw the scoreboard as needed 225 | int i = 4; 226 | bool on_score_area = false; 227 | while(i --> 0) { 228 | uint8_t x_coord = i * 2; 229 | uint8_t y_coord = x_coord + 1; 230 | 231 | // uint8_t _x = CURRENT_TETROMINO_COORDS[x_coord]; 232 | uint8_t _y = CURRENT_TETROMINO_COORDS[y_coord]; 233 | 234 | // if a tetromino is within the top 3 rows of the playing field, redraw 235 | // that area of the playing field. 236 | // the third row is considered when the tetromino leaves the score area, 237 | // it will clear the previous position. 238 | if(_y <= 2) { 239 | on_score_area = true; 240 | break; 241 | } 242 | } 243 | 244 | if(on_score_area) { 245 | 246 | // re-draw playfield area where score is located in 247 | int n = PLAYFIELD_WIDTH * 2 - 1; 248 | while(n --> 0) { 249 | int x = n % PLAYFIELD_WIDTH; 250 | int y = n / PLAYFIELD_WIDTH; 251 | 252 | set_playfield(x, y, get_playfield(x, y)); 253 | } 254 | 255 | draw_playing_field(); 256 | 257 | // re-draw tetromino 258 | render_current_tetromino(CURRENT_TETROMINO); 259 | 260 | render_score(); 261 | } 262 | 263 | 264 | 265 | Tetromino_Movement request = CURRENT_TETROMINO; 266 | 267 | // action from keyboard 268 | switch(TETROMINO_ACTION) { 269 | case NONE: 270 | // do nothing - don't bother redrawing 271 | break; 272 | 273 | case ROTATE: 274 | request.rotation = (request.rotation + 1) % 4; 275 | render_current_tetromino(request); 276 | break; 277 | 278 | case LEFT: 279 | request.x -= 1; 280 | render_current_tetromino(request); 281 | break; 282 | 283 | case RIGHT: 284 | request.x += 1; 285 | render_current_tetromino(request); 286 | 287 | break; 288 | 289 | case DROP: 290 | 291 | request.y += 1; 292 | while(render_current_tetromino(request)) 293 | request.y += 1; 294 | 295 | lockTetromino(); 296 | 297 | break; 298 | 299 | case DOWN: 300 | request.y += 1; 301 | if(!render_current_tetromino(request)) { 302 | lock_delay_count++; 303 | } else { 304 | lock_delay_count = 0; 305 | } 306 | break; 307 | 308 | case RESTART: 309 | initTetris(); 310 | break; 311 | 312 | case AUTO_DROP: 313 | 314 | request.y += 1; 315 | if (!render_current_tetromino(request)) { 316 | lock_delay_count++; 317 | } else { 318 | lock_delay_count = 0; 319 | } 320 | 321 | if (lock_delay_count >= lock_delay_threshold) { 322 | lockTetromino(); 323 | } 324 | 325 | break; 326 | } 327 | TETROMINO_ACTION = NONE; 328 | 329 | } 330 | 331 | void spawn_tetromino() { 332 | 333 | current_queue_index++; 334 | if(current_queue_index >= tetromino_queue_size) { 335 | current_queue_index = 0; 336 | 337 | // apply shuffle algorithm 338 | shuffle(tetromino_queue, tetromino_queue_size, sizeof(uint8_t)); 339 | } 340 | 341 | Tetromino type; 342 | 343 | switch(tetromino_queue[current_queue_index]) { 344 | case 1: 345 | type = TETRA_I; 346 | break; 347 | case 2: 348 | type = TETRA_J; 349 | break; 350 | case 3: 351 | type = TETRA_L; 352 | break; 353 | case 4: 354 | type = TETRA_O; 355 | break; 356 | case 5: 357 | type = TETRA_S; 358 | break; 359 | case 6: 360 | type = TETRA_T; 361 | break; 362 | case 7: 363 | type = TETRA_Z; 364 | break; 365 | } 366 | 367 | Tetromino_Movement tetra_request = { 368 | type, 369 | 0, 370 | 3, 0 371 | }; 372 | 373 | if(!render_current_tetromino(tetra_request)) { 374 | 375 | // Reset the game 376 | initTetris(); 377 | } 378 | } 379 | 380 | bool can_render_tetromino(Tetromino_Movement tetra_request, uint8_t block_render_queue[]) { 381 | uint16_t bit, piece; 382 | uint8_t row = 0, col = 0; 383 | 384 | piece = tetra_request.type.rotation[tetra_request.rotation]; 385 | uint8_t x = tetra_request.x; 386 | uint8_t y = tetra_request.y; 387 | 388 | 389 | // loop through tetramino data 390 | int i = 0; 391 | for (bit = 0x8000; bit > 0 && i < 8; bit = bit >> 1) { 392 | 393 | if (piece & bit) { 394 | 395 | uint8_t _x = x + col; 396 | uint8_t _y = y + row; 397 | 398 | // bounds check 399 | if ((_x < 0) || (_x >= PLAYFIELD_WIDTH) 400 | || (_y < 0) || (_y >= PLAYFIELD_HEIGHT) 401 | || get_playfield(_x, _y) != EMPTY) { 402 | 403 | // unable to render tetramino block 404 | return false; 405 | break; 406 | } else { 407 | 408 | if(block_render_queue != NULL) { 409 | block_render_queue[i * 2] = _x; 410 | block_render_queue[i * 2 + 1] = _y; 411 | } 412 | 413 | i++; 414 | } 415 | } 416 | 417 | // cycle col between 0 to 3 418 | // if col is 0 then increment row 419 | ((col = ++col % 4) == 0 && ++row); 420 | 421 | } 422 | 423 | return true; 424 | 425 | } 426 | 427 | bool render_current_tetromino(Tetromino_Movement tetra_request) { 428 | 429 | // create ghost 430 | Tetromino ghost = tetra_request.type; 431 | 432 | // change alpha to ~50% 433 | ghost.color = ghost.color & 0x00FFFFFF; 434 | ghost.color = ghost.color | 0x66000000; 435 | 436 | Tetromino_Movement ghost_request = tetra_request; 437 | ghost_request.type = ghost; 438 | 439 | // render ghost tetromino 440 | while(render_tetromino(ghost_request, GHOST_TETROMINO_COORDS)) 441 | ghost_request.y += 1; 442 | 443 | // change alpha to 90% 444 | tetra_request.type.color = tetra_request.type.color & 0x00FFFFFF; 445 | tetra_request.type.color = tetra_request.type.color | 0xE5000000; 446 | 447 | if(render_tetromino(tetra_request, CURRENT_TETROMINO_COORDS)) { 448 | CURRENT_TETROMINO = tetra_request; 449 | 450 | return true; 451 | } 452 | 453 | return false; 454 | } 455 | 456 | // render tetromino movement request 457 | // returns true if tetromino is rendered succesfully; false otherwise 458 | bool render_tetromino(Tetromino_Movement tetra_request, uint8_t current_coords[]) { 459 | 460 | // simple 'queue' to store coords of blocks to render on playing field. 461 | // Each tetromino has 4 blocks with total of 4 coordinates. 462 | // 463 | // To access a coord, if 0 <= i < 4, then 464 | // x = i * 2, y = x + 1 465 | // 466 | uint8_t block_render_queue[8] = {0}; 467 | 468 | if(!can_render_tetromino(tetra_request, block_render_queue)) 469 | return false; 470 | 471 | // clear old tetromino position 472 | int i = 4; 473 | while(i --> 0) { 474 | uint8_t x_coord = i * 2; 475 | uint8_t y_coord = x_coord + 1; 476 | 477 | uint8_t _x = current_coords[x_coord]; 478 | uint8_t _y = current_coords[y_coord]; 479 | 480 | draw_block(_x, _y, EMPTY); 481 | } 482 | 483 | 484 | // render new tetromino blocks 485 | i = 4; 486 | while(i --> 0) { 487 | 488 | uint8_t x_coord = i * 2; 489 | uint8_t y_coord = x_coord + 1; 490 | 491 | // store and draw new tetromino position 492 | uint8_t _x = block_render_queue[x_coord]; 493 | uint8_t _y = block_render_queue[y_coord]; 494 | 495 | current_coords[x_coord] = _x; 496 | current_coords[y_coord] = _y; 497 | 498 | draw_block(_x, _y, tetra_request.type.color); 499 | 500 | } 501 | 502 | return true; 503 | } 504 | 505 | Color_Block get_playfield(uint8_t x, uint8_t y) { 506 | return playfield[(y * PLAYFIELD_WIDTH) + x]; 507 | } 508 | 509 | void set_playfield(uint8_t x, uint8_t y, Color_Block color) { 510 | playfield[(y * PLAYFIELD_WIDTH) + x] = color; 511 | 512 | draw_block(x, y, color); 513 | } 514 | -------------------------------------------------------------------------------- /orig/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[22 * 10]; 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 | -------------------------------------------------------------------------------- /orig/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 | -------------------------------------------------------------------------------- /orig/src/utility.h: -------------------------------------------------------------------------------- 1 | #include "defs.h" 2 | 3 | sds printfcomma(int n); 4 | -------------------------------------------------------------------------------- /src/Ubuntu-M.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weskinner/crystal-tetris/6d92f211c88a7a9049976c9f2d38e3cc75e48519/src/Ubuntu-M.ttf -------------------------------------------------------------------------------- /src/fps.cr: -------------------------------------------------------------------------------- 1 | class Fps 2 | def initialize 3 | @last_ticks = @now_ticks = SDL2.ticks 4 | @frames_ticks = @last_ticks 5 | @frames = @fps_sum = @fps_samples = 0 6 | end 7 | 8 | def start 9 | @now_ticks = SDL2.ticks 10 | @dt = @now_ticks - @last_ticks 11 | end 12 | 13 | def stop 14 | @last_ticks = @now_ticks 15 | @frames += 1 16 | 17 | if @now_ticks - @frames_ticks > 1000 18 | fps = @frames.to_f / (@now_ticks - @frames_ticks) * 1000 19 | @fps_sum += fps 20 | @fps_samples += 1 21 | puts "FPS: #{fps}" 22 | puts "AVERAGE: #{@fps_sum / @fps_samples}" 23 | @frames_ticks = @now_ticks 24 | @frames = 0 25 | end 26 | end 27 | end 28 | 29 | -------------------------------------------------------------------------------- /src/game.cr: -------------------------------------------------------------------------------- 1 | require "./tetromino.cr" 2 | 3 | module Tetris 4 | class Game 5 | def initialize(@graphics) 6 | @cb_timer = 0 7 | @playfield = Array(UInt32).new PLAYFIELD_HEIGHT * PLAYFIELD_WIDTH, ColorBlock::EMPTY 8 | @tetromino_action = :none 9 | @ghost_tetromino_coords = Array(UInt8).new(8, 0_u8) 10 | @current_tetromino = TetrominoMovement.new Piece::TETRA_O, 0, 3, 0 11 | @ghost_movement = TetrominoMovement.new @current_tetromino 12 | @lock_delay_count = 0 13 | @lock_delay_threshold = 2 14 | @score = 0 15 | end 16 | 17 | def setup 18 | @score = 0 19 | 20 | # set up SDL timer 21 | LibSDL2.remove_timer(@cb_timer) unless @cb_timer == 0 22 | @cb_timer = 0 23 | 24 | @tetromino_action = :none; 25 | 26 | empty_playfield 27 | draw_playing_field 28 | spawn_tetromino 29 | end 30 | 31 | def spawn_tetromino 32 | type = Piece::TETRA_O 33 | case (1..7).to_a.sample 34 | when 1 35 | type = Piece::TETRA_I 36 | when 2 37 | type = Piece::TETRA_J 38 | when 3 39 | type = Piece::TETRA_L 40 | when 4 41 | type = Piece::TETRA_O 42 | when 5 43 | type = Piece::TETRA_S 44 | when 6 45 | type = Piece::TETRA_T 46 | when 7 47 | type = Piece::TETRA_Z 48 | end 49 | 50 | @current_tetromino.reset(type) 51 | setup unless render_current_tetromino 52 | end 53 | 54 | def update(@tetromino_action) 55 | if @cb_timer == 0 56 | @cb_timer = LibSDL2.add_timer(1000_u32, ->(interval, param) { return (param as Game).auto_drop_timer(interval) }, self as Void*) 57 | end 58 | 59 | if on_score_area 60 | redraw_playfield_score_area 61 | draw_playing_field 62 | render_current_tetromino 63 | render_score 64 | end 65 | 66 | handle_input 67 | end 68 | 69 | def handle_input 70 | request = TetrominoRequest.new 71 | 72 | # action from keyboard 73 | case @tetromino_action 74 | when :none 75 | when :rotate 76 | request.rotation = (request.rotation + 1) % 4 77 | render_current_tetromino(request) 78 | when :left 79 | request.x -= 1 80 | render_current_tetromino(request) 81 | when :right 82 | request.x += 1 83 | render_current_tetromino(request) 84 | when :drop 85 | request.y += 1 86 | while render_current_tetromino(request) 87 | end 88 | lock_tetromino 89 | when :down 90 | request.y += 1 91 | unless render_current_tetromino(request) 92 | @lock_delay_count += 1 93 | else 94 | @lock_delay_count = 0 95 | end 96 | when :restart 97 | setup 98 | when :auto_drop 99 | request.y += 1 100 | unless render_current_tetromino(request) 101 | @lock_delay_count += 1 102 | else 103 | @lock_delay_count = 0 104 | end 105 | 106 | lock_tetromino if @lock_delay_count >= @lock_delay_threshold 107 | end 108 | 109 | @tetromino_action = :none 110 | end 111 | 112 | def on_score_area 113 | on_score_area = false 114 | @current_tetromino.coords do |x,y| 115 | if y <= 2 116 | on_score_area = true 117 | break 118 | end 119 | end 120 | 121 | return on_score_area 122 | end 123 | 124 | def lock_tetromino 125 | @lock_delay_count = 0 126 | 127 | @current_tetromino.coords do |x,y| 128 | set_playfield(x, y, @current_tetromino.type.color) 129 | end 130 | @current_tetromino.reset 131 | @ghost_tetromino_coords = Array(UInt8).new(8, 0_u8) 132 | 133 | row_to_copy_to = -1; 134 | completed_lines = 0; 135 | (PLAYFIELD_HEIGHT-1).downto(0) do |row| 136 | complete_line = true; 137 | # check if line is complete 138 | (0...PLAYFIELD_WIDTH).each do |col| 139 | if get_playfield(col, row) == ColorBlock::EMPTY 140 | complete_line = false 141 | break 142 | end 143 | end 144 | 145 | # clear line 146 | if complete_line 147 | completed_lines += 1 148 | row_to_copy_to = row if row_to_copy_to < row 149 | (0...PLAYFIELD_WIDTH).each do |col| 150 | set_playfield(col, row, ColorBlock::EMPTY) 151 | end 152 | elsif row_to_copy_to > row 153 | (0...PLAYFIELD_WIDTH).each do |col| 154 | set_playfield(col, row_to_copy_to, get_playfield(col, row)) 155 | end 156 | row_to_copy_to -= 1 157 | end 158 | end 159 | 160 | # update score 161 | if completed_lines > 0 162 | # tetris 163 | @score += completed_lines/4 * 800; 164 | completed_lines = completed_lines % 4; 165 | 166 | # triple 167 | @score += completed_lines/3 * 500; 168 | completed_lines = completed_lines % 3; 169 | 170 | # double 171 | @score += completed_lines/2 * 300; 172 | completed_lines = completed_lines % 2; 173 | 174 | # single 175 | @score += completed_lines * 100; 176 | end 177 | 178 | 179 | spawn_tetromino 180 | end 181 | 182 | def render_score 183 | @graphics.render_text @score.to_s 184 | end 185 | 186 | def render_current_tetromino(tetra_request = TetrominoRequest.new) 187 | #clear old ghost 188 | @ghost_movement.coords do |x,y| 189 | @graphics.draw_block(x, y, ColorBlock::EMPTY) 190 | end 191 | 192 | ghost = Tetromino.new @current_tetromino.type 193 | 194 | # change alpha to ~50% 195 | ghost.color = ghost.color & 0x00FFFFFF; 196 | ghost.color = ghost.color | 0x66000000; 197 | 198 | @ghost_movement = TetrominoMovement.new @current_tetromino 199 | @ghost_movement.type = ghost 200 | @ghost_movement.update tetra_request 201 | 202 | ghost_request = TetrominoRequest.new 0, 1, 0 203 | 204 | # render ghost tetromino 205 | while render_tetromino(@ghost_movement, ghost_request) 206 | @ghost_movement.update ghost_request 207 | end 208 | 209 | # change alpha to 90% 210 | tetra_request.color = @current_tetromino.type.color & 0x00FFFFFF; 211 | tetra_request.color = tetra_request.color | 0xE5000000; 212 | 213 | if render_tetromino(@current_tetromino, tetra_request) 214 | @current_tetromino.update tetra_request 215 | return true 216 | end 217 | 218 | return false 219 | end 220 | 221 | # render tetromino movement request 222 | # returns true if tetromino is rendered succesfully; false otherwise 223 | def render_tetromino(tetromino, request) 224 | (future = TetrominoMovement.new(tetromino)).update(request) 225 | return false unless can_render_tetromino(future) 226 | 227 | # clear old tetromino position 228 | tetromino.coords do |x,y| 229 | @graphics.draw_block(x, y, ColorBlock::EMPTY) 230 | end 231 | 232 | # render new tetromino blocks 233 | future.coords do |x,y| 234 | @graphics.draw_block(x, y, tetromino.type.color) 235 | end 236 | 237 | return true 238 | end 239 | 240 | private def can_render_tetromino(tetromino) 241 | tetromino.coords do |_x,_y| 242 | if (_x < 0) || (_x >= PLAYFIELD_WIDTH) || 243 | (_y < 0) || (_y >= PLAYFIELD_HEIGHT) || 244 | get_playfield(_x, _y) != ColorBlock::EMPTY 245 | 246 | # unable to render tetramino block 247 | return false 248 | end 249 | end 250 | 251 | return true 252 | end 253 | 254 | private def get_playfield(x, y) 255 | return @playfield[(y * PLAYFIELD_WIDTH) + x] 256 | end 257 | 258 | private def set_playfield(x, y, color) 259 | @playfield[(y * PLAYFIELD_WIDTH) + x] = color 260 | @graphics.draw_block(x, y, color) 261 | end 262 | 263 | def draw_playing_field 264 | @graphics.clear_background 265 | 266 | ((PLAYFIELD_HEIGHT * PLAYFIELD_WIDTH)-1).downto(0) do |i| 267 | set_playfield(i % PLAYFIELD_WIDTH, i / PLAYFIELD_WIDTH, @playfield[i]) 268 | end 269 | 270 | @graphics.set_render_changed 271 | end 272 | 273 | def empty_playfield 274 | ((PLAYFIELD_HEIGHT * PLAYFIELD_WIDTH)-1).downto(0) do |i| 275 | @playfield[i] = ColorBlock::EMPTY 276 | end 277 | end 278 | 279 | def redraw_playfield_score_area 280 | (0..PLAYFIELD_WIDTH * 2 - 1).each do |n| 281 | x = n % PLAYFIELD_WIDTH; 282 | y = n / PLAYFIELD_WIDTH; 283 | 284 | set_playfield(x, y, get_playfield(x, y)); 285 | end 286 | end 287 | 288 | def auto_drop_timer(interval) 289 | event = LibSDL2::Event.new 290 | userevent = LibSDL2::UserEvent.new 291 | 292 | userevent.type = EventType::USEREVENT; 293 | userevent.code = 0; 294 | userevent.data1 = Pointer(Void).null; 295 | userevent.data2 = Pointer(Void).null; 296 | 297 | event.type = EventType::USEREVENT; 298 | event.user = userevent; 299 | 300 | LibSDL2.push_event(pointerof(event)) 301 | interval 302 | end 303 | end 304 | end 305 | -------------------------------------------------------------------------------- /src/graphics.cr: -------------------------------------------------------------------------------- 1 | require "./lib_sdl2_gfx" 2 | 3 | include SDL2 4 | 5 | module Tetris 6 | WINDOW_TITLE = "tetris-sdl-c" 7 | 8 | # a block 'pixel' of a playing field is 15px by 15px in size 9 | BLOCK_SIZE = 20 10 | 11 | # standard size of a tetris playing field 12 | PLAYFIELD_HEIGHT = 22 13 | PLAYFIELD_WIDTH = 10 14 | 15 | WINDOW_HEIGHT = PLAYFIELD_HEIGHT * ( BLOCK_SIZE + 1) + 1 16 | WINDOW_WIDTH = PLAYFIELD_WIDTH * ( BLOCK_SIZE + 1) + 1 17 | 18 | class Graphics 19 | property font 20 | property render 21 | 22 | def initialize 23 | @render_changed = false 24 | 25 | @window = LibSDL2.create_window(WINDOW_TITLE, LibSDL2::WINDOWPOS_CENTERED, LibSDL2::WINDOWPOS_CENTERED, 26 | WINDOW_WIDTH, WINDOW_HEIGHT, Window::Flags::SHOWN) 27 | unless @window 28 | raise "SDL_CreateWindow Error: #{SDL2.error}" 29 | end 30 | 31 | # Create a renderer that will draw to the window, -1 specifies that we want to load whichever 32 | # video driver supports the flags we're passing 33 | # 34 | # Flags: 35 | # SDL_RENDERER_ACCELERATED: We want to use hardware accelerated rendering 36 | # SDL_RENDERER_PRESENTVSYNC: We want the renderer's present function (update screen) to be 37 | # synchornized with the monitor's refresh rate 38 | @render = LibSDL2.create_renderer(@window, -1, Renderer::Flags::ACCELERATED | Renderer::Flags::PRESENTVSYNC | Renderer::Flags::TARGETTEXTURE) 39 | unless @render 40 | raise "SDL_CreateRenderer Error: #{SDL2.error}" 41 | end 42 | 43 | LibSDL2.set_render_draw_blend_mode(@render, LibSDL2::BlendMode::BLEND) 44 | 45 | # texture for render context 46 | @display = LibSDL2.create_texture(@render, 0x16462004_u32, LibSDL2::TextureAccess::TARGET, WINDOW_WIDTH, WINDOW_HEIGHT) 47 | 48 | LibSDL2.set_render_target(@render, @display) 49 | 50 | # Load font 51 | raise "TTF_OpenFont Error: #{SDL2.error}" unless @font = LibSDL2_TTF.open_font(File.join(File.dirname(__FILE__), "Ubuntu-M.ttf"), 20) 52 | end 53 | 54 | def update_render 55 | if @render_changed 56 | LibSDL2.set_render_target(@render, nil) 57 | LibSDL2.render_copy(@render, @display, nil, nil) 58 | 59 | LibSDL2.render_present(@render) 60 | @render_changed = false; 61 | end 62 | end 63 | 64 | def set_render_changed 65 | @render_changed = true 66 | end 67 | 68 | def clear_background 69 | LibSDL2.set_render_draw_color(@render, 0x4f_u8, 0x4c_u8, 0x42_u8, 255_u8) 70 | LibSDL2.render_clear(@render) 71 | end 72 | 73 | def draw_block(x, y, color) 74 | # raise if x >= 0 && x < PLAYFIELD_WIDTH 75 | # raise if y >= 0 && y < PLAYFIELD_HEIGHT 76 | 77 | # top-left coords of block 78 | x_tl = x.to_i16 * (BLOCK_SIZE + 1) + 1; 79 | y_tl = y.to_i16 * (BLOCK_SIZE + 1) + 1; 80 | 81 | # bottom-right coords of block 82 | x_br = x_tl + BLOCK_SIZE; 83 | y_br = y_tl + BLOCK_SIZE; 84 | 85 | LibSDL2_GFX.box_color(@render as Void*, x_tl, y_tl, x_br, y_br, color); 86 | 87 | set_render_changed 88 | end 89 | 90 | def render_text(text) 91 | # Show tetris score after all tetris operations are finished 92 | text_color = LibSDL2::Color.new(r: 0x11_u8, g: 0x1F_u8, b: 0x3F_u8) 93 | text_surface = LibSDL2_TTF.render_text_blended(@font, text, text_color) 94 | raise "TTF_Render Error #{LibSDL2.get_error}" if text_surface == nil 95 | 96 | mtexture = LibSDL2.create_texture_from_surface(@render, text_surface) 97 | raise "SDL_CreateTextureFromSurface Error #{LibSDL2.get_error}" if mtexture == nil 98 | 99 | mWidth = text_surface.value.w; 100 | mHeight = text_surface.value.h; 101 | 102 | LibSDL2.free_surface(text_surface) 103 | 104 | # render text 105 | render_quad = LibSDL2::Rect.new(x: (WINDOW_WIDTH - mWidth - 10), y: 10, w: mWidth, h: mHeight) 106 | LibSDL2.render_copy_ex(@render, mtexture, nil, pointerof(render_quad), 0_f64, nil, RenderFlip::NONE); 107 | 108 | LibSDL2.destroy_texture(mtexture) 109 | end 110 | 111 | def prerender 112 | LibSDL2.set_render_target(@render, @display) 113 | end 114 | 115 | def finalize 116 | LibSDL2.destroy_renderer(@render) 117 | LibSDL2.destroy_window(@window) 118 | end 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /src/lib_sdl2_gfx.cr: -------------------------------------------------------------------------------- 1 | 2 | @[Link("SDL2_gfx")] 3 | lib LibSDL2_GFX 4 | fun box_color = boxColor(dst : Void*, x1 : Int16, y1 : Int16, x2 : Int16, y2 : Int16, color : UInt32) : Int32 5 | end 6 | -------------------------------------------------------------------------------- /src/lib_sdl2_ttf.cr: -------------------------------------------------------------------------------- 1 | @[Link("SDL2_ttf")] 2 | lib LibSDL2_TTF 3 | alias Font = Void* 4 | 5 | fun init = TTF_Init() : Int32 6 | fun quit = TTF_Quit() : Void 7 | fun open_font = TTF_OpenFont(file : UInt8*, ptsize : Int32) : Font* 8 | fun render_text_blended = TTF_RenderText_Blended(font : Font*, text : UInt8*, fg : LibSDL2::Color) : LibSDL2::Surface* 9 | fun render_text_solid = TTF_RenderText_Solid(font : Font*, text : UInt8*, fg : LibSDL2::Color) : LibSDL2::Surface* 10 | end 11 | -------------------------------------------------------------------------------- /src/main.cr: -------------------------------------------------------------------------------- 1 | require "sdl2" 2 | require "./lib_sdl2_ttf" 3 | require "./graphics" 4 | require "./game" 5 | require "./fps" 6 | 7 | include SDL2 8 | 9 | def get_input 10 | while LibSDL2.poll_event(out e) == 1 11 | case e.type 12 | when EventType::QUIT 13 | return :quit 14 | when EventType::KEYDOWN 15 | case e.key.key_sym.scan_code 16 | when Scancode::ESCAPE; return :quit 17 | when Scancode::DOWN, Scancode::S; return :down 18 | when Scancode::RIGHT, Scancode::D; return :right 19 | when Scancode::LEFT, Scancode::A; return :left 20 | when Scancode::UP, Scancode::W; return :rotate 21 | when Scancode::R; return :restart 22 | when Scancode::SPACE; return :drop 23 | end 24 | when EventType::KEYUP 25 | return :none 26 | when EventType::USEREVENT 27 | return :auto_drop 28 | end 29 | end 30 | return :none 31 | end 32 | 33 | SDL2.run SDL2::INIT::EVERYTHING do 34 | raise "TTF_Init Error: #{SDL2.error}" unless LibSDL2_TTF.init() != -1 35 | graphics = Tetris::Graphics.new 36 | tetris = Tetris::Game.new(graphics) 37 | tetris.setup 38 | 39 | fps = Fps.new 40 | quit = false 41 | until quit 42 | fps.start 43 | 44 | graphics.prerender 45 | 46 | action = get_input 47 | quit = true if action == :quit 48 | 49 | tetris.update action 50 | graphics.update_render 51 | 52 | fps.stop 53 | end 54 | 55 | LibSDL2_TTF.quit 56 | end 57 | -------------------------------------------------------------------------------- /src/tetromino.cr: -------------------------------------------------------------------------------- 1 | module Tetris 2 | module ColorBlock 3 | EMPTY = 0xFF4f4c42_u32 4 | TEAL = 0xFFFFDB7F_u32 5 | BLUE = 0xFFD97400_u32 6 | ORANGE = 0xFF1B85FF_u32 7 | YELLOW = 0xFF00DCFF_u32 8 | GREEN = 0xFF40CC2E_u32 9 | PURPLE = 0xFF4B1485_u32 10 | RED = 0xFF4B59F2_u32 11 | end 12 | 13 | module Piece 14 | TETRA_I = Tetromino.new [0x0F00, 0x2222, 0x00F0, 0x4444], ColorBlock::TEAL 15 | TETRA_J = Tetromino.new [0x8E00, 0x6440, 0x0E20, 0x44C0], ColorBlock::BLUE 16 | TETRA_L = Tetromino.new [0x2E00, 0x4460, 0x0E80, 0xC440], ColorBlock::ORANGE 17 | TETRA_O = Tetromino.new [0x6600, 0x6600, 0x6600, 0x6600], ColorBlock::YELLOW 18 | TETRA_S = Tetromino.new [0x6C00, 0x4620, 0x06C0, 0x8c40], ColorBlock::GREEN 19 | TETRA_T = Tetromino.new [0x4E00, 0x4640, 0x0E40, 0x4C40], ColorBlock::PURPLE 20 | TETRA_Z = Tetromino.new [0xC600, 0x2640, 0x0C60, 0x4C80], ColorBlock::RED 21 | end 22 | 23 | class Tetromino 24 | property rotation 25 | property color 26 | 27 | def initialize(@rotation, @color) 28 | end 29 | 30 | def initialize(other) 31 | @rotation = other.rotation 32 | @color = other.color 33 | end 34 | end 35 | 36 | class TetrominoRequest 37 | property x, y, rotation, color 38 | 39 | def initialize(@x = 0, @y = 0, @rotation = 0, @color = 0x00000000) 40 | end 41 | end 42 | 43 | # Interface 44 | # -set type 45 | # -get coords 46 | # -update location / rotation from request 47 | # -reset to top of screen 48 | class TetrominoMovement 49 | property type 50 | property rotation 51 | property x 52 | property y 53 | 54 | def initialize(@type, @rotation, @x, @y, @current_coords = Array(UInt8).new(8, 0_u8)) 55 | end 56 | 57 | def initialize(other) 58 | @type = other.type 59 | @rotation = other.rotation 60 | @x = other.x 61 | @y = other.y 62 | @current_coords = Array(UInt8).new(8, 0_u8) 63 | (0..7).each {|i| @current_coords[i] = other.current_coords[i]} 64 | end 65 | 66 | protected def current_coords 67 | @current_coords 68 | end 69 | 70 | def coords 71 | (0..3).each do |i| 72 | x_coord = i * 2 73 | y_coord = x_coord + 1 74 | 75 | _x = @current_coords[x_coord] 76 | _y = @current_coords[y_coord] 77 | 78 | yield _x, _y 79 | end 80 | end 81 | 82 | def update(tetra_request) 83 | @rotation = (@rotation + tetra_request.rotation) % 4 84 | @x += tetra_request.x 85 | @y += tetra_request.y 86 | 87 | block_render_queue = Array(UInt8).new(8, 0_u8) 88 | 89 | row = 0_u8 90 | col = 0_u8 91 | 92 | piece = type.rotation[@rotation]; 93 | x = @x 94 | y = @y 95 | 96 | # loop through tetramino data 97 | i = 0 98 | bit = 0x8000_u16 99 | while bit > 0 && i < 8 100 | if (piece & bit) != 0 101 | _x = (x + col).to_u8 102 | _y = (y + row).to_u8 103 | 104 | # puts block_render_queue 105 | if block_render_queue != nil 106 | block_render_queue[i * 2] = _x; 107 | block_render_queue[i * 2 + 1] = _y; 108 | end 109 | 110 | i += 1 111 | end 112 | 113 | # cycle col between 0 to 3 114 | # if col is 0 then increment row 115 | col = (col + 1) % 4 116 | row = row + 1 if col == 0 117 | 118 | bit = bit >> 1 119 | end 120 | 121 | (0..3).each do |j| 122 | x_coord = j * 2 123 | y_coord = x_coord + 1 124 | 125 | # store and draw new tetromino position 126 | _x = block_render_queue[x_coord] 127 | _y = block_render_queue[y_coord] 128 | 129 | @current_coords[x_coord] = _x 130 | @current_coords[y_coord] = _y 131 | end 132 | end 133 | 134 | def reset(type = Piece::TETRA_I) 135 | @type = type 136 | @x = 3 137 | @y = 0 138 | @rotation = 0 139 | @current_coords = Array(UInt8).new(8, 0_u8) 140 | end 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /tetris.cr: -------------------------------------------------------------------------------- 1 | require "./src/main.cr" 2 | --------------------------------------------------------------------------------