Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create Windows Build #45

Merged
merged 1 commit into from
Feb 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions cmake/Modules/FindCurses.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#[=======================================================================[.rst:
FindCurses
----------

Find the curses, pdcurses, or ncurses include file and library.

https://cmake.org/cmake/help/v3.4/manual/cmake-developer.7.html#find-modules

#]=======================================================================]

# guard against creating the curses target multiple times
include_guard(GLOBAL)

set(_PDCURSES_MODULE_NAME curses)
if (PDCURSES_REPO_DIR)
set(_PDCURSES_REPO_DIR ${PDCURSES_REPO_DIR})
else()
set(_PDCURSES_REPO_DIR $ENV{HOME}/Documents/PDCurses)
message(
WARNING
"The variable PDCURSES_REPO_DIR was not set via a command line argument."
" Guessing ${_PDCURSES_REPO_DIR}"
)
endif()
get_filename_component(_PDCURSES_ARCHIVE_PATH ${_PDCURSES_REPO_DIR}/wincon/pdcurses.a ABSOLUTE)

set(CURSES_FOUND True)
set(CURSES_LIBRARY ${_PDCURSES_MODULE_NAME})
set(CURSES_LIBRARIES ${_PDCURSES_MODULE_NAME})
set(CURSES_INCLUDE_DIRS ${_PDCURSES_REPO_DIR})

# this is a helper function cmake provides to implement expected behavior of a FindXXXX.cmake file
find_package_handle_standard_args(Curses DEFAULT_MSG
CURSES_LIBRARY CURSES_INCLUDE_DIRS)

include_directories(CURSES_INCLUDE_DIRS)
# setting policy CMP0111 to NEW will mandate that the target IMPORTED_LOCATION is set
cmake_policy(SET CMP0111 NEW)
# STATIC: indicates that this library is composed of archives of object files for use when linking other targets
add_library(${_PDCURSES_MODULE_NAME} STATIC IMPORTED GLOBAL)
set_target_properties(${_PDCURSES_MODULE_NAME} PROPERTIES IMPORTED_LOCATION ${_PDCURSES_ARCHIVE_PATH})

unset(_PDCURSES_MODULE_NAME)
unset(_PDCURSES_REPO_DIR)
138 changes: 138 additions & 0 deletions docs/WINDOWS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Building with Powershell

This is my preferred approach, using no IDE.

## 1. Install Chocolatey

## 2. Install Build Tools

Install build tools using chocolatey (if not already installed).

```powershell
choco install git cmake mingw
```

## 3. Choose install location

Change to the directory that you would like to contain the source code.

For the rest of this guide, we will assume you are using your "Documents"
directory.

## 4. Clone and Build PDCurses

```powershell
git.exe clone https://github.com/wmcbrine/PDCurses
bitoffdev marked this conversation as resolved.
Show resolved Hide resolved
```

## 5. Clone and Build Tetris

Clone https://github.com/wmcbrine/PDCurses

```powershell
git.exe clone https://github.com/nitepone/terminally-tetris
```

Clone submodules

```powershell
git.exe submodule init
git.exe submodule update
```

Set the path to the PDCurses repo using an environmental variable.

Generate our Makefile using CMake. In the command below, replace
`C:/Users/elliot/Documents/PDCurses` with the location that you cloned
PDCurses. Note that *forward slashes* must be used here to comply with cmake.

```powershell
& "C:\Program Files\CMake\bin\cmake.exe" -G "MinGW Makefiles" -D "PDCURSES_REPO_DIR=C:/Users/elliot/Documents/PDCurses" .
```

*Note: Make sure you type the above command exactly as written. The amperstand
tells powershell to treat the quoted path as a program and execute it.*

*Note: We have to explicitly name the generator we want, in this case
"MinGW Makefiles"*

Run make

```powershell
make
```

## 6. [Optional] Run Tests

```powershell
.\bin\unit_tests.exe
```

*Note: Some tests (that rely on a specific Linux `rand_r` seeding) will fail on
bitoffdev marked this conversation as resolved.
Show resolved Hide resolved
Windows. Hopefully, we will fix this in the future.*

### 7. [Optional] Run the server

If you want to play online, start a server.

```powershell
.\bin\server.exe
```

### 8. Run the Tetris Client Application

```powershell
.\bin\client.exe
```

# Formatting Code with Clang Format

First, make sure you have chocolatey installed as described under "Building
with Powershell".

Install LLVM Tools

```powershell
choco install LLVM
```

