| @ -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 <string.h> // 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; | |||
| } | |||
| } | |||