mirror of
https://github.com/Tha14/toxic.git
synced 2024-11-14 18:53:04 +01:00
Implement Conway's Game of Life
This commit is contained in:
parent
c293fbe0c7
commit
5e67571908
@ -1,5 +1,5 @@
|
|||||||
# Variables for game support
|
# Variables for game support
|
||||||
GAMES_CFLAGS = -DGAMES
|
GAMES_CFLAGS = -DGAMES
|
||||||
GAMES_OBJ = game_base.o game_centipede.o game_chess.o game_util.o game_snake.o
|
GAMES_OBJ = game_base.o game_centipede.o game_chess.o game_life.o game_util.o game_snake.o
|
||||||
CFLAGS += $(GAMES_CFLAGS)
|
CFLAGS += $(GAMES_CFLAGS)
|
||||||
OBJ += $(GAMES_OBJ)
|
OBJ += $(GAMES_OBJ)
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
#include "game_centipede.h"
|
#include "game_centipede.h"
|
||||||
#include "game_base.h"
|
#include "game_base.h"
|
||||||
#include "game_chess.h"
|
#include "game_chess.h"
|
||||||
|
#include "game_life.h"
|
||||||
#include "game_snake.h"
|
#include "game_snake.h"
|
||||||
#include "line_info.h"
|
#include "line_info.h"
|
||||||
#include "misc_tools.h"
|
#include "misc_tools.h"
|
||||||
@ -77,6 +78,7 @@ struct GameList {
|
|||||||
static struct GameList game_list[] = {
|
static struct GameList game_list[] = {
|
||||||
{ "centipede", GT_Centipede },
|
{ "centipede", GT_Centipede },
|
||||||
{ "chess", GT_Chess },
|
{ "chess", GT_Chess },
|
||||||
|
{ "life", GT_Life },
|
||||||
{ "snake", GT_Snake },
|
{ "snake", GT_Snake },
|
||||||
{ NULL, GT_Invalid },
|
{ NULL, GT_Invalid },
|
||||||
};
|
};
|
||||||
@ -213,6 +215,11 @@ static int game_initialize_type(GameData *game, const uint8_t *data, size_t leng
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case GT_Life: {
|
||||||
|
ret = life_initialize(game);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -980,6 +987,11 @@ void game_update_score(GameData *game, long int points)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game_set_score(GameData *game, long int val)
|
||||||
|
{
|
||||||
|
game->score = val;
|
||||||
|
}
|
||||||
|
|
||||||
long int game_get_score(const GameData *game)
|
long int game_get_score(const GameData *game)
|
||||||
{
|
{
|
||||||
return game->score;
|
return game->score;
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
#define GAME_MESSAGE_DEFAULT_TIMEOUT 3
|
#define GAME_MESSAGE_DEFAULT_TIMEOUT 3
|
||||||
|
|
||||||
|
|
||||||
/***** NETWORKING DEFINES *****/
|
/***** NETWORKING CONSTANTS *****/
|
||||||
|
|
||||||
/* Header starts after custom packet type byte. Comprised of: NetworkVersion (1b) + GameType (1b) + id (4b) */
|
/* Header starts after custom packet type byte. Comprised of: NetworkVersion (1b) + GameType (1b) + id (4b) */
|
||||||
#define GAME_PACKET_HEADER_SIZE (1 + 1 + sizeof(uint32_t))
|
#define GAME_PACKET_HEADER_SIZE (1 + 1 + sizeof(uint32_t))
|
||||||
@ -99,6 +99,7 @@ typedef enum GameStatus {
|
|||||||
typedef enum GameType {
|
typedef enum GameType {
|
||||||
GT_Centipede = 0u,
|
GT_Centipede = 0u,
|
||||||
GT_Chess,
|
GT_Chess,
|
||||||
|
GT_Life,
|
||||||
GT_Snake,
|
GT_Snake,
|
||||||
GT_Invalid,
|
GT_Invalid,
|
||||||
} GameType;
|
} GameType;
|
||||||
@ -297,6 +298,11 @@ void game_window_notify(const GameData *game, const char *message);
|
|||||||
*/
|
*/
|
||||||
void game_update_score(GameData *game, long int points);
|
void game_update_score(GameData *game, long int points);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets game score to `val`.
|
||||||
|
*/
|
||||||
|
void game_set_score(GameData *game, long int score);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns the game's current score.
|
* Returns the game's current score.
|
||||||
*/
|
*/
|
||||||
|
@ -1593,6 +1593,7 @@ void cent_cb_kill(GameData *game, void *cb_data)
|
|||||||
game_set_cb_update_state(game, NULL, NULL);
|
game_set_cb_update_state(game, NULL, NULL);
|
||||||
game_set_cb_render_window(game, NULL, NULL);
|
game_set_cb_render_window(game, NULL, NULL);
|
||||||
game_set_cb_kill(game, NULL, NULL);
|
game_set_cb_kill(game, NULL, NULL);
|
||||||
|
game_set_cb_on_keypress(game, NULL, NULL);
|
||||||
game_set_cb_on_pause(game, NULL, NULL);
|
game_set_cb_on_pause(game, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1411,7 +1411,7 @@ static void chess_move_curs_left(ChessState *state)
|
|||||||
{
|
{
|
||||||
Board *board = &state->board;
|
Board *board = &state->board;
|
||||||
|
|
||||||
size_t new_x = state->curs_x - CHESS_TILE_SIZE_X;
|
int new_x = state->curs_x - CHESS_TILE_SIZE_X;
|
||||||
|
|
||||||
if (new_x < board->x_left_bound) {
|
if (new_x < board->x_left_bound) {
|
||||||
return;
|
return;
|
||||||
@ -1424,7 +1424,7 @@ static void chess_move_curs_right(ChessState *state)
|
|||||||
{
|
{
|
||||||
Board *board = &state->board;
|
Board *board = &state->board;
|
||||||
|
|
||||||
size_t new_x = state->curs_x + CHESS_TILE_SIZE_X;
|
int new_x = state->curs_x + CHESS_TILE_SIZE_X;
|
||||||
|
|
||||||
if (new_x > board->x_right_bound) {
|
if (new_x > board->x_right_bound) {
|
||||||
return;
|
return;
|
||||||
@ -1437,7 +1437,7 @@ static void chess_move_curs_up(ChessState *state)
|
|||||||
{
|
{
|
||||||
Board *board = &state->board;
|
Board *board = &state->board;
|
||||||
|
|
||||||
size_t new_y = state->curs_y - CHESS_TILE_SIZE_Y;
|
int new_y = state->curs_y - CHESS_TILE_SIZE_Y;
|
||||||
|
|
||||||
if (new_y < board->y_top_bound) {
|
if (new_y < board->y_top_bound) {
|
||||||
return;
|
return;
|
||||||
@ -1450,7 +1450,7 @@ static void chess_move_curs_down(ChessState *state)
|
|||||||
{
|
{
|
||||||
Board *board = &state->board;
|
Board *board = &state->board;
|
||||||
|
|
||||||
size_t new_y = state->curs_y + CHESS_TILE_SIZE_Y;
|
int new_y = state->curs_y + CHESS_TILE_SIZE_Y;
|
||||||
|
|
||||||
if (new_y >= board->y_bottom_bound) {
|
if (new_y >= board->y_bottom_bound) {
|
||||||
return;
|
return;
|
||||||
@ -1716,7 +1716,6 @@ void chess_cb_render_window(GameData *game, WINDOW *win, void *cb_data)
|
|||||||
|
|
||||||
move(state->curs_y, state->curs_x);
|
move(state->curs_y, state->curs_x);
|
||||||
|
|
||||||
|
|
||||||
curs_set(1);
|
curs_set(1);
|
||||||
|
|
||||||
chess_draw_board(win, state);
|
chess_draw_board(win, state);
|
||||||
|
602
src/game_life.c
Normal file
602
src/game_life.c
Normal file
@ -0,0 +1,602 @@
|
|||||||
|
/* game_life.c
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Copyright (C) 2021 Toxic All Rights Reserved.
|
||||||
|
*
|
||||||
|
* This file is part of Toxic.
|
||||||
|
*
|
||||||
|
* Toxic is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Toxic is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "game_life.h"
|
||||||
|
|
||||||
|
#define LIFE_DEFAULT_CELL_CHAR 'o'
|
||||||
|
#define LIFE_CELL_DEFAULT_COLOUR CYAN
|
||||||
|
#define LIFE_DEFAULT_SPEED 25
|
||||||
|
#define LIFE_MAX_SPEED 40
|
||||||
|
|
||||||
|
/* Determines the additional size of the grid beyond the visible boundaries.
|
||||||
|
*
|
||||||
|
* This buffer allows cells to continue growing off-screen giving the illusion of an
|
||||||
|
* infinite grid to a certain point.
|
||||||
|
*/
|
||||||
|
#define LIFE_BOUNDARY_BUFFER 50
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct Cell {
|
||||||
|
Coords coords;
|
||||||
|
bool alive;
|
||||||
|
bool marked; // true if cell should invert alive status at end of current cycle
|
||||||
|
int display_char;
|
||||||
|
size_t age;
|
||||||
|
} Cell;
|
||||||
|
|
||||||
|
typedef struct LifeState {
|
||||||
|
TIME_MS time_last_cycle;
|
||||||
|
size_t speed;
|
||||||
|
size_t generation;
|
||||||
|
|
||||||
|
Cell **cells;
|
||||||
|
int num_columns;
|
||||||
|
int num_rows;
|
||||||
|
|
||||||
|
int curs_x;
|
||||||
|
int curs_y;
|
||||||
|
|
||||||
|
int x_left_bound;
|
||||||
|
int x_right_bound;
|
||||||
|
int y_top_bound;
|
||||||
|
int y_bottom_bound;
|
||||||
|
|
||||||
|
short display_candy;
|
||||||
|
} LifeState;
|
||||||
|
|
||||||
|
|
||||||
|
static void life_increase_speed(LifeState *state)
|
||||||
|
{
|
||||||
|
if (state->speed < LIFE_MAX_SPEED) {
|
||||||
|
++state->speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void life_decrease_speed(LifeState *state)
|
||||||
|
{
|
||||||
|
if (state->speed > 1) {
|
||||||
|
--state->speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int life_get_display_char(const LifeState *state, const Cell *cell)
|
||||||
|
{
|
||||||
|
if (state->display_candy == 1) {
|
||||||
|
if (cell->age == 1) {
|
||||||
|
return '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '+';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state->display_candy == 2) {
|
||||||
|
if (cell->age == 1) {
|
||||||
|
return '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell->age == 2) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell->age == 3) {
|
||||||
|
return 'o';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'O';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'o';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void life_toggle_display_candy(LifeState *state)
|
||||||
|
{
|
||||||
|
state->display_candy = (state->display_candy + 1) % 3; // magic number depends on life_get_display_char()
|
||||||
|
}
|
||||||
|
|
||||||
|
static Cell *life_get_cell_at_coords(const LifeState *state, const int x, const int y)
|
||||||
|
{
|
||||||
|
const int i = y - (state->y_top_bound - (LIFE_BOUNDARY_BUFFER / 2));
|
||||||
|
const int j = x - (state->x_left_bound - (LIFE_BOUNDARY_BUFFER / 2));
|
||||||
|
|
||||||
|
if (i >= 0 && j >= 0) {
|
||||||
|
return &state->cells[i][j];
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void life_draw_cells(const GameData *game, WINDOW *win, LifeState *state)
|
||||||
|
{
|
||||||
|
for (int i = LIFE_BOUNDARY_BUFFER / 2; i < state->num_rows - (LIFE_BOUNDARY_BUFFER / 2); ++i) {
|
||||||
|
for (int j = LIFE_BOUNDARY_BUFFER / 2; j < state->num_columns + 1 - (LIFE_BOUNDARY_BUFFER / 2); ++j) {
|
||||||
|
Cell *cell = &state->cells[i][j];
|
||||||
|
|
||||||
|
if (cell->alive) {
|
||||||
|
Coords coords = cell->coords;
|
||||||
|
wattron(win, A_BOLD | COLOR_PAIR(LIFE_CELL_DEFAULT_COLOUR));
|
||||||
|
mvwaddch(win, coords.y, coords.x, cell->display_char);
|
||||||
|
wattroff(win, A_BOLD | COLOR_PAIR(LIFE_CELL_DEFAULT_COLOUR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void life_toggle_cell(LifeState *state)
|
||||||
|
{
|
||||||
|
Cell *cell = life_get_cell_at_coords(state, state->curs_x, state->curs_y);
|
||||||
|
|
||||||
|
if (cell == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cell->alive ^= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the number of live neighbours of `idx` cell.
|
||||||
|
*/
|
||||||
|
static int life_get_live_neighbours(const LifeState *state, const int i, const int j)
|
||||||
|
{
|
||||||
|
Cell *n[8] = {0};
|
||||||
|
|
||||||
|
if (i > 0 && j > 0) {
|
||||||
|
n[0] = &state->cells[i - 1][j - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
n[1] = &state->cells[i - 1][j];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i > 0 && j < state->num_columns - 1) {
|
||||||
|
n[2] = &state->cells[i - 1][j + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j > 0) {
|
||||||
|
n[3] = &state->cells[i][j - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j < state->num_columns - 1) {
|
||||||
|
n[4] = &state->cells[i][j + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < state->num_rows - 1 && j > 0) {
|
||||||
|
n[5] = &state->cells[i + 1][j - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < state->num_rows - 1) {
|
||||||
|
n[6] = &state->cells[i + 1][j];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < state->num_rows - 1 && j < state->num_columns - 1) {
|
||||||
|
n[7] = &state->cells[i + 1][j + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 8; ++i) {
|
||||||
|
if (n[i] == NULL) {
|
||||||
|
return 0; // If we're at a boundary kill cell
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n[i]->alive) {
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void life_restart(GameData *game, LifeState *state)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < state->num_rows; ++i) {
|
||||||
|
for (int j = 0; j < state->num_columns; ++j) {
|
||||||
|
Cell *cell = &state->cells[i][j];
|
||||||
|
cell->alive = false;
|
||||||
|
cell->marked = false;
|
||||||
|
cell->display_char = LIFE_DEFAULT_CELL_CHAR;
|
||||||
|
cell->age = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
game_set_score(game, 0);
|
||||||
|
|
||||||
|
state->generation = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void life_do_cells(LifeState *state)
|
||||||
|
{
|
||||||
|
|
||||||
|
for (int i = 0; i < state->num_rows; ++i) {
|
||||||
|
for (int j = 0; j < state->num_columns; ++j) {
|
||||||
|
Cell *cell = &state->cells[i][j];
|
||||||
|
|
||||||
|
if (cell->marked) {
|
||||||
|
cell->marked = false;
|
||||||
|
cell->alive ^= 1;
|
||||||
|
cell->age = cell->alive;
|
||||||
|
cell->display_char = life_get_display_char(state, cell);
|
||||||
|
} else if (cell->alive) {
|
||||||
|
++cell->age;
|
||||||
|
cell->display_char = life_get_display_char(state, cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void life_cycle(GameData *game, LifeState *state)
|
||||||
|
{
|
||||||
|
if (state->generation == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TIME_MS cur_time = get_time_millis();
|
||||||
|
|
||||||
|
if (!game_do_object_state_update(game, cur_time, state->time_last_cycle, state->speed)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->time_last_cycle = get_time_millis();
|
||||||
|
|
||||||
|
++state->generation;
|
||||||
|
|
||||||
|
size_t live_cells = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < state->num_rows; ++i) {
|
||||||
|
for (int j = 0; j < state->num_columns; ++j) {
|
||||||
|
Cell *cell = &state->cells[i][j];
|
||||||
|
|
||||||
|
int live_neighbours = life_get_live_neighbours(state, i, j);
|
||||||
|
|
||||||
|
if (cell->alive) {
|
||||||
|
if (!(live_neighbours == 2 || live_neighbours == 3)) {
|
||||||
|
cell->marked = true;
|
||||||
|
} else {
|
||||||
|
++live_cells;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (live_neighbours == 3) {
|
||||||
|
cell->marked = true;
|
||||||
|
++live_cells;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (live_cells == 0) {
|
||||||
|
life_restart(game, state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
life_do_cells(state);
|
||||||
|
|
||||||
|
game_update_score(game, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void life_start(GameData *game, LifeState *state)
|
||||||
|
{
|
||||||
|
state->generation = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void life_cb_update_game_state(GameData *game, void *cb_data)
|
||||||
|
{
|
||||||
|
if (!cb_data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LifeState *state = (LifeState *)cb_data;
|
||||||
|
|
||||||
|
life_cycle(game, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void life_cb_render_window(GameData *game, WINDOW *win, void *cb_data)
|
||||||
|
{
|
||||||
|
if (!cb_data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LifeState *state = (LifeState *)cb_data;
|
||||||
|
|
||||||
|
move(state->curs_y, state->curs_x);
|
||||||
|
|
||||||
|
curs_set(1);
|
||||||
|
|
||||||
|
life_draw_cells(game, win, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void life_move_curs_left(LifeState *state)
|
||||||
|
{
|
||||||
|
int new_x = state->curs_x - 1;
|
||||||
|
|
||||||
|
if (new_x < state->x_left_bound) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->curs_x = new_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void life_move_curs_right(LifeState *state)
|
||||||
|
{
|
||||||
|
int new_x = state->curs_x + 1;
|
||||||
|
|
||||||
|
if (new_x > state->x_right_bound) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->curs_x = new_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void life_move_curs_up(LifeState *state)
|
||||||
|
{
|
||||||
|
int new_y = state->curs_y - 1;
|
||||||
|
|
||||||
|
if (new_y < state->y_top_bound) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->curs_y = new_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void life_move_curs_down(LifeState *state)
|
||||||
|
{
|
||||||
|
int new_y = state->curs_y + 1;
|
||||||
|
|
||||||
|
if (new_y >= state->y_bottom_bound) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->curs_y = new_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void life_move_curs_up_left(LifeState *state)
|
||||||
|
{
|
||||||
|
life_move_curs_up(state);
|
||||||
|
life_move_curs_left(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void life_move_curs_up_right(LifeState *state)
|
||||||
|
{
|
||||||
|
life_move_curs_up(state);
|
||||||
|
life_move_curs_right(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void life_move_curs_down_right(LifeState *state)
|
||||||
|
{
|
||||||
|
life_move_curs_down(state);
|
||||||
|
life_move_curs_right(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void life_move_curs_down_left(LifeState *state)
|
||||||
|
{
|
||||||
|
life_move_curs_down(state);
|
||||||
|
life_move_curs_left(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void life_cb_on_keypress(GameData *game, int key, void *cb_data)
|
||||||
|
{
|
||||||
|
if (!cb_data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LifeState *state = (LifeState *)cb_data;
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case KEY_LEFT: {
|
||||||
|
life_move_curs_left(state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case KEY_RIGHT: {
|
||||||
|
life_move_curs_right(state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case KEY_DOWN: {
|
||||||
|
life_move_curs_down(state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case KEY_UP: {
|
||||||
|
life_move_curs_up(state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case KEY_HOME: {
|
||||||
|
life_move_curs_up_left(state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case KEY_END: {
|
||||||
|
life_move_curs_down_left(state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case KEY_PPAGE: {
|
||||||
|
life_move_curs_up_right(state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case KEY_NPAGE: {
|
||||||
|
life_move_curs_down_right(state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '\r': {
|
||||||
|
if (state->generation > 0) {
|
||||||
|
life_restart(game, state);
|
||||||
|
} else {
|
||||||
|
life_start(game, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ' ': {
|
||||||
|
life_toggle_cell(state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '=':
|
||||||
|
|
||||||
|
/* intentional fallthrough */
|
||||||
|
|
||||||
|
case '+': {
|
||||||
|
life_increase_speed(state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
|
||||||
|
/* intentional fallthrough */
|
||||||
|
|
||||||
|
case '_': {
|
||||||
|
life_decrease_speed(state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '\t': {
|
||||||
|
life_toggle_display_candy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void life_free_cells(LifeState *state)
|
||||||
|
{
|
||||||
|
if (state->cells == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < state->num_rows; ++i) {
|
||||||
|
if (state->cells[i]) {
|
||||||
|
free(state->cells[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(state->cells);
|
||||||
|
}
|
||||||
|
|
||||||
|
void life_cb_kill(GameData *game, void *cb_data)
|
||||||
|
{
|
||||||
|
if (!cb_data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LifeState *state = (LifeState *)cb_data;
|
||||||
|
|
||||||
|
life_free_cells(state);
|
||||||
|
free(state);
|
||||||
|
|
||||||
|
game_set_cb_update_state(game, NULL, NULL);
|
||||||
|
game_set_cb_render_window(game, NULL, NULL);
|
||||||
|
game_set_cb_kill(game, NULL, NULL);
|
||||||
|
game_set_cb_on_keypress(game, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int life_init_state(GameData *game, LifeState *state)
|
||||||
|
{
|
||||||
|
const int x_left = game_x_left_bound(game) ;
|
||||||
|
const int x_right = game_x_right_bound(game);
|
||||||
|
const int y_top = game_y_top_bound(game);
|
||||||
|
const int y_bottom = game_y_bottom_bound(game) + 1;
|
||||||
|
|
||||||
|
state->x_left_bound = x_left;
|
||||||
|
state->x_right_bound = x_right;
|
||||||
|
state->y_top_bound = y_top;
|
||||||
|
state->y_bottom_bound = y_bottom;
|
||||||
|
|
||||||
|
const int x_mid = x_left + ((x_right - x_left) / 2);
|
||||||
|
const int y_mid = y_top + ((y_bottom - y_top) / 2);
|
||||||
|
|
||||||
|
state->curs_x = x_mid;
|
||||||
|
state->curs_y = y_mid;
|
||||||
|
|
||||||
|
const int num_rows = (y_bottom - y_top) + LIFE_BOUNDARY_BUFFER;
|
||||||
|
const int num_columns = (x_right - x_left) + LIFE_BOUNDARY_BUFFER;
|
||||||
|
|
||||||
|
if (num_rows <= 0 || num_columns <= 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->num_columns = num_columns;
|
||||||
|
state->num_rows = num_rows;
|
||||||
|
|
||||||
|
state->cells = calloc(1, num_rows * sizeof(Cell *));
|
||||||
|
|
||||||
|
if (state->cells == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < num_rows; ++i) {
|
||||||
|
state->cells[i] = calloc(1, num_columns * sizeof(Cell));
|
||||||
|
|
||||||
|
if (state->cells[i] == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < num_columns; ++j) {
|
||||||
|
state->cells[i][j].coords.y = i + (state->y_top_bound - (LIFE_BOUNDARY_BUFFER / 2));
|
||||||
|
state->cells[i][j].coords.x = j + (state->x_left_bound - (LIFE_BOUNDARY_BUFFER / 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state->speed = LIFE_DEFAULT_SPEED;
|
||||||
|
|
||||||
|
life_restart(game, state);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int life_initialize(GameData *game)
|
||||||
|
{
|
||||||
|
if (game_set_window_shape(game, GW_ShapeRectangle) == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
LifeState *state = calloc(1, sizeof(LifeState));
|
||||||
|
|
||||||
|
if (state == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (life_init_state(game, state) == -1) {
|
||||||
|
life_free_cells(state);
|
||||||
|
free(state);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
game_set_update_interval(game, 40);
|
||||||
|
game_show_score(game, true);
|
||||||
|
|
||||||
|
game_set_cb_update_state(game, life_cb_update_game_state, state);
|
||||||
|
game_set_cb_render_window(game, life_cb_render_window, state);
|
||||||
|
game_set_cb_on_keypress(game, life_cb_on_keypress, state);
|
||||||
|
game_set_cb_kill(game, life_cb_kill, state);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
31
src/game_life.h
Normal file
31
src/game_life.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/* game_life.h
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Copyright (C) 2021 Toxic All Rights Reserved.
|
||||||
|
*
|
||||||
|
* This file is part of Toxic.
|
||||||
|
*
|
||||||
|
* Toxic is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Toxic is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef GAME_LIFE
|
||||||
|
#define GAME_LIFE
|
||||||
|
|
||||||
|
#include "game_base.h"
|
||||||
|
|
||||||
|
int life_initialize(GameData *game);
|
||||||
|
|
||||||
|
#endif // GAME_LIFE
|
||||||
|
|
@ -801,6 +801,7 @@ void snake_cb_kill(GameData *game, void *cb_data)
|
|||||||
game_set_cb_update_state(game, NULL, NULL);
|
game_set_cb_update_state(game, NULL, NULL);
|
||||||
game_set_cb_render_window(game, NULL, NULL);
|
game_set_cb_render_window(game, NULL, NULL);
|
||||||
game_set_cb_kill(game, NULL, NULL);
|
game_set_cb_kill(game, NULL, NULL);
|
||||||
|
game_set_cb_on_keypress(game, NULL, NULL);
|
||||||
game_set_cb_on_pause(game, NULL, NULL);
|
game_set_cb_on_pause(game, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user