From ce48af53646cd9e7ec762fc1ac176b3aa620b11d Mon Sep 17 00:00:00 2001 From: manuel Date: Thu, 5 May 2011 00:57:07 +0200 Subject: - refactorized the whole project and made a few subprojects - replaced tcp with enet - added connect dialog - some smaller bugfixes --- pacman-c++/server/anyoption.cpp | 1175 +++++++++++++++++++++++++++++++++++++++ pacman-c++/server/anyoption.h | 270 +++++++++ pacman-c++/server/server.cpp | 971 ++++++++++++++++++++++++++++++++ pacman-c++/server/server.h | 85 +++ pacman-c++/server/server.pro | 10 + 5 files changed, 2511 insertions(+) create mode 100644 pacman-c++/server/anyoption.cpp create mode 100644 pacman-c++/server/anyoption.h create mode 100644 pacman-c++/server/server.cpp create mode 100644 pacman-c++/server/server.h create mode 100644 pacman-c++/server/server.pro (limited to 'pacman-c++/server') diff --git a/pacman-c++/server/anyoption.cpp b/pacman-c++/server/anyoption.cpp new file mode 100644 index 0000000..4e3828e --- /dev/null +++ b/pacman-c++/server/anyoption.cpp @@ -0,0 +1,1175 @@ +/* + * AnyOption 1.3 + * + * kishan at hackorama dot com www.hackorama.com JULY 2001 + * + * + Acts as a common facade class for reading + * commandline options as well as options from + * an optionfile with delimited type value pairs + * + * + Handles the POSIX style single character options ( -w ) + * as well as the newer GNU long options ( --width ) + * + * + The option file assumes the traditional format of + * first character based comment lines and type value + * pairs with a delimiter , and flags which are not pairs + * + * # this is a coment + * # next line is an option value pair + * width : 100 + * # next line is a flag + * noimages + * + * + Supports printing out Help and Usage + * + * + Why not just use getopt() ? + * + * getopt() Its a POSIX standard not part of ANSI-C. + * So it may not be available on platforms like Windows. + * + * + Why it is so long ? + * + * The actual code which does command line parsing + * and option file parsing are done in few methods. + * Most of the extra code are for providing a flexible + * common public interface to both a resourcefile and + * and command line supporting POSIX style and + * GNU long option as well as mixing of both. + * + * + Please see "anyoption.h" for public method descriptions + * + */ + +/* Updated Auguest 2004 + * Fix from Michael D Peters (mpeters at sandia.gov) + * to remove static local variables, allowing multiple instantiations + * of the reader (for using multiple configuration files). There is + * an error in the destructor when using multiple instances, so you + * cannot delete your objects (it will crash), but not calling the + * destructor only introduces a small memory leak, so I + * have not bothered tracking it down. + * + * Also updated to use modern C++ style headers, rather than + * depricated iostream.h (it was causing my compiler problems) +*/ + +/* + * Updated September 2006 + * Fix from Boyan Asenov for a bug in mixing up option indexes + * leading to exception when mixing different options types + */ + +#include "anyoption.h" +#include + +AnyOption::AnyOption() +{ + init(); +} + +AnyOption::AnyOption(int maxopt) +{ + init( maxopt , maxopt ); +} + +AnyOption::AnyOption(int maxopt, int maxcharopt) +{ + init( maxopt , maxcharopt ); +} + +AnyOption::~AnyOption() +{ + if( mem_allocated ) + cleanup(); +} + +void +AnyOption::init() +{ + init( DEFAULT_MAXOPTS , DEFAULT_MAXOPTS ); +} + +void +AnyOption::init(int maxopt, int maxcharopt ) +{ + + max_options = maxopt; + max_char_options = maxcharopt; + max_usage_lines = DEFAULT_MAXUSAGE; + usage_lines = 0 ; + argc = 0; + argv = NULL; + posix_style = true; + verbose = false; + filename = NULL; + appname = NULL; + option_counter = 0; + optchar_counter = 0; + new_argv = NULL; + new_argc = 0 ; + max_legal_args = 0 ; + command_set = false; + file_set = false; + values = NULL; + g_value_counter = 0; + mem_allocated = false; + command_set = false; + file_set = false; + opt_prefix_char = '-'; + file_delimiter_char = ':'; + file_comment_char = '#'; + equalsign = '='; + comment = '#' ; + delimiter = ':' ; + endofline = '\n'; + whitespace = ' ' ; + nullterminate = '\0'; + set = false; + once = true; + hasoptions = false; + autousage = false; + + strcpy( long_opt_prefix , "--" ); + + if( alloc() == false ){ + cout << endl << "OPTIONS ERROR : Failed allocating memory" ; + cout << endl ; + cout << "Exiting." << endl ; + exit (0); + } +} + +bool +AnyOption::alloc() +{ + int i = 0 ; + int size = 0 ; + + if( mem_allocated ) + return true; + + size = (max_options+1) * sizeof(const char*); + options = (const char**)malloc( size ); + optiontype = (int*) malloc( (max_options+1)*sizeof(int) ); + optionindex = (int*) malloc( (max_options+1)*sizeof(int) ); + if( options == NULL || optiontype == NULL || optionindex == NULL ) + return false; + else + mem_allocated = true; + for( i = 0 ; i < max_options ; i++ ){ + options[i] = NULL; + optiontype[i] = 0 ; + optionindex[i] = -1 ; + } + optionchars = (char*) malloc( (max_char_options+1)*sizeof(char) ); + optchartype = (int*) malloc( (max_char_options+1)*sizeof(int) ); + optcharindex = (int*) malloc( (max_char_options+1)*sizeof(int) ); + if( optionchars == NULL || + optchartype == NULL || + optcharindex == NULL ) + { + mem_allocated = false; + return false; + } + for( i = 0 ; i < max_char_options ; i++ ){ + optionchars[i] = '0'; + optchartype[i] = 0 ; + optcharindex[i] = -1 ; + } + + size = (max_usage_lines+1) * sizeof(const char*) ; + usage = (const char**) malloc( size ); + + if( usage == NULL ){ + mem_allocated = false; + return false; + } + for( i = 0 ; i < max_usage_lines ; i++ ) + usage[i] = NULL; + + return true; +} + +bool +AnyOption::doubleOptStorage() +{ + options = (const char**)realloc( options, + ((2*max_options)+1) * sizeof( const char*) ); + optiontype = (int*) realloc( optiontype , + ((2 * max_options)+1)* sizeof(int) ); + optionindex = (int*) realloc( optionindex, + ((2 * max_options)+1) * sizeof(int) ); + if( options == NULL || optiontype == NULL || optionindex == NULL ) + return false; + /* init new storage */ + for( int i = max_options ; i < 2*max_options ; i++ ){ + options[i] = NULL; + optiontype[i] = 0 ; + optionindex[i] = -1 ; + } + max_options = 2 * max_options ; + return true; +} + +bool +AnyOption::doubleCharStorage() +{ + optionchars = (char*) realloc( optionchars, + ((2*max_char_options)+1)*sizeof(char) ); + optchartype = (int*) realloc( optchartype, + ((2*max_char_options)+1)*sizeof(int) ); + optcharindex = (int*) realloc( optcharindex, + ((2*max_char_options)+1)*sizeof(int) ); + if( optionchars == NULL || + optchartype == NULL || + optcharindex == NULL ) + return false; + /* init new storage */ + for( int i = max_char_options ; i < 2*max_char_options ; i++ ){ + optionchars[i] = '0'; + optchartype[i] = 0 ; + optcharindex[i] = -1 ; + } + max_char_options = 2 * max_char_options; + return true; +} + +bool +AnyOption::doubleUsageStorage() +{ + usage = (const char**)realloc( usage, + ((2*max_usage_lines)+1) * sizeof( const char*) ); + if ( usage == NULL ) + return false; + for( int i = max_usage_lines ; i < 2*max_usage_lines ; i++ ) + usage[i] = NULL; + max_usage_lines = 2 * max_usage_lines ; + return true; + +} + + +void +AnyOption::cleanup() +{ + free (options); + free (optiontype); + free (optionindex); + free (optionchars); + free (optchartype); + free (optcharindex); + free (usage); + if( values != NULL ) + free (values); + if( new_argv != NULL ) + free (new_argv); +} + +void +AnyOption::setCommandPrefixChar( char _prefix ) +{ + opt_prefix_char = _prefix; +} + +void +AnyOption::setCommandLongPrefix( char *_prefix ) +{ + if( strlen( _prefix ) > MAX_LONG_PREFIX_LENGTH ){ + *( _prefix + MAX_LONG_PREFIX_LENGTH ) = '\0'; + } + + strcpy (long_opt_prefix, _prefix); +} + +void +AnyOption::setFileCommentChar( char _comment ) +{ + file_delimiter_char = _comment; +} + + +void +AnyOption::setFileDelimiterChar( char _delimiter ) +{ + file_comment_char = _delimiter ; +} + +bool +AnyOption::CommandSet() +{ + return( command_set ); +} + +bool +AnyOption::FileSet() +{ + return( file_set ); +} + +void +AnyOption::noPOSIX() +{ + posix_style = false; +} + +bool +AnyOption::POSIX() +{ + return posix_style; +} + + +void +AnyOption::setVerbose() +{ + verbose = true ; +} + +void +AnyOption::printVerbose() +{ + if( verbose ) + cout << endl ; +} +void +AnyOption::printVerbose( const char *msg ) +{ + if( verbose ) + cout << msg ; +} + +void +AnyOption::printVerbose( char *msg ) +{ + if( verbose ) + cout << msg ; +} + +void +AnyOption::printVerbose( char ch ) +{ + if( verbose ) + cout << ch ; +} + +bool +AnyOption::hasOptions() +{ + return hasoptions; +} + +void +AnyOption::autoUsagePrint(bool _autousage) +{ + autousage = _autousage; +} + +void +AnyOption::useCommandArgs( int _argc, char **_argv ) +{ + argc = _argc; + argv = _argv; + command_set = true; + appname = argv[0]; + if(argc > 1) hasoptions = true; +} + +void +AnyOption::useFiileName( const char *_filename ) +{ + filename = _filename; + file_set = true; +} + +/* + * set methods for options + */ + +void +AnyOption::setCommandOption( const char *opt ) +{ + addOption( opt , COMMAND_OPT ); + g_value_counter++; +} + +void +AnyOption::setCommandOption( char opt ) +{ + addOption( opt , COMMAND_OPT ); + g_value_counter++; +} + +void +AnyOption::setCommandOption( const char *opt , char optchar ) +{ + addOption( opt , COMMAND_OPT ); + addOption( optchar , COMMAND_OPT ); + g_value_counter++; +} + +void +AnyOption::setCommandFlag( const char *opt ) +{ + addOption( opt , COMMAND_FLAG ); + g_value_counter++; +} + +void +AnyOption::setCommandFlag( char opt ) +{ + addOption( opt , COMMAND_FLAG ); + g_value_counter++; +} + +void +AnyOption::setCommandFlag( const char *opt , char optchar ) +{ + addOption( opt , COMMAND_FLAG ); + addOption( optchar , COMMAND_FLAG ); + g_value_counter++; +} + +void +AnyOption::setFileOption( const char *opt ) +{ + addOption( opt , FILE_OPT ); + g_value_counter++; +} + +void +AnyOption::setFileOption( char opt ) +{ + addOption( opt , FILE_OPT ); + g_value_counter++; +} + +void +AnyOption::setFileOption( const char *opt , char optchar ) +{ + addOption( opt , FILE_OPT ); + addOption( optchar, FILE_OPT ); + g_value_counter++; +} + +void +AnyOption::setFileFlag( const char *opt ) +{ + addOption( opt , FILE_FLAG ); + g_value_counter++; +} + +void +AnyOption::setFileFlag( char opt ) +{ + addOption( opt , FILE_FLAG ); + g_value_counter++; +} + +void +AnyOption::setFileFlag( const char *opt , char optchar ) +{ + addOption( opt , FILE_FLAG ); + addOption( optchar , FILE_FLAG ); + g_value_counter++; +} + +void +AnyOption::setOption( const char *opt ) +{ + addOption( opt , COMMON_OPT ); + g_value_counter++; +} + +void +AnyOption::setOption( char opt ) +{ + addOption( opt , COMMON_OPT ); + g_value_counter++; +} + +void +AnyOption::setOption( const char *opt , char optchar ) +{ + addOption( opt , COMMON_OPT ); + addOption( optchar , COMMON_OPT ); + g_value_counter++; +} + +void +AnyOption::setFlag( const char *opt ) +{ + addOption( opt , COMMON_FLAG ); + g_value_counter++; +} + +void +AnyOption::setFlag( const char opt ) +{ + addOption( opt , COMMON_FLAG ); + g_value_counter++; +} + +void +AnyOption::setFlag( const char *opt , char optchar ) +{ + addOption( opt , COMMON_FLAG ); + addOption( optchar , COMMON_FLAG ); + g_value_counter++; +} + +void +AnyOption::addOption( const char *opt, int type ) +{ + if( option_counter >= max_options ){ + if( doubleOptStorage() == false ){ + addOptionError( opt ); + return; + } + } + options[ option_counter ] = opt ; + optiontype[ option_counter ] = type ; + optionindex[ option_counter ] = g_value_counter; + option_counter++; +} + +void +AnyOption::addOption( char opt, int type ) +{ + if( !POSIX() ){ + printVerbose("Ignoring the option character \""); + printVerbose( opt ); + printVerbose( "\" ( POSIX options are turned off )" ); + printVerbose(); + return; + } + + + if( optchar_counter >= max_char_options ){ + if( doubleCharStorage() == false ){ + addOptionError( opt ); + return; + } + } + optionchars[ optchar_counter ] = opt ; + optchartype[ optchar_counter ] = type ; + optcharindex[ optchar_counter ] = g_value_counter; + optchar_counter++; +} + +void +AnyOption::addOptionError( const char *opt ) +{ + cout << endl ; + cout << "OPTIONS ERROR : Failed allocating extra memory " << endl ; + cout << "While adding the option : \""<< opt << "\"" << endl; + cout << "Exiting." << endl ; + cout << endl ; + exit(0); +} + +void +AnyOption::addOptionError( char opt ) +{ + cout << endl ; + cout << "OPTIONS ERROR : Failed allocating extra memory " << endl ; + cout << "While adding the option: \""<< opt << "\"" << endl; + cout << "Exiting." << endl ; + cout << endl ; + exit(0); +} + +void +AnyOption::processOptions() +{ + if( ! valueStoreOK() ) + return; +} + +void +AnyOption::processCommandArgs(int max_args) +{ + max_legal_args = max_args; + processCommandArgs(); +} + +void +AnyOption::processCommandArgs( int _argc, char **_argv, int max_args ) +{ + max_legal_args = max_args; + processCommandArgs( _argc, _argv ); +} + +void +AnyOption::processCommandArgs( int _argc, char **_argv ) +{ + useCommandArgs( _argc, _argv ); + processCommandArgs(); +} + +void +AnyOption::processCommandArgs() +{ + if( ! ( valueStoreOK() && CommandSet() ) ) + return; + + if( max_legal_args == 0 ) + max_legal_args = argc; + new_argv = (int*) malloc( (max_legal_args+1) * sizeof(int) ); + for( int i = 1 ; i < argc ; i++ ){/* ignore first argv */ + if( argv[i][0] == long_opt_prefix[0] && + argv[i][1] == long_opt_prefix[1] ) { /* long GNU option */ + int match_at = parseGNU( argv[i]+2 ); /* skip -- */ + if( match_at >= 0 && i < argc-1 ) /* found match */ + setValue( options[match_at] , argv[++i] ); + }else if( argv[i][0] == opt_prefix_char ) { /* POSIX char */ + if( POSIX() ){ + char ch = parsePOSIX( argv[i]+1 );/* skip - */ + if( ch != '0' && i < argc-1 ) /* matching char */ + setValue( ch , argv[++i] ); + } else { /* treat it as GNU option with a - */ + int match_at = parseGNU( argv[i]+1 ); /* skip - */ + if( match_at >= 0 && i < argc-1 ) /* found match */ + setValue( options[match_at] , argv[++i] ); + } + }else { /* not option but an argument keep index */ + if( new_argc < max_legal_args ){ + new_argv[ new_argc ] = i ; + new_argc++; + }else{ /* ignore extra arguments */ + printVerbose( "Ignoring extra argument: " ); + printVerbose( argv[i] ); + printVerbose( ); + printAutoUsage(); + } + printVerbose( "Unknown command argument option : " ); + printVerbose( argv[i] ); + printVerbose( ); + printAutoUsage(); + } + } +} + +char +AnyOption::parsePOSIX( char* arg ) +{ + + for( unsigned int i = 0 ; i < strlen(arg) ; i++ ){ + char ch = arg[i] ; + if( matchChar(ch) ) { /* keep matching flags till an option */ + /*if last char argv[++i] is the value */ + if( i == strlen(arg)-1 ){ + return ch; + }else{/* else the rest of arg is the value */ + i++; /* skip any '=' and ' ' */ + while( arg[i] == whitespace + || arg[i] == equalsign ) + i++; + setValue( ch , arg+i ); + return '0'; + } + } + } + printVerbose( "Unknown command argument option : " ); + printVerbose( arg ); + printVerbose( ); + printAutoUsage(); + return '0'; +} + +int +AnyOption::parseGNU( char *arg ) +{ + int split_at = 0; + /* if has a '=' sign get value */ + for( unsigned int i = 0 ; i < strlen(arg) ; i++ ){ + if(arg[i] == equalsign ){ + split_at = i ; /* store index */ + i = strlen(arg); /* get out of loop */ + } + } + if( split_at > 0 ){ /* it is an option value pair */ + char* tmp = (char*) malloc( (split_at+1)*sizeof(char) ); + for( int i = 0 ; i < split_at ; i++ ) + tmp[i] = arg[i]; + tmp[split_at] = '\0'; + + if ( matchOpt( tmp ) >= 0 ){ + setValue( options[matchOpt(tmp)] , arg+split_at+1 ); + free (tmp); + }else{ + printVerbose( "Unknown command argument option : " ); + printVerbose( arg ); + printVerbose( ); + printAutoUsage(); + free (tmp); + return -1; + } + }else{ /* regular options with no '=' sign */ + return matchOpt(arg); + } + return -1; +} + + +int +AnyOption::matchOpt( char *opt ) +{ + for( int i = 0 ; i < option_counter ; i++ ){ + if( strcmp( options[i], opt ) == 0 ){ + if( optiontype[i] == COMMON_OPT || + optiontype[i] == COMMAND_OPT ) + { /* found option return index */ + return i; + }else if( optiontype[i] == COMMON_FLAG || + optiontype[i] == COMMAND_FLAG ) + { /* found flag, set it */ + setFlagOn( opt ); + return -1; + } + } + } + printVerbose( "Unknown command argument option : " ); + printVerbose( opt ) ; + printVerbose( ); + printAutoUsage(); + return -1; +} +bool +AnyOption::matchChar( char c ) +{ + for( int i = 0 ; i < optchar_counter ; i++ ){ + if( optionchars[i] == c ) { /* found match */ + if(optchartype[i] == COMMON_OPT || + optchartype[i] == COMMAND_OPT ) + { /* an option store and stop scanning */ + return true; + }else if( optchartype[i] == COMMON_FLAG || + optchartype[i] == COMMAND_FLAG ) { /* a flag store and keep scanning */ + setFlagOn( c ); + return true; + } + } + } + //printVerbose( "Unknown command argument option : " ); + //printVerbose( c ) ; + //printVerbose( ); + printAutoUsage(); + return false; +} + +bool +AnyOption::valueStoreOK( ) +{ + int size= 0; + if( !set ){ + if( g_value_counter > 0 ){ + size = g_value_counter * sizeof(char*); + values = (char**)malloc( size ); + for( int i = 0 ; i < g_value_counter ; i++) + values[i] = NULL; + set = true; + } + } + return set; +} + +/* + * public get methods + */ +char* +AnyOption::getValue( const char *option ) +{ + if( !valueStoreOK() ) + return NULL; + + for( int i = 0 ; i < option_counter ; i++ ){ + if( strcmp( options[i], option ) == 0 ) + return values[ optionindex[i] ]; + } + return NULL; +} + +bool +AnyOption::getFlag( const char *option ) +{ + if( !valueStoreOK() ) + return false; + for( int i = 0 ; i < option_counter ; i++ ){ + if( strcmp( options[i], option ) == 0 ) + return findFlag( values[ optionindex[i] ] ); + } + return false; +} + +char* +AnyOption::getValue( char option ) +{ + if( !valueStoreOK() ) + return NULL; + for( int i = 0 ; i < optchar_counter ; i++ ){ + if( optionchars[i] == option ) + return values[ optcharindex[i] ]; + } + return NULL; +} + +bool +AnyOption::getFlag( char option ) +{ + if( !valueStoreOK() ) + return false; + for( int i = 0 ; i < optchar_counter ; i++ ){ + if( optionchars[i] == option ) + return findFlag( values[ optcharindex[i] ] ) ; + } + return false; +} + +bool +AnyOption::findFlag( char* val ) +{ + if( val == NULL ) + return false; + + if( strcmp( TRUE_FLAG , val ) == 0 ) + return true; + + return false; +} + +/* + * private set methods + */ +bool +AnyOption::setValue( const char *option , char *value ) +{ + if( !valueStoreOK() ) + return false; + for( int i = 0 ; i < option_counter ; i++ ){ + if( strcmp( options[i], option ) == 0 ){ + values[ optionindex[i] ] = (char*) malloc((strlen(value)+1)*sizeof(char)); + strcpy( values[ optionindex[i] ], value ); + return true; + } + } + return false; +} + +bool +AnyOption::setFlagOn( const char *option ) +{ + if( !valueStoreOK() ) + return false; + for( int i = 0 ; i < option_counter ; i++ ){ + if( strcmp( options[i], option ) == 0 ){ + values[ optionindex[i] ] = (char*) malloc((strlen(TRUE_FLAG)+1)*sizeof(char)); + strcpy( values[ optionindex[i] ] , TRUE_FLAG ); + return true; + } + } + return false; +} + +bool +AnyOption::setValue( char option , char *value ) +{ + if( !valueStoreOK() ) + return false; + for( int i = 0 ; i < optchar_counter ; i++ ){ + if( optionchars[i] == option ){ + values[ optcharindex[i] ] = (char*) malloc((strlen(value)+1)*sizeof(char)); + strcpy( values[ optcharindex[i] ], value ); + return true; + } + } + return false; +} + +bool +AnyOption::setFlagOn( char option ) +{ + if( !valueStoreOK() ) + return false; + for( int i = 0 ; i < optchar_counter ; i++ ){ + if( optionchars[i] == option ){ + values[ optcharindex[i] ] = (char*) malloc((strlen(TRUE_FLAG)+1)*sizeof(char)); + strcpy( values[ optcharindex[i] ] , TRUE_FLAG ); + return true; + } + } + return false; +} + + +int +AnyOption::getArgc( ) +{ + return new_argc; +} + +char* +AnyOption::getArgv( int index ) +{ + if( index < new_argc ){ + return ( argv[ new_argv[ index ] ] ); + } + return NULL; +} + +/* dotfile sub routines */ + +bool +AnyOption::processFile() +{ + if( ! (valueStoreOK() && FileSet()) ) + return false; + return ( consumeFile(readFile()) ); +} + +bool +AnyOption::processFile( const char *filename ) +{ + useFiileName(filename ); + return ( processFile() ); +} + +char* +AnyOption::readFile() +{ + return ( readFile(filename) ); +} + +/* + * read the file contents to a character buffer + */ + +char* +AnyOption::readFile( const char* fname ) +{ + int length; + char *buffer; + ifstream is; + is.open ( fname , ifstream::in ); + if( ! is.good() ){ + is.close(); + return NULL; + } + is.seekg (0, ios::end); + length = is.tellg(); + is.seekg (0, ios::beg); + buffer = (char*) malloc(length*sizeof(char)); + is.read (buffer,length); + is.close(); + return buffer; +} + +/* + * scans a char* buffer for lines that does not + * start with the specified comment character. + */ +bool +AnyOption::consumeFile( char *buffer ) +{ + + if( buffer == NULL ) + return false; + + char *cursor = buffer;/* preserve the ptr */ + char *pline = NULL ; + int linelength = 0; + bool newline = true; + for( unsigned int i = 0 ; i < strlen( buffer ) ; i++ ){ + if( *cursor == endofline ) { /* end of line */ + if( pline != NULL ) /* valid line */ + processLine( pline, linelength ); + pline = NULL; + newline = true; + }else if( newline ){ /* start of line */ + newline = false; + if( (*cursor != comment ) ){ /* not a comment */ + pline = cursor ; + linelength = 0 ; + } + } + cursor++; /* keep moving */ + linelength++; + } + free (buffer); + return true; +} + + +/* + * find a valid type value pair separated by a delimiter + * character and pass it to valuePairs() + * any line which is not valid will be considered a value + * and will get passed on to justValue() + * + * assuming delimiter is ':' the behaviour will be, + * + * width:10 - valid pair valuePairs( width, 10 ); + * width : 10 - valid pair valuepairs( width, 10 ); + * + * :::: - not valid + * width - not valid + * :10 - not valid + * width: - not valid + * :: - not valid + * : - not valid + * + */ + +void +AnyOption::processLine( char *theline, int length ) +{ + bool found = false; + char *pline = (char*) malloc( (length+1)*sizeof(char) ); + for( int i = 0 ; i < length ; i ++ ) + pline[i]= *(theline++); + pline[length] = nullterminate; + char *cursor = pline ; /* preserve the ptr */ + if( *cursor == delimiter || *(cursor+length-1) == delimiter ){ + justValue( pline );/* line with start/end delimiter */ + }else{ + for( int i = 1 ; i < length-1 && !found ; i++){/* delimiter */ + if( *cursor == delimiter ){ + *(cursor-1) = nullterminate; /* two strings */ + found = true; + valuePairs( pline , cursor+1 ); + } + cursor++; + } + cursor++; + if( !found ) /* not a pair */ + justValue( pline ); + } + free (pline); +} + +/* + * removes trailing and preceeding whitespaces from a string + */ +char* +AnyOption::chomp( char *str ) +{ + while( *str == whitespace ) + str++; + char *end = str+strlen(str)-1; + while( *end == whitespace ) + end--; + *(end+1) = nullterminate; + return str; +} + +void +AnyOption::valuePairs( char *type, char *value ) +{ + if ( strlen(chomp(type)) == 1 ){ /* this is a char option */ + for( int i = 0 ; i < optchar_counter ; i++ ){ + if( optionchars[i] == type[0] ){ /* match */ + if( optchartype[i] == COMMON_OPT || + optchartype[i] == FILE_OPT ) + { + setValue( type[0] , chomp(value) ); + return; + } + } + } + } + /* if no char options matched */ + for( int i = 0 ; i < option_counter ; i++ ){ + if( strcmp( options[i], type ) == 0 ){ /* match */ + if( optiontype[i] == COMMON_OPT || + optiontype[i] == FILE_OPT ) + { + setValue( type , chomp(value) ); + return; + } + } + } + printVerbose( "Unknown option in resourcefile : " ); + printVerbose( type ); + printVerbose( ); +} + +void +AnyOption::justValue( char *type ) +{ + + if ( strlen(chomp(type)) == 1 ){ /* this is a char option */ + for( int i = 0 ; i < optchar_counter ; i++ ){ + if( optionchars[i] == type[0] ){ /* match */ + if( optchartype[i] == COMMON_FLAG || + optchartype[i] == FILE_FLAG ) + { + setFlagOn( type[0] ); + return; + } + } + } + } + /* if no char options matched */ + for( int i = 0 ; i < option_counter ; i++ ){ + if( strcmp( options[i], type ) == 0 ){ /* match */ + if( optiontype[i] == COMMON_FLAG || + optiontype[i] == FILE_FLAG ) + { + setFlagOn( type ); + return; + } + } + } + printVerbose( "Unknown option in resourcefile : " ); + printVerbose( type ); + printVerbose( ); +} + +/* + * usage and help + */ + + +void +AnyOption::printAutoUsage() +{ + if( autousage ) printUsage(); +} + +void +AnyOption::printUsage() +{ + + if( once ) { + once = false ; + cout << endl ; + for( int i = 0 ; i < usage_lines ; i++ ) + cout << usage[i] << endl ; + cout << endl ; + } +} + + +void +AnyOption::addUsage( const char *line ) +{ + if( usage_lines >= max_usage_lines ){ + if( doubleUsageStorage() == false ){ + addUsageError( line ); + exit(1); + } + } + usage[ usage_lines ] = line ; + usage_lines++; +} + +void +AnyOption::addUsageError( const char *line ) +{ + cout << endl ; + cout << "OPTIONS ERROR : Failed allocating extra memory " << endl ; + cout << "While adding the usage/help : \""<< line << "\"" << endl; + cout << "Exiting." << endl ; + cout << endl ; + exit(0); + +} diff --git a/pacman-c++/server/anyoption.h b/pacman-c++/server/anyoption.h new file mode 100644 index 0000000..3f7a5de --- /dev/null +++ b/pacman-c++/server/anyoption.h @@ -0,0 +1,270 @@ +#ifndef _ANYOPTION_H +#define _ANYOPTION_H + +#include +#include +#include +#include + +#define COMMON_OPT 1 +#define COMMAND_OPT 2 +#define FILE_OPT 3 +#define COMMON_FLAG 4 +#define COMMAND_FLAG 5 +#define FILE_FLAG 6 + +#define COMMAND_OPTION_TYPE 1 +#define COMMAND_FLAG_TYPE 2 +#define FILE_OPTION_TYPE 3 +#define FILE_FLAG_TYPE 4 +#define UNKNOWN_TYPE 5 + +#define DEFAULT_MAXOPTS 10 +#define MAX_LONG_PREFIX_LENGTH 2 + +#define DEFAULT_MAXUSAGE 3 +#define DEFAULT_MAXHELP 10 + +#define TRUE_FLAG "true" + +using namespace std; + +class AnyOption +{ + +public: /* the public interface */ + AnyOption(); + AnyOption(int maxoptions ); + AnyOption(int maxoptions , int maxcharoptions); + ~AnyOption(); + + /* + * following set methods specifies the + * special characters and delimiters + * if not set traditional defaults will be used + */ + + void setCommandPrefixChar( char _prefix ); /* '-' in "-w" */ + void setCommandLongPrefix( char *_prefix ); /* '--' in "--width" */ + void setFileCommentChar( char _comment ); /* '#' in shellscripts */ + void setFileDelimiterChar( char _delimiter );/* ':' in "width : 100" */ + + /* + * provide the input for the options + * like argv[] for commndline and the + * option file name to use; + */ + + void useCommandArgs( int _argc, char **_argv ); + void useFiileName( const char *_filename ); + + /* + * turn off the POSIX style options + * this means anything starting with a '-' or "--" + * will be considered a valid option + * which alo means you cannot add a bunch of + * POIX options chars together like "-lr" for "-l -r" + * + */ + + void noPOSIX(); + + /* + * prints warning verbose if you set anything wrong + */ + void setVerbose(); + + + /* + * there are two types of options + * + * Option - has an associated value ( -w 100 ) + * Flag - no value, just a boolean flag ( -nogui ) + * + * the options can be either a string ( GNU style ) + * or a character ( traditional POSIX style ) + * or both ( --width, -w ) + * + * the options can be common to the commandline and + * the optionfile, or can belong only to either of + * commandline and optionfile + * + * following set methods, handle all the aboove + * cases of options. + */ + + /* options comman to command line and option file */ + void setOption( const char *opt_string ); + void setOption( char opt_char ); + void setOption( const char *opt_string , char opt_char ); + void setFlag( const char *opt_string ); + void setFlag( char opt_char ); + void setFlag( const char *opt_string , char opt_char ); + + /* options read from commandline only */ + void setCommandOption( const char *opt_string ); + void setCommandOption( char opt_char ); + void setCommandOption( const char *opt_string , char opt_char ); + void setCommandFlag( const char *opt_string ); + void setCommandFlag( char opt_char ); + void setCommandFlag( const char *opt_string , char opt_char ); + + /* options read from an option file only */ + void setFileOption( const char *opt_string ); + void setFileOption( char opt_char ); + void setFileOption( const char *opt_string , char opt_char ); + void setFileFlag( const char *opt_string ); + void setFileFlag( char opt_char ); + void setFileFlag( const char *opt_string , char opt_char ); + + /* + * process the options, registerd using + * useCommandArgs() and useFileName(); + */ + void processOptions(); + void processCommandArgs(); + void processCommandArgs( int max_args ); + bool processFile(); + + /* + * process the specified options + */ + void processCommandArgs( int _argc, char **_argv ); + void processCommandArgs( int _argc, char **_argv, int max_args ); + bool processFile( const char *_filename ); + + /* + * get the value of the options + * will return NULL if no value is set + */ + char *getValue( const char *_option ); + bool getFlag( const char *_option ); + char *getValue( char _optchar ); + bool getFlag( char _optchar ); + + /* + * Print Usage + */ + void printUsage(); + void printAutoUsage(); + void addUsage( const char *line ); + void printHelp(); + /* print auto usage printing for unknown options or flag */ + void autoUsagePrint(bool flag); + + /* + * get the argument count and arguments sans the options + */ + int getArgc(); + char* getArgv( int index ); + bool hasOptions(); + +private: /* the hidden data structure */ + int argc; /* commandline arg count */ + char **argv; /* commndline args */ + const char* filename; /* the option file */ + char* appname; /* the application name from argv[0] */ + + int *new_argv; /* arguments sans options (index to argv) */ + int new_argc; /* argument count sans the options */ + int max_legal_args; /* ignore extra arguments */ + + + /* option strings storage + indexing */ + int max_options; /* maximum number of options */ + const char **options; /* storage */ + int *optiontype; /* type - common, command, file */ + int *optionindex; /* index into value storage */ + int option_counter; /* counter for added options */ + + /* option chars storage + indexing */ + int max_char_options; /* maximum number options */ + char *optionchars; /* storage */ + int *optchartype; /* type - common, command, file */ + int *optcharindex; /* index into value storage */ + int optchar_counter; /* counter for added options */ + + /* values */ + char **values; /* common value storage */ + int g_value_counter; /* globally updated value index LAME! */ + + /* help and usage */ + const char **usage; /* usage */ + int max_usage_lines; /* max usage lines reseverd */ + int usage_lines; /* number of usage lines */ + + bool command_set; /* if argc/argv were provided */ + bool file_set; /* if a filename was provided */ + bool mem_allocated; /* if memory allocated in init() */ + bool posix_style; /* enables to turn off POSIX style options */ + bool verbose; /* silent|verbose */ + bool print_usage; /* usage verbose */ + bool print_help; /* help verbose */ + + char opt_prefix_char; /* '-' in "-w" */ + char long_opt_prefix[MAX_LONG_PREFIX_LENGTH]; /* '--' in "--width" */ + char file_delimiter_char; /* ':' in width : 100 */ + char file_comment_char; /* '#' in "#this is a comment" */ + char equalsign; + char comment; + char delimiter; + char endofline; + char whitespace; + char nullterminate; + + bool set; //was static member + bool once; //was static member + + bool hasoptions; + bool autousage; + +private: /* the hidden utils */ + void init(); + void init(int maxopt, int maxcharopt ); + bool alloc(); + void cleanup(); + bool valueStoreOK(); + + /* grow storage arrays as required */ + bool doubleOptStorage(); + bool doubleCharStorage(); + bool doubleUsageStorage(); + + bool setValue( const char *option , char *value ); + bool setFlagOn( const char *option ); + bool setValue( char optchar , char *value); + bool setFlagOn( char optchar ); + + void addOption( const char* option , int type ); + void addOption( char optchar , int type ); + void addOptionError( const char *opt); + void addOptionError( char opt); + bool findFlag( char* value ); + void addUsageError( const char *line ); + bool CommandSet(); + bool FileSet(); + bool POSIX(); + + char parsePOSIX( char* arg ); + int parseGNU( char *arg ); + bool matchChar( char c ); + int matchOpt( char *opt ); + + /* dot file methods */ + char *readFile(); + char *readFile( const char* fname ); + bool consumeFile( char *buffer ); + void processLine( char *theline, int length ); + char *chomp( char *str ); + void valuePairs( char *type, char *value ); + void justValue( char *value ); + + void printVerbose( const char *msg ); + void printVerbose( char *msg ); + void printVerbose( char ch ); + void printVerbose( ); + + +}; + +#endif /* ! _ANYOPTION_H */ diff --git a/pacman-c++/server/server.cpp b/pacman-c++/server/server.cpp new file mode 100644 index 0000000..c9e4fff --- /dev/null +++ b/pacman-c++/server/server.cpp @@ -0,0 +1,971 @@ +#include "server.h" +#include "util.h" +#include "pacman.pb.h" +#include "block.h" +#include "anyoption.h" +#include "bonuspoint.h" +#include "point.h" +#include +#include + +Server::Server(QWidget *parent) + : SceneHolder(parent), m_host(NULL), m_bindaddress(QHostAddress::Any), + m_port(Constants::Networking::port), m_numbots(0), + m_rounds(3), m_curRound(0), m_running(false), m_finishRound(false) +{ + /* determine max players by using order array */ + for(m_maxplayers = 0; Color::order[m_maxplayers] != Color::none; ++m_maxplayers); +} + +Server::~Server() +{ + if (m_host != NULL) + { + foreach(ENetPeer *peer, m_clientConnections.keys()) + enet_peer_disconnect(peer, 0); + + /* allow up to 3 seconds for the disconnect to succeed + * and drop any packets received packets + */ + ENetEvent event; + while (m_clientConnections.count() > 0 + && enet_host_service(m_host, &event, 3000) > 0) + { + switch (event.type) + { + case ENET_EVENT_TYPE_RECEIVE: + enet_packet_destroy(event.packet); + break; + case ENET_EVENT_TYPE_DISCONNECT: + m_clientConnections.remove(event.peer); + return; + default: + break; + } + } + + enet_host_destroy(m_host); + } +} + +bool Server::run() +{ + /* create eating order list first */ + for(unsigned int i = 0; i < m_maxplayers; ++i) + m_eatingorder.append(Color::order[i]); + m_eatingorder.append(m_eatingorder.at(0)); + + m_tickTimer = new QTimer(this); + m_tickTimer->setInterval(Constants::tick); + connect(m_tickTimer, SIGNAL(timeout()), this, SLOT(tick())); + + std::cout << "[Server] Running server..." << std::endl + << "[Server] Max players: " << m_maxplayers << std::endl + << "[Server] Number of bots: " << m_numbots << std::endl + << "[Server] Number of rounds: " << m_rounds << std::endl; + if (!waitForClientConnections()) + return false; + + connect(this, SIGNAL(allPointsRemoved()), this, SLOT(setFinishRound())); + initRoundMap(); + return true; +} + +void Server::tick() +{ + //qDebug() << "[Tick] Doing server update"; + if (m_finishRound) + stopGame(true); + if (!m_running) + { + Transmission::map_t map = Util::createEmptyMap(); + sendUpdate(map); + Util::deleteMap(map); + return; + } + + /* fetch key updates */ + keyPressUpdate(); + + /* let the bots move */ + foreach (Color::Color color, m_bots) + botCalculate(m_actors[color]); + + /* move on the virtual map */ + Transmission::map_t map = calculateUpdates(); + updateMap(map); + + /* add a random bonus point */ + QPoint pos = addRandomPoint(map, Transmission::bonuspoint); + if (!pos.isNull()) + updateMap(map, pos.x(), pos.y()); + + /* add/remove random colorized block */ + if (this->property("coloredblocks").toBool()) + colorizeBlocks(map); + + sendUpdate(map); + Util::deleteMap(map); +} + +Transmission::map_t Server::calculateUpdates() +{ + QMap > movements; + Transmission::map_t map = Util::createEmptyMap(); + + QMutableMapIterator i(m_actorMovements); + while (i.hasNext()) + { + i.next(); + Color::Color color = i.key(); + Actor *actor = m_actors[color]; + QPoint mapPosition = CoordToMapPosition(actor->pos().toPoint()); + Actor::Movement direction = i.value(); + int turn = 0; + +invalid_direction: + ++turn; + qDebug() << "[Calc] Actor wants to move: color=" << color + << "pos=" << mapPosition << "direction=" << direction; + + QPoint newMapPosition = mapPosition + Actor::movementToPoint(direction); + if (newMapPosition.x() < 0) + newMapPosition.setX(0); + if (newMapPosition.x() >= visualMap.size()) + newMapPosition.setX(visualMap.size() - 1); + if (newMapPosition.y() < 0) + newMapPosition.setY(0); + if (newMapPosition.y() >= visualMap[newMapPosition.x()].size()) + newMapPosition.setY(visualMap[newMapPosition.x()].size() - 1); + + /* check if there's an item at new location of actor */ + GameEntity *item = visualMap[newMapPosition.x()][newMapPosition.y()]; + GameEntity *oldItem = visualMap[mapPosition.x()][mapPosition.y()]; + if (item != NULL && oldItem != item) + { + qDebug() << "[Calc] Found item at new actor location"; + if (!item->checkEnter(actor)) + { + /* movement invalid. e.g. move against wall */ + newMapPosition = mapPosition; + qDebug() << "[Calc] Item blocks actor"; + } + else + { + /* apply actions of entering this field */ + GameEntity::EnteredState survive = item->enter(actor); + if (survive == GameEntity::DestroyedEntity) + map[newMapPosition.x()][newMapPosition.y()] = Transmission::empty | actor->color(); + else if (survive == GameEntity::DestroyedActor) + { + map[newMapPosition.x()][newMapPosition.y()] = Transmission::death | actor->color(); + m_actors[item->color()]->addRoundPoints(actor->getRoundPoints()); + actor->finishRound(true); + setFinishRound(); + } + } + } + + /* movement didn't work - e.g. was blocked */ + if (mapPosition == newMapPosition) + { + /* check turn */ + if (turn == 1 && direction != actor->direction()) + { + /* set direction back to last known direction and try again */ + qDebug() << "[Calc] Movement was blocked. Trying last known actor direction"; + direction = actor->direction(); + goto invalid_direction; + } + else + { + /* second turn didn't work too -> stop movement */ + direction = Actor::None; + qDebug() << "[Calc] No good direction known. Movement stopped"; + } + } + + /* store movement for collision */ + movements.insert(color, QPair(mapPosition, newMapPosition)); + + /* set direction (used for collision detection) */ + actor->setDirection(direction); + + /* DEBUG: uncomments to disable auto-movement */ + //direction = Actor::None; + + /* actor is not moving anymore: remove from list */ + if (direction == Actor::None) + i.remove(); + } + + /* check for actor collision */ + QList blocked; + foreach(Color::Color color, movements.keys()) + { + Actor *actor = m_actors[color]; + QPoint oldpos = movements[color].first; + QPoint newpos = movements[color].second; + QPoint scenepos = actor->pos().toPoint(); + + /* first move actor to new position */ + actor->move(actor->direction()); + + /* next check for collisions */ + Actor *orderer = NULL; + Actor *meal = NULL; + foreach(Actor *other, m_actors) + { + if (actor == other) + continue; + if (!actor->collidesWithItem(other)) + continue; + /* both move in the same direction */ + if (actor->direction() == other->direction()) + continue; + + if (other->canEat(actor, m_eatingorder)) + { + qDebug() << "[Collision] Actor" << actor->color() << "got EATEN by" << other->color(); + orderer = other; + meal = actor; + break; + } + else if (actor->canEat(other, m_eatingorder)) + { + qDebug() << "[Collision] Actor" << actor->color() << "EATS" << other->color(); + orderer = actor; + meal = other; + blocked.append(other); + break; + } + else + { + qDebug() << "[Collision] Actor" << actor->color() << "got BLOCKED by" << other->color(); + blocked.append(actor); + /* no break here */ + } + } + + /* update map depending on collision */ + if (orderer != NULL && meal != NULL) + { + map[newpos.x()][newpos.y()] |= Transmission::pacman | Transmission::death + | orderer->color() | meal->color(); + orderer->addRoundPoints(meal->getRoundPoints()); + meal->finishRound(true); + setFinishRound(); + } + else if (blocked.contains(actor)) + { + /* move actor back early cause he won't move */ + actor->setPos(scenepos); + map[oldpos.x()][oldpos.y()] |= Transmission::pacman | color; + } + else + { + map[newpos.x()][newpos.y()] |= Transmission::pacman | color; + } + } + + /* move all actors back to their origin position */ + foreach(Color::Color color, movements.keys()) + m_actors[color]->setPos(mapPositionToCoord(movements[color].first)); + +#ifndef QT_NO_DEBUG + /* collision sanity check: colors must be unique */ + foreach(Color::Color col, m_eatingorder.toSet()) + { + QList found; + for (unsigned int x = 0; x < Constants::map_size.width; ++x) + { + for (unsigned int y = 0; y < Constants::map_size.height; ++y) + { + if ((map[x][y] & Transmission::color_mask) & col) + found.append(QPoint(x, y)); + } + } + if (found.count() > 1) + qCritical() << "[Collision] found" << found.count() << "fields with color=" << col; + } +#endif + + return map; +} + +QPoint Server::addRandomPoint(Transmission::map_t map, Transmission::field_t type) +{ + int chance = Constants::Game::bouns_point_chance + - Constants::Game::bouns_point_chance_playerfactor * (m_maxplayers - 1); + int rand = (int) (chance * (qrand() / (RAND_MAX + 1.0))); + if (rand != 0) + return QPoint(); + + /* get list of valid positions */ + QList validpos; + for (unsigned int x = 0; x < Constants::map_size.width; ++x) + { + for (unsigned int y = 0; y < Constants::map_size.height; ++y) + { + if (visualMap[x][y] == NULL) + validpos.append(QPoint(x, y)); + } + } + + /* remove actors positions from list + * performance would be better if actors would be listed in visualMap too + * but this isn't possible that easily. see comment in updateMap(map) + */ + foreach (Actor *actor, m_actors) + { + QPoint actorpos = CoordToMapPosition(actor->pos().toPoint()); + validpos.removeAll(actorpos); + } + + if (validpos.empty()) + return QPoint(); + + rand = (int) (validpos.size() * (qrand() / (RAND_MAX + 1.0))); + QPoint pos = validpos.at(rand); + map[pos.x()][pos.y()] = type; + return pos; +} + +void Server::colorizeBlocks(Transmission::map_t map) +{ + /* decrement tickcount of colored blocks */ + if (!m_coloredBlocks.empty()) + { + QMutableMapIterator i(m_coloredBlocks); + while(i.hasNext()) + { + i.next(); + unsigned val = i.value(); + if (val > 0) + i.setValue(--val); + else + { + QPoint block = i.key(); + /* check for actor collision */ + bool skip = false; + foreach (Actor *actor, m_actors) + { + if (CoordToMapPosition(actor->pos().toPoint()) == block) + skip = true; + if (skip) + break; + } + if (skip) + continue; + + map[block.x()][block.y()] |= Transmission::block | Color::none; + updateMap(map, block.x(), block.y()); + m_blocks.append(block); + i.remove(); + } + } + } + + /* colorize a random block */ + int rand = (int) (Constants::Game::colorize_block_chance * (qrand() / (RAND_MAX + 1.0))); + if (rand == 0) + { + rand = (int) (m_blocks.size() * (qrand() / (RAND_MAX + 1.0))); + QPoint block = m_blocks.at(rand); + m_blocks.removeAt(rand); + + unsigned int min = Constants::Game::colorize_block_tickcount_min; + unsigned int max = Constants::Game::colorize_block_tickcount_max; + int tickcount = min + (int) ((max - min + 1) * (qrand() / (RAND_MAX + 1.0))); + m_coloredBlocks.insert(block, tickcount); + + unsigned int color = (int) (m_maxplayers * (qrand() / (RAND_MAX + 1.0))); + map[block.x()][block.y()] |= Transmission::block | Color::order[color]; + updateMap(map, block.x(), block.y()); + } +} + +bool Server::waitForClientConnections() +{ + // server must stay alive as long as sockets (qt parent mem mechanism) + ENetAddress address; + address.host = ENET_HOST_ANY; + if (m_bindaddress != QHostAddress::Any) + enet_address_set_host(&address, qPrintable(m_bindaddress.toString())); + address.port = m_port; + + m_host = enet_host_create(&address, m_maxplayers - m_numbots, 1, 0, 0); + if (m_host == NULL) + { + qCritical() << "An error occurred while trying to create an ENet server host"; + return false; + } + + char buf[1024]; + enet_address_get_host_ip(&m_host->address, buf, 1024); + std::cout << "[Server] Listening on: " + << qPrintable(QString("%1:%2").arg(buf).arg(m_host->address.port)) << std::endl; + + /* add bots first */ + for (unsigned int i = (m_maxplayers - m_numbots); i < m_maxplayers; ++i) + { + m_bots.append(Color::order[i]); + m_actorMovements[Color::order[i]] = Actor::None; + } + + std::cout << "[Server] Waiting for clients" << std::endl; + ProtoBuf::Init packet; + packet.set_maxplayers(m_maxplayers); + unsigned int i = 0; + while(m_clientConnections.count() < int(m_maxplayers - m_numbots)) + { + ENetEvent event; + if (enet_host_service(m_host, &event, 3000) > 0) + { + switch (event.type) + { + case ENET_EVENT_TYPE_CONNECT: + { + /* assign color and notify client */ + Color::Color color = Color::order[i++]; + m_clientConnections[event.peer] = color; + packet.set_color(color); + Util::sendPacket(packet, event.peer, m_host); + std::cout << "[Connect] New Player: color=" << qPrintable(Util::colorToString(color)) << std::endl; + } + break; + case ENET_EVENT_TYPE_RECEIVE: + keyPressUpdate(&event); + break; + case ENET_EVENT_TYPE_DISCONNECT: + keyPressUpdate(&event); + break; + default: + break; + } + } + } + + qDebug() << "[Server] All Clients connected"; + return true; +} + +void Server::sendUpdate(Transmission::map_t map, bool firstPacket) +{ + m_updatepacket.Clear(); + + for (unsigned int x = 0; x < Constants::map_size.width; ++x) + { + for (unsigned int y = 0; y < Constants::map_size.height; ++y) + m_updatepacket.add_field(map[x][y]); + } + + for(unsigned int i = 0; i < m_maxplayers; ++i) + { + m_updatepacket.add_round_points(m_actors.value(Color::order[i])->getRoundPoints()); + m_updatepacket.add_game_points(m_actors.value(Color::order[i])->getGamePoints()); + } + + /* we send the eating_order inside the first packet */ + if (firstPacket) + { + foreach(Color::Color color, m_eatingorder) + m_updatepacket.add_eating_order(color); + } + + QSharedPointer data = Util::createPacket(m_updatepacket); + QMutableMapIterator i(m_clientConnections); + while(i.hasNext()) + { + i.next(); + ENetPeer *peer = i.key(); + + if (!Util::sendPacket(data.data(), peer, m_host)) + { + qWarning() << "[Connect] Error while sending data to client" << i.value() << "-> Disconnecting..."; + std::cout << "[Connect] Actor color=" << qPrintable(Util::colorToString(i.value())) + << " is now a bot" << std::endl; + m_bots.append(i.value()); + ++m_numbots; + i.remove(); + } + } + + if (m_maxplayers == m_numbots) + { + std::cout << "[Connect] No more real players left. Exiting..." << std::endl; + qApp->quit(); + } +} + +void Server::botCalculate(Actor *actor) +{ + QPoint actorpos = CoordToMapPosition(actor->pos().toPoint()); + + /* first make list of possible directions based on current actor position */ + QHash directions; + if (actorpos.x() > 0) + directions.insert(Actor::Left, 0); + if (actorpos.x() < visualMap.size() - 1) + directions.insert(Actor::Right, 0); + if (actorpos.y() > 0) + directions.insert(Actor::Up, 0); + if (actorpos.y() < visualMap[actorpos.x()].size() - 1) + directions.insert(Actor::Down, 0); + + /* check neighbours for blocks first */ + QMutableHashIterator i(directions); + while(i.hasNext()) + { + i.next(); + QPoint newpos = actorpos + Actor::movementToPoint(i.key()); + if (newpos.x() < 0 || newpos.x() >= visualMap.size()) + continue; + if (newpos.y() < 0 || newpos.y() >= visualMap[newpos.x()].size()) + continue; + GameEntity *item = visualMap[newpos.x()][newpos.y()]; + + /* check if neighbour is a block */ + Block *block = qgraphicsitem_cast(item); + if (block != NULL && block->color() != actor->color()) + i.remove(); + } + + /* we're enclosed by blocks */ + if (directions.empty()) + return; + + /* determine if other actors are in range to be afraid/to hunt */ + int mindistance = Constants::AI::player_minimum_distance; + QList pos_afraid; + QList pos_hunt; + foreach (Actor *other, m_actors) + { + if (actor == other) + continue; + + QList *ptr = NULL; + if (other->canEat(actor, m_eatingorder)) + ptr = &pos_afraid; + else if (actor->canEat(other, m_eatingorder)) + ptr = &pos_hunt; + if (ptr == NULL) + continue; + + QPoint otherpos = CoordToMapPosition(other->pos().toPoint()); + QPoint distance = actorpos - otherpos; + if (distance.manhattanLength() < mindistance) + ptr->append(otherpos); + } + + /* check new directions and change direction-weight */ + i = directions; + while(i.hasNext()) + { + i.next(); + QPoint newpos = actorpos + Actor::movementToPoint(i.key()); + + /* check for new positions in afraid list */ + foreach(QPoint otherpos, pos_afraid) + { + int olddistance = (actorpos - otherpos).manhattanLength(); + int newdistance = (newpos - otherpos).manhattanLength(); + if (newdistance >= olddistance) + i.setValue(i.value() + Constants::AI::weight_afraid); + + /* check for blocks of own color: other pacman can't follow their */ + GameEntity *item = visualMap[newpos.x()][newpos.y()]; + Block *block = qgraphicsitem_cast(item); + if (block != NULL && block->color() == actor->color()) + i.setValue(i.value() + Constants::AI::weight_colorblock); + } + + /* check for new positions in hunt list */ + foreach(QPoint otherpos, pos_hunt) + { + int olddistance = (actorpos - otherpos).manhattanLength(); + int newdistance = (newpos - otherpos).manhattanLength(); + if (newdistance <= olddistance) + i.setValue(i.value() + Constants::AI::weight_hunt); + } + + /* check for bonuspoint */ + GameEntity *item = visualMap[newpos.x()][newpos.y()]; + BonusPoint *bpoint = qgraphicsitem_cast(item); + if (bpoint != NULL) + i.setValue(i.value() + Constants::AI::weight_bonus_point); + + /* check for normal point */ + Point *point = qgraphicsitem_cast(item); + if (point != NULL) + i.setValue(i.value() + Constants::AI::weight_point); + } + + /* sort directions */ + QList weightlist = directions.values(); + qSort(weightlist.begin(), weightlist.end(), qGreater()); + + /* remove directions with lesser weight */ + unsigned int max = weightlist.at(0); + i = directions; + while(i.hasNext()) + { + i.next(); + if (i.value() < max) + i.remove(); + } + + QList list = directions.keys(); + + /* default is no direction change */ + if (list.contains(actor->direction())) + return; + + /* random direction */ + int rand = (int) (list.size() * (qrand() / (RAND_MAX + 1.0))); + m_actorMovements[actor->color()] = list.at(rand); +} + +void Server::keyPressUpdate() +{ + ProtoBuf::KeyPressUpdate packet; + ENetEvent event; + while (enet_host_service(m_host, &event, 1) > 0) + keyPressUpdate(&event); +} + +void Server::keyPressUpdate(ENetEvent *event) +{ + ProtoBuf::KeyPressUpdate packet; + switch(event->type) + { + case ENET_EVENT_TYPE_RECEIVE: + { + QSharedPointer data = Util::receivePacket(event->packet); + enet_packet_destroy(event->packet); + bool worked = packet.ParseFromArray(data->data(), data->size()); + Q_ASSERT(worked); + Q_UNUSED(worked); + Transmission::field_t direction = packet.newkey(); + Color::Color color = m_clientConnections[event->peer]; + qDebug() << "[KeyPress] actor=" << color << "direction=" << direction; + m_actorMovements[color] = Util::transmissionMovementToActor(direction); + } + break; + case ENET_EVENT_TYPE_DISCONNECT: + { + Color::Color color = m_clientConnections[event->peer]; + std::cout << "[Connect] Actor color=" << qPrintable(Util::colorToString(color)) + << " is now a bot" << std::endl; + m_bots.append(color); + ++m_numbots; + m_clientConnections.remove(event->peer); + } + break; + default: + break; + } +} + +void Server::initRoundMap() +{ + std::cout << "[Game] New round starts..." << std::endl; + m_tickTimer->stop(); + + /* reset scene and clean up items */ + reset(); + + /* randomize color eating order */ + m_eatingorder.removeLast(); + random_shuffle(m_eatingorder.begin(), m_eatingorder.end()); + m_eatingorder.append(m_eatingorder.at(0)); + + /* create new map */ + Transmission::map_t map = Util::createDemoMap(); + Util::placeActors(map, m_maxplayers, Color::order); + Util::fillPoints(map); + +#if 0 // actor eating actor tests - TODO: remove + m_actorMovements.clear(); +#if 0 + //works + map[0][0] = Color::order[0] | Transmission::pacman; + map[0][1] = Color::order[1] | Transmission::pacman; + m_actorMovements.insert(Color::order[0], Actor::Down); +#elif 0 + //works + map[0][0] = Color::order[0] | Transmission::pacman; + map[0][3] = Color::order[1] | Transmission::pacman; + m_actorMovements.insert(Color::order[0], Actor::Down); +#elif 0 + //works + map[0][0] = Color::order[0] | Transmission::pacman; + map[0][4] = Color::order[1] | Transmission::pacman; + m_actorMovements.insert(Color::order[0], Actor::Down); +#elif 0 + //works + map[0][0] = Color::order[0] | Transmission::pacman; + map[0][5] = Color::order[1] | Transmission::pacman; + m_actorMovements.insert(Color::order[0], Actor::Down); +#elif 0 + //works + map[0][0] = Color::order[1] | Transmission::pacman; + map[0][5] = Color::order[0] | Transmission::pacman; + m_actorMovements.insert(Color::order[1], Actor::Down); +#elif 0 + //works + map[0][0] = Color::order[0] | Transmission::pacman; + map[0][5] = Color::order[1] | Transmission::pacman; + m_actorMovements.insert(Color::order[0], Actor::Down); + m_actorMovements.insert(Color::order[1], Actor::Up); +#elif 0 + //works + map[0][0] = Color::order[0] | Transmission::pacman; + map[0][6] = Color::order[1] | Transmission::pacman; + m_actorMovements.insert(Color::order[0], Actor::Down); + m_actorMovements.insert(Color::order[1], Actor::Up); +#elif 0 + //works + map[0][0] = Color::order[0] | Transmission::pacman; + map[0][7] = Color::order[1] | Transmission::pacman; + m_actorMovements.insert(Color::order[0], Actor::Down); + m_actorMovements.insert(Color::order[1], Actor::Up); +#elif 1 + //works + map[0][0] = Color::order[0] | Transmission::pacman; + map[0][1] = Color::order[1] | Transmission::pacman; + m_actorMovements.insert(Color::order[0], Actor::Down); + m_actorMovements.insert(Color::order[1], Actor::Down); +#endif +#endif + + /* save positions of blocks for later usage */ + m_blocks.clear(); + for (unsigned int x = 0; x < Constants::map_size.width; ++x) + { + for (unsigned int y = 0; y < Constants::map_size.height; ++y) + { + Transmission::field_t &cur = map[x][y]; + if (cur & Transmission::block) + m_blocks.append(QPoint(x, y)); + } + } + + updateMap(map); + sendUpdate(map, true); + Util::deleteMap(map); + map = NULL; + + m_actorMovements.clear(); + + disconnect(AudioManager::self()->audioPlayer(), NULL, this, NULL); + connect(AudioManager::self()->audioPlayer(), SIGNAL(finished()), this, SLOT(startGame())); + AudioManager::self()->play(Sound::Intro, true); + m_tickTimer->start(); +} + +void Server::startGame() +{ + m_running = true; +} + +void Server::stopGame(bool delay) +{ + /* first finish previous round */ + foreach(Actor *actor, m_actors) + actor->finishRound(); + m_finishRound = false; + m_running = false; + + /* add delay if requested */ + if (delay) + { + disconnect(AudioManager::self()->audioPlayer(), NULL, this, NULL); + connect(AudioManager::self()->audioPlayer(), SIGNAL(finished()), this, SLOT(stopGame())); + AudioManager::self()->play(Sound::Die, true); + return; + } + + std::cout << "[Game] Round finished..." << std::endl; + + /* do next-round work */ + ++m_curRound; + if(m_rounds == 0 || m_curRound < m_rounds) + initRoundMap(); + else + { + /* end of game */ + std::cout << "[Game] All round finished. Exiting..." << std::endl; + qApp->quit(); + } +} + +void Server::setFinishRound() +{ + m_finishRound = true; +} + +bool Server::parseCommandline() +{ + AnyOption opt; + opt.setVerbose(); + + /* usage strings must remain valid until parsing is done */ + QString exec = QFileInfo(qApp->applicationFilePath()).fileName(); + QByteArray usage; + QTextStream out(&usage, QIODevice::ReadWrite | QIODevice::Text); + out << "Usage: " << exec << " [OPTION]" << endl + << "Usage: " << exec << " -h" << endl + << endl; + out << " -b, --bind " << endl + << " Specifies the ip address on which the server listens for connections" << endl + << " Default: " << m_bindaddress.toString() << endl + << endl; + opt.setOption("bind", 'b'); + out << " -p, --port " << endl + << " Specifies the port on which the server listens for connections" << endl + << " Default: " << m_port << endl + << endl; + opt.setOption("port", 'p'); + out << " -m, --maxplayers [1.." << m_maxplayers << "]" << endl + << " Specifies the maximum number of players/pacmans" << endl + << " Default: " << m_maxplayers << endl + << endl; + opt.setOption("maxplayers", 'm'); + out << " --bots [0..maxplayers-1]" << endl + << " Specifies the number of AI pacmans/bots" << endl + << " Default: " << m_numbots << endl + << endl; + opt.setOption("bots"); + out << " --nocolorblocks" << endl + << " Disable random colorized blocks" << endl + << endl; + opt.setOption("rounds", 'r'); + out << " -r, --rounds [0 | 1..n]" << endl + << " Number of rounds to play" << endl + << " Default: " << m_rounds << endl + << endl; + opt.setFlag("nocolorblocks"); + out << " -h, --help" << endl + << " Prints this help message" << endl; + opt.setFlag("help", 'h'); + out.flush(); + opt.addUsage(usage.constData()); + opt.processCommandArgs(qApp->argc(), qApp->argv()); + + if (opt.getFlag("help") || opt.getFlag('h')) + { + opt.printUsage(); + return false; + } + + if (opt.getValue("port") != NULL) + { + bool ok; + m_port = QString(opt.getValue("port")).toUInt(&ok); + if (!ok || m_port < 1 || m_port > 65535) + { + qCritical() << "Invalid port-option:" << opt.getValue("port") << endl + << "Port must be between 1 and 65535"; + return false; + } + } + + if (opt.getValue("bind") != NULL) + { + m_bindaddress = opt.getValue("bind"); + if (m_bindaddress.isNull()) + { + qCritical() << "Invalid bind-option:" << opt.getValue("bind") << endl + << "Bind address must be an ip address"; + return false; + } + } + + if (opt.getValue("maxplayers") != NULL) + { + bool ok; + unsigned int maxplayers = QString(opt.getValue("maxplayers")).toUInt(&ok); + if (!ok || maxplayers < 1 || maxplayers > m_maxplayers) + { + qCritical() << "Invalid maxplayers-option:" << opt.getValue("maxplayers") << endl + << "Maxplayers must be between 1 and" << m_maxplayers; + return false; + } + m_maxplayers = maxplayers; + if (m_maxplayers == 2) + { + qCritical() << "2 player game is not supported (who wins if a player gets eaten?)"; + return false; + } + } + + if (opt.getValue("bots") != NULL) + { + bool ok; + unsigned int numbots = QString(opt.getValue("bots")).toUInt(&ok); + if (!ok || numbots >= m_maxplayers) + { + qCritical() << "Invalid numbots-options:" << opt.getValue("bots") << endl + << "AI pacmans/bots must be between 0 and" << m_maxplayers - 1; + return false; + } + m_numbots = numbots; + } + + if (opt.getValue("rounds") != NULL) + { + bool ok; + unsigned int rounds = QString(opt.getValue("rounds")).toUInt(&ok); + if (!ok) + { + qCritical() << "Invalid number of rounds: " << opt.getValue("rounds") << endl; + return false; + } + m_rounds = rounds; + } + + this->setProperty("coloredblocks", !opt.getFlag("nocolorblocks")); + + return true; +} + +bool operator<(const QPoint& lhs, const QPoint& rhs) +{ + if (lhs.x() < rhs.x()) + return true; + else if (lhs.x() == rhs.x()) + return lhs.y() < rhs.y(); + else + return false; +} + +bool Constants::server = true; + +int main(int argc, char **argv) +{ + /* Verify that the version of the library that we linked against is + * compatible with the version of the headers we compiled against. + */ + GOOGLE_PROTOBUF_VERIFY_VERSION; + + QApplication app(argc, argv, false); + app.setApplicationName("Pacman Server"); + app.setWindowIcon(QIcon(":/appicon")); + + qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime())); + + int ret = 0; + Server server; + if (!ret && !server.parseCommandline()) + ret = 1; + if (!ret && !server.run()) + ret = 1; + if (!ret) + ret = app.exec(); + + /* Delete all global objects allocated by libprotobuf */ + google::protobuf::ShutdownProtobufLibrary(); + + return ret; +} diff --git a/pacman-c++/server/server.h b/pacman-c++/server/server.h new file mode 100644 index 0000000..857f23d --- /dev/null +++ b/pacman-c++/server/server.h @@ -0,0 +1,85 @@ +#ifndef SERVER_H +#define SERVER_H + +#include "sceneholder.h" +#include "actor.h" +#include "pacman.pb.h" +#include +#include + +extern "C" { +#include "enet/enet.h" +} + +class QTcpSocket; + +class Server + : public SceneHolder +{ + Q_OBJECT +public: + Server(QWidget *parent = 0); + ~Server(); + bool parseCommandline(); + bool run(); + +protected slots: + void tick(); + + /* receive updates of client */ + void keyPressUpdate(); + void keyPressUpdate(ENetEvent *event); + +protected: + /* block until we have connections from all clients */ + bool waitForClientConnections(); + + /* calculate updates of current tick for sending to client */ + Transmission::map_t calculateUpdates(); + + /* update client maps */ + void sendUpdate(Transmission::map_t map, bool firstPacket = false); + + QPoint addRandomPoint(Transmission::map_t map, Transmission::field_t type = Transmission::bonuspoint); + void colorizeBlocks(Transmission::map_t map); + void botCalculate(Actor *actor); + void initRoundMap(); + +protected slots: + /* called when a round is started/finished */ + void startGame(); + void stopGame(bool delay = false); + void setFinishRound(); + +protected: + ENetHost *m_host; + QMap m_clientConnections; + QList m_bots; + + /* current movements. required to make pacmans continue their movement */ + QMap m_actorMovements; + + /* allocate as member variable as this packet is large and used often */ + ProtoBuf::MapUpdate m_updatepacket; + + /* list of blocks */ + QList m_blocks; + /* currently colored blocks + tickcount before they will turn to non-colored back */ + QMap m_coloredBlocks; + + QHostAddress m_bindaddress; + unsigned int m_port; + unsigned int m_maxplayers; + unsigned int m_numbots; + /* number of rounds (>= 1) */ + unsigned int m_rounds; + /* current round, starting at 0 */ + unsigned int m_curRound; + bool m_running; + bool m_finishRound; + + QTimer *m_tickTimer; + +}; + +#endif // SERVER_H diff --git a/pacman-c++/server/server.pro b/pacman-c++/server/server.pro new file mode 100644 index 0000000..05d5c9d --- /dev/null +++ b/pacman-c++/server/server.pro @@ -0,0 +1,10 @@ +TEMPLATE = app +TARGET = pacman-server + +SOURCES += anyoption.cpp \ + server.cpp +HEADERS += anyoption.h \ + server.h + +include(../common.pri) +PRE_TARGETDEPS += ../common/libcommon.a -- cgit v1.2.3