Extend grogs, better example program

This commit is contained in:
2025-09-21 20:41:09 +10:00
parent 52e95e987f
commit 7e76d473e7
5 changed files with 596 additions and 91 deletions

View File

@@ -1,6 +1,6 @@
# grogs (Ground graphics) library
This library uses SDL3 to add graphics support to Ground.
This library uses SDL3 to add graphics support to Ground, including window management, event handling, and simple 2D drawing.
## Compiling
@@ -10,50 +10,181 @@ First, ensure SDL3 is installed on your system. Then:
g++ -shared -fPIC -lSDL3 -o grogs.so grogs.cpp
```
Or with pkg-config:
```bash
g++ -shared -fPIC $(pkg-config --cflags --libs sdl3) -o grogs.so grogs.cpp
```
## Functions
### fun -int !initSDL -string &windowTitle -int &width -int &height
### Window Management
This function initialises SDL for use in Grogs. **This must be run before trying to run any other Grogs functions!**
#### `fun -int !initSDL -string &windowTitle -int &width -int &height`
First argument is the window title, second and third are width and height of the window, respectively.
Initializes SDL and creates a window. **This must be run before any other Grogs functions!**
If everything goes fine, returns 0.
**Arguments:**
- `windowTitle`: Title of the window
- `width`: Window width in pixels
- `height`: Window height in pixels
If SDL couldn't create the window, returns 1. If already initialized, returns 2.
Example:
**Returns:**
- `0`: Success
- `1`: SDL initialization or window creation failed
- `2`: Already initialized
**Example:**
```
pusharg "My Awesome Window" 640 480
!grogs:initSDL &result
pusharg "My Awesome Window" 800 600
call !grogs:initSDL &result
```
### fun -int !pollEvent
#### `fun -int !getWindowSize -int &dimension`
Returns a code for an event that has been polled. For now, only window closing is supported, which will return 0. Anything else for now will return -1.
Gets window dimensions.
### fun -int !clearRenderer
**Arguments:**
- `dimension`: `0` for width, `1` for height
Clears the renderer of any pixels to be rendered. Returns 0, unless SDL is not initialized.
**Returns:** The requested dimension, or `-1` if not initialized.
### fun -int !renderFrame
#### `fun -int !cleanup`
Renders frame on the screen. Whichever pixels have been set will be displayed. Returns 0, unless SDL is not initialized.
Properly cleans up SDL resources. Call this before your program exits.
### fun -int !setPixel -int &xpos -int &ypos -int &red -int &green -int &blue
**Returns:** Always `0`
Sets a pixel at the desired location to the desired colour. Returns 0, unless SDL is not initialized.
### Event Handling
First argument: xpos
Second argument: ypos
Third argument: red value (0 to 255)
Fourth argument: green value (0 to 255)
Fifth argument: blue value (0 to 255)
#### `fun -int !pollEvent`
This example sets the pixel at (10, 10) to white (RGB(255, 255, 255)):
Polls for SDL events and returns event type codes.
**Returns:**
- `0`: Quit event (window closed)
- `1`: Key pressed down
- `2`: Key released
- `3`: Mouse button pressed
- `4`: Mouse button released
- `5`: Mouse moved
- `6`: Other event
- `-1`: No event
#### `fun -int !getLastKey`
Gets the key code of the last key event (after calling `pollEvent`).
**Returns:** SDL key code, or `-1` if no key event or not initialized.
#### `fun -int !getMousePos -int &coord`
Gets current mouse position.
**Arguments:**
- `coord`: `0` for X coordinate, `1` for Y coordinate
**Returns:** Mouse coordinate, or `-1` if not initialized.
### Rendering
#### `fun -int !clearRenderer [optional: -int &red -int &green -int &blue]`
Clears the renderer. Optionally specify background color.
**Arguments (optional):**
- `red`: Red value (0-255)
- `green`: Green value (0-255)
- `blue`: Blue value (0-255)
**Returns:** `0` on success, `-1` if not initialized.
**Examples:**
```
pusharg 10 10 255 255 255
call !grogs:setPixel &result
# Clear to black (default)
call !grogs:clearRenderer &result
# Clear to blue background
pusharg 0 100 200
call !grogs:clearRenderer &result
```
#### `fun -int !renderFrame`
Presents the rendered frame to the screen.
**Returns:** `0` on success, `-1` if not initialized.
### Drawing Functions
#### `fun -int !setPixel -int &x -int &y -int &red -int &green -int &blue`
Sets a single pixel to the specified color.
**Arguments:**
- `x`, `y`: Pixel coordinates
- `red`, `green`, `blue`: Color values (0-255, automatically clamped)
**Returns:** `0` on success, `-1` if not initialized or out of bounds.
#### `fun -int !drawLine -int &x1 -int &y1 -int &x2 -int &y2 -int &red -int &green -int &blue`
Draws a line between two points.
**Arguments:**
- `x1`, `y1`: Start point coordinates
- `x2`, `y2`: End point coordinates
- `red`, `green`, `blue`: Line color (0-255)
**Returns:** `0` on success, `-1` if not initialized.
#### `fun -int !drawRect -int &x -int &y -int &width -int &height -int &red -int &green -int &blue`
Draws a filled rectangle.
**Arguments:**
- `x`, `y`: Top-left corner coordinates
- `width`, `height`: Rectangle dimensions
- `red`, `green`, `blue`: Fill color (0-255)
**Returns:** `0` on success, `-1` if not initialized or invalid dimensions.
#### `fun -int !drawRectOutline -int &x -int &y -int &width -int &height -int &red -int &green -int &blue`
Draws a rectangle outline (border only).
**Arguments:** Same as `drawRect`
**Returns:** `0` on success, `-1` if not initialized or invalid dimensions.
#### `fun -int !drawCircle -int &centerX -int &centerY -int &radius -int &red -int &green -int &blue`
Draws a filled circle.
**Arguments:**
- `centerX`, `centerY`: Circle center coordinates
- `radius`: Circle radius in pixels
- `red`, `green`, `blue`: Fill color (0-255)
**Returns:** `0` on success, `-1` if not initialized or invalid radius.
### Utility Functions
#### `fun -int !delay -int &milliseconds`
Pauses execution for the specified time. Useful for frame rate control.
**Arguments:**
- `milliseconds`: Delay duration
**Returns:** `0` on success, `-1` if negative value provided.
## Error Handling
All functions return status codes. Always check return values:
- `0`: Success
- `1`: SDL-specific error
- `2`: Already initialized (initSDL only)
- `-1`: General error (not initialized, invalid parameters, out of bounds, etc.)
Functions that require SDL to be initialized will print error messages and return `-1` if called before `initSDL`.

View File

@@ -2,55 +2,140 @@
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_render.h>
#include <iostream>
SDL_Window* window;
SDL_Renderer* renderer;
SDL_Window* window = nullptr;
SDL_Renderer* renderer = nullptr;
SDL_Event event;
int width;
int height;
int width = 0;
int height = 0;
bool hasRunInit = false;
bool hasRunInit;
// Helper function to clamp values
int clamp(int value, int min, int max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
// Initialize SDL and create window
GroundValue initSDL(GroundValue* args, int arg_count) {
if (hasRunInit) return GROUND_INT_VAL(2);
// args are window title, width, height
VALIDATE_ARGS_3(GROUND_STRING, GROUND_INT, GROUND_INT);
SDL_Init(SDL_INIT_VIDEO);
width = GET_INT(args[1]);
height = GET_INT(args[2]);
SDL_CreateWindowAndRenderer(GET_STRING(args[0]), width, height, SDL_WINDOW_OPENGL, &window, &renderer);
if (window == NULL) {
std::cout << "Couldn't create SDL window" << std::endl;
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
std::cout << "SDL could not initialize! SDL Error: " << SDL_GetError() << std::endl;
return GROUND_INT_VAL(1);
}
width = GET_INT(args[1]);
height = GET_INT(args[2]);
// Validate dimensions
if (width <= 0 || height <= 0) {
std::cout << "Invalid window dimensions" << std::endl;
SDL_Quit();
return GROUND_INT_VAL(1);
}
if (SDL_CreateWindowAndRenderer(GET_STRING(args[0]), width, height,
SDL_WINDOW_OPENGL, &window, &renderer) < 0) {
std::cout << "Window could not be created! SDL Error: " << SDL_GetError() << std::endl;
SDL_Quit();
return GROUND_INT_VAL(1);
}
hasRunInit = true;
return GROUND_INT_VAL(0);
}
// Poll for events (enhanced with more event types)
GroundValue pollEvent(GroundValue* args, int arg_count) {
if (!hasRunInit) {
std::cout << "Cannot poll event, please run !grogs:initSDL first" << std::endl;
return GROUND_INT_VAL(-1);
}
SDL_PollEvent(&event);
if (event.type == SDL_EVENT_QUIT) {
return GROUND_INT_VAL(0);
if (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_EVENT_QUIT:
return GROUND_INT_VAL(0);
case SDL_EVENT_KEY_DOWN:
return GROUND_INT_VAL(1);
case SDL_EVENT_KEY_UP:
return GROUND_INT_VAL(2);
case SDL_EVENT_MOUSE_BUTTON_DOWN:
return GROUND_INT_VAL(3);
case SDL_EVENT_MOUSE_BUTTON_UP:
return GROUND_INT_VAL(4);
case SDL_EVENT_MOUSE_MOTION:
return GROUND_INT_VAL(5);
default:
return GROUND_INT_VAL(6); // Other event
}
}
return GROUND_INT_VAL(-1); // No event
}
// Get last key pressed (requires polling events first)
GroundValue getLastKey(GroundValue* args, int arg_count) {
if (!hasRunInit) {
std::cout << "Cannot get key, please run !grogs:initSDL first" << std::endl;
return GROUND_INT_VAL(-1);
}
if (event.type == SDL_EVENT_KEY_DOWN || event.type == SDL_EVENT_KEY_UP) {
return GROUND_INT_VAL(event.key.key);
}
return GROUND_INT_VAL(-1);
}
// Get mouse position
GroundValue getMousePos(GroundValue* args, int arg_count) {
if (!hasRunInit) {
std::cout << "Cannot get mouse position, please run !grogs:initSDL first" << std::endl;
return GROUND_INT_VAL(-1);
}
VALIDATE_ARGS_1(GROUND_INT);
int coord = GET_INT(args[0]); // 0 for x, 1 for y
float mouseX, mouseY;
SDL_GetMouseState(&mouseX, &mouseY);
if (coord == 0) {
return GROUND_INT_VAL((int)mouseX);
} else if (coord == 1) {
return GROUND_INT_VAL((int)mouseY);
}
return GROUND_INT_VAL(-1);
}
// Clear renderer with optional color
GroundValue clearRenderer(GroundValue* args, int arg_count) {
if (!hasRunInit) {
std::cout << "Cannot clear renderer, please run !grogs:initSDL first" << std::endl;
return GROUND_INT_VAL(-1);
}
if (arg_count == 3) {
VALIDATE_ARGS_3(GROUND_INT, GROUND_INT, GROUND_INT);
int r = clamp(GET_INT(args[0]), 0, 255);
int g = clamp(GET_INT(args[1]), 0, 255);
int b = clamp(GET_INT(args[2]), 0, 255);
SDL_SetRenderDrawColor(renderer, r, g, b, 255);
} else {
// Default to black
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
}
SDL_RenderClear(renderer);
return GROUND_INT_VAL(0);
}
// Render frame to screen
GroundValue renderFrame(GroundValue* args, int arg_count) {
if (!hasRunInit) {
std::cout << "Cannot render frame, please run !grogs:initSDL first" << std::endl;
@@ -60,21 +145,185 @@ GroundValue renderFrame(GroundValue* args, int arg_count) {
return GROUND_INT_VAL(0);
}
// Set a single pixel
GroundValue setPixel(GroundValue* args, int arg_count) {
if (!hasRunInit) {
std::cout << "Cannot set pixel, please run !grogs:initSDL first" << std::endl;
return GROUND_INT_VAL(-1);
}
VALIDATE_ARGS_5(GROUND_INT, GROUND_INT, GROUND_INT, GROUND_INT, GROUND_INT);
// xpos, ypos, red, green, blue
int pixelX = GET_INT(args[0]);
int pixelY = GET_INT(args[1]);
int red = GET_INT(args[2]);
int green = GET_INT(args[3]);
int blue = GET_INT(args[4]);
SDL_SetRenderDrawColor(renderer, red, green, blue, 255);
SDL_RenderPoint(renderer, pixelX, pixelY);
int red = clamp(GET_INT(args[2]), 0, 255);
int green = clamp(GET_INT(args[3]), 0, 255);
int blue = clamp(GET_INT(args[4]), 0, 255);
// Bounds checking
if (pixelX < 0 || pixelX >= width || pixelY < 0 || pixelY >= height) {
return GROUND_INT_VAL(-1); // Out of bounds
}
SDL_SetRenderDrawColor(renderer, red, green, blue, 255);
SDL_RenderPoint(renderer, (float)pixelX, (float)pixelY);
return GROUND_INT_VAL(0);
}
// Draw a line
GroundValue drawLine(GroundValue* args, int arg_count) {
if (!hasRunInit) {
std::cout << "Cannot draw line, please run !grogs:initSDL first" << std::endl;
return GROUND_INT_VAL(-1);
}
VALIDATE_ARGS_7(GROUND_INT, GROUND_INT, GROUND_INT, GROUND_INT, GROUND_INT, GROUND_INT, GROUND_INT);
int x1 = GET_INT(args[0]);
int y1 = GET_INT(args[1]);
int x2 = GET_INT(args[2]);
int y2 = GET_INT(args[3]);
int red = clamp(GET_INT(args[4]), 0, 255);
int green = clamp(GET_INT(args[5]), 0, 255);
int blue = clamp(GET_INT(args[6]), 0, 255);
SDL_SetRenderDrawColor(renderer, red, green, blue, 255);
SDL_RenderLine(renderer, (float)x1, (float)y1, (float)x2, (float)y2);
return GROUND_INT_VAL(0);
}
// Draw a filled rectangle
GroundValue drawRect(GroundValue* args, int arg_count) {
if (!hasRunInit) {
std::cout << "Cannot draw rectangle, please run !grogs:initSDL first" << std::endl;
return GROUND_INT_VAL(-1);
}
VALIDATE_ARGS_7(GROUND_INT, GROUND_INT, GROUND_INT, GROUND_INT, GROUND_INT, GROUND_INT, GROUND_INT);
int x = GET_INT(args[0]);
int y = GET_INT(args[1]);
int w = GET_INT(args[2]);
int h = GET_INT(args[3]);
int red = clamp(GET_INT(args[4]), 0, 255);
int green = clamp(GET_INT(args[5]), 0, 255);
int blue = clamp(GET_INT(args[6]), 0, 255);
if (w <= 0 || h <= 0) {
return GROUND_INT_VAL(-1); // Invalid dimensions
}
SDL_FRect rect = {(float)x, (float)y, (float)w, (float)h};
SDL_SetRenderDrawColor(renderer, red, green, blue, 255);
SDL_RenderFillRect(renderer, &rect);
return GROUND_INT_VAL(0);
}
// Draw a rectangle outline
GroundValue drawRectOutline(GroundValue* args, int arg_count) {
if (!hasRunInit) {
std::cout << "Cannot draw rectangle outline, please run !grogs:initSDL first" << std::endl;
return GROUND_INT_VAL(-1);
}
VALIDATE_ARGS_7(GROUND_INT, GROUND_INT, GROUND_INT, GROUND_INT, GROUND_INT, GROUND_INT, GROUND_INT);
int x = GET_INT(args[0]);
int y = GET_INT(args[1]);
int w = GET_INT(args[2]);
int h = GET_INT(args[3]);
int red = clamp(GET_INT(args[4]), 0, 255);
int green = clamp(GET_INT(args[5]), 0, 255);
int blue = clamp(GET_INT(args[6]), 0, 255);
if (w <= 0 || h <= 0) {
return GROUND_INT_VAL(-1); // Invalid dimensions
}
SDL_FRect rect = {(float)x, (float)y, (float)w, (float)h};
SDL_SetRenderDrawColor(renderer, red, green, blue, 255);
SDL_RenderRect(renderer, &rect);
return GROUND_INT_VAL(0);
}
// Draw a circle (filled)
GroundValue drawCircle(GroundValue* args, int arg_count) {
if (!hasRunInit) {
std::cout << "Cannot draw circle, please run !grogs:initSDL first" << std::endl;
return GROUND_INT_VAL(-1);
}
VALIDATE_ARGS_6(GROUND_INT, GROUND_INT, GROUND_INT, GROUND_INT, GROUND_INT, GROUND_INT);
int centerX = GET_INT(args[0]);
int centerY = GET_INT(args[1]);
int radius = GET_INT(args[2]);
int red = clamp(GET_INT(args[3]), 0, 255);
int green = clamp(GET_INT(args[4]), 0, 255);
int blue = clamp(GET_INT(args[5]), 0, 255);
if (radius <= 0) {
return GROUND_INT_VAL(-1); // Invalid radius
}
SDL_SetRenderDrawColor(renderer, red, green, blue, 255);
// Simple circle drawing using midpoint algorithm
for (int y = -radius; y <= radius; y++) {
for (int x = -radius; x <= radius; x++) {
if (x*x + y*y <= radius*radius) {
SDL_RenderPoint(renderer, (float)(centerX + x), (float)(centerY + y));
}
}
}
return GROUND_INT_VAL(0);
}
// Get window dimensions
GroundValue getWindowSize(GroundValue* args, int arg_count) {
if (!hasRunInit) {
std::cout << "Cannot get window size, please run !grogs:initSDL first" << std::endl;
return GROUND_INT_VAL(-1);
}
VALIDATE_ARGS_1(GROUND_INT);
int dimension = GET_INT(args[0]); // 0 for width, 1 for height
if (dimension == 0) {
return GROUND_INT_VAL(width);
} else if (dimension == 1) {
return GROUND_INT_VAL(height);
}
return GROUND_INT_VAL(-1);
}
// Delay execution (useful for frame rate control)
GroundValue delay(GroundValue* args, int arg_count) {
VALIDATE_ARGS_1(GROUND_INT);
int milliseconds = GET_INT(args[0]);
if (milliseconds < 0) {
return GROUND_INT_VAL(-1);
}
SDL_Delay(milliseconds);
return GROUND_INT_VAL(0);
}
// Cleanup SDL resources
GroundValue cleanup(GroundValue* args, int arg_count) {
if (hasRunInit) {
if (renderer) {
SDL_DestroyRenderer(renderer);
renderer = nullptr;
}
if (window) {
SDL_DestroyWindow(window);
window = nullptr;
}
SDL_Quit();
hasRunInit = false;
}
return GROUND_INT_VAL(0);
}
@@ -84,9 +333,19 @@ GROUND_LIBRARY_INIT()
REGISTER_GROUND_FUNCTION(initSDL);
REGISTER_GROUND_FUNCTION(renderFrame);
REGISTER_GROUND_FUNCTION(pollEvent);
REGISTER_GROUND_FUNCTION(getLastKey);
REGISTER_GROUND_FUNCTION(getMousePos);
REGISTER_GROUND_FUNCTION(clearRenderer);
REGISTER_GROUND_FUNCTION(setPixel);
REGISTER_GROUND_FUNCTION(drawLine);
REGISTER_GROUND_FUNCTION(drawRect);
REGISTER_GROUND_FUNCTION(drawRectOutline);
REGISTER_GROUND_FUNCTION(drawCircle);
REGISTER_GROUND_FUNCTION(getWindowSize);
REGISTER_GROUND_FUNCTION(delay);
REGISTER_GROUND_FUNCTION(cleanup);
GROUND_LIBRARY_INIT_END()
GROUND_LIBRARY_CLEANUP()
cleanup(nullptr, 0);
GROUND_LIBRARY_CLEANUP_END()

Binary file not shown.

View File

@@ -108,6 +108,25 @@ inline GroundValue ground_cstring_val(const char* str) {
REQUIRE_TYPE(3, type4); \
REQUIRE_TYPE(4, type5);
#define VALIDATE_ARGS_6(type1, type2, type3, type4, type5, type6) \
REQUIRE_ARGS(5); \
REQUIRE_TYPE(0, type1); \
REQUIRE_TYPE(1, type2); \
REQUIRE_TYPE(2, type3); \
REQUIRE_TYPE(3, type4); \
REQUIRE_TYPE(4, type5); \
REQUIRE_TYPE(5, type6);
#define VALIDATE_ARGS_7(type1, type2, type3, type4, type5, type6, type7) \
REQUIRE_ARGS(5); \
REQUIRE_TYPE(0, type1); \
REQUIRE_TYPE(1, type2); \
REQUIRE_TYPE(2, type3); \
REQUIRE_TYPE(3, type4); \
REQUIRE_TYPE(4, type5); \
REQUIRE_TYPE(5, type6); \
REQUIRE_TYPE(6, type7);
// Function registration helpers
class GroundLibrary {
private:

View File

@@ -1,52 +1,148 @@
extern "grogs"
struct -rectangle
init &topx -int
init &topy -int
init &bottomx -int
init &bottomy -int
init &red -int
init &green -int
init &blue -int
struct -character
init &xpos -int
init &ypos -int
endstruct
fun -int !draw -rectangle &r
set &ycount $r.topy
@columnloop
set &xcount $r.topx
add 1 $ycount &ycount
greater $ycount $r.bottomy &cond
if $cond %endloop
@rowloop
pusharg $xcount $ycount $r.red $r.green $r.blue
!grogs:setPixel &out
add 1 $xcount &xcount
greater $xcount $r.bottomx &cond
if $cond %columnloop
jump %rowloop
@endloop
return 0
endfun
init &player -character
set &player.xpos 100
set &player.ypos 100
pusharg "Grogs Window" 640 480
!grogs:initSDL &status
set &movingUp false
set &movingDown false
set &movingLeft false
set &movingRight false
init &rect -rectangle
set &rect.topx 10
set &rect.topy 10
set &rect.bottomx 150
set &rect.bottomy 100
set &rect.red 0
set &rect.blue 255
set &rect.green 255
pusharg "Grogs Window" 800 600
call !grogs:initSDL &result
@mainLoop
!grogs:pollEvent &event
# nothing
equal $event -1 &cond
if $cond %skipChecks
# exit
equal $event 0 &cond
if $cond %endMainLoop
# key pressed
equal $event 1 &cond
if $cond %checkPressedKey
# key unpressed
equal $event 2 &cond
if $cond %checkUnpressedKey
@checkPressedKey
!grogs:getLastKey &key
# up
equal $key 1073741906 &cond
if $cond %processUpPress
# down
equal $key 1073741905 &cond
if $cond %processDownPress
# left
equal $key 1073741904 &cond
if $cond %processLeftPress
#right
equal $key 1073741903 &cond
if $cond %processRightPress
jump %skipChecks
@processUpPress
set &movingUp true
jump %skipChecks
@processDownPress
set &movingDown true
jump %skipChecks
@processLeftPress
set &movingLeft true
jump %skipChecks
@processRightPress
set &movingRight true
jump %skipChecks
@checkUnpressedKey
!grogs:getLastKey &key
# up
equal $key 1073741906 &cond
if $cond %processUpUnpress
# down
equal $key 1073741905 &cond
if $cond %processDownUnpress
# left
equal $key 1073741904 &cond
if $cond %processLeftUnpress
#right
equal $key 1073741903 &cond
if $cond %processRightUnpress
jump %skipChecks
@processUpUnpress
set &movingUp false
jump %skipChecks
@processDownUnpress
set &movingDown false
jump %skipChecks
@processLeftUnpress
set &movingLeft false
jump %skipChecks
@processRightUnpress
set &movingRight false
jump %skipChecks
@skipChecks
if $movingUp %processUp
jump %checkDown
@processUp
subtract $player.ypos 10 &player.ypos
@checkDown
if $movingDown %processDown
jump %checkLeft
@processDown
add $player.ypos 10 &player.ypos
@checkLeft
if $movingLeft %processLeft
jump %checkRight
@processLeft
subtract $player.xpos 10 &player.xpos
@checkRight
if $movingRight %processRight
jump %endMovement
@processRight
add $player.xpos 10 &player.xpos
@endMovement
pusharg $player.xpos $player.ypos 20 20 0 255 255
!grogs:drawRect &out
@loop
pusharg $rect
!draw &out
!grogs:renderFrame &out
!grogs:pollEvent &out
equal $out 0 &cond
if $cond %end
jump %loop
@end
!grogs:clearRenderer &out
pusharg 16
!grogs:delay &out
jump %mainLoop
@endMainLoop