├── example ├── blackjack.bat ├── Vera.ttf ├── blackjack_images │ ├── 1.png │ ├── 10.png │ ├── 11.png │ ├── 12.png │ ├── 13.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ ├── 9.png │ ├── card.png │ ├── face_jack.png │ ├── face_king.png │ ├── face_queen.png │ ├── mini_club.png │ ├── mini_heart.png │ ├── mini_spade.png │ ├── pip_club.png │ ├── pip_heart.png │ ├── pip_spade.png │ ├── mini_diamond.png │ ├── pip_diamond.png │ └── card_face_down.png └── blackjack.lua ├── engine.zip ├── LICENSE ├── README.md └── engine.c /example/blackjack.bat: -------------------------------------------------------------------------------- 1 | engine.exe blackjack.lua 2 | pause -------------------------------------------------------------------------------- /engine.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/engine.zip -------------------------------------------------------------------------------- /example/Vera.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/Vera.ttf -------------------------------------------------------------------------------- /example/blackjack_images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/1.png -------------------------------------------------------------------------------- /example/blackjack_images/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/10.png -------------------------------------------------------------------------------- /example/blackjack_images/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/11.png -------------------------------------------------------------------------------- /example/blackjack_images/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/12.png -------------------------------------------------------------------------------- /example/blackjack_images/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/13.png -------------------------------------------------------------------------------- /example/blackjack_images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/2.png -------------------------------------------------------------------------------- /example/blackjack_images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/3.png -------------------------------------------------------------------------------- /example/blackjack_images/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/4.png -------------------------------------------------------------------------------- /example/blackjack_images/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/5.png -------------------------------------------------------------------------------- /example/blackjack_images/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/6.png -------------------------------------------------------------------------------- /example/blackjack_images/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/7.png -------------------------------------------------------------------------------- /example/blackjack_images/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/8.png -------------------------------------------------------------------------------- /example/blackjack_images/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/9.png -------------------------------------------------------------------------------- /example/blackjack_images/card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/card.png -------------------------------------------------------------------------------- /example/blackjack_images/face_jack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/face_jack.png -------------------------------------------------------------------------------- /example/blackjack_images/face_king.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/face_king.png -------------------------------------------------------------------------------- /example/blackjack_images/face_queen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/face_queen.png -------------------------------------------------------------------------------- /example/blackjack_images/mini_club.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/mini_club.png -------------------------------------------------------------------------------- /example/blackjack_images/mini_heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/mini_heart.png -------------------------------------------------------------------------------- /example/blackjack_images/mini_spade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/mini_spade.png -------------------------------------------------------------------------------- /example/blackjack_images/pip_club.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/pip_club.png -------------------------------------------------------------------------------- /example/blackjack_images/pip_heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/pip_heart.png -------------------------------------------------------------------------------- /example/blackjack_images/pip_spade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/pip_spade.png -------------------------------------------------------------------------------- /example/blackjack_images/mini_diamond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/mini_diamond.png -------------------------------------------------------------------------------- /example/blackjack_images/pip_diamond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/pip_diamond.png -------------------------------------------------------------------------------- /example/blackjack_images/card_face_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santoslove/mini-lua-game-framework/HEAD/example/blackjack_images/card_face_down.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A mini Lua game framework 2 | 3 | This is: 4 | * a simple example of SDL in use 5 | * a simple example of Lua's C API in use 6 | * a simple example of a game framework 7 | 8 | Unfortunately, it's written badly and I don't really know how to use the Lua and SDL APIs or how to program in C, and it probably has lots of bugs. 9 | 10 | But, it still might be fun for someone out there to read and experiment with. 11 | 12 | ## Compilation instructions for Windows 13 | 14 | 1. Install MinGW 15 | 2. Unzip engine.zip 16 | 3. Run compile.bat 17 | 18 | ## The API 19 | 20 | ### Callbacks 21 | 22 | ```lua 23 | engine.frame() -- Called 60 times per second 24 | engine.down(string) -- When a key or mouse button is pressed 25 | engine.up(string) -- When a key or mouse button is released 26 | ``` 27 | 28 | ### Window settings 29 | 30 | ```lua 31 | engine.width = number 32 | engine.height = number 33 | engine.scale = number -- Window and graphics scale up by this number 34 | ``` 35 | 36 | ### Input 37 | 38 | ```lua 39 | boolean = engine.isDown(string) -- True if the given key or mouse button is down 40 | number = engine.mouseX() 41 | number = engine.mouseY() 42 | ``` 43 | 44 | ### Graphics 45 | 46 | ```lua 47 | engine.image(path, x, y, angle, scaleX, scaleY, originX, originY, r, g, b, a) 48 | engine.line(x1, y1, x2, y2, r, g, b, a) 49 | engine.rectangle(x, y, width, height, r, g, b, a) 50 | engine.text(message, fontPath, fontSize, x, y, r, g, b, a) 51 | ``` 52 | 53 | ### Sound 54 | 55 | ```lua 56 | engine.sound(path) 57 | engine.loop(path) 58 | engine.stopLoop() 59 | ``` 60 | 61 | ## Example 62 | 63 | ```lua 64 | engine.width = 400 65 | engine.height = 300 66 | engine.scale = 2 67 | 68 | player = {x = 0, y = 0} 69 | 70 | function engine.frame() 71 | if engine.isDown('right') or engine.isDown('d') then 72 | player.x = player.x + 1 73 | end 74 | 75 | if engine.isDown('mouseLeft') then 76 | player.y = player.y + 1 77 | end 78 | 79 | engine.image('player.png', player.x, player.y) 80 | end 81 | 82 | function engine.down(input) 83 | if input == 'space' then 84 | player.x = 0 85 | end 86 | end 87 | ``` 88 | -------------------------------------------------------------------------------- /example/blackjack.lua: -------------------------------------------------------------------------------- 1 | engine.width = 400 2 | engine.height = 300 3 | engine.scale = 2 4 | 5 | images = {} 6 | 7 | function takeCard(hand) 8 | table.insert(hand, table.remove(deck, math.random(#deck))) 9 | end 10 | 11 | function getTotal(hand) 12 | local total = 0 13 | local hasAce = false 14 | 15 | for cardIndex, card in ipairs(hand) do 16 | if card.rank > 10 then 17 | total = total + 10 18 | else 19 | total = total + card.rank 20 | end 21 | 22 | if card.rank == 1 then 23 | hasAce = true 24 | end 25 | end 26 | 27 | if hasAce and total <= 11 then 28 | total = total + 10 29 | end 30 | 31 | return total 32 | end 33 | 34 | buttonY = 230 35 | buttonHeight = 25 36 | 37 | buttonHitX = 10 38 | buttonHitWidth = 53 39 | 40 | buttonStandX = 70 41 | buttonStandWidth = 53 42 | 43 | buttonPlayAgainX = 10 44 | buttonPlayAgainWidth = 113 45 | 46 | function isMouseInButton(buttonX, buttonWidth) 47 | return engine.mouseX() >= buttonX 48 | and engine.mouseX() < buttonX + buttonWidth 49 | and engine.mouseY() >= buttonY 50 | and engine.mouseY() < buttonY + buttonHeight 51 | end 52 | 53 | function reset() 54 | deck = {} 55 | for suitIndex, suit in ipairs({'club', 'diamond', 'heart', 'spade'}) do 56 | for rank = 1, 13 do 57 | table.insert(deck, {suit = suit, rank = rank}) 58 | end 59 | end 60 | 61 | playerHand = {} 62 | takeCard(playerHand) 63 | takeCard(playerHand) 64 | 65 | dealerHand = {} 66 | takeCard(dealerHand) 67 | takeCard(dealerHand) 68 | 69 | roundOver = false 70 | end 71 | 72 | reset() 73 | 74 | function engine.frame() 75 | local output = {} 76 | 77 | engine.text(table.concat(output, '\n'), 'Vera.ttf', 12, 15, 15) 78 | 79 | if roundOver then 80 | table.insert(output, '') 81 | 82 | local function hasHandWon(thisHand, otherHand) 83 | return getTotal(thisHand) <= 21 84 | and ( 85 | getTotal(otherHand) > 21 86 | or getTotal(thisHand) > getTotal(otherHand) 87 | ) 88 | end 89 | 90 | if hasHandWon(playerHand, dealerHand) then 91 | table.insert(output, 'Player wins') 92 | elseif hasHandWon(dealerHand, playerHand) then 93 | table.insert(output, 'Dealer wins') 94 | else 95 | table.insert(output, 'Draw') 96 | end 97 | end 98 | 99 | local function drawCard(card, x, y) 100 | engine.image('blackjack_images/card.png', x, y) 101 | 102 | local color 103 | if card.suit == 'heart' or card.suit == 'diamond' then 104 | color = {228, 15, 100} 105 | else 106 | color = {50, 50, 50} 107 | end 108 | 109 | local cardWidth = 53 110 | local cardHeight = 73 111 | 112 | local function drawCorner(image, offsetX, offsetY) 113 | engine.image( 114 | 'blackjack_images/'..image, 115 | x + offsetX, 116 | y + offsetY, 117 | 0, 118 | 1, 119 | 1, 120 | 0, 121 | 0, 122 | color[1], 123 | color[2], 124 | color[3] 125 | ) 126 | engine.image( 127 | 'blackjack_images/'..image, 128 | x + cardWidth - offsetX, 129 | y + cardHeight - offsetY, 130 | 0, 131 | -1, 132 | -1, 133 | 0, 134 | 0, 135 | color[1], 136 | color[2], 137 | color[3] 138 | ) 139 | end 140 | 141 | drawCorner(card.rank..'.png', 3, 4) 142 | drawCorner('mini_'..card.suit..'.png', 3, 14) 143 | 144 | if card.rank > 10 then 145 | local faceImage 146 | if card.rank == 11 then 147 | faceImage = 'face_jack.png' 148 | elseif card.rank == 12 then 149 | faceImage = 'face_queen.png' 150 | elseif card.rank == 13 then 151 | faceImage = 'face_king.png' 152 | end 153 | engine.image('blackjack_images/'..faceImage, x + 12, y + 11) 154 | else 155 | local function drawPip(offsetX, offsetY, mirrorX, mirrorY) 156 | local pipImage = 'pip_'..card.suit..'.png' 157 | local pipWidth = 11 158 | engine.image( 159 | 'blackjack_images/'..pipImage, 160 | x + offsetX, 161 | y + offsetY, 162 | 0, 163 | 1, 164 | 1, 165 | 0, 166 | 0, 167 | color[1], 168 | color[2], 169 | color[3] 170 | ) 171 | if mirrorX then 172 | engine.image( 173 | 'blackjack_images/'..pipImage, 174 | x + cardWidth - offsetX - pipWidth, 175 | y + offsetY, 176 | 0, 177 | 1, 178 | 1, 179 | 0, 180 | 0, 181 | color[1], 182 | color[2], 183 | color[3] 184 | ) 185 | end 186 | if mirrorY then 187 | engine.image( 188 | 'blackjack_images/'..pipImage, 189 | x + offsetX + pipWidth, 190 | y + cardHeight - offsetY, 191 | 0, 192 | -1, 193 | -1, 194 | 0, 195 | 0, 196 | color[1], 197 | color[2], 198 | color[3] 199 | ) 200 | end 201 | if mirrorX and mirrorY then 202 | engine.image( 203 | 'blackjack_images/'..pipImage, 204 | x + cardWidth - offsetX, 205 | y + cardHeight - offsetY, 206 | 0, 207 | -1, 208 | -1, 209 | 0, 210 | 0, 211 | color[1], 212 | color[2], 213 | color[3] 214 | ) 215 | end 216 | end 217 | 218 | local xLeft = 11 219 | local xMid = 21 220 | local yTop = 7 221 | local yThird = 19 222 | local yQtr = 23 223 | local yMid = 31 224 | 225 | if card.rank == 1 then 226 | drawPip(xMid, yMid) 227 | elseif card.rank == 2 then 228 | drawPip(xMid, yTop, false, true) 229 | elseif card.rank == 3 then 230 | drawPip(xMid, yTop, false, true) 231 | drawPip(xMid, yMid) 232 | elseif card.rank == 4 then 233 | drawPip(xLeft, yTop, true, true) 234 | elseif card.rank == 5 then 235 | drawPip(xLeft, yTop, true, true) 236 | drawPip(xMid, yMid) 237 | elseif card.rank == 6 then 238 | drawPip(xLeft, yTop, true, true) 239 | drawPip(xLeft, yMid, true) 240 | elseif card.rank == 7 then 241 | drawPip(xLeft, yTop, true, true) 242 | drawPip(xLeft, yMid, true) 243 | drawPip(xMid, yThird) 244 | elseif card.rank == 8 then 245 | drawPip(xLeft, yTop, true, true) 246 | drawPip(xLeft, yMid, true) 247 | drawPip(xMid, yThird, false, true) 248 | elseif card.rank == 9 then 249 | drawPip(xLeft, yTop, true, true) 250 | drawPip(xLeft, yQtr, true, true) 251 | drawPip(xMid, yMid) 252 | elseif card.rank == 10 then 253 | drawPip(xLeft, yTop, true, true) 254 | drawPip(xLeft, yQtr, true, true) 255 | drawPip(xMid, 16, false, true) 256 | end 257 | end 258 | end 259 | 260 | local cardSpacing = 60 261 | local marginX = 10 262 | 263 | for cardIndex, card in ipairs(dealerHand) do 264 | local dealerMarginY = 30 265 | if not roundOver and cardIndex == 1 then 266 | engine.image('blackjack_images/card_face_down.png', marginX, dealerMarginY) 267 | else 268 | drawCard(card, ((cardIndex - 1) * cardSpacing) + marginX, dealerMarginY) 269 | end 270 | end 271 | 272 | for cardIndex, card in ipairs(playerHand) do 273 | drawCard(card, ((cardIndex - 1) * cardSpacing) + marginX, 140) 274 | end 275 | 276 | if roundOver then 277 | engine.text('Total: '..getTotal(dealerHand), 'Vera.ttf', 12, marginX, 10) 278 | else 279 | engine.text('Total: ?', 'Vera.ttf', 12, marginX, 10) 280 | end 281 | 282 | engine.text('Total: '..getTotal(playerHand), 'Vera.ttf', 12, marginX, 120) 283 | 284 | if roundOver then 285 | local function hasHandWon(thisHand, otherHand) 286 | return getTotal(thisHand) <= 21 287 | and ( 288 | getTotal(otherHand) > 21 289 | or getTotal(thisHand) > getTotal(otherHand) 290 | ) 291 | end 292 | 293 | local function drawWinner(message) 294 | engine.text(message, 'Vera.ttf', 12, marginX, 268, 0, 0, 0) 295 | end 296 | 297 | if hasHandWon(playerHand, dealerHand) then 298 | drawWinner('Player wins') 299 | elseif hasHandWon(dealerHand, playerHand) then 300 | drawWinner('Dealer wins') 301 | else 302 | drawWinner('Draw') 303 | end 304 | end 305 | 306 | local function drawButton(text, buttonX, buttonWidth, textOffsetX) 307 | local buttonY = 230 308 | local buttonHeight = 25 309 | 310 | if engine.mouseX() >= buttonX 311 | and engine.mouseX() < buttonX + buttonWidth 312 | and engine.mouseY() >= buttonY 313 | and engine.mouseY() < buttonY + buttonHeight then 314 | engine.rectangle(buttonX, buttonY, buttonWidth, buttonHeight, 255, 200, 50) 315 | else 316 | engine.rectangle(buttonX, buttonY, buttonWidth, buttonHeight, 255, 120, 30) 317 | end 318 | engine.text(text, 'Vera.ttf', 12, buttonX + textOffsetX, buttonY + 6, 1, 1, 1) 319 | end 320 | 321 | if roundOver then 322 | drawButton('Play again', buttonPlayAgainX, buttonPlayAgainWidth, 24) 323 | else 324 | drawButton('Hit!', buttonHitX, buttonHitWidth, 16) 325 | drawButton('Stand', buttonStandX, buttonStandWidth, 8) 326 | end 327 | end 328 | 329 | function engine.up(input) 330 | if input == 'mouseLeft' then 331 | if not roundOver then 332 | if isMouseInButton(buttonHitX, buttonHitWidth) then 333 | takeCard(playerHand) 334 | if getTotal(playerHand) > 21 then 335 | roundOver = true 336 | end 337 | elseif isMouseInButton(buttonStandX, buttonStandWidth) then 338 | roundOver = true 339 | end 340 | 341 | if roundOver then 342 | while getTotal(dealerHand) < 17 do 343 | takeCard(dealerHand) 344 | end 345 | end 346 | elseif isMouseInButton(buttonPlayAgainX, buttonPlayAgainWidth) then 347 | reset() 348 | end 349 | end 350 | end 351 | -------------------------------------------------------------------------------- /engine.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | SDL_Texture *images[1000]; 14 | char *image_filenames[1000]; 15 | int image_count = 0; 16 | 17 | TTF_Font *fonts[1000]; 18 | char *font_filenames[1000]; 19 | int font_sizes[1000]; 20 | int font_count = 0; 21 | 22 | Mix_Chunk *sounds[1000]; 23 | char *sound_filenames[1000]; 24 | int sound_count = 0; 25 | 26 | Mix_Music *musics[1000]; 27 | char *music_filenames[1000]; 28 | int music_count = 0; 29 | 30 | int width; 31 | int height; 32 | int scale; 33 | 34 | SDL_Window *window; 35 | SDL_Renderer *renderer; 36 | 37 | int key_count = 84; 38 | char *keys[] = {"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","1","2","3","4","5","6","7","8","9","0","return","backspace","tab","space","-","=","[","]","\\",";","'","`",",",".","/","capslock","f1","f2","f3","f4","f5","f6","f7","f8","f9","f10","f11","f12","scrolllock","pause","insert","home","pageup","delete","end","pagedown","right","left","down","up","lctrl","lshift","lalt","lgui","rctrl","rshift","ralt","rgui"}; 39 | SDL_Scancode scancodes[] = {SDL_SCANCODE_A,SDL_SCANCODE_B,SDL_SCANCODE_C,SDL_SCANCODE_D,SDL_SCANCODE_E,SDL_SCANCODE_F,SDL_SCANCODE_G,SDL_SCANCODE_H,SDL_SCANCODE_I,SDL_SCANCODE_J,SDL_SCANCODE_K,SDL_SCANCODE_L,SDL_SCANCODE_M,SDL_SCANCODE_N,SDL_SCANCODE_O,SDL_SCANCODE_P,SDL_SCANCODE_Q,SDL_SCANCODE_R,SDL_SCANCODE_S,SDL_SCANCODE_T,SDL_SCANCODE_U,SDL_SCANCODE_V,SDL_SCANCODE_W,SDL_SCANCODE_X,SDL_SCANCODE_Y,SDL_SCANCODE_Z,SDL_SCANCODE_1,SDL_SCANCODE_2,SDL_SCANCODE_3,SDL_SCANCODE_4,SDL_SCANCODE_5,SDL_SCANCODE_6,SDL_SCANCODE_7,SDL_SCANCODE_8,SDL_SCANCODE_9,SDL_SCANCODE_0,SDL_SCANCODE_RETURN,SDL_SCANCODE_BACKSPACE,SDL_SCANCODE_TAB,SDL_SCANCODE_SPACE,SDL_SCANCODE_MINUS,SDL_SCANCODE_EQUALS,SDL_SCANCODE_LEFTBRACKET,SDL_SCANCODE_RIGHTBRACKET,SDL_SCANCODE_BACKSLASH,SDL_SCANCODE_SEMICOLON,SDL_SCANCODE_APOSTROPHE,SDL_SCANCODE_GRAVE,SDL_SCANCODE_COMMA,SDL_SCANCODE_PERIOD,SDL_SCANCODE_SLASH,SDL_SCANCODE_CAPSLOCK,SDL_SCANCODE_F1,SDL_SCANCODE_F2,SDL_SCANCODE_F3,SDL_SCANCODE_F4,SDL_SCANCODE_F5,SDL_SCANCODE_F6,SDL_SCANCODE_F7,SDL_SCANCODE_F8,SDL_SCANCODE_F9,SDL_SCANCODE_F10,SDL_SCANCODE_F11,SDL_SCANCODE_F12,SDL_SCANCODE_SCROLLLOCK,SDL_SCANCODE_PAUSE,SDL_SCANCODE_INSERT,SDL_SCANCODE_HOME,SDL_SCANCODE_PAGEUP,SDL_SCANCODE_DELETE,SDL_SCANCODE_END,SDL_SCANCODE_PAGEDOWN,SDL_SCANCODE_RIGHT,SDL_SCANCODE_LEFT,SDL_SCANCODE_DOWN,SDL_SCANCODE_UP,SDL_SCANCODE_LCTRL,SDL_SCANCODE_LSHIFT,SDL_SCANCODE_LALT,SDL_SCANCODE_LGUI,SDL_SCANCODE_RCTRL,SDL_SCANCODE_RSHIFT,SDL_SCANCODE_RALT,SDL_SCANCODE_RGUI}; 40 | 41 | void error (const char *string) 42 | { 43 | printf("Error: %s\n", string); 44 | exit(1); 45 | } 46 | 47 | int isDown (lua_State *L) 48 | { 49 | const Uint8 *state = SDL_GetKeyboardState(NULL); 50 | 51 | if (!lua_isstring(L, 1)) { 52 | error("isDown: needs string"); 53 | } 54 | 55 | const char *input = lua_tostring(L, 1); 56 | int boolean = 0; 57 | 58 | if ( 59 | (strcmp(input, "mouseLeft") == 0 && SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_LEFT)) || 60 | (strcmp(input, "mouseMiddle") == 0 && SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_MIDDLE)) || 61 | (strcmp(input, "mouseRight") == 0 && SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_RIGHT)) 62 | ) { 63 | boolean = 1; 64 | } 65 | 66 | for (int i = 0; i < key_count; i++) { 67 | if (strcmp(input, keys[i]) == 0) { 68 | if (state[scancodes[i]]) { 69 | boolean = 1; 70 | break; 71 | } 72 | } 73 | } 74 | 75 | lua_pushboolean(L, boolean); 76 | return 1; 77 | } 78 | 79 | int mouseX (lua_State *L) 80 | { 81 | int x; 82 | SDL_GetMouseState(&x, NULL); 83 | lua_pushnumber(L, x/scale); 84 | return 1; 85 | } 86 | 87 | int mouseY (lua_State *L) 88 | { 89 | int y; 90 | SDL_GetMouseState(NULL, &y); 91 | lua_pushnumber(L, y/scale); 92 | return 1; 93 | } 94 | 95 | int get_number(lua_State *L, int x, int d) 96 | { 97 | if (lua_isnoneornil(L, x)) { 98 | return d; 99 | } else { 100 | return lua_tonumber(L, x); 101 | } 102 | } 103 | 104 | int image (lua_State *L) 105 | { 106 | if (!lua_isstring(L, 1)) error("image: filename is not a string"); 107 | if (!lua_isnumber(L, 2) && !lua_isnoneornil(L, 2)) error("image: x is not a number"); 108 | if (!lua_isnumber(L, 3) && !lua_isnoneornil(L, 3)) error("image: y is not a number"); 109 | if (!lua_isnumber(L, 4) && !lua_isnoneornil(L, 4)) error("image: angle is not a number"); 110 | if (!lua_isnumber(L, 5) && !lua_isnoneornil(L, 5)) error("image: sx is not a number"); 111 | if (!lua_isnumber(L, 6) && !lua_isnoneornil(L, 6)) error("image: sy is not a number"); 112 | if (!lua_isnumber(L, 7) && !lua_isnoneornil(L, 7)) error("image: ox is not a number"); 113 | if (!lua_isnumber(L, 8) && !lua_isnoneornil(L, 8)) error("image: oy is not a number"); 114 | if (!lua_isnumber(L, 9) && !lua_isnoneornil(L, 9)) error("image: r is not a number"); 115 | if (!lua_isnumber(L, 10) && !lua_isnoneornil(L, 10)) error("image: g is not a number"); 116 | if (!lua_isnumber(L, 11) && !lua_isnoneornil(L, 11)) error("image: b is not a number"); 117 | if (!lua_isnumber(L, 12) && !lua_isnoneornil(L, 12)) error("image: a is not a number"); 118 | 119 | const char *filename = lua_tostring(L, 1); 120 | int x = get_number(L, 2, 0); 121 | int y = get_number(L, 3, 0); 122 | double angle = get_number(L, 4, 0); 123 | double sx = get_number(L, 5, 1); 124 | double sy = get_number(L, 6, 1); 125 | int ox = get_number(L, 7, 0); 126 | int oy = get_number(L, 8, 0); 127 | int r = get_number(L, 9, 255); 128 | int g = get_number(L, 10, 255); 129 | int b = get_number(L, 11, 255); 130 | int a = get_number(L, 12, 255); 131 | 132 | SDL_Texture *texture = NULL; 133 | 134 | for (int i = 0; i < image_count; i++) { 135 | if (strcmp(filename, image_filenames[i]) == 0) { 136 | texture = images[i]; 137 | break; 138 | } 139 | } 140 | 141 | if (!texture) { 142 | texture = IMG_LoadTexture(renderer, filename); 143 | if (!texture) { 144 | error(SDL_GetError()); 145 | } 146 | images[image_count] = texture; 147 | image_filenames[image_count] = malloc(strlen(filename) + 1); 148 | strcpy(image_filenames[image_count], filename); 149 | image_count++; 150 | } 151 | 152 | SDL_Point center; 153 | center.x = ox; 154 | center.y = oy; 155 | 156 | SDL_Rect dst; 157 | dst.x = x; 158 | dst.y = y; 159 | 160 | SDL_QueryTexture(texture, NULL, NULL, &dst.w, &dst.h); 161 | dst.w *= sx; 162 | dst.h *= sy; 163 | 164 | SDL_SetTextureColorMod(texture, r, g, b); 165 | SDL_SetTextureAlphaMod(texture, a); 166 | 167 | SDL_RenderCopyEx(renderer, texture, NULL, &dst, angle, ¢er, SDL_FLIP_NONE); 168 | } 169 | 170 | int text (lua_State *L) 171 | { 172 | const char *string = lua_tostring(L, 1); 173 | const char *filename = lua_tostring(L, 2); 174 | int size = lua_tonumber(L, 3); 175 | int x = lua_tonumber(L, 4); 176 | int y = lua_tonumber(L, 5); 177 | 178 | SDL_Color color; 179 | int a; 180 | if (!lua_isnumber(L, 6) || !lua_isnumber(L, 7) || !lua_isnumber(L, 8)) { 181 | color.r = 0; 182 | color.g = 0; 183 | color.b = 0; 184 | a = 255; 185 | } else { 186 | color.r = lua_tonumber(L, 6); 187 | color.g = lua_tonumber(L, 7); 188 | color.b = lua_tonumber(L, 8); 189 | if (!lua_isnumber(L, 9)) { 190 | a = 255; 191 | } else { 192 | a = lua_tonumber(L, 9); 193 | } 194 | } 195 | 196 | TTF_Font *font = NULL; 197 | for (int i = 0; i < font_count; i++) { 198 | if (strcmp(filename, font_filenames[i]) == 0 && font_sizes[i] == size) { 199 | font = fonts[i]; 200 | break; 201 | } 202 | } 203 | 204 | if (!font) { 205 | font = TTF_OpenFont(filename, size); 206 | if (!font) { 207 | error("Couldn't open font"); 208 | } 209 | fonts[font_count] = font; 210 | font_filenames[font_count] = malloc(strlen(filename) + 1); 211 | strcpy(font_filenames[font_count], filename); 212 | font_sizes[font_count] = size; 213 | font_count++; 214 | } 215 | 216 | SDL_Surface *surface = TTF_RenderText_Blended(font, string, color); 217 | SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface); 218 | 219 | SDL_Rect dst; 220 | dst.x = x; 221 | dst.y = y; 222 | 223 | SDL_QueryTexture(texture, NULL, NULL, &dst.w, &dst.h); 224 | SDL_SetTextureAlphaMod(texture, a); 225 | SDL_RenderCopy(renderer, texture, NULL, &dst); 226 | SDL_FreeSurface(surface); 227 | SDL_DestroyTexture(texture); 228 | } 229 | 230 | int line (lua_State *L) 231 | { 232 | if (!lua_isnumber(L, 1)) error("line: x1 is not a number"); 233 | if (!lua_isnumber(L, 2)) error("line: y1 is not a number"); 234 | if (!lua_isnumber(L, 3)) error("line: x2 is not a number"); 235 | if (!lua_isnumber(L, 4)) error("line: y2 is not a number"); 236 | 237 | int x1 = lua_tonumber(L, 1); 238 | int y1 = lua_tonumber(L, 2); 239 | int x2 = lua_tonumber(L, 3); 240 | int y2 = lua_tonumber(L, 4); 241 | 242 | int r; 243 | int g; 244 | int b; 245 | int a; 246 | 247 | if (!lua_isnumber(L, 5) || !lua_isnumber(L, 6) || !lua_isnumber(L, 7)) { 248 | r = 0; 249 | g = 0; 250 | b = 0; 251 | a = 255; 252 | } else { 253 | r = lua_tonumber(L, 5); 254 | g = lua_tonumber(L, 6); 255 | b = lua_tonumber(L, 7); 256 | if (!lua_isnumber(L, 8)) { 257 | a = 255; 258 | } else { 259 | a = lua_tonumber(L, 8); 260 | } 261 | } 262 | 263 | SDL_SetRenderDrawColor(renderer, r, g, b, a); 264 | SDL_RenderDrawLine(renderer, x1, y1, x2, y2); 265 | } 266 | 267 | int rectangle (lua_State *L) 268 | { 269 | if (!lua_isnumber(L, 1)) error("rectangle: x is not a number"); 270 | if (!lua_isnumber(L, 2)) error("rectangle: y is not a number"); 271 | if (!lua_isnumber(L, 3)) error("rectangle: width is not a number"); 272 | if (!lua_isnumber(L, 4)) error("rectangle: height is not a number"); 273 | 274 | int x = lua_tonumber(L, 1); 275 | int y = lua_tonumber(L, 2); 276 | int w = lua_tonumber(L, 3); 277 | int h = lua_tonumber(L, 4); 278 | 279 | int r; 280 | int g; 281 | int b; 282 | int a; 283 | 284 | if (!lua_isnumber(L, 5) || !lua_isnumber(L, 6) || !lua_isnumber(L, 7)) { 285 | r = 0; 286 | g = 0; 287 | b = 0; 288 | a = 255; 289 | } else { 290 | r = lua_tonumber(L, 5); 291 | g = lua_tonumber(L, 6); 292 | b = lua_tonumber(L, 7); 293 | if (lua_isnoneornil(L, 8)) { 294 | a = 255; 295 | } else { 296 | a = lua_tonumber(L, 8); 297 | } 298 | } 299 | 300 | SDL_Rect rect; 301 | rect.x = x; 302 | rect.y = y; 303 | rect.w = w; 304 | rect.h = h; 305 | 306 | SDL_SetRenderDrawColor(renderer, r, g, b, a); 307 | SDL_RenderFillRect(renderer, &rect); 308 | } 309 | 310 | int sound (lua_State *L) 311 | { 312 | const char *filename = lua_tostring(L, 1); 313 | Mix_Chunk *chunk = NULL; 314 | 315 | for (int i = 0; i < sound_count; i++) { 316 | if (strcmp(filename, sound_filenames[i]) == 0) { 317 | chunk = sounds[i]; 318 | break; 319 | } 320 | } 321 | 322 | if (!chunk) { 323 | chunk = Mix_LoadWAV(filename); 324 | if (!chunk) { 325 | error("sound: Couldn't open sound file"); 326 | } 327 | sounds[sound_count] = chunk; 328 | sound_filenames[sound_count] = malloc(strlen(filename) + 1); 329 | strcpy(sound_filenames[sound_count], filename); 330 | sound_count++; 331 | } 332 | 333 | Mix_PlayChannel(-1, chunk, 0); 334 | } 335 | 336 | int loop (lua_State *L) 337 | { 338 | Mix_HaltMusic(); 339 | 340 | const char *filename = lua_tostring(L, 1); 341 | Mix_Music *music = NULL; 342 | 343 | for (int i = 0; i < music_count; i++) { 344 | if (strcmp(filename, music_filenames[i]) == 0) { 345 | music = musics[i]; 346 | break; 347 | } 348 | } 349 | 350 | if (!music) { 351 | music = Mix_LoadMUS(filename); 352 | if (!music) { 353 | error("loop: Couldn't open file"); 354 | } 355 | musics[music_count] = music; 356 | music_filenames[music_count] = malloc(strlen(filename) + 1); 357 | strcpy(music_filenames[music_count], filename); 358 | music_count++; 359 | } 360 | 361 | Mix_PlayMusic(music, -1); 362 | } 363 | 364 | int stopLoop (lua_State *L) 365 | { 366 | Mix_HaltMusic(); 367 | } 368 | 369 | int traceback (lua_State *L) { 370 | const char *message = lua_tostring(L, 1); 371 | 372 | if (message) { 373 | luaL_traceback(L, L, message, 1); 374 | } else if (!lua_isnoneornil(L, 1)) { 375 | if (!luaL_callmeta(L, 1, "__tostring")) { 376 | lua_pushliteral(L, "(no error message)"); 377 | } 378 | } 379 | 380 | return 1; 381 | } 382 | 383 | int call_mouse_function (lua_State *L, char *g, char *l) { 384 | lua_pushcfunction(L, traceback); 385 | 386 | lua_getglobal(L, "engine"); 387 | lua_getfield(L, -1, g); 388 | 389 | if (!lua_isfunction(L, -1)) { 390 | lua_pop(L, 3); 391 | return 0; 392 | } 393 | 394 | lua_pushstring(L, l); 395 | 396 | if (lua_pcall(L, 1, 0, -4)) { 397 | printf("%s", lua_tostring(L, -1)); 398 | lua_pop(L, 3); 399 | return 1; 400 | } 401 | 402 | lua_pop(L, 2); 403 | 404 | return 0; 405 | } 406 | 407 | int call_key_function (lua_State *L, SDL_Event event, char *g) { 408 | lua_pushcfunction(L, traceback); 409 | 410 | lua_getglobal(L, "engine"); 411 | lua_getfield(L, -1, g); 412 | 413 | if (!lua_isfunction(L, -1)) { 414 | lua_pop(L, 3); 415 | return 0; 416 | } else { 417 | for (int i = 0; i < key_count; i++) { 418 | if (event.key.keysym.scancode == scancodes[i]) { 419 | lua_pushstring(L, keys[i]); 420 | if (lua_pcall(L, 1, 0, -4)) { 421 | printf("%s", lua_tostring(L, -1)); 422 | lua_pop(L, 3); 423 | return 1; 424 | } else { 425 | lua_pop(L, 2); 426 | return 0; 427 | } 428 | } 429 | } 430 | } 431 | 432 | lua_pop(L, 3); 433 | 434 | return 0; 435 | } 436 | 437 | int get_setting (lua_State *L, char *g, int default_) { 438 | lua_getglobal(L, "engine"); 439 | lua_getfield(L, -1, g); 440 | int number = lua_tonumber(L, -1); 441 | lua_pop(L, 2); 442 | if (number == 0) { 443 | return default_; 444 | } else { 445 | return number; 446 | } 447 | } 448 | 449 | int main (int argc, char **argv) 450 | { 451 | if (argc < 2) { 452 | printf("usage: engine file.lua\n"); 453 | return 1; 454 | } 455 | 456 | SDL_Init(SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO); 457 | Mix_Init(MIX_INIT_MP3 | MIX_INIT_OGG); 458 | Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048); 459 | TTF_Init(); 460 | 461 | lua_State *L = luaL_newstate(); 462 | 463 | luaL_openlibs(L); 464 | 465 | lua_getglobal(L, "math"); 466 | lua_getfield(L, -1, "randomseed"); 467 | lua_pushnumber(L, time(NULL)); 468 | lua_call(L, 1, 0); 469 | lua_getfield(L, -1, "random"); 470 | lua_call(L, 0, 0); 471 | lua_getfield(L, -1, "random"); 472 | lua_call(L, 0, 0); 473 | lua_pop(L, 1); 474 | 475 | lua_newtable(L); 476 | 477 | lua_pushcfunction(L, isDown); 478 | lua_setfield(L, -2, "isDown"); 479 | lua_pushcfunction(L, text); 480 | lua_setfield(L, -2, "text"); 481 | lua_pushcfunction(L, isDown); 482 | lua_setfield(L, -2, "isDown"); 483 | lua_pushcfunction(L, mouseX); 484 | lua_setfield(L, -2, "mouseX"); 485 | lua_pushcfunction(L, mouseY); 486 | lua_setfield(L, -2, "mouseY"); 487 | lua_pushcfunction(L, image); 488 | lua_setfield(L, -2, "image"); 489 | lua_pushcfunction(L, text); 490 | lua_setfield(L, -2, "text"); 491 | lua_pushcfunction(L, line); 492 | lua_setfield(L, -2, "line"); 493 | lua_pushcfunction(L, rectangle); 494 | lua_setfield(L, -2, "rectangle"); 495 | lua_pushcfunction(L, sound); 496 | lua_setfield(L, -2, "sound"); 497 | lua_pushcfunction(L, loop); 498 | lua_setfield(L, -2, "loop"); 499 | lua_pushcfunction(L, stopLoop); 500 | lua_setfield(L, -2, "stopLoop"); 501 | 502 | lua_setglobal(L, "engine"); 503 | 504 | SDL_assert(lua_gettop(L) == 0); 505 | 506 | lua_pushcfunction(L, traceback); 507 | 508 | if (luaL_loadfile(L, argv[1]) || lua_pcall(L, 0, 0, -2)) { 509 | printf("Cannot run script: %s", lua_tostring(L, -1)); 510 | lua_pop(L, 2); 511 | return 1; 512 | } 513 | 514 | lua_pop(L, 1); 515 | SDL_assert(lua_gettop(L) == 0); 516 | 517 | lua_getglobal(L, "engine"); 518 | lua_getfield(L, -1, "frame"); 519 | if (!lua_isfunction(L, -1)) { 520 | printf("No 'frame' function"); 521 | return 1; 522 | } 523 | lua_pop(L, 2); 524 | 525 | width = get_setting(L, "width", 500); 526 | height = get_setting(L, "height", 500); 527 | scale = get_setting(L, "scale", 1); 528 | 529 | window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width * scale, height * scale, SDL_WINDOW_SHOWN); 530 | renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); 531 | 532 | SDL_RenderSetScale(renderer, scale, scale); 533 | SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); 534 | 535 | SDL_Event event; 536 | 537 | int start_time = SDL_GetTicks(); 538 | int end_time; 539 | int delay; 540 | int call_result; 541 | 542 | while (1) { 543 | call_result = 0; 544 | while (SDL_PollEvent(&event)) { 545 | if (event.type == SDL_QUIT) { 546 | return 0; 547 | } else if (event.type == SDL_KEYDOWN && !event.key.repeat) { 548 | if (event.key.keysym.sym == SDLK_ESCAPE) { 549 | return 0; 550 | } 551 | call_result = call_key_function(L, event, "down"); 552 | } else if (event.type == SDL_KEYUP) { 553 | call_result = call_key_function(L, event, "up"); 554 | } else if (event.type == SDL_MOUSEBUTTONDOWN) { 555 | if (event.button.button == SDL_BUTTON_LEFT) { 556 | call_result = call_mouse_function(L, "down", "mouseLeft"); 557 | } else if (event.button.button == SDL_BUTTON_MIDDLE) { 558 | call_result = call_mouse_function(L, "down", "mouseMiddle"); 559 | } else if (event.button.button == SDL_BUTTON_RIGHT) { 560 | call_result = call_mouse_function(L, "down", "mouseRight"); 561 | } 562 | } else if (event.type == SDL_MOUSEBUTTONUP) { 563 | if (event.button.button == SDL_BUTTON_LEFT) { 564 | call_result = call_mouse_function(L, "up", "mouseLeft"); 565 | } else if (event.button.button == SDL_BUTTON_MIDDLE) { 566 | call_result = call_mouse_function(L, "up", "mouseMiddle"); 567 | } else if (event.button.button == SDL_BUTTON_RIGHT) { 568 | call_result = call_mouse_function(L, "up", "mouseRight"); 569 | } 570 | } else if (event.type == SDL_MOUSEWHEEL) { 571 | if (event.wheel.y < 0) { 572 | call_result = call_mouse_function(L, "down", "mouseWheelDown"); 573 | } else if (event.wheel.y > 0) { 574 | call_result = call_mouse_function(L, "down", "mouseWheelUp"); 575 | } 576 | } 577 | } 578 | 579 | if (call_result == 1) { 580 | return 1; 581 | } 582 | 583 | SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); 584 | SDL_RenderClear(renderer); 585 | SDL_assert(lua_gettop(L) == 0); 586 | lua_pushcfunction(L, traceback); 587 | 588 | lua_getglobal(L, "engine"); 589 | lua_getfield(L, -1, "frame"); 590 | if (lua_pcall(L, 0, 0, -2)) { 591 | printf("Cannot run script: %s", lua_tostring(L, -1)); 592 | return 1; 593 | } else { 594 | lua_pop(L, 2); 595 | SDL_assert(lua_gettop(L) == 0); 596 | SDL_RenderPresent(renderer); 597 | end_time = SDL_GetTicks(); 598 | delay = 33 - end_time + start_time; 599 | start_time = end_time; 600 | if (delay > 0) { 601 | SDL_Delay(delay); 602 | } 603 | SDL_assert(lua_gettop(L) == 0); 604 | } 605 | } 606 | 607 | SDL_assert(lua_gettop(L) == 0); 608 | return 0; 609 | } --------------------------------------------------------------------------------