From e5ff66a84e137d614c83e81daef67ef675818fb5 Mon Sep 17 00:00:00 2001 From: Barry Kane Date: Wed, 15 Feb 2023 22:24:24 +0000 Subject: [PATCH] Alpha 0.5 release of SilverMUD: Features Changed: - Refactored old lists and queues to a single type. - Commented and cleaned up the codebase. - Various improvements. See commits for more details. Squashed commit of the following: commit ff281e5ce6b9a74158a1c5aa97b7e429727b1018 Author: Barry Kane Date: Wed Feb 15 22:16:12 2023 +0000 Increment version number commit f5cb3ad16ed4f3d75f9ee39ec5fe7c9981c4646a Author: Barry Kane Date: Wed Feb 15 22:09:21 2023 +0000 More cleaning up. - Brought remaining files in line with style guides, and improved comments. commit f31f0c79a55681f7d4d1d4886bbf2bde7dc25483 Author: Barry Kane Date: Wed Feb 15 21:43:13 2023 +0000 Cleaned up gamelogic.c/h - Improved comments to clarify the purpose and usage of the data structures and functions. - Brought the files in line with the SilverMUD style guide. commit c2af4a551a4e44e8e53579cac1927341be40e46c Author: Barry Kane Date: Wed Feb 15 21:30:40 2023 +0000 Cleaned up inputoutput.h - Changed the comments about the data structures to be more descriptive about their function. commit c7531828274f5a725a31238d7d50ce6a83e8f96f Author: Barry Kane Date: Wed Feb 15 21:22:14 2023 +0000 Cleaned up inputoutput.c. - Added additional comments in sections functions that were unclear. - Renamed targetIndex to sentCount, in order to clarify the usage of the variable. - Linted according to the current SilverMUD style guide. commit f411333203df3d0ab01e9f4701fd81a477bcac0a Author: Barry Kane Date: Mon Feb 13 17:23:30 2023 +0000 Refactored SilverMUD to use the queue type for command events. - Removed the old commandQueue type. - Removed the functions relating to creating, pushing, popping, and peeking the commandQueues. - Refactored the gameLogicHandler to use a queue type. commit 602f177a8f9b7d1eef5dc28bd36878b31aa06789 Author: Barry Kane Date: Sun Feb 12 23:32:39 2023 +0000 Added some more comments. - Commented the data structures in areadata.h and gamelogic.h. commit d0e4a8f9fc06f9813a2f585f324ab6ddeb80160b Author: Barry Kane Date: Sun Feb 12 23:13:10 2023 +0000 Removed the body of the try command. - Removed the try command's test functionality to allow for the later implementation of the actual functionality. commit 66e0279e781fef6e6f7a3f6732c0e56084c50999 Author: Barry Kane Date: Sat Feb 11 00:07:30 2023 +0000 Commented the data structures in queue.h. - Commented all the data structures in queue, to make it slightly clearer as to their usage and what they store. commit feb17434252d76cd81ec41f6b1d41f6bdbad8a99 Author: Barry Kane Date: Fri Feb 10 23:33:36 2023 +0000 Added naming rule 2 commit 52fd7ef6fb5102897b9243fd52cc3c037761641b Author: Barry Kane Date: Thu Feb 9 21:53:23 2023 +0000 Wrote more documentation and a rule for the style guide. Documented basic usage of the client's launch options to join a server and added the no one letter variable names rule. commit a38cbb70a8b6dc22696f85bef8a8fafedc304025 Author: Barry Kane Date: Wed Feb 8 17:15:23 2023 +0000 Fixed Thomas's Bug. In short: - When a large amount of input was recieved, the server would hang. - The server would hang on queue->lock for the input queue, in pushQueue(). - Upon debugging, it was revealed that queue->lock was actually false at this time. - GCC had optimized out the actual loop, leaving it to get stuck even though the variable had changed. - Adding the volatile keyword to the lock fixed this issue. commit c2c77d634343a66b2d7a54e96d4908e9b436f91e Author: Barry Kane Date: Wed Dec 21 20:31:32 2022 +0000 Cleaned and styled SilverMUDClient.c - Brought SilverMUDClient.c to a consistent style. - Cleaned and neatened various parts of SilverMUDClient.c. - Minor cleanup of playerdata.h. - Began writing the SilverMUD Style Guide. - Removed outputQueue-test.c, because outputQueue no longer exists. commit 6a653c75b98e59f03909da05711a09b834311f01 Author: Barry Kane Date: Wed Dec 21 00:49:26 2022 +0000 Implemented proper thread sleeping and additional output thread - Replaced previous inefficient "spin-waiting" with proper thread sleeping. - Added threading primitives to the queue type to enable this. - Added additional thread for output management. - Miscellanous cleanup and restructuring. commit 15d82f59ee96cca51bfc9960c77c46ff59a19011 Author: Barry Kane Date: Tue Dec 20 15:55:24 2022 +0000 Refactored the outputQueue to use the new queue type - Patched a bug where I didn't set the correct type for queue nodes. - Removed all traces of the previous outputMessageQueue type. - Removed the pointer "next" in outputMessage. - Rewrote the the main thread and game logic thread to use a queue for output. - Refactored outputMessage to allow for a variable amount of playerInfo pointers. commit 9b3df5928b45ba2eb74c27f25ee71fa1813fa0af Author: Barry Kane Date: Mon Dec 19 23:44:16 2022 +0000 Refactored the input queue to use the new queue type - Removed all traces of the previous inputMessageQueue type. - Removed the pointer "next" in inputMessage. - Rewrote the the main thread and game logic thread to use a queue for input. commit 8ae3eaf2b844390512deb9fc46159635562130d1 Author: Barry Kane Date: Sun Dec 18 21:07:10 2022 +0000 Added queue.c and queue.h. - Added a singular queue type for input/output messages and commands. - Added a test of the queue type in tests. - Made some edits to the Makefile to enable the addition of debug code using the preprocessor. - Minor styling and spelling updates in gamelogic.c/h and text effects .h. - Ready to integrate the new queue type in place of the previous ones. commit 2ab873b40b9ce6c9bf7edc38609c50f267318181 Author: Barry Kane Date: Wed Nov 30 15:31:14 2022 +0000 Make the client respect the set character delay. - The client now respects the character delay set on the command line. commit 4cc0d3a0f61e66f38872ffbebd7ec8f853819909 Author: Barry Kane Date: Tue Nov 29 21:04:36 2022 +0000 Completed the conversion to the new linked-list type. - Moved all code relating to skills to use the new linked-lists. - Removed all old code relating to other lists. - Improved linked lists to get nodes more efficiently in the second half of the list. commit 51f1a953e71be24c827a8aa2b120fb277eb3f3d1 Author: Barry Kane Date: Fri Nov 18 14:44:25 2022 +0000 Refactored areas to use linked-lists. - Refactored the server to rely on the linked-list version of area lists. - Removed all old code pertaining to Area/Path lists. - Removed a no-longer useful test for corestat-from string performance.y commit 6b3d9febf64b4083ec6b3a464aac7d4dcd3df5ea Author: Barry Kane Date: Sun Nov 13 18:26:36 2022 +0000 Linked lists now destroy the pathList of an area. - Modified destroyList to destroy pathLists in areas. commit d843f0b170e5c58f5ed7ce0c762d7887449c566f Author: Barry Kane Date: Sun Nov 13 18:21:06 2022 +0000 Added one-way path function. - Added a function to create one way paths. - Changed the test areas to a new setting and to include one-way paths. commit 582a0d02aecc22d2b7274bb00e1a68176fbcdf00 Author: Barry Kane Date: Sun Nov 13 00:23:42 2022 +0000 Refactored paths to use linked lists. - Removed the code for the now-obsolete-before-actually-being-used pathLists. - playerAreas now contain a list called pathList. - Refactored createArea, createPath, and the /move and /look commands. - Added typedefs to prevent the compiler being unable to link. commit f3ad758e4f4344b58c2218db0c3eaa46b7e89fd4 Author: Barry Kane Date: Fri Nov 11 22:58:05 2022 +0000 Preliminary implementation of linked-list.c - Implemented first pass of linked-list.c. TODO: - Polish linked-list.c 'till you can see a reflection in it. - Refactor existing codebase to use linked-list.c.y commit c68e66e7bc92bf6fb349ac09adcadfdff29960db Author: Barry Kane Date: Mon Oct 31 01:55:44 2022 +0000 Squashed current warnings. - Fixed all current warnings. - Added the ability to set the text delay as a command line option in the server. commit ca8ba5e41043aaea9823b4423ea1b69f30d7664a Author: Barry Kane Date: Sun Oct 30 13:00:18 2022 +0000 Incremented version numbering in preperation for merge to master. - Incremented version numbering to Alpha 0.4. commit d9497679cb8a3bd906400f13cd08bdb54876dee1 Author: Barry Kane Date: Sun Oct 30 12:58:39 2022 +0000 Implemented Skill Checks. - Implemented skillCheck. - Allowed for the in-game testing of skillCheck via /try. - Slightly reorganized the Makefile. - Tweaked the logoString to display correctly. - Edited the client and server to generate gprof data when in debug builds. commit f2dd83857ffc6c60cd40872910fe73057202d492 Author: Barry Kane Date: Sun Oct 23 17:07:13 2022 +0100 Completed Reorganization of Area Data - Moved the appropriate data structures and functions into areaData. - Made movePlayerToArea a gameplay primitive. commit 52b4b1e2f05ba27a0576e05fd3e4d7fa0b8b744b Author: Barry Kane Date: Tue Oct 18 21:00:57 2022 +0100 Warning fixes and Makefile edits - Adjusted the Makefile to create gprof data in the server (currently inactive as the server never terminates.) - Fixed warnings in areadata.c and gamelogic.c. - Added bruteforcePrint for completeness. commit 60110d3abd48a413c904c559a39f1d13c705709b Author: Barry Kane Date: Sun Oct 16 21:28:32 2022 +0100 Made client exit gracefully upon server exit: - The client now checks the return value of messageReceive. - Renamed lists.c/.h to areadata.c/.h. commit b8189ae2dee1ffd4f44faa18c3482c3718a73da2 Author: Barry Kane Date: Sun Oct 16 16:13:33 2022 +0100 Began implementation of skills and stats. - Added text wrapping in client. - Implemented functions for managing skill data. - Rewrote some existing functionality to allow for variable length game messages over multiple userMessages. - Reorganized the code yet again. - Introduced enums for coreStats and outcomes. - Implemented core stat checks. - Added more example skills. - Rewrote test areas to have longer descriptions. commit 8673bb1ad5391e0e61f7b3ebf734ef74c01c0ef5 Author: Barry Kane Date: Fri May 20 22:28:07 2022 +0100 Reorganized file structure. - Reimplemented /LOOK. - Commands are now accepted in both upper and lower case. - Move now accepts a number for easier movement. commit 151f3002b81d4629b656598ba957ae401503bae0 Author: Barry Kane Date: Thu May 5 19:45:27 2022 +0100 Began implementing game logic and re-implementing commands - Reimplemented /MOVE and /EXIT - The server is now multi-threaded - Input and output is now queued commit 0b3a72beffb789f6d56799626207008890a78a40 Author: Barry Date: Thu Apr 7 01:39:59 2022 +0100 Removed inputhandling library The functionality was moved to inputoutput. commit 4ddb80b8b26f13b240a790d62e9047dfd9cd15f0 Author: Barry Date: Thu Apr 7 01:38:36 2022 +0100 Basic message queuing implemented - Messages are now queued on reception by the server. - Message queue datastructures are now added. commit e4b86930374c70a8d95e1c9986ef90a77cf65f4f Author: Barry Date: Tue Mar 15 14:52:49 2022 +0000 Cleaned up client. - Cleaned up the client codebase. - Throughly commented SilverMUDClient.c. - Added a boolean for bolding slowPrintNcurses. - Added a user-configurable delay for text printing. - Other small improvements. commit 5d772df46985213f10cc955ad2db1975f7078e15 Author: Barry Date: Sun Mar 6 00:36:42 2022 +0000 Added initial GnuTLS encryption. - Added inputoutput.c - Added inputoutput.h - inputoutput contains wrapper and helper functions for transmitting messages over GnuTLS. - Moved the userMessage struct definition to inputoutput. - Reworked client and server to use GnuTLS. - Removed all commands from server in preperation for upcoming command and message queues. - Names and areas are no longer considered for messaging. - Changed Makefile to link GnuTLS. commit 235ff8e74ffb1afdbd7585b5226a3efb5dff4b9a Author: Barry Date: Sun Dec 26 19:07:30 2021 +0000 Added basic logging support and command-line options to the client. - Added basic logging support to the client. - Added basic command-line options to the client: -g: Enables a game-log, must have a file-path. -c: Enables a chat-log, must have a file-path. -i: Sets the IP address to connect to. - Removed the C-c handler, appeared to be broken anyways. Consider reimplementation at some point. - Added /EXIT command to allow for leaving the game. - The client now exits gracefully if the server dies. commit 6c93805d6f3bf5f660562f0ec4c2b267ca124a76 Author: Barry Date: Sun Dec 5 23:33:53 2021 +0000 Added initial implementation of doubly-linked lists - Added lists.c - Added lists.h - Changed initialisation of rooms to add a third room and to add the rooms to a list. - Added datastructures for area and path nodes for doubly-linked lists. commit 241ac7a92b6a335d780486f3c23a3b8385e3d408 Author: Barry Date: Thu Nov 4 23:14:47 2021 +0000 Added area and path creation functions - Added missing header guards. - Increased the size of message contents to 2048. - Added area and path initialization functions. - movePlayerToArea no longer segfaults. - /LOOK added to allow players to find exits. - Amount of paths allowed out of an area has been decreased to 16. - Debug builds are now available from the Makefile. - Removed unused variables. - Input sanatization has been moved to the server-side, phew. - Server messages are now displayed differently to player messages. - New area initialization has been added until I can integrate Guile. - Server's sendBuffer has been renamed messageBuffer. - Areas now have descriptions. - Descriptions are sent to the player upon joining an area and /LOOK-ing. commit 85a31a293390ee88fd462a68c26681f089474085 Author: Barry Date: Thu Oct 21 21:58:55 2021 +0100 Added basic area system - Added playerdata.c - Added basic move command - Added a basic initialisation of two connected rooms - Added datastructures for areas and paths commit ae8373d4ce4cddd6632893623cd86d68eb12b84c Author: Barry Kane Date: Wed Sep 15 00:12:05 2021 +0100 Incremented Version Number. - Incremented version number in preperation for merge. commit 18a4f416f6970bd826a6a5157cb03a61e1702048 Author: Barry Kane Date: Wed Sep 15 00:07:13 2021 +0100 Added basic name system - Added basic name system. - Added playerdata.h. - Added basic /NAME command. TODO: Create proper command system. - Added datastructures for user messages and user names. commit 94118039427c81e047424c73f2f6c3ccb2e88f94 Author: Barry Kane Date: Fri Sep 10 15:07:42 2021 +0100 Increment version message for merge. Incremented the version number by 0.1 for the server. Added version splash to the client. commit 7047d0ee08dd522709d3130fa340d33f4ab5e23f Author: Barry Kane Date: Fri Sep 10 15:03:02 2021 +0100 Added two-window messaging to the client. Client now has two seperate Ncurses windows for sending and receiving. Added SIGINT handler which sets a global boolean to gracefully exit and free memory. Sending and Receiving are now on their own threads. A pointer-to-struct is now passed to the threads. The main thread will now wait to cancel the threads upon receiving SIGINT. slowPrintNcurses now takes a window argument. The server now doesn't check that a client receives the message that they sent, allowing for full chat history. commit 33bc9bcda0c5d4afbbfa9b5371ad2ef83b5e6f1b Author: Barry Kane Date: Fri Sep 3 18:47:11 2021 +0100 Adapted client to use Ncurses instead of raw terminal output: Created "slowPrintNcurses", which is a version of "slowPrint" compatible with Ncurses screens. Ncurses is now used in place of raw-terminal output. The screen clears after inital start-up messages. C-d no longer exits, and still doesn't spam. Added Ncurses to the ld options of client in the Makefile. Created ld options for server in the Makefile. commit 849a80bd377ffad8c3f4cad4880540d45c36173c Author: Barry Kane Date: Thu Aug 19 23:07:58 2021 +0100 Basic input sanatization: Created new library to deal with user input. Implemented check in client to prevent C-d spamming the server. C-d now exits. Implemented check in client to prevent clients sending messages containing only newlines to the server. commit 2c093903a4f5c32a659f085922f9cab28dd8a2b0 Author: Barry Kane Date: Tue Aug 17 18:57:56 2021 +0100 Git Sanity Check --- Makefile | 4 +- SilverMUD.org | 91 +++++- src/areadata.c | 207 ++----------- src/areadata.h | 60 +--- src/client/SilverMUDClient.c | 193 +++++++----- src/gamelogic.c | 571 +++++++++++++++-------------------- src/gamelogic.h | 88 +++--- src/inputoutput.c | 356 ++++++---------------- src/inputoutput.h | 122 +++----- src/linkedlist.c | 479 +++++++++++++++++++++++++++++ src/linkedlist.h | 85 ++++++ src/playerdata.c | 178 ++++------- src/playerdata.h | 41 +-- src/queue.c | 220 ++++++++++++++ src/queue.h | 71 +++++ src/server/SilverMUDServer.c | 173 ++++++----- src/texteffects.c | 37 ++- src/texteffects.h | 22 +- tests/list-test.c | 56 ++-- tests/queue-test.c | 132 ++++++++ 20 files changed, 1858 insertions(+), 1328 deletions(-) create mode 100644 src/linkedlist.c create mode 100644 src/linkedlist.h create mode 100644 src/queue.c create mode 100644 src/queue.h create mode 100644 tests/queue-test.c diff --git a/Makefile b/Makefile index 90805f5..e1478ab 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,6 @@ clean: rm -f $(clientobj) $(serverobj) SilverMUDClient SilverMUDServer SilverMUDClientDebug SilverMUDServerDebug all: clean SilverMUDClient SilverMUDServer -all: CFLAGS += -Wall -Wextra -Ofast -debug: CFLAGS += -Wall -Wextra -pg -ggdb -Og +all: CFLAGS += -Wall -Wextra -Ofast +debug: CFLAGS += -Wall -Wextra -pg -ggdb -Og -D debug debug: clean SilverMUDClientDebug SilverMUDServerDebug diff --git a/SilverMUD.org b/SilverMUD.org index 070bfe8..8fc4d46 100644 --- a/SilverMUD.org +++ b/SilverMUD.org @@ -1,28 +1,91 @@ +#+LATEX_HEADER: \RequirePackage[left=0.3in,top=0.3in,right=0.3in,bottom=0.3in, a4paper]{geometry} * SilverMUD: The Hackable Terminal-Top Roleplaying Game. SilverMUD is a tool for creating engaging and communal stories, all over the world through the internet. It's designed to give a gamemaster the same power to improvise that they have at the table, through simple programming and easy-to-understand structures. -** Player's Guide -*** The Basic Commands + +* Player's Guide +** Running The Client +*** How To Connect To A Server: +To connect to a server, use the command-line option =-i=, and the IP address of +the server. If the server admin is hosting the server on a port other than the +default port of 5000, you can use the =-p= option with the number of the port. If +the connection is successful, you will be placed in the server's login +area. Type =/join =, where player name is a name of your choosing, +and you will be placed in the spawn area, ready to play. + +*** Other Launch Options: + +** The Basic Commands SilverMUD is played through a set of very simple commands. To use a command, type a forward-slash (/) followed immediately by the command name. The command can be upper or lower-case. -| Command | Arguments | Effect | -|---------+---------------------------------------------------+--------------------------------------------------------------------| -| JOIN | Takes a character name | Logs you into the server with the given character name. | -| MOVE | Takes a path name or a path number | Moves you down the given path. | -| LOOK | None | Gives you a description of what's around you, and what you can do. | -| STAT | None | Displays your current status and character sheet. | -| SPEC | Core stat name | Allows you to apply spec points to a given stat. | -| TRY | Core stat name or skill name and an object number | Attempt to use the given stat or skill on the object. | +| Command | Arguments | Effect | +|---------+------------------------------------------+---------------------------------------------------------| +| JOIN | Character Name | Logs you into the server with the given character name. | +| MOVE | Path Name/Path Number | Moves you down the given path. | +| LOOK | None | Describes the current area. | +| STAT | None | Displays your current status and character sheet. | +| SPEC | Core Stat Name | Allows you to apply spec points to a given stat. | +| TRY | Core Stat Name/Skill Name, Object Number | Attempt to use the given stat or skill on the object. | -** Gamemaster's Guide -*** Running the Server: +* Gamemaster's Guide +** Running the Server: -** Developer's Guide -*** Build Prerequisites: +* Developer's Guide +** Build Prerequisites: SilverMUD has the following dependencies: - GnuTLS - ncurses + +** C Style Guide: +These rules attempt to make the program as visually clear as possible, while +some rules may be made based on my own personal tastes. + +- () :: These are parentheses. +- [] :: These are brackets. +- {} :: These are braces. +*** Formatting: +**** Control Statements: +- A space should be between the keyword and the condition. This is to make + control statements visually distinct from function calls. + +- Opening braces should be on the line after the control statement, and closing + braces on the line after the last statement, on it's own. This is to make the + scope of the control statement easily identifiable. + +- else and else if should always be on a new line, not the same line as an if + statement's closing brace. This is to more easily distinguish the seperate + blocks. + +- Control statements should never omit braces and do single statements. This is + mostly personal preference, but I do think it makes things more clear. + +*** Naming: +**** Rule 0: NEVER USE i AND j! +Never use the variable names i and j. These are easy to confuse, and often make +nested loops awful to read. Name these more descriptively. +For example: +- If you are using a variable to index an array, name the variable index. +- If you are indexing multiple arrays, name it "array name + Index". +- If you are using it to count something, call it count, or "name of the + thing you are counting + count". + +**** Rule 1: No one letter variable names, unless in a mathematical function. +You should never use one letter variable names. They're needlessly obtuse and +you will not remember their meaning upon re-reading of the source code. The +exception to this is when you are writing a function which replicates a +mathematical formula or function with commonly accepted notation. However, you +should consider if it would be better to break mathematical convention for +clarity inside the program, such as when the variable names are the first letter +of a word or the mathematical notation uses many similar looking variables. + +**** Rule 2: Prefer to use full words in variable and function names: +You should always prefer to use full words in variable and function names. It +makes the source code much easier to read, like a sentence. Ideally, if you want +to shorten the name, use synonyms or rephrasing before you resort to removing +letters. + +*** Comments: diff --git a/src/areadata.c b/src/areadata.c index cdab489..50a61b9 100644 --- a/src/areadata.c +++ b/src/areadata.c @@ -3,6 +3,7 @@ #include #include "areadata.h" #include "playerdata.h" +#include "linkedlist.h" // ==================== // -=[ Area/Paths: ]=-: @@ -16,18 +17,15 @@ playerArea * createArea(char * nameString, char * descriptionString) // Copy the strings into the newly created area: strncpy(createdArea->areaName, nameString, 32 - 1); - strncpy(createdArea->areaDescription, descriptionString, MAX - 35); + strncpy(createdArea->areaDescription, descriptionString, MAX - 36); // Properly null-terminate the strings: createdArea->areaName[31] = '\0'; - createdArea->areaDescription[MAX] = '\0'; - - // Ensure that all the paths are set to NULL: - for(int index = 0; index < 16; index++) - { - createdArea->areaExits[index] = NULL; - } + createdArea->areaDescription[MAX - 36] = '\0'; + // Create a list for the paths in the area: + createdArea->pathList = createList(PATH); + // Return the pointer: return createdArea; } @@ -35,183 +33,40 @@ playerArea * createArea(char * nameString, char * descriptionString) // Create a path between two areas given two areas and two strings: int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescription, char * toDescription) { - int fromAreaSlot, toAreaSlot; - for(fromAreaSlot = 0; fromAreaSlot < 16; fromAreaSlot++) - { - if(fromArea->areaExits[fromAreaSlot] == NULL) - { - break; - } - if((fromArea->areaExits[fromAreaSlot] != NULL) && (fromAreaSlot == 15)) - { - return 1; - } - } - for(toAreaSlot = 0; toAreaSlot < 32; toAreaSlot++) - { - if(toArea->areaExits[toAreaSlot] == 0) - { - break; - } - if((toArea->areaExits[toAreaSlot] != 0) && (toAreaSlot == 31)) - { - return 2; - } - } + // Allocate the new paths: playerPath * fromPath = malloc(sizeof(playerPath)); playerPath * toPath = malloc(sizeof(playerPath)); - fromArea->areaExits[fromAreaSlot] = fromPath; - toArea->areaExits[toAreaSlot] = toPath; + + // Setup the from path: strncpy(fromPath->pathName, fromDescription, 32 - 1); - fromPath->pathName[31] = '\0'; + fromPath->pathName[31] = '\0'; + fromPath->areaToJoin = toArea; + + // Setup the to path: strncpy(toPath->pathName, toDescription, 32 - 1); - toPath->pathName[31] = '\0'; - fromArea->areaExits[fromAreaSlot]->areaToJoin = toArea; - toArea->areaExits[toAreaSlot]->areaToJoin = fromArea; + toPath->pathName[31] = '\0'; + toPath->areaToJoin = fromArea; + + // Add to the lists: + addToList(fromArea->pathList, fromPath, PATH); + addToList(toArea->pathList, toPath, PATH); + return 0; } -// ========================= -// -=[ Area/Path Lists: ]=-: -// ========================= - -// Create and initialize an areaList: -areaNode * createAreaList(playerArea * initialArea) +// Create a one-way path between two areas given two areas and a string: +int createOneWayPath(playerArea * fromArea, playerArea * toArea, char * description) { - areaNode * newAreaList = malloc(sizeof(areaNode)); - newAreaList->data = initialArea; - newAreaList->next = NULL; - newAreaList->prev = NULL; - return newAreaList; -} + // Allocate the new paths: + playerPath * path = calloc(1, sizeof(playerPath)); -// Create and initialize an pathList: -pathNode * createPathList(playerPath * initialPath) -{ - pathNode * newPathList = malloc(sizeof(pathNode)); - newPathList->data = initialPath; - newPathList->next = NULL; - newPathList->prev = NULL; - return newPathList; -} + // Setup the path: + strncpy(path->pathName, description, 32 - 1); + path->pathName[31] = '\0'; + path->areaToJoin = toArea; -// Adds an areaNode to the end of a list, returning it's position: -int addAreaNodeToList(areaNode * toList, playerArea * areaToAdd) -{ - areaNode * current; - int index = 0; - current = toList; - while(current->next != NULL) - { - current = current->next; - index++; - } - current->next = malloc(sizeof(areaNode)); - current->next->prev = current; - current->next->data = areaToAdd; - current->next->next = NULL; + // Add to the list: + addToList(fromArea->pathList, path, PATH); + return 0; } - -// Removes an areaNode from the list, returning 0 on success and -1 on failure: -int deleteAreaNodeFromList(areaNode * fromList, playerArea * areaToDelete) -{ - areaNode * current = fromList; - while(current->data != areaToDelete && current->next != NULL) - { - current = current->next; - } - if(current->next == NULL && current->data != areaToDelete) - { - return -1; - } - current->prev->next = current->next; - if(current->next != NULL) - { - current->next->prev = current->prev; - } - free(current); - return 0; -} - -// Adds an pathNode to the end of a list, returning it's position: -int addPathNodeToList(pathNode * toList, playerPath * pathToAdd) -{ - pathNode * current; - int index = 0; - current = toList; - while(current->next != NULL) - { - current = current->next; - index++; - } - current->next = malloc(sizeof(pathNode)); - current->next->prev = current; - current->next->data = pathToAdd; - current->next->next = NULL; - return index; -} - -// Removes an pathNode from the list, returning 0 on success and -1 on failure: -int deletePathNodeFromList(pathNode * fromList, playerPath * pathToDelete) -{ - pathNode * current = fromList; - while(current->data != pathToDelete || current->next != NULL) - { - current = current->next; - } - if(current->next == NULL && current->data != pathToDelete) - { - return -1; - } - current->prev->next = current->next; - if(current->next != NULL) - { - current->next->prev = current->prev; - } - free(current); - return 0; -} - -// Return the areaNode at the given index from the list: -areaNode * getAreaNode(areaNode * fromList, int listIndex) -{ - areaNode * current = fromList; - for(int index = 0; index < listIndex; index++) - { - if(current->next != NULL) - { - current = current->next; - } - else - { - return NULL; - } - } - return current; -} - -// Return the pathNode at the given index from the list: -pathNode * getPathNode(pathNode * fromList, int listIndex) -{ - pathNode * current = fromList; - for(int index = 0; index < listIndex; index++) - { - if(current->next != NULL) - { - current = current->next; - } - else - { - return NULL; - } - } - return current; -} - -// Return the playerArea of the areaNode at the given index from the list: -playerArea * getAreaFromList(areaNode * fromList, int listIndex) -{ - areaNode * current = getAreaNode(fromList, listIndex); - return current->data; -} diff --git a/src/areadata.h b/src/areadata.h index 53da6e6..327db34 100644 --- a/src/areadata.h +++ b/src/areadata.h @@ -3,79 +3,39 @@ #ifndef AREADATA_H #define AREADATA_H #include "constants.h" +#include "linkedlist.h" + // ==================== // -=[ Area/Paths: ]=-: // ==================== +// Let the compiler know that we're going to define these types: typedef struct playerPath playerPath; typedef struct playerArea playerArea; +// A path, which contains a name, and a pointer to the area which the player will travel to: struct playerPath { char pathName[32]; playerArea * areaToJoin; }; +// An area, containing a list of paths exiting from the area, and a name and description of the area: struct playerArea { + list * pathList; char areaName[32]; char areaDescription[MAX - 35]; - playerPath * areaExits[16]; }; -// Create an area given a name and description: +// Create an area given a name and description, returning a pointer to the new area: playerArea * createArea(char * nameString, char * descriptionString); -// Create a path between two areas given two areas and two strings: +// Create a path between two areas given two areas and two strings, adding it to the both area's list of paths: int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescription, char * toDescription); -// ========================= -// -=[ Area/Path Lists: ]=-: -// ========================= - -typedef struct areaNode areaNode; -typedef struct pathNode pathNode; - -struct pathNode -{ - playerPath * data; - pathNode * next; - pathNode * prev; -}; - -struct areaNode -{ - playerArea * data; - areaNode * next; - areaNode * prev; -}; - -// Create and initialize an areaList: -areaNode * createAreaList(playerArea * initialArea); - -// Create and initialize an pathList: -pathNode * createPathList(playerPath * initialPath); - -// Adds an areaNode to the end of a list, returning it's position: -int addAreaNodeToList(areaNode * toList, playerArea * areaToAdd); - -// Removes an areaNode from the list, returning 0 on success and -1 on failure: -int deleteAreaNodeFromList(areaNode * fromList, playerArea * areaToDelete); - -// Adds an pathNode to the end of a list, returning it's position: -int addPathNodeToList(pathNode * toList, playerPath * pathToAdd); - -// Removes an pathNode from the list, returning 0 on success and -1 on failure: -int deletePathNodeFromList(pathNode * fromList, playerPath * pathToDelete); - -// Return the areaNode at the given index from the list: -areaNode * getAreaNode(areaNode * fromList, int listIndex); - -// Return the pathNode at the given index from the list: -pathNode * getPathNode(pathNode * fromList, int listIndex); - -// Return the playerArea of the areaNode at the given index from the list: -playerArea * getAreaFromList(areaNode * fromList, int listIndex); +// Create a one-way path between two areas given two areas and a string, adding it to the first area's list of paths: +int createOneWayPath(playerArea * fromArea, playerArea * toArea, char * description); // TO BE IMPLEMENTED: /* int saveAreaList(areaNode * listToSave); */ diff --git a/src/client/SilverMUDClient.c b/src/client/SilverMUDClient.c index b0a8c50..2822f68 100644 --- a/src/client/SilverMUDClient.c +++ b/src/client/SilverMUDClient.c @@ -1,4 +1,4 @@ -// Silverkin Industries Comm-Link Client, Public Demonstration Sample Alpha 0.4. +// Silverkin Industries Comm-Link Client, Public Demonstration Sample Alpha 0.5. // PROJECT CODENAME: WHAT DO I PAY YOU FOR? | Level-3 Clearance. // Barry Kane, 2021 #include @@ -14,18 +14,20 @@ #include #include +#include "../queue.h" #include "../constants.h" #include "../playerdata.h" #include "../texteffects.h" #include "../inputoutput.h" -// A struct for bundling all needed paramaters for a thread so we can pass them using a void pointer: +// A struct for bundling all needed parameters for a thread so we can pass them using a void pointer: typedef struct threadparameters { gnutls_session_t tlsSession; FILE * loggingStream; bool loggingFlag; WINDOW * window; + int characterDelay; } threadparameters; // Use sockaddr as a type: @@ -34,17 +36,22 @@ typedef struct sockaddr sockaddr; // A globally available exit boolean: bool shouldExit = false; +// A function for managing the sending thread: void * messageSender(void * parameters) { - struct threadparameters *threadParameters = parameters; + threadparameters * threadParameters = parameters; + gnutls_session_t tlsSession = threadParameters->tlsSession; + FILE * loggingStream = threadParameters->loggingStream; + bool loggingFlag = threadParameters->loggingFlag; + WINDOW * window = threadParameters->window; userMessage sendBuffer; // Repeatedly get input from the user, place it in a userMessage, and send it to the server: while (!shouldExit) { // Print the prompt: - wprintw(threadParameters->window, "\n\n\nCOMM-LINK> "); - if (wgetnstr(threadParameters->window, sendBuffer.messageContent, MAX) == ERR) + wprintw(window, "\n\n\nCOMM-LINK> "); + if (wgetnstr(window, sendBuffer.messageContent, MAX) == ERR) { // Quit if there's any funny business with getting input: pthread_exit(NULL); @@ -57,78 +64,104 @@ void * messageSender(void * parameters) } // Send the message to the log if logging is enabled: - if (threadParameters->loggingFlag == true) + if (loggingFlag == true) { - fputs(sendBuffer.messageContent, threadParameters->loggingStream); - fputs("\n", threadParameters->loggingStream); - fflush(threadParameters->loggingStream); + fputs(sendBuffer.messageContent, loggingStream); + fputs("\n", loggingStream); + fflush(loggingStream); } // Send the message off to the server: - messageSend(threadParameters->tlsSession, &sendBuffer); + messageSend(tlsSession, &sendBuffer); } // Rejoin the main thread: pthread_exit(NULL); } +// A function for managing the receiving thread: void * messageReceiver(void * parameters) { + threadparameters * threadParameters = parameters; + gnutls_session_t tlsSession = threadParameters->tlsSession; + FILE * loggingStream = threadParameters->loggingStream; + int characterDelay = threadParameters->characterDelay; + bool loggingFlag = threadParameters->loggingFlag; + WINDOW * window = threadParameters->window; + int returnValue = 0; userMessage receiveBuffer; bool serverMessage = false; - struct threadparameters *threadParameters = parameters; int screenWidth = getmaxx(threadParameters->window); // Repeatedly take messages from the server and print them to the chat log window: while (!shouldExit) { - returnValue = messageReceive(threadParameters->tlsSession, &receiveBuffer); + // Get the next message: + returnValue = messageReceive(tlsSession, &receiveBuffer); + // Check we haven't been disconnected: - if(returnValue == -10 || returnValue == 0) + if (returnValue == -10 || returnValue == 0) { shouldExit = true; } + + // Check if it's a server message: else if (receiveBuffer.senderName[0] == '\0') { - wrapString(receiveBuffer.messageContent, - strlen(receiveBuffer.messageContent) - 1, screenWidth); + // Check if it's a command to disconnect: if (receiveBuffer.messageContent[0] == '\0') { shouldExit = true; pthread_exit(NULL); } - if(serverMessage == false) + + // Fit the string to the screen: + wrapString(receiveBuffer.messageContent, strlen(receiveBuffer.messageContent) - 1, screenWidth); + + // If it's the first server message in a block, begin a block of server messages: + if (serverMessage == false) { - slowPrintNcurses("\n --====<>====--", 4000, threadParameters->window, true); + slowPrintNcurses("\n --====<>====--", characterDelay, window, true); serverMessage = true; } - slowPrintNcurses("\n", 4000, threadParameters->window, true); - slowPrintNcurses(receiveBuffer.messageContent, 4000, threadParameters->window, false); - slowPrintNcurses("\n", 4000, threadParameters->window, true); + + // Print the message: + slowPrintNcurses("\n", characterDelay, window, true); + slowPrintNcurses(receiveBuffer.messageContent, characterDelay, + window, false); + slowPrintNcurses("\n", characterDelay, window, true); } + // It's a user message: else { - wrapString(receiveBuffer.messageContent, - strlen(receiveBuffer.messageContent) - 1, + // Fit the string to the screen: + wrapString(receiveBuffer.messageContent, strlen(receiveBuffer.messageContent) - 1, screenWidth - strlen(receiveBuffer.senderName) - 2); - if (threadParameters->loggingFlag == true) + + // If the user has requested logging, insert the message into the file: + if (loggingFlag == true) { - fputs(receiveBuffer.senderName, threadParameters->loggingStream); - fputs(": ", threadParameters->loggingStream); - fputs(receiveBuffer.messageContent, threadParameters->loggingStream); - fflush(threadParameters->loggingStream); + fputs(receiveBuffer.senderName, loggingStream); + fputs(": ", loggingStream); + fputs(receiveBuffer.messageContent, loggingStream); + fflush(loggingStream); } - if(serverMessage == true) + + // If we're in a block of server messages, end it: + if (serverMessage == true) { - slowPrintNcurses("\n --====<>====-- \n", 4000, threadParameters->window, true); + slowPrintNcurses("\n --====<>====-- \n", characterDelay, window, true); serverMessage = false; } - slowPrintNcurses(receiveBuffer.senderName, 4000, threadParameters->window, true); - slowPrintNcurses(": ", 4000, threadParameters->window, true); - slowPrintNcurses(receiveBuffer.messageContent, 4000, threadParameters->window, false); + + // Print the message: + slowPrintNcurses(receiveBuffer.senderName, characterDelay, window, true); + slowPrintNcurses(": ", characterDelay, window, true); + slowPrintNcurses(receiveBuffer.messageContent, characterDelay, window, false); } } + // Exit the thread if shouldExit is true: pthread_exit(NULL); } @@ -148,61 +181,61 @@ int main(int argc, char ** argv) bool chatLogging = false, gameLogging = false; // Print welcome message: - slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK CLIENT ====--\nVersion Alpha 0.4\n", 5000); + slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK CLIENT ====--\nVersion Alpha 0.5\n", 5000); // Parse command-line options: while ((currentopt = getopt(argc, argv, "i:c:g:p:d:")) != -1) { switch (currentopt) { - case 'i': - { - strncpy(ipAddress, optarg, 32); - break; - } - case 'c': - { - strncpy(chatLogPath, optarg, PATH_MAX + 1); - chatLog = fopen(chatLogPath, "a+"); - if (chatLog == NULL) + case 'i': { - chatLogging = false; + memcpy(ipAddress, optarg, 32); + break; } - else + case 'c': { - chatLogging = true; + memcpy(chatLogPath, optarg, PATH_MAX + 1); + chatLog = fopen(chatLogPath, "a+"); + if (chatLog == NULL) + { + chatLogging = false; + } + else + { + chatLogging = true; + } + break; } - break; - } - case 'g': - { - strncpy(gameLogPath, optarg, PATH_MAX + 1); - gameLog = fopen(gameLogPath, "a+"); - if (gameLog == NULL) + case 'g': { - gameLogging = false; + memcpy(gameLogPath, optarg, PATH_MAX + 1); + gameLog = fopen(gameLogPath, "a+"); + if (gameLog == NULL) + { + gameLogging = false; + } + else + { + gameLogging = true; + } + break; } - else + case 'p': { - gameLogging = true; + port = atoi(optarg); + break; + } + case 'd': + { + characterDelay = atoi(optarg); + break; + } + case '?': + { + return 1; + break; } - break; - } - case 'p': - { - port = atoi(optarg); - break; - } - case 'd': - { - characterDelay = atoi(optarg); - break; - } - case '?': - { - return 1; - break; - } } } @@ -217,9 +250,8 @@ int main(int argc, char ** argv) { slowPrint("Socket successfully created.\n", characterDelay); } - bzero(&serverAddress, sizeof(serverAddress)); - // Set our IP Address and port. Default to localhost for testing: + // Set our IP address and port. Default to localhost for testing: serverAddress.sin_family = AF_INET; serverAddress.sin_addr.s_addr = inet_addr(ipAddress); serverAddress.sin_port = htons(port); @@ -230,17 +262,11 @@ int main(int argc, char ** argv) slowPrint("Connection with the Silverkin Industries Comm-Link Server Failed:\nPlease contact your service representative.\n", characterDelay); exit(0); } - else - { - slowPrint("Connected to the Silverkin Industries Comm-Link Server:\nHave a pleasant day.\n", characterDelay); - } - usleep(100000); // Setup a GnuTLS session and initialize it: gnutls_session_t tlsSession = NULL; if (gnutls_init(&tlsSession, GNUTLS_CLIENT) < 0) - { - // Failure Case + { exit(EXIT_FAILURE); } @@ -256,7 +282,7 @@ int main(int argc, char ** argv) // Use the default for the GnuTLS handshake timeout: gnutls_handshake_set_timeout(tlsSession, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); - // Repeatedly attempt to handshake unless we encounter a fatal error: + // Repeatedly attempt to handshake unless we encounter a fatal error: int returnValue = -1; do { @@ -278,6 +304,7 @@ int main(int argc, char ** argv) logArea->window = newwin(LINES - 5, COLS - 2, 1, 1); logArea->tlsSession = tlsSession; logArea->loggingFlag = chatLogging; + logArea->characterDelay = characterDelay; if (chatLog != NULL) { logArea->loggingStream = chatLog; diff --git a/src/gamelogic.c b/src/gamelogic.c index 112291f..fdb0d2b 100644 --- a/src/gamelogic.c +++ b/src/gamelogic.c @@ -5,9 +5,11 @@ #include #include #include +#include "queue.h" #include "constants.h" #include "gamelogic.h" #include "playerdata.h" +#include "linkedlist.h" #include "inputoutput.h" // ======================= @@ -15,92 +17,84 @@ // ======================= // Thread function which runs the main game loop, given the needed parameters: -void * gameLogicLoop(void * parameters) +void * gameLogicHandler(void * parameters) { gameLogicParameters * threadParameters = parameters; inputMessage * currentInput = NULL; - commandQueue * commandQueue = createCommandQueue(); - while(true) + queue * commandQueue = createQueue(); + while (true) { // Evaluate remaining commands: - if(commandQueue->currentLength != 0) + while (commandQueue->itemCount != 0) { evaluateNextCommand(threadParameters, commandQueue); } - // Check for new messages and pop them off the queue: - if(threadParameters->inputQueue->currentLength != 0) + + // Wait if there is nothing to do: + if (threadParameters->inputQueue->itemCount == 0) { - while(threadParameters->inputQueue->lock == true); + pthread_cond_wait(&threadParameters->inputQueue->condition, &threadParameters->inputQueue->mutex); + } + + // Check for new messages and pop them off the queue: + if (threadParameters->inputQueue->itemCount != 0) + { + while (threadParameters->inputQueue->lock == true); threadParameters->inputQueue->lock = true; - currentInput = peekInputMessage(threadParameters->inputQueue); + currentInput = peekQueue(threadParameters->inputQueue)->data.inputMessage; userInputSanatize(currentInput->content->messageContent, MAX); // A slash as the first character means the message is a user command: - if(currentInput->content->messageContent[0] == '/') + if (currentInput->content->messageContent[0] == '/') { queueMessagedCommand(commandQueue, currentInput); } - else if(currentInput->sender->currentArea == getAreaFromList(threadParameters->areaList, 0)) - { - currentInput = NULL; - threadParameters->inputQueue->lock = false; - dequeueInputMessage(threadParameters->inputQueue); - } - else + + else if (!(currentInput->sender->currentArea == getFromList(threadParameters->areaList, 0)->area) && + currentInput->content->messageContent[0] != '\n') { + // Copy the correct name into the sender name field: strncpy(currentInput->content->senderName, currentInput->sender->playerName, 32); - // Create an array of players in the same area to receive the message: - playerInfo ** recipients = malloc(sizeof(playerInfo*) * *threadParameters->playerCount); - for(int index = 0; index < *threadParameters->playerCount; index++) + currentInput->content->senderName[31] = '\0'; + + // Allocate an array of playerInfo to store the current players in the area for the output message: + playerInfo ** recipients = malloc(sizeof(playerInfo*) * PLAYERCOUNT); + + // Initialize them all to NULL: + for (int index = 0; index < PLAYERCOUNT; index++) { recipients[index] = NULL; } - int recipientCount = 0; - for(int playerIndex = 0; playerIndex < *threadParameters->playerCount; playerIndex++) + + // Get the players in the current area and add them to our array: + int recipientIndex = 0; + for (int playerIndex = 0; playerIndex < *threadParameters->playerCount; playerIndex++) { - if(threadParameters->connectedPlayers[playerIndex].currentArea == currentInput->sender->currentArea) + if (threadParameters->connectedPlayers[playerIndex].currentArea == currentInput->sender->currentArea) { - recipients[recipientCount] = &threadParameters->connectedPlayers[playerIndex]; - recipientCount++; + recipients[recipientIndex] = &threadParameters->connectedPlayers[playerIndex]; + recipientIndex++; } } - if(currentInput->content->messageContent[0] != '\n') - { - queueTargetedOutputMessage(threadParameters->outputQueue, currentInput->content, recipients, recipientCount); - } + + // Create the outputMessage for the queue: + outputMessage * newOutputMessage = createTargetedOutputMessage(currentInput->content, recipients, recipientIndex); + + // Push the message onto the queue: + pushQueue(threadParameters->outputQueue, newOutputMessage, OUTPUT_MESSAGE); + + // Free the array; free(recipients); } currentInput = NULL; threadParameters->inputQueue->lock = false; - dequeueInputMessage(threadParameters->inputQueue); + popQueue(threadParameters->inputQueue); } } pthread_exit(NULL); } -// Create a commandQueue: -commandQueue * createCommandQueue(void) -{ - commandQueue * newCommandQueue = calloc(1, sizeof(commandQueue)); - newCommandQueue->back = NULL; - newCommandQueue->front = NULL; - newCommandQueue->lock = false; - newCommandQueue->paused = false; - newCommandQueue->currentLength = 0; - return newCommandQueue; -} - -// Return the front commandEvent from a commandQueue: -commandEvent * peekCommand(commandQueue * queue) -{ - // Do nothing until the command queue is unlocked. - while(queue->lock); - - // Return the front item. - return queue->front; -} - -// Enqueue a messaged command to a commandQueue: -int queueMessagedCommand(commandQueue * queue, inputMessage * messageToQueue) +// Evaluate the next commandEvent in a queue: +void queueMessagedCommand(queue * queue, inputMessage * messageToQueue) { // Prepare the new commandEvent: commandEvent * newCommand = calloc(1, sizeof(commandEvent)); @@ -112,60 +106,29 @@ int queueMessagedCommand(commandQueue * queue, inputMessage * messageToQueue) strtok(messageToQueue->content->messageContent, " "); // Copy the command and arguments to the new commandEvent: - strncpy(newCommand->command, &messageToQueue->content->messageContent[1], 16); - strncpy(newCommand->arguments, &messageToQueue->content->messageContent[strlen(newCommand->command) + 2], + memcpy(newCommand->command, &messageToQueue->content->messageContent[1], 16); + memcpy(newCommand->arguments, &messageToQueue->content->messageContent[strlen(newCommand->command) + 2], MAX - (strlen(newCommand->command) + 2)); // Ensure the arguments are safe to parse, without adding newlines: userNameSanatize(newCommand->command, 16); + newCommand->command[15] = '\0'; + userNameSanatize(newCommand->arguments, MAX); - + newCommand->arguments[MAX - 1] = '\0'; + // Lowercase the command for easier comparison: for (char * character = newCommand->command; *character; ++character) { *character = tolower(*character); } - // Wait for the queue to unlock: - while (queue->lock); - - // Check that we're not overflowing the queue: - if ((queue->currentLength + 1) > MAXQUEUELENGTH) - { - // Unlock the queue: - queue->lock = false; - return -1; - } - else - { - // If the queue is empty, set the first commandEvent as both the front and back of the queue: - if(queue->front == NULL) - { - queue->front = newCommand; - queue->back = newCommand; - queue->currentLength++; - - // Unlock the queue: - queue->lock = false; - - return 0; - } - else - { - queue->back->next = newCommand; - queue->back = newCommand; - queue->currentLength++; - - // Unlock the queue: - queue->lock = false; - - return 0; - } - } + pushQueue(queue, newCommand, COMMAND); } -// Enqueue a command to a commandQueue: -int queueCommand(commandQueue * queue, char * command, char * arguments, int commandLength, int argumentsLength, playerInfo * callingPlayer) +// Enqueue a command to a queue: +void queueCommand(queue * queue, char * command, char * arguments, int commandLength, int argumentsLength, + playerInfo * callingPlayer) { // Prepare the new commandEvent: commandEvent * newCommand = calloc(1, sizeof(commandEvent)); @@ -175,171 +138,72 @@ int queueCommand(commandQueue * queue, char * command, char * arguments, int com // Copy the command and arguments: strncpy(newCommand->command, command, commandLength); - strncpy(newCommand->arguments, arguments, argumentsLength); - + if (argumentsLength > 0) + { + strncpy(newCommand->arguments, arguments, argumentsLength); + } // Ensure the arguments are safe to parse, without adding newlines: userNameSanatize(newCommand->command, 16); - // Wait for the queue to unlock: - while (queue->lock); - - // Check that we're not overflowing the queue: - if ((queue->currentLength + 1) > MAXQUEUELENGTH) - { - // Unlock the queue: - queue->lock = false; - return -1; - } - else - { - // If the queue is empty, set the first commandEvent as both the front and back of the queue: - if(queue->front == NULL) - { - queue->front = newCommand; - queue->back = newCommand; - queue->currentLength++; - - // Unlock the queue: - queue->lock = false; - - return 0; - } - else - { - queue->back->next = newCommand; - queue->back = newCommand; - queue->currentLength++; - - // Unlock the queue: - queue->lock = false; - - return 0; - } - } + pushQueue(queue, newCommand, COMMAND); } -// Dequeue the front commandEvent from a commandQueue: -int dequeueCommand(commandQueue * queue) +// Evaluate the next commandEvent in a queue: +int evaluateNextCommand(gameLogicParameters * parameters, queue * queue) { - // Wait for the queue to unlock: + commandEvent * currentCommand = peekQueue(queue)->data.command; while (queue->lock); - - // Lock the queue: - queue->lock = true; - - // Check the list isn't empty: - if(queue->front == NULL) - { - queue->lock = false; - return -1; - } - - // If there is only one item in the queue: - else if(queue->front == queue->back) - { - free(queue->front->command); - free(queue->front->arguments); - free(queue->front); - queue->front = NULL; - queue->back = NULL; - queue->currentLength--; - queue->lock = false; - return 0; - } - - // Remove the front item: - else - { - commandEvent * commandToDelete = queue->front; - queue->front = queue->front->next; - free(commandToDelete->command); - free(commandToDelete->arguments); - free(commandToDelete); - queue->currentLength--; - queue->lock = false; - return 0; - } -} - -// Evaluate the next commandEvent: -int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) -{ - commandEvent * currentCommand = peekCommand(queue); - while(queue->lock); queue->lock = true; - if(currentCommand == NULL) + if (currentCommand == NULL) { return -1; } // Try command: Attempt to use a stat or skill on an object: - if(strncmp(currentCommand->command, "try", 3) == 0) + if (strncmp(currentCommand->command, "try", 3) == 0) { userMessage * tryMessage = malloc(sizeof(userMessage)); tryMessage->senderName[0] = '\0'; - switch (getCoreStatFromString(currentCommand->arguments, 9)) - { - case STRENGTH: - { - switch (statCheck(currentCommand->caller, 20, STRENGTH)) - { - case CRITICAL_FAILURE: - { - strcpy(tryMessage->messageContent, "You weak, puny shit. Bet you don't even lift, bro.\n"); - break; - } - case FAILURE: - { - strcpy(tryMessage->messageContent, "Come on, bro, you should be able to get this set done.\n"); - break; - } - case SUCCESS: - { - strcpy(tryMessage->messageContent, "Nice set, bro. Keep it up.\n"); - break; - } - case CRITICAL_SUCCESS: - { - strcpy(tryMessage->messageContent, "HOLY SHIT, BRO! THAT'S SOME MAD REPS RIGHT THERE!\n"); - break; - } - default: - { - strcpy(tryMessage->messageContent, "I don't even, bro.\n"); - } - } - break; - } - default: - { - sprintf(tryMessage->messageContent,"%d", - skillCheck(currentCommand->caller, 10, currentCommand->arguments, strlen(currentCommand->arguments), - parameters->globalSkillList)); - break; - } - } - queueTargetedOutputMessage(parameters->outputQueue, tryMessage, ¤tCommand->caller, 1); + + // Temporary message until we can implement objects, events, and challenges. + strcpy(tryMessage->messageContent, "The try command is currently not implemented. Implement it if you want to use it.\n"); + + // Allocate an outputMessage for the queue: + outputMessage * tryOutputMessage = createTargetedOutputMessage(tryMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, tryOutputMessage, OUTPUT_MESSAGE); + + // Free the userMessage: free(tryMessage); } // Exit command: Sends an "empty" exit message to disconnect a client: - if(strncmp(currentCommand->command, "exit", 4) == 0) + if (strncmp(currentCommand->command, "exit", 4) == 0) { + // Allocate a userMessage containing null characters as the first char in both fields: userMessage * exitMessage = malloc(sizeof(userMessage)); exitMessage->senderName[0] = '\0'; exitMessage->messageContent[0] = '\0'; - queueTargetedOutputMessage(parameters->outputQueue, exitMessage, ¤tCommand->caller, 1); + + // Allocate an outputMessage for the queue: + outputMessage * exitOutputMessage = createTargetedOutputMessage(exitMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, exitOutputMessage, OUTPUT_MESSAGE); + + // Free the userMessage free(exitMessage); } // Move command: Moves the caller to a different area given a path name or number: - if(strncmp(currentCommand->command, "move", 4) == 0) + if (strncmp(currentCommand->command, "move", 4) == 0) { char requestedPath[32]; - if(strlen(currentCommand->arguments) > 0 && currentCommand->caller->currentArea != getAreaFromList(parameters->areaList, 0)) + if (strlen(currentCommand->arguments) > 0 && currentCommand->caller->currentArea != getFromList(parameters->areaList, 0)->area) { - strncpy(requestedPath, currentCommand->arguments, 32); + memcpy(requestedPath, currentCommand->arguments, 32); userNameSanatize(requestedPath, 32); requestedPath[31] = '\0'; - if(movePlayerToArea(currentCommand->caller, requestedPath) == 0) + if (movePlayerToArea(currentCommand->caller, requestedPath) == 0) { // Call the look command after moving. It's fine to unlock, because the loop won't // continue until the command is queued: @@ -351,7 +215,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) } // Look command: Returns the description of the current area and paths: - if(strncmp(currentCommand->command, "look", 4) == 0) + if (strncmp(currentCommand->command, "look", 4) == 0) { char formattedString[64]; userMessage * lookMessage = calloc(1, sizeof(userMessage)); @@ -359,45 +223,69 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) strncat(lookMessage->messageContent, currentCommand->caller->currentArea->areaName, 33); strncat(lookMessage->messageContent, "\n", 2); strncat(lookMessage->messageContent, currentCommand->caller->currentArea->areaDescription, MAX - 35); - queueTargetedOutputMessage(parameters->outputQueue, lookMessage, ¤tCommand->caller, 1); + + // Allocate an outputMessage for the queue: + outputMessage * lookOutputMessage = createTargetedOutputMessage(lookMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE); + + //queueTargetedOutputMessage(parameters->outputQueue, lookMessage, ¤tCommand->caller, 1); bzero(lookMessage, sizeof(userMessage)); - if(currentCommand->caller->currentArea->areaExits[0] != NULL) + + // Loop through the paths and send the appropriate amount of messages: + int charCount = 13; + strncat(lookMessage->messageContent, "You can go:", 13); + + if (currentCommand->caller->currentArea->pathList->itemCount > 0) { - strncat(lookMessage->messageContent, "You can go:", 13); - for(int index = 0; index < 16; index++) + for(size_t index = 0; index < currentCommand->caller->currentArea->pathList->itemCount; index++) { - if(currentCommand->caller->currentArea->areaExits[index] != NULL) + if ((charCount + 64) >= MAX) { - snprintf(formattedString, 64, "\n\t%d. %s", index + 1, currentCommand->caller->currentArea->areaExits[index]->pathName); - strncat(lookMessage->messageContent, formattedString, 64); + lookOutputMessage = createTargetedOutputMessage(lookMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE); + + bzero(lookMessage, sizeof(userMessage)); + charCount = 0; } + snprintf(formattedString, 64, "\n\t%ld. %s", index + 1, + getFromList(currentCommand->caller->currentArea->pathList, index)->path->pathName); + strncat(lookMessage->messageContent, formattedString, 64); + charCount += 64; } - queueTargetedOutputMessage(parameters->outputQueue, lookMessage, ¤tCommand->caller, 1); + // Allocate another outputMessage for the queue: + lookOutputMessage = createTargetedOutputMessage(lookMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, lookOutputMessage, OUTPUT_MESSAGE); } free(lookMessage); } // Join command: Allows the player to join the game given a name: // TODO: Implement login/character creation. Will be a while: - if(strncmp(currentCommand->command, "join", 4) == 0) + if (strncmp(currentCommand->command, "join", 4) == 0) { - if(currentCommand->caller->currentArea == getAreaFromList(parameters->areaList, 0)) + if (currentCommand->caller->currentArea == getFromList(parameters->areaList, 0)->area) { bool validName = true; for(int index = 0; index < *parameters->playerCount; index++) { - if(currentCommand->arguments[0] == '\0') + if (currentCommand->arguments[0] == '\0') { validName = false; } - if(strncmp(currentCommand->arguments, parameters->connectedPlayers[index].playerName, 16) == 0) + if (strncmp(currentCommand->arguments, parameters->connectedPlayers[index].playerName, 16) == 0) { validName = false; } } - if(validName) + if (validName) { strncpy(currentCommand->caller->playerName, currentCommand->arguments, 16); - currentCommand->caller->currentArea = getAreaFromList(parameters->areaList, 1); + currentCommand->caller->currentArea = getFromList(parameters->areaList, 1)->area; // Call the look command after joining. It's fine to unlock, because the loop won't // continue until the command is queued: queue->lock = false; @@ -407,20 +295,12 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) } } // Talk command: Allows the player to begin a chat session with another player: - if(strncmp(currentCommand->command, "talk", 4) == 0) + if (strncmp(currentCommand->command, "talk", 4) == 0) { // TODO: Implement. } - if(strncmp(currentCommand->command, "skillissue", 10) == 0) - { - userMessage * statMessage = calloc(1, sizeof(userMessage)); - statMessage->senderName[0] = '\0'; - strcpy(statMessage->messageContent, "Have you tried getting good?"); - queueTargetedOutputMessage(parameters->outputQueue, statMessage, ¤tCommand->caller, 1); - free(statMessage); - } // Stat command: Displays the current character's sheet. - if(strncmp(currentCommand->command, "stat", 4) == 0) + if (strncmp(currentCommand->command, "stat", 4) == 0) { char * formattedString = calloc(121, sizeof(char)); userMessage * statMessage = calloc(1, sizeof(userMessage)); @@ -440,7 +320,7 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) strncat(statMessage->messageContent, formattedString, 120); // Levelling stats: Current XP, and spec points. - if(currentCommand->caller->stats->specPoints > 0 || currentCommand->caller->stats->skillPoints > 0) + if (currentCommand->caller->stats->specPoints > 0 || currentCommand->caller->stats->skillPoints > 0) { snprintf(formattedString, 120, "Current Experience: %ld | Spec Points Available: %d | Skill Points Available: %d", currentCommand->caller->stats->experience, currentCommand->caller->stats->specPoints, currentCommand->caller->stats->skillPoints); @@ -450,29 +330,39 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) snprintf(formattedString, 120, "Current Experience: %ld", currentCommand->caller->stats->experience); } strncat(statMessage->messageContent, formattedString, 120); + + // Allocate an outputMessage for the queue: + outputMessage * statOutputMessage = createTargetedOutputMessage(statMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE); - queueTargetedOutputMessage(parameters->outputQueue, statMessage, ¤tCommand->caller, 1); bzero(statMessage->messageContent, sizeof(char) * MAX); - if(currentCommand->caller->skills->head != NULL) + if (currentCommand->caller->skills->head != NULL) { - skillNode * currentSkill = currentCommand->caller->skills->head; + size_t skillIndex = 0; int charCount = 0; bool addNewline = false; - while(currentSkill != NULL) + playerSkill * skill; + while (skillIndex < currentCommand->caller->skills->itemCount) { - snprintf(formattedString, 120, "| %2d | %31s ", - currentSkill->skill->skillPoints, currentSkill->skill->skillName); + skill = getFromList(currentCommand->caller->skills, skillIndex)->skill; + skillIndex++; + snprintf(formattedString, 120, "| %2d | %31s ", skill->skillPoints, skill->skillName); charCount += 43; strncat(statMessage->messageContent, formattedString, 120); - if((charCount + 43) >= MAX) + if ((charCount + 43) >= MAX) { - strncat(statMessage->messageContent, "\n", 2); - queueTargetedOutputMessage(parameters->outputQueue, statMessage, ¤tCommand->caller, 1); + // Allocate an outputMessage for the queue: + statOutputMessage = createTargetedOutputMessage(statMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE); bzero(statMessage, sizeof(userMessage)); charCount = 0; break; } - else if(addNewline) + else if (addNewline) { strncat(statMessage->messageContent, "|\n", 3); charCount++; @@ -481,29 +371,31 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) else { addNewline = true; - } - currentSkill = currentSkill->next; - + } } - queueTargetedOutputMessage(parameters->outputQueue, statMessage, ¤tCommand->caller, 1); + // Allocate an outputMessage for the queue: + statOutputMessage = createTargetedOutputMessage(statMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, statOutputMessage, OUTPUT_MESSAGE); } free(statMessage); free(formattedString); } // Spec command: Assign spec points to stats: - if(strncmp(currentCommand->command, "spec", 4) == 0) + if (strncmp(currentCommand->command, "spec", 4) == 0) { userMessage * specMessage = calloc(1, sizeof(userMessage)); specMessage->senderName[0] = '\0'; char * formattedString = calloc(121, sizeof(char)); - if(currentCommand->caller->stats->specPoints > 0) + if (currentCommand->caller->stats->specPoints > 0) { int selectedAmount = 0; strtok(currentCommand->arguments, " "); selectedAmount = atoi(¤tCommand->arguments[strlen(currentCommand->arguments) + 1]); coreStat selectedStat = getCoreStatFromString(currentCommand->arguments, 16); - if(selectedAmount > 0 && (currentCommand->caller->stats->specPoints - selectedAmount) >= 0) + if (selectedAmount > 0 && (currentCommand->caller->stats->specPoints - selectedAmount) >= 0) { switch (selectedStat) { @@ -558,8 +450,11 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) strncat(specMessage->messageContent, "You have no spec points available.", 35); } - // Send the message: - queueTargetedOutputMessage(parameters->outputQueue, specMessage, ¤tCommand->caller, 1); + // Allocate an outputMessage for the queue: + outputMessage * specOutputMessage = createTargetedOutputMessage(specMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, specOutputMessage, OUTPUT_MESSAGE); // Show the new stat sheet: queue->lock = false; @@ -570,11 +465,11 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) free(specMessage); free(formattedString); } - if(strncmp(currentCommand->command, "skill", 5) == 0) + if (strncmp(currentCommand->command, "skill", 5) == 0) { userMessage * skillMessage = calloc(1, sizeof(userMessage)); skillMessage->senderName[0] = '\0'; - if((currentCommand->caller->stats->skillPoints - 1) >= 0) + if ((currentCommand->caller->stats->skillPoints - 1) >= 0) { int returnValue = takeSkill(parameters->globalSkillList, currentCommand->arguments, strlen(currentCommand->arguments), currentCommand->caller); @@ -599,29 +494,42 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) { strcpy(skillMessage->messageContent, "You don't have enough skill points to take this skill.\n"); } - queueTargetedOutputMessage(parameters->outputQueue, skillMessage, ¤tCommand->caller, 1); + + // Allocate an outputMessage for the queue: + outputMessage * skillOutputMessage = createTargetedOutputMessage(skillMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, skillOutputMessage, OUTPUT_MESSAGE); + free(skillMessage); } - if(strncmp(currentCommand->command, "listskills", 10) == 0) + if (strncmp(currentCommand->command, "listskills", 10) == 0) { - skillNode * currentSkill = parameters->globalSkillList->head; userMessage * listMessage = calloc(1, sizeof(userMessage)); char * formattedString = calloc(121, sizeof(char)); int charCount = 0; + size_t skillIndex = 0; bool addNewline = false; - while(currentSkill != NULL) + playerSkill * currentSkill; + while (skillIndex < parameters->globalSkillList->itemCount) { - snprintf(formattedString, 120, "| %-31s ", currentSkill->skill->skillName); + currentSkill = getFromList(parameters->globalSkillList, skillIndex)->skill; + snprintf(formattedString, 120, "| %-31s ", currentSkill->skillName); charCount += 43; strncat(listMessage->messageContent, formattedString, 120); - if((charCount + 46) >= MAX) + if ((charCount + 46) >= MAX) { - queueTargetedOutputMessage(parameters->outputQueue, listMessage, ¤tCommand->caller, 1); + // Allocate an outputMessage for the queue: + outputMessage * listOutputMessage = createTargetedOutputMessage(listMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, listOutputMessage, OUTPUT_MESSAGE); + bzero(listMessage, sizeof(userMessage)); charCount = 0; addNewline = false; } - else if(addNewline) + else if (addNewline) { strncat(listMessage->messageContent, "|\n", 3); charCount++; @@ -631,24 +539,28 @@ int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue) { addNewline = true; } - currentSkill = currentSkill->next; + skillIndex++; } - queueTargetedOutputMessage(parameters->outputQueue, listMessage, ¤tCommand->caller, 1); + // Allocate an outputMessage for the queue: + outputMessage * listOutputMessage = createTargetedOutputMessage(listMessage, ¤tCommand->caller, 1); + + // Queue the outputMessage: + pushQueue(parameters->outputQueue, listOutputMessage, OUTPUT_MESSAGE); free(listMessage); free(formattedString); } // Remove the current command and unlock the queue: currentCommand = NULL; queue->lock = false; - dequeueCommand(queue); + popQueue(queue); return 0; } -// Run a stat check: +// Run a stat check for the given player, returning an outcome: outcome statCheck(playerInfo * player, int chance, coreStat statToCheck) { // Calculate the chance: - if(chance > 100 || chance < 0) + if (chance > 100 || chance < 0) { return ERROR; } @@ -689,9 +601,9 @@ outcome statCheck(playerInfo * player, int chance, coreStat statToCheck) } } int attempt = (random() % 100) + modifier; - if(attempt >= chance) + if (attempt >= chance) { - if(attempt >= 98) + if (attempt >= 98) { return CRITICAL_SUCCESS; } @@ -702,7 +614,7 @@ outcome statCheck(playerInfo * player, int chance, coreStat statToCheck) } else { - if(attempt <= 2) + if (attempt <= 2) { return CRITICAL_FAILURE; } @@ -713,11 +625,11 @@ outcome statCheck(playerInfo * player, int chance, coreStat statToCheck) } } -// Run a skill check: -outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t skillNameLength, skillList * globalSkillList) +// Run a skill check for the given player, returning an outcome: +outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t skillNameLength, list * globalSkillList) { // Calculate the chance: - if(chance > 100 || chance < 0) + if (chance > 100 || chance < 0) { return ERROR; } @@ -725,53 +637,46 @@ outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t ski // Check if the player has the given skill: bool playerHasSkill = false; - skillNode * currentPlayerNode = player->skills->head; - while(currentPlayerNode != NULL) + size_t playerIndex = 0; + while (playerIndex < player->skills->itemCount) { - if(strncmp(skillName, currentPlayerNode->skill->skillName, skillNameLength) == 0) + if (strncmp(skillName, getFromList(player->skills, playerIndex)->skill->skillName, skillNameLength) != 0) { playerHasSkill = true; break; } - currentPlayerNode = currentPlayerNode->next; + playerIndex++; } // If the player doesn't have the skill, check if it's in the game and is trained: bool trainedSkill = false; - if(!playerHasSkill) + size_t globalIndex = 0; + while (globalIndex < globalSkillList->itemCount) { - skillNode * currentNode = globalSkillList->head; - while(strncmp(skillName, currentNode->skill->skillName, 32) != 0) + if (strncmp(skillName, getFromList(globalSkillList, globalIndex)->skill->skillName, skillNameLength) != 0) { - if(currentNode->next == NULL) - { - fprintf(stderr, "Skill doesn't exist in skill list.\n"); - return ERROR; - } - currentNode = currentNode->next; - } - if(currentNode->skill->trainedSkill == true) - { - trainedSkill = true; + trainedSkill = getFromList(globalSkillList, globalIndex)->skill->trainedSkill; + break; } + globalIndex++; } - + // Calculate the modifier: int modifier = 0; - if(trainedSkill) + if (trainedSkill) { modifier = -100; } - else + else if (playerHasSkill) { - modifier = currentPlayerNode->skill->skillPoints * 4; + modifier = getFromList(player->skills, playerIndex)->skill->skillModifier * 4; } // Attempt the check: int attempt = (random() % 100) + modifier; - if(attempt >= chance) + if (attempt >= chance) { - if(attempt >= 98) + if (attempt >= 98) { return CRITICAL_SUCCESS; } @@ -782,7 +687,7 @@ outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t ski } else { - if(attempt <= 2) + if (attempt <= 2) { return CRITICAL_FAILURE; } @@ -793,17 +698,17 @@ outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t ski } } -// Move a player to a different area given a path in the area: +// Move a player along a path in their current area: int movePlayerToArea(playerInfo * player, char * requestedPath) { // Check if a number was given first: - int selected = atoi(requestedPath); - if(selected != 0) + size_t selected = atoi(requestedPath); + if (selected != 0 && !(selected > player->currentArea->pathList->itemCount)) { - if(player->currentArea->areaExits[selected - 1] != NULL && - player->currentArea->areaExits[selected - 1]->areaToJoin != NULL) + if (getFromList(player->currentArea->pathList, selected - 1)->path != NULL && + getFromList(player->currentArea->pathList, selected - 1)->path->areaToJoin != NULL) { - player->currentArea = player->currentArea->areaExits[selected - 1]->areaToJoin; + player->currentArea = getFromList(player->currentArea->pathList, selected - 1)->path->areaToJoin; return 0; } else @@ -813,17 +718,15 @@ int movePlayerToArea(playerInfo * player, char * requestedPath) } // Otherwise search for the description: - for (int index = 0; index < 16; index++) + for (size_t index = 0; index < player->currentArea->pathList->itemCount; index++) { - if(player->currentArea->areaExits[index] != NULL) + if (strncmp(getFromList(player->currentArea->pathList, index)->path->pathName, + requestedPath, 32) == 0) { - if(strncmp(player->currentArea->areaExits[index]->pathName, requestedPath, 32) == 0) - { - printf("%s: %s\n", player->playerName, player->currentArea->areaExits[index]->pathName); - player->currentArea = player->currentArea->areaExits[index]->areaToJoin; - return 0; - } - } - } + printf("%s: %s\n", player->playerName, getFromList(player->currentArea->pathList, index)->path->pathName); + player->currentArea = getFromList(player->currentArea->pathList, index)->path->areaToJoin; + return 0; + } + } return 1; } diff --git a/src/gamelogic.h b/src/gamelogic.h index 7660063..1e717c5 100644 --- a/src/gamelogic.h +++ b/src/gamelogic.h @@ -1,34 +1,18 @@ -// gamelogic.h: Header file contatning function prototypes and datastructures -// for dealing with the game's logic. +// gamelogic.h: Function prototypes and data-structures for dealing with game logic. // Barry Kane, 2022. #ifndef GAMELOGIC_H #define GAMELOGIC_H +#include "queue.h" #include "areadata.h" #include "constants.h" #include "playerdata.h" #include "inputoutput.h" -// ======================= -// -=[ Main Game Loop ]=-: -// ======================= +// ======================== +// -=[ Data Structures ]=-: +// ======================== -// A datastructure containing the needed parameters for a main game loop: -typedef struct gameLogicParameters -{ - int * playerCount; - areaNode * areaList; - playerInfo * connectedPlayers; - inputMessageQueue * inputQueue; - outputMessageQueue * outputQueue; - skillList * globalSkillList; -} gameLogicParameters; - -// Thread function which runs the main game loop, given the needed parameters: -void * gameLogicLoop(void * parameters); - -// ====================== -// -=[ Command Queue ]=-: -// ====================== +// An event for storing the information needed to evaluate a command: typedef struct commandEvent commandEvent; typedef struct commandEvent { @@ -38,45 +22,44 @@ typedef struct commandEvent char * arguments; } commandEvent; -// A first-in first-out queue for message input from players: -typedef struct commandQueue +// A data-structure containing the needed parameters for the main game loop: +typedef struct gameLogicParameters { - bool lock; - bool paused; - int currentLength; - commandEvent * back; - commandEvent * front; -} commandQueue; + // Players: + int * playerCount; + playerInfo * connectedPlayers; -// Create a commandQueue: -commandQueue * createCommandQueue(void); + // Queues: + queue * inputQueue; + queue * outputQueue; -// Enqueue a command to a commandQueue: -int queueCommand(commandQueue * queue, char * command, char * arguments, - int commandLength, int argumentsLength , playerInfo * callingPlayer); + // Lists: + list * areaList; + list * globalSkillList; +} gameLogicParameters; -// Enqueue a messaged command to a commandQueue: -int queueMessagedCommand(commandQueue * queue, inputMessage * messageToQueue); +// ======================== +// -=[ Functions ]=-: +// ======================== -// Dequeue the front commandEvent from a commandQueue: -int dequeueCommand(commandQueue * queue); +// Thread function which runs the main game loop, given the needed parameters: +void * gameLogicHandler(void * parameters); -// Return the front commandEvent from a commandQueue: -commandEvent * peekCommand(commandQueue * queue); +// Enqueue a command that has been sent as a message from a user to a queue: +void queueMessagedCommand(queue * queue, inputMessage * messageToQueue); -// Evaluate the next commandEvent: -int evaluateNextCommand(gameLogicParameters * parameters, commandQueue * queue); +// Evaluate the next commandEvent in a queue: +int evaluateNextCommand(gameLogicParameters * parameters, queue * queue); -/* // Evaluate the given commandEvent: */ -/* int evaluateCommand(gameLogicParameters * parameters, commandEvent * command); */ +// Enqueue a command to a queue: +void queueCommand(queue * queue, char * command, char * arguments, int commandLength, int argumentsLength, + playerInfo * callingPlayer); // ============================ // -=[ Gameplay Primitives ]=-: // ============================ -// Player movement: -int movePlayerToArea(playerInfo * player, char * requestedPath); - +// The possible outcomes of a check or challenge: typedef enum outcome { CRITICAL_FAILURE, @@ -86,10 +69,13 @@ typedef enum outcome ERROR } outcome; -// Run a stat check: +// Move a player along a path in their current area: +int movePlayerToArea(playerInfo * player, char * requestedPath); + +// Run a stat check for the given player, returning an outcome: outcome statCheck(playerInfo * player, int chance, coreStat statToCheck); -// Run a skill check: -outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t skillNameLength, skillList * globalSkillList); +// Run a skill check for the given player, returning an outcome: +outcome skillCheck(playerInfo * player, int chance, char * skillName, size_t skillNameLength, list * globalSkillList); #endif diff --git a/src/inputoutput.c b/src/inputoutput.c index e6af436..b8e4136 100644 --- a/src/inputoutput.c +++ b/src/inputoutput.c @@ -5,7 +5,10 @@ #include #include #include +#include #include + +#include "queue.h" #include "constants.h" #include "playerdata.h" #include "inputoutput.h" @@ -14,11 +17,14 @@ int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend) { int returnValue = 0; + // Continuously attempt to send the name field until it succeeds or fatally errors: do { returnValue = gnutls_record_send(receivingSession, messageToSend->senderName, sizeof(((userMessage*)0)->senderName)); } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); + + // Continuously attempt to send the message field until it succeeds or fatally errors: do { returnValue = gnutls_record_send(receivingSession, messageToSend->messageContent, @@ -32,11 +38,14 @@ int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend) int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage) { int returnValue = 0; + // Continuously attempt to receive the name field until it succeeds or fatally errors: do { returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->senderName, sizeof(((userMessage*)0)->senderName)); } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); + + // Continuously attempt to receive the message field until it succeeds or fatally errors: do { returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->messageContent, @@ -46,282 +55,95 @@ int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToM return returnValue; } -outputMessageQueue * createOutputMessageQueue(void) +// Allocate and initialize an outputMessage targeted to a variable amount of players: +outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, playerInfo ** recipients, int recipientsCount) { - outputMessageQueue * newQueue = malloc(sizeof(outputMessageQueue)); - newQueue->front = NULL; - newQueue->back = NULL; - newQueue->currentLength = 0; - newQueue->lock = false; - return newQueue; -} - -int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue) -{ - // Copy the message into a new output message: + // Allocate a new output message: outputMessage * newOutputMessage = malloc(sizeof(outputMessage)); - - // Allocate the internal userMessage to store the message: - newOutputMessage->content = malloc(sizeof(userMessage)); - - // Copy the userMessage to the internal userMessage: - strncpy(newOutputMessage->content->senderName, messageToQueue.senderName, 32); - strncpy(newOutputMessage->content->messageContent, messageToQueue.messageContent, MAX); - - // We have no targets, NULL sends to all players in an area: - newOutputMessage->targets[0] = NULL; - - // Wait for the queue to unlock: - while (queue->lock); - - // Lock the queue: - queue->lock = true; - - // Check that we're not overflowing the queue: - if ((queue->currentLength + 1) > MAXQUEUELENGTH) - { - // Unlock the queue: - queue->lock = false; - return -1; - } - else - { - // If the queue is empty, set the first message as both the front and back of the queue: - if(queue->front == NULL) - { - queue->front = newOutputMessage; - queue->back = newOutputMessage; - queue->currentLength++; - - // Unlock the queue: - queue->lock = false; - return 0; - } - else - { - queue->back->next = newOutputMessage; - queue->back = newOutputMessage; - queue->currentLength++; - - // Unlock the queue: - queue->lock = false; - return 0; - } - } -} - -int queueTargetedOutputMessage(outputMessageQueue * queue, - userMessage * messageToQueue, playerInfo ** targets, int numberOfTargets) -{ - // Copy the message into a new output message: - outputMessage * newOutputMessage = malloc(sizeof(outputMessage)); - - // Allocate the internal userMessage to store the message: newOutputMessage->content = malloc(sizeof(userMessage)); - // Set the appropriate recipients: - for(int index = 0; index < numberOfTargets && index < PLAYERCOUNT; index++) - { - newOutputMessage->targets[index] = targets[index]; - } - for(int index = numberOfTargets; index < PLAYERCOUNT; index++) - { - newOutputMessage->targets[index] = NULL; - } + // Allocate an array of playerInfo for the output message recepients: + newOutputMessage->recipients = malloc(sizeof(playerInfo*) * recipientsCount); + + // Copy in the appropriate data: + memcpy(newOutputMessage->recipients, recipients, sizeof(playerInfo *) * recipientsCount); + memcpy(newOutputMessage->content, messageToQueue, sizeof(userMessage)); + newOutputMessage->recipientsCount = recipientsCount; + + // Return a pointer to the new outputMessage: + return newOutputMessage; +} + +// A function for the output thread, which sends queued messages: +void * outputThreadHandler(void * parameters) +{ + outputThreadParameters * variables = parameters; + queue * outputQueue = variables->outputQueue; + gnutls_session_t * tlssessions = variables->tlssessions; + playerInfo * connectedPlayers = variables->connectedPlayers; - // Copy the userMessage to the internal userMessage: - strncpy(newOutputMessage->content->senderName, messageToQueue->senderName, 32); - strncpy(newOutputMessage->content->messageContent, messageToQueue->messageContent, MAX); + while (true) + { + // If there's nothing to do, put the thread to sleep: + if (outputQueue->itemCount == 0) + { + pthread_cond_wait(&outputQueue->condition, &outputQueue->mutex); + } - // Wait for the queue to unlock: - while (queue->lock); - - // Lock the queue: - queue->lock = true; - - // Check that we're not overflowing the queue: - if ((queue->currentLength + 1) > MAXQUEUELENGTH) - { - // Unlock the queue: - queue->lock = false; - return -1; - } - else - { - // If the queue is empty, set the first message as both the front and back of the queue: - if(queue->front == NULL) + // Run through the output queue and send all unsent messages: + while (outputQueue->itemCount != 0) { - queue->front = newOutputMessage; - queue->back = newOutputMessage; - queue->currentLength++; + // Wait until the queue unlocks: + while (outputQueue->lock); + // Lock the queue: + outputQueue->lock = true; + + // Get a message off the queue: + outputMessage * message = peekQueue(outputQueue)->data.outputMessage; + // Unlock the queue: - queue->lock = false; - return 0; - } - else - { - queue->back->next = newOutputMessage; - queue->back = newOutputMessage; - queue->currentLength++; - - // Unlock the queue: - queue->lock = false; - return 0; - } - } -} - -int dequeueOutputMessage(outputMessageQueue * queue) -{ - // Wait for the queue to unlock: - while (queue->lock); - - // Lock the queue: - queue->lock = true; - - // Check the list isn't empty: - if(queue->front == NULL) - { - queue->lock = false; - return -1; - } - - // If there is only one item in the queue: - else if(queue->front == queue->back) - { - free(queue->front->content); - free(queue->front); - queue->front = NULL; - queue->back = NULL; - queue->currentLength--; - queue->lock = false; - return 0; - } - - // Remove the front item: - else - { - outputMessage * messageToDelete = queue->front; - queue->front = queue->front->next; - free(messageToDelete->content); - free(messageToDelete); - queue->currentLength--; - queue->lock = false; - return 0; - } -} - -inputMessageQueue * createInputMessageQueue(void) -{ - inputMessageQueue * newQueue = malloc(sizeof(inputMessageQueue)); - newQueue->front = NULL; - newQueue->back = NULL; - newQueue->currentLength = 0; - newQueue->lock = false; - return newQueue; -} - -int dequeueInputMessage(inputMessageQueue * queue) -{ - // Wait for the queue to unlock: - while (queue->lock); - - // Lock the queue: - queue->lock = true; - - // Check the list isn't empty: - if(queue->front == NULL) - { - queue->lock = false; - return -1; - } - - // If there is only one item in the queue: - else if(queue->front == queue->back) - { - free(queue->front->content); - free(queue->front); - queue->front = NULL; - queue->back = NULL; - queue->currentLength--; - queue->lock = false; - return 0; - } - - // Remove the front item: - else - { - inputMessage * messageToDelete = queue->front; - queue->front = queue->front->next; - free(messageToDelete->content); - free(messageToDelete); - queue->currentLength--; - queue->lock = false; - return 0; - } -} - -int queueInputMessage(inputMessageQueue * queue, userMessage messageToQueue, playerInfo * sendingPlayer) -{ - // Copy the message into a new input message: - inputMessage * inputMessage = malloc(sizeof(inputMessage)); - - // Allocate the internal userMessage to store the message: - inputMessage->content = malloc(sizeof(userMessage)); - - // Copy the userMessage to the internal userMessage: - strncpy(inputMessage->content->senderName, messageToQueue.senderName, 32); - strncpy(inputMessage->content->messageContent, messageToQueue.messageContent, MAX); - - // We have no targets, NULL sends to all players in an area: - inputMessage->sender = sendingPlayer; - - // Wait for the queue to unlock: - while (queue->lock); - - // Lock the queue: - queue->lock = true; - - // Check that we're not overflowing the queue: - if ((queue->currentLength + 1) > MAXQUEUELENGTH) - { - // Unlock the queue: - queue->lock = false; - return -1; - } - else - { - // If the queue is empty, set the first message as both the front and back of the queue: - if(queue->front == NULL) - { - queue->front = inputMessage; - queue->back = inputMessage; - queue->currentLength++; + outputQueue->lock = false; - // Unlock the queue: - queue->lock = false; + // If the first target is set to NULL, it's intended for all connected: + if (message->recipientsCount == 0) + { + for (int index = 0; index < PLAYERCOUNT; index++) + { + messageSend(tlssessions[index], message->content); + } + } - return 0; + // Otherwise, send it only to the targeted players: + else + { + int sentCount = 0; + for (int index = 0; index < PLAYERCOUNT; index++) + { + if (sentCount == message->recipientsCount) + { + break; + } + if (&connectedPlayers[index] == message->recipients[sentCount]) + { + sentCount++; + messageSend(tlssessions[index], message->content); + } + } + } + + // Remove the output message from the queue: + popQueue(outputQueue); } - else - { - queue->back->next = inputMessage; - queue->back = inputMessage; - queue->currentLength++; - - // Unlock the queue: - queue->lock = false; - - return 0; - } - } + } } +// Sanatize user input to ensure it's okay to process: void userInputSanatize(char * inputString, int length) { - for(int index = 0; index <= length; index++) + for (int index = 0; index <= length; index++) { + // If it's not a printable character, it has no business being here: if(!isprint(inputString[index])) { inputString[index] = '\n'; @@ -329,29 +151,23 @@ void userInputSanatize(char * inputString, int length) break; } } + + // Make sure it's null-terminated: inputString[length - 1] = '\0'; } +// Sanatize user names so they display correctly: void userNameSanatize(char * inputString, int length) { for(int index = 0; index <= length; index++) { + // If it's not a printable character, it has no business being here: if(!isprint(inputString[index])) { inputString[index] = '\0'; break; } } + // Make sure it's null-terminated: inputString[length - 1] = '\0'; } - -// Return the front inputMessage from an inputMessageQueue: -inputMessage * peekInputMessage(inputMessageQueue * queue) -{ - return queue->front; -} - -outputMessage * peekOutputMessage(outputMessageQueue * queue) -{ - return queue->front; -} diff --git a/src/inputoutput.h b/src/inputoutput.h index 177fac0..c7d6fc0 100644 --- a/src/inputoutput.h +++ b/src/inputoutput.h @@ -4,22 +4,54 @@ #ifndef INPUTOUTPUT_H #define INPUTOUTPUT_H #include -#include #include -#include "constants.h" -#include "playerdata.h" +#include #include -// A message datastructure containing a user/character name and the content: +#include "queue.h" +#include "constants.h" +#include "playerdata.h" + +// Let the compiler know there will be structs defined elsewhere: +typedef struct queue queue; + +// ======================== +// -=[ Data Structures ]=-: +// ======================== + +// Contains a character/player name and the content of a message: typedef struct userMessage { char senderName[32]; char messageContent[MAX]; } userMessage; -// ================== -// -=[Message I/O]=-: -// ================== +// Contains a message sent to the server and a pointer to the playerInfo of the connection which sent it: +typedef struct inputMessage +{ + playerInfo * sender; + userMessage * content; +} inputMessage; + +// Contains a message to be sent, the amount of recipients, and pointers to their playerInfo: +typedef struct outputMessage +{ + int recipientsCount; + userMessage * content; + playerInfo ** recipients; +} outputMessage; + +// Contains pointers to the necessary information to be shared outputThreadHandler function: +typedef struct outputThreadParameters +{ + queue * outputQueue; + gnutls_session_t * tlssessions; + playerInfo * connectedPlayers; +} outputThreadParameters; + +// ======================== +// -=[ Functions ]=-: +// ======================== // Sends a message to a given TLS session, wraps the calls to gnutls_write: int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend); @@ -27,80 +59,16 @@ int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend); // Receives a message from a given TLS session, wraps the calls to gnutls_read: int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage); -// =================== -// -=[Output Queue]=-: -// =================== -typedef struct outputMessage outputMessage; -typedef struct outputMessage -{ - outputMessage * next; - playerInfo * targets[PLAYERCOUNT]; - userMessage * content; -} outputMessage; +// Create a targetedOutput message to be delivered to the players pointed to in recipients: +outputMessage * createTargetedOutputMessage(userMessage * messageToQueue, playerInfo ** recipients, int recipientCount); -// A first-in first-out queue for message output to players: -typedef struct outputMessageQueue -{ - bool lock; - int currentLength; - outputMessage * back; - outputMessage * front; -} outputMessageQueue; +// A function for the output thread, which sends queued messages: +void * outputThreadHandler(void * parameters); -// Creates and initializes a outputMessageQueue: -outputMessageQueue * createOutputMessageQueue(void); - -// Enqueue a userMessage to an outputMessageQueue: -int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue); -int queueTargetedOutputMessage(outputMessageQueue * queue, userMessage * messageToQueue, - playerInfo ** targets, int numberOfTargets); - -// Dequeue the front outputMessage from an outputMessageQueue: -int dequeueOutputMessage(outputMessageQueue * queue); - -// Return the front outputMessage from an outputMessageQueue: -outputMessage * peekOutputMessage(outputMessageQueue * queue); - -// ================== -// -=[Input Queue]=-: -// ================== -typedef struct inputMessage inputMessage; -typedef struct inputMessage -{ - inputMessage * next; - playerInfo * sender; - userMessage * content; -} inputMessage; - -// A first-in first-out queue for message input from players: -typedef struct inputMessageQueue -{ - bool lock; - int currentLength; - inputMessage * back; - inputMessage * front; -} inputMessageQueue; - -// Create a inputMessageQueue: -inputMessageQueue * createInputMessageQueue(void); - -// Enqueue a userMessage to an inputMessageQueue: -int queueInputMessage(inputMessageQueue * queue, userMessage messageToQueue, playerInfo * sendingPlayer); - -// Dequeue the front inputMessage from an inputMessageQueue: -int dequeueInputMessage(inputMessageQueue * queue); - -// Return the front inputMessage from an inputMessageQueue: -inputMessage * peekInputMessage(inputMessageQueue * queue); - -// ======================= -// -=[Input Sanitation]=-: -// ======================= - -// Sanatize user input to ensure it's okay to send to the server: +// Sanatize user input to ensure it's okay to process: void userInputSanatize(char * inputString, int length); -// Sanatize user names so they display correctly; +// Sanatize user names so they display correctly: void userNameSanatize(char * inputString, int length); #endif diff --git a/src/linkedlist.c b/src/linkedlist.c new file mode 100644 index 0000000..59e7bab --- /dev/null +++ b/src/linkedlist.c @@ -0,0 +1,479 @@ +// linkedlist.h: Function definitions for the list type for SilverMUD. +// Barry Kane, 2022. +#include +#include +#include +#include +#include "playerdata.h" +#include "linkedlist.h" + +// Deallocate a given list node, including it's data: +static inline void deallocateListNode(listNode * node, listDataType type) +{ + // Delete the node: + switch (type) + { + case PLAYER: + { + deallocatePlayer(node->data.player); + break; + } + case AREA: + { + destroyList(&(node->data.area->pathList)); + free(node->data.area); + free(node); + break; + } + case PATH: + { + free(node->data.path); + free(node); + break; + } + case SKILL: + { + free(node->data.skill); + free(node); + break; + } + } +} + +// Allocates and instantiates a list of the specified type: +list * createList(listDataType type) +{ + // Allocate and clear the memory for the list: + list * newList = calloc(sizeof(list), 1); + + // Set the appropriate values in the new list: + newList->type = type; + newList->itemCount = 0; + newList->head = NULL; + newList->tail = NULL; + + // Return the new list: + return newList; +} + +// Deallocates a list and all of it's members: +int destroyList(list ** list) +{ + // Check if the list is empty: + if ((*list)->itemCount == 0) + { + free(*list); + list = NULL; + return 0; + } + else + { + while ((*list)->itemCount > 0) + { + removeFromList((*list), (*list)->type, (*list)->itemCount - 1); + } + free(*list); + *list = NULL; + return 0; + } +} + +// Returns the data at a given index in a list: +listData * getFromList(list * list, size_t listIndex) +{ + // Check that we were given a valid index: + if (listIndex > (list->itemCount - 1)) + { + perror("Invalid index specified.\n"); + return NULL; + } + // Return the head if index is 0: + else if (listIndex == 0) + { + return &(list->head->data); + } + // Loop through the entries in the list until we get to the right one: + else + { + listNode * currentNode = list->head; + while (listIndex-- > 0) + { + currentNode = currentNode->next; + } + return &(currentNode->data); + } +} + +// Returns the node at a given index in a list: +listNode * getNodeFromList(list * list, size_t listIndex) +{ + // Check that we were given a valid index: + if (listIndex > (list->itemCount - 1)) + { + perror("Invalid index specified.\n"); + return NULL; + } + // Return the head if index is 0: + else if (listIndex == 0) + { + return list->head; + } + // Loop through the entries in the list until we get to the right one: + else + { + if ((list->itemCount / 2) < listIndex) + { + listNode * currentNode = list->tail; + while (listIndex-- > 0) + { + currentNode = currentNode->previous; + } + return currentNode; + } + else + { + listNode * currentNode = list->head; + while (listIndex-- > 0) + { + currentNode = currentNode->next; + } + return currentNode; + } + } +} + +// Adds the given data to the end of a list: +listNode * addToList(list * list, void * data, listDataType type) +{ + // Check the type: + if (type != list->type) + { + fprintf(stderr, "Not the correct type for this list.\n"); + return NULL; + } + + // If this is the first item in the list: + if (list->itemCount == 0) + { + // Allocate the new node for the list: + list->head = calloc(1, sizeof(listNode)); + + // Set the appropriate pointers for the list: + list->head->next = NULL; + list->head->previous = NULL; + list->tail = list->head; + + // Add the data to the new node: + switch (type) + { + case PATH: + { + list->head->data.path = (playerPath *)data; + break; + } + case AREA: + { + list->head->data.area = (playerArea *)data; + break; + } + case PLAYER: + { + list->head->data.player = (playerInfo *)data; + break; + } + case SKILL: + { + list->head->data.skill = (playerSkill *)data; + break; + } + } + } + else + { + // Allocate the new node at the end of the list: + list->tail->next = calloc(1, sizeof(listNode)); + + // Add the data to the new node: + switch (type) + { + case PATH: + { + list->tail->next->data.path = (playerPath *)data; + break; + } + case AREA: + { + list->tail->next->data.area = (playerArea *)data; + break; + } + case PLAYER: + { + list->tail->next->data.player = (playerInfo *)data; + break; + } + case SKILL: + { + list->tail->next->data.skill = (playerSkill *)data; + break; + } + } + + // Set the appropriate pointers in the new node: + list->tail->next->previous = list->tail; + + // Set the list's tail to the new tail: + list->tail = list->tail->next; + } + // Increase the count of items in the list: + list->itemCount++; + + // Return the new item in the list: + return list->tail; +} + +// Insert the given data at a given index in the list: +listNode * insertIntoList(list * list, void * data, listDataType type, size_t listIndex) +{ + // Check that the types are correct: + if (list->type != type) + { + fprintf(stderr, "Types do not match.\n"); + return NULL; + } + + // Handle the special case of adding to the end of the list: + if (listIndex == (list->itemCount - 1)) + { + return addToList(list, data, type); + } + + // Handle the special case of adding to the beginning of the list: + if (listIndex == 0) + { + // Create the new node: + listNode * newNode = calloc(1, sizeof(listNode)); + + // Add the data to the node: + switch (type) + { + case PATH: + { + newNode->data.path = (playerPath *)data; + break; + } + case AREA: + { + newNode->data.area = (playerArea *)data; + break; + } + case PLAYER: + { + newNode->data.player = (playerInfo *)data; + break; + } + case SKILL: + { + newNode->data.skill = (playerSkill *)data; + break; + } + } + + // Place it in the list: + newNode->next = list->head; + newNode->previous = NULL; + list->head->previous = newNode; + list->head = newNode; + list->itemCount++; + + // Return the node: + return newNode; + } + + // Check that the index is valid: + if (listIndex > (list->itemCount - 1)) + { + fprintf(stderr, "Index is invalid for the list.\n"); + return NULL; + } + + // Get the current node at the index: + listNode * currentNode = list->head; + for(size_t index = 0; index < listIndex; index++) + { + currentNode = currentNode->next; + } + + // Get the node before the current node: + listNode * previousNode = currentNode->previous; + + // Create the new node: + previousNode->next = calloc(1, sizeof(listNode)); + currentNode->previous = previousNode->next; + previousNode->next->next = currentNode; + previousNode->next->previous = previousNode; + + // Add the data to the node: + switch (type) + { + case PATH: + { + previousNode->next->data.path = (playerPath *)data; + break; + } + case AREA: + { + previousNode->next->data.area = (playerArea *)data; + break; + } + case PLAYER: + { + previousNode->next->data.player = (playerInfo *)data; + break; + } + case SKILL: + { + previousNode->next->data.skill = (playerSkill *)data; + break; + } + } + list->itemCount++; + return previousNode->next; +} + +// Delete the given data from a list: +bool deleteFromList(list * list, void * data, listDataType type) +{ + size_t index = 0; + if (getIndexFromList(list, data, type, &index) == false) + { + return false; + } + else + { + removeFromList(list, type, index); + return true; + } +} + +// Delete the data from a given point in a list: +int removeFromList(list * list, listDataType type, size_t listIndex) +{ + // Check that we're removing the correct type: + if (list->type != type) + { + return -1; + } + + // Check the list index is valid: + if (listIndex > list->itemCount - 1) + { + return -2; + } + + // The first node in the list: + if (listIndex == 0) + { + // Get the current head and move the list's head on: + listNode * oldHead = list->head; + list->head = list->head->next; + + // If we haven't removed the last item, set the previous pointer + // in the new head to null. + if (list->head != NULL) + { + list->head->previous = NULL; + } + + // Delete the node: + deallocateListNode(oldHead, type); + + // Return the new amount of items in the list: + list->itemCount--; + return list->itemCount; + } + // The last node in the list: + else if (listIndex == (list->itemCount - 1)) + { + // Move the tail up by one: + list->tail = list->tail->previous; + + // Deallocate the former tail: + deallocateListNode(list->tail->next, type); + + // Set the appropriate pointer: + list->tail->next = NULL; + + // Return the new amount of items in the list: + list->itemCount--; + return list->itemCount; + } + // A node in the middle of the list: + else + { + // Get the needed node as a pointer: + listNode * nodeToDelete = getNodeFromList(list, listIndex); + + // Set the appropriate pointers for the surrounding nodes: + nodeToDelete->previous->next = nodeToDelete->next; + nodeToDelete->next->previous = nodeToDelete->previous; + + // Deallocate the node: + deallocateListNode(nodeToDelete, type); + + // Return the new amount of items in the list: + list->itemCount--; + return list->itemCount; + } +} + +// Get the index of a given piece of data in a list: +bool getIndexFromList(list * list, void * data, listDataType type, size_t * index) +{ + // Check the list types are the same: + if (list->type == type) + { + fprintf(stderr, "List types do not match.\n"); + return false; + } + + for(*index = 0; *index < list->itemCount; *index += 1) + { + switch (type) + { + case AREA: + { + if (getFromList(list, *index)->area == data) + { + return true; + } + break; + } + case PLAYER: + { + if (getFromList(list, *index)->player == data) + { + return true; + } + break; + } + case PATH: + { + if (getFromList(list, *index)->path == data) + { + return true; + } + break; + } + case SKILL: + { + if (getFromList(list, *index)->skill == data) + { + return true; + } + break; + } + } + } + return false; +} + diff --git a/src/linkedlist.h b/src/linkedlist.h new file mode 100644 index 0000000..b43e9ba --- /dev/null +++ b/src/linkedlist.h @@ -0,0 +1,85 @@ +// linkedlist.h: Defines the linked list datatype for SilverMUD. +// Barry Kane, 2022. +#ifndef LINKEDLIST_H +#define LINKEDLIST_H +#include "areadata.h" +#include "playerdata.h" + +// Let the compiler know there will be structs defined elsewhere: +typedef struct playerPath playerPath; +typedef struct playerArea playerArea; +typedef struct playerInfo playerInfo; +typedef struct playerSkill playerSkill; + +// ======================== +// -=[ Data Structures ]=-: +// ======================== + +// An enum of the possible data types that can be stored in a list: +typedef enum listDataType +{ + PATH, + AREA, + PLAYER, + SKILL +} listDataType; + +// A union containing a pointers to all data types that can be stored in a list: +typedef union listData +{ + playerPath * path; + playerArea * area; + playerInfo * player; + playerSkill * skill; +} listData; + +// A doubly linked node for the linked list type: +typedef struct listNode listNode; +typedef struct listNode +{ + listData data; + listNode * next; + listNode * previous; +} listNode; + +// A header structure for the list containing the length, head, tail, and type of the list. +typedef struct list +{ + listDataType type; + size_t itemCount; + listNode * head; + listNode * tail; +} list; + +// ======================== +// -=[ Functions ]=-: +// ======================== + +// Allocates and instantiates a list of the specified type: +list * createList(listDataType type); + +// Deallocates a list and all of it's members: +int destroyList(list ** list); + +// Returns the data at a given index in a list: +listData * getFromList(list * list, size_t listIndex); + +// Returns the node at a given index in a list: +listNode * getNodeFromList(list * list, size_t listIndex); + +// Adds the given data to the end of a list: +listNode * addToList(list * list, void * data, listDataType type); + +// Insert the given data at a given index in the list: +listNode * insertIntoList(list * list, void * data, listDataType type, size_t listIndex); + +// Delete the given data from a list: +bool deleteFromList(list * list, void * data, listDataType type); + +// Delete the data from a given point in a list: +int removeFromList(list * list, listDataType type, size_t listIndex); + +// Get the index of a given piece of data in a list: +bool getIndexFromList(list * list, void * data, listDataType type, size_t * index); + +#endif diff --git a/src/playerdata.c b/src/playerdata.c index 50f32e0..56bb4ed 100644 --- a/src/playerdata.c +++ b/src/playerdata.c @@ -9,12 +9,12 @@ #include "playerdata.h" // Create a new skill and add it to the global skill list: -int createSkill(skillList * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill) +listNode * createSkill(list * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill) { - if(skillNameLength >= 32) + if (skillNameLength >= 32) { fprintf(stderr, "Skill name is too long. Please shorten the name and try again.\n"); - return -1; + return NULL; } playerSkill * newSkill = malloc(sizeof(playerSkill)); @@ -26,109 +26,56 @@ int createSkill(skillList * globalSkillList, char * skillName, int skillNameLeng newSkill->trainedSkill = trainedSkill; // Add the skill to a node in the list: - return(addSkillNode(globalSkillList, newSkill)); -} - -// Add a skill node to a skill list: -int addSkillNode(skillList * skillList, playerSkill * skill) -{ - if(skillList->head == NULL) - { - skillList->head = malloc(sizeof(skillNode)); - skillList->head->skill = skill; - skillList->head->next = NULL; - skillList->skillCount = 1; - return 0; - } - else - { - skillNode * currentNode = skillList->head; - while(currentNode->next != NULL) - { - currentNode = currentNode->next; - } - currentNode->next = malloc(sizeof(skillNode)); - currentNode->next->skill = skill; - currentNode->next->next = NULL; - skillList->skillCount++; - return skillList->skillCount; - } -} - -// Remove a skill node from a skill list: -int removeSkillNode(skillList * skillList, playerSkill * skill) -{ - if(skillList->head->skill == skill) - { - free(skillList->head->skill); - if(skillList->head->next != NULL) - { - skillNode * deletedNode = skillList->head; - skillList->head = skillList->head->next; - free(deletedNode); - return 0; - } - else - { - skillNode * currentNode = skillList->head; - skillNode * previousNode = skillList->head; - while(currentNode->skill != skill) - { - if(currentNode->next == NULL) - { - return -1; - } - previousNode = currentNode; - currentNode = currentNode->next; - } - free(currentNode->skill); - previousNode->next = currentNode->next; - free(currentNode); - return 0; - } - } - return -1; + return(addToList(globalSkillList, newSkill, SKILL)); } // Take a skill and add it to the player's skill list: -int takeSkill(skillList * globalSkillList, char * skillName, int skillNameLength, playerInfo * targetPlayer) +int takeSkill(list * globalSkillList, char * skillName, int skillNameLength, playerInfo * targetPlayer) { - - skillNode * currentNode = globalSkillList->head; - while(strncmp(skillName, currentNode->skill->skillName, 32) != 0) + // Check if the skill exists in the game: + size_t globalIndex = 0; + bool skillExists = false; + while (globalIndex < globalSkillList->itemCount) { - if(currentNode->next == NULL) + if (strncmp(skillName, getFromList(globalSkillList, globalIndex)->skill->skillName, skillNameLength) == 0) { - fprintf(stderr, "Skill doesn't exist in skill list.\n"); - return -1; + skillExists = true; + break; } - currentNode = currentNode->next; + globalIndex++; } - bool playerHasSkill = false; - skillNode * currentPlayerNode = targetPlayer->skills->head; - while(currentPlayerNode != NULL) + if (!skillExists) { - if(strncmp(skillName, currentPlayerNode->skill->skillName, skillNameLength) == 0) + fprintf(stderr, "Skill doesn't exist in skill list.\n"); + return -1; + } + + // Check if the player has the skill: + size_t playerIndex = 0; + bool playerHasSkill = false; + while (playerIndex < targetPlayer->skills->itemCount) + { + if (strncmp(skillName, getFromList(targetPlayer->skills, playerIndex)->skill->skillName, skillNameLength) == 0) { playerHasSkill = true; break; } - currentPlayerNode = currentPlayerNode->next; + playerIndex++; } - if(playerHasSkill) + if (playerHasSkill) { - currentPlayerNode->skill->skillPoints++; + getFromList(targetPlayer->skills, playerIndex)->skill->skillPoints++; } + + // Copy the skill into the player's skill list: else { - addSkillNode(targetPlayer->skills, currentNode->skill); - currentPlayerNode = targetPlayer->skills->head; - while(currentPlayerNode->next != NULL) - { - currentPlayerNode = currentPlayerNode->next; - } - currentPlayerNode->skill->skillPoints = 1; + playerSkill * newSkill = calloc(1, sizeof(playerSkill)); + strncpy(newSkill->skillName, getFromList(globalSkillList, globalIndex)->skill->skillName, 32); + printf("%s ", newSkill->skillName); + newSkill->skillPoints = 1; + addToList(targetPlayer->skills, newSkill, SKILL); } return 0; } @@ -138,7 +85,7 @@ int takeSkill(skillList * globalSkillList, char * skillName, int skillNameLength coreStat getCoreStatFromString(char * inputString, int stringLength) { // Check we've got a long enough string to fit a stat: - if(stringLength < 4) + if (stringLength < 4) { return INVALID; } @@ -152,11 +99,11 @@ coreStat getCoreStatFromString(char * inputString, int stringLength) // If we have a string that's at most just the stat name plus a null character, or // a dirtier string, we can check in a better order and ignore impossibilites: - if(stringLength < 9) + if (stringLength < 9) { - if(stringLength <= 4) + if (stringLength <= 4) { - if(strncmp(string, "wits", 4) == 0) + if (strncmp(string, "wits", 4) == 0) { free(string); return WITS; @@ -168,19 +115,19 @@ coreStat getCoreStatFromString(char * inputString, int stringLength) } } // Hopefully one of the seven letter long ones: - else if(stringLength <= 7) + else if (stringLength <= 7) { - if(strncmp(string, "strength", 7) == 0) + if (strncmp(string, "strength", 7) == 0) { free(string); return STRENGTH; } - else if(strncmp(string, "dexerity", 7) == 0) + else if (strncmp(string, "dexerity", 7) == 0) { free(string); return DEXERITY; } - if(strncmp(string, "wits", 4) == 0) + if (strncmp(string, "wits", 4) == 0) { free(string); return WITS; @@ -194,27 +141,27 @@ coreStat getCoreStatFromString(char * inputString, int stringLength) // Hopefully one of the 8 letter long stats: else { - if(strncmp(string, "intellect", 8) == 0) + if (strncmp(string, "intellect", 8) == 0) { free(string); return INTELLECT; } - else if(strncmp(string, "endurance", 8) == 0) + else if (strncmp(string, "endurance", 8) == 0) { free(string); return ENDURANCE; } - else if(strncmp(string, "strength", 7) == 0) + else if (strncmp(string, "strength", 7) == 0) { free(string); return STRENGTH; } - else if(strncmp(string, "dexerity", 7) == 0) + else if (strncmp(string, "dexerity", 7) == 0) { free(string); return DEXERITY; } - if(strncmp(string, "wits", 4) == 0) + if (strncmp(string, "wits", 4) == 0) { free(string); return WITS; @@ -229,27 +176,27 @@ coreStat getCoreStatFromString(char * inputString, int stringLength) // Worst case, it's definitely a dirty string, compare them all: else { - if(strncmp(string, "wits", 4) == 0) + if (strncmp(string, "wits", 4) == 0) { free(string); return WITS; } - else if(strncmp(string, "intellect", 8) == 0) + else if (strncmp(string, "intellect", 8) == 0) { free(string); return INTELLECT; } - else if(strncmp(string, "strength", 7) == 0) + else if (strncmp(string, "strength", 7) == 0) { free(string); return STRENGTH; } - else if(strncmp(string, "endurance", 8) == 0) + else if (strncmp(string, "endurance", 8) == 0) { free(string); return ENDURANCE; } - else if(strncmp(string, "dexerity", 7) == 0) + else if (strncmp(string, "dexerity", 7) == 0) { free(string); return DEXERITY; @@ -262,28 +209,11 @@ coreStat getCoreStatFromString(char * inputString, int stringLength) } } +// Deallocate a player's information including the skill lists and stats: int deallocatePlayer(playerInfo * playerToDeallocate) { // Deallocate the skill list: - if(playerToDeallocate->skills->skillCount > 0) - { - // Allocate enough pointers: - skillNode * nodesToDeallocate[playerToDeallocate->skills->skillCount]; - skillNode * currentSkillNode = playerToDeallocate->skills->head; - - // Get a list of all the nodes together: - for(int index = 0; index < playerToDeallocate->skills->skillCount; index++) - { - nodesToDeallocate[index] = currentSkillNode; - currentSkillNode = currentSkillNode->next; - } - - // Deallocate all the nodes: - for(int index = 0; index < playerToDeallocate->skills->skillCount; index++) - { - free(nodesToDeallocate[index]); - } - } + destroyList(&(playerToDeallocate->skills)); // Deallocate the stat block: free(playerToDeallocate->stats); diff --git a/src/playerdata.h b/src/playerdata.h index 01564b2..f703682 100644 --- a/src/playerdata.h +++ b/src/playerdata.h @@ -6,7 +6,15 @@ #include #include "areadata.h" #include "constants.h" +#include "linkedlist.h" +// Let the compiler know there will be structs defined elsewhere: +typedef struct playerArea playerArea; +typedef struct playerPath playerPath; +typedef struct listNode listNode; +typedef struct list list; + +// The basic information that needs to be stored for a player or creature's stats: typedef struct statBlock { // Levelling: @@ -29,6 +37,7 @@ typedef struct statBlock int skillPoints; } statBlock; +// Information about a skill, including skill levels and modifiers for the player: typedef struct playerSkill { char skillName[32]; @@ -37,27 +46,16 @@ typedef struct playerSkill bool trainedSkill; } playerSkill; -typedef struct skillNode skillNode; -struct skillNode -{ - playerSkill * skill; - skillNode * next; -}; - -typedef struct skillList -{ - skillNode * head; - int skillCount; -} skillList; -\ +// Information about a single player's character: typedef struct playerInfo { char playerName[32]; playerArea * currentArea; statBlock * stats; - skillList * skills; + list * skills; } playerInfo; +// An enum of the main stats of the game: typedef enum coreStat { WITS, @@ -69,23 +67,16 @@ typedef enum coreStat } coreStat; // Create a new skill and add it to the global skill list: -int createSkill(skillList * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill); - -// Add a skill node to a skill list: -int addSkillNode(skillList * skillList, playerSkill * skill); - -// Remove a skill node from a skill list: -int removeSkillNode(skillList * skillList, playerSkill * skill); -int removeSkillByID(skillList * skillList, playerSkill * skill); +listNode * createSkill(list * globalSkillList, char * skillName, int skillNameLength, bool trainedSkill); // Take a skill and add it to the player's skill list: -int takeSkill(skillList * globalSkillList, char * skillName, int skillNameLength, playerInfo * targetPlayer); -int takeSkillbyID(skillList * globalSkillList, int skillID, playerInfo * targetPlayer); +int takeSkill(list * globalSkillList, char * skillName, int skillNameLength, playerInfo * targetPlayer); +int takeSkillbyID(list * globalSkillList, int skillID, playerInfo * targetPlayer); // Take a string containing a core stat name and return the core stat: coreStat getCoreStatFromString(char * string, int stringLength); -// Deallocate a player: +// Deallocate a player's information including the skill lists and stats: int deallocatePlayer(playerInfo * playerToDeallocate); #endif diff --git a/src/queue.c b/src/queue.c new file mode 100644 index 0000000..74ba8e8 --- /dev/null +++ b/src/queue.c @@ -0,0 +1,220 @@ +// queue.c: Implements the queue data type and associated functions for SilverMUD. +// Barry Kane, 2022 +#include +#include "queue.h" + +// Allocates and instantiates a queue: +queue * createQueue(void) +{ + // Allocate the memory for the queue: + queue * newQueue = malloc(sizeof(queue)); + + // Instantiate the variables in the data-structure: + newQueue->itemCount = 0; + newQueue->front = NULL; + newQueue->back = NULL; + + // Create the threading constructs: + newQueue->mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; + newQueue->condition = (pthread_cond_t)PTHREAD_COND_INITIALIZER; + + // Return the pointer to the new queue: + return newQueue; +} + +// Destroys a queue and all of it's members: +void destroyQueue(queue ** queue) +{ + // Pop everything off of the queue: + while ((*queue)->itemCount > 0) + { + popQueue(*queue); + } + + // Deallocate the queue: + free(*queue); + + // Point the queue pointer to NULL; + *queue = NULL; +} + +// Returns the data at the front of the given queue: +queueNode * peekQueue(queue * queue) +{ + return queue->front; +} + +// Removes the current data from the front of the queue: +void popQueue(queue * queue) +{ + // Check if the queue is locked, and wait: + while (queue->lock); + + // Lock the queue: + queue->lock = true; + + // Check there is actually anything to remove: + if (queue->itemCount == 0) + { + queue->lock = false; + return; + } + + // Handle the special case of being the last item in the queue: + else if (queue->itemCount == 1) + { + // Deallocate the correct data: + switch (queue->front->type) + { + case EVENT: + { + // TODO: Implement events. + } + case COMMAND: + { + free(queue->front->data.command->command); + free(queue->front->data.command->arguments); + free(queue->front->data.command); + break; + } + case INPUT_MESSAGE: + { + free(queue->front->data.inputMessage->content); + free(queue->front->data.inputMessage); + break; + } + case OUTPUT_MESSAGE: + { + free(queue->front->data.outputMessage->content); + free(queue->front->data.outputMessage); + break; + } + } + + // Deallocate the node: + free(queue->front); + + // Set the correct variables for the queue: + queue->front = NULL; + queue->back = NULL; + queue->itemCount = 0; + + // Unlock the queue: + queue->lock = false; + + return; + } + + // Remove the current front of the queue: + else + { + // Deallocate the correct data: + switch (queue->front->type) + { + case EVENT: + { + // TODO: Implement events. + break; + } + case COMMAND: + { + free(queue->front->data.command->command); + free(queue->front->data.command->arguments); + free(queue->front->data.command); + break; + } + case INPUT_MESSAGE: + { + free(queue->front->data.inputMessage->content); + free(queue->front->data.inputMessage); + break; + } + case OUTPUT_MESSAGE: + { + free(queue->front->data.outputMessage->content); + free(queue->front->data.outputMessage); + break; + } + } + + // Save a pointer to the current node so we don't leak it: + queueNode * nodeToDelete = queue->front; + + // Advance the queue: + queue->front = queue->front->next; + queue->itemCount--; + + // Deallocate the old node: + free(nodeToDelete); + + // Unlock the queue: + queue->lock = false; + + return; + } +} + +// Adds data to the back of a queue: +void pushQueue(queue * queue, void * data, queueDataType type) +{ + // Check if the queue is locked: + while (queue->lock); + + // Create a new node: + queueNode * newNode = malloc(sizeof(queueNode)); + newNode->type = type; + // Copy the data into the correct slot for the type: + switch (type) + { + case EVENT: + { + // TODO: Implement events. + break; + } + case COMMAND: + { + newNode->data.command = data; + break; + } + case INPUT_MESSAGE: + { + newNode->data.inputMessage = data; + break; + } + case OUTPUT_MESSAGE: + { + newNode->data.outputMessage = data; + break; + } + + } + + // Check if the queue is locked: + while (queue->lock); + + // Lock the queue: + queue->lock = true; + + // Set the correct pointers: + newNode->next = NULL; + + if (queue->itemCount == 0) + { + queue->front = newNode; + queue->back = newNode; + } + else + { + queue->back->next = newNode; + queue->back = newNode; + } + + // Increase the queue item count: + queue->itemCount++; + + // Unlock the queue: + queue->lock = false; + + // Flag that the queue was modified: + pthread_cond_broadcast(&queue->condition); +} diff --git a/src/queue.h b/src/queue.h new file mode 100644 index 0000000..3217698 --- /dev/null +++ b/src/queue.h @@ -0,0 +1,71 @@ +// queue.h: Defines the queue data type and associated function prototypes for SilverMUD. +// Barry Kane, 2022 +#ifndef QUEUE_H +#define QUEUE_H +#include "gamelogic.h" +#include "inputoutput.h" + +// ======================== +// -=[ Data Structures ]=-: +// ======================== + +// Let the compiler know there will be structs defined elsewhere: +typedef struct queue queue; + +// An enum which is used to state what type of data is being stored in a queueNode: +typedef enum queueDataType +{ + EVENT, + COMMAND, + INPUT_MESSAGE, + OUTPUT_MESSAGE +} queueDataType; + +// A union for storing a pointer to all the types of data a queueNode may hold: +typedef union queueData +{ + outputMessage * outputMessage; + inputMessage * inputMessage; + commandEvent * command; +} queueData; + +// A queue node, a singly-linked list node for our queue: +typedef struct queueNode queueNode; +typedef struct queueNode +{ + queueDataType type; + queueData data; + queueNode * next; +} queueNode; + +// A queue, with pointers to the head and tail of the linked list, and data for multi-threading, locking, and an item count. +typedef struct queue +{ + volatile bool lock; + size_t itemCount; + queueNode * front; + queueNode * back; + pthread_mutex_t mutex; + pthread_cond_t condition; +} queue; + +// ======================== +// -=[ Functions ]=-: +// ======================== + +// Allocates and instantiates a queue: +queue * createQueue(void); + +// Destroys a queue and all of it's members: +void destroyQueue(queue ** queue); + +// Returns the node at the front of the given queue: +queueNode * peekQueue(queue * queue); + +// Removes the current node from the front of the queue: +void popQueue(queue * queue); + +// Adds data to the back of a queue: +void pushQueue(queue * queue, void * data, queueDataType type); + +#endif diff --git a/src/server/SilverMUDServer.c b/src/server/SilverMUDServer.c index 824de8d..51aee27 100644 --- a/src/server/SilverMUDServer.c +++ b/src/server/SilverMUDServer.c @@ -1,4 +1,4 @@ -// Silverkin Industries Comm-Link Server, Engineering Sample Alpha 0.4. +// Silverkin Industries Comm-Link Server, Engineering Sample Alpha 0.5 // PROJECT CODENAME: WHAT DO I PAY YOU FOR? | Level-3 Clearance. // Barry Kane, 2021 #include @@ -17,59 +17,84 @@ #include #include +#include "../queue.h" #include "../areadata.h" #include "../gamelogic.h" #include "../constants.h" #include "../playerdata.h" +#include "../linkedlist.h" #include "../texteffects.h" #include "../inputoutput.h" typedef struct sockaddr sockaddr; void sigintHandler(int signal) { - exit(EXIT_SUCCESS); + printf("Caught signal %d.\n", signal); + _exit(EXIT_SUCCESS); } int main(int argc, char ** argv) { time_t currentTime; + unsigned delay = 4000; int socketFileDesc, connectionFileDesc, length, clientsAmount, socketCheck, activityCheck, returnVal; fd_set connectedClients; - pthread_t gameLogicThread; + pthread_t gameLogicThread, outputThread; int clientSockets[PLAYERCOUNT]; userMessage sendBuffer, receiveBuffer; playerInfo connectedPlayers[PLAYERCOUNT]; char testString[32] = "Hehe."; struct sockaddr_in serverAddress, clientAddress; - inputMessageQueue * inputQueue = createInputMessageQueue(); - outputMessageQueue * outputQueue = createOutputMessageQueue(); + queue * inputQueue = createQueue(), * outputQueue = createQueue(); + + // Parse command-line options: + int currentopt = 0; + while ((currentopt = getopt(argc, argv, "d:")) != -1) + { + switch(currentopt) + { + case 'd': + { + delay = atoi(optarg); + break; + } + } + } // Set the handler for SIGINT: signal(2, sigintHandler); // -==[ TEST GAME-STATE INITIALIZATION ]==- // Initialize test areas: - areaNode * areas = createAreaList(createArea("Login Area", "Please login with the /join command.")); - addAreaNodeToList(areas, createArea("Temple Entrance", - "You are standing outside a large, elaborate temple, of white marble and delicate construction. " - "Etched onto the left pillar next to the large opening is the same symbol, over and over again, a gentle curve with it's ends pointing to the right. " - "A similar symbol is on the right pillar, but it's ends are pointing to the left. ")); - - addAreaNodeToList(areas, createArea("The Hall of Documentation", - "Just past the threshold of the entrance lies a large hall, with bookshelves lining the walls, ceiling to floor. " - "The shelves are filled to the brim with finely-bound books, each with titles in silver lettering on the spine. " - "There are countless books, but you notice a large lectern in the center of the room, and a path leading upwards at the back. ")); - - addAreaNodeToList(areas, createArea("Monument to GNU", - "A beautifully ornate statue of GNU is above you on a pedestal. " - "Inscribed into the pillar, over and over, is the phrase \"M-x exalt\", in delicate gold letters. " - "You can't help but be awestruck.")); + list * areas = createList(AREA); + addToList(areas, createArea("Login Area", "Please login with the /join command."), AREA); + + // Create the areas: + addToList(areas, createArea("Octal One - Docking Bay Alpha", + "You are standing in the main docking bay of the largest station in the Octal System. " + "The sheer size of the bay is awe-inpiring. The number of ships is endless. " + "The bay is curved along with the body of the station. A catwalk runs along the back wall of the bay. " + "Two large arches lie at each end, leading to the other bays, and in the center, a set of doors leading to the interior of the station."), AREA); + + addToList(areas, createArea("Octal One - Station Access Control", + "You enter into the hallway leading to the main interior of the station." + "The attendant informs you that due to a computer error, exits cannot be proccessed at the moment," + " so you will be unable to leave, until it is resolved. " + "He apologizes profusely for the inconvenience, and clears you for entry if you wish to continue."), AREA); + + addToList(areas, createArea("Octal One - Floor Zero", + "You've never quite seen so many people in one place. A large ring of shopfronts surrounds an area filled with benches and tables. " + "There's so many buisnesses in sight that you feel you could find everything you need, and this is only one of 25 main floors, " + "not to mention the 6 outer pylons which surround the main hull of the station. Staircases lead to an upper platform allowing access to the pylons. "), AREA); + // Initialize test paths: - createPath(getAreaFromList(areas, 1), getAreaFromList(areas, 2), "Go inside the temple.", "Leave the temple."); - createPath(getAreaFromList(areas, 3), getAreaFromList(areas, 2), "Back to the Hall of Documentation.", "Path to Enlightenment."); - skillList * globalSkillList = malloc(sizeof(skillList)); - globalSkillList->head = NULL; + createPath(getFromList(areas, 1)->area, getFromList(areas, 2)->area, + "Enter the station interior.", "Return to Docking Bay Alpha."); + createOneWayPath(getFromList(areas, 2)->area, getFromList(areas, 3)->area, + "Continue to station interior. "); + + list * globalSkillList = createList(SKILL); // Create a few basic skills: createSkill(globalSkillList, "Medicine", 8, true); @@ -86,19 +111,18 @@ int main(int argc, char ** argv) // OH NO IT'S NOT MEMORY SAFE BETTER REWRITE IT IN RUST // But wait, we know the string won't be too big, so it's fine. strcpy(connectedPlayers[index].playerName, testString); - connectedPlayers[index].currentArea = getAreaFromList(areas, 0); + connectedPlayers[index].currentArea = getFromList(areas, 0)->area; connectedPlayers[index].stats = calloc(1, sizeof(statBlock)); connectedPlayers[index].stats->specPoints = 30; connectedPlayers[index].stats->skillPoints = 30; - connectedPlayers[index].skills = calloc(1, sizeof(skillList)); - connectedPlayers[index].skills->head = NULL; + connectedPlayers[index].skills = createList(SKILL); } // -==[ TEST GAME-STATE INITIALIZATION END ]==- // Give an intro: Display the Silverkin Industries logo and splash text. - slowPrint(logostring, 3000); - slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK SERVER ====--\nVersion Alpha 0.4\n", 5000); + slowPrint(logostring, delay); + slowPrint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK SERVER ====--\nVersion Alpha 0.5\n", delay); // Seed random number generator from the current time: srandom((unsigned)time(¤tTime)); @@ -119,10 +143,9 @@ int main(int argc, char ** argv) else { - slowPrint("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n", 5000); + slowPrint("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n", delay); } - // bzero(&serverAddress, sizeof(serverAddress)); // Assign IP and port: @@ -139,7 +162,7 @@ int main(int argc, char ** argv) else { - slowPrint("\tSocket Binding is:\t\033[32;40mGREEN.\033[0m\n", 5000); + slowPrint("\tSocket Binding is:\t\033[32;40mGREEN.\033[0m\n", delay); } // Let's start listening: @@ -150,7 +173,7 @@ int main(int argc, char ** argv) } else { - slowPrint("\tServer Listener is:\t\033[32;40mGREEN.\033[0m\n", 5000); + slowPrint("\tServer Listener is:\t\033[32;40mGREEN.\033[0m\n", delay); } length = sizeof(clientAddress); @@ -173,7 +196,7 @@ int main(int argc, char ** argv) gnutls_credentials_set(tlssessions[index], GNUTLS_CRD_ANON, &serverkey); gnutls_handshake_set_timeout(tlssessions[index], GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); } - slowPrint("\tTLS Preparation is:\t\033[32;40mGREEN.\033[0m\n", 5000); + slowPrint("\tTLS Preparation is:\t\033[32;40mGREEN.\033[0m\n", delay); // Prepare the game logic thread: gameLogicParameters * gameLogicThreadParameters = malloc(sizeof(gameLogicParameters)); @@ -183,15 +206,23 @@ int main(int argc, char ** argv) gameLogicThreadParameters->outputQueue = outputQueue; gameLogicThreadParameters->inputQueue = inputQueue; gameLogicThreadParameters->areaList = areas; - pthread_create(&gameLogicThread, NULL, &gameLogicLoop, gameLogicThreadParameters); + pthread_create(&gameLogicThread, NULL, &gameLogicHandler, gameLogicThreadParameters); - slowPrint("\tEvent Thread is:\t\033[32;40mGREEN.\033[0m\n", 5000); - slowPrint("=====\n", 5000); - struct timeval timeout = {0, 500}; + slowPrint("\tEvent Thread is:\t\033[32;40mGREEN.\033[0m\n", delay); + + // Prepare the output queue thread: + outputThreadParameters * outputParameters = malloc(sizeof(outputThreadParameters)); + outputParameters->outputQueue = outputQueue; + outputParameters->tlssessions = tlssessions; + outputParameters->connectedPlayers = connectedPlayers; + pthread_create(&outputThread, NULL, &outputThreadHandler, outputParameters); + + slowPrint("\tOutput Thread is:\t\033[32;40mGREEN.\033[0m\n", delay); + slowPrint("=====\n", delay); while(true) { - // Clear the set of file descriptors angad add the master socket: + // Clear the set of file descriptors and add the master socket: FD_ZERO(&connectedClients); FD_SET(socketFileDesc, &connectedClients); clientsAmount = socketFileDesc; @@ -215,7 +246,7 @@ int main(int argc, char ** argv) } // See if a connection is ready to be interacted with: - activityCheck = select((clientsAmount + 1), &connectedClients, NULL, NULL, &timeout); + activityCheck = select((clientsAmount + 1), &connectedClients, NULL, NULL, NULL); // Check if select() worked: if ((activityCheck < 0) && (errno != EINTR)) @@ -251,7 +282,17 @@ int main(int argc, char ** argv) strcpy(sendBuffer.messageContent, "Welcome to the server!"); messageSend(tlssessions[index], &sendBuffer); strcpy(receiveBuffer.messageContent, "/look"); - queueInputMessage(inputQueue, receiveBuffer, &connectedPlayers[index]); + + // Allocate the memory for a new input message: + inputMessage * newMessage = malloc(sizeof(inputMessage)); + newMessage->content = malloc(sizeof(userMessage)); + + // Copy in the correct data: + memcpy(newMessage->content, &receiveBuffer, sizeof(userMessage)); + newMessage->sender = &connectedPlayers[index]; + + // Push the new message onto the queue: + pushQueue(inputQueue, newMessage, INPUT_MESSAGE); break; } } @@ -280,7 +321,7 @@ int main(int argc, char ** argv) // Clear out the old player state so that a new one may join: sprintf(testString, "UNNAMED %d", index); strcpy(connectedPlayers[index].playerName, testString); - connectedPlayers[index].currentArea = getAreaFromList(areas, 0); + connectedPlayers[index].currentArea = getFromList(areas, 0)->area; // Prepare a fresh SSL session for the next new player: gnutls_init(&tlssessions[index], GNUTLS_SERVER); @@ -291,48 +332,22 @@ int main(int argc, char ** argv) // Otherwise, they've sent a message: else { - queueInputMessage(inputQueue, receiveBuffer, &connectedPlayers[index]); + // Allocate the memory for a new input message: + inputMessage * newMessage = malloc(sizeof(inputMessage)); + newMessage->content = malloc(sizeof(userMessage)); + + // Copy in the correct data: + memcpy(newMessage->content, &receiveBuffer, sizeof(userMessage)); + newMessage->sender = &connectedPlayers[index]; + + // Push the new message onto the queue: + pushQueue(inputQueue, newMessage, INPUT_MESSAGE); } } } } - - // Run through the output queue and send all unused messages: - while(outputQueue->currentLength != 0) - { - while(outputQueue->lock); - outputQueue->lock = true; - outputMessage * message = peekOutputMessage(outputQueue); - outputQueue->lock = false; - - // If the first target is set to NULL, it's intended for all connected: - if(message->targets[0] == NULL) - { - for (int index = 0; index < PLAYERCOUNT; index++) - { - messageSend(tlssessions[index], message->content); - } - } - else - { - int targetIndex = 0; - for(int index = 0; index < PLAYERCOUNT; index++) - { - if(message->targets[targetIndex] == NULL) - { - break; - } - if(&connectedPlayers[index] == message->targets[targetIndex]) - { - targetIndex++; - messageSend(tlssessions[index], message->content); - } - } - } - dequeueOutputMessage(outputQueue); - } } pthread_cancel(gameLogicThread); + pthread_cancel(outputThread); exit(EXIT_SUCCESS); } - diff --git a/src/texteffects.c b/src/texteffects.c index 34328fc..7cdaf57 100644 --- a/src/texteffects.c +++ b/src/texteffects.c @@ -5,10 +5,11 @@ #include #include -void slowPrint(char * stringToPrint, int delay) +// A character by character print, similar to a serial terminal with lower baud rate: +void slowPrint(const char * stringToPrint, int delay) { int characterIndex = 0; - while(stringToPrint[characterIndex] != '\0') + while (stringToPrint[characterIndex] != '\0') { putchar(stringToPrint[characterIndex]); // Flush the buffer so there's no line buffering. @@ -18,14 +19,15 @@ void slowPrint(char * stringToPrint, int delay) } } -void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bolded) +// The same, altered to work with ncurses: +void slowPrintNcurses(const char * stringToPrint, int delay, WINDOW * window, bool bolded) { int characterIndex = 0; - if(bolded) + if (bolded) { wattron(window, A_BOLD); } - while(stringToPrint[characterIndex] != '\0') + while (stringToPrint[characterIndex] != '\0') { waddch(window, stringToPrint[characterIndex]); // Refresh the ncurses screen. @@ -33,17 +35,18 @@ void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bol usleep(delay); characterIndex++; } - if(bolded) + if (bolded) { wattroff(window, A_BOLD); } wrefresh(window); } -void bruteforcePrint(char * stringToPrint, int delay) +// A character by character "brute-force" print, similar to Hollywood hacking scenes: +void bruteforcePrint(const char * stringToPrint, int delay) { unsigned int characterIndex = 0; - while(stringToPrint[characterIndex] != '\0') + while (stringToPrint[characterIndex] != '\0') { for(unsigned char currentCharacter = 32; currentCharacter <= stringToPrint[characterIndex]; currentCharacter++) { @@ -58,14 +61,15 @@ void bruteforcePrint(char * stringToPrint, int delay) } } -void bruteforcePrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bolded) +// The same, altered to work with ncurses: +void bruteforcePrintNcurses(const char * stringToPrint, int delay, WINDOW * window, bool bolded) { int characterIndex = 0; - if(bolded) + if (bolded) { wattron(window, A_BOLD); } - while(stringToPrint[characterIndex] != '\0') + while (stringToPrint[characterIndex] != '\0') { for(char currentCharacter = 32; currentCharacter <= stringToPrint[characterIndex]; currentCharacter++) { @@ -78,19 +82,20 @@ void bruteforcePrintNcurses(char * stringToPrint, int delay, WINDOW * window, bo waddch(window, stringToPrint[characterIndex]); characterIndex++; } - if(bolded) + if (bolded) { wattroff(window, A_BOLD); } wrefresh(window); } +// Word-wrap a string to a given width: void wrapString(char * stringToWrap, int stringLength, int screenWidth) { int characterCount = 0; for(int index = 0; index < stringLength; index++) { - if(stringToWrap[index] == '\n') + if (stringToWrap[index] == '\n') { characterCount = 0; } @@ -98,13 +103,13 @@ void wrapString(char * stringToWrap, int stringLength, int screenWidth) { characterCount++; } - if(characterCount == screenWidth) + if (characterCount == screenWidth) { - while(!isspace(stringToWrap[index]) && index > 0) + while (!isspace(stringToWrap[index]) && index > 0) { index--; } - if(index == 0) + if (index == 0) { return; } diff --git a/src/texteffects.h b/src/texteffects.h index 060c99c..37292c4 100644 --- a/src/texteffects.h +++ b/src/texteffects.h @@ -5,20 +5,23 @@ #include #include -// A character by character print, similar to a serial terminal with lower baud rate. -void slowPrint(char * stringToPrint, int delay); +// A character by character print, similar to a serial terminal with lower baud rate: +void slowPrint(const char * stringToPrint, int delay); -// The same, altered to work with ncurses. -void slowPrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bolded); +// The same, altered to work with ncurses: +void slowPrintNcurses(const char * stringToPrint, int delay, WINDOW * window, bool bolded); -// A character by character "brute-force" print, similar to Hollywood hacking scenes. -void bruteforcePrint(char * stringToPrint, int delay); +// A character by character "brute-force" print, similar to Hollywood hacking scenes: +void bruteforcePrint(const char * stringToPrint, int delay); -// The same, altered to work with ncurses. -void bruteforcePrintNcurses(char * stringToPrint, int delay, WINDOW * window, bool bolded); +// The same, altered to work with ncurses: +void bruteforcePrintNcurses(const char * stringToPrint, int delay, WINDOW * window, bool bolded); + +// Word-wrap a string to a given width: +void wrapString(char * stringToWrap, int stringLength, int screenWidth); // A string containing an ASCII art version of the Silverkin Industries logo. -char * logostring = +const char * logostring = " ///////\n" " //////////////////////////////////////////\n" " ///////////////////////////////////////////////////////////\n" @@ -32,5 +35,4 @@ char * logostring = " # # # # # # # # ## # ### # # ## //\n" " # # ### ##### ##### ### # # # # #### ### /\n"; -void wrapString(char * stringToWrap, int stringLength, int screenWidth); #endif diff --git a/tests/list-test.c b/tests/list-test.c index 9fbeb6a..94f3db1 100644 --- a/tests/list-test.c +++ b/tests/list-test.c @@ -1,24 +1,46 @@ -#include "../src/lists.h" -#include "../src/playerdata.h" +#include "../src/linkedlist.h" #include +static inline void printAreaList(list * areaList) +{ + listData * currentData; + for(int index = 0; index < areaList->itemCount; index++) + { + currentData = getFromList(areaList, index); + printf("%d\t| %s - %s\n", index, currentData->area->areaName, currentData->area->areaDescription); + } +} + void main() { - areaNode * areaList = createAreaList(createArea("Test Area A", "This is Test Area A")); - areaNode * counter = areaList; - addAreaNodeToList(areaList, createArea("Test Area B", "This is Test Area B")); - addAreaNodeToList(areaList, createArea("Test Area C", "This is Test Area C")); - for(int index = 0; index <= 2; index++) + list * areaList = createList(AREA); + char areaName[256]; + char areaDescription[256]; + + printf("\n--==[ Generating a list of ten items. ]==--\n\n"); + for(int count = 1; count <= 10; count++) { - printf("%s\n", counter->data->areaName); - counter = counter->next; - } - deleteAreaNodeFromList(areaList, getAreaFromList(areaList, 1)); - addAreaNodeToList(areaList, createArea("Test Area D", "This is Test Area D")); - counter = areaList; - for(int index = 0; index <= 2; index++) - { - printf("%s\n", counter->data->areaName); - counter = counter->next; + sprintf(areaName, "Area %d", count); + sprintf(areaDescription, "This is Area %d.", count); + + addToList(areaList, createArea(areaName, areaDescription) , AREA); } + printAreaList(areaList); + + printf("\n--==[ Inserting items into specific indexes. ]==--\n\n"); + insertIntoList(areaList, createArea("Cool, it worked.", "Cool, it worked."), AREA, 0); + insertIntoList(areaList, createArea("Cool, it worked.", "Cool, it worked."), AREA, 6); + insertIntoList(areaList, createArea("Cool, it worked.", "Cool, it worked."), AREA, 11); + printAreaList(areaList); + + printf("\n--==[ Removing certain areas from the list. ]==--\n\n"); + removeFromList(areaList, AREA, 12); + removeFromList(areaList, AREA, 6); + removeFromList(areaList, AREA, 0); + + printAreaList(areaList); + + destroyList(&areaList); + printf(""); } + diff --git a/tests/queue-test.c b/tests/queue-test.c new file mode 100644 index 0000000..d248f9c --- /dev/null +++ b/tests/queue-test.c @@ -0,0 +1,132 @@ +// Test for the queue type in SilverMUD: +#include +#include +#include +#include "../src/queue.h" + +#define formatBoolean(b) ((b) ? "true" : "false") +#define formatEquality(b) ((b) ? "equal" : "not equal") + +int main(void) +{ + // Create a queue: + printf("-=[ Creating queue ]=-:\n"); + queue * testQueue = createQueue(); + + // Check that the queue has the correct values: + printf("-=[ Checking initial values ]=-:\n"); + + printf("- Item count should be 0:\n"); + assert(testQueue->itemCount == 0); + printf("Item count is %d.\n\n", testQueue->itemCount); + + printf("- Lock should be false:\n"); + assert(testQueue->lock == false); + printf("Lock is %s.\n\n", formatBoolean(testQueue->lock)); + + printf("- Front should be (nil):\n"); + assert(testQueue->front == NULL); + printf("Front is %p.\n\n", testQueue->front); + + printf("- Back should be (nil):\n"); + assert(testQueue->back == NULL); + printf("Back is %p.\n\n", testQueue->front); + + // Create some items for the queue: + inputMessage * testInputMessage = malloc(sizeof(inputMessage)); + testInputMessage->sender = NULL; + testInputMessage->next = NULL; + testInputMessage->content = malloc(sizeof(userMessage)); + strcpy(testInputMessage->content->senderName,"Queue Test Input Sender"); + strcpy(testInputMessage->content->messageContent, "Queue Test Input Content - Hello!"); + + outputMessage * testOutputMessage = malloc(sizeof(outputMessage)); + for(int index = 0; index < PLAYERCOUNT; index++) + { + testOutputMessage->targets[index] = NULL; + } + testOutputMessage->next = NULL; + testOutputMessage->content = malloc(sizeof(userMessage)); + strcpy(testOutputMessage->content->senderName, "Queue Test Output Sender"); + strcpy(testOutputMessage->content->messageContent, "Queue Test Output Content - World!"); + + commandEvent * testCommandEvent = malloc(sizeof(commandEvent)); + testCommandEvent->next = NULL; + testCommandEvent->caller = NULL; + testCommandEvent->command = malloc(5 * sizeof(char)); + testCommandEvent->arguments = malloc(15 * sizeof(char)); + strcpy(testCommandEvent->command, "Test"); + strcpy(testCommandEvent->arguments, "Test Arguments"); + + // Add them to the queue: + printf("-=[ Adding items to the queue ]=-:\n"); + printf("- First item, Item count should be 1. Front and Back should be equal.\n"); + pushQueue(testQueue, testInputMessage, INPUT_MESSAGE); + assert(testQueue->itemCount == 1); + assert(testQueue->front == testQueue->back); + printf("Item count is: %d, Front and Back are %s.\n\n", testQueue->itemCount, + formatEquality(testQueue->front == testQueue->back)); + + printf("- Second item, Item count should be 2. Front and Back should be not equal.\n"); + pushQueue(testQueue, testOutputMessage, OUTPUT_MESSAGE); + assert(testQueue->itemCount == 2); + assert(testQueue->front != testQueue->back); + printf("Item count is: %d, Front and Back are %s.\n\n", testQueue->itemCount, + formatEquality(testQueue->front == testQueue->back)); + + printf("- Third item, Item count should be 3. Front and Back should be not equal.\n"); + pushQueue(testQueue, testCommandEvent, COMMAND); + assert(testQueue->itemCount == 3); + assert(testQueue->front != testQueue->back); + printf("Item count is: %d, Front and Back are %s.\n\n", testQueue->itemCount, + formatEquality(testQueue->front == testQueue->back)); + + printf("-=[ Checking items and popping from queue ]=-:\n"); + printf("- First item peeked should point to testInputMessage.\n"); + assert(peekQueue(testQueue)->data.inputMessage == testInputMessage); + printf("Peeked data is located at: %p, testInputMessage is located at: %p.\n\n", + peekQueue(testQueue)->data.inputMessage, testInputMessage); + + printf("- Popping first item, Item count should be 2, Front and Back should not be equal.\n"); + popQueue(testQueue); + assert(testQueue->itemCount == 2); + assert(testQueue->front != testQueue->back); + printf("Item count is: %d, Front and Back are %s.\n\n", testQueue->itemCount, + formatEquality(testQueue->front == testQueue->back)); + + printf("- Second item peeked should point to testOutputMessage.\n"); + assert(peekQueue(testQueue)->data.outputMessage == testOutputMessage); + printf("Peeked data is located at: %p, testOutputMessage is located at: %p.\n\n", + peekQueue(testQueue)->data.outputMessage, testOutputMessage); + + printf("- Popping second item, Item count should be 1, Front and Back should be equal.\n"); + popQueue(testQueue); + assert(testQueue->itemCount == 1); + assert(testQueue->front == testQueue->back); + printf("Item count is: %d, Front and Back are %s.\n\n", testQueue->itemCount, + formatEquality(testQueue->front == testQueue->back)); + + printf("- Third item peeked should point to testCommandEvent.\n"); + assert(peekQueue(testQueue)->data.command == testCommandEvent); + printf("Peeked data is located at: %p, testCommandEvent is located at: %p.\n\n", + peekQueue(testQueue)->data.command, testCommandEvent); + + printf("- Popping third item:\n"); + popQueue(testQueue); + + printf("- Item count should be 0:\n"); + assert(testQueue->itemCount == 0); + printf("Item count is %d.\n\n", testQueue->itemCount); + + printf("- Lock should be false:\n"); + assert(testQueue->lock == false); + printf("Lock is %s.\n\n", formatBoolean(testQueue->lock)); + + printf("- Front should be (nil):\n"); + assert(testQueue->front == NULL); + printf("Front is %p.\n\n", testQueue->front); + + printf("- Back should be (nil):\n"); + assert(testQueue->back == NULL); + printf("Back is %p.\n\n", testQueue->front); +}