|  | /******************************************************************************************* | 
						
						
							|  | * | 
						
						
							|  | *   raylib [core] example - undo redo | 
						
						
							|  | * | 
						
						
							|  | *   Example complexity rating: [★★★☆] 3/4 | 
						
						
							|  | * | 
						
						
							|  | *   Example originally created with raylib 5.5, last time updated with raylib 5.6 | 
						
						
							|  | * | 
						
						
							|  | *   Example contributed by Ramon Santamaria (@raysan5) | 
						
						
							|  | * | 
						
						
							|  | *   Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, | 
						
						
							|  | *   BSD-like license that allows static linking with closed source software | 
						
						
							|  | * | 
						
						
							|  | *   Copyright (c) 2025 Ramon Santamaria (@raysan5) | 
						
						
							|  | * | 
						
						
							|  | ********************************************************************************************/ | 
						
						
							|  | 
 | 
						
						
							|  | #include "raylib.h" | 
						
						
							|  |  | 
						
						
							|  | #include <stdlib.h>     // Required for: calloc(), free() | 
						
						
							|  | #include <string.h>     // Required for: memcpy(), strcmp() | 
						
						
							|  |  | 
						
						
							|  | #define MAX_UNDO_STATES             26      // Maximum undo states supported for the ring buffer | 
						
						
							|  |  | 
						
						
							|  | #define GRID_CELL_SIZE              24       | 
						
						
							|  | #define MAX_GRID_CELLS_X            30 | 
						
						
							|  | #define MAX_GRID_CELLS_Y            13 | 
						
						
							|  |  | 
						
						
							|  | //---------------------------------------------------------------------------------- | 
						
						
							|  | // Types and Structures Definition | 
						
						
							|  | //---------------------------------------------------------------------------------- | 
						
						
							|  | // Point struct, like Vector2 but using int | 
						
						
							|  | typedef struct { | 
						
						
							|  |     int x; | 
						
						
							|  |     int y; | 
						
						
							|  | } Point; | 
						
						
							|  | 
 | 
						
						
							|  | // Player state struct | 
						
						
							|  | // NOTE: Contains all player data that needs to be affected by undo/redo | 
						
						
							|  | typedef struct { | 
						
						
							|  |     Point cell; | 
						
						
							|  |     Color color; | 
						
						
							|  | } PlayerState; | 
						
						
							|  | 
 | 
						
						
							|  | //------------------------------------------------------------------------------------ | 
						
						
							|  | // Module Functions Declaration | 
						
						
							|  | //------------------------------------------------------------------------------------ | 
						
						
							|  | // Draw undo system visualization logic | 
						
						
							|  | static void DrawUndoBuffer(Vector2 position, int firstUndoIndex, int lastUndoIndex, int currentUndoIndex, int slotSize); | 
						
						
							|  | 
 | 
						
						
							|  | //------------------------------------------------------------------------------------ | 
						
						
							|  | // Program main entry point | 
						
						
							|  | //------------------------------------------------------------------------------------ | 
						
						
							|  | int main(void) | 
						
						
							|  | { | 
						
						
							|  |     // Initialization | 
						
						
							|  |     //-------------------------------------------------------------------------------------- | 
						
						
							|  |     const int screenWidth = 800; | 
						
						
							|  |     const int screenHeight = 450; | 
						
						
							|  |      | 
						
						
							|  |     // We have multiple options to implement an Undo/Redo system | 
						
						
							|  |     // Probably the most professional one is using the Command pattern to | 
						
						
							|  |     // define Actions and store those actions into an array as the events happen, | 
						
						
							|  |     // raylib internal Automation System actually uses a similar approach, | 
						
						
							|  |     // but in this example we are using another more simple solution, | 
						
						
							|  |     // just record PlayerState changes when detected, checking for changes every certain frames | 
						
						
							|  |     // This approach requires more memory and is more performance costly but it is quite simple to implement | 
						
						
							|  |  | 
						
						
							|  |     InitWindow(screenWidth, screenHeight, "raylib [core] example - undo redo"); | 
						
						
							|  | 
 | 
						
						
							|  |     // Undo/redo system variables | 
						
						
							|  |     int currentUndoIndex = 0; | 
						
						
							|  |     int firstUndoIndex = 0; | 
						
						
							|  |     int lastUndoIndex = 0; | 
						
						
							|  |     int undoFrameCounter = 0; | 
						
						
							|  |     Vector2 undoInfoPos = { 110, 400 }; | 
						
						
							|  | 
 | 
						
						
							|  |     // Init current player state and undo/redo recorded states array | 
						
						
							|  |     PlayerState player = { 0 }; | 
						
						
							|  |     player.cell = (Point){ 10, 10 }; | 
						
						
							|  |     player.color = RED; | 
						
						
							|  | 
 | 
						
						
							|  |     // Init undo buffer to store MAX_UNDO_STATES states | 
						
						
							|  |     PlayerState *states = (PlayerState *)RL_CALLOC(MAX_UNDO_STATES, sizeof(PlayerState)); | 
						
						
							|  |     // Init all undo states to current state | 
						
						
							|  |     for (int i = 0; i < MAX_UNDO_STATES; i++) memcpy(&states[i], &player, sizeof(PlayerState)); | 
						
						
							|  | 
 | 
						
						
							|  |     // Grid variables | 
						
						
							|  |     Vector2 gridPosition = { 40, 60 }; | 
						
						
							|  | 
 | 
						
						
							|  |     SetTargetFPS(60); | 
						
						
							|  |     //-------------------------------------------------------------------------------------- | 
						
						
							|  |  | 
						
						
							|  |     // Main game loop | 
						
						
							|  |     while (!WindowShouldClose())    // Detect window close button or ESC key | 
						
						
							|  |     { | 
						
						
							|  |         // Update | 
						
						
							|  |         //---------------------------------------------------------------------------------- | 
						
						
							|  |         // Player movement logic | 
						
						
							|  |         if (IsKeyPressed(KEY_RIGHT)) player.cell.x++; | 
						
						
							|  |         else if (IsKeyPressed(KEY_LEFT)) player.cell.x--; | 
						
						
							|  |         else if (IsKeyPressed(KEY_UP)) player.cell.y--; | 
						
						
							|  |         else if (IsKeyPressed(KEY_DOWN)) player.cell.y++; | 
						
						
							|  | 
 | 
						
						
							|  |         // Make sure player does not go out of bounds | 
						
						
							|  |         if (player.cell.x < 0) player.cell.x = 0; | 
						
						
							|  |         else if (player.cell.x >= MAX_GRID_CELLS_X) player.cell.x = MAX_GRID_CELLS_X - 1; | 
						
						
							|  |         if (player.cell.y < 0) player.cell.y = 0; | 
						
						
							|  |         else if (player.cell.y >= MAX_GRID_CELLS_Y) player.cell.y = MAX_GRID_CELLS_Y - 1; | 
						
						
							|  | 
 | 
						
						
							|  |         // Player color change logic | 
						
						
							|  |         if (IsKeyPressed(KEY_SPACE)) | 
						
						
							|  |         { | 
						
						
							|  |             player.color.r = (unsigned char)GetRandomValue(20, 255); | 
						
						
							|  |             player.color.g = (unsigned char)GetRandomValue(20, 220); | 
						
						
							|  |             player.color.b = (unsigned char)GetRandomValue(20, 240); | 
						
						
							|  |         } | 
						
						
							|  | 
 | 
						
						
							|  |         // Undo state change logic | 
						
						
							|  |         undoFrameCounter++; | 
						
						
							|  | 
 | 
						
						
							|  |         // Waiting a number of frames before checking if we should store a new state snapshot | 
						
						
							|  |         if (undoFrameCounter >= 2) // Checking every 2 frames | 
						
						
							|  |         { | 
						
						
							|  |             if (memcmp(&states[currentUndoIndex], &player, sizeof(PlayerState)) != 0) | 
						
						
							|  |             { | 
						
						
							|  |                 // Move cursor to next available position of the undo ring buffer to record state | 
						
						
							|  |                 currentUndoIndex++; | 
						
						
							|  |                 if (currentUndoIndex >= MAX_UNDO_STATES) currentUndoIndex = 0; | 
						
						
							|  |                 if (currentUndoIndex == firstUndoIndex) firstUndoIndex++; | 
						
						
							|  |                 if (firstUndoIndex >= MAX_UNDO_STATES) firstUndoIndex = 0; | 
						
						
							|  | 
 | 
						
						
							|  |                 memcpy(&states[currentUndoIndex], &player, sizeof(PlayerState)); | 
						
						
							|  |                 lastUndoIndex = currentUndoIndex; | 
						
						
							|  |             } | 
						
						
							|  | 
 | 
						
						
							|  |             undoFrameCounter = 0; | 
						
						
							|  |         } | 
						
						
							|  | 
 | 
						
						
							|  |         // Recover previous state from buffer: CTRL+Z | 
						
						
							|  |         if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_Z)) | 
						
						
							|  |         { | 
						
						
							|  |             if (currentUndoIndex != firstUndoIndex) | 
						
						
							|  |             { | 
						
						
							|  |                 currentUndoIndex--; | 
						
						
							|  |                 if (currentUndoIndex < 0) currentUndoIndex = MAX_UNDO_STATES - 1; | 
						
						
							|  | 
 | 
						
						
							|  |                 if (memcmp(&states[currentUndoIndex], &player, sizeof(PlayerState)) != 0) | 
						
						
							|  |                 { | 
						
						
							|  |                     memcpy(&player, &states[currentUndoIndex], sizeof(PlayerState)); | 
						
						
							|  |                 } | 
						
						
							|  |             } | 
						
						
							|  |         } | 
						
						
							|  | 
 | 
						
						
							|  |         // Recover next state from buffer: CTRL+Y | 
						
						
							|  |         if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_Y)) | 
						
						
							|  |         { | 
						
						
							|  |             if (currentUndoIndex != lastUndoIndex) | 
						
						
							|  |             { | 
						
						
							|  |                 int nextUndoIndex = currentUndoIndex + 1; | 
						
						
							|  |                 if (nextUndoIndex >= MAX_UNDO_STATES) nextUndoIndex = 0; | 
						
						
							|  | 
 | 
						
						
							|  |                 if (nextUndoIndex != firstUndoIndex) | 
						
						
							|  |                 { | 
						
						
							|  |                     currentUndoIndex = nextUndoIndex; | 
						
						
							|  | 
 | 
						
						
							|  |                     if (memcmp(&states[currentUndoIndex], &player, sizeof(PlayerState)) != 0) | 
						
						
							|  |                     { | 
						
						
							|  |                         memcpy(&player, &states[currentUndoIndex], sizeof(PlayerState)); | 
						
						
							|  |                     } | 
						
						
							|  |                 } | 
						
						
							|  |             } | 
						
						
							|  |         } | 
						
						
							|  |         //---------------------------------------------------------------------------------- | 
						
						
							|  |  | 
						
						
							|  |         // Draw | 
						
						
							|  |         //---------------------------------------------------------------------------------- | 
						
						
							|  |         BeginDrawing(); | 
						
						
							|  |             ClearBackground(RAYWHITE); | 
						
						
							|  | 
 | 
						
						
							|  |             // Draw controls info | 
						
						
							|  |             DrawText("[ARROWS] MOVE PLAYER - [SPACE] CHANGE PLAYER COLOR", 40, 20, 20, DARKGRAY); | 
						
						
							|  | 
 | 
						
						
							|  |             // Draw player visited cells recorded by undo | 
						
						
							|  |             // NOTE: Remember we are using a ring buffer approach so, | 
						
						
							|  |             // some cells info could start at the end of the array and end at the beginning | 
						
						
							|  |             if (lastUndoIndex > firstUndoIndex) | 
						
						
							|  |             { | 
						
						
							|  |                 for (int i = firstUndoIndex; i < currentUndoIndex; i++) | 
						
						
							|  |                     DrawRectangle(gridPosition.x + states[i].cell.x*GRID_CELL_SIZE, gridPosition.y + states[i].cell.y*GRID_CELL_SIZE, | 
						
						
							|  |                         GRID_CELL_SIZE, GRID_CELL_SIZE, LIGHTGRAY); | 
						
						
							|  |             } | 
						
						
							|  |             else if (firstUndoIndex > lastUndoIndex) | 
						
						
							|  |             { | 
						
						
							|  |                 if ((currentUndoIndex < MAX_UNDO_STATES) && (currentUndoIndex > lastUndoIndex)) | 
						
						
							|  |                 { | 
						
						
							|  |                     for (int i = firstUndoIndex; i < currentUndoIndex; i++) | 
						
						
							|  |                         DrawRectangle(gridPosition.x + states[i].cell.x*GRID_CELL_SIZE, gridPosition.y + states[i].cell.y*GRID_CELL_SIZE, | 
						
						
							|  |                             GRID_CELL_SIZE, GRID_CELL_SIZE, LIGHTGRAY); | 
						
						
							|  |                 } | 
						
						
							|  |                 else | 
						
						
							|  |                 { | 
						
						
							|  |                     for (int i = firstUndoIndex; i < MAX_UNDO_STATES; i++) | 
						
						
							|  |                         DrawRectangle(gridPosition.x + states[i].cell.x*GRID_CELL_SIZE, gridPosition.y + states[i].cell.y*GRID_CELL_SIZE, | 
						
						
							|  |                             GRID_CELL_SIZE, GRID_CELL_SIZE, LIGHTGRAY); | 
						
						
							|  |                     for (int i = 0; i < currentUndoIndex; i++) | 
						
						
							|  |                         DrawRectangle(gridPosition.x + states[i].cell.x*GRID_CELL_SIZE, gridPosition.y + states[i].cell.y*GRID_CELL_SIZE, | 
						
						
							|  |                             GRID_CELL_SIZE, GRID_CELL_SIZE, LIGHTGRAY); | 
						
						
							|  |                 } | 
						
						
							|  |             } | 
						
						
							|  | 
 | 
						
						
							|  |             // Draw game grid | 
						
						
							|  |             for (int y = 0; y <= MAX_GRID_CELLS_Y; y++) | 
						
						
							|  |                 DrawLine(gridPosition.x, gridPosition.y + y*GRID_CELL_SIZE, | 
						
						
							|  |                     gridPosition.x + MAX_GRID_CELLS_X*GRID_CELL_SIZE, gridPosition.y + y*GRID_CELL_SIZE, GRAY); | 
						
						
							|  |             for (int x = 0; x <= MAX_GRID_CELLS_X; x++) | 
						
						
							|  |                 DrawLine(gridPosition.x + x*GRID_CELL_SIZE, gridPosition.y, | 
						
						
							|  |                     gridPosition.x + x*GRID_CELL_SIZE, gridPosition.y + MAX_GRID_CELLS_Y*GRID_CELL_SIZE, GRAY); | 
						
						
							|  | 
 | 
						
						
							|  |             // Draw player | 
						
						
							|  |             DrawRectangle(gridPosition.x + player.cell.x*GRID_CELL_SIZE, gridPosition.y + player.cell.y*GRID_CELL_SIZE, | 
						
						
							|  |                 GRID_CELL_SIZE + 1, GRID_CELL_SIZE + 1, player.color); | 
						
						
							|  | 
 | 
						
						
							|  |             // Draw undo system buffer info | 
						
						
							|  |             DrawText("UNDO STATES:", undoInfoPos.x - 85, undoInfoPos.y + 9, 10, DARKGRAY); | 
						
						
							|  |             DrawUndoBuffer(undoInfoPos, firstUndoIndex, lastUndoIndex, currentUndoIndex, 24); | 
						
						
							|  | 
 | 
						
						
							|  |         EndDrawing(); | 
						
						
							|  |         //---------------------------------------------------------------------------------- | 
						
						
							|  |     } | 
						
						
							|  | 
 | 
						
						
							|  |     // De-Initialization | 
						
						
							|  |     //-------------------------------------------------------------------------------------- | 
						
						
							|  |     RL_FREE(states);        // Free undo states array | 
						
						
							|  |  | 
						
						
							|  |     CloseWindow();          // Close window and OpenGL context | 
						
						
							|  |     //-------------------------------------------------------------------------------------- | 
						
						
							|  |  | 
						
						
							|  |     return 0; | 
						
						
							|  | } | 
						
						
							|  | 
 | 
						
						
							|  | //------------------------------------------------------------------------------------ | 
						
						
							|  | // Module Functions Definition | 
						
						
							|  | //------------------------------------------------------------------------------------ | 
						
						
							|  | // Draw undo system visualization logic | 
						
						
							|  | // NOTE: Visualizing the ring buffer array, every square can store a player state | 
						
						
							|  | static void DrawUndoBuffer(Vector2 position, int firstUndoIndex, int lastUndoIndex, int currentUndoIndex, int slotSize) | 
						
						
							|  | { | 
						
						
							|  |     // Draw index marks | 
						
						
							|  |     DrawRectangle(position.x + 8 + slotSize*currentUndoIndex, position.y - 10, 8, 8, RED); | 
						
						
							|  |     DrawRectangleLines(position.x + 2 + slotSize*firstUndoIndex, position.y + 27, 8, 8, BLACK); | 
						
						
							|  |     DrawRectangle(position.x + 14 + slotSize*lastUndoIndex, position.y + 27, 8, 8, BLACK); | 
						
						
							|  | 
 | 
						
						
							|  |     // Draw background gray slots | 
						
						
							|  |     for (int i = 0; i < MAX_UNDO_STATES; i++) | 
						
						
							|  |     { | 
						
						
							|  |         DrawRectangle(position.x + slotSize*i, position.y, slotSize, slotSize, LIGHTGRAY); | 
						
						
							|  |         DrawRectangleLines(position.x + slotSize*i, position.y, slotSize, slotSize, GRAY); | 
						
						
							|  |     } | 
						
						
							|  | 
 | 
						
						
							|  |     // Draw occupied slots: firstUndoIndex --> lastUndoIndex | 
						
						
							|  |     if (firstUndoIndex <= lastUndoIndex) | 
						
						
							|  |     { | 
						
						
							|  |         for (int i = firstUndoIndex; i < lastUndoIndex + 1; i++) | 
						
						
							|  |         { | 
						
						
							|  |             DrawRectangle(position.x + slotSize*i, position.y, slotSize, slotSize, SKYBLUE); | 
						
						
							|  |             DrawRectangleLines(position.x + slotSize*i, position.y, slotSize, slotSize, BLUE); | 
						
						
							|  |         } | 
						
						
							|  |     } | 
						
						
							|  |     else if (lastUndoIndex < firstUndoIndex) | 
						
						
							|  |     { | 
						
						
							|  |         for (int i = firstUndoIndex; i < MAX_UNDO_STATES; i++) | 
						
						
							|  |         { | 
						
						
							|  |             DrawRectangle(position.x + slotSize*i, position.y, slotSize, slotSize, SKYBLUE); | 
						
						
							|  |             DrawRectangleLines(position.x + slotSize*i, position.y, slotSize, slotSize, BLUE); | 
						
						
							|  |         } | 
						
						
							|  | 
 | 
						
						
							|  |         for (int i = 0; i < lastUndoIndex + 1; i++) | 
						
						
							|  |         { | 
						
						
							|  |             DrawRectangle(position.x + slotSize*i, position.y, slotSize, slotSize, SKYBLUE); | 
						
						
							|  |             DrawRectangleLines(position.x + slotSize*i, position.y, slotSize, slotSize, BLUE); | 
						
						
							|  |         } | 
						
						
							|  |     } | 
						
						
							|  | 
 | 
						
						
							|  |     // Draw occupied slots: firstUndoIndex --> currentUndoIndex | 
						
						
							|  |     if (firstUndoIndex < currentUndoIndex) | 
						
						
							|  |     { | 
						
						
							|  |         for (int i = firstUndoIndex; i < currentUndoIndex; i++) | 
						
						
							|  |         { | 
						
						
							|  |             DrawRectangle(position.x + slotSize*i, position.y, slotSize, slotSize, GREEN); | 
						
						
							|  |             DrawRectangleLines(position.x + slotSize*i, position.y, slotSize, slotSize, LIME); | 
						
						
							|  |         } | 
						
						
							|  |     } | 
						
						
							|  |     else if (currentUndoIndex < firstUndoIndex) | 
						
						
							|  |     { | 
						
						
							|  |         for (int i = firstUndoIndex; i < MAX_UNDO_STATES; i++) | 
						
						
							|  |         { | 
						
						
							|  |             DrawRectangle(position.x + slotSize*i, position.y, slotSize, slotSize, GREEN); | 
						
						
							|  |             DrawRectangleLines(position.x + slotSize*i, position.y, slotSize, slotSize, LIME); | 
						
						
							|  |         } | 
						
						
							|  | 
 | 
						
						
							|  |         for (int i = 0; i < currentUndoIndex; i++) | 
						
						
							|  |         { | 
						
						
							|  |             DrawRectangle(position.x + slotSize*i, position.y, slotSize, slotSize, GREEN); | 
						
						
							|  |             DrawRectangleLines(position.x + slotSize*i, position.y, slotSize, slotSize, LIME); | 
						
						
							|  |         } | 
						
						
							|  |     } | 
						
						
							|  | 
 | 
						
						
							|  |     // Draw current selected UNDO slot | 
						
						
							|  |     DrawRectangle(position.x + slotSize*currentUndoIndex, position.y, slotSize, slotSize, GOLD); | 
						
						
							|  |     DrawRectangleLines(position.x + slotSize*currentUndoIndex, position.y, slotSize, slotSize, ORANGE); | 
						
						
							|  | }
 |