Kajetan Johannes Hammerle 3 years ago
commit
79f6336493
13 changed files with 581 additions and 0 deletions
  1. 74 0
      Client.cpp
  2. 31 0
      Client.h
  3. 186 0
      Game.cpp
  4. 36 0
      Game.h
  5. 32 0
      Main.cpp
  6. 89 0
      Socket.cpp
  7. 21 0
      Socket.h
  8. 48 0
      String.cpp
  9. 22 0
      String.h
  10. 6 0
      Types.h
  11. 18 0
      Utils.cpp
  12. 8 0
      Utils.h
  13. 10 0
      meson.build

+ 74 - 0
Client.cpp

@@ -0,0 +1,74 @@
+#include <iostream>
+#include <cstring>
+
+#include <unistd.h>
+#include <sys/socket.h>
+
+#include "Client.h"
+#include "Utils.h"
+
+void nothing() {
+}
+
+Client::Client() : id(-1), thread(nothing) {
+}
+
+Client::~Client() {
+    closeSocket();
+    if(thread.joinable()) {
+        thread.join();
+    }
+}
+
+bool Client::isFree() const {
+    return id == -1;
+}
+
+void Client::sendString(const char* buffer) const {
+    send(id, buffer, strlen(buffer), 0);
+}
+
+void Client::start(int clientId) {
+    id = clientId;
+    if(thread.joinable()) {
+        thread.join();
+    }
+    thread = std::thread(&Client::loop, this);
+}
+
+void Client::loop() {
+    String s;
+
+    game.reset(s);
+    sendString(s);
+
+    while(true) {
+        int pollResult = Utils::pollFileDescriptor(id, 100, "cannot poll client socket");
+        if(pollResult < 0) {
+            break;
+        } else if(pollResult == 0) {
+            continue;
+        }
+
+        String buffer;
+        if(buffer.receiveFromSocket(id)) {
+            break;
+        }
+        String output;
+        game.parse(buffer, output);
+        sendString(output);
+    }
+    std::cout << "client removed\n";
+    closeSocket();
+}
+
+void Client::onReceive(char* buffer) {
+    std::cout << buffer << "\n";
+}
+
+void Client::closeSocket() {
+    if(id != -1 && close(id) == -1) {
+        perror("cannot close client socket");
+    }
+    id = -1;
+}

+ 31 - 0
Client.h

@@ -0,0 +1,31 @@
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include <thread>
+
+#include "Game.h"
+
+class Client final {
+public:
+    Client();
+    ~Client();
+    Client(const Client& other) = delete;
+    Client(Client&& other) = delete;
+    Client& operator=(Client&& other) = delete;
+    Client& operator=(const Client& other) = delete;
+
+    bool isFree() const;
+    void start(int clientId);
+
+private:
+    void loop();
+    void sendString(const char* buffer) const;
+    void onReceive(char* buffer);
+    void closeSocket();
+
+    int id;
+    std::thread thread;
+    Game game;
+};
+
+#endif

+ 186 - 0
Game.cpp