Change to the root directory of this repo, wherever you cloned it.

Run clang-format.

```powershell
& "C:\Program Files\LLVM\bin\clang-format.exe" -style=file -i src/*.c src/*.h
```

*Note: Make sure you type the above command exactly as written. The amperstand
tells powershell to treat the quoted path as a program and execute it.*

# Appendix

## PDCurses Shortfalls

### Missing Menu and Forms

PDCurses does not come with menu and forms like curses does. We may need to
rewrite some code to get around this.

## Considered Alternatives

### Cygwin

The main advantage of Cygwin is that it offers a greater degree of
POSIX-compliance, which might allow us to use fewer pre-processing directives
to support Windows.

### LLVM

LLVM seems like it could be a good way to go, although *PDCurses does not come
with an out-of-the-box* Makefile for clang, and after overriding `CC` in the
`GCC` makefile, I found that a number of libaries were missing. We could look
into this more in the future, but it seemed like more work.

## C Package Managers

- Open Question: Is there a better way to link PDCurses, or are we stuck
compiling it ourselves?
- Could we use NuGet?
43 changes: 31 additions & 12 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ cmake_minimum_required (VERSION 3.3)
# projectname is the same as the main-executable
project(ttetris)

add_definitions('-g')
add_definitions('-Wall')
add_definitions('-std=gnu99')
add_definitions('-fcommon')
# Previously, we used add_definitions() to set the compile flags, but that is not always supported.
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -fcommon")

if (WIN32)
# add our custom cmake modules (for curses)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
set(ADDITIONAL_LIBS wsock32 ws2_32)
endif()

find_package(Threads)
find_package(Curses)
include_directories(${CURSES_INCLUDE_DIRS})


