diff --git a/examples/core/core_game_of_life.c b/examples/core/core_game_of_life.c new file mode 100644 index 000000000..ad057a24f --- /dev/null +++ b/examples/core/core_game_of_life.c @@ -0,0 +1,282 @@ +/******************************************************************************************* +* +* raylib [core] example - Game of life +* +* +* Example originally created with raylib 5.5 +* +* 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) 2024 Paco Algar Muñoz (@P4k02) +* +********************************************************************************************/ + +/******************************************************************************************* +* +* Controls: +* - Right Mouse Button: Drag to move the camera +* - Left Mouse Button: Toggle cell state in Draw mode +* - Mouse Wheel: Zoom in/out +* - Space Key: Toggle between Play/Draw mode (only works if cells are alive) +* - UP Arrow: Increase generations interval +* - DOWN Arrow: Decrease generations interval +* - R Key: Reset the grid +* +*******************************************************************************************/ + +#include // Required for: memcpy() + +#include "raylib.h" +#include "raymath.h" // Required for: Vector2Add(), Vector2Scale() + +//------------------------------------------------------------------------------------------ +// Constants Definition +//------------------------------------------------------------------------------------------ +#define SCREEN_HEIGHT 900 +#define SCREEN_WIDTH 1000 +#define INITIAL_CAMERA_ZOOM 1.0f +#define ZOOM_SCALE 0.25f +#define BOARD_SPACING 50 +#define BOARD_ROWS 100 +#define BOARD_COLS 100 +#define INIT_INTERVAL 0.2f +#define MAX_GENERATIONS 500 + +//------------------------------------------------------------------------------------------ +// Types and Structures Definition +//------------------------------------------------------------------------------------------ +typedef struct { + int isAlive; + Vector2 pos; + Vector2 size; +} Cell; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static Cell board[BOARD_ROWS][BOARD_COLS]; +static int aliveCells = 0; +static int generations = 0; +static int playMode = 0; + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +static void InitGrid(void); +static void DrawBoard(void); +static void DrawCells(void); +static void UpdateBoard(void); +static int CountAliveNeighbors(int x, int y); +static void ToggleCells(int x, int y); +static void NextGeneration(double *lastGenerationTime, float *generationInterval); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(int argc, char **argv) { + InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raylib [core] example - game of live"); + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + + // Camera initialization + Camera2D camera = { 0 }; + camera.zoom = INITIAL_CAMERA_ZOOM; + camera.offset = (Vector2){ SCREEN_WIDTH / 2.0f, SCREEN_HEIGHT / 2.0f }; + camera.target = (Vector2){ (BOARD_COLS * BOARD_SPACING) / 2.0f, (BOARD_ROWS * BOARD_SPACING) / 2.0f }; + + double lastGenerationTime = 0.0; + float generations_interval = INIT_INTERVAL; + + InitGrid(); + + // Main game loop + while (!WindowShouldClose()) { + if (aliveCells == 0) { + playMode = 0; + generations = 0; + } + + // Toggle play/draw mode with space key + if (IsKeyPressed(KEY_SPACE) && aliveCells > 0 && generations < MAX_GENERATIONS) playMode = !playMode; + + // Move camera with right mouse button + if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) { + Vector2 delta = GetMouseDelta(); + delta = Vector2Scale(delta, -INITIAL_CAMERA_ZOOM / camera.zoom); + camera.target = Vector2Add(camera.target, delta); + } + + // Zoom in/out with mouse wheel + float wheel = GetMouseWheelMove(); + if (wheel) { + Vector2 mousePos = GetScreenToWorld2D(GetMousePosition(), camera); + camera.offset = GetMousePosition(); + camera.target = mousePos; + float scaleFactor = INITIAL_CAMERA_ZOOM + (ZOOM_SCALE * fabsf(wheel)); + if (wheel < 0) scaleFactor = INITIAL_CAMERA_ZOOM / scaleFactor; + camera.zoom = Clamp(camera.zoom * scaleFactor, 0.125f, 64.0f); + } + + // Reset all with R key + if (IsKeyPressed(KEY_R)) { + playMode = 0; + generations = 0; + aliveCells = 0; + generations_interval = INIT_INTERVAL; + InitGrid(); + } + + // Increase generations interval with UP arrow + if (IsKeyPressed(KEY_UP)) { + generations_interval += 0.1f; + if (generations_interval > MAX_GENERATIONS) generations_interval = MAX_GENERATIONS; + } + + // Decrease generations interval with DOWN arrow + if (IsKeyPressed(KEY_DOWN)) { + generations_interval -= 0.1f; + if (generations_interval < 0.0) generations_interval = 0.0; + } + + // Draw mode actions with left mouse button + if (!playMode && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { + Vector2 mousePos = GetScreenToWorld2D(GetMousePosition(), camera); + int boardX = (int)(mousePos.x / BOARD_SPACING); + int boardY = (int)(mousePos.y / BOARD_SPACING); + ToggleCells(boardX, boardY); + } + + if (!playMode && IsKeyPressed(KEY_SPACE)) + NextGeneration(&lastGenerationTime, &generations_interval); + + // Start generations with space key + if (playMode && aliveCells > 0) { + NextGeneration(&lastGenerationTime, &generations_interval); + if (generations >= MAX_GENERATIONS) + playMode = 0; + } + + BeginDrawing(); + ClearBackground(BLACK); + + DrawRectangle(5, 5, 300, 110, Fade(RAYWHITE, 0.8f)); + DrawText(playMode ? "Play mode" : "Draw mode", 10, 10, 20, BLACK); + DrawText(TextFormat("Generation: %d (Max: %d)", generations, MAX_GENERATIONS), 10, 35, 20, BLACK); + DrawText(TextFormat("Cells: %d", aliveCells), 10, 60, 20, BLACK); + DrawText(TextFormat("Generation interval: %.1fs", generations_interval), 10, 85, 20, BLACK); + + BeginMode2D(camera); + DrawBoard(); + DrawCells(); + EndMode2D(); + EndDrawing(); + } + + CloseWindow(); + + return 0; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +static void InitGrid(void) { + for (int i = 0; i < BOARD_ROWS; i++) { + for (int j = 0; j < BOARD_COLS; j++) { + board[i][j].isAlive = 0; + board[i][j].pos.x = j * BOARD_SPACING; + board[i][j].pos.y = i * BOARD_SPACING; + board[i][j].size.x = BOARD_SPACING; + board[i][j].size.y = BOARD_SPACING; + } + } +} + +static void DrawBoard(void) { + for (int i = 0; i <= BOARD_ROWS; i++) { + Vector2 start = {0, i * BOARD_SPACING}; + Vector2 end = {BOARD_COLS * BOARD_SPACING, i * BOARD_SPACING}; + DrawLineV(start, end, GRAY); + } + + for (int j = 0; j <= BOARD_COLS; j++) { + Vector2 start = {j * BOARD_SPACING, 0}; + Vector2 end = {j * BOARD_SPACING, BOARD_ROWS * BOARD_SPACING}; + DrawLineV(start, end, GRAY); + } +} + +static int CountAliveNeighbors(int x, int y) { + int count = 0; + + // This is going to check the 8 surrounding cells + // If any of them are alive, then the cell is alive + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + if (dx == 0 && dy == 0) continue; + + // Calculate the neighbor's coordinates + int nx = (x + dx + BOARD_COLS) % BOARD_COLS; + int ny = (y + dy + BOARD_ROWS) % BOARD_ROWS; + + count += board[ny][nx].isAlive; + } + } + + return count; +} + +static void ToggleCells(int x, int y) { + if (x >= 0 && x < BOARD_COLS && y >= 0 && y < BOARD_ROWS) { + board[y][x].isAlive = !board[y][x].isAlive; + aliveCells += (board[y][x].isAlive ? 1 : -1); + } +} + +static void UpdateBoard(void) { + Cell nextGrid[BOARD_ROWS][BOARD_COLS]; + int newAliveCells = 0; + + for (int i = 0; i < BOARD_ROWS; i++) { + for (int j = 0; j < BOARD_COLS; j++) { + int aliveNeighbors = CountAliveNeighbors(j, i); + nextGrid[i][j] = board[i][j]; + nextGrid[i][j].isAlive = (board[i][j].isAlive && (aliveNeighbors == 2 || aliveNeighbors == 3)) || + (!board[i][j].isAlive && aliveNeighbors == 3); + if (nextGrid[i][j].isAlive) newAliveCells++; + } + } + + memcpy(board, nextGrid, sizeof(board)); + aliveCells = newAliveCells; +} + +static void DrawCells(void) { + for(int i = 0; i < BOARD_ROWS; i++) { + for(int j = 0; j < BOARD_COLS; j++) { + if (board[i][j].isAlive) { + DrawRectangleV(board[i][j].pos, board[i][j].size, WHITE); + DrawRectangleLinesEx( + (Rectangle) { + board[i][j].pos.x, + board[i][j].pos.y, + board[i][j].size.x, + board[i][j].size.y + }, + 1, + BLACK + ); + } + } + } +} + +static void NextGeneration(double *lastGenerationTime, float *generations_interval) { + double time = GetTime(); + + if (time - *lastGenerationTime >= *generations_interval) { + UpdateBoard(); + generations++; + *lastGenerationTime = time; + } +} \ No newline at end of file diff --git a/examples/core/core_game_of_life.png b/examples/core/core_game_of_life.png new file mode 100644 index 000000000..b75c573eb Binary files /dev/null and b/examples/core/core_game_of_life.png differ