diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6502eb1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,55 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +.idea/* +cmake-build-debug/* \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..59fb7e4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "cereal"] + path = cereal + url = git@github.com:USCiLab/cereal.git +[submodule "googletest"] + path = googletest + url = git@github.com:google/googletest.git +[submodule "dtl"] + path = dtl + url = git@github.com:cubicdaiya/dtl.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..996cc90 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.6) + +project(Battleship) +enable_language(ASM_NASM) + +set(CMAKE_CXX_STANDARD 14) + +add_subdirectory(googletest) + +add_subdirectory(src) + +add_subdirectory(test) + + + diff --git a/README.md b/README.md index 0d7ba70..8f04e76 100644 --- a/README.md +++ b/README.md @@ -1 +1,79 @@ -CSCI_366_Programming_Assignments +# CSCI 466 PA2 - Bit Board Storage + +## Instructions + + +Complete the following assignment individually. +Submit your work on D2L into the "Programming Assignment 2" folder. + + +## Learning Objectives + +In this programming assignment you will: + +- Implement a class to store the setup board as bits. The core functionality of your class will be implemented in the + Netwide Assembler (NASM). + + +## Overview + +Your objective is to extend your basic implementation of the +[Battleship](https://en.wikipedia.org/wiki/Battleship_\(game\)) +game to store the player setup boards in two dimensional bit arrays. +The core of the idea is that the `Server` reads in the player setup boards and instead of referencing the file to + evaluate each shot, it saves the board in internal memory. +You might wonder why not store the board in a two dimensional vector? +Well, since all we want to store is whether a ship is present in a given position, using a vector, even just of bytes, still uses 8 bits for every ship position. +We want to get this down to one bit. +Of course you could use something like an array of [bitsets](http://www.cplusplus.com/reference/bitset/bitset/), but + you can save even more memory by implementing a two dimensional bit storage yourself in assembly. +For very large data sets, or on very memory-constrained devices, every bit counts! + +To complete this assignment you will extend your current implementation of Battleship. +I have added some functions that you'll need to implement, but most of your `Server` code should carry over. +Your `Client` code will remain unchanged. +I have also added a few tests to check the new functionality, while keeping all the old tests in tact. +So, if you have those working, great! +If not, you can earn back some points by getting your code to pass the tests you missed in PA1. + + +## Program Invocation + +To play Battleship you should first start the server by running the `run-server` executable. +Then start the player clients by running two instances of the `run-client` executable. +Unfortunately, the client and server executables will not do anything interesting until you implement `Client.cpp` +and `Server.cpp`. +As you progress in these implementations, your code will pass more and more tests in `tests.cpp`. +When your code passes all the tests, you will be able to run the client and server executables to play the game. + + +## Bonus + +I will award __one bonus point__ for each of the following: + +* Server sends a different result when a ship is sunk. The client implementation notifies the player when they sink a + ship (implementation and tests). + This will require you to store more than 1 bit in the setup board array - you will also need to store ship type. + + +## What to Submit + +Submit your work on D2L into the "Programming Assignment 2" folder. + +* A text file containing a link to a GitHub repository with your solution for the base assignment. +Please not that your repository should be private, so that other students cannot see your solutions. +You can create an unlimited number of private repositories by upgrading your github account to the pro version for + free using the [academic discount](https://help.github.com/en/github/teaching-and-learning-with-github-education/applying-for-an-educator-or-researcher-discount) with your school email. +In order for us to be able to see your solution, please add github users `cooperstrahan` and `mwittie` as + collaborators through repository settings. + +* If you implement the bonus solution, please submit a YouTube video of your implementation. +Be sure to show the tests you have written as well as your assembly code. + + +## Grading + +We will grade your submissions based on how many test cases in `tests.cpp` your solution passes. + + + diff --git a/cereal b/cereal new file mode 160000 index 0000000..a5a3095 --- /dev/null +++ b/cereal @@ -0,0 +1 @@ +Subproject commit a5a30953125e70b115a28dd76b64adf3c97cc883 diff --git a/dtl b/dtl new file mode 160000 index 0000000..6b030d6 --- /dev/null +++ b/dtl @@ -0,0 +1 @@ +Subproject commit 6b030d6397909e7658cf785f369f647f40c046c1 diff --git a/googletest b/googletest new file mode 160000 index 0000000..8b4817e --- /dev/null +++ b/googletest @@ -0,0 +1 @@ +Subproject commit 8b4817e3df3746a20502a84580f661ac448821be diff --git a/images/system_architecture.png b/images/system_architecture.png new file mode 100644 index 0000000..d187f89 Binary files /dev/null and b/images/system_architecture.png differ diff --git a/player_1.setup_board.txt b/player_1.setup_board.txt new file mode 100644 index 0000000..64e30ab --- /dev/null +++ b/player_1.setup_board.txt @@ -0,0 +1,10 @@ +CCCCC_____ +BBBB______ +RRR_______ +SSS_______ +D_________ +D_________ +__________ +__________ +__________ +__________ diff --git a/player_2.setup_board.txt b/player_2.setup_board.txt new file mode 100644 index 0000000..45285e5 --- /dev/null +++ b/player_2.setup_board.txt @@ -0,0 +1,10 @@ +C________D +C_________ +C_________ +C_________ +CBBBB_____ +_______RRR +_________S +_________S +_________S +D_________ diff --git a/src/BitArray2D.asm b/src/BitArray2D.asm new file mode 100644 index 0000000..c1d3cb9 --- /dev/null +++ b/src/BitArray2D.asm @@ -0,0 +1,38 @@ + + global set_bit_elem + global get_bit_elem + section .text + +set_bit_elem: + push rbp ; save the base pointer on the stack (at rsp+8) + mov rbp, rsp ; set up the rbp for the bottom of this frame + + ; rdi contains array pointer + ; rsi contains row width + ; rdx contains row + ; rcx contains col + + ; add your code here + + mov rsp, rbp ; restore stack pointer to before we pushed parameters onto the stack + pop rbp ; remove rbp from the stack to restore rsp to initial value + ret ; return value in rax + + + + +get_bit_elem: + push rbp ; save the base pointer on the stack (at rsp+8) + mov rbp, rsp ; set up the rbp for the bottom of this frame + + ; rdi contains array pointer + ; rsi contains row width + ; rdx contains row + ; rcx contains col + + ; add your code here - for now returning 0 + mov rax, 0 + + mov rsp, rbp ; restore stack pointer to before we pushed parameters onto the stack + pop rbp ; remove rbp from the stack to restore rsp to initial value + ret ; return value in rax diff --git a/src/BitArray2D.cpp b/src/BitArray2D.cpp new file mode 100644 index 0000000..984f7a6 --- /dev/null +++ b/src/BitArray2D.cpp @@ -0,0 +1,44 @@ +// Battleship game assignment for MSU CSCI 366 +// Copyright (C) 2020 Mike P. Wittie +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include "BitArray2D.hpp" + +BitArray2D::BitArray2D(unsigned int rows, unsigned int columns) { + +} + + +BitArray2D::~BitArray2D() { + +} + + +bool BitArray2D::get(unsigned int row, unsigned int column){ + // check array bounds + + // get the element + return get_bit_elem(array, columns, row, column); +} + + + +void BitArray2D::set(unsigned int row, unsigned int column){ + // check array bounds + + // set the element + set_bit_elem(array, columns, row, column); +} diff --git a/src/BitArray2D.hpp b/src/BitArray2D.hpp new file mode 100644 index 0000000..1efc7c0 --- /dev/null +++ b/src/BitArray2D.hpp @@ -0,0 +1,110 @@ +// Battleship game assignment for MSU CSCI 366 +// Copyright (C) 2020 Mike P. Wittie +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + + +#include +#include + +#ifndef BATTLESHIP_BITARRAY2D_HPP +#define BATTLESHIP_BITARRAY2D_HPP + +using namespace std; + +class BitArray2DException: public exception +{ +private: + char *cstr; + +public: + BitArray2DException(string message){ + cstr = new char[message.size() + 1]; + message.copy(cstr, message.size() + 1); + cstr[message.size()] = '\0'; + } + + ~BitArray2DException(){ + delete cstr; + } + + virtual const char* what() const throw(){ + return cstr; + } +}; + + +/** + * Sets a bit in a two dimensional bit array + * @param array - pointer to a bit array + * @param row_width - the number of bits in a row of the two dimensional array + * @param row - row index (0 indexed) + * @param col - column index (0 indexed) + */ +extern "C" void set_bit_elem(char *array, unsigned int row_width, unsigned int row, unsigned int col); + +/** + * Gets a bit in a two dimensional bit array + * @param array - pointer to a bit array + * @param row_width - the number of bits in a row of the two dimensional array + * @param row - row index (0 indexed) + * @param col - column index (0 indexed) + * @return a boolean containing the bit at [row][column] + */ +extern "C" bool get_bit_elem(char *array, unsigned int row_width, unsigned int row, unsigned int col); + + +class BitArray2D { +private: + /** + * The 'char' types does not mean that each bit is stored as a character - we just need the pointer + * to have some type since there is no type for bit. + */ + char* array = nullptr; + + unsigned int rows; + unsigned int columns; + +public: + /** + * Sets up the array to store rows * columns bits + * @param rows - number of rows + * @param columns - number of columns + */ + BitArray2D(unsigned int rows, unsigned int columns); + + /** + * Deallocate memory used for array + */ + ~BitArray2D(); + + /** + * Get bit at row and column + * @param row + * @param column + * @return bit at row and column as bool + */ + bool get(unsigned int row, unsigned int column); + + /** + * Set bit to true at row and column + * @param row + * @param column + */ + void set(unsigned int row, unsigned int column); +}; + + +#endif //BATTLESHIP_BIT_ARRAY_HPP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..409d93e --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,19 @@ +#copy the board files into runtime directory +configure_file(${PROJECT_SOURCE_DIR}/player_1.setup_board.txt ${PROJECT_SOURCE_DIR}/cmake-build-debug/src/player_1.setup_board.txt COPYONLY) +configure_file(${PROJECT_SOURCE_DIR}/player_2.setup_board.txt ${PROJECT_SOURCE_DIR}/cmake-build-debug/src/player_2.setup_board.txt COPYONLY) + +# convert player setup boards to unix line endings +execute_process(COMMAND dos2unix ${PROJECT_SOURCE_DIR}/cmake-build-debug/src/player_1.setup_board.txt) +execute_process(COMMAND dos2unix ${PROJECT_SOURCE_DIR}/cmake-build-debug/src/player_2.setup_board.txt) + +include_directories(${PROJECT_SOURCE_DIR}/cereal/include) + +set_source_files_properties(BitArray2D.asm PROPERTIES COMPILE_FLAGS "-g -Fdwarf") + +add_library(SERVER Server.cpp Server.hpp BitArray2D.cpp BitArray2D.hpp BitArray2D.asm) + +add_library(CLIENT Client.cpp Client.hpp) + +add_executable(run_server server_main.cpp Server.cpp BitArray2D.cpp BitArray2D.hpp BitArray2D.asm) + +add_executable(run_client client_main.cpp Client.cpp BitArray2D.cpp BitArray2D.hpp BitArray2D.asm) diff --git a/src/Client.cpp b/src/Client.cpp new file mode 100644 index 0000000..0b2835e --- /dev/null +++ b/src/Client.cpp @@ -0,0 +1,46 @@ +// Battleship game assignment for MSU CSCI 366 +// Copyright (C) 2020 Mike P. Wittie +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "common.hpp" +#include "Client.hpp" + +Client::~Client() { +} + + +void Client::initialize(unsigned int player, unsigned int board_size){ +} + + +void Client::fire(unsigned int x, unsigned int y) { +} + + +bool Client::result_available() { +} + + +int Client::get_result() { +} + + + +void Client::update_action_board(int result, unsigned int x, unsigned int y) { +} + + +string Client::render_action_board(){ +} \ No newline at end of file diff --git a/src/Client.hpp b/src/Client.hpp new file mode 100644 index 0000000..5777ebd --- /dev/null +++ b/src/Client.hpp @@ -0,0 +1,110 @@ +// Battleship game assignment for MSU CSCI 366 +// Copyright (C) 2020 Mike P. Wittie +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include +#include + +using namespace std; + +class ClientWrongPlayerNumberException: public exception{ +public: + virtual const char* what() const throw(){ + return "Player number our of bounds."; + } +}; + +class ClientException: public exception +{ +private: + char *cstr; + +public: + ClientException(string message){ + cstr = new char[message.size() + 1]; + message.copy(cstr, message.size() + 1); + cstr[message.size()] = '\0'; + } + + ~ClientException(){ + delete cstr; + } + + virtual const char* what() const throw(){ + return cstr; + } +}; + +class Client { +private: + unsigned int player; + string board_name; +public: + unsigned int board_size; + bool initialized = false; + +public: + /** + * Destructor to remove the action board created in initialize() + */ + ~Client(); + + /** + * Performs the initialization functions that normally would be in a constructor, but needs to be a separate + * function for googletest so clients may be initialized in the SetUp() function. + * Creates player_#.action_board.json. + * @param player - the id of the player + * @param board_size - the square size of the action board + */ + void initialize(unsigned int player, unsigned int board_size); + + /** + * Fires a shot on the coordinate target and creates a player_#.shot.json file + * @param x - coordinate + * @param y - coordinate + */ + void fire(unsigned int x, unsigned int y); + + /** + * Checks if a result file is available for + * @return true if result is available, false otherwise + */ + bool result_available(); + + /** + * Gets the result from the player_#.result.json + * @return the result as either HIT, MISS, or OUT_OF_BOUNDS + */ + int get_result(); + + /** + * Updates the internal representation of player_#.action_board.json on the result of a shot. + * @param result - the result returned from the server + * @param x - coordinate + * @param y - coordinate + */ + void update_action_board(int result, unsigned int x, unsigned int y); + + /** + * Formats a string representing player_#.action_board.json as ASCII + * @return ASCII representation of the action board + */ + string render_action_board(); + +}; + + diff --git a/src/Server.cpp b/src/Server.cpp new file mode 100644 index 0000000..6429ab7 --- /dev/null +++ b/src/Server.cpp @@ -0,0 +1,51 @@ +// Battleship game assignment for MSU CSCI 366 +// Copyright (C) 2020 Mike P. Wittie +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#include "common.hpp" +#include "Server.hpp" + + +/** + * Calculate the length of a file (helper function) + * + * @param file - the file whose length we want to query + * @return length of the file in bytes + */ +int get_file_length(ifstream *file){ +} + + +void Server::initialize(unsigned int board_size, + string p1_setup_board, + string p2_setup_board){ +} + + +Server::~Server() { +} + + +BitArray2D *Server::scan_setup_board(string setup_board_name){ +} + +int Server::evaluate_shot(unsigned int player, unsigned int x, unsigned int y) { +} + + +int Server::process_shot(unsigned int player) { + return NO_SHOT_FILE; +} \ No newline at end of file diff --git a/src/Server.hpp b/src/Server.hpp new file mode 100644 index 0000000..91d8e12 --- /dev/null +++ b/src/Server.hpp @@ -0,0 +1,116 @@ +// Battleship game assignment for MSU CSCI 366 +// Copyright (C) 2020 Mike P. Wittie +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#include +#include +#include +#include +#include +#include + +#include + +#include "BitArray2D.hpp" + +#define SHOT_FILE_PROCESSED 0 +#define NO_SHOT_FILE 1 + +#define SHIPS "CBRSD" //Carrier, Battleship, cRuiser, Submarine, and Destroyer + +using namespace std; + + +class ServerException: public exception +{ +private: + char *cstr; + +public: + ServerException(string message){ + cstr = new char[message.size() + 1]; + message.copy(cstr, message.size() + 1); + cstr[message.size()] = '\0'; + } + + ~ServerException(){ + delete cstr; + } + + virtual const char* what() const throw(){ + return cstr; + } +}; + +class Server{ +public: + unsigned int board_size; + +private: + + BitArray2D *p1_setup_board = nullptr; + BitArray2D *p2_setup_board = nullptr; + + /** + * Sets up a BitArray2D pointer with bits filled in based on a setup_board_name + * @param setup_board_name - the name of the setup board file + * @return an internally allocated pointer to a BitArray2D object + */ + BitArray2D *scan_setup_board(string setup_board_name); + +public: + /** + * Performs Server initialization + * + * Need to do initialization outside of the constructor, so that the object may be initialized inside the + * googletest SetUp() method. + * The method opens player setup boards containing ship positions and checks the size of boards compared to + * board_size parameter. + * @param board_size - the size of a square board + * @param p1_setup_board - file name of player 1's board + * @param p2_setup_board - file name of player 2's board + */ + void initialize(unsigned int board_size, + string p1_setup_board, + string p2_setup_board); + + /** + * Checks the coordinates of a shot against setup board of player + * + * Check that player number within bounds, checks that shot coordinates within bounds, determines if the shot + * results in a HIT, or a MISS. + * @param player - player number + * @param x - coordinate + * @param y - coordinate + * @return returns shot result as either HIT, MISS, or OUT_OF_BOUNDS + */ + int evaluate_shot(unsigned int player, unsigned int x, unsigned int y); + + /** + * Processes a shot issued by player + * + * Gets the shot from player, extracts coordinates, passes the information of evaluate_shot, and writes the result + * into player_#.result.json. + * @param player - player number + * @return returns SHOT_PROCESSED, or NO_SHOT_FILE if nothing to process + */ + int process_shot(unsigned int player); + + /** + * Deallocates Server memory + */ + ~Server(); +}; diff --git a/src/client_main.cpp b/src/client_main.cpp new file mode 100644 index 0000000..153c464 --- /dev/null +++ b/src/client_main.cpp @@ -0,0 +1,70 @@ +// Battleship game assignment for MSU CSCI 366 +// Copyright (C) 2020 Mike P. Wittie +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#include "common.hpp" +#include "Client.hpp" + +#include +#include + +int main() +{ + printf("Starting Battleship client\n"); + + // initialize the client with a correct player number + Client c; + while(! c.initialized){ + try{ + cout << "Enter player number: "; + int player; + cin >> player; + c.initialize(player, BOARD_SIZE); + } catch (ClientWrongPlayerNumberException& e){ + cout << e.what() << '\n'; + } + + } + + // keep firing and updating the action board with results from the server + unsigned int x, y; + while(true){ + // get coordinates + cout << "Enter fire x position: "; + cin >> x; + cout << "Enter fire y position: "; + cin >> y; + + // fire and get result + c.fire(x, y); + while(! c.result_available()) { + cout << "Waiting for result" << endl; + this_thread::sleep_for(std::chrono::milliseconds(2000)); + } + int result = c.get_result(); + printf("Shot at (%d, %d) result %d\n", x, y, result); + if(result == OUT_OF_BOUNDS){ + cout << "Entered coordinates are out of bounds" << "\n"; + continue; + } + + // update the action board + c.update_action_board(result, x, y); + cout << c.render_action_board(); + } + + return 0; +} diff --git a/src/common.hpp b/src/common.hpp new file mode 100644 index 0000000..2c05682 --- /dev/null +++ b/src/common.hpp @@ -0,0 +1,27 @@ +// Battleship game assignment for MSU CSCI 366 +// Copyright (C) 2020 Mike P. Wittie +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef BATTLESHIP_COMMON_H +#define BATTLESHIP_COMMON_H + +#define MAX_PLAYERS 2 +#define HIT 1 +#define MISS -1 +#define OUT_OF_BOUNDS 0 + +#define BOARD_SIZE 10 + +#endif //BATTLESHIP_COMMON_H diff --git a/src/server_main.cpp b/src/server_main.cpp new file mode 100644 index 0000000..121643a --- /dev/null +++ b/src/server_main.cpp @@ -0,0 +1,44 @@ +// Battleship game assignment for MSU CSCI 366 +// Copyright (C) 2020 Mike P. Wittie +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "common.hpp" +#include "Server.hpp" + +#include +#include + + +int main(){ + cout << "Starting Battleship server" << endl; + + Server s; + s.initialize(BOARD_SIZE, "player_1.setup_board.txt", "player_2.setup_board.txt"); + + // run the server process in a loop + while(true){ + while(s.process_shot(1) == NO_SHOT_FILE) { + cout << "Waiting for shot from player 1" << endl; + this_thread::sleep_for(std::chrono::milliseconds(2000)); + } + cout << "Processed shot from player 1" << endl; + while(s.process_shot(2) == NO_SHOT_FILE) { + cout << "Waiting for shot from player 2" << endl; + this_thread::sleep_for(std::chrono::milliseconds(2000)); + } + cout << "Processed shot from player 2" << endl; + } + +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..7323c9b --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,41 @@ +#copy the board files into runtime directory +configure_file(${PROJECT_SOURCE_DIR}/player_1.setup_board.txt ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/player_1.setup_board.txt COPYONLY) +configure_file(${PROJECT_SOURCE_DIR}/player_2.setup_board.txt ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/player_2.setup_board.txt COPYONLY) + +# copy test action boards into runtime directory +configure_file(${PROJECT_SOURCE_DIR}/test/correct_start_action_board.json + ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/correct_start_action_board.json + COPYONLY) +configure_file(${PROJECT_SOURCE_DIR}/test/correct_fire_message.json + ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/correct_fire_message.json + COPYONLY) +configure_file(${PROJECT_SOURCE_DIR}/test/correct_hit_action_board.json + ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/correct_hit_action_board.json + COPYONLY) +configure_file(${PROJECT_SOURCE_DIR}/test/correct_miss_action_board.json + ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/correct_miss_action_board.json + COPYONLY) +configure_file(${PROJECT_SOURCE_DIR}/test/correct_hit_result.json + ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/correct_hit_result.json + COPYONLY) +configure_file(${PROJECT_SOURCE_DIR}/test/correct_miss_result.json + ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/correct_miss_result.json + COPYONLY) +configure_file(${PROJECT_SOURCE_DIR}/test/correct_out_of_bounds_result.json + ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/correct_out_of_bounds_result.json + COPYONLY) + +# convert boards to unix line endings +execute_process(COMMAND dos2unix ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/player_1.setup_board.txt) +execute_process(COMMAND dos2unix ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/player_2.setup_board.txt) +execute_process(COMMAND dos2unix ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/correct_start_action_board.json) +execute_process(COMMAND dos2unix ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/correct_fire_message.json) +execute_process(COMMAND dos2unix ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/correct_hit_action_board.json) +execute_process(COMMAND dos2unix ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/correct_miss_action_board.json) +execute_process(COMMAND dos2unix ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/correct_hit_result.json) +execute_process(COMMAND dos2unix ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/correct_miss_result.json) +execute_process(COMMAND dos2unix ${PROJECT_SOURCE_DIR}/cmake-build-debug/test/correct_out_of_bounds_result.json) + +add_executable(battleship_tests tests.cpp) +include_directories(${PROJECT_SOURCE_DIR}/dtl/dtl ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/cereal/include) +target_link_libraries(battleship_tests SERVER CLIENT gtest gmock_main) \ No newline at end of file diff --git a/test/correct_fire_message.json b/test/correct_fire_message.json new file mode 100644 index 0000000..b1ecb78 --- /dev/null +++ b/test/correct_fire_message.json @@ -0,0 +1,4 @@ +{ + "x": 0, + "y": 1 +} \ No newline at end of file diff --git a/test/correct_hit_action_board.json b/test/correct_hit_action_board.json new file mode 100644 index 0000000..84172a1 --- /dev/null +++ b/test/correct_hit_action_board.json @@ -0,0 +1,124 @@ +{ + "board": [ + [ + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + ] +} \ No newline at end of file diff --git a/test/correct_hit_result.json b/test/correct_hit_result.json new file mode 100644 index 0000000..688ec1f --- /dev/null +++ b/test/correct_hit_result.json @@ -0,0 +1,3 @@ +{ + "result": 1 +} \ No newline at end of file diff --git a/test/correct_miss_action_board.json b/test/correct_miss_action_board.json new file mode 100644 index 0000000..67f68a0 --- /dev/null +++ b/test/correct_miss_action_board.json @@ -0,0 +1,124 @@ +{ + "board": [ + [ + -1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + ] +} \ No newline at end of file diff --git a/test/correct_miss_result.json b/test/correct_miss_result.json new file mode 100644 index 0000000..ca4f6be --- /dev/null +++ b/test/correct_miss_result.json @@ -0,0 +1,3 @@ +{ + "result": -1 +} \ No newline at end of file diff --git a/test/correct_out_of_bounds_result.json b/test/correct_out_of_bounds_result.json new file mode 100644 index 0000000..48777f7 --- /dev/null +++ b/test/correct_out_of_bounds_result.json @@ -0,0 +1,3 @@ +{ + "result": 0 +} \ No newline at end of file diff --git a/test/correct_result_message.json b/test/correct_result_message.json new file mode 100644 index 0000000..688ec1f --- /dev/null +++ b/test/correct_result_message.json @@ -0,0 +1,3 @@ +{ + "result": 1 +} \ No newline at end of file diff --git a/test/correct_start_action_board.json b/test/correct_start_action_board.json new file mode 100644 index 0000000..a6c495c --- /dev/null +++ b/test/correct_start_action_board.json @@ -0,0 +1,124 @@ +{ + "board": [ + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + ] +} \ No newline at end of file diff --git a/test/tests.cpp b/test/tests.cpp new file mode 100644 index 0000000..b9190e7 --- /dev/null +++ b/test/tests.cpp @@ -0,0 +1,424 @@ +// Battleship game assignment for MSU CSCI 366 +// Copyright (C) 2020 Mike P. Wittie +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + + +#include +#include +#include + +#include "common.hpp" +#include "Server.hpp" +#include "Client.hpp" + + + +/** + * Compute the diff distance between two files using the dtl library + * @param file1 - name of the first file + * @param file2 - name of the second file + * @return distance between the contents of two files + */ +#include // a library for diffing strings +long get_diff_dist(string file1, string file2){ + // load the file contents + ifstream ifstr1(file1); + string file1_S((std::istreambuf_iterator(ifstr1)), + std::istreambuf_iterator()); + ifstr1.close(); + ifstream ifstr2(file2); + string file2_S((std::istreambuf_iterator(ifstr2)), + std::istreambuf_iterator()); + ifstr2.close(); + + // compute the distance between correct and constructed boards + dtl::Diff d(file1_S, file2_S); + d.onOnlyEditDistance(); + d.compose(); + + return d.getEditDistance(); +} + + +TEST(BitArray2DCreate, Correct_1by1){ + BitArray2D *array; + ASSERT_NO_THROW(array = new BitArray2D(1,1)); +} + +TEST(BitArray2DCreate, Correct_10by10){ + BitArray2D *array; + ASSERT_NO_THROW(array = new BitArray2D(10,10)); +} + +TEST(BitArray2DCreate, Incorrect){ + BitArray2D *array; + ASSERT_ANY_THROW(array = new BitArray2D(0,0)); +} + + + + +class BitArray2DSetGet : public ::testing::Test{ +protected: + BitArray2D *array; + + void SetUp() override { + array = new BitArray2D(10, 10); + }; + + void TearDown() override{ + delete array; + } + +}; + +TEST_F(BitArray2DSetGet, Zero){ + int row=0, col=0; + array->set(row, col); + ASSERT_EQ(1, array->get(row,col)); +} + +TEST_F(BitArray2DSetGet, One){ + int row=0, col=1; + array->set(row, col); + ASSERT_EQ(1, array->get(row,col)); +} + +TEST_F(BitArray2DSetGet, Seven){ + int row=0, col=7; + array->set(row, col); + ASSERT_EQ(1, array->get(row,col)); +} + +TEST_F(BitArray2DSetGet, Eight){ + int row=0, col=8; + array->set(row, col); + ASSERT_EQ(1, array->get(row,col)); +} + +TEST_F(BitArray2DSetGet, Nine){ + int row=0, col=9; + array->set(row, col); + ASSERT_EQ(1, array->get(row,col)); +} + +TEST_F(BitArray2DSetGet, Ten){ + int row=9, col=1; + array->set(row, col); + ASSERT_EQ(1, array->get(row,col)); +} + +TEST_F(BitArray2DSetGet, Hundred){ + int row=9, col=9; + array->set(row, col); + ASSERT_EQ(1, array->get(row,col)); +} + +TEST_F(BitArray2DSetGet, Get_Out_Of_Bounds_High){ + ASSERT_ANY_THROW(array->get(BOARD_SIZE, BOARD_SIZE)); +} + +TEST_F(BitArray2DSetGet, Get_Out_Of_Bounds_Low){ + ASSERT_ANY_THROW(array->get(-1, -1)); +} + +TEST_F(BitArray2DSetGet, Set_Out_Of_Bounds_High){ + ASSERT_ANY_THROW(array->set(BOARD_SIZE, BOARD_SIZE)); +} + +TEST_F(BitArray2DSetGet, Set_Out_Of_Bounds_Low){ + ASSERT_ANY_THROW(array->set(-1, -1)); +} + +class ServerInitialize : public ::testing::Test{ +protected: + Server srv; +}; + +TEST_F(ServerInitialize, Correct_Board_Size){ + ASSERT_NO_THROW(srv.initialize(BOARD_SIZE, "player_1.setup_board.txt", "player_2.setup_board.txt")); +} + +TEST_F(ServerInitialize, Wrong_Board_Size){ + ASSERT_ANY_THROW(srv.initialize(BOARD_SIZE-1, "player_1.setup_board.txt", "player_2.setup_board.txt")); +} + +TEST_F(ServerInitialize, Bad_File_Name){ + ASSERT_ANY_THROW(srv.initialize(BOARD_SIZE, "", "")); +} + + +class ServerEvaluateShot : public ::testing::Test{ +protected: + Server srv; + void SetUp() override{ + srv.initialize(BOARD_SIZE, "player_1.setup_board.txt", "player_2.setup_board.txt"); + } +}; + +TEST_F(ServerEvaluateShot, Hit_Detected){ + ASSERT_EQ(HIT, srv.evaluate_shot(1,9,0)); +} + +TEST_F(ServerEvaluateShot, Miss_Detected){ + ASSERT_EQ(MISS, srv.evaluate_shot(1,9,1)); +} + +TEST_F(ServerEvaluateShot, Out_Of_Bounds_X_High){ + ASSERT_EQ(OUT_OF_BOUNDS, srv.evaluate_shot(1,srv.board_size+1,1)); +} + +TEST_F(ServerEvaluateShot, Out_Of_Bounds_X_Low){ + ASSERT_EQ(OUT_OF_BOUNDS, srv.evaluate_shot(1,-1,1)); +} + +TEST_F(ServerEvaluateShot, Out_Of_Bounds_Y_High){ + ASSERT_EQ(OUT_OF_BOUNDS, srv.evaluate_shot(1,1,srv.board_size+1)); +} + +TEST_F(ServerEvaluateShot, Out_Of_Bounds_Y_Low){ + ASSERT_EQ(OUT_OF_BOUNDS, srv.evaluate_shot(1,1,-1)); +} + +TEST_F(ServerEvaluateShot, Max_In_Bounds){ + ASSERT_NO_THROW(srv.evaluate_shot(1,srv.board_size-1,srv.board_size-1)); +} + +TEST_F(ServerEvaluateShot, Bad_Player_Number_Low){ + ASSERT_ANY_THROW(srv.evaluate_shot(0,0,0)); +} + +TEST_F(ServerEvaluateShot, Bad_Player_Number_High){ + ASSERT_ANY_THROW(srv.evaluate_shot(MAX_PLAYERS+1,0,0)); +} + + +class ServerProcessShot : public ::testing::Test{ +protected: + Server srv; + + void set_up_shot(unsigned int x, unsigned int y){ + string coords = "{\"x\": "+to_string(x)+",\"y\": "+to_string(y)+"}"; + ofstream shot_file("player_1.shot.json"); + shot_file << coords; + shot_file.close(); + } + + void SetUp() override{ + srv.initialize(BOARD_SIZE, "player_1.setup_board.txt", "player_2.setup_board.txt"); + } + + void TearDown() override{ + remove("player_1.shot.json"); + remove("player_1.result.json"); + } +}; + + +TEST_F(ServerProcessShot, Hit_Detected){ + set_up_shot(0, 1); + srv.process_shot(1); + ASSERT_EQ(0, get_diff_dist("correct_hit_result.json", "player_1.result.json")); +} + +TEST_F(ServerProcessShot, Miss_Detected){ + set_up_shot(1, 1); + srv.process_shot(1); + ASSERT_EQ(0, get_diff_dist("correct_miss_result.json", "player_1.result.json")); +} + +TEST_F(ServerProcessShot, Out_Of_Bounds_X){ + set_up_shot(srv.board_size, 0); + srv.process_shot(1); + ASSERT_EQ(0, get_diff_dist("correct_out_of_bounds_result.json", "player_1.result.json")); +} + +TEST_F(ServerProcessShot, Out_Of_Bounds_Y){ + set_up_shot(0, srv.board_size); + srv.process_shot(1); + ASSERT_EQ(0, get_diff_dist("correct_out_of_bounds_result.json", "player_1.result.json")); +} + +TEST_F(ServerProcessShot, Max_In_Bounds){ + set_up_shot(srv.board_size-1, srv.board_size-1); + ASSERT_NO_THROW(srv.process_shot(1)); +} + +TEST_F(ServerProcessShot, Bad_Player_Number_Low){ + set_up_shot(0, 0); + ASSERT_ANY_THROW(srv.process_shot(0)); +} + +TEST_F(ServerProcessShot, Bad_Player_Number_Low_High){ + set_up_shot(0, 0); + ASSERT_ANY_THROW(srv.process_shot(MAX_PLAYERS+1)); +} + +TEST_F(ServerProcessShot, Cleanup){ + set_up_shot(0,0); + srv.process_shot(1); + ifstream f("player_1.shot.json"); + ASSERT_FALSE(f.good()); +} + + +class ClientInitialize : public ::testing::Test{ +protected: + Client client; + + void SetUp() override{ + client.initialize(1, BOARD_SIZE); + } + + void TearDown() override{ + remove("player_1.action_board.json"); + } +}; + +TEST_F(ClientInitialize, Creates_Action_Board){ + ASSERT_EQ(0, get_diff_dist("player_1.action_board.json", "correct_start_action_board.json")); +} + + +class ClientFire : public ::testing::Test{ +protected: + Client client; + + void SetUp() override{ + client.initialize(1, BOARD_SIZE); + } + + void TearDown() override{ + remove("player_1.shot.json"); + } +}; + +TEST_F(ClientFire, Creates_Fire_Message){ + client.fire(0,1); + ASSERT_EQ(0, get_diff_dist("player_1.shot.json", "correct_fire_message.json")); +} + + +class ClientResultAvailable : public ::testing::Test{ +protected: + Client client; + + void set_up_result(int result){ + string result_str = "{\n" + " \"result\": "+to_string(result)+"\n" + "}"; + ofstream result_file("player_1.result.json"); + result_file << result_str; + result_file.close(); + } + + void SetUp() override{ + client.initialize(1, BOARD_SIZE); + } + + void TearDown() override{ + remove("player_1.result.json"); + } +}; + +TEST_F(ClientResultAvailable, NoResultFile){ + ASSERT_FALSE(client.result_available()); +} + +TEST_F(ClientResultAvailable, GoodFile){ + set_up_result(HIT); + ASSERT_TRUE(client.result_available()); +} + + +class ClientGetResult : public ::testing::Test{ +protected: + Client client; + + void set_up_result(int result){ + string result_str = "{\n" + " \"result\": "+to_string(result)+"\n" + "}"; + ofstream result_file("player_1.result.json"); + result_file << result_str; + result_file.close(); + } + + void SetUp() override{ + client.initialize(1, BOARD_SIZE); + } + + void TearDown() override{ + remove("player_1.result.json"); + } +}; + +TEST_F(ClientGetResult, Return_Hit){ + set_up_result(HIT); + ASSERT_EQ(HIT, client.get_result()); +} + +TEST_F(ClientGetResult, Return_Miss){ + set_up_result(MISS); + ASSERT_EQ(MISS, client.get_result()); +} + +TEST_F(ClientGetResult, Return_Out_Of_Bounds){ + set_up_result(OUT_OF_BOUNDS); + ASSERT_EQ(OUT_OF_BOUNDS, client.get_result()); +} + +TEST_F(ClientGetResult, Catch_Bad_Result){ + set_up_result(999); + ASSERT_ANY_THROW(client.get_result()); +} + +TEST_F(ClientGetResult, Cleanup){ + set_up_result(HIT); + client.get_result(); + ifstream f("player_1.result.json"); + ASSERT_FALSE(f.good()); +} + + +class ClientUpdateActionBoard : public ::testing::Test{ +protected: + Client client; + + void SetUp() override{ + client.initialize(1, BOARD_SIZE); + } + + void TearDown() override{ + remove("player_1.action_board.json"); + } +}; + +TEST_F(ClientUpdateActionBoard, Record_Hit){ + client.update_action_board(HIT, 0, 0); + ASSERT_EQ(0, get_diff_dist("player_1.action_board.json", "correct_hit_action_board.json")); +} + +TEST_F(ClientUpdateActionBoard, Record_Miss){ + client.update_action_board(MISS, 0, 0); + + ASSERT_EQ(0, get_diff_dist("player_1.action_board.json", "correct_miss_action_board.json")); +} + + +