list(APPEND ttetris_SOURCES
${CMAKE_CURRENT_LIST_DIR}/tetris_game.c
Expand All @@ -23,20 +30,32 @@ list(APPEND ttetris_SOURCES
${CMAKE_CURRENT_LIST_DIR}/render.c
${CMAKE_CURRENT_LIST_DIR}/widgets.c
${CMAKE_CURRENT_LIST_DIR}/curses_text_entry.c
${CMAKE_CURRENT_LIST_DIR}/curses_combobox.c
${CMAKE_CURRENT_LIST_DIR}/terminal_size.c
${CMAKE_CURRENT_LIST_DIR}/log.c
${CMAKE_CURRENT_LIST_DIR}/os_compat.c
${CMAKE_CURRENT_LIST_DIR}/party.c
${CMAKE_CURRENT_LIST_DIR}/event.c
)

add_library(ttetrislib OBJECT ${ttetris_SOURCES} log.h log.c party.c party.h event.c event.h curses_combobox.c curses_combobox.h)
add_library(ttetrislib OBJECT ${ttetris_SOURCES})

# Solo main uses termios, which is *nix only. For now, just skip building
# solo main on Windows.
if (NOT WIN32)
add_executable(solo_main solo_main.c tetris_game.c)
target_link_libraries(solo_main ${CMAKE_THREAD_LIBS_INIT} )
endif()

add_executable(solo_main solo_main.c tetris_game.c)
add_executable(client client.c $<TARGET_OBJECTS:ttetrislib>)
add_executable(server server.c $<TARGET_OBJECTS:ttetrislib>)
add_executable(test_render test_render.c $<TARGET_OBJECTS:ttetrislib>)
add_executable(test_client_conn test_client_conn.c $<TARGET_OBJECTS:ttetrislib>)
add_executable(test_player test_player.c $<TARGET_OBJECTS:ttetrislib>)

target_link_libraries(solo_main ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries(client ${CURSES_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries(server ${CURSES_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries(test_render ${CURSES_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries(test_client_conn ${CURSES_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries(test_player ${CURSES_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries(ttetrislib ${CURSES_LIBRARIES} ${CURSES_INCLUDE_DIRS})
target_link_libraries(client ${CURSES_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${ADDITIONAL_LIBS})
target_link_libraries(server ${CURSES_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${ADDITIONAL_LIBS})
target_link_libraries(test_render ${CURSES_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${ADDITIONAL_LIBS})
target_link_libraries(test_client_conn ${CURSES_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${ADDITIONAL_LIBS})
target_link_libraries(test_player ${CURSES_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${ADDITIONAL_LIBS})
44 changes: 36 additions & 8 deletions src/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "event.h"
#include "log.h"
#include "offline.h"
#include "os_compat.h"
#include "player.h"
#include "render.h"
#include "tetris_game.h"
Expand All @@ -19,7 +20,7 @@
*/
void usage() {
fprintf(stderr,
"Usage: ./client [-h] [-l] [-s] [-a ADDRESS] [-p PORT]\n");
"Usage: ./client [-h] [-f] [-l] [-s] [-a ADDRESS] [-p PORT]\n");
exit(EXIT_FAILURE);
}

Expand Down Expand Up @@ -92,14 +93,16 @@ void *player_lobby(void *_net_client) {
* run an online game of tetris
*/
int run_online(char *host, int port) {
fprintf(logging_fp, "run_online(%s, %d)\n", host, port);
// initialize the player list
player_init();

// connect to the server
NetClient *net_client = net_client_init(host, port);
if (tetris_connect(net_client, host, port) == EXIT_FAILURE) {
// print the error at the top-left of the terminal using curses
mvprintw(0, 0, strerror(errno));
char errmsg[256];
last_error_message_to_buffer(errmsg, 256);
fprintf(logging_fp, "run_online: %s\n", errmsg);
return EXIT_FAILURE;
}
tetris_listen(net_client);
Expand All @@ -108,17 +111,21 @@ int run_online(char *host, int port) {
char username[32];
ttviz_entry(username, "Enter username: ", PLAYER_NAME_MAX_CHARS);

fprintf(logging_fp, "Entered username=%s\n", username);

// create our player
Player *player = player_create(0, username);
player->fd = net_client->fd;
net_client->player = player;

fprintf(logging_fp, "Sending registration username=%s\n", username);

// register our player
NetRequest *request = tetris_register(net_client, username);
ttetris_net_request_block_for_response(request);

fprintf(
stderr,
logging_fp,
"Registered successfully! Fetching online players from server...");

// get the list of possible opponents
Expand All @@ -129,6 +136,8 @@ int run_online(char *host, int port) {
// block until we hear from the server that the game has started
ttetris_event_block_for_completion(player->game_start_event);

fprintf(logging_fp, "run_online: unblocked by game start\n");

// cancel the lobby thread if it is still running
pthread_cancel(_thread);

Expand Down Expand Up @@ -164,6 +173,9 @@ int main_menu(char *host, int port) {
selection = ttviz_select(choices, 4, "Gameplay Mode", 1);
selected_choice = selection_to_index(selection);

fprintf(logging_fp, "main_menu: selection=%s\n",
choices[selected_choice]);

switch (selected_choice) {
case 0:
run_offline();
Expand Down Expand Up @@ -191,15 +203,18 @@ int main(int argc, char *argv[]) {

int list_players = 0;

// set the logger file pointer to /dev/null
logging_set_fp(fopen("/dev/null", "w"));
#ifdef THIS_IS_WINDOWS
char *log_filename = "nul";
#else
char *log_filename = "/dev/null";
#endif

// Parse command line flags. The optstring passed to getopt has a
// preceding colon to tell getopt that missing flag values should be
// treated differently than unknown flags. The proceding colons indicate
// that flags must have a value.
int opt;
while ((opt = getopt(argc, argv, ":hla:p:")) != -1) {
while ((opt = getopt(argc, argv, ":hlf:a:p:")) != -1) {
switch (opt) {
case 'h':
usage();
Expand All @@ -208,6 +223,9 @@ int main(int argc, char *argv[]) {
strncpy(host, optarg, 127);
printf("address: %s\n", optarg);
break;
case 'f':
log_filename = optarg;
break;
case 'p':
strncpy(port, optarg, 5);
printf("port: %s\n", optarg);
Expand All @@ -224,10 +242,20 @@ int main(int argc, char *argv[]) {
}
}

// set the logger file pointer to /dev/null
FILE *fp = fopen(log_filename, "w");
logging_set_fp(fp);
// disable buffering on the logging file pointer so that we don't miss
// any data in the event that the program crashes, etc.
setvbuf(fp, NULL, _IONBF, 0);

fprintf(stderr, "Using logfile %s\n", log_filename);
fprintf(logging_fp, "Using logfile %s\n", log_filename);

// convert the string port to a number port
uintmax_t numeric_port = strtoumax(port, NULL, 10);
if (numeric_port == UINTMAX_MAX && errno == ERANGE) {
fprintf(stderr, "Provided port is invalid\n");
fprintf(logging_fp, "Provided port is invalid\n");
usage();
}

Expand Down
Loading