@@ -0,0 +1,186 @@
+#include <iostream>
+
+#include "Game.h"
+#include "Types.h"
+
+Game::Game() : activeX(-1), activeY(-1) {
+    reset();
+}
+
+void Game::reset() {
+    activeX = -1;
+    activeY = -1;
+    for(uint x = 0; x < 9; x++) {
+        for(uint y = 0; y < 5; y++) {
+            fields[x][y] = (y > 2 ? FieldState::WHITE : FieldState::BLACK);
+        }
+    }
+    fields[0][2] = FieldState::WHITE;
+    fields[2][2] = FieldState::WHITE;
+    fields[4][2] = FieldState::EMPTY;
+    fields[5][2] = FieldState::WHITE;
+    fields[7][2] = FieldState::WHITE;
+}
+
+void Game::reset(String& output) {
+    reset();
+    output.append("Fanorona Game\n");
+    print(output);
+    output.append("Human Color: White(O)\nAIColor: Black(#)\nHuman turn\nselect stone: ");
+}
+
+void Game::print(String& s) const {
+    s.append("\n  0 1 2 3 4 5 6 7 8\n");
+    printLine(s, 0);
+    s.append("  |\\|/|\\|/|\\|/|\\|/|\n");
+    printLine(s, 1);
+    s.append("  |/|\\|/|\\|/|\\|/|\\|\n");
+    printLine(s, 2);
+    s.append("  |\\|/|\\|/|\\|/|\\|/|\n");
+    printLine(s, 3);
+    s.append("  |/|\\|/|\\|/|\\|/|\\|\n");
+    printLine(s, 4);
+    s.append('\n');
+}
+
+void Game::printLine(String& s, int index) const {
+    static const char map[] = {
+        '#', 'O', '.'
+    };
+    s.append(index + '0').append(' ');
+    for(int x = 0; x < 8; x++) {
+        if(x == activeX && index == activeY) {
+            s.append("*-");
+            continue;
+        }
+        s.append(map[fields[x][index]]).append('-');
+    }
+    if(8 == activeX && index == activeY) {
+        s.append('*');
+    } else {
+        s.append(map[fields[8][index]]);
+    }
+    s.append('\n');
+}
+
+void Game::parse(const String& input, String& output) {
+    if(input.getLength() <= 2 || !isDigit(input[0]) || input[1] != ' ' || !isDigit(input[2])) {
+        revertToSelection(output);
+        return;
+    }
+    uint x = input[0] - '0';
+    uint y = input[2] - '0';
+    if(x >= 9 || y >= 6) {
+        revertToSelection(output);
+        return;
+    }
+    if(activeX == -1 || activeY == -1) {
+        markActive(x, y, output);
+    } else {
+        move(x, y, output);
+    }
+}
+
+bool Game::isDigit(char c) const {
+    return c >= '0' && c <= '9';
+}
+
+void Game::markActive(int x, int y, String& output) {
+    if(fields[x][y] != FieldState::WHITE) {
+        revertToSelection(output);
+        return;
+    }
+    activeX = x;
+    activeY = y;
+    print(output);
+    output.append("Human Color: White(O)\nAIColor: Black(#)\nselect location to move: ");
+}
+
+void Game::move(int x, int y, String& output) {
+    if(fields[x][y] != FieldState::EMPTY || !areNeighbours(activeX, activeY, x, y)) {
+        revertToSelection(output);
+        return;
+    }
+    fields[activeX][activeY] = FieldState::EMPTY;
+    fields[x][y] = FieldState::WHITE;
+    removeLine(x, y, activeX, activeY, FieldState::BLACK);
+    removeLine(activeX, activeY, x, y, FieldState::BLACK);
+    activeX = x;
+    activeY = y;
+    print(output);
+    output.append("Human Color: White(O)\nAIColor: Black(#)\n");
+    print(output);
+    output.append("Human Color: White(O)\nAIColor: Black(#)\nAI turn\n");
+    activeX = -1;
+    activeY = -1;
+    botMove(output);
+}
+
+bool Game::areNeighbours(int x, int y, int x2, int y2) const {
+    if(x == x2 && y == y2) {
+        return false;
+    }
+    int diffX = x - x2;
+    int diffY = y - y2;
+    if(diffX < -1 || diffX > 1 || diffY < -1 || diffY > 1) {
+        return false;
+    }
+    if(diffX == 0 || diffY == 0) {
+        return true;
+    }
+    return !((x & 1) ^ (y & 1));
+}
+
+void Game::revertToSelection(String& output) {
+    activeX = -1;
+    activeY = -1;
+    print(output);
+    output.append("Human Color: White(O)\nAIColor: Black(#)\nHuman turn\nselect stone: ");
+}
+
+void Game::removeLine(int x, int y, int x2, int y2, FieldState remove) {
+    int diffX = x2 - x;
+    int diffY = y2 - y;
+    while(true) {
+        x2 += diffX;
+        y2 += diffY;
+        if(x2 < 0 || x2 >= 9 || y2 < 0 || y2 >= 5 || fields[x2][y2] != remove) {
+            return;
+        }
+        fields[x2][y2] = FieldState::EMPTY;
+    }
+}
+
+void Game::botMove(String& output) {
+    while(true) {
+        int x = 0;
+        int y = 0;
+        while(true) {
+            x = rand() % 9;
+            y = rand() % 5;
+            if(fields[x][y] == FieldState::EMPTY) {
+                break;
+            }
+        }
+        for(uint i = 0; i < 10; i++) {
+            int mx = x + (rand() % 3) - 1;
+            int my = y + (rand() % 3) - 1;
+            if((mx == x && my == y) || mx < 0 || mx >= 9 || my < 0 || my >= 5 || fields[mx][my] != FieldState::BLACK) {
+                continue;
+            }
+            fields[mx][my] = FieldState::EMPTY;
+            fields[x][y] = FieldState::BLACK;
+            removeLine(mx, my, x, y, FieldState::WHITE);
+            removeLine(x, y, mx, my, FieldState::WHITE);
+            
+            activeX = x;
+            activeY = y;
+            print(output);
+            activeX = -1;
+            activeY = -1;
+            print(output);
+            revertToSelection(output);
+            return;
+        }
+    }
+}

