From 3a59d7c143c68d4c0d44f467812b5dc6bffe25a4 Mon Sep 17 00:00:00 2001 From: Pacatro Date: Thu, 12 Dec 2024 21:33:21 +0000 Subject: [PATCH] add game of life to core examples --- examples/core/core_game_of_life.c | 282 ++++++++++++++++++++++++++++ examples/core/core_game_of_life.png | Bin 0 -> 7113 bytes 2 files changed, 282 insertions(+) create mode 100644 examples/core/core_game_of_life.c create mode 100644 examples/core/core_game_of_life.png 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 0000000000000000000000000000000000000000..b75c573ebbd4e190f7205c8f26636ed1e2892558 GIT binary patch literal 7113 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 ag5Gqo-zZTo0{`O%96DfSTDJe>PyYp!+PuL4 literal 0 HcmV?d00001