mirror of
https://github.com/Tha14/toxic.git
synced 2025-06-26 19:36:45 +02:00
Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
768617a129 | |||
8dfd009e0e | |||
321f694bb8 | |||
4514ecd839 | |||
5e67571908 | |||
c293fbe0c7 | |||
93fb84206d | |||
35aa6922d6 | |||
7abf6388f8 | |||
7aeb1a0aac | |||
60bdcf0ba5 | |||
a623976a0e | |||
556a522637 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -17,3 +17,6 @@ build/toxic
|
||||
build/*.o
|
||||
build/*.d
|
||||
apidoc/python/build
|
||||
*.vim
|
||||
*.tox
|
||||
*.nvim*
|
||||
|
@ -58,6 +58,7 @@ Run `make doc` in the build directory after editing the asciidoc files to regene
|
||||
* `DISABLE_QRCODE` → Disable QR exporting support
|
||||
* `DISABLE_QRPNG` → Disable support for exporting QR as PNG
|
||||
* `DISABLE_DESKTOP_NOTIFY=1` → Disable desktop notifications support
|
||||
* `DISABLE_GAMES=1` → Disable support for games
|
||||
* `ENABLE_PYTHON=1` → Build toxic with Python scripting support
|
||||
* `ENABLE_RELEASE=1` → Build toxic without debug symbols and with full compiler optimizations
|
||||
* `ENABLE_ASAN=1` → Build toxic with LLVM Address Sanitizer enabled
|
||||
|
4
Makefile
4
Makefile
@ -13,8 +13,8 @@ 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 += file_transfers.o friendlist.o global_commands.o conference_commands.o conference.o help.o input.o line_info.o
|
||||
OBJ += 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
|
||||
|
||||
# Check if debug build is enabled
|
||||
|
@ -55,9 +55,9 @@ author = 'Jakob Kreuze'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.10.1'
|
||||
version = '0.11.1'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.10.1'
|
||||
release = '0.11.1'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
@ -151,7 +151,4 @@ texinfo_documents = [
|
||||
(master_doc, 'toxic_api', 'toxic_api Documentation',
|
||||
author, 'toxic_api', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
||||
|
||||
]
|
@ -22,6 +22,12 @@ endif
|
||||
endif
|
||||
endif
|
||||
|
||||
#check if we want to build with game support
|
||||
GAMES := $(shell if [ -z "$(DISABLE_GAMES)" ] || [ "$(DISABLE_GAMES)" = "0" ] ; then echo enabled ; else echo disabled ; fi)
|
||||
ifneq ($(GAMES), disabled)
|
||||
-include $(CHECKS_DIR)/games.mk
|
||||
endif
|
||||
|
||||
# Check if we want build sound notifications support
|
||||
SND_NOTIFY := $(shell if [ -z "$(DISABLE_SOUND_NOTIFY)" ] || [ "$(DISABLE_SOUND_NOTIFY)" = "0" ] ; then echo enabled ; else echo disabled ; fi)
|
||||
ifneq ($(SND_NOTIFY), disabled)
|
||||
|
5
cfg/checks/games.mk
Normal file
5
cfg/checks/games.mk
Normal file
@ -0,0 +1,5 @@
|
||||
# Variables for game support
|
||||
GAMES_CFLAGS = -DGAMES
|
||||
GAMES_OBJ = game_base.o game_centipede.o game_chess.o game_life.o game_util.o game_snake.o
|
||||
CFLAGS += $(GAMES_CFLAGS)
|
||||
OBJ += $(GAMES_OBJ)
|
@ -1,5 +1,5 @@
|
||||
# Version
|
||||
TOXIC_VERSION = 0.10.1
|
||||
TOXIC_VERSION = 0.11.1
|
||||
REV = $(shell git rev-list HEAD --count 2>/dev/null || echo -n "error")
|
||||
ifneq (, $(findstring error, $(REV)))
|
||||
VERSION = $(TOXIC_VERSION)
|
||||
|
@ -16,6 +16,7 @@ help:
|
||||
@echo " DISABLE_DESKTOP_NOTIFY: Set to \"1\" to force building without desktop notifications support"
|
||||
@echo " DISABLE_QRCODE: Set to \"1\" to force building without QR export support"
|
||||
@echo " DISABLE_QRPNG: Set to \"1\" to force building without QR exported as PNG support"
|
||||
@echo " DISABLE_GAMES: Set to \"1\" to force building without game support"
|
||||
@echo " ENABLE_PYTHON: Set to \"1\" to enable building with Python scripting support"
|
||||
@echo " ENABLE_RELEASE: Set to \"1\" to build without debug symbols and with full compiler optimizations"
|
||||
@echo " ENABLE_ASAN: Set to \"1\" to build with LLVM address sanitizer enabled.
|
||||
|
@ -166,4 +166,4 @@ JFreegman <JFreegman@gmail\&.com>
|
||||
.sp
|
||||
Project page: https://github\&.com/JFreegman/toxic
|
||||
.sp
|
||||
IRC channel: chat\&.freenode\&.net#tox
|
||||
IRC channel: irc\&.libera\&.chat#tox
|
||||
|
@ -105,4 +105,4 @@ LINKS
|
||||
-----
|
||||
Project page: <https://github.com/JFreegman/toxic>
|
||||
|
||||
IRC channel: chat.freenode.net#tox
|
||||
IRC channel: irc.libera.chat#tox
|
||||
|
@ -2,12 +2,12 @@
|
||||
.\" Title: toxic.conf
|
||||
.\" Author: [see the "AUTHORS" section]
|
||||
.\" Generator: DocBook XSL Stylesheets v1.79.1 <http://docbook.sf.net/>
|
||||
.\" Date: 2020-11-18
|
||||
.\" Date: 2020-12-05
|
||||
.\" Manual: Toxic Manual
|
||||
.\" Source: toxic __VERSION__
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "TOXIC\&.CONF" "5" "2020\-11\-18" "toxic __VERSION__" "Toxic Manual"
|
||||
.TH "TOXIC\&.CONF" "5" "2020\-12\-05" "toxic __VERSION__" "Toxic Manual"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
@ -414,7 +414,7 @@ Configuration example\&.
|
||||
.sp
|
||||
Project page: https://github\&.com/JFreegman/toxic
|
||||
.sp
|
||||
IRC channel: chat\&.freenode\&.net#tox
|
||||
IRC channel: irc\&.libera\&.chat#tox
|
||||
.SH "AUTHORS"
|
||||
.sp
|
||||
JFreegman <JFreegman@gmail\&.com>
|
||||
|
@ -267,7 +267,7 @@ RESOURCES
|
||||
---------
|
||||
Project page: <https://github.com/JFreegman/toxic>
|
||||
|
||||
IRC channel: chat.freenode.net#tox
|
||||
IRC channel: irc.libera.chat#tox
|
||||
|
||||
|
||||
AUTHORS
|
||||
|
@ -131,11 +131,11 @@ mkdir -p "$BUILD_DIR"
|
||||
# Build Toxcore
|
||||
cd "$BUILD_DIR"
|
||||
|
||||
TOXCORE_VERSION="0.2.12"
|
||||
TOXCORE_HASH="30ae3263c9b68d3bef06f799ba9d7a67e3fad447030625f0ffa4bb22684228b0"
|
||||
TOXCORE_VERSION="25a56c354937e9c8c4c50a64c3b4cfc099c34e29"
|
||||
TOXCORE_HASH="78749dccfd2e6ec95d59d7a07c882aa034754a9ff62113b95281927e71b42574"
|
||||
TOXCORE_FILENAME="c-toxcore-$TOXCORE_VERSION.tar.gz"
|
||||
|
||||
wget --timeout=10 -O "$TOXCORE_FILENAME" "https://github.com/TokTok/c-toxcore/archive/v$TOXCORE_VERSION.tar.gz"
|
||||
wget --timeout=10 -O "$TOXCORE_FILENAME" "https://github.com/TokTok/c-toxcore/archive/master.tar.gz"
|
||||
check_sha256 "$TOXCORE_HASH" "$TOXCORE_FILENAME"
|
||||
tar -o -xf "$TOXCORE_FILENAME"
|
||||
rm "$TOXCORE_FILENAME"
|
||||
@ -214,6 +214,7 @@ CFLAGS="-static" PKG_CONFIG_PATH="$BUILD_DIR/prefix-toxcore/lib64/pkgconfig:$BUI
|
||||
ENABLE_PYTHON=0 \
|
||||
ENABLE_RELEASE=1 \
|
||||
ENABLE_ASAN=0 \
|
||||
DISABLE_GAMES=0 \
|
||||
install
|
||||
|
||||
|
||||
@ -295,3 +296,4 @@ mv "$PREPARE_ARTIFACT_DIR" "$PREPARE_ARTIFACT_DIR/../$ARTIFACT_NAME"
|
||||
tar -cJf "$ARTIFACT_NAME.tar.xz" "$ARTIFACT_NAME"
|
||||
mv "$ARTIFACT_NAME.tar.xz" "$ARTIFACT_DIR"
|
||||
chmod 777 -R "$ARTIFACT_DIR"
|
||||
|
||||
|
84
src/chat.c
84
src/chat.c
@ -47,6 +47,10 @@
|
||||
#include "toxic_strings.h"
|
||||
#include "windows.h"
|
||||
|
||||
#ifdef GAMES
|
||||
#include "game_base.h"
|
||||
#endif
|
||||
|
||||
#ifdef AUDIO
|
||||
#include "audio_call.h"
|
||||
#ifdef VIDEO
|
||||
@ -76,6 +80,10 @@ static const char *chat_cmd_list[] = {
|
||||
"/connect",
|
||||
"/exit",
|
||||
"/conference",
|
||||
#ifdef GAMES
|
||||
"/game",
|
||||
"/play",
|
||||
#endif
|
||||
"/help",
|
||||
"/invite",
|
||||
"/join",
|
||||
@ -756,6 +764,78 @@ static void chat_onConferenceInvite(ToxWindow *self, Tox *m, int32_t friendnumbe
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Type \"/join\" to join the chat.");
|
||||
}
|
||||
|
||||
#ifdef GAMES
|
||||
void chat_onGameInvite(ToxWindow *self, Tox *m, uint32_t friend_number, const uint8_t *data, size_t length)
|
||||
{
|
||||
if (!self || self->num != friend_number) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (length < GAME_PACKET_HEADER_SIZE || length > GAME_MAX_DATA_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t version = data[0];
|
||||
|
||||
if (version != GAME_NETWORKING_VERSION) {
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0,
|
||||
"Game invite failed. Friend has network protocol version %d, you have version %d.", version, GAME_NETWORKING_VERSION);
|
||||
return;
|
||||
}
|
||||
|
||||
GameType type = data[1];
|
||||
|
||||
if (!game_type_is_multiplayer(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t id;
|
||||
game_util_unpack_u32(data + 2, &id);
|
||||
|
||||
const char *game_string = game_get_name_string(type);
|
||||
|
||||
if (game_string == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
Friends.list[friend_number].game_invite.type = type;
|
||||
Friends.list[friend_number].game_invite.id = id;
|
||||
Friends.list[friend_number].game_invite.pending = true;
|
||||
|
||||
uint32_t data_length = length - GAME_PACKET_HEADER_SIZE;
|
||||
Friends.list[friend_number].game_invite.data_length = data_length;
|
||||
|
||||
if (data_length > 0) {
|
||||
free(Friends.list[friend_number].game_invite.data);
|
||||
|
||||
uint8_t *buf = calloc(1, data_length);
|
||||
|
||||
if (buf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(buf, data + GAME_PACKET_HEADER_SIZE, data_length);
|
||||
Friends.list[friend_number].game_invite.data = buf;
|
||||
}
|
||||
|
||||
char name[TOX_MAX_NAME_LENGTH];
|
||||
get_nick_truncate(m, name, friend_number);
|
||||
|
||||
if (self->active_box != -1) {
|
||||
box_notify2(self, generic_message, NT_WNDALERT_2 | user_settings->bell_on_invite, self->active_box,
|
||||
"invites you to play %s", game_string);
|
||||
} else {
|
||||
box_notify(self, generic_message, NT_WNDALERT_2 | user_settings->bell_on_invite, &self->active_box, name,
|
||||
"invites you to play %s", game_string);
|
||||
}
|
||||
|
||||
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s has invited you to a game of %s.", name, game_string);
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Type \"/play\" to join the game.");
|
||||
}
|
||||
|
||||
#endif // GAMES
|
||||
|
||||
/* AV Stuff */
|
||||
#ifdef AUDIO
|
||||
|
||||
@ -1490,6 +1570,10 @@ ToxWindow *new_chat(Tox *m, uint32_t friendnum)
|
||||
ret->ringing_sound = -1;
|
||||
#endif /* AUDIO */
|
||||
|
||||
#ifdef GAMES
|
||||
ret->onGameInvite = &chat_onGameInvite;
|
||||
#endif /* GAMES */
|
||||
|
||||
ret->active_box = -1;
|
||||
|
||||
char nick[TOX_MAX_NAME_LENGTH];
|
||||
|
@ -172,6 +172,57 @@ void cmd_conference_join(WINDOW *window, ToxWindow *self, Tox *m, int argc, char
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef GAMES
|
||||
|
||||
void cmd_game_join(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
|
||||
{
|
||||
UNUSED_VAR(window);
|
||||
UNUSED_VAR(m);
|
||||
|
||||
if (!Friends.list[self->num].game_invite.pending) {
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "No pending game invite.");
|
||||
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;
|
||||
}
|
||||
|
||||
GameType type = Friends.list[self->num].game_invite.type;
|
||||
uint32_t id = Friends.list[self->num].game_invite.id;
|
||||
uint8_t *data = Friends.list[self->num].game_invite.data;
|
||||
size_t length = Friends.list[self->num].game_invite.data_length;
|
||||
|
||||
int ret = game_initialize(self, m, type, id, data, length);
|
||||
|
||||
switch (ret) {
|
||||
case 0: {
|
||||
free(data);
|
||||
Friends.list[self->num].game_invite.data = NULL;
|
||||
Friends.list[self->num].game_invite.pending = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case -1: {
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Window is too small. Try enlarging your window.");
|
||||
return;
|
||||
}
|
||||
|
||||
case -2: {
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Game failed to initialize (network error)");
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Game failed to initialize (error %d)", ret);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // GAMES
|
||||
|
||||
void cmd_savefile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
|
||||
{
|
||||
UNUSED_VAR(window);
|
||||
|
@ -29,6 +29,7 @@
|
||||
void cmd_cancelfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
|
||||
void cmd_conference_invite(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
|
||||
void cmd_conference_join(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
|
||||
void cmd_game_join(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
|
||||
void cmd_savefile(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
|
||||
void cmd_sendfile(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
|
||||
|
||||
|
@ -88,6 +88,9 @@ static const char *conference_cmd_list[] = {
|
||||
"/decline",
|
||||
"/exit",
|
||||
"/conference",
|
||||
#ifdef GAMES
|
||||
"/game",
|
||||
#endif
|
||||
"/help",
|
||||
"/log",
|
||||
#ifdef AUDIO
|
||||
|
@ -49,6 +49,9 @@ static struct cmd_func global_commands[] = {
|
||||
{ "/decline", cmd_decline },
|
||||
{ "/exit", cmd_quit },
|
||||
{ "/conference", cmd_conference },
|
||||
#ifdef GAMES
|
||||
{ "/game", cmd_game },
|
||||
#endif
|
||||
{ "/help", cmd_prompt_help },
|
||||
{ "/log", cmd_log },
|
||||
{ "/myid", cmd_myid },
|
||||
@ -80,6 +83,9 @@ static struct cmd_func chat_commands[] = {
|
||||
{ "/cancel", cmd_cancelfile },
|
||||
{ "/invite", cmd_conference_invite },
|
||||
{ "/join", cmd_conference_join },
|
||||
#ifdef GAMES
|
||||
{ "/play", cmd_game_join },
|
||||
#endif
|
||||
{ "/savefile", cmd_savefile },
|
||||
{ "/sendfile", cmd_sendfile },
|
||||
#ifdef AUDIO
|
||||
|
@ -116,8 +116,11 @@ static void realloc_blocklist(int n)
|
||||
void kill_friendlist(ToxWindow *self)
|
||||
{
|
||||
for (size_t i = 0; i < Friends.max_idx; ++i) {
|
||||
if (Friends.list[i].active && Friends.list[i].conference_invite.key != NULL) {
|
||||
if (Friends.list[i].active) {
|
||||
free(Friends.list[i].conference_invite.key);
|
||||
#ifdef GAMES
|
||||
free(Friends.list[i].game_invite.data);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -582,6 +585,38 @@ static void friendlist_add_blocked(uint32_t fnum, uint32_t bnum)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef GAMES
|
||||
|
||||
static void friendlist_onGameInvite(ToxWindow *self, Tox *m, uint32_t friend_number, const uint8_t *data, size_t length)
|
||||
{
|
||||
UNUSED_VAR(self);
|
||||
UNUSED_VAR(data);
|
||||
UNUSED_VAR(length);
|
||||
|
||||
if (friend_number >= Friends.max_idx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Friends.list[friend_number].chatwin != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (get_num_active_windows() < MAX_WINDOWS_NUM) {
|
||||
Friends.list[friend_number].chatwin = add_window(m, new_chat(m, Friends.list[friend_number].num));
|
||||
return;
|
||||
}
|
||||
|
||||
char nick[TOX_MAX_NAME_LENGTH];
|
||||
get_nick_truncate(m, nick, friend_number);
|
||||
|
||||
line_info_add(prompt, false, NULL, NULL, SYS_MSG, 0, RED,
|
||||
"* Game invite from %s failed: Too many windows are open.", nick);
|
||||
|
||||
sound_notify(prompt, notif_error, NT_WNDALERT_1, NULL);
|
||||
}
|
||||
|
||||
#endif // GAMES
|
||||
|
||||
static void friendlist_onFileRecv(ToxWindow *self, Tox *m, uint32_t num, uint32_t filenum,
|
||||
uint64_t file_size, const char *filename, size_t name_length)
|
||||
{
|
||||
@ -1383,6 +1418,10 @@ ToxWindow *new_friendlist(void)
|
||||
ret->is_call = false;
|
||||
#endif /* AUDIO */
|
||||
|
||||
#ifdef GAMES
|
||||
ret->onGameInvite = &friendlist_onGameInvite;
|
||||
#endif
|
||||
|
||||
ret->num = -1;
|
||||
ret->active_box = -1;
|
||||
|
||||
|
@ -29,6 +29,10 @@
|
||||
#include "toxic.h"
|
||||
#include "windows.h"
|
||||
|
||||
#ifdef GAMES
|
||||
#include "game_base.h"
|
||||
#endif
|
||||
|
||||
struct LastOnline {
|
||||
uint64_t last_on;
|
||||
struct tm tm;
|
||||
@ -42,6 +46,18 @@ struct ConferenceInvite {
|
||||
bool pending;
|
||||
};
|
||||
|
||||
#ifdef GAMES
|
||||
|
||||
struct GameInvite {
|
||||
uint8_t *data;
|
||||
size_t data_length;
|
||||
GameType type;
|
||||
uint32_t id;
|
||||
bool pending;
|
||||
};
|
||||
|
||||
#endif // GAMES
|
||||
|
||||
typedef struct {
|
||||
char name[TOXIC_MAX_NAME_LENGTH + 1];
|
||||
uint16_t namelength;
|
||||
@ -59,6 +75,10 @@ typedef struct {
|
||||
struct LastOnline last_online;
|
||||
struct ConferenceInvite conference_invite;
|
||||
|
||||
#ifdef GAMES
|
||||
struct GameInvite game_invite;
|
||||
#endif
|
||||
|
||||
struct FileTransfer file_receiver[MAX_FILES];
|
||||
struct FileTransfer file_sender[MAX_FILES];
|
||||
} ToxicFriend;
|
||||
|
1134
src/game_base.c
Normal file
1134
src/game_base.c
Normal file
File diff suppressed because it is too large
Load Diff
400
src/game_base.h
Normal file
400
src/game_base.h
Normal file
@ -0,0 +1,400 @@
|
||||
/* 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"
|
||||
|
||||
#define GAME_BORDER_COLOUR BAR_TEXT
|
||||
|
||||
|
||||
/* Max size of a default square game window */
|
||||
#define GAME_MAX_SQUARE_Y_DEFAULT 26
|
||||
#define GAME_MAX_SQUARE_X_DEFAULT (GAME_MAX_SQUARE_Y_DEFAULT * 2)
|
||||
|
||||
/* Max size of a large square game window */
|
||||
#define GAME_MAX_SQUARE_Y_LARGE 52
|
||||
#define GAME_MAX_SQUARE_X_LARGE (GAME_MAX_SQUARE_Y_LARGE * 2)
|
||||
|
||||
/* Max size of a default size rectangle game window */
|
||||
#define GAME_MAX_RECT_Y_DEFAULT 24
|
||||
#define GAME_MAX_RECT_X_DEFAULT (GAME_MAX_RECT_Y_DEFAULT * 4)
|
||||
|
||||
/* Max size of a large rectangle game window */
|
||||
#define GAME_MAX_RECT_Y_LARGE 52
|
||||
#define GAME_MAX_RECT_X_LARGE (GAME_MAX_RECT_Y_LARGE * 4)
|
||||
|
||||
/* Maximum length of a game message set with game_set_message() */
|
||||
#define GAME_MAX_MESSAGE_SIZE 64
|
||||
|
||||
/* Default number of seconds a game message is displayed for */
|
||||
#define GAME_MESSAGE_DEFAULT_TIMEOUT 3
|
||||
|
||||
|
||||
/***** START NETWORKING CONSTANTS *****/
|
||||
|
||||
/* 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))
|
||||
|
||||
/* Max size of a game packet including the header */
|
||||
#define GAME_MAX_PACKET_SIZE 1024
|
||||
|
||||
/* Max size of a game packet payload */
|
||||
#define GAME_MAX_DATA_SIZE (GAME_MAX_PACKET_SIZE - GAME_PACKET_HEADER_SIZE - 1)
|
||||
|
||||
/* Current version of networking protocol */
|
||||
#define GAME_NETWORKING_VERSION 0x01
|
||||
|
||||
/***** END NETWORKING CONSTANTS *****/
|
||||
|
||||
|
||||
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 void cb_game_on_packet(GameData *game, const uint8_t *data, size_t length, void *cb_data);
|
||||
|
||||
typedef enum GamePacketType {
|
||||
GP_Invite = 0u,
|
||||
GP_Data,
|
||||
} GamePacketType;
|
||||
|
||||
typedef enum GameWindowShape {
|
||||
GW_ShapeSquare = 0u,
|
||||
GW_ShapeSquareLarge,
|
||||
GW_ShapeRectangle,
|
||||
GW_ShapeRectangleLarge,
|
||||
GW_ShapeInvalid,
|
||||
} GameWindowShape;
|
||||
|
||||
typedef enum GameStatus {
|
||||
GS_None = 0u,
|
||||
GS_Paused,
|
||||
GS_Running,
|
||||
GS_Finished,
|
||||
GS_Invalid,
|
||||
} GameStatus;
|
||||
|
||||
typedef enum GameType {
|
||||
GT_Centipede = 0u,
|
||||
GT_Chess,
|
||||
GT_Life,
|
||||
GT_Snake,
|
||||
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;
|
||||
bool is_multiplayer;
|
||||
|
||||
bool show_lives;
|
||||
bool show_score;
|
||||
bool show_high_score;
|
||||
bool show_level;
|
||||
|
||||
GameMessage *messages;
|
||||
size_t messages_size;
|
||||
|
||||
int game_max_x; // max dimensions of game window
|
||||
int game_max_y;
|
||||
|
||||
int parent_max_x; // max dimensions of parent window
|
||||
int parent_max_y;
|
||||
|
||||
int window_id;
|
||||
WINDOW *window;
|
||||
|
||||
Tox *tox; // must be locked with Winthread mutex
|
||||
|
||||
GameWindowShape window_shape;
|
||||
|
||||
uint32_t id; // indentifies multiplayer instance
|
||||
uint32_t friend_number; // friendnumber associated with parent window
|
||||
|
||||
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;
|
||||
|
||||
cb_game_on_packet *cb_game_on_packet;
|
||||
void *cb_game_on_packet_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);
|
||||
|
||||
/*
|
||||
* Sets the callback for the game packet event.
|
||||
*/
|
||||
void game_set_cb_on_packet(GameData *game, cb_game_on_packet *func, void *cb_data);
|
||||
|
||||
/*
|
||||
* Initializes game instance.
|
||||
*
|
||||
* `type` must be a valid GameType.
|
||||
*
|
||||
* `id` should be a unique integer to indentify the game instance. If we're being invited to a game
|
||||
* this identifier should be sent via the invite packet.
|
||||
*
|
||||
* if `multiplayer_data` is non-null this indicates that we accepted a game invite from a contact.
|
||||
* The data contains any information we need to initialize the game state.
|
||||
*
|
||||
* Return 0 on success.
|
||||
* Return -1 if screen is too small.
|
||||
* Return -2 on network related error.
|
||||
* Return -3 if multiplayer game is being initialized outside of a contact's window.
|
||||
* Return -4 on other failure.
|
||||
*/
|
||||
int game_initialize(const ToxWindow *self, Tox *m, GameType type, uint32_t id, const uint8_t *multiplayer_data,
|
||||
size_t length);
|
||||
|
||||
/*
|
||||
* Sets game window to `shape`.
|
||||
*
|
||||
* This must be called on game initialization.
|
||||
*
|
||||
* 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);
|
||||
|
||||
/*
|
||||
* Returns the name represented as a string associated with `type`.
|
||||
*/
|
||||
const char *game_get_name_string(GameType type);
|
||||
|
||||
/*
|
||||
* Prints all available games to window associated with `self`.
|
||||
*/
|
||||
void game_list_print(ToxWindow *self);
|
||||
|
||||
/*
|
||||
* Return true if game `type` has a multiplayer mode.
|
||||
*/
|
||||
bool game_type_is_multiplayer(GameType type);
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
/*
|
||||
* Toggle whether the respective game info is shown around the game window.
|
||||
*/
|
||||
void game_show_score(GameData *game, bool show_score);
|
||||
void game_show_high_score(GameData *game, bool show_high_score);
|
||||
void game_show_lives(GameData *game, bool show_lives);
|
||||
void game_show_level(GameData *game, bool show_level);
|
||||
|
||||
/*
|
||||
* Sends a notification to the window associated with `game`.
|
||||
*
|
||||
* `message` - the notification message that will be displayed.
|
||||
*/
|
||||
void game_window_notify(const GameData *game, const char *message);
|
||||
|
||||
/*
|
||||
* Updates game score.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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);
|
||||
|
||||
/*
|
||||
* Sends a packet containing payload `data` of size `length` to the friendnumber associated with the game's
|
||||
* parent window.
|
||||
*
|
||||
* `length` must not exceed GAME_MAX_DATA_SIZE bytes.
|
||||
*
|
||||
* `packet_type` should be GP_Invite for an invite packet or GP_Data for all other game data.
|
||||
*/
|
||||
int game_packet_send(const GameData *game, const uint8_t *data, size_t length, GamePacketType packet_type);
|
||||
|
||||
#endif // GAME_BASE
|
||||
|
1750
src/game_centipede.c
Normal file
1750
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
|
2158
src/game_chess.c
Normal file
2158
src/game_chess.c
Normal file
File diff suppressed because it is too large
Load Diff
44
src/game_chess.h
Normal file
44
src/game_chess.h
Normal file
@ -0,0 +1,44 @@
|
||||
/* game_chess.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_CHESS
|
||||
#define GAME_CHESS
|
||||
|
||||
#include "game_base.h"
|
||||
|
||||
/*
|
||||
* Initializes chess game state.
|
||||
*
|
||||
* If `init_data` is non-null, this indicates that we were invited to the game.
|
||||
*
|
||||
* If we're the inviter, we send an invite packet after initialization. If we're the
|
||||
* invitee, we send a handshake response packet to the inviter.
|
||||
*
|
||||
* Return 0 on success.
|
||||
* Return -1 if window is too small.
|
||||
* Return -2 on network related error.
|
||||
* Return -3 on other error.
|
||||
*/
|
||||
int chess_initialize(GameData *game, const uint8_t *init_data, size_t length);
|
||||
|
||||
#endif // GAME_CHESS
|
||||
|
676
src/game_life.c
Normal file
676
src/game_life.c
Normal file
@ -0,0 +1,676 @@
|
||||
/* 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;
|
||||
bool paused;
|
||||
|
||||
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;
|
||||
int colour;
|
||||
} 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 void life_cycle_colour(LifeState *state)
|
||||
{
|
||||
switch (state->colour) {
|
||||
case RED: {
|
||||
state->colour = YELLOW;
|
||||
break;
|
||||
}
|
||||
|
||||
case YELLOW: {
|
||||
state->colour = GREEN;
|
||||
break;
|
||||
}
|
||||
|
||||
case GREEN: {
|
||||
state->colour = CYAN;
|
||||
break;
|
||||
}
|
||||
|
||||
case CYAN: {
|
||||
state->colour = BLUE;
|
||||
break;
|
||||
}
|
||||
|
||||
case BLUE: {
|
||||
state->colour = MAGENTA;
|
||||
break;
|
||||
}
|
||||
|
||||
case MAGENTA: {
|
||||
state->colour = RED;
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
state->colour = RED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
wattron(win, A_BOLD | COLOR_PAIR(state->colour));
|
||||
|
||||
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;
|
||||
mvwaddch(win, coords.y, coords.x, cell->display_char);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wattroff(win, A_BOLD | COLOR_PAIR(state->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 cell at `i` `j` position.
|
||||
*
|
||||
* Returns NULL if cell is touching a border.
|
||||
*/
|
||||
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);
|
||||
|
||||
if (state->generation == 0 || state->paused) {
|
||||
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);
|
||||
break;
|
||||
}
|
||||
|
||||
case '`': {
|
||||
life_cycle_colour(state);
|
||||
break;
|
||||
}
|
||||
|
||||
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_pause(GameData *game, bool is_paused, void *cb_data)
|
||||
{
|
||||
if (!cb_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
LifeState *state = (LifeState *)cb_data;
|
||||
|
||||
state->paused = is_paused;
|
||||
}
|
||||
|
||||
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;
|
||||
state->colour = LIFE_CELL_DEFAULT_COLOUR;
|
||||
|
||||
life_restart(game, state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int life_initialize(GameData *game)
|
||||
{
|
||||
// Try best fit from largest to smallest before giving up
|
||||
if (game_set_window_shape(game, GW_ShapeRectangleLarge) == -1) {
|
||||
if (game_set_window_shape(game, GW_ShapeSquareLarge) == -1) {
|
||||
if (game_set_window_shape(game, GW_ShapeRectangle) == -1) {
|
||||
if (game_set_window_shape(game, GW_ShapeSquare) == -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_on_pause(game, life_cb_pause, 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
|
||||
|
906
src/game_snake.c
Normal file
906
src/game_snake.c
Normal file
@ -0,0 +1,906 @@
|
||||
/* 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_DEFAULT * GAME_MAX_SQUARE_Y_DEFAULT)
|
||||
#define SNAKE_AGENT_MAX_LIST_SIZE (GAME_MAX_SQUARE_X_DEFAULT * GAME_MAX_SQUARE_Y_DEFAULT)
|
||||
|
||||
#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_keypress(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)
|
||||
{
|
||||
// note: if this changes we must update SNAKE_MAX_SNAKE_LENGTH and SNAKE_AGENT_MAX_LIST_SIZE
|
||||
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_show_level(game, true);
|
||||
game_show_score(game, true);
|
||||
game_show_high_score(game, true);
|
||||
|
||||
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
|
200
src/game_util.c
Normal file
200
src/game_util.c
Normal file
@ -0,0 +1,200 @@
|
||||
/* 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 <stdint.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: {
|
||||
if (coords->y > 0) {
|
||||
--(coords->y);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SOUTH: {
|
||||
++(coords->y); // Will rollover if you do something stupid
|
||||
break;
|
||||
}
|
||||
|
||||
case EAST: {
|
||||
++(coords->x); // Will rollover if you do something stupid
|
||||
break;
|
||||
}
|
||||
|
||||
case WEST: {
|
||||
if (coords->x > 0) {
|
||||
--(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;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t net_pack_u16(uint8_t *bytes, uint16_t v)
|
||||
{
|
||||
bytes[0] = (v >> 8) & 0xff;
|
||||
bytes[1] = v & 0xff;
|
||||
return sizeof(v);
|
||||
}
|
||||
|
||||
static size_t net_unpack_u16(const uint8_t *bytes, uint16_t *v)
|
||||
{
|
||||
uint8_t hi = bytes[0];
|
||||
uint8_t lo = bytes[1];
|
||||
*v = ((uint16_t)hi << 8) | lo;
|
||||
return sizeof(*v);
|
||||
}
|
||||
|
||||
size_t game_util_pack_u32(uint8_t *bytes, uint32_t v)
|
||||
{
|
||||
uint8_t *p = bytes;
|
||||
p += net_pack_u16(p, (v >> 16) & 0xffff);
|
||||
p += net_pack_u16(p, v & 0xffff);
|
||||
return p - bytes;
|
||||
}
|
||||
|
||||
size_t game_util_unpack_u32(const uint8_t *bytes, uint32_t *v)
|
||||
{
|
||||
const uint8_t *p = bytes;
|
||||
uint16_t hi;
|
||||
uint16_t lo;
|
||||
p += net_unpack_u16(p, &hi);
|
||||
p += net_unpack_u16(p, &lo);
|
||||
*v = ((uint32_t)hi << 16) | lo;
|
||||
return p - bytes;
|
||||
}
|
||||
|
103
src/game_util.h
Normal file
103
src/game_util.h
Normal file
@ -0,0 +1,103 @@
|
||||
/* 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>
|
||||
#include <time.h>
|
||||
|
||||
typedef struct Coords {
|
||||
int x;
|
||||
int y;
|
||||
} Coords;
|
||||
|
||||
// don't change these
|
||||
typedef enum Direction {
|
||||
NORTH = 0u,
|
||||
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) < (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);
|
||||
|
||||
/*
|
||||
* Packs an unsigned 32 bit integer `v` into `bytes`.
|
||||
*/
|
||||
size_t game_util_pack_u32(uint8_t *bytes, uint32_t v);
|
||||
|
||||
/*
|
||||
* Unpacks an unsigned 32 bit integer in `bytes` to `v`.
|
||||
*/
|
||||
size_t game_util_unpack_u32(const uint8_t *bytes, uint32_t *v);
|
||||
|
||||
#endif // GAME_UTIL
|
@ -38,6 +38,10 @@
|
||||
#include "toxic_strings.h"
|
||||
#include "windows.h"
|
||||
|
||||
#ifdef GAMES
|
||||
#include "game_base.h"
|
||||
#endif
|
||||
|
||||
extern char *DATA_FILE;
|
||||
extern ToxWindow *prompt;
|
||||
extern FriendsList Friends;
|
||||
@ -340,6 +344,63 @@ void cmd_decline(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)
|
||||
--FrndRequests.num_requests;
|
||||
}
|
||||
|
||||
#ifdef GAMES
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
uint32_t id = rand();
|
||||
int ret = game_initialize(self, m, type, id, NULL, 0);
|
||||
|
||||
switch (ret) {
|
||||
case 0: {
|
||||
break;
|
||||
}
|
||||
|
||||
case -1: {
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Window is too small.");
|
||||
return;
|
||||
}
|
||||
|
||||
case -2: {
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Game failed to initialize: Network error.");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
case -3: {
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0,
|
||||
"Game is multiplayer only. Try the command again in the chat window of the contact you wish to play with.");
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Game failed to initialize (error %d)", ret);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // GAMES
|
||||
|
||||
void cmd_conference(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
|
||||
{
|
||||
UNUSED_VAR(window);
|
||||
|
@ -62,4 +62,8 @@ void cmd_change_video_device(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv
|
||||
void cmd_run(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
|
||||
#endif /* PYTHON */
|
||||
|
||||
#ifdef GAMES
|
||||
void cmd_game(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
|
||||
#endif /* GAMES */
|
||||
|
||||
#endif /* GLOBAL_COMMANDS_H */
|
||||
|
17
src/help.c
17
src/help.c
@ -181,6 +181,10 @@ static void help_draw_global(ToxWindow *self)
|
||||
wprintw(win, " /log <on> or <off> : Enable/disable logging\n");
|
||||
wprintw(win, " /conference <type> : Create a conference where type: text | audio\n");
|
||||
wprintw(win, " /myid : Print your Tox ID\n");
|
||||
#ifdef GAMES
|
||||
wprintw(win, " /game : Play a game\n");
|
||||
#endif /* GAMES */
|
||||
|
||||
#ifdef QRCODE
|
||||
#ifdef QRPNG
|
||||
wprintw(win, " /myqr <txt> or <png> : Print your Tox ID's QR code to a file.\n");
|
||||
@ -264,6 +268,10 @@ static void help_draw_chat(ToxWindow *self)
|
||||
wprintw(win, " /video : Toggle video in call\n");
|
||||
#endif /* VIDEO */
|
||||
|
||||
#ifdef GAMES
|
||||
wprintw(win, " /game : Play a game with contact\n");
|
||||
#endif /* GAMES */
|
||||
|
||||
help_draw_bottom_menu(win);
|
||||
|
||||
box(win, ACS_VLINE, ACS_HLINE);
|
||||
@ -378,11 +386,11 @@ void help_onKey(ToxWindow *self, wint_t key)
|
||||
|
||||
case L'c':
|
||||
#ifdef VIDEO
|
||||
help_init_window(self, 25, 80);
|
||||
help_init_window(self, 26, 80);
|
||||
#elif AUDIO
|
||||
help_init_window(self, 20, 80);
|
||||
help_init_window(self, 21, 80);
|
||||
#else
|
||||
help_init_window(self, 10, 80);
|
||||
help_init_window(self, 11, 80);
|
||||
#endif
|
||||
self->help->type = HELP_CHAT;
|
||||
break;
|
||||
@ -396,6 +404,9 @@ void help_onKey(ToxWindow *self, wint_t key)
|
||||
#endif
|
||||
#ifdef PYTHON
|
||||
height += 2;
|
||||
#endif
|
||||
#ifdef GAMES
|
||||
height += 1;
|
||||
#endif
|
||||
help_init_window(self, height, 80);
|
||||
self->help->type = HELP_GLOBAL;
|
||||
|
@ -60,6 +60,9 @@ static const char *glob_cmd_list[] = {
|
||||
"/decline",
|
||||
"/exit",
|
||||
"/conference",
|
||||
#ifdef GAMES
|
||||
"/game",
|
||||
#endif
|
||||
"/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;
|
||||
@ -376,8 +376,10 @@ static void init_term(void)
|
||||
init_pair(YELLOW, COLOR_YELLOW, bg_color);
|
||||
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(WHITE_GREEN, COLOR_WHITE, COLOR_GREEN);
|
||||
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);
|
||||
@ -804,6 +806,7 @@ static void init_tox_callbacks(Tox *m)
|
||||
tox_callback_file_chunk_request(m, on_file_chunk_request);
|
||||
tox_callback_file_recv_control(m, on_file_recv_control);
|
||||
tox_callback_file_recv_chunk(m, on_file_recv_chunk);
|
||||
tox_callback_friend_lossless_packet(m, on_lossless_custom_packet);
|
||||
}
|
||||
|
||||
static void init_tox_options(struct Tox_Options *tox_opts)
|
||||
@ -1461,6 +1464,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 */
|
||||
|
@ -135,5 +135,7 @@ void on_file_recv(Tox *m, uint32_t friendnumber, uint32_t filenumber, uint32_t k
|
||||
const uint8_t *filename, size_t filename_length, void *userdata);
|
||||
void on_friend_typing(Tox *m, uint32_t friendnumber, bool is_typing, void *userdata);
|
||||
void on_friend_read_receipt(Tox *m, uint32_t friendnumber, uint32_t receipt, void *userdata);
|
||||
void on_lossless_custom_packet(Tox *m, uint32_t friendnumber, const uint8_t *data, size_t length, void *userdata);
|
||||
|
||||
|
||||
#endif /* TOXIC_H */
|
||||
|
137
src/windows.c
137
src/windows.c
@ -37,6 +37,10 @@
|
||||
#include "toxic.h"
|
||||
#include "windows.h"
|
||||
|
||||
#ifdef GAMES
|
||||
#include "game_base.h"
|
||||
#endif
|
||||
|
||||
extern char *DATA_FILE;
|
||||
extern struct Winthread Winthread;
|
||||
|
||||
@ -322,6 +326,53 @@ void on_friend_read_receipt(Tox *m, uint32_t friendnumber, uint32_t receipt, voi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void on_lossless_custom_packet(Tox *m, uint32_t friendnumber, const uint8_t *data, size_t length, void *userdata)
|
||||
{
|
||||
UNUSED_VAR(userdata);
|
||||
|
||||
if (length == 0 || data == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t type = data[0];
|
||||
|
||||
switch (type) {
|
||||
#ifdef GAMES
|
||||
|
||||
case CUSTOM_PACKET_GAME_INVITE: {
|
||||
for (size_t i = 0; i < MAX_WINDOWS_NUM; ++i) {
|
||||
ToxWindow *window = windows[i];
|
||||
|
||||
if (window != NULL && window->onGameInvite != NULL) {
|
||||
window->onGameInvite(window, m, friendnumber, data + 1, length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CUSTOM_PACKET_GAME_DATA: {
|
||||
for (size_t i = 0; i < MAX_WINDOWS_NUM; ++i) {
|
||||
ToxWindow *window = windows[i];
|
||||
|
||||
if (window != NULL && window->onGameData != NULL) {
|
||||
window->onGameData(window, m, friendnumber, data + 1, length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
#endif // GAMES
|
||||
|
||||
default: {
|
||||
fprintf(stderr, "Got unknown custom packet of type: %u\n", type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* CALLBACKS END */
|
||||
|
||||
int add_window(Tox *m, ToxWindow *w)
|
||||
@ -406,7 +457,12 @@ void del_window(ToxWindow *w)
|
||||
refresh();
|
||||
|
||||
if (num_active_windows > 0) {
|
||||
if (active_window_index == 2) { // if closing current window would bring us to friend list
|
||||
set_next_window(-1); // skip back to the home window instead. FIXME: magic numbers
|
||||
}
|
||||
|
||||
set_next_window(-1);
|
||||
|
||||
--num_active_windows;
|
||||
}
|
||||
}
|
||||
@ -440,6 +496,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 +514,29 @@ void on_window_resize(void)
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef GAMES
|
||||
|
||||
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);
|
||||
|
||||
if (y2 <= 0 || x2 <= 0) {
|
||||
fprintf(stderr, "Failed to resize game window: max_x: %d, max_y: %d\n", x2, y2);
|
||||
delwin(w->window);
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#endif // GAMES
|
||||
|
||||
if (w->help->active) {
|
||||
wclear(w->help->win);
|
||||
}
|
||||
@ -473,14 +555,12 @@ 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) {
|
||||
fprintf(stderr, "Failed to resize window: max_x: %d, max_y: %d\n", x2, y2);
|
||||
delwin(w->window);
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (w->show_peerlist) {
|
||||
@ -710,6 +790,26 @@ void draw_active_window(Tox *m)
|
||||
a->onDraw(a, m);
|
||||
wrefresh(a->window);
|
||||
|
||||
#ifdef GAMES
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
a->onKey(a, m, ch, false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#endif // GAMES
|
||||
|
||||
wint_t ch = 0;
|
||||
int printable = get_current_char(&ch);
|
||||
|
||||
@ -796,14 +896,35 @@ 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);
|
||||
switch (w->type) {
|
||||
case WINDOW_TYPE_CHAT: {
|
||||
kill_chat_window(w, m);
|
||||
break;
|
||||
}
|
||||
|
||||
case WINDOW_TYPE_CONFERENCE: {
|
||||
free_conference(w, w->num);
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef GAMES
|
||||
|
||||
case WINDOW_TYPE_GAME: {
|
||||
game_kill(w);
|
||||
break;
|
||||
}
|
||||
|
||||
#endif // GAMES
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,13 @@
|
||||
#define TOP_BAR_HEIGHT 1
|
||||
#define WINDOW_BAR_HEIGHT 1
|
||||
|
||||
|
||||
typedef enum CustomPacket {
|
||||
CUSTOM_PACKET_GAME_INVITE = 160,
|
||||
CUSTOM_PACKET_GAME_DATA = 161,
|
||||
} CustomPacket;
|
||||
|
||||
|
||||
/* ncurses colour pairs as FOREGROUND_BACKGROUND. No background defaults to black. */
|
||||
typedef enum {
|
||||
WHITE,
|
||||
@ -53,8 +60,10 @@ typedef enum {
|
||||
YELLOW,
|
||||
MAGENTA,
|
||||
BLACK,
|
||||
BLUE_BLACK,
|
||||
BLACK_WHITE,
|
||||
WHITE_BLACK,
|
||||
WHITE_BLUE,
|
||||
WHITE_GREEN,
|
||||
BAR_TEXT,
|
||||
STATUS_ONLINE,
|
||||
BAR_ACCENT,
|
||||
@ -78,6 +87,10 @@ typedef enum {
|
||||
WINDOW_TYPE_CHAT,
|
||||
WINDOW_TYPE_CONFERENCE,
|
||||
WINDOW_TYPE_FRIEND_LIST,
|
||||
|
||||
#ifdef GAMES
|
||||
WINDOW_TYPE_GAME,
|
||||
#endif
|
||||
} WINDOW_TYPE;
|
||||
|
||||
/* Fixes text color problem on some terminals.
|
||||
@ -130,6 +143,10 @@ typedef struct PromptBuf PromptBuf;
|
||||
typedef struct ChatContext ChatContext;
|
||||
typedef struct Help Help;
|
||||
|
||||
#ifdef GAMES
|
||||
typedef struct GameData GameData;
|
||||
#endif
|
||||
|
||||
struct ToxWindow {
|
||||
/* ncurses */
|
||||
bool(*onKey)(ToxWindow *, Tox *, wint_t, bool);
|
||||
@ -156,6 +173,11 @@ struct ToxWindow {
|
||||
void(*onTypingChange)(ToxWindow *, Tox *, uint32_t, bool);
|
||||
void(*onReadReceipt)(ToxWindow *, Tox *, uint32_t, uint32_t);
|
||||
|
||||
#ifdef GAMES
|
||||
void(*onGameInvite)(ToxWindow *, Tox *, uint32_t, const uint8_t *, size_t);
|
||||
void(*onGameData)(ToxWindow *, Tox *, uint32_t, const uint8_t *, size_t);
|
||||
#endif // GAMES
|
||||
|
||||
#ifdef AUDIO
|
||||
|
||||
void(*onInvite)(ToxWindow *, ToxAV *, uint32_t, int);
|
||||
@ -193,6 +215,10 @@ struct ToxWindow {
|
||||
StatusBar *stb;
|
||||
Help *help;
|
||||
|
||||
#ifdef GAMES
|
||||
GameData *game;
|
||||
#endif
|
||||
|
||||
WINDOW *window;
|
||||
WINDOW *window_bar;
|
||||
};
|
||||
@ -284,4 +310,5 @@ void draw_window_bar(ToxWindow *self);
|
||||
call at least once per second */
|
||||
void refresh_inactive_windows(void);
|
||||
|
||||
#endif /* WINDOWS_H */
|
||||
#endif // WINDOWS_H
|
||||
|
||||
|
Reference in New Issue
Block a user