+ 36 - 0
Game.h

@@ -0,0 +1,36 @@
+#ifndef GAME_H
+#define GAME_H
+
+#include "String.h"
+#include "Types.h"
+
+class Game final {
+public:
+
+    enum FieldState {
+        BLACK, WHITE, EMPTY
+    };
+    
+    Game();
+    
+    void reset(String& output);
+    void parse(const String& input, String& output);
+
+private:
+    bool isDigit(char c) const;
+    void reset();
+    void print(String& s) const;
+    void printLine(String& s, int index) const;
+    void markActive(int x, int y, String& output);
+    void move(int x, int y, String& output);
+    void removeLine(int x, int y, int x2, int y2, FieldState remove);
+    void revertToSelection(String& output);
+    bool areNeighbours(int x, int y, int x2, int y2) const;
+    void botMove(String& output);
+    
+    FieldState fields[9][5];
+    int activeX;
+    int activeY;
+};
+
+#endif

+ 32 - 0
Main.cpp

@@ -0,0 +1,32 @@
+#include <iostream>
+#include <array>
+
+#include "Socket.h" 
+#include "Client.h"
+#include "Game.h" 
+
+int main() {
+    Socket socket;
+    if(socket.start(4455)) {
+        return 0;
+    }
+    std::array<Client, 20> clients;
+    while(true) {
+        int clientId = socket.waitForClients(100);
+        if(clientId != -1) {
+            for(Client& client : clients) {
+                if(client.isFree()) {
+                    client.start(clientId);
+                    break;
+                }
+            }
+            std::cout << "client added" << "\n";
+        }
+
+        char buffer[256];
+        if(socket.readConsole(buffer, 256, 100)) {
+            break;
+        }
+    }
+    return 0;
+}

+ 89 - 0
Socket.cpp

@@ -0,0 +1,89 @@
+#include <iostream>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <vector>
+
+#include "Socket.h"
+#include "Utils.h"
+
+Socket::Socket() : id(-1) {
+}
+
+Socket::~Socket() {
+    if(id != -1 && close(id) == -1) {
+        perror("cannot close socket");
+    }
+}
+
+bool Socket::start(uint16_t port) {
+    id = socket(AF_INET, SOCK_STREAM, 0);
+    if(id == -1) {
+        perror("cannot create socket");
+        return true;
+    }
+
+    linger lin;
+    lin.l_onoff = 1;
+    lin.l_linger = 0;
+    setsockopt(id, SOL_SOCKET, SO_LINGER, &lin, sizeof (lin));
+
+    struct sockaddr_in address;
+    address.sin_family = AF_INET;
+    address.sin_addr.s_addr = INADDR_ANY;
+    address.sin_port = htons(port);
+
+    if(bind(id, reinterpret_cast<sockaddr*> (&address), sizeof (sockaddr_in)) == -1) {
+        perror("cannot bind socket");
+        return true;
+    }
+
+    if(listen(id, 10) == -1) {
+        perror("cannot start listening on socket");
+        return true;
+    }
+
+    return false;
+}
+
+int Socket::waitForClients(int timeoutMillis) const {
+    struct sockaddr_in clientAddress;
+    socklen_t length = sizeof (sockaddr_in);
+
+    int pollResult = Utils::pollFileDescriptor(id, timeoutMillis, "cannot poll socket");
+    if(pollResult <= 0) {
+        return -1;
+    }
+
+    int clientId = accept(id, reinterpret_cast<sockaddr*> (&clientAddress), &length);
+    if(clientId == -1) {
+        perror("cannot accept client connection");
+        return -1;
+    }
+    return clientId;
+}
+
+bool Socket::readConsole(char* buffer, size_t bufferLength, int timeoutMillis) const {
+    if(bufferLength == 0) {
+        return false;
+    }
+    buffer[0] = '\0';
+
+    int pollResult = Utils::pollFileDescriptor(STDIN_FILENO, timeoutMillis, "cannot poll standard input");
+    if(pollResult <= 0) {
+        return false;
+    }
+
+    size_t index = 0;
+    while(index < bufferLength) {
+        buffer[index] = getchar();
+        if(buffer[index] == '\n') {
+            buffer[index] = '\0';
+            break;
+        }
+        index++;
+    }
+    return true;
+}

