diff --git a/license.md b/license.md new file mode 100644 index 0000000..2ec28ee --- /dev/null +++ b/license.md @@ -0,0 +1,25 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Copyright (c) 2019 Andrey Penechko + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..a9554e1 --- /dev/null +++ b/readme.md @@ -0,0 +1,8 @@ +# Roguelike tutorial 2019 made with Vox programming language + +[Vox compiler](https://github.com/MrSmith33/tiny_jit) + +Compile with `tjc source/main.vx source/kernel32.vx source/sdl.vx source/sdlimage.vx source/utils.vx SDL2.dll SDL2_image.dll C:\Windows\System32\kernel32.dll` +produces `main.exe` for win64 + +Requires `SDL2.dll` and `SDL2_image.dll` libraries for compilation and start. \ No newline at end of file diff --git a/source/kernel32.vx b/source/kernel32.vx new file mode 100644 index 0000000..3fb2940 --- /dev/null +++ b/source/kernel32.vx @@ -0,0 +1,19 @@ +void ExitProcess(u32 uExitCode); +u64 GetTickCount64(); +bool QueryPerformanceCounter(i64* performanceCount); + +u8 WriteConsoleA( + void* hConsoleOutput, + u8* lpBuffer, + u32 nNumberOfCharsToWrite, + u32* lpNumberOfCharsWritten, + void* lpReserved +); + +void* GetStdHandle(u32 nStdHandle); + +enum : u32 { + STD_INPUT_HANDLE = 0xFFFFFFF6, + STD_OUTPUT_HANDLE = 0xFFFFFFF5, + STD_ERROR_HANDLE = 0xFFFFFFF4 +} diff --git a/source/main.vx b/source/main.vx new file mode 100644 index 0000000..3774ceb --- /dev/null +++ b/source/main.vx @@ -0,0 +1,514 @@ +import utils; +import sdl; +import sdlimage; +import kernel32; + +enum scale = 2; +enum i32 TILE_W = 10; +enum i32 TILE_H = 10; +enum Color DARK_WALL = Color(0, 0, 100); +enum Color DARK_GROUND = Color(50, 50, 150); + +struct Color { + u8 r; + u8 g; + u8 b; +} + +struct Entity +{ + i32 x; + i32 y; + u8 char; + u8 r; + u8 g; + u8 b; + + void move(i32 dx, i32 dy) { + print("Move "); + printInt(x); + print(" "); + printInt(y); + x += dx; + y += dy; + print(" -> "); + printInt(x); + print(" "); + printInt(y); + println(null); + } +} + +struct Window +{ + SDL_Window* sdl_window; + SDL_Renderer* sdl_renderer; + SDL_Texture* font; +} + +i32 window_init(Window* w, i32 width, i32 height, u8[] window_title) +{ + SDL_SetMainReady; + + if(SDL_Init(SDL_INIT_VIDEO) != 0) { + println("Failed to init SDL"); + return 1; + } + + w.sdl_window = SDL_CreateWindow(window_title.ptr, + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_SHOWN); + + if (w.sdl_window == null) { + println("Failed to create window"); + return 2; + } + + w.sdl_renderer = SDL_CreateRenderer(w.sdl_window, -1, SDL_RENDERER_ACCELERATED); + if (w.sdl_renderer == null) { + print("Failed to create renderer"); + return 3; + } + + SDL_SetRenderDrawColor(w.sdl_renderer, 0, 0, 0, 0); + + i32 flags = IMG_INIT_PNG; + i32 initted = IMG_Init(flags); + if((initted & flags) != flags) { + return 4; + } + + SDL_Surface* temp_surf = IMG_Load("arial10x10.png"); + if (temp_surf == null) return 5; + + w.font = SDL_CreateTextureFromSurface(w.sdl_renderer, temp_surf); + if (w.font == null) return 6; + + SDL_FreeSurface(temp_surf); + + return 0; +} + +void window_destroy(Window* w) +{ + SDL_DestroyTexture(w.font); + SDL_DestroyRenderer(w.sdl_renderer); + SDL_DestroyWindow(w.sdl_window); + IMG_Quit; + SDL_Quit; +} + +enum TileFlags : u8 { + blocks_walk = 1 << 0, + blocks_sight = 1 << 1, + // true if player sees this tile + is_visible = 1 << 2, + // true if player has seen the tile before + is_explored = 1 << 3, +} + +struct Tile +{ + u8 flags; + bool blocked() { return (flags & TileFlags.blocks_walk) != 0; } + bool block_sight() { return (flags & TileFlags.blocks_sight) != 0; } + bool visible() { return (flags & TileFlags.is_visible) != 0; } + bool explored() { return (flags & TileFlags.is_explored) != 0; } +} + +struct GameMap +{ + enum map_width = 80; + enum map_height = 80; + u64[2]* rand_state; + i32 view_offset_x; + i32 view_offset_y; + i32 viewport_w; + i32 viewport_h; + i32 view_radius; + Tile[map_width][map_height] tiles; + + bool valid_pos(i32 x, i32 y) + { + if (x < 0) return false; + if (x >= map_width) return false; + if (y < 0) return false; + if (y >= map_height) return false; + return true; + } + + bool blocks_walk(i32 x, i32 y) + { + if (x < 0) return true; + if (x >= map_width) return true; + if (y < 0) return true; + if (y >= map_height) return true; + return tiles[y][x].blocked; + } + + void update_view_offset(i32 player_x, i32 player_y) + { + i32 halfw = viewport_w / 2; + i32 halfh = viewport_h / 2; + + view_offset_x = (halfw - player_x) * TILE_W * scale; + view_offset_y = (halfh - player_y) * TILE_H * scale; + } +} + + +void initialize_tiles(GameMap* map) +{ + for (i32 y = 0; y < map.map_height; ++y) + { + for (i32 x = 0; x < map.map_width; ++x) + { + map.tiles[y][x].flags = TileFlags.blocks_walk | TileFlags.blocks_sight; + } + } +} + +enum u32 max_rooms = 30; +enum i32 room_min_size = 3; +enum i32 room_max_size = 20; + +void make_map(GameMap* map, Entity* player) +{ + Rect[max_rooms] rooms; + + //create_room(map, Rect(0,0,map.map_width-1, map.map_height-1)); + + for (u32 roomIndex = 0; roomIndex < max_rooms; ++roomIndex) + { + // random width and height + i32 w = cast(i32)uniform(room_min_size, room_max_size, map.rand_state); + i32 h = cast(i32)uniform(room_min_size, room_max_size, map.rand_state); + + // random position without going out of the boundaries of the map + i32 x = cast(i32)uniform(0, map.map_width - w - 1, map.rand_state); + i32 y = cast(i32)uniform(0, map.map_height - h - 1, map.rand_state); + + rooms[roomIndex] = Rect(x, y, x+w, y+h); + + bool hasIntersections; + for (u32 otherRoom = 0; otherRoom < roomIndex; ++otherRoom) + { + if (intersect(rooms[roomIndex], rooms[otherRoom])) { + hasIntersections = true; + break; + } + } + + if (!hasIntersections) + { + create_room(map, rooms[roomIndex]); + i32 new_x = (rooms[roomIndex].x1 + rooms[roomIndex].x2) / 2; + i32 new_y = (rooms[roomIndex].y1 + rooms[roomIndex].y2) / 2; + + if (roomIndex == 0) + { + player.x = new_x; + player.y = new_y; + } + else + { + // connect to the previous room with a tunnel + Point prev; + rooms[roomIndex - 1].center(&prev); + + if (uniform(0, 1, map.rand_state) == 1) + { + // first move horizontally, then vertically + create_h_tunnel(map, prev.x, new_x, prev.y); + create_v_tunnel(map, prev.y, new_y, new_x); + } + else + { + // first move vertically, then horizontally + create_v_tunnel(map, prev.y, new_y, prev.x); + create_h_tunnel(map, prev.x, new_x, new_y); + } + } + } + } +} + +struct Rect +{ + i32 x1; + i32 y1; + i32 x2; + i32 y2; + + // TODO: return Point when compiler will support returning small structs + void center(Point* p) + { + p.x = (x1 + x2) / 2; + p.y = (y1 + y2) / 2; + } +} + +struct Point +{ + i32 x; + i32 y; +} + +bool intersect(Rect a, Rect b) +{ + return a.x1 <= b.x2 && a.x2 >= b.x1 && + a.y1 <= b.y2 && a.y2 >= b.y1; +} + +void create_room(GameMap* map, Rect room) +{ + for (i32 y = room.y1 + 1; y < room.y2; ++y) + { + for (i32 x = room.x1 + 1; x < room.x2; ++x) + { + map.tiles[y][x].flags &= ~(TileFlags.blocks_walk | TileFlags.blocks_sight); + map.tiles[y][x].flags |= TileFlags.is_visible; + } + } +} + +void create_h_tunnel(GameMap* map, i32 x1, i32 x2, i32 y) +{ + i32 max_x = max(x1, x2) + 1; + for (i32 x = min(x1, x2); x < max_x; ++x) + { + map.tiles[y][x].flags &= ~(TileFlags.blocks_walk | TileFlags.blocks_sight); + map.tiles[y][x].flags |= TileFlags.is_visible; + } +} + +void create_v_tunnel(GameMap* map, i32 y1, i32 y2, i32 x) +{ + i32 max_y = max(y1, y2) + 1; + for (i32 y = min(y1, y2); y < max_y; ++y) + { + map.tiles[y][x].flags &= ~(TileFlags.blocks_walk | TileFlags.blocks_sight); + map.tiles[y][x].flags |= TileFlags.is_visible; + } +} + +enum VIEW_RADIUS = 7; +enum VIEW_RADIUS_SQR = VIEW_RADIUS * VIEW_RADIUS; + +struct fov_data +{ + GameMap* map; + i32 player_x; + i32 player_y; +} + +bool visibility_check(void* userData, i32 x, i32 y) +{ + fov_data* data = cast(fov_data*)userData; + GameMap* map = data.map; + if (x*x + y*y > map.view_radius * map.view_radius) return true; + i32 map_x = data.player_x + x; + i32 map_y = data.player_y + y; + map.tiles[map_y][map_x].flags |= TileFlags.is_visible; + return map.blocks_walk(map_x, map_y); +} + +void update_fov(GameMap* map, Entity* player) +{ + // clear visibility + for (i32 y = 0; y < map.map_height; ++y) + { + for (i32 x = 0; x < map.map_width; ++x) + { + map.tiles[y][x].flags &= ~TileFlags.is_visible; + } + } + + fov_data data = fov_data(map, player.x, player.y); + + i32 view_radius = map.view_radius; + for (i32 i = -view_radius; i <= view_radius; ++i) + { + tran_thong(0, 0, -view_radius, i, &visibility_check, cast(void*)&data); + tran_thong(0, 0, view_radius, i, &visibility_check, cast(void*)&data); + tran_thong(0, 0, i, view_radius, &visibility_check, cast(void*)&data); + tran_thong(0, 0, i, -view_radius, &visibility_check, cast(void*)&data); + } +} + +void render_all(Window* win, GameMap* map, Entity[] entities) +{ + SDL_Rect from; + SDL_Rect to; + + for (i32 y = 0; y < map.map_height; ++y) + { + for (i32 x = 0; x < map.map_width; ++x) + { + Tile tile = map.tiles[y][x]; + if (tile.visible) + { + if (tile.block_sight) { + SDL_SetRenderDrawColor(win.sdl_renderer, 20, 20, 120, 255); + //color = LIGHT_WALL; + } else { + SDL_SetRenderDrawColor(win.sdl_renderer, 70, 70, 170, 255); + //color = LIGHT_GROUND; + } + map.tiles[y][x].flags |= TileFlags.is_explored; + } + else if (tile.explored) + { + if (tile.block_sight) { + SDL_SetRenderDrawColor(win.sdl_renderer, 0, 0, 100, 255); + //color = DARK_WALL; + } else { + SDL_SetRenderDrawColor(win.sdl_renderer, 50, 50, 150, 255); + //color = DARK_GROUND; + } + } + else + SDL_SetRenderDrawColor(win.sdl_renderer, 0, 0, 0, 255); + + + //SDL_SetRenderDrawColor(win.sdl_renderer, color[0], color[1], color[2]); + to = SDL_Rect( + x * scale * TILE_W + map.view_offset_x, + y * scale * TILE_H + map.view_offset_y, + TILE_W * scale, + TILE_H * scale); + SDL_RenderFillRect(win.sdl_renderer, &to); + } + } + + for (u32 i = 0; i < entities.length; ++i) + { + Tile tile = map.tiles[entities[i].y][entities[i].x]; + if (!tile.visible) continue; + from = SDL_Rect(0 * TILE_W, 1 * TILE_H, TILE_W, TILE_H); // TODO: use letter coords from font + to = SDL_Rect( + entities[i].x * scale * TILE_W + map.view_offset_x, + entities[i].y * scale * TILE_H + map.view_offset_y, + TILE_W * scale, TILE_H * scale); // TODO: use letter coords from font + + SDL_SetTextureColorMod(win.font, entities[i].r, entities[i].g, entities[i].b); + SDL_RenderCopy(win.sdl_renderer, win.font, &from, &to); + } +} + +i32 main(void* hInstance, void* hPrevInstance, u8* lpCmdLine, i32 nShowCmd) +{ + u64[2] rand_state; + init_rand_state(&rand_state); + + Window win; + i32 SCREEN_WIDTH = 1600; + i32 SCREEN_HEIGHT = 1000; + + window_init(&win, SCREEN_WIDTH, SCREEN_HEIGHT, "RL tutorial in Vox lang"); + + bool run = true; + SDL_Event e; + + GameMap map; + map.rand_state = &rand_state; + map.view_offset_x = 0; + map.view_offset_y = 0; + map.view_radius = 7; + map.viewport_w = SCREEN_WIDTH / (TILE_W * scale); + map.viewport_h = SCREEN_HEIGHT / (TILE_H * scale); + + Entity[2] entities; + entities[0] = Entity(map.map_width/2, map.map_height/2, '@', 0, 255, 0); + entities[1] = Entity(10, 10, 'O', 0, 0, 255); + Entity* player = &entities[0]; + + initialize_tiles(&map); + make_map(&map, player); + + bool recalc_fov = true; + + i32 mouse_x; + i32 mouse_y; + + while (run) + { + u32 buttons_pressed = SDL_GetMouseState(&mouse_x, &mouse_y); + i32 cursor_map_x = (mouse_x - map.view_offset_x) / (TILE_W * scale); + i32 cursor_map_y = (mouse_y - map.view_offset_y) / (TILE_H * scale); + + if (map.valid_pos(cursor_map_x, cursor_map_y)) + { + if (buttons_pressed & SDL_BUTTON_LEFT_MASK) + { + map.tiles[cursor_map_y][cursor_map_x].flags |= TileFlags.blocks_walk | TileFlags.blocks_sight; + recalc_fov = true; + } + else if (buttons_pressed & SDL_BUTTON_RIGHT_MASK) + { + map.tiles[cursor_map_y][cursor_map_x].flags &= ~(TileFlags.blocks_walk | TileFlags.blocks_sight); + recalc_fov = true; + } + } + + while (SDL_PollEvent(&e) != 0) + { + i32 dx; + i32 dy; + bool doMove = false; + + if (e.type == SDL_EventType.SDL_QUIT) { + run = false; + } + else if (e.type == SDL_EventType.SDL_KEYDOWN) + { + SDL_KeyboardEvent* key = cast(SDL_KeyboardEvent*)&e; + if (key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_ESCAPE) { + run = false; + } + + if (key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_D) { + doMove = true; + ++dx; + } else if (key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_A) { + doMove = true; + --dx; + } else if (key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_W) { + doMove = true; + --dy; + } else if (key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_S) { + doMove = true; + ++dy; + } else if (key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_LEFTBRACKET) { + if (map.view_radius > 0) { + --map.view_radius; + recalc_fov = true; + } + } else if (key.keysym.scancode == SDL_Scancode.SDL_SCANCODE_RIGHTBRACKET) { + ++map.view_radius; + recalc_fov = true; + } + } + + if (doMove && !map.blocks_walk(player.x + dx, player.y + dy)) + { + recalc_fov = true; + player.move(dx, dy); + } + } + + SDL_SetRenderDrawColor(win.sdl_renderer, 0, 0, 0, 0); + SDL_RenderClear(win.sdl_renderer); + + if (recalc_fov) { + update_fov(&map, player); + map.update_view_offset(player.x, player.y); + recalc_fov = false; + } + render_all(&win, &map, entities); + SDL_RenderPresent(win.sdl_renderer); + } + + window_destroy(&win); + ExitProcess(0); + return 0; +} diff --git a/source/sdl.vx b/source/sdl.vx new file mode 100644 index 0000000..330d534 --- /dev/null +++ b/source/sdl.vx @@ -0,0 +1,106 @@ +struct SDL_Window; +struct SDL_Renderer; +struct SDL_Texture; +struct SDL_Surface; + +void SDL_SetMainReady(); +i32 SDL_Init(u32); +void SDL_Quit(); +SDL_Window* SDL_CreateWindow(u8* title, i32 x, i32 y, i32 w, i32 h, u32 flags); +SDL_Renderer* SDL_CreateRenderer(SDL_Window* window, i32 index, u32 flags); +void SDL_DestroyRenderer(SDL_Renderer* renderer); +void SDL_DestroyWindow(SDL_Window* window); +i32 SDL_PollEvent(SDL_Event* event); +u32 SDL_GetMouseState(i32* x, i32* y); +u8 SDL_BUTTON(u8 X) { + return cast(u8)(1 << (X - 1)); +} +u8* SDL_GetError(); +i32 SDL_SetRenderDrawColor(SDL_Renderer* renderer, u8 r, u8 g, u8 b, u8 a); +i32 SDL_SetRenderDrawBlendMode(SDL_Renderer* renderer, u32 blendMode); +i32 SDL_SetTextureColorMod(SDL_Texture* texture, u8 r, u8 g, u8 b); +i32 SDL_RenderDrawRect(SDL_Renderer* renderer, SDL_Rect* rect); +i32 SDL_RenderFillRect(SDL_Renderer* renderer, SDL_Rect* rect); + +enum : u8 { + SDL_BUTTON_LEFT = 1, + SDL_BUTTON_MIDDLE = 2, + SDL_BUTTON_RIGHT = 3, + SDL_BUTTON_X1 = 4, + SDL_BUTTON_X2 = 5, + SDL_BUTTON_LEFT_MASK = 1 << 0, + SDL_BUTTON_MIDDLE_MASK = 1 << 1, + SDL_BUTTON_RIGHT_MASK = 1 << 2, + SDL_BUTTON_X1_MASK = 1 << 3, + SDL_BUTTON_X2_MASK = 1 << 4, +} + +enum SDL_BlendMode { + SDL_BLENDMODE_NONE = 0x00000000, + SDL_BLENDMODE_BLEND = 0x00000001, + SDL_BLENDMODE_ADD = 0x00000002, + SDL_BLENDMODE_MOD = 0x00000004 +} +struct SDL_Event +{ + u32 type; + u8[52] padding; +} + +enum SDL_EventType : u32 { + SDL_FIRSTEVENT = 0, + SDL_QUIT = 0x100, + SDL_KEYDOWN = 0x300, + SDL_KEYUP = 0x301, +} + +struct SDL_KeyboardEvent { + u32 type; + u32 timestamp; + u32 windowID; + u8 state; + u8 repeat; + u8 padding2; + u8 padding3; + SDL_Keysym keysym; +} + +struct SDL_Keysym { + u32 scancode; + u32 sym; + u16 mod; + u32 unicode; +} + +enum SDL_Scancode : u32 { + SDL_SCANCODE_A = 4, + SDL_SCANCODE_D = 7, + SDL_SCANCODE_S = 22, + SDL_SCANCODE_W = 26, + SDL_SCANCODE_ESCAPE = 41, + SDL_SCANCODE_LEFTBRACKET = 47, + SDL_SCANCODE_RIGHTBRACKET = 48, +} + +enum u32 SDL_INIT_VIDEO = 0x00000020; +enum u32 SDL_RENDERER_SOFTWARE = 0x00000001; +enum u32 SDL_RENDERER_ACCELERATED = 0x00000002; +enum u32 SDL_RENDERER_PRESENTVSYNC = 0x00000004; +enum u32 SDL_RENDERER_TARGETTEXTURE = 0x00000008; +enum i32 SDL_WINDOWPOS_UNDEFINED = 0x1FFF0000; +enum u32 SDL_WINDOW_SHOWN = 0x00000004; + +SDL_Texture* SDL_CreateTextureFromSurface(SDL_Renderer* renderer, SDL_Surface* surface); +void SDL_FreeSurface(SDL_Surface*); +void SDL_DestroyTexture(SDL_Texture*); +i32 SDL_RenderClear(SDL_Renderer*); +i32 SDL_RenderCopy(SDL_Renderer*, SDL_Texture*, SDL_Rect*, SDL_Rect*); +void SDL_RenderPresent(SDL_Renderer*); + +struct SDL_Rect +{ + i32 x; + i32 y; + i32 w; + i32 h; +} diff --git a/source/sdlimage.vx b/source/sdlimage.vx new file mode 100644 index 0000000..d4c968e --- /dev/null +++ b/source/sdlimage.vx @@ -0,0 +1,13 @@ +import sdl; + +i32 IMG_Init(i32 flags); +i32 IMG_Quit(); + +SDL_Surface* IMG_Load(u8*); + +enum { + IMG_INIT_JPG = 0x00000001, + IMG_INIT_PNG = 0x00000002, + IMG_INIT_TIF = 0x00000004, + IMG_INIT_WEBP = 0x00000008, +} diff --git a/source/utils.vx b/source/utils.vx new file mode 100644 index 0000000..f60788f --- /dev/null +++ b/source/utils.vx @@ -0,0 +1,233 @@ +import kernel32; + +i32 min(i32 a, i32 b) { + if (a < b) return a; + return b; +} + +i32 max(i32 a, i32 b) { + if (a > b) return a; + return b; +} + +void print(u8[] str) { + void* handle = GetStdHandle(STD_OUTPUT_HANDLE); + u32 numWritten; + WriteConsoleA( + handle, + str.ptr, + cast(u32)str.length, + &numWritten, + null); +} + +void setRangeu8(u8* slice, i64 from, i64 to, u8 value) { + while(from < to) { + slice[from] = value; + ++from; + } +} + +void printInt(i64 i) +{ + u8[21] buf; + u8[] res; + formatInt(i, &buf, 1, &res); + print(res); +} + +void formatInt(i64 i, u8[21]* output, u32 minSize, u8[]* result) +{ + u32 numDigits = 0; + if (i == 0) + { + if (minSize == 0) + minSize = 1; + setRangeu8((*output).ptr, 21 - minSize, 21, '0'); + (*output)[20] = '0'; + numDigits = minSize; + } + else + { + bool neg = i < 0; + if (neg) { + i = -i; + } + bool overflow = i < 0; + if (overflow) + i = i64.max; + + while (i) + { + u8 c = cast(u8)('0' + i % 10); + (*output)[21 - ++numDigits] = c; + i /= 10; + } + + while (numDigits < minSize) { + (*output)[21 - ++numDigits] = '0'; + } + + if (neg) { + (*output)[21 - ++numDigits] = '-'; + } + if (overflow) { + ++(*output)[20]; + } + } + //u8[] result; + (*result).ptr = (*output).ptr + (21 - numDigits); + (*result).length = numDigits; + //return result; + //result = (*output)[$-numDigits..$] +} + +void println(u8[] str) { + print(str); + u8[2] endl; + endl[0] = '\n'; + endl[1] = '\0'; + u8[] slice; + slice.ptr = cast(u8*)&endl; + slice.length = 1; + print(endl); +} + +i64 cstrlen(u8* str) { + if (str == null) return 0; + + u8* start = str; + while(*str) + { + ++str; + } + return str - start; +} + +// +u64 rotl(u64 x, u32 k) { + return (x << k) | (x >> (64 - k)); +} + +void init_rand_state(u64[2]* state) { + state[0][0] = GetTickCount64(); + state[0][1] = 0xac32_ee10_a135_0e00; + xoroshiro128ss_next(state); + state[0][1] = GetTickCount64(); +} + +// xoroshiro128** +// init state to random values before starting +u64 xoroshiro128ss_next(u64[2]* state) { + u64 s0 = (*state)[0]; + u64 s1 = (*state)[1]; + u64 result = rotl(s0 * 5, 7) * 9; + + s1 ^= s0; + (*state)[0] = rotl(s0, 24) ^ s1 ^ (s1 << 16); // a, b + (*state)[1] = rotl(s1, 37); // c + + return result; +} + +i64 uniform(i64 a, i64 b, u64[2]* state) // inclusive interval [] +{ + i64 distance = b - a; + u64 rand_num = xoroshiro128ss_next(state); + i64 interval_pos = cast(i64)(rand_num % cast(u64)distance); + return a + interval_pos; +} + +// Line visiting algorithm +// Calls callback for all cells between starting and ending position (inclusive) +// Passes userData as first arg of callback +void tran_thong(i32 xstart, i32 ystart, i32 xend, i32 yend, bool function(void*, i32, i32) callback, void* userData) +{ + i32 x = xstart; + i32 y = ystart; + + if (callback(userData, x, y)) return; + + i32 deltax; + i32 signdx; + if (xend >= xstart) { + deltax = xend - xstart; + signdx = 1; + } else { + deltax = xstart - xend; + signdx = -1; + } + + i32 deltay; + i32 signdy; + if (yend >= ystart) { + deltay = yend - ystart; + signdy = 1; + } else { + deltay = ystart - yend; + signdy = -1; + } + + i32 test; + if (signdy == -1) + test = -1; + else + test = 0; + + if (deltax >= deltay) { + test = (deltax + test) >> 1; + for (i32 i = 1; i < deltax; ++i) { + test -= deltay; + x += signdx; + if (test < 0) { + y += signdy; + test += deltax; + } + if (callback(userData, x, y)) return; + } + } else { + test = (deltay + test) >> 1; + for (i32 i = 1; i < deltay; ++i) { + test -= deltax; + y += signdy; + if (test < 0) { + x += signdx; + test += deltay; + } + if (callback(userData, x, y)) return; + } + } + if (callback(userData, xend, yend)) return; +} + +// Bresenham’s circle drawing algorithm +void visit_circle(i32 xcenter, i32 ycenter, i32 radius, void function(void*, i32, i32) callback, void* userData) +{ + i32 x = 0; + i32 y = radius; + i32 decision = 3 - (2 * radius); // initial decision parameter + while (x <= y) + { + callback(userData, y + xcenter, x + ycenter); // octet 1 + callback(userData, x + xcenter, y + ycenter); // octet 2 + callback(userData, -x + xcenter, y + ycenter); // octet 3 + callback(userData, -y + xcenter, x + ycenter); // octet 4 + callback(userData, -y + xcenter, -x + ycenter); // octet 5 + callback(userData, -x + xcenter, -y + ycenter); // octet 6 + callback(userData, x + xcenter, -y + ycenter); // octet 7 + callback(userData, y + xcenter, -x + ycenter); // octet 8 + + // next decision parameter + if (decision < 0) + { + decision = decision + 4 * x + 6; + ++x; + } + else + { + decision = decision + 4 * (x - y) + 10; + ++x; + --y; + } + } +}