src/main.c

Sun, 29 Mar 2026 15:17:28 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 29 Mar 2026 15:17:28 +0200
changeset 94
864f59271974
parent 92
84e0dec5db16
permissions
-rw-r--r--

implement option to play via Unix domain socket

resolves #816

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2016 Mike Becker. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */

#define PROGRAM_VERSION "1.0 alpha"

#include "game.h"
#include "input.h"
#include "network.h"
#include "colors.h"
#include <string.h>
#include <time.h>
#include <getopt.h>
#include <locale.h>
#include <sys/stat.h>

int get_settings(int argc, char **argv, Settings *settings) {
    char *valid;
    unsigned long int time, port;
    uint8_t timeunit = 60;
    size_t len;
    bool port_set = false;
    
    for (int opt ; (opt = getopt(argc, argv, "a:bc:Fhp:rsS:t:uUv")) != -1 ;) {
        switch (opt) {
        case 'c':
            settings->continuepgn = optarg;
            break;
        case 'b':
            settings->gameinfo.servercolor = BLACK;
            break;
        case 'r':
            settings->gameinfo.servercolor = rand() & 1 ? WHITE : BLACK;
            break;
        case 's':
            settings->singlemachine = true;
            break;
        case 'F':
            settings->disableflip = true;
            break;
        case 'u':
            if (port_set) {
                fprintf(stderr, "Cannot use Unix domain sockets "
                        "when a TCP port was specified.\n");
                return 1;
            }
            settings->usedomainsocket = true;
            break;
        case 'U':
            settings->unicode = false;
            break;
        case 't':
        case 'a':
            len = strlen(optarg);
            if (optarg[len-1] == 's') {
                optarg[len-1] = '\0';
                timeunit = 1;
            }
            
            if ((time = strtoul(optarg, &valid, 10))*timeunit > UINT16_MAX
                || *valid != '\0') {
                fprintf(stderr, "Specified time is invalid (%s)"
                    "- Maximum: 65535 seconds (1092 minutes)\n", optarg);
                return 1;
            } else {
                settings->gameinfo.timecontrol = 1;
                if (opt=='t') {
                    settings->gameinfo.time = timeunit * time;
                } else {
                    settings->gameinfo.addtime = time;
                }
            }
            break;
        case 'p':
            if (port_set) {
                fprintf(stderr, "Cannot use -p twice.\n");
                return 1;
            }
            if (settings->usedomainsocket) {
                fprintf(stderr, "Cannot specify TCP port "
                        "when using Unix domain sockets.\n");
                return 1;
            }
            port = strtol(optarg, &valid, 10);
            if (port < 1025 || port > 65535 || *valid != '\0') {
                fprintf(stderr,
                    "Invalid port number (%s) - choose a number between "
                    "1025 and 65535\n",
                    optarg);
                return 1;
            } else {
                settings->port = (short) port;
                port_set = true;
            }
            break;
        case 'v':
            printf("terminal-chess : Version %s (Netcode Version %d)\n",
                PROGRAM_VERSION, NETCODE_VERSION);
            exit(0);
        case 'h':
        case '?':
            printf(
"Usage: terminal-chess [OPTION]... [HOST]\n"
"Starts/joins a network chess game\n"
"\nGeneral options\n"
"  -c <PGN file> Continue the specified game\n"
"  -h            This help page\n"
"  -p            TCP port to use (default: 27015)\n"
"  -u            Use Unix domain socket instead of TCP\n"
"  -U            Disables unicode pieces\n"
"  -v            Print version information and exits\n"
"\nServer options\n"
"  -a <time>     Specifies the time to add after each move\n"
"  -b            Server plays black pieces (default: white)\n"
"  -r            Distribute color randomly\n"
"  -t <time>     Specifies time limit (default: no limit)\n"
"\nHot seat\n"
"  -s            Play a hot seat game (network options are ignored)\n"
"  -F            Do not automatically flip the board in hot seat games\n"
"\nNotes\n"
"The time unit for -a is seconds and for -t minutes by default. To "
"specify\nseconds for the -t option, use the s suffix.\n"
"Example: -t 150s\n\n"
"Use '-' for PGN files to read PGN data from standard input\n\n"
"When playing over Unix domain socket, the HOST denotes the socket path.\n"
"When the path doest not exist, a game is created. Otherwise, the program\n"
"joins the existing game. When HOST is omitted, /tmp/chess.sock is used.\n"
            );
            exit(0);
        }
    }
    
    if (optind == argc - 1) {
        settings->serverhost = argv[optind];
    } else if (optind < argc - 1) {
        fprintf(stderr, "Too many arguments\n");
        return 1;
    }
    
        
    if (settings->continuepgn) {
        if (settings->serverhost) {
            fprintf(stderr, "Can't continue a game when joining a server.\n");
            return 1;
        }
    }

    if (settings->usedomainsocket) {
        if (!settings->serverhost) {
            settings->serverhost = "/tmp/chess.sock";
        }
        struct stat st;
        if (stat(settings->serverhost, &st) == 0) {
            if (S_ISSOCK(st.st_mode)) {
                settings->ishost = false;
            } else {
                fprintf(stderr, "%s is not a Unix domain socket.\n",
                    settings->serverhost);
                return 1;
            }
        } else {
            settings->ishost = true;
        }
    } else {
        settings->ishost = !settings->serverhost;
    }
    
    return 0;
}

static Settings settings;

static void init_settings(void) {
    memset(&settings, 0, sizeof(settings));
    settings.gameinfo.servercolor = WHITE;
    settings.port = 27015;
    settings.unicode = !!setlocale(LC_CTYPE, "C.UTF-8");
}

static void cleanup() {
    endwin();
    if (settings.usedomainsocket && settings.ishost) {
        remove(settings.serverhost);
    }
}

int main(int argc, char **argv) {
    srand(time(NULL));

    init_settings();
    if (get_settings(argc, argv, &settings)) {
        return 1;
    }

    initscr();
    halfdelay(1);
    keypad(stdscr, true);
    if (has_colors()) {
        start_color();
        init_colorpairs();
        bkgd(COLOR_PAIR(COL_APP));
    } else {
        fprintf(stderr, "Non-colored terminals are not supported yet.");
        endwin();
        return EXIT_FAILURE;
    }
    atexit(cleanup);
    
    int exitcode;
    if (settings.singlemachine) {
        game_play_singlemachine(&settings);
        exitcode = EXIT_SUCCESS;
    } else {
        exitcode = settings.ishost ?
            server_run(&settings) : client_run(&settings);
    }
    
    mvaddstr(getmaxy(stdscr)-1, 0,
        "Game has ended. Press any key to leave...");
    clrtoeol();
    refresh();
    cbreak();
    flushinp();
    getch();
    
    return exitcode;
}

mercurial