Created
April 25, 2019 21:08
-
-
Save edubart/d01c5c63e6733792670fa770ecf74740 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- import SDL headers | |
| !!cdefine 'SDL_DISABLE_IMMINTRIN_H' | |
| !!cinclude '<SDL2/SDL.h>' | |
| !!linklib 'SDL2' | |
| -- import SDL structures | |
| local SDL_Event = @record { | |
| type: uint32, | |
| padding: array<byte, 56> | |
| } | |
| local SDL_Keysym = @record { | |
| scancode: cint, | |
| sym: int32, | |
| mod: uint16, | |
| unused: uint32 | |
| } | |
| local SDL_KeyboardEvent = @record { | |
| type: uint32, | |
| timestamp: uint32, | |
| windowID: uint32, | |
| state: uint8, | |
| repeated: uint8, | |
| padding: uint16, | |
| keysym: SDL_Keysym | |
| } | |
| local SDL_Rect = @record { | |
| x: cint, y: cint, | |
| w: cint, h: cint, | |
| }; | |
| -- import SDL pointers | |
| local SDL_Window = @pointer<record{}> | |
| local SDL_Renderer = @pointer<record{}> | |
| -- import SDL constants | |
| local SDL_INIT_VIDEO: uint32 !cimport | |
| local SDL_WINDOWPOS_UNDEFINED: cint !cimport | |
| local SDL_WINDOW_OPENGL: uint32 !cimport | |
| local SDL_QUIT: uint32 !cimport | |
| local SDL_KEYDOWN: uint32 !cimport | |
| local SDLK_UP: int32 !cimport | |
| local SDLK_DOWN: int32 !cimport | |
| local SDLK_LEFT: int32 !cimport | |
| local SDLK_RIGHT: int32 !cimport | |
| local SDL_RENDERER_ACCELERATED: uint32 !cimport | |
| local SDL_RENDERER_PRESENTVSYNC: uint32 !cimport | |
| -- import SDL functions | |
| local function SDL_Init(flags: uint32): int32 !cimport end | |
| local function SDL_CreateWindow(title: cstring, x: cint, y: cint, w: cint, h: cint, flags: uint32): SDL_Window !cimport end | |
| local function SDL_Quit() !cimport end | |
| local function SDL_DestroyWindow(window: SDL_Window) !cimport end | |
| local function SDL_Delay(ms: uint32) !cimport end | |
| local function SDL_PollEvent(event: pointer<SDL_Event>): int32 !cimport end | |
| local function SDL_WaitEvent(event: pointer<SDL_Event>): int32 !cimport end | |
| local function SDL_CreateRenderer(window: SDL_Window, index: cint, flags: uint32): SDL_Renderer !cimport end | |
| local function SDL_DestroyRenderer(renderer: SDL_Renderer) !cimport end | |
| local function SDL_RenderPresent(renderer: SDL_Renderer) !cimport end | |
| local function SDL_RenderClear(renderer: SDL_Renderer) !cimport end | |
| local function SDL_SetRenderDrawColor(renderer: SDL_Renderer, r: uint8, g: uint8, b: uint8, a: uint8): cint !cimport end | |
| local function SDL_RenderFillRect(renderer: SDL_Renderer, rect: pointer<SDL_Rect>): cint !cimport end | |
| local function rand(): int32 !cimport('rand') end | |
| -- game types | |
| local Point2D = @record{x: integer, y: integer} | |
| local Direction = @enum<uint8>{NONE=0_u8, UP, DOWN, RIGHT, LEFT} | |
| local Color = @record{r: uint8, g: uint8, b: uint8} | |
| -- game constants | |
| local const TILE_SIZE = 16 | |
| local const SCREEN_SIZE = 512 | |
| local const MAP_SIZE = 32 --SCREEN_SIZE / TILE_SIZE | |
| local COLOR_PINK = Color{r=255, g=0, b=255} | |
| local COLOR_GREEN = Color{r=0, g=255, b=0} | |
| local COLOR_BLACK = Color{r=0, g=0, b=0} | |
| -- game state variables | |
| local renderer | |
| local movedir = Direction.UP | |
| local quit = false | |
| local movecounter = 0 | |
| local headpos, tailpos, applepos | |
| local TileMap = @array<array<Direction, MAP_SIZE>, MAP_SIZE> | |
| local tiles: TileMap | |
| -- poll input events | |
| local function poll_events() | |
| local event: SDL_Event | |
| while SDL_PollEvent(&event) do | |
| switch event.type | |
| case SDL_QUIT then | |
| quit = true | |
| case SDL_KEYDOWN then | |
| local kevent = @pointer<SDL_KeyboardEvent>(&event) | |
| switch kevent.keysym.sym | |
| case SDLK_UP then | |
| if movedir ~= Direction.DOWN then | |
| movedir = Direction.UP | |
| end | |
| case SDLK_DOWN then | |
| if movedir ~= Direction.UP then | |
| movedir = Direction.DOWN | |
| end | |
| case SDLK_RIGHT then | |
| if movedir ~= Direction.LEFT then | |
| movedir = Direction.RIGHT | |
| end | |
| case SDLK_LEFT then | |
| if movedir ~= Direction.RIGHT then | |
| movedir = Direction.LEFT | |
| end | |
| end | |
| end | |
| end | |
| end | |
| local function move_point(pos: Point2D, dir: Direction) | |
| switch dir | |
| case Direction.UP then | |
| pos.y = pos.y - 1 | |
| case Direction.DOWN then | |
| pos.y = pos.y + 1 | |
| case Direction.RIGHT then | |
| pos.x = pos.x + 1 | |
| case Direction.LEFT then | |
| pos.x = pos.x - 1 | |
| end | |
| return pos | |
| end | |
| local function respawn_apple() | |
| -- respawn until there is no collision with its body | |
| repeat | |
| applepos = Point2D{ | |
| x = rand() % MAP_SIZE, | |
| y = rand() % MAP_SIZE | |
| } | |
| until tiles[applepos.x][applepos.y] == Direction.NONE | |
| end | |
| local function init_game() | |
| for i=0,MAP_SIZE-1 do | |
| for j=0,MAP_SIZE-1 do | |
| tiles[i][j] = Direction.NONE | |
| end | |
| end | |
| headpos = Point2D{x=MAP_SIZE//2, y=MAP_SIZE//2} | |
| tailpos = Point2D{x=headpos.x, y=headpos.y+1} | |
| tiles[headpos.x][headpos.y] = Direction.UP | |
| tiles[tailpos.x][tailpos.y] = Direction.UP | |
| movedir = Direction.UP | |
| respawn_apple() | |
| end | |
| local function poll_game() | |
| movecounter = movecounter + 1 | |
| if movecounter < 8 then return end | |
| movecounter = 0 | |
| tiles[headpos.x][headpos.y] = movedir | |
| headpos = move_point(headpos, movedir) | |
| -- check collision with map boundaries | |
| if headpos.x >= MAP_SIZE or headpos.y >= MAP_SIZE or | |
| headpos.x < 0 or headpos.y < 0 then | |
| init_game() -- game over | |
| return | |
| end | |
| -- check collisions with its body | |
| if tiles[headpos.x][headpos.y] ~= Direction.NONE then | |
| init_game() | |
| return | |
| end | |
| tiles[headpos.x][headpos.y] = movedir | |
| -- check collision with apple | |
| if headpos.x == applepos.x and headpos.y == applepos.y then | |
| respawn_apple() | |
| else | |
| -- eat tail | |
| local taildir = tiles[tailpos.x][tailpos.y] | |
| tiles[tailpos.x][tailpos.y] = Direction.NONE | |
| tailpos = move_point(tailpos, taildir) | |
| end | |
| end | |
| local function draw_background(color: Color) | |
| SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, 255) | |
| SDL_RenderClear(renderer) | |
| end | |
| local function draw_tile(pos: Point2D, color: Color) | |
| local rect = SDL_Rect{ | |
| x = @int32(pos.x * TILE_SIZE), | |
| y = @int32(pos.y * TILE_SIZE), | |
| w = @int32(TILE_SIZE), | |
| h = @int32(TILE_SIZE) | |
| } | |
| SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, 255) | |
| SDL_RenderFillRect(renderer, &rect) | |
| end | |
| local function draw_snake() | |
| -- draw apple | |
| draw_tile(applepos, COLOR_PINK) | |
| -- draw the snake body | |
| for x=0,MAP_SIZE-1 do | |
| for y=0,MAP_SIZE-1 do | |
| if tiles[x][y] ~= Direction.NONE then | |
| -- snake is present at this tile | |
| draw_tile(Point2D{x=x, y=y}, COLOR_GREEN) | |
| end | |
| end | |
| end | |
| end | |
| local function draw() | |
| draw_background(COLOR_BLACK) | |
| draw_snake() | |
| SDL_RenderPresent(renderer) | |
| end | |
| local function go() | |
| SDL_Init(SDL_INIT_VIDEO) | |
| -- create window | |
| local window = SDL_CreateWindow("An SDL2 Window", | |
| SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, | |
| @int32(SCREEN_SIZE), @int32(SCREEN_SIZE), SDL_WINDOW_OPENGL) | |
| assert(window, "Could not create window") | |
| -- create renderer | |
| renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC) | |
| assert(renderer, "Could not create renderer") | |
| init_game() | |
| -- draw loop | |
| repeat | |
| poll_events() | |
| poll_game() | |
| draw() | |
| until quit | |
| -- cleanup and finish | |
| SDL_DestroyWindow(window) | |
| SDL_DestroyRenderer(renderer) | |
| SDL_Quit() | |
| end | |
| go() | |
| --TODO: rinitialize records to zero | |
| --TODO: const for complex types (Color) | |
| --TODO: unions | |
| --TODO: syntax sugar for pointer, syntax sugar for array | |
| --TODO: function type overload | |
| --TODO: multiple returns | |
| --TODO: var& | |
| --TODO: integer constants should not warn if the value is in range |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment