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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