+ 21 - 0
Socket.h

@@ -0,0 +1,21 @@
+#ifndef SOCKET_H
+#define SOCKET_H
+
+class Socket final {
+public:
+    Socket();
+    ~Socket();
+    Socket(const Socket& other) = delete;
+    Socket(Socket&& other) = delete;
+    Socket& operator=(const Socket& other) = delete;
+    Socket& operator=(Socket&& other) = delete;
+    
+    bool start(uint16_t port);
+    int waitForClients(int timeoutMillis) const;
+    bool readConsole(char* buffer, size_t bufferLength, int timeoutMillis) const;
+    
+private:
+    int id;
+};
+
+#endif

+ 48 - 0
String.cpp

@@ -0,0 +1,48 @@
+#include <iostream>
+
+#include <unistd.h>
+#include <sys/socket.h>
+
+#include "String.h"
+#include "Types.h"
+
+String::String() : length(0) {
+    data[0] = '\0';
+}
+
+String::operator const char*() const {
+    return data;
+}
+
+String& String::append(char c) {
+    if(length + 1 < LENGTH) {
+        data[length++] = c;
+        data[length] = '\0';
+    }
+    return *this;
+}
+
+String& String::append(const char* str) {
+    for(uint i = 0; length + 1 < LENGTH && str[i] != '\0'; length++, i++) {
+        data[length] = str[i];
+    }
+    data[length] = '\0';
+    return *this;
+}
+
+bool String::receiveFromSocket(int socket) {
+    ssize_t result = recv(socket, data, LENGTH, MSG_DONTWAIT);
+    if(result == -1) {
+        perror("cannot receive from socket");
+        return true;
+    } else if(result == 0) {
+        return true;
+    }
+    data[result - 1] = '\0';
+    length = result - 1;
+    return false;
+}
+
+uint String::getLength() const {
+    return length;
+}

+ 22 - 0
String.h

@@ -0,0 +1,22 @@
+#ifndef STRING_H
+#define STRING_H
+
+#include "Types.h"
+
+class String {
+public:
+    String();
+    operator const char*() const;
+    String& append(char c);
+    String& append(const char* str);
+    bool receiveFromSocket(int socket);
+    
+    uint getLength() const;
+    
+private:
+    static const uint LENGTH = 2048;
+    uint length;
+    char data[LENGTH];
+};
+
+#endif

+ 6 - 0
Types.h

@@ -0,0 +1,6 @@
+#ifndef TYPES_H
+#define TYPES_H
+
+typedef unsigned int uint;
+
+#endif

+ 18 - 0
Utils.cpp

@@ -0,0 +1,18 @@
+#include <iostream>
+
+#include <poll.h>
+
+#include "Utils.h"
+
+int Utils::pollFileDescriptor(int fd, int timeoutMillis, const char* error) {
+    struct pollfd fds;
+    fds.fd = fd;
+    fds.events = POLLIN;
+    fds.revents = 0;
+
+    int pollResult = poll(&fds, 1, timeoutMillis);
+    if(pollResult == -1) {
+        perror(error);
+    }
+    return pollResult;
+}

+ 8 - 0
Utils.h

@@ -0,0 +1,8 @@
+#ifndef UTILS_H
+#define UTILS_H
+
+namespace Utils {
+    int pollFileDescriptor(int fd, int timeoutMillis, const char* error);
+}
+
+#endif

+ 10 - 0
meson.build

@@ -0,0 +1,10 @@
+project('fanorona server bot', 'cpp')
+
+sources = ['Main.cpp', 'Socket.cpp', 'Client.cpp', 'Utils.cpp', 'Game.cpp', 'String.cpp']
+
+threadDep = dependency('threads')
+
+executable('fanorona_server_bot', 
+    sources: sources,
+    dependencies : [threadDep],
+    cpp_args: ['-Wall', '-Wextra', '-pedantic', '-Werror'])