Skip to content
Ryan Srichai edited this page Jan 6, 2026 · 22 revisions

The turtle module is the basic functionality for all turtle libraries, defined in turtle.h. When you include this module, you get a turtle.

Unlike the list module, you're only meant to have one turtle. All turtle functions will act on this one global turtle. This turtle is called turtle in the code.

Creating a window

The turtle module and libraries uses the open source library glfw under the hood to create and manage the window. GLFW is a fantastic cross-platform windowing library that gives us a simple way to control our window. Because it's so good, I chose not to wrap GLFW's interface into turtle. But we'll need this code to create a window and initialise turtle within it. I would recommend putting this in your C main function:

/* Initialise glfw */
if (!glfwInit()) {
    return -1;
}
glfwWindowHint(GLFW_SAMPLES, 4); // MSAA (Anti-Aliasing) with 4 samples (must be done before window is created)

/* Create a windowed mode window and its OpenGL context */
const GLFWvidmode *monitorSize = glfwGetVideoMode(glfwGetPrimaryMonitor());
int32_t windowHeight = monitorSize -> height;
GLFWwindow *window = glfwCreateWindow(windowHeight * 16 / 9, windowHeight, "turtle demo", NULL, NULL);
if (!window) {
    glfwTerminate();
    return -1;
}
glfwMakeContextCurrent(window);
glfwSetWindowSizeLimits(window, windowHeight * 16 / 9 * 0.4, windowHeight * 0.4, windowHeight * 16 / 9, windowHeight);

/* initialise turtle */
turtleInit(window, -320, -180, 320, 180);
glfwSetWindowSize(window, windowHeight * 16 / 9 * 0.85, monitorSize -> height * 0.85); // doing it this way ensures the window spawns in the top left of the monitor and fixes resizing limits

This specific code will create a window with a 16 by 9 aspect ratio with a height set to 85% the height of your primary monitor (whatever GLFW determines your primary monitor to be). We've given it the title "turtle demo" and created in in windowed mode. For more documentation and customisation of window creating, see GLFW's documentation.

Turtle Coordinates

In the window creating example, we initialised turtle with the GLFW window as well as the coordinate bounds -320, -180, 320, 180. This means that we've created a canvas with this coordinate system:

image

All turtle functions will use this coordinate system, and it won't change if the window is resized. If you want to create a typical coordinate system with (-1, -1) at the top left and (1, 1) at the bottom right, initialise turtle with turtleInit(window, -1, 1, 1, -1);. For the rest of the examples, I'll be using this 640x360 coordinate canvas with (0, 0) as the center.

Drawing

Turtle's drawing system comes in two main forms. We'll discuss the default path-based way to draw first. Just like python turtle, the turtle module functions like a physical object that moves around and leaves a trail behind it. You can't see the actual turtle - it's not a real entity, but you can control where it goes and leaves a path.

First we'll set the path color and size, and then we'll draw a line from (0, 0) to (50, 50)

turtlePenColor(255, 0, 0); // set pen color to red
turtlePenSize(5); // set pen size to 5
turtleGoto(0, 0); // set the turtle's coordinates to 0, 0
turtlePenDown(); // start drawing
turtleGoto(50, 50); // set the turtle's coordinates to 0, 0
turtlePenUp(); // stop drawing
turtleUpdate(); // update the screen
turtleMainLoop(); // keep the window alive

The size function, like all turtle functions, uses the coordinate system. So our path is 5 coordinates wide.

image

Believe it or not, you now have the ability to draw anything.

Mouse and keyboard

Turtle also supports mouse and keyboard input through a very simple interface. Call the function turtleGetMouseCoords() to get the mouse coordinates in variables turtle.mouseX and turtle.mouseY.

while (turtle.close == 0) {
    turtleGetMouseCoords();
    printf("%lf %lf\n", turtle.mouseX, turtle.mouseY);
    turtleUpdate();
}

You can also ask if a mouse or keyboard button is being pressed using the turtleMouseDown() and turtleKeyPressed(). These functions return a 1 if the button is being pressed and a 0 if they aren't. The turtleKeyPressed() function uses the GLFW key macros.

