mirror of
https://github.com/Tha14/toxic.git
synced 2024-12-22 17:33:26 +01:00
Add game module (WIP)
This commit is contained in:
parent
556a522637
commit
a623976a0e
6
Makefile
6
Makefile
@ -13,9 +13,9 @@ LDFLAGS ?=
|
||||
LDFLAGS += ${USER_LDFLAGS}
|
||||
|
||||
OBJ = autocomplete.o avatars.o bootstrap.o chat.o chat_commands.o configdir.o curl_util.o execute.o
|
||||
OBJ += file_transfers.o friendlist.o global_commands.o conference_commands.o conference.o help.o input.o
|
||||
OBJ += line_info.o log.o message_queue.o misc_tools.o name_lookup.o notify.o prompt.o qr_code.o settings.o
|
||||
OBJ += term_mplex.o toxic.o toxic_strings.o windows.o
|
||||
OBJ += file_transfers.o friendlist.o game_base.o game_centipede.o game_util.o game_snake.o global_commands.o conference_commands.o
|
||||
OBJ += conference.o help.o input.o line_info.o log.o message_queue.o misc_tools.o name_lookup.o notify.o
|
||||
OBJ += prompt.o qr_code.o settings.o term_mplex.o toxic.o toxic_strings.o windows.o
|
||||
|
||||
# Check if debug build is enabled
|
||||
RELEASE := $(shell if [ -z "$(ENABLE_RELEASE)" ] || [ "$(ENABLE_RELEASE)" = "0" ] ; then echo disabled ; else echo enabled ; fi)
|
||||
|
@ -76,6 +76,7 @@ static const char *chat_cmd_list[] = {
|
||||
"/connect",
|
||||
"/exit",
|
||||
"/conference",
|
||||
"/game",
|
||||
"/help",
|
||||
"/invite",
|
||||
"/join",
|
||||
|
@ -88,6 +88,7 @@ static const char *conference_cmd_list[] = {
|
||||
"/decline",
|
||||
"/exit",
|
||||
"/conference",
|
||||
"/game",
|
||||
"/help",
|
||||
"/log",
|
||||
#ifdef AUDIO
|
||||
|
@ -49,6 +49,7 @@ static struct cmd_func global_commands[] = {
|
||||
{ "/decline", cmd_decline },
|
||||
{ "/exit", cmd_quit },
|
||||
{ "/conference", cmd_conference },
|
||||
{ "/game", cmd_game },
|
||||
{ "/help", cmd_prompt_help },
|
||||
{ "/log", cmd_log },
|
||||
{ "/myid", cmd_myid },
|
||||
|
914
src/game_base.c
Normal file
914
src/game_base.c
Normal file
@ -0,0 +1,914 @@
|
||||
/* game_base.c
|
||||
*
|
||||
*
|
||||
* Copyright (C) 2020 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 <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "game_centipede.h"
|
||||
#include "game_base.h"
|
||||
#include "game_snake.h"
|
||||
#include "line_info.h"
|
||||
#include "misc_tools.h"
|
||||
|
||||
/*
|
||||
* Determines the base rate at which game objects should update their state.
|
||||
* Inversely correlated with frame rate.
|
||||
*/
|
||||
#define GAME_OBJECT_UPDATE_INTERVAL_MULTIPLIER 25
|
||||
|
||||
/* Determines overall game speed; lower makes it faster and vice versa.
|
||||
* Inversely correlated with frame rate.
|
||||
*/
|
||||
#define GAME_DEFAULT_UPDATE_INTERVAL 10
|
||||
#define GAME_MAX_UPDATE_INTERVAL 50
|
||||
|
||||
|
||||
#define GAME_BORDER_COLOUR BAR_TEXT
|
||||
|
||||
/* Determines if window is large enough for a respective window type */
|
||||
#define WINDOW_SIZE_LARGE_SQUARE_VALID(max_x, max_y)((((max_y) - 4) >= (GAME_MAX_SQUARE_Y))\
|
||||
&& ((max_x) >= (GAME_MAX_SQUARE_X)))
|
||||
|
||||
#define WINDOW_SIZE_SMALL_SQUARE_VALID(max_x, max_y)((((max_y) - 4) >= (GAME_MAX_SQUARE_Y_SMALL))\
|
||||
&& ((max_x) >= (GAME_MAX_SQUARE_X_SMALL)))
|
||||
|
||||
#define WINDOW_SIZE_LARGE_RECT_VALID(max_x, max_y)((((max_y) - 4) >= (GAME_MAX_RECT_Y))\
|
||||
&& ((max_x) >= (GAME_MAX_RECT_X)))
|
||||
|
||||
#define WINDOW_SIZE_SMALL_RECT_VALID(max_x, max_y)((((max_y) - 4) >= (GAME_MAX_RECT_Y_SMALL))\
|
||||
&& ((max_x) >= (GAME_MAX_RECT_X_SMALL)))
|
||||
|
||||
|
||||
static ToxWindow *game_new_window(GameType type);
|
||||
|
||||
struct GameList {
|
||||
const char *name;
|
||||
GameType type;
|
||||
};
|
||||
|
||||
static struct GameList game_list[] = {
|
||||
{ "centipede", GT_Centipede },
|
||||
{ "snake", GT_Snake },
|
||||
{ NULL, GT_Invalid },
|
||||
};
|
||||
|
||||
|
||||
static void game_get_parent_max_x_y(const ToxWindow *parent, int *max_x, int *max_y)
|
||||
{
|
||||
getmaxyx(parent->window, *max_y, *max_x);
|
||||
*max_y -= (CHATBOX_HEIGHT + WINDOW_BAR_HEIGHT);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the GameType associated with `game_string`.
|
||||
*/
|
||||
GameType game_get_type(const char *game_string)
|
||||
{
|
||||
const char *match_string = NULL;
|
||||
|
||||
for (size_t i = 0; (match_string = game_list[i].name); ++i) {
|
||||
if (strcasecmp(game_string, match_string) == 0) {
|
||||
return game_list[i].type;
|
||||
}
|
||||
}
|
||||
|
||||
return GT_Invalid;
|
||||
}
|
||||
|
||||
/* Returns the string name associated with `type`. */
|
||||
static const char *game_get_name_string(GameType type)
|
||||
{
|
||||
GameType match_type;
|
||||
|
||||
for (size_t i = 0; (match_type = game_list[i].type) < GT_Invalid; ++i) {
|
||||
if (match_type == type) {
|
||||
return game_list[i].name;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prints all available games to window associated with `self`.
|
||||
*/
|
||||
void game_list_print(ToxWindow *self)
|
||||
{
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Available games:");
|
||||
|
||||
const char *name = NULL;
|
||||
|
||||
for (size_t i = 0; (name = game_list[i].name); ++i) {
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%d: %s", i + 1, name);
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns the current wall time in milliseconds */
|
||||
TIME_MS get_time_millis(void)
|
||||
{
|
||||
struct timespec t;
|
||||
timespec_get(&t, TIME_UTC);
|
||||
return ((TIME_MS) t.tv_sec) * 1000 + ((TIME_MS) t.tv_nsec) / 1000000;
|
||||
}
|
||||
|
||||
void game_kill(ToxWindow *self)
|
||||
{
|
||||
GameData *game = self->game;
|
||||
|
||||
if (game->cb_game_kill) {
|
||||
game->cb_game_kill(game, game->cb_game_kill_data);
|
||||
}
|
||||
|
||||
delwin(game->window);
|
||||
free(game->messages);
|
||||
free(game);
|
||||
del_window(self);
|
||||
}
|
||||
|
||||
static void game_toggle_pause(GameData *game)
|
||||
{
|
||||
GameStatus status = game->status;
|
||||
|
||||
if (status == GS_Running) {
|
||||
game->status = GS_Paused;
|
||||
} else if (status == GS_Paused) {
|
||||
game->status = GS_Running;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (game->cb_game_pause) {
|
||||
game->cb_game_pause(game, game->status == GS_Paused, game->cb_game_pause_data);
|
||||
}
|
||||
}
|
||||
|
||||
static int game_initialize_type(GameData *game)
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
switch (game->type) {
|
||||
case GT_Snake: {
|
||||
ret = snake_initialize(game);
|
||||
break;
|
||||
}
|
||||
|
||||
case GT_Centipede: {
|
||||
ret = centipede_initialize(game);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initializes game instance.
|
||||
*
|
||||
* Return 0 on success.
|
||||
* Return -1 if screen is too small.
|
||||
* Return -2 on other failure.
|
||||
*/
|
||||
int game_initialize(const ToxWindow *parent, Tox *m, GameType type, bool force_small_window)
|
||||
{
|
||||
int max_x;
|
||||
int max_y;
|
||||
game_get_parent_max_x_y(parent, &max_x, &max_y);
|
||||
|
||||
int max_game_window_x = GAME_MAX_SQUARE_X;
|
||||
int max_game_window_y = GAME_MAX_SQUARE_Y;
|
||||
|
||||
if (!force_small_window && !WINDOW_SIZE_LARGE_SQUARE_VALID(max_x, max_y)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (force_small_window) {
|
||||
max_game_window_x = GAME_MAX_SQUARE_X_SMALL;
|
||||
max_game_window_y = GAME_MAX_SQUARE_Y_SMALL;
|
||||
|
||||
if (!WINDOW_SIZE_SMALL_SQUARE_VALID(max_x, max_y)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ToxWindow *self = game_new_window(type);
|
||||
|
||||
if (self == NULL) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
GameData *game = self->game;
|
||||
|
||||
int window_id = add_window(m, self);
|
||||
|
||||
if (window_id == -1) {
|
||||
free(game);
|
||||
free(self);
|
||||
return -2;
|
||||
}
|
||||
|
||||
game->parent = parent;
|
||||
game->window_shape = GW_ShapeSquare;
|
||||
game->game_max_x = max_game_window_x;
|
||||
game->game_max_y = max_game_window_y;
|
||||
game->update_interval = GAME_DEFAULT_UPDATE_INTERVAL;
|
||||
game->type = type;
|
||||
game->window_id = window_id;
|
||||
game->level = 0;
|
||||
game->window = subwin(self->window, max_y, max_x, 0, 0);
|
||||
|
||||
if (game->window == NULL) {
|
||||
game_kill(self);
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (game_initialize_type(game) == -1) {
|
||||
game_kill(self);
|
||||
return -1;
|
||||
}
|
||||
|
||||
game->status = GS_Running;
|
||||
|
||||
set_active_window_index(window_id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int game_set_window_shape(GameData *game, GameWindowShape shape)
|
||||
{
|
||||
if (shape >= GW_ShapeInvalid) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (game->status != GS_None) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (shape == game->window_shape) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (shape == GW_ShapeSquare) {
|
||||
game->game_max_x = GAME_MAX_SQUARE_X;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const ToxWindow *parent = game->parent;
|
||||
|
||||
int max_x;
|
||||
int max_y;
|
||||
game_get_parent_max_x_y(parent, &max_x, &max_y);
|
||||
|
||||
if (WINDOW_SIZE_LARGE_RECT_VALID(max_x, max_y)) {
|
||||
game->game_max_x = GAME_MAX_RECT_X;
|
||||
game->game_max_y = GAME_MAX_RECT_Y;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (WINDOW_SIZE_SMALL_RECT_VALID(max_x, max_y)) {
|
||||
game->game_max_x = GAME_MAX_RECT_X_SMALL;
|
||||
game->game_max_y = GAME_MAX_RECT_Y_SMALL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void game_fix_message_coords(const GameData *game, Direction direction, Coords *coords, size_t length)
|
||||
{
|
||||
if (direction >= INVALID_DIRECTION) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (direction == EAST || direction == WEST) {
|
||||
coords->y = game_coordinates_in_bounds(game, coords->x, coords->y + 2) ? coords->y + 2 : coords->y - 2;
|
||||
} else {
|
||||
coords->x = game_coordinates_in_bounds(game, coords->x + 2, coords->y)
|
||||
? coords->x + 2 : coords->x - (length + 2);
|
||||
}
|
||||
|
||||
if (!game_coordinates_in_bounds(game, coords->x + length, coords->y)
|
||||
|| !game_coordinates_in_bounds(game, coords->x, coords->y)) {
|
||||
int max_x;
|
||||
int max_y;
|
||||
getmaxyx(game->window, max_y, max_x);
|
||||
|
||||
const int x_left_bound = (max_x - game->game_max_x) / 2;
|
||||
const int x_right_bound = (max_x + game->game_max_x) / 2;
|
||||
const int y_top_bound = (max_y - game->game_max_y) / 2;
|
||||
const int y_bottom_bound = (max_y + game->game_max_y) / 2;
|
||||
|
||||
if (coords->x + length >= x_right_bound) {
|
||||
coords->x -= (length + 2);
|
||||
} else if (coords->x <= x_left_bound) {
|
||||
coords->x = x_left_bound + 2;
|
||||
}
|
||||
|
||||
if (coords->y >= y_bottom_bound) {
|
||||
coords->y -= 2;
|
||||
} else if (coords->y <= y_top_bound) {
|
||||
coords->y += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void game_clear_message(GameData *game, size_t index)
|
||||
{
|
||||
memset(&game->messages[index], 0, sizeof(GameMessage));
|
||||
}
|
||||
|
||||
static void game_clear_all_messages(GameData *game)
|
||||
{
|
||||
for (size_t i = 0; i < game->messages_size; ++i) {
|
||||
game_clear_message(game, i);
|
||||
}
|
||||
}
|
||||
|
||||
static GameMessage *game_get_new_message_holder(GameData *game)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < game->messages_size; ++i) {
|
||||
GameMessage *m = &game->messages[i];
|
||||
|
||||
if (m->length == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == game->messages_size) {
|
||||
GameMessage *tmp = realloc(game->messages, sizeof(GameMessage) * (i + 1));
|
||||
|
||||
if (tmp == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(&tmp[i], 0, sizeof(GameMessage));
|
||||
|
||||
game->messages = tmp;
|
||||
game->messages_size = i + 1;
|
||||
}
|
||||
|
||||
return &game->messages[i];
|
||||
|
||||
}
|
||||
|
||||
int game_set_message(GameData *game, const char *message, size_t length, Direction dir, int attributes, int colour,
|
||||
TIME_S timeout, const Coords *coords, bool sticky, bool priority)
|
||||
{
|
||||
if (length == 0 || length > GAME_MAX_MESSAGE_SIZE) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (coords == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
GameMessage *m = game_get_new_message_holder(game);
|
||||
|
||||
if (m == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(m->message, message, length);
|
||||
m->message[length] = 0;
|
||||
|
||||
m->length = length;
|
||||
m->timeout = timeout > 0 ? timeout : GAME_MESSAGE_DEFAULT_TIMEOUT;
|
||||
m->set_time = get_unix_time();
|
||||
m->attributes = attributes;
|
||||
m->colour = colour;
|
||||
m->direction = dir;
|
||||
m->coords = coords;
|
||||
m->sticky = sticky;
|
||||
m->priority = priority;
|
||||
|
||||
m->original_coords = (Coords) {
|
||||
coords->x, coords->y
|
||||
};
|
||||
|
||||
if (GAME_UTIL_DIRECTION_VALID(dir)) {
|
||||
game_fix_message_coords(game, dir, &m->original_coords, length);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int game_restart(GameData *game)
|
||||
{
|
||||
if (game->cb_game_kill) {
|
||||
game->cb_game_kill(game, game->cb_game_kill_data);
|
||||
}
|
||||
|
||||
game->update_interval = GAME_DEFAULT_UPDATE_INTERVAL;
|
||||
game->status = GS_Running;
|
||||
game->score = 0;
|
||||
game->level = 0;
|
||||
game->lives = 0;
|
||||
game->last_frame_time = 0;
|
||||
|
||||
game_clear_all_messages(game);
|
||||
|
||||
if (game_initialize_type(game) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void game_draw_help_bar(WINDOW *win)
|
||||
{
|
||||
int max_x;
|
||||
int max_y;
|
||||
UNUSED_VAR(max_x);
|
||||
|
||||
getmaxyx(win, max_y, max_x);
|
||||
|
||||
wmove(win, max_y - 1, 1);
|
||||
|
||||
wprintw(win, "Pause: ");
|
||||
wattron(win, A_BOLD);
|
||||
wprintw(win, "F2 ");
|
||||
wattroff(win, A_BOLD);
|
||||
|
||||
wprintw(win, "Quit: ");
|
||||
wattron(win, A_BOLD);
|
||||
wprintw(win, "F9");
|
||||
wattroff(win, A_BOLD);
|
||||
}
|
||||
|
||||
static void game_draw_border(const GameData *game, const int max_x, const int max_y)
|
||||
{
|
||||
WINDOW *win = game->window;
|
||||
|
||||
const int game_max_x = game->game_max_x;
|
||||
const int game_max_y = game->game_max_y;
|
||||
|
||||
const int x = (max_x - game_max_x) / 2;
|
||||
const int y = (max_y - game_max_y) / 2;
|
||||
|
||||
wattron(win, A_BOLD | COLOR_PAIR(GAME_BORDER_COLOUR));
|
||||
|
||||
mvwaddch(win, y, x, ' ');
|
||||
mvwhline(win, y, x + 1, ' ', game_max_x - 1);
|
||||
mvwvline(win, y + 1, x, ' ', game_max_y - 1);
|
||||
mvwvline(win, y, x - 1, ' ', game_max_y + 1);
|
||||
mvwaddch(win, y, x + game_max_x, ' ');
|
||||
mvwvline(win, y + 1, x + game_max_x, ' ', game_max_y - 1);
|
||||
mvwvline(win, y, x + game_max_x + 1, ' ', game_max_y + 1);
|
||||
mvwaddch(win, y + game_max_y, x, ' ');
|
||||
mvwhline(win, y + game_max_y, x + 1, ' ', game_max_x - 1);
|
||||
mvwaddch(win, y + game_max_y, x + game_max_x, ' ');
|
||||
|
||||
wattroff(win, A_BOLD | COLOR_PAIR(GAME_BORDER_COLOUR));
|
||||
}
|
||||
|
||||
static void game_draw_status(const GameData *game, const int max_x, const int max_y)
|
||||
{
|
||||
WINDOW *win = game->window;
|
||||
|
||||
int x = ((max_x - game->game_max_x) / 2) - 1;
|
||||
int y = ((max_y - game->game_max_y) / 2) - 1;
|
||||
|
||||
wattron(win, A_BOLD);
|
||||
mvwprintw(win, y, x, "Score: %zu", game->score);
|
||||
|
||||
mvwprintw(win, y + game->game_max_y + 2, x, "High Score: %zu", game->high_score);
|
||||
|
||||
x = ((max_x / 2) + (game->game_max_x / 2)) - 7;
|
||||
|
||||
if (game->level > 0) {
|
||||
mvwprintw(win, y, x, "Level: %zu", game->level);
|
||||
}
|
||||
|
||||
if (game->lives >= 0) {
|
||||
mvwprintw(win, y + game->game_max_y + 2, x, "Lives: %zu", game->lives);
|
||||
}
|
||||
|
||||
wattroff(win, A_BOLD);
|
||||
}
|
||||
|
||||
static void game_draw_game_over(const GameData *game)
|
||||
{
|
||||
WINDOW *win = game->window;
|
||||
|
||||
int max_x;
|
||||
int max_y;
|
||||
getmaxyx(win, max_y, max_x);
|
||||
|
||||
const int x = max_x / 2;
|
||||
const int y = max_y / 2;
|
||||
|
||||
const char *message = "GAME OVER!";
|
||||
size_t length = strlen(message);
|
||||
|
||||
wattron(win, A_BOLD | COLOR_PAIR(RED));
|
||||
mvwprintw(win, y - 1, x - (length / 2), "%s", message);
|
||||
wattroff(win, A_BOLD | COLOR_PAIR(RED));
|
||||
|
||||
message = "Press F5 to play again";
|
||||
length = strlen(message);
|
||||
|
||||
mvwprintw(win, y + 1, x - (length / 2), "%s", message);
|
||||
}
|
||||
|
||||
static void game_draw_pause_screen(const GameData *game)
|
||||
{
|
||||
WINDOW *win = game->window;
|
||||
|
||||
int max_x;
|
||||
int max_y;
|
||||
getmaxyx(win, max_y, max_x);
|
||||
|
||||
const int x = max_x / 2;
|
||||
const int y = max_y / 2;
|
||||
|
||||
wattron(win, A_BOLD | COLOR_PAIR(YELLOW));
|
||||
mvwprintw(win, y, x - 3, "PAUSED");
|
||||
wattroff(win, A_BOLD | COLOR_PAIR(YELLOW));
|
||||
}
|
||||
|
||||
static void game_draw_messages(GameData *game, bool priority)
|
||||
{
|
||||
WINDOW *win = game->window;
|
||||
|
||||
for (size_t i = 0; i < game->messages_size; ++i) {
|
||||
GameMessage *m = &game->messages[i];
|
||||
|
||||
if (m->length == 0 || m->coords == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (timed_out(m->set_time, m->timeout)) {
|
||||
game_clear_message(game, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m->priority != priority) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!m->sticky) {
|
||||
wattron(win, m->attributes | COLOR_PAIR(m->colour));
|
||||
mvwprintw(win, m->original_coords.y, m->original_coords.x, "%s", m->message);
|
||||
wattroff(win, m->attributes | COLOR_PAIR(m->colour));
|
||||
continue;
|
||||
}
|
||||
|
||||
Coords fixed_coords = {
|
||||
m->coords->x,
|
||||
m->coords->y
|
||||
};
|
||||
|
||||
// TODO: we should only have to do this if the coordinates changed
|
||||
game_fix_message_coords(game, m->direction, &fixed_coords, m->length);
|
||||
|
||||
wattron(win, m->attributes | COLOR_PAIR(m->colour));
|
||||
mvwprintw(win, fixed_coords.y, fixed_coords.x, "%s", m->message);
|
||||
wattroff(win, m->attributes | COLOR_PAIR(m->colour));
|
||||
}
|
||||
}
|
||||
|
||||
static void game_update_state(GameData *game)
|
||||
{
|
||||
if (!game->cb_game_update_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
TIME_MS cur_time = get_time_millis();
|
||||
|
||||
if (cur_time - game->last_frame_time > 500) {
|
||||
game->last_frame_time = cur_time;
|
||||
}
|
||||
|
||||
size_t iterations = (cur_time - game->last_frame_time) / game->update_interval;
|
||||
|
||||
for (size_t i = 0; i < iterations; ++i) {
|
||||
game->cb_game_update_state(game, game->cb_game_update_state_data);
|
||||
game->last_frame_time += game->update_interval;
|
||||
}
|
||||
}
|
||||
|
||||
void game_onDraw(ToxWindow *self, Tox *m)
|
||||
{
|
||||
UNUSED_VAR(m); // Note: This function is not thread safe if we ever need to use `m`
|
||||
|
||||
curs_set(0);
|
||||
|
||||
game_draw_help_bar(self->window);
|
||||
draw_window_bar(self);
|
||||
|
||||
GameData *game = self->game;
|
||||
|
||||
int max_x;
|
||||
int max_y;
|
||||
getmaxyx(game->window, max_y, max_x);
|
||||
|
||||
wclear(game->window);
|
||||
|
||||
game_draw_border(game, max_x, max_y);
|
||||
|
||||
game_draw_messages(game, false);
|
||||
|
||||
if (game->cb_game_render_window) {
|
||||
game->cb_game_render_window(game, game->window, game->cb_game_render_window_data);
|
||||
}
|
||||
|
||||
game_draw_status(game, max_x, max_y);
|
||||
|
||||
switch (game->status) {
|
||||
case GS_Running: {
|
||||
game_update_state(game);
|
||||
break;
|
||||
}
|
||||
|
||||
case GS_Paused: {
|
||||
game_draw_pause_screen(game);
|
||||
break;
|
||||
}
|
||||
|
||||
case GS_Finished: {
|
||||
game_draw_game_over(game);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
fprintf(stderr, "Unknown game status: %d\n", game->status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
game_draw_messages(game, true);
|
||||
|
||||
wnoutrefresh(self->window);
|
||||
}
|
||||
|
||||
bool game_onKey(ToxWindow *self, Tox *m, wint_t key, bool is_printable)
|
||||
{
|
||||
UNUSED_VAR(m); // Note: this function is not thread safe if we ever need to use `m`
|
||||
UNUSED_VAR(is_printable);
|
||||
|
||||
GameData *game = self->game;
|
||||
|
||||
if (key == KEY_F(9)) {
|
||||
game_kill(self);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == KEY_F(2)) {
|
||||
game_toggle_pause(self->game);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (game->status == GS_Finished && key == KEY_F(5)) {
|
||||
if (game_restart(self->game) == -1) {
|
||||
fprintf(stderr, "Warning: game_restart() failed\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (game->cb_game_key_press) {
|
||||
game->cb_game_key_press(game, key, game->cb_game_key_press_data);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void game_onInit(ToxWindow *self, Tox *m)
|
||||
{
|
||||
UNUSED_VAR(m);
|
||||
|
||||
int max_x;
|
||||
int max_y;
|
||||
getmaxyx(self->window, max_y, max_x);
|
||||
|
||||
if (max_y <= 0 || max_x <= 0) {
|
||||
exit_toxic_err("failed in game_onInit", FATALERR_CURSES);
|
||||
}
|
||||
|
||||
self->window_bar = subwin(self->window, WINDOW_BAR_HEIGHT, max_x, max_y - 2, 0);
|
||||
}
|
||||
|
||||
static ToxWindow *game_new_window(GameType type)
|
||||
{
|
||||
const char *window_name = game_get_name_string(type);
|
||||
|
||||
if (window_name == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ToxWindow *ret = calloc(1, sizeof(ToxWindow));
|
||||
|
||||
if (ret == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret->type = WINDOW_TYPE_GAME;
|
||||
|
||||
ret->onInit = &game_onInit;
|
||||
ret->onDraw = &game_onDraw;
|
||||
ret->onKey = &game_onKey;
|
||||
|
||||
ret->game = calloc(1, sizeof(GameData));
|
||||
|
||||
if (ret->game == NULL) {
|
||||
free(ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
snprintf(ret->name, sizeof(ret->name), "%s", window_name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool game_coordinates_in_bounds(const GameData *game, int x, int y)
|
||||
{
|
||||
const int game_max_x = game->game_max_x;
|
||||
const int game_max_y = game->game_max_y;
|
||||
|
||||
int max_x;
|
||||
int max_y;
|
||||
getmaxyx(game->window, max_y, max_x);
|
||||
|
||||
const int x_left_bound = (max_x - game_max_x) / 2;
|
||||
const int x_right_bound = (max_x + game_max_x) / 2;
|
||||
const int y_top_bound = (max_y - game_max_y) / 2;
|
||||
const int y_bottom_bound = (max_y + game_max_y) / 2;
|
||||
|
||||
return x > x_left_bound && x < x_right_bound && y > y_top_bound && y < y_bottom_bound;
|
||||
}
|
||||
|
||||
void game_random_coords(const GameData *game, Coords *coords)
|
||||
{
|
||||
const int game_max_x = game->game_max_x;
|
||||
const int game_max_y = game->game_max_y;
|
||||
|
||||
int max_x;
|
||||
int max_y;
|
||||
getmaxyx(game->window, max_y, max_x);
|
||||
|
||||
const int x_left_bound = ((max_x - game_max_x) / 2) + 1;
|
||||
const int x_right_bound = ((max_x + game_max_x) / 2) - 1;
|
||||
const int y_top_bound = ((max_y - game_max_y) / 2) + 1;
|
||||
const int y_bottom_bound = ((max_y + game_max_y) / 2) - 1;
|
||||
|
||||
coords->x = (rand() % (x_right_bound - x_left_bound + 1)) + x_left_bound;
|
||||
coords->y = (rand() % (y_bottom_bound - y_top_bound + 1)) + y_top_bound;
|
||||
}
|
||||
|
||||
void game_max_x_y(const GameData *game, int *x, int *y)
|
||||
{
|
||||
getmaxyx(game->window, *y, *x);
|
||||
}
|
||||
|
||||
int game_y_bottom_bound(const GameData *game)
|
||||
{
|
||||
int max_x;
|
||||
int max_y;
|
||||
UNUSED_VAR(max_x);
|
||||
|
||||
getmaxyx(game->window, max_y, max_x);
|
||||
|
||||
return ((max_y + game->game_max_y) / 2) - 1;
|
||||
}
|
||||
|
||||
int game_y_top_bound(const GameData *game)
|
||||
{
|
||||
int max_x;
|
||||
int max_y;
|
||||
UNUSED_VAR(max_x);
|
||||
|
||||
getmaxyx(game->window, max_y, max_x);
|
||||
|
||||
return ((max_y - game->game_max_y) / 2) + 1;
|
||||
}
|
||||
|
||||
int game_x_right_bound(const GameData *game)
|
||||
{
|
||||
int max_x;
|
||||
int max_y;
|
||||
UNUSED_VAR(max_y);
|
||||
|
||||
getmaxyx(game->window, max_y, max_x);
|
||||
|
||||
return ((max_x + game->game_max_x) / 2) - 1;
|
||||
}
|
||||
|
||||
int game_x_left_bound(const GameData *game)
|
||||
{
|
||||
int max_x;
|
||||
int max_y;
|
||||
UNUSED_VAR(max_y);
|
||||
|
||||
getmaxyx(game->window, max_y, max_x);
|
||||
|
||||
return ((max_x - game->game_max_x) / 2) + 1;
|
||||
}
|
||||
|
||||
void game_update_score(GameData *game, long int points)
|
||||
{
|
||||
game->score += points;
|
||||
|
||||
if (game->score > game->high_score) {
|
||||
game->high_score = game->score;
|
||||
}
|
||||
}
|
||||
|
||||
long int game_get_score(const GameData *game)
|
||||
{
|
||||
return game->score;
|
||||
}
|
||||
|
||||
void game_increment_level(GameData *game)
|
||||
{
|
||||
++game->level;
|
||||
}
|
||||
|
||||
void game_update_lives(GameData *game, int lives)
|
||||
{
|
||||
fprintf(stderr, "%d\n", lives);
|
||||
game->lives += lives;
|
||||
}
|
||||
|
||||
int game_get_lives(const GameData *game)
|
||||
{
|
||||
return game->lives;
|
||||
}
|
||||
|
||||
size_t game_get_current_level(const GameData *game)
|
||||
{
|
||||
return game->level;
|
||||
}
|
||||
|
||||
void game_set_status(GameData *game, GameStatus status)
|
||||
{
|
||||
if (status < GS_Invalid) {
|
||||
game->status = status;
|
||||
}
|
||||
}
|
||||
|
||||
void game_set_update_interval(GameData *game, TIME_MS update_interval)
|
||||
{
|
||||
game->update_interval = MIN(update_interval, GAME_MAX_UPDATE_INTERVAL);
|
||||
}
|
||||
|
||||
bool game_do_object_state_update(const GameData *game, TIME_MS current_time, TIME_MS last_moved_time, TIME_MS speed)
|
||||
{
|
||||
TIME_MS delta = (current_time - last_moved_time) * speed;
|
||||
return delta > game->update_interval * GAME_OBJECT_UPDATE_INTERVAL_MULTIPLIER;
|
||||
}
|
||||
|
||||
void game_set_cb_update_state(GameData *game, cb_game_update_state *func, void *cb_data)
|
||||
{
|
||||
game->cb_game_update_state = func;
|
||||
game->cb_game_update_state_data = cb_data;
|
||||
}
|
||||
|
||||
void game_set_cb_on_keypress(GameData *game, cb_game_key_press *func, void *cb_data)
|
||||
{
|
||||
game->cb_game_key_press = func;
|
||||
game->cb_game_key_press_data = cb_data;
|
||||
}
|
||||
|
||||
void game_set_cb_render_window(GameData *game, cb_game_render_window *func, void *cb_data)
|
||||
{
|
||||
game->cb_game_render_window = func;
|
||||
game->cb_game_render_window_data = cb_data;
|
||||
}
|
||||
|
||||
void game_set_cb_kill(GameData *game, cb_game_kill *func, void *cb_data)
|
||||
{
|
||||
game->cb_game_kill = func;
|
||||
game->cb_game_kill_data = cb_data;
|
||||
}
|
||||
|
||||
void game_set_cb_on_pause(GameData *game, cb_game_pause *func, void *cb_data)
|
||||
{
|
||||
game->cb_game_pause = func;
|
||||
game->cb_game_pause_data = cb_data;
|
||||
}
|
295
src/game_base.h
Normal file
295
src/game_base.h
Normal file
@ -0,0 +1,295 @@
|
||||
/* game_base.h
|
||||
*
|
||||
*
|
||||
* Copyright (C) 2020 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_BASE
|
||||
#define GAME_BASE
|
||||
|
||||
#include <ncurses.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <tox/tox.h>
|
||||
|
||||
#include "game_util.h"
|
||||
#include "windows.h"
|
||||
|
||||
/* Max size of a default size square game window */
|
||||
#define GAME_MAX_SQUARE_Y 26
|
||||
#define GAME_MAX_SQUARE_X (GAME_MAX_SQUARE_Y * 2)
|
||||
|
||||
/* Max size of a small square game window */
|
||||
#define GAME_MAX_SQUARE_Y_SMALL 18
|
||||
#define GAME_MAX_SQUARE_X_SMALL (GAME_MAX_SQUARE_Y_SMALL * 2)
|
||||
|
||||
/* Max size of a default size rectangle game window */
|
||||
#define GAME_MAX_RECT_Y 24
|
||||
#define GAME_MAX_RECT_X (GAME_MAX_RECT_Y * 4)
|
||||
|
||||
/* Max size of a small rectangle game window */
|
||||
#define GAME_MAX_RECT_Y_SMALL 12
|
||||
#define GAME_MAX_RECT_X_SMALL (GAME_MAX_RECT_Y_SMALL * 4)
|
||||
|
||||
/* Maximum length of a game message set with game_set_message() */
|
||||
#define GAME_MAX_MESSAGE_SIZE 64
|
||||
|
||||
#define GAME_MESSAGE_DEFAULT_TIMEOUT 3
|
||||
|
||||
typedef void cb_game_update_state(GameData *game, void *cb_data);
|
||||
typedef void cb_game_render_window(GameData *game, WINDOW *window, void *cb_data);
|
||||
typedef void cb_game_kill(GameData *game, void *cb_data);
|
||||
typedef void cb_game_pause(GameData *game, bool is_paused, void *cb_data);
|
||||
typedef void cb_game_key_press(GameData *game, int key, void *cb_data);
|
||||
|
||||
|
||||
typedef enum GameWindowShape {
|
||||
GW_ShapeSquare = 0,
|
||||
GW_ShapeRectangle,
|
||||
GW_ShapeInvalid,
|
||||
} GameWindowShape;
|
||||
|
||||
typedef enum GameStatus {
|
||||
GS_None = 0,
|
||||
GS_Paused,
|
||||
GS_Running,
|
||||
GS_Finished,
|
||||
GS_Invalid,
|
||||
} GameStatus;
|
||||
|
||||
typedef enum GameType {
|
||||
GT_Snake = 0,
|
||||
GT_Centipede,
|
||||
GT_Invalid,
|
||||
} GameType;
|
||||
|
||||
typedef struct GameMessage {
|
||||
char message[GAME_MAX_MESSAGE_SIZE + 1];
|
||||
size_t length;
|
||||
const Coords *coords; // pointer to coords so we can track movement
|
||||
Coords original_coords; // static coords at time of being set
|
||||
time_t timeout;
|
||||
time_t set_time;
|
||||
int attributes;
|
||||
int colour;
|
||||
Direction direction;
|
||||
bool sticky;
|
||||
bool priority;
|
||||
} GameMessage;
|
||||
|
||||
struct GameData {
|
||||
TIME_MS last_frame_time;
|
||||
TIME_MS update_interval; // determines the refresh rate (lower means faster)
|
||||
long int score;
|
||||
size_t high_score;
|
||||
int lives;
|
||||
size_t level;
|
||||
GameStatus status;
|
||||
GameType type;
|
||||
|
||||
GameMessage *messages;
|
||||
size_t messages_size;
|
||||
|
||||
int game_max_x; // max dimensions of game window
|
||||
int game_max_y;
|
||||
int window_id;
|
||||
WINDOW *window;
|
||||
const ToxWindow *parent;
|
||||
GameWindowShape window_shape;
|
||||
|
||||
cb_game_update_state *cb_game_update_state;
|
||||
void *cb_game_update_state_data;
|
||||
|
||||
cb_game_render_window *cb_game_render_window;
|
||||
void *cb_game_render_window_data;
|
||||
|
||||
cb_game_kill *cb_game_kill;
|
||||
void *cb_game_kill_data;
|
||||
|
||||
cb_game_pause *cb_game_pause;
|
||||
void *cb_game_pause_data;
|
||||
|
||||
cb_game_key_press *cb_game_key_press;
|
||||
void *cb_game_key_press_data;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Sets the callback for game state updates.
|
||||
*/
|
||||
void game_set_cb_update_state(GameData *game, cb_game_update_state *func, void *cb_data);
|
||||
|
||||
/*
|
||||
* Sets the callback for frame rendering.
|
||||
*/
|
||||
void game_set_cb_render_window(GameData *game, cb_game_render_window *func, void *cb_data);
|
||||
|
||||
/*
|
||||
* Sets the callback for game termination.
|
||||
*/
|
||||
void game_set_cb_kill(GameData *game, cb_game_kill *func, void *cb_data);
|
||||
|
||||
/*
|
||||
* Sets the callback for the game pause event.
|
||||
*/
|
||||
void game_set_cb_on_pause(GameData *game, cb_game_pause *func, void *cb_data);
|
||||
|
||||
/*
|
||||
* Sets the callback for the key press event.
|
||||
*/
|
||||
void game_set_cb_on_keypress(GameData *game, cb_game_key_press *func, void *cb_data);
|
||||
|
||||
/*
|
||||
* Initializes game instance.
|
||||
*
|
||||
* Return 0 on success.
|
||||
* Return -1 if screen is too small.
|
||||
* Return -2 on other failure.
|
||||
*/
|
||||
int game_initialize(const ToxWindow *self, Tox *m, GameType type, bool force_small_window);
|
||||
|
||||
/*
|
||||
* Sets game window to `shape` and attempts to adjust size for best fit.
|
||||
*
|
||||
* This should be called in the game's initialize function.
|
||||
*
|
||||
* Return 0 on success.
|
||||
* Return -1 if window is too small or shape is invalid.
|
||||
* Return -2 if function is called while the game state is valid.
|
||||
*/
|
||||
int game_set_window_shape(GameData *game, GameWindowShape shape);
|
||||
|
||||
/*
|
||||
* Returns the GameType associated with `game_string`.
|
||||
*/
|
||||
GameType game_get_type(const char *game_string);
|
||||
|
||||
/*
|
||||
* Prints all available games to window associated with `self`.
|
||||
*/
|
||||
void game_list_print(ToxWindow *self);
|
||||
|
||||
/*
|
||||
* Returns true if coordinates designated by `x` and `y` are within the game window boundaries.
|
||||
*/
|
||||
bool game_coordinates_in_bounds(const GameData *game, int x, int y);
|
||||
|
||||
/*
|
||||
* Put random coordinates that fit within the game window in `coords`.
|
||||
*/
|
||||
void game_random_coords(const GameData *game, Coords *coords);
|
||||
|
||||
/*
|
||||
*Gets the current max dimensions of the game window.
|
||||
*/
|
||||
void game_max_x_y(const GameData *game, int *x, int *y);
|
||||
|
||||
/*
|
||||
* Returns the respective coordinate boundary of the game window.
|
||||
*/
|
||||
int game_y_bottom_bound(const GameData *game);
|
||||
int game_y_top_bound(const GameData *game);
|
||||
int game_x_right_bound(const GameData *game);
|
||||
int game_x_left_bound(const GameData *game);
|
||||
|
||||
/*
|
||||
* Updates game score.
|
||||
*/
|
||||
void game_update_score(GameData *game, long int points);
|
||||
|
||||
/*
|
||||
* Returns the game's current score.
|
||||
*/
|
||||
long int game_get_score(const GameData *game);
|
||||
|
||||
/*
|
||||
* Increments level.
|
||||
*
|
||||
* This function should be called on initialization if game wishes to display level.
|
||||
*/
|
||||
void game_increment_level(GameData *game);
|
||||
|
||||
/*
|
||||
* Updates lives with `amount`.
|
||||
*
|
||||
* If lives becomes negative the lives counter will not be drawn.
|
||||
*/
|
||||
void game_update_lives(GameData *game, int amount);
|
||||
|
||||
/*
|
||||
* Returns the remaining number of lives for the game.
|
||||
*/
|
||||
int game_get_lives(const GameData *game);
|
||||
|
||||
/*
|
||||
* Returns the current level.
|
||||
*/
|
||||
size_t game_get_current_level(const GameData *game);
|
||||
|
||||
/*
|
||||
* Sets the game status to `status`.
|
||||
*/
|
||||
void game_set_status(GameData *game, GameStatus status);
|
||||
|
||||
/*
|
||||
* Sets the game base update interval.
|
||||
*
|
||||
* Lower values of `update_interval` make the game faster, where 1 is the fastest and 50 is slowest.
|
||||
* If this function is never called the game chooses a reasonable default.
|
||||
*/
|
||||
void game_set_update_interval(GameData *game, TIME_MS update_interval);
|
||||
|
||||
/*
|
||||
* Creates a message `message` of size `length` to be displayed at `coords` for `timeout` seconds.
|
||||
*
|
||||
* Message must be no greater than GAME_MAX_MESSAGE_SIZE bytes in length.
|
||||
*
|
||||
* If `sticky` is true the message will follow coords if they move.
|
||||
*
|
||||
* If `dir` is a valid direction, the message will be positioned a few squares away from `coords`
|
||||
* so as to not overlap with its associated object.
|
||||
*
|
||||
* If `timeout` is zero, the default timeout value will be used.
|
||||
*
|
||||
* If `priority` true, messages will be printed on top of game objects.
|
||||
*
|
||||
* Return 0 on success.
|
||||
* Return -1 on failure.
|
||||
*/
|
||||
int game_set_message(GameData *game, const char *message, size_t length, Direction dir, int attributes, int colour,
|
||||
time_t timeout, const Coords *coords, bool sticky, bool priority);
|
||||
|
||||
/*
|
||||
* Returns true if game should update an object's state according to its last moved time and current speed.
|
||||
*
|
||||
* This is used to independently control the speed of various game objects.
|
||||
*/
|
||||
bool game_do_object_state_update(const GameData *game, TIME_MS current_time, TIME_MS last_moved_time, TIME_MS speed);
|
||||
|
||||
/*
|
||||
* Returns the current wall time in milliseconds.
|
||||
*/
|
||||
TIME_MS get_time_millis(void);
|
||||
|
||||
/*
|
||||
* Ends game associated with `self` and cleans up.
|
||||
*/
|
||||
void game_kill(ToxWindow *self);
|
||||
|
||||
#endif // GAME_BASE
|
1738
src/game_centipede.c
Normal file
1738
src/game_centipede.c
Normal file
File diff suppressed because it is too large
Load Diff
30
src/game_centipede.h
Normal file
30
src/game_centipede.h
Normal file
@ -0,0 +1,30 @@
|
||||
/* game_centipede.h
|
||||
*
|
||||
*
|
||||
* Copyright (C) 2020 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_CENTIPEDE
|
||||
#define GAME_CENTIPEDE
|
||||
|
||||
#include "game_base.h"
|
||||
|
||||
int centipede_initialize(GameData *game);
|
||||
|
||||
#endif // GAME_CENTIPEDE
|
901
src/game_snake.c
Normal file
901
src/game_snake.c
Normal file
@ -0,0 +1,901 @@
|
||||
/* game_snake.c
|
||||
*
|
||||
*
|
||||
* Copyright (C) 2020 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_base.h"
|
||||
#include "game_util.h"
|
||||
#include "game_snake.h"
|
||||
#include "misc_tools.h"
|
||||
|
||||
#define SNAKE_MAX_SNAKE_LENGTH (GAME_MAX_SQUARE_X * GAME_MAX_SQUARE_Y)
|
||||
#define SNAKE_AGENT_MAX_LIST_SIZE (GAME_MAX_SQUARE_X * GAME_MAX_SQUARE_Y)
|
||||
|
||||
#define SNAKE_DEFAULT_SNAKE_SPEED 6
|
||||
#define SNAKE_DEFAULT_AGENT_SPEED 1
|
||||
#define SNAKE_MAX_SNAKE_SPEED 12
|
||||
#define SNAKE_MAX_AGENT_SPEED (SNAKE_MAX_SNAKE_SPEED / 2)
|
||||
|
||||
#define SNAKE_DEFAULT_UPDATE_INTERVAL 20
|
||||
|
||||
/* How often we update frames independent of the speed of the game or objects */
|
||||
#define SNAKE_FRAME_DRAW_SPEED 5
|
||||
|
||||
/* How long a regular message stays on the screen */
|
||||
#define SNAKE_DEFAULT_MESSAGE_TIMER 5
|
||||
|
||||
/* Increment snake speed by 1 every time level increases by this amount */
|
||||
#define SNAKE_LEVEL_SPEED_INTERVAL 5
|
||||
|
||||
/* Increment level by 1 every time snake eats this many foods */
|
||||
#define SNAKE_LEVEL_UP_FOOD_LIMIT 4
|
||||
|
||||
/* Increment agent speed by 1 every time level increases by this amount */
|
||||
#define SNAKE_AGENT_LEVEL_SPEED_INTERVAL 2
|
||||
|
||||
/* Points multiplier for getting a powerup */
|
||||
#define SNAKE_POWERUP_BONUS_MULTIPLIER 5
|
||||
|
||||
/* Extra bonus for running over a glowing agent */
|
||||
#define SNAKE_AGENT_GLOWING_MULTIPLIER 2
|
||||
|
||||
/* Agents begin glowing if their speed is greater than this */
|
||||
#define SNAKE_AGENT_GLOWING_SPEED (SNAKE_DEFAULT_AGENT_SPEED + 2)
|
||||
|
||||
/* A new powerup is placed on the board after this many seconds since last one wore off */
|
||||
#define SNAKE_POWERUP_INTERVAL 45
|
||||
|
||||
/* How long a powerup lasts */
|
||||
#define SNAKE_POWERUP_TIMER 12
|
||||
|
||||
/* Number of key presses to queue; one key press is retrieved per state update */
|
||||
#define SNAKE_KEY_PRESS_QUEUE_SIZE 3
|
||||
|
||||
/* These decide how many points to decay and how often to penalize camping for powerups */
|
||||
#define SNAKE_DECAY_POINTS_INTERVAL 1
|
||||
#define SNAKE_DECAY_POINTS_FRACTION 10
|
||||
|
||||
#define SNAKE_HEAD_COLOUR GREEN
|
||||
|
||||
#define SNAKE_DEAD_BODY_CHAR 'o'
|
||||
#define SNAKE_DEAD_BODY_COLOUR RED
|
||||
|
||||
#define SNAKE_BODY_COLOUR CYAN
|
||||
#define SNAKE_BODY_CHAR 'o'
|
||||
|
||||
#define SNAKE_FOOD_COLOUR YELLOW
|
||||
#define SNAKE_FOOD_CHAR '*'
|
||||
|
||||
#define SNAKE_AGENT_NORMAL_COLOUR RED
|
||||
#define SNAKE_AGENT_GLOWING_COLOUR GREEN
|
||||
#define SNAKE_AGENT_NORMAL_CHAR 'x'
|
||||
#define SNAKE_AGENT_GLOWING_CHAR 'X'
|
||||
|
||||
#define SNAKE_POWERUP_CHAR 'P'
|
||||
|
||||
typedef struct NasaAgent {
|
||||
Coords coords;
|
||||
bool is_alive;
|
||||
bool is_glowing;
|
||||
TIME_MS last_time_moved;
|
||||
size_t speed;
|
||||
char display_char;
|
||||
int colour;
|
||||
int attributes;
|
||||
} NasaAgent;
|
||||
|
||||
typedef struct Snake {
|
||||
Coords coords;
|
||||
char display_char;
|
||||
int colour;
|
||||
int attributes;
|
||||
} Snake;
|
||||
|
||||
typedef struct SnakeState {
|
||||
Snake *snake;
|
||||
size_t snake_length;
|
||||
size_t snake_speed;
|
||||
TIME_MS snake_time_last_moved;
|
||||
bool has_powerup;
|
||||
Direction direction;
|
||||
|
||||
Coords powerup;
|
||||
TIME_S powerup_timer;
|
||||
TIME_S last_powerup_time;
|
||||
|
||||
Coords food;
|
||||
|
||||
NasaAgent *agents;
|
||||
size_t agent_list_size;
|
||||
|
||||
TIME_S last_time_points_decayed;
|
||||
TIME_S pause_time;
|
||||
|
||||
int key_press_queue[SNAKE_KEY_PRESS_QUEUE_SIZE];
|
||||
size_t keys_skip_counter;
|
||||
|
||||
TIME_MS last_draw_update;
|
||||
bool game_over;
|
||||
} SnakeState;
|
||||
|
||||
|
||||
static void snake_create_points_message(GameData *game, Direction dir, long int points, const Coords *coords)
|
||||
{
|
||||
char buf[GAME_MAX_MESSAGE_SIZE + 1];
|
||||
snprintf(buf, sizeof(buf), "%ld", points);
|
||||
|
||||
if (game_set_message(game, buf, strlen(buf), dir, A_BOLD, WHITE, 0, coords, false, false) == -1) {
|
||||
fprintf(stderr, "failed to set points message\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void snake_create_message(GameData *game, Direction dir, const char *message, int attributes,
|
||||
int colour, TIME_S timeout, const Coords *coords, bool priority)
|
||||
{
|
||||
if (game_set_message(game, message, strlen(message), dir, attributes, colour, timeout, coords, false, priority) == -1) {
|
||||
fprintf(stderr, "failed to set message\n");
|
||||
}
|
||||
}
|
||||
|
||||
static Coords *snake_get_head_coords(const SnakeState *state)
|
||||
{
|
||||
return &state->snake[0].coords;
|
||||
}
|
||||
|
||||
static void snake_set_head_char(SnakeState *state)
|
||||
{
|
||||
Snake *snake_head = &state->snake[0];
|
||||
|
||||
switch (state->direction) {
|
||||
case NORTH:
|
||||
snake_head->display_char = '^';
|
||||
break;
|
||||
|
||||
case SOUTH:
|
||||
snake_head->display_char = 'v';
|
||||
break;
|
||||
|
||||
case EAST:
|
||||
snake_head->display_char = '>';
|
||||
return;
|
||||
|
||||
case WEST:
|
||||
snake_head->display_char = '<';
|
||||
break;
|
||||
|
||||
default:
|
||||
snake_head->display_char = '?';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool snake_validate_direction(const SnakeState *state, Direction dir)
|
||||
{
|
||||
if (!GAME_UTIL_DIRECTION_VALID(dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int diff = abs((int)state->direction - (int)dir);
|
||||
|
||||
return diff != 1;
|
||||
}
|
||||
|
||||
static void snake_update_direction(SnakeState *state)
|
||||
{
|
||||
for (size_t i = 0; i < SNAKE_KEY_PRESS_QUEUE_SIZE; ++i) {
|
||||
int key = state->key_press_queue[i];
|
||||
|
||||
if (key == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Direction dir = game_util_get_direction(key);
|
||||
|
||||
if (!GAME_UTIL_DIRECTION_VALID(dir)) {
|
||||
state->key_press_queue[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (snake_validate_direction(state, dir)) {
|
||||
state->direction = dir;
|
||||
snake_set_head_char(state);
|
||||
state->key_press_queue[i] = 0;
|
||||
state->keys_skip_counter = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (state->keys_skip_counter++ >= SNAKE_KEY_PRESS_QUEUE_SIZE) {
|
||||
state->keys_skip_counter = 0;
|
||||
memset(state->key_press_queue, 0, sizeof(state->key_press_queue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void snake_set_key_press(SnakeState *state, int key)
|
||||
{
|
||||
for (size_t i = 0; i < SNAKE_KEY_PRESS_QUEUE_SIZE; ++i) {
|
||||
if (state->key_press_queue[i] == 0) {
|
||||
state->key_press_queue[i] = key;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
memset(state->key_press_queue, 0, sizeof(state->key_press_queue));
|
||||
state->key_press_queue[0] = key;
|
||||
}
|
||||
|
||||
static void snake_update_score(GameData *game, const SnakeState *state, long int points)
|
||||
{
|
||||
const Coords *head = snake_get_head_coords(state);
|
||||
|
||||
snake_create_points_message(game, state->direction, points, head);
|
||||
|
||||
game_update_score(game, points);
|
||||
}
|
||||
|
||||
static long int snake_get_move_points(const SnakeState *state)
|
||||
{
|
||||
return state->snake_length + (2 * state->snake_speed);
|
||||
}
|
||||
|
||||
/* Return true if snake body is occupying given coordinates */
|
||||
static bool snake_coords_contain_body(const SnakeState *state, const Coords *coords)
|
||||
{
|
||||
for (size_t i = 1; i < state->snake_length; ++i) {
|
||||
Coords snake_coords = state->snake[i].coords;
|
||||
|
||||
if (COORDINATES_OVERLAP(coords->x, coords->y, snake_coords.x, snake_coords.y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool snake_self_consume(const SnakeState *state)
|
||||
{
|
||||
const Coords *head = snake_get_head_coords(state);
|
||||
return snake_coords_contain_body(state, head);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a pointer to the agent at x and y coordinates.
|
||||
*
|
||||
* Returns NULL if no living agent is at coords.
|
||||
*/
|
||||
static NasaAgent *snake_get_agent_at_coords(const SnakeState *state, const Coords *coords)
|
||||
{
|
||||
for (size_t i = 0; i < state->agent_list_size; ++i) {
|
||||
NasaAgent *agent = &state->agents[i];
|
||||
|
||||
if (!agent->is_alive) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (COORDINATES_OVERLAP(coords->x, coords->y, agent->coords.x, agent->coords.y)) {
|
||||
return agent;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if snake got caught by an agent and doesn't have a powerup.
|
||||
*
|
||||
* If snake runs over agent and does have a powerup the agent is killed and score updated.
|
||||
*/
|
||||
static bool snake_agent_caught(GameData *game, SnakeState *state, const Coords *coords)
|
||||
{
|
||||
NasaAgent *agent = snake_get_agent_at_coords(state, coords);
|
||||
|
||||
if (agent == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!state->has_powerup) {
|
||||
return true;
|
||||
}
|
||||
|
||||
agent->is_alive = false;
|
||||
|
||||
long int points = snake_get_move_points(state) * (agent->speed + 1);
|
||||
|
||||
if (agent->is_glowing) {
|
||||
points *= SNAKE_AGENT_GLOWING_MULTIPLIER;
|
||||
}
|
||||
|
||||
snake_update_score(game, state, points);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool snake_state_valid(GameData *game, SnakeState *state)
|
||||
{
|
||||
const Coords *head = snake_get_head_coords(state);
|
||||
|
||||
if (!game_coordinates_in_bounds(game, head->x, head->y)) {
|
||||
snake_create_message(game, state->direction, "Ouch!", A_BOLD, WHITE, SNAKE_DEFAULT_MESSAGE_TIMER, head, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (snake_self_consume(state)) {
|
||||
snake_create_message(game, state->direction, "Tastes like chicken", A_BOLD, WHITE, SNAKE_DEFAULT_MESSAGE_TIMER, head,
|
||||
true);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (snake_agent_caught(game, state, head)) {
|
||||
snake_create_message(game, state->direction, "ARGH they got me!", A_BOLD, WHITE, SNAKE_DEFAULT_MESSAGE_TIMER, head,
|
||||
true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets colour and attributes for entire snake except head.
|
||||
*
|
||||
* If colour is set to -1 a random colour is chosen for each body part.
|
||||
*/
|
||||
static void snake_set_body_attributes(Snake *snake, size_t length, int colour, int attributes)
|
||||
{
|
||||
for (size_t i = 1; i < length; ++i) {
|
||||
Snake *body = &snake[i];
|
||||
|
||||
if (colour == -1) {
|
||||
body->colour = game_util_random_colour();
|
||||
} else {
|
||||
body->colour = colour;
|
||||
}
|
||||
|
||||
body->attributes = attributes;
|
||||
}
|
||||
}
|
||||
|
||||
static void snake_move_body(SnakeState *state)
|
||||
{
|
||||
for (size_t i = state->snake_length - 1; i > 0; --i) {
|
||||
Coords *curr = &state->snake[i].coords;
|
||||
Coords prev = state->snake[i - 1].coords;
|
||||
curr->x = prev.x;
|
||||
curr->y = prev.y;
|
||||
}
|
||||
}
|
||||
|
||||
static void snake_move_head(SnakeState *state)
|
||||
{
|
||||
Coords *head = snake_get_head_coords(state);
|
||||
game_util_move_coords(state->direction, head);
|
||||
}
|
||||
|
||||
static void snake_grow(SnakeState *state)
|
||||
{
|
||||
size_t index = state->snake_length;
|
||||
|
||||
if (index >= SNAKE_MAX_SNAKE_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
state->snake[index].coords.x = -1;
|
||||
state->snake[index].coords.y = -1;
|
||||
state->snake[index].display_char = SNAKE_BODY_CHAR;
|
||||
state->snake[index].colour = SNAKE_BODY_COLOUR;
|
||||
state->snake[index].attributes = A_BOLD;
|
||||
|
||||
state->snake_length = index + 1;
|
||||
}
|
||||
|
||||
static long int snake_check_food(const GameData *game, SnakeState *state)
|
||||
{
|
||||
Coords *food = &state->food;
|
||||
const Coords *head = snake_get_head_coords(state);
|
||||
|
||||
if (!COORDINATES_OVERLAP(head->x, head->y, food->x, food->y)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
snake_grow(state);
|
||||
|
||||
game_random_coords(game, food);
|
||||
|
||||
return snake_get_move_points(state);
|
||||
}
|
||||
|
||||
static long int snake_check_powerup(GameData *game, SnakeState *state)
|
||||
{
|
||||
Coords *powerup = &state->powerup;
|
||||
const Coords *head = snake_get_head_coords(state);
|
||||
|
||||
if (!COORDINATES_OVERLAP(head->x, head->y, powerup->x, powerup->y)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
snake_create_message(game, state->direction, "AAAAA", A_BOLD, RED, 2, head, false);
|
||||
|
||||
TIME_S t = get_unix_time();
|
||||
|
||||
state->has_powerup = true;
|
||||
state->powerup_timer = t;
|
||||
|
||||
powerup->x = -1;
|
||||
powerup->y = -1;
|
||||
|
||||
return snake_get_move_points(state) * SNAKE_POWERUP_BONUS_MULTIPLIER;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the first unoccupied index in agent array.
|
||||
*/
|
||||
static size_t snake_get_empty_agent_index(const NasaAgent *agents)
|
||||
{
|
||||
for (size_t i = 0; i < SNAKE_AGENT_MAX_LIST_SIZE; ++i) {
|
||||
if (!agents[i].is_alive) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "Warning: Agent array is full. This should be impossible\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snake_initialize_agent(SnakeState *state, const Coords *coords)
|
||||
{
|
||||
size_t idx = snake_get_empty_agent_index(state->agents);
|
||||
|
||||
if ((idx >= state->agent_list_size) && (idx + 1 <= SNAKE_AGENT_MAX_LIST_SIZE)) {
|
||||
state->agent_list_size = idx + 1;
|
||||
}
|
||||
|
||||
NasaAgent *agent = &state->agents[idx];
|
||||
|
||||
agent->coords = (Coords) {
|
||||
coords->x,
|
||||
coords->y
|
||||
};
|
||||
|
||||
agent->is_alive = true;
|
||||
agent->is_glowing = false;
|
||||
agent->display_char = SNAKE_AGENT_NORMAL_CHAR;
|
||||
agent->colour = SNAKE_AGENT_NORMAL_COLOUR;
|
||||
agent->attributes = A_BOLD;
|
||||
agent->last_time_moved = 0;
|
||||
agent->speed = SNAKE_DEFAULT_AGENT_SPEED;
|
||||
}
|
||||
|
||||
static void snake_dispatch_new_agent(const GameData *game, SnakeState *state)
|
||||
{
|
||||
Coords new_coords;
|
||||
const Coords *head = snake_get_head_coords(state);
|
||||
|
||||
size_t tries = 0;
|
||||
|
||||
do {
|
||||
if (tries++ >= 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
game_random_coords(game, &new_coords);
|
||||
} while (COORDINATES_OVERLAP(new_coords.x, new_coords.y, head->x, head->y)
|
||||
|| snake_get_agent_at_coords(state, &new_coords) != NULL);
|
||||
|
||||
snake_initialize_agent(state, &new_coords);
|
||||
}
|
||||
|
||||
static void snake_place_powerup(const GameData *game, SnakeState *state)
|
||||
{
|
||||
Coords *powerup = &state->powerup;
|
||||
|
||||
if (powerup->x != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!timed_out(state->last_powerup_time, SNAKE_POWERUP_INTERVAL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
game_random_coords(game, powerup);
|
||||
}
|
||||
|
||||
static void snake_do_powerup(const GameData *game, SnakeState *state)
|
||||
{
|
||||
if (!state->has_powerup) {
|
||||
snake_place_powerup(game, state);
|
||||
return;
|
||||
}
|
||||
|
||||
if (timed_out(state->powerup_timer, SNAKE_POWERUP_TIMER)) {
|
||||
state->last_powerup_time = get_unix_time();
|
||||
state->has_powerup = false;
|
||||
snake_set_body_attributes(state->snake, state->snake_length, SNAKE_BODY_COLOUR, A_BOLD);
|
||||
}
|
||||
}
|
||||
|
||||
static void snake_decay_points(GameData *game, SnakeState *state)
|
||||
{
|
||||
|
||||
long int score = game_get_score(game);
|
||||
long int decay = snake_get_move_points(state) / SNAKE_DECAY_POINTS_FRACTION;
|
||||
|
||||
if (score > decay && timed_out(state->last_time_points_decayed, SNAKE_DECAY_POINTS_INTERVAL)) {
|
||||
game_update_score(game, -decay);
|
||||
state->last_time_points_decayed = get_unix_time();
|
||||
}
|
||||
}
|
||||
|
||||
static void snake_do_points_update(GameData *game, SnakeState *state, long int points)
|
||||
{
|
||||
snake_update_score(game, state, points);
|
||||
|
||||
if (state->snake_length % SNAKE_LEVEL_UP_FOOD_LIMIT != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
game_increment_level(game);
|
||||
|
||||
size_t level = game_get_current_level(game);
|
||||
|
||||
if (level % SNAKE_LEVEL_SPEED_INTERVAL == 0 && state->snake_speed < SNAKE_MAX_SNAKE_SPEED) {
|
||||
++state->snake_speed;
|
||||
}
|
||||
|
||||
if (level % SNAKE_AGENT_LEVEL_SPEED_INTERVAL == 0) {
|
||||
for (size_t i = 0; i < state->agent_list_size; ++i) {
|
||||
NasaAgent *agent = &state->agents[i];
|
||||
|
||||
if (!agent->is_alive) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (agent->speed < SNAKE_MAX_AGENT_SPEED) {
|
||||
++agent->speed;
|
||||
}
|
||||
|
||||
if (agent->speed > SNAKE_AGENT_GLOWING_SPEED && !agent->is_glowing) {
|
||||
agent->is_glowing = true;
|
||||
agent->display_char = SNAKE_AGENT_GLOWING_CHAR;
|
||||
agent->colour = SNAKE_AGENT_GLOWING_COLOUR;
|
||||
snake_create_message(game, state->direction, "*glows*", A_BOLD, GREEN, 2, &agent->coords, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
snake_dispatch_new_agent(game, state);
|
||||
}
|
||||
|
||||
static void snake_game_over(SnakeState *state)
|
||||
{
|
||||
state->game_over = true;
|
||||
state->has_powerup = false;
|
||||
state->snake[0].colour = SNAKE_DEAD_BODY_COLOUR;
|
||||
state->snake[0].attributes = A_BOLD | A_BLINK;
|
||||
|
||||
snake_set_body_attributes(state->snake, state->snake_length, SNAKE_DEAD_BODY_COLOUR, A_BOLD | A_BLINK);
|
||||
}
|
||||
|
||||
static void snake_move(GameData *game, SnakeState *state, TIME_MS cur_time)
|
||||
{
|
||||
const TIME_MS real_speed = GAME_UTIL_REAL_SPEED(state->direction, state->snake_speed);
|
||||
|
||||
if (!game_do_object_state_update(game, cur_time, state->snake_time_last_moved, real_speed)) {
|
||||
return;
|
||||
}
|
||||
|
||||
state->snake_time_last_moved = cur_time;
|
||||
|
||||
snake_update_direction(state);
|
||||
snake_move_body(state);
|
||||
snake_move_head(state);
|
||||
|
||||
if (!snake_state_valid(game, state)) {
|
||||
snake_game_over(state);
|
||||
game_set_status(game, GS_Finished);
|
||||
return;
|
||||
}
|
||||
|
||||
long int points = snake_check_food(game, state) + snake_check_powerup(game, state);
|
||||
|
||||
if (points > 0) {
|
||||
snake_do_points_update(game, state, points);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempts to move every agent in list.
|
||||
*
|
||||
* If an agent is normal it will move in a random direction. If it's glowing it will
|
||||
* move towards the snake.
|
||||
*/
|
||||
static void snake_agent_move(GameData *game, SnakeState *state, TIME_MS cur_time)
|
||||
{
|
||||
const Coords *head = snake_get_head_coords(state);
|
||||
|
||||
for (size_t i = 0; i < state->agent_list_size; ++i) {
|
||||
NasaAgent *agent = &state->agents[i];
|
||||
|
||||
if (!agent->is_alive) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Coords *coords = &agent->coords;
|
||||
|
||||
Coords new_coords = (Coords) {
|
||||
coords->x,
|
||||
coords->y
|
||||
};
|
||||
|
||||
Direction dir = !agent->is_glowing ? game_util_random_direction()
|
||||
: game_util_move_towards(coords, head, state->has_powerup);
|
||||
|
||||
const TIME_MS real_speed = agent->is_glowing ? GAME_UTIL_REAL_SPEED(dir, agent->speed) : agent->speed;
|
||||
|
||||
if (!game_do_object_state_update(game, cur_time, agent->last_time_moved, real_speed)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
agent->last_time_moved = cur_time;
|
||||
|
||||
game_util_move_coords(dir, &new_coords);
|
||||
|
||||
if (!game_coordinates_in_bounds(game, new_coords.x, new_coords.y)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (snake_coords_contain_body(state, &new_coords)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (snake_get_agent_at_coords(state, &new_coords) != NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
coords->x = new_coords.x;
|
||||
coords->y = new_coords.y;
|
||||
|
||||
if (!state->has_powerup && COORDINATES_OVERLAP(head->x, head->y, new_coords.x, new_coords.y)) {
|
||||
snake_game_over(state);
|
||||
game_set_status(game, GS_Finished);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void snake_update_frames(const GameData *game, SnakeState *state, TIME_MS cur_time)
|
||||
{
|
||||
if (!game_do_object_state_update(game, cur_time, state->last_draw_update, SNAKE_FRAME_DRAW_SPEED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
state->last_draw_update = cur_time;
|
||||
|
||||
if (state->has_powerup) {
|
||||
const int time_left = SNAKE_POWERUP_TIMER - (get_unix_time() - state->powerup_timer);
|
||||
|
||||
if (time_left <= 5 && time_left % 2 == 0) {
|
||||
snake_set_body_attributes(state->snake, state->snake_length, SNAKE_BODY_COLOUR, A_BOLD);
|
||||
} else {
|
||||
snake_set_body_attributes(state->snake, state->snake_length, -1, A_BOLD);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void snake_draw_self(WINDOW *win, const SnakeState *state)
|
||||
{
|
||||
for (size_t i = 0; i < state->snake_length; ++i) {
|
||||
const Snake *body = &state->snake[i];
|
||||
|
||||
if (body->coords.x <= 0 || body->coords.y <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
wattron(win, body->attributes | COLOR_PAIR(body->colour));
|
||||
mvwaddch(win, body->coords.y, body->coords.x, body->display_char);
|
||||
wattroff(win, body->attributes | COLOR_PAIR(body->colour));
|
||||
}
|
||||
}
|
||||
|
||||
static void snake_draw_food(WINDOW *win, const SnakeState *state)
|
||||
{
|
||||
wattron(win, A_BOLD | COLOR_PAIR(SNAKE_FOOD_COLOUR));
|
||||
mvwaddch(win, state->food.y, state->food.x, SNAKE_FOOD_CHAR);
|
||||
wattroff(win, A_BOLD | COLOR_PAIR(SNAKE_FOOD_COLOUR));
|
||||
}
|
||||
|
||||
static void snake_draw_agent(WINDOW *win, const SnakeState *state)
|
||||
{
|
||||
for (size_t i = 0; i < state->agent_list_size; ++i) {
|
||||
NasaAgent agent = state->agents[i];
|
||||
|
||||
if (agent.is_alive) {
|
||||
Coords coords = agent.coords;
|
||||
|
||||
wattron(win, agent.attributes | COLOR_PAIR(agent.colour));
|
||||
mvwaddch(win, coords.y, coords.x, agent.display_char);
|
||||
wattroff(win, agent.attributes | COLOR_PAIR(agent.colour));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void snake_draw_powerup(WINDOW *win, const SnakeState *state)
|
||||
{
|
||||
Coords powerup = state->powerup;
|
||||
|
||||
if (powerup.x != -1) {
|
||||
int colour = game_util_random_colour();
|
||||
|
||||
wattron(win, A_BOLD | COLOR_PAIR(colour));
|
||||
mvwaddch(win, powerup.y, powerup.x, SNAKE_POWERUP_CHAR);
|
||||
wattroff(win, A_BOLD | COLOR_PAIR(colour));
|
||||
}
|
||||
}
|
||||
|
||||
void snake_cb_update_game_state(GameData *game, void *cb_data)
|
||||
{
|
||||
if (!cb_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
SnakeState *state = (SnakeState *)cb_data;
|
||||
|
||||
TIME_MS cur_time = get_time_millis();
|
||||
|
||||
snake_do_powerup(game, state);
|
||||
snake_agent_move(game, state, cur_time);
|
||||
snake_move(game, state, cur_time);
|
||||
snake_decay_points(game, state);
|
||||
|
||||
if (!state->game_over) {
|
||||
snake_update_frames(game, state, cur_time);
|
||||
}
|
||||
}
|
||||
|
||||
void snake_cb_render_window(GameData *game, WINDOW *win, void *cb_data)
|
||||
{
|
||||
if (!cb_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
SnakeState *state = (SnakeState *)cb_data;
|
||||
|
||||
snake_draw_food(win, state);
|
||||
snake_draw_powerup(win, state);
|
||||
snake_draw_agent(win, state);
|
||||
snake_draw_self(win, state);
|
||||
}
|
||||
|
||||
void snake_cb_kill(GameData *game, void *cb_data)
|
||||
{
|
||||
if (!cb_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
SnakeState *state = (SnakeState *)cb_data;
|
||||
|
||||
free(state->snake);
|
||||
free(state->agents);
|
||||
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_pause(game, NULL, NULL);
|
||||
}
|
||||
|
||||
void snake_cb_on_keypress(GameData *game, int key, void *cb_data)
|
||||
{
|
||||
if (!cb_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
SnakeState *state = (SnakeState *)cb_data;
|
||||
|
||||
snake_set_key_press(state, key);
|
||||
}
|
||||
|
||||
void snake_cb_pause(GameData *game, bool is_paused, void *cb_data)
|
||||
{
|
||||
UNUSED_VAR(game);
|
||||
|
||||
if (!cb_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
SnakeState *state = (SnakeState *)cb_data;
|
||||
|
||||
TIME_S t = get_unix_time();
|
||||
|
||||
if (is_paused) {
|
||||
state->pause_time = t;
|
||||
} else {
|
||||
state->powerup_timer += (t - state->pause_time);
|
||||
state->last_powerup_time += (t - state->pause_time);
|
||||
}
|
||||
}
|
||||
|
||||
static void snake_initialize_snake_head(const GameData *game, Snake *snake)
|
||||
{
|
||||
int max_x;
|
||||
int max_y;
|
||||
game_max_x_y(game, &max_x, &max_y);
|
||||
|
||||
snake[0].coords.x = max_x / 2;
|
||||
snake[0].coords.y = max_y / 2;
|
||||
snake[0].colour = SNAKE_HEAD_COLOUR;
|
||||
snake[0].attributes = A_BOLD;
|
||||
}
|
||||
|
||||
int snake_initialize(GameData *game)
|
||||
{
|
||||
if (game_set_window_shape(game, GW_ShapeSquare) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
SnakeState *state = calloc(1, sizeof(SnakeState));
|
||||
|
||||
if (state == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
state->snake = calloc(1, SNAKE_MAX_SNAKE_LENGTH * sizeof(Snake));
|
||||
|
||||
if (state->snake == NULL) {
|
||||
free(state);
|
||||
return -1;
|
||||
}
|
||||
|
||||
state->agents = calloc(1, SNAKE_AGENT_MAX_LIST_SIZE * sizeof(NasaAgent));
|
||||
|
||||
if (state->agents == NULL) {
|
||||
free(state->snake);
|
||||
free(state);
|
||||
return -1;
|
||||
}
|
||||
|
||||
snake_initialize_snake_head(game, state->snake);
|
||||
|
||||
state->snake_speed = SNAKE_DEFAULT_SNAKE_SPEED;
|
||||
state->snake_length = 1;
|
||||
state->direction = NORTH;
|
||||
snake_set_head_char(state);
|
||||
|
||||
state->powerup.x = -1;
|
||||
state->powerup.y = -1;
|
||||
|
||||
state->last_powerup_time = get_unix_time();
|
||||
|
||||
game_update_lives(game, -1);
|
||||
game_increment_level(game);
|
||||
game_set_update_interval(game, SNAKE_DEFAULT_UPDATE_INTERVAL);
|
||||
game_random_coords(game, &state->food);
|
||||
|
||||
game_set_cb_update_state(game, snake_cb_update_game_state, state);
|
||||
game_set_cb_render_window(game, snake_cb_render_window, state);
|
||||
game_set_cb_on_keypress(game, snake_cb_on_keypress, state);
|
||||
game_set_cb_kill(game, snake_cb_kill, state);
|
||||
game_set_cb_on_pause(game, snake_cb_pause, state);
|
||||
|
||||
return 0;
|
||||
}
|
30
src/game_snake.h
Normal file
30
src/game_snake.h
Normal file
@ -0,0 +1,30 @@
|
||||
/* game_snake.h
|
||||
*
|
||||
*
|
||||
* Copyright (C) 2020 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_SNAKE
|
||||
#define GAME_SNAKE
|
||||
|
||||
#include "game_base.h"
|
||||
|
||||
int snake_initialize(GameData *game);
|
||||
|
||||
#endif // GAME_SNAKE
|
158
src/game_util.c
Normal file
158
src/game_util.c
Normal file
@ -0,0 +1,158 @@
|
||||
/* game_util.c
|
||||
*
|
||||
*
|
||||
* Copyright (C) 2020 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 <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "game_util.h"
|
||||
#include "toxic.h"
|
||||
#include "windows.h"
|
||||
|
||||
Direction game_util_get_direction(int key)
|
||||
{
|
||||
switch (key) {
|
||||
case KEY_UP: {
|
||||
return NORTH;
|
||||
}
|
||||
|
||||
case KEY_DOWN: {
|
||||
return SOUTH;
|
||||
}
|
||||
|
||||
case KEY_RIGHT: {
|
||||
return EAST;
|
||||
}
|
||||
|
||||
case KEY_LEFT: {
|
||||
return WEST;
|
||||
}
|
||||
|
||||
default: {
|
||||
return INVALID_DIRECTION;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Direction game_util_move_towards(const Coords *coords_a, const Coords *coords_b, bool inverse)
|
||||
{
|
||||
const int x1 = coords_a->x;
|
||||
const int y1 = coords_a->y;
|
||||
const int x2 = coords_b->x;
|
||||
const int y2 = coords_b->y;
|
||||
|
||||
const int x_diff = abs(x1 - x2);
|
||||
const int y_diff = abs(y1 - y2);
|
||||
|
||||
if (inverse) {
|
||||
if (x_diff > y_diff) {
|
||||
return x2 >= x1 ? WEST : EAST;
|
||||
} else {
|
||||
return y2 >= y1 ? NORTH : SOUTH;
|
||||
}
|
||||
} else {
|
||||
if (x_diff > y_diff) {
|
||||
return x2 < x1 ? WEST : EAST;
|
||||
} else {
|
||||
return y2 < y1 ? NORTH : SOUTH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Direction game_util_random_direction(void)
|
||||
{
|
||||
int r = rand() % 4;
|
||||
|
||||
switch (r) {
|
||||
case 0:
|
||||
return NORTH;
|
||||
|
||||
case 1:
|
||||
return SOUTH;
|
||||
|
||||
case 2:
|
||||
return EAST;
|
||||
|
||||
case 3:
|
||||
return WEST;
|
||||
|
||||
default: // impossible
|
||||
return NORTH;
|
||||
}
|
||||
}
|
||||
|
||||
void game_util_move_coords(Direction direction, Coords *coords)
|
||||
{
|
||||
switch (direction) {
|
||||
case NORTH: {
|
||||
--(coords->y);
|
||||
break;
|
||||
}
|
||||
|
||||
case SOUTH: {
|
||||
++(coords->y);
|
||||
break;
|
||||
}
|
||||
|
||||
case EAST: {
|
||||
++(coords->x);
|
||||
break;
|
||||
}
|
||||
|
||||
case WEST: {
|
||||
--(coords->x);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
fprintf(stderr, "Warning: tried to move in an invalid direction\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int game_util_random_colour(void)
|
||||
{
|
||||
int r = rand() % 6;
|
||||
|
||||
switch (r) {
|
||||
case 0:
|
||||
return GREEN;
|
||||
|
||||
case 1:
|
||||
return CYAN;
|
||||
|
||||
case 2:
|
||||
return RED;
|
||||
|
||||
case 3:
|
||||
return BLUE;
|
||||
|
||||
case 4:
|
||||
return YELLOW;
|
||||
|
||||
case 5:
|
||||
return MAGENTA;
|
||||
|
||||
default: // impossible
|
||||
return RED;
|
||||
}
|
||||
}
|
92
src/game_util.h
Normal file
92
src/game_util.h
Normal file
@ -0,0 +1,92 @@
|
||||
/* game_util.h
|
||||
*
|
||||
*
|
||||
* Copyright (C) 2020 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_UTIL
|
||||
#define GAME_UTIL
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct Coords {
|
||||
int x;
|
||||
int y;
|
||||
} Coords;
|
||||
|
||||
// don't change these
|
||||
typedef enum Direction {
|
||||
NORTH = 0,
|
||||
SOUTH = 1,
|
||||
EAST = 3,
|
||||
WEST = 4,
|
||||
INVALID_DIRECTION = 5
|
||||
} Direction;
|
||||
|
||||
|
||||
/*
|
||||
* Use these for ms and second timestamps respectively so we don't accidentally interchange them
|
||||
*/
|
||||
typedef int64_t TIME_MS;
|
||||
typedef time_t TIME_S;
|
||||
|
||||
|
||||
/*
|
||||
* Return true if coordinates x1, y1 overlap with x2, y2.
|
||||
*/
|
||||
#define COORDINATES_OVERLAP(x1, y1, x2, y2)(((x1) == (x2)) && ((y1) == (y2)))
|
||||
|
||||
/*
|
||||
* Halves speed if moving north or south. This accounts for the fact that Y steps are twice as large as X steps.
|
||||
*/
|
||||
#define GAME_UTIL_REAL_SPEED(dir, speed)((((dir) == (NORTH)) || ((dir) == (SOUTH))) ? (MAX(1, ((speed) / 2))) : (speed))
|
||||
|
||||
/*
|
||||
* Return true if dir is a valid Direction.
|
||||
*/
|
||||
#define GAME_UTIL_DIRECTION_VALID(dir)(((dir) >= 0) && ((dir) < (INVALID_DIRECTION)))
|
||||
|
||||
/*
|
||||
* Returns cardinal direction mapped to `key`.
|
||||
*/
|
||||
Direction game_util_get_direction(int key);
|
||||
|
||||
/*
|
||||
* Returns the direction that will move `coords_a` closest to `coords_b`.
|
||||
*
|
||||
* If `inverse` is true, returns the opposite result.
|
||||
*/
|
||||
Direction game_util_move_towards(const Coords *coords_a, const Coords *coords_b, bool inverse);
|
||||
|
||||
/*
|
||||
* Returns a random direction.
|
||||
*/
|
||||
Direction game_util_random_direction(void);
|
||||
|
||||
/*
|
||||
* Moves `coords` one square towards `direction`.
|
||||
*/
|
||||
void game_util_move_coords(Direction direction, Coords *coords);
|
||||
|
||||
/*
|
||||
* Returns a random colour.
|
||||
*/
|
||||
int game_util_random_colour(void);
|
||||
|
||||
#endif // GAME_UTIL
|
@ -26,6 +26,7 @@
|
||||
#include "avatars.h"
|
||||
#include "conference.h"
|
||||
#include "friendlist.h"
|
||||
#include "game_base.h"
|
||||
#include "help.h"
|
||||
#include "line_info.h"
|
||||
#include "log.h"
|
||||
@ -340,6 +341,58 @@ void cmd_decline(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)
|
||||
--FrndRequests.num_requests;
|
||||
}
|
||||
|
||||
void cmd_game(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
|
||||
{
|
||||
UNUSED_VAR(window);
|
||||
|
||||
if (argc < 1) {
|
||||
game_list_print(self);
|
||||
return;
|
||||
}
|
||||
|
||||
GameType type = game_get_type(argv[1]);
|
||||
|
||||
if (type >= GT_Invalid) {
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Unknown game.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (get_num_active_windows() >= MAX_WINDOWS_NUM) {
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, RED, " * Warning: Too many windows are open.");
|
||||
return;
|
||||
}
|
||||
|
||||
bool force_small = false;
|
||||
|
||||
if (argc >= 2) {
|
||||
force_small = strcasecmp(argv[2], "small") == 0;
|
||||
|
||||
if (!force_small) {
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Unknown argument.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int ret = game_initialize(self, m, type, force_small);
|
||||
|
||||
switch (ret) {
|
||||
case 0: {
|
||||
break;
|
||||
}
|
||||
|
||||
case -1: {
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0,
|
||||
"Window is too small. Try enlarging your window or re-running the command with the 'small' argument.");
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Game failed to initialize (error %d)", ret);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cmd_conference(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
|
||||
{
|
||||
UNUSED_VAR(window);
|
||||
|
@ -33,6 +33,7 @@ void cmd_clear(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE
|
||||
void cmd_connect(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
|
||||
void cmd_decline(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
|
||||
void cmd_conference(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
|
||||
void cmd_game(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
|
||||
void cmd_log(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
|
||||
void cmd_myid(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
|
||||
#ifdef QRCODE
|
||||
|
@ -60,6 +60,7 @@ static const char *glob_cmd_list[] = {
|
||||
"/decline",
|
||||
"/exit",
|
||||
"/conference",
|
||||
"/game",
|
||||
"/help",
|
||||
"/log",
|
||||
"/myid",
|
||||
|
@ -256,7 +256,7 @@ static void init_term(void)
|
||||
keypad(stdscr, 1);
|
||||
noecho();
|
||||
nonl();
|
||||
timeout(50);
|
||||
timeout(30);
|
||||
|
||||
if (has_colors()) {
|
||||
short bg_color = COLOR_BLACK;
|
||||
@ -377,7 +377,9 @@ static void init_term(void)
|
||||
init_pair(MAGENTA, COLOR_MAGENTA, bg_color);
|
||||
init_pair(BLACK, COLOR_BLACK, COLOR_BLACK);
|
||||
init_pair(BLUE_BLACK, COLOR_BLUE, COLOR_BLACK);
|
||||
init_pair(WHITE_BLUE, COLOR_WHITE, COLOR_BLUE);
|
||||
init_pair(BLACK_WHITE, COLOR_BLACK, COLOR_WHITE);
|
||||
init_pair(WHITE_BLACK, COLOR_WHITE, COLOR_BLACK);
|
||||
init_pair(BLACK_BG, COLOR_BLACK, bar_bg_color);
|
||||
init_pair(PURPLE_BG, COLOR_MAGENTA, bar_bg_color);
|
||||
init_pair(BAR_TEXT, bar_fg_color, bar_bg_color);
|
||||
@ -1461,6 +1463,8 @@ int main(int argc, char **argv)
|
||||
/* Make sure all written files are read/writeable only by the current user. */
|
||||
umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
||||
|
||||
srand(time(NULL)); // We use rand() for trivial/non-security related things
|
||||
|
||||
parse_args(argc, argv);
|
||||
|
||||
/* Use the -b flag to enable stderr */
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "conference.h"
|
||||
#include "file_transfers.h"
|
||||
#include "friendlist.h"
|
||||
#include "game_base.h"
|
||||
#include "line_info.h"
|
||||
#include "misc_tools.h"
|
||||
#include "prompt.h"
|
||||
@ -440,6 +441,9 @@ void on_window_resize(void)
|
||||
refresh();
|
||||
clear();
|
||||
|
||||
int x2;
|
||||
int y2;
|
||||
|
||||
for (uint8_t i = 0; i < MAX_WINDOWS_NUM; ++i) {
|
||||
ToxWindow *w = windows[i];
|
||||
|
||||
@ -455,6 +459,19 @@ void on_window_resize(void)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (w->type == WINDOW_TYPE_GAME) {
|
||||
delwin(w->window_bar);
|
||||
delwin(w->window);
|
||||
delwin(w->game->window);
|
||||
w->window = newwin(LINES, COLS, 0, 0);
|
||||
|
||||
getmaxyx(w->window, y2, x2);
|
||||
|
||||
w->window_bar = subwin(w->window, WINDOW_BAR_HEIGHT, COLS, LINES - 2, 0);
|
||||
w->game->window = subwin(w->window, y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT, x2, 0, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (w->help->active) {
|
||||
wclear(w->help->win);
|
||||
}
|
||||
@ -473,8 +490,6 @@ void on_window_resize(void)
|
||||
|
||||
w->window = newwin(LINES, COLS, 0, 0);
|
||||
|
||||
int x2;
|
||||
int y2;
|
||||
getmaxyx(w->window, y2, x2);
|
||||
|
||||
if (y2 <= 0 || x2 <= 0) {
|
||||
@ -710,6 +725,23 @@ void draw_active_window(Tox *m)
|
||||
a->onDraw(a, m);
|
||||
wrefresh(a->window);
|
||||
|
||||
if (a->type == WINDOW_TYPE_GAME) {
|
||||
int ch = getch();
|
||||
|
||||
if (ch == ERR) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ch == user_settings->key_next_tab || ch == user_settings->key_prev_tab) {
|
||||
set_next_window(ch);
|
||||
ch = KEY_F(2);
|
||||
}
|
||||
|
||||
a->onKey(a, m, ch, false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
wint_t ch = 0;
|
||||
int printable = get_current_char(&ch);
|
||||
|
||||
@ -796,14 +828,18 @@ int get_num_active_windows(void)
|
||||
void kill_all_windows(Tox *m)
|
||||
{
|
||||
for (uint8_t i = 2; i < MAX_WINDOWS_NUM; ++i) {
|
||||
if (windows[i] == NULL) {
|
||||
ToxWindow *w = windows[i];
|
||||
|
||||
if (w == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (windows[i]->type == WINDOW_TYPE_CHAT) {
|
||||
kill_chat_window(windows[i], m);
|
||||
} else if (windows[i]->type == WINDOW_TYPE_CONFERENCE) {
|
||||
free_conference(windows[i], windows[i]->num);
|
||||
if (w->type == WINDOW_TYPE_CHAT) {
|
||||
kill_chat_window(w, m);
|
||||
} else if (w->type == WINDOW_TYPE_CONFERENCE) {
|
||||
free_conference(w, w->num);
|
||||
} else if (w->type == WINDOW_TYPE_GAME) {
|
||||
game_kill(w);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,8 @@ typedef enum {
|
||||
BLACK,
|
||||
BLUE_BLACK,
|
||||
BLACK_WHITE,
|
||||
WHITE_BLACK,
|
||||
WHITE_BLUE,
|
||||
BAR_TEXT,
|
||||
STATUS_ONLINE,
|
||||
BAR_ACCENT,
|
||||
@ -78,6 +80,7 @@ typedef enum {
|
||||
WINDOW_TYPE_CHAT,
|
||||
WINDOW_TYPE_CONFERENCE,
|
||||
WINDOW_TYPE_FRIEND_LIST,
|
||||
WINDOW_TYPE_GAME,
|
||||
} WINDOW_TYPE;
|
||||
|
||||
/* Fixes text color problem on some terminals.
|
||||
@ -129,6 +132,7 @@ typedef struct StatusBar StatusBar;
|
||||
typedef struct PromptBuf PromptBuf;
|
||||
typedef struct ChatContext ChatContext;
|
||||
typedef struct Help Help;
|
||||
typedef struct GameData GameData;
|
||||
|
||||
struct ToxWindow {
|
||||
/* ncurses */
|
||||
@ -193,6 +197,8 @@ struct ToxWindow {
|
||||
StatusBar *stb;
|
||||
Help *help;
|
||||
|
||||
GameData *game;
|
||||
|
||||
WINDOW *window;
|
||||
WINDOW *window_bar;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user