#define _POSIX_C_SOURCE 199309L
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <time.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <sys/select.h>
#include "platform.h"
#define SERVER_SOCKET "server-socket"
#define CLIENT_SOCKET "client-socket"
#define OUTPUT_SIZE 10
#define EVENT_MESSAGE1_START 19
#define EVENT_MESSAGE2_START 70
#define EVENT_MESSAGE1_SIZE 51
#define EVENT_MESSAGE2_SIZE 30
#define EVENT_SIZE 100
#define MAX_INPUT_SIZE 5
#define MOUSE_INPUT_SIZE 4
#define OUTPUT_BUFFER_SIZE 1000
//Custom events
#define REFRESH_SCREEN 50
enum StatusTypes
{
DEEPEST_LEVEL_STATUS,
GOLD_STATUS,
SEED_STATUS,
EASY_MODE_STATUS,
STATUS_TYPES_NUMBER
};
extern playerCharacter rogue;
static struct sockaddr_un addr_write;
static int wfd, rfd;
static FILE *logfile;
static unsigned char outputBuffer[OUTPUT_BUFFER_SIZE];
static int outputBufferPos = 0;
static int refreshScreenOnly = 0;
static void gameLoop();
static void openLogfile();
static void closeLogfile();
static void writeToLog(const char *msg);
static void setupSockets();
static int readFromSocket(unsigned char *buf, int size);
static void writeToSocket(unsigned char *buf, int size);
static void flushOutputBuffer();
static void gameLoop() {
openLogfile();
writeToLog("Logfile started\n");
setupSockets();
rogueMain();
closeLogfile();
}
static void openLogfile() {
logfile = fopen("brogue-web.txt", "a");
if (logfile == NULL)
{
fprintf(stderr, "Logfile not created, errno = %d\n", errno);
}
}
static void closeLogfile() {
fclose(logfile);
}
static void writeToLog(const char *msg) {
fprintf(logfile, msg);
fflush(logfile);
}
static void setupSockets() {
struct sockaddr_un addr_read;
// Open read socket (from external)
rfd = socket(AF_UNIX, SOCK_DGRAM, 0);
remove(SERVER_SOCKET);
memset(&addr_read, 0, sizeof(struct sockaddr_un));
addr_read.sun_family = AF_UNIX;
strncpy(addr_read.sun_path, SERVER_SOCKET, sizeof(addr_read.sun_path) - 1);
bind(rfd, (struct sockaddr *)&addr_read, sizeof(struct sockaddr_un));
// Open write socket (to external)
wfd = socket(AF_UNIX, SOCK_DGRAM, 0);
memset(&addr_write, 0, sizeof(struct sockaddr_un));
addr_write.sun_family = AF_UNIX;
strncpy(addr_write.sun_path, CLIENT_SOCKET, sizeof(addr_write.sun_path) - 1);
}
int readFromSocket(unsigned char *buf, int size) {
return recvfrom(rfd, buf, size, 0, NULL, NULL);
}
static void flushOutputBuffer() {
char msg[80];
int no_bytes_sent;
no_bytes_sent = sendto(wfd, outputBuffer, outputBufferPos, 0, (struct sockaddr *)&addr_write, sizeof(struct sockaddr_un));
if (no_bytes_sent == -1) {
snprintf(msg, 80, "Error: %s\n", strerror(errno));
writeToLog(msg);
} else if (no_bytes_sent != outputBufferPos) {
snprintf(msg, 80, "Sent %d bytes only - %s\n", no_bytes_sent, strerror(errno));
writeToLog(msg);
}
outputBufferPos = 0;
}
static void writeToSocket(unsigned char *buf, int size)
{
if (outputBufferPos + size > OUTPUT_BUFFER_SIZE) {
flushOutputBuffer();
}
memcpy(outputBuffer + outputBufferPos, buf, size);
outputBufferPos += size;
}
// Map characters which are missing or rendered as emoji on some platforms
static unsigned int fixUnicode(unsigned int code) {
switch (code) {
case U_ARIES: return 0x03C8;
case U_CIRCLE: return 'o';
case U_CIRCLE_BARS: return 0x25C6;
case U_FILLED_CIRCLE_BARS: return 0x25C7;
default: return code;
}
}
static void web_plotChar(enum displayGlyph inputChar,
short xLoc, short yLoc,
short foreRed, short foreGreen, short foreBlue,
short backRed, short backGreen, short backBlue) {
unsigned char outputBuffer[OUTPUT_SIZE];
unsigned char firstCharByte, secondCharByte;
enum displayGlyph translatedChar;
translatedChar = glyphToUnicode(inputChar);
translatedChar = fixUnicode(inputChar);
firstCharByte = translatedChar >> 8 & 0xff;
secondCharByte = translatedChar;
outputBuffer[0] = (unsigned char)xLoc;
outputBuffer[1] = (unsigned char)yLoc;
outputBuffer[2] = firstCharByte;
outputBuffer[3] = secondCharByte;
outputBuffer[4] = (unsigned char)foreRed * 255 / 100;
outputBuffer[5] = (unsigned char)foreGreen * 255 / 100;
outputBuffer[6] = (unsigned char)foreBlue * 255 / 100;
outputBuffer[7] = (unsigned char)backRed * 255 / 100;
outputBuffer[8] = (unsigned char)backGreen * 255 / 100;
outputBuffer[9] = (unsigned char)backBlue * 255 / 100;
writeToSocket(outputBuffer, OUTPUT_SIZE);
}
static void sendStatusUpdate() {
unsigned char statusOutputBuffer[OUTPUT_SIZE];
unsigned long statusValues[STATUS_TYPES_NUMBER];
int i, j;
statusValues[DEEPEST_LEVEL_STATUS] = rogue.deepestLevel;
statusValues[GOLD_STATUS] = rogue.gold;
statusValues[SEED_STATUS] = rogue.seed;
statusValues[EASY_MODE_STATUS] = rogue.easyMode;
memset(statusOutputBuffer, 0, OUTPUT_SIZE);
for (i = 0; i < STATUS_TYPES_NUMBER; i++) {
// Coordinates of (255, 255) will let the server and client know that this is a status update rather than a cell update
statusOutputBuffer[0] = 255;
statusOutputBuffer[1] = 255;
// Status type
statusOutputBuffer[2] = i;
// Status values
statusOutputBuffer[3] = statusValues[i] >> 24 & 0xff;
statusOutputBuffer[4] = statusValues[i] >> 16 & 0xff;
statusOutputBuffer[5] = statusValues[i] >> 8 & 0xff;
statusOutputBuffer[6] = statusValues[i];
// Fill
for (j = 7; j < OUTPUT_SIZE; j++) {
statusOutputBuffer[j] = 0;
}
writeToSocket(statusOutputBuffer, OUTPUT_SIZE);
}
}
// Pause by doing a blocking poll on the socket
static boolean web_pauseForMilliseconds(short milliseconds) {
fd_set input;
struct timeval timeout;
FD_ZERO(&input);
FD_SET(rfd, &input);
timeout.tv_sec = milliseconds / 1000;
timeout.tv_usec = (milliseconds % 1000) * 1000;
return select(rfd + 1, &input, NULL, NULL, &timeout);
}
static void web_nextKeyOrMouseEvent(rogueEvent *returnEvent, boolean textInput, boolean colorsDance) {
unsigned char inputBuffer[MAX_INPUT_SIZE];
unsigned short keyCharacter;
// Because we will halt execution until we get more input, we definitely cannot have any dancing colors from the server side.
colorsDance = false;
// Send a status update of game variables we want on the client
if (!refreshScreenOnly) {
sendStatusUpdate();
}
refreshScreenOnly = 0;
// Flush output buffer
flushOutputBuffer();
// Block for next command
readFromSocket(inputBuffer, MAX_INPUT_SIZE);
returnEvent->eventType = inputBuffer[0];
if (returnEvent->eventType == REFRESH_SCREEN) {
// Custom event type - not a command for the brogue game
refreshScreen();
// Don't send a status update if this was only a screen refresh (may be sent by observer)
refreshScreenOnly = 1;
return;
}
if (returnEvent->eventType == KEYSTROKE) {
keyCharacter = inputBuffer[1] << 8 | inputBuffer[2];
// 13 is sent for RETURN on web, map to RETURN_KEY
if (keyCharacter == 13) {
keyCharacter = RETURN_KEY;
}
returnEvent->param1 = keyCharacter;
returnEvent->controlKey = inputBuffer[3];
returnEvent->shiftKey = inputBuffer[4];
} else { // Mouse event
fread(inputBuffer, sizeof(char), MOUSE_INPUT_SIZE, stdin);
returnEvent->param1 = inputBuffer[1]; //x coord
returnEvent->param2 = inputBuffer[2]; //y coord
returnEvent->controlKey = inputBuffer[3];
returnEvent->shiftKey = inputBuffer[4];
}
}
static void web_remap(const char *input_name, const char *output_name) {
// Not needed
}
static boolean web_modifierHeld(int modifier) {
// Not needed, modifiers past directly with the event data
return 0;
}
static void web_notifyEvent(short eventId, int data1, int data2, const char *str1, const char *str2) {
unsigned char statusOutputBuffer[EVENT_SIZE];
// Coordinates of (254, 254) will let the server and client know that this is a event notification update rather than a cell update
statusOutputBuffer[0] = 254;
statusOutputBuffer[1] = 254;
statusOutputBuffer[2] = eventId;
statusOutputBuffer[3] = data1 >> 24 & 0xff;
statusOutputBuffer[4] = data1 >> 16 & 0xff;
statusOutputBuffer[5] = data1 >> 8 & 0xff;
statusOutputBuffer[6] = data1;
statusOutputBuffer[7] = rogue.depthLevel >> 8 & 0xff;
statusOutputBuffer[8] = rogue.depthLevel;
statusOutputBuffer[9] = rogue.easyMode >> 8 & 0xff;
statusOutputBuffer[10] = rogue.easyMode;
statusOutputBuffer[11] = rogue.gold >> 24 & 0xff;
statusOutputBuffer[12] = rogue.gold >> 16 & 0xff;
statusOutputBuffer[13] = rogue.gold >> 8 & 0xff;
statusOutputBuffer[14] = rogue.gold;
statusOutputBuffer[15] = rogue.seed >> 24 & 0xff;
statusOutputBuffer[16] = rogue.seed >> 16 & 0xff;
statusOutputBuffer[17] = rogue.seed >> 8 & 0xff;
statusOutputBuffer[18] = rogue.seed;
// str1 is the death / victory message
memcpy(statusOutputBuffer + EVENT_MESSAGE1_START, str1, EVENT_MESSAGE1_SIZE);
statusOutputBuffer[EVENT_MESSAGE2_START - 1] = 0;
// str2 is unused
memcpy(statusOutputBuffer + EVENT_MESSAGE1_START + EVENT_MESSAGE1_SIZE, str2, EVENT_MESSAGE2_SIZE);
statusOutputBuffer[EVENT_SIZE - 1] = 0;
writeToSocket(statusOutputBuffer, EVENT_SIZE);
flushOutputBuffer();
}
struct brogueConsole webConsole = {
gameLoop,
web_pauseForMilliseconds,
web_nextKeyOrMouseEvent,
web_plotChar,
web_remap,
web_modifierHeld,
web_notifyEvent,
NULL,
NULL
};