while (turtle.close == 0) {
    if (turtleMouseDown()) {
        printf("Left mouse pressed\n");
    }
    if (turtleKeyPressed(GLFW_KEY_SPACE)) {
        printf("Space key pressed\n");
    }
    turtleUpdate();
}

There are additional functions for right mouse button turtleMouseRight() and middle mouse button turtleMouseMiddle(). You can also use detect the scroll wheel turtleMouseWheel(), note that turtleMouseWheel() returns the scroll wheel's difference from the last time you called it. That means if you call this function twice in a row it will always return 0 on the second call. To get around this, store the value of this function in a variable every tick.

while (turtle.close == 0) {
    if (turtleMouseRight()) {
        printf("Right mouse pressed\n");
    }
    if (turtleMouseMiddle()) {
        printf("Middle mouse pressed\n");
    }
    double wheel = turtleMouseWheel();
    if (wheel != 0) {
        printf("%lf\n", wheel);
    }
    turtleUpdate();
}

Textures

Turtle has some support for loading and displaying images, but texture features have to be explicitly enabled. If you're building with the library, you just have to swap out the -lturtle flag with -lturtletextures.

> gcc turtle.c -L./Windows -lturtletextures -lglfw3 -lopengl32 -lgdi32 -lglad -lole32 -luuid -lwsock32 -lWs2_32 -DOS_WINDOWS -O3 -o turtle.exe

If you're building from source instead, add #define TURTLE_ENABLE_TEXTURES next to the #define TURTLE_IMPLEMENTATION line in your code.

To load and display a texture, use the functions turtleTextureLoad() and turtleTexture()

turtle_texture_t empvImage = turtleTextureLoad("images/EMPV.png");
while (turtle.close == 0) {
    turtleClear(); // clear the screen
    turtleTexture(empvImage, -144, -81, 144, 81, 0, 255, 255, 255); // draw texture
    turtleUpdate(); // update the screen
    end = clock();
    while ((double) (end - start) / CLOCKS_PER_SEC < (1.0 / tps)) {
        end = clock();
    }
    tick++;
}
image

The turtleTextureLoad function uses stb_image under the hood. This means that all image formats supported by stb_image should work.

The turtleTexture function creates a rectangle image to map the texture on to. The texture will stretch to fit the dimensions of the rectangle. You can also rotate the texture with the rot parameter (0 is not rotated, angle is in degrees using a bearing system). The color parameters (r, g, and b) should be set to 255 to display the image normally. I'm honestly probably going to remove them because who wants to only render the red parts of an image.

You can also free a texture using turtleTextureUnload()

turtleTextureUnload(empvImage);

This will effectively free the image and disable the handle. You cannot render a texture that has been unloaded.

Examples

See turtle examples for a more complete list.

Drawing with mouse example:

See the full source code here.

uint32_t tps = 60; // ticks per second (locked to fps in this case)
uint64_t tick = 0; // count number of ticks since application started
clock_t start, end;

turtlePenColor(0, 0, 0); // set color to black
turtlePenSize(10); // set size to 10

while (turtle.close == 0) {
    start = clock();
    turtleGetMouseCoords();
    turtleGoto(turtle.mouseX, turtle.mouseY);
    if (turtleMouseDown()) {
        turtlePenDown();
    } else {
        turtlePenUp();
    }
    turtleUpdate(); // update the screen
    end = clock();
    while ((double) (end - start) / CLOCKS_PER_SEC < (1.0 / tps)) {
        end = clock();
    }
    tick++;
}
turtleFree();
glfwTerminate();
image

Moving circle with arrow keys example:

See the full source code here.

uint32_t tps = 60; // ticks per second (locked to fps in this case)
uint64_t tick = 0; // count number of ticks since application started
clock_t start, end;

turtlePenColor(0, 0, 0); // set color to black
turtlePenSize(50); // set size to 10
double ballX = 0;
double ballY = 0;
double ballSpeed = 5.0;

