mirror of
				https://github.com/Tha14/toxic.git
				synced 2025-11-04 11:56:52 +01:00 
			
		
		
		
	Compare commits
	
		
			16 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					9e353443c2 | ||
| 
						 | 
					d02f3b4acb | ||
| 
						 | 
					f2b1c81279 | ||
| 
						 | 
					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,15 @@ mkdir -p "$BUILD_DIR"
 | 
			
		||||
# Build Toxcore
 | 
			
		||||
cd "$BUILD_DIR"
 | 
			
		||||
 | 
			
		||||
TOXCORE_VERSION="0.2.12"
 | 
			
		||||
TOXCORE_HASH="30ae3263c9b68d3bef06f799ba9d7a67e3fad447030625f0ffa4bb22684228b0"
 | 
			
		||||
# The git hash of the c-toxcore version we're using
 | 
			
		||||
TOXCORE_VERSION="25a56c354937e9c8c4c50a64c3b4cfc099c34e29"
 | 
			
		||||
 | 
			
		||||
# The sha256sum of the c-toxcore tarball for TOXCORE_VERSION
 | 
			
		||||
TOXCORE_HASH="8448752e6286c747130254571fde2db8e2fc073a8116f9fff489ed53af546c0a"
 | 
			
		||||
 | 
			
		||||
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/$TOXCORE_VERSION.tar.gz"
 | 
			
		||||
check_sha256 "$TOXCORE_HASH" "$TOXCORE_FILENAME"
 | 
			
		||||
tar -o -xf "$TOXCORE_FILENAME"
 | 
			
		||||
rm "$TOXCORE_FILENAME"
 | 
			
		||||
@@ -160,8 +164,8 @@ cmake --build _build --target install
 | 
			
		||||
# location with SSL_CERT_FILE env variable.
 | 
			
		||||
cd "$BUILD_DIR"
 | 
			
		||||
 | 
			
		||||
CURL_VERSION="7.74.0"
 | 
			
		||||
CURL_HASH="e56b3921eeb7a2951959c02db0912b5fcd5fdba5aca071da819e1accf338bbd7"
 | 
			
		||||
CURL_VERSION="7.77.0"
 | 
			
		||||
CURL_HASH="b0a3428acb60fa59044c4d0baae4e4fc09ae9af1d8a3aa84b2e3fbcd99841f77"
 | 
			
		||||
CURL_FILENAME="curl-$CURL_VERSION.tar.gz"
 | 
			
		||||
 | 
			
		||||
wget --timeout=10 -O "$CURL_FILENAME" "https://curl.haxx.se/download/$CURL_FILENAME"
 | 
			
		||||
@@ -178,7 +182,8 @@ cd curl*
 | 
			
		||||
  --without-ca-path \
 | 
			
		||||
  --with-ca-fallback \
 | 
			
		||||
  --with-nghttp2 \
 | 
			
		||||
  --with-brotli
 | 
			
		||||
  --with-brotli \
 | 
			
		||||
  --with-openssl
 | 
			
		||||
make
 | 
			
		||||
make install
 | 
			
		||||
sed -i 's|-lbrotlidec |-lbrotlidec-static -lbrotlicommon-static |g' $BUILD_DIR/prefix-curl/lib/pkgconfig/libcurl.pc
 | 
			
		||||
@@ -214,6 +219,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 +301,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_SOLID
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* 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 */
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								src/help.c
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								src/help.c
									
									
									
									
									
								
							@@ -174,13 +174,17 @@ static void help_draw_global(ToxWindow *self)
 | 
			
		||||
    wprintw(win, "  /decline <id>              : Decline friend request\n");
 | 
			
		||||
    wprintw(win, "  /requests                  : List pending friend requests\n");
 | 
			
		||||
    wprintw(win, "  /connect <ip> <port> <key> : Manually connect to a DHT node\n");
 | 
			
		||||
    wprintw(win, "  /status <type> <msg>       : Set status with optional note\n");
 | 
			
		||||
    wprintw(win, "  /status <type>             : Set status (Online, Busy, Away)\n");
 | 
			
		||||
    wprintw(win, "  /note <msg>                : Set a personal note\n");
 | 
			
		||||
    wprintw(win, "  /nick <nick>               : Set your nickname\n");
 | 
			
		||||
    wprintw(win, "  /nospam <value>            : Change part of your Tox ID to stop spam\n");
 | 
			
		||||
    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",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								src/toxic.c
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/toxic.c
									
									
									
									
									
								
							@@ -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,11 +376,14 @@ 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);
 | 
			
		||||
        init_pair(BAR_SOLID, bar_bg_color, bar_bg_color);
 | 
			
		||||
        init_pair(BAR_ACCENT, bar_accent_color, bar_bg_color);
 | 
			
		||||
        init_pair(BAR_NOTIFY, bar_notify_color, bar_bg_color);
 | 
			
		||||
        init_pair(STATUS_ONLINE, COLOR_GREEN, bar_bg_color);
 | 
			
		||||
@@ -804,6 +807,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 +1465,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,
 | 
			
		||||
@@ -63,6 +72,7 @@ typedef enum {
 | 
			
		||||
    STATUS_BUSY,
 | 
			
		||||
    STATUS_AWAY,
 | 
			
		||||
    BAR_NOTIFY,
 | 
			
		||||
    BAR_SOLID,
 | 
			
		||||
} C_COLOURS;
 | 
			
		||||
 | 
			
		||||
/* tab alert types: lower types take priority (this relies on the order of C_COLOURS) */
 | 
			
		||||
@@ -78,6 +88,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 +144,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 +174,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 +216,10 @@ struct ToxWindow {
 | 
			
		||||
    StatusBar *stb;
 | 
			
		||||
    Help *help;
 | 
			
		||||
 | 
			
		||||
#ifdef GAMES
 | 
			
		||||
    GameData *game;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    WINDOW *window;
 | 
			
		||||
    WINDOW *window_bar;
 | 
			
		||||
};
 | 
			
		||||
@@ -284,4 +311,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