Compare commits

...

3 Commits

Author SHA1 Message Date
5e7de482e7 Mathlib round corrections 2025-09-22 20:03:17 +10:00
7e76d473e7 Extend grogs, better example program 2025-09-21 20:41:09 +10:00
52e95e987f Add readme 2025-09-21 16:03:19 +10:00
7 changed files with 633 additions and 69 deletions

190
grogs/README.md Normal file
View File

@@ -0,0 +1,190 @@
# grogs (Ground graphics) library
This library uses SDL3 to add graphics support to Ground, including window management, event handling, and simple 2D drawing.
## Compiling
First, ensure SDL3 is installed on your system. Then:
```bash
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
### Window Management
#### `fun -int !initSDL -string &windowTitle -int &width -int &height`
Initializes SDL and creates a window. **This must be run before any other Grogs functions!**
**Arguments:**
- `windowTitle`: Title of the window
- `width`: Window width in pixels
- `height`: Window height in pixels
**Returns:**
- `0`: Success
- `1`: SDL initialization or window creation failed
- `2`: Already initialized
**Example:**
```
pusharg "My Awesome Window" 800 600
call !grogs:initSDL &result
```
#### `fun -int !getWindowSize -int &dimension`
Gets window dimensions.
**Arguments:**
- `dimension`: `0` for width, `1` for height
**Returns:** The requested dimension, or `-1` if not initialized.
#### `fun -int !cleanup`
Properly cleans up SDL resources. Call this before your program exits.
**Returns:** Always `0`
### Event Handling
#### `fun -int !pollEvent`
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:**
```
# 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,79 +2,328 @@
#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) {
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;
return GROUND_INT_VAL(-1);
}
SDL_RenderPresent(renderer);
return GROUND_BOOL_VAL(true);
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 0 255 255
!grogs:drawCircle &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

View File

@@ -30,17 +30,17 @@ GroundValue modVal(GroundValue* args, int arg_count) {
GroundValue floorVal(GroundValue* args, int arg_count) {
VALIDATE_ARGS_1(GROUND_DOUBLE);
return GROUND_DOUBLE_VAL(floor(GET_DOUBLE(args[0])));
return GROUND_INT_VAL(int(floor(GET_DOUBLE(args[0]))));
}
GroundValue ceilVal(GroundValue* args, int arg_count) {
VALIDATE_ARGS_1(GROUND_DOUBLE);
return GROUND_DOUBLE_VAL(ceil(GET_DOUBLE(args[0])));
return GROUND_INT_VAL(int(ceil(GET_DOUBLE(args[0]))));
}
GroundValue roundVal(GroundValue* args, int arg_count) {
VALIDATE_ARGS_1(GROUND_DOUBLE);
return GROUND_DOUBLE_VAL(round(GET_DOUBLE(args[0])));
return GROUND_INT_VAL(int(round(GET_DOUBLE(args[0]))));
}
GroundValue randomInt(GroundValue* args, int arg_count) {

BIN
math/math.so Executable file

Binary file not shown.