while (turtle.close == 0) {
    start = clock();
    if (turtleKeyPressed(GLFW_KEY_UP)) {
        ballY += ballSpeed;
    }
    if (turtleKeyPressed(GLFW_KEY_DOWN)) {
        ballY -= ballSpeed;
    }
    if (turtleKeyPressed(GLFW_KEY_RIGHT)) {
        ballX += ballSpeed;
    }
    if (turtleKeyPressed(GLFW_KEY_LEFT)) {
        ballX -= ballSpeed;
    }
    turtleClear(); // clear the screen
    turtleGoto(ballX, ballY);
    turtlePenDown();
    turtlePenUp();
    turtleUpdate(); // update the screen
    end = clock();
    while ((double) (end - start) / CLOCKS_PER_SEC < (1.0 / tps)) {
        end = clock();
    }
    tick++;
}
turtleFree();
glfwTerminate();
image

Alternate drawing methods

As a convenience feature, I have added the ability to directly draw simple filled shapes to the screen. These come in the form of turtleTriangle(), turtleRectangle(), turtleCircle(), and turtleQuad(). Note that triangles, rectangles, and quads require direct coordinates to all of their points (rather than width and height). Rectangle is a special form of quad that only requires the two corner points (and assumes that the sides of the rectangle align with the X/Y axis). And circle requires an X, Y and radius parameter.

turtlePenColor(0, 255, 255);
turtleTriangle(-20, 20, -180, 20, -100, 140);
turtlePenColor(255, 0, 255);
turtleRectangle(20, 20, 140, 140);
turtlePenColor(255, 255, 0);
turtleCircle(-100, -80, 80);
turtlePenColor(0, 0, 0);
turtleQuad(10, -10, 140, -20, 140, -120, 20, -140);
turtleMainLoop();
image

All Functions

Initialise turtle:

void turtleInit(GLFWwindow *window, int32_t minX, int32_t minY, int32_t maxX, int32_t maxY);

Clear the screen:

void turtleClear();

Set the background color:

void turtleBgColor(double r, double g, double b);

Set the pen size (coordinates):

void turtlePenSize(double size);

Set the pen color (0-255 scale):

void turtlePenColor(double r, double g, double b);

Set the pen color with transparency (0 is no transparency, 255 is fully transparent):

void turtlePenColorAlpha(double r, double g, double b, double a);

Set the turtle position (coordinates):

void turtleGoto(double x, double y);

Start drawing:

void turtlePenDown();

Stop drawing:

void turtlePenUp();

Set the pen shape ("circle", "square", "triangle", "none", or "connected"):

void turtlePenShape(char *selection);

Get the mouse coordinates (returns to turtle.mouseX and turtle.mouseY):

void turtleGetMouseCoords();

Detect left mouse button (returns 0 if not clicked, returns non-zero if clicked):

int8_t turtleMouseDown();

Detect right mouse button:

int8_t turtleMouseRight();

Detect middle mouse button:

int8_t turtleMouseMiddle();

Detect scroll wheel (returns change since last call):

double turtleMouseWheel();

Detect key (using GLFW key macros):

int8_t turtleKeyPressed(int32_t key);

Set world coordinates (not recommended to change coordinates while a program is running):

void turtleSetWorldCoordinates(double leftX, double bottomY, double rightX, double topY);

Place circle:

void turtleCircle(double x, double y, double radius);

Place triangle:

void turtleTriangle(double x1, double y1, double x2, double y2, double x3, double y3);

Place rectangle:

void turtleRectangle(double x1, double y1, double x2, double y2);

Place quadrilateral:

void turtleQuad(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4);

Set the circle precision (how many sides a circle with diameter in coordinates equal to the mathematical constant e, sides of a rendered circle scales with the logarithm of the diameter):

void turtlePenPrez(double prez);

Update the screen:

void turtleUpdate();

Keep window alive until closed:

void turtleMainLoop();

Free all turtle data:

void turtleFree();

Clone this wiki locally