Intialize and cleanup repo
This commit is contained in:
2
server/.gitignore
vendored
Normal file
2
server/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
builds
|
||||
.vscode
|
9
server/Dockerfile
Normal file
9
server/Dockerfile
Normal file
@ -0,0 +1,9 @@
|
||||
FROM fedora:32
|
||||
|
||||
RUN /bin/bash -c "dnf install g++ enet-devel gtest gmock gmock-devel gtest-devel -y"
|
||||
|
||||
ADD / /dt
|
||||
|
||||
RUN cd /dt; /dt/build.sh
|
||||
|
||||
CMD ["./builds/server.out"]
|
16
server/ReadMe.md
Normal file
16
server/ReadMe.md
Normal file
@ -0,0 +1,16 @@
|
||||
# LD46 Server
|
||||
|
||||
## Build Requirements
|
||||
|
||||
Enet development files:
|
||||
|
||||
`dnf install enet-devel`
|
||||
|
||||
|
||||
G++
|
||||
|
||||
`dnf install g++`
|
||||
|
||||
For testing:
|
||||
|
||||
`dnf install gtest gmock gmock-devel gtest-devel`
|
10
server/build.sh
Executable file
10
server/build.sh
Executable file
@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
mkdir -p builds
|
||||
|
||||
# Remove old version
|
||||
if [ -f builds/server.out ]; then
|
||||
rm builds/server.out
|
||||
fi
|
||||
|
||||
g++ main.cpp -o builds/server.out -lenet -lpthread
|
67
server/console.hpp
Normal file
67
server/console.hpp
Normal file
@ -0,0 +1,67 @@
|
||||
#ifndef CONSOLE_HPP
|
||||
#define CONSOLE_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <pthread.h>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
||||
|
||||
bool console_is_running = false;
|
||||
|
||||
class ServerConsole
|
||||
{
|
||||
public:
|
||||
ServerConsole();
|
||||
void stop(void);
|
||||
bool is_running(void);
|
||||
private:
|
||||
pthread_t thread;
|
||||
};
|
||||
|
||||
|
||||
void * console_logic(void *)
|
||||
{
|
||||
printf("Started server console...\n");
|
||||
|
||||
while(console_is_running)
|
||||
{
|
||||
char c [256];
|
||||
fgets(c,sizeof(c), stdin);
|
||||
if(c[0] == 'q')
|
||||
{
|
||||
console_is_running = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Invalid console command!\n");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ServerConsole::ServerConsole(void)
|
||||
{
|
||||
//Intialized values
|
||||
console_is_running = true;
|
||||
|
||||
thread = pthread_t();
|
||||
|
||||
pthread_create(&(this->thread), NULL, console_logic, NULL);
|
||||
|
||||
}
|
||||
|
||||
void ServerConsole::stop(void)
|
||||
{
|
||||
if(this->thread != 0)
|
||||
{
|
||||
pthread_join(this->thread, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
bool ServerConsole::is_running(void)
|
||||
{
|
||||
return console_is_running;
|
||||
}
|
||||
|
||||
#endif
|
73
server/gameentity.hpp
Normal file
73
server/gameentity.hpp
Normal file
@ -0,0 +1,73 @@
|
||||
#ifndef GAMEENTITY_HPP
|
||||
#define GAMEENTITY_HPP
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
class GameEntity
|
||||
{
|
||||
public:
|
||||
GameEntity(std::string,std::string,int,int);
|
||||
std::string get_id(void);
|
||||
std::string get_type(void);
|
||||
int get_x(void);
|
||||
int get_y(void);
|
||||
std::string set_x(int);
|
||||
std::string set_y(int);
|
||||
std::string get_dump(void);
|
||||
private:
|
||||
int pos_x;
|
||||
int pos_y;
|
||||
std::string id;
|
||||
std::string type;
|
||||
|
||||
};
|
||||
|
||||
GameEntity::GameEntity(std::string entity_id, std::string entity_type, int x = 0, int y = 0)
|
||||
{
|
||||
id = entity_id;
|
||||
type = entity_type;
|
||||
this->set_x(x);
|
||||
this->set_y(y);
|
||||
std::cout << "Entity created with id: '" << id << "' at " << x << "," << y << std::endl;
|
||||
}
|
||||
|
||||
|
||||
int GameEntity::get_x(void)
|
||||
{
|
||||
return pos_x;
|
||||
}
|
||||
|
||||
int GameEntity::get_y(void)
|
||||
{
|
||||
return pos_y;
|
||||
}
|
||||
|
||||
std::string GameEntity::get_dump(void)
|
||||
{
|
||||
return std::to_string(pos_x) + "," + std::to_string(pos_y) + "," + type + ":" + id + '\n';
|
||||
}
|
||||
|
||||
std::string GameEntity::set_x(int x)
|
||||
{
|
||||
pos_x = x;
|
||||
return this -> get_dump();
|
||||
}
|
||||
std::string GameEntity::set_y(int y)
|
||||
{
|
||||
pos_y = y;
|
||||
return this -> get_dump();
|
||||
}
|
||||
|
||||
std::string GameEntity::get_id(void)
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
std::string GameEntity::get_type(void)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
140
server/gamemap.hpp
Normal file
140
server/gamemap.hpp
Normal file
@ -0,0 +1,140 @@
|
||||
#ifndef GAMEMAP_HPP
|
||||
#define GAMEMAP_HPP
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "gameentity.hpp"
|
||||
|
||||
class GameMap
|
||||
{
|
||||
public:
|
||||
GameMap(int,int);
|
||||
int get_tile(int,int);
|
||||
void set_tile(int,int,int);
|
||||
std::string get_tile_dump(void);
|
||||
std::string get_entity_dump(void);
|
||||
std::string spawn_entity(std::string,std::string,int,int);
|
||||
std::string move_entity(std::string,std::string,int,int);
|
||||
int get_size_x(void);
|
||||
int get_size_y(void);
|
||||
private:
|
||||
int ** map_data;
|
||||
int size_x;
|
||||
int size_y;
|
||||
bool valid_x_y(int,int);
|
||||
std::vector<GameEntity> entities;
|
||||
|
||||
};
|
||||
|
||||
GameMap::GameMap(int x, int y)
|
||||
{
|
||||
size_x = x;
|
||||
size_y = y;
|
||||
map_data = new int * [x];
|
||||
for (int i = 0; i < x; i++)
|
||||
{
|
||||
map_data[i] = new int [y];
|
||||
for(int q = 0; q < y; q++)
|
||||
{
|
||||
if(q == 0 || i == 0 || i == x-1 || q == y-1)
|
||||
{
|
||||
map_data[i][q] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
map_data[i][q] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this -> spawn_entity("spawn","crystal",size_x/2, size_y/2);
|
||||
|
||||
}
|
||||
|
||||
std::string GameMap::spawn_entity(std::string entity_id, std::string entity_type, int x = 0, int y = 0)
|
||||
{
|
||||
GameEntity entity = GameEntity(entity_id, entity_type, x, y);
|
||||
entities.push_back(entity);
|
||||
return entity.get_dump();
|
||||
}
|
||||
|
||||
std::string GameMap::move_entity(std::string entity_id, std::string entity_type, int x = 0, int y = 0)
|
||||
{
|
||||
for(int i = 0; i < entities.size(); i++)
|
||||
{
|
||||
if(entities[i].get_id() == entity_id && entities[i].get_type() == entity_type)
|
||||
{
|
||||
if(map_data[entities[i].get_x() + x][entities[i].get_y() + y] % 2 == 1)
|
||||
{
|
||||
entities[i].set_x(entities[i].get_x() + x);
|
||||
entities[i].set_y(entities[i].get_y() + y);
|
||||
}
|
||||
return entities[i].get_dump();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
bool GameMap::valid_x_y(int x, int y)
|
||||
{
|
||||
if(x < size_x && x >= 0)
|
||||
{
|
||||
if(y < size_y && y >= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int GameMap::get_tile(int x, int y)
|
||||
{
|
||||
if(this->valid_x_y(x,y))
|
||||
{
|
||||
return map_data[x][y];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
void GameMap::set_tile(int x, int y, int tile_type)
|
||||
{
|
||||
if(this->valid_x_y(x,y))
|
||||
{
|
||||
map_data[x][y] = tile_type;
|
||||
}
|
||||
}
|
||||
std::string GameMap::get_tile_dump(void)
|
||||
{
|
||||
std::string data_string("");
|
||||
for(int x = 0; x < size_x; x++)
|
||||
{
|
||||
for(int y = 0; y < size_y; y++)
|
||||
{
|
||||
data_string += std::to_string(x) + "," + std::to_string(y) + "," + std::to_string(map_data[x][y]) + '\n';
|
||||
}
|
||||
}
|
||||
return data_string;
|
||||
}
|
||||
|
||||
std::string GameMap::get_entity_dump(void)
|
||||
{
|
||||
std::string data_string("");
|
||||
for(int i = 0; i < entities.size(); i++)
|
||||
{
|
||||
data_string += entities[i].get_dump();
|
||||
}
|
||||
return data_string;
|
||||
}
|
||||
|
||||
int GameMap::get_size_x(void)
|
||||
{
|
||||
return size_x;
|
||||
}
|
||||
|
||||
int GameMap::get_size_y(void)
|
||||
{
|
||||
return size_y;
|
||||
}
|
||||
|
||||
#endif
|
107
server/gameserver.hpp
Normal file
107
server/gameserver.hpp
Normal file
@ -0,0 +1,107 @@
|
||||
#ifndef GAMESERVER_HPP
|
||||
#define GAMESERVER_HPP
|
||||
|
||||
#include <enet/enet.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
|
||||
#include "gamemap.hpp"
|
||||
|
||||
const int SERVER_PORT = 7777;
|
||||
const int MAX_PLAYERS = 32;
|
||||
|
||||
const int EVENT_RATE = 1000;
|
||||
|
||||
//Game Data
|
||||
static GameMap gamemap(256,256);
|
||||
static std::string usernames[MAX_PLAYERS];
|
||||
|
||||
static ENetHost* server;
|
||||
|
||||
//===================
|
||||
// Network Processes
|
||||
//===================
|
||||
void PlayerMove(ENetEvent* event)
|
||||
{
|
||||
int peer_id = event->peer -> incomingPeerID;
|
||||
|
||||
int move_x = 0;
|
||||
int move_y = 1;
|
||||
|
||||
|
||||
std::string data_input((char*)event->packet->data);
|
||||
data_input = data_input.substr(2);
|
||||
|
||||
//Parse input string
|
||||
std::stringstream ss(data_input);
|
||||
std::string tempString;
|
||||
std::getline(ss, tempString, ',');
|
||||
move_x = std::stoi(tempString);
|
||||
std::getline(ss, tempString, '\n');
|
||||
move_y = std::stoi(tempString);
|
||||
|
||||
//Update player position
|
||||
std::string resp = "2|" + gamemap.move_entity(usernames[peer_id], "player", move_x, move_y);
|
||||
const char* data = resp.c_str();
|
||||
ENetPacket* packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
|
||||
enet_host_broadcast(server, 0, packet);
|
||||
}
|
||||
|
||||
void DataRequest(ENetEvent* event)
|
||||
{
|
||||
//Create world data dump
|
||||
std::string map_dump_resp("2|");
|
||||
map_dump_resp += gamemap.get_tile_dump() + gamemap.get_entity_dump();
|
||||
const char* data = map_dump_resp.c_str();
|
||||
|
||||
//Build and send reliable packet to requester
|
||||
ENetPacket* packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
|
||||
enet_peer_send(event->peer, 0, packet);
|
||||
}
|
||||
|
||||
void Authenticate(ENetEvent* event)
|
||||
{
|
||||
//Grab username from packet data
|
||||
std::string username((char*)event->packet->data);
|
||||
username = username.substr(2);
|
||||
|
||||
//Determine if peer already has username
|
||||
int peer_id = event -> peer -> incomingPeerID;
|
||||
const char* data;
|
||||
if(usernames[peer_id] == "")
|
||||
{
|
||||
//Update username array
|
||||
usernames[peer_id] = username;
|
||||
|
||||
//Spawn entity
|
||||
std::string spawn_data = gamemap.spawn_entity(usernames[peer_id], "player", 127, 127);
|
||||
|
||||
//Tell peers about new player
|
||||
data = ("2|" + spawn_data).c_str();
|
||||
ENetPacket* packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
|
||||
enet_host_broadcast(server, 0, packet);
|
||||
|
||||
std::cout << usernames[peer_id] << " spawned!" << std::endl;
|
||||
std::cout << peer_id << " | " << username << std::endl;
|
||||
|
||||
std::string okay_response("1|OK");
|
||||
data = (okay_response + username).c_str();
|
||||
|
||||
//Return success
|
||||
packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
|
||||
enet_peer_send(event -> peer, 0, packet);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string resp("1|Already logged in!");
|
||||
data = resp.c_str();
|
||||
|
||||
//Return reliable error message
|
||||
ENetPacket* packet = enet_packet_create(data, strlen(data) + 1, ENET_PACKET_FLAG_RELIABLE);
|
||||
enet_peer_send(event -> peer, 0, packet);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
114
server/main.cpp
Normal file
114
server/main.cpp
Normal file
@ -0,0 +1,114 @@
|
||||
/* ./server/main.cpp */
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <enet/enet.h>
|
||||
|
||||
#include "console.hpp"
|
||||
#include "gameserver.hpp"
|
||||
|
||||
int main (int argc, char ** argv)
|
||||
{
|
||||
printf("Starting LD46 game server...\n");
|
||||
|
||||
for(int i = 0; i < MAX_PLAYERS; i++)
|
||||
{
|
||||
usernames[i] = "";
|
||||
}
|
||||
|
||||
|
||||
if (enet_initialize () != 0)
|
||||
{
|
||||
fprintf (stderr, "ENet server failed to initialize!\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
atexit (enet_deinitialize);
|
||||
|
||||
|
||||
ServerConsole console;
|
||||
|
||||
//Create ENet objects
|
||||
ENetEvent event;
|
||||
ENetAddress address;
|
||||
|
||||
|
||||
address.host = ENET_HOST_ANY;
|
||||
address.port = SERVER_PORT;
|
||||
|
||||
|
||||
const int CHANNEL_COUNT = 1;
|
||||
server = enet_host_create (&address, MAX_PLAYERS, CHANNEL_COUNT, 0, 0);
|
||||
|
||||
if (server == NULL)
|
||||
{
|
||||
std::cout << "Failed to create ENet server host!" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Awaiting connections..." << std::endl;
|
||||
while(console_is_running)
|
||||
{
|
||||
ENetEvent event;
|
||||
/* Wait up to 1000 milliseconds for an event. */
|
||||
while (enet_host_service (server, & event, EVENT_RATE) > 0)
|
||||
{
|
||||
printf("Parsing event!\n");
|
||||
switch (event.type)
|
||||
{
|
||||
case ENET_EVENT_TYPE_CONNECT:
|
||||
std::cout << "A new client connected from " << event.peer -> address.host
|
||||
<< ":" << event.peer -> address.port << std::endl;
|
||||
break;
|
||||
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
std::cout << "A packet of length " << event.packet -> dataLength << " containing "
|
||||
<< event.packet -> data << " was received from " << event.peer -> data << " on channel"
|
||||
<< event.channelID << std::endl;
|
||||
|
||||
char c [1];
|
||||
c[0] = event.packet->data[0];
|
||||
|
||||
/* Determine which function to call based on
|
||||
the value of the first character of packet */
|
||||
std::cout << "Packet Type: " << c << std::endl;
|
||||
switch (c[0])
|
||||
{
|
||||
case '1':
|
||||
Authenticate(&event);
|
||||
break;
|
||||
case '2':
|
||||
DataRequest(&event);
|
||||
break;
|
||||
case '3':
|
||||
PlayerMove(&event);
|
||||
break;
|
||||
default:
|
||||
std::cout << "Invalid packet recieved!" <<std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
//Destroy packet now that it has been used
|
||||
enet_packet_destroy (event.packet);
|
||||
break;
|
||||
|
||||
case ENET_EVENT_TYPE_DISCONNECT:
|
||||
std::cout << event.peer -> data << " disconnected." << std::endl;
|
||||
//Remove peer data on disconnect
|
||||
|
||||
// DELETE ENTITY HERE
|
||||
|
||||
usernames[event.peer -> incomingPeerID] = "";
|
||||
event.peer -> data = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exit process
|
||||
console.stop();
|
||||
enet_host_destroy(server);
|
||||
return 0;
|
||||
}
|
3
server/run_build.sh
Executable file
3
server/run_build.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
bash build.sh
|
||||
./builds/server.out
|
4
server/run_tests.sh
Executable file
4
server/run_tests.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
mkdir -p builds
|
||||
g++ tests.cpp -o builds/tests.out -lgtest
|
||||
./builds/tests.out
|
69
server/tests.cpp
Normal file
69
server/tests.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "gameentity.hpp"
|
||||
#include "gamemap.hpp"
|
||||
|
||||
//Test Entities
|
||||
const std::string TEST_ENTITY_ID = "test_entity";
|
||||
const std::string TEST_ENTITY_TYPE= "test";
|
||||
GameEntity CreateTestEntity()
|
||||
{
|
||||
return GameEntity(TEST_ENTITY_ID, TEST_ENTITY_TYPE,0,0);
|
||||
}
|
||||
TEST(EntityTest, SetX)
|
||||
{
|
||||
GameEntity entity = CreateTestEntity();
|
||||
entity.set_x(4);
|
||||
EXPECT_EQ(entity.get_x(), 4);
|
||||
}
|
||||
TEST(EntityTest, SetY)
|
||||
{
|
||||
GameEntity entity = CreateTestEntity();
|
||||
entity.set_y(4);
|
||||
EXPECT_EQ(entity.get_y(), 4);
|
||||
}
|
||||
TEST(EntityTest, GetId)
|
||||
{
|
||||
GameEntity entity = CreateTestEntity();
|
||||
EXPECT_EQ(entity.get_id(), TEST_ENTITY_ID);
|
||||
}
|
||||
TEST(EntityTest, GetType)
|
||||
{
|
||||
GameEntity entity = CreateTestEntity();
|
||||
EXPECT_EQ(entity.get_type(), TEST_ENTITY_TYPE);
|
||||
}
|
||||
TEST(EntityTest, GetBasicDump)
|
||||
{
|
||||
GameEntity entity = CreateTestEntity();
|
||||
//Test intial location (0,0)
|
||||
EXPECT_EQ(entity.get_dump(), std::string("0,0," + TEST_ENTITY_TYPE + ":" + TEST_ENTITY_ID + "\n"));
|
||||
|
||||
//Test non-intial location (1,2)
|
||||
entity.set_x(1);
|
||||
entity.set_y(2);
|
||||
EXPECT_EQ(entity.get_dump(), std::string("1,2," + TEST_ENTITY_TYPE + ":" + TEST_ENTITY_ID + "\n"));
|
||||
}
|
||||
|
||||
//Test GameMap Object
|
||||
GameMap CreateMapEntity(int x = 16, int y = 16)
|
||||
{
|
||||
return GameMap(x,y);
|
||||
}
|
||||
TEST(GameMapTest, CheckMapSize)
|
||||
{
|
||||
GameMap map = CreateMapEntity(8,8);
|
||||
EXPECT_EQ(map.get_size_x(), 8);
|
||||
EXPECT_EQ(map.get_size_y(), 8);
|
||||
|
||||
map = CreateMapEntity(16, 4);
|
||||
EXPECT_EQ(map.get_size_x(), 16);
|
||||
EXPECT_EQ(map.get_size_y(), 4);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
Reference in New Issue
Block a user