Repository: happypepper/DeepHoldem Branch: master Commit: 68953137dd9d Files: 121 Total size: 63.7 MB Directory structure: gitextract_p10ow7qd/ ├── .gitignore ├── ACPCServer/ │ ├── LICENCE │ ├── Makefile │ ├── README │ ├── README.submission │ ├── acpc_play_match.pl │ ├── all_in_expectation.c │ ├── bm_run_matches.c │ ├── bm_server.c │ ├── bm_server.config │ ├── bm_widget.c │ ├── dealer.c │ ├── evalHandTables │ ├── example_player.c │ ├── example_player.kuhn.limit.3p.sh │ ├── example_player.limit.2p.sh │ ├── example_player.limit.3p.sh │ ├── example_player.nolimit.2p.sh │ ├── example_player.nolimit.3p.sh │ ├── game.c │ ├── game.h │ ├── holdem.limit.2p.reverse_blinds.game │ ├── holdem.limit.3p.game │ ├── holdem.nolimit.2p.reverse_blinds.game │ ├── holdem.nolimit.3p.game │ ├── kuhn.limit.3p.game │ ├── leduc.game │ ├── net.c │ ├── net.h │ ├── play_match.pl │ ├── protocol.odt │ ├── rng.c │ ├── rng.h │ ├── sum_values.pl │ └── validate_submission.pl ├── Data/ │ ├── .gitignore │ └── Models/ │ └── NoLimit/ │ ├── flop/ │ │ └── final_cpu.info │ ├── preflop-aux/ │ │ ├── final_cpu.info │ │ └── final_cpu.model │ ├── river/ │ │ └── final_cpu.info │ └── turn/ │ └── final_cpu.info ├── Source/ │ ├── ACPC/ │ │ ├── Tests/ │ │ │ └── test_parser.lua │ │ ├── acpc_game.lua │ │ ├── network_communication.lua │ │ └── protocol_to_node.lua │ ├── DataGeneration/ │ │ ├── aux_data_generation.lua │ │ ├── data_generation.lua │ │ ├── main_aux_data_generation.lua │ │ ├── main_data_generation.lua │ │ ├── random_card_generator.lua │ │ └── range_generator.lua │ ├── Game/ │ │ ├── Evaluation/ │ │ │ └── evaluator.lua │ │ ├── bet_sizing.lua │ │ ├── card_to_string_conversion.lua │ │ └── card_tools.lua │ ├── Lookahead/ │ │ ├── Tests/ │ │ │ ├── ranges/ │ │ │ │ ├── flop-situation1-p1.txt │ │ │ │ ├── flop-situation1-p2.txt │ │ │ │ ├── flop-situation2-p1.txt │ │ │ │ ├── flop-situation2-p2.txt │ │ │ │ ├── flop-situation3-p1.txt │ │ │ │ ├── flop-situation3-p2.txt │ │ │ │ ├── flop-situation4-p1.txt │ │ │ │ ├── flop-situation4-p2.txt │ │ │ │ ├── situation-p1.txt │ │ │ │ ├── situation-p2.txt │ │ │ │ ├── situation2-p1.txt │ │ │ │ ├── situation2-p2.txt │ │ │ │ ├── situation3-p1.txt │ │ │ │ └── situation3-p2.txt │ │ │ ├── test_flop_nl.lua │ │ │ ├── test_preflop_nl.lua │ │ │ ├── test_river_nl.lua │ │ │ └── test_turn_nl.lua │ │ ├── cfrd_gadget.lua │ │ ├── lookahead.lua │ │ ├── lookahead_builder.lua │ │ ├── mock_resolving.lua │ │ └── resolving.lua │ ├── Nn/ │ │ ├── Bucketing/ │ │ │ ├── .gitignore │ │ │ ├── flop_tools.lua │ │ │ ├── river_tools.lua │ │ │ └── turn_tools.lua │ │ ├── bucket_conversion.lua │ │ ├── bucketer.lua │ │ ├── cpu_gpu_model_converter.lua │ │ ├── masked_huber_loss.lua │ │ ├── mock_nn_terminal.lua │ │ ├── net_builder.lua │ │ ├── next_round_value.lua │ │ ├── next_round_value_pre.lua │ │ ├── next_round_value_test.lua │ │ └── value_nn.lua │ ├── Player/ │ │ ├── continual_resolving.lua │ │ ├── deepstack.lua │ │ ├── deepstack_server.lua │ │ ├── manual_player.lua │ │ ├── slum_util.py │ │ └── slumbot_player.py │ ├── Settings/ │ │ ├── arguments.lua │ │ ├── constants.lua │ │ └── game_settings.lua │ ├── TerminalEquity/ │ │ └── terminal_equity.lua │ ├── Training/ │ │ ├── data_stream.lua │ │ ├── main_train.lua │ │ ├── raw_converter.lua │ │ └── train.lua │ ├── Tree/ │ │ ├── Tests/ │ │ │ ├── test_tree_builder.lua │ │ │ ├── test_tree_cfr.lua │ │ │ ├── test_tree_strategy_fillings.lua │ │ │ ├── test_tree_values.lua │ │ │ └── test_tree_visualiser.lua │ │ ├── strategy_filling.lua │ │ ├── tree_builder.lua │ │ ├── tree_cfr.lua │ │ ├── tree_strategy_filling.lua │ │ ├── tree_values.lua │ │ └── tree_visualiser.lua │ └── tools.lua ├── readme.md └── torch/ ├── extra/ │ └── cutorch/ │ └── TensorMath.lua └── pkg/ └── torch/ └── TensorMath.lua ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ **/a.out **/.DS_Store ================================================ FILE: ACPCServer/LICENCE ================================================ Copyright (C) 2011 by the Computer Poker Research Group, University of Alberta Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Except as contained in this notice, the name(s) of the above copyright holders shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: ACPCServer/Makefile ================================================ CC = gcc CFLAGS = -O3 -Wall PROGRAMS = all_in_expectation bm_run_matches dealer example_player all: $(PROGRAMS) clean: rm -f $(PROGRAMS) all_in_expectation: all_in_expectation.c game.c game.h rng.c rng.h net.c net.h $(CC) $(CFLAGS) -o $@ all_in_expectation.c game.c rng.c net.c bm_server: bm_server.c game.c game.h rng.c rng.h net.c net.h $(CC) $(CFLAGS) -o $@ bm_server.c game.c rng.c net.c bm_widget: bm_widget.c net.c net.h $(CC) $(CFLAGS) -o $@ bm_widget.c net.c bm_run_matches: bm_run_matches.c net.c net.h $(CC) $(CFLAGS) -o $@ bm_run_matches.c net.c dealer: game.c game.h evalHandTables rng.c rng.h dealer.c net.c net.h $(CC) $(CFLAGS) -o $@ game.c rng.c dealer.c net.c example_player: game.c game.h evalHandTables rng.c rng.h example_player.c net.c net.h $(CC) $(CFLAGS) -o $@ game.c rng.c example_player.c net.c ================================================ FILE: ACPCServer/README ================================================ This README contains information about the server code for the Annual Computer Poker Competition. Please see the LICENCE file for information regarding the code's licence. ===== Software Requirements ===== This code was developed and tested for use on Unix based systems. Though it may work on other platforms, there are no guarantees. You will need standard Unix developer tools to build the software including gcc, and make. ===== Getting Started ===== * Building the code The Makefile provides instructions for compiling the code. Running 'make' from the command line will compile the required programs. * The programs dealer - Communicates with agents connected over sockets to play a game example_player - A sample player implemented in C play_match.pl - A perl script for running matches with the dealer Usage information for each of the programs is available by running the executable without any arguments. * Playing a match The fastest way to start a match is through the play_match.pl script. An example follows: $ ./play_match.pl matchName holdem.limit.2p.reverse_blinds.game 1000 0 Alice ./example_player.limit.2p.sh Bob ./example_player.limit.2p.sh After play_match.pl finishes running, there will be two output files for the dealer and two output files for each player in the game: matchName.err - The stderr from dealer including the messages sent to players matchName.log - The log for the hands played during the match matchName.playerN.std - stdout from player N matchName.playerN.err - stderr from player N Note, play_match.pl expects player executables that take exactly two arguments: the server IP followed by the port number. The executable must be specified such that it is either a path or the executable name if it can be found in your $PATH. If you need to pass specific arguments to you agent, we suggest wrapping it in another script. play_match.pl will pass any extra arguments to dealer. Matches can also be started by calling dealer and starting the players manually. More information on this is contained in the dealer section below. * dealer Running dealer will start a process that waits for other players to connect to it. After starting dealer, it will output something similar to the following: $ ./dealer matchName holdem.limit.2p.reverse_blinds.game 1000 0 Alice Bob 16177 48777 # name/game/hands/seed matchName holdem.limit.2p.reverse_blinds.game 1000 0 #--t_response 10000 #--t_hand 600000 #--t_per_hand 6000 On the first line of output there should be as many numbers as there are players in the game (in this case, "16177" and "48777"). These are the ports the dealer is listening on for players. Note that these ports are specific to the positions for players in the game. Once all the players have connected to the game, the dealer will begin playing the game and outputting the messages sent to each player. After the end of the match, you should have a log file called matchName.log in the directory where dealer was started with the hands that were played. Matches can also be started by starting the dealer and connecting the executables by hand. This can be useful if you want to start your own program in a way that is difficult to script (such as running it in a debugger). ==== Game Definitions ==== The dealer takes game definition files to determine which game of poker it plays. Please see the included game definitions for some examples. The code for handling game definitions is found in game.c and game.h. Game definitions can have the following fields (case is ignored): gamedef - the starting tag for a game definition end gamedef - ending tag for a game definition stack - the stack size for each player at the start of each hand (for no-limit) blind - the size of the blinds for each player (relative to the dealer) raisesize - the size of raises on each round (for limit games) limit - specifies a limit game nolimit - specifies a no-limit game numplayers - number of players in the game numrounds - number of betting rounds per hand of the game firstplayer - the player that acts first (relative to the dealer) on each round maxraises - the maximum number of raises on each round numsuits - the number of different suits in the deck numranks - the number of different ranks in the deck numholecards - the number of private cards to deal to each player numboardcards - the number of cards revealed on each round Empty lines or lines with '#' as the very first character will be ignored If you are creating your own game definitions, please note that game.h defines some constants for maximums in games (e.g., number of rounds). These may need to be changed for games outside of the what is being run for the Annual Computer Poker Competition. ================================================ FILE: ACPCServer/README.submission ================================================ ############################################################# # Please fill out the following information about your team ############################################################# Team Name: Agent Name (can be the same as team name): For each team member, please list the following: Name, Team leader (y/n)?, e-mail, Academic (y/n, position - e.g., PhD student)?, University/Business affiliation, Location (city, province/state, country) Was this submission part of an academic class project? What level of class (undergraduate/graduate)? ########################################################################### # Please provide as much information about your agent as possible as the # competition organizers are very interested in knowing more about the # techniques used by our competitors. ########################################################################### 1) Is your agent dynamic? That is, does its strategy change throughout the course of a match, or is the strategy played the same throughout the match? 2) Does your agent use a (approximate) Nash equilibrium strategy? 3) Does your agent attempt to model your opponents? If so, does it do so online during the competition or offline from data (e.g., using logs of play or the benchmark server)? 4) Does your agent use techniques that would benefit from additional CPU time during the competition? 5) Does your agent use techniques that would benefit from additional RAM during the competition? 6) Would you agent benefit from additional disk space? One/Two Paragraph Summary of Technique References to relevant papers, if any ================================================ FILE: ACPCServer/acpc_play_match.pl ================================================ #!/usr/bin/perl # Copyright (C) 2011 by the Computer Poker Research Group, University of Alberta use Socket; use File::Basename; $hostname = `hostname` or die "could not get hostname"; chomp $hostname; @hostent = gethostbyname( $hostname ); $#hostent >= 4 or die "could not look up $hostname"; $hostip = inet_ntoa( $hostent[ 4 ] ); $#ARGV >= 3 or die "usage: play_match.pl matchName gameDefFile #Hands rngSeed player1name player1exe player2name player2exe ... [options]"; $numPlayers = -1; open FILE, '<', $ARGV[ 1 ] or die "couldn't open game definition $ARGV[ 1 ]"; while( $_ = ) { @_ = split; if( uc( $_[ 0 ] ) eq 'NUMPLAYERS' ) { $numPlayers = $_[ $#_ ]; } } close FILE; $numPlayers > 1 or die "couldn't get number of players from $ARGV[ 1 ]"; $#ARGV >= 3 + $numPlayers * 2 or die "too few players on command line"; pipe STDINREADPIPE, STDINWRITEPIPE or die "couldn't create stdin pipe"; pipe STDOUTREADPIPE, STDOUTWRITEPIPE or die "couldn't create stdout pipe"; $dealerPID = fork(); if( $dealerPID == 0 ) { # we're the child # replace standard in and standard out with pipe close STDINWRITEPIPE; close STDOUTREADPIPE; open STDIN, '<&STDINREADPIPE' or die "can't dup STDIN"; open STDOUT, '>&STDOUTWRITEPIPE' or die "can't dup STDOUT"; open STDERR, ">>$ARGV[ 0 ].err" or die "can't open log file $ARGV[ 0 ].err"; @args = ( "dealer", $ARGV[ 0 ], $ARGV[ 1 ], $ARGV[ 2 ], $ARGV[ 3 ] ); # add names to the arguments for( $p = 0; $p < $numPlayers; ++$p ) { push @args, $ARGV[ 4 + $p * 2 ]; } # add any extra arguments (options?) to the arguments for( $i = 4 + $numPlayers * 2; $i <= $#ARGV; ++$i ) { push @args, $ARGV[ $i ]; } exec { "./dealer" } @args or die "Couldn't run dealer"; } close STDINREADPIPE; close STDOUTWRITEPIPE; $_ = or die "couldn't read port description from dealer"; @_ = split; $#_ + 1 >= $numPlayers or die "couldn't get enough ports from $_"; for( $p = 0; $p < $numPlayers; ++$p ) { $playerPID[ $p ] = fork(); if( $playerPID[ $p ] == 0 ) { # we're the child # log standard out and standard error open STDOUT, ">$ARGV[ 0 ].player$p.std" or die "can't dup player $p STDOUT"; open STDERR, ">$ARGV[ 0 ].player$p.err" or die "can't dup player $p STDERR"; ($playerExec, $playerDir) = fileparse( $ARGV[ 4 + $p * 2 + 1 ] ); chdir $playerDir or die "Can't cd to $playerDir: $!\n"; exec { "./$playerExec" } ( "./$playerExec", $hostip, $_[ $p ] ) or die "couldn't run $playerExec from $playerDir for player $p"; } } $_ = ; for( $p = 0; $p < $numPlayers; ++$p ) { waitpid( $playerPID[ $p ], 0 ); } waitpid( $dealerPID, 0 ); $_ or die "couldn't get values from dealer"; print $_; exit( 0 ); ================================================ FILE: ACPCServer/all_in_expectation.c ================================================ /* Copyright (C) 2014 by the Computer Poker Research Group, University of Alberta */ #include #include #define __STDC_LIMIT_MACROS #include #include #include #include #include #include #include #include #include #include "game.h" #include "net.h" void getUsedCards( const Game *game, const State *state, const int lastRound, uint8_t *used ) { int i, p; /* start with no cards used */ memset( used, 0, sizeof( used[ 0 ] ) * game->numSuits * game->numRanks ); /* collect the player cards */ for( p = 0; p < game->numPlayers; ++p ) { for( i = 0; i < game->numHoleCards; ++i ) { used[ state->holeCards[ p ][ i ] ] = 1; } } /* collect the board cards up to lastRound */ p = sumBoardCards( game, lastRound ); for( i = 0; i < p; ++i ) { used[ state->boardCards[ i ] ] = 1; } } int main( int argc, char **argv ) { int stateEnd, r, i, p, deckSize, numBoards; FILE *file; Game *game; State state; uint8_t deck[ MAX_SUITS * MAX_RANKS ]; uint8_t used[ MAX_SUITS * MAX_RANKS ]; double value[ MAX_PLAYERS ]; char line[ 4096 ]; if( argc < 3 ) { fprintf( stderr, "USAGE: %s game_def log_file\n", argv[ 0 ] ); exit( EXIT_FAILURE ); } /* get the game definition */ file = fopen( argv[ 1 ], "r" ); if( file == NULL ) { fprintf( stderr, "ERROR: could not open game definition %s\n", argv[ 1 ] ); exit( EXIT_FAILURE ); } game = readGame( file ); if( game == NULL ) { fprintf( stderr, "ERROR: could not read game %s\n", argv[ 1 ] ); exit( EXIT_FAILURE ); } fclose( file ); /* get the log file */ file = fopen( argv[ 2 ], "r" ); if( file == NULL ) { fprintf( stderr, "ERROR: could not open log file %s\n", argv[ 2 ] ); exit( EXIT_FAILURE ); } /* read every line and process all hands */ while( fgets( line, 4096, file ) ) { stateEnd = readState( line, game, &state ); if( stateEnd < 0 ) { /* couldn't read a state from the line */ continue; } if( numAllIn( game, &state ) == 0 || numFolded( game, &state ) + 1 >= game->numPlayers ) { /* no one all in, or game didn't end in a showdown */ printf( "%s", line ); continue; } /* find last round where someone made an action */ for( r = state.round; r > 0; --r ) { if( state.numActions[ r ] ) { break; } } if( r + 1 == game->numRounds ) { /* there are no board cards left to roll out on the final round */ printf( "%s", line ); continue; } /* initialise values to 0 */ memset( value, 0, sizeof( value ) ); /* set up a deck containing all cards up to round r */ getUsedCards( game, &state, r, used ); deckSize = 0; for( i = 0; i < game->numSuits * game->numRanks; ++i ) { if( !used[ i ] ) { deck[ deckSize ] = i; ++deckSize; } } /* switch to using used[] as the index into deck[] for the remaining cards used on the board sort hands in ascending order, start with highest indexed hand */ const int bcStart = sumBoardCards( game, r ); const int numCards = sumBoardCards( game, game->numRounds - 1 ) - bcStart; for( i = 0; i < numCards; ++i ) { used[ i ] = deckSize - numCards + i; state.boardCards[ bcStart + i ] = deck[ used[ i ] ]; } /* try every possible board */ numBoards = 0; while( 1 ) { /* get the values */ for( p = 0; p < game->numPlayers; ++p ) { value[ p ] += valueOfState( game, &state, p ); } /* move on to the next board */ ++numBoards; /* find position of first card we can decrement */ i = 0; while( used[ i ] == i && i < numCards ) { ++ i; } if( i == numCards ) { /* can't decrement any cards, so we're done */ break; } /* decrement the card */ --used[ i ]; state.boardCards[ bcStart + i ] = deck[ used[ i ] ]; /* fill in all earlier cards with highest possible index */ while( i > 0 ) { /* move to previous card, set index to one lower then current card */ --i; used[ i ] = used[ i + 1 ] - 1; state.boardCards[ bcStart + i ] = deck[ used[ i ] ]; } } /* do the printout - start with the state */ if( line[ stateEnd ] != 0 ) { if( line[ stateEnd ] != ':' && line[ stateEnd ] != '\n' ) { fprintf( stderr, "ERROR: expected input of STATE:VALUES:PLAYERS\n" ); exit( EXIT_FAILURE ); } line[ stateEnd ] = 0; ++stateEnd; } printf( "%s:", line ); /* print out the averaged values */ for( p = 0; p < game->numPlayers; ++p ) { printf( p ? "|%lf" : "%lf", value[ p ] / (double)numBoards ); } /* find the player names in the state line */ for( i = stateEnd; line[ i ] && line[ i ] != ':'; ++i ); if( line[ i ] == ':' ) { printf( "%s", &line[ i ] ); } else { printf( "\n" ); } } fclose( file ); exit( EXIT_SUCCESS ); } ================================================ FILE: ACPCServer/bm_run_matches.c ================================================ #include #include #define __STDC_FORMAT_MACROS #include #include #include #include #include #include #include #include #include #include #include "net.h" #define ARG_SERVERNAME 1 #define ARG_SERVERPORT 2 #define ARG_BOT_COMMAND 7 #define ARG_MIN_ARGS 6 static void printUsage( FILE *file ) { fprintf( file, "Sample usages:\n" ); fprintf( file, " bm_run_matches " "games\n" ); fprintf( file, " See a list of possible opponents\n" ); fprintf( file, " bm_run_matches " "run 2pl <# runs> " "\n" ); fprintf( file, " Run two-player limit matches\n" ); fprintf( file, " bm_run_matches " "run 2pn <# runs> " "\n" ); fprintf( file, " Run two-player no-limit matches\n" ); fprintf( file, " bm_run_matches " "run 3pl <# runs> " "\n" ); fprintf( file, " Run three-player limit matches\n" ); fprintf( file, " bm_run_matches " "rerun 2pl " " ()\n" ); fprintf( file, " Rerun a match that failed\n" ); fprintf( file, "\n" ); fprintf( file, " is your benchmark server username assigned to " "you by the competition chair\n" ); fprintf( file, " is your benchmark server password assigned to you by " "the competition chair\n" ); fprintf( file, " is the script that runs your agent locally. " "It must take a hostname/IP and a port\n" ); fprintf( file, " is the number of matches you want to run\n" ); fprintf( file, " is a name for this set of matches which will appear " "in the names of the log files\n" ); fprintf( file, " is a seed used to generate the random seeds that " "determine the cards in each match\n" ); fprintf( file, " is either the name of an opponent or \"local\" " "for your local agent\n" ); fprintf( file, "\n" ); fprintf( file, "To run N duplicate heads-up matches, do one run of N " "matches with a given seed, then run a second set of N matches " "with the same seed but the order of the players reversed\n" ); fprintf( file, "\n" ); fprintf( file, "If one match in a set fails, you can use the \"rerun\" " "command to rerun the specified match with the specified seed. " "For example, if you tried to run twenty matches with seed 0 and " "the last match failed, you could use the \"rerun\" command with " "seed 0 and match index 19.\n" ); } int main( int argc, char **argv ) { int sock, i; pid_t childPID; uint16_t port; ReadBuf *fromServer; fd_set readfds; char line[ READBUF_LEN ]; if( argc < ARG_MIN_ARGS ) { printUsage( stderr ); exit( EXIT_FAILURE ); } /* connect to the server */ if( sscanf( argv[ ARG_SERVERPORT ], "%"SCNu16, &port ) < 1 ) { fprintf( stderr, "ERROR: invalid port %s\n", argv[ ARG_SERVERPORT ] ); exit( EXIT_FAILURE ); } sock = connectTo( argv[ ARG_SERVERNAME ], port ); if( sock < 0 ) { exit( EXIT_FAILURE ); } // EJ additions 9/3/2012 // Turn on keep-alive for socket connection with more frequent checking // than the Linux default. What I've observed is that if a socket // connection is idle for long enough it gets dropped. This only // happens for some users. int on = 1; if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) { fprintf( stderr, "ERROR: setsockopt failed; errno %i\n", errno ); exit( EXIT_FAILURE ); } #ifdef __linux__ // Not sure what this should be int num_before_failure = 2; if (setsockopt(sock, SOL_TCP, TCP_KEEPCNT, &num_before_failure, sizeof(num_before_failure)) == -1) { fprintf( stderr, "ERROR: setsockopt failed; errno %i\n", errno ); exit( EXIT_FAILURE ); } // First check after 60 seconds int initial_secs = 60; if (setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, &initial_secs, sizeof(initial_secs)) == -1) { fprintf( stderr, "ERROR: setsockopt failed; errno %i\n", errno ); exit( EXIT_FAILURE ); } // Thereafter, also check every 60 seconds int interval_secs = 60; if (setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &interval_secs, sizeof(interval_secs)) == -1) { fprintf( stderr, "ERROR: setsockopt failed; errno %i\n", errno ); exit( EXIT_FAILURE ); } #endif /* set up read buffers */ fromServer = createReadBuf( sock ); /* write to server */ line[0] = 0; for( i = 3; i < argc; ++i ) { strcat( line, argv[i] ); if ( i < argc - 1 ) { strcat( line, " " ); } } strcat( line, "\n" ); int len = strlen(line); if( write( sock, line, len ) < 0 ) { fprintf( stderr, "ERROR: failed while sending to server\n" ); exit( EXIT_FAILURE ); } /* main loop */ while( 1 ) { /* clean up any children */ while( waitpid( -1, NULL, WNOHANG ) > 0 ); /* wait for input */ FD_ZERO( &readfds ); FD_SET( sock, &readfds ); i = select( sock + 1, &readfds, NULL, NULL, NULL ); if( i < 0 ) { fprintf( stderr, "ERROR: select failed\n" ); exit( EXIT_FAILURE ); } if( i == 0 ) { /* nothing ready - shouldn't happen without timeout */ continue; } /* handle server messages */ if( FD_ISSET( sock, &readfds ) ) { /* get the input */ while( ( i = getLine( fromServer, READBUF_LEN, line, 0 ) ) >= 0 ) { if( i == 0 ) { /* This could be an error or could just signify successful completion of all matches */ fprintf( stderr, "Server closed connection\n" ); exit( EXIT_SUCCESS ); } /* check for server commands */ if( strncasecmp( line, "run ", 4 ) == 0 ) { /* split the rest of the line into name ' ' port */ for( i = 4; line[ i ]; ++i ) { if( line[ i ] == ' ' ) { /* found the separator */ line[ i ] = 0; break; } } printf( "starting match %s:%s", &line[ 4 ], &line[ i + 1 ] ); fflush( stdout ); /* run `command machine port` */ childPID = fork(); if( childPID < 0 ) { fprintf( stderr, "ERROR: fork() failed\n" ); exit( EXIT_FAILURE ); } if( childPID == 0 ) { /* child runs the command */ execl( argv[ ARG_BOT_COMMAND ], argv[ ARG_BOT_COMMAND ], &line[ 4 ], &line[ i + 1 ], NULL ); fprintf( stderr, "ERROR: could not run %s\n", argv[ ARG_BOT_COMMAND ] ); exit( EXIT_FAILURE ); } } else { /* just a message, print it out */ if( fwrite( line, 1, i, stdout ) < 0 ) { fprintf( stderr, "ERROR: failed while printing server message\n" ); exit( EXIT_FAILURE ); } fflush( stdout ); if( ! strcmp( line, "Matches finished\n") ) { exit( EXIT_SUCCESS ); } } } } } return EXIT_SUCCESS; } ================================================ FILE: ACPCServer/bm_server.c ================================================ /* Copyright (C) 2011 by the Computer Poker Research Group, University of Alberta */ #include #include #define __STDC_FORMAT_MACROS #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "game.h" #include "net.h" #include "rng.h" #define STATUS_CLOSED 0 #define STATUS_UNVALIDATED 1 #define STATUS_OKAY 2 #define BM_DEALER "dealer" #define BM_LOGDIR "logs" #define BM_DEALER_WAIT_SECS 5 #define BM_MAX_IOWAIT_SECS 1 typedef struct LLPoolEntry_struct { struct LLPoolEntry_struct *next; struct LLPoolEntry_struct *prev; char data[ 0 ]; } LLPoolEntry; typedef struct { LLPoolEntry *head; LLPoolEntry *free; int dataSize; int numEntries; } LLPool; /* structure giving the specification for a local bot */ typedef struct { char *name; char *command; } BotSpec; /* structure giving the specification for a user */ typedef struct { char *name; char *passwd; struct timeval waitStart; } UserSpec; typedef struct { uint16_t maxMatchRuns; /* maximum number of runs for a match */ uint16_t maxRunningJobs; /* maximum simultaneous jobs at a time for game 0 disables the check */ uint32_t matchHands; /* number of hands in a match */ Game *game; char *gameFile; LLPool *bots; int curRunningJobs; } GameConfig; typedef struct { uint16_t port; uint16_t maxRunningBots; /* maximum simultaneous bots at a time 0 disables the check */ uint16_t startupTimeoutSecs; /* maximum time to wait for clients to connect 0 disables the timer */ uint16_t responseTimeoutSecs; /* maximum time to wait for clients to respond with an action */ uint16_t handTimeoutSecs; /* maximum time to allowed per hand of play */ uint16_t avgHandTimeSecs; /* average time per hand allowed for the match */ LLPool *games; LLPool *users; } Config; typedef struct { int status; UserSpec *user; /* NULL when status is STATUS_UNVALIDATED */ ReadBuf *connBuf; } Connection; typedef struct { GameConfig *gameConf; UserSpec *user; int numRuns; rng_state_t rng; uint32_t rngSeed; int useRngForSeed; /* 0: use rngSeed as seed for each dealer run 1: use genrand_int32( match->rng ) */ char *tag; struct timeval queueTime; struct { int isNetworkPlayer; LLPoolEntry *entry; /* connection if network player, bot otherwise */ } players[ MAX_PLAYERS ]; int isRunning; } Match; typedef struct { pid_t dealerPID; pid_t botPID[ MAX_PLAYERS ]; LLPoolEntry *matchEntry; char *tag; /* based on tag from the match for this job */ uint16_t ports[ MAX_PLAYERS ]; } MatchJob; typedef struct { int listenSocket; LLPool *conns; LLPool *matches; LLPool *jobs; rng_state_t rng; char *hostname; int devnullfd; } ServerState; LLPool *newLLPool( const int dataSize ) { LLPool *pool; pool = (LLPool*)malloc( sizeof( LLPool ) ); assert( pool != 0 ); pool->head = NULL; pool->free = NULL; pool->dataSize = dataSize; pool->numEntries = 0; return pool; } int entryInList( LLPoolEntry *list, LLPoolEntry *entry ) { while( list ) { if( entry == list ) { return 1; } if( list->next ) { assert( list->next->prev == list ); } list = list->next; } return 0; } /* add an object to the pool. data must have a size of pool->dataSize */ LLPoolEntry *LLPoolAddItem( LLPool *pool, void *item ) { LLPoolEntry *entry; if( pool->free ) { entry = pool->free; pool->free = entry->next; } else { entry = (LLPoolEntry*)malloc( sizeof( LLPoolEntry ) + pool->dataSize ); assert( entry != 0 ); } assert( !entryInList( pool->head, entry ) ); entry->next = pool->head; entry->prev = NULL; memcpy( entry->data, item, pool->dataSize ); if( pool->head ) { pool->head->prev = entry; } pool->head = entry; ++pool->numEntries; return entry; } /* remove an item from the pool, placing it in the free list. entry must have been generated by LLPoolAddItem( pool, ... ) (that is, calling LLPoolRemoveEntry on an entry from another pool is potentially a very bad idea...) */ void LLPoolRemoveEntry( LLPool *pool, LLPoolEntry *entry ) { if( entry->prev ) { assert( entry->prev->next == entry ); entry->prev->next = entry->next; } else { assert( pool->head == entry ); pool->head = entry->next; } if( entry->next ) { assert( entry->next->prev == entry ); entry->next->prev = entry->prev; } assert( !entryInList( pool->free, entry ) ); if( pool->free ) { pool->free->prev = entry; } entry->next = pool->free; pool->free = entry; --pool->numEntries; } /* LLPool iterator start */ LLPoolEntry *LLPoolFirstEntry( LLPool *pool ) { return pool->head; } /* removing entries while iterating through the list is fine, as long as cur is not the entry being removed. */ LLPoolEntry *LLPoolNextEntry( LLPoolEntry *cur ) { if( cur ) { return cur->next; } return NULL; } void *LLPoolGetItem( LLPoolEntry *entry ) { return &entry->data; } void printUsage( FILE *file ) { fprintf( file, "usage: bm_server config_file\n" ); } void setGameDefaults( GameConfig *gameConf ) { gameConf->maxMatchRuns = 10; gameConf->maxRunningJobs = 1; gameConf->matchHands = 5000; gameConf->game = NULL; gameConf->gameFile = NULL; gameConf->bots = newLLPool( sizeof( BotSpec ) ); gameConf->curRunningJobs = 0; } void setDefaults( Config *conf ) { conf->port = 54000; conf->maxRunningBots = 0; conf->startupTimeoutSecs = 600; conf->responseTimeoutSecs = 6000; /* Value from 2011 ACPC */ conf->handTimeoutSecs = 3000 * 7; /* Not enforced for 2011 ACPC */ conf->avgHandTimeSecs = 70; /* Value from 2011 ACPC */ conf->games = newLLPool( sizeof( GameConfig ) ); conf->users = newLLPool( sizeof( UserSpec ) ); } /* returns entry for bot on success, NULL on failure */ LLPoolEntry *findBot( const GameConfig *game, const char *name ) { LLPoolEntry *cur; for( cur = LLPoolFirstEntry( game->bots ); cur != NULL; cur = LLPoolNextEntry( cur ) ) { if( !strcmp( ( (BotSpec *)LLPoolGetItem( cur ) )->name, name ) ) { return cur; } } return NULL; } void addBot( GameConfig *gameConf, const char *spec ) { BotSpec bot; char name[ READBUF_LEN ]; char command[ READBUF_LEN ]; /* split the line into name and command */ if( sscanf( spec, " %s %s", name, command ) < 2 ) { fprintf( stderr, "BM_ERROR: could not get bot name and command from: %s", spec ); exit( EXIT_FAILURE ); } /* make sure there are no duplicates */ if( !strcmp( name, "LOCAL" ) ) { fprintf( stderr, "BM_ERROR: LOCAL is a reserved bot name\n" ); exit( EXIT_FAILURE ); } if( findBot( gameConf, name ) ) { fprintf( stderr, "BM_ERROR: duplicate bot %s\n", name ); exit( EXIT_FAILURE ); } /* add the bot */ bot.name = strdup( name ); bot.command = strdup( command ); LLPoolAddItem( gameConf->bots, &bot ); } /* returns entry for user on success, NULL on failure */ LLPoolEntry *findUser( const Config *conf, const char *name ) { LLPoolEntry *cur; for( cur = LLPoolFirstEntry( conf->users ); cur != NULL; cur = LLPoolNextEntry( cur ) ) { if( !strcmp( ( (UserSpec *)LLPoolGetItem( cur ) )->name, name ) ) { return cur; } } return NULL; } void addUser( Config *conf, const char *spec ) { UserSpec user; char name[ READBUF_LEN ]; char passwd[ READBUF_LEN ]; /* split the line into name and password */ if( sscanf( spec, " %s %s", name, passwd ) < 2 ) { fprintf( stderr, "BM_ERROR: could not get user name and password from: %s", spec ); exit( EXIT_FAILURE ); } /* make sure there are no duplicates */ if( findUser( conf, name ) ) { fprintf( stderr, "BM_ERROR: duplicate user %s\n", name ); exit( EXIT_FAILURE ); } /* add the user */ user.name = strdup( name ); user.passwd = strdup( passwd ); gettimeofday( &user.waitStart, NULL ); LLPoolAddItem( conf->users, &user ); } /* returns entry for game on success, NULL on failure */ LLPoolEntry *findGame( const Config *conf, const char *name ) { LLPoolEntry *cur; for( cur = LLPoolFirstEntry( conf->games ); cur != NULL; cur = LLPoolNextEntry( cur ) ) { if( !strcmp( ( (GameConfig *)LLPoolGetItem( cur ) )->gameFile, name ) ) { return cur; } } return NULL; } /* validate a logon request returns user on success, or NULL on failure */ UserSpec *validateLogon( const Config *conf, const char *line ) { LLPoolEntry *cur; char name[ READBUF_LEN ]; char passwd[ READBUF_LEN ]; if( sscanf( line, " %s %s", name, passwd ) < 2 ) { return NULL; } for( cur = LLPoolFirstEntry( conf->users ); cur != NULL; cur = LLPoolNextEntry( cur ) ) { UserSpec *user = (UserSpec *)LLPoolGetItem( cur ); if( !strcmp( user->name, name ) ) { if( !strcmp( user->passwd, passwd ) ) { return user; } return NULL; } } return NULL; } void readConfig( const char *filename, Config *conf ) { int start; FILE *file; GameConfig *gameConf; char *line, lineBuf[ READBUF_LEN ]; file = fopen( filename, "r" ); if( file == NULL ) { fprintf( stderr, "BM_ERROR: could not open configuration file %s\n", filename ); exit( EXIT_FAILURE ); } gameConf = NULL; while( fgets( lineBuf, READBUF_LEN, file ) ) { /* skip past white space at start of line */ start = 0; while( isspace( lineBuf[ start ] ) ) { ++start; } line = &lineBuf[ start ]; /* ignore comments or empty lines */ if( line[ 0 ] == '#' || line[ 0 ] == ';' || line[ 0 ] == '\n' || line[ 0 ] == 0 ) { continue; } if( strncasecmp( line, "port", 4 ) == 0 ) { if( gameConf != NULL ) { fprintf( stderr, "BM_ERROR: server port must be defined outside of game blocks\n" ); exit( EXIT_FAILURE ); } if( sscanf( &line[ 4 ], "%"SCNu16, &conf->port ) < 1 ) { fprintf( stderr, "BM_ERROR: could not get port from: %s", line ); exit( EXIT_FAILURE ); } } else if( strncasecmp( line, "game", 4 ) == 0 ) { FILE *file; GameConfig gc; char game[ READBUF_LEN ]; if( gameConf != NULL ) { fprintf( stderr, "BM_ERROR: can't define a game within another game block\n" ); exit( EXIT_FAILURE ); } if( sscanf( &line[ 4 ], " %s", game ) < 1 ) { fprintf( stderr, "BM_ERROR: could not get game name from: %s", line ); exit( EXIT_FAILURE ); } if( findGame( conf, game ) ) { fprintf( stderr, "BM_ERROR: game %s has already been used\n", game ); exit( EXIT_FAILURE ); } setGameDefaults( &gc ); gc.gameFile = strdup( game ); file = fopen( gc.gameFile, "r" ); if( file == NULL ) { fprintf( stderr, "BM_ERROR: could not open game file %s\n", gc.gameFile ); exit( EXIT_FAILURE ); } gc.game = readGame( file ); fclose( file ); if( gc.game == NULL ) { fprintf( stderr, "BM_ERROR: could not read game %s", gc.gameFile ); exit( EXIT_FAILURE ); } gameConf = (GameConfig *)LLPoolGetItem( LLPoolAddItem( conf->games, &gc ) ); } else if( strncmp( line, "}", 1 ) == 0 ) { /* finished game definition */ gameConf = NULL; } else if( strncasecmp( line, "maxRunningBots", 14 ) == 0 ) { if( gameConf != NULL ) { fprintf( stderr, "BM_ERROR: maxRunningBots must be defined outside of game blocks\n" ); exit( EXIT_FAILURE ); } if( sscanf( &line[ 14 ], "%"SCNu16, &conf->maxRunningBots ) < 1 ) { fprintf( stderr, "BM_ERROR: could not get maximum number of bots running from: %s", line ); exit( EXIT_FAILURE ); } } else if( strncasecmp( line, "startupTimeoutSecs", 18 ) == 0 ) { if( gameConf != NULL ) { fprintf( stderr, "BM_ERROR: startupTimeoutSecs must be defined outside of game blocks\n" ); exit( EXIT_FAILURE ); } if( sscanf( &line[ 18 ], "%"SCNu16, &conf->startupTimeoutSecs ) < 1 ) { fprintf( stderr, "BM_ERROR: could not get maximum dealer startup timeout: %s", line ); exit( EXIT_FAILURE ); } } else if( strncasecmp( line, "responseTimeoutSecs", 19 ) == 0 ) { if( gameConf != NULL ) { fprintf( stderr, "BM_ERROR: responseTimeoutSecs must be defined outside of game blocks\n" ); exit( EXIT_FAILURE ); } if( sscanf( &line[ 19 ], "%"SCNu16, &conf->responseTimeoutSecs ) < 1 ) { fprintf( stderr, "BM_ERROR: could not get maximum dealer action timeout: %s", line ); exit( EXIT_FAILURE ); } } else if( strncasecmp( line, "handTimeoutSecs", 15 ) == 0 ) { if( gameConf != NULL ) { fprintf( stderr, "BM_ERROR: handTimeoutSecs must be defined outside of game blocks\n" ); exit( EXIT_FAILURE ); } if( sscanf( &line[ 15 ], "%"SCNu16, &conf->handTimeoutSecs ) < 1 ) { fprintf( stderr, "BM_ERROR: could not get maximum dealer hand timeout: %s", line ); exit( EXIT_FAILURE ); } } else if( strncasecmp( line, "avgHandTimeSecs", 15 ) == 0 ) { if( gameConf != NULL ) { fprintf( stderr, "BM_ERROR: avgHandTimeSecs must be defined outside of game blocks\n" ); exit( EXIT_FAILURE ); } if( sscanf( &line[ 15 ], "%"SCNu16, &conf->avgHandTimeSecs ) < 1 ) { fprintf( stderr, "BM_ERROR: could not get dealer average hand time: %s", line ); exit( EXIT_FAILURE ); } } else if( strncasecmp( line, "maxMatchRuns", 12 ) == 0 ) { if( gameConf == NULL ) { fprintf( stderr, "BM_ERROR: maxMatchRuns must be defined within a game block\n" ); exit( EXIT_FAILURE ); } if( sscanf( &line[ 12 ], "%"SCNu16, &gameConf->maxMatchRuns ) < 1 ) { fprintf( stderr, "BM_ERROR: could not get maximum number of runs in a match from: %s", line ); exit( EXIT_FAILURE ); } } else if( strncasecmp( line, "maxRunningJobs", 14 ) == 0 ) { if( gameConf == NULL ) { fprintf( stderr, "BM_ERROR: maxRunningJobs must be defined within a game block\n" ); exit( EXIT_FAILURE ); } if( sscanf( &line[ 14 ], "%"SCNu16, &gameConf->maxRunningJobs ) < 1 ) { fprintf( stderr, "BM_ERROR: could not get maximum number of running jobs from: %s", line ); exit( EXIT_FAILURE ); } } else if( strncasecmp( line, "matchHands", 10 ) == 0 ) { if( gameConf == NULL ) { fprintf( stderr, "BM_ERROR: matchHands must be defined within a game block\n" ); exit( EXIT_FAILURE ); } if( sscanf( &line[ 10 ], "%"SCNu32, &gameConf->matchHands ) < 1 ) { fprintf( stderr, "BM_ERROR: could not get number of hands in a match from: %s", line ); exit( EXIT_FAILURE ); } } else if( strncasecmp( line, "bot", 3 ) == 0 ) { if( gameConf == NULL ) { fprintf( stderr, "BM_ERROR: matchHands must be defined within a game block\n" ); exit( EXIT_FAILURE ); } addBot( gameConf, &line[ 3 ] ); } else if( strncasecmp( line, "user", 4 ) == 0 ) { if( gameConf != NULL ) { fprintf( stderr, "BM_ERROR: users must be defined outside of game blocks\n" ); exit( EXIT_FAILURE ); } addUser( conf, &line[ 4 ] ); } else { fprintf( stderr, "BM_ERROR: unknown configuration option %s", line ); exit( EXIT_FAILURE ); } } fclose( file ); } void addConnection( ServerState *serv, const int sock ) { Connection conn; /* add the connection */ conn.status = STATUS_UNVALIDATED; conn.user = NULL; conn.connBuf = createReadBuf( sock ); if( conn.connBuf == 0 ) { fprintf( stderr, "BM_ERROR: could not create read buffer for socket\n" ); exit( EXIT_FAILURE ); } LLPoolAddItem( serv->conns, &conn ); } int matchUsesConnection( const Match *match, const LLPoolEntry *connEntry ) { int p; for( p = 0; p < match->gameConf->game->numPlayers; ++p ) { if( match->players[ p ].isNetworkPlayer && match->players[ p ].entry == connEntry ) { return 1; } } return 0; } void closeConnection( ServerState *serv, LLPoolEntry *connEntry ) { Connection *conn = (Connection*)LLPoolGetItem( connEntry ); LLPoolEntry *cur, *next; destroyReadBuf( conn->connBuf ); conn->status = STATUS_CLOSED; /* remove any pending matches which relied on the connection */ for( cur = LLPoolFirstEntry( serv->matches ); cur != NULL; cur = next ) { next = LLPoolNextEntry( cur ); Match *match = (Match *)LLPoolGetItem( cur ); if( matchUsesConnection( match, connEntry ) ) { match->numRuns = 0; } } } void handleListenSocket( const Config *conf, ServerState *serv ) { int sock; struct sockaddr_in addr; socklen_t addrLen; addrLen = sizeof( addr ); sock = accept( serv->listenSocket, (struct sockaddr *)&addr, &addrLen ); if( sock < 0 ) { fprintf( stderr, "WARNING: failed to accept incoming connection\n" ); return; } addConnection( serv, sock ); } /* -1 on failure, 0 on success */ int parseMatchSpec( const Config *conf, ServerState *serv, const char *spec, LLPoolEntry *connEntry, Match *match ) { uint32_t rngSeed; int pos, t, p; LLPoolEntry *entry; char tag[ READBUF_LEN ]; char name[ READBUF_LEN ]; pos = 0; if( sscanf( &spec[ pos ], " %s%n", name, &t ) < 1 ) { return -1; } pos += t; entry = findGame( conf, name ); if( entry == NULL ) { return -1; } match->gameConf = (GameConfig *)LLPoolGetItem( entry ); if( sscanf( &spec[ pos ], " %d %s %"SCNu32" %n", &match->numRuns, tag, &rngSeed, &t ) < 3 ) { return -1; } pos += t; if( match->numRuns < 0 || match->numRuns > match->gameConf->maxMatchRuns ) { return -1; } /* make sure tag has no characters in it */ if( strchr( tag, '/' ) != NULL ) { return -1; } /* get bots */ for( p = 0; p < match->gameConf->game->numPlayers; ++p ) { /* get the name */ if( sscanf( &spec[ pos ], " %s%n", name, &t ) < 1 ) { return -1; } pos += t; /* translate the name into an index */ if( !strcmp( name, "LOCAL" ) ) { match->players[ p ].isNetworkPlayer = 1; match->players[ p ].entry = connEntry; } else { match->players[ p ].isNetworkPlayer = 0; match->players[ p ].entry = findBot( match->gameConf, name ); if( match->players[ p ].entry == NULL ) { return -1; } } } match->tag = strdup( tag ); match->rngSeed = rngSeed; if( rngSeed ) { init_genrand( &match->rng, rngSeed ); if( match->numRuns == 1 ) { match->useRngForSeed = 0; } else { match->useRngForSeed = 1; } } else { init_genrand( &match->rng, genrand_int32( &serv->rng ) ); } return 0; } void writeHelpMessage( int fd ) { int r; r = write( fd, "HELP - this message\n", 20 ); r = write( fd, "GAMES - list available games and players\n", 41 ); r = write( fd, "QSTAT - show the current queue\n", 31 ); r = write( fd, "RUNMATCHES game #runs tag rngSeed player ... - submit match request\n", 68 ); r = write( fd, " - Player order decides match seating\n", 39 ); r = write( fd, " - \"LOCAL\" player runs the bm_widget agent (bot_command)\n", 60 ); } void writeGameList( const Config *conf, int fd ) { int r; LLPoolEntry *cur, *botCur; char line[ READBUF_LEN ]; for( cur = LLPoolFirstEntry( conf->games ); cur != NULL; cur = LLPoolNextEntry( cur ) ) { GameConfig *game = (GameConfig *)LLPoolGetItem( cur ); r = snprintf( line, sizeof( line ), "\n%s\n", game->gameFile ); assert( r > 0 ); r = write( fd, line, r ); for( botCur = LLPoolFirstEntry( game->bots ); botCur != NULL; botCur = LLPoolNextEntry( botCur ) ) { BotSpec *bot = (BotSpec *)LLPoolGetItem( botCur ); r = snprintf( line, sizeof( line ), " %s\n", bot->name ); assert( r > 0 ); r = write( fd, line, r ); } } } void writeQueueStatus( const Config *conf, const ServerState *serv, int fd ) { int r; LLPoolEntry *cur; char line[ READBUF_LEN * 4 ]; if( serv->matches->numEntries == 0 ) { r = write( fd, "Queue empty\n", 12 ); } for( cur = LLPoolFirstEntry( serv->matches ); cur != NULL; cur = LLPoolNextEntry( cur ) ) { Match *match = (Match *)LLPoolGetItem( cur ); r = snprintf( line, sizeof( line ), "%s %s %s * %d %s\n", match->user->name, match->tag, match->gameConf->gameFile, match->numRuns, match->isRunning ? "R" : "Q" ); assert( r > 0 ); r = write( fd, line, r ); } } void handleConnection( Config *conf, ServerState *serv, LLPoolEntry *connEntry ) { int r; Connection *conn = (Connection *)LLPoolGetItem( connEntry ); char line[ READBUF_LEN ]; while( ( r = getLine( conn->connBuf, READBUF_LEN, line, 0 ) ) >= 0 ) { if( r == 0 ) { closeConnection( serv, connEntry ); return; } if( conn->status == STATUS_UNVALIDATED ) { UserSpec *user; user = validateLogon( conf, line ); if( user == NULL ) { /* couldn't authenticate */ r = write( conn->connBuf->fd, "BAD LOGON\n", 10 ); fprintf( stderr, "BM_ERROR: connection failed to log in\n" ); closeConnection( serv, connEntry ); return; } /* send an okay message */ r = write( conn->connBuf->fd, "LOGON OKAY - type help for commands\n", 36 ); /* connection status is now okay */ conn->user = user; conn->status = STATUS_OKAY; return; } if( !strncasecmp( line, "HELP", 4 ) ) { writeHelpMessage( conn->connBuf->fd ); } else if( !strncasecmp( line, "GAMES", 5 ) ) { writeGameList( conf, conn->connBuf->fd ); } else if( !strncasecmp( line, "QSTAT", 5 ) ) { writeQueueStatus( conf, serv, conn->connBuf->fd ); } else if( !strncasecmp( line, "RUNMATCHES", 10 ) ) { Match match; if( parseMatchSpec( conf, serv, &line[ 10 ], connEntry, &match ) < 0 ) { fprintf( stderr, "BM_ERROR: bad RUNMATCHES command: %s", line ); r = write( conn->connBuf->fd, "BAD RUNMATCHES COMMAND\n", 23 ); return; } match.user = ( (Connection *)LLPoolGetItem( connEntry ) )->user; match.isRunning = 0; gettimeofday( &match.queueTime, NULL ); LLPoolAddItem( serv->matches, &match ); return; } else { r = write( conn->connBuf->fd, "UNKNOWN\n", 8 ); return; } } } int timeIsEarlier( struct timeval *a, struct timeval *b ) { if( a->tv_sec < b->tv_sec ) { return 1; } else if( a->tv_sec == b->tv_sec && a->tv_usec < b->tv_usec ) { return 1; } return 0; } /* how many bots will match start? */ int botsInMatch( const Match *match ) { int p, num; num = 0; for( p = 0; p < match->gameConf->game->numPlayers; ++p ) { if( !match->players[ p ].isNetworkPlayer ) { ++num; } } return num; } void startDealer( const Config *conf, const Match *match, MatchJob *job, const uint32_t rngSeed ) { int stdoutPipe[ 2 ], p, arg; char handsString[ 16 ], rngString[ 16 ]; char startupTimeoutString[ 16 ], responseTimeoutString[ 16 ]; char handTimeoutString[ 16 ], avgHandTimeString[ 16 ]; char *argv[ MAX_PLAYERS + 64 ]; if( pipe( stdoutPipe ) < 0 ) { fprintf( stderr, "BM_ERROR: could not create pipe for new dealer\n" ); exit( EXIT_FAILURE ); } job->dealerPID = fork(); if( job->dealerPID < 0 ) { fprintf( stderr, "BM_ERROR: fork() failed\n" ); exit( EXIT_FAILURE ); } if( !job->dealerPID ) { /* child runs the dealer command */ int stderrfd; char tag[ READBUF_LEN ]; snprintf( tag, sizeof( tag ), "%s/%s.stderr", BM_LOGDIR, job->tag ); stderrfd = open( tag, O_WRONLY | O_APPEND | O_CREAT, 0644 ); if( stderrfd < 0 ) { fprintf( stderr, "BM_ERROR: could not create error log %s\n", tag ); exit( EXIT_FAILURE ); } dup2( stderrfd, 2 ); /* change stdout to be the write end of the pipe */ close( stdoutPipe[ 0 ] ); dup2( stdoutPipe[ 1 ], 1 ); arg = 0; argv[ arg ] = BM_DEALER; ++arg; snprintf( tag, sizeof( tag ), "%s/%s", BM_LOGDIR, job->tag ); argv[ arg ] = tag; ++arg; argv[ arg ] = match->gameConf->gameFile; ++arg; snprintf( handsString, sizeof( handsString ), "%"PRIu32, match->gameConf->matchHands ); argv[ arg ] = handsString; ++arg; snprintf( rngString, sizeof( rngString ), "%"PRIu32, rngSeed ); argv[ arg ] = rngString; ++arg; for( p = 0; p < match->gameConf->game->numPlayers; ++p ) { if( match->players[ p ].isNetworkPlayer ) { argv[ arg ] = ( (Connection *)LLPoolGetItem( match->players[ p ].entry ) ) ->user->name; } else { argv[ arg ] = ( (BotSpec *)LLPoolGetItem( match->players[ p ].entry ) )->name; } ++arg; } if( conf->startupTimeoutSecs ) { argv[ arg ] = "--start_timeout"; ++arg; snprintf( startupTimeoutString, sizeof( startupTimeoutString ), "%d", (int)conf->startupTimeoutSecs * 1000 ); argv[ arg ] = startupTimeoutString; ++arg; } /* Add maximum per action timeout argument */ argv[ arg ] = "--t_response"; ++arg; snprintf( responseTimeoutString, sizeof( responseTimeoutString ), "%d", (int)conf->responseTimeoutSecs * 1000 ); argv[ arg ] = responseTimeoutString; ++arg; /* Add maximum per hand timeout argument */ argv[ arg ] = "--t_hand"; ++arg; snprintf( handTimeoutString, sizeof( handTimeoutString ), "%d", (int)conf->handTimeoutSecs * 1000 ); argv[ arg ] = handTimeoutString; ++arg; /* Add average per hand time argument */ argv[ arg ] = "--t_per_hand"; ++arg; snprintf( avgHandTimeString, sizeof( avgHandTimeString ), "%d", (int)conf->avgHandTimeSecs * 1000 ); argv[ arg ] = avgHandTimeString; ++arg; argv[ arg ] = "-q"; ++arg; /* Restore the appending behaviour so multiple matches get appended into * the same log file */ argv[ arg ] = "-a"; ++arg; argv[ arg ] = NULL; execv( BM_DEALER, argv ); fprintf( stderr, "BM_ERROR: could not start dealer\n" ); exit( EXIT_FAILURE ); } /* parent has to talk to child to get ports */ ssize_t r; int pos, t; fd_set readfds; struct timeval timeout; char portString[ READBUF_LEN ]; close( stdoutPipe[ 1 ] ); timeout.tv_sec = BM_DEALER_WAIT_SECS; timeout.tv_usec = 0; FD_ZERO( &readfds ); FD_SET( stdoutPipe[ 0 ], &readfds ); if( select( stdoutPipe[ 0 ] + 1, &readfds, NULL, NULL, &timeout ) < 1 ) { fprintf( stderr, "BM_ERROR: timed out waiting for port string from dealer\n" ); exit( EXIT_FAILURE ); } r = read( stdoutPipe[ 0 ], portString, READBUF_LEN ); if( r <= 0 || portString[ r - 1 ] != '\n' ) { fprintf( stderr, "BM_ERROR: could not read port string from dealer\n" ); exit( EXIT_FAILURE ); } portString[ r ] = 0; /* parse the port string */ pos = 0; for( p = 0; p < match->gameConf->game->numPlayers; ++p ) { if( sscanf( &portString[ pos ], " %"SCNu16"%n", &job->ports[ p ], &t ) < 1 ) { fprintf( stderr, "BM_ERROR: could not get port for player %d from dealer\n", p + 1 ); exit( EXIT_FAILURE ); } pos += t; } } pid_t startBot( const ServerState *serv, const BotSpec *bot, const uint16_t port, const int botPosition ) { pid_t pid; pid = fork(); if( pid < 0 ) { fprintf( stderr, "BM_ERROR: fork() failed\n" ); exit( EXIT_FAILURE ); } if( !pid ) { /* child runs the bot command */ char portString[ 8 ]; char posString[ 16 ]; snprintf( portString, sizeof( portString ), "%"PRIu16, port ); snprintf( posString, sizeof( posString ), "%d", botPosition ); /* throw away bot output */ dup2( serv->devnullfd, 1 ); dup2( serv->devnullfd, 2 ); execl( bot->command, bot->command, serv->hostname, portString, posString, NULL ); fprintf( stderr, "BM_ERROR: could not start bot %s\n", bot->command ); exit( EXIT_FAILURE ); } return pid; } int sendStartMessage( const ServerState *serv, const MatchJob *job, const Connection *conn, const uint16_t port ) { int len; char msg[ strlen( serv->hostname ) + 12 + READBUF_LEN ]; len = snprintf( msg, sizeof( msg ), "# RUNNING %s\n", job->tag ); assert( len > 0 ); if( write( conn->connBuf->fd, msg, len ) < len ) { fprintf( stderr, "BM_ERROR: short write to connection\n" ); return -1; } len = snprintf( msg, sizeof( msg ), "RUN %s %"PRIu16"\n", serv->hostname, port ); assert( len > 0 ); if( write( conn->connBuf->fd, msg, len ) < len ) { fprintf( stderr, "BM_ERROR: short write to connection\n" ); return -1; } return 0; } MatchJob runMatchJob( const Config *conf, const ServerState *serv, LLPoolEntry *matchEntry, const uint32_t rngSeed ) { int p, botPosition; MatchJob job; Match *match = (Match *)LLPoolGetItem( matchEntry ); char tag[ READBUF_LEN ]; job.matchEntry = matchEntry; /* make the tag from the match tag */ snprintf( tag, sizeof( tag ), "%s.%s", match->user->name, match->tag ); job.tag = strdup( tag ); /* initialise all PIDs to 0 */ job.dealerPID = 0; for( p = 0; p < match->gameConf->game->numPlayers; ++p ) { job.botPID[ p ] = 0; } /* start the dealer */ startDealer( conf, match, &job, rngSeed ); /* deal with all the players */ botPosition = 0; for( p = 0; p < match->gameConf->game->numPlayers; ++p ) { if( match->players[ p ].isNetworkPlayer ) { /* send message with port to network player to start up */ Connection *conn = (Connection*)LLPoolGetItem( match->players[ p ].entry ); if( sendStartMessage( serv, &job, conn, job.ports[ p ] ) < 0 ) { /* abort the job... */ fprintf( stderr, "BM_ERROR: aborting job\n" ); kill( job.dealerPID, SIGTERM ); while( p > 0 ) { --p; if( job.botPID[ p ] ) { kill( job.botPID[ p ], SIGTERM ); } } return job; } } else { /* start up bot */ job.botPID[ p ] = startBot( serv, (BotSpec *)LLPoolGetItem( match->players[ p ].entry ), job.ports[ p ], botPosition ); ++botPosition; } } return job; } int startMatchJob( const Config *conf, ServerState *serv ) { int running; LLPoolEntry *cur, *next, *best; Match *curMatch, *bestMatch; MatchJob job; /* automatically done adding things if we've got no more matches */ if( serv->matches->numEntries == 0 ) { return 0; } /* how many bots are currently running */ running = 0; for( cur = LLPoolFirstEntry( serv->jobs ); cur != NULL; cur = LLPoolNextEntry( cur ) ) { MatchJob *job = (MatchJob *)LLPoolGetItem( cur ); running += botsInMatch( (Match*)LLPoolGetItem( job->matchEntry ) ); } /* pick the best match to start */ best = 0; bestMatch = 0; for( cur = LLPoolFirstEntry( serv->matches ); cur != NULL; cur = next ) { next = LLPoolNextEntry( cur ); curMatch = (Match *)LLPoolGetItem( cur ); if( curMatch->isRunning ) { continue; } if( curMatch->numRuns <= 0 ) { /* match is finished - clean it up */ free( curMatch->tag ); LLPoolRemoveEntry( serv->matches, cur ); continue; } if( curMatch->gameConf->maxRunningJobs && curMatch->gameConf->curRunningJobs >= curMatch->gameConf->maxRunningJobs ) { /* cur refers to a match in a game which is currently too busy */ continue; } if( best == 0 || timeIsEarlier( &curMatch->user->waitStart, &bestMatch->user->waitStart ) || ( !timeIsEarlier( &bestMatch->user->waitStart, &curMatch->user->waitStart ) && timeIsEarlier( &curMatch->queueTime, &bestMatch->queueTime ) ) ) { best = cur; bestMatch = curMatch; } } /* return failure if we couldn't find a runnable job */ if( best == NULL ) { return 0; } /* check if we have the space to run the bots */ if( conf->maxRunningBots && botsInMatch( bestMatch ) + running > conf->maxRunningBots ) { return 0; } /* create the job */ job = runMatchJob( conf, serv, best, bestMatch->useRngForSeed ? genrand_int32( &bestMatch->rng ) : bestMatch->rngSeed ); assert( job.dealerPID ); LLPoolAddItem( serv->jobs, &job ); /* update status about running jobs */ ++( bestMatch->gameConf->curRunningJobs ); bestMatch->isRunning = 1; /* update the user */ gettimeofday( &bestMatch->user->waitStart, NULL ); /* update the match */ --bestMatch->numRuns; gettimeofday( &bestMatch->queueTime, NULL ); return 1; } void initServerState( const Config *conf, ServerState *serv ) { struct addrinfo hints, *info; uint16_t port; int hnm, r; char *hn; char ipstr[ INET6_ADDRSTRLEN ]; serv->conns = newLLPool( sizeof( Connection ) ); serv->matches = newLLPool( sizeof( Match ) ); serv->jobs = newLLPool( sizeof( MatchJob ) ); /* create the socket clients will connect to */ port = conf->port; serv->listenSocket = getListenSocket( &port ); if( serv->listenSocket < 0 ) { fprintf( stderr, "BM_ERROR: could not open socket for listening\n" ); exit( EXIT_FAILURE ); } printf( "starting server on port %"PRIu16"\n", conf->port ); init_genrand( &serv->rng, time( NULL ) ); hnm = sysconf( _SC_HOST_NAME_MAX ); hn = (char*)malloc( hnm ); assert( hn != 0 ); if( gethostname( hn, hnm + 1 ) < 0 ) { fprintf( stderr, "BM_ERROR: could not get hostname\n" ); exit( EXIT_FAILURE ); } memset( &hints, 0, sizeof( hints ) ); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; if( ( r = getaddrinfo( hn, NULL, &hints, &info ) ) != 0 ) { fprintf( stderr, "BM_ERROR: could not get address info for host %s\n", hn ); exit( 1 ); } free( hn ); /* Get an address for the server */ void *addr; /* get the pointer to the address itself, * different fields in IPv4 and IPv6: */ if ( info->ai_family == AF_INET ) { /* IPv4 */ struct sockaddr_in *ipv4 = ( struct sockaddr_in * ) info->ai_addr; addr = &( ipv4->sin_addr ); } else { /* IPv6 */ struct sockaddr_in6 *ipv6 = ( struct sockaddr_in6 * ) info->ai_addr; addr = &( ipv6->sin6_addr ); } /* convert the IP to a string and store it:*/ inet_ntop( info->ai_family, addr, ipstr, sizeof( ipstr ) ); serv->hostname = strdup( ipstr ); freeaddrinfo( info ); /* free the linked list */ serv->devnullfd = open( "/dev/null", O_WRONLY ); if( serv->devnullfd < 0 ) { fprintf( stderr, "BM_ERROR: could not open /dev/null\n" ); exit( EXIT_FAILURE ); } } int checkIfJobFinished( MatchJob *job ) { int status, r, p, allDone; Match *match = (Match *)LLPoolGetItem( job->matchEntry ); allDone = 1; if( job->dealerPID ) { r = waitpid( job->dealerPID, &status, WNOHANG ); if( r < 0 ) { fprintf( stderr, "BM_ERROR: could not wait on child\n" ); exit( EXIT_FAILURE ); } if( r == job->dealerPID ) { job->dealerPID = 0; } else { allDone = 0; } } for( p = 0; p < match->gameConf->game->numPlayers; ++p ) { if( job->botPID[ p ] == 0 ) { continue; } r = waitpid( job->botPID[ p ], &status, WNOHANG ); if( r < 0 ) { fprintf( stderr, "BM_ERROR: could not wait on child\n" ); exit( EXIT_FAILURE ); } if( r == job->botPID[ p ] ) { job->botPID[ p ] = 0; } else { allDone = 0; } } return allDone; } void finishedJob( ServerState *serv, LLPoolEntry *jobEntry ) { MatchJob *job = (MatchJob *)LLPoolGetItem( jobEntry ); Match *match = (Match *)LLPoolGetItem( job->matchEntry ); free( job->tag ); --( match->gameConf->curRunningJobs ); match->isRunning = 0; LLPoolRemoveEntry( serv->jobs, jobEntry ); } int main( int argc, char **argv ) { Config conf; ServerState serv; int maxfd; fd_set readfds; LLPoolEntry *cur, *next; struct timeval tv; if( argc < 2 ) { printUsage( stderr ); exit( EXIT_FAILURE ); } /* Ignore SIGPIPE. It seems that SIGPIPE can be raised when the underlying * IO fails with a SIGPIPE. Unfortunately this causes the entire benchmark * server to crash and jobs are lost. Ignore the signal to avoid death */ /* ???: May also need to catch SIGCHLD */ signal( SIGPIPE, SIG_IGN ); /* use the config file */ setDefaults( &conf ); readConfig( argv[ 1 ], &conf ); /* initialise server state */ initServerState( &conf, &serv ); /* main I/O loop */ while( 1 ) { /* clean up any finished jobs */ for( cur = LLPoolFirstEntry( serv.jobs ); cur != NULL; cur = next ) { next = LLPoolNextEntry( cur ); MatchJob *job = (MatchJob *)LLPoolGetItem( cur ); if( checkIfJobFinished( job ) ) { finishedJob( &serv, cur ); } } /* clean up any closed connections */ for( cur = LLPoolFirstEntry( serv.conns ); cur != NULL; cur = next ) { next = LLPoolNextEntry( cur ); if( ( (Connection *)LLPoolGetItem( cur ) )->status == STATUS_CLOSED ) { LLPoolRemoveEntry( serv.conns, cur ); } } /* start jobs, up to the maximum */ while( startMatchJob( &conf, &serv ) ); /* wait for input */ FD_ZERO( &readfds ); FD_SET( serv.listenSocket, &readfds ); maxfd = serv.listenSocket; tv.tv_sec = BM_MAX_IOWAIT_SECS; tv.tv_usec = 0; for( cur = LLPoolFirstEntry( serv.conns ); cur != NULL; cur = LLPoolNextEntry( cur ) ) { Connection *conn = (Connection *)LLPoolGetItem( cur ); FD_SET( conn->connBuf->fd, &readfds ); if( conn->connBuf->fd > maxfd ) { maxfd = conn->connBuf->fd; } } if( select( maxfd + 1, &readfds, NULL, NULL, &tv ) < 0 ) { fprintf( stderr, "BM_ERROR: select failed\n" ); exit( -1 ); } /* process anything that's happened */ if( FD_ISSET( serv.listenSocket, &readfds ) ) { handleListenSocket( &conf, &serv ); } for( cur = LLPoolFirstEntry( serv.conns ); cur != NULL; cur = LLPoolNextEntry( cur ) ) { Connection *conn = (Connection *)LLPoolGetItem( cur ); if( FD_ISSET( conn->connBuf->fd, &readfds ) ) { handleConnection( &conf, &serv, cur ); } } } close( serv.listenSocket ); return EXIT_SUCCESS; } ================================================ FILE: ACPCServer/bm_server.config ================================================ # port to connect to the server port 54000 # maxmimum number of simultaneously locally running bots # 0 disables maxRunningBots 0 # maximum time in seconds to wait for clients to connect when starting a match startupTimeoutSecs 1000 # maximum time in seconds to wait for clients to act during a match responseTimeoutSecs 60000 # maximum time in seconds allowed for a client to play a given hand handTimeoutSecs 210000 # average time in seconds allowed for a client to spend on each hand avgHandTimeSecs 700 # heads up limit Texas Hold'em game holdem.limit.2p.reverse_blinds.game { # maximum number of times a match can be run with a single player request maxMatchRuns 10 # maxmimum number of simultaneously running matches using this game # 0 disables maxRunningJobs 1 # number of hands in a match matchHands 5000 # bot botName botStartupScript # botStartupScript is run with 3 args: server name, port, local position # local postion indicates which LOCAL bot this is (index starting from 0) # This is useful when determining which of multiple machines to run on bot testBot example_player.limit.2p.sh } # heads up limit Texas Hold'em game holdem.nolimit.2p.reverse_blinds.game { # maximum number of times a match can be run with a single player request maxMatchRuns 10 # maxmimum number of simultaneously running matches using this game # 0 disables maxRunningJobs 1 # number of hands in a match matchHands 5000 # bot botName botStartupScript # botStartupScript is run with 3 args: server name, port, local position # local postion indicates which LOCAL bot this is (index starting from 0) # This is useful when determining which of multiple machines to run on bot testBot example_player.nolimit.2p.sh } # heads up limit Texas Hold'em game holdem.limit.3p.game { # maximum number of times a match can be run with a single player request maxMatchRuns 10 # maxmimum number of simultaneously running matches using this game # 0 disables maxRunningJobs 1 # number of hands in a match matchHands 5000 # bot botName botStartupScript # botStartupScript is run with 3 args: server name, port, local position # local postion indicates which LOCAL bot this is (index starting from 0) # This is useful when determining which of multiple machines to run on bot testBot example_player.limit.3p.sh } # Users authorized to run jobs on the benchmark (user name pass) user neil test ================================================ FILE: ACPCServer/bm_widget.c ================================================ /* Copyright (C) 2011 by the Computer Poker Research Group, University of Alberta */ #include #include #define __STDC_FORMAT_MACROS #include #include #include #include #include #include #include #include #include #include #include "net.h" #define ARG_SERVERNAME 1 #define ARG_SERVERPORT 2 #define ARG_BOT_COMMAND 3 #define ARG_NUM_ARGS 4 static void printUsage( FILE *file ) { fprintf( file, "usage: bm_widget bm_hostname bm_port bot_command\n" ); fprintf( file, " bot_command: agent executable, passed \"hostname port\"\n"); } /* 0 on success, -1 on failure */ int login( char *user, char *passwd, FILE *conn ) { if( fprintf( conn, "%s %s\n", user, passwd ) < 0 ) { return -1; } fflush( conn ); return 0; } int main( int argc, char **argv ) { int sock, i; pid_t childPID; uint16_t port; ReadBuf *fromUser, *fromServer; fd_set readfds; char line[ READBUF_LEN ]; if( argc < ARG_NUM_ARGS ) { printUsage( stderr ); exit( EXIT_FAILURE ); } /* connect to the server */ if( sscanf( argv[ ARG_SERVERPORT ], "%"SCNu16, &port ) < 1 ) { fprintf( stderr, "ERROR: invalid port %s\n", argv[ ARG_SERVERPORT ] ); exit( EXIT_FAILURE ); } sock = connectTo( argv[ ARG_SERVERNAME ], port ); if( sock < 0 ) { exit( EXIT_FAILURE ); } // EJ additions 9/3/2012 // Turn on keep-alive for socket connection with more frequent checking // than the Linux default. What I've observed is that if a socket // connection is idle for long enough it gets dropped. This only // happens for some users. int on = 1; if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) { fprintf( stderr, "ERROR: setsockopt failed; errno %i\n", errno ); exit( EXIT_FAILURE ); } #ifdef __linux__ // Not sure what this should be int num_before_failure = 2; if (setsockopt(sock, SOL_TCP, TCP_KEEPCNT, &num_before_failure, sizeof(num_before_failure)) == -1) { fprintf( stderr, "ERROR: setsockopt failed; errno %i\n", errno ); exit( EXIT_FAILURE ); } // First check after 60 seconds int initial_secs = 60; if (setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, &initial_secs, sizeof(initial_secs)) == -1) { fprintf( stderr, "ERROR: setsockopt failed; errno %i\n", errno ); exit( EXIT_FAILURE ); } // Thereafter, also check every 60 seconds int interval_secs = 60; if (setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &interval_secs, sizeof(interval_secs)) == -1) { fprintf( stderr, "ERROR: setsockopt failed; errno %i\n", errno ); exit( EXIT_FAILURE ); } #endif /* set up read buffers */ fromUser = createReadBuf( 0 ); fromServer = createReadBuf( sock ); printf( "Log in with 'user password'\n" ); fflush( stdout ); /* main loop */ while( 1 ) { /* clean up any children */ while( waitpid( -1, NULL, WNOHANG ) > 0 ); /* wait for input */ FD_ZERO( &readfds ); FD_SET( 0, &readfds ); FD_SET( sock, &readfds ); i = select( sock + 1, &readfds, NULL, NULL, NULL ); if( i < 0 ) { fprintf( stderr, "ERROR: select failed\n" ); exit( EXIT_FAILURE ); } if( i == 0 ) { /* nothing ready - shouldn't happen without timeout */ continue; } /* handle user input by passing it directly to server */ if( FD_ISSET( 0, &readfds ) ) { /* get the input */ while( ( i = getLine( fromUser, READBUF_LEN, line, 0 ) ) >= 0 ) { if( i == 0 ) { /* Done! */ exit( EXIT_SUCCESS ); } /* write to server */ if( write( sock, line, i ) < 0 ) { fprintf( stderr, "ERROR: failed while sending to server\n" ); exit( EXIT_FAILURE ); } } } /* handle server messages */ if( FD_ISSET( sock, &readfds ) ) { /* get the input */ while( ( i = getLine( fromServer, READBUF_LEN, line, 0 ) ) >= 0 ) { if( i == 0 ) { fprintf( stderr, "ERROR: server closed connection?\n" ); exit( EXIT_FAILURE ); } /* check for server commands */ if( strncasecmp( line, "run ", 4 ) == 0 ) { /* split the rest of the line into name ' ' port */ for( i = 4; line[ i ]; ++i ) { if( line[ i ] == ' ' ) { /* found the separator */ line[ i ] = 0; break; } } printf( "starting match %s:%s", &line[ 4 ], &line[ i + 1 ] ); fflush( stdout ); /* run `command machine port` */ childPID = fork(); if( childPID < 0 ) { fprintf( stderr, "ERROR: fork() failed\n" ); exit( EXIT_FAILURE ); } if( childPID == 0 ) { /* child runs the command */ execl( argv[ ARG_BOT_COMMAND ], argv[ ARG_BOT_COMMAND ], &line[ 4 ], &line[ i + 1 ], NULL ); fprintf( stderr, "ERROR: could not run %s\n", argv[ ARG_BOT_COMMAND ] ); exit( EXIT_FAILURE ); } } else { /* just a message, print it out */ if( fwrite( line, 1, i, stdout ) < 0 ) { fprintf( stderr, "ERROR: failed while printing server message\n" ); exit( EXIT_FAILURE ); } fflush( stdout ); } } } } return EXIT_SUCCESS; } ================================================ FILE: ACPCServer/dealer.c ================================================ /* Copyright (C) 2011 by the Computer Poker Research Group, University of Alberta */ #include #include #define __STDC_LIMIT_MACROS #include #include #include #include #include #include #include #include #include "game.h" #include "net.h" /* the ports for players to connect to will be printed on standard out (in player order) if log file is enabled, matchName.log will contain finished states and values, followed by the final total values for each player if transaction file is enabled, matchName.tlog will contain a list of actions taken and timestamps that is sufficient to recreate an interrupted match if the quiet option is not enabled, standard error will print out the messages sent to and receieved from the players the final total values for each player will be printed to both standard out and standard error exit value is EXIT_SUCCESS if the match was a success, or EXIT_FAILURE on any failure */ #define DEFAULT_MAX_INVALID_ACTIONS UINT32_MAX #define DEFAULT_MAX_RESPONSE_MICROS 6000000000 #define DEFAULT_MAX_USED_HAND_MICROS 6000000000 #define DEFAULT_MAX_USED_PER_HAND_MICROS 70000000 typedef struct { uint32_t maxInvalidActions; uint64_t maxResponseMicros; uint64_t maxUsedHandMicros; uint64_t maxUsedMatchMicros; uint32_t numInvalidActions[ MAX_PLAYERS ]; uint64_t usedHandMicros[ MAX_PLAYERS ]; uint64_t usedMatchMicros[ MAX_PLAYERS ]; } ErrorInfo; static void printUsage( FILE *file, int verbose ) { fprintf( file, "usage: dealer matchName gameDefFile #Hands rngSeed p1name p2name ... [options]\n" ); fprintf( file, " -f use fixed dealer button at table\n" ); fprintf( file, " -l/L disable/enable log file - enabled by default\n" ); fprintf( file, " -p player1_port,player2_port,... [default is random]\n" ); fprintf( file, " -q only print errors, warnings, and final value to stderr\n" ); fprintf( file, " -t/T disable/enable transaction file - disabled by default\n" ); fprintf( file, " -a append to log/transaction files - disabled by default\n" ); fprintf( file, " --t_response [milliseconds] maximum time per response\n" ); fprintf( file, " --t_hand [milliseconds] maximum player time per hand\n" ); fprintf( file, " --t_per_hand [milliseconds] maximum average player time for match\n" ); fprintf( file, " --start_timeout [milliseconds] maximum time to wait for players to connect\n" ); fprintf( file, " <0 [default] is no timeout\n" ); } /* returns >= 0 on success, -1 on error */ static int scanPortString( const char *string, uint16_t listenPort[ MAX_PLAYERS ] ) { int c, r, p; c = 0; for( p = 0; p < MAX_PLAYERS; ++p ) { if( string[ c ] == 0 ) { /* finished parsing the string */ break; } if( p ) { /* look for separator */ if( string[ c ] != ',' ) { /* numbers should be comma separated */ return -1; } ++c; } if( sscanf( &string[ c ], "%"SCNu16"%n", &listenPort[ p ], &r ) < 1 ) { /* couldn't get a number */ return -1; } c += r; } return 0; } static void initErrorInfo( const uint32_t maxInvalidActions, const uint64_t maxResponseMicros, const uint64_t maxUsedHandMicros, const uint64_t maxUsedMatchMicros, ErrorInfo *info ) { int s; info->maxInvalidActions = maxInvalidActions; info->maxResponseMicros = maxResponseMicros; info->maxUsedHandMicros = maxUsedHandMicros; info->maxUsedMatchMicros = maxUsedMatchMicros; for( s = 0; s < MAX_PLAYERS; ++s ) { info->numInvalidActions[ s ] = 0; info->usedHandMicros[ s ] = 0; info->usedMatchMicros[ s ] = 0; } } /* update the number of invalid actions for seat returns >= 0 if match should continue, -1 for failure */ static int checkErrorInvalidAction( const uint8_t seat, ErrorInfo *info ) { ++( info->numInvalidActions[ seat ] ); if( info->numInvalidActions[ seat ] > info->maxInvalidActions ) { return -1; } return 0; } /* update the time used by seat returns >= 0 if match should continue, -1 for failure */ static int checkErrorTimes( const uint8_t seat, const struct timeval *sendTime, const struct timeval *recvTime, ErrorInfo *info ) { uint64_t responseMicros; /* calls to gettimeofday can return earlier times on later calls :/ */ if( recvTime->tv_sec < sendTime->tv_sec || ( recvTime->tv_sec == sendTime->tv_sec && recvTime->tv_usec < sendTime->tv_usec ) ) { return 0; } /* figure out how many microseconds the response took */ responseMicros = ( recvTime->tv_sec - sendTime->tv_sec ) * 1000000 + recvTime->tv_usec - sendTime->tv_usec; /* update usage counts */ info->usedHandMicros[ seat ] += responseMicros; info->usedMatchMicros[ seat ] += responseMicros; /* check time used for the response */ if( responseMicros > info->maxResponseMicros ) { return -1; } /* check time used in the current hand */ if( info->usedHandMicros[ seat ] > info->maxUsedHandMicros ) { return -1; } /* check time used in the entire match */ if( info->usedMatchMicros[ seat ] > info->maxUsedMatchMicros ) { return -1; } return 0; } /* note that there is a new hand returns >= 0 if match should continue, -1 for failure */ static int checkErrorNewHand( const Game *game, ErrorInfo *info ) { uint8_t p; for( p = 0; p < game->numPlayers; ++p ) { info->usedHandMicros[ p ] = 0; } return 0; } static uint8_t seatToPlayer( const Game *game, const uint8_t player0Seat, const uint8_t seat ) { return ( seat + game->numPlayers - player0Seat ) % game->numPlayers; } static uint8_t playerToSeat( const Game *game, const uint8_t player0Seat, const uint8_t player ) { return ( player + player0Seat ) % game->numPlayers; } /* returns >= 0 if match should continue, -1 for failure */ static int sendPlayerMessage( const Game *game, const MatchState *state, const int quiet, const uint8_t seat, const int seatFD, struct timeval *sendTime ) { int c; char line[ MAX_LINE_LEN ]; /* prepare the message */ c = printMatchState( game, state, MAX_LINE_LEN, line ); if( c < 0 || c > MAX_LINE_LEN - 3 ) { /* message is too long */ fprintf( stderr, "ERROR: state message too long\n" ); return -1; } line[ c ] = '\r'; line[ c + 1 ] = '\n'; line[ c + 2 ] = 0; c += 2; /* send it to the player and flush */ if( write( seatFD, line, c ) != c ) { /* couldn't send the line */ fprintf( stderr, "ERROR: could not send state to seat %"PRIu8"\n", seat + 1 ); return -1; } /* note when we sent the message */ gettimeofday( sendTime, NULL ); /* log the message */ if( !quiet ) { fprintf( stderr, "TO %d at %zu.%.06zu %s", seat + 1, sendTime->tv_sec, sendTime->tv_usec, line ); } return 0; } /* returns >= 0 if action/size has been set to a valid action returns -1 for failure (disconnect, timeout, too many bad actions, etc) */ static int readPlayerResponse( const Game *game, const MatchState *state, const int quiet, const uint8_t seat, const struct timeval *sendTime, ErrorInfo *errorInfo, ReadBuf *readBuf, Action *action, struct timeval *recvTime ) { int c, r; MatchState tempState; char line[ MAX_LINE_LEN ]; while( 1 ) { /* read a line of input from player */ struct timeval start; gettimeofday( &start, NULL ); if( getLine( readBuf, MAX_LINE_LEN, line, errorInfo->maxResponseMicros ) <= 0 ) { /* couldn't get any input from player */ struct timeval after; gettimeofday( &after, NULL ); uint64_t micros_spent = (uint64_t)( after.tv_sec - start.tv_sec ) * 1000000 + ( after.tv_usec - start.tv_usec ); fprintf( stderr, "ERROR: could not get action from seat %"PRIu8"\n", seat + 1 ); // Print out how much time has passed so we can see if this was a // timeout as opposed to some other sort of failure (e.g., socket // closing). fprintf( stderr, "%.1f seconds spent waiting; timeout %.1f\n", micros_spent / 1000000.0, errorInfo->maxResponseMicros / 1000000.0); return -1; } /* note when the message arrived */ gettimeofday( recvTime, NULL ); /* log the response */ if( !quiet ) { fprintf( stderr, "FROM %d at %zu.%06zu %s", seat + 1, recvTime->tv_sec, recvTime->tv_usec, line ); } /* ignore comments */ if( line[ 0 ] == '#' || line[ 0 ] == ';' ) { continue; } /* check for any timeout issues */ if( checkErrorTimes( seat, sendTime, recvTime, errorInfo ) < 0 ) { fprintf( stderr, "ERROR: seat %"PRIu8" ran out of time\n", seat + 1 ); return -1; } /* parse out the state */ c = readMatchState( line, game, &tempState ); if( c < 0 ) { /* couldn't get an intelligible state */ fprintf( stderr, "WARNING: bad state format in response\n" ); continue; } /* ignore responses that don't match the current state */ if( !matchStatesEqual( game, state, &tempState ) ) { fprintf( stderr, "WARNING: ignoring un-requested response\n" ); continue; } /* get the action */ if( line[ c++ ] != ':' || ( r = readAction( &line[ c ], game, action ) ) < 0 ) { if( checkErrorInvalidAction( seat, errorInfo ) < 0 ) { fprintf( stderr, "ERROR: bad action format in response\n" ); } fprintf( stderr, "WARNING: bad action format in response, changed to call\n" ); action->type = a_call; action->size = 0; goto doneRead; } c += r; /* make sure the action is valid */ if( !isValidAction( game, &state->state, 1, action ) ) { if( checkErrorInvalidAction( seat, errorInfo ) < 0 ) { fprintf( stderr, "ERROR: invalid action\n" ); return -1; } fprintf( stderr, "WARNING: invalid action, changed to call\n" ); action->type = a_call; action->size = 0; } goto doneRead; } doneRead: return 0; } /* returns >= 0 if match should continue, -1 for failure */ static int setUpNewHand( const Game *game, const uint8_t fixedSeats, uint32_t *handId, uint8_t *player0Seat, rng_state_t *rng, ErrorInfo *errorInfo, State *state ) { ++( *handId ); /* rotate the players around the table */ if( !fixedSeats ) { *player0Seat = ( *player0Seat + 1 ) % game->numPlayers; } if( checkErrorNewHand( game, errorInfo ) < 0 ) { fprintf( stderr, "ERROR: unexpected game\n" ); return -1; } initState( game, *handId, state ); dealCards( game, rng, state ); return 0; } /* returns >= 0 if match should continue, -1 for failure */ static int processTransactionFile( const Game *game, const int fixedSeats, uint32_t *handId, uint8_t *player0Seat, rng_state_t *rng, ErrorInfo *errorInfo, double totalValue[ MAX_PLAYERS ], MatchState *state, FILE *file ) { int c, r; uint32_t h; uint8_t s; Action action; struct timeval sendTime, recvTime; char line[ MAX_LINE_LEN ]; while( fgets( line, MAX_LINE_LEN, file ) ) { /* get the log entry */ /* ACTION */ c = readAction( line, game, &action ); if( c < 0 ) { fprintf( stderr, "ERROR: could not parse transaction action %s", line ); return -1; } /* ACTION HANDID SEND RECV */ if( sscanf( &line[ c ], " %"SCNu32" %zu.%06zu %zu.%06zu%n", &h, &sendTime.tv_sec, &sendTime.tv_usec, &recvTime.tv_sec, &recvTime.tv_usec, &r ) < 4 ) { fprintf( stderr, "ERROR: could not parse transaction stamp %s", line ); return -1; } c += r; /* check that we're processing the expected handId */ if( h != *handId ) { fprintf( stderr, "ERROR: handId mismatch in transaction log: %s", line ); return -1; } /* make sure the action is valid */ if( !isValidAction( game, &state->state, 0, &action ) ) { fprintf( stderr, "ERROR: invalid action in transaction log: %s", line ); return -1; } /* check for any timeout issues */ s = playerToSeat( game, *player0Seat, currentPlayer( game, &state->state ) ); if( checkErrorTimes( s, &sendTime, &recvTime, errorInfo ) < 0 ) { fprintf( stderr, "ERROR: seat %"PRIu8" ran out of time in transaction file\n", s + 1 ); return -1; } doAction( game, &action, &state->state ); if( stateFinished( &state->state ) ) { /* hand is finished */ /* update the total value for each player */ for( s = 0; s < game->numPlayers; ++s ) { totalValue[ s ] += valueOfState( game, &state->state, seatToPlayer( game, *player0Seat, s ) ); } /* move on to next hand */ if( setUpNewHand( game, fixedSeats, handId, player0Seat, rng, errorInfo, &state->state ) < 0 ) { return -1; } } } return 0; } /* returns >= 0 if match should continue, -1 on failure */ static int logTransaction( const Game *game, const State *state, const Action *action, const struct timeval *sendTime, const struct timeval *recvTime, FILE *file ) { int c, r; char line[ MAX_LINE_LEN ]; c = printAction( game, action, MAX_LINE_LEN, line ); if( c < 0 ) { fprintf( stderr, "ERROR: transaction message too long\n" ); return -1; } r = snprintf( &line[ c ], MAX_LINE_LEN - c, " %"PRIu32" %zu.%06zu %zu.%06zu\n", state->handId, sendTime->tv_sec, sendTime->tv_usec, recvTime->tv_sec, recvTime->tv_usec ); if( r < 0 ) { fprintf( stderr, "ERROR: transaction message too long\n" ); return -1; } c += r; if( fwrite( line, 1, c, file ) != c ) { fprintf( stderr, "ERROR: could not write to transaction file\n" ); return -1; } fflush( file ); return c; } /* returns >= 0 if match should continue, -1 on failure */ static int checkVersion( const uint8_t seat, ReadBuf *readBuf ) { uint32_t major, minor, rev; char line[ MAX_LINE_LEN ]; if( getLine( readBuf, MAX_LINE_LEN, line, -1 ) <= 0 ) { fprintf( stderr, "ERROR: could not read version string from seat %"PRIu8"\n", seat + 1 ); return -1; } if( sscanf( line, "VERSION:%"SCNu32".%"SCNu32".%"SCNu32, &major, &minor, &rev ) < 3 ) { fprintf( stderr, "ERROR: invalid version string %s", line ); return -1; } if( major != VERSION_MAJOR || minor > VERSION_MINOR ) { fprintf( stderr, "ERROR: this server is currently using version %"SCNu32".%"SCNu32".%"SCNu32"\n", VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION ); } return 0; } /* returns >= 0 if match should continue, -1 on failure */ static int addToLogFile( const Game *game, const State *state, const double value[ MAX_PLAYERS ], const uint8_t player0Seat, char *seatName[ MAX_PLAYERS ], FILE *logFile ) { int c, r; uint8_t p; char line[ MAX_LINE_LEN ]; /* prepare the message */ c = printState( game, state, MAX_LINE_LEN, line ); if( c < 0 ) { /* message is too long */ fprintf( stderr, "ERROR: log state message too long\n" ); return -1; } /* add the values */ for( p = 0; p < game->numPlayers; ++p ) { r = snprintf( &line[ c ], MAX_LINE_LEN - c, p ? "|%.6f" : ":%.6f", value[ p ] ); if( r < 0 ) { fprintf( stderr, "ERROR: log message too long\n" ); return -1; } c += r; /* remove trailing zeros after decimal-point */ while( line[ c - 1 ] == '0' ) { --c; } if( line[ c - 1 ] == '.' ) { --c; } line[ c ] = 0; } /* add the player names */ for( p = 0; p < game->numPlayers; ++p ) { r = snprintf( &line[ c ], MAX_LINE_LEN - c, p ? "|%s" : ":%s", seatName[ playerToSeat( game, player0Seat, p ) ] ); if( r < 0 ) { fprintf( stderr, "ERROR: log message too long\n" ); return -1; } c += r; } /* print the line to log and flush */ if( fprintf( logFile, "%s\n", line ) < 0 ) { fprintf( stderr, "ERROR: logging failed for game %s\n", line ); return -1; } fflush( logFile ); return 0; } /* returns >= 0 if match should continue, -1 on failure */ static int printInitialMessage( const char *matchName, const char *gameName, const uint32_t numHands, const uint32_t seed, const ErrorInfo *info, FILE *logFile ) { int c; char line[ MAX_LINE_LEN ]; c = snprintf( line, MAX_LINE_LEN, "# name/game/hands/seed %s %s %"PRIu32" %"PRIu32"\n#--t_response %"PRIu64"\n#--t_hand %"PRIu64"\n#--t_per_hand %"PRIu64"\n", matchName, gameName, numHands, seed, info->maxResponseMicros / 1000, info->maxUsedHandMicros / 1000, info->maxUsedMatchMicros / numHands / 1000 ); if( c < 0 ) { /* message is too long */ fprintf( stderr, "ERROR: initial game comment too long\n" ); return -1; } fprintf( stderr, "%s", line ); if( logFile ) { fprintf( logFile, "%s", line ); } return 0; } /* returns >= 0 if match should continue, -1 on failure */ static int printFinalMessage( const Game *game, char *seatName[ MAX_PLAYERS ], const double totalValue[ MAX_PLAYERS ], FILE *logFile ) { int c, r; uint8_t s; char line[ MAX_LINE_LEN ]; c = snprintf( line, MAX_LINE_LEN, "SCORE" ); if( c < 0 ) { /* message is too long */ fprintf( stderr, "ERROR: value state message too long\n" ); return -1; } for( s = 0; s < game->numPlayers; ++s ) { r = snprintf( &line[ c ], MAX_LINE_LEN - c, s ? "|%.6f" : ":%.6f", totalValue[ s ] ); if( r < 0 ) { fprintf( stderr, "ERROR: value message too long\n" ); return -1; } c += r; /* remove trailing zeros after decimal-point */ while( line[ c - 1 ] == '0' ) { --c; } if( line[ c - 1 ] == '.' ) { --c; } line[ c ] = 0; } /* add the player names */ for( s = 0; s < game->numPlayers; ++s ) { r = snprintf( &line[ c ], MAX_LINE_LEN - c, s ? "|%s" : ":%s", seatName[ s ] ); if( r < 0 ) { fprintf( stderr, "ERROR: log message too long\n" ); return -1; } c += r; } fprintf( stdout, "%s\n", line ); fprintf( stderr, "%s\n", line ); if( logFile ) { fprintf( logFile, "%s\n", line ); } return 0; } /* run a match of numHands hands of the supplied game cards are dealt using rng, error conditions like timeouts are controlled and stored in errorInfo actions are read/sent to seat p on seatFD[ p ] if quiet is not zero, only print out errors, warnings, and final value if logFile is not NULL, print out a single line for each completed match with the final state and all player values. The values are printed in player, not seat order. if transactionFile is not NULL, a transaction log of actions made is written to the file, and if there is any input left to read on the stream when gameLoop is called, it will be processed to initialise the state returns >=0 if the match finished correctly, -1 on error */ static int gameLoop( const Game *game, char *seatName[ MAX_PLAYERS ], const uint32_t numHands, const int quiet, const int fixedSeats, rng_state_t *rng, ErrorInfo *errorInfo, const int seatFD[ MAX_PLAYERS ], ReadBuf *readBuf[ MAX_PLAYERS ], FILE *logFile, FILE *transactionFile ) { uint32_t handId; uint8_t seat, p, player0Seat, currentP, currentSeat; struct timeval t, sendTime, recvTime; Action action; MatchState state; double value[ MAX_PLAYERS ], totalValue[ MAX_PLAYERS ]; /* check version string for each player */ for( seat = 0; seat < game->numPlayers; ++seat ) { if( checkVersion( seat, readBuf[ seat ] ) < 0 ) { /* error messages already handled in function */ return -1; } } gettimeofday( &sendTime, NULL ); if( !quiet ) { fprintf( stderr, "STARTED at %zu.%06zu\n", sendTime.tv_sec, sendTime.tv_usec ); } /* start at the first hand */ handId = 0; if( checkErrorNewHand( game, errorInfo ) < 0 ) { fprintf( stderr, "ERROR: unexpected game\n" ); return -1; } initState( game, handId, &state.state ); dealCards( game, rng, &state.state ); for( seat = 0; seat < game->numPlayers; ++seat ) { totalValue[ seat ] = 0.0; } /* seat 0 is player 0 in first game */ player0Seat = 0; /* process the transaction file */ if( transactionFile != NULL ) { if( processTransactionFile( game, fixedSeats, &handId, &player0Seat, rng, errorInfo, totalValue, &state, transactionFile ) < 0 ) { /* error messages already handled in function */ return -1; } } if( handId >= numHands ) { goto finishedGameLoop; } /* play all the (remaining) hands */ while( 1 ) { /* play the hand */ while( !stateFinished( &state.state ) ) { /* find the current player */ currentP = currentPlayer( game, &state.state ); /* send state to each player */ for( seat = 0; seat < game->numPlayers; ++seat ) { state.viewingPlayer = seatToPlayer( game, player0Seat, seat ); if( sendPlayerMessage( game, &state, quiet, seat, seatFD[ seat ], &t ) < 0 ) { /* error messages already handled in function */ return -1; } /* remember the seat and send time if player is acting */ if( state.viewingPlayer == currentP ) { sendTime = t; } } /* get action from current player */ state.viewingPlayer = currentP; currentSeat = playerToSeat( game, player0Seat, currentP ); if( readPlayerResponse( game, &state, quiet, currentSeat, &sendTime, errorInfo, readBuf[ currentSeat ], &action, &recvTime ) < 0 ) { /* error messages already handled in function */ return -1; } /* log the transaction */ if( transactionFile != NULL ) { if( logTransaction( game, &state.state, &action, &sendTime, &recvTime, transactionFile ) < 0 ) { /* error messages already handled in function */ return -1; } } /* do the action */ doAction( game, &action, &state.state ); } /* get values */ for( p = 0; p < game->numPlayers; ++p ) { value[ p ] = valueOfState( game, &state.state, p ); totalValue[ playerToSeat( game, player0Seat, p ) ] += value[ p ]; } /* add the game to the log */ if( logFile != NULL ) { if( addToLogFile( game, &state.state, value, player0Seat, seatName, logFile ) < 0 ) { /* error messages already handled in function */ return -1; } } /* send final state to each player */ for( seat = 0; seat < game->numPlayers; ++seat ) { state.viewingPlayer = seatToPlayer( game, player0Seat, seat ); if( sendPlayerMessage( game, &state, quiet, seat, seatFD[ seat ], &t ) < 0 ) { /* error messages already handled in function */ return -1; } } if ( !quiet ) { if ( handId % 100 == 0) { for( seat = 0; seat < game->numPlayers; ++seat ) { fprintf(stderr, "Seconds cumulatively spent in match for seat %i: " "%i\n", seat, (int)(errorInfo->usedMatchMicros[ seat ] / 1000000)); } } } /* start a new hand */ if( setUpNewHand( game, fixedSeats, &handId, &player0Seat, rng, errorInfo, &state.state ) < 0 ) { /* error messages already handled in function */ return -1; } if( handId >= numHands ) { break; } } finishedGameLoop: /* print out the final values */ if( !quiet ) { gettimeofday( &t, NULL ); fprintf( stderr, "FINISHED at %zu.%06zu\n", sendTime.tv_sec, sendTime.tv_usec ); } if( printFinalMessage( game, seatName, totalValue, logFile ) < 0 ) { /* error messages already handled in function */ return -1; } return 0; } int main( int argc, char **argv ) { int i, listenSocket[ MAX_PLAYERS ], v, longOpt; int fixedSeats, quiet, append; int seatFD[ MAX_PLAYERS ]; FILE *file, *logFile, *transactionFile; ReadBuf *readBuf[ MAX_PLAYERS ]; Game *game; rng_state_t rng; ErrorInfo errorInfo; struct sockaddr_in addr; socklen_t addrLen; char *seatName[ MAX_PLAYERS ]; int useLogFile, useTransactionFile; uint64_t maxResponseMicros, maxUsedHandMicros, maxUsedPerHandMicros; int64_t startTimeoutMicros; uint32_t numHands, seed, maxInvalidActions; uint16_t listenPort[ MAX_PLAYERS ]; struct timeval startTime, tv; char name[ MAX_LINE_LEN ]; static struct option longOptions[] = { { "t_response", 1, 0, 0 }, { "t_hand", 1, 0, 0 }, { "t_per_hand", 1, 0, 0 }, { "start_timeout", 1, 0, 0 }, { 0, 0, 0, 0 } }; /* set defaults */ /* game error conditions */ maxInvalidActions = DEFAULT_MAX_INVALID_ACTIONS; maxResponseMicros = DEFAULT_MAX_RESPONSE_MICROS; maxUsedHandMicros = DEFAULT_MAX_USED_HAND_MICROS; maxUsedPerHandMicros = DEFAULT_MAX_USED_PER_HAND_MICROS; /* use random ports */ for( i = 0; i < MAX_PLAYERS; ++i ) { listenPort[ i ] = 0; } /* use log file, don't use transaction file */ useLogFile = 1; useTransactionFile = 0; /* print all messages */ quiet = 0; /* by default, overwrite preexisting log/transaction files */ append = 0; /* players rotate around the table */ fixedSeats = 0; /* no timeout on startup */ startTimeoutMicros = -1; /* parse options */ while( 1 ) { i = getopt_long( argc, argv, "flLp:qtTa", longOptions, &longOpt ); if( i < 0 ) { break; } switch( i ) { case 0: /* long option longOpt */ switch( longOpt ) { case 0: /* t_response */ if( sscanf( optarg, "%"SCNu64, &maxResponseMicros ) < 1 ) { fprintf( stderr, "ERROR: could not get response timeout from %s\n", optarg ); exit( EXIT_FAILURE ); } /* convert from milliseconds to microseconds */ maxResponseMicros *= 1000; break; case 1: /* t_hand */ if( sscanf( optarg, "%"SCNu64, &maxUsedHandMicros ) < 1 ) { fprintf( stderr, "ERROR: could not get player hand timeout from %s\n", optarg ); exit( EXIT_FAILURE ); } /* convert from milliseconds to microseconds */ maxUsedHandMicros *= 1000; break; case 2: /* t_per_hand */ if( sscanf( optarg, "%"SCNu64, &maxUsedPerHandMicros ) < 1 ) { fprintf( stderr, "ERROR: could not get average player hand timeout from %s\n", optarg ); exit( EXIT_FAILURE ); } /* convert from milliseconds to microseconds */ maxUsedPerHandMicros *= 1000; break; case 3: /* start_timeout */ if( sscanf( optarg, "%"SCNd64, &startTimeoutMicros ) < 1 ) { fprintf( stderr, "ERROR: could not get start timeout %s\n", optarg ); exit( EXIT_FAILURE ); } /* convert from milliseconds to microseconds */ if( startTimeoutMicros > 0 ) { startTimeoutMicros *= 1000; } break; } break; case 'f': /* fix the player seats */; fixedSeats = 1; break; case 'l': /* no transactionFile */; useLogFile = 0; break; case 'L': /* use transactionFile */; useLogFile = 1; break; case 'p': /* port specification */ if( scanPortString( optarg, listenPort ) < 0 ) { fprintf( stderr, "ERROR: bad port string %s\n", optarg ); exit( EXIT_FAILURE ); } break; case 'q': quiet = 1; break; case 't': /* no transactionFile */ useTransactionFile = 0; break; case 'T': /* use transactionFile */ useTransactionFile = 1; break; case 'a': append = 1; break; default: fprintf( stderr, "ERROR: unknown option %c\n", i ); exit( EXIT_FAILURE ); } } if( optind + 4 > argc ) { printUsage( stdout, 0 ); exit( EXIT_FAILURE ); } /* get the game definition */ file = fopen( argv[ optind + 1 ], "r" ); if( file == NULL ) { fprintf( stderr, "ERROR: could not open game definition %s\n", argv[ optind + 1 ] ); exit( EXIT_FAILURE ); } game = readGame( file ); if( game == NULL ) { fprintf( stderr, "ERROR: could not read game %s\n", argv[ optind + 1 ] ); exit( EXIT_FAILURE ); } fclose( file ); /* save the seat names */ if( optind + 4 + game->numPlayers > argc ) { printUsage( stdout, 0 ); exit( EXIT_FAILURE ); } for( i = 0; i < game->numPlayers; ++i ) { seatName[ i ] = argv[ optind + 4 + i ]; } /* get number of hands */ if( sscanf( argv[ optind + 2 ], "%"SCNu32, &numHands ) < 1 || numHands == 0 ) { fprintf( stderr, "ERROR: invalid number of hands %s\n", argv[ optind + 2 ] ); exit( EXIT_FAILURE ); } /* get random number seed */ if( sscanf( argv[ optind + 3 ], "%"SCNu32, &seed ) < 1 ) { fprintf( stderr, "ERROR: invalid random number seed %s\n", argv[ optind + 3 ] ); exit( EXIT_FAILURE ); } init_genrand( &rng, seed ); srandom( seed ); /* used for random port selection */ if( useLogFile ) { /* create/open the log */ if( snprintf( name, MAX_LINE_LEN, "%s.log", argv[ optind ] ) < 0 ) { fprintf( stderr, "ERROR: match file name too long %s\n", argv[ optind ] ); exit( EXIT_FAILURE ); } if (append) { logFile = fopen( name, "a+" ); } else { logFile = fopen( name, "w" ); } if( logFile == NULL ) { fprintf( stderr, "ERROR: could not open log file %s\n", name ); exit( EXIT_FAILURE ); } } else { /* no log file */ logFile = NULL; } if( useTransactionFile ) { /* create/open the transaction log */ if( snprintf( name, MAX_LINE_LEN, "%s.tlog", argv[ optind ] ) < 0 ) { fprintf( stderr, "ERROR: match file name too long %s\n", argv[ optind ] ); exit( EXIT_FAILURE ); } if (append) { transactionFile = fopen( name, "a+" ); } else { transactionFile = fopen( name, "w" ); } if( transactionFile == NULL ) { fprintf( stderr, "ERROR: could not open transaction file %s\n", name ); exit( EXIT_FAILURE ); } } else { /* no transaction file */ transactionFile = NULL; } /* set up the error info */ initErrorInfo( maxInvalidActions, maxResponseMicros, maxUsedHandMicros, maxUsedPerHandMicros * numHands, &errorInfo ); /* open sockets for players to connect to */ for( i = 0; i < game->numPlayers; ++i ) { listenSocket[ i ] = getListenSocket( &listenPort[ i ] ); if( listenSocket[ i ] < 0 ) { fprintf( stderr, "ERROR: could not create listen socket for player %d\n", i + 1 ); exit( EXIT_FAILURE ); } } /* print out the final port assignments */ for( i = 0; i < game->numPlayers; ++i ) { printf( i ? " %"PRIu16 : "%"PRIu16, listenPort[ i ] ); } printf( "\n" ); fflush( stdout ); /* print out usage information */ printInitialMessage( argv[ optind ], argv[ optind + 1 ], numHands, seed, &errorInfo, logFile ); /* wait for each player to connect */ gettimeofday( &startTime, NULL ); for( i = 0; i < game->numPlayers; ++i ) { if( startTimeoutMicros >= 0 ) { uint64_t startTimeLeft; fd_set fds; gettimeofday( &tv, NULL ); startTimeLeft = startTimeoutMicros - (uint64_t)( tv.tv_sec - startTime.tv_sec ) * 1000000 - ( tv.tv_usec - startTime.tv_usec ); if( startTimeLeft < 0 ) { startTimeLeft = 0; } tv.tv_sec = startTimeLeft / 1000000; tv.tv_usec = startTimeLeft % 1000000; FD_ZERO( &fds ); FD_SET( listenSocket[ i ], &fds ); if( select( listenSocket[ i ] + 1, &fds, NULL, NULL, &tv ) < 1 ) { /* no input ready within time, or an actual error */ fprintf( stderr, "ERROR: timed out waiting for seat %d to connect\n", i + 1 ); exit( EXIT_FAILURE ); } } addrLen = sizeof( addr ); seatFD[ i ] = accept( listenSocket[ i ], (struct sockaddr *)&addr, &addrLen ); if( seatFD[ i ] < 0 ) { fprintf( stderr, "ERROR: seat %d could not connect\n", i + 1 ); exit( EXIT_FAILURE ); } close( listenSocket[ i ] ); v = 1; setsockopt( seatFD[ i ], IPPROTO_TCP, TCP_NODELAY, (char *)&v, sizeof(int) ); readBuf[ i ] = createReadBuf( seatFD[ i ] ); } /* play the match */ if( gameLoop( game, seatName, numHands, quiet, fixedSeats, &rng, &errorInfo, seatFD, readBuf, logFile, transactionFile ) < 0 ) { /* should have already printed an error message */ exit( EXIT_FAILURE ); } fflush( stderr ); fflush( stdout ); if( transactionFile != NULL ) { fclose( transactionFile ); } if( logFile != NULL ) { fclose( logFile ); } free( game ); return EXIT_SUCCESS; } ================================================ FILE: ACPCServer/evalHandTables ================================================ /* Copyright (C) 2011 by the Computer Poker Research Group, University of Alberta */ /* high card 1287 0 pair 3718 1287 two pair 3601 5005 trips 1014 8606 straight 13 9620 flush 1287 9633 f_house 1014 10920 quads 169 11934 s_flush 13 12103 */ #define HANDCLASS_SINGLE_CARD 0 #define HANDCLASS_PAIR 1287 #define HANDCLASS_TWO_PAIR 5005 #define HANDCLASS_TRIPS 8606 #define HANDCLASS_STRAIGHT 9620 #define HANDCLASS_FLUSH 9633 #define HANDCLASS_FULL_HOUSE 10920 #define HANDCLASS_QUADS 11934 #define HANDCLASS_STRAIGHT_FLUSH 12103 typedef union { uint16_t bySuit[ 4 ]; uint64_t cards; } Cardset; static const uint16_t oneSuitVal[ 8192 ] = { 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, 12107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9634, 0, 0, 0, 0, 0, 0, 0, 9635, 0, 0, 0, 9636, 0, 9637, 12108, 12108, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9639, 0, 0, 0, 0, 0, 0, 0, 9640, 0, 0, 0, 9641, 0, 9642, 9643, 12107, 0, 0, 0, 0, 0, 0, 0, 9644, 0, 0, 0, 9645, 0, 9646, 9647, 9647, 0, 0, 0, 9648, 0, 9649, 9650, 9650, 0, 9651, 9652, 9652, 12109, 12109, 12109, 12109, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9654, 0, 0, 0, 0, 0, 0, 0, 9655, 0, 0, 0, 9656, 0, 9657, 9658, 12107, 0, 0, 0, 0, 0, 0, 0, 9659, 0, 0, 0, 9660, 0, 9661, 9662, 9662, 0, 0, 0, 9663, 0, 9664, 9665, 9665, 0, 9666, 9667, 9667, 9668, 9668, 12108, 12108, 0, 0, 0, 0, 0, 0, 0, 9669, 0, 0, 0, 9670, 0, 9671, 9672, 9672, 0, 0, 0, 9673, 0, 9674, 9675, 9675, 0, 9676, 9677, 9677, 9678, 9678, 9678, 12107, 0, 0, 0, 9679, 0, 9680, 9681, 9681, 0, 9682, 9683, 9683, 9684, 9684, 9684, 9684, 0, 9685, 9686, 9686, 9687, 9687, 9687, 9687, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9689, 0, 0, 0, 0, 0, 0, 0, 9690, 0, 0, 0, 9691, 0, 9692, 9693, 12107, 0, 0, 0, 0, 0, 0, 0, 9694, 0, 0, 0, 9695, 0, 9696, 9697, 9697, 0, 0, 0, 9698, 0, 9699, 9700, 9700, 0, 9701, 9702, 9702, 9703, 9703, 12108, 12108, 0, 0, 0, 0, 0, 0, 0, 9704, 0, 0, 0, 9705, 0, 9706, 9707, 9707, 0, 0, 0, 9708, 0, 9709, 9710, 9710, 0, 9711, 9712, 9712, 9713, 9713, 9713, 12107, 0, 0, 0, 9714, 0, 9715, 9716, 9716, 0, 9717, 9718, 9718, 9719, 9719, 9719, 9719, 0, 9720, 9721, 9721, 9722, 9722, 9722, 9722, 9723, 9723, 9723, 9723, 12109, 12109, 12109, 12109, 0, 0, 0, 0, 0, 0, 0, 9724, 0, 0, 0, 9725, 0, 9726, 9727, 9727, 0, 0, 0, 9728, 0, 9729, 9730, 9730, 0, 9731, 9732, 9732, 9733, 9733, 9733, 12107, 0, 0, 0, 9734, 0, 9735, 9736, 9736, 0, 9737, 9738, 9738, 9739, 9739, 9739, 9739, 0, 9740, 9741, 9741, 9742, 9742, 9742, 9742, 9743, 9743, 9743, 9743, 9743, 9743, 12108, 12108, 0, 0, 0, 9744, 0, 9745, 9746, 9746, 0, 9747, 9748, 9748, 9749, 9749, 9749, 9749, 0, 9750, 9751, 9751, 9752, 9752, 9752, 9752, 9753, 9753, 9753, 9753, 9753, 9753, 9753, 12107, 0, 9754, 9755, 9755, 9756, 9756, 9756, 9756, 9757, 9757, 9757, 9757, 9757, 9757, 9757, 9757, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9759, 0, 0, 0, 0, 0, 0, 0, 9760, 0, 0, 0, 9761, 0, 9762, 9763, 12107, 0, 0, 0, 0, 0, 0, 0, 9764, 0, 0, 0, 9765, 0, 9766, 9767, 9767, 0, 0, 0, 9768, 0, 9769, 9770, 9770, 0, 9771, 9772, 9772, 9773, 9773, 12108, 12108, 0, 0, 0, 0, 0, 0, 0, 9774, 0, 0, 0, 9775, 0, 9776, 9777, 9777, 0, 0, 0, 9778, 0, 9779, 9780, 9780, 0, 9781, 9782, 9782, 9783, 9783, 9783, 12107, 0, 0, 0, 9784, 0, 9785, 9786, 9786, 0, 9787, 9788, 9788, 9789, 9789, 9789, 9789, 0, 9790, 9791, 9791, 9792, 9792, 9792, 9792, 9793, 9793, 9793, 9793, 12109, 12109, 12109, 12109, 0, 0, 0, 0, 0, 0, 0, 9794, 0, 0, 0, 9795, 0, 9796, 9797, 9797, 0, 0, 0, 9798, 0, 9799, 9800, 9800, 0, 9801, 9802, 9802, 9803, 9803, 9803, 12107, 0, 0, 0, 9804, 0, 9805, 9806, 9806, 0, 9807, 9808, 9808, 9809, 9809, 9809, 9809, 0, 9810, 9811, 9811, 9812, 9812, 9812, 9812, 9813, 9813, 9813, 9813, 9813, 9813, 12108, 12108, 0, 0, 0, 9814, 0, 9815, 9816, 9816, 0, 9817, 9818, 9818, 9819, 9819, 9819, 9819, 0, 9820, 9821, 9821, 9822, 9822, 9822, 9822, 9823, 9823, 9823, 9823, 9823, 9823, 9823, 12107, 0, 9824, 9825, 9825, 9826, 9826, 9826, 9826, 9827, 9827, 9827, 9827, 9827, 9827, 9827, 9827, 9828, 9828, 9828, 9828, 9828, 9828, 9828, 9828, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 0, 0, 0, 0, 0, 0, 0, 9829, 0, 0, 0, 9830, 0, 9831, 9832, 9832, 0, 0, 0, 9833, 0, 9834, 9835, 9835, 0, 9836, 9837, 9837, 9838, 9838, 9838, 12107, 0, 0, 0, 9839, 0, 9840, 9841, 9841, 0, 9842, 9843, 9843, 9844, 9844, 9844, 9844, 0, 9845, 9846, 9846, 9847, 9847, 9847, 9847, 9848, 9848, 9848, 9848, 9848, 9848, 12108, 12108, 0, 0, 0, 9849, 0, 9850, 9851, 9851, 0, 9852, 9853, 9853, 9854, 9854, 9854, 9854, 0, 9855, 9856, 9856, 9857, 9857, 9857, 9857, 9858, 9858, 9858, 9858, 9858, 9858, 9858, 12107, 0, 9859, 9860, 9860, 9861, 9861, 9861, 9861, 9862, 9862, 9862, 9862, 9862, 9862, 9862, 9862, 9863, 9863, 9863, 9863, 9863, 9863, 9863, 9863, 9863, 9863, 9863, 9863, 12109, 12109, 12109, 12109, 0, 0, 0, 9864, 0, 9865, 9866, 9866, 0, 9867, 9868, 9868, 9869, 9869, 9869, 9869, 0, 9870, 9871, 9871, 9872, 9872, 9872, 9872, 9873, 9873, 9873, 9873, 9873, 9873, 9873, 12107, 0, 9874, 9875, 9875, 9876, 9876, 9876, 9876, 9877, 9877, 9877, 9877, 9877, 9877, 9877, 9877, 9878, 9878, 9878, 9878, 9878, 9878, 9878, 9878, 9878, 9878, 9878, 9878, 9878, 9878, 12108, 12108, 0, 9879, 9880, 9880, 9881, 9881, 9881, 9881, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9883, 9883, 9883, 9883, 9883, 9883, 9883, 9883, 9883, 9883, 9883, 9883, 9883, 9883, 9883, 12107, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9885, 0, 0, 0, 0, 0, 0, 0, 9886, 0, 0, 0, 9887, 0, 9888, 9889, 12107, 0, 0, 0, 0, 0, 0, 0, 9890, 0, 0, 0, 9891, 0, 9892, 9893, 9893, 0, 0, 0, 9894, 0, 9895, 9896, 9896, 0, 9897, 9898, 9898, 9899, 9899, 12108, 12108, 0, 0, 0, 0, 0, 0, 0, 9900, 0, 0, 0, 9901, 0, 9902, 9903, 9903, 0, 0, 0, 9904, 0, 9905, 9906, 9906, 0, 9907, 9908, 9908, 9909, 9909, 9909, 12107, 0, 0, 0, 9910, 0, 9911, 9912, 9912, 0, 9913, 9914, 9914, 9915, 9915, 9915, 9915, 0, 9916, 9917, 9917, 9918, 9918, 9918, 9918, 9919, 9919, 9919, 9919, 12109, 12109, 12109, 12109, 0, 0, 0, 0, 0, 0, 0, 9920, 0, 0, 0, 9921, 0, 9922, 9923, 9923, 0, 0, 0, 9924, 0, 9925, 9926, 9926, 0, 9927, 9928, 9928, 9929, 9929, 9929, 12107, 0, 0, 0, 9930, 0, 9931, 9932, 9932, 0, 9933, 9934, 9934, 9935, 9935, 9935, 9935, 0, 9936, 9937, 9937, 9938, 9938, 9938, 9938, 9939, 9939, 9939, 9939, 9939, 9939, 12108, 12108, 0, 0, 0, 9940, 0, 9941, 9942, 9942, 0, 9943, 9944, 9944, 9945, 9945, 9945, 9945, 0, 9946, 9947, 9947, 9948, 9948, 9948, 9948, 9949, 9949, 9949, 9949, 9949, 9949, 9949, 12107, 0, 9950, 9951, 9951, 9952, 9952, 9952, 9952, 9953, 9953, 9953, 9953, 9953, 9953, 9953, 9953, 9954, 9954, 9954, 9954, 9954, 9954, 9954, 9954, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 0, 0, 0, 0, 0, 0, 0, 9955, 0, 0, 0, 9956, 0, 9957, 9958, 9958, 0, 0, 0, 9959, 0, 9960, 9961, 9961, 0, 9962, 9963, 9963, 9964, 9964, 9964, 12107, 0, 0, 0, 9965, 0, 9966, 9967, 9967, 0, 9968, 9969, 9969, 9970, 9970, 9970, 9970, 0, 9971, 9972, 9972, 9973, 9973, 9973, 9973, 9974, 9974, 9974, 9974, 9974, 9974, 12108, 12108, 0, 0, 0, 9975, 0, 9976, 9977, 9977, 0, 9978, 9979, 9979, 9980, 9980, 9980, 9980, 0, 9981, 9982, 9982, 9983, 9983, 9983, 9983, 9984, 9984, 9984, 9984, 9984, 9984, 9984, 12107, 0, 9985, 9986, 9986, 9987, 9987, 9987, 9987, 9988, 9988, 9988, 9988, 9988, 9988, 9988, 9988, 9989, 9989, 9989, 9989, 9989, 9989, 9989, 9989, 9989, 9989, 9989, 9989, 12109, 12109, 12109, 12109, 0, 0, 0, 9990, 0, 9991, 9992, 9992, 0, 9993, 9994, 9994, 9995, 9995, 9995, 9995, 0, 9996, 9997, 9997, 9998, 9998, 9998, 9998, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 12107, 0, 10000, 10001, 10001, 10002, 10002, 10002, 10002, 10003, 10003, 10003, 10003, 10003, 10003, 10003, 10003, 10004, 10004, 10004, 10004, 10004, 10004, 10004, 10004, 10004, 10004, 10004, 10004, 10004, 10004, 12108, 12108, 0, 10005, 10006, 10006, 10007, 10007, 10007, 10007, 10008, 10008, 10008, 10008, 10008, 10008, 10008, 10008, 10009, 10009, 10009, 10009, 10009, 10009, 10009, 10009, 10009, 10009, 10009, 10009, 10009, 10009, 10009, 12107, 10010, 10010, 10010, 10010, 10010, 10010, 10010, 10010, 10010, 10010, 10010, 10010, 10010, 10010, 10010, 10010, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 0, 0, 0, 0, 0, 0, 0, 10011, 0, 0, 0, 10012, 0, 10013, 10014, 10014, 0, 0, 0, 10015, 0, 10016, 10017, 10017, 0, 10018, 10019, 10019, 10020, 10020, 10020, 12107, 0, 0, 0, 10021, 0, 10022, 10023, 10023, 0, 10024, 10025, 10025, 10026, 10026, 10026, 10026, 0, 10027, 10028, 10028, 10029, 10029, 10029, 10029, 10030, 10030, 10030, 10030, 10030, 10030, 12108, 12108, 0, 0, 0, 10031, 0, 10032, 10033, 10033, 0, 10034, 10035, 10035, 10036, 10036, 10036, 10036, 0, 10037, 10038, 10038, 10039, 10039, 10039, 10039, 10040, 10040, 10040, 10040, 10040, 10040, 10040, 12107, 0, 10041, 10042, 10042, 10043, 10043, 10043, 10043, 10044, 10044, 10044, 10044, 10044, 10044, 10044, 10044, 10045, 10045, 10045, 10045, 10045, 10045, 10045, 10045, 10045, 10045, 10045, 10045, 12109, 12109, 12109, 12109, 0, 0, 0, 10046, 0, 10047, 10048, 10048, 0, 10049, 10050, 10050, 10051, 10051, 10051, 10051, 0, 10052, 10053, 10053, 10054, 10054, 10054, 10054, 10055, 10055, 10055, 10055, 10055, 10055, 10055, 12107, 0, 10056, 10057, 10057, 10058, 10058, 10058, 10058, 10059, 10059, 10059, 10059, 10059, 10059, 10059, 10059, 10060, 10060, 10060, 10060, 10060, 10060, 10060, 10060, 10060, 10060, 10060, 10060, 10060, 10060, 12108, 12108, 0, 10061, 10062, 10062, 10063, 10063, 10063, 10063, 10064, 10064, 10064, 10064, 10064, 10064, 10064, 10064, 10065, 10065, 10065, 10065, 10065, 10065, 10065, 10065, 10065, 10065, 10065, 10065, 10065, 10065, 10065, 12107, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 10066, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 0, 0, 0, 10067, 0, 10068, 10069, 10069, 0, 10070, 10071, 10071, 10072, 10072, 10072, 10072, 0, 10073, 10074, 10074, 10075, 10075, 10075, 10075, 10076, 10076, 10076, 10076, 10076, 10076, 10076, 12107, 0, 10077, 10078, 10078, 10079, 10079, 10079, 10079, 10080, 10080, 10080, 10080, 10080, 10080, 10080, 10080, 10081, 10081, 10081, 10081, 10081, 10081, 10081, 10081, 10081, 10081, 10081, 10081, 10081, 10081, 12108, 12108, 0, 10082, 10083, 10083, 10084, 10084, 10084, 10084, 10085, 10085, 10085, 10085, 10085, 10085, 10085, 10085, 10086, 10086, 10086, 10086, 10086, 10086, 10086, 10086, 10086, 10086, 10086, 10086, 10086, 10086, 10086, 12107, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 10087, 12109, 12109, 12109, 12109, 0, 10088, 10089, 10089, 10090, 10090, 10090, 10090, 10091, 10091, 10091, 10091, 10091, 10091, 10091, 10091, 10092, 10092, 10092, 10092, 10092, 10092, 10092, 10092, 10092, 10092, 10092, 10092, 10092, 10092, 10092, 12107, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 10093, 12108, 12108, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10095, 0, 0, 0, 0, 0, 0, 0, 10096, 0, 0, 0, 10097, 0, 10098, 10099, 12107, 0, 0, 0, 0, 0, 0, 0, 10100, 0, 0, 0, 10101, 0, 10102, 10103, 10103, 0, 0, 0, 10104, 0, 10105, 10106, 10106, 0, 10107, 10108, 10108, 10109, 10109, 12108, 12108, 0, 0, 0, 0, 0, 0, 0, 10110, 0, 0, 0, 10111, 0, 10112, 10113, 10113, 0, 0, 0, 10114, 0, 10115, 10116, 10116, 0, 10117, 10118, 10118, 10119, 10119, 10119, 12107, 0, 0, 0, 10120, 0, 10121, 10122, 10122, 0, 10123, 10124, 10124, 10125, 10125, 10125, 10125, 0, 10126, 10127, 10127, 10128, 10128, 10128, 10128, 10129, 10129, 10129, 10129, 12109, 12109, 12109, 12109, 0, 0, 0, 0, 0, 0, 0, 10130, 0, 0, 0, 10131, 0, 10132, 10133, 10133, 0, 0, 0, 10134, 0, 10135, 10136, 10136, 0, 10137, 10138, 10138, 10139, 10139, 10139, 12107, 0, 0, 0, 10140, 0, 10141, 10142, 10142, 0, 10143, 10144, 10144, 10145, 10145, 10145, 10145, 0, 10146, 10147, 10147, 10148, 10148, 10148, 10148, 10149, 10149, 10149, 10149, 10149, 10149, 12108, 12108, 0, 0, 0, 10150, 0, 10151, 10152, 10152, 0, 10153, 10154, 10154, 10155, 10155, 10155, 10155, 0, 10156, 10157, 10157, 10158, 10158, 10158, 10158, 10159, 10159, 10159, 10159, 10159, 10159, 10159, 12107, 0, 10160, 10161, 10161, 10162, 10162, 10162, 10162, 10163, 10163, 10163, 10163, 10163, 10163, 10163, 10163, 10164, 10164, 10164, 10164, 10164, 10164, 10164, 10164, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 0, 0, 0, 0, 0, 0, 0, 10165, 0, 0, 0, 10166, 0, 10167, 10168, 10168, 0, 0, 0, 10169, 0, 10170, 10171, 10171, 0, 10172, 10173, 10173, 10174, 10174, 10174, 12107, 0, 0, 0, 10175, 0, 10176, 10177, 10177, 0, 10178, 10179, 10179, 10180, 10180, 10180, 10180, 0, 10181, 10182, 10182, 10183, 10183, 10183, 10183, 10184, 10184, 10184, 10184, 10184, 10184, 12108, 12108, 0, 0, 0, 10185, 0, 10186, 10187, 10187, 0, 10188, 10189, 10189, 10190, 10190, 10190, 10190, 0, 10191, 10192, 10192, 10193, 10193, 10193, 10193, 10194, 10194, 10194, 10194, 10194, 10194, 10194, 12107, 0, 10195, 10196, 10196, 10197, 10197, 10197, 10197, 10198, 10198, 10198, 10198, 10198, 10198, 10198, 10198, 10199, 10199, 10199, 10199, 10199, 10199, 10199, 10199, 10199, 10199, 10199, 10199, 12109, 12109, 12109, 12109, 0, 0, 0, 10200, 0, 10201, 10202, 10202, 0, 10203, 10204, 10204, 10205, 10205, 10205, 10205, 0, 10206, 10207, 10207, 10208, 10208, 10208, 10208, 10209, 10209, 10209, 10209, 10209, 10209, 10209, 12107, 0, 10210, 10211, 10211, 10212, 10212, 10212, 10212, 10213, 10213, 10213, 10213, 10213, 10213, 10213, 10213, 10214, 10214, 10214, 10214, 10214, 10214, 10214, 10214, 10214, 10214, 10214, 10214, 10214, 10214, 12108, 12108, 0, 10215, 10216, 10216, 10217, 10217, 10217, 10217, 10218, 10218, 10218, 10218, 10218, 10218, 10218, 10218, 10219, 10219, 10219, 10219, 10219, 10219, 10219, 10219, 10219, 10219, 10219, 10219, 10219, 10219, 10219, 12107, 10220, 10220, 10220, 10220, 10220, 10220, 10220, 10220, 10220, 10220, 10220, 10220, 10220, 10220, 10220, 10220, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 0, 0, 0, 0, 0, 0, 0, 10221, 0, 0, 0, 10222, 0, 10223, 10224, 10224, 0, 0, 0, 10225, 0, 10226, 10227, 10227, 0, 10228, 10229, 10229, 10230, 10230, 10230, 12107, 0, 0, 0, 10231, 0, 10232, 10233, 10233, 0, 10234, 10235, 10235, 10236, 10236, 10236, 10236, 0, 10237, 10238, 10238, 10239, 10239, 10239, 10239, 10240, 10240, 10240, 10240, 10240, 10240, 12108, 12108, 0, 0, 0, 10241, 0, 10242, 10243, 10243, 0, 10244, 10245, 10245, 10246, 10246, 10246, 10246, 0, 10247, 10248, 10248, 10249, 10249, 10249, 10249, 10250, 10250, 10250, 10250, 10250, 10250, 10250, 12107, 0, 10251, 10252, 10252, 10253, 10253, 10253, 10253, 10254, 10254, 10254, 10254, 10254, 10254, 10254, 10254, 10255, 10255, 10255, 10255, 10255, 10255, 10255, 10255, 10255, 10255, 10255, 10255, 12109, 12109, 12109, 12109, 0, 0, 0, 10256, 0, 10257, 10258, 10258, 0, 10259, 10260, 10260, 10261, 10261, 10261, 10261, 0, 10262, 10263, 10263, 10264, 10264, 10264, 10264, 10265, 10265, 10265, 10265, 10265, 10265, 10265, 12107, 0, 10266, 10267, 10267, 10268, 10268, 10268, 10268, 10269, 10269, 10269, 10269, 10269, 10269, 10269, 10269, 10270, 10270, 10270, 10270, 10270, 10270, 10270, 10270, 10270, 10270, 10270, 10270, 10270, 10270, 12108, 12108, 0, 10271, 10272, 10272, 10273, 10273, 10273, 10273, 10274, 10274, 10274, 10274, 10274, 10274, 10274, 10274, 10275, 10275, 10275, 10275, 10275, 10275, 10275, 10275, 10275, 10275, 10275, 10275, 10275, 10275, 10275, 12107, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 10276, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 0, 0, 0, 10277, 0, 10278, 10279, 10279, 0, 10280, 10281, 10281, 10282, 10282, 10282, 10282, 0, 10283, 10284, 10284, 10285, 10285, 10285, 10285, 10286, 10286, 10286, 10286, 10286, 10286, 10286, 12107, 0, 10287, 10288, 10288, 10289, 10289, 10289, 10289, 10290, 10290, 10290, 10290, 10290, 10290, 10290, 10290, 10291, 10291, 10291, 10291, 10291, 10291, 10291, 10291, 10291, 10291, 10291, 10291, 10291, 10291, 12108, 12108, 0, 10292, 10293, 10293, 10294, 10294, 10294, 10294, 10295, 10295, 10295, 10295, 10295, 10295, 10295, 10295, 10296, 10296, 10296, 10296, 10296, 10296, 10296, 10296, 10296, 10296, 10296, 10296, 10296, 10296, 10296, 12107, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 10297, 12109, 12109, 12109, 12109, 0, 10298, 10299, 10299, 10300, 10300, 10300, 10300, 10301, 10301, 10301, 10301, 10301, 10301, 10301, 10301, 10302, 10302, 10302, 10302, 10302, 10302, 10302, 10302, 10302, 10302, 10302, 10302, 10302, 10302, 10302, 12107, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 10303, 12108, 12108, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 12107, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 0, 0, 0, 0, 0, 0, 0, 10305, 0, 0, 0, 10306, 0, 10307, 10308, 10308, 0, 0, 0, 10309, 0, 10310, 10311, 10311, 0, 10312, 10313, 10313, 10314, 10314, 10314, 12107, 0, 0, 0, 10315, 0, 10316, 10317, 10317, 0, 10318, 10319, 10319, 10320, 10320, 10320, 10320, 0, 10321, 10322, 10322, 10323, 10323, 10323, 10323, 10324, 10324, 10324, 10324, 10324, 10324, 12108, 12108, 0, 0, 0, 10325, 0, 10326, 10327, 10327, 0, 10328, 10329, 10329, 10330, 10330, 10330, 10330, 0, 10331, 10332, 10332, 10333, 10333, 10333, 10333, 10334, 10334, 10334, 10334, 10334, 10334, 10334, 12107, 0, 10335, 10336, 10336, 10337, 10337, 10337, 10337, 10338, 10338, 10338, 10338, 10338, 10338, 10338, 10338, 10339, 10339, 10339, 10339, 10339, 10339, 10339, 10339, 10339, 10339, 10339, 10339, 12109, 12109, 12109, 12109, 0, 0, 0, 10340, 0, 10341, 10342, 10342, 0, 10343, 10344, 10344, 10345, 10345, 10345, 10345, 0, 10346, 10347, 10347, 10348, 10348, 10348, 10348, 10349, 10349, 10349, 10349, 10349, 10349, 10349, 12107, 0, 10350, 10351, 10351, 10352, 10352, 10352, 10352, 10353, 10353, 10353, 10353, 10353, 10353, 10353, 10353, 10354, 10354, 10354, 10354, 10354, 10354, 10354, 10354, 10354, 10354, 10354, 10354, 10354, 10354, 12108, 12108, 0, 10355, 10356, 10356, 10357, 10357, 10357, 10357, 10358, 10358, 10358, 10358, 10358, 10358, 10358, 10358, 10359, 10359, 10359, 10359, 10359, 10359, 10359, 10359, 10359, 10359, 10359, 10359, 10359, 10359, 10359, 12107, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 0, 0, 0, 10361, 0, 10362, 10363, 10363, 0, 10364, 10365, 10365, 10366, 10366, 10366, 10366, 0, 10367, 10368, 10368, 10369, 10369, 10369, 10369, 10370, 10370, 10370, 10370, 10370, 10370, 10370, 12107, 0, 10371, 10372, 10372, 10373, 10373, 10373, 10373, 10374, 10374, 10374, 10374, 10374, 10374, 10374, 10374, 10375, 10375, 10375, 10375, 10375, 10375, 10375, 10375, 10375, 10375, 10375, 10375, 10375, 10375, 12108, 12108, 0, 10376, 10377, 10377, 10378, 10378, 10378, 10378, 10379, 10379, 10379, 10379, 10379, 10379, 10379, 10379, 10380, 10380, 10380, 10380, 10380, 10380, 10380, 10380, 10380, 10380, 10380, 10380, 10380, 10380, 10380, 12107, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 10381, 12109, 12109, 12109, 12109, 0, 10382, 10383, 10383, 10384, 10384, 10384, 10384, 10385, 10385, 10385, 10385, 10385, 10385, 10385, 10385, 10386, 10386, 10386, 10386, 10386, 10386, 10386, 10386, 10386, 10386, 10386, 10386, 10386, 10386, 10386, 12107, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 10387, 12108, 12108, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 12107, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 10388, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 0, 0, 0, 10389, 0, 10390, 10391, 10391, 0, 10392, 10393, 10393, 10394, 10394, 10394, 10394, 0, 10395, 10396, 10396, 10397, 10397, 10397, 10397, 10398, 10398, 10398, 10398, 10398, 10398, 10398, 12107, 0, 10399, 10400, 10400, 10401, 10401, 10401, 10401, 10402, 10402, 10402, 10402, 10402, 10402, 10402, 10402, 10403, 10403, 10403, 10403, 10403, 10403, 10403, 10403, 10403, 10403, 10403, 10403, 10403, 10403, 12108, 12108, 0, 10404, 10405, 10405, 10406, 10406, 10406, 10406, 10407, 10407, 10407, 10407, 10407, 10407, 10407, 10407, 10408, 10408, 10408, 10408, 10408, 10408, 10408, 10408, 10408, 10408, 10408, 10408, 10408, 10408, 10408, 12107, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 10409, 12109, 12109, 12109, 12109, 0, 10410, 10411, 10411, 10412, 10412, 10412, 10412, 10413, 10413, 10413, 10413, 10413, 10413, 10413, 10413, 10414, 10414, 10414, 10414, 10414, 10414, 10414, 10414, 10414, 10414, 10414, 10414, 10414, 10414, 10414, 12107, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 10415, 12108, 12108, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 12107, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 0, 10417, 10418, 10418, 10419, 10419, 10419, 10419, 10420, 10420, 10420, 10420, 10420, 10420, 10420, 10420, 10421, 10421, 10421, 10421, 10421, 10421, 10421, 10421, 10421, 10421, 10421, 10421, 10421, 10421, 10421, 12107, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 10422, 12108, 12108, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 12107, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 10423, 12109, 12109, 12109, 12109, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 12114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12106, 0, 0, 0, 0, 0, 0, 0, 10426, 0, 0, 0, 10427, 0, 10428, 10429, 12107, 0, 0, 0, 0, 0, 0, 0, 10430, 0, 0, 0, 10431, 0, 10432, 10433, 12106, 0, 0, 0, 10434, 0, 10435, 10436, 10436, 0, 10437, 10438, 10438, 10439, 10439, 12108, 12108, 0, 0, 0, 0, 0, 0, 0, 10440, 0, 0, 0, 10441, 0, 10442, 10443, 12106, 0, 0, 0, 10444, 0, 10445, 10446, 10446, 0, 10447, 10448, 10448, 10449, 10449, 10449, 12107, 0, 0, 0, 10450, 0, 10451, 10452, 10452, 0, 10453, 10454, 10454, 10455, 10455, 10455, 12106, 0, 10456, 10457, 10457, 10458, 10458, 10458, 10458, 10459, 10459, 10459, 10459, 12109, 12109, 12109, 12109, 0, 0, 0, 0, 0, 0, 0, 10460, 0, 0, 0, 10461, 0, 10462, 10463, 12106, 0, 0, 0, 10464, 0, 10465, 10466, 10466, 0, 10467, 10468, 10468, 10469, 10469, 10469, 12107, 0, 0, 0, 10470, 0, 10471, 10472, 10472, 0, 10473, 10474, 10474, 10475, 10475, 10475, 12106, 0, 10476, 10477, 10477, 10478, 10478, 10478, 10478, 10479, 10479, 10479, 10479, 10479, 10479, 12108, 12108, 0, 0, 0, 10480, 0, 10481, 10482, 10482, 0, 10483, 10484, 10484, 10485, 10485, 10485, 12106, 0, 10486, 10487, 10487, 10488, 10488, 10488, 10488, 10489, 10489, 10489, 10489, 10489, 10489, 10489, 12107, 0, 10490, 10491, 10491, 10492, 10492, 10492, 10492, 10493, 10493, 10493, 10493, 10493, 10493, 10493, 12106, 10494, 10494, 10494, 10494, 10494, 10494, 10494, 10494, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 0, 0, 0, 0, 0, 0, 0, 10495, 0, 0, 0, 10496, 0, 10497, 10498, 12106, 0, 0, 0, 10499, 0, 10500, 10501, 10501, 0, 10502, 10503, 10503, 10504, 10504, 10504, 12107, 0, 0, 0, 10505, 0, 10506, 10507, 10507, 0, 10508, 10509, 10509, 10510, 10510, 10510, 12106, 0, 10511, 10512, 10512, 10513, 10513, 10513, 10513, 10514, 10514, 10514, 10514, 10514, 10514, 12108, 12108, 0, 0, 0, 10515, 0, 10516, 10517, 10517, 0, 10518, 10519, 10519, 10520, 10520, 10520, 12106, 0, 10521, 10522, 10522, 10523, 10523, 10523, 10523, 10524, 10524, 10524, 10524, 10524, 10524, 10524, 12107, 0, 10525, 10526, 10526, 10527, 10527, 10527, 10527, 10528, 10528, 10528, 10528, 10528, 10528, 10528, 12106, 10529, 10529, 10529, 10529, 10529, 10529, 10529, 10529, 10529, 10529, 10529, 10529, 12109, 12109, 12109, 12109, 0, 0, 0, 10530, 0, 10531, 10532, 10532, 0, 10533, 10534, 10534, 10535, 10535, 10535, 12106, 0, 10536, 10537, 10537, 10538, 10538, 10538, 10538, 10539, 10539, 10539, 10539, 10539, 10539, 10539, 12107, 0, 10540, 10541, 10541, 10542, 10542, 10542, 10542, 10543, 10543, 10543, 10543, 10543, 10543, 10543, 12106, 10544, 10544, 10544, 10544, 10544, 10544, 10544, 10544, 10544, 10544, 10544, 10544, 10544, 10544, 12108, 12108, 0, 10545, 10546, 10546, 10547, 10547, 10547, 10547, 10548, 10548, 10548, 10548, 10548, 10548, 10548, 12106, 10549, 10549, 10549, 10549, 10549, 10549, 10549, 10549, 10549, 10549, 10549, 10549, 10549, 10549, 10549, 12107, 10550, 10550, 10550, 10550, 10550, 10550, 10550, 10550, 10550, 10550, 10550, 10550, 10550, 10550, 10550, 12106, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 0, 0, 0, 0, 0, 0, 0, 10551, 0, 0, 0, 10552, 0, 10553, 10554, 12106, 0, 0, 0, 10555, 0, 10556, 10557, 10557, 0, 10558, 10559, 10559, 10560, 10560, 10560, 12107, 0, 0, 0, 10561, 0, 10562, 10563, 10563, 0, 10564, 10565, 10565, 10566, 10566, 10566, 12106, 0, 10567, 10568, 10568, 10569, 10569, 10569, 10569, 10570, 10570, 10570, 10570, 10570, 10570, 12108, 12108, 0, 0, 0, 10571, 0, 10572, 10573, 10573, 0, 10574, 10575, 10575, 10576, 10576, 10576, 12106, 0, 10577, 10578, 10578, 10579, 10579, 10579, 10579, 10580, 10580, 10580, 10580, 10580, 10580, 10580, 12107, 0, 10581, 10582, 10582, 10583, 10583, 10583, 10583, 10584, 10584, 10584, 10584, 10584, 10584, 10584, 12106, 10585, 10585, 10585, 10585, 10585, 10585, 10585, 10585, 10585, 10585, 10585, 10585, 12109, 12109, 12109, 12109, 0, 0, 0, 10586, 0, 10587, 10588, 10588, 0, 10589, 10590, 10590, 10591, 10591, 10591, 12106, 0, 10592, 10593, 10593, 10594, 10594, 10594, 10594, 10595, 10595, 10595, 10595, 10595, 10595, 10595, 12107, 0, 10596, 10597, 10597, 10598, 10598, 10598, 10598, 10599, 10599, 10599, 10599, 10599, 10599, 10599, 12106, 10600, 10600, 10600, 10600, 10600, 10600, 10600, 10600, 10600, 10600, 10600, 10600, 10600, 10600, 12108, 12108, 0, 10601, 10602, 10602, 10603, 10603, 10603, 10603, 10604, 10604, 10604, 10604, 10604, 10604, 10604, 12106, 10605, 10605, 10605, 10605, 10605, 10605, 10605, 10605, 10605, 10605, 10605, 10605, 10605, 10605, 10605, 12107, 10606, 10606, 10606, 10606, 10606, 10606, 10606, 10606, 10606, 10606, 10606, 10606, 10606, 10606, 10606, 12106, 10606, 10606, 10606, 10606, 10606, 10606, 10606, 10606, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 0, 0, 0, 10607, 0, 10608, 10609, 10609, 0, 10610, 10611, 10611, 10612, 10612, 10612, 12106, 0, 10613, 10614, 10614, 10615, 10615, 10615, 10615, 10616, 10616, 10616, 10616, 10616, 10616, 10616, 12107, 0, 10617, 10618, 10618, 10619, 10619, 10619, 10619, 10620, 10620, 10620, 10620, 10620, 10620, 10620, 12106, 10621, 10621, 10621, 10621, 10621, 10621, 10621, 10621, 10621, 10621, 10621, 10621, 10621, 10621, 12108, 12108, 0, 10622, 10623, 10623, 10624, 10624, 10624, 10624, 10625, 10625, 10625, 10625, 10625, 10625, 10625, 12106, 10626, 10626, 10626, 10626, 10626, 10626, 10626, 10626, 10626, 10626, 10626, 10626, 10626, 10626, 10626, 12107, 10627, 10627, 10627, 10627, 10627, 10627, 10627, 10627, 10627, 10627, 10627, 10627, 10627, 10627, 10627, 12106, 10627, 10627, 10627, 10627, 10627, 10627, 10627, 10627, 10627, 10627, 10627, 10627, 12109, 12109, 12109, 12109, 0, 10628, 10629, 10629, 10630, 10630, 10630, 10630, 10631, 10631, 10631, 10631, 10631, 10631, 10631, 12106, 10632, 10632, 10632, 10632, 10632, 10632, 10632, 10632, 10632, 10632, 10632, 10632, 10632, 10632, 10632, 12107, 10633, 10633, 10633, 10633, 10633, 10633, 10633, 10633, 10633, 10633, 10633, 10633, 10633, 10633, 10633, 12106, 10633, 10633, 10633, 10633, 10633, 10633, 10633, 10633, 10633, 10633, 10633, 10633, 10633, 10633, 12108, 12108, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 12106, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 10634, 12107, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 0, 0, 0, 0, 0, 0, 0, 10635, 0, 0, 0, 10636, 0, 10637, 10638, 12106, 0, 0, 0, 10639, 0, 10640, 10641, 10641, 0, 10642, 10643, 10643, 10644, 10644, 10644, 12107, 0, 0, 0, 10645, 0, 10646, 10647, 10647, 0, 10648, 10649, 10649, 10650, 10650, 10650, 12106, 0, 10651, 10652, 10652, 10653, 10653, 10653, 10653, 10654, 10654, 10654, 10654, 10654, 10654, 12108, 12108, 0, 0, 0, 10655, 0, 10656, 10657, 10657, 0, 10658, 10659, 10659, 10660, 10660, 10660, 12106, 0, 10661, 10662, 10662, 10663, 10663, 10663, 10663, 10664, 10664, 10664, 10664, 10664, 10664, 10664, 12107, 0, 10665, 10666, 10666, 10667, 10667, 10667, 10667, 10668, 10668, 10668, 10668, 10668, 10668, 10668, 12106, 10669, 10669, 10669, 10669, 10669, 10669, 10669, 10669, 10669, 10669, 10669, 10669, 12109, 12109, 12109, 12109, 0, 0, 0, 10670, 0, 10671, 10672, 10672, 0, 10673, 10674, 10674, 10675, 10675, 10675, 12106, 0, 10676, 10677, 10677, 10678, 10678, 10678, 10678, 10679, 10679, 10679, 10679, 10679, 10679, 10679, 12107, 0, 10680, 10681, 10681, 10682, 10682, 10682, 10682, 10683, 10683, 10683, 10683, 10683, 10683, 10683, 12106, 10684, 10684, 10684, 10684, 10684, 10684, 10684, 10684, 10684, 10684, 10684, 10684, 10684, 10684, 12108, 12108, 0, 10685, 10686, 10686, 10687, 10687, 10687, 10687, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 12106, 10689, 10689, 10689, 10689, 10689, 10689, 10689, 10689, 10689, 10689, 10689, 10689, 10689, 10689, 10689, 12107, 10690, 10690, 10690, 10690, 10690, 10690, 10690, 10690, 10690, 10690, 10690, 10690, 10690, 10690, 10690, 12106, 10690, 10690, 10690, 10690, 10690, 10690, 10690, 10690, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 0, 0, 0, 10691, 0, 10692, 10693, 10693, 0, 10694, 10695, 10695, 10696, 10696, 10696, 12106, 0, 10697, 10698, 10698, 10699, 10699, 10699, 10699, 10700, 10700, 10700, 10700, 10700, 10700, 10700, 12107, 0, 10701, 10702, 10702, 10703, 10703, 10703, 10703, 10704, 10704, 10704, 10704, 10704, 10704, 10704, 12106, 10705, 10705, 10705, 10705, 10705, 10705, 10705, 10705, 10705, 10705, 10705, 10705, 10705, 10705, 12108, 12108, 0, 10706, 10707, 10707, 10708, 10708, 10708, 10708, 10709, 10709, 10709, 10709, 10709, 10709, 10709, 12106, 10710, 10710, 10710, 10710, 10710, 10710, 10710, 10710, 10710, 10710, 10710, 10710, 10710, 10710, 10710, 12107, 10711, 10711, 10711, 10711, 10711, 10711, 10711, 10711, 10711, 10711, 10711, 10711, 10711, 10711, 10711, 12106, 10711, 10711, 10711, 10711, 10711, 10711, 10711, 10711, 10711, 10711, 10711, 10711, 12109, 12109, 12109, 12109, 0, 10712, 10713, 10713, 10714, 10714, 10714, 10714, 10715, 10715, 10715, 10715, 10715, 10715, 10715, 12106, 10716, 10716, 10716, 10716, 10716, 10716, 10716, 10716, 10716, 10716, 10716, 10716, 10716, 10716, 10716, 12107, 10717, 10717, 10717, 10717, 10717, 10717, 10717, 10717, 10717, 10717, 10717, 10717, 10717, 10717, 10717, 12106, 10717, 10717, 10717, 10717, 10717, 10717, 10717, 10717, 10717, 10717, 10717, 10717, 10717, 10717, 12108, 12108, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 12106, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 12107, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 10718, 12106, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 0, 0, 0, 10719, 0, 10720, 10721, 10721, 0, 10722, 10723, 10723, 10724, 10724, 10724, 12106, 0, 10725, 10726, 10726, 10727, 10727, 10727, 10727, 10728, 10728, 10728, 10728, 10728, 10728, 10728, 12107, 0, 10729, 10730, 10730, 10731, 10731, 10731, 10731, 10732, 10732, 10732, 10732, 10732, 10732, 10732, 12106, 10733, 10733, 10733, 10733, 10733, 10733, 10733, 10733, 10733, 10733, 10733, 10733, 10733, 10733, 12108, 12108, 0, 10734, 10735, 10735, 10736, 10736, 10736, 10736, 10737, 10737, 10737, 10737, 10737, 10737, 10737, 12106, 10738, 10738, 10738, 10738, 10738, 10738, 10738, 10738, 10738, 10738, 10738, 10738, 10738, 10738, 10738, 12107, 10739, 10739, 10739, 10739, 10739, 10739, 10739, 10739, 10739, 10739, 10739, 10739, 10739, 10739, 10739, 12106, 10739, 10739, 10739, 10739, 10739, 10739, 10739, 10739, 10739, 10739, 10739, 10739, 12109, 12109, 12109, 12109, 0, 10740, 10741, 10741, 10742, 10742, 10742, 10742, 10743, 10743, 10743, 10743, 10743, 10743, 10743, 12106, 10744, 10744, 10744, 10744, 10744, 10744, 10744, 10744, 10744, 10744, 10744, 10744, 10744, 10744, 10744, 12107, 10745, 10745, 10745, 10745, 10745, 10745, 10745, 10745, 10745, 10745, 10745, 10745, 10745, 10745, 10745, 12106, 10745, 10745, 10745, 10745, 10745, 10745, 10745, 10745, 10745, 10745, 10745, 10745, 10745, 10745, 12108, 12108, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 12106, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 12107, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 12106, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 10746, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 0, 10747, 10748, 10748, 10749, 10749, 10749, 10749, 10750, 10750, 10750, 10750, 10750, 10750, 10750, 12106, 10751, 10751, 10751, 10751, 10751, 10751, 10751, 10751, 10751, 10751, 10751, 10751, 10751, 10751, 10751, 12107, 10752, 10752, 10752, 10752, 10752, 10752, 10752, 10752, 10752, 10752, 10752, 10752, 10752, 10752, 10752, 12106, 10752, 10752, 10752, 10752, 10752, 10752, 10752, 10752, 10752, 10752, 10752, 10752, 10752, 10752, 12108, 12108, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 12106, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 12107, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 12106, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 10753, 12109, 12109, 12109, 12109, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 12106, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 12107, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 12106, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 10754, 12108, 12108, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 12113, 0, 0, 0, 0, 0, 0, 0, 10755, 0, 0, 0, 10756, 0, 10757, 10758, 12106, 0, 0, 0, 10759, 0, 10760, 10761, 10761, 0, 10762, 10763, 10763, 10764, 10764, 10764, 12107, 0, 0, 0, 10765, 0, 10766, 10767, 10767, 0, 10768, 10769, 10769, 10770, 10770, 10770, 12106, 0, 10771, 10772, 10772, 10773, 10773, 10773, 10773, 10774, 10774, 10774, 10774, 10774, 10774, 12108, 12108, 0, 0, 0, 10775, 0, 10776, 10777, 10777, 0, 10778, 10779, 10779, 10780, 10780, 10780, 12106, 0, 10781, 10782, 10782, 10783, 10783, 10783, 10783, 10784, 10784, 10784, 10784, 10784, 10784, 10784, 12107, 0, 10785, 10786, 10786, 10787, 10787, 10787, 10787, 10788, 10788, 10788, 10788, 10788, 10788, 10788, 12106, 10789, 10789, 10789, 10789, 10789, 10789, 10789, 10789, 10789, 10789, 10789, 10789, 12109, 12109, 12109, 12109, 0, 0, 0, 10790, 0, 10791, 10792, 10792, 0, 10793, 10794, 10794, 10795, 10795, 10795, 12106, 0, 10796, 10797, 10797, 10798, 10798, 10798, 10798, 10799, 10799, 10799, 10799, 10799, 10799, 10799, 12107, 0, 10800, 10801, 10801, 10802, 10802, 10802, 10802, 10803, 10803, 10803, 10803, 10803, 10803, 10803, 12106, 10804, 10804, 10804, 10804, 10804, 10804, 10804, 10804, 10804, 10804, 10804, 10804, 10804, 10804, 12108, 12108, 0, 10805, 10806, 10806, 10807, 10807, 10807, 10807, 10808, 10808, 10808, 10808, 10808, 10808, 10808, 12106, 10809, 10809, 10809, 10809, 10809, 10809, 10809, 10809, 10809, 10809, 10809, 10809, 10809, 10809, 10809, 12107, 10810, 10810, 10810, 10810, 10810, 10810, 10810, 10810, 10810, 10810, 10810, 10810, 10810, 10810, 10810, 12106, 10810, 10810, 10810, 10810, 10810, 10810, 10810, 10810, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 0, 0, 0, 10811, 0, 10812, 10813, 10813, 0, 10814, 10815, 10815, 10816, 10816, 10816, 12106, 0, 10817, 10818, 10818, 10819, 10819, 10819, 10819, 10820, 10820, 10820, 10820, 10820, 10820, 10820, 12107, 0, 10821, 10822, 10822, 10823, 10823, 10823, 10823, 10824, 10824, 10824, 10824, 10824, 10824, 10824, 12106, 10825, 10825, 10825, 10825, 10825, 10825, 10825, 10825, 10825, 10825, 10825, 10825, 10825, 10825, 12108, 12108, 0, 10826, 10827, 10827, 10828, 10828, 10828, 10828, 10829, 10829, 10829, 10829, 10829, 10829, 10829, 12106, 10830, 10830, 10830, 10830, 10830, 10830, 10830, 10830, 10830, 10830, 10830, 10830, 10830, 10830, 10830, 12107, 10831, 10831, 10831, 10831, 10831, 10831, 10831, 10831, 10831, 10831, 10831, 10831, 10831, 10831, 10831, 12106, 10831, 10831, 10831, 10831, 10831, 10831, 10831, 10831, 10831, 10831, 10831, 10831, 12109, 12109, 12109, 12109, 0, 10832, 10833, 10833, 10834, 10834, 10834, 10834, 10835, 10835, 10835, 10835, 10835, 10835, 10835, 12106, 10836, 10836, 10836, 10836, 10836, 10836, 10836, 10836, 10836, 10836, 10836, 10836, 10836, 10836, 10836, 12107, 10837, 10837, 10837, 10837, 10837, 10837, 10837, 10837, 10837, 10837, 10837, 10837, 10837, 10837, 10837, 12106, 10837, 10837, 10837, 10837, 10837, 10837, 10837, 10837, 10837, 10837, 10837, 10837, 10837, 10837, 12108, 12108, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 12106, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 12107, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 10838, 12106, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 0, 0, 0, 10839, 0, 10840, 10841, 10841, 0, 10842, 10843, 10843, 10844, 10844, 10844, 12106, 0, 10845, 10846, 10846, 10847, 10847, 10847, 10847, 10848, 10848, 10848, 10848, 10848, 10848, 10848, 12107, 0, 10849, 10850, 10850, 10851, 10851, 10851, 10851, 10852, 10852, 10852, 10852, 10852, 10852, 10852, 12106, 10853, 10853, 10853, 10853, 10853, 10853, 10853, 10853, 10853, 10853, 10853, 10853, 10853, 10853, 12108, 12108, 0, 10854, 10855, 10855, 10856, 10856, 10856, 10856, 10857, 10857, 10857, 10857, 10857, 10857, 10857, 12106, 10858, 10858, 10858, 10858, 10858, 10858, 10858, 10858, 10858, 10858, 10858, 10858, 10858, 10858, 10858, 12107, 10859, 10859, 10859, 10859, 10859, 10859, 10859, 10859, 10859, 10859, 10859, 10859, 10859, 10859, 10859, 12106, 10859, 10859, 10859, 10859, 10859, 10859, 10859, 10859, 10859, 10859, 10859, 10859, 12109, 12109, 12109, 12109, 0, 10860, 10861, 10861, 10862, 10862, 10862, 10862, 10863, 10863, 10863, 10863, 10863, 10863, 10863, 12106, 10864, 10864, 10864, 10864, 10864, 10864, 10864, 10864, 10864, 10864, 10864, 10864, 10864, 10864, 10864, 12107, 10865, 10865, 10865, 10865, 10865, 10865, 10865, 10865, 10865, 10865, 10865, 10865, 10865, 10865, 10865, 12106, 10865, 10865, 10865, 10865, 10865, 10865, 10865, 10865, 10865, 10865, 10865, 10865, 10865, 10865, 12108, 12108, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 12106, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 12107, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 12106, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 10866, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 0, 10867, 10868, 10868, 10869, 10869, 10869, 10869, 10870, 10870, 10870, 10870, 10870, 10870, 10870, 12106, 10871, 10871, 10871, 10871, 10871, 10871, 10871, 10871, 10871, 10871, 10871, 10871, 10871, 10871, 10871, 12107, 10872, 10872, 10872, 10872, 10872, 10872, 10872, 10872, 10872, 10872, 10872, 10872, 10872, 10872, 10872, 12106, 10872, 10872, 10872, 10872, 10872, 10872, 10872, 10872, 10872, 10872, 10872, 10872, 10872, 10872, 12108, 12108, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 12106, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 12107, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 12106, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 10873, 12109, 12109, 12109, 12109, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 12106, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 12107, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 12106, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 12108, 12108, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 12106, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 10874, 12107, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 12112, 0, 0, 0, 10875, 0, 10876, 10877, 10877, 0, 10878, 10879, 10879, 10880, 10880, 10880, 12106, 0, 10881, 10882, 10882, 10883, 10883, 10883, 10883, 10884, 10884, 10884, 10884, 10884, 10884, 10884, 12107, 0, 10885, 10886, 10886, 10887, 10887, 10887, 10887, 10888, 10888, 10888, 10888, 10888, 10888, 10888, 12106, 10889, 10889, 10889, 10889, 10889, 10889, 10889, 10889, 10889, 10889, 10889, 10889, 10889, 10889, 12108, 12108, 0, 10890, 10891, 10891, 10892, 10892, 10892, 10892, 10893, 10893, 10893, 10893, 10893, 10893, 10893, 12106, 10894, 10894, 10894, 10894, 10894, 10894, 10894, 10894, 10894, 10894, 10894, 10894, 10894, 10894, 10894, 12107, 10895, 10895, 10895, 10895, 10895, 10895, 10895, 10895, 10895, 10895, 10895, 10895, 10895, 10895, 10895, 12106, 10895, 10895, 10895, 10895, 10895, 10895, 10895, 10895, 10895, 10895, 10895, 10895, 12109, 12109, 12109, 12109, 0, 10896, 10897, 10897, 10898, 10898, 10898, 10898, 10899, 10899, 10899, 10899, 10899, 10899, 10899, 12106, 10900, 10900, 10900, 10900, 10900, 10900, 10900, 10900, 10900, 10900, 10900, 10900, 10900, 10900, 10900, 12107, 10901, 10901, 10901, 10901, 10901, 10901, 10901, 10901, 10901, 10901, 10901, 10901, 10901, 10901, 10901, 12106, 10901, 10901, 10901, 10901, 10901, 10901, 10901, 10901, 10901, 10901, 10901, 10901, 10901, 10901, 12108, 12108, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 12106, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 12107, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 12106, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 10902, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 0, 10903, 10904, 10904, 10905, 10905, 10905, 10905, 10906, 10906, 10906, 10906, 10906, 10906, 10906, 12106, 10907, 10907, 10907, 10907, 10907, 10907, 10907, 10907, 10907, 10907, 10907, 10907, 10907, 10907, 10907, 12107, 10908, 10908, 10908, 10908, 10908, 10908, 10908, 10908, 10908, 10908, 10908, 10908, 10908, 10908, 10908, 12106, 10908, 10908, 10908, 10908, 10908, 10908, 10908, 10908, 10908, 10908, 10908, 10908, 10908, 10908, 12108, 12108, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 12106, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 12107, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 12106, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 10909, 12109, 12109, 12109, 12109, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 12106, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 12107, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 12106, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 12108, 12108, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 12106, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 12107, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 10910, 12106, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 12111, 0, 10911, 10912, 10912, 10913, 10913, 10913, 10913, 10914, 10914, 10914, 10914, 10914, 10914, 10914, 12106, 10915, 10915, 10915, 10915, 10915, 10915, 10915, 10915, 10915, 10915, 10915, 10915, 10915, 10915, 10915, 12107, 10916, 10916, 10916, 10916, 10916, 10916, 10916, 10916, 10916, 10916, 10916, 10916, 10916, 10916, 10916, 12106, 10916, 10916, 10916, 10916, 10916, 10916, 10916, 10916, 10916, 10916, 10916, 10916, 10916, 10916, 12108, 12108, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 12106, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 12107, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 12106, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 10917, 12109, 12109, 12109, 12109, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 12106, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 12107, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 12106, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 12108, 12108, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 12106, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 12107, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 12106, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 10918, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 12110, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115, 12115 }; static const uint16_t pairOtherVal[ 8192 ] = { 0, 0, 1, 0, 2, 1, 2, 0, 3, 3, 4, 1, 5, 2, 3, 3, 4, 6, 7, 4, 8, 5, 6, 6, 9, 7, 8, 8, 9, 9, 9, 9, 5, 10, 11, 10, 12, 11, 12, 12, 13, 13, 14, 14, 15, 15, 15, 15, 14, 16, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 6, 15, 16, 20, 17, 21, 22, 22, 18, 23, 24, 24, 25, 25, 25, 25, 19, 26, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 20, 30, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 33, 33, 33, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 7, 21, 22, 35, 23, 36, 37, 37, 24, 38, 39, 39, 40, 40, 40, 40, 25, 41, 42, 42, 43, 43, 43, 43, 44, 44, 44, 44, 44, 44, 44, 44, 26, 45, 46, 46, 47, 47, 47, 47, 48, 48, 48, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 27, 50, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 53, 53, 53, 53, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 8, 28, 29, 56, 30, 57, 58, 58, 31, 59, 60, 60, 61, 61, 61, 61, 32, 62, 63, 63, 64, 64, 64, 64, 65, 65, 65, 65, 65, 65, 65, 65, 33, 66, 67, 67, 68, 68, 68, 68, 69, 69, 69, 69, 69, 69, 69, 69, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 34, 71, 72, 72, 73, 73, 73, 73, 74, 74, 74, 74, 74, 74, 74, 74, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 35, 77, 78, 78, 79, 79, 79, 79, 80, 80, 80, 80, 80, 80, 80, 80, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 9, 36, 37, 84, 38, 85, 86, 86, 39, 87, 88, 88, 89, 89, 89, 89, 40, 90, 91, 91, 92, 92, 92, 92, 93, 93, 93, 93, 93, 93, 93, 93, 41, 94, 95, 95, 96, 96, 96, 96, 97, 97, 97, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 42, 99, 100, 100, 101, 101, 101, 101, 102, 102, 102, 102, 102, 102, 102, 102, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 43, 105, 106, 106, 107, 107, 107, 107, 108, 108, 108, 108, 108, 108, 108, 108, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 44, 112, 113, 113, 114, 114, 114, 114, 115, 115, 115, 115, 115, 115, 115, 115, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 10, 45, 46, 120, 47, 121, 122, 122, 48, 123, 124, 124, 125, 125, 125, 125, 49, 126, 127, 127, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 50, 130, 131, 131, 132, 132, 132, 132, 133, 133, 133, 133, 133, 133, 133, 133, 134, 134, 134, 134, 134, 134, 134, 134, 134, 134, 134, 134, 134, 134, 134, 134, 51, 135, 136, 136, 137, 137, 137, 137, 138, 138, 138, 138, 138, 138, 138, 138, 139, 139, 139, 139, 139, 139, 139, 139, 139, 139, 139, 139, 139, 139, 139, 139, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 52, 141, 142, 142, 143, 143, 143, 143, 144, 144, 144, 144, 144, 144, 144, 144, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 145, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 147, 53, 148, 149, 149, 150, 150, 150, 150, 151, 151, 151, 151, 151, 151, 151, 151, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 155, 54, 156, 157, 157, 158, 158, 158, 158, 159, 159, 159, 159, 159, 159, 159, 159, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 11, 55, 56, 165, 57, 166, 167, 167, 58, 168, 169, 169, 170, 170, 170, 170, 59, 171, 172, 172, 173, 173, 173, 173, 174, 174, 174, 174, 174, 174, 174, 174, 60, 175, 176, 176, 177, 177, 177, 177, 178, 178, 178, 178, 178, 178, 178, 178, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 61, 180, 181, 181, 182, 182, 182, 182, 183, 183, 183, 183, 183, 183, 183, 183, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 62, 186, 187, 187, 188, 188, 188, 188, 189, 189, 189, 189, 189, 189, 189, 189, 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 63, 193, 194, 194, 195, 195, 195, 195, 196, 196, 196, 196, 196, 196, 196, 196, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 64, 201, 202, 202, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 65, 210, 211, 211, 212, 212, 212, 212, 213, 213, 213, 213, 213, 213, 213, 213, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 12, 66, 67, 220, 68, 221, 222, 222, 69, 223, 224, 224, 225, 225, 225, 225, 70, 226, 227, 227, 228, 228, 228, 228, 229, 229, 229, 229, 229, 229, 229, 229, 71, 230, 231, 231, 232, 232, 232, 232, 233, 233, 233, 233, 233, 233, 233, 233, 234, 234, 234, 234, 234, 234, 234, 234, 234, 234, 234, 234, 234, 234, 234, 234, 72, 235, 236, 236, 237, 237, 237, 237, 238, 238, 238, 238, 238, 238, 238, 238, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 73, 241, 242, 242, 243, 243, 243, 243, 244, 244, 244, 244, 244, 244, 244, 244, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 74, 248, 249, 249, 250, 250, 250, 250, 251, 251, 251, 251, 251, 251, 251, 251, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 75, 256, 257, 257, 258, 258, 258, 258, 259, 259, 259, 259, 259, 259, 259, 259, 260, 260, 260, 260, 260, 260, 260, 260, 260, 260, 260, 260, 260, 260, 260, 260, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 264, 76, 265, 266, 266, 267, 267, 267, 267, 268, 268, 268, 268, 268, 268, 268, 268, 269, 269, 269, 269, 269, 269, 269, 269, 269, 269, 269, 269, 269, 269, 269, 269, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, 77, 275, 276, 276, 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285 }; static const uint16_t anySuitVal[ 8192 ] = { 0, 0, 1, 0, 2, 1, 2, 0, 3, 3, 4, 1, 5, 2, 3, 0, 4, 6, 7, 4, 8, 5, 6, 1, 9, 7, 8, 2, 9, 3, 4, 9624, 5, 10, 11, 10, 12, 11, 12, 5, 13, 13, 14, 6, 15, 7, 8, 1, 14, 16, 17, 9, 18, 10, 11, 2, 19, 12, 13, 3, 14, 4, 9625, 9625, 6, 15, 16, 20, 17, 21, 22, 15, 18, 23, 24, 16, 25, 17, 18, 6, 19, 26, 27, 19, 28, 20, 21, 7, 29, 22, 23, 8, 24, 9, 10, 9624, 20, 30, 31, 25, 32, 26, 27, 11, 33, 28, 29, 12, 30, 13, 14, 14, 34, 31, 32, 15, 33, 16, 17, 17, 34, 18, 19, 19, 9626, 9626, 9626, 9626, 7, 21, 22, 35, 23, 36, 37, 35, 24, 38, 39, 36, 40, 37, 38, 21, 25, 41, 42, 39, 43, 40, 41, 22, 44, 42, 43, 23, 44, 24, 25, 9624, 26, 45, 46, 45, 47, 46, 47, 26, 48, 48, 49, 27, 50, 28, 29, 29, 49, 51, 52, 30, 53, 31, 32, 32, 54, 33, 34, 34, 35, 35, 9625, 9625, 27, 50, 51, 55, 52, 56, 57, 36, 53, 58, 59, 37, 60, 38, 39, 39, 54, 61, 62, 40, 63, 41, 42, 42, 64, 43, 44, 44, 45, 45, 45, 9624, 55, 65, 66, 46, 67, 47, 48, 48, 68, 49, 50, 50, 51, 51, 51, 51, 69, 52, 53, 53, 54, 54, 54, 54, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 8, 28, 29, 56, 30, 57, 58, 70, 31, 59, 60, 71, 61, 72, 73, 56, 32, 62, 63, 74, 64, 75, 76, 57, 65, 77, 78, 58, 79, 59, 60, 9624, 33, 66, 67, 80, 68, 81, 82, 61, 69, 83, 84, 62, 85, 63, 64, 64, 70, 86, 87, 65, 88, 66, 67, 67, 89, 68, 69, 69, 70, 70, 9625, 9625, 34, 71, 72, 90, 73, 91, 92, 71, 74, 93, 94, 72, 95, 73, 74, 74, 75, 96, 97, 75, 98, 76, 77, 77, 99, 78, 79, 79, 80, 80, 80, 9624, 76, 100, 101, 81, 102, 82, 83, 83, 103, 84, 85, 85, 86, 86, 86, 86, 104, 87, 88, 88, 89, 89, 89, 89, 90, 90, 90, 90, 9626, 9626, 9626, 9626, 35, 77, 78, 105, 79, 106, 107, 91, 80, 108, 109, 92, 110, 93, 94, 94, 81, 111, 112, 95, 113, 96, 97, 97, 114, 98, 99, 99, 100, 100, 100, 9624, 82, 115, 116, 101, 117, 102, 103, 103, 118, 104, 105, 105, 106, 106, 106, 106, 119, 107, 108, 108, 109, 109, 109, 109, 110, 110, 110, 110, 110, 110, 9625, 9625, 83, 120, 121, 111, 122, 112, 113, 113, 123, 114, 115, 115, 116, 116, 116, 116, 124, 117, 118, 118, 119, 119, 119, 119, 120, 120, 120, 120, 120, 120, 120, 9624, 125, 121, 122, 122, 123, 123, 123, 123, 124, 124, 124, 124, 124, 124, 124, 124, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9, 36, 37, 84, 38, 85, 86, 126, 39, 87, 88, 127, 89, 128, 129, 126, 40, 90, 91, 130, 92, 131, 132, 127, 93, 133, 134, 128, 135, 129, 130, 9624, 41, 94, 95, 136, 96, 137, 138, 131, 97, 139, 140, 132, 141, 133, 134, 134, 98, 142, 143, 135, 144, 136, 137, 137, 145, 138, 139, 139, 140, 140, 9625, 9625, 42, 99, 100, 146, 101, 147, 148, 141, 102, 149, 150, 142, 151, 143, 144, 144, 103, 152, 153, 145, 154, 146, 147, 147, 155, 148, 149, 149, 150, 150, 150, 9624, 104, 156, 157, 151, 158, 152, 153, 153, 159, 154, 155, 155, 156, 156, 156, 156, 160, 157, 158, 158, 159, 159, 159, 159, 160, 160, 160, 160, 9626, 9626, 9626, 9626, 43, 105, 106, 161, 107, 162, 163, 161, 108, 164, 165, 162, 166, 163, 164, 164, 109, 167, 168, 165, 169, 166, 167, 167, 170, 168, 169, 169, 170, 170, 170, 9624, 110, 171, 172, 171, 173, 172, 173, 173, 174, 174, 175, 175, 176, 176, 176, 176, 175, 177, 178, 178, 179, 179, 179, 179, 180, 180, 180, 180, 180, 180, 9625, 9625, 111, 176, 177, 181, 178, 182, 183, 183, 179, 184, 185, 185, 186, 186, 186, 186, 180, 187, 188, 188, 189, 189, 189, 189, 190, 190, 190, 190, 190, 190, 190, 9624, 181, 191, 192, 192, 193, 193, 193, 193, 194, 194, 194, 194, 194, 194, 194, 194, 195, 195, 195, 195, 195, 195, 195, 195, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 44, 112, 113, 182, 114, 183, 184, 196, 115, 185, 186, 197, 187, 198, 199, 199, 116, 188, 189, 200, 190, 201, 202, 202, 191, 203, 204, 204, 205, 205, 205, 9624, 117, 192, 193, 206, 194, 207, 208, 208, 195, 209, 210, 210, 211, 211, 211, 211, 196, 212, 213, 213, 214, 214, 214, 214, 215, 215, 215, 215, 215, 215, 9625, 9625, 118, 197, 198, 216, 199, 217, 218, 218, 200, 219, 220, 220, 221, 221, 221, 221, 201, 222, 223, 223, 224, 224, 224, 224, 225, 225, 225, 225, 225, 225, 225, 9624, 202, 226, 227, 227, 228, 228, 228, 228, 229, 229, 229, 229, 229, 229, 229, 229, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 9626, 9626, 9626, 9626, 119, 203, 204, 231, 205, 232, 233, 233, 206, 234, 235, 235, 236, 236, 236, 236, 207, 237, 238, 238, 239, 239, 239, 239, 240, 240, 240, 240, 240, 240, 240, 9624, 208, 241, 242, 242, 243, 243, 243, 243, 244, 244, 244, 244, 244, 244, 244, 244, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 9625, 9625, 209, 246, 247, 247, 248, 248, 248, 248, 249, 249, 249, 249, 249, 249, 249, 249, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 9624, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 10, 45, 46, 120, 47, 121, 122, 210, 48, 123, 124, 211, 125, 212, 213, 252, 49, 126, 127, 214, 128, 215, 216, 253, 129, 217, 218, 254, 219, 255, 256, 9624, 50, 130, 131, 220, 132, 221, 222, 257, 133, 223, 224, 258, 225, 259, 260, 260, 134, 226, 227, 261, 228, 262, 263, 263, 229, 264, 265, 265, 266, 266, 9625, 9625, 51, 135, 136, 230, 137, 231, 232, 267, 138, 233, 234, 268, 235, 269, 270, 270, 139, 236, 237, 271, 238, 272, 273, 273, 239, 274, 275, 275, 276, 276, 276, 9624, 140, 240, 241, 277, 242, 278, 279, 279, 243, 280, 281, 281, 282, 282, 282, 282, 244, 283, 284, 284, 285, 285, 285, 285, 286, 286, 286, 286, 9626, 9626, 9626, 9626, 52, 141, 142, 245, 143, 246, 247, 287, 144, 248, 249, 288, 250, 289, 290, 290, 145, 251, 252, 291, 253, 292, 293, 293, 254, 294, 295, 295, 296, 296, 296, 9624, 146, 255, 256, 297, 257, 298, 299, 299, 258, 300, 301, 301, 302, 302, 302, 302, 259, 303, 304, 304, 305, 305, 305, 305, 306, 306, 306, 306, 306, 306, 9625, 9625, 147, 260, 261, 307, 262, 308, 309, 309, 263, 310, 311, 311, 312, 312, 312, 312, 264, 313, 314, 314, 315, 315, 315, 315, 316, 316, 316, 316, 316, 316, 316, 9624, 265, 317, 318, 318, 319, 319, 319, 319, 320, 320, 320, 320, 320, 320, 320, 320, 321, 321, 321, 321, 321, 321, 321, 321, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 53, 148, 149, 266, 150, 267, 268, 322, 151, 269, 270, 323, 271, 324, 325, 325, 152, 272, 273, 326, 274, 327, 328, 328, 275, 329, 330, 330, 331, 331, 331, 9624, 153, 276, 277, 332, 278, 333, 334, 334, 279, 335, 336, 336, 337, 337, 337, 337, 280, 338, 339, 339, 340, 340, 340, 340, 341, 341, 341, 341, 341, 341, 9625, 9625, 154, 281, 282, 342, 283, 343, 344, 344, 284, 345, 346, 346, 347, 347, 347, 347, 285, 348, 349, 349, 350, 350, 350, 350, 351, 351, 351, 351, 351, 351, 351, 9624, 286, 352, 353, 353, 354, 354, 354, 354, 355, 355, 355, 355, 355, 355, 355, 355, 356, 356, 356, 356, 356, 356, 356, 356, 356, 356, 356, 356, 9626, 9626, 9626, 9626, 155, 287, 288, 357, 289, 358, 359, 359, 290, 360, 361, 361, 362, 362, 362, 362, 291, 363, 364, 364, 365, 365, 365, 365, 366, 366, 366, 366, 366, 366, 366, 9624, 292, 367, 368, 368, 369, 369, 369, 369, 370, 370, 370, 370, 370, 370, 370, 370, 371, 371, 371, 371, 371, 371, 371, 371, 371, 371, 371, 371, 371, 371, 9625, 9625, 293, 372, 373, 373, 374, 374, 374, 374, 375, 375, 375, 375, 375, 375, 375, 375, 376, 376, 376, 376, 376, 376, 376, 376, 376, 376, 376, 376, 376, 376, 376, 9624, 377, 377, 377, 377, 377, 377, 377, 377, 377, 377, 377, 377, 377, 377, 377, 377, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 54, 156, 157, 294, 158, 295, 296, 378, 159, 297, 298, 379, 299, 380, 381, 381, 160, 300, 301, 382, 302, 383, 384, 384, 303, 385, 386, 386, 387, 387, 387, 9624, 161, 304, 305, 388, 306, 389, 390, 390, 307, 391, 392, 392, 393, 393, 393, 393, 308, 394, 395, 395, 396, 396, 396, 396, 397, 397, 397, 397, 397, 397, 9625, 9625, 162, 309, 310, 398, 311, 399, 400, 400, 312, 401, 402, 402, 403, 403, 403, 403, 313, 404, 405, 405, 406, 406, 406, 406, 407, 407, 407, 407, 407, 407, 407, 9624, 314, 408, 409, 409, 410, 410, 410, 410, 411, 411, 411, 411, 411, 411, 411, 411, 412, 412, 412, 412, 412, 412, 412, 412, 412, 412, 412, 412, 9626, 9626, 9626, 9626, 163, 315, 316, 413, 317, 414, 415, 415, 318, 416, 417, 417, 418, 418, 418, 418, 319, 419, 420, 420, 421, 421, 421, 421, 422, 422, 422, 422, 422, 422, 422, 9624, 320, 423, 424, 424, 425, 425, 425, 425, 426, 426, 426, 426, 426, 426, 426, 426, 427, 427, 427, 427, 427, 427, 427, 427, 427, 427, 427, 427, 427, 427, 9625, 9625, 321, 428, 429, 429, 430, 430, 430, 430, 431, 431, 431, 431, 431, 431, 431, 431, 432, 432, 432, 432, 432, 432, 432, 432, 432, 432, 432, 432, 432, 432, 432, 9624, 433, 433, 433, 433, 433, 433, 433, 433, 433, 433, 433, 433, 433, 433, 433, 433, 433, 433, 433, 433, 433, 433, 433, 433, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 164, 322, 323, 434, 324, 435, 436, 436, 325, 437, 438, 438, 439, 439, 439, 439, 326, 440, 441, 441, 442, 442, 442, 442, 443, 443, 443, 443, 443, 443, 443, 9624, 327, 444, 445, 445, 446, 446, 446, 446, 447, 447, 447, 447, 447, 447, 447, 447, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 9625, 9625, 328, 449, 450, 450, 451, 451, 451, 451, 452, 452, 452, 452, 452, 452, 452, 452, 453, 453, 453, 453, 453, 453, 453, 453, 453, 453, 453, 453, 453, 453, 453, 9624, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 454, 9626, 9626, 9626, 9626, 329, 455, 456, 456, 457, 457, 457, 457, 458, 458, 458, 458, 458, 458, 458, 458, 459, 459, 459, 459, 459, 459, 459, 459, 459, 459, 459, 459, 459, 459, 459, 9624, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 460, 9625, 9625, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 11, 55, 56, 165, 57, 166, 167, 330, 58, 168, 169, 331, 170, 332, 333, 462, 59, 171, 172, 334, 173, 335, 336, 463, 174, 337, 338, 464, 339, 465, 466, 9624, 60, 175, 176, 340, 177, 341, 342, 467, 178, 343, 344, 468, 345, 469, 470, 470, 179, 346, 347, 471, 348, 472, 473, 473, 349, 474, 475, 475, 476, 476, 9625, 9625, 61, 180, 181, 350, 182, 351, 352, 477, 183, 353, 354, 478, 355, 479, 480, 480, 184, 356, 357, 481, 358, 482, 483, 483, 359, 484, 485, 485, 486, 486, 486, 9624, 185, 360, 361, 487, 362, 488, 489, 489, 363, 490, 491, 491, 492, 492, 492, 492, 364, 493, 494, 494, 495, 495, 495, 495, 496, 496, 496, 496, 9626, 9626, 9626, 9626, 62, 186, 187, 365, 188, 366, 367, 497, 189, 368, 369, 498, 370, 499, 500, 500, 190, 371, 372, 501, 373, 502, 503, 503, 374, 504, 505, 505, 506, 506, 506, 9624, 191, 375, 376, 507, 377, 508, 509, 509, 378, 510, 511, 511, 512, 512, 512, 512, 379, 513, 514, 514, 515, 515, 515, 515, 516, 516, 516, 516, 516, 516, 9625, 9625, 192, 380, 381, 517, 382, 518, 519, 519, 383, 520, 521, 521, 522, 522, 522, 522, 384, 523, 524, 524, 525, 525, 525, 525, 526, 526, 526, 526, 526, 526, 526, 9624, 385, 527, 528, 528, 529, 529, 529, 529, 530, 530, 530, 530, 530, 530, 530, 530, 531, 531, 531, 531, 531, 531, 531, 531, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 63, 193, 194, 386, 195, 387, 388, 532, 196, 389, 390, 533, 391, 534, 535, 535, 197, 392, 393, 536, 394, 537, 538, 538, 395, 539, 540, 540, 541, 541, 541, 9624, 198, 396, 397, 542, 398, 543, 544, 544, 399, 545, 546, 546, 547, 547, 547, 547, 400, 548, 549, 549, 550, 550, 550, 550, 551, 551, 551, 551, 551, 551, 9625, 9625, 199, 401, 402, 552, 403, 553, 554, 554, 404, 555, 556, 556, 557, 557, 557, 557, 405, 558, 559, 559, 560, 560, 560, 560, 561, 561, 561, 561, 561, 561, 561, 9624, 406, 562, 563, 563, 564, 564, 564, 564, 565, 565, 565, 565, 565, 565, 565, 565, 566, 566, 566, 566, 566, 566, 566, 566, 566, 566, 566, 566, 9626, 9626, 9626, 9626, 200, 407, 408, 567, 409, 568, 569, 569, 410, 570, 571, 571, 572, 572, 572, 572, 411, 573, 574, 574, 575, 575, 575, 575, 576, 576, 576, 576, 576, 576, 576, 9624, 412, 577, 578, 578, 579, 579, 579, 579, 580, 580, 580, 580, 580, 580, 580, 580, 581, 581, 581, 581, 581, 581, 581, 581, 581, 581, 581, 581, 581, 581, 9625, 9625, 413, 582, 583, 583, 584, 584, 584, 584, 585, 585, 585, 585, 585, 585, 585, 585, 586, 586, 586, 586, 586, 586, 586, 586, 586, 586, 586, 586, 586, 586, 586, 9624, 587, 587, 587, 587, 587, 587, 587, 587, 587, 587, 587, 587, 587, 587, 587, 587, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 64, 201, 202, 414, 203, 415, 416, 588, 204, 417, 418, 589, 419, 590, 591, 591, 205, 420, 421, 592, 422, 593, 594, 594, 423, 595, 596, 596, 597, 597, 597, 9624, 206, 424, 425, 598, 426, 599, 600, 600, 427, 601, 602, 602, 603, 603, 603, 603, 428, 604, 605, 605, 606, 606, 606, 606, 607, 607, 607, 607, 607, 607, 9625, 9625, 207, 429, 430, 608, 431, 609, 610, 610, 432, 611, 612, 612, 613, 613, 613, 613, 433, 614, 615, 615, 616, 616, 616, 616, 617, 617, 617, 617, 617, 617, 617, 9624, 434, 618, 619, 619, 620, 620, 620, 620, 621, 621, 621, 621, 621, 621, 621, 621, 622, 622, 622, 622, 622, 622, 622, 622, 622, 622, 622, 622, 9626, 9626, 9626, 9626, 208, 435, 436, 623, 437, 624, 625, 625, 438, 626, 627, 627, 628, 628, 628, 628, 439, 629, 630, 630, 631, 631, 631, 631, 632, 632, 632, 632, 632, 632, 632, 9624, 440, 633, 634, 634, 635, 635, 635, 635, 636, 636, 636, 636, 636, 636, 636, 636, 637, 637, 637, 637, 637, 637, 637, 637, 637, 637, 637, 637, 637, 637, 9625, 9625, 441, 638, 639, 639, 640, 640, 640, 640, 641, 641, 641, 641, 641, 641, 641, 641, 642, 642, 642, 642, 642, 642, 642, 642, 642, 642, 642, 642, 642, 642, 642, 9624, 643, 643, 643, 643, 643, 643, 643, 643, 643, 643, 643, 643, 643, 643, 643, 643, 643, 643, 643, 643, 643, 643, 643, 643, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 209, 442, 443, 644, 444, 645, 646, 646, 445, 647, 648, 648, 649, 649, 649, 649, 446, 650, 651, 651, 652, 652, 652, 652, 653, 653, 653, 653, 653, 653, 653, 9624, 447, 654, 655, 655, 656, 656, 656, 656, 657, 657, 657, 657, 657, 657, 657, 657, 658, 658, 658, 658, 658, 658, 658, 658, 658, 658, 658, 658, 658, 658, 9625, 9625, 448, 659, 660, 660, 661, 661, 661, 661, 662, 662, 662, 662, 662, 662, 662, 662, 663, 663, 663, 663, 663, 663, 663, 663, 663, 663, 663, 663, 663, 663, 663, 9624, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 664, 9626, 9626, 9626, 9626, 449, 665, 666, 666, 667, 667, 667, 667, 668, 668, 668, 668, 668, 668, 668, 668, 669, 669, 669, 669, 669, 669, 669, 669, 669, 669, 669, 669, 669, 669, 669, 9624, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 9625, 9625, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, 9624, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 65, 210, 211, 450, 212, 451, 452, 672, 213, 453, 454, 673, 455, 674, 675, 675, 214, 456, 457, 676, 458, 677, 678, 678, 459, 679, 680, 680, 681, 681, 681, 9624, 215, 460, 461, 682, 462, 683, 684, 684, 463, 685, 686, 686, 687, 687, 687, 687, 464, 688, 689, 689, 690, 690, 690, 690, 691, 691, 691, 691, 691, 691, 9625, 9625, 216, 465, 466, 692, 467, 693, 694, 694, 468, 695, 696, 696, 697, 697, 697, 697, 469, 698, 699, 699, 700, 700, 700, 700, 701, 701, 701, 701, 701, 701, 701, 9624, 470, 702, 703, 703, 704, 704, 704, 704, 705, 705, 705, 705, 705, 705, 705, 705, 706, 706, 706, 706, 706, 706, 706, 706, 706, 706, 706, 706, 9626, 9626, 9626, 9626, 217, 471, 472, 707, 473, 708, 709, 709, 474, 710, 711, 711, 712, 712, 712, 712, 475, 713, 714, 714, 715, 715, 715, 715, 716, 716, 716, 716, 716, 716, 716, 9624, 476, 717, 718, 718, 719, 719, 719, 719, 720, 720, 720, 720, 720, 720, 720, 720, 721, 721, 721, 721, 721, 721, 721, 721, 721, 721, 721, 721, 721, 721, 9625, 9625, 477, 722, 723, 723, 724, 724, 724, 724, 725, 725, 725, 725, 725, 725, 725, 725, 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, 9624, 727, 727, 727, 727, 727, 727, 727, 727, 727, 727, 727, 727, 727, 727, 727, 727, 727, 727, 727, 727, 727, 727, 727, 727, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 218, 478, 479, 728, 480, 729, 730, 730, 481, 731, 732, 732, 733, 733, 733, 733, 482, 734, 735, 735, 736, 736, 736, 736, 737, 737, 737, 737, 737, 737, 737, 9624, 483, 738, 739, 739, 740, 740, 740, 740, 741, 741, 741, 741, 741, 741, 741, 741, 742, 742, 742, 742, 742, 742, 742, 742, 742, 742, 742, 742, 742, 742, 9625, 9625, 484, 743, 744, 744, 745, 745, 745, 745, 746, 746, 746, 746, 746, 746, 746, 746, 747, 747, 747, 747, 747, 747, 747, 747, 747, 747, 747, 747, 747, 747, 747, 9624, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 748, 9626, 9626, 9626, 9626, 485, 749, 750, 750, 751, 751, 751, 751, 752, 752, 752, 752, 752, 752, 752, 752, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 9624, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 754, 9625, 9625, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 9624, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 219, 486, 487, 756, 488, 757, 758, 758, 489, 759, 760, 760, 761, 761, 761, 761, 490, 762, 763, 763, 764, 764, 764, 764, 765, 765, 765, 765, 765, 765, 765, 9624, 491, 766, 767, 767, 768, 768, 768, 768, 769, 769, 769, 769, 769, 769, 769, 769, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 9625, 9625, 492, 771, 772, 772, 773, 773, 773, 773, 774, 774, 774, 774, 774, 774, 774, 774, 775, 775, 775, 775, 775, 775, 775, 775, 775, 775, 775, 775, 775, 775, 775, 9624, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 776, 9626, 9626, 9626, 9626, 493, 777, 778, 778, 779, 779, 779, 779, 780, 780, 780, 780, 780, 780, 780, 780, 781, 781, 781, 781, 781, 781, 781, 781, 781, 781, 781, 781, 781, 781, 781, 9624, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 782, 9625, 9625, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 9624, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 783, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 494, 784, 785, 785, 786, 786, 786, 786, 787, 787, 787, 787, 787, 787, 787, 787, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 9624, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 789, 9625, 9625, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 9624, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 790, 9626, 9626, 9626, 9626, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 9631, 12, 66, 67, 220, 68, 221, 222, 495, 69, 223, 224, 496, 225, 497, 498, 9623, 70, 226, 227, 499, 228, 500, 501, 793, 229, 502, 503, 794, 504, 795, 796, 9624, 71, 230, 231, 505, 232, 506, 507, 797, 233, 508, 509, 798, 510, 799, 800, 9623, 234, 511, 512, 801, 513, 802, 803, 803, 514, 804, 805, 805, 806, 806, 9625, 9625, 72, 235, 236, 515, 237, 516, 517, 807, 238, 518, 519, 808, 520, 809, 810, 9623, 239, 521, 522, 811, 523, 812, 813, 813, 524, 814, 815, 815, 816, 816, 816, 9624, 240, 525, 526, 817, 527, 818, 819, 819, 528, 820, 821, 821, 822, 822, 822, 9623, 529, 823, 824, 824, 825, 825, 825, 825, 826, 826, 826, 826, 9626, 9626, 9626, 9626, 73, 241, 242, 530, 243, 531, 532, 827, 244, 533, 534, 828, 535, 829, 830, 9623, 245, 536, 537, 831, 538, 832, 833, 833, 539, 834, 835, 835, 836, 836, 836, 9624, 246, 540, 541, 837, 542, 838, 839, 839, 543, 840, 841, 841, 842, 842, 842, 9623, 544, 843, 844, 844, 845, 845, 845, 845, 846, 846, 846, 846, 846, 846, 9625, 9625, 247, 545, 546, 847, 547, 848, 849, 849, 548, 850, 851, 851, 852, 852, 852, 9623, 549, 853, 854, 854, 855, 855, 855, 855, 856, 856, 856, 856, 856, 856, 856, 9624, 550, 857, 858, 858, 859, 859, 859, 859, 860, 860, 860, 860, 860, 860, 860, 9623, 861, 861, 861, 861, 861, 861, 861, 861, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 74, 248, 249, 551, 250, 552, 553, 862, 251, 554, 555, 863, 556, 864, 865, 9623, 252, 557, 558, 866, 559, 867, 868, 868, 560, 869, 870, 870, 871, 871, 871, 9624, 253, 561, 562, 872, 563, 873, 874, 874, 564, 875, 876, 876, 877, 877, 877, 9623, 565, 878, 879, 879, 880, 880, 880, 880, 881, 881, 881, 881, 881, 881, 9625, 9625, 254, 566, 567, 882, 568, 883, 884, 884, 569, 885, 886, 886, 887, 887, 887, 9623, 570, 888, 889, 889, 890, 890, 890, 890, 891, 891, 891, 891, 891, 891, 891, 9624, 571, 892, 893, 893, 894, 894, 894, 894, 895, 895, 895, 895, 895, 895, 895, 9623, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 9626, 9626, 9626, 9626, 255, 572, 573, 897, 574, 898, 899, 899, 575, 900, 901, 901, 902, 902, 902, 9623, 576, 903, 904, 904, 905, 905, 905, 905, 906, 906, 906, 906, 906, 906, 906, 9624, 577, 907, 908, 908, 909, 909, 909, 909, 910, 910, 910, 910, 910, 910, 910, 9623, 911, 911, 911, 911, 911, 911, 911, 911, 911, 911, 911, 911, 911, 911, 9625, 9625, 578, 912, 913, 913, 914, 914, 914, 914, 915, 915, 915, 915, 915, 915, 915, 9623, 916, 916, 916, 916, 916, 916, 916, 916, 916, 916, 916, 916, 916, 916, 916, 9624, 917, 917, 917, 917, 917, 917, 917, 917, 917, 917, 917, 917, 917, 917, 917, 9623, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 75, 256, 257, 579, 258, 580, 581, 918, 259, 582, 583, 919, 584, 920, 921, 9623, 260, 585, 586, 922, 587, 923, 924, 924, 588, 925, 926, 926, 927, 927, 927, 9624, 261, 589, 590, 928, 591, 929, 930, 930, 592, 931, 932, 932, 933, 933, 933, 9623, 593, 934, 935, 935, 936, 936, 936, 936, 937, 937, 937, 937, 937, 937, 9625, 9625, 262, 594, 595, 938, 596, 939, 940, 940, 597, 941, 942, 942, 943, 943, 943, 9623, 598, 944, 945, 945, 946, 946, 946, 946, 947, 947, 947, 947, 947, 947, 947, 9624, 599, 948, 949, 949, 950, 950, 950, 950, 951, 951, 951, 951, 951, 951, 951, 9623, 952, 952, 952, 952, 952, 952, 952, 952, 952, 952, 952, 952, 9626, 9626, 9626, 9626, 263, 600, 601, 953, 602, 954, 955, 955, 603, 956, 957, 957, 958, 958, 958, 9623, 604, 959, 960, 960, 961, 961, 961, 961, 962, 962, 962, 962, 962, 962, 962, 9624, 605, 963, 964, 964, 965, 965, 965, 965, 966, 966, 966, 966, 966, 966, 966, 9623, 967, 967, 967, 967, 967, 967, 967, 967, 967, 967, 967, 967, 967, 967, 9625, 9625, 606, 968, 969, 969, 970, 970, 970, 970, 971, 971, 971, 971, 971, 971, 971, 9623, 972, 972, 972, 972, 972, 972, 972, 972, 972, 972, 972, 972, 972, 972, 972, 9624, 973, 973, 973, 973, 973, 973, 973, 973, 973, 973, 973, 973, 973, 973, 973, 9623, 973, 973, 973, 973, 973, 973, 973, 973, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 264, 607, 608, 974, 609, 975, 976, 976, 610, 977, 978, 978, 979, 979, 979, 9623, 611, 980, 981, 981, 982, 982, 982, 982, 983, 983, 983, 983, 983, 983, 983, 9624, 612, 984, 985, 985, 986, 986, 986, 986, 987, 987, 987, 987, 987, 987, 987, 9623, 988, 988, 988, 988, 988, 988, 988, 988, 988, 988, 988, 988, 988, 988, 9625, 9625, 613, 989, 990, 990, 991, 991, 991, 991, 992, 992, 992, 992, 992, 992, 992, 9623, 993, 993, 993, 993, 993, 993, 993, 993, 993, 993, 993, 993, 993, 993, 993, 9624, 994, 994, 994, 994, 994, 994, 994, 994, 994, 994, 994, 994, 994, 994, 994, 9623, 994, 994, 994, 994, 994, 994, 994, 994, 994, 994, 994, 994, 9626, 9626, 9626, 9626, 614, 995, 996, 996, 997, 997, 997, 997, 998, 998, 998, 998, 998, 998, 998, 9623, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 999, 9624, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 9623, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 9625, 9625, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 9623, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 9624, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 76, 265, 266, 615, 267, 616, 617, 1002, 268, 618, 619, 1003, 620, 1004, 1005, 9623, 269, 621, 622, 1006, 623, 1007, 1008, 1008, 624, 1009, 1010, 1010, 1011, 1011, 1011, 9624, 270, 625, 626, 1012, 627, 1013, 1014, 1014, 628, 1015, 1016, 1016, 1017, 1017, 1017, 9623, 629, 1018, 1019, 1019, 1020, 1020, 1020, 1020, 1021, 1021, 1021, 1021, 1021, 1021, 9625, 9625, 271, 630, 631, 1022, 632, 1023, 1024, 1024, 633, 1025, 1026, 1026, 1027, 1027, 1027, 9623, 634, 1028, 1029, 1029, 1030, 1030, 1030, 1030, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 9624, 635, 1032, 1033, 1033, 1034, 1034, 1034, 1034, 1035, 1035, 1035, 1035, 1035, 1035, 1035, 9623, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1036, 9626, 9626, 9626, 9626, 272, 636, 637, 1037, 638, 1038, 1039, 1039, 639, 1040, 1041, 1041, 1042, 1042, 1042, 9623, 640, 1043, 1044, 1044, 1045, 1045, 1045, 1045, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 9624, 641, 1047, 1048, 1048, 1049, 1049, 1049, 1049, 1050, 1050, 1050, 1050, 1050, 1050, 1050, 9623, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 9625, 9625, 642, 1052, 1053, 1053, 1054, 1054, 1054, 1054, 1055, 1055, 1055, 1055, 1055, 1055, 1055, 9623, 1056, 1056, 1056, 1056, 1056, 1056, 1056, 1056, 1056, 1056, 1056, 1056, 1056, 1056, 1056, 9624, 1057, 1057, 1057, 1057, 1057, 1057, 1057, 1057, 1057, 1057, 1057, 1057, 1057, 1057, 1057, 9623, 1057, 1057, 1057, 1057, 1057, 1057, 1057, 1057, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 273, 643, 644, 1058, 645, 1059, 1060, 1060, 646, 1061, 1062, 1062, 1063, 1063, 1063, 9623, 647, 1064, 1065, 1065, 1066, 1066, 1066, 1066, 1067, 1067, 1067, 1067, 1067, 1067, 1067, 9624, 648, 1068, 1069, 1069, 1070, 1070, 1070, 1070, 1071, 1071, 1071, 1071, 1071, 1071, 1071, 9623, 1072, 1072, 1072, 1072, 1072, 1072, 1072, 1072, 1072, 1072, 1072, 1072, 1072, 1072, 9625, 9625, 649, 1073, 1074, 1074, 1075, 1075, 1075, 1075, 1076, 1076, 1076, 1076, 1076, 1076, 1076, 9623, 1077, 1077, 1077, 1077, 1077, 1077, 1077, 1077, 1077, 1077, 1077, 1077, 1077, 1077, 1077, 9624, 1078, 1078, 1078, 1078, 1078, 1078, 1078, 1078, 1078, 1078, 1078, 1078, 1078, 1078, 1078, 9623, 1078, 1078, 1078, 1078, 1078, 1078, 1078, 1078, 1078, 1078, 1078, 1078, 9626, 9626, 9626, 9626, 650, 1079, 1080, 1080, 1081, 1081, 1081, 1081, 1082, 1082, 1082, 1082, 1082, 1082, 1082, 9623, 1083, 1083, 1083, 1083, 1083, 1083, 1083, 1083, 1083, 1083, 1083, 1083, 1083, 1083, 1083, 9624, 1084, 1084, 1084, 1084, 1084, 1084, 1084, 1084, 1084, 1084, 1084, 1084, 1084, 1084, 1084, 9623, 1084, 1084, 1084, 1084, 1084, 1084, 1084, 1084, 1084, 1084, 1084, 1084, 1084, 1084, 9625, 9625, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 9623, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 9624, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 9623, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 274, 651, 652, 1086, 653, 1087, 1088, 1088, 654, 1089, 1090, 1090, 1091, 1091, 1091, 9623, 655, 1092, 1093, 1093, 1094, 1094, 1094, 1094, 1095, 1095, 1095, 1095, 1095, 1095, 1095, 9624, 656, 1096, 1097, 1097, 1098, 1098, 1098, 1098, 1099, 1099, 1099, 1099, 1099, 1099, 1099, 9623, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 1100, 9625, 9625, 657, 1101, 1102, 1102, 1103, 1103, 1103, 1103, 1104, 1104, 1104, 1104, 1104, 1104, 1104, 9623, 1105, 1105, 1105, 1105, 1105, 1105, 1105, 1105, 1105, 1105, 1105, 1105, 1105, 1105, 1105, 9624, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 9623, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 9626, 9626, 9626, 9626, 658, 1107, 1108, 1108, 1109, 1109, 1109, 1109, 1110, 1110, 1110, 1110, 1110, 1110, 1110, 9623, 1111, 1111, 1111, 1111, 1111, 1111, 1111, 1111, 1111, 1111, 1111, 1111, 1111, 1111, 1111, 9624, 1112, 1112, 1112, 1112, 1112, 1112, 1112, 1112, 1112, 1112, 1112, 1112, 1112, 1112, 1112, 9623, 1112, 1112, 1112, 1112, 1112, 1112, 1112, 1112, 1112, 1112, 1112, 1112, 1112, 1112, 9625, 9625, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 9623, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 9624, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 9623, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 1113, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 659, 1114, 1115, 1115, 1116, 1116, 1116, 1116, 1117, 1117, 1117, 1117, 1117, 1117, 1117, 9623, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 9624, 1119, 1119, 1119, 1119, 1119, 1119, 1119, 1119, 1119, 1119, 1119, 1119, 1119, 1119, 1119, 9623, 1119, 1119, 1119, 1119, 1119, 1119, 1119, 1119, 1119, 1119, 1119, 1119, 1119, 1119, 9625, 9625, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 9623, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 9624, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 9623, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 1120, 9626, 9626, 9626, 9626, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 9623, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 9624, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 9623, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 1121, 9625, 9625, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 9630, 77, 275, 276, 660, 277, 661, 662, 1122, 278, 663, 664, 1123, 665, 1124, 1125, 9623, 279, 666, 667, 1126, 668, 1127, 1128, 1128, 669, 1129, 1130, 1130, 1131, 1131, 1131, 9624, 280, 670, 671, 1132, 672, 1133, 1134, 1134, 673, 1135, 1136, 1136, 1137, 1137, 1137, 9623, 674, 1138, 1139, 1139, 1140, 1140, 1140, 1140, 1141, 1141, 1141, 1141, 1141, 1141, 9625, 9625, 281, 675, 676, 1142, 677, 1143, 1144, 1144, 678, 1145, 1146, 1146, 1147, 1147, 1147, 9623, 679, 1148, 1149, 1149, 1150, 1150, 1150, 1150, 1151, 1151, 1151, 1151, 1151, 1151, 1151, 9624, 680, 1152, 1153, 1153, 1154, 1154, 1154, 1154, 1155, 1155, 1155, 1155, 1155, 1155, 1155, 9623, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 9626, 9626, 9626, 9626, 282, 681, 682, 1157, 683, 1158, 1159, 1159, 684, 1160, 1161, 1161, 1162, 1162, 1162, 9623, 685, 1163, 1164, 1164, 1165, 1165, 1165, 1165, 1166, 1166, 1166, 1166, 1166, 1166, 1166, 9624, 686, 1167, 1168, 1168, 1169, 1169, 1169, 1169, 1170, 1170, 1170, 1170, 1170, 1170, 1170, 9623, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 9625, 9625, 687, 1172, 1173, 1173, 1174, 1174, 1174, 1174, 1175, 1175, 1175, 1175, 1175, 1175, 1175, 9623, 1176, 1176, 1176, 1176, 1176, 1176, 1176, 1176, 1176, 1176, 1176, 1176, 1176, 1176, 1176, 9624, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 9623, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 283, 688, 689, 1178, 690, 1179, 1180, 1180, 691, 1181, 1182, 1182, 1183, 1183, 1183, 9623, 692, 1184, 1185, 1185, 1186, 1186, 1186, 1186, 1187, 1187, 1187, 1187, 1187, 1187, 1187, 9624, 693, 1188, 1189, 1189, 1190, 1190, 1190, 1190, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 9623, 1192, 1192, 1192, 1192, 1192, 1192, 1192, 1192, 1192, 1192, 1192, 1192, 1192, 1192, 9625, 9625, 694, 1193, 1194, 1194, 1195, 1195, 1195, 1195, 1196, 1196, 1196, 1196, 1196, 1196, 1196, 9623, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 9624, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 9623, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 9626, 9626, 9626, 9626, 695, 1199, 1200, 1200, 1201, 1201, 1201, 1201, 1202, 1202, 1202, 1202, 1202, 1202, 1202, 9623, 1203, 1203, 1203, 1203, 1203, 1203, 1203, 1203, 1203, 1203, 1203, 1203, 1203, 1203, 1203, 9624, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 9623, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 1204, 9625, 9625, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 9623, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 9624, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 1205, 9623, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 284, 696, 697, 1206, 698, 1207, 1208, 1208, 699, 1209, 1210, 1210, 1211, 1211, 1211, 9623, 700, 1212, 1213, 1213, 1214, 1214, 1214, 1214, 1215, 1215, 1215, 1215, 1215, 1215, 1215, 9624, 701, 1216, 1217, 1217, 1218, 1218, 1218, 1218, 1219, 1219, 1219, 1219, 1219, 1219, 1219, 9623, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 9625, 9625, 702, 1221, 1222, 1222, 1223, 1223, 1223, 1223, 1224, 1224, 1224, 1224, 1224, 1224, 1224, 9623, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 9624, 1226, 1226, 1226, 1226, 1226, 1226, 1226, 1226, 1226, 1226, 1226, 1226, 1226, 1226, 1226, 9623, 1226, 1226, 1226, 1226, 1226, 1226, 1226, 1226, 1226, 1226, 1226, 1226, 9626, 9626, 9626, 9626, 703, 1227, 1228, 1228, 1229, 1229, 1229, 1229, 1230, 1230, 1230, 1230, 1230, 1230, 1230, 9623, 1231, 1231, 1231, 1231, 1231, 1231, 1231, 1231, 1231, 1231, 1231, 1231, 1231, 1231, 1231, 9624, 1232, 1232, 1232, 1232, 1232, 1232, 1232, 1232, 1232, 1232, 1232, 1232, 1232, 1232, 1232, 9623, 1232, 1232, 1232, 1232, 1232, 1232, 1232, 1232, 1232, 1232, 1232, 1232, 1232, 1232, 9625, 9625, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 9623, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 9624, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 9623, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 1233, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 704, 1234, 1235, 1235, 1236, 1236, 1236, 1236, 1237, 1237, 1237, 1237, 1237, 1237, 1237, 9623, 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238, 1238, 9624, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 9623, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 1239, 9625, 9625, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 9623, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 9624, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 9623, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 1240, 9626, 9626, 9626, 9626, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 9623, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 9624, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 9623, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 9625, 9625, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 9623, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 1241, 9624, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 9629, 285, 705, 706, 1242, 707, 1243, 1244, 1244, 708, 1245, 1246, 1246, 1247, 1247, 1247, 9623, 709, 1248, 1249, 1249, 1250, 1250, 1250, 1250, 1251, 1251, 1251, 1251, 1251, 1251, 1251, 9624, 710, 1252, 1253, 1253, 1254, 1254, 1254, 1254, 1255, 1255, 1255, 1255, 1255, 1255, 1255, 9623, 1256, 1256, 1256, 1256, 1256, 1256, 1256, 1256, 1256, 1256, 1256, 1256, 1256, 1256, 9625, 9625, 711, 1257, 1258, 1258, 1259, 1259, 1259, 1259, 1260, 1260, 1260, 1260, 1260, 1260, 1260, 9623, 1261, 1261, 1261, 1261, 1261, 1261, 1261, 1261, 1261, 1261, 1261, 1261, 1261, 1261, 1261, 9624, 1262, 1262, 1262, 1262, 1262, 1262, 1262, 1262, 1262, 1262, 1262, 1262, 1262, 1262, 1262, 9623, 1262, 1262, 1262, 1262, 1262, 1262, 1262, 1262, 1262, 1262, 1262, 1262, 9626, 9626, 9626, 9626, 712, 1263, 1264, 1264, 1265, 1265, 1265, 1265, 1266, 1266, 1266, 1266, 1266, 1266, 1266, 9623, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 9624, 1268, 1268, 1268, 1268, 1268, 1268, 1268, 1268, 1268, 1268, 1268, 1268, 1268, 1268, 1268, 9623, 1268, 1268, 1268, 1268, 1268, 1268, 1268, 1268, 1268, 1268, 1268, 1268, 1268, 1268, 9625, 9625, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 9623, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 9624, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 9623, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 1269, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 713, 1270, 1271, 1271, 1272, 1272, 1272, 1272, 1273, 1273, 1273, 1273, 1273, 1273, 1273, 9623, 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, 1274, 9624, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 9623, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 9625, 9625, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 9623, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 9624, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 9623, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 1276, 9626, 9626, 9626, 9626, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 9623, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 9624, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 9623, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 9625, 9625, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 9623, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 9624, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 1277, 9623, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 9628, 714, 1278, 1279, 1279, 1280, 1280, 1280, 1280, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 9623, 1282, 1282, 1282, 1282, 1282, 1282, 1282, 1282, 1282, 1282, 1282, 1282, 1282, 1282, 1282, 9624, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 9623, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 9625, 9625, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 9623, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 9624, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 9623, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 9626, 9626, 9626, 9626, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 9623, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 9624, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 9623, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 9625, 9625, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 9623, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 9624, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 9623, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 9627, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632, 9632 }; static const uint8_t topBit[ 8192 ] = { 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 }; static const uint8_t tripsOtherVal[ 8192 ] = { 0, 0, 1, 0, 2, 1, 2, 2, 3, 3, 4, 4, 5, 5, 5, 5, 4, 6, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 5, 10, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 6, 15, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 7, 21, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 8, 28, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 9, 36, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39, 39, 39, 39, 39, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 10, 45, 46, 46, 47, 47, 47, 47, 48, 48, 48, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 11, 55, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 58, 58, 58, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 12, 66, 67, 67, 68, 68, 68, 68, 69, 69, 69, 69, 69, 69, 69, 69, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77 }; static const uint16_t quadsVal[ 13 ] = { 11934, 11947, 11960, 11973, 11986, 11999, 12012, 12025, 12038, 12051, 12064, 12077, 12090 }; static const uint16_t tripsVal[ 13 ] = { 8606, 8684, 8762, 8840, 8918, 8996, 9074, 9152, 9230, 9308, 9386, 9464, 9542 }; static const uint16_t fullHouseOtherVal = HANDCLASS_FULL_HOUSE - HANDCLASS_TRIPS; static const uint16_t pairsVal[ 13 ] = { 1287, 1573, 1859, 2145, 2431, 2717, 3003, 3289, 3575, 3861, 4147, 4433, 4719 }; static const uint16_t twoPairOtherVal[ 13 ] = { 3718, 3731, 3744, 3757, 3770, 3783, 3796, 3809, 3822, 3835, 3848, 3861, 3874 }; static int rankCardset( const Cardset cards ) { int postponed, r; Cardset sets; postponed = oneSuitVal[ cards.bySuit[ 0 ] ]; if( oneSuitVal[ cards.bySuit[ 1 ] ] > postponed ) { postponed = oneSuitVal[ cards.bySuit[ 1 ] ]; } if( oneSuitVal[ cards.bySuit[ 2 ] ] > postponed ) { postponed = oneSuitVal[ cards.bySuit[ 2 ] ]; } if( oneSuitVal[ cards.bySuit[ 3 ] ] > postponed ) { postponed = oneSuitVal[ cards.bySuit[ 3 ] ]; } if( postponed >= HANDCLASS_STRAIGHT_FLUSH ) { /* straight flush */ return postponed; } sets.bySuit[ 0 ] = cards.bySuit[ 0 ] | cards.bySuit[ 1 ]; sets.bySuit[ 1 ] = cards.bySuit[ 0 ] & cards.bySuit[ 1 ]; sets.bySuit[ 2 ] = sets.bySuit[ 1 ] & cards.bySuit[ 2 ]; sets.bySuit[ 1 ] |= sets.bySuit[ 0 ] & cards.bySuit[ 2 ]; sets.bySuit[ 0 ] |= cards.bySuit[ 2 ]; sets.bySuit[ 3 ] = sets.bySuit[ 2 ] & cards.bySuit[ 3 ]; sets.bySuit[ 2 ] |= sets.bySuit[ 1 ] & cards.bySuit[ 3 ]; sets.bySuit[ 1 ] |= sets.bySuit[ 0 ] & cards.bySuit[ 3 ]; sets.bySuit[ 0 ] |= cards.bySuit[ 3 ]; if( sets.bySuit[ 3 ] ) { /* quads */ r = topBit[ sets.bySuit[ 3 ] ]; return quadsVal[ r ] + topBit[ sets.bySuit[ 0 ] ^ ( 1 << r ) ]; } if( sets.bySuit[ 2 ] ) { /* trips or full house */ r = topBit[ sets.bySuit[ 2 ] ]; sets.bySuit[ 1 ] ^= ( 1 << r ); if( sets.bySuit[ 1 ] ) { /* full house */ return tripsVal[ r ] + fullHouseOtherVal + topBit[ sets.bySuit[ 1 ] ]; } if( postponed ) { /* flush */ return postponed; } postponed = anySuitVal[ sets.bySuit[ 0 ] ]; if( postponed >= HANDCLASS_STRAIGHT ) { /* straight */ return postponed; } /* trips */ sets.bySuit[ 0 ] ^= ( 1 << r ); return tripsVal[ r ] + tripsOtherVal[ sets.bySuit[ 0 ] ]; } else { if( postponed ) { /* flush */ return postponed; } postponed = anySuitVal[ sets.bySuit[ 0 ] ]; if( postponed >= HANDCLASS_STRAIGHT ) { /* straight */ return postponed; } } if( sets.bySuit[ 1 ] ) { /* pair or two pair */ r = topBit[ sets.bySuit[ 1 ] ]; sets.bySuit[ 0 ] ^= ( 1 << r ); sets.bySuit[ 1 ] ^= ( 1 << r ); if( sets.bySuit[ 1 ] ) { /* two pair */ sets.bySuit[ 0 ] ^= ( 1 << topBit[ sets.bySuit[ 1 ] ] ); return pairsVal[ r ] + twoPairOtherVal[ topBit[ sets.bySuit[ 1 ] ] ] + topBit[ sets.bySuit[ 0 ] ]; } return pairsVal[ r ] + pairOtherVal[ sets.bySuit[ 0 ] ]; } return postponed; } static Cardset emptyCardset() { Cardset c; c.cards = 0; return c; } static void addCardToCardset( Cardset *c, int suit, int rank ) { c->cards |= (uint64_t)1 << ( ( suit << 4 ) + rank ); } ================================================ FILE: ACPCServer/example_player.c ================================================ /* Copyright (C) 2011 by the Computer Poker Research Group, University of Alberta */ #include #include #include #include #include #include #include #include #include #include #include #include "game.h" #include "rng.h" #include "net.h" int main( int argc, char **argv ) { int sock, len, r, a; int32_t min, max; uint16_t port; double p; Game *game; MatchState state; Action action; FILE *file, *toServer, *fromServer; struct timeval tv; double probs[ NUM_ACTION_TYPES ]; double actionProbs[ NUM_ACTION_TYPES ]; rng_state_t rng; char line[ MAX_LINE_LEN ]; /* we make some assumptions about the actions - check them here */ assert( NUM_ACTION_TYPES == 3 ); if( argc < 4 ) { fprintf( stderr, "usage: player game server port\n" ); exit( EXIT_FAILURE ); } /* Define the probabilities of actions for the player */ probs[ a_fold ] = 0.06; probs[ a_call ] = ( 1.0 - probs[ a_fold ] ) * 0.5; probs[ a_raise ] = ( 1.0 - probs[ a_fold ] ) * 0.5; /* Initialize the player's random number state using time */ gettimeofday( &tv, NULL ); init_genrand( &rng, tv.tv_usec ); /* get the game */ file = fopen( argv[ 1 ], "r" ); if( file == NULL ) { fprintf( stderr, "ERROR: could not open game %s\n", argv[ 1 ] ); exit( EXIT_FAILURE ); } game = readGame( file ); if( game == NULL ) { fprintf( stderr, "ERROR: could not read game %s\n", argv[ 1 ] ); exit( EXIT_FAILURE ); } fclose( file ); /* connect to the dealer */ if( sscanf( argv[ 3 ], "%"SCNu16, &port ) < 1 ) { fprintf( stderr, "ERROR: invalid port %s\n", argv[ 3 ] ); exit( EXIT_FAILURE ); } sock = connectTo( argv[ 2 ], port ); if( sock < 0 ) { exit( EXIT_FAILURE ); } toServer = fdopen( sock, "w" ); fromServer = fdopen( sock, "r" ); if( toServer == NULL || fromServer == NULL ) { fprintf( stderr, "ERROR: could not get socket streams\n" ); exit( EXIT_FAILURE ); } /* send version string to dealer */ if( fprintf( toServer, "VERSION:%"PRIu32".%"PRIu32".%"PRIu32"\n", VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION ) != 14 ) { fprintf( stderr, "ERROR: could not get send version to server\n" ); exit( EXIT_FAILURE ); } fflush( toServer ); /* play the game! */ while( fgets( line, MAX_LINE_LEN, fromServer ) ) { /* ignore comments */ if( line[ 0 ] == '#' || line[ 0 ] == ';' ) { continue; } len = readMatchState( line, game, &state ); if( len < 0 ) { fprintf( stderr, "ERROR: could not read state %s", line ); exit( EXIT_FAILURE ); } if( stateFinished( &state.state ) ) { /* ignore the game over message */ continue; } if( currentPlayer( game, &state.state ) != state.viewingPlayer ) { /* we're not acting */ continue; } /* add a colon (guaranteed to fit because we read a new-line in fgets) */ line[ len ] = ':'; ++len; /* build the set of valid actions */ p = 0; for( a = 0; a < NUM_ACTION_TYPES; ++a ) { actionProbs[ a ] = 0.0; } /* consider fold */ action.type = a_fold; action.size = 0; if( isValidAction( game, &state.state, 0, &action ) ) { actionProbs[ a_fold ] = probs[ a_fold ]; p += probs[ a_fold ]; } /* consider call */ action.type = a_call; action.size = 0; actionProbs[ a_call ] = probs[ a_call ]; p += probs[ a_call ]; /* consider raise */ if( raiseIsValid( game, &state.state, &min, &max ) ) { actionProbs[ a_raise ] = probs[ a_raise ]; p += probs[ a_raise ]; } /* normalise the probabilities */ assert( p > 0.0 ); for( a = 0; a < NUM_ACTION_TYPES; ++a ) { actionProbs[ a ] /= p; } /* choose one of the valid actions at random */ p = genrand_real2( &rng ); for( a = 0; a < NUM_ACTION_TYPES - 1; ++a ) { if( p <= actionProbs[ a ] ) { break; } p -= actionProbs[ a ]; } action.type = (enum ActionType)a; if( a == a_raise ) { action.size = min + genrand_int32( &rng ) % ( max - min + 1 ); } /* do the action! */ assert( isValidAction( game, &state.state, 0, &action ) ); r = printAction( game, &action, MAX_LINE_LEN - len - 2, &line[ len ] ); if( r < 0 ) { fprintf( stderr, "ERROR: line too long after printing action\n" ); exit( EXIT_FAILURE ); } len += r; line[ len ] = '\r'; ++len; line[ len ] = '\n'; ++len; if( fwrite( line, 1, len, toServer ) != len ) { fprintf( stderr, "ERROR: could not get send response to server\n" ); exit( EXIT_FAILURE ); } fflush( toServer ); } return EXIT_SUCCESS; } ================================================ FILE: ACPCServer/example_player.kuhn.limit.3p.sh ================================================ #!/bin/bash ./example_player kuhn.limit.3p.game $1 $2 ================================================ FILE: ACPCServer/example_player.limit.2p.sh ================================================ #!/bin/bash ./example_player holdem.limit.2p.reverse_blinds.game $1 $2 ================================================ FILE: ACPCServer/example_player.limit.3p.sh ================================================ #!/bin/bash ./example_player holdem.limit.3p.game $1 $2 ================================================ FILE: ACPCServer/example_player.nolimit.2p.sh ================================================ #!/bin/bash ./example_player holdem.nolimit.2p.reverse_blinds.game $1 $2 ================================================ FILE: ACPCServer/example_player.nolimit.3p.sh ================================================ #!/bin/bash ./example_player holdem.nolimit.3p.game $1 $2 ================================================ FILE: ACPCServer/game.c ================================================ /* Copyright (C) 2011 by the Computer Poker Research Group, University of Alberta */ #include #include #include #include #include #define __STDC_LIMIT_MACROS #include #include "game.h" #include "rng.h" #include "evalHandTables" static enum ActionType charToAction[ 256 ] = { /* 0x0X */ a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, /* 0x1X */ a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, /* 0x2X */ a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, /* 0x3X */ a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, /* 0x4X */ a_invalid, a_invalid, a_raise, a_call, a_invalid, a_invalid, a_fold, a_invalid, a_invalid, a_invalid, a_invalid, a_call, a_invalid, a_invalid, a_invalid, a_invalid, /* 0x5X */ a_invalid, a_invalid, a_raise, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, /* 0x6X */ a_invalid, a_invalid, a_raise, a_call, a_invalid, a_invalid, a_fold, a_invalid, a_invalid, a_invalid, a_invalid, a_call, a_invalid, a_invalid, a_invalid, a_invalid, /* 0x7X */ a_invalid, a_invalid, a_raise, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, /* 0x8X */ a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, /* 0x9X */ a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, /* 0xAX */ a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, /* 0xBX */ a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, /* 0xCX */ a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, /* 0xDX */ a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, /* 0xEX */ a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, /* 0xFX */ a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid, a_invalid }; static char actionChars[ a_invalid + 1 ] = "fcr"; static char suitChars[ MAX_SUITS + 1 ] = "cdhs"; static char rankChars[ MAX_RANKS + 1 ] = "23456789TJQKA"; static int consumeSpaces( const char *string, int consumeEqual ) { int i; for( i = 0; string[ i ] != 0 && ( isspace( string[ i ] ) || ( consumeEqual && string[ i ] == '=' ) ); ++i ) { } return i; } /* reads up to numItems with scanf format itemFormat from string, returning item i in *items[ i ] ignore the '=' character if consumeEqual is non-zero returns the number of characters consumed doing this in charsConsumed returns the number of items read */ static int readItems( const char *itemFormat, const int numItems, const char *string, const int consumeEqual, void *items, const size_t itemSize, int *charsConsumed ) { int i, c, r; char *fmt; i = strlen( itemFormat ); fmt = (char*)malloc( i + 3 ); assert( fmt != 0 ); strcpy( fmt, itemFormat ); fmt[ i ] = '%'; fmt[ i + 1 ] = 'n'; fmt[ i + 2 ] = 0; c = 0; for( i = 0; i < numItems; ++i ) { c += consumeSpaces( &string[ c ], consumeEqual ); if( sscanf( &string[ c ], fmt, items + i * itemSize, &r ) < 1 ) { break; } c += r; } free( fmt ); *charsConsumed = c; return i; } Game *readGame( FILE *file ) { int stackRead, blindRead, raiseSizeRead, boardCardsRead, c, t; char line[ MAX_LINE_LEN ]; Game *game; game = (Game*)malloc( sizeof( *game ) ); assert( game != 0 ); stackRead = 4; for( c = 0; c < MAX_ROUNDS; ++c ) { game->stack[ c ] = INT32_MAX; } blindRead = 0; raiseSizeRead = 0; game->bettingType = limitBetting; game->numPlayers = 0; game->numRounds = 0; for( c = 0; c < MAX_ROUNDS; ++c ) { game->firstPlayer[ c ] = 1; } for( c = 0; c < MAX_ROUNDS; ++c ) { game->maxRaises[ c ] = UINT8_MAX; } game->numSuits = 0; game->numRanks = 0; game->numHoleCards = 0; boardCardsRead = 0; while( fgets( line, MAX_LINE_LEN, file ) ) { if( line[ 0 ] == '#' || line[ 0 ] == '\n' ) { continue; } if( !strncasecmp( line, "end gamedef", 11 ) ) { break; } else if( !strncasecmp( line, "gamedef", 7 ) ) { continue; } else if( !strncasecmp( line, "stack", 5 ) ) { stackRead = readItems( "%"SCNd32, MAX_PLAYERS, &line[ 5 ], 1, game->stack, 4, &c ); } else if( !strncasecmp( line, "blind", 5 ) ) { blindRead = readItems( "%"SCNd32, MAX_PLAYERS, &line[ 5 ], 1, game->blind, 4, &c ); } else if( !strncasecmp( line, "raisesize", 9 ) ) { raiseSizeRead = readItems( "%"SCNd32, MAX_PLAYERS, &line[ 9 ], 1, game->raiseSize, 4, &c ); } else if( !strncasecmp( line, "limit", 5 ) ) { game->bettingType = limitBetting; } else if( !strncasecmp( line, "nolimit", 7 ) ) { game->bettingType = noLimitBetting; } else if( !strncasecmp( line, "numplayers", 10 ) ) { readItems( "%"SCNu8, 1, &line[ 10 ], 1, &game->numPlayers, 1, &c ); } else if( !strncasecmp( line, "numrounds", 9 ) ) { readItems( "%"SCNu8, 1, &line[ 9 ], 1, &game->numRounds, 1, &c ); } else if( !strncasecmp( line, "firstplayer", 11 ) ) { readItems( "%"SCNu8, MAX_ROUNDS, &line[ 11 ], 1, game->firstPlayer, 1, &c ); } else if( !strncasecmp( line, "maxraises", 9 ) ) { readItems( "%"SCNu8, MAX_ROUNDS, &line[ 9 ], 1, game->maxRaises, 1, &c ); } else if( !strncasecmp( line, "numsuits", 8 ) ) { readItems( "%"SCNu8, 1, &line[ 8 ], 1, &game->numSuits, 1, &c ); } else if( !strncasecmp( line, "numranks", 8 ) ) { readItems( "%"SCNu8, 1, &line[ 8 ], 1, &game->numRanks, 1, &c ); } else if( !strncasecmp( line, "numholecards", 12 ) ) { readItems( "%"SCNu8, 1, &line[ 12 ], 1, &game->numHoleCards, 1, &c ); } else if( !strncasecmp( line, "numboardcards", 13 ) ) { boardCardsRead = readItems( "%"SCNu8, MAX_ROUNDS, &line[ 13 ], 1, game->numBoardCards, 1, &c ); } } /* do sanity checks */ if( game->numRounds == 0 || game->numRounds > MAX_ROUNDS ) { fprintf( stderr, "invalid number of rounds: %"PRIu8"\n", game->numRounds ); free( game ); return NULL; } if( game->numPlayers < 2 || game->numPlayers > MAX_PLAYERS ) { fprintf( stderr, "invalid number of players: %"PRIu8"\n", game->numPlayers ); free( game ); return NULL; } if( stackRead < game->numPlayers ) { fprintf( stderr, "only read %"PRIu8" stack sizes, need %"PRIu8"\n", stackRead, game->numPlayers ); free( game ); return NULL; } if( blindRead < game->numPlayers ) { fprintf( stderr, "only read %"PRIu8" blinds, need %"PRIu8"\n", blindRead, game->numPlayers ); free( game ); return NULL; } for( c = 0; c < game->numPlayers; ++c ) { if( game->blind[ c ] > game->stack[ c ] ) { fprintf( stderr, "blind for player %d is greater than stack size\n", c + 1 ); free( game ); return NULL; } } if( game->bettingType == limitBetting && raiseSizeRead < game->numRounds ) { fprintf( stderr, "only read %"PRIu8" raise sizes, need %"PRIu8"\n", raiseSizeRead, game->numRounds ); free( game ); return NULL; } for( c = 0; c < game->numRounds; ++c ) { if( game->firstPlayer[ c ] == 0 || game->firstPlayer[ c ] > game->numPlayers ) { fprintf( stderr, "invalid first player %"PRIu8" on round %d\n", game->firstPlayer[ c ], c + 1 ); free( game ); return NULL; } --game->firstPlayer[ c ]; } if( game->numSuits == 0 || game->numSuits > MAX_SUITS ) { fprintf( stderr, "invalid number of suits: %"PRIu8"\n", game->numSuits ); free( game ); return NULL; } if( game->numRanks == 0 || game->numRanks > MAX_RANKS ) { fprintf( stderr, "invalid number of ranks: %"PRIu8"\n", game->numRanks ); free( game ); return NULL; } if( game->numHoleCards == 0 || game->numHoleCards > MAX_HOLE_CARDS ) { fprintf( stderr, "invalid number of hole cards: %"PRIu8"\n", game->numHoleCards ); free( game ); return NULL; } if( boardCardsRead < game->numRounds ) { fprintf( stderr, "only read %"PRIu8" board card numbers, need %"PRIu8"\n", boardCardsRead, game->numRounds ); free( game ); return NULL; } t = game->numHoleCards * game->numPlayers; for( c = 0; c < game->numRounds; ++c ) { t += game->numBoardCards[ c ]; } if( t > game->numSuits * game->numRanks ) { fprintf( stderr, "too many hole and board cards for specified deck\n" ); free( game ); return NULL; } return game; } void printGame( FILE *file, const Game *game ) { int i; fprintf( file, "GAMEDEF\n" ); if( game->bettingType == noLimitBetting ) { fprintf( file, "nolimit\n" ); } else { fprintf( file, "limit\n" ); } fprintf( file, "numPlayers = %"PRIu8"\n", game->numPlayers ); fprintf( file, "numRounds = %"PRIu8"\n", game->numRounds ); for( i = 0; i < game->numPlayers; ++i ) { if( game->stack[ i ] < INT32_MAX ) { fprintf( file, "stack =" ); for( i = 0; i < game->numPlayers; ++i ) { fprintf( file, " %"PRId32, game->stack[ i ] ); } fprintf( file, "\n" ); break; } } fprintf( file, "blind =" ); for( i = 0; i < game->numPlayers; ++i ) { fprintf( file, " %"PRId32, game->blind[ i ] ); } fprintf( file, "\n" ); if( game->bettingType == limitBetting ) { fprintf( file, "raiseSize =" ); for( i = 0; i < game->numRounds; ++i ) { fprintf( file, " %"PRId32, game->raiseSize[ i ] ); } fprintf( file, "\n" ); } for( i = 0; i < game->numRounds; ++i ) { if( game->firstPlayer[ i ] != 0 ) { fprintf( file, "firstPlayer =" ); for( i = 0; i < game->numRounds; ++i ) { fprintf( file, " %"PRIu8, game->firstPlayer[ i ] + 1 ); } fprintf( file, "\n" ); break; } } for( i = 0; i < game->numRounds; ++i ) { if( game->maxRaises[ i ] != UINT8_MAX ) { fprintf( file, "maxRaises =" ); for( i = 0; i < game->numRounds; ++i ) { fprintf( file, " %"PRIu8, game->maxRaises[ i ] ); } fprintf( file, "\n" ); break; } } fprintf( file, "numSuits = %"PRIu8"\n", game->numSuits ); fprintf( file, "numRanks = %"PRIu8"\n", game->numRanks ); fprintf( file, "numHoleCards = %"PRIu8"\n", game->numHoleCards ); fprintf( file, "numBoardCards =" ); for( i = 0; i < game->numRounds; ++i ) { fprintf( file, " %"PRIu8, game->numBoardCards[ i ] ); } fprintf( file, "\n" ); fprintf( file, "END GAMEDEF\n" ); } uint8_t bcStart( const Game *game, const uint8_t round ) { int r; uint8_t start; start = 0; for( r = 0; r < round; ++r ) { start += game->numBoardCards[ r ]; } return start; } uint8_t sumBoardCards( const Game *game, const uint8_t round ) { int r; uint8_t total; total = 0; for( r = 0; r <= round; ++r ) { total += game->numBoardCards[ r ]; } return total; } static uint8_t nextPlayer( const Game *game, const State *state, const uint8_t curPlayer ) { uint8_t n; n = curPlayer; do { n = ( n + 1 ) % game->numPlayers; } while( state->playerFolded[ n ] || state->spent[ n ] >= game->stack[ n ] ); return n; } uint8_t currentPlayer( const Game *game, const State *state ) { /* if action has already been made, compute next player from last player */ if( state->numActions[ state->round ] ) { return nextPlayer( game, state, state->actingPlayer[ state->round ] [ state->numActions[ state->round ] - 1 ] ); } /* first player in a round is determined by the game and round use nextPlayer() because firstPlayer[round] might be unable to act */ return nextPlayer( game, state, game->firstPlayer[ state->round ] + game->numPlayers - 1 ); } uint8_t numRaises( const State *state ) { int i; uint8_t ret; ret = 0; for( i = 0; i < state->numActions[ state->round ]; ++i ) { if( state->action[ state->round ][ i ].type == a_raise ) { ++ret; } } return ret; } uint8_t numFolded( const Game *game, const State *state ) { int p; uint8_t ret; ret = 0; for( p = 0; p < game->numPlayers; ++p ) { if( state->playerFolded[ p ] ) { ++ret; } } return ret; } uint8_t numCalled( const Game *game, const State *state ) { int i; uint8_t ret, p; ret = 0; for( i = state->numActions[ state->round ]; i > 0; --i ) { p = state->actingPlayer[ state->round ][ i - 1 ]; if( state->action[ state->round ][ i - 1 ].type == a_raise ) { /* player initiated the bet, so they've called it */ if( state->spent[ p ] < game->stack[ p ] ) { /* player is not all-in, so they're still acting */ ++ret; } /* this is the start of the current bet, so we're finished */ return ret; } else if( state->action[ state->round ][ i - 1 ].type == a_call ) { if( state->spent[ p ] < game->stack[ p ] ) { /* player is not all-in, so they're still acting */ ++ret; } } } return ret; } uint8_t numAllIn( const Game *game, const State *state ) { int p; uint8_t ret; ret = 0; for( p = 0; p < game->numPlayers; ++p ) { if( state->spent[ p ] >= game->stack[ p ] ) { ++ret; } } return ret; } uint8_t numActingPlayers( const Game *game, const State *state ) { int p; uint8_t ret; ret = 0; for( p = 0; p < game->numPlayers; ++p ) { if( state->playerFolded[ p ] == 0 && state->spent[ p ] < game->stack[ p ] ) { ++ret; } } return ret; } void initState( const Game *game, const uint32_t handId, State *state ) { int p, r; state->handId = handId; state->maxSpent = 0; for( p = 0; p < game->numPlayers; ++p ) { state->spent[ p ] = game->blind[ p ]; if( game->blind[ p ] > state->maxSpent ) { state->maxSpent = game->blind[ p ]; } } if( game->bettingType == noLimitBetting ) { /* no-limit games need to keep track of the minimum bet */ if( state->maxSpent ) { /* we'll have to call the big blind and then raise by that amount, so the minimum raise-to is 2*maximum blinds */ state->minNoLimitRaiseTo = state->maxSpent * 2; } else { /* need to bet at least one chip, and there are no blinds/ante */ state->minNoLimitRaiseTo = 1; } } else { /* no need to worry about minimum raises outside of no-limit games */ state->minNoLimitRaiseTo = 0; } for( p = 0; p < game->numPlayers; ++p ) { state->spent[ p ] = game->blind[ p ]; if( game->blind[ p ] > state->maxSpent ) { state->maxSpent = game->blind[ p ]; } state->playerFolded[ p ] = 0; } for( r = 0; r < game->numRounds; ++r ) { state->numActions[ r ] = 0; } state->round = 0; state->finished = 0; } static uint8_t dealCard( rng_state_t *rng, uint8_t *deck, const int numCards ) { int i; uint8_t ret; i = genrand_int32( rng ) % numCards; ret = deck[ i ]; deck[ i ] = deck[ numCards - 1 ]; return ret; } void dealCards( const Game *game, rng_state_t *rng, State *state ) { int r, s, numCards, i, p; uint8_t deck[ MAX_RANKS * MAX_SUITS ]; numCards = 0; for( s = MAX_SUITS - game->numSuits; s < MAX_SUITS; ++s ) { for( r = MAX_RANKS - game->numRanks; r < MAX_RANKS; ++r ) { deck[ numCards ] = makeCard( r, s ); ++numCards; } } for( p = 0; p < game->numPlayers; ++p ) { for( i = 0; i < game->numHoleCards; ++i ) { state->holeCards[ p ][ i ] = dealCard( rng, deck, numCards ); --numCards; } } s = 0; for( r = 0; r < game->numRounds; ++r ) { for( i = 0; i < game->numBoardCards[ r ]; ++i ) { state->boardCards[ s ] = dealCard( rng, deck, numCards ); --numCards; ++s; } } } /* check whether some portions of a state are equal, common to both statesEqual and matchStatesEqual */ static int statesEqualCommon( const Game *game, const State *a, const State *b ) { int r, i, t; /* is it the same hand? */ if( a->handId != b->handId ) { return 0; } /* make sure the betting is the same */ if( a->round != b->round ) { return 0; } for( r = 0; r <= a->round; ++r ) { if( a->numActions[ r ] != b->numActions[ r ] ) { return 0; } for( i = 0; i < a->numActions[ r ]; ++i ) { if( a->action[ r ][ i ].type != b->action[ r ][ i ].type ) { return 0; } if( a->action[ r ][ i ].size != b->action[ r ][ i ].size ) { return 0; } } } /* spent, maxSpent, actingPlayer, finished, and playerFolded are all determined by the betting taken, so if it's equal, so are they (at least for valid states) */ /* are the board cards the same? */ t = sumBoardCards( game, a->round ); for( i = 0; i < t; ++i ) { if( a->boardCards[ i ] != b->boardCards[ i ] ) { return 0; } } /* all tests say states are equal */ return 1; } int statesEqual( const Game *game, const State *a, const State *b ) { int p, i; if( !statesEqualCommon( game, a, b ) ) { return 0; } /* are all the hole cards the same? */ for( p = 0; p < game->numPlayers; ++p ) { for( i = 0; i < game->numHoleCards; ++i ) { if( a->holeCards[ p ][ i ] != b->holeCards[ p ][ i ] ) { return 0; } } } return 1; } int matchStatesEqual( const Game *game, const MatchState *a, const MatchState *b ) { int p, i; if( a->viewingPlayer != b->viewingPlayer ) { return 0; } if( !statesEqualCommon( game, &a->state, &b->state ) ) { return 0; } /* are the viewing player's hole cards the same? */ p = a->viewingPlayer; for( i = 0; i < game->numHoleCards; ++i ) { if( a->state.holeCards[ p ][ i ] != b->state.holeCards[ p ][ i ] ) { return 0; } } return 1; } int raiseIsValid( const Game *game, const State *curState, int32_t *minSize, int32_t *maxSize ) { int p; if( numRaises( curState ) >= game->maxRaises[ curState->round ] ) { /* already made maximum number of raises */ return 0; } if( curState->numActions[ curState->round ] + game->numPlayers > MAX_NUM_ACTIONS ) { /* 1 raise + NUM PLAYERS-1 calls is too many actions */ fprintf( stderr, "WARNING: #actions in round is too close to MAX_NUM_ACTIONS, forcing call/fold\n" ); return 0; } if( numActingPlayers( game, curState ) <= 1 ) { /* last remaining player can't bet if there's no one left to call (this check is needed if the 2nd last player goes all in, and the last player has enough stack left to bet) */ return 0; } if( game->bettingType != noLimitBetting ) { /* if it's not no-limit betting, don't worry about sizes */ *minSize = 0; *maxSize = 0; return 1; } p = currentPlayer( game, curState ); *minSize = curState->minNoLimitRaiseTo; *maxSize = game->stack[ p ]; /* handle case where remaining player stack is too small */ if( *minSize > game->stack[ p ] ) { /* can't handle the minimum bet size - can we bet at all? */ if( curState->maxSpent >= game->stack[ p ] ) { /* not enough money to increase current bet */ return 0; } else { /* can raise by going all-in */ *minSize = *maxSize; return 1; } } return 1; } int isValidAction( const Game *game, const State *curState, const int tryFixing, Action *action ) { int min, max, p; if( stateFinished( curState ) || action->type == a_invalid ) { return 0; } p = currentPlayer( game, curState ); if( action->type == a_raise ) { if( !raiseIsValid( game, curState, &min, &max ) ) { /* there are no valid raise sizes */ return 0; } if( game->bettingType == noLimitBetting ) { /* no limit games have a size */ if( action->size < min ) { /* bet size is too small */ if( !tryFixing ) { return 0; } fprintf( stderr, "WARNING: raise of %d increased to %d\n", action->size, min ); action->size = min; } else if( action->size > max ) { /* bet size is too big */ if( !tryFixing ) { return 0; } fprintf( stderr, "WARNING: raise of %d decreased to %d\n", action->size, max ); action->size = max; } } else { } } else if( action->type == a_fold ) { if( curState->spent[ p ] == curState->maxSpent || curState->spent[ p ] == game->stack[ p ] ) { /* player has already called all bets, or is all-in */ return 0; } if( action->size != 0 ) { fprintf( stderr, "WARNING: size given for fold\n" ); action->size = 0; } } else { /* everything else */ if( action->size != 0 ) { fprintf( stderr, "WARNING: size given for something other than a no-limit raise\n" ); action->size = 0; } } return 1; } void doAction( const Game *game, const Action *action, State *state ) { int p = currentPlayer( game, state ); assert( state->numActions[ state->round ] < MAX_NUM_ACTIONS ); state->action[ state->round ][ state->numActions[ state->round ] ] = *action; state->actingPlayer[ state->round ][ state->numActions[ state->round ] ] = p; ++state->numActions[ state->round ]; switch( action->type ) { case a_fold: state->playerFolded[ p ] = 1; break; case a_call: if( state->maxSpent > game->stack[ p ] ) { /* calling puts player all-in */ state->spent[ p ] = game->stack[ p ]; } else { /* player matches the bet by spending same amount of money */ state->spent[ p ] = state->maxSpent; } break; case a_raise: if( game->bettingType == noLimitBetting ) { /* no-limit betting uses size in action */ assert( action->size > state->maxSpent ); assert( action->size <= game->stack[ p ] ); /* next raise must call this bet, and raise by at least this much */ if( action->size + action->size - state->maxSpent > state->minNoLimitRaiseTo ) { state->minNoLimitRaiseTo = action->size + action->size - state->maxSpent; } state->maxSpent = action->size; } else { /* limit betting uses a fixed amount on top of current bet size */ if( state->maxSpent + game->raiseSize[ state->round ] > game->stack[ p ] ) { /* raise puts player all-in */ state->maxSpent = game->stack[ p ]; } else { /* player raises by the normal limit size */ state->maxSpent += game->raiseSize[ state->round ]; } } state->spent[ p ] = state->maxSpent; break; default: fprintf( stderr, "ERROR: trying to do invalid action %d", action->type ); assert( 0 ); } /* see if the round or game has ended */ if( numFolded( game, state ) + 1 >= game->numPlayers ) { /* only one player left - game is immediately over, no showdown */ state->finished = 1; } else if( numCalled( game, state ) >= numActingPlayers( game, state ) ) { /* >= 2 non-folded players, all acting players have called */ if( numActingPlayers( game, state ) > 1 ) { /* there are at least 2 acting players */ if( state->round + 1 < game->numRounds ) { /* active players move onto next round */ ++state->round; /* minimum raise-by is reset to minimum of big blind or 1 chip */ state->minNoLimitRaiseTo = 1; for( p = 0; p < game->numPlayers; ++p ) { if( game->blind[ p ] > state->minNoLimitRaiseTo ) { state->minNoLimitRaiseTo = game->blind[ p ]; } } /* we finished at least one round, so raise-to = raise-by + maxSpent */ state->minNoLimitRaiseTo += state->maxSpent; } else { /* no more betting rounds, so we're totally finished */ state->finished = 1; } } else { /* not enough players for more betting, but still need a showdown */ state->finished = 1; state->round = game->numRounds - 1; } } } static int rankHand( const Game *game, const State *state, const uint8_t player ) { int i; Cardset c = emptyCardset(); for( i = 0; i < game->numHoleCards; ++i ) { addCardToCardset( &c, suitOfCard( state->holeCards[ player ][ i ] ), rankOfCard( state->holeCards[ player ][ i ] ) ); } for( i = 0; i < sumBoardCards( game, state->round ); ++i ) { addCardToCardset( &c, suitOfCard( state->boardCards[ i ] ), rankOfCard( state->boardCards[ i ] ) ); } return rankCardset( c ); } double valueOfState( const Game *game, const State *state, const uint8_t player ) { double value; int p, numPlayers, playerIdx, numWinners, newNumPlayers; int32_t size, spent[ MAX_PLAYERS ]; int rank[ MAX_PLAYERS ], winRank; if( state->playerFolded[ player ] ) { /* folding player loses all spent money */ return (double)-state->spent[ player ]; } if( numFolded( game, state ) + 1 == game->numPlayers ) { /* everyone else folded, so player takes the pot */ value = 0.0; for( p = 0; p < game->numPlayers; ++p ) { if( p == player ) { continue; } value += (double)state->spent[ p ]; } return value; } /* there's a showdown, and player is particpating. Exciting! */ /* make up a list of players */ numPlayers = 0; playerIdx = -1; /* useless, but gets rid of a warning */ for( p = 0; p < game->numPlayers; ++p ) { if( state->spent[ p ] == 0 ) { continue; } if( state->playerFolded[ p ] ) { /* folding players have a negative rank so they lose to a real hand we have also tested for fold, so p can't be the player of interest */ rank[ numPlayers ] = -1; } else { /* p is participating in a showdown */ if( p == player ) { playerIdx = numPlayers; } rank[ numPlayers ] = rankHand( game, state, p ); } spent[ numPlayers ] = state->spent[ p ]; ++numPlayers; } assert( numPlayers > 1 ); /* go through all the sidepots player is participating in */ value = 0.0; while( 1 ) { /* find the smallest remaining sidepot, largest rank, and number of winners with largest rank */ size = INT32_MAX; winRank = 0; numWinners = 0; for( p = 0; p < numPlayers; ++p ) { assert( spent[ p ] > 0 ); if( spent[ p ] < size ) { size = spent[ p ]; } if( rank[ p ] > winRank ) { /* new largest rank - only one player with this rank so far */ winRank = rank[ p ]; numWinners = 1; } else if( rank[ p ] == winRank ) { /* another player with highest rank */ ++numWinners; } } if( rank[ playerIdx ] == winRank ) { /* player has spent size, and splits pot with other winners */ value += (double)( size * ( numPlayers - numWinners ) ) / (double)numWinners; } else { /* player loses this pot */ value -= (double)size; } /* update list of players for next pot */ newNumPlayers = 0; for( p = 0; p < numPlayers; ++p ) { spent[ p ] -= size; if( spent[ p ] == 0 ) { /* player p is not participating in next side pot */ if( p == playerIdx ) { /* p is the player of interest, so we're done */ return value; } continue; } if( p == playerIdx ) { /* p is the player of interest, so note the new index */ playerIdx = newNumPlayers; } if( p != newNumPlayers ) { /* put entry p into new position */ spent[ newNumPlayers ] = spent[ p ]; rank[ newNumPlayers ] = rank[ p ]; } ++newNumPlayers; } numPlayers = newNumPlayers; } } /* read actions from a string, updating state with the actions reading is terminated by '\0' and ':' returns number of characters consumed, or -1 on failure state will be modified, even on failure */ static int readBetting( const char *string, const Game *game, State *state ) { int c, r; Action action; c = 0; while( 1 ) { if( string[ c ] == 0 ) { break; } if( string[ c ] == ':' ) { ++c; break; } /* ignore / character */ if( string[ c ] == '/' ) { ++c; continue; } r = readAction( &string[ c ], game, &action ); if( r < 0 ) { return -1; } if( !isValidAction( game, state, 0, &action ) ) { return -1; } doAction( game, &action, state ); c += r; } return c; } /* print actions to a string returns number of characters printed to string, or -1 on failure DOES NOT COUNT FINAL 0 TERMINATOR IN THIS COUNT!!! */ static int printBetting( const Game *game, const State *state, const int maxLen, char *string ) { int i, a, c, r; c = 0; for( i = 0; i <= state->round; ++i ) { /* print state separator */ if( i != 0 ) { if( c >= maxLen ) { return -1; } string[ c ] = '/'; ++c; } /* print betting for round */ for( a = 0; a < state->numActions[ i ]; ++a ) { r = printAction( game, &state->action[ i ][ a ], maxLen - c, &string[ c ] ); if( r < 0 ) { return -1; } c += r; } } if( c >= maxLen ) { return -1; } string[ c ] = 0; return c; } static int readHoleCards( const char *string, const Game *game, State *state ) { int p, c, r, num; c = 0; for( p = 0; p < game->numPlayers; ++p ) { /* check for player separator '|' */ if( p != 0 ) { if( string[ c ] == '|' ) { ++c; } } num = readCards( &string[ c ], game->numHoleCards, state->holeCards[ p ], &r ); if( num == 0 ) { /* no cards for player p */ continue; } if( num != game->numHoleCards ) { /* read some cards, but not enough - bad! */ return -1; } c += r; } return c; } static int printAllHoleCards( const Game *game, const State *state, const int maxLen, char *string ) { int p, c, r; c = 0; for( p = 0; p < game->numPlayers; ++p ) { /* print player separator '|' */ if( p != 0 ) { if( c >= maxLen ) { return -1; } string[ c ] = '|'; ++c; } r = printCards( game->numHoleCards, state->holeCards[ p ], maxLen - c, &string[ c ] ); if( r < 0 ) { return -1; } c += r; } if( c >= maxLen ) { return -1; } string[ c ] = 0; return c; } static int printPlayerHoleCards( const Game *game, const State *state, const uint8_t player, const int maxLen, char *string ) { int p, c, r; c = 0; for( p = 0; p < game->numPlayers; ++p ) { /* print player separator '|' */ if( p != 0 ) { if( c >= maxLen ) { return -1; } string[ c ] = '|'; ++c; } if( p != player ) { /* don't print other player's cards unless there was a showdown and they didn't fold */ if( !stateFinished( state ) ) { continue; } if( state->playerFolded[ p ] ) { continue; } if( numFolded( game, state ) + 1 == game->numPlayers ) { continue; } } r = printCards( game->numHoleCards, state->holeCards[ p ], maxLen - c, &string[ c ] ); if( r < 0 ) { return -1; } c += r; } if( c >= maxLen ) { return -1; } string[ c ] = 0; return c; } static int readBoardCards( const char *string, const Game *game, State *state ) { int i, c, r; c = 0; for( i = 0; i <= state->round; ++i ) { /* check for round separator '/' */ if( i != 0 ) { if( string[ c ] == '/' ) { ++c; } } if( readCards( &string[ c ], game->numBoardCards[ i ], &state->boardCards[ bcStart( game, i ) ], &r ) != game->numBoardCards[ i ] ) { /* couldn't read the required number of cards - bad! */ return -1; } c += r; } return c; } static int printBoardCards( const Game *game, const State *state, const int maxLen, char *string ) { int i, c, r; c = 0; for( i = 0; i <= state->round; ++i ) { /* print round separator '/' */ if( i != 0 ) { if( c >= maxLen ) { return -1; } string[ c ] = '/'; ++c; } r = printCards( game->numBoardCards[ i ], &state->boardCards[ bcStart( game, i ) ], maxLen - c, &string[ c ] ); if( r < 0 ) { return -1; } c += r; } if( c >= maxLen ) { return -1; } string[ c ] = 0; return c; } static int readStateCommon( const char *string, const Game *game, State *state ) { uint32_t handId; int c, r; /* HEADER */ c = 0; /* HEADER:handId */ if( sscanf( string, ":%"SCNu32"%n", &handId, &r ) < 1 ) { return -1; } c += r; initState( game, handId, state ); /* HEADER:handId: */ if( string[ c ] != ':' ) { return -1; } ++c; /* HEADER:handId:betting: */ r = readBetting( &string[ c ], game, state ); if( r < 0 ) { return -1; } c += r; /* HEADER:handId:betting:holeCards */ r = readHoleCards( &string[ c ], game, state ); if( r < 0 ) { return -1; } c += r; /* HEADER:handId:betting:holeCards boardCards */ r = readBoardCards( &string[ c ], game, state ); if( r < 0 ) { return -1; } c += r; return c; } int readState( const char *string, const Game *game, State *state ) { int c, r; /* HEADER = STATE */ if( strncmp( string, "STATE", 5 ) != 0 ) { return -1; } c = 5; /* read rest of state */ r = readStateCommon( &string[ 5 ], game, state ); if( r < 0 ) { return -1; } c += r; return c; } int readMatchState( const char *string, const Game *game, MatchState *state ) { int c, r; /* HEADER = MATCHSTATE:player */ if( sscanf( string, "MATCHSTATE:%"SCNu8"%n", &state->viewingPlayer, &c ) < 1 || state->viewingPlayer >= game->numPlayers ) { return -1; } /* read rest of state */ r = readStateCommon( &string[ c ], game, &state->state ); if( r < 0 ) { return -1; } c += r; return c; } static int printStateCommon( const Game *game, const State *state, const int maxLen, char *string ) { int c, r; /* HEADER */ c = 0; /* HEADER:handId: */ r = snprintf( &string[ c ], maxLen - c, ":%"PRIu32":", state->handId ); if( r < 0 ) { return -1; } c += r; /* HEADER:handId:betting */ r = printBetting( game, state, maxLen - c, &string[ c ] ); if( r < 0 ) { return -1; } c += r; /* HEADER:handId:betting: */ if( c >= maxLen ) { return -1; } string[ c ] = ':'; ++c; return c; } int printState( const Game *game, const State *state, const int maxLen, char *string ) { int c, r; c = 0; /* STATE */ r = snprintf( &string[ c ], maxLen - c, "STATE" ); if( r < 0 ) { return -1; } c += r; /* STATE:handId:betting: */ r = printStateCommon( game, state, maxLen - c, &string[ c ] ); if( r < 0 ) { return -1; } c += r; /* STATE:handId:betting:holeCards */ r = printAllHoleCards( game, state, maxLen - c, &string[ c ] ); if( r < 0 ) { return -1; } c += r; /* STATE:handId:betting:holeCards boardCards */ r = printBoardCards( game, state, maxLen - c, &string[ c ] ); if( r < 0 ) { return -1; } c += r; if( c >= maxLen ) { return -1; } string[ c ] = 0; return c; } int printMatchState( const Game *game, const MatchState *state, const int maxLen, char *string ) { int c, r; c = 0; /* MATCHSTATE:player */ r = snprintf( &string[ c ], maxLen - c, "MATCHSTATE:%"PRIu8, state->viewingPlayer ); if( r < 0 ) { return -1; } c += r; /* MATCHSTATE:player:handId:betting: */ r = printStateCommon( game, &state->state, maxLen - c, &string[ c ] ); if( r < 0 ) { return -1; } c += r; /* MATCHSTATE:player:handId:betting:holeCards */ r = printPlayerHoleCards( game, &state->state, state->viewingPlayer, maxLen - c, &string[ c ] ); if( r < 0 ) { return -1; } c += r; /* MATCHSTATE:player:handId:betting:holeCards boardCards */ r = printBoardCards( game, &state->state, maxLen - c, &string[ c ] ); if( r < 0 ) { return -1; } c += r; if( c >= maxLen ) { return -1; } string[ c ] = 0; return c; } int readAction( const char *string, const Game *game, Action *action ) { int c, r; action->type = charToAction[ (uint8_t)string[ 0 ] ]; if( action->type < 0 ) { return -1; } c = 1; if( action->type == a_raise && game->bettingType == noLimitBetting ) { /* no-limit bet/raise needs to read a size */ if( sscanf( &string[ c ], "%"SCNd32"%n", &action->size, &r ) < 1 ) { return -1; } c += r; } else { /* size is zero for anything but a no-limit raise */ action->size = 0; } return c; } int printAction( const Game *game, const Action *action, const int maxLen, char *string ) { int c, r; if( maxLen == 0 ) { return -1; } c = 0; string[ c ] = actionChars[ action->type ]; ++c; if( game->bettingType == noLimitBetting && action->type == a_raise ) { /* 2010 AAAI no-limit format has a size for bet/raise */ r = snprintf( &string[ c ], maxLen - c, "%"PRId32, action->size ); if( r < 0 ) { return -1; } c += r; } if( c >= maxLen ) { return -1; } string[ c ] = 0; return c; } int readCard( const char *string, uint8_t *card ) { char *spos; uint8_t c; if( string[ 0 ] == 0 ) { return -1; } spos = strchr( rankChars, toupper( string[ 0 ] ) ); if( spos == 0 ) { return -1; } c = spos - rankChars; if( string[ 1 ] == 0 ) { return -1; } spos = strchr( suitChars, tolower( string[ 1 ] ) ); if( spos == 0 ) { return -1; } *card = makeCard( c, spos - suitChars ); return 2; } int readCards( const char *string, const int maxCards, uint8_t *cards, int *charsConsumed ) { int i, c, r; c = 0; for( i = 0; i < maxCards; ++i ) { r = readCard( &string[ c ], &cards[ i ] ); if( r < 0 ) { break; } c += r; } *charsConsumed = c; return i; } int printCard( const uint8_t card, const int maxLen, char *string ) { if( 3 > maxLen ) { return -1; } string[ 0 ] = rankChars[ rankOfCard( card ) ]; string[ 1 ] = suitChars[ suitOfCard( card ) ]; string[ 2 ] = 0; return 2; } int printCards( const int numCards, const uint8_t *cards, const int maxLen, char *string ) { int i, c, r; c = 0; for( i = 0; i < numCards; ++i ) { r = printCard( cards[ i ], maxLen - c, &string[ c ] ); if( r < 0 ) { return -1; } c += r; } /* no need to null terminate, we know for sure that printCard does */ return c; } ================================================ FILE: ACPCServer/game.h ================================================ /* Copyright (C) 2011 by the Computer Poker Research Group, University of Alberta */ #ifndef _GAME_H #define _GAME_H #define __STDC_FORMAT_MACROS #include #include "rng.h" #include "net.h" #define VERSION_MAJOR 2 #define VERSION_MINOR 0 #define VERSION_REVISION 0 #define MAX_ROUNDS 4 #define MAX_PLAYERS 10 #define MAX_BOARD_CARDS 7 #define MAX_HOLE_CARDS 3 #define MAX_NUM_ACTIONS 64 #define MAX_SUITS 4 #define MAX_RANKS 13 #define MAX_LINE_LEN READBUF_LEN #define NUM_ACTION_TYPES 3 enum BettingType { limitBetting, noLimitBetting }; enum ActionType { a_fold = 0, a_call = 1, a_raise = 2, a_invalid = NUM_ACTION_TYPES }; typedef struct { enum ActionType type; /* is action a fold, call, or raise? */ int32_t size; /* for no-limit raises, we need a size MUST BE 0 IN ALL CASES WHERE IT IS NOT USED */ } Action; typedef struct { /* stack sizes for each player */ int32_t stack[ MAX_PLAYERS ]; /* entry fee for game, per player */ int32_t blind[ MAX_PLAYERS ]; /* size of fixed raises for limitBetting games */ int32_t raiseSize[ MAX_ROUNDS ]; /* general class of game */ enum BettingType bettingType; /* number of players in the game */ uint8_t numPlayers; /* number of betting rounds */ uint8_t numRounds; /* first player to act in a round */ uint8_t firstPlayer[ MAX_ROUNDS ]; /* number of bets/raises that may be made in each round */ uint8_t maxRaises[ MAX_ROUNDS ]; /* number of suits and ranks in the deck of cards */ uint8_t numSuits; uint8_t numRanks; /* number of private player cards */ uint8_t numHoleCards; /* number of shared public cards each round */ uint8_t numBoardCards[ MAX_ROUNDS ]; } Game; typedef struct { uint32_t handId; /* largest bet so far, including all previous rounds */ int32_t maxSpent; /* minimum number of chips a player must have spend in total to raise only used for noLimitBetting games */ int32_t minNoLimitRaiseTo; /* spent[ p ] gives the total amount put into the pot by player p */ int32_t spent[ MAX_PLAYERS ]; /* action[ r ][ i ] gives the i'th action in round r */ Action action[ MAX_ROUNDS ][ MAX_NUM_ACTIONS ]; /* actingPlayer[ r ][ i ] gives the player who made action i in round r we can always figure this out from the actions taken, but it's easier to just remember this in multiplayer (because of folds) */ uint8_t actingPlayer[ MAX_ROUNDS ][ MAX_NUM_ACTIONS ]; /* numActions[ r ] gives the number of actions made in round r */ uint8_t numActions[ MAX_ROUNDS ]; /* current round: a value between 0 and game.numRounds-1 a showdown is still in numRounds-1, not a separate round */ uint8_t round; /* finished is non-zero if and only if the game is over */ uint8_t finished; /* playerFolded[ p ] is non-zero if and only player p has folded */ uint8_t playerFolded[ MAX_PLAYERS ]; /* public cards (including cards which may not yet be visible to players) */ uint8_t boardCards[ MAX_BOARD_CARDS ]; /* private cards */ uint8_t holeCards[ MAX_PLAYERS ][ MAX_HOLE_CARDS ]; } State; typedef struct { State state; uint8_t viewingPlayer; } MatchState; /* returns a game structure, or NULL on failure */ Game *readGame( FILE *file ); void printGame( FILE *file, const Game *game ); /* initialise a state so that it is at the beginning of a hand DOES NOT DEAL OUT CARDS */ void initState( const Game *game, const uint32_t handId, State *state ); /* shuffle a deck of cards and deal them out, writing the results to state */ void dealCards( const Game *game, rng_state_t *rng, State *state ); int statesEqual( const Game *game, const State *a, const State *b ); int matchStatesEqual( const Game *game, const MatchState *a, const MatchState *b ); /* check if a raise is possible, and the range of valid sizes returns non-zero if raise is a valid action and sets *minSize and maxSize, or zero if raise is not valid */ int raiseIsValid( const Game *game, const State *curState, int32_t *minSize, int32_t *maxSize ); /* check if an action is valid if tryFixing is non-zero, try modifying the given action to produce a valid action, as in the AAAI rules. Currently this only means that a no-limit raise will be modified to the nearest valid raise size returns non-zero if final action/size is valid for state, 0 otherwise */ int isValidAction( const Game *game, const State *curState, const int tryFixing, Action *action ); /* record the given action in state does not check that action is valid */ void doAction( const Game *game, const Action *action, State *state ); /* returns non-zero if hand is finished, zero otherwise */ #define stateFinished( constStatePtr ) ((constStatePtr)->finished) /* get the current player to act in the state */ uint8_t currentPlayer( const Game *game, const State *state ); /* number of raises in the current round */ uint8_t numRaises( const State *state ); /* number of players who have folded */ uint8_t numFolded( const Game *game, const State *state ); /* number of players who have called the current bet (or initiated it) doesn't count non-acting players who are all-in */ uint8_t numCalled( const Game *game, const State *state ); /* number of players who are all-in */ uint8_t numAllIn( const Game *game, const State *state ); /* number of players who can still act (ie not all-in or folded) */ uint8_t numActingPlayers( const Game *game, const State *state ); /* get the index into array state.boardCards[] for the first board card in round (where the first round is round 0) */ uint8_t bcStart( const Game *game, const uint8_t round ); /* get the total number of board cards dealt out after (zero based) round */ uint8_t sumBoardCards( const Game *game, const uint8_t round ); /* return the value of a finished hand for a player returns a double because pots may be split when players tie WILL HAVE UNDEFINED BEHAVIOUR IF HAND ISN'T FINISHED (stateFinished(state)==0) */ double valueOfState( const Game *game, const State *state, const uint8_t player ); /* returns number of characters consumed on success, -1 on failure state will be modified even on a failure to read */ int readState( const char *string, const Game *game, State *state ); /* returns number of characters consumed on success, -1 on failure state will be modified even on a failure to read */ int readMatchState( const char *string, const Game *game, MatchState *state ); /* print a state to a string, as viewed by viewingPlayer returns the number of characters in string, or -1 on error DOES NOT COUNT FINAL 0 TERMINATOR IN THIS COUNT!!! */ int printState( const Game *game, const State *state, const int maxLen, char *string ); /* print a state to a string, as viewed by viewingPlayer returns the number of characters in string, or -1 on error DOES NOT COUNT FINAL 0 TERMINATOR IN THIS COUNT!!! */ int printMatchState( const Game *game, const MatchState *state, const int maxLen, char *string ); /* read an action, returning the action in the passed pointer action and size will be modified even on a failure to read returns number of characters consumed on succes, -1 on failure */ int readAction( const char *string, const Game *game, Action *action ); /* print an action to a string returns the number of characters in string, or -1 on error DOES NOT COUNT FINAL 0 TERMINATOR IN THIS COUNT!!! */ int printAction( const Game *game, const Action *action, const int maxLen, char *string ); /* returns number of characters consumed, or -1 on error on success, returns the card in *card */ int readCard( const char *string, uint8_t *card ); /* read up to maxCards cards returns number of cards successfully read returns number of characters consumed in charsConsumed */ int readCards( const char *string, const int maxCards, uint8_t *cards, int *charsConsumed ); /* print a card to a string returns the number of characters in string, or -1 on error DOES NOT COUNT FINAL 0 TERMINATOR IN THIS COUNT!!! */ int printCard( const uint8_t card, const int maxLen, char *string ); /* print out numCards cards to a string returns the number of characters in string DOES NOT COUNT FINAL 0 TERMINATOR IN THIS COUNT!!! */ int printCards( const int numCards, const uint8_t *cards, const int maxLen, char *string ); #define rankOfCard( card ) ((card)/MAX_SUITS) #define suitOfCard( card ) ((card)%MAX_SUITS) #define makeCard( rank, suit ) ((rank)*MAX_SUITS+(suit)) #endif ================================================ FILE: ACPCServer/holdem.limit.2p.reverse_blinds.game ================================================ GAMEDEF limit numPlayers = 2 numRounds = 4 blind = 10 5 raiseSize = 10 10 20 20 firstPlayer = 2 1 1 1 maxRaises = 3 4 4 4 numSuits = 4 numRanks = 13 numHoleCards = 2 numBoardCards = 0 3 1 1 END GAMEDEF ================================================ FILE: ACPCServer/holdem.limit.3p.game ================================================ GAMEDEF limit numPlayers = 3 numRounds = 4 blind = 5 10 0 raiseSize = 10 10 20 20 firstPlayer = 3 1 1 1 maxRaises = 3 4 4 4 numSuits = 4 numRanks = 13 numHoleCards = 2 numBoardCards = 0 3 1 1 END GAMEDEF ================================================ FILE: ACPCServer/holdem.nolimit.2p.reverse_blinds.game ================================================ GAMEDEF nolimit numPlayers = 2 numRounds = 4 stack = 20000 20000 blind = 50 100 firstPlayer = 1 2 2 2 numSuits = 4 numRanks = 13 numHoleCards = 2 numBoardCards = 0 3 1 1 END GAMEDEF ================================================ FILE: ACPCServer/holdem.nolimit.3p.game ================================================ GAMEDEF nolimit numPlayers = 3 numRounds = 4 stack = 20000 20000 20000 blind = 50 100 0 firstPlayer = 3 1 1 1 numSuits = 4 numRanks = 13 numHoleCards = 2 numBoardCards = 0 3 1 1 END GAMEDEF ================================================ FILE: ACPCServer/kuhn.limit.3p.game ================================================ GAMEDEF limit numPlayers = 3 numRounds = 1 blind = 1 1 1 raiseSize = 1 firstPlayer = 1 maxRaises = 1 numSuits = 1 numRanks = 4 numHoleCards = 1 numBoardCards = 0 END GAMEDEF ================================================ FILE: ACPCServer/leduc.game ================================================ GAMEDEF nolimit numPlayers = 2 numRounds = 2 stack = 1200 1200 blind = 100 100 firstPlayer = 1 1 numSuits = 2 numRanks = 3 numHoleCards = 1 numBoardCards = 0 1 END GAMEDEF ================================================ FILE: ACPCServer/net.c ================================================ /* Copyright (C) 2011 by the Computer Poker Research Group, University of Alberta */ #include #include #include #include #include #include #include #include "net.h" ReadBuf *createReadBuf( int fd ) { ReadBuf *readBuf = (ReadBuf*)malloc( sizeof( ReadBuf ) ); if( readBuf == 0 ) { return readBuf; } readBuf->fd = fd; readBuf->bufStart = 0; readBuf->bufEnd = 0; return readBuf; } void destroyReadBuf( ReadBuf *readBuf ) { close( readBuf->fd ); free( readBuf ); } /* get a newline terminated line and place it as a string in 'line' terminates the string with a 0 character if timeoutMicros is non-negative, do not spend more than that number of microseconds waiting to read data return number of characters read (including newline, excluding 0) 0 on end of file, or -1 on error or timeout */ ssize_t getLine( ReadBuf *readBuf, size_t maxLen, char *line, int64_t timeoutMicros ) { int haveStartTime, c; ssize_t len; fd_set fds; struct timeval start, tv; /* reserve space for string terminator */ --maxLen; if( maxLen < 0 ) { return -1; } /* read the line */ haveStartTime = 0; len = 0; while( len < maxLen ) { if( readBuf->bufStart >= readBuf->bufEnd ) { /* buffer is empty */ if( timeoutMicros >= 0 ) { /* figure out how much time is left for reading */ uint64_t timeLeft; timeLeft = timeoutMicros; if( haveStartTime ) { gettimeofday( &tv, NULL ); timeLeft -= (uint64_t)( tv.tv_sec - start.tv_sec ) * 1000000 + ( tv.tv_usec - start.tv_usec ); if( timeLeft < 0 ) { timeLeft = 0; } } else { haveStartTime = 1; gettimeofday( &start, NULL ); } tv.tv_sec = timeLeft / 1000000; tv.tv_usec = timeLeft % 1000000; /* wait for file descriptor to be ready */ FD_ZERO( &fds ); FD_SET( readBuf->fd, &fds ); if( select( readBuf->fd + 1, &fds, NULL, NULL, &tv ) < 1 ) { /* no input ready within time, or an actual error */ return -1; } } /* try reading a buffer full of data */ readBuf->bufStart = 0; readBuf->bufEnd = read( readBuf->fd, readBuf->buf, READBUF_LEN ); if( readBuf->bufEnd == 0 ) { /* end of input */ break; } else if( readBuf->bufEnd < 0 ) { /* error condition */ readBuf->bufEnd = 0; return -1; } } /* keep adding to the string until we see a newline */ c = readBuf->buf[ readBuf->bufStart ]; ++readBuf->bufStart; line[ len ] = c; ++len; if( c == '\n' ) { break; } } /* terminate the string */ line[ len ] = 0; return len; } int connectTo( char *hostname, uint16_t port ) { int sock; struct hostent *hostent; struct sockaddr_in addr; hostent = gethostbyname( hostname ); if( hostent == NULL ) { fprintf( stderr, "ERROR: could not look up address for %s\n", hostname ); return -1; } if( ( sock = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 ) { fprintf( stderr, "ERROR: could not open socket\n" ); return -1; } addr.sin_family = AF_INET; addr.sin_port = htons( port ); memcpy( &addr.sin_addr, hostent->h_addr_list[ 0 ], hostent->h_length ); if( connect( sock, (struct sockaddr *)&addr, sizeof( addr ) ) < 0 ) { fprintf( stderr, "ERROR: could not connect to %s:%"PRIu16"\n", hostname, port ); return -1; } return sock; } int getListenSocket( uint16_t *desiredPort ) { int sock, t; struct sockaddr_in addr; if( ( sock = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 ) { return -1; } /* allow fast socket reuse - ignore failure */ t = 1; setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &t, sizeof( int ) ); /* bind the socket to the port */ if( *desiredPort != 0 ) { addr.sin_family = AF_INET; addr.sin_port = htons( *desiredPort ); addr.sin_addr.s_addr = htonl( INADDR_ANY ); if( bind( sock, (struct sockaddr *)&addr, sizeof( addr ) ) < 0 ) { return -1; } } else { t = 0; while( 1 ) { addr.sin_family = AF_INET; *desiredPort = ( random() % 64512 ) + 1024; addr.sin_port = htons( *desiredPort ); addr.sin_addr.s_addr = htonl( INADDR_ANY ); if( bind( sock, (struct sockaddr *)&addr, sizeof( addr ) ) < 0 ) { if( t < NUM_PORT_CREATION_ATTEMPTS ) { ++t; continue; } else { return -1; } } break; } } /* listen on the socket */ if( listen( sock, 8 ) < 0 ) { return -1; } return sock; } ================================================ FILE: ACPCServer/net.h ================================================ /* Copyright (C) 2011 by the Computer Poker Research Group, University of Alberta */ #ifndef _NET_H #define _NET_H #include #include #include #define __STDC_FORMAT_MACROS #include #define READBUF_LEN 4096 #define NUM_PORT_CREATION_ATTEMPTS 10 /* buffered I/O on file descriptors Yes... this is basically re-implementing bits of a standard FILE. Unfortunately, trying to mix timeouts and FILE streams either a) doesn't work, or b) is fairly system specific */ typedef struct { int fd; int bufStart; int bufEnd; char buf[ READBUF_LEN ]; } ReadBuf; /* open a socket to hostname/port returns file descriptor on success, <0 on failure */ int connectTo( char *hostname, uint16_t port ); /* try opening a socket suitable for connecting to if *desiredPort>0, uses specified port, otherwise use a random port returns actual port in *desiredPort returns file descriptor for socket, or -1 on failure */ int getListenSocket( uint16_t *desiredPort ); /* create a read buffer structure returns 0 on failure */ ReadBuf *createReadBuf( int fd ); /* destroy a read buffer - like fdopen, it will close the file descriptor */ void destroyReadBuf( ReadBuf *readBuf ); /* get a newline terminated line and place it as a string in 'line' terminates the string with a 0 character if timeoutMicros is non-negative, do not spend more than that number of microseconds waiting to read data return number of characters read (including newline, excluding 0) 0 on end of file, or -1 on error or timeout */ ssize_t getLine( ReadBuf *readBuf, size_t maxLen, char *line, int64_t timeoutMicros ); #endif ================================================ FILE: ACPCServer/play_match.pl ================================================ #!/usr/bin/perl # Copyright (C) 2011 by the Computer Poker Research Group, University of Alberta use Socket; $hostname = `hostname` or die "could not get hostname"; chomp $hostname; @hostent = gethostbyname( $hostname ); $#hostent >= 4 or die "could not look up $hostname"; $hostip = inet_ntoa( $hostent[ 4 ] ); $#ARGV >= 3 or die "usage: play_match.pl matchName gameDefFile #Hands rngSeed player1name player1exe player2name player2exe ... [options]"; $numPlayers = -1; open FILE, '<', $ARGV[ 1 ] or die "couldn't open game definition $ARGV[ 1 ]"; while( $_ = ) { @_ = split; if( uc( $_[ 0 ] ) eq 'NUMPLAYERS' ) { $numPlayers = $_[ $#_ ]; } } close FILE; $numPlayers > 1 or die "couldn't get number of players from $ARGV[ 1 ]"; $#ARGV >= 3 + $numPlayers * 2 or die "too few players on command line"; pipe STDINREADPIPE, STDINWRITEPIPE or die "couldn't create stdin pipe"; pipe STDOUTREADPIPE, STDOUTWRITEPIPE or die "couldn't create stdout pipe"; $dealerPID = fork(); if( $dealerPID == 0 ) { # we're the child # replace standard in and standard out with pipe close STDINWRITEPIPE; close STDOUTREADPIPE; open STDIN, '<&STDINREADPIPE' or die "can't dup STDIN"; open STDOUT, '>&STDOUTWRITEPIPE' or die "can't dup STDOUT"; open STDERR, ">>$ARGV[ 0 ].err" or die "can't open log file $ARGV[ 0 ].err"; @args = ( "dealer", $ARGV[ 0 ], $ARGV[ 1 ], $ARGV[ 2 ], $ARGV[ 3 ] ); # add names to the arguments for( $p = 0; $p < $numPlayers; ++$p ) { push @args, $ARGV[ 4 + $p * 2 ]; } # add any extra arguments (options?) to the arguments for( $i = 4 + $numPlayers * 2; $i <= $#ARGV; ++$i ) { push @args, $ARGV[ $i ]; } exec { "./dealer" } @args or die "Couldn't run dealer"; } close STDINREADPIPE; close STDOUTWRITEPIPE; $_ = or die "couldn't read port description from dealer"; @_ = split; $#_ + 1 >= $numPlayers or die "couldn't get enough ports from $_"; for( $p = 0; $p < $numPlayers; ++$p ) { $playerPID[ $p ] = fork(); if( $playerPID[ $p ] == 0 ) { # we're the child # log standard out and standard error open STDOUT, ">$ARGV[ 0 ].player$p.std" or die "can't dup player $p STDOUT"; open STDERR, ">$ARGV[ 0 ].player$p.err" or die "can't dup player $p STDERR"; exec { $ARGV[ 4 + $p * 2 + 1 ] } ( $ARGV[ 4 + $p * 2 + 1 ], $hostip, $_[ $p ] ) or die "couldn't run $ARGV[ 4 + $p * 2 + 1 ] for player $p"; } } $_ = ; for( $p = 0; $p < $numPlayers; ++$p ) { waitpid( $playerPID[ $p ], 0 ); } waitpid( $dealerPID, 0 ); $_ or die "couldn't get values from dealer"; print $_; exit( 0 ); ================================================ FILE: ACPCServer/rng.c ================================================ /* A C-program for MT19937, with initialization improved 2002/1/26. Coded by Takuji Nishimura and Makoto Matsumoto. Before using, initialize the state by using init_genrand(seed) or init_by_array(init_key, key_length). Copyright (C) 2011 by the Computer Poker Research Group, University of Alberta This file is a modification of work by Makoto Matsumoto and Takuji Nishimura. That work was provided with the following license: Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, 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. 3. The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. 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 OWNER 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. Any feedback is very welcome. http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space) */ #include "rng.h" /* NOTE changes made on 2005/9/7 by Neil Burch - if you have problems with this code, DON'T complain to Makoto Matsumoto... */ /* initializes mt[RNG_N] with a seed */ void init_genrand( rng_state_t *state, uint32_t s ) { state->mt[0]= s & 0xffffffffUL; for (state->mti=1; state->mtimti++) { state->mt[state->mti] = (1812433253UL * (state->mt[state->mti-1] ^ (state->mt[state->mti-1] >> 30)) + state->mti); /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ /* In the previous versions, MSBs of the seed affect */ /* only MSBs of the array mt[]. */ /* 2002/01/09 modified by Makoto Matsumoto */ } } /* initialize by an array with array-length */ /* init_key is the array for initializing keys */ /* key_length is its length */ /* slight change for C++, 2004/2/26 */ void init_by_array( rng_state_t *state, uint32_t init_key[], int key_length ) { int i, j, k; init_genrand(state, 19650218UL); i=1; j=0; k = (RNG_N>key_length ? RNG_N : key_length); for (; k; k--) { state->mt[i] = ( state->mt[i] ^ ( ( state->mt[i-1] ^ ( state->mt[i-1] >> 30 ) ) * 1664525UL ) ) + init_key[j] + j; /* non linear */ i++; j++; if (i>=RNG_N) { state->mt[0] = state->mt[RNG_N-1]; i=1; } if (j>=key_length) j=0; } for (k=RNG_N-1; k; k--) { state->mt[i] = ( state->mt[i] ^ ( ( state->mt[i-1] ^ ( state->mt[i-1] >> 30 ) ) * 1566083941UL ) ) - i; /* non linear */ state->mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */ i++; if (i>=RNG_N) { state->mt[0] = state->mt[RNG_N-1]; i=1; } } state->mt[0]|= 0x80000000UL; /* MSB is 1; assuring non-zero initial array */ } /* generates a random number on [0,0xffffffff]-interval */ uint32_t genrand_int32( rng_state_t *state ) { uint32_t y; static uint32_t mag01[2]={0x0UL, MATRIX_A}; /* mag01[x] = x * MATRIX_A for x=0,1 */ if (state->mti == RNG_N) { /* generate RNG_N words at one time */ int kk; for (kk=0;kkmt[kk]&UPPER_MASK)|(state->mt[kk+1]&LOWER_MASK); state->mt[kk] = state->mt[kk+RNG_M] ^ (y >> 1) ^ mag01[y & 0x1UL]; } for (;kkmt[kk]&UPPER_MASK)|(state->mt[kk+1]&LOWER_MASK); state->mt[kk] = state->mt[kk+(RNG_M-RNG_N)] ^ (y >> 1) ^ mag01[y & 0x1UL]; } y = (state->mt[RNG_N-1]&UPPER_MASK)|(state->mt[0]&LOWER_MASK); state->mt[RNG_N-1] = state->mt[RNG_M-1] ^ (y >> 1) ^ mag01[y & 0x1UL]; state->mti = 0; } y = state->mt[state->mti++]; /* Tempering */ y ^= (y >> 11); y ^= (y << 7) & 0x9d2c5680UL; y ^= (y << 15) & 0xefc60000UL; y ^= (y >> 18); return y; } ================================================ FILE: ACPCServer/rng.h ================================================ /* Copyright (C) 2011 by the Computer Poker Research Group, University of Alberta Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, All rights reserved. */ #ifndef _RNG_H #define _RNG_H #define __STDC_FORMAT_MACROS #include /* functions included in Takuji Nishimura and Makoto Matsumoto's RNG code */ /* NOTE changes made on 2005/9/7 by Neil Burch - if you have problems with this code, DON'T complain to Makoto Matsumoto... */ /* Period parameters */ #define RNG_N 624 #define RNG_M 397 #define MATRIX_A 0x9908b0dfUL /* constant vector a */ #define UPPER_MASK 0x80000000UL /* most significant w-r bits */ #define LOWER_MASK 0x7fffffffUL /* least significant r bits */ typedef struct { uint32_t mt[ RNG_N ]; int mti; } rng_state_t; /* initializes rng state using an integer seed */ void init_genrand( rng_state_t *state, uint32_t s ); /* initialize by an array with array-length */ /* init_key is the array for initializing keys */ /* key_length is its length */ void init_by_array( rng_state_t *state, uint32_t init_key[], int key_length ); /* all of the functions below can not be called until init_* has been called */ /* generates a random number on [0,0xffffffff]-interval */ uint32_t genrand_int32( rng_state_t *state ); /* generates a random number on [0,0xffffffff]-interval */ #define genrand_int31(state) ((int32_t)(genrand_int32(state)>>1)) /* These real versions are due to Isaku Wada, 2002/01/09 added */ /* generates a random number on [0,1]-real-interval */ #define genrand_real1(state) (genrand_int32(state)*(1.0/4294967295.0)) /* generates a random number on [0,1)-real-interval */ #define genrand_real2(state) (genrand_int32(state)*(1.0/4294967296.0)) /* generates a random number on (0,1)-real-interval */ #define genrand_real3(state) ((((double)genrand_int32(state))+0.5)*(1.0/4294967296.0)) /* generates a random number on [0,1) with 53-bit resolution*/ #define genrand_res53(state) (((genrand_int32(state)>>5)*67108864.0+(genrand_int32(state)>>6))*(1.0/9007199254740992.0)) #endif ================================================ FILE: ACPCServer/sum_values.pl ================================================ #!/usr/bin/perl #Copyright (C) 2014 by the Computer Poker Research Group, University of Alberta while( $_ = <> ) { chomp $_; @_ = split /:/, $_; if( $_[ 0 ] ne "STATE" || @_ != 6 ) { next; } @values = split /\|/, $_[ 4 ]; @players = split /\|/, $_[ 5 ]; $#values == $#players or die "badly formed line: $_"; for( $i = 0; $i < @players; ++$i ) { $totals{ $players[ $i ] } += $values[ $i ]; } } print "SCORE:"; @players = keys( %totals ); for( $i = 0; $i < @players; ++$i ) { if( $i ) { print "|"; } print $totals{ $players[ $i ] }; } for( $i = 0; $i < @players; ++$i ) { if( $i ) { print "|"; } print $players[ $i ]; } print "\n"; ================================================ FILE: ACPCServer/validate_submission.pl ================================================ #!/usr/bin/perl -w # Copyright (C) 2011 by the Computer Poker Research Group, University of Alberta use strict; use POSIX; use File::Path; use File::Copy; use File::Spec; use File::stat; use Getopt::Long; use Pod::Usage; ############### # Main script # ############### # There are several variables that should be set here by the competition # organizer specifying what minimum length of testing is desired my $max_submission_size = 50 * ( 1024 ** 3 ); # 50Gb of disk my $max_avg_millisec_per_hand = 7 * 1000; # 7 sec average per hand my $max_response_millisec = 10 * 60 * 1000; #10 minute max per response my $max_millisec_per_hand = $max_avg_millisec_per_hand * 3000; # Unconstrained my $max_shutdown_secs = 10; # Agents have 10 seconds to shutdown after a match my $min_test_hands = 12000; my $num_test_hands = 12000; my %game_types = ( "submission_2pl" => { game_def => "holdem.limit.2p.reverse_blinds.game", chump => "example_player.limit.2p.sh", num_hands => 3000, big_blind => 10, players => 2 }, "submission_2pn" => { game_def => "holdem.nolimit.2p.reverse_blinds.game", chump => "example_player.nolimit.2p.sh", num_hands => 3000, big_blind => 100, players => 2 }, "submission_3pl" => { game_def => "holdem.limit.3p.game", chump => "example_player.limit.3p.sh", num_hands => 1000, big_blind => 10, players => 3 } # For the possibility of future 3p_nolimit games # "3p_nolimit" => { # gamedef => "holdem.nolimit.3p.game", # chump => "example_player.nolimit.3p.sh", # num_hands => 1000, # big_blind => 100, # players => 3 # } ); my $man; my $help; # Parse command line arguments GetOptions( 'help' => \$help, 'man' => \$man, 'num_hands=i' => \$num_test_hands ) or pod2usage( -exitstatus => 2, -message => "Invalid arguments.\n" . "Use --help or --man for detailed usage." ); pod2usage( -verbose => 1 ) if( $help ); pod2usage( -verbose => 2 ) if( $man ); $#ARGV >= 0 or pod2usage( -exitstatus => 2, -message => "Insufficient arguments.\n" . "Use --help or --man for detailed usage." ); ( $num_test_hands >= $min_test_hands ) or die "Must play at least $min_test_hands hands for validation, stopped"; # Set up file paths my $submission_path = File::Spec->rel2abs( $ARGV[ 0 ] ) ; my ( $submission_volume, $submission_dir, $submission_last_comp ) = File::Spec->splitpath( $submission_path ); my $validation_dir = "$submission_path.validation"; my $validation_path = File::Spec->catfile( $validation_dir, "$submission_last_comp.validation" ); # XXX: Script relies on its relative position with the rest of the code. This # isn't great, but avoids other configuration files specifying this my ( $volume, $directories, $test_script_file ) = File::Spec->splitpath( File::Spec->rel2abs( $0 ) ); my $scripts_dir = File::Spec->catpath( $volume, $directories, '' ); my $server_dir = File::Spec->catpath( $volume, $directories, '' ); my $test_script_path = File::Spec->catfile( $scripts_dir, $test_script_file ); my $dealer_path = File::Spec->catfile( $server_dir, "dealer" ); my $example_player_path = File::Spec->catfile( $server_dir, "example_player" ); my $play_match_path = File::Spec->catfile( $server_dir, "acpc_play_match.pl" ); # Ensure we aren't overwriting an existing validation file ( ! -e $validation_dir ) or die "ERROR: $validation_dir already exists. Move/remove it and rerun\n"; mkpath( $validation_dir ); # Open the validation file for writing my $VALIDATION; open( $VALIDATION, '>', $validation_path ) or die "ERROR: Cannot open validation file $validation_path" . "for writing: $!\n"; # Begin validation tests print $VALIDATION "Validating $submission_path\n\n"; print "Validating $submission_path\n"; print "Validation files will be placed in $validation_dir\n"; print "Matches in progress will have output in $server_dir\n"; print "Check $validation_path for more detailed progress or errors\n\n"; # Test the versions of the files using checksums print $VALIDATION "Gathering file versions (md5sums)...\n\n"; # Ensure the testing script is the right version my $test_script_version_cmd = "md5sum $test_script_path"; print $VALIDATION "Testing script: $test_script_path\n"; print $VALIDATION "$test_script_version_cmd\n"; my $test_script_version_output = `$test_script_version_cmd 2>&1`; print $VALIDATION "$test_script_version_output\n"; if( $? != 0 ) { print $VALIDATION "FAILED.\n"; print $VALIDATION "Validation FAILED.\n"; die "$test_script_version_cmd\n$test_script_version_output\n" . "ERROR: Unable to verify testing script version, stopped"; } # Verify the version of the dealer program my $dealer_version_cmd = "md5sum $dealer_path"; print $VALIDATION "dealer: $dealer_path\n"; print $VALIDATION "$dealer_version_cmd\n"; my $dealer_version_output = `$dealer_version_cmd 2>&1`; print $VALIDATION "$dealer_version_output\n"; if( $? != 0 ) { print $VALIDATION "FAILED.\n"; print $VALIDATION "Validation FAILED.\n"; die "$dealer_version_cmd\n$dealer_version_output\n" . "ERROR: Unable to verify dealer version, stopped"; } # Verify the version of the example_player program my $example_player_version_cmd = "md5sum $example_player_path"; print $VALIDATION "example_player: $example_player_path\n"; print $VALIDATION "$example_player_version_cmd\n"; my $example_player_version_output = `$example_player_version_cmd 2>&1`; print $VALIDATION "$example_player_version_output\n"; if( $? != 0 ) { print $VALIDATION "FAILED.\n"; print $VALIDATION "Validation FAILED.\n"; die "$example_player_version_cmd\n$example_player_version_output\n" . "ERROR: Unable to verify example_player version, stopped"; } # Verify the version of the play_match program my $play_match_version_cmd = "md5sum $play_match_path"; print $VALIDATION "play_match: $play_match_path\n"; print $VALIDATION "$play_match_version_cmd\n"; my $play_match_version_output = `$play_match_version_cmd 2>&1`; print $VALIDATION "$play_match_version_output\n"; if( $? != 0 ) { print $VALIDATION "FAILED.\n"; print $VALIDATION "Validation FAILED.\n"; die "$play_match_version_cmd\n$play_match_version_output\n" . "ERROR: Unable to verify play_match.pl version, stopped"; } # Test for what kind of submission it is (i.e., which game) my $game_label = undef; print $VALIDATION "Checking for game label... "; foreach my $game ( keys( %game_types ) ) { if( $submission_last_comp eq $game ) { $game_label = $game; print $VALIDATION "PASSED. Found $game_label\n\n"; last; } } if( not defined $game_label ) { $, = ", "; # Sets the list printing separator print $VALIDATION " FAILED.\n"; print $VALIDATION "Unrecognized directory name $submission_last_comp\n"; print $VALIDATION "Submission directory must be one of: ", print $VALIDATION keys( %game_types ); print $VALIDATION "\n"; print $VALIDATION "Validation FAILED.\n"; die "ERROR: Unrecognized submission directory name, stopped"; } my $agent_name; if( -d $submission_path ) { $agent_name = ( File::Spec->splitdir( $submission_path ) )[ -1 ]; } else { print $VALIDATION "FAILED.\n"; print $VALIDATION "$submission_path is not a directory.\n"; print $VALIDATION "Validation FAILED.\n"; die "ERROR: Expected a directory, stopped"; } # Check if the directory contains a README file print $VALIDATION "Checking for README file... "; if( -e File::Spec->catfile( $submission_path, "README" ) ) { print $VALIDATION "PASSED.\n\n"; } else { print $VALIDATION "FAILED.\n"; print $VALIDATION "Validation FAILED.\n"; die "ERROR: Missing README file, stopped"; } # Check if the directory contains an executable startme.sh script print $VALIDATION "Checking for executable startme.sh script... "; my $startme_stat = stat( File::Spec->catfile( $submission_path, "startme.sh" ) ); if( $startme_stat ) { my $mode = $startme_stat->mode; my $perm_mask = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; my $perm_str = sprintf( "%03o", $perm_mask & 00777 ); # Test if the file has the same permissions as the mask if( ( $mode & $perm_mask ) == $perm_mask ) { print $VALIDATION "PASSED.\n\n"; } else { print $VALIDATION "FAILED.\n"; print $VALIDATION "startme.sh must have at least $perm_str permissions\n"; print $VALIDATION "Validation FAILED.\n"; die "ERROR: startme.sh must have at least $perm_str permissions, stopped"; } } else { print $VALIDATION "FAILED.\n"; print $VALIDATION "Validation FAILED.\n"; die "ERROR: Unable to stat startme.sh file: $!, stopped"; } # Check if the uncompressed size of the agent submission directory is too big # NOTE: This isn't done is a very platorm independent way. du does not always # have -b available (not to mention other OSes). Should probably just walk the # file structure ourselves. print $VALIDATION "Checking $submission_path for uncompressed file size... "; my $agent_size_cmd = "du -bc $submission_path"; my @agent_size_output = `$agent_size_cmd 2>&1`; # $? is the return value of the backtick command, test it for failure if( $? == 0 ) { my @fields = split( /\W/, $agent_size_output[ -1 ] ); my $agent_size = $fields[ 0 ]; if( $agent_size <= $max_submission_size ) { print $VALIDATION "PASSED. ($agent_size bytes)\n\n"; } else { print $VALIDATION "FAILED.\n"; print $VALIDATION "Validation FAILED.\n"; die "ERROR: $submission_path is too large, stopped"; } } else { $, = ""; # Sets the list printing separator print $VALIDATION "FAILED.\n"; print $VALIDATION "Validation FAILED.\n"; die "$agent_size_cmd\n@agent_size_output\n" . "ERROR: Unable to get agent directory size, stopped"; } # Run the submitted agent for the specified number of trial hands. my $matches_played = 0; my $min_matches = ceil( $num_test_hands / $game_types{ $game_label }{ num_hands } ); my $total_decision_time = 0; my $total_score = 0; my $game_def = $game_types{ $game_label }{ game_def }; my $num_players = $game_types{ $game_label }{ players }; my $num_hands = $game_types{ $game_label }{ num_hands }; my $big_blind = $game_types{ $game_label }{ big_blind }; my $example_player_startme_path = File::Spec->catfile( $server_dir, $game_types{ $game_label }{ chump } ); print "Running matches...\n"; # Change directory to the server directory as play_match relies on relative # paths from that directory chdir( $server_dir ) or die "Unable to chdir to $server_dir: $!, stopped"; # Play all of the matches needed to at least meet the minimum hand count while( $matches_played < $min_matches ) { my ( $match_start_time, $match_end_time ); my $match_name = "$agent_name.test_match.$matches_played"; # TODO?: Should the generated shell scripts be used to run the agent # If not, then files could be left over or we may not detect issues with # using the scripts # Construct the command for running a match my $match_cmd = "$play_match_path $match_name $game_def $num_hands " . "$matches_played $agent_name $submission_path/startme.sh"; # Add the remaining players for( my $player = 1; $player < $num_players; $player++ ) { $match_cmd = $match_cmd . " chump" . ( $num_players > 2 ? "-$player" : "" ) . " $example_player_startme_path" } # Add the server timing options $match_cmd = $match_cmd . " --t_per_hand $max_avg_millisec_per_hand " . "--t_response $max_response_millisec " . "--t_hand $max_millisec_per_hand"; # Give some visual output of progress print "Match ", $matches_played + 1, " of $min_matches: $match_name\n"; print $VALIDATION "========== Match ", $matches_played + 1, " of $min_matches: $match_name ==========\n"; # Ensure no other startme.sh script are running prior to the next match print $VALIDATION "Checking for existing agents... "; my $lingering_agent_cmd = "ps -eo command"; my @lingering_agent_output = `$lingering_agent_cmd 2>&1`; # $? is the return value of the backtick command, test it for failure if( $? == 0 ) { my @lingering_agents = grep { /startme\.sh/ } @lingering_agent_output; if( @lingering_agents == 0 ) { print $VALIDATION "PASSED.\n\n"; } else { $, = "\n"; # Sets the list printing separator print $VALIDATION "FAILED.\n"; print $VALIDATION "Found the following existing agents:\n"; print $VALIDATION @lingering_agents; print $VALIDATION "\n"; print $VALIDATION "Stop other agents before proceeding with testing\n"; print $VALIDATION "Validation FAILED.\n"; die "ERROR: lingering startme.sh found before match $match_name, stopped"; } } else { $, = ""; # Sets the list printing separator print $VALIDATION "FAILED.\n"; print $VALIDATION "Validation FAILED.\n"; die "$lingering_agent_cmd\n@lingering_agent_output\n" . "ERROR: Unable to get processes to check for lingering agents, stopped"; } print $VALIDATION "$match_cmd\n\n"; # Collect timing statistics for the run. $match_start_time = time(); my $match_output = `$match_cmd 2>&1`; $match_end_time = time(); # Move any available output files to the $validation_dir foreach my $file ( glob( "$server_dir/$match_name*" ) ) { move( $file, $validation_dir ); } chomp( $match_output ); print $VALIDATION "$match_output\n"; if( $? != 0 ) { print $VALIDATION "FAILED.\n"; print $VALIDATION "Validation FAILED.\n"; die "$match_cmd\n$match_output\n" . "ERROR: Match $agent_name.test_match.$matches_played FAILED " . "(returned $?).\n" . "Check match log files in validation directory for cause, stopped"; } # Extract the score from the dealer output # e.g., SCORE:-1430|1430:2p_limit_foo|chump my $scores = ( split( /:/, $match_output ) )[ 1 ]; $total_score += ( split( /\|/, $scores ) )[ 0 ]; $matches_played++; $total_decision_time += $match_end_time - $match_start_time; # Verify that the agent is shutting down correctly print $VALIDATION "Checking for lingering agents... "; # Wait a short while to give the agent time to exit after the match sleep( $max_shutdown_secs ); @lingering_agent_output = `$lingering_agent_cmd 2>&1`; # $? is the return value of the backtick command, test it for failure if( $? == 0 ) { my @lingering_agents = grep { /startme\.sh/ } @lingering_agent_output; if( @lingering_agents == 0 ) { print $VALIDATION "PASSED.\n\n"; } else { $, = "\n"; # Sets the list printing separator print $VALIDATION "FAILED.\n"; print $VALIDATION "Found the following lingering agents:\n"; print $VALIDATION @lingering_agents; print $VALIDATION "\n"; print $VALIDATION "Validation FAILED.\n"; die "ERROR: lingering startme.sh found after match $match_name, stopped"; } } else { print $VALIDATION "FAILED.\n"; print $VALIDATION "Validation FAILED.\n"; die "$lingering_agent_cmd\n@lingering_agent_output\n" . "ERROR: Unable to get processes to check for lingering agents, stopped"; } # Check for any warnings in the output print $VALIDATION "Checking $match_name.err output for errors/warnings... "; if( not open( DEALER_STDERR, File::Spec->catfile( $validation_dir, "$match_name.err" ) ) ) { print $VALIDATION "FAILED.\n"; print $VALIDATION "Unable to open $match_name.err for reading: $!\n"; print $VALIDATION "Validation FAILED.\n"; die "ERROR: Cannot open $match_name.err for reading to check for " . "errors in match $match_name: $!, stopped"; } my @match_warnings = grep { /(WARNING|ERROR)/ } ; close DEALER_STDERR; if( @match_warnings == 0 ) { print $VALIDATION "PASSED.\n\n"; } else { $, = ""; # Sets the list printing separator print $VALIDATION "FAILED.\n"; print $VALIDATION "Found the following warnings/errors:\n"; print $VALIDATION @match_warnings; print $VALIDATION "\n"; print $VALIDATION "Validation FAILED.\n"; die "ERROR: dealer warnings detected in match $match_name, stopped"; } } print $VALIDATION "========= Test match statistics ==========\n"; print $VALIDATION "Matches played: $matches_played\n"; print $VALIDATION "Hands played: ", ( $matches_played * $num_hands ), "\n"; print $VALIDATION "Average time per hand (seconds): ", $total_decision_time / ( $matches_played * $num_hands ), "\n"; print $VALIDATION "Average time per match (seconds): ", $total_decision_time / $matches_played, "\n"; my $performance = ( 1000 * $total_score ) / ( $big_blind * $matches_played * $num_hands ); print $VALIDATION "Performance (milli big blinds per hand): ", $performance, "\n"; print $VALIDATION "Checking for winning performance... "; if( $performance >= 0 ) { print $VALIDATION "PASSED.\n\n"; } else { print $VALIDATION "FAILED.\n"; print $VALIDATION "Validation FAILED.\n"; die "ERROR: Submission unable to beat chump in test matches, stopped"; } # Print completion messages print "Validation PASSED.\n"; print $VALIDATION "Validation PASSED.\n"; close $VALIDATION; # POD Documentation for usage message __END__ =head1 NAME B - Checks that the sepcified submission file for the Annual Computer Poker Competition is well formed and works. =head1 SYNOPSIS B [options] submission_dir =head1 DESCRIPTION B - Tests that the specified submission file passes basic sanity checks expected of entries in the Annual Computer Poker Competition. This process could take a long time depending on the size of your submission file and the rate at which it plays. Note that this script expects that your agent: =over 8 =item * Is contained in a single directory named submission_2pl, submission_2pn or submission_3pl =item * Has an startme.sh script with permissions of at least 755 at the root of your directory =item * Has a README file at the root of your directory =item * Is within the size limit =item * Plays only valid actions =item * Cleans itself up once the match is over =item * Beats an agent that plays randomly and does not look at its cards =back Failure on any of these points will cause the validation to fail. Futhermore, ensure that you are B as they will interfere with each other. The required argument is: =over 8 =item B A path to a submission directory that you want to check for basic correctness =back =head1 OPTIONS =over 8 =item B<--help> Print a brief help message and exits. =item B<--man> Prints the manual page and exits. =item B<--num_hands=[positive integer]> Set the number of hands you want to test for. Defaults to the minimum number of hands for validation. Please run as many as you can. =back =cut ================================================ FILE: Data/.gitignore ================================================ Dot/ TrainSamples/ *epoch*.info *epoch*.model ================================================ FILE: Data/Models/NoLimit/preflop-aux/final_cpu.model ================================================ [File too large to display: 62.8 MB] ================================================ FILE: Source/ACPC/Tests/test_parser.lua ================================================ require "ACPC.protocol_to_node" local protocol_to_node = ACPCProtocolToNode() local state = protocol_to_node:parse_state("MATCHSTATE:0:99:cc/r8146:Kh|/As") local debug = 0 ================================================ FILE: Source/ACPC/acpc_game.lua ================================================ --- Handles communication to and from DeepStack using the ACPC protocol. -- -- For details on the ACPC protocol, see -- . -- @classmod acpc_game require 'ACPC.network_communication' require 'ACPC.protocol_to_node' local arguments = require 'Settings.arguments' local constants = require "Settings.constants" --if you want to fake what messages the acpc dealer sends, put them in the following list and uncomment it. local debug_msg = nil--{"MATCHSTATE:0:99::Kh|/", "MATCHSTATE:0:99:cr200:Kh |/", "MATCHSTATE:0:99:cr200:Kh|/Ks"} local ACPCGame = torch.class('ACPCGame') --- Constructor function ACPCGame:__init() self.protocol_to_node = ACPCProtocolToNode() end --- Connects to a specified ACPC server which acts as the dealer. -- -- @param server the server that sends states to DeepStack, which responds -- with actions -- @param port the port to connect on -- @see network_communication.connect function ACPCGame:connect(server, port) if not debug_msg then self.network_communication = ACPCNetworkCommunication() self.network_communication:connect(server, port) end end function ACPCGame:string_to_statenode(msg) local parsed_state = self.protocol_to_node:parse_state(msg) --current player to act is us if parsed_state.acting_player == parsed_state.position then --we should not act since this is an allin situations if parsed_state.bet1 == parsed_state.bet2 and parsed_state.bet1 == arguments.stack then print("Not our turn - all in") --we should act else print("Our turn") self.last_msg = msg --create a tree node from the current state local node = self.protocol_to_node:parsed_state_to_node(parsed_state) return parsed_state, node end --current player to act is the opponent else print("Not our turn") end return nil, nil end --- Receives and parses the next poker situation where DeepStack must act. -- -- Blocks until the server sends a situation where DeepStack acts. -- @return the parsed state representation of the poker situation (see -- @{protocol_to_node.parse_state}) -- @return a public tree node for the state (see -- @{protocol_to_node.parsed_state_to_node}) function ACPCGame:get_next_situation() while true do local msg = nil --1.0 get the message from the dealer if not debug_msg then msg = self.network_communication:get_line() else msg = table.remove(debug_msg, 1) end print("Received acpc dealer message:") print(msg) --2.0 parse the string to our state representation local parsed_state = self.protocol_to_node:parse_state(msg) --3.0 figure out if we should act --current player to act is us if parsed_state.acting_player == parsed_state.position then --we should not act since this is an allin situations if parsed_state.bet1 == parsed_state.bet2 and parsed_state.bet1 == arguments.stack then print("Not our turn - alling") --we should act else print("Our turn") self.last_msg = msg --create a tree node from the current state local node = self.protocol_to_node:parsed_state_to_node(parsed_state) return parsed_state, node end --current player to act is the opponent else print("Not our turn") end end end --- Informs the server that DeepStack is playing a specified action. -- @param adviced_action a table specifying the action chosen by Deepstack, -- with the fields: -- -- * `action`: an element of @{constants.acpc_actions} -- -- * `raise_amount`: the number of chips raised (if `action` is raise) function ACPCGame:play_action(adviced_action) local message = self.protocol_to_node:action_to_message(self.last_msg, adviced_action) print("Sending a message to the acpc dealer:") print(message) if not debug_msg then self.network_communication:send_line(message) end end ================================================ FILE: Source/ACPC/network_communication.lua ================================================ --- Handles network communication for DeepStack. -- -- Requires [luasocket](http://w3.impa.br/~diego/software/luasocket/) -- (can be installed with `luarocks install luasocket`). -- @classmod network_communication local arguments = require "Settings.arguments" local socket = require "socket" local ACPCNetworkCommunication = torch.class('ACPCNetworkCommunication') --- Constructor function ACPCNetworkCommunication:_init() end --- Connects over a network socket. -- -- @param server the server that sends states to DeepStack, and to which -- DeepStack sends actions -- @param port the port to connect on function ACPCNetworkCommunication:connect(server, port) server = server or arguments.acpc_server port = port or arguments.acpc_server_port self.connection = assert(socket.connect(server, port)) self:_handshake() end --- Sends a handshake message to initialize network communication. -- @local function ACPCNetworkCommunication:_handshake() self:send_line("VERSION:2.0.0") end --- Sends a message to the server. -- @param line a string to send to the server function ACPCNetworkCommunication:send_line(line) self.connection:send(line .. '\r\n') end --- Waits for a text message from the server. Blocks until a message is -- received. -- @return the message received function ACPCNetworkCommunication:get_line() local out, status = self.connection:receive('*l') assert(status ~= "closed") return out end --- Ends the network communication. function ACPCNetworkCommunication:close() self.connection:close() end ================================================ FILE: Source/ACPC/protocol_to_node.lua ================================================ --- Converts between DeepStack's internal representation and the ACPC protocol -- used to communicate with the dealer. -- -- For details on the ACPC protocol, see -- . -- @classmod protocol_to_node local arguments = require 'Settings.arguments' local constants = require "Settings.constants" local tools = require 'tools' local card_tools = require 'Game.card_tools' local card_to_string = require "Game.card_to_string_conversion" local ACPCProtocolToNode = torch.class('ACPCProtocolToNode') --- Constructor function ACPCProtocolToNode:_init() end --- Checks if a string starts with a given substring. -- @param string the string to check -- @param start the substring to check as the prefix of `String` -- @return `true` if `start` is a prefix of `string` -- @local function string.starts(string,start) return string.sub(string,1,string.len(start))==start end --- Parses a list of actions from a string representation. -- @param actions a string representing a series of actions in ACPC format -- @return a list of actions, each of which is a table with fields: -- -- * `action`: an element of @{constants.acpc_actions} -- -- * `raise_amount`: the number of chips raised (if `action` is raise) -- @local function ACPCProtocolToNode:_parse_actions(actions) local out = {} local actions_remainder = actions while actions_remainder ~= '' do local parsed_chunk = '' if string.starts(actions_remainder, "c") then table.insert(out, {action = constants.acpc_actions.ccall}) parsed_chunk = "c" elseif string.starts(actions_remainder, "r") then local _ local raise_amount _, _, raise_amount = string.find(actions_remainder, "^r(%d*).*") raise_amount = tonumber(raise_amount) table.insert(out, {action = constants.acpc_actions.raise, raise_amount = raise_amount}) parsed_chunk = "r" .. raise_amount elseif string.starts(actions_remainder, "f") then table.insert(out, {action = constants.acpc_actions.fold}) parsed_chunk = "f" else assert(false) end assert(#parsed_chunk > 0) actions_remainder = string.sub(actions_remainder, #parsed_chunk + 1) end return out end --- Parses a set of parameters that represent a poker state, from a string -- representation. -- @param state a string representation of a poker state in ACPC format -- @return a table of state parameters, containing the fields: -- -- * `position`: the acting player -- -- * `hand_id`: a numerical id for the hand -- -- * `actions`: a list of actions which reached the state, for each -- betting round - each action is a table with fields: -- -- * `action`: an element of @{constants.acpc_actions} -- -- * `raise_amount`: the number of chips raised (if `action` is raise) -- -- * `actions_raw`: a string representation of actions for each betting round -- -- * `board`: a string representation of the board cards -- -- * `hand_p1`: a string representation of the first player's private hand -- -- * `hand_p2`: a string representation of the second player's private hand -- @local function ACPCProtocolToNode:_parse_state(state) --MATCHSTATE:0:99:cc/r8146c/cc/cc:4cTs|Qs9s/9h5d8d/6c/6d local cards local actions local position local hand_id local _ _, _, position, hand_id, actions, cards = string.find(state, "^MATCHSTATE:(%d):(%d*):([^:]*):(.*)") --print('position: ', position) --print('actions: ', actions) --print('cards: ', cards) --cc/r8146c/cc/cc local preflop_actions local flop_actions local turn_actions local river_actions _, _, preflop_actions, flop_actions, turn_actions, river_actions = string.find(actions, "([^/]*)/?([^/]*)/?([^/]*)/?([^/]*)") --print('preflop_actions: ', preflop_actions) --print('flop_actions: ', flop_actions) --print('turn_actions: ', turn_actions) --print('river_actions: ', river_actions) --4cTs|Qs9s/9h5d8d/6c/6d local hand_p1 local hand_p2 local flop = "" local turn = "" local river = "" _, _, hand_p1, hand_p2, flop, turn, river = string.find(cards, "([^|]*)|([^/]*)/?([^/]*)/?([^/]*)/?([^/]*)") --print('hand_p1: ', hand_p1) -- print('hand_p2: ', hand_p2) -- print('flop: ', flop) --print('turn: ', turn) --print('river: ', river) local out = {} out.position = position out.hand_id = hand_id out.actions = {} out.actions[1] = self:_parse_actions(preflop_actions) out.actions[2] = self:_parse_actions(flop_actions) out.actions[3] = self:_parse_actions(turn_actions) out.actions[4] = self:_parse_actions(river_actions) out.actions_raw = {} out.actions_raw[1] = preflop_actions out.actions_raw[2] = flop_actions out.actions_raw[3] = turn_actions out.actions_raw[4] = river_actions out.board = flop .. turn .. river out.hand_p1 = hand_p1 out.hand_p2 = hand_p2 return out end --- Processes a list of actions for a betting round. -- @param actions a list of actions (see @{_parse_actions}) -- @param street the betting round on which the actions takes place -- @param all_actions A list which the actions are appended to. Fields `player`, -- `street`, and `index` are added to each action. -- @local function ACPCProtocolToNode:_convert_actions_street(actions, street, all_actions) local street_first_player = street == 1 and constants.players.P1 or constants.players.P2 for i=1, #actions do local acting_player = -1 if i % 2 == 1 then acting_player = street_first_player else acting_player = 3 - street_first_player end local action = actions[i] action.player = acting_player action.street = street action.index = #all_actions + 1 table.insert(all_actions, action) end end --- Processes all actions. -- @param actions a list of actions for each betting round -- @return a of list actions, processed with @{_convert_actions_street} and -- concatenated -- @local function ACPCProtocolToNode:_convert_actions(actions) local all_actions = {} for street = 1, 4 do self:_convert_actions_street(actions[street], street, all_actions) end return all_actions end --- Further processes a parsed state into a format understandable by DeepStack. -- @param parsed_state a parsed state returned by @{_parse_state} -- @return a table of state parameters, with the fields: -- -- * `position`: which player DeepStack is (element of @{constants.players}) -- -- * `current_street`: the current betting round -- -- * `actions`: a list of actions which reached the state, for each -- betting round - each action is a table with fields: -- -- * `action`: an element of @{constants.acpc_actions} -- -- * `raise_amount`: the number of chips raised (if `action` is raise) -- -- * `actions_raw`: a string representation of actions for each betting round -- -- * `all_actions`: a concatenated list of all of the actions in `actions`, -- with the following fields added: -- -- * `player`: the player who made the action -- -- * `street`: the betting round on which the action was taken -- -- * `index`: the index of the action in `all_actions` -- -- * `board`: a string representation of the board cards -- -- * `hand_id`: a numerical id for the current hand -- -- * `hand_string`: a string representation of DeepStack's private hand -- -- * `hand_id`: a numerical representation of DeepStack's private hand -- -- * `acting_player`: which player is acting (element of @{constants.players}) -- -- * `bet1`, `bet2`: the number of chips committed by each player -- @local function ACPCProtocolToNode:_process_parsed_state(parsed_state) local out = {} --1.0 figure out the current street local current_street = 1 if parsed_state.board ~= '' then current_street = string.len(parsed_state.board) / 2 - 1 end --print('current_street: ', current_street) --2.0 convert actions to player actions local all_actions all_actions = self:_convert_actions(parsed_state.actions) --print('all_actions: ', tools:table_to_string(all_actions)) --3.0 current board local board = parsed_state.board --print('board: ', board) --in protocol 0=SB 1=BB, need to convert to our representation out.position = parsed_state.position + 1 out.current_street = current_street out.actions = parsed_state.actions out.actions_raw = parsed_state.actions_raw out.all_actions = all_actions out.board = board out.hand_number = parsed_state.hand_id if out.position == constants.players.P1 then out.hand_string = parsed_state.hand_p1 else out.hand_string = parsed_state.hand_p2 end out.hand_id = card_tools:string_to_hole_index(out.hand_string) local acting_player = self:_get_acting_player(out) --print('acting_player: ', acting_player) out.acting_player = acting_player --5.0 compute bets local bet1 local bet2 bet1, bet2 = self:_compute_bets(out) assert(bet1 <= bet2) if out.position == constants.players.P1 then out.bet1 = bet1 out.bet2 = bet2 else out.bet1 = bet2 out.bet2 = bet1 end return out end --- Computes the number of chips committed by each player at a state. -- @param processed_state a table containing the fields returned by -- @{_process_parsed_state}, except for `bet1` and `bet2` -- @return the number of chips committed by the first player -- @return the number of chips committed by the second player -- @local function ACPCProtocolToNode:_compute_bets(processed_state) if processed_state.acting_player == -1 then return -1, -1 end local first_p1_action = {action = constants.acpc_actions.raise, raise_amount = arguments.sb, player = constants.players.P1, street = 1} local first_p2_action = {action = constants.acpc_actions.raise, raise_amount = arguments.bb, player = constants.players.P2, street = 1} local last_action = first_p2_action local prev_last_action = first_p1_action local prev_last_bet = first_p2_action for i = 1, #processed_state.all_actions do local action = processed_state.all_actions[i] assert(action.player == constants.players.P1 or action.player == constants.players.P2) prev_last_action = last_action last_action = action if action.action == constants.acpc_actions.raise and i <= (#processed_state.all_actions - 2) then prev_last_bet = action end end local bet1 = nil local bet2 = nil if last_action.action == constants.acpc_actions.raise and prev_last_action.action == constants.acpc_actions.raise then bet1 = prev_last_action.raise_amount bet2 = last_action.raise_amount else if last_action.action == constants.acpc_actions.ccall and prev_last_action.action == constants.acpc_actions.ccall then bet1 = prev_last_bet.raise_amount bet2 = prev_last_bet.raise_amount else --either ccal/raise or raise/ccal situation assert(last_action.action.player ~= prev_last_action.player) --raise/ccall if last_action.action == constants.acpc_actions.ccall then assert(prev_last_action.action == constants.acpc_actions.raise and prev_last_action.raise_amount) bet1 = prev_last_action.raise_amount bet2 = prev_last_action.raise_amount else --call/raise assert(last_action.action == constants.acpc_actions.raise and last_action.raise_amount) bet1 = prev_last_bet.raise_amount bet2 = last_action.raise_amount end end end assert(bet1) assert(bet2) --print("bet1 :", bet1) --print("bet2 :", bet2) return bet1, bet2 end --- Gives the acting player at a given state. -- @param processed_state a table containing the fields returned by -- @{_process_parsed_state}, except for `acting_player`, `bet1`, and `bet2` -- @return the acting player, as defined by @{constants.players} -- @local function ACPCProtocolToNode:_get_acting_player(processed_state) if #processed_state.all_actions == 0 then assert(processed_state.current_street == 1) return constants.players.P1 end local last_action = processed_state.all_actions[#processed_state.all_actions] --has the street changed since the last action? if last_action.street ~= processed_state.current_street then return constants.players.P2 end --is the hand over? if last_action.action == constants.acpc_actions.fold then return -1 end if processed_state.current_street == 4 and #processed_state.actions[4] >= 2 and last_action.action == constants.acpc_actions.ccall then return -1 end --there are some actions on the current street --the acting player is the opponent of the one who made the last action return 3 - last_action.player end --- Turns a string representation of a poker state into a table understandable -- by DeepStack. -- @param state a string representation of a poker state, in ACPC format -- @return a table of state parameters, with the fields: -- -- * `position`: which player DeepStack is (element of @{constants.players}) -- -- * `current_street`: the current betting round -- -- * `actions`: a list of actions which reached the state, for each -- betting round - each action is a table with fields: -- -- * `action`: an element of @{constants.acpc_actions} -- -- * `raise_amount`: the number of chips raised (if `action` is raise) -- -- * `actions_raw`: a string representation of actions for each betting round -- -- * `all_actions`: a concatenated list of all of the actions in `actions`, -- with the following fields added: -- -- * `player`: the player who made the action -- -- * `street`: the betting round on which the action was taken -- -- * `index`: the index of the action in `all_actions` -- -- * `board`: a string representation of the board cards -- -- * `hand_string`: a string representation of DeepStack's private hand -- -- * `hand_id`: a numerical representation of DeepStack's private hand -- -- * `acting_player`: which player is acting (element of @{constants.players}) -- -- * `bet1`, `bet2`: the number of chips committed by each player function ACPCProtocolToNode:parse_state(state) local parsed_state = self:_parse_state(state) local processed_state = self:_process_parsed_state(parsed_state) return processed_state end --- Gets a representation of the public tree node which corresponds to a -- processed state. -- @param processed_state a processed state representation returned by -- @{parse_state} -- @return a table representing a public tree node, with the fields: -- -- * `street`: the current betting round -- -- * `board`: a (possibly empty) vector of board cards -- -- * `current_player`: the currently acting player -- -- * `bets`: a vector of chips committed by each player function ACPCProtocolToNode:parsed_state_to_node(processed_state) local node = {} node.street = processed_state.current_street node.board = card_to_string:string_to_board(processed_state.board) node.current_player = processed_state.acting_player node.bets = arguments.Tensor{processed_state.bet1, processed_state.bet2} if (node.bets[1] == arguments.sb and node.bets[2] == arguments.bb) or (node.bets[1] == arguments.bb and node.bets[2] == arguments.sb) then node.num_bets = 1 else node.num_bets = 0 end return node end --- Converts an action taken by DeepStack into a string representation. -- @param adviced_action the action that DeepStack chooses to take, with fields -- -- * `action`: an element of @{constants.acpc_actions} -- -- * `raise_amount`: the number of chips to raise (if `action` is raise) -- @return a string representation of the action -- @local function ACPCProtocolToNode:_bet_to_protocol_action(adviced_action) if adviced_action.action == constants.acpc_actions.ccall then return "c" elseif adviced_action.action == constants.acpc_actions.fold then return "f" elseif adviced_action.action == constants.acpc_actions.raise then return "r" .. adviced_action.raise_amount else assert(false) end end --- Generates a message to send to the ACPC protocol server, given DeepStack's -- chosen action. -- @param last_message the last state message sent by the server -- @param adviced_action the action that DeepStack chooses to take, with fields -- -- * `action`: an element of @{constants.acpc_actions} -- -- * `raise_amount`: the number of chips to raise (if `action` is raise) -- @return a string messsage in ACPC format to send to the server function ACPCProtocolToNode:action_to_message(last_message, adviced_action) local out = last_message local protocol_action = self:_bet_to_protocol_action(adviced_action) out = out .. ":" .. protocol_action return out end ================================================ FILE: Source/DataGeneration/aux_data_generation.lua ================================================ --- Generates neural net training data by solving random poker situations. -- @module aux_data_generation local arguments = require 'Settings.arguments' local game_settings = require 'Settings.game_settings' local card_generator = require 'DataGeneration.random_card_generator' local card_to_string_conversion = require 'Game.card_to_string_conversion' local constants = require 'Settings.constants' local bucketer = require 'Nn.bucketer' local card_tools = require 'Game.card_tools' require 'Nn.bucket_conversion' require 'Nn.next_round_value_pre' require 'Nn.value_nn' require 'DataGeneration.range_generator' require 'TerminalEquity.terminal_equity' require 'Lookahead.lookahead' require 'Lookahead.resolving' require 'tools' local M = {} --- Generates training files by sampling random poker -- situations and solving them. -- -- @param train_data_count the number of training examples to generate function M:generate_data(train_data_count, filename, street) --valid data generation local timer = torch.Timer() timer:reset() print('Generating auxiliary data ...') self:generate_data_file(train_data_count, filename, street) print('gen time: ' .. timer:time().real) end --- Generates data files containing examples of random poker situations with -- counterfactual values from an associated solution. -- -- Each poker situation is randomly generated using @{range_generator} and -- @{random_card_generator}. For description of neural net input and target -- type, see @{net_builder}. -- -- @param data_count the number of examples to generate -- @param file_name the prefix of the files where the data is saved (appended -- with `.inputs`, `.targets`, and `.mask`). function M:generate_data_file(data_count, file_name, street) local range_generator = RangeGenerator() local batch_size = arguments.gen_batch_size assert(data_count % batch_size == 0, 'data count has to be divisible by the batch size') local batch_count = data_count / batch_size local target_size = game_settings.hand_count * constants.players_count local targets = arguments.Tensor(batch_size, target_size) local input_size = game_settings.hand_count * constants.players_count + 1 local inputs = arguments.Tensor(batch_size, input_size) local mask = arguments.Tensor(batch_size, game_settings.hand_count):zero() local board = arguments.Tensor() local te = TerminalEquity() te:set_board(board) range_generator:set_board(te, board) local bucket_conversion = BucketConversion() bucket_conversion:set_board(board) local next_round = NextRoundValuePre(ValueNn(street), nil, board) local bucket_count = bucketer:get_bucket_count(street) local bucketed_target_size = bucket_count * constants.players_count local bucketed_input_size = bucket_count * constants.players_count + 1 local input_batch = arguments.Tensor(arguments.gen_batch_size, bucketed_input_size) local target_batch = arguments.Tensor(arguments.gen_batch_size, bucketed_target_size) local raw_indexes = {{1, game_settings.hand_count}, {game_settings.hand_count + 1, game_settings.hand_count * 2}} local bucket_indexes = {{1, bucket_count}, {bucket_count + 1, bucket_count * 2}} for batch = 1, batch_count do local timer = torch.Timer() timer:reset() --generating ranges local ranges = arguments.Tensor(constants.players_count, batch_size, game_settings.hand_count) for player = 1, constants.players_count do range_generator:generate_range(ranges[player]) end --generating pot sizes between ante and stack - 0.1 local min_pot = {} local max_pot = {} if game_settings.nl then min_pot = {100,200,400,2000,6000} max_pot = {100,400,2000,6000,18000} else if street == 4 then min_pot = {2,12,24} max_pot = {12,24,48} elseif street == 3 then min_pot = {2,8,16} max_pot = {8,16,24} elseif street == 2 then min_pot = {2,4,6} max_pot = {4,6,10} end end local pot_range = {} for i = 1,#min_pot do pot_range[i] = max_pot[i] - min_pot[i] end local random_pot_cats = torch.rand(arguments.gen_batch_size):mul(#min_pot):add(1):floor() local random_pot_sizes = torch.rand(arguments.gen_batch_size,1) for i = 1, arguments.gen_batch_size do random_pot_sizes[i][1] = random_pot_sizes[i][1] * pot_range[random_pot_cats[i]] random_pot_sizes[i][1] = random_pot_sizes[i][1] + min_pot[random_pot_cats[i]] end --pot features are pot sizes normalized between (ante/stack,1) local pot_size_features = game_settings.nl and random_pot_sizes:clone():mul(1/arguments.stack) or random_pot_sizes:clone():mul(1/max_pot[3]) --translating ranges to features local pot_feature_index = -1 inputs[{{}, pot_feature_index}]:copy(pot_size_features) input_batch[{{}, pot_feature_index}]:copy(pot_size_features) local player_indexes = {{1, game_settings.hand_count}, {game_settings.hand_count + 1, game_settings.hand_count * 2}} for player = 1, constants.players_count do local player_index = player_indexes[player] inputs[{{}, player_index}]:copy(ranges[player]) end for i = 1, arguments.gen_batch_size do local next_street_boxes_inputs = arguments.Tensor(1, constants.players_count, game_settings.hand_count):zero() local next_street_boxes_outputs = next_street_boxes_inputs:clone() for player = 1, constants.players_count do local player_index = player_indexes[player] next_street_boxes_inputs[{{},player,{}}]:copy(inputs[{i,player_index}]) end next_round:start_computation(random_pot_sizes[i], 1) next_round:get_value(next_street_boxes_inputs, next_street_boxes_outputs) for player = 1, constants.players_count do local player_index = player_indexes[player] targets[{i, player_index}]:copy(next_street_boxes_outputs[{{},player,{}}]) end end for player = 1, constants.players_count do local player_index = raw_indexes[player] local bucket_index = bucket_indexes[player] bucket_conversion:card_range_to_bucket_range(inputs[{{},player_index}],input_batch[{{}, bucket_index}]) end for player = 1, constants.players_count do local player_index = raw_indexes[player] local bucket_index = bucket_indexes[player] bucket_conversion:hand_cfvs_to_bucket_cfvs( inputs[{{}, player_index}], targets[{{}, player_index}], input_batch[{{}, bucket_index}], target_batch[{{}, bucket_index}]) end local basename = file_name .. '-' .. batch local train_folder = "xxx/" if game_settings.nl then train_folder = "NoLimit/" else train_folder = "Limit/" end torch.save(arguments.data_path .. train_folder .. basename .. '.inputs', input_batch:float()) torch.save(arguments.data_path .. train_folder .. basename .. '.targets', target_batch:float()) end end return M ================================================ FILE: Source/DataGeneration/data_generation.lua ================================================ --- Generates neural net training data by solving random poker situations. -- @module data_generation local arguments = require 'Settings.arguments' local game_settings = require 'Settings.game_settings' local card_generator = require 'DataGeneration.random_card_generator' local card_to_string_conversion = require 'Game.card_to_string_conversion' local constants = require 'Settings.constants' require 'DataGeneration.range_generator' require 'TerminalEquity.terminal_equity' require 'Lookahead.lookahead' require 'Lookahead.resolving' local M = {} --- Generates training files by sampling random poker -- situations and solving them. -- -- @param train_data_count the number of training examples to generate function M:generate_data(train_data_count, filename, street) --valid data generation local timer = torch.Timer() timer:reset() print('Generating data ...') self:generate_data_file(train_data_count, filename, street) print('gen time: ' .. timer:time().real) end --- Generates data files containing examples of random poker situations with -- counterfactual values from an associated solution. -- -- Each poker situation is randomly generated using @{range_generator} and -- @{random_card_generator}. For description of neural net input and target -- type, see @{net_builder}. -- -- @param data_count the number of examples to generate -- @param file_name the prefix of the files where the data is saved (appended -- with `.inputs`, `.targets`, and `.mask`). function M:generate_data_file(data_count, file_name, street) local range_generator = RangeGenerator() local batch_size = arguments.gen_batch_size assert(data_count % batch_size == 0, 'data count has to be divisible by the batch size') local batch_count = data_count / batch_size local target_size = game_settings.hand_count * constants.players_count local targets = arguments.Tensor(batch_size, target_size) local input_size = game_settings.hand_count * constants.players_count + 1 local inputs = arguments.Tensor(batch_size, input_size) local mask = arguments.Tensor(batch_size, game_settings.hand_count):zero() local te = TerminalEquity() for batch = 1, batch_count do local timer = torch.Timer() timer:reset() local board = card_generator:generate_cards(game_settings.board_card_count[street]) local board_string = card_to_string_conversion:cards_to_string(board) te:set_board(board) range_generator:set_board(te, board) print('terminal time: ' .. timer:time().real) --generating ranges local ranges = arguments.Tensor(constants.players_count, batch_size, game_settings.hand_count) for player = 1, constants.players_count do range_generator:generate_range(ranges[player]) end --generating pot sizes between ante and stack - 0.1 local min_pot = {} local max_pot = {} if game_settings.nl then min_pot = {100,200,400,2000,6000} max_pot = {100,400,2000,6000,18000} else if street == 4 then min_pot = {2,12,24} max_pot = {12,24,48} elseif street == 3 then min_pot = {2,8,16} max_pot = {8,16,24} elseif street == 2 then min_pot = {2,4,6} max_pot = {4,6,10} end end local pot_range = {} for i = 1,#min_pot do pot_range[i] = max_pot[i] - min_pot[i] end local random_pot_cat = torch.rand(1):mul(#min_pot):add(1):floor()[1] local random_pot_size = torch.rand(1)[1] random_pot_size = random_pot_size * pot_range[random_pot_cat] random_pot_size = random_pot_size + min_pot[random_pot_cat] random_pot_size = math.floor(random_pot_size) --pot features are pot sizes normalized between (ante/stack,1) local pot_size_feature = game_settings.nl and (random_pot_size / arguments.stack) or (random_pot_size / max_pot[3]) --translating ranges to features local pot_feature_index = -1 inputs[{{}, pot_feature_index}]:fill(pot_size_feature) local player_indexes = {{1, game_settings.hand_count}, {game_settings.hand_count + 1, game_settings.hand_count * 2}} for player = 1, constants.players_count do local player_index = player_indexes[player] inputs[{{}, player_index}]:copy(ranges[player]) end --computaton of values using re-solving local values = arguments.Tensor(batch_size, constants.players_count, game_settings.hand_count) local pot_size = random_pot_size print(board_string .. ' ' .. batch .. ' ' .. pot_size) local resolving = Resolving(te) local current_node = {} current_node.board = board current_node.street = street current_node.num_bets = 0 current_node.current_player = street == 1 and constants.players.P1 or constants.players.P2 --TODO support preflop bets current_node.bets = arguments.Tensor{pot_size, pot_size} local p1_range = ranges[1] local p2_range = ranges[2] resolving:resolve_first_node(current_node, p1_range, p2_range) local root_values = resolving:get_root_cfv_both_players() root_values:mul(1/pot_size) values:copy(root_values) for player = 1, constants.players_count do local player_index = player_indexes[player] targets[{{}, player_index}]:copy(values[{{},player,{}}]) end local basename = file_name .. '-' .. board_string .. '-' .. batch local train_folder = "xxx/" if game_settings.nl then train_folder = "NoLimit/" else train_folder = "Limit/" end torch.save(arguments.data_path .. train_folder .. basename .. '.inputs', inputs:float()) torch.save(arguments.data_path .. train_folder .. basename .. '.targets', targets:float()) end end return M ================================================ FILE: Source/DataGeneration/main_aux_data_generation.lua ================================================ --- Script that generates training and validation files. -- @see data_generation -- @script main_data_generation local filename = os.time() if #arg == 0 then print("Please specify the street. 1 = preflop, 4 = river") return end local arguments = require 'Settings.arguments' local aux_data_generation = require 'DataGeneration.aux_data_generation' aux_data_generation:generate_data(arguments.train_data_count, filename, tonumber(arg[1])) ================================================ FILE: Source/DataGeneration/main_data_generation.lua ================================================ --- Script that generates training and validation files. -- @see data_generation -- @script main_data_generation local filename = os.time() if #arg == 0 then print("Please specify the street. 1 = preflop, 4 = river") return end local arguments = require 'Settings.arguments' local data_generation = require 'DataGeneration.data_generation' data_generation:generate_data(arguments.train_data_count, filename, tonumber(arg[1])) ================================================ FILE: Source/DataGeneration/random_card_generator.lua ================================================ --- Samples random card combinations. -- @module random_card_generator require "torch" local M = {} local game_settings = require 'Settings.game_settings' local arguments = require 'Settings.arguments' --- Samples a random set of cards. -- -- Each subset of the deck of the correct size is sampled with -- uniform probability. -- -- @param count the number of cards to sample -- @return a vector of cards, represented numerically function M:generate_cards( count ) --marking all used cards local used_cards = torch.ByteTensor(game_settings.card_count):zero() local out = arguments.Tensor(count) --counter for generated cards local generated_cards_count = 0 while(generated_cards_count < count) do local card = torch.random(1, game_settings.card_count) if ( used_cards[card] == 0 ) then generated_cards_count = generated_cards_count + 1 out[generated_cards_count] = card used_cards[card] = 1 end end return out end return M ================================================ FILE: Source/DataGeneration/range_generator.lua ================================================ --- Samples random probability vectors for use as player ranges. -- @classmod range_generator require "math" require "torch" local arguments = require 'Settings.arguments' local game_settings = require 'Settings.game_settings' local evaluator = require 'Game.Evaluation.evaluator' local card_tools = require 'Game.card_tools' local card_to_string = require 'Game.card_to_string_conversion' local RangeGenerator = torch.class('RangeGenerator') --- Recursively samples a section of the range vector. -- @param cards an NxJ section of the range tensor, where N is the batch size -- and J is the length of the range sub-vector -- @param mass a vector of remaining probability mass for each batch member -- @see generate_range -- @local function RangeGenerator:_generate_recursion(cards, mass) local batch_size = cards:size(1) assert(mass:size(1) == batch_size) --we terminate recursion at size of 1 local card_count = cards:size(2) if card_count == 1 then cards:copy(mass) else local rand = torch.rand(batch_size) if arguments.gpu then rand = rand:cuda() end local mass1 = mass:clone():cmul(rand) mass1[torch.lt(mass1, 0.00001)] = 0 mass1[torch.gt(mass1, 0.99999)] = 1 local mass2 = mass -mass1 local halfSize = card_count/2 --if the tensor contains an odd number of cards, randomize which way the --middle card goes if halfSize % 1 ~= 0 then halfSize = halfSize - 0.5 halfSize = halfSize + torch.random(0,1) end self:_generate_recursion(cards[{{}, {1, halfSize}}], mass1) self:_generate_recursion(cards[{{}, {halfSize +1, -1}}], mass2) end end --- Samples a batch of ranges with hands sorted by strength on the board. -- @param range a NxK tensor in which to store the sampled ranges, where N is -- the number of ranges to sample and K is the range size -- @see generate_range -- @local function RangeGenerator:_generate_sorted_range(range) local batch_size = range:size(1) self:_generate_recursion(range, arguments.Tensor(batch_size):fill(1)) end --- Sets the (possibly empty) board cards to sample ranges with. -- -- The sampled ranges will assign 0 probability to any private hands that -- share any cards with the board. -- -- @param board a possibly empty vector of board cards function RangeGenerator:set_board(te, board) local hand_strengths = arguments.Tensor(game_settings.hand_count) for i=1,game_settings.hand_count do hand_strengths[i] = i end if board:dim() == 0 then hand_strengths = te:get_hand_strengths():squeeze() elseif board:size(1) == 5 then hand_strengths = evaluator:batch_eval(board) else hand_strengths = te:get_hand_strengths():squeeze() end local possible_hand_indexes = card_tools:get_possible_hand_indexes(board) self.possible_hands_count = possible_hand_indexes:sum(1)[1] self.possible_hands_mask = possible_hand_indexes:view(1, -1) if not arguments.gpu then self.possible_hands_mask = self.possible_hands_mask:byte() else self.possible_hands_mask = self.possible_hands_mask:cudaByte() end local non_colliding_strengths = arguments.Tensor(self.possible_hands_count) non_colliding_strengths:maskedSelect(hand_strengths, self.possible_hands_mask) local order _, order = non_colliding_strengths:sort() _, self.reverse_order = order:sort() self.reverse_order = self.reverse_order:view(1, -1):long() self.reordered_range = arguments.Tensor() self.sorted_range =arguments.Tensor() end --- Samples a batch of random range vectors. -- -- Each vector is sampled indepently by randomly splitting the probability -- mass between the bottom half and the top half of the range, and then -- recursing on the two halfs. -- -- @{set_board} must be called first. -- -- @param range a NxK tensor in which to store the sampled ranges, where N is -- the number of ranges to sample and K is the range size function RangeGenerator:generate_range(range) local batch_size = range:size(1) self.sorted_range:resize(batch_size, self.possible_hands_count) self:_generate_sorted_range(self.sorted_range, self.possible_hands_count) --we have to reorder the the range back to undo the sort by strength local index = self.reverse_order:expandAs(self.sorted_range) if arguments.gpu then index = index:cuda() end self.reordered_range = self.sorted_range:gather(2, index) range:zero() range:maskedCopy(self.possible_hands_mask:expandAs(range), self.reordered_range) end ================================================ FILE: Source/Game/Evaluation/evaluator.lua ================================================ --- Evaluates hand strength in Leduc Hold'em and variants. -- -- Works with hands which contain two or three cards, but assumes that -- the deck contains no more than two cards of each rank (so three-of-a-kind -- is not a possible hand). -- -- Hand strength is given as a numerical value, where a lower strength means -- a stronger hand: high pair < low pair < high card < low card -- @module evaluator require 'torch' require 'math' local game_settings = require 'Settings.game_settings' local card_to_string = require 'Game.card_to_string_conversion' local card_tools = require 'Game.card_tools' local arguments = require 'Settings.arguments' local M = {_texas_lookup = nil} function M:_init() self._idx_to_cards = arguments.Tensor(game_settings.hand_count, game_settings.hand_card_count) for card1 = 1, game_settings.card_count do for card2 = card1 + 1, game_settings.card_count do local idx = card_tools:get_hole_index({card1,card2}) --print(card_to_string:card_to_string(card1) .. card_to_string:card_to_string(card2) .. ': ' .. idx) self._idx_to_cards[idx][1] = card1 self._idx_to_cards[idx][2] = card2 end end if self._texas_lookup == nil then local f = assert(io.open("./Game/Evaluation/HandRanks.dat", "rb")) local data = f:read("*all") self._texas_lookup = arguments.Tensor(string.len(data) / 4):fill(0):long() if arguments.gpu then self._texas_lookup = self._texas_lookup:cudaLong() end for i = 1, string.len(data), 4 do local num = 0 for j = i,i+3 do num = num + data:byte(j) * (2 ^ ((j - i) * 8)) end self._texas_lookup[(i - 1) / 4 + 1] = num end f:close() end end M:_init() --- Gives a strength representation for a hand containing two cards. -- @param hand_ranks the rank of each card in the hand -- @return the strength value of the hand -- @local function M:evaluate_two_card_hand(hand_ranks) --check for the pair local hand_value = nil if hand_ranks[1] == hand_ranks[2] then --hand is a pair hand_value = hand_ranks[1] else --hand is a high card hand_value = hand_ranks[1] * game_settings.rank_count + hand_ranks[2] end return hand_value end --- Gives a strength representation for a hand containing three cards. -- @param hand_ranks the rank of each card in the hand -- @return the strength value of the hand -- @local function M:evaluate_three_card_hand(hand_ranks) local hand_value = nil --check for the pair if hand_ranks[1] == hand_ranks[2] then --paired hand, value of the pair goes first, value of the kicker goes second hand_value = hand_ranks[1] * game_settings.rank_count + hand_ranks[3] elseif hand_ranks[2] == hand_ranks[3] then --paired hand, value of the pair goes first, value of the kicker goes second hand_value = hand_ranks[2] * game_settings.rank_count + hand_ranks[1] else --hand is a high card hand_value = hand_ranks[1] * game_settings.rank_count * game_settings.rank_count + hand_ranks[2] * game_settings.rank_count + hand_ranks[3] end return hand_value end --- Gives a strength representation for a texas hold'em hand containing seven cards. -- @param hand_ranks the rank of each card in the hand -- @return the strength value of the hand -- @local function M:evaluate_seven_card_hand(hand) local rank = self._texas_lookup[54 + (hand[1] - 1) + 1] for c = 2, hand:size(1) do rank = self._texas_lookup[1 + rank + (hand[c] - 1) + 1] end return -rank end --- Gives a strength representation for a two or three card hand. -- @param hand a vector of two or three cards -- @param[opt] impossible_hand_value the value to return if the hand is invalid -- @return the strength value of the hand, or `impossible_hand_value` if the -- hand is invalid function M:evaluate(hand, impossible_hand_value) assert(hand:max() <= game_settings.card_count and hand:min() > 0, 'hand does not correspond to any cards' ) impossible_hand_value = impossible_hand_value or -1 if not card_tools:hand_is_possible(hand) then return impossible_hand_value end --we are not interested in the hand suit - we will use ranks instead of cards if hand:size(1) == 2 then local hand_ranks = hand:clone() for i = 1, hand_ranks:size(1) do hand_ranks[i] = card_to_string:card_to_rank(hand_ranks[i]) end hand_ranks = hand_ranks:sort() return self:evaluate_two_card_hand(hand_ranks) elseif hand:size(1) == 3 then local hand_ranks = hand:clone() for i = 1, hand_ranks:size(1) do hand_ranks[i] = card_to_string:card_to_rank(hand_ranks[i]) end hand_ranks = hand_ranks:sort() return self:evaluate_three_card_hand(hand_ranks) elseif hand:size(1) == 7 then return self:evaluate_seven_card_hand(hand) else assert(false, 'unsupported size of hand!' ) end end function M:evaluate_fast(hands) local ret = self._texas_lookup:index(1,torch.add(hands[{{},1}],54)) for c = 2, hands:size(2) do ret = self._texas_lookup:index(1, torch.add(hands[{{},c}],ret):add(1)) end ret:cmul(card_tools:get_possible_hands_mask(hands)) ret:mul(-1) return ret end --- Gives strength representations for all private hands on the given board. -- @param board a possibly empty vector of board cards -- @param impossible_hand_value the value to assign to hands which are invalid -- on the board -- @return a vector containing a strength value or `impossible_hand_value` for -- every private hand function M:batch_eval(board, impossible_hand_value) local hand_values = arguments.Tensor(game_settings.hand_count):fill(-1) if board:dim() == 0 then -- kuhn poker for hand = 1, game_settings.card_count do hand_values[hand] = math.floor((hand - 1 ) / game_settings.suit_count ) + 1 end else local board_size = board:size(1) assert(board_size == 1 or board_size == 2 or board_size == 5, 'Incorrect board size for Leduc' ) local whole_hand = arguments.Tensor(board_size + game_settings.hand_card_count) whole_hand[{{1, -1 - game_settings.hand_card_count}}]:copy(board) if game_settings.hand_card_count == 1 then for card = 1, game_settings.card_count do whole_hand[-1] = card hand_values[card] = self:evaluate(whole_hand, impossible_hand_value) end elseif game_settings.hand_card_count == 2 then for card1 = 1, game_settings.card_count do for card2 = card1 + 1, game_settings.card_count do whole_hand[-2] = card1 whole_hand[-1] = card2 local idx = card_tools:get_hole_index({card1,card2}) --print(card_to_string:card_to_string(card1) .. card_to_string:card_to_string(card2) .. ': ' .. idx) hand_values[idx] = self:evaluate(whole_hand, impossible_hand_value) end end else assert(false, "unsupported hand_card_count: " .. game_settings.hand_card_count) end end return hand_values end function M:batch_eval_fast(board) if board:dim() == 0 then -- kuhn poker return nil elseif board:dim() == 2 then local batch_size = board:size(1) local hands = arguments.Tensor(batch_size, game_settings.hand_count, board:size(2) + game_settings.hand_card_count):long() if arguments.gpu then hands = hands:cudaLong() end hands[{{},{},{1,board:size(2)}}]:copy( board:view(batch_size, 1, board:size(2)) :expand(batch_size, game_settings.hand_count, board:size(2))) hands[{{},{},{-2,-1}}]:copy( self._idx_to_cards:view(1, game_settings.hand_count, game_settings.hand_card_count) :expand(batch_size, game_settings.hand_count, game_settings.hand_card_count)) return self:evaluate_fast(hands:view(-1, board:size(2) + game_settings.hand_card_count)):view(batch_size, game_settings.hand_count) elseif board:dim() == 1 then local hands = arguments.Tensor(game_settings.hand_count, board:size(1) + game_settings.hand_card_count):long() if arguments.gpu then hands = hands:cudaLong() end hands[{{},{1,board:size(1)}}]:copy(board:view(1,board:size(1)):expand(game_settings.hand_count,board:size(1))) hands[{{},{-2,-1}}]:copy(self._idx_to_cards) return self:evaluate_fast(hands) else assert(false, "weird board dim " .. board:dim()) end end return M ================================================ FILE: Source/Game/bet_sizing.lua ================================================ --- Gives allowed bets during a game. -- Bets are restricted to be from a list of predefined fractions of the pot. -- @classmod bet_sizing require 'math' local arguments = require 'Settings.arguments' local BetSizing = torch.class('BetSizing') --- Constructor -- @param pot_fractions a list of fractions of the pot which are allowed -- as bets, sorted in ascending order function BetSizing:__init(pot_fractions) pot_fractions = pot_fractions or {1} self.pot_fractions = pot_fractions end --- Gives the bets which are legal at a game state. -- @param node a representation of the current game state, with fields: -- -- * `bets`: the number of chips currently committed by each player -- -- * `current_player`: the currently acting player -- @return an Nx2 tensor where N is the number of new possible game states, -- containing N sets of new commitment levels for each player function BetSizing:get_possible_bets(node) local current_player = node.current_player assert(current_player == 1 or current_player == 2, 'Wrong player for bet size computation') local opponent = 3 - node.current_player local opponent_bet = node.bets[opponent] assert(node.bets[current_player] <= opponent_bet) --compute min possible raise size local max_raise_size = arguments.stack - opponent_bet local min_raise_size = opponent_bet - node.bets[current_player] min_raise_size = math.max(min_raise_size, arguments.ante) min_raise_size = math.min(max_raise_size, min_raise_size) if min_raise_size == 0 then return arguments.Tensor() elseif min_raise_size == max_raise_size then local out = arguments.Tensor(1,2):fill(opponent_bet) out[1][current_player] = opponent_bet + min_raise_size return out else --iterate through all bets and check if they are possible local fractions = {} if node.num_bets == 0 then fractions = self.pot_fractions[1] elseif node.num_bets == 1 then fractions = self.pot_fractions[2] else fractions = self.pot_fractions[3] end local max_possible_bets_count = #fractions + 1 --we can always go allin local out = arguments.Tensor(max_possible_bets_count,2):fill(opponent_bet) --take pot size after opponent bet is called local pot = opponent_bet * 2 local used_bets_count = 0; --try all pot fractions bet and see if we can use them for i = 1, #fractions do local raise_size = pot * fractions[i] if raise_size >= min_raise_size and raise_size < max_raise_size then used_bets_count = used_bets_count + 1 out[{used_bets_count, current_player}] = opponent_bet + raise_size end end --adding allin used_bets_count = used_bets_count + 1 assert(used_bets_count <= max_possible_bets_count) out[{used_bets_count, current_player}] = opponent_bet + max_raise_size return out[{{1, used_bets_count}, {}}] end end ================================================ FILE: Source/Game/card_to_string_conversion.lua ================================================ --- Converts between string and numeric representations of cards. -- @module card_to_string_conversion require "string" require "torch" local arguments = require 'Settings.arguments' local game_settings = require 'Settings.game_settings' local M = {}; ---All possible card suits - only the first 2 are used in Leduc Hold'em. M.suit_table = {'c', 'd', 'h', 's'} ---All possible card ranks - only the first 3-4 are used in Leduc Hold'em and -- variants. M.rank_table = {'2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A'} --- Gets the suit of a card. -- @param card the numeric representation of the card -- @return the index of the suit function M:card_to_suit(card) return (card - 1) % game_settings.suit_count + 1 end --- Gets the rank of a card. -- @param card the numeric representation of the card -- @return the index of the rank function M:card_to_rank(card) return torch.floor((card - 1) / game_settings.suit_count ) + 1 end; --- Holds the string representation for every possible card, indexed by its -- numeric representation. M.card_to_string_table ={} for card = 1, game_settings.card_count do local rank_name = M.rank_table[M:card_to_rank(card)] local suit_name = M.suit_table[M:card_to_suit(card)] M.card_to_string_table[card] = rank_name .. suit_name end --- Holds the numeric representation for every possible card, indexed by its -- string representation. M.string_to_card_table = {} for card = 1, game_settings.card_count do M.string_to_card_table[M.card_to_string_table[card]] = card end --- Converts a card's numeric representation to its string representation. -- @param card the numeric representation of a card -- @return the string representation of the card function M:card_to_string(card) assert(card > 0 and card <= game_settings.card_count ) return M.card_to_string_table[card] end --- Converts several cards' numeric representations to their string -- representations. -- @param cards a vector of numeric representations of cards -- @return a string containing each card's string representation, concatenated function M:cards_to_string(cards) if cards:dim() == 0 then return "" end local out = "" for card =1, cards:size(1) do out = out .. self:card_to_string(cards[card]) end return out end --- Converts a card's string representation to its numeric representation. -- @param card_string the string representation of a card -- @return the numeric representation of the card function M:string_to_card(card_string) local card = M.string_to_card_table[card_string] assert(card > 0 and card <= game_settings.card_count ) return card end --- Converts a string representing zero or more board cards to a -- vector of numeric representations. -- @param card_string either the empty string or a string representation of a -- card -- @return either an empty tensor or a tensor containing the numeric -- representation of the card function M:string_to_board(card_string) assert(card_string) if card_string == '' then return arguments.Tensor{} end local num_cards = string.len(card_string) / 2 board = arguments.Tensor(num_cards) for i = 1, num_cards do board[i] = self:string_to_card(string.sub(card_string, i * 2 - 1, i * 2)) end return board end return M ================================================ FILE: Source/Game/card_tools.lua ================================================ --- A set of tools for basic operations on cards and sets of cards. -- -- Several of the functions deal with "range vectors", which are probability -- vectors over the set of possible private hands. For Leduc Hold'em, -- each private hand consists of one card. -- @module card_tools local game_settings = require 'Settings.game_settings' local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local tools = require 'tools' local card_to_string_conversion = require 'Game.card_to_string_conversion' local M = {} --- Gives whether a set of cards is valid. -- @param hand a vector of cards -- @return `true` if the tensor contains valid cards and no card is repeated function M:hand_is_possible(hand) assert(hand:min() > 0 and hand:max() <= game_settings.card_count, 'Illegal cards in hand' ) local used_cards = torch.FloatTensor(game_settings.card_count):fill(0); for i = 1, hand:size(1) do used_cards[hand[i]] = used_cards[hand[i]] + 1 end return used_cards:max() < 2 end function M:get_possible_hands_mask(hands) local used_cards = arguments.Tensor(hands:size(1), game_settings.card_count):fill(0) used_cards:scatterAdd(2,hands,arguments.Tensor(hands:size(1), 7):fill(1)) local ret = torch.le(torch.max(used_cards, 2), 1):long() if arguments.gpu then ret = ret:cudaLong() end return ret end --- Gives the private hands which are valid with a given board. -- @param board a possibly empty vector of board cards -- @return a vector with an entry for every possible hand (private card), which -- is `1` if the hand shares no cards with the board and `0` otherwise -- TODO generalize function M:get_possible_hand_indexes(board) local out = arguments.Tensor(game_settings.hand_count):fill(0) if board:dim() == 0 then out:fill(1) return out end local used = {} for i = 1, board:size(1) do used[board[i]] = 1 end for card1 = 1, game_settings.card_count do if not used[card1] then for card2 = card1+1, game_settings.card_count do if not used[card2] then out[M:get_hole_index({card1,card2})] = 1 end end end end return out end --- Gives the private hands which are invalid with a given board. -- @param board a possibly empty vector of board cards -- @return a vector with an entry for every possible hand (private card), which -- is `1` if the hand shares at least one card with the board and `0` otherwise function M:get_impossible_hand_indexes(board) local out = self:get_possible_hand_indexes(board) out:add(-1) out:mul(-1) return out end --- Gives a range vector that has uniform probability on each hand which is -- valid with a given board. -- @param board a possibly empty vector of board cards -- @return a range vector where invalid hands have 0 probability and valid -- hands have uniform probability function M:get_uniform_range(board) local out = self:get_possible_hand_indexes(board) out:div(out:sum()) return out end function M:get_file_range(filename) local out = arguments.Tensor(game_settings.hand_count):fill(0) local f = assert(io.open(filename, "r")) while true do local s = f:read(4) if s == nil then break end s = s:gsub("t","T") s = s:gsub("j","J") s = s:gsub("q","Q") s = s:gsub("k","K") s = s:gsub("a","A") local n = f:read("*number") if n == nil then break end -- read newline f:read(1) local hand = card_to_string_conversion:string_to_board(s) if hand[1] > hand[2] then local temp = hand[1] hand[1] = hand[2] hand[2] = temp end local idx = self:get_hole_index({hand[1], hand[2]}) out[idx] = n end out:div(out:sum()) return out end --- Randomly samples a range vector which is valid with a given board. -- @param board a possibly empty vector of board cards -- @param[opt] seed a seed for the random number generator -- @return a range vector where invalid hands are given 0 probability, each -- valid hand is given a probability randomly sampled from the uniform -- distribution on [0,1), and the resulting range is normalized function M:get_random_range(board, seed) seed = seed or torch.random() local gen = torch.Generator() torch.manualSeed(gen, seed) local out = torch.rand(gen, game_settings.hand_count):typeAs(arguments.Tensor()) out:cmul(self:get_possible_hand_indexes(board)) out:div(out:sum()) return out end --- Checks if a range vector is valid with a given board. -- @param range a range vector to check -- @param board a possibly empty vector of board cards -- @return `true` if the range puts 0 probability on invalid hands and has -- total probability 1 function M:is_valid_range(range, board) local check = range:clone() local only_possible_hands = range:clone():cmul(self:get_impossible_hand_indexes(board)):sum() == 0 local sums_to_one = math.abs(1.0 - range:sum()) < 0.0001 return only_possible_hands and sums_to_one end --- Gives the current betting round based on a board vector. -- @param board a possibly empty vector of board cards -- @return the current betting round function M:board_to_street(board) if board:dim() == 0 then return 1 else for i = 1,constants.streets_count do if board:size(1) == game_settings.board_card_count[i] then return i end end assert(false, 'bad board dims') end end function M:_build_boards(boards, cur_board, out, card_index, last_index, base_index) if card_index == last_index + 1 then for i = 1, last_index do boards.boards[boards.index][i] = cur_board[i] end out[boards.index]:copy(cur_board) boards.index = boards.index + 1 return end local startindex = 1 if card_index > base_index then startindex = cur_board[card_index-1] + 1 end for i = startindex, game_settings.card_count do local good = true for j = 1, card_index - 1 do if cur_board[j] == i then good = false end end if good then cur_board[card_index] = i self:_build_boards(boards,cur_board, out, card_index+1, last_index, base_index) end end end --- Gives all possible sets of board cards for the game. -- @return an NxK tensor, where N is the number of possible boards, and K is -- the number of cards on each board function M:get_next_round_boards(board) local street = self:board_to_street(board) local boards_count = self:get_next_boards_count(street) local out = arguments.Tensor(boards_count, game_settings.board_card_count[street+1]) local boards = {index = 1, boards = out} local cur_board = arguments.Tensor(game_settings.board_card_count[street+1]) if board:dim() > 0 then for i = 1, board:size(1) do cur_board[i] = board[i] end end self:_build_boards(boards, cur_board, out, game_settings.board_card_count[street] + 1, game_settings.board_card_count[street+1], game_settings.board_card_count[street] + 1) -- assert(boards.index == boards_count, boards.index .. ' ' .. boards_count) if self.flop_board_idx == nil and board:dim() == 0 then self.flop_board_idx = arguments.Tensor(game_settings.card_count, game_settings.card_count, game_settings.card_count) for i = 1, boards_count do local card1 = out[i][1] local card2 = out[i][2] local card3 = out[i][3] self.flop_board_idx[card1][card2][card3] = i self.flop_board_idx[card1][card3][card2] = i self.flop_board_idx[card2][card1][card3] = i self.flop_board_idx[card2][card3][card1] = i self.flop_board_idx[card3][card1][card2] = i self.flop_board_idx[card3][card2][card1] = i end end return out end --- Gives all possible sets of board cards for the game. -- @return an NxK tensor, where N is the number of possible boards, and K is -- the number of cards on each board function M:get_last_round_boards(board) local street = self:board_to_street(board) local boards_count = self:get_last_boards_count(street) local out = arguments.Tensor(boards_count, game_settings.board_card_count[constants.streets_count]) local boards = {index = 1, boards = out} local cur_board = arguments.Tensor(game_settings.board_card_count[constants.streets_count]) if board:dim() > 0 then for i = 1, board:size(1) do cur_board[i] = board[i] end end self:_build_boards(boards, cur_board, out, game_settings.board_card_count[street] + 1, game_settings.board_card_count[constants.streets_count], game_settings.board_card_count[street] + 1) -- assert(boards.index == boards_count, boards.index .. ' ' .. boards_count) return out end --- Gives the number of possible boards. -- @return the number of possible boards function M:get_next_boards_count(street) local used_cards = game_settings.board_card_count[street] local new_cards = game_settings.board_card_count[street+1] - game_settings.board_card_count[street] return tools:choose(game_settings.card_count - used_cards, new_cards) end --- Gives the number of possible boards. -- @return the number of possible boards function M:get_last_boards_count(street) local used_cards = game_settings.board_card_count[street] local new_cards = game_settings.board_card_count[constants.streets_count] - game_settings.board_card_count[street] return tools:choose(game_settings.card_count - used_cards, new_cards) end --- Gives a numerical index for a set of board cards. -- @param board a non-empty vector of board cards -- @return the numerical index for the board function M:get_board_index(board) assert(board:size(1) > 3) local used_cards = arguments.Tensor(game_settings.card_count):fill(0) for i = 1, board:size(1) - 1 do used_cards[board[i]] = 1 end local ans = 0 for i = 1, game_settings.card_count do if used_cards[i] == 0 then ans = ans + 1 end if i == board[-1] then return ans end end return -1 end --- Gives a numerical index for a set of board cards. -- @param board a non-empty vector of board cards -- @return the numerical index for the board function M:get_flop_board_index(board) if self.flop_board_idx == nil then self:get_next_round_boards(arguments.Tensor()) end return self.flop_board_idx[board[1]][board[2]][board[3]] end --- Gives a numerical index for a set of hole cards. -- @param hand a non-empty vector of hole cards, sorted -- @return the numerical index for the hand function M:get_hole_index(hand) local index = 1 for i = 1, #hand do index = index + tools:choose(hand[i] - 1, i) end return index end --- Gives a numerical index for a set of hole cards. -- @param hand a non-empty vector of hole cards, sorted -- @return the numerical index for the hand function M:string_to_hole_index(hand_string) local hole = card_to_string_conversion:string_to_board(hand_string) hole = torch.sort(hole) index = 1 for i = 1, hole:size(1) do index = index + tools:choose(hole[i] - 1, i) end return index end --- Normalizes a range vector over hands which are valid with a given board. -- @param board a possibly empty vector of board cards -- @param range a range vector -- @return a modified version of `range` where each invalid hand is given 0 -- probability and the vector is normalized function M:normalize_range(board, range) local mask = self:get_possible_hand_indexes(board) local out = range:clone():cmul(mask) --return zero range if it all collides with board (avoid div by zero) if out:sum() == 0 then return out end out:div(out:sum()) return out end return M ================================================ FILE: Source/Lookahead/Tests/ranges/flop-situation1-p1.txt ================================================ 4d7c 0.054927 2c7c 0.995828 5d9c 0.99831 4dJc 0.998619 5d9h 0.99831 2s5s 0.997305 5d9s 0.99831 4dJs 0.998619 2c8c 0.996726 4dTc 0.746475 5d8c 0.998541 4dTh 0.746475 4c7s 0.054927 4cJh 0.998619 2c6c 0.995154 5d8h 0.998541 5dTc 0.998028 2s8s 0.996726 5dTh 0.998028 5dTs 0.998028 2s9s 0.996303 2s4s 0.997887 4dJh 0.998619 2s6s 0.995154 3dKs 0.999133 3dAh 0.999944 2sQc 0.998384 2sKh 0.999134 2sKc 0.999134 2sAh 0.999375 2sAc 0.999375 2sQh 0.998384 4d7h 0.054927 2s7s 0.995828 2h3h 0.996645 4cTh 0.746475 5d8s 0.998541 4d6c 0.998793 2c9c 0.996303 4d6h 0.998793 4cJd 0.998619 4cJs 0.998619 4cTd 0.746475 3dJc 0.99866 3dJh 0.99866 4s7c 0.054927 4cTs 0.746475 2cQs 0.998384 2cQh 0.998384 2cQd 0.998384 3dAs 0.999944 2cKs 0.999134 2hQs 0.998384 2cKh 0.999134 2hQd 0.998384 2cKd 0.999134 2hKs 0.999134 2cKc 0.999069 2hKd 0.999134 2cAs 0.999375 2hAs 0.999375 2cAh 0.999375 2hAd 0.999375 2cAd 0.999375 4sJd 0.998619 4c6h 0.998793 4sTc 0.746475 5d7c 0.999284 4sTd 0.746475 5d7h 0.999284 2d5d 0.997305 5d7s 0.999284 2d6d 0.995154 4s7d 0.054927 2d7d 0.995828 5cTs 0.998028 2s3s 0.996645 2d8d 0.996726 4s7h 0.054927 5c9s 0.99831 2d9d 0.996303 3d8d 0.997718 3s7s 0.999628 4c7h 0.054927 4s6c 0.998793 2dQs 0.998384 4s6d 0.998793 3s8s 0.997718 2dKs 0.999134 4s6h 0.998793 2dKd 0.999069 5sTc 0.998028 2dAs 0.999375 2dAh 0.999375 3hKd 0.999133 2dAc 0.999375 5sTd 0.998028 4c7d 0.054927 5c7d 0.999284 5c7s 0.999284 2c4c 0.997887 5sTh 0.998028 3dKh 0.999133 2sQd 0.998384 2sKd 0.999134 5s9c 0.99831 2sAd 0.999375 3sJh 0.99866 4d7s 0.054927 3sJd 0.99866 3dQh 0.998311 3sJc 0.99866 4d6s 0.998793 5s9d 0.99831 3sQh 0.998311 3dQs 0.998311 3sQd 0.998311 3dJs 0.99866 3sQc 0.998311 4c6s 0.998793 5s9h 0.99831 3sKh 0.999133 3sKd 0.999133 2hQc 0.998384 3sKc 0.999133 2hKc 0.999134 5s8c 0.998541 2hAc 0.999375 3sAh 0.999944 2d3d 0.996645 3sAd 0.999944 3sAc 0.999944 4sTh 0.746475 5s8d 0.998541 5cTd 0.998028 5c9d 0.99831 5c8d 0.998541 2dQh 0.998384 2dKh 0.999134 5c8s 0.998541 5s8h 0.998541 2c5c 0.997305 4dTs 0.746475 5s7c 0.999284 3hAs 0.999944 3h7h 0.999628 3dQc 0.998311 5s7d 0.999284 2h6h 0.995154 5s7h 0.999284 2h8h 0.996726 3h8h 0.997718 3hKc 0.999133 2d4d 0.997887 4c6d 0.998793 3dAc 0.999944 5c9h 0.99831 2dQc 0.998384 5c7h 0.999284 2h7h 0.995828 3hJs 0.99866 4sJc 0.998619 3hAc 0.999944 4sJh 0.998619 3hJd 0.99866 5cTh 0.998028 3hJc 0.99866 3hQs 0.998311 3dKc 0.999133 3hAd 0.999944 3d7d 0.999628 2hKh 0.999069 3hKs 0.999133 3hQc 0.998311 3hQd 0.998311 2h9h 0.996303 2sKs 0.999069 2dKc 0.999134 5c8h 0.998541 2cAc 0.99962 2dAd 0.99962 2sAs 0.99962 2hAh 0.99962 2sQs 1.0 2dQd 1.0 2cQc 1.0 2hQh 1.0 2hTh 0.996464 2sTs 0.996464 2dTd 0.996464 2cTc 0.996464 4sAd 0.99996 4cAs 0.99996 4dAs 0.99996 4dAh 0.99996 4dAc 0.99996 4sAc 0.99996 4cAd 0.99996 4sAh 0.99996 4cAh 0.99996 4dQs 0.998753 4sQd 0.998753 4cQs 0.998753 4sQc 0.998753 4cQd 0.998753 4cQh 0.998753 4sQh 0.998753 4dQh 0.998753 4dQc 0.998753 4c9c 0.998765 4d9d 0.998765 4s9s 0.998765 6h8s 0.999003 6d8s 0.999003 6c8d 0.999003 6c8h 0.999003 6c8s 0.999003 6s8h 0.999003 6d8c 0.999003 6d8h 0.999003 6s8c 0.999003 6h8c 0.999003 6h8d 0.999003 6s8d 0.999003 6cJd 0.998794 6cJh 0.998794 6sJc 0.998794 6cJs 0.998794 6hJc 0.998794 6hJd 0.998794 6dJs 0.998794 6dJh 0.998794 6dJc 0.998794 6hJs 0.998794 6sJh 0.998794 6sJd 0.998794 6sTh 0.998801 6cTh 0.998801 6dTc 0.998801 6cTd 0.998801 6hTc 0.998801 6hTd 0.998801 6hTs 0.998801 6cTs 0.998801 6dTh 0.998801 6dTs 0.998801 6sTd 0.998801 6sTc 0.998801 2sJs 0.99771 2cJc 0.99771 2hJh 0.99771 2dJd 0.99771 6d9s 0.998993 6c9s 0.998993 6h9s 0.998993 6s9d 0.998993 6h9d 0.998993 6h9c 0.998993 6c9d 0.998993 6c9h 0.998993 6s9c 0.998993 6d9c 0.998993 6d9h 0.998993 6s9h 0.998993 3d9d 0.997845 3s9s 0.997845 3h9h 0.997845 5cJs 0.998705 5dJs 0.998705 5sJh 0.998705 5cJd 0.998705 5dJc 0.998705 5dJh 0.998705 5sJd 0.998705 5cJh 0.998705 5sJc 0.998705 4c5d 0.99914 4s5d 0.99914 4d5s 0.99914 4d5c 0.99914 4c5s 0.99914 4s5c 0.99914 4dKh 0.999263 4sKd 0.999263 4dKs 0.999263 4sKh 0.999263 4cKd 0.999263 4dKc 0.999263 4sKc 0.999263 4cKh 0.999263 4cKs 0.999263 5d6h 0.998976 5d6c 0.998976 5d6s 0.998976 5c6s 0.998976 5c6h 0.998976 5s6h 0.998976 5s6d 0.998976 5s6c 0.998976 5c6d 0.998976 7hQc 0.999282 7cQd 0.999282 7sQh 0.999282 7dQh 0.999282 7hQs 0.999282 7hQd 0.999282 7dQs 0.999282 7sQc 0.999282 7sQd 0.999282 7cQh 0.999282 7cQs 0.999282 7dQc 0.999282 4s8s 0.99947 4c8c 0.99947 4d8d 0.99947 7s9h 0.999505 7s9d 0.999505 7c9h 0.999505 7d9c 0.999505 7c9s 0.999505 7d9h 0.999505 7h9s 0.999505 7c9d 0.999505 7s9c 0.999505 7h9d 0.999505 7h9c 0.999505 7d9s 0.999505 5cKd 0.99968 5cKh 0.99968 5cKs 0.99968 5sKc 0.99968 5dKc 0.99968 5dKh 0.99968 5sKh 0.99968 5dKs 0.99968 5sKd 0.99968 7hTc 0.99917 7cTs 0.99917 7dTh 0.99917 7hTd 0.99917 7dTc 0.99917 7hTs 0.99917 7cTd 0.99917 7cTh 0.99917 7sTd 0.99917 7sTh 0.99917 7dTs 0.99917 7sTc 0.99917 5dQh 0.999182 5sQh 0.999182 5cQd 0.999182 5cQh 0.999182 5sQd 0.999182 5sQc 0.999182 5cQs 0.999182 5dQs 0.999182 5dQc 0.999182 8dQh 0.99951 8cQh 0.99951 8sQc 0.99951 8cQs 0.99951 8sQd 0.99951 8hQs 0.99951 8cQd 0.99951 8hQc 0.99951 8sQh 0.99951 8hQd 0.99951 8dQs 0.99951 8dQc 0.99951 3dTd 0.998642 3hTh 0.998642 3sTs 0.998642 3dKd 0.999834 3hKh 0.999834 3sKs 0.999834 6d7h 0.999388 6c7h 0.999388 6c7s 0.999388 6d7s 0.999388 6h7c 0.999388 6h7d 0.999388 6c7d 0.999388 6h7s 0.999388 6s7h 0.999388 6s7c 0.999388 6s7d 0.999388 6d7c 0.999388 7cJh 0.999225 7cJd 0.999225 7dJc 0.999225 7hJc 0.999225 7dJh 0.999225 7dJs 0.999225 7sJd 0.999225 7cJs 0.999225 7hJs 0.999225 7sJc 0.999225 7hJd 0.999225 7sJh 0.999225 8cKd 0.999476 8dKc 0.999476 8hKc 0.999476 8sKd 0.999476 8dKs 0.999476 8dKh 0.999476 8hKs 0.999476 8sKh 0.999476 8cKs 0.999476 8sKc 0.999476 8cKh 0.999476 8hKd 0.999476 7h8c 0.999334 7h8s 0.999334 7d8s 0.999334 7c8s 0.999334 7c8h 0.999334 7s8c 0.999334 7c8d 0.999334 7h8d 0.999334 7s8d 0.999334 7d8c 0.999334 7s8h 0.999334 7d8h 0.999334 6dAh 0.999538 6sAh 0.999538 6cAd 0.999538 6sAd 0.999538 6hAs 0.999538 6hAd 0.999538 6dAc 0.999538 6cAs 0.999538 6dAs 0.999538 6sAc 0.999538 6hAc 0.999538 6cAh 0.999538 3h6h 0.997878 3s6s 0.997878 3d6d 0.997878 8hJc 0.999249 8dJc 0.999249 8hJs 0.999249 8sJd 0.999249 8dJh 0.999249 8sJh 0.999249 8dJs 0.999249 8hJd 0.999249 8sJc 0.999249 8cJs 0.999249 8cJh 0.999249 8cJd 0.999249 6dQh 0.999268 6cQh 0.999268 6dQs 0.999268 6hQs 0.999268 6cQs 0.999268 6hQc 0.999268 6hQd 0.999268 6sQc 0.999268 6dQc 0.999268 6sQh 0.999268 6sQd 0.999268 6cQd 0.999268 3sQs 1.0 3hQh 1.0 3dQd 1.0 9cQd 0.999676 9sQh 0.999676 9sQd 0.999676 9hQs 0.999676 9hQc 0.999676 9cQh 0.999676 9dQs 0.999676 9dQh 0.999676 9hQd 0.999676 9dQc 0.999676 9cQs 0.999676 9sQc 0.999676 3dJd 1.0 3hJh 1.0 3sJs 1.0 7dKc 0.999681 7dKh 0.999681 7dKs 0.999681 7hKc 0.999681 7hKs 0.999681 7hKd 0.999681 7cKd 0.999681 7cKs 0.999681 7sKh 0.999681 7sKc 0.999681 7sKd 0.999681 7cKh 0.999681 6hKs 0.999635 6sKh 0.999635 6dKs 0.999635 6sKc 0.999635 6dKc 0.999635 6hKc 0.999635 6hKd 0.999635 6dKh 0.999635 6cKs 0.999635 6sKd 0.999635 6cKd 0.999635 6cKh 0.999635 8hTd 0.999166 8hTc 0.999166 8cTh 0.999166 8sTh 0.999166 8cTd 0.999166 8dTh 0.999166 8hTs 0.999166 8dTc 0.999166 8sTd 0.999166 8sTc 0.999166 8dTs 0.999166 8cTs 0.999166 5cAh 0.99993 5sAh 0.99993 5dAs 0.99993 5dAc 0.99993 5cAs 0.99993 5sAd 0.99993 5sAc 0.99993 5dAh 0.99993 5cAd 0.99993 5dTd 1.0 5sTs 1.0 5cTc 1.0 9sJc 0.999412 9cJs 0.999412 9sJd 0.999412 9cJd 0.999412 9hJd 0.999412 9dJs 0.999412 9sJh 0.999412 9hJc 0.999412 9hJs 0.999412 9cJh 0.999412 9dJc 0.999412 9dJh 0.999412 TcQs 0.999585 TsQh 0.999585 TcQh 0.999585 TdQh 0.999585 TdQc 0.999585 TdQs 0.999585 TcQd 0.999585 TsQd 0.999585 ThQc 0.999585 ThQd 0.999585 ThQs 0.999585 TsQc 0.999585 8c9d 0.999263 8s9h 0.999263 8h9c 0.999263 8c9s 0.999263 8d9s 0.999263 8c9h 0.999263 8d9h 0.999263 8d9c 0.999263 8h9s 0.999263 8s9d 0.999263 8s9c 0.999263 8h9d 0.999263 TsJh 0.999552 ThJc 0.999552 TcJs 0.999552 ThJd 0.999552 TdJs 0.999552 TcJd 0.999552 TcJh 0.999552 TsJc 0.999552 ThJs 0.999552 TdJh 0.999552 TdJc 0.999552 TsJd 0.999552 3sAs 1.0 3dAd 1.0 3hAh 1.0 9cKs 1.0 9hKd 1.0 9sKc 1.0 9hKs 1.0 9sKd 1.0 9sKh 1.0 9dKs 1.0 9dKh 1.0 9hKc 1.0 9dKc 1.0 9cKh 1.0 9cKd 1.0 9sTh 0.999319 9dTh 0.999319 9sTd 0.999319 9cTs 0.999319 9dTs 0.999319 9sTc 0.999319 9cTd 0.999319 9cTh 0.999319 9hTs 0.999319 9dTc 0.999319 9hTc 0.999319 9hTd 0.999319 4dTd 0.999633 4cTc 0.999633 4sTs 0.999633 4s7s 1.0 4d7d 1.0 4c7c 1.0 JdQs 0.999668 JdQh 0.999668 JcQs 0.999668 JdQc 0.999668 JsQh 0.999668 JsQd 0.999668 JsQc 0.999668 JhQs 0.999668 JhQc 0.999668 JhQd 0.999668 JcQh 0.999668 JcQd 0.999668 7cAh 0.999432 7sAc 0.999432 7hAd 0.999432 7dAh 0.999432 7cAd 0.999432 7hAs 0.999432 7sAd 0.999432 7hAc 0.999432 7cAs 0.999432 7sAh 0.999432 7dAc 0.999432 7dAs 0.999432 6sJs 0.99937 6cJc 0.99937 6hJh 0.99937 6dJd 0.99937 2s2h 0.994712 2s2d 0.994712 2d2c 0.994712 2s2c 0.994712 2h2d 0.994712 2h2c 0.994712 TcKd 0.999596 TdKs 0.999596 ThKs 0.999596 TsKh 0.999596 ThKc 0.999596 TdKc 0.999596 TdKh 0.999596 TsKc 0.999596 TcKs 0.999596 ThKd 0.999596 TcKh 0.999596 TsKd 0.999596 4dJd 0.999691 4sJs 0.999691 4cJc 0.999691 8sAh 0.998841 8cAs 0.998841 8dAh 0.998841 8dAs 0.998841 8hAs 0.998841 8hAc 0.998841 8sAd 0.998841 8dAc 0.998841 8hAd 0.998841 8cAh 0.998841 8cAd 0.998841 8sAc 0.998841 4sQs 0.999011 4cQc 0.999011 4dQd 0.999011 7dJd 1.0 7sJs 1.0 7hJh 1.0 7cJc 1.0 4sKs 0.999506 4cKc 0.999506 4dKd 0.999506 5sQs 1.0 5cQc 1.0 5dQd 1.0 JcKh 0.999541 JsKh 0.999541 JdKh 0.999541 JhKd 0.999541 JhKc 0.999541 JsKc 0.999541 JhKs 0.999541 JdKs 0.999541 JcKs 0.999541 JdKc 0.999541 JcKd 0.999541 JsKd 0.999541 5s9s 0.99958 5c9c 0.99958 5d9d 0.99958 5c8c 0.999487 5s8s 0.999487 5d8d 0.999487 7hQh 0.999947 7dQd 0.999947 7cQc 0.999947 7sQs 0.999947 6dQd 1.0 6cQc 1.0 6hQh 1.0 6sQs 1.0 5sJs 0.999097 5dJd 0.999097 5cJc 0.999097 3s4s 0.998532 3d4d 0.998532 3d5d 0.999632 3s5s 0.999632 6dTd 0.999359 6cTc 0.999359 6sTs 0.999359 6hTh 0.999359 QdKc 0.999563 QdKs 0.999563 QsKc 0.999563 QcKd 0.999563 QhKs 0.999563 QsKh 0.999563 QhKd 0.999563 QhKc 0.999563 QsKd 0.999563 QdKh 0.999563 QcKs 0.999563 QcKh 0.999563 9dAs 0.999563 9cAd 0.999563 9sAc 0.999563 9sAh 0.999563 9sAd 0.999563 9dAc 0.999563 9dAh 0.999563 9hAs 0.999563 9cAs 0.999563 9hAd 0.999563 9hAc 0.999563 9cAh 0.999563 7cTc 0.999773 7dTd 0.999773 7sTs 0.999773 7hTh 0.999773 8hJh 1.0 8sJs 1.0 8cJc 1.0 8dJd 1.0 6s9s 0.999471 6c9c 0.999471 6h9h 0.999471 6d9d 0.999471 7h9h 0.99974 7c9c 0.99974 7d9d 0.99974 7s9s 0.99974 3s3h 0.998777 3s3d 0.998777 3h3d 0.998777 5sKs 0.999668 5cKc 0.999668 5dKd 0.999668 8cTc 0.99998 8sTs 0.99998 8dTd 0.99998 8hTh 0.99998 4s6s 0.99937 4d6d 0.99937 4c6c 0.99937 5s7s 0.999569 5d7d 0.999569 5c7c 0.999569 6s8s 0.999658 6c8c 0.999658 6h8h 0.999658 6d8d 0.999658 8h9h 0.999906 8d9d 0.999906 8c9c 0.999906 8s9s 0.999906 TdAc 1.0 TsAh 1.0 TdAh 1.0 TcAd 1.0 TsAc 1.0 TcAs 1.0 TsAd 1.0 ThAc 1.0 TcAh 1.0 ThAd 1.0 TdAs 1.0 ThAs 1.0 8dQd 0.999833 8cQc 0.999833 8sQs 0.999833 8hQh 0.999833 7s8s 1.0 7c8c 1.0 7h8h 1.0 7d8d 1.0 9dJd 0.999681 9cJc 0.999681 9hJh 0.999681 9sJs 0.999681 9dTd 0.99983 9hTh 0.99983 9sTs 0.99983 9cTc 0.99983 JhAc 1.0 JcAd 1.0 JdAs 1.0 JdAc 1.0 JsAc 1.0 JhAs 1.0 JhAd 1.0 JdAh 1.0 JsAh 1.0 JcAs 1.0 JcAh 1.0 JsAd 1.0 6d7d 1.0 6s7s 1.0 6c7c 1.0 6h7h 1.0 6dKd 0.999788 6cKc 0.999788 6sKs 0.999788 6hKh 0.999788 KsAc 0.999809 KhAd 0.999809 KcAs 0.999809 KsAh 0.999809 KdAc 0.999809 KdAh 0.999809 KdAs 0.999809 KhAc 0.999809 KsAd 0.999809 KhAs 0.999809 KcAd 0.999809 KcAh 0.999809 JsAs 0.999261 AdAc 0.99494 8dKd 0.999783 4d4c 1.0 9dKd 1.0 4d5d 0.999435 JhJd 1.0 9dAd 1.0 JhJc 1.0 JhQh 1.0 4dAd 1.0 4c5c 0.999435 JhKh 1.0 4cAc 1.0 5s5d 1.0 8d8c 1.0 9hAh 1.0 JhAh 0.999261 TsTh 1.0 8hAh 0.999686 TsTc 1.0 5s5c 1.0 JdJc 1.0 5s6s 1.0 JdQd 1.0 8hKh 0.999783 5sAs 1.0 9hQh 0.999783 JdKd 1.0 ThTc 1.0 5d5c 1.0 ThJh 1.0 5d6d 1.0 8h8c 1.0 ThQh 1.0 JdAd 0.999261 8h8d 1.0 ThKh 1.0 9sAs 1.0 ThAh 0.999204 8sAs 0.999686 JcQc 1.0 8sKs 0.999783 TdJd 1.0 5dAd 1.0 5c6c 1.0 TdQd 1.0 JcKc 1.0 9sQs 0.999783 5cAc 1.0 TdKd 1.0 6s6h 1.0 6s6d 1.0 TdAd 0.999204 JcAc 0.999261 9s9h 1.0 QsQh 1.0 QsQd 1.0 QsQc 1.0 QsKs 1.0 8cKc 0.999783 6s6c 1.0 8s8c 1.0 8s8d 1.0 4s4c 1.0 QsAs 0.99921 JsJh 1.0 QsAh 1.0 JsJc 1.0 QsAd 1.0 QsAc 1.0 QhQd 1.0 4s5s 0.999435 QhQc 1.0 4sAs 1.0 8s8h 1.0 QhKh 1.0 7cAc 0.999863 7hKh 0.999909 7cKc 0.999909 QhAs 1.0 9cQc 0.999783 QhAh 0.99921 9cKc 1.0 QhAd 1.0 9cAc 1.0 QhAc 1.0 TsJs 1.0 QdQc 1.0 TsQs 1.0 6sAs 1.0 TsKs 1.0 6h6d 1.0 TsAs 0.999204 QdKd 1.0 ThTd 1.0 6h6c 1.0 QdAs 1.0 QdAh 1.0 9h9d 1.0 QdAd 0.99921 9sKs 1.0 QdAc 1.0 6hAh 1.0 6d6c 1.0 7dAd 0.999863 9s9c 1.0 QcKc 1.0 8cAc 0.999686 QcAs 1.0 QcAh 1.0 QcAd 1.0 4s4d 1.0 QcAc 0.99921 JsJd 1.0 KsKh 1.0 KsKd 1.0 8dAd 0.999686 KsKc 1.0 9dQd 0.999783 KsAs 1.0 6dAd 1.0 7dKd 0.999909 TsTd 1.0 6cAc 1.0 KhKd 1.0 KhKc 1.0 7s7h 1.0 KhAh 1.0 7s7d 1.0 7s7c 1.0 TcJc 1.0 KdKc 1.0 TcKc 1.0 7sKs 0.999909 JsQs 1.0 7sAs 0.999863 KdAd 1.0 7h7d 1.0 7d7c 1.0 7h7c 1.0 TdTc 1.0 7hAh 0.999863 9s9d 1.0 KcAc 1.0 TcAc 0.999204 AsAh 0.99494 9hKh 1.0 AhAc 0.99494 AhAd 0.99494 AsAc 0.99494 AsAd 0.99494 9h9c 1.0 9d9c 1.0 JsKs 1.0 TcQc 1.0 ================================================ FILE: Source/Lookahead/Tests/ranges/flop-situation1-p2.txt ================================================ 3s5c 1.0 4d7c 1.0 2c7c 1.0 4c9h 1.0 5d9c 1.0 4dJc 1.0 5d9h 1.0 2s5s 1.0 5d9s 1.0 4dJs 1.0 2c8c 1.0 4dTc 1.0 5d8c 1.0 4dTh 1.0 4c7s 1.0 4cJh 1.0 2c6c 1.0 5d8h 1.0 5dTc 1.0 2s8s 1.0 5dTh 1.0 4d9c 1.0 5dTs 1.0 2s9s 1.0 2s4s 1.0 4d9s 1.0 3d9h 1.0 2sTd 1.0 4dJh 1.0 4d8h 1.0 2s6s 1.0 2sJd 1.0 3dKs 1.0 3dAh 1.0 4d9h 1.0 3d9s 1.0 4d8c 1.0 2sQc 1.0 2sTh 1.0 2sKh 1.0 2sTc 1.0 2sKc 1.0 2sJh 1.0 2sAh 1.0 2sJc 1.0 2sAc 1.0 2sQh 1.0 4d7h 1.0 2s7s 1.0 2h3h 1.0 4cTh 1.0 4c9d 1.0 5d8s 1.0 4d6c 1.0 2c9c 1.0 4d6h 1.0 2cTs 1.0 4cJd 1.0 2cTh 1.0 4cJs 1.0 2cTd 1.0 4cTd 1.0 3d4c 1.0 3d6h 1.0 2cJs 1.0 3dJc 1.0 2cJh 1.0 3dJh 1.0 2cJd 1.0 3dTc 1.0 4s7c 1.0 4cTs 1.0 2cQs 1.0 2hTs 1.0 2cQh 1.0 2hTd 1.0 2cQd 1.0 2hJs 1.0 3dAs 1.0 2hJd 1.0 2cKs 1.0 2hQs 1.0 2cKh 1.0 2hQd 1.0 2cKd 1.0 2hKs 1.0 2cKc 1.0 2hKd 1.0 2cAs 1.0 2hAs 1.0 2cAh 1.0 2hAd 1.0 2cAd 1.0 4sJd 1.0 4c6h 1.0 4sTc 1.0 5d7c 1.0 4sTd 1.0 5d7h 1.0 2d5d 1.0 5d7s 1.0 3d9c 1.0 3s4d 1.0 2d6d 1.0 3s4c 1.0 4s9c 1.0 4s7d 1.0 2d7d 1.0 3s5d 1.0 5cTs 1.0 2s3s 1.0 2d8d 1.0 4s7h 1.0 5c9s 1.0 3s6h 1.0 2d9d 1.0 3s6d 1.0 2dTs 1.0 3s6c 1.0 3d8d 1.0 3s7s 1.0 2dJs 1.0 4c7h 1.0 4s9d 1.0 4s6c 1.0 2dQs 1.0 4s6d 1.0 3d5s 1.0 3s8s 1.0 2dKs 1.0 4s6h 1.0 2dKd 1.0 5sTc 1.0 2dAs 1.0 2dAh 1.0 3hKd 1.0 2dAc 1.0 5sTd 1.0 4c7d 1.0 5c7d 1.0 3s9h 1.0 5c7s 1.0 3s9d 1.0 2c4c 1.0 3s9c 1.0 4s8c 1.0 5sTh 1.0 4s8d 1.0 3sTh 1.0 3dKh 1.0 3sTd 1.0 2sQd 1.0 3sTc 1.0 2sKd 1.0 5s9c 1.0 2sAd 1.0 3sJh 1.0 4d7s 1.0 3sJd 1.0 3dQh 1.0 3sJc 1.0 4d6s 1.0 5s9d 1.0 3d6c 1.0 3sQh 1.0 3dQs 1.0 3sQd 1.0 3dJs 1.0 3sQc 1.0 4c6s 1.0 5s9h 1.0 2hTc 1.0 3sKh 1.0 2hJc 1.0 3sKd 1.0 2hQc 1.0 3sKc 1.0 2hKc 1.0 5s8c 1.0 2hAc 1.0 3sAh 1.0 2d3d 1.0 3sAd 1.0 3dTh 1.0 3sAc 1.0 4sTh 1.0 5s8d 1.0 5cTd 1.0 3h4s 1.0 5c9d 1.0 3h4d 1.0 5c8d 1.0 3h4c 1.0 2dTh 1.0 3h5s 1.0 2dJh 1.0 3h5d 1.0 2dQh 1.0 3h5c 1.0 2dKh 1.0 3h6s 1.0 5c8s 1.0 5s8h 1.0 4s9h 1.0 3h6d 1.0 2c5c 1.0 3h6c 1.0 4dTs 1.0 5s7c 1.0 3hAs 1.0 3h7h 1.0 3dQc 1.0 5s7d 1.0 2h6h 1.0 5s7h 1.0 2h8h 1.0 4c8s 1.0 3d6s 1.0 3h8h 1.0 3d5c 1.0 4c8h 1.0 3hKc 1.0 4c8d 1.0 2d4d 1.0 3h9s 1.0 4c6d 1.0 3dAc 1.0 5c9h 1.0 3h9d 1.0 2dTc 1.0 3h9c 1.0 2dQc 1.0 3hTs 1.0 5c7h 1.0 3d4s 1.0 4s8h 1.0 3hTd 1.0 4d8s 1.0 3hTc 1.0 2h7h 1.0 3hJs 1.0 4sJc 1.0 3hAc 1.0 4sJh 1.0 3hJd 1.0 5cTh 1.0 3hJc 1.0 2dJc 1.0 3hQs 1.0 3dKc 1.0 3hAd 1.0 3d7d 1.0 2hKh 1.0 4c9s 1.0 3hKs 1.0 3hQc 1.0 3hQd 1.0 3dTs 1.0 2h9h 1.0 2sKs 1.0 2dKc 1.0 5c8h 1.0 2cAc 0.998052 2dAd 0.998052 2sAs 0.998052 2hAh 0.998052 2sQs 0.994289 2dQd 0.994289 2cQc 0.994289 2hQh 0.994289 2hTh 0.991671 2sTs 0.991671 2dTd 0.991671 2cTc 0.991671 4sAd 0.991476 4cAs 0.991476 4dAs 0.991476 4dAh 0.991476 4dAc 0.991476 4sAc 0.991476 4cAd 0.991476 4sAh 0.991476 4cAh 0.991476 4dQs 0.986773 4sQd 0.986773 4cQs 0.986773 4sQc 0.986773 4cQd 0.986773 4cQh 0.986773 4sQh 0.986773 4dQh 0.986773 4dQc 0.986773 4c9c 0.984597 4d9d 0.984597 4s9s 0.984597 6h8s 0.982601 6d8s 0.982601 6c8d 0.982601 6c8h 0.982601 6c8s 0.982601 6s8h 0.982601 6d8c 0.982601 6d8h 0.982601 6s8c 0.982601 6h8c 0.982601 6h8d 0.982601 6s8d 0.982601 6cJd 0.981204 6cJh 0.981204 6sJc 0.981204 6cJs 0.981204 6hJc 0.981204 6hJd 0.981204 6dJs 0.981204 6dJh 0.981204 6dJc 0.981204 6hJs 0.981204 6sJh 0.981204 6sJd 0.981204 6sTh 0.979326 6cTh 0.979326 6dTc 0.979326 6cTd 0.979326 6hTc 0.979326 6hTd 0.979326 6hTs 0.979326 6cTs 0.979326 6dTh 0.979326 6dTs 0.979326 6sTd 0.979326 6sTc 0.979326 2sJs 0.977355 2cJc 0.977355 2hJh 0.977355 2dJd 0.977355 6d9s 0.96981 6c9s 0.96981 6h9s 0.96981 6s9d 0.96981 6h9d 0.96981 6h9c 0.96981 6c9d 0.96981 6c9h 0.96981 6s9c 0.96981 6d9c 0.96981 6d9h 0.96981 6s9h 0.96981 3d9d 0.969495 3s9s 0.969495 3h9h 0.969495 5cJs 0.968892 5dJs 0.968892 5sJh 0.968892 5cJd 0.968892 5dJc 0.968892 5dJh 0.968892 5sJd 0.968892 5cJh 0.968892 5sJc 0.968892 4c5d 0.960249 4s5d 0.960249 4d5s 0.960249 4d5c 0.960249 4c5s 0.960249 4s5c 0.960249 4dKh 0.95173 4sKd 0.95173 4dKs 0.95173 4sKh 0.95173 4cKd 0.95173 4dKc 0.95173 4sKc 0.95173 4cKh 0.95173 4cKs 0.95173 5d6h 0.903048 5d6c 0.903048 5d6s 0.903048 5c6s 0.903048 5c6h 0.903048 5s6h 0.903048 5s6d 0.903048 5s6c 0.903048 5c6d 0.903048 7hQc 0.866116 7cQd 0.866116 7sQh 0.866116 7dQh 0.866116 7hQs 0.866116 7hQd 0.866116 7dQs 0.866116 7sQc 0.866116 7sQd 0.866116 7cQh 0.866116 7cQs 0.866116 7dQc 0.866116 4s8s 0.86404 4c8c 0.86404 4d8d 0.86404 7s9h 0.861573 7s9d 0.861573 7c9h 0.861573 7d9c 0.861573 7c9s 0.861573 7d9h 0.861573 7h9s 0.861573 7c9d 0.861573 7s9c 0.861573 7h9d 0.861573 7h9c 0.861573 7d9s 0.861573 5cKd 0.854165 5cKh 0.854165 5cKs 0.854165 5sKc 0.854165 5dKc 0.854165 5dKh 0.854165 5sKh 0.854165 5dKs 0.854165 5sKd 0.854165 7hTc 0.847387 7cTs 0.847387 7dTh 0.847387 7hTd 0.847387 7dTc 0.847387 7hTs 0.847387 7cTd 0.847387 7cTh 0.847387 7sTd 0.847387 7sTh 0.847387 7dTs 0.847387 7sTc 0.847387 5dQh 0.847241 5sQh 0.847241 5cQd 0.847241 5cQh 0.847241 5sQd 0.847241 5sQc 0.847241 5cQs 0.847241 5dQs 0.847241 5dQc 0.847241 8dQh 0.846896 8cQh 0.846896 8sQc 0.846896 8cQs 0.846896 8sQd 0.846896 8hQs 0.846896 8cQd 0.846896 8hQc 0.846896 8sQh 0.846896 8hQd 0.846896 8dQs 0.846896 8dQc 0.846896 3dTd 0.844179 3hTh 0.844179 3sTs 0.844179 3dKd 0.84136 3hKh 0.84136 3sKs 0.84136 6d7h 0.830509 6c7h 0.830509 6c7s 0.830509 6d7s 0.830509 6h7c 0.830509 6h7d 0.830509 6c7d 0.830509 6h7s 0.830509 6s7h 0.830509 6s7c 0.830509 6s7d 0.830509 6d7c 0.830509 7cJh 0.819686 7cJd 0.819686 7dJc 0.819686 7hJc 0.819686 7dJh 0.819686 7dJs 0.819686 7sJd 0.819686 7cJs 0.819686 7hJs 0.819686 7sJc 0.819686 7hJd 0.819686 7sJh 0.819686 8cKd 0.815898 8dKc 0.815898 8hKc 0.815898 8sKd 0.815898 8dKs 0.815898 8dKh 0.815898 8hKs 0.815898 8sKh 0.815898 8cKs 0.815898 8sKc 0.815898 8cKh 0.815898 8hKd 0.815898 7h8c 0.801449 7h8s 0.801449 7d8s 0.801449 7c8s 0.801449 7c8h 0.801449 7s8c 0.801449 7c8d 0.801449 7h8d 0.801449 7s8d 0.801449 7d8c 0.801449 7s8h 0.801449 7d8h 0.801449 6dAh 0.797539 6sAh 0.797539 6cAd 0.797539 6sAd 0.797539 6hAs 0.797539 6hAd 0.797539 6dAc 0.797539 6cAs 0.797539 6dAs 0.797539 6sAc 0.797539 6hAc 0.797539 6cAh 0.797539 3h6h 0.788231 3s6s 0.788231 3d6d 0.788231 8hJc 0.775972 8dJc 0.775972 8hJs 0.775972 8sJd 0.775972 8dJh 0.775972 8sJh 0.775972 8dJs 0.775972 8hJd 0.775972 8sJc 0.775972 8cJs 0.775972 8cJh 0.775972 8cJd 0.775972 6dQh 0.773292 6cQh 0.773292 6dQs 0.773292 6hQs 0.773292 6cQs 0.773292 6hQc 0.773292 6hQd 0.773292 6sQc 0.773292 6dQc 0.773292 6sQh 0.773292 6sQd 0.773292 6cQd 0.773292 3sQs 0.770838 3hQh 0.770838 3dQd 0.770838 9cQd 0.766948 9sQh 0.766948 9sQd 0.766948 9hQs 0.766948 9hQc 0.766948 9cQh 0.766948 9dQs 0.766948 9dQh 0.766948 9hQd 0.766948 9dQc 0.766948 9cQs 0.766948 9sQc 0.766948 3dJd 0.762357 3hJh 0.762357 3sJs 0.762357 7dKc 0.75886 7dKh 0.75886 7dKs 0.75886 7hKc 0.75886 7hKs 0.75886 7hKd 0.75886 7cKd 0.75886 7cKs 0.75886 7sKh 0.75886 7sKc 0.75886 7sKd 0.75886 7cKh 0.75886 6hKs 0.757507 6sKh 0.757507 6dKs 0.757507 6sKc 0.757507 6dKc 0.757507 6hKc 0.757507 6hKd 0.757507 6dKh 0.757507 6cKs 0.757507 6sKd 0.757507 6cKd 0.757507 6cKh 0.757507 8hTd 0.756533 8hTc 0.756533 8cTh 0.756533 8sTh 0.756533 8cTd 0.756533 8dTh 0.756533 8hTs 0.756533 8dTc 0.756533 8sTd 0.756533 8sTc 0.756533 8dTs 0.756533 8cTs 0.756533 5cAh 0.728201 5sAh 0.728201 5dAs 0.728201 5dAc 0.728201 5cAs 0.728201 5sAd 0.728201 5sAc 0.728201 5dAh 0.728201 5cAd 0.728201 5dTd 0.727556 5sTs 0.727556 5cTc 0.727556 9sJc 0.726804 9cJs 0.726804 9sJd 0.726804 9cJd 0.726804 9hJd 0.726804 9dJs 0.726804 9sJh 0.726804 9hJc 0.726804 9hJs 0.726804 9cJh 0.726804 9dJc 0.726804 9dJh 0.726804 2s9c 0.687853 2d9s 0.687853 2h9d 0.687853 2h9c 0.687853 2d9c 0.687853 2c9s 0.687853 2c9h 0.687853 2d9h 0.687853 2h9s 0.687853 2s9h 0.687853 2c9d 0.687853 2s9d 0.687853 TcQs 0.687123 TsQh 0.687123 TcQh 0.687123 TdQh 0.687123 TdQc 0.687123 TdQs 0.687123 TcQd 0.687123 TsQd 0.687123 ThQc 0.687123 ThQd 0.687123 ThQs 0.687123 TsQc 0.687123 8c9d 0.674819 8s9h 0.674819 8h9c 0.674819 8c9s 0.674819 8d9s 0.674819 8c9h 0.674819 8d9h 0.674819 8d9c 0.674819 8h9s 0.674819 8s9d 0.674819 8s9c 0.674819 8h9d 0.674819 TsJh 0.63339 ThJc 0.63339 TcJs 0.63339 ThJd 0.63339 TdJs 0.63339 TcJd 0.63339 TcJh 0.63339 TsJc 0.63339 ThJs 0.63339 TdJh 0.63339 TdJc 0.63339 TsJd 0.63339 3sAs 0.624236 3dAd 0.624236 3hAh 0.624236 9cKs 0.617503 9hKd 0.617503 9sKc 0.617503 9hKs 0.617503 9sKd 0.617503 9sKh 0.617503 9dKs 0.617503 9dKh 0.617503 9hKc 0.617503 9dKc 0.617503 9cKh 0.617503 9cKd 0.617503 9sTh 0.607465 9dTh 0.607465 9sTd 0.607465 9cTs 0.607465 9dTs 0.607465 9sTc 0.607465 9cTd 0.607465 9cTh 0.607465 9hTs 0.607465 9dTc 0.607465 9hTc 0.607465 9hTd 0.607465 4dTd 0.590917 4cTc 0.590917 4sTs 0.590917 4s7s 0.579037 4d7d 0.579037 4c7c 0.579037 JdQs 0.578568 JdQh 0.578568 JcQs 0.578568 JdQc 0.578568 JsQh 0.578568 JsQd 0.578568 JsQc 0.578568 JhQs 0.578568 JhQc 0.578568 JhQd 0.578568 JcQh 0.578568 JcQd 0.578568 7cAh 0.57586 7sAc 0.57586 7hAd 0.57586 7dAh 0.57586 7cAd 0.57586 7hAs 0.57586 7sAd 0.57586 7hAc 0.57586 7cAs 0.57586 7sAh 0.57586 7dAc 0.57586 7dAs 0.57586 6sJs 0.575364 6cJc 0.575364 6hJh 0.575364 6dJd 0.575364 2s2h 0.543474 2s2d 0.543474 2d2c 0.543474 2s2c 0.543474 2h2d 0.543474 2h2c 0.543474 TcKd 0.504897 TdKs 0.504897 ThKs 0.504897 TsKh 0.504897 ThKc 0.504897 TdKc 0.504897 TdKh 0.504897 TsKc 0.504897 TcKs 0.504897 ThKd 0.504897 TcKh 0.504897 TsKd 0.504897 4dJd 0.470046 4sJs 0.470046 4cJc 0.470046 8sAh 0.424842 8cAs 0.424842 8dAh 0.424842 8dAs 0.424842 8hAs 0.424842 8hAc 0.424842 8sAd 0.424842 8dAc 0.424842 8hAd 0.424842 8cAh 0.424842 8cAd 0.424842 8sAc 0.424842 4sQs 0.40569 4cQc 0.40569 4dQd 0.40569 7dJd 0.394607 7sJs 0.394607 7hJh 0.394607 7cJc 0.394607 4sKs 0.394111 4cKc 0.394111 4dKd 0.394111 5sQs 0.387608 5cQc 0.387608 5dQd 0.387608 JcKh 0.384868 JsKh 0.384868 JdKh 0.384868 JhKd 0.384868 JhKc 0.384868 JsKc 0.384868 JhKs 0.384868 JdKs 0.384868 JcKs 0.384868 JdKc 0.384868 JcKd 0.384868 JsKd 0.384868 5s9s 0.382969 5c9c 0.382969 5d9d 0.382969 5c8c 0.369193 5s8s 0.369193 5d8d 0.369193 7hQh 0.364789 7dQd 0.364789 7cQc 0.364789 7sQs 0.364789 6dQd 0.355186 6cQc 0.355186 6hQh 0.355186 6sQs 0.355186 5sJs 0.352002 5dJd 0.352002 5cJc 0.352002 3s4s 0.321444 3d4d 0.321444 3d5d 0.320805 3s5s 0.320805 3s7c 0.264895 3h7c 0.264895 3d7c 0.264895 3s7h 0.264895 3s7d 0.264895 3d7h 0.264895 3h7s 0.264895 3h7d 0.264895 3d7s 0.264895 6dTd 0.254433 6cTc 0.254433 6sTs 0.254433 6hTh 0.254433 QdKc 0.235819 QdKs 0.235819 QsKc 0.235819 QcKd 0.235819 QhKs 0.235819 QsKh 0.235819 QhKd 0.235819 QhKc 0.235819 QsKd 0.235819 QdKh 0.235819 QcKs 0.235819 QcKh 0.235819 9dAs 0.224596 9cAd 0.224596 9sAc 0.224596 9sAh 0.224596 9sAd 0.224596 9dAc 0.224596 9dAh 0.224596 9hAs 0.224596 9cAs 0.224596 9hAd 0.224596 9hAc 0.224596 9cAh 0.224596 7cTc 0.217163 7dTd 0.217163 7sTs 0.217163 7hTh 0.217163 8hJh 0.207646 8sJs 0.207646 8cJc 0.207646 8dJd 0.207646 6s9s 0.181485 6c9c 0.181485 6h9h 0.181485 6d9d 0.181485 7h9h 0.166302 7c9c 0.166302 7d9d 0.166302 7s9s 0.166302 3s3h 0.165745 3s3d 0.165745 3h3d 0.165745 5sKs 0.152201 5cKc 0.152201 5dKd 0.152201 8cTc 0.151698 8sTs 0.151698 8dTd 0.151698 8hTh 0.151698 2s5c 0.137359 2h5s 0.137359 2c5d 0.137359 2c5s 0.137359 2d5c 0.137359 2h5d 0.137359 2d5s 0.137359 2h5c 0.137359 2s5d 0.137359 4s6s 0.135811 4d6d 0.135811 4c6c 0.135811 5s7s 0.129799 5d7d 0.129799 5c7c 0.129799 6s8s 0.112885 6c8c 0.112885 6h8h 0.112885 6d8d 0.112885 8h9h 0.077446 8d9d 0.077446 8c9c 0.077446 8s9s 0.077446 TdAc 0.07454 TsAh 0.07454 TdAh 0.07454 TcAd 0.07454 TsAc 0.07454 TcAs 0.07454 TsAd 0.07454 ThAc 0.07454 TcAh 0.07454 ThAd 0.07454 TdAs 0.07454 ThAs 0.07454 8dQd 0.070912 8cQc 0.070912 8sQs 0.070912 8hQh 0.070912 7s8s 0.062422 7c8c 0.062422 7h8h 0.062422 7d8d 0.062422 9dJd 0.038739 9cJc 0.038739 9hJh 0.038739 9sJs 0.038739 9dTd 0.033651 9hTh 0.033651 9sTs 0.033651 9cTc 0.033651 JhAc 0.0119 JcAd 0.0119 JdAs 0.0119 JdAc 0.0119 JsAc 0.0119 JhAs 0.0119 JhAd 0.0119 JdAh 0.0119 JsAh 0.0119 JcAs 0.0119 JcAh 0.0119 JsAd 0.0119 6d7d 0.009426 6s7s 0.009426 6c7c 0.009426 6h7h 0.009426 6dKd 0.003541 6cKc 0.003541 6sKs 0.003541 6hKh 0.003541 KsAc 0.00052 KhAd 0.00052 KcAs 0.00052 KsAh 0.00052 KdAc 0.00052 KdAh 0.00052 KdAs 0.00052 KhAc 0.00052 KsAd 0.00052 KhAs 0.00052 KcAd 0.00052 KcAh 0.00052 ================================================ FILE: Source/Lookahead/Tests/ranges/flop-situation2-p1.txt ================================================ 4d7c 0.054927 2c7c 0.995828 5d9c 0.99831 4dJc 0.998619 5d9h 0.99831 2s5s 0.997305 5d9s 0.99831 4dJs 0.998619 2c8c 0.996726 4dTc 0.746475 5d8c 0.998541 4dTh 0.746475 4c7s 0.054927 4cJh 0.998619 2c6c 0.995154 5d8h 0.998541 5dTc 0.998028 2s8s 0.996726 5dTh 0.998028 5dTs 0.998028 2s9s 0.996303 2s4s 0.997887 4dJh 0.998619 2s6s 0.995154 3dKs 0.999133 3dAh 0.999944 2sQc 0.998384 2sKh 0.999134 2sKc 0.999134 2sAh 0.999375 2sAc 0.999375 2sQh 0.998384 4d7h 0.054927 2s7s 0.995828 2h3h 0.996645 4cTh 0.746475 5d8s 0.998541 4d6c 0.998793 2c9c 0.996303 4d6h 0.998793 4cJd 0.998619 4cJs 0.998619 4cTd 0.746475 3dJc 0.99866 3dJh 0.99866 4s7c 0.054927 4cTs 0.746475 2cQs 0.998384 2cQh 0.998384 2cQd 0.998384 3dAs 0.999944 2cKs 0.999134 2hQs 0.998384 2cKh 0.999134 2hQd 0.998384 2cKd 0.999134 2hKs 0.999134 2cKc 0.999069 2hKd 0.999134 2cAs 0.999375 2hAs 0.999375 2cAh 0.999375 2hAd 0.999375 2cAd 0.999375 4sJd 0.998619 4c6h 0.998793 4sTc 0.746475 5d7c 0.999284 4sTd 0.746475 5d7h 0.999284 2d5d 0.997305 5d7s 0.999284 2d6d 0.995154 4s7d 0.054927 2d7d 0.995828 5cTs 0.998028 2s3s 0.996645 2d8d 0.996726 4s7h 0.054927 5c9s 0.99831 2d9d 0.996303 3d8d 0.997718 3s7s 0.999628 4c7h 0.054927 4s6c 0.998793 2dQs 0.998384 4s6d 0.998793 3s8s 0.997718 2dKs 0.999134 4s6h 0.998793 2dKd 0.999069 5sTc 0.998028 2dAs 0.999375 2dAh 0.999375 3hKd 0.999133 2dAc 0.999375 5sTd 0.998028 4c7d 0.054927 5c7d 0.999284 5c7s 0.999284 2c4c 0.997887 5sTh 0.998028 3dKh 0.999133 2sQd 0.998384 2sKd 0.999134 5s9c 0.99831 2sAd 0.999375 3sJh 0.99866 4d7s 0.054927 3sJd 0.99866 3dQh 0.998311 3sJc 0.99866 4d6s 0.998793 5s9d 0.99831 3sQh 0.998311 3dQs 0.998311 3sQd 0.998311 3dJs 0.99866 3sQc 0.998311 4c6s 0.998793 5s9h 0.99831 3sKh 0.999133 3sKd 0.999133 2hQc 0.998384 3sKc 0.999133 2hKc 0.999134 5s8c 0.998541 2hAc 0.999375 3sAh 0.999944 2d3d 0.996645 3sAd 0.999944 3sAc 0.999944 4sTh 0.746475 5s8d 0.998541 5cTd 0.998028 5c9d 0.99831 5c8d 0.998541 2dQh 0.998384 2dKh 0.999134 5c8s 0.998541 5s8h 0.998541 2c5c 0.997305 4dTs 0.746475 5s7c 0.999284 3hAs 0.999944 3h7h 0.999628 3dQc 0.998311 5s7d 0.999284 2h6h 0.995154 5s7h 0.999284 2h8h 0.996726 3h8h 0.997718 3hKc 0.999133 2d4d 0.997887 4c6d 0.998793 3dAc 0.999944 5c9h 0.99831 2dQc 0.998384 5c7h 0.999284 2h7h 0.995828 3hJs 0.99866 4sJc 0.998619 3hAc 0.999944 4sJh 0.998619 3hJd 0.99866 5cTh 0.998028 3hJc 0.99866 3hQs 0.998311 3dKc 0.999133 3hAd 0.999944 3d7d 0.999628 2hKh 0.999069 3hKs 0.999133 3hQc 0.998311 3hQd 0.998311 2h9h 0.996303 2sKs 0.999069 2dKc 0.999134 5c8h 0.998541 2cAc 0.99962 2dAd 0.99962 2sAs 0.99962 2hAh 0.99962 2sQs 1.0 2dQd 1.0 2cQc 1.0 2hQh 1.0 2hTh 0.996464 2sTs 0.996464 2dTd 0.996464 2cTc 0.996464 4sAd 0.99996 4cAs 0.99996 4dAs 0.99996 4dAh 0.99996 4dAc 0.99996 4sAc 0.99996 4cAd 0.99996 4sAh 0.99996 4cAh 0.99996 4dQs 0.998753 4sQd 0.998753 4cQs 0.998753 4sQc 0.998753 4cQd 0.998753 4cQh 0.998753 4sQh 0.998753 4dQh 0.998753 4dQc 0.998753 4c9c 0.998765 4d9d 0.998765 4s9s 0.998765 6h8s 0.999003 6d8s 0.999003 6c8d 0.999003 6c8h 0.999003 6c8s 0.999003 6s8h 0.999003 6d8c 0.999003 6d8h 0.999003 6s8c 0.999003 6h8c 0.999003 6h8d 0.999003 6s8d 0.999003 6cJd 0.998794 6cJh 0.998794 6sJc 0.998794 6cJs 0.998794 6hJc 0.998794 6hJd 0.998794 6dJs 0.998794 6dJh 0.998794 6dJc 0.998794 6hJs 0.998794 6sJh 0.998794 6sJd 0.998794 6sTh 0.998801 6cTh 0.998801 6dTc 0.998801 6cTd 0.998801 6hTc 0.998801 6hTd 0.998801 6hTs 0.998801 6cTs 0.998801 6dTh 0.998801 6dTs 0.998801 6sTd 0.998801 6sTc 0.998801 2sJs 0.99771 2cJc 0.99771 2hJh 0.99771 2dJd 0.99771 6d9s 0.998993 6c9s 0.998993 6h9s 0.998993 6s9d 0.998993 6h9d 0.998993 6h9c 0.998993 6c9d 0.998993 6c9h 0.998993 6s9c 0.998993 6d9c 0.998993 6d9h 0.998993 6s9h 0.998993 3d9d 0.997845 3s9s 0.997845 3h9h 0.997845 5cJs 0.998705 5dJs 0.998705 5sJh 0.998705 5cJd 0.998705 5dJc 0.998705 5dJh 0.998705 5sJd 0.998705 5cJh 0.998705 5sJc 0.998705 4c5d 0.99914 4s5d 0.99914 4d5s 0.99914 4d5c 0.99914 4c5s 0.99914 4s5c 0.99914 4dKh 0.999263 4sKd 0.999263 4dKs 0.999263 4sKh 0.999263 4cKd 0.999263 4dKc 0.999263 4sKc 0.999263 4cKh 0.999263 4cKs 0.999263 5d6h 0.998976 5d6c 0.998976 5d6s 0.998976 5c6s 0.998976 5c6h 0.998976 5s6h 0.998976 5s6d 0.998976 5s6c 0.998976 5c6d 0.998976 7hQc 0.999282 7cQd 0.999282 7sQh 0.999282 7dQh 0.999282 7hQs 0.999282 7hQd 0.999282 7dQs 0.999282 7sQc 0.999282 7sQd 0.999282 7cQh 0.999282 7cQs 0.999282 7dQc 0.999282 4s8s 0.99947 4c8c 0.99947 4d8d 0.99947 7s9h 0.999505 7s9d 0.999505 7c9h 0.999505 7d9c 0.999505 7c9s 0.999505 7d9h 0.999505 7h9s 0.999505 7c9d 0.999505 7s9c 0.999505 7h9d 0.999505 7h9c 0.999505 7d9s 0.999505 5cKd 0.99968 5cKh 0.99968 5cKs 0.99968 5sKc 0.99968 5dKc 0.99968 5dKh 0.99968 5sKh 0.99968 5dKs 0.99968 5sKd 0.99968 7hTc 0.99917 7cTs 0.99917 7dTh 0.99917 7hTd 0.99917 7dTc 0.99917 7hTs 0.99917 7cTd 0.99917 7cTh 0.99917 7sTd 0.99917 7sTh 0.99917 7dTs 0.99917 7sTc 0.99917 5dQh 0.999182 5sQh 0.999182 5cQd 0.999182 5cQh 0.999182 5sQd 0.999182 5sQc 0.999182 5cQs 0.999182 5dQs 0.999182 5dQc 0.999182 8dQh 0.99951 8cQh 0.99951 8sQc 0.99951 8cQs 0.99951 8sQd 0.99951 8hQs 0.99951 8cQd 0.99951 8hQc 0.99951 8sQh 0.99951 8hQd 0.99951 8dQs 0.99951 8dQc 0.99951 3dTd 0.998642 3hTh 0.998642 3sTs 0.998642 3dKd 0.999834 3hKh 0.999834 3sKs 0.999834 6d7h 0.999388 6c7h 0.999388 6c7s 0.999388 6d7s 0.999388 6h7c 0.999388 6h7d 0.999388 6c7d 0.999388 6h7s 0.999388 6s7h 0.999388 6s7c 0.999388 6s7d 0.999388 6d7c 0.999388 7cJh 0.999225 7cJd 0.999225 7dJc 0.999225 7hJc 0.999225 7dJh 0.999225 7dJs 0.999225 7sJd 0.999225 7cJs 0.999225 7hJs 0.999225 7sJc 0.999225 7hJd 0.999225 7sJh 0.999225 8cKd 0.999476 8dKc 0.999476 8hKc 0.999476 8sKd 0.999476 8dKs 0.999476 8dKh 0.999476 8hKs 0.999476 8sKh 0.999476 8cKs 0.999476 8sKc 0.999476 8cKh 0.999476 8hKd 0.999476 7h8c 0.999334 7h8s 0.999334 7d8s 0.999334 7c8s 0.999334 7c8h 0.999334 7s8c 0.999334 7c8d 0.999334 7h8d 0.999334 7s8d 0.999334 7d8c 0.999334 7s8h 0.999334 7d8h 0.999334 6dAh 0.999538 6sAh 0.999538 6cAd 0.999538 6sAd 0.999538 6hAs 0.999538 6hAd 0.999538 6dAc 0.999538 6cAs 0.999538 6dAs 0.999538 6sAc 0.999538 6hAc 0.999538 6cAh 0.999538 3h6h 0.997878 3s6s 0.997878 3d6d 0.997878 8hJc 0.999249 8dJc 0.999249 8hJs 0.999249 8sJd 0.999249 8dJh 0.999249 8sJh 0.999249 8dJs 0.999249 8hJd 0.999249 8sJc 0.999249 8cJs 0.999249 8cJh 0.999249 8cJd 0.999249 6dQh 0.999268 6cQh 0.999268 6dQs 0.999268 6hQs 0.999268 6cQs 0.999268 6hQc 0.999268 6hQd 0.999268 6sQc 0.999268 6dQc 0.999268 6sQh 0.999268 6sQd 0.999268 6cQd 0.999268 3sQs 1.0 3hQh 1.0 3dQd 1.0 9cQd 0.999676 9sQh 0.999676 9sQd 0.999676 9hQs 0.999676 9hQc 0.999676 9cQh 0.999676 9dQs 0.999676 9dQh 0.999676 9hQd 0.999676 9dQc 0.999676 9cQs 0.999676 9sQc 0.999676 3dJd 1.0 3hJh 1.0 3sJs 1.0 7dKc 0.999681 7dKh 0.999681 7dKs 0.999681 7hKc 0.999681 7hKs 0.999681 7hKd 0.999681 7cKd 0.999681 7cKs 0.999681 7sKh 0.999681 7sKc 0.999681 7sKd 0.999681 7cKh 0.999681 6hKs 0.999635 6sKh 0.999635 6dKs 0.999635 6sKc 0.999635 6dKc 0.999635 6hKc 0.999635 6hKd 0.999635 6dKh 0.999635 6cKs 0.999635 6sKd 0.999635 6cKd 0.999635 6cKh 0.999635 8hTd 0.999166 8hTc 0.999166 8cTh 0.999166 8sTh 0.999166 8cTd 0.999166 8dTh 0.999166 8hTs 0.999166 8dTc 0.999166 8sTd 0.999166 8sTc 0.999166 8dTs 0.999166 8cTs 0.999166 5cAh 0.99993 5sAh 0.99993 5dAs 0.99993 5dAc 0.99993 5cAs 0.99993 5sAd 0.99993 5sAc 0.99993 5dAh 0.99993 5cAd 0.99993 5dTd 1.0 5sTs 1.0 5cTc 1.0 9sJc 0.999412 9cJs 0.999412 9sJd 0.999412 9cJd 0.999412 9hJd 0.999412 9dJs 0.999412 9sJh 0.999412 9hJc 0.999412 9hJs 0.999412 9cJh 0.999412 9dJc 0.999412 9dJh 0.999412 TcQs 0.999585 TsQh 0.999585 TcQh 0.999585 TdQh 0.999585 TdQc 0.999585 TdQs 0.999585 TcQd 0.999585 TsQd 0.999585 ThQc 0.999585 ThQd 0.999585 ThQs 0.999585 TsQc 0.999585 8c9d 0.999263 8s9h 0.999263 8h9c 0.999263 8c9s 0.999263 8d9s 0.999263 8c9h 0.999263 8d9h 0.999263 8d9c 0.999263 8h9s 0.999263 8s9d 0.999263 8s9c 0.999263 8h9d 0.999263 TsJh 0.999552 ThJc 0.999552 TcJs 0.999552 ThJd 0.999552 TdJs 0.999552 TcJd 0.999552 TcJh 0.999552 TsJc 0.999552 ThJs 0.999552 TdJh 0.999552 TdJc 0.999552 TsJd 0.999552 3sAs 1.0 3dAd 1.0 3hAh 1.0 9cKs 1.0 9hKd 1.0 9sKc 1.0 9hKs 1.0 9sKd 1.0 9sKh 1.0 9dKs 1.0 9dKh 1.0 9hKc 1.0 9dKc 1.0 9cKh 1.0 9cKd 1.0 9sTh 0.999319 9dTh 0.999319 9sTd 0.999319 9cTs 0.999319 9dTs 0.999319 9sTc 0.999319 9cTd 0.999319 9cTh 0.999319 9hTs 0.999319 9dTc 0.999319 9hTc 0.999319 9hTd 0.999319 4dTd 0.999633 4cTc 0.999633 4sTs 0.999633 4s7s 1.0 4d7d 1.0 4c7c 1.0 JdQs 0.999668 JdQh 0.999668 JcQs 0.999668 JdQc 0.999668 JsQh 0.999668 JsQd 0.999668 JsQc 0.999668 JhQs 0.999668 JhQc 0.999668 JhQd 0.999668 JcQh 0.999668 JcQd 0.999668 7cAh 0.999432 7sAc 0.999432 7hAd 0.999432 7dAh 0.999432 7cAd 0.999432 7hAs 0.999432 7sAd 0.999432 7hAc 0.999432 7cAs 0.999432 7sAh 0.999432 7dAc 0.999432 7dAs 0.999432 6sJs 0.99937 6cJc 0.99937 6hJh 0.99937 6dJd 0.99937 2s2h 0.994712 2s2d 0.994712 2d2c 0.994712 2s2c 0.994712 2h2d 0.994712 2h2c 0.994712 TcKd 0.999596 TdKs 0.999596 ThKs 0.999596 TsKh 0.999596 ThKc 0.999596 TdKc 0.999596 TdKh 0.999596 TsKc 0.999596 TcKs 0.999596 ThKd 0.999596 TcKh 0.999596 TsKd 0.999596 4dJd 0.999691 4sJs 0.999691 4cJc 0.999691 8sAh 0.998841 8cAs 0.998841 8dAh 0.998841 8dAs 0.998841 8hAs 0.998841 8hAc 0.998841 8sAd 0.998841 8dAc 0.998841 8hAd 0.998841 8cAh 0.998841 8cAd 0.998841 8sAc 0.998841 4sQs 0.999011 4cQc 0.999011 4dQd 0.999011 7dJd 1.0 7sJs 1.0 7hJh 1.0 7cJc 1.0 4sKs 0.999506 4cKc 0.999506 4dKd 0.999506 5sQs 1.0 5cQc 1.0 5dQd 1.0 JcKh 0.999541 JsKh 0.999541 JdKh 0.999541 JhKd 0.999541 JhKc 0.999541 JsKc 0.999541 JhKs 0.999541 JdKs 0.999541 JcKs 0.999541 JdKc 0.999541 JcKd 0.999541 JsKd 0.999541 5s9s 0.99958 5c9c 0.99958 5d9d 0.99958 5c8c 0.999487 5s8s 0.999487 5d8d 0.999487 7hQh 0.999947 7dQd 0.999947 7cQc 0.999947 7sQs 0.999947 6dQd 1.0 6cQc 1.0 6hQh 1.0 6sQs 1.0 5sJs 0.999097 5dJd 0.999097 5cJc 0.999097 3s4s 0.998532 3d4d 0.998532 3d5d 0.999632 3s5s 0.999632 6dTd 0.999359 6cTc 0.999359 6sTs 0.999359 6hTh 0.999359 QdKc 0.999563 QdKs 0.999563 QsKc 0.999563 QcKd 0.999563 QhKs 0.999563 QsKh 0.999563 QhKd 0.999563 QhKc 0.999563 QsKd 0.999563 QdKh 0.999563 QcKs 0.999563 QcKh 0.999563 9dAs 0.999563 9cAd 0.999563 9sAc 0.999563 9sAh 0.999563 9sAd 0.999563 9dAc 0.999563 9dAh 0.999563 9hAs 0.999563 9cAs 0.999563 9hAd 0.999563 9hAc 0.999563 9cAh 0.999563 7cTc 0.999773 7dTd 0.999773 7sTs 0.999773 7hTh 0.999773 8hJh 1.0 8sJs 1.0 8cJc 1.0 8dJd 1.0 6s9s 0.999471 6c9c 0.999471 6h9h 0.999471 6d9d 0.999471 7h9h 0.99974 7c9c 0.99974 7d9d 0.99974 7s9s 0.99974 3s3h 0.998777 3s3d 0.998777 3h3d 0.998777 5sKs 0.999668 5cKc 0.999668 5dKd 0.999668 8cTc 0.99998 8sTs 0.99998 8dTd 0.99998 8hTh 0.99998 4s6s 0.99937 4d6d 0.99937 4c6c 0.99937 5s7s 0.999569 5d7d 0.999569 5c7c 0.999569 6s8s 0.999658 6c8c 0.999658 6h8h 0.999658 6d8d 0.999658 8h9h 0.999906 8d9d 0.999906 8c9c 0.999906 8s9s 0.999906 TdAc 1.0 TsAh 1.0 TdAh 1.0 TcAd 1.0 TsAc 1.0 TcAs 1.0 TsAd 1.0 ThAc 1.0 TcAh 1.0 ThAd 1.0 TdAs 1.0 ThAs 1.0 8dQd 0.999833 8cQc 0.999833 8sQs 0.999833 8hQh 0.999833 7s8s 1.0 7c8c 1.0 7h8h 1.0 7d8d 1.0 9dJd 0.999681 9cJc 0.999681 9hJh 0.999681 9sJs 0.999681 9dTd 0.99983 9hTh 0.99983 9sTs 0.99983 9cTc 0.99983 JhAc 1.0 JcAd 1.0 JdAs 1.0 JdAc 1.0 JsAc 1.0 JhAs 1.0 JhAd 1.0 JdAh 1.0 JsAh 1.0 JcAs 1.0 JcAh 1.0 JsAd 1.0 6d7d 1.0 6s7s 1.0 6c7c 1.0 6h7h 1.0 6dKd 0.999788 6cKc 0.999788 6sKs 0.999788 6hKh 0.999788 KsAc 0.999809 KhAd 0.999809 KcAs 0.999809 KsAh 0.999809 KdAc 0.999809 KdAh 0.999809 KdAs 0.999809 KhAc 0.999809 KsAd 0.999809 KhAs 0.999809 KcAd 0.999809 KcAh 0.999809 JsAs 0.999261 AdAc 0.99494 8dKd 0.999783 4d4c 1.0 9dKd 1.0 4d5d 0.999435 JhJd 1.0 9dAd 1.0 JhJc 1.0 JhQh 1.0 4dAd 1.0 4c5c 0.999435 JhKh 1.0 4cAc 1.0 5s5d 1.0 8d8c 1.0 9hAh 1.0 JhAh 0.999261 TsTh 1.0 8hAh 0.999686 TsTc 1.0 5s5c 1.0 JdJc 1.0 5s6s 1.0 JdQd 1.0 8hKh 0.999783 5sAs 1.0 9hQh 0.999783 JdKd 1.0 ThTc 1.0 5d5c 1.0 ThJh 1.0 5d6d 1.0 8h8c 1.0 ThQh 1.0 JdAd 0.999261 8h8d 1.0 ThKh 1.0 9sAs 1.0 ThAh 0.999204 8sAs 0.999686 JcQc 1.0 8sKs 0.999783 TdJd 1.0 5dAd 1.0 5c6c 1.0 TdQd 1.0 JcKc 1.0 9sQs 0.999783 5cAc 1.0 TdKd 1.0 6s6h 1.0 6s6d 1.0 TdAd 0.999204 JcAc 0.999261 9s9h 1.0 QsQh 1.0 QsQd 1.0 QsQc 1.0 QsKs 1.0 8cKc 0.999783 6s6c 1.0 8s8c 1.0 8s8d 1.0 4s4c 1.0 QsAs 0.99921 JsJh 1.0 QsAh 1.0 JsJc 1.0 QsAd 1.0 QsAc 1.0 QhQd 1.0 4s5s 0.999435 QhQc 1.0 4sAs 1.0 8s8h 1.0 QhKh 1.0 7cAc 0.999863 7hKh 0.999909 7cKc 0.999909 QhAs 1.0 9cQc 0.999783 QhAh 0.99921 9cKc 1.0 QhAd 1.0 9cAc 1.0 QhAc 1.0 TsJs 1.0 QdQc 1.0 TsQs 1.0 6sAs 1.0 TsKs 1.0 6h6d 1.0 TsAs 0.999204 QdKd 1.0 ThTd 1.0 6h6c 1.0 QdAs 1.0 QdAh 1.0 9h9d 1.0 QdAd 0.99921 9sKs 1.0 QdAc 1.0 6hAh 1.0 6d6c 1.0 7dAd 0.999863 9s9c 1.0 QcKc 1.0 8cAc 0.999686 QcAs 1.0 QcAh 1.0 QcAd 1.0 4s4d 1.0 QcAc 0.99921 JsJd 1.0 KsKh 1.0 KsKd 1.0 8dAd 0.999686 KsKc 1.0 9dQd 0.999783 KsAs 1.0 6dAd 1.0 7dKd 0.999909 TsTd 1.0 6cAc 1.0 KhKd 1.0 KhKc 1.0 7s7h 1.0 KhAh 1.0 7s7d 1.0 7s7c 1.0 TcJc 1.0 KdKc 1.0 TcKc 1.0 7sKs 0.999909 JsQs 1.0 7sAs 0.999863 KdAd 1.0 7h7d 1.0 7d7c 1.0 7h7c 1.0 TdTc 1.0 7hAh 0.999863 9s9d 1.0 KcAc 1.0 TcAc 0.999204 AsAh 0.99494 9hKh 1.0 AhAc 0.99494 AhAd 0.99494 AsAc 0.99494 AsAd 0.99494 9h9c 1.0 9d9c 1.0 JsKs 1.0 TcQc 1.0 ================================================ FILE: Source/Lookahead/Tests/ranges/flop-situation2-p2.txt ================================================ 6d7h 0.830509 6c7h 0.830509 6c7s 0.830509 6d7s 0.830509 6h7c 0.830509 6h7d 0.830509 6c7d 0.830509 6h7s 0.830509 6s7h 0.830509 6s7c 0.830509 6s7d 0.830509 6d7c 0.830509 7cJh 0.819686 7cJd 0.819686 7dJc 0.819686 7hJc 0.819686 7dJh 0.819686 7dJs 0.819686 7sJd 0.819686 7cJs 0.819686 ================================================ FILE: Source/Lookahead/Tests/ranges/flop-situation3-p1.txt ================================================ 8s8h 1.0 AhAc 0.99494 4s4c 1.0 5sAs 1.0 9dQd 0.999783 9hAh 1.0 JhJc 0.999812 JsQs 1.0 JhJd 0.999812 KhKd 0.99985 9cAc 1.0 AsAh 0.99494 TsTh 0.999517 KsKd 0.99985 JsAs 0.999261 QcAc 0.99921 8dKd 0.999783 QcAs 1.0 TsTd 0.999517 8s8c 1.0 JsKs 1.0 QdAc 1.0 4s4h 1.0 QdAs 1.0 4s4d 1.0 7dKd 0.999833 TsTc 0.999517 QdQc 0.999789 KhAh 1.0 QhAh 0.99921 JsJc 0.999812 7d7c 0.999893 JsJd 0.999812 7hAh 0.999378 JsJh 0.999812 QhQd 0.999789 TcAc 0.999078 QsAh 1.0 6d6c 0.999991 8sAs 0.999386 6hAh 1.0 QsKs 1.0 TsJs 0.999815 QsQd 0.999789 5hAh 1.0 JcAc 0.999261 6h6c 0.999991 5s6s 1.0 TcQc 1.0 7hKh 0.999833 4s5s 0.999406 JcQc 1.0 6h6d 0.999991 7sAs 0.999378 6sAs 1.0 5s5c 1.0 TcJc 0.999815 5s5d 1.0 5d5c 1.0 AsAc 0.99494 8cAc 0.999386 8hAh 0.999386 4sAs 0.999938 8d8c 1.0 9s9h 1.0 JdJc 0.999812 9s9d 1.0 JhAh 0.999261 9s9c 1.0 7s7d 0.999893 TdKd 0.999882 7s7h 0.999893 TsQs 1.0 5h6h 1.0 4c5c 0.999406 9dKd 1.0 4h4d 1.0 KsAs 1.0 TdQd 1.0 QcAh 1.0 4h4c 1.0 7cAc 0.999378 4h5h 0.999406 QdKd 1.0 6s6c 0.999991 QhAc 1.0 TdJd 0.999815 QhKh 1.0 9sQs 0.999783 QsAc 1.0 6s6d 0.999991 8h8d 1.0 TdTc 0.999517 QsQh 0.999789 6s6h 0.999991 8h8c 1.0 ThAh 0.999078 7h7d 0.999893 5cAc 1.0 9cQc 0.999783 9sKs 1.0 8hKh 0.999783 ThKh 0.999882 7s7c 0.999893 9sAs 1.0 4cAc 0.999938 9h9d 1.0 6cAc 1.0 4hAh 0.999938 5h5d 1.0 ThQh 1.0 8s8d 1.0 4d4c 1.0 8sKs 0.999783 4d5d 0.999406 QhQc 0.999789 9h9c 1.0 QsQc 0.999789 ThJh 0.999815 7h7c 0.999893 9d9c 1.0 JdKd 1.0 ThTc 0.999517 5s5h 1.0 ThTd 0.999517 JhQh 1.0 5c6c 1.0 QdAh 1.0 9hQh 0.999783 QsAs 0.99921 7sKs 0.999833 TsKs 0.999882 5d6d 1.0 9hKh 1.0 TsAs 0.999078 JdQd 1.0 5h5c 1.0 QhAs 1.0 KsKh 0.99985 JhKh 1.0 KdAc 0.999809 KdAs 0.999809 KhAc 0.999809 KsAh 0.999809 KdAh 0.999809 KhAs 0.999809 KsAc 0.999809 6dKd 0.999788 6hKh 0.999788 6sKs 0.999788 6d7d 1.0 6s7s 1.0 6c7c 1.0 6h7h 1.0 JhAs 0.999991 JsAc 0.999991 JhAc 0.999991 JsAh 0.999991 JdAs 0.999991 JcAh 0.999991 JcAs 0.999991 JdAc 0.999991 JdAh 0.999991 9hTh 0.999241 9cTc 0.999241 9sTs 0.999241 9dTd 0.999241 9cJc 0.999681 9hJh 0.999681 9sJs 0.999681 9dJd 0.999681 7s8s 0.999593 7d8d 0.999593 7h8h 0.999593 7c8c 0.999593 8cQc 0.999833 8hQh 0.999833 8sQs 0.999833 8dQd 0.999833 ThAs 1.0 TcAs 1.0 TcAh 1.0 TsAh 1.0 ThAc 1.0 TdAh 1.0 TdAc 1.0 TsAc 1.0 TdAs 1.0 8c9c 0.99976 8d9d 0.99976 8s9s 0.99976 8h9h 0.99976 6c8c 0.999658 6d8d 0.999658 6h8h 0.999658 6s8s 0.999658 5h7h 0.999569 5c7c 0.999569 5d7d 0.999569 5s7s 0.999569 4s6s 0.99937 4d6d 0.99937 4h6h 0.99937 4c6c 0.99937 8dTd 0.999506 8hTh 0.999506 8cTc 0.999506 8sTs 0.999506 5sKs 0.999668 5hKh 0.999668 5dKd 0.999668 3s3d 0.998486 3h3d 0.998486 3s3h 0.998486 7h9h 0.999421 7d9d 0.999421 7s9s 0.999421 7c9c 0.999421 6d9d 0.999321 6h9h 0.999321 6c9c 0.999321 6s9s 0.999321 8dJd 1.0 8sJs 1.0 8hJh 1.0 8cJc 1.0 7hTh 0.99966 7cTc 0.99966 7dTd 0.99966 7sTs 0.99966 9cAs 0.999563 9sAc 0.999563 9dAs 0.999563 9cAh 0.999563 9dAh 0.999563 9sAh 0.999563 9hAc 0.999563 9dAc 0.999563 9hAs 0.999563 QsKh 0.999563 QcKs 0.999563 QhKd 0.999563 QhKs 0.999563 QdKs 0.999563 QcKh 0.999563 QsKd 0.999563 QcKd 0.999563 QdKh 0.999563 6cTc 0.999359 6dTd 0.999359 6sTs 0.999359 6hTh 0.999359 3h5h 0.999632 3s5s 0.999632 3d5d 0.999632 3d4d 0.998532 3h4h 0.998532 3s4s 0.998532 5hJh 0.999097 5dJd 0.999097 5sJs 0.999097 5cJc 0.999097 6hQh 1.0 6cQc 1.0 6sQs 1.0 6dQd 1.0 7sQs 0.999947 7dQd 0.999947 7cQc 0.999947 7hQh 0.999947 5s8s 0.999487 5c8c 0.999487 5d8d 0.999487 5h8h 0.999487 5s9s 0.999539 5h9h 0.999539 5c9c 0.999539 5d9d 0.999539 JcKs 0.999541 JhKs 0.999541 JdKs 0.999541 JsKd 0.999541 JhKd 0.999541 JsKh 0.999541 JcKh 0.999541 JcKd 0.999541 JdKh 0.999541 5dQd 1.0 5cQc 1.0 5sQs 1.0 5hQh 1.0 4dKd 0.999506 4sKs 0.999506 4hKh 0.999506 7cJc 1.0 7sJs 1.0 7dJd 1.0 7hJh 1.0 4sQs 0.998908 4dQd 0.998908 4cQc 0.998908 4hQh 0.998908 8dAh 0.998841 8hAc 0.998841 8hAs 0.998841 8sAh 0.998841 8dAc 0.998841 8cAs 0.998841 8cAh 0.998841 8sAc 0.998841 8dAs 0.998841 4cJc 0.999519 4hJh 0.999519 4sJs 0.999519 4dJd 0.999519 TcKd 0.999596 TcKh 0.999596 ThKs 0.999596 TdKs 0.999596 TdKh 0.999596 ThKd 0.999596 TsKh 0.999596 TcKs 0.999596 TsKd 0.999596 2s2h 0.994151 2d2c 0.994151 2h2c 0.994151 2h2d 0.994151 2s2c 0.994151 2s2d 0.994151 6sJs 0.99937 6hJh 0.99937 6cJc 0.99937 6dJd 0.99937 7dAs 0.999388 7hAc 0.999388 7sAh 0.999388 7sAc 0.999388 7dAh 0.999388 7hAs 0.999388 7cAh 0.999388 7cAs 0.999388 7dAc 0.999388 JcQd 0.999517 JhQc 0.999517 JdQh 0.999517 JsQh 0.999517 JcQh 0.999517 JdQc 0.999517 JdQs 0.999517 JsQc 0.999517 JhQs 0.999517 JhQd 0.999517 JsQd 0.999517 JcQs 0.999517 4c7c 1.0 4d7d 1.0 4h7h 1.0 4s7s 1.0 4hTh 0.999347 4dTd 0.999347 4sTs 0.999347 4cTc 0.999347 9hTc 0.999158 9cTh 0.999158 9dTh 0.999158 9hTs 0.999158 9sTc 0.999158 9sTh 0.999158 9cTd 0.999158 9hTd 0.999158 9sTd 0.999158 9dTc 0.999158 9cTs 0.999158 9dTs 0.999158 9cKs 1.0 9cKh 1.0 9dKs 1.0 9sKd 1.0 9hKs 1.0 9hKd 1.0 9dKh 1.0 9sKh 1.0 9cKd 1.0 3hAh 0.999815 3sAs 0.999815 TdJh 0.999512 TcJs 0.999512 TsJh 0.999512 TdJs 0.999512 TsJd 0.999512 TsJc 0.999512 ThJd 0.999512 ThJc 0.999512 TdJc 0.999512 ThJs 0.999512 TcJh 0.999512 TcJd 0.999512 8s9h 0.999263 8c9s 0.999263 8s9d 0.999263 8s9c 0.999263 8h9c 0.999263 8c9h 0.999263 8d9s 0.999263 8d9h 0.999263 8c9d 0.999263 8h9d 0.999263 8d9c 0.999263 8h9s 0.999263 TsQd 0.999368 ThQs 0.999368 TsQh 0.999368 TsQc 0.999368 TdQs 0.999368 TcQd 0.999368 TdQh 0.999368 TcQh 0.999368 TcQs 0.999368 ThQd 0.999368 ThQc 0.999368 TdQc 0.999368 9sJh 0.999412 9dJc 0.999412 9hJs 0.999412 9hJc 0.999412 9sJc 0.999412 9hJd 0.999412 9dJs 0.999412 9cJs 0.999412 9cJh 0.999412 9cJd 0.999412 9dJh 0.999412 9sJd 0.999412 5cTc 1.0 5sTs 1.0 5dTd 1.0 5hTh 1.0 5dAs 0.999721 5hAc 0.999721 5sAc 0.999721 5hAs 0.999721 5cAh 0.999721 5sAh 0.999721 5cAs 0.999721 5dAc 0.999721 5dAh 0.999721 8sTc 0.999166 8dTh 0.999166 8sTd 0.999166 8hTd 0.999166 8sTh 0.999166 8hTc 0.999166 8hTs 0.999166 8dTc 0.999166 8cTd 0.999166 8dTs 0.999166 8cTs 0.999166 8cTh 0.999166 6cKs 0.999635 6hKd 0.999635 6cKh 0.999635 6cKd 0.999635 6sKd 0.999635 6dKh 0.999635 6sKh 0.999635 6hKs 0.999635 6dKs 0.999635 7sKd 0.999455 7dKh 0.999455 7cKs 0.999455 7hKs 0.999455 7dKs 0.999455 7cKh 0.999455 7hKd 0.999455 7cKd 0.999455 7sKh 0.999455 3sJs 1.0 3hJh 1.0 3dJd 1.0 9dQc 0.999502 9hQc 0.999502 9sQc 0.999502 9cQs 0.999502 9sQh 0.999502 9hQs 0.999502 9sQd 0.999502 9hQd 0.999502 9cQh 0.999502 9cQd 0.999502 9dQs 0.999502 9dQh 0.999502 3hQh 0.999811 3sQs 0.999811 3dQd 0.999811 6hQc 0.999029 6hQd 0.999029 6cQh 0.999029 6sQh 0.999029 6sQc 0.999029 6dQs 0.999029 6hQs 0.999029 6sQd 0.999029 6dQh 0.999029 6cQd 0.999029 6dQc 0.999029 6cQs 0.999029 8sJc 0.999249 8dJs 0.999249 8dJc 0.999249 8sJd 0.999249 8sJh 0.999249 8cJh 0.999249 8dJh 0.999249 8hJd 0.999249 8hJc 0.999249 8cJd 0.999249 8hJs 0.999249 8cJs 0.999249 3d6d 0.997878 3s6s 0.997878 3h6h 0.997878 6dAc 0.999486 6dAh 0.999486 6dAs 0.999486 6cAh 0.999486 6hAc 0.999486 6sAh 0.999486 6sAc 0.999486 6hAs 0.999486 6cAs 0.999486 7c8h 0.999334 7h8c 0.999334 7d8s 0.999334 7s8c 0.999334 7s8d 0.999334 7h8s 0.999334 7h8d 0.999334 7s8h 0.999334 7c8s 0.999334 7d8h 0.999334 7d8c 0.999334 7c8d 0.999334 8dKh 0.999476 8hKd 0.999476 8dKs 0.999476 8cKs 0.999476 8cKh 0.999476 8cKd 0.999476 8sKh 0.999476 8sKd 0.999476 8hKs 0.999476 7sJd 0.99917 7hJs 0.99917 7dJs 0.99917 7cJh 0.99917 7dJh 0.99917 7hJc 0.99917 7dJc 0.99917 7hJd 0.99917 7sJh 0.99917 7cJd 0.99917 7cJs 0.99917 7sJc 0.99917 6d7h 0.999388 6c7s 0.999388 6c7h 0.999388 6d7c 0.999388 6h7s 0.999388 6h7d 0.999388 6h7c 0.999388 6s7h 0.999388 6d7s 0.999388 6s7c 0.999388 6s7d 0.999388 6c7d 0.999388 3sKs 0.999802 3dKd 0.999802 3hKh 0.999802 3dTd 0.998642 3sTs 0.998642 3hTh 0.998642 8hQd 0.999244 8dQs 0.999244 8cQs 0.999244 8hQc 0.999244 8hQs 0.999244 8dQc 0.999244 8sQc 0.999244 8sQd 0.999244 8cQh 0.999244 8cQd 0.999244 8sQh 0.999244 8dQh 0.999244 5dQs 0.999073 5dQh 0.999073 5sQc 0.999073 5dQc 0.999073 5sQd 0.999073 5hQs 0.999073 5cQd 0.999073 5hQd 0.999073 5hQc 0.999073 5cQh 0.999073 5cQs 0.999073 5sQh 0.999073 7cTs 0.99917 7dTh 0.99917 7sTc 0.99917 7dTc 0.99917 7cTd 0.99917 7sTh 0.99917 7sTd 0.99917 7cTh 0.99917 7hTc 0.99917 7hTd 0.99917 7hTs 0.99917 7dTs 0.99917 5cKs 0.999452 5hKd 0.999452 5hKs 0.999452 5cKh 0.999452 5dKs 0.999452 5sKh 0.999452 5dKh 0.999452 5cKd 0.999452 5sKd 0.999452 7c9h 0.999505 7c9d 0.999505 7d9c 0.999505 7h9s 0.999505 7d9h 0.999505 7d9s 0.999505 7s9c 0.999505 7h9d 0.999505 7c9s 0.999505 7s9h 0.999505 7s9d 0.999505 7h9c 0.999505 4h8h 0.99947 4s8s 0.99947 4c8c 0.99947 4d8d 0.99947 7hQc 0.999282 7cQs 0.999282 7dQc 0.999282 7cQd 0.999282 7dQh 0.999282 7hQd 0.999282 7sQc 0.999282 7hQs 0.999282 7dQs 0.999282 7cQh 0.999282 7sQd 0.999282 7sQh 0.999282 5h6s 0.998976 5c6s 0.998976 5s6h 0.998976 5h6c 0.998976 5s6d 0.998976 5h6d 0.998976 5d6c 0.998976 5c6h 0.998976 5c6d 0.998976 5d6s 0.998976 5s6c 0.998976 5d6h 0.998976 4dKh 0.999183 4cKh 0.999183 4sKh 0.999183 4dKs 0.999183 4hKd 0.999183 4sKd 0.999183 4cKd 0.999183 4hKs 0.999183 4cKs 0.999183 4c5d 0.99914 4h5s 0.99914 4s5d 0.99914 4s5h 0.99914 4d5h 0.99914 4d5s 0.99914 4s5c 0.99914 4h5c 0.99914 4d5c 0.99914 4c5h 0.99914 4c5s 0.99914 4h5d 0.99914 5dJs 0.998705 5sJh 0.998705 5cJh 0.998705 5sJd 0.998705 5cJs 0.998705 5sJc 0.998705 5dJc 0.998705 5hJd 0.998705 5hJc 0.998705 5hJs 0.998705 5dJh 0.998705 5cJd 0.998705 3s9s 0.997845 3h9h 0.997845 3d9d 0.997845 6d9h 0.998993 6h9s 0.998993 6s9h 0.998993 6d9s 0.998993 6d9c 0.998993 6h9c 0.998993 6c9d 0.998993 6s9d 0.998993 6c9s 0.998993 6h9d 0.998993 6c9h 0.998993 6s9c 0.998993 2hJh 0.99771 2dJd 0.99771 2sJs 0.99771 2cJc 0.99771 6dTs 0.998801 6hTc 0.998801 6cTs 0.998801 6cTd 0.998801 6sTh 0.998801 6cTh 0.998801 6hTd 0.998801 6sTc 0.998801 6hTs 0.998801 6sTd 0.998801 6dTh 0.998801 6dTc 0.998801 6hJd 0.998794 6sJh 0.998794 6cJh 0.998794 6hJs 0.998794 6dJc 0.998794 6sJc 0.998794 6dJh 0.998794 6sJd 0.998794 6hJc 0.998794 6cJs 0.998794 6cJd 0.998794 6dJs 0.998794 6s8h 0.999003 6h8c 0.999003 6c8h 0.999003 6d8s 0.999003 6s8d 0.999003 6d8c 0.999003 6d8h 0.999003 6c8s 0.999003 6s8c 0.999003 6h8d 0.999003 6h8s 0.999003 6c8d 0.999003 4d9d 0.998765 4c9c 0.998765 4h9h 0.998765 4s9s 0.998765 4sQd 0.998753 4cQs 0.998753 4dQs 0.998753 4sQc 0.998753 4hQc 0.998753 4hQd 0.998753 4hQs 0.998753 4dQc 0.998753 4sQh 0.998753 4cQh 0.998753 4cQd 0.998753 4dQh 0.998753 4dAs 0.999831 4hAs 0.999831 4hAc 0.999831 4dAc 0.999831 4dAh 0.999831 4sAh 0.999831 4sAc 0.999831 4cAh 0.999831 4cAs 0.999831 2dTd 0.996464 2sTs 0.996464 2hTh 0.996464 2cTc 0.996464 2sQs 1.0 2hQh 1.0 2cQc 1.0 2dQd 1.0 2cAc 0.99962 2hAh 0.99962 2sAs 0.99962 4hJs 0.998619 4hTd 0.746475 5c7d 0.999284 4hTs 0.746475 5c8s 0.998541 5c8h 0.998541 4d7h 0.054927 2cAh 0.999367 2cAs 0.999367 4h7d 0.054927 2cKd 0.999134 4h7s 0.054927 2cKh 0.999134 4h6d 0.998793 2cKs 0.999134 4h6s 0.998793 4d7s 0.054927 4hJd 0.998619 2cQd 0.998384 2cQh 0.998384 5s7h 0.999284 2cQs 0.998384 4c7d 0.054927 5c8d 0.998541 5s7c 0.999284 5s8d 0.998541 4dTs 0.746475 5s8c 0.998541 4cTd 0.746475 4sJd 0.998619 5s9h 0.99831 4sTd 0.746475 5s9d 0.99831 2c9c 0.996303 4c7s 0.054927 5s9c 0.99831 2c8c 0.996726 4s7d 0.054927 5sTh 0.998028 4s6d 0.998793 5sTd 0.998028 2c7c 0.995828 5h7s 0.999284 5h7c 0.999284 4dTc 0.746475 3dAc 0.999944 2c6c 0.995154 3dAs 0.999944 3dKh 0.999133 3dQc 0.998311 3dQh 0.998311 2c5c 0.997305 3dJc 0.99866 3dJh 0.99866 2c4c 0.997887 2dAc 0.999367 2dAh 0.999367 2dAs 0.999367 2dKd 0.999069 2dKh 0.999134 2dKs 0.999134 3hAc 0.999944 2dQc 0.998384 3hAs 0.999944 4cTh 0.746475 5d7s 0.999284 2dQh 0.998384 3hQc 0.998311 2dQs 0.998384 5d7h 0.999284 3hJc 0.99866 5c9s 0.99831 5d7c 0.999284 5d8s 0.998541 4cTs 0.746475 5d8h 0.998541 3h8h 0.997718 2d9d 0.996303 3h7h 0.999628 5d8c 0.998541 2d8d 0.996726 5d9s 0.99831 5d9h 0.99831 5d9c 0.99831 2d7d 0.995828 3sAh 0.999944 3sKd 0.999133 5dTh 0.998028 3sQd 0.998311 2d6d 0.995154 5dTc 0.998028 3sJd 0.99866 4c6d 0.998793 2d5d 0.997305 4dJc 0.998619 4c6h 0.998793 2d4d 0.997887 3s8s 0.997718 3s7s 0.999628 2d3d 0.996645 5c7s 0.999284 5c9h 0.99831 5c7h 0.999284 2hAc 0.999367 4cJd 0.998619 4d6c 0.998793 4d7c 0.054927 2hAs 0.999367 2hKd 0.999134 2hKh 0.999069 4c7h 0.054927 2hKs 0.999134 2hQc 0.998384 4hJc 0.998619 2hQd 0.998384 5s8h 0.998541 4dTh 0.746475 2hQs 0.998384 4sJh 0.998619 4sTh 0.746475 5c9d 0.99831 4s7h 0.054927 4s6h 0.998793 5h7d 0.999284 5h8d 0.998541 5h8c 0.998541 5h9s 0.99831 4dJs 0.998619 2h9h 0.996303 5h9d 0.99831 5h9c 0.99831 3d8d 0.997718 3d7d 0.999628 2h8h 0.996726 5hTs 0.998028 4dJh 0.998619 5hTd 0.998028 5hTc 0.998028 2h7h 0.995828 3hKs 0.999133 3hQs 0.998311 3hJs 0.99866 2h6h 0.995154 2h5h 0.997305 5dTs 0.998028 3sQc 0.998311 2h4h 0.997887 3sJc 0.99866 4cJs 0.998619 2h3h 0.996645 5cTs 0.998028 5cTh 0.998028 2sAc 0.999367 4hTc 0.746475 2sAh 0.999367 4h6c 0.998793 2sKd 0.999134 2sKh 0.999134 2sKs 0.999069 4sTc 0.746475 2sQc 0.998384 2sQd 0.998384 4s6c 0.998793 2sQh 0.998384 5h8s 0.998541 3dKs 0.999133 3dJs 0.99866 5cTd 0.998028 3hKd 0.999133 3hJd 0.99866 3sAc 0.999944 3sQh 0.998311 2s9s 0.996303 2s8s 0.996726 4cJh 0.998619 4sJc 0.998619 4s7c 0.054927 3dAh 0.999944 2s7s 0.995828 3hQd 0.998311 2s6s 0.995154 3sJh 0.99866 4d6h 0.998793 4h7c 0.054927 2s5s 0.997305 3dQs 0.998311 2s4s 0.997887 5s7d 0.999284 4d6s 0.998793 4c6s 0.998793 2s3s 0.996645 5sTc 0.998028 3sKh 0.999133 ================================================ FILE: Source/Lookahead/Tests/ranges/flop-situation3-p2.txt ================================================ 8s8h 1.0 AhAc 1.0 4s4c 1.0 5sAs 1.0 9dQd 1.0 9hAh 1.0 JhJc 1.0 JsQs 1.0 JhJd 1.0 KhKd 1.0 9cAc 1.0 AsAh 1.0 TsTh 1.0 KsKd 1.0 JsAs 1.0 QcAc 1.0 8dKd 1.0 QcAs 1.0 TsTd 1.0 8s8c 1.0 JsKs 1.0 QdAc 1.0 4s4h 1.0 QdAs 1.0 4s4d 1.0 7dKd 1.0 TsTc 1.0 QdQc 1.0 KhAh 1.0 QhAh 1.0 JsJc 1.0 7d7c 1.0 JsJd 1.0 7hAh 1.0 JsJh 1.0 QhQd 1.0 TcAc 1.0 QsAh 1.0 6d6c 1.0 8sAs 1.0 6hAh 1.0 QsKs 1.0 TsJs 1.0 QsQd 1.0 5hAh 1.0 JcAc 1.0 6h6c 1.0 5s6s 1.0 TcQc 1.0 7hKh 1.0 4s5s 1.0 JcQc 1.0 6h6d 1.0 7sAs 1.0 6sAs 1.0 5s5c 1.0 TcJc 1.0 5s5d 1.0 5d5c 1.0 AsAc 1.0 8cAc 1.0 8hAh 1.0 4sAs 1.0 8d8c 1.0 9s9h 1.0 JdJc 1.0 9s9d 1.0 JhAh 1.0 9s9c 1.0 7s7d 1.0 TdKd 1.0 7s7h 1.0 TsQs 1.0 5h6h 1.0 4c5c 1.0 9dKd 1.0 4h4d 1.0 KsAs 1.0 TdQd 1.0 QcAh 1.0 4h4c 1.0 7cAc 1.0 4h5h 1.0 QdKd 1.0 6s6c 1.0 QhAc 1.0 TdJd 1.0 QhKh 1.0 9sQs 1.0 QsAc 1.0 6s6d 1.0 8h8d 1.0 TdTc 1.0 QsQh 1.0 6s6h 1.0 8h8c 1.0 ThAh 1.0 7h7d 1.0 5cAc 1.0 9cQc 1.0 9sKs 1.0 8hKh 1.0 ThKh 1.0 7s7c 1.0 9sAs 1.0 4cAc 1.0 9h9d 1.0 6cAc 1.0 4hAh 1.0 5h5d 1.0 ThQh 1.0 8s8d 1.0 4d4c 1.0 8sKs 1.0 4d5d 1.0 QhQc 1.0 9h9c 1.0 QsQc 1.0 ThJh 1.0 7h7c 1.0 9d9c 1.0 JdKd 1.0 ThTc 1.0 5s5h 1.0 ThTd 1.0 JhQh 1.0 5c6c 1.0 QdAh 1.0 9hQh 1.0 QsAs 1.0 7sKs 1.0 TsKs 1.0 5d6d 1.0 9hKh 1.0 TsAs 1.0 JdQd 1.0 5h5c 1.0 QhAs 1.0 KsKh 1.0 JhKh 1.0 KdAc 0.99948 KdAs 0.99948 KhAc 0.99948 KsAh 0.99948 KdAh 0.99948 KhAs 0.99948 KsAc 0.99948 6dKd 0.996459 6hKh 0.996459 6sKs 0.996459 6d7d 0.990574 6s7s 0.990574 6c7c 0.990574 6h7h 0.990574 JhAs 0.9881 JsAc 0.9881 JhAc 0.9881 JsAh 0.9881 JdAs 0.9881 JcAh 0.9881 JcAs 0.9881 JdAc 0.9881 JdAh 0.9881 9hTh 0.966349 9cTc 0.966349 9sTs 0.966349 9dTd 0.966349 9cJc 0.961261 9hJh 0.961261 9sJs 0.961261 9dJd 0.961261 7s8s 0.937578 7d8d 0.937578 7h8h 0.937578 7c8c 0.937578 8cQc 0.929088 8hQh 0.929088 8sQs 0.929088 8dQd 0.929088 ThAs 0.92546 TcAs 0.92546 TcAh 0.92546 TsAh 0.92546 ThAc 0.92546 TdAh 0.92546 TdAc 0.92546 TsAc 0.92546 TdAs 0.92546 8c9c 0.922554 8d9d 0.922554 8s9s 0.922554 8h9h 0.922554 6c8c 0.887115 6d8d 0.887115 6h8h 0.887115 6s8s 0.887115 5h7h 0.870201 5c7c 0.870201 5d7d 0.870201 5s7s 0.870201 4s6s 0.864189 4d6d 0.864189 4h6h 0.864189 4c6c 0.864189 8dTd 0.848302 8hTh 0.848302 8cTc 0.848302 8sTs 0.848302 5sKs 0.847799 5hKh 0.847799 5dKd 0.847799 3s3d 0.834255 3h3d 0.834255 3s3h 0.834255 7h9h 0.833698 7d9d 0.833698 7s9s 0.833698 7c9c 0.833698 6d9d 0.818515 6h9h 0.818515 6c9c 0.818515 6s9s 0.818515 8dJd 0.792354 8sJs 0.792354 8hJh 0.792354 8cJc 0.792354 7hTh 0.782837 7cTc 0.782837 7dTd 0.782837 7sTs 0.782837 9cAs 0.775404 9sAc 0.775404 9dAs 0.775404 9cAh 0.775404 9dAh 0.775404 9sAh 0.775404 9hAc 0.775404 9dAc 0.775404 9hAs 0.775404 QsKh 0.764181 QcKs 0.764181 QhKd 0.764181 QhKs 0.764181 QdKs 0.764181 QcKh 0.764181 QsKd 0.764181 QcKd 0.764181 QdKh 0.764181 6cTc 0.745567 6dTd 0.745567 6sTs 0.745567 6hTh 0.745567 3h5h 0.679195 3s5s 0.679195 3d5d 0.679195 3d4d 0.678556 3h4h 0.678556 3s4s 0.678556 5hJh 0.647998 5dJd 0.647998 5sJs 0.647998 5cJc 0.647998 6hQh 0.644814 6cQc 0.644814 6sQs 0.644814 6dQd 0.644814 7sQs 0.635211 7dQd 0.635211 7cQc 0.635211 7hQh 0.635211 5s8s 0.630807 5c8c 0.630807 5d8d 0.630807 5h8h 0.630807 5s9s 0.617031 5h9h 0.617031 5c9c 0.617031 5d9d 0.617031 JcKs 0.615132 JhKs 0.615132 JdKs 0.615132 JsKd 0.615132 JhKd 0.615132 JsKh 0.615132 JcKh 0.615132 JcKd 0.615132 JdKh 0.615132 5dQd 0.612392 5cQc 0.612392 5sQs 0.612392 5hQh 0.612392 4dKd 0.605889 4sKs 0.605889 4hKh 0.605889 7cJc 0.605393 7sJs 0.605393 7dJd 0.605393 7hJh 0.605393 4sQs 0.59431 4dQd 0.59431 4cQc 0.59431 4hQh 0.59431 8dAh 0.575158 8hAc 0.575158 8hAs 0.575158 8sAh 0.575158 8dAc 0.575158 8cAs 0.575158 8cAh 0.575158 8sAc 0.575158 8dAs 0.575158 4cJc 0.529954 4hJh 0.529954 4sJs 0.529954 4dJd 0.529954 TcKd 0.495103 TcKh 0.495103 ThKs 0.495103 TdKs 0.495103 TdKh 0.495103 ThKd 0.495103 TsKh 0.495103 TcKs 0.495103 TsKd 0.495103 2s2h 0.456526 2d2c 0.456526 2h2c 0.456526 2h2d 0.456526 2s2c 0.456526 2s2d 0.456526 6sJs 0.424636 6hJh 0.424636 6cJc 0.424636 6dJd 0.424636 7dAs 0.42414 7hAc 0.42414 7sAh 0.42414 7sAc 0.42414 7dAh 0.42414 7hAs 0.42414 7cAh 0.42414 7cAs 0.42414 7dAc 0.42414 JcQd 0.421432 JhQc 0.421432 JdQh 0.421432 JsQh 0.421432 JcQh 0.421432 JdQc 0.421432 JdQs 0.421432 JsQc 0.421432 JhQs 0.421432 JhQd 0.421432 JsQd 0.421432 JcQs 0.421432 4c7c 0.420963 4d7d 0.420963 4h7h 0.420963 4s7s 0.420963 4hTh 0.409083 4dTd 0.409083 4sTs 0.409083 4cTc 0.409083 9hTc 0.392535 9cTh 0.392535 9dTh 0.392535 9hTs 0.392535 9sTc 0.392535 9sTh 0.392535 9cTd 0.392535 9hTd 0.392535 9sTd 0.392535 9dTc 0.392535 9cTs 0.392535 9dTs 0.392535 9cKs 0.382497 9cKh 0.382497 9dKs 0.382497 9sKd 0.382497 9hKs 0.382497 9hKd 0.382497 9dKh 0.382497 9sKh 0.382497 9cKd 0.382497 3hAh 0.375764 3sAs 0.375764 TdJh 0.36661 TcJs 0.36661 TsJh 0.36661 TdJs 0.36661 TsJd 0.36661 TsJc 0.36661 ThJd 0.36661 ThJc 0.36661 TdJc 0.36661 ThJs 0.36661 TcJh 0.36661 TcJd 0.36661 8s9h 0.325181 8c9s 0.325181 8s9d 0.325181 8s9c 0.325181 8h9c 0.325181 8c9h 0.325181 8d9s 0.325181 8d9h 0.325181 8c9d 0.325181 8h9d 0.325181 8d9c 0.325181 8h9s 0.325181 TsQd 0.312877 ThQs 0.312877 TsQh 0.312877 TsQc 0.312877 TdQs 0.312877 TcQd 0.312877 TdQh 0.312877 TcQh 0.312877 TcQs 0.312877 ThQd 0.312877 ThQc 0.312877 TdQc 0.312877 9sJh 0.273196 9dJc 0.273196 9hJs 0.273196 9hJc 0.273196 9sJc 0.273196 9hJd 0.273196 9dJs 0.273196 9cJs 0.273196 9cJh 0.273196 9cJd 0.273196 9dJh 0.273196 9sJd 0.273196 5cTc 0.272444 5sTs 0.272444 5dTd 0.272444 5hTh 0.272444 5dAs 0.271799 5hAc 0.271799 5sAc 0.271799 5hAs 0.271799 5cAh 0.271799 5sAh 0.271799 5cAs 0.271799 5dAc 0.271799 5dAh 0.271799 8sTc 0.243467 8dTh 0.243467 8sTd 0.243467 8hTd 0.243467 8sTh 0.243467 8hTc 0.243467 8hTs 0.243467 8dTc 0.243467 8cTd 0.243467 8dTs 0.243467 8cTs 0.243467 8cTh 0.243467 6cKs 0.242493 6hKd 0.242493 6cKh 0.242493 6cKd 0.242493 6sKd 0.242493 6dKh 0.242493 6sKh 0.242493 6hKs 0.242493 6dKs 0.242493 7sKd 0.24114 7dKh 0.24114 7cKs 0.24114 7hKs 0.24114 7dKs 0.24114 7cKh 0.24114 7hKd 0.24114 7cKd 0.24114 7sKh 0.24114 3sJs 0.237643 3hJh 0.237643 3dJd 0.237643 9dQc 0.233052 9hQc 0.233052 9sQc 0.233052 9cQs 0.233052 9sQh 0.233052 9hQs 0.233052 9sQd 0.233052 9hQd 0.233052 9cQh 0.233052 9cQd 0.233052 9dQs 0.233052 9dQh 0.233052 3hQh 0.229162 3sQs 0.229162 3dQd 0.229162 6hQc 0.226708 6hQd 0.226708 6cQh 0.226708 6sQh 0.226708 6sQc 0.226708 6dQs 0.226708 6hQs 0.226708 6sQd 0.226708 6dQh 0.226708 6cQd 0.226708 6dQc 0.226708 6cQs 0.226708 8sJc 0.224028 8dJs 0.224028 8dJc 0.224028 8sJd 0.224028 8sJh 0.224028 8cJh 0.224028 8dJh 0.224028 8hJd 0.224028 8hJc 0.224028 8cJd 0.224028 8hJs 0.224028 8cJs 0.224028 3d6d 0.211769 3s6s 0.211769 3h6h 0.211769 6dAc 0.202461 6dAh 0.202461 6dAs 0.202461 6cAh 0.202461 6hAc 0.202461 6sAh 0.202461 6sAc 0.202461 6hAs 0.202461 6cAs 0.202461 7c8h 0.198551 7h8c 0.198551 7d8s 0.198551 7s8c 0.198551 7s8d 0.198551 7h8s 0.198551 7h8d 0.198551 7s8h 0.198551 7c8s 0.198551 7d8h 0.198551 7d8c 0.198551 7c8d 0.198551 8dKh 0.184102 8hKd 0.184102 8dKs 0.184102 8cKs 0.184102 8cKh 0.184102 8cKd 0.184102 8sKh 0.184102 8sKd 0.184102 8hKs 0.184102 7sJd 0.180314 7hJs 0.180314 7dJs 0.180314 7cJh 0.180314 7dJh 0.180314 7hJc 0.180314 7dJc 0.180314 7hJd 0.180314 7sJh 0.180314 7cJd 0.180314 7cJs 0.180314 7sJc 0.180314 6d7h 0.169491 6c7s 0.169491 6c7h 0.169491 6d7c 0.169491 6h7s 0.169491 6h7d 0.169491 6h7c 0.169491 6s7h 0.169491 6d7s 0.169491 6s7c 0.169491 6s7d 0.169491 6c7d 0.169491 3sKs 0.15864 3dKd 0.15864 3hKh 0.15864 3dTd 0.155821 3sTs 0.155821 3hTh 0.155821 8hQd 0.153104 8dQs 0.153104 8cQs 0.153104 8hQc 0.153104 8hQs 0.153104 8dQc 0.153104 8sQc 0.153104 8sQd 0.153104 8cQh 0.153104 8cQd 0.153104 8sQh 0.153104 8dQh 0.153104 5dQs 0.152759 5dQh 0.152759 5sQc 0.152759 5dQc 0.152759 5sQd 0.152759 5hQs 0.152759 5cQd 0.152759 5hQd 0.152759 5hQc 0.152759 5cQh 0.152759 5cQs 0.152759 5sQh 0.152759 7cTs 0.152613 7dTh 0.152613 7sTc 0.152613 7dTc 0.152613 7cTd 0.152613 7sTh 0.152613 7sTd 0.152613 7cTh 0.152613 7hTc 0.152613 7hTd 0.152613 7hTs 0.152613 7dTs 0.152613 5cKs 0.145835 5hKd 0.145835 5hKs 0.145835 5cKh 0.145835 5dKs 0.145835 5sKh 0.145835 5dKh 0.145835 5cKd 0.145835 5sKd 0.145835 7c9h 0.138427 7c9d 0.138427 7d9c 0.138427 7h9s 0.138427 7d9h 0.138427 7d9s 0.138427 7s9c 0.138427 7h9d 0.138427 7c9s 0.138427 7s9h 0.138427 7s9d 0.138427 7h9c 0.138427 4h8h 0.13596 4s8s 0.13596 4c8c 0.13596 4d8d 0.13596 7hQc 0.133884 7cQs 0.133884 7dQc 0.133884 7cQd 0.133884 7dQh 0.133884 7hQd 0.133884 7sQc 0.133884 7hQs 0.133884 7dQs 0.133884 7cQh 0.133884 7sQd 0.133884 7sQh 0.133884 5h6s 0.096952 5c6s 0.096952 5s6h 0.096952 5h6c 0.096952 5s6d 0.096952 5h6d 0.096952 5d6c 0.096952 5c6h 0.096952 5c6d 0.096952 5d6s 0.096952 5s6c 0.096952 5d6h 0.096952 4dKh 0.04827 4cKh 0.04827 4sKh 0.04827 4dKs 0.04827 4hKd 0.04827 4sKd 0.04827 4cKd 0.04827 4hKs 0.04827 4cKs 0.04827 4c5d 0.039751 4h5s 0.039751 4s5d 0.039751 4s5h 0.039751 4d5h 0.039751 4d5s 0.039751 4s5c 0.039751 4h5c 0.039751 4d5c 0.039751 4c5h 0.039751 4c5s 0.039751 4h5d 0.039751 5dJs 0.031108 5sJh 0.031108 5cJh 0.031108 5sJd 0.031108 5cJs 0.031108 5sJc 0.031108 5dJc 0.031108 5hJd 0.031108 5hJc 0.031108 5hJs 0.031108 5dJh 0.031108 5cJd 0.031108 3s9s 0.030505 3h9h 0.030505 3d9d 0.030505 6d9h 0.03019 6h9s 0.03019 6s9h 0.03019 6d9s 0.03019 6d9c 0.03019 6h9c 0.03019 6c9d 0.03019 6s9d 0.03019 6c9s 0.03019 6h9d 0.03019 6c9h 0.03019 6s9c 0.03019 2hJh 0.022645 2dJd 0.022645 2sJs 0.022645 2cJc 0.022645 6dTs 0.020674 6hTc 0.020674 6cTs 0.020674 6cTd 0.020674 6sTh 0.020674 6cTh 0.020674 6hTd 0.020674 6sTc 0.020674 6hTs 0.020674 6sTd 0.020674 6dTh 0.020674 6dTc 0.020674 6hJd 0.018796 6sJh 0.018796 6cJh 0.018796 6hJs 0.018796 6dJc 0.018796 6sJc 0.018796 6dJh 0.018796 6sJd 0.018796 6hJc 0.018796 6cJs 0.018796 6cJd 0.018796 6dJs 0.018796 6s8h 0.017399 6h8c 0.017399 6c8h 0.017399 6d8s 0.017399 6s8d 0.017399 6d8c 0.017399 6d8h 0.017399 6c8s 0.017399 6s8c 0.017399 6h8d 0.017399 6h8s 0.017399 6c8d 0.017399 4d9d 0.015403 4c9c 0.015403 4h9h 0.015403 4s9s 0.015403 4sQd 0.013227 4cQs 0.013227 4dQs 0.013227 4sQc 0.013227 4hQc 0.013227 4hQd 0.013227 4hQs 0.013227 4dQc 0.013227 4sQh 0.013227 4cQh 0.013227 4cQd 0.013227 4dQh 0.013227 4dAs 0.008524 4hAs 0.008524 4hAc 0.008524 4dAc 0.008524 4dAh 0.008524 4sAh 0.008524 4sAc 0.008524 4cAh 0.008524 4cAs 0.008524 2dTd 0.008329 2sTs 0.008329 2hTh 0.008329 2cTc 0.008329 2sQs 0.005711 2hQh 0.005711 2cQc 0.005711 2dQd 0.005711 2cAc 0.001948 2hAh 0.001948 2sAs 0.001948 ================================================ FILE: Source/Lookahead/Tests/ranges/flop-situation4-p1.txt ================================================ 4d4c 1.0 AdAc 0.99494 9h9c 1.0 AhAd 0.99494 9h9d 1.0 AsAd 0.99494 9sKs 1.0 KcAc 1.0 JhQh 1.0 9hQh 0.999783 AsAh 0.99494 8s8c 1.0 AsAc 0.99494 JhJc 0.999812 JhAh 0.999261 9hKh 1.0 AhAc 0.99494 KhAh 1.0 6s6c 0.999991 KhKc 0.99985 8hAh 0.999386 8dAd 0.999386 JhKh 1.0 9d9c 1.0 9dQd 0.999783 KsKc 0.99985 KdAd 1.0 KsKh 0.99985 9dKd 1.0 QcAd 1.0 JhJd 0.999812 QcAs 1.0 8cAc 0.999386 4d5d 0.999406 8hKh 0.999783 4dAd 0.999938 8h8c 1.0 QdAd 0.99921 JsAs 0.999261 QdAs 1.0 6sAs 1.0 QdKd 1.0 6h6d 0.999991 5h5d 1.0 6h6c 0.999991 QhAc 1.0 JsKs 1.0 QhAh 0.99921 JsQs 1.0 5h5c 1.0 JsJc 0.999812 QhKh 1.0 JsJd 0.999812 QhQc 0.999789 JsJh 0.999812 QsAc 1.0 TcAc 0.999078 QsAh 1.0 9dAd 1.0 5d5c 1.0 8h8d 1.0 5dAd 1.0 6hAh 1.0 QsQc 0.999789 TcKc 0.999882 QsQh 0.999789 6d6c 0.999991 9sQs 0.999783 8sAs 0.999386 9sAs 1.0 8sKs 0.999783 5c6c 1.0 TcQc 1.0 4s4d 1.0 TcJc 0.999815 9s9d 1.0 6dAd 1.0 9s9h 1.0 TdAd 0.999078 5cAc 1.0 9cQc 0.999783 6s6h 0.999991 9cKc 1.0 JdQd 1.0 6cAc 1.0 KdKc 0.99985 TdKd 0.999882 9hAh 1.0 7s7h 0.999893 8dKd 0.999783 7s7d 0.999893 KsKd 0.99985 TdQd 1.0 QcAh 1.0 TdJd 0.999815 4sAs 0.999938 TdTc 0.999517 QdAh 1.0 7s7c 0.999893 4cAc 0.999938 7sKs 0.999833 QhAd 1.0 ThAh 0.999078 5h6h 1.0 7sAs 0.999378 QhQd 0.999789 7h7d 0.999893 QsAs 0.99921 7h7c 0.999893 QsKs 1.0 ThKh 0.999882 JcAc 0.999261 7hKh 0.999833 JcKc 1.0 ThQh 1.0 JcQc 1.0 ThJh 0.999815 8d8c 1.0 ThTc 0.999517 6s6d 0.999991 ThTd 0.999517 8cKc 0.999783 7hAh 0.999378 KsAs 1.0 7d7c 0.999893 QcKc 1.0 7dKd 0.999833 4c5c 0.999406 TsAs 0.999078 QhAs 1.0 7dAd 0.999378 QsAd 1.0 7cKc 0.999833 QsQd 0.999789 7cAc 0.999378 4s4c 1.0 TsKs 0.999882 JdKd 1.0 TsQs 1.0 KhKd 0.99985 TsJs 0.999815 QdAc 1.0 TsTc 0.999517 5hAh 1.0 TsTd 0.999517 9s9c 1.0 JdJc 0.999812 8s8d 1.0 8s8h 1.0 9cAc 1.0 TsTh 0.999517 QcAc 0.99921 JdAd 0.999261 5d6d 1.0 QdQc 0.999789 KcAs 0.999809 KdAc 0.999809 KcAd 0.999809 KdAh 0.999809 KsAh 0.999809 KsAd 0.999809 KdAs 0.999809 KcAh 0.999809 KhAc 0.999809 KsAc 0.999809 KhAs 0.999809 KhAd 0.999809 6sKs 0.999788 6cKc 0.999788 6dKd 0.999788 6hKh 0.999788 6d7d 1.0 6h7h 1.0 6c7c 1.0 6s7s 1.0 JsAh 0.999991 JdAh 0.999991 JsAd 0.999991 JsAc 0.999991 JcAd 0.999991 JcAh 0.999991 JcAs 0.999991 JdAc 0.999991 JhAs 0.999991 JdAs 0.999991 JhAc 0.999991 JhAd 0.999991 9hTh 0.999241 9cTc 0.999241 9dTd 0.999241 9sTs 0.999241 9dJd 0.999681 9hJh 0.999681 9sJs 0.999681 9cJc 0.999681 7c8c 0.999593 7s8s 0.999593 7d8d 0.999593 7h8h 0.999593 8cQc 0.999833 8dQd 0.999833 8sQs 0.999833 8hQh 0.999833 TcAs 1.0 TdAh 1.0 TsAd 1.0 TcAh 1.0 ThAc 1.0 TsAc 1.0 TdAc 1.0 TsAh 1.0 TcAd 1.0 ThAs 1.0 ThAd 1.0 TdAs 1.0 8h9h 0.99976 8s9s 0.99976 8d9d 0.99976 8c9c 0.99976 6d8d 0.999658 6h8h 0.999658 6c8c 0.999658 6s8s 0.999658 5h7h 0.999569 5c7c 0.999569 5d7d 0.999569 4c6c 0.99937 4d6d 0.99937 4s6s 0.99937 8dTd 0.999506 8cTc 0.999506 8sTs 0.999506 8hTh 0.999506 5hKh 0.999668 5cKc 0.999668 5dKd 0.999668 3s3d 0.998486 3s3h 0.998486 3h3d 0.998486 7d9d 0.999421 7c9c 0.999421 7s9s 0.999421 7h9h 0.999421 6c9c 0.999321 6h9h 0.999321 6d9d 0.999321 6s9s 0.999321 8dJd 1.0 8sJs 1.0 8hJh 1.0 8cJc 1.0 7sTs 0.99966 7cTc 0.99966 7hTh 0.99966 7dTd 0.99966 9cAh 0.999563 9sAc 0.999563 9cAs 0.999563 9dAs 0.999563 9dAh 0.999563 9dAc 0.999563 9hAc 0.999563 9hAd 0.999563 9sAh 0.999563 9cAd 0.999563 9hAs 0.999563 9sAd 0.999563 QdKh 0.999563 QsKh 0.999563 QcKh 0.999563 QcKs 0.999563 QdKs 0.999563 QcKd 0.999563 QhKs 0.999563 QdKc 0.999563 QhKd 0.999563 QsKc 0.999563 QsKd 0.999563 QhKc 0.999563 6cTc 0.999359 6dTd 0.999359 6hTh 0.999359 6sTs 0.999359 3d5d 0.999632 3h5h 0.999632 3s4s 0.998532 3d4d 0.998532 5hJh 0.999097 5cJc 0.999097 5dJd 0.999097 6sQs 1.0 6dQd 1.0 6cQc 1.0 6hQh 1.0 7hQh 0.999947 7sQs 0.999947 7dQd 0.999947 7cQc 0.999947 5c8c 0.999487 5h8h 0.999487 5d8d 0.999487 5d9d 0.999539 5c9c 0.999539 5h9h 0.999539 JdKc 0.999541 JcKd 0.999541 JhKs 0.999541 JsKc 0.999541 JcKh 0.999541 JcKs 0.999541 JhKd 0.999541 JsKh 0.999541 JhKc 0.999541 JsKd 0.999541 JdKs 0.999541 JdKh 0.999541 5dQd 1.0 5cQc 1.0 5hQh 1.0 4cKc 0.999506 4dKd 0.999506 4sKs 0.999506 7hJh 1.0 7dJd 1.0 7cJc 1.0 7sJs 1.0 4sQs 0.998908 4dQd 0.998908 4cQc 0.998908 8dAc 0.998841 8hAd 0.998841 8sAh 0.998841 8cAs 0.998841 8cAd 0.998841 8hAc 0.998841 8dAh 0.998841 8cAh 0.998841 8hAs 0.998841 8sAc 0.998841 8sAd 0.998841 8dAs 0.998841 4sJs 0.999519 4cJc 0.999519 4dJd 0.999519 TsKc 0.999596 ThKc 0.999596 TdKh 0.999596 TsKh 0.999596 TcKh 0.999596 TcKd 0.999596 TdKc 0.999596 TdKs 0.999596 TsKd 0.999596 ThKd 0.999596 ThKs 0.999596 TcKs 0.999596 2s2h 0.994151 2d2c 0.994151 2h2d 0.994151 2s2c 0.994151 2s2d 0.994151 2h2c 0.994151 6sJs 0.99937 6hJh 0.99937 6dJd 0.99937 6cJc 0.99937 7hAs 0.999388 7dAs 0.999388 7hAd 0.999388 7hAc 0.999388 7cAh 0.999388 7cAd 0.999388 7dAc 0.999388 7dAh 0.999388 7sAc 0.999388 7cAs 0.999388 7sAd 0.999388 7sAh 0.999388 JcQd 0.999517 JdQc 0.999517 JhQc 0.999517 JhQd 0.999517 JhQs 0.999517 JdQs 0.999517 JcQs 0.999517 JcQh 0.999517 JdQh 0.999517 JsQc 0.999517 JsQd 0.999517 JsQh 0.999517 4c7c 1.0 4d7d 1.0 4s7s 1.0 4cTc 0.999347 4dTd 0.999347 4sTs 0.999347 9sTc 0.999158 9hTd 0.999158 9hTs 0.999158 9cTs 0.999158 9cTd 0.999158 9cTh 0.999158 9sTd 0.999158 9sTh 0.999158 9dTs 0.999158 9hTc 0.999158 9dTc 0.999158 9dTh 0.999158 9hKc 1.0 9dKc 1.0 9sKh 1.0 9cKd 1.0 9sKd 1.0 9dKs 1.0 9hKd 1.0 9sKc 1.0 9cKh 1.0 9cKs 1.0 9hKs 1.0 9dKh 1.0 3sAs 0.999815 3hAh 0.999815 3dAd 0.999815 TsJh 0.999512 TsJd 0.999512 TdJs 0.999512 TcJs 0.999512 TdJc 0.999512 TsJc 0.999512 TcJd 0.999512 ThJs 0.999512 TcJh 0.999512 TdJh 0.999512 ThJc 0.999512 ThJd 0.999512 8s9h 0.999263 8d9h 0.999263 8h9s 0.999263 8h9d 0.999263 8d9s 0.999263 8c9d 0.999263 8c9h 0.999263 8s9d 0.999263 8c9s 0.999263 8d9c 0.999263 8h9c 0.999263 8s9c 0.999263 ThQc 0.999368 TsQc 0.999368 TdQs 0.999368 TcQd 0.999368 TcQh 0.999368 TsQh 0.999368 TsQd 0.999368 TcQs 0.999368 TdQc 0.999368 ThQs 0.999368 TdQh 0.999368 ThQd 0.999368 9sJc 0.999412 9hJc 0.999412 9dJc 0.999412 9hJs 0.999412 9cJs 0.999412 9cJh 0.999412 9dJs 0.999412 9cJd 0.999412 9dJh 0.999412 9hJd 0.999412 9sJh 0.999412 9sJd 0.999412 5hTh 1.0 5dTd 1.0 5cTc 1.0 5dAs 0.999721 5dAh 0.999721 5cAh 0.999721 5hAs 0.999721 5hAd 0.999721 5hAc 0.999721 5cAd 0.999721 5dAc 0.999721 5cAs 0.999721 8sTd 0.999166 8sTh 0.999166 8cTh 0.999166 8sTc 0.999166 8cTd 0.999166 8hTd 0.999166 8hTs 0.999166 8dTs 0.999166 8hTc 0.999166 8dTc 0.999166 8dTh 0.999166 8cTs 0.999166 6cKd 0.999635 6hKc 0.999635 6dKc 0.999635 6cKs 0.999635 6dKh 0.999635 6hKd 0.999635 6dKs 0.999635 6sKd 0.999635 6cKh 0.999635 6sKh 0.999635 6sKc 0.999635 6hKs 0.999635 7hKc 0.999455 7sKh 0.999455 7cKs 0.999455 7cKh 0.999455 7dKc 0.999455 7dKh 0.999455 7sKd 0.999455 7dKs 0.999455 7cKd 0.999455 7sKc 0.999455 7hKs 0.999455 7hKd 0.999455 3dJd 1.0 3hJh 1.0 3sJs 1.0 9dQc 0.999502 9cQh 0.999502 9hQs 0.999502 9dQh 0.999502 9sQc 0.999502 9dQs 0.999502 9sQd 0.999502 9hQc 0.999502 9cQd 0.999502 9hQd 0.999502 9cQs 0.999502 9sQh 0.999502 3hQh 0.999811 3dQd 0.999811 3sQs 0.999811 6cQd 0.999029 6cQs 0.999029 6dQh 0.999029 6cQh 0.999029 6hQd 0.999029 6hQc 0.999029 6dQc 0.999029 6dQs 0.999029 6sQc 0.999029 6hQs 0.999029 6sQh 0.999029 6sQd 0.999029 8dJc 0.999249 8dJh 0.999249 8sJc 0.999249 8cJd 0.999249 8hJc 0.999249 8hJs 0.999249 8cJs 0.999249 8sJd 0.999249 8cJh 0.999249 8sJh 0.999249 8hJd 0.999249 8dJs 0.999249 3h6h 0.997878 3s6s 0.997878 3d6d 0.997878 6sAc 0.999486 6dAs 0.999486 6sAh 0.999486 6sAd 0.999486 6cAd 0.999486 6cAh 0.999486 6dAc 0.999486 6dAh 0.999486 6hAc 0.999486 6cAs 0.999486 6hAs 0.999486 6hAd 0.999486 7h8c 0.999334 7s8h 0.999334 7s8d 0.999334 7h8s 0.999334 7s8c 0.999334 7c8d 0.999334 7c8h 0.999334 7h8d 0.999334 7d8s 0.999334 7d8h 0.999334 7c8s 0.999334 7d8c 0.999334 8dKh 0.999476 8hKd 0.999476 8cKs 0.999476 8hKc 0.999476 8hKs 0.999476 8cKh 0.999476 8cKd 0.999476 8dKc 0.999476 8sKd 0.999476 8sKc 0.999476 8dKs 0.999476 8sKh 0.999476 7sJc 0.99917 7sJd 0.99917 7hJd 0.99917 7dJs 0.99917 7cJd 0.99917 7hJc 0.99917 7dJh 0.99917 7sJh 0.99917 7dJc 0.99917 7hJs 0.99917 7cJs 0.99917 7cJh 0.99917 6d7c 0.999388 6c7s 0.999388 6c7h 0.999388 6d7s 0.999388 6h7d 0.999388 6h7c 0.999388 6d7h 0.999388 6s7c 0.999388 6h7s 0.999388 6s7d 0.999388 6s7h 0.999388 6c7d 0.999388 3sKs 0.999802 3hKh 0.999802 3dKd 0.999802 3dTd 0.998642 3sTs 0.998642 3hTh 0.998642 8sQh 0.999244 8dQc 0.999244 8cQd 0.999244 8dQh 0.999244 8cQs 0.999244 8sQc 0.999244 8cQh 0.999244 8hQd 0.999244 8sQd 0.999244 8hQc 0.999244 8hQs 0.999244 8dQs 0.999244 5cQd 0.999073 5dQc 0.999073 5dQh 0.999073 5dQs 0.999073 5cQs 0.999073 5hQc 0.999073 5cQh 0.999073 5hQs 0.999073 5hQd 0.999073 7dTc 0.99917 7cTd 0.99917 7sTd 0.99917 7dTs 0.99917 7sTc 0.99917 7hTc 0.99917 7cTs 0.99917 7cTh 0.99917 7dTh 0.99917 7sTh 0.99917 7hTs 0.99917 7hTd 0.99917 5cKs 0.999452 5cKd 0.999452 5cKh 0.999452 5dKc 0.999452 5dKh 0.999452 5dKs 0.999452 5hKc 0.999452 5hKd 0.999452 5hKs 0.999452 7h9s 0.999505 7d9s 0.999505 7c9s 0.999505 7c9h 0.999505 7d9h 0.999505 7s9c 0.999505 7d9c 0.999505 7c9d 0.999505 7s9h 0.999505 7h9c 0.999505 7h9d 0.999505 7s9d 0.999505 4s8s 0.99947 4c8c 0.99947 4d8d 0.99947 7dQs 0.999282 7hQc 0.999282 7sQh 0.999282 7sQd 0.999282 7sQc 0.999282 7hQs 0.999282 7cQh 0.999282 7dQh 0.999282 7hQd 0.999282 7cQs 0.999282 7cQd 0.999282 7dQc 0.999282 5d6c 0.998976 5h6c 0.998976 5d6s 0.998976 5c6d 0.998976 5d6h 0.998976 5c6h 0.998976 5c6s 0.998976 5h6s 0.998976 5h6d 0.998976 4dKc 0.999183 4cKd 0.999183 4cKh 0.999183 4sKc 0.999183 4cKs 0.999183 4dKs 0.999183 4dKh 0.999183 4sKh 0.999183 4sKd 0.999183 4d5h 0.99914 4s5c 0.99914 4s5d 0.99914 4s5h 0.99914 4d5c 0.99914 4c5d 0.99914 4c5h 0.99914 5dJh 0.998705 5cJs 0.998705 5cJh 0.998705 5dJc 0.998705 5hJd 0.998705 5hJc 0.998705 5dJs 0.998705 5cJd 0.998705 5hJs 0.998705 3s9s 0.997845 3d9d 0.997845 3h9h 0.997845 6s9c 0.998993 6d9h 0.998993 6s9h 0.998993 6d9s 0.998993 6h9s 0.998993 6h9c 0.998993 6c9s 0.998993 6c9h 0.998993 6c9d 0.998993 6h9d 0.998993 6s9d 0.998993 6d9c 0.998993 2dJd 0.99771 2cJc 0.99771 2hJh 0.99771 2sJs 0.99771 6hTs 0.998801 6dTs 0.998801 6cTd 0.998801 6sTc 0.998801 6cTh 0.998801 6dTh 0.998801 6sTh 0.998801 6cTs 0.998801 6dTc 0.998801 6sTd 0.998801 6hTd 0.998801 6hTc 0.998801 6sJh 0.998794 6hJd 0.998794 6dJc 0.998794 6dJh 0.998794 6dJs 0.998794 6hJc 0.998794 6sJc 0.998794 6cJh 0.998794 6cJs 0.998794 6hJs 0.998794 6sJd 0.998794 6cJd 0.998794 6h8s 0.999003 6c8s 0.999003 6s8d 0.999003 6d8c 0.999003 6c8h 0.999003 6c8d 0.999003 6s8c 0.999003 6s8h 0.999003 6h8c 0.999003 6d8h 0.999003 6d8s 0.999003 6h8d 0.999003 4s9s 0.998765 4c9c 0.998765 4d9d 0.998765 4cQh 0.998753 4cQd 0.998753 4cQs 0.998753 4sQh 0.998753 4sQd 0.998753 4sQc 0.998753 4dQh 0.998753 4dQs 0.998753 4dQc 0.998753 4sAh 0.999831 4sAc 0.999831 4cAd 0.999831 4sAd 0.999831 4cAh 0.999831 4dAh 0.999831 4cAs 0.999831 4dAs 0.999831 4dAc 0.999831 2sTs 0.996464 2cTc 0.996464 2dTd 0.996464 2hTh 0.996464 2cQc 1.0 2sQs 1.0 2hQh 1.0 2dQd 1.0 2sAs 0.99962 2dAd 0.99962 2cAc 0.99962 2hAh 0.99962 4sTd 0.746475 3hJc 0.99866 2c4c 0.997887 3sAd 0.999944 3sAc 0.999944 3sKd 0.999133 3sKh 0.999133 3dQh 0.998311 3dJc 0.99866 2dAc 0.999367 3dQc 0.998311 4c6d 0.998793 3sQc 0.998311 3d7d 0.999628 3dKc 0.999133 4c6h 0.998793 3sQd 0.998311 4c6s 0.998793 3sQh 0.998311 2dAh 0.999367 5cTd 0.998028 3sJc 0.99866 3sJh 0.99866 2dAs 0.999367 3dAc 0.999944 2dKc 0.999134 2dKd 0.999069 5c9s 0.99831 2dKh 0.999134 2dKs 0.999134 5c8h 0.998541 2dQc 0.998384 5c7d 0.999284 2dQh 0.998384 5c7s 0.999284 2dQs 0.998384 4s6h 0.998793 5dTc 0.998028 5dTs 0.998028 5d9c 0.99831 5d9h 0.99831 5d9s 0.99831 4s7d 0.054927 5d8h 0.998541 5d7c 0.999284 2d9d 0.996303 4dJc 0.998619 5d7h 0.999284 4dJh 0.998619 4dJs 0.998619 4dTc 0.746475 4dTh 0.746475 5hTd 0.998028 2d8d 0.996726 2cAd 0.999367 2cAs 0.999367 2cKd 0.999134 2cKh 0.999134 2d7d 0.995828 5h9d 0.99831 2cKs 0.999134 5h8c 0.998541 2d6d 0.995154 5h8s 0.998541 2cQd 0.998384 5h7s 0.999284 4dTs 0.746475 2cQs 0.998384 3hJs 0.99866 4sTh 0.746475 2d5d 0.997305 4sJh 0.998619 3d8d 0.997718 2d4d 0.997887 3h8h 0.997718 2d3d 0.996645 4cJh 0.998619 2hAc 0.999367 4cJs 0.998619 4cTh 0.746475 4cTs 0.746475 2hAd 0.999367 2hAs 0.999367 2hKc 0.999134 2hKd 0.999134 2hKh 0.999069 2hKs 0.999134 2hQc 0.998384 2hQd 0.998384 4c7d 0.054927 4c7h 0.054927 4d7c 0.054927 3hAc 0.999944 4d7h 0.054927 3sKc 0.999133 2hQs 0.998384 3dJh 0.99866 3dKh 0.999133 3hAd 0.999944 3dAh 0.999944 3sJd 0.99866 5c9d 0.99831 3hAs 0.999944 4d7s 0.054927 5c8s 0.998541 4d6c 0.998793 5c7h 0.999284 3hKc 0.999133 4s6d 0.998793 5dTh 0.998028 2h9h 0.996303 4s7h 0.054927 5d8c 0.998541 5d8s 0.998541 3s7s 0.999628 2h8h 0.996726 4s7c 0.054927 5d7s 0.999284 3h7h 0.999628 2h7h 0.995828 5hTc 0.998028 2cAh 0.999367 5hTs 0.998028 2h6h 0.995154 5h8d 0.998541 5h7c 0.999284 2cQh 0.998384 2h5h 0.997305 4sTc 0.746475 4sJc 0.998619 2h3h 0.996645 2c8c 0.996726 4d6h 0.998793 4d6s 0.998793 2sAc 0.999367 2sAd 0.999367 2sAh 0.999367 3hKd 0.999133 2c5c 0.997305 2sKc 0.999134 3dQs 0.998311 2sKd 0.999134 3dKs 0.999133 2sKh 0.999134 3dJs 0.99866 2sKs 0.999069 5cTs 0.998028 2sQc 0.998384 5c8d 0.998541 2sQd 0.998384 2sQh 0.998384 3hKs 0.999133 3hJd 0.99866 2cKc 0.999069 5h9s 0.99831 5h7d 0.999284 3hQc 0.998311 3hQs 0.998311 4sJd 0.998619 2c9c 0.996303 4cTd 0.746475 2s9s 0.996303 2c6c 0.995154 4c7s 0.054927 2s8s 0.996726 5cTh 0.998028 2s7s 0.995828 5h9c 0.99831 2s6s 0.995154 3dAs 0.999944 4s6c 0.998793 2s4s 0.997887 4cJd 0.998619 3sAh 0.999944 3hQd 0.998311 2s3s 0.996645 5c9h 0.99831 2c7c 0.995828 3s8s 0.997718 ================================================ FILE: Source/Lookahead/Tests/ranges/flop-situation4-p2.txt ================================================ 4d4c 1.0 AdAc 1.0 9h9c 1.0 AhAd 1.0 9h9d 1.0 AsAd 1.0 9sKs 1.0 KcAc 1.0 JhQh 1.0 9hQh 1.0 AsAh 1.0 8s8c 1.0 AsAc 1.0 JhJc 1.0 JhAh 1.0 9hKh 1.0 AhAc 1.0 KhAh 1.0 6s6c 1.0 KhKc 1.0 8hAh 1.0 8dAd 1.0 JhKh 1.0 9d9c 1.0 9dQd 1.0 KsKc 1.0 KdAd 1.0 KsKh 1.0 9dKd 1.0 QcAd 1.0 JhJd 1.0 QcAs 1.0 8cAc 1.0 4d5d 1.0 8hKh 1.0 4dAd 1.0 8h8c 1.0 QdAd 1.0 JsAs 1.0 QdAs 1.0 6sAs 1.0 QdKd 1.0 6h6d 1.0 5h5d 1.0 6h6c 1.0 QhAc 1.0 JsKs 1.0 QhAh 1.0 JsQs 1.0 5h5c 1.0 JsJc 1.0 QhKh 1.0 JsJd 1.0 QhQc 1.0 JsJh 1.0 QsAc 1.0 TcAc 1.0 QsAh 1.0 9dAd 1.0 5d5c 1.0 8h8d 1.0 5dAd 1.0 6hAh 1.0 QsQc 1.0 TcKc 1.0 QsQh 1.0 6d6c 1.0 9sQs 1.0 8sAs 1.0 9sAs 1.0 8sKs 1.0 5c6c 1.0 TcQc 1.0 4s4d 1.0 TcJc 1.0 9s9d 1.0 6dAd 1.0 9s9h 1.0 TdAd 1.0 5cAc 1.0 9cQc 1.0 6s6h 1.0 9cKc 1.0 JdQd 1.0 6cAc 1.0 KdKc 1.0 TdKd 1.0 9hAh 1.0 7s7h 1.0 8dKd 1.0 7s7d 1.0 KsKd 1.0 TdQd 1.0 QcAh 1.0 TdJd 1.0 4sAs 1.0 TdTc 1.0 QdAh 1.0 7s7c 1.0 4cAc 1.0 7sKs 1.0 QhAd 1.0 ThAh 1.0 5h6h 1.0 7sAs 1.0 QhQd 1.0 7h7d 1.0 QsAs 1.0 7h7c 1.0 QsKs 1.0 ThKh 1.0 JcAc 1.0 7hKh 1.0 JcKc 1.0 ThQh 1.0 JcQc 1.0 ThJh 1.0 8d8c 1.0 ThTc 1.0 6s6d 1.0 ThTd 1.0 8cKc 1.0 7hAh 1.0 KsAs 1.0 7d7c 1.0 QcKc 1.0 7dKd 1.0 4c5c 1.0 TsAs 1.0 QhAs 1.0 7dAd 1.0 QsAd 1.0 7cKc 1.0 QsQd 1.0 7cAc 1.0 4s4c 1.0 TsKs 1.0 JdKd 1.0 TsQs 1.0 KhKd 1.0 TsJs 1.0 QdAc 1.0 TsTc 1.0 5hAh 1.0 TsTd 1.0 9s9c 1.0 JdJc 1.0 8s8d 1.0 8s8h 1.0 9cAc 1.0 TsTh 1.0 QcAc 1.0 JdAd 1.0 5d6d 1.0 QdQc 1.0 KcAs 0.99948 KdAc 0.99948 KcAd 0.99948 KdAh 0.99948 KsAh 0.99948 KsAd 0.99948 KdAs 0.99948 KcAh 0.99948 KhAc 0.99948 KsAc 0.99948 KhAs 0.99948 KhAd 0.99948 6sKs 0.996459 6cKc 0.996459 6dKd 0.996459 6hKh 0.996459 6d7d 0.990574 6h7h 0.990574 6c7c 0.990574 6s7s 0.990574 JsAh 0.9881 JdAh 0.9881 JsAd 0.9881 JsAc 0.9881 JcAd 0.9881 JcAh 0.9881 JcAs 0.9881 JdAc 0.9881 JhAs 0.9881 JdAs 0.9881 JhAc 0.9881 JhAd 0.9881 9hTh 0.966349 9cTc 0.966349 9dTd 0.966349 9sTs 0.966349 9dJd 0.961261 9hJh 0.961261 9sJs 0.961261 9cJc 0.961261 7c8c 0.937578 7s8s 0.937578 7d8d 0.937578 7h8h 0.937578 8cQc 0.929088 8dQd 0.929088 8sQs 0.929088 8hQh 0.929088 TcAs 0.92546 TdAh 0.92546 TsAd 0.92546 TcAh 0.92546 ThAc 0.92546 TsAc 0.92546 TdAc 0.92546 TsAh 0.92546 TcAd 0.92546 ThAs 0.92546 ThAd 0.92546 TdAs 0.92546 8h9h 0.922554 8s9s 0.922554 8d9d 0.922554 8c9c 0.922554 6d8d 0.887115 6h8h 0.887115 6c8c 0.887115 6s8s 0.887115 5h7h 0.870201 5c7c 0.870201 5d7d 0.870201 4c6c 0.864189 4d6d 0.864189 4s6s 0.864189 8dTd 0.848302 8cTc 0.848302 8sTs 0.848302 8hTh 0.848302 5hKh 0.847799 5cKc 0.847799 5dKd 0.847799 3s3d 0.834255 3s3h 0.834255 3h3d 0.834255 7d9d 0.833698 7c9c 0.833698 7s9s 0.833698 7h9h 0.833698 6c9c 0.818515 6h9h 0.818515 6d9d 0.818515 6s9s 0.818515 8dJd 0.792354 8sJs 0.792354 8hJh 0.792354 8cJc 0.792354 7sTs 0.782837 7cTc 0.782837 7hTh 0.782837 7dTd 0.782837 9cAh 0.775404 9sAc 0.775404 9cAs 0.775404 9dAs 0.775404 9dAh 0.775404 9dAc 0.775404 9hAc 0.775404 9hAd 0.775404 9sAh 0.775404 9cAd 0.775404 9hAs 0.775404 9sAd 0.775404 QdKh 0.764181 QsKh 0.764181 QcKh 0.764181 QcKs 0.764181 QdKs 0.764181 QcKd 0.764181 QhKs 0.764181 QdKc 0.764181 QhKd 0.764181 QsKc 0.764181 QsKd 0.764181 QhKc 0.764181 6cTc 0.745567 6dTd 0.745567 6hTh 0.745567 6sTs 0.745567 3d5d 0.679195 3h5h 0.679195 3s4s 0.678556 3d4d 0.678556 5hJh 0.647998 5cJc 0.647998 5dJd 0.647998 6sQs 0.644814 6dQd 0.644814 6cQc 0.644814 6hQh 0.644814 7hQh 0.635211 7sQs 0.635211 7dQd 0.635211 7cQc 0.635211 5c8c 0.630807 5h8h 0.630807 5d8d 0.630807 5d9d 0.617031 5c9c 0.617031 5h9h 0.617031 JdKc 0.615132 JcKd 0.615132 JhKs 0.615132 JsKc 0.615132 JcKh 0.615132 JcKs 0.615132 JhKd 0.615132 JsKh 0.615132 JhKc 0.615132 JsKd 0.615132 JdKs 0.615132 JdKh 0.615132 5dQd 0.612392 5cQc 0.612392 5hQh 0.612392 4cKc 0.605889 4dKd 0.605889 4sKs 0.605889 7hJh 0.605393 7dJd 0.605393 7cJc 0.605393 7sJs 0.605393 4sQs 0.59431 4dQd 0.59431 4cQc 0.59431 8dAc 0.575158 8hAd 0.575158 8sAh 0.575158 8cAs 0.575158 8cAd 0.575158 8hAc 0.575158 8dAh 0.575158 8cAh 0.575158 8hAs 0.575158 8sAc 0.575158 8sAd 0.575158 8dAs 0.575158 4sJs 0.529954 4cJc 0.529954 4dJd 0.529954 TsKc 0.495103 ThKc 0.495103 TdKh 0.495103 TsKh 0.495103 TcKh 0.495103 TcKd 0.495103 TdKc 0.495103 TdKs 0.495103 TsKd 0.495103 ThKd 0.495103 ThKs 0.495103 TcKs 0.495103 2s2h 0.456526 2d2c 0.456526 2h2d 0.456526 2s2c 0.456526 2s2d 0.456526 2h2c 0.456526 6sJs 0.424636 6hJh 0.424636 6dJd 0.424636 6cJc 0.424636 7hAs 0.42414 7dAs 0.42414 7hAd 0.42414 7hAc 0.42414 7cAh 0.42414 7cAd 0.42414 7dAc 0.42414 7dAh 0.42414 7sAc 0.42414 7cAs 0.42414 7sAd 0.42414 7sAh 0.42414 JcQd 0.421432 JdQc 0.421432 JhQc 0.421432 JhQd 0.421432 JhQs 0.421432 JdQs 0.421432 JcQs 0.421432 JcQh 0.421432 JdQh 0.421432 JsQc 0.421432 JsQd 0.421432 JsQh 0.421432 4c7c 0.420963 4d7d 0.420963 4s7s 0.420963 4cTc 0.409083 4dTd 0.409083 4sTs 0.409083 9sTc 0.392535 9hTd 0.392535 9hTs 0.392535 9cTs 0.392535 9cTd 0.392535 9cTh 0.392535 9sTd 0.392535 9sTh 0.392535 9dTs 0.392535 9hTc 0.392535 9dTc 0.392535 9dTh 0.392535 9hKc 0.382497 9dKc 0.382497 9sKh 0.382497 9cKd 0.382497 9sKd 0.382497 9dKs 0.382497 9hKd 0.382497 9sKc 0.382497 9cKh 0.382497 9cKs 0.382497 9hKs 0.382497 9dKh 0.382497 3sAs 0.375764 3hAh 0.375764 3dAd 0.375764 TsJh 0.36661 TsJd 0.36661 TdJs 0.36661 TcJs 0.36661 TdJc 0.36661 TsJc 0.36661 TcJd 0.36661 ThJs 0.36661 TcJh 0.36661 TdJh 0.36661 ThJc 0.36661 ThJd 0.36661 8s9h 0.325181 8d9h 0.325181 8h9s 0.325181 8h9d 0.325181 8d9s 0.325181 8c9d 0.325181 8c9h 0.325181 8s9d 0.325181 8c9s 0.325181 8d9c 0.325181 8h9c 0.325181 8s9c 0.325181 ThQc 0.312877 TsQc 0.312877 TdQs 0.312877 TcQd 0.312877 TcQh 0.312877 TsQh 0.312877 TsQd 0.312877 TcQs 0.312877 TdQc 0.312877 ThQs 0.312877 TdQh 0.312877 ThQd 0.312877 9sJc 0.273196 9hJc 0.273196 9dJc 0.273196 9hJs 0.273196 9cJs 0.273196 9cJh 0.273196 9dJs 0.273196 9cJd 0.273196 9dJh 0.273196 9hJd 0.273196 9sJh 0.273196 9sJd 0.273196 5hTh 0.272444 5dTd 0.272444 5cTc 0.272444 5dAs 0.271799 5dAh 0.271799 5cAh 0.271799 5hAs 0.271799 5hAd 0.271799 5hAc 0.271799 5cAd 0.271799 5dAc 0.271799 5cAs 0.271799 8sTd 0.243467 8sTh 0.243467 8cTh 0.243467 8sTc 0.243467 8cTd 0.243467 8hTd 0.243467 8hTs 0.243467 8dTs 0.243467 8hTc 0.243467 8dTc 0.243467 8dTh 0.243467 8cTs 0.243467 6cKd 0.242493 6hKc 0.242493 6dKc 0.242493 6cKs 0.242493 6dKh 0.242493 6hKd 0.242493 6dKs 0.242493 6sKd 0.242493 6cKh 0.242493 6sKh 0.242493 6sKc 0.242493 6hKs 0.242493 7hKc 0.24114 7sKh 0.24114 7cKs 0.24114 7cKh 0.24114 7dKc 0.24114 7dKh 0.24114 7sKd 0.24114 7dKs 0.24114 7cKd 0.24114 7sKc 0.24114 7hKs 0.24114 7hKd 0.24114 3dJd 0.237643 3hJh 0.237643 3sJs 0.237643 9dQc 0.233052 9cQh 0.233052 9hQs 0.233052 9dQh 0.233052 9sQc 0.233052 9dQs 0.233052 9sQd 0.233052 9hQc 0.233052 9cQd 0.233052 9hQd 0.233052 9cQs 0.233052 9sQh 0.233052 3hQh 0.229162 3dQd 0.229162 3sQs 0.229162 6cQd 0.226708 6cQs 0.226708 6dQh 0.226708 6cQh 0.226708 6hQd 0.226708 6hQc 0.226708 6dQc 0.226708 6dQs 0.226708 6sQc 0.226708 6hQs 0.226708 6sQh 0.226708 6sQd 0.226708 8dJc 0.224028 8dJh 0.224028 8sJc 0.224028 8cJd 0.224028 8hJc 0.224028 8hJs 0.224028 8cJs 0.224028 8sJd 0.224028 8cJh 0.224028 8sJh 0.224028 8hJd 0.224028 8dJs 0.224028 3h6h 0.211769 3s6s 0.211769 3d6d 0.211769 6sAc 0.202461 6dAs 0.202461 6sAh 0.202461 6sAd 0.202461 6cAd 0.202461 6cAh 0.202461 6dAc 0.202461 6dAh 0.202461 6hAc 0.202461 6cAs 0.202461 6hAs 0.202461 6hAd 0.202461 7h8c 0.198551 7s8h 0.198551 7s8d 0.198551 7h8s 0.198551 7s8c 0.198551 7c8d 0.198551 7c8h 0.198551 7h8d 0.198551 7d8s 0.198551 7d8h 0.198551 7c8s 0.198551 7d8c 0.198551 8dKh 0.184102 8hKd 0.184102 8cKs 0.184102 8hKc 0.184102 8hKs 0.184102 8cKh 0.184102 8cKd 0.184102 8dKc 0.184102 8sKd 0.184102 8sKc 0.184102 8dKs 0.184102 8sKh 0.184102 7sJc 0.180314 7sJd 0.180314 7hJd 0.180314 7dJs 0.180314 7cJd 0.180314 7hJc 0.180314 7dJh 0.180314 7sJh 0.180314 7dJc 0.180314 7hJs 0.180314 7cJs 0.180314 7cJh 0.180314 6d7c 0.169491 6c7s 0.169491 6c7h 0.169491 6d7s 0.169491 6h7d 0.169491 6h7c 0.169491 6d7h 0.169491 6s7c 0.169491 6h7s 0.169491 6s7d 0.169491 6s7h 0.169491 6c7d 0.169491 3sKs 0.15864 3hKh 0.15864 3dKd 0.15864 3dTd 0.155821 3sTs 0.155821 3hTh 0.155821 8sQh 0.153104 8dQc 0.153104 8cQd 0.153104 8dQh 0.153104 8cQs 0.153104 8sQc 0.153104 8cQh 0.153104 8hQd 0.153104 8sQd 0.153104 8hQc 0.153104 8hQs 0.153104 8dQs 0.153104 5cQd 0.152759 5dQc 0.152759 5dQh 0.152759 5dQs 0.152759 5cQs 0.152759 5hQc 0.152759 5cQh 0.152759 5hQs 0.152759 5hQd 0.152759 7dTc 0.152613 7cTd 0.152613 7sTd 0.152613 7dTs 0.152613 7sTc 0.152613 7hTc 0.152613 7cTs 0.152613 7cTh 0.152613 7dTh 0.152613 7sTh 0.152613 7hTs 0.152613 7hTd 0.152613 5cKs 0.145835 5cKd 0.145835 5cKh 0.145835 5dKc 0.145835 5dKh 0.145835 5dKs 0.145835 5hKc 0.145835 5hKd 0.145835 5hKs 0.145835 7h9s 0.138427 7d9s 0.138427 7c9s 0.138427 7c9h 0.138427 7d9h 0.138427 7s9c 0.138427 7d9c 0.138427 7c9d 0.138427 7s9h 0.138427 7h9c 0.138427 7h9d 0.138427 7s9d 0.138427 4s8s 0.13596 4c8c 0.13596 4d8d 0.13596 7dQs 0.133884 7hQc 0.133884 7sQh 0.133884 7sQd 0.133884 7sQc 0.133884 7hQs 0.133884 7cQh 0.133884 7dQh 0.133884 7hQd 0.133884 7cQs 0.133884 7cQd 0.133884 7dQc 0.133884 5d6c 0.096952 5h6c 0.096952 5d6s 0.096952 5c6d 0.096952 5d6h 0.096952 5c6h 0.096952 5c6s 0.096952 5h6s 0.096952 5h6d 0.096952 4dKc 0.04827 4cKd 0.04827 4cKh 0.04827 4sKc 0.04827 4cKs 0.04827 4dKs 0.04827 4dKh 0.04827 4sKh 0.04827 4sKd 0.04827 4d5h 0.039751 4s5c 0.039751 4s5d 0.039751 4s5h 0.039751 4d5c 0.039751 4c5d 0.039751 4c5h 0.039751 5dJh 0.031108 5cJs 0.031108 5cJh 0.031108 5dJc 0.031108 5hJd 0.031108 5hJc 0.031108 5dJs 0.031108 5cJd 0.031108 5hJs 0.031108 3s9s 0.030505 3d9d 0.030505 3h9h 0.030505 6s9c 0.03019 6d9h 0.03019 6s9h 0.03019 6d9s 0.03019 6h9s 0.03019 6h9c 0.03019 6c9s 0.03019 6c9h 0.03019 6c9d 0.03019 6h9d 0.03019 6s9d 0.03019 6d9c 0.03019 2dJd 0.022645 2cJc 0.022645 2hJh 0.022645 2sJs 0.022645 6hTs 0.020674 6dTs 0.020674 6cTd 0.020674 6sTc 0.020674 6cTh 0.020674 6dTh 0.020674 6sTh 0.020674 6cTs 0.020674 6dTc 0.020674 6sTd 0.020674 6hTd 0.020674 6hTc 0.020674 6sJh 0.018796 6hJd 0.018796 6dJc 0.018796 6dJh 0.018796 6dJs 0.018796 6hJc 0.018796 6sJc 0.018796 6cJh 0.018796 6cJs 0.018796 6hJs 0.018796 6sJd 0.018796 6cJd 0.018796 6h8s 0.017399 6c8s 0.017399 6s8d 0.017399 6d8c 0.017399 6c8h 0.017399 6c8d 0.017399 6s8c 0.017399 6s8h 0.017399 6h8c 0.017399 6d8h 0.017399 6d8s 0.017399 6h8d 0.017399 4s9s 0.015403 4c9c 0.015403 4d9d 0.015403 4cQh 0.013227 4cQd 0.013227 4cQs 0.013227 4sQh 0.013227 4sQd 0.013227 4sQc 0.013227 4dQh 0.013227 4dQs 0.013227 4dQc 0.013227 4sAh 0.008524 4sAc 0.008524 4cAd 0.008524 4sAd 0.008524 4cAh 0.008524 4dAh 0.008524 4cAs 0.008524 4dAs 0.008524 4dAc 0.008524 2sTs 0.008329 2cTc 0.008329 2dTd 0.008329 2hTh 0.008329 2cQc 0.005711 2sQs 0.005711 2hQh 0.005711 2dQd 0.005711 2sAs 0.001948 2dAd 0.001948 2cAc 0.001948 2hAh 0.001948 ================================================ FILE: Source/Lookahead/Tests/ranges/situation-p1.txt ================================================ 7hjh 1.000 6h7h 1.000 4s7s 1.000 9d9c 1.000 9s9d 1.000 6s7s 1.000 tsqs 1.000 9s9h 1.000 9s9c 1.000 4h7h 1.000 9h9c 1.000 9h9d 1.000 9sks 1.000 jhjc 1.000 jdjc 1.000 jhjd 1.000 qsqh 1.000 qsqc 1.000 qhqc 1.000 6sks 1 9sqs 1 7hth 1 tsth 1 thtd 1 tdtc 1 thtc 1 7s9h 1 7h9s 1 7s9c 1 7h9c 1 7h9d 1 7s9d 1 7s9s 0.999 7h9h 0.999 6c7s 0.999 6s7h 0.999 6d7h 0.999 6c7h 0.999 6h7s 0.999 6d7s 0.999 7sas 0.999 4s6s 0.999 4c6c 0.999 4h6h 0.999 4d6d 0.999 6h9h 0.999 7sqh 0.999 7sjd 0.999 7sth 0.999 7htc 0.999 7sjh 0.999 7std 0.999 7hjd 0.999 7sjc 0.999 7stc 0.999 7hjc 0.999 7htd 0.999 6s9h 0.999 6d9s 0.999 6s9d 0.999 6h9s 0.999 6c9s 0.999 6s9c 0.999 4d6s 0.999 4c6h 0.999 4d6c 0.999 4d6h 0.999 4h6d 0.999 4h6s 0.999 4h6c 0.999 4c6d 0.999 4c6s 0.999 3h7h 0.997 jsjh 0.996 2h7h 0.996 2s7s 0.996 4s6d 0.990 4s6c 0.990 6h9c 0.986 6h9d 0.986 6c9h 0.985 6d9h 0.985 8hks 0.979 tstc 0.978 tstd 0.978 6c9d 0.977 6d9c 0.977 jsjd 0.976 jsjc 0.976 8cah 0.976 8dah 0.976 4s6h 0.972 6d9d 0.968 6c9c 0.968 6s6h 0.966 7sts 0.965 7sqs 0.965 8hac 0.959 8had 0.959 5h6h 0.942 8h9s 0.939 6sts 0.936 8das 0.935 8cas 0.935 6h8d 0.933 6h8c 0.933 8dad 0.929 8cac 0.929 6d8d 0.927 6c8c 0.927 7sjs 0.927 6d8c 0.926 6c8d 0.926 8has 0.924 6hqs 0.919 8dac 0.912 8cad 0.912 6hth 0.911 7sqc 0.908 6sas 0.904 6h8h 0.901 5h6s 0.900 3s7s 0.896 7hqh 0.894 6sqs 0.894 8hah 0.893 7sks 0.889 8hkh 0.884 7hqc 0.880 7hts 0.886 kskh 0.863 6c8h 0.862 6d8h 0.862 6skh 0.82 7hkc 0.817 7hkd 0.817 7hjs 0.794 7hqs 0.788 7hkh 0.772 7skd 0.758 7skc 0.758 6cqs 0.757 6dqs 0.757 6dtd 0.753 6ctc 0.753 9hjh 0.742 4s9s 0.741 6s8d 0.722 6s8c 0.722 6s6d 0.719 6s6c 0.719 7skh 0.706 6s8h 0.701 9sts 0.697 9cjc 0.680 9djd 0.68 7hks 0.663 5h7s 0.653 5h7h 0.650 6sjs 0.643 6cjc 0.635 6djd 0.635 6hjh 0.623 5d7s 0.615 5c7s 0.615 5h6d 0.615 5h6c 0.615 khkc 0.614 khkd 0.614 7hac 0.602 7had 0.602 5d7h 0.599 5c7h 0.599 7hah 0.587 7has 0.568 9dtd 0.551 9ctc 0.551 8cks 0.532 8dks 0.532 6skc 0.517 6skd 0.517 2s3s 0.512 6hqh 0.510 8h9h 0.51 9hth 0.506 4sks 0.481 3sqs 0.465 9hqh 0.456 7sad 0.448 7sac 0.448 6hks 0.446 5c6c 0.444 5d6d 0.444 5c6h 0.440 5d6h 0.440 tsjs 0.439 7sah 0.428 3c6c 0.412 3d6d 0.412 7s7h 0.366 7h8h 0.350 7h8c 0.343 7h8d 0.343 6h6d 0.342 6h6c 0.342 9hqs 0.335 7s8c 0.333 7s8d 0.333 2s6s 0.327 8h8d 0.326 8h8c 0.326 kskc 0.311 kskd 0.311 7s8h 0.307 6cqc 0.293 6s9s 0.246 6hjs 0.229 6hqc 0.226 6sqc 0.222 3s9s 0.222 6d6c 0.216 9sqc 0.209 6sqh 0.193 5c6s 0.185 5d6s 0.185 9sjh 0.175 9dts 0.163 9cts 0.163 9cqc 0.160 9ctd 0.157 9dtc 0.157 9sqh 0.142 9htc 0.132 9sjd 0.111 9sjc 0.111 3s4s 0.109 9cjs 0.102 9djs 0.102 9hjs 0.092 9hjd 0.088 9hjc 0.088 3s6s 0.074 2s9s 0.074 9dth 0.072 9cth 0.072 6hkh 0.072 8hkc 0.068 8hkd 0.068 9hts 0.066 4c7s 0.055 4d7s 0.055 4s7h 0.055 4d7h 0.055 4h7s 0.055 4c7h 0.055 9djc 0.049 9cjd 0.049 6cjs 0.039 6djs 0.039 8d8c 0.036 2s4s 0.032 8dkh 0.024 8ckh 0.024 9sjs 0.021 6hkd 0.011 6hkc 0.011 9hqc 0.001 ================================================ FILE: Source/Lookahead/Tests/ranges/situation-p2.txt ================================================ 5d5c 1 5h5d 1 5h5c 1 7sas 0.991 7hah 0.982 8d8c 0.969 8h8c 0.953 8h8d 0.953 5h7h 0.870 7sks 0.855 7h9h 0.834 7s9s 0.834 7hkh 0.807 7sts 0.783 6s7s 0.688 6h9h 0.662 7sqs 0.635 7hqh 0.635 7sjs 0.605 7hjh 0.605 7h8h 0.581 6d9d 0.560 6c9c 0.560 4s6s 0.548 7hth 0.526 4h6h 0.524 3s4s 0.516 7s7h 0.493 4s7s 0.421 7sah 0.402 7sad 0.394 7sac 0.394 7has 0.390 7had 0.387 7hac 0.387 6s9s 0.318 4c6c 0.281 4d6d 0.281 6h7h 0.274 9hjh 0.245 7hkd 0.233 7hkc 0.233 7skc 0.229 7skd 0.229 6hjh 0.229 6djd 0.228 6cjc 0.228 9sjs 0.227 6sjs 0.223 9sts 0.220 9djd 0.214 9cjc 0.214 7skh 0.208 4h7h 0.206 6hqh 0.184 7sjh 0.180 7hjc 0.180 7hjs 0.180 7sjc 0.180 7hjd 0.180 7sjd 0.180 7h8c 0.177 7h8d 0.177 6hth 0.177 6sts 0.176 7hks 0.168 6sqs 0.167 3sqs 0.165 9cqc 0.163 6cqc 0.160 7s8h 0.155 7s8d 0.151 7s8c 0.151 3c6c 0.148 3d6d 0.148 7s9c 0.138 7h9c 0.138 7h9d 0.138 7s9d 0.138 7hqc 0.134 7hqs 0.134 7sqc 0.134 6dtd 0.133 6ctc 0.133 6sqh 0.133 7sqh 0.131 9hqh 0.126 4sqs 0.103 3h6h 0.101 3s6s 0.100 6sqc 0.098 7htc 0.097 7htd 0.097 3s3h 0.083 tsjd 0.064 tsjc 0.064 3h3c 0.064 3h3d 0.064 6hqs 0.064 9hth 0.064 9ctc 0.061 9dtd 0.061 9hqs 0.060 7s9h 0.052 7h9s 0.051 tsjh 0.049 4hks 0.047 4hkh 0.047 9sqh 0.037 7stc 0.035 7std 0.035 6h9s 0.030 6h9d 0.030 6h9c 0.030 9dqs 0.030 9cqs 0.030 6c9d 0.027 6d9c 0.027 7hts 0.025 tdjs 0.024 tcjs 0.024 2sjs 0.023 9sjh 0.023 9hts 0.022 6dts 0.021 6cts 0.021 thjs 0.020 6hts 0.019 tsjs 0.018 6dqh 0.016 6cqh 0.016 6cqs 0.013 6dqs 0.013 thjh 0.013 tcjc 0.013 tdjd 0.013 3sjs 0.012 6stc 0.011 6std 0.011 6hjs 0.010 6d9s 0.010 6c9s 0.010 4dks 0.010 4cks 0.010 6sth 0.009 6sjh 0.009 2sts 0.008 6sjc 0.008 6sjd 0.008 8dac 0.007 8cad 0.007 2sqs 0.006 2s2h 0.005 6dqc 0.005 8dah 0.005 8cah 0.005 8cas 0.003 8das 0.003 9sqc 0.002 9d9c 0.001 ================================================ FILE: Source/Lookahead/Tests/ranges/situation2-p1.txt ================================================ TsAs 0.012875 QcAc 0.192686 QdAs 0.177237 QcAs 0.177237 QcAd 0.184158 QdAc 0.184158 QdAd 0.192686 JsAs 0.005649 KcAc 0.138829 KsAd 0.009415 KsAc 0.009415 KcAs 0.052031 KcAd 0.137483 KsAs 0.170011 AsAd 0.208837 AsAc 0.208837 AdAc 0.199488 QhAc 0.170189 QhAd 0.170189 QhAs 0.157085 QdQc 0.052385 5dAd 0.017711 5cAc 0.017711 JcAc 0.008799 JdAd 0.008799 JcAd 0.007662 JdAc 0.007662 5sAs 0.001169 TsJs 0.042813 KhAs 0.085118 KhAd 0.115602 KhAc 0.115602 JhAs 0.001147 TdJd 0.010486 TcJc 0.010486 JdAs 0.005942 JcAs 0.005942 QhQd 0.202023 QhQc 0.202023 8h9h 0.041717 9hTh 0.001733 3c4c 0.026533 3d4d 0.026533 7h8h 0.03391 8hTh 0.018794 5d5c 0.052883 6h8h 0.027873 6h7h 0.04121 5s5c 0.076916 5s5d 0.076916 3s4s 0.059114 ThAc 0.030011 ThAd 0.030011 7h9h 0.029347 4h6h 0.022629 3h4h 0.009688 6h9h 0.024147 TdJc 0.007639 TcJd 0.007639 7hTh 0.034049 TdJh 0.055531 TcJh 0.055531 ThAs 0.071851 4h7h 0.012634 TdQd 0.000673 TcQc 0.000673 5sAc 0.064845 5sAd 0.064845 TdAd 0.079246 TcAc 0.079246 4hTh 0.043931 ThJd 0.03863 ThJc 0.03863 5dAs 0.072294 5cAs 0.072294 TsJh 0.038892 3hJh 0.069059 5dAc 0.013776 5cAd 0.013776 6h6d 0.009231 6h6c 0.009231 6hTh 0.030378 TcAd 0.07591 TdAc 0.07591 JhKh 0.009611 5s7s 0.025734 5s8s 0.02286 5cQc 0.035853 5dQd 0.035853 4hJh 0.060204 JsKs 0.005812 7hJh 0.008289 ThJs 0.040627 6hJh 0.030884 ThKh 0.000347 TdQh 0.001823 TcQh 0.001823 3hTh 0.037308 JcKh 0.000934 JdKh 0.000934 3s5s 0.024575 3h6h 0.012474 9cQh 0.004281 9dQh 0.004281 7h7c 0.002059 7h7d 0.002059 TsKs 0.002045 4d5d 0.025888 4c5c 0.025888 2s2h 0.028576 5s6s 0.024846 3s3h 0.001982 TdKh 0.013255 TcKh 0.013255 5s9s 0.005584 5d6d 0.004354 5c6c 0.004354 TdQc 0.001049 TcQd 0.001049 4h8h 0.014043 3d5d 0.03399 3c5c 0.03399 4hKh 0.018503 4s5s 0.027164 5sQc 0.033502 5sQd 0.033502 3hKh 0.072335 5sKs 0.13698 5cQd 0.035113 5dQc 0.035113 8dQd 0.003277 8cQc 0.003277 2hJh 0.041547 5dQh 0.124608 5cQh 0.124608 5sQh 0.091956 5cKc 0.048871 2hTh 0.035427 3h9h 0.029182 5dKh 0.07234 5cKh 0.07234 5dKc 0.042486 2hQh 0.124463 9hAc 0.003917 9hAd 0.003917 4h5d 0.066644 4h5c 0.066644 9dAd 0.052463 9cAc 0.052463 5sKh 0.044651 4h5s 0.03626 8dQh 0.017434 8cQh 0.017434 4h9h 0.033067 9dAc 0.037488 9cAd 0.037488 5dKs 0.008237 5cKs 0.008237 2hKc 0.008389 5s9h 0.008825 5d9h 0.008854 5c9h 0.008854 8cAc 0.011438 8dAd 0.011438 2d3d 0.011506 2c3c 0.011506 8cAs 0.013006 8dAs 0.013006 8cAd 0.013162 8dAc 0.013162 5c8h 0.014688 5d8h 0.014688 5dJd 0.016029 5cJc 0.016029 5dJs 0.00012 2hKs 0.016178 5s6h 0.000494 6dQd 0.000581 3h7h 0.016483 8cQd 0.001366 6s6h 0.01679 5d7h 0.017885 2hQd 0.002738 5c7h 0.017885 3sKh 0.017964 5cJd 0.017986 5dJc 0.017986 2h6h 0.019178 3h8h 0.019209 2c5c 0.006802 4cKh 0.01921 3cQh 0.008199 4dKh 0.01921 5sTh 0.019601 4sKh 0.019823 5c6h 0.022814 5d6h 0.022814 3dKh 0.023955 3cKh 0.023955 2h7h 0.024077 7cQh 0.026845 7dQh 0.026845 6cQh 0.028938 6dQh 0.028938 2h3h 0.0296 3hKs 0.030566 2h8h 0.031678 5sJh 0.033156 6cQc 0.000581 6cQd 0.000774 9hAs 0.001626 5d6s 0.001928 7cQd 0.002405 7dQc 0.002405 5s7h 0.002685 5sTs 0.033328 7cQc 0.003244 8s8h 0.004057 2cKh 0.033397 5cTd 0.004871 5dTd 0.005643 2dKh 0.033397 4cQh 0.006077 2d5d 0.006802 5dTh 0.034641 2d4d 0.007283 7s7h 0.008199 5cTh 0.034641 2h4h 0.035935 2h9h 0.03762 5cJh 0.040793 5dJh 0.040793 5sJs 0.044384 6dQc 0.000774 8dQc 0.001366 5c6s 0.001928 4hKs 0.002551 7dQd 0.003244 9cAs 0.045121 5dTc 0.004871 4dQh 0.006077 9dAs 0.045121 3dQh 0.008199 2s4s 0.047701 5cJs 0.00012 TdAs 0.063543 2hQc 0.002738 TcAs 0.063543 5cTc 0.005643 2hKh 0.065274 2s3s 0.070069 2s5s 0.070284 2c4c 0.007283 2sKh 0.109528 5s8h 0.004287 ================================================ FILE: Source/Lookahead/Tests/ranges/situation2-p2.txt ================================================ ThQh 1.0 TsAs 1.0 QcAc 1.0 QdAs 1.0 QcAs 1.0 QcAd 1.0 QdAc 1.0 QdAd 1.0 JsAs 1.0 KcAc 0.999988 KsAd 0.99948 KsAc 0.99948 KcAs 0.99948 KcAd 0.99948 KsAs 0.999246 AsAd 0.995804 AsAc 0.995804 AdAc 0.995165 QhAc 0.985218 QhAd 0.985218 9hQh 0.983855 QhAs 0.983609 QdQc 0.94031 5dAd 0.938396 5cAc 0.938396 JcAc 0.922008 JdAd 0.922008 JhQh 0.91829 JcAd 0.909971 JdAc 0.909971 5sAs 0.892997 TsJs 0.884349 JsAc 0.864439 JsAd 0.864439 KhAs 0.861762 KhAd 0.78992 KhAc 0.78992 JhAs 0.783567 TdJd 0.77909 TcJc 0.77909 ThJh 0.728156 JdAs 0.724391 JcAs 0.724391 QhQd 0.695481 QhQc 0.695481 8h9h 0.690308 9sAs 0.687567 8hQh 0.682829 9hTh 0.67847 JhAd 0.649819 JhAc 0.649819 8sAs 0.598506 6s7s 0.586651 3c4c 0.568265 3d4d 0.568265 7h8h 0.531157 8hTh 0.524748 5d5c 0.51912 8s9s 0.508511 6h8h 0.502902 6h7h 0.49231 5s5c 0.489826 5s5d 0.489826 3s4s 0.465243 7hQh 0.428411 ThAc 0.421081 ThAd 0.421081 7sAs 0.415702 7h9h 0.408588 4h6h 0.391207 3h4h 0.387373 9sTs 0.367639 6h9h 0.359178 9cQc 0.336767 9dQd 0.336767 TdJc 0.336202 TcJd 0.336202 7hTh 0.327239 TdJh 0.323304 TcJh 0.323304 7hAc 0.312333 7hAd 0.312333 ThAs 0.28378 4h7h 0.2764 6d6c 0.274163 TdQd 0.259542 TcQc 0.259542 7s8s 0.259143 5sAc 0.258624 5sAd 0.258624 TdAd 0.255515 TcAc 0.255515 QhKh 0.255014 4hTh 0.247383 ThJd 0.242815 ThJc 0.242815 5dAs 0.24056 5cAs 0.24056 8c9h 0.240101 8d9h 0.240101 4s6s 0.240024 TsJh 0.239936 8h9d 0.237024 8h9c 0.237024 8sTs 0.236164 3hJh 0.235815 5dAc 0.23249 5cAd 0.23249 6h6d 0.230447 6h6c 0.230447 6hTh 0.223814 TcAd 0.222871 TdAc 0.222871 JhKh 0.217936 5s7s 0.217457 5s8s 0.209134 5cQc 0.205279 5dQd 0.205279 4hJh 0.204402 4s7s 0.190774 8hAd 0.188829 8hAc 0.188829 TsQh 0.188518 6d7d 0.182818 6c7c 0.182818 JsKs 0.182139 7hJh 0.180032 ThJs 0.175976 JcQc 0.174283 JdQd 0.174283 6hQh 0.171648 9sJs 0.171103 6h7d 0.169491 6h7c 0.169491 9cTh 0.16643 9dTh 0.16643 6hJh 0.16181 6d7h 0.154108 6c7h 0.154108 ThKh 0.15041 TdQh 0.147366 TcQh 0.147366 3hTh 0.145828 9cJh 0.143185 9dJh 0.143185 8dJh 0.135506 8cJh 0.135506 7h8c 0.135312 7h8d 0.135312 JcKh 0.130459 JdKh 0.130459 5c8c 0.130091 5d8d 0.130091 3s5s 0.129354 8hJh 0.127313 8sJs 0.125964 5d7d 0.12219 5c7c 0.12219 3h6h 0.122119 6hAd 0.12198 6hAc 0.12198 9cQh 0.120908 9dQh 0.120908 7c8h 0.119344 7d8h 0.119344 6cKh 0.11848 6dKh 0.11848 2h2c 0.118067 2h2d 0.118067 7cKh 0.113613 7dKh 0.113613 7h7c 0.111914 7h7d 0.111914 TsKs 0.11125 4d5d 0.110589 4c5c 0.110589 9hQd 0.104607 9hQc 0.104607 2s2h 0.100401 9hTd 0.099982 9hTc 0.099982 JsKh 0.098504 5s6s 0.098339 3s3h 0.097965 8dTh 0.095905 8cTh 0.095905 6s8s 0.095264 TdKh 0.095156 TcKh 0.095156 5s9s 0.089266 5d6d 0.087465 5c6c 0.087465 TdQc 0.087005 TcQd 0.087005 7sKs 0.086537 6hKh 0.084513 9hJh 0.084366 TdJs 0.083068 TcJs 0.083068 9dKh 0.081128 9cKh 0.081128 3h3d 0.079948 3h3c 0.079948 8h8c 0.077443 8h8d 0.077443 9sQh 0.077198 JsQh 0.076678 4h4d 0.073616 4h4c 0.073616 4h8h 0.073565 9dQc 0.071635 9cQd 0.071635 3d5d 0.071211 3c5c 0.071211 8cKh 0.071153 8dKh 0.071153 7hKh 0.066619 4hKh 0.066001 4s5s 0.063675 5sQc 0.063187 5sQd 0.063187 5c9c 0.062731 5d9d 0.062731 JcQd 0.061811 JdQc 0.061811 TsKh 0.061088 8hKh 0.056488 JhKc 0.056085 9hJd 0.05348 9hJc 0.05348 3hKh 0.050775 8hQd 0.05038 8hQc 0.05038 3s6s 0.049144 TsQc 0.049088 TsQd 0.049088 6sAs 0.046578 ThQc 0.045762 ThQd 0.045762 JcQh 0.045513 JdQh 0.045513 8hJd 0.045208 8hJc 0.045208 5sKs 0.044395 9sKs 0.044308 JcKc 0.043908 5cQd 0.039281 5dQc 0.039281 8dQd 0.035404 8cQc 0.035404 6d7c 0.035172 6c7d 0.035172 7sTs 0.035108 TsJd 0.032898 TsJc 0.032898 7hAs 0.032515 9hKc 0.03239 6hAs 0.030463 8sKs 0.030326 6s6d 0.028536 6s6c 0.028536 7hKc 0.026387 8s9h 0.025309 9h9c 0.025287 9h9d 0.025287 8hTc 0.024807 8hTd 0.024807 3hQh 0.024048 9s9h 0.022733 2hJh 0.022645 7hQc 0.021782 7hQd 0.021782 JdKc 0.020455 4d5c 0.019111 4c5d 0.019111 6h8d 0.017399 6h8c 0.017399 6sKs 0.017271 5dQh 0.016629 5cQh 0.016629 3sTs 0.016566 9hKs 0.016399 TcKc 0.014618 5sQh 0.014519 7d7c 0.014207 6hKc 0.012789 5cKc 0.012455 8h9s 0.012401 8hKc 0.010877 TsAc 0.009255 TsAd 0.009255 2hTh 0.008329 6d8h 0.008122 6c8h 0.008122 JsQd 0.00778 JsQc 0.00778 3h9h 0.007491 4s4h 0.007365 TdKc 0.007048 4sAs 0.006925 9sQc 0.006557 9sQd 0.006557 5dKh 0.00609 5cKh 0.00609 5dKc 0.006084 2hQh 0.005711 4c5s 0.005594 4d5s 0.005594 4hQh 0.005377 QcKc 0.005261 9hAc 0.005189 9hAd 0.005189 JhQd 0.004636 JhQc 0.004636 5c6d 0.004245 5d6c 0.004245 4h5d 0.004108 4h5c 0.004108 9dAd 0.003654 9cAc 0.003654 5sKh 0.002873 ThTd 0.002718 ThTc 0.002718 7s7c 0.002255 7s7d 0.002255 QdKc 0.002196 4h5s 0.001528 5s6d 0.001469 5s6c 0.001469 7sQh 0.001327 8dQh 0.000626 8cQh 0.000626 4h9h 0.00042 2sTs 0.000129 9dAc 0.000111 9cAd 0.000111 ================================================ FILE: Source/Lookahead/Tests/ranges/situation3-p1.txt ================================================ 7c9d 0.576598 7c9s 0.576598 8dQc 0.004872 8cQd 0.081817 8cQs 0.081817 8sQc 0.004872 2dKc 0.735301 2sKc 0.735301 2hQd 0.280807 2hQs 0.280807 7s9d 0.000698 7d9s 0.000698 2hKd 0.385034 2hKs 0.385034 7s9c 0.532047 7d9c 0.532047 2hQc 0.408286 8sJc 0.367419 8dJc 0.367419 8dJs 0.271071 8cJs 0.376085 8sJd 0.271071 8cJd 0.376085 9dQc 0.367292 9sQc 0.367292 9cQs 0.320529 9sQd 0.382032 9cQd 0.320529 9dQs 0.382032 7cTd 0.626715 7cTs 0.626715 3d8d 0.974662 3s8s 0.974662 8dTc 0.736024 8cTs 0.731034 8sTd 0.520424 8cTd 0.731034 8sTc 0.736024 8dTs 0.520424 4cTd 0.661603 4cTs 0.661603 7dTs 0.180292 7sTd 0.180292 2dQc 0.537249 2sQc 0.537249 2hKc 0.508368 2cKs 0.705708 2cKd 0.705708 6cKd 0.616555 6cKs 0.616555 2d8d 0.067562 2s8s 0.067562 9cJd 0.482259 9dJc 0.515822 9sJc 0.515822 9dJs 0.42837 9cJs 0.482259 9sJd 0.42837 2cQd 0.604471 2cQs 0.604471 6sKc 0.748055 6dKc 0.748055 6cQd 0.602979 6cQs 0.602979 6dKs 0.35925 6sKd 0.35925 6hQd 0.160684 6hQs 0.160684 6hKd 0.184386 6hKs 0.184386 7sTc 0.527372 7dTc 0.527372 4sTd 0.382808 4dTs 0.382808 4sTc 0.490031 4dTc 0.490031 4cJd 0.579057 4cJs 0.579057 3dQh 0.209001 3sQh 0.209001 TdQc 0.665616 TsQc 0.665616 TcQd 0.630535 TcQs 0.630535 TdQs 0.553059 TsQd 0.553059 2hKh 0.184811 6dQc 0.577865 6sQc 0.577865 3sJd 0.302203 3dJs 0.302203 8cKd 0.809187 8cKs 0.809187 8c9d 0.938184 8d9s 0.607714 8s9d 0.607714 8s9c 0.999263 8d9c 0.999263 8c9s 0.938184 3sJc 0.260805 3dJc 0.260805 2sKd 0.635289 2dKs 0.635289 2dKd 0.636623 2sKs 0.636623 2sQd 0.409617 2dQs 0.409617 6hKc 0.29862 8h9d 0.687069 8h9s 0.687069 2dQd 0.41179 2sQs 0.41179 4dJc 0.487367 4sJc 0.487367 6hQc 0.260818 4s9s 0.825609 4d9d 0.825609 TsJd 0.603136 TdJc 0.763068 TdJs 0.603136 TcJs 0.701756 TcJd 0.701756 TsJc 0.763068 6sQd 0.361312 6dQs 0.361312 8dKc 0.570998 8sKc 0.570998 4dJs 0.405203 4sJd 0.405203 6sJd 0.390351 6dJs 0.390351 3dJh 0.037906 3sJh 0.037906 3sQd 0.339884 3dQs 0.339884 8dKs 0.156603 8sKd 0.156603 6hJs 0.251252 6hJd 0.251252 7c8s 0.761798 7c8d 0.761798 3dQc 0.374428 3sQc 0.374428 6dTs 0.328233 6sTd 0.328233 9hTc 0.491011 9sTd 0.741425 9dTc 0.958052 9sTh 0.660847 9dTs 0.741425 9hTs 0.57964 9cTd 0.980837 9dTh 0.660847 9cTs 0.980837 9sTc 0.958052 9hTd 0.57964 4s8s 0.891436 4d8d 0.891436 9cKd 0.768042 9cKs 0.768042 9dKc 0.732077 9sKc 0.732077 7s8c 0.697854 7d8c 0.697854 7cJs 0.546098 7cJd 0.546098 TdJh 0.528774 TsJh 0.528774 7d8s 0.451054 7s8d 0.451054 7sJd 0.322379 7dJs 0.322379 9sKd 0.271689 9dKs 0.271689 8hJs 0.248402 8hJd 0.248402 7dKs 0.45682 7sKd 0.45682 TcJh 0.447069 8s9h 0.705435 8d9h 0.705435 JcQd 0.999668 JdQs 0.989737 JsQd 0.989737 JsQc 0.999668 JcQs 0.999668 JdQc 0.999668 2sAc 0.26047 2dAc 0.26047 7cAs 0.372264 7cAd 0.372264 7sAc 0.383251 7dAs 0.201945 7sAd 0.201945 7dAc 0.383251 9cTh 0.567681 7dQs 0.19954 7sQd 0.19954 6cJs 0.417697 6cJd 0.417697 7dJc 0.478664 7sJc 0.478664 8hQd 0.105077 8hQs 0.105077 7cKd 0.798464 7cKs 0.798464 ThJc 0.362791 6d9s 0.33735 6s9d 0.33735 8hTd 0.358348 8hTs 0.358348 4cQs 0.486572 4cQd 0.486572 7hKd 0.200805 7hKs 0.200805 ThJs 0.366346 ThJd 0.366346 8hJc 0.158606 2sAd 0.125948 2dAs 0.125948 2sAs 0.12656 2dAd 0.12656 6cTd 0.344955 6cTs 0.344955 9hQs 0.399326 9hQd 0.399326 6dJc 0.368011 6sJc 0.368011 6sAd 0.475394 6dAs 0.475394 6dKh 0.210138 6sKh 0.210138 3sJs 0.307328 3dJd 0.307328 2hQh 0.011411 6s8d 0.218216 6d8s 0.218216 7hKc 0.409867 6dTc 0.316415 6sTc 0.316415 7hTd 0.072864 7hTs 0.072864 7h8c 0.412121 3s9s 0.915402 3d9d 0.915402 7h9d 0.112383 7h9s 0.112383 7dKc 0.746292 7sKc 0.746292 2s6s 0.011833 2d6d 0.011833 3sAd 0.020721 3dAs 0.020721 6hJc 0.255927 6cKh 0.277723 6c9d 0.391757 6c9s 0.391757 6s8c 0.251417 6d8c 0.251417 5d9s 0.008335 5s9d 0.008335 6s9c 0.387243 6d9c 0.387243 6dAc 0.603403 6sAc 0.603403 5c9s 0.008803 5c9d 0.008803 7hTc 0.120492 3sKc 0.019864 3dKc 0.019864 5s9c 0.016244 5d9c 0.016244 4sQd 0.321008 4dQs 0.321008 8h9c 0.714389 9hQc 0.32268 3dQd 0.348615 3sQs 0.348615 2h6h 0.374044 7h9c 0.218194 3dKs 0.026622 3sKd 0.026622 6c8s 0.272968 6c8d 0.272968 ThQc 0.469357 6cAs 0.593829 6cAd 0.593829 8hQc 0.187573 8hKd 0.61596 8hKs 0.61596 ThQs 0.627559 ThQd 0.627559 2cAd 0.229155 2cAs 0.229155 2c6c 0.086933 TcQh 0.141596 7cQd 0.306419 7cQs 0.306419 4sQc 0.429419 4dQc 0.429419 4sTs 0.469618 4dTd 0.469618 7dQc 0.233307 7sQc 0.233307 7h8s 0.371923 7h8d 0.371923 4d5c 0.375902 4s5c 0.375902 6hTd 0.254551 6hTs 0.254551 6s7d 0.035004 6d7s 0.035004 2h7h 0.80108 6s7h 0.042054 6d7h 0.042054 6c7h 0.045399 2d7d 0.850397 2s7s 0.850397 TsQh 0.218291 TdQh 0.218291 2cQh 0.020234 3dTd 0.547335 3sTs 0.547335 6h8c 0.108551 9hKd 0.528364 9hKs 0.528364 6hTc 0.229084 8c9h 0.808115 2sKh 0.200768 2dKh 0.200768 8hKc 0.742747 6c7s 0.033139 6c7d 0.033139 TdKc 0.619885 TsKc 0.619885 8cJh 0.032788 JhQs 0.849488 JhQd 0.849488 3sAc 0.026183 3dAc 0.026183 6s7c 0.040271 6d7c 0.040271 JcQh 0.999668 4c5s 0.278276 4c5d 0.278276 TcKd 0.600251 TcKs 0.600251 JhQc 0.875425 8dJh 0.024449 8sJh 0.024449 6h7d 0.02614 6h7s 0.02614 3dKd 0.022563 3sKs 0.022563 TdKs 0.271795 TsKd 0.271795 2c7c 0.883777 2sJs 0.306491 2dJd 0.306491 6h8d 0.103823 6h8s 0.103823 5c8s 0.182661 5c8d 0.182661 6sJh 0.089906 6dJh 0.089906 9hJd 0.314241 9hJs 0.314241 6h7c 0.025703 JdQh 0.999668 JsQh 0.999668 6h9c 0.224107 5s8d 0.101857 5d8s 0.101857 4d5s 0.134176 4s5d 0.134176 2c8c 0.004952 6dTh 0.076579 6sTh 0.076579 6d8h 0.08826 6s8h 0.08826 6cTh 0.084164 2cQc 0.100448 6c8h 0.074305 5d8c 0.228083 5s8c 0.228083 9dJh 0.254641 9sJh 0.254641 6cQh 0.048666 6sQh 0.021021 6dQh 0.021021 8cKh 0.440539 8hAs 0.285489 8hAd 0.285489 2cKc 0.337836 9hKc 0.616775 8hAc 0.607257 9hJc 0.293782 6cJh 0.100179 7dKh 0.244947 7sKh 0.244947 6h9s 0.293552 6h9d 0.293552 2s9s 0.033625 2d9d 0.033625 7cTh 0.010525 7hQs 0.186607 7hQd 0.186607 7dQh 0.069081 7sQh 0.069081 6s9h 0.145742 6d9h 0.145742 6hJh 0.031416 7cKh 0.381057 6cJc 0.234386 8sKh 0.246953 8dKh 0.246953 2dAh 0.037724 2sAh 0.037724 8cAs 0.998841 8cAd 0.998841 8sAd 0.793184 8dAs 0.793184 6dJd 0.345568 6sJs 0.345568 8sTh 0.424029 8dTh 0.424029 9dKh 0.362624 9sKh 0.362624 2cKh 0.248771 7c8h 0.272613 4dJd 0.38451 4sJs 0.38451 9cKh 0.437988 6dQd 0.406045 6sQs 0.406045 7cQh 0.021296 6c9h 0.176795 2cTc 0.010327 4c9c 0.276899 8sAc 0.998841 8dAc 0.998841 2dTd 0.084362 2sTs 0.084362 7d8h 0.20001 7s8h 0.20001 JcKs 0.398371 JcKd 0.398371 7hJd 0.129925 7hJs 0.129925 JsKc 0.413866 JdKc 0.413866 2hAd 0.063911 2hAs 0.063911 9cJh 0.193916 JdKs 0.265609 JsKd 0.265609 7hQc 0.251705 7hJc 0.283341 2hAc 0.092257 2cAh 0.051811 8sAh 0.412602 8dAh 0.412602 8hTc 0.384091 4cTh 0.168071 7sAh 0.00395 7dAh 0.00395 2cJc 0.130121 6sAh 0.313061 6dAh 0.313061 7sJs 0.487453 7dJd 0.487453 ThKd 0.284108 ThKs 0.284108 TsKh 0.216933 TdKh 0.216933 4cJh 0.050055 7cJh 0.113612 JdKh 0.119767 JsKh 0.119767 4dTh 0.074387 4sTh 0.074387 ThKc 0.392161 8cTh 0.395134 JhKs 0.095369 JhKd 0.095369 7sJh 0.072911 7dJh 0.072911 8cAh 0.793549 6cAh 0.344281 4dJh 0.009256 4sJh 0.009256 JcKh 0.122454 4dQd 0.301753 4sQs 0.301753 TcKh 0.288389 JhKc 0.064511 7sQs 0.166611 7dQd 0.166611 7hAs 0.120704 7hAd 0.120704 8sJs 1.0 8dJd 1.0 5d9d 0.004281 5s9s 0.004281 9sAd 0.670271 9dAs 0.670271 9sAc 0.98371 9dAc 0.98371 9cAs 0.996905 9cAd 0.996905 3s4s 0.050704 3d4d 0.050704 QdKh 0.16917 QsKh 0.16917 7dTd 0.59285 7sTs 0.59285 7hAc 0.11673 6cQc 0.071691 7s9s 0.979811 7d9d 0.979811 9hAs 0.097754 9hAd 0.097754 QhKs 0.145851 QhKd 0.145851 3d6d 0.181929 3s6s 0.181929 5d8d 0.126323 5s8s 0.126323 8sTs 0.99998 8dTd 0.99998 QcKh 0.238365 9sAh 0.297461 9dAh 0.297461 QhKc 0.185222 9cAh 0.34224 6cTc 0.158978 6hAd 0.129174 6hAs 0.129174 6sTs 0.239982 6dTd 0.239982 9hAc 0.222999 4cQh 0.008509 6hTh 0.029482 4c8c 0.481243 6hAc 0.17409 2s2d 0.065175 8cTc 0.679599 QcKs 0.575361 QcKd 0.575361 8cJc 0.710125 6c9c 0.165487 6d9d 0.301487 6s9s 0.301487 8d9d 0.999906 8s9s 0.999906 6h8h 0.252305 QsKc 0.586199 QdKc 0.586199 3d5d 0.566697 3s5s 0.566697 8sQs 0.869797 8dQd 0.869797 2s2c 0.066527 2d2c 0.066527 3s7s 0.557516 3d7d 0.557516 TsAd 0.555883 TdAs 0.555883 TsAc 0.781149 TdAc 0.781149 7d8d 0.476335 7s8s 0.476335 TcAs 0.715185 TcAd 0.715185 QdKs 0.326483 QsKd 0.326483 6h9h 0.117428 6s8s 0.096711 6d8d 0.096711 6c8c 0.038704 TcAh 0.286361 TdAh 0.188743 TsAh 0.188743 9dJd 0.999681 9sJs 0.999681 9cJc 0.845093 9sTs 0.99983 9dTd 0.99983 4d7d 0.043146 4s7s 0.043146 ThAs 0.162751 ThAd 0.162751 8c9c 0.563958 4c7d 0.053904 4c7s 0.053904 9cTc 0.913975 ThAc 0.290529 JsAd 0.400535 JdAs 0.400535 JcAs 0.656617 JcAd 0.656617 JdAc 0.664358 JsAc 0.664358 6h7h 0.09862 6s7s 0.11192 6d7d 0.11192 6c7c 0.112457 6dKd 0.437013 6sKs 0.437013 JhAd 0.180453 JhAs 0.180453 JhAc 0.252079 JdAh 0.08668 JsAh 0.08668 AdAc 0.146827 JsJd 0.230457 8cKc 0.032623 JsJc 0.339728 JsQs 1.0 6cAc 0.499908 JsKs 0.454858 8sKs 0.999783 7sAs 0.175029 6sAs 0.483072 JsAs 0.408229 8sAs 0.860818 9sQs 0.999783 JhJd 0.556002 JhJc 0.618049 7hAh 0.131257 JhQh 0.996181 5s5d 0.372318 9cQc 0.910015 JhKh 0.417914 7dAd 0.175029 9cKc 0.386251 4c5c 0.105699 9cAc 0.036749 JhAh 0.148733 TsJs 1.0 4d7c 0.054927 JdJc 0.339728 TsQs 1.0 4d5d 0.14437 TsKs 0.705848 JdQd 1.0 9sAs 0.691414 TsAs 0.557691 JdKd 0.454858 4s4c 0.009303 8hAh 0.041345 JdAd 0.408229 8dKd 0.999783 JcQc 1.0 8dAd 0.860818 JcKc 0.193321 JcAh 0.071483 QsQh 0.90616 TcJc 0.970424 QsQd 0.765069 QsQc 0.791112 TcQc 1.0 QsKs 0.372611 TcKc 0.174523 QsAs 0.32174 QsAh 0.042789 9hKh 0.074643 QsAd 0.321823 7sKs 0.608566 QsAc 0.570296 QhQd 0.90616 9dQd 0.999783 QhQc 1.0 9dKd 1.0 9dAd 0.691414 QhKh 0.559857 QhAs 0.336777 QhAh 0.038874 QhAd 0.336777 QhAc 0.458822 7c9c 0.031346 QdQc 0.791112 4d4c 0.009303 4s7c 0.054927 4s5s 0.14437 QdKd 0.372611 ThJh 0.046726 8cAc 0.083064 ThQh 0.241456 QdAs 0.321823 ThKh 0.313879 QdAh 0.042789 ThAh 0.155236 QdAd 0.32174 QdAc 0.570296 8cQc 0.412002 QcKc 0.100869 QcAs 0.583435 QcAh 0.061764 QcAd 0.583435 JsJh 0.556002 6dAd 0.483072 KsKh 1.0 KsKd 1.0 KsKc 1.0 5d5c 0.426142 KsAs 0.138113 7dKd 0.608566 KsAh 0.088275 KsAd 0.124245 KsAc 0.169566 7cAc 0.099207 KhKd 1.0 KhKc 1.0 KhAs 0.191619 TdJd 1.0 KhAh 0.030432 TdKd 0.705848 KhAd 0.191619 KhAc 0.099757 KdKc 1.0 9hAh 0.084663 KdAs 0.124245 KdAh 0.088275 5s5c 0.426142 KdAd 0.138113 KdAc 0.169566 KcAs 0.192207 9sKs 1.0 KcAh 0.096592 TdAd 0.557691 KcAd 0.192207 KcAc 0.245405 TdQd 1.0 AsAc 0.146827 AsAd 0.087461 6hAh 0.532644 ================================================ FILE: Source/Lookahead/Tests/ranges/situation3-p2.txt ================================================ 4s8c 0.881958 4d8c 0.881958 7c9d 0.861573 7c9s 0.861573 5c6h 0.859565 4c8d 0.854665 4c8s 0.854665 8dQc 0.846896 8cQd 0.846896 8sQd 0.846896 8dQs 0.846896 8cQs 0.846896 8sQc 0.846896 2dKc 0.841781 2sKc 0.841781 2hQd 0.829355 2hQs 0.829355 7s9d 0.81952 7d9s 0.81952 2hKd 0.810361 2hKs 0.810361 7s9c 0.808142 7d9c 0.808142 5d6h 0.806732 5s6h 0.806732 2sTd 0.801388 2dTs 0.801388 4c9s 0.800455 4c9d 0.800455 2hQc 0.783974 8sJc 0.775972 8dJc 0.775972 8dJs 0.775972 8cJs 0.775972 8sJd 0.775972 8cJd 0.775972 3s9d 0.769837 3d9s 0.769837 2cJs 0.768696 2cJd 0.768696 9dQc 0.766948 9sQc 0.766948 9cQs 0.766948 9sQd 0.766948 9cQd 0.766948 9dQs 0.766948 7cTd 0.763856 7cTs 0.763856 3d8d 0.757138 3s8s 0.757138 8dTc 0.756533 8cTs 0.756533 8sTd 0.756533 8cTd 0.756533 8sTc 0.756533 8dTs 0.756533 4cTd 0.754814 4cTs 0.754814 7dTs 0.751536 7sTd 0.751536 2dQc 0.744011 2sQc 0.744011 2sJc 0.741821 2dJc 0.741821 2hKc 0.738867 2cKs 0.738122 2cKd 0.738122 6cKd 0.734634 6cKs 0.734634 4d9c 0.730726 4s9c 0.730726 2d8d 0.729593 2s8s 0.729593 9cJd 0.726804 9dJc 0.726804 9sJc 0.726804 9dJs 0.726804 9cJs 0.726804 9sJd 0.726804 2cTd 0.726795 2cTs 0.726795 3s9h 0.725011 3d9h 0.725011 2cQd 0.724142 2cQs 0.724142 6sKc 0.723886 6dKc 0.723886 6cQd 0.723544 6cQs 0.723544 6dKs 0.721497 6sKd 0.721497 6hQd 0.71565 6hQs 0.71565 6hKd 0.715036 6hKs 0.715036 7sTc 0.713413 7dTc 0.713413 4s9d 0.713244 4d9s 0.713244 3dTh 0.712363 3sTh 0.712363 4sTd 0.709956 4dTs 0.709956 4sTc 0.702201 4dTc 0.702201 4cJd 0.699521 4cJs 0.699521 3dQh 0.688411 3sQh 0.688411 TdQc 0.687123 TsQc 0.687123 TcQd 0.687123 TcQs 0.687123 TdQs 0.687123 TsQd 0.687123 2hKh 0.684846 4c8h 0.684784 6dQc 0.684341 6sQc 0.684341 2dJs 0.681606 2sJd 0.681606 2sTc 0.680903 2dTc 0.680903 5cAh 0.676354 3sJd 0.676227 3dJs 0.676227 8cKd 0.675704 8cKs 0.675704 8c9d 0.674819 8d9s 0.674819 8s9d 0.674819 8s9c 0.674819 8d9c 0.674819 8c9s 0.674819 3sJc 0.672359 3dJc 0.672359 2sKd 0.668517 2dKs 0.668517 2dKd 0.66715 2sKs 0.66715 5dAh 0.662678 5sAh 0.662678 3d9c 0.660672 3s9c 0.660672 2sQd 0.656557 2dQs 0.656557 6hKc 0.65313 8h9d 0.652331 8h9s 0.652331 2dQd 0.650591 2sQs 0.650591 4dJc 0.647619 4sJc 0.647619 5d7h 0.644339 5s7h 0.644339 6hQc 0.641382 4s9s 0.64116 4d9d 0.64116 4d8s 0.637148 4s8d 0.637148 3sTc 0.63494 3dTc 0.63494 TsJd 0.63339 TdJc 0.63339 TdJs 0.63339 TcJs 0.63339 TcJd 0.63339 TsJc 0.63339 5cAs 0.633095 5cAd 0.633095 6sQd 0.631843 6dQs 0.631843 3dTs 0.630206 3sTd 0.630206 8dKc 0.629777 8sKc 0.629777 4dJs 0.621776 4sJd 0.621776 6sJd 0.61969 6dJs 0.61969 3dJh 0.61941 3sJh 0.61941 3sQd 0.617987 3dQs 0.617987 8dKs 0.61711 8sKd 0.61711 6hJs 0.616595 6hJd 0.616595 5sAc 0.61611 5dAc 0.61611 7c8s 0.61482 7c8d 0.61482 3dQc 0.61357 3sQc 0.61357 5c7h 0.610477 4s8h 0.608517 4d8h 0.608517 6dTs 0.608188 6sTd 0.608188 9hTc 0.607465 9sTd 0.607465 9dTc 0.607465 9sTh 0.607465 9dTs 0.607465 9hTs 0.607465 9cTd 0.607465 9dTh 0.607465 9cTs 0.607465 9sTc 0.607465 9hTd 0.607465 4s8s 0.605972 4d8d 0.605972 2hAh 0.605116 9cKd 0.604811 9cKs 0.604811 9dKc 0.59754 9sKc 0.59754 7s8c 0.595891 7d8c 0.595891 7cJs 0.594581 7cJd 0.594581 TdJh 0.594114 TsJh 0.594114 7d8s 0.592579 7s8d 0.592579 7sJd 0.59141 7dJs 0.59141 9sKd 0.590182 9dKs 0.590182 8hJs 0.589286 8hJd 0.589286 7dKs 0.58673 7sKd 0.58673 5sAd 0.586005 5dAs 0.586005 TcJh 0.584357 8s9h 0.584066 8d9h 0.584066 3s4d 0.583234 3d4s 0.583234 JcQd 0.578568 JdQs 0.578568 JsQd 0.578568 JsQc 0.578568 JcQs 0.578568 JdQc 0.578568 2sAc 0.576387 2dAc 0.576387 7cAs 0.57586 7cAd 0.57586 7sAc 0.57586 7dAs 0.57586 7sAd 0.57586 7dAc 0.57586 9cTh 0.57584 7dQs 0.575241 7sQd 0.575241 6cJs 0.574339 6cJd 0.574339 2s9d 0.571157 2d9s 0.571157 7dJc 0.571117 7sJc 0.571117 2c9s 0.569907 2c9d 0.569907 8hQd 0.568713 8hQs 0.568713 5s7d 0.566817 5d7s 0.566817 7cKd 0.565582 7cKs 0.565582 ThJc 0.564455 6d9s 0.563007 6s9d 0.563007 8hTd 0.560199 8hTs 0.560199 4cQs 0.554764 4cQd 0.554764 7hKd 0.553292 7hKs 0.553292 ThJs 0.551742 ThJd 0.551742 8hJc 0.551416 2sAd 0.549286 2dAs 0.549286 2sAs 0.548264 2dAd 0.548264 6cTd 0.548224 6cTs 0.548224 9hQs 0.546688 9hQd 0.546688 6dJc 0.544357 6sJc 0.544357 6sAd 0.543642 6dAs 0.543642 6dKh 0.54048 6sKh 0.54048 3sJs 0.536983 3dJd 0.536983 2hQh 0.53586 6s8d 0.534367 6d8s 0.534367 7hKc 0.534244 5c7s 0.532697 5c7d 0.532697 6dTc 0.531807 6sTc 0.531807 7hTd 0.530769 7hTs 0.530769 2hJs 0.530126 2hJd 0.530126 7h8c 0.527387 3s9s 0.526384 3d9d 0.526384 7h9d 0.523639 7h9s 0.523639 7dKc 0.523582 7sKc 0.523582 2s6s 0.522768 2d6d 0.522768 3d4c 0.522302 3s4c 0.522302 3sAd 0.521458 3dAs 0.521458 6hJc 0.521321 6cKh 0.52101 6c9d 0.520397 6c9s 0.520397 2dQh 0.519635 2sQh 0.519635 6s8c 0.518079 6d8c 0.518079 5d9s 0.515053 5s9d 0.515053 6s9c 0.513525 6d9c 0.513525 6dAc 0.513417 6sAc 0.513417 5c9s 0.508228 5c9d 0.508228 7hTc 0.507323 5d7c 0.507054 5s7c 0.507054 3sKc 0.503217 3dKc 0.503217 5s9c 0.502875 5d9c 0.502875 4sQd 0.500368 4dQs 0.500368 8h9c 0.498788 9hQc 0.497507 3dQd 0.49209 3sQs 0.49209 2h6h 0.49199 7h9c 0.491731 3dKs 0.490327 3sKd 0.490327 2d9c 0.488018 2s9c 0.488018 6c8s 0.487537 6c8d 0.487537 ThQc 0.486874 6cAs 0.484979 6cAd 0.484979 8hQc 0.483452 5s6c 0.482801 5d6c 0.482801 8hKd 0.482753 8hKs 0.482753 ThQs 0.478548 ThQd 0.478548 2cAd 0.471578 2cAs 0.471578 2c6c 0.470016 TcQh 0.469512 7cQd 0.465349 7cQs 0.465349 4sQc 0.463569 4dQc 0.463569 4cKs 0.460243 4cKd 0.460243 5s6d 0.45998 5d6s 0.45998 4sTs 0.459872 4dTd 0.459872 7dQc 0.459599 7sQc 0.459599 7h8s 0.457556 7h8d 0.457556 4d5c 0.453388 4s5c 0.453388 2hJc 0.452567 5c6s 0.451708 5c6d 0.451708 6hTd 0.451543 6hTs 0.451543 6s7d 0.446832 6d7s 0.446832 2h7h 0.446739 6s7h 0.445689 6d7h 0.445689 6c7h 0.444657 2cAc 0.443942 2hJh 0.443548 2d7d 0.443153 2s7s 0.443153 TsQh 0.443119 TdQh 0.443119 2cQh 0.442182 3dTd 0.441837 3sTs 0.441837 6h8c 0.441356 9hKd 0.440807 9hKs 0.440807 4sKc 0.440131 4dKc 0.440131 6hTc 0.438427 8c9h 0.437888 2sKh 0.437283 2dKh 0.437283 8hKc 0.437028 6c7s 0.436142 6c7d 0.436142 TdKc 0.435839 TsKc 0.435839 8cJh 0.434557 JhQs 0.433714 JhQd 0.433714 3sAc 0.432159 3dAc 0.432159 6s7c 0.432005 6d7c 0.432005 JcQh 0.427511 4c5s 0.427249 4c5d 0.427249 TcKd 0.426329 TcKs 0.426329 8cQh 0.4259 JhQc 0.425862 8dJh 0.425836 8sJh 0.425836 9cQh 0.42563 9dQh 0.425267 9sQh 0.425267 6h7d 0.42203 6h7s 0.42203 3dKd 0.42179 3sKs 0.42179 TdKs 0.421001 TsKd 0.421001 2c7c 0.420599 2sJs 0.417717 2dJd 0.417717 6h8d 0.417381 6h8s 0.417381 2sJh 0.416781 2dJh 0.416781 5c8s 0.414821 5c8d 0.414821 2sTh 0.410692 2dTh 0.410692 6sJh 0.409772 6dJh 0.409772 2hTc 0.40791 2hTd 0.407358 2hTs 0.407358 4dKs 0.405428 4sKd 0.405428 9hJd 0.405137 9hJs 0.405137 6h7c 0.404542 JdQh 0.404515 JsQh 0.404515 6h9c 0.403489 5s8d 0.401791 5d8s 0.401791 4d5s 0.400943 4s5d 0.400943 2c8c 0.399809 3sKh 0.39807 3dKh 0.39807 6dTh 0.397227 6sTh 0.397227 6d8h 0.396358 6s8h 0.396358 6cTh 0.394384 4c9h 0.393598 2cQc 0.392485 6c8h 0.392341 5d8c 0.388593 5s8c 0.388593 9dJh 0.387233 9sJh 0.387233 6cQh 0.387233 6sQh 0.386894 6dQh 0.386894 8cKh 0.384867 8hAs 0.383148 8hAd 0.383148 5cTc 0.381417 2cKc 0.37809 9hKc 0.376966 8hAc 0.375766 9hJc 0.372838 6cJh 0.372686 3sAs 0.372198 3dAd 0.372198 7dKh 0.371631 7sKh 0.371631 6h9s 0.369473 6h9d 0.369473 8dQh 0.369035 8sQh 0.369035 2s9s 0.368913 2d9d 0.368913 2h9c 0.364873 7dTh 0.362978 7sTh 0.362978 7cTh 0.362317 5dTs 0.361281 5sTd 0.361281 7hQs 0.358753 7hQd 0.358753 7dQh 0.354952 7sQh 0.354952 6s9h 0.352637 6d9h 0.352637 2hTh 0.351357 6hJh 0.348217 2c5c 0.347294 7cKh 0.347005 6cJc 0.345294 8sKh 0.34523 8dKh 0.34523 2dAh 0.343125 2sAh 0.343125 4s9h 0.342893 4d9h 0.342893 5d9h 0.342683 5s9h 0.342683 8cAs 0.342452 8cAd 0.342452 5sKh 0.341696 5dKh 0.341696 8sAd 0.34136 8dAs 0.34136 6dJd 0.338814 6sJs 0.338814 8sTh 0.337533 8dTh 0.337533 9dKh 0.328731 9sKh 0.328731 2cKh 0.328257 7c8h 0.327815 4dJd 0.326977 4sJs 0.326977 9cKh 0.325952 6dQd 0.324619 6sQs 0.324619 5dTh 0.324302 5sTh 0.324302 7cQh 0.32257 5sJh 0.322057 5dJh 0.322057 6c9h 0.32193 2cTc 0.320232 4c9c 0.319251 5sJd 0.318142 5dJs 0.318142 8sAc 0.317203 8dAc 0.317203 2d5d 0.315707 2s5s 0.315707 2dTd 0.313139 2sTs 0.313139 2cJh 0.311784 7d8h 0.311479 7s8h 0.311479 JcKs 0.305074 JcKd 0.305074 7hJd 0.304085 7hJs 0.304085 4cTc 0.303031 JsKc 0.302803 JdKc 0.302803 2hAd 0.301026 2hAs 0.301026 9cJh 0.300179 JdKs 0.298841 JsKd 0.298841 7hQc 0.29871 5cKh 0.298153 5c9h 0.297453 7hJc 0.294925 2hAc 0.294798 5cQc 0.293607 2cAh 0.293424 5cJh 0.291386 5cTs 0.290873 5cTd 0.290873 8sAh 0.290692 8dAh 0.290692 5sTs 0.290161 5dTd 0.290161 8hTc 0.289911 4cTh 0.288751 7sAh 0.287937 7dAh 0.287937 2cJc 0.287415 6sAh 0.285472 6dAh 0.285472 7sJs 0.280879 7dJd 0.280879 4c6h 0.280003 5cTh 0.278633 2h8h 0.277092 5cJc 0.273145 3s5d 0.27137 3d5s 0.27137 4c7c 0.267779 ThKd 0.267531 ThKs 0.267531 2c9c 0.265985 TsKh 0.258355 TdKh 0.258355 4cJh 0.257402 7cJh 0.255882 JdKh 0.253222 JsKh 0.253222 7c9h 0.253169 4dTh 0.248875 4sTh 0.248875 5sQd 0.245573 5dQs 0.245573 ThKc 0.243941 6hQh 0.243418 8cTh 0.243291 JhKs 0.243266 JhKd 0.243266 7sJh 0.242945 7dJh 0.242945 5sQh 0.242396 5dQh 0.242396 2h9s 0.240665 2h9d 0.240665 8cAh 0.240528 3sAh 0.238657 3dAh 0.238657 5cQh 0.238394 6cAh 0.235067 2s9h 0.234634 2d9h 0.234634 3d5c 0.234141 3s5c 0.234141 4dJh 0.232266 4sJh 0.232266 2cTh 0.231091 JcKh 0.222938 4dQd 0.222756 4sQs 0.222756 TcKh 0.221203 4cAh 0.220981 JhKc 0.2196 4d6s 0.217583 4s6d 0.217583 7d9h 0.216261 7s9h 0.216261 7sQs 0.211534 7dQd 0.211534 5c9c 0.211309 4s6h 0.211274 4d6h 0.211274 7hAs 0.207778 7hAd 0.207778 8sJs 0.207646 8dJd 0.207646 5sTc 0.207326 5dTc 0.207326 5d9d 0.204859 5s9s 0.204859 2h9h 0.200715 9sAd 0.1993 9dAs 0.1993 4sAh 0.198887 4dAh 0.198887 9sAc 0.196012 9dAc 0.196012 9cAs 0.193003 9cAd 0.193003 4c6s 0.186541 4c6d 0.186541 3s4s 0.1856 3d4d 0.1856 QdKh 0.180514 QsKh 0.180514 4dKd 0.178358 4sKs 0.178358 7dTd 0.175097 7sTs 0.175097 4d6c 0.174517 4s6c 0.174517 7hAc 0.174269 3d6s 0.173655 3s6d 0.173655 5sKd 0.171159 5dKs 0.171159 6cQc 0.16864 7s9s 0.166302 7d9d 0.166302 9hAs 0.16608 9hAd 0.16608 4cJc 0.165406 5s8h 0.16028 5d8h 0.16028 QhKs 0.158384 QhKd 0.158384 5cJs 0.156567 5cJd 0.156567 3d6d 0.156108 3s6s 0.156108 7hQh 0.155928 5d8d 0.153019 5s8s 0.153019 8sTs 0.151698 8dTd 0.151698 QcKh 0.151696 9sAh 0.148784 9dAh 0.148784 5c8c 0.148504 QhKc 0.146484 9cAh 0.14552 6cTc 0.144473 5cKd 0.140991 5cKs 0.140991 7cQc 0.139987 6hAd 0.136209 6hAs 0.136209 2c9h 0.135915 4sAd 0.133077 4dAs 0.133077 3s6c 0.132641 3d6c 0.132641 6sTs 0.131875 6dTd 0.131875 7cAh 0.130279 9hAc 0.128203 4cQh 0.126641 6hTh 0.125276 4c8c 0.122816 6hAc 0.122632 5sJs 0.121467 5dJd 0.121467 4d7s 0.120499 4s7d 0.120499 5cKc 0.118993 2s2d 0.11767 5dJc 0.113259 5sJc 0.113259 8cTc 0.111347 3s3d 0.108814 4cKc 0.108105 5c8h 0.106791 4dAc 0.10386 4sAc 0.10386 4cAd 0.100997 4cAs 0.100997 4cQc 0.100893 5dKc 0.099362 5sKc 0.099362 QcKs 0.098493 QcKd 0.098493 8cJc 0.095884 7h9h 0.09021 7hJh 0.088104 6c9c 0.086437 7hTh 0.082578 4dQh 0.082093 4sQh 0.082093 6d9d 0.079497 6s9s 0.079497 8d9d 0.077446 8s9s 0.077446 6h8h 0.076819 QsKc 0.076346 QdKc 0.076346 5s7s 0.073504 5d7d 0.073504 3d5d 0.07308 3s5s 0.07308 8hJh 0.071782 5dQd 0.071706 5sQs 0.071706 8hQh 0.070912 8sQs 0.070912 8dQd 0.070912 2s2c 0.070304 2d2c 0.070304 3s7s 0.068599 3d7d 0.068599 5c7c 0.067969 3s6h 0.067076 3d6h 0.067076 TsAd 0.064721 TdAs 0.064721 4c6c 0.06436 7h8h 0.062422 8hTh 0.058682 5cQd 0.058434 5cQs 0.058434 TsAc 0.056709 TdAc 0.056709 7d8d 0.056371 7s8s 0.056371 TcAs 0.055202 TcAd 0.055202 5dQc 0.054235 5sQc 0.054235 2c4c 0.052469 QdKs 0.052078 QsKd 0.052078 6h9h 0.04885 6s8s 0.048505 6d8d 0.048505 2h5s 0.044106 2h5d 0.044106 6c8c 0.043889 TcAh 0.040533 TdAh 0.039736 TsAh 0.039736 7cJc 0.039016 9dJd 0.038739 9sJs 0.038739 9cJc 0.038739 2h5c 0.038258 2d4d 0.037231 2s4s 0.037231 9sTs 0.033651 9dTd 0.033651 4d7d 0.031961 4s7s 0.031961 ThAs 0.028638 ThAd 0.028638 2s5d 0.026995 2d5s 0.026995 9hJh 0.02648 2s3s 0.026405 2d3d 0.026405 2s2h 0.026087 2h2d 0.026087 5dKd 0.025867 5sKs 0.025867 4sKh 0.024913 4dKh 0.024913 8c9c 0.022968 4c7d 0.022859 4c7s 0.022859 4d6d 0.021855 4s6s 0.021855 4c7h 0.021521 9hTh 0.021319 9cTc 0.017396 ThAc 0.016293 3s7d 0.015654 3d7s 0.015654 2h2c 0.015472 4d7h 0.014897 4s7h 0.014897 2d5c 0.014503 2s5c 0.014503 JsAd 0.010391 JdAs 0.010391 4cKh 0.010012 7cTc 0.008745 JcAs 0.00834 JcAd 0.00834 JdAc 0.008115 JsAc 0.008115 6h7h 0.006856 6s7s 0.005127 6d7d 0.005127 6c7c 0.004914 6dKd 0.003541 6hKh 0.003541 6sKs 0.003541 JhAd 0.002695 JhAs 0.002695 JhAc 0.002533 JdAh 0.001386 JsAh 0.001386 ================================================ FILE: Source/Lookahead/Tests/test_flop_nl.lua ================================================ local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local game_settings = require 'Settings.game_settings' local card_to_string = require 'Game.card_to_string_conversion' local card_tools = require 'Game.card_tools' require 'TerminalEquity.terminal_equity' require 'Lookahead.lookahead' require 'Lookahead.resolving' local current_node = {} current_node.board = card_to_string:string_to_board('3cAdKc') current_node.street = 2 current_node.current_player = constants.players.P2 current_node.bets = arguments.Tensor{600, 600} current_node.num_bets = 0 local te = TerminalEquity() te:set_board(current_node.board) local player_range = card_tools:get_file_range('Lookahead/Tests/ranges/flop-situation3-p2.txt') local opponent_range = card_tools:get_file_range('Lookahead/Tests/ranges/flop-situation3-p1.txt') local player_range_tensor = arguments.Tensor(1,player_range:size(1)) local opponent_range_tensor = arguments.Tensor(1,opponent_range:size(1)) player_range_tensor[1]:copy(player_range) opponent_range_tensor[1]:copy(opponent_range) --player_range_tensor[1]:copy(card_tools:get_uniform_range(current_node.board)) --opponent_range_tensor[1]:copy(card_tools:get_uniform_range(current_node.board)) local resolving = Resolving(te) local results = resolving:resolve_first_node(current_node, player_range_tensor, opponent_range_tensor) for card1 = 1,game_settings.card_count do for card2 = card1+1,game_settings.card_count do local idx = card_tools:get_hole_index({card1,card2}) print(idx .. ": " .. card_to_string:card_to_string(card1) .. card_to_string:card_to_string(card2)) print(" " .. results.strategy[1][1][idx] .. " " .. results.strategy[2][1][idx] .. " " .. results.strategy[3][1][idx] .. " " .. results.strategy[4][1][idx] .. " " .. results.strategy[5][1][idx] .. " " .. results.root_cfvs_both_players[1][1][idx]) end end --print(results.strategy) --print(results.achieved_cfvs) --[[ local resolving = Resolving(terminal_equity) print(results.strategy) print(results.achieved_cfvs) print(results.root_cfvs) ]] --resolving:resolve(current_node, player_range, opponent_range) --[[ local lookahead = Lookahead() local current_node = {} current_node.board = card_to_string:string_to_board('Ks') current_node.street = 2 current_node.current_player = constants.players.P1 current_node.bets = arguments.Tensor{100, 100} lookahead:build_lookahead(current_node) ]] --[[ local starting_ranges = arguments.Tensor(constants.players_count, constants.card_count) starting_ranges[1]:copy(card_tools:get_random_range(current_node.board, 2)) starting_ranges[2]:copy(card_tools:get_random_range(current_node.board, 4)) lookahead:resolve_first_node(starting_ranges) lookahead:get_strategy() ]] --[[ local player_range = card_tools:get_random_range(current_node.board, 2) local opponent_cfvs = card_tools:get_random_range(current_node.board, 4) lookahead:resolve(player_range, opponent_cfvs) lookahead:get_results() ]] ================================================ FILE: Source/Lookahead/Tests/test_preflop_nl.lua ================================================ local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local game_settings = require 'Settings.game_settings' local card_to_string = require 'Game.card_to_string_conversion' local card_tools = require 'Game.card_tools' require 'TerminalEquity.terminal_equity' require 'Lookahead.lookahead' require 'Lookahead.resolving' local current_node = {} current_node.board = card_to_string:string_to_board('') current_node.street = 1 current_node.current_player = constants.players.P1 current_node.bets = arguments.Tensor{50, 100} current_node.num_bets = 1 local te = TerminalEquity() te:set_board(current_node.board) local player_range_tensor = arguments.Tensor(1,game_settings.hand_count) local opponent_range_tensor = arguments.Tensor(1,game_settings.hand_count) player_range_tensor[1]:copy(card_tools:get_uniform_range(current_node.board)) opponent_range_tensor[1]:copy(card_tools:get_uniform_range(current_node.board)) local resolving = Resolving(te) local results = resolving:resolve_first_node(current_node, player_range_tensor, opponent_range_tensor) for card1 = 1,game_settings.card_count do for card2 = card1+1,game_settings.card_count do local idx = card_tools:get_hole_index({card1,card2}) print(idx .. ": " .. card_to_string:card_to_string(card1) .. card_to_string:card_to_string(card2)) print(" " .. results.strategy[1][1][idx] .. " " .. results.strategy[2][1][idx] .. " " .. results.strategy[3][1][idx] .. " " .. results.strategy[4][1][idx] .. " " .. results.root_cfvs_both_players[1][1][idx]) end end --print(results.strategy) --print(results.achieved_cfvs) --[[ local resolving = Resolving(terminal_equity) print(results.strategy) print(results.achieved_cfvs) print(results.root_cfvs) ]] --resolving:resolve(current_node, player_range, opponent_range) --[[ local lookahead = Lookahead() local current_node = {} current_node.board = card_to_string:string_to_board('Ks') current_node.street = 2 current_node.current_player = constants.players.P1 current_node.bets = arguments.Tensor{100, 100} lookahead:build_lookahead(current_node) ]] --[[ local starting_ranges = arguments.Tensor(constants.players_count, constants.card_count) starting_ranges[1]:copy(card_tools:get_random_range(current_node.board, 2)) starting_ranges[2]:copy(card_tools:get_random_range(current_node.board, 4)) lookahead:resolve_first_node(starting_ranges) lookahead:get_strategy() ]] --[[ local player_range = card_tools:get_random_range(current_node.board, 2) local opponent_cfvs = card_tools:get_random_range(current_node.board, 4) lookahead:resolve(player_range, opponent_cfvs) lookahead:get_results() ]] ================================================ FILE: Source/Lookahead/Tests/test_river_nl.lua ================================================ local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local game_settings = require 'Settings.game_settings' local card_to_string = require 'Game.card_to_string_conversion' local card_tools = require 'Game.card_tools' local bucketer = require 'Nn.bucketer' require 'TerminalEquity.terminal_equity' require 'Lookahead.lookahead' require 'Lookahead.resolving' require 'Nn.value_nn' require 'Nn.next_round_value' local current_node = {} current_node.board = card_to_string:string_to_board('7d7c8s5sQd') current_node.street = 4 current_node.current_player = constants.players.P2 current_node.bets = arguments.Tensor{8000, 8000} current_node.num_bets = 0 local te = TerminalEquity() te:set_board(current_node.board) local resolving = Resolving(te) local player_range = card_tools:get_file_range('Lookahead/Tests/ranges/situation-p2.txt') local opponent_range = card_tools:get_file_range('Lookahead/Tests/ranges/situation-p1.txt') local player_range_tensor = arguments.Tensor(1,player_range:size(1)) local opponent_range_tensor = arguments.Tensor(1,opponent_range:size(1)) player_range_tensor[1]:copy(player_range) opponent_range_tensor[1]:copy(opponent_range) --player_range_tensor[1]:copy(card_tools:get_uniform_range(current_node.board)) --opponent_range_tensor[1]:copy(card_tools:get_uniform_range(current_node.board)) local results = resolving:resolve_first_node(current_node, player_range_tensor, opponent_range_tensor) for card1 = 1,game_settings.card_count do for card2 = card1+1,game_settings.card_count do local idx = card_tools:get_hole_index({card1,card2}) if player_range_tensor[1][idx] > 0 then print(card_to_string:card_to_string(card1) .. card_to_string:card_to_string(card2) .. ": " .. results.root_cfvs_both_players[1][1][idx]) end end end io.read() for card1 = 1,game_settings.card_count do for card2 = card1+1,game_settings.card_count do local idx = card_tools:get_hole_index({card1,card2}) if opponent_range_tensor[1][idx] > 0 then print(card_to_string:card_to_string(card1) .. card_to_string:card_to_string(card2)) print(results.strategy[{{},1,idx}]) end end end --print(results.strategy) --print(results.achieved_cfvs) --[[ local resolving = Resolving(terminal_equity) print(results.strategy) print(results.achieved_cfvs) ]] --resolving:resolve(current_node, player_range, opponent_range) --[[ local lookahead = Lookahead() local current_node = {} current_node.board = card_to_string:string_to_board('Ks') current_node.street = 2 current_node.current_player = constants.players.P1 current_node.bets = arguments.Tensor{100, 100} lookahead:build_lookahead(current_node) ]] --[[ local starting_ranges = arguments.Tensor(constants.players_count, constants.card_count) starting_ranges[1]:copy(card_tools:get_random_range(current_node.board, 2)) starting_ranges[2]:copy(card_tools:get_random_range(current_node.board, 4)) lookahead:resolve_first_node(starting_ranges) lookahead:get_strategy() ]] --[[ local player_range = card_tools:get_random_range(current_node.board, 2) local opponent_cfvs = card_tools:get_random_range(current_node.board, 4) lookahead:resolve(player_range, opponent_cfvs) lookahead:get_results() ]] ================================================ FILE: Source/Lookahead/Tests/test_turn_nl.lua ================================================ local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local game_settings = require 'Settings.game_settings' local card_to_string = require 'Game.card_to_string_conversion' local card_tools = require 'Game.card_tools' require 'TerminalEquity.terminal_equity' require 'Lookahead.lookahead' require 'Lookahead.resolving' local current_node = {} current_node.board = card_to_string:string_to_board('3c5h4h3h') current_node.street = 3 current_node.current_player = constants.players.P2 current_node.bets = arguments.Tensor{600, 600} current_node.num_bets = 0 local te = TerminalEquity() te:set_board(current_node.board) local player_range = card_tools:get_file_range('Lookahead/Tests/ranges/situation3-p2.txt') local opponent_range = card_tools:get_file_range('Lookahead/Tests/ranges/situation3-p1.txt') local player_range_tensor = arguments.Tensor(1,player_range:size(1)) local opponent_range_tensor = arguments.Tensor(1,opponent_range:size(1)) player_range_tensor[1]:copy(player_range) opponent_range_tensor[1]:copy(opponent_range) --player_range_tensor[1]:copy(card_tools:get_uniform_range(current_node.board)) --opponent_range_tensor[1]:copy(card_tools:get_uniform_range(current_node.board)) local resolving = Resolving(te) local results = resolving:resolve_first_node(current_node, player_range_tensor, opponent_range_tensor) for card1 = 1,game_settings.card_count do for card2 = card1+1,game_settings.card_count do local idx = card_tools:get_hole_index({card1,card2}) if player_range_tensor[1][idx] > 0 then print(card_to_string:card_to_string(card1) .. card_to_string:card_to_string(card2)) print(" " .. results.strategy[1][1][idx] .. " " .. results.strategy[2][1][idx] .. " " .. results.strategy[3][1][idx] .. " " .. results.strategy[4][1][idx] .. " " .. results.strategy[4][1][idx]) end end end --print(results.strategy) --print(results.achieved_cfvs) --[[ local resolving = Resolving(terminal_equity) print(results.strategy) print(results.achieved_cfvs) print(results.root_cfvs) ]] --resolving:resolve(current_node, player_range, opponent_range) --[[ local lookahead = Lookahead() local current_node = {} current_node.board = card_to_string:string_to_board('Ks') current_node.street = 2 current_node.current_player = constants.players.P1 current_node.bets = arguments.Tensor{100, 100} lookahead:build_lookahead(current_node) ]] --[[ local starting_ranges = arguments.Tensor(constants.players_count, constants.card_count) starting_ranges[1]:copy(card_tools:get_random_range(current_node.board, 2)) starting_ranges[2]:copy(card_tools:get_random_range(current_node.board, 4)) lookahead:resolve_first_node(starting_ranges) lookahead:get_strategy() ]] --[[ local player_range = card_tools:get_random_range(current_node.board, 2) local opponent_cfvs = card_tools:get_random_range(current_node.board, 4) lookahead:resolve(player_range, opponent_cfvs) lookahead:get_results() ]] ================================================ FILE: Source/Lookahead/cfrd_gadget.lua ================================================ --- Uses the the CFR-D gadget to generate opponent ranges for re-solving. -- -- See [Solving Imperfect Information Games Using Decomposition](http://poker.cs.ualberta.ca/publications/aaai2014-cfrd.pdf) -- @classmod cfrd_gadget local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local game_settings = require 'Settings.game_settings' local tools = require 'tools' local card_tools = require 'Game.card_tools' local CFRDGadget = torch.class('CFRDGadget') --- Constructor -- @param board board card -- @param player_range an initial range vector for the opponent -- @param opponent_cfvs the opponent counterfactual values vector used for re-solving function CFRDGadget:__init(board, player_range, opponent_cfvs) assert(board) self.input_opponent_range = player_range:clone() self.input_opponent_value = opponent_cfvs:clone() self.curent_opponent_values = arguments.Tensor(game_settings.hand_count) self.regret_epsilon = 1.0/100000000 self.play_current_strategy = arguments.Tensor(game_settings.hand_count):fill(0) self.terminate_current_strategy = arguments.Tensor(game_settings.hand_count):fill(1) --holds achieved CFVs at each iteration so that we can compute regret self.total_values = arguments.Tensor(game_settings.hand_count) self.terminate_regrets = arguments.Tensor(game_settings.hand_count):fill(0) self.play_regrets = arguments.Tensor(game_settings.hand_count):fill(0) --init range mask for masking out impossible hands self.range_mask = card_tools:get_possible_hand_indexes(board) end --- Uses one iteration of the gadget game to generate an opponent range for -- the current re-solving iteration. -- @param current_opponent_cfvs the vector of cfvs that the opponent receives -- with the current strategy in the re-solve game -- @param iteration the current iteration number of re-solving -- @return the opponent range vector for this iteration function CFRDGadget:compute_opponent_range(current_opponent_cfvs, iteration) local play_values = current_opponent_cfvs local terminate_values = self.input_opponent_value --1.0 compute current regrets torch.cmul(self.total_values, play_values, self.play_current_strategy) self.total_values_p2 = self.total_values_p2 or self.total_values:clone():zero() torch.cmul(self.total_values_p2, terminate_values, self.terminate_current_strategy) self.total_values:add(self.total_values_p2) self.play_current_regret = self.play_current_regret or play_values:clone():zero() self.terminate_current_regret = self.terminate_current_regret or self.play_current_regret:clone():zero() self.play_current_regret:copy(play_values) self.play_current_regret:csub(self.total_values) self.terminate_current_regret:copy(terminate_values) self.terminate_current_regret:csub(self.total_values) --1.1 cumulate regrets self.play_regrets:add(self.play_current_regret) self.terminate_regrets:add(self.terminate_current_regret) --2.0 we use cfr+ in reconstruction self.terminate_regrets:clamp(self.regret_epsilon, tools:max_number()) self.play_regrets:clamp(self.regret_epsilon, tools:max_number()) self.play_possitive_regrets = self.play_regrets self.terminate_possitive_regrets = self.terminate_regrets --3.0 regret matching self.regret_sum = self.regret_sum or self.play_possitive_regrets:clone():zero() self.regret_sum:copy(self.play_possitive_regrets) self.regret_sum:add(self.terminate_possitive_regrets) self.play_current_strategy:copy(self.play_possitive_regrets) self.terminate_current_strategy:copy(self.terminate_possitive_regrets) self.play_current_strategy:cdiv(self.regret_sum) self.terminate_current_strategy:cdiv(self.regret_sum) --4.0 for poker, the range size is larger than the allowed hands --we need to make sure reconstruction does not choose a range --that is not allowed self.play_current_strategy:cmul(self.range_mask) self.terminate_current_strategy:cmul(self.range_mask) self.input_opponent_range = self.input_opponent_range or self.play_current_strategy:clone():zero() self.input_opponent_range:copy(self.play_current_strategy) return self.input_opponent_range end ================================================ FILE: Source/Lookahead/lookahead.lua ================================================ --- A depth-limited lookahead of the game tree used for re-solving. -- @classmod lookahead require 'Lookahead.lookahead_builder' require 'TerminalEquity.terminal_equity' require 'Lookahead.cfrd_gadget' local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local game_settings = require 'Settings.game_settings' local tools = require 'tools' local card_tools = require 'Game.card_tools' local card_to_string = require 'Game.card_to_string_conversion' local Lookahead = torch.class('Lookahead') local timings = {} --- Constructor function Lookahead:__init(terminal_equity, batch_size) self.builder = LookaheadBuilder(self) self.terminal_equity = terminal_equity self.batch_size = batch_size end --- Constructs the lookahead from a game's public tree. -- -- Must be called to initialize the lookahead. -- @param tree a public tree function Lookahead:build_lookahead(tree) self.builder:build_from_tree(tree) end function Lookahead:reset() self.builder:reset() end --- Re-solves the lookahead using input ranges. -- -- Uses the input range for the opponent instead of a gadget range, so only -- appropriate for re-solving the root node of the game tree (where ranges -- are fixed). -- -- @{build_lookahead} must be called first. -- -- @param player_range a range vector for the re-solving player -- @param opponent_range a range vector for the opponent function Lookahead:resolve_first_node(player_range, opponent_range) self.ranges_data[1][{{}, {}, {}, {}, 1, {}}]:copy(player_range) self.ranges_data[1][{{}, {}, {}, {}, 2, {}}]:copy(opponent_range) self:_compute() end --- Re-solves the lookahead using an input range for the player and -- the @{cfrd_gadget|CFRDGadget} to generate ranges for the opponent. -- -- @{build_lookahead} must be called first. -- -- @param player_range a range vector for the re-solving player -- @param opponent_cfvs a vector of cfvs achieved by the opponent -- before re-solving function Lookahead:resolve(player_range, opponent_cfvs) assert(player_range) assert(opponent_cfvs) self.reconstruction_gadget = CFRDGadget(self.tree.board, player_range, opponent_cfvs) self.ranges_data[1][{{}, {}, {}, {}, 1, {}}]:copy(player_range) self.reconstruction_opponent_cfvs = opponent_cfvs self:_compute() end --- Re-solves the lookahead. -- @local function Lookahead:_compute() --1.0 main loop for i=1,8 do timings[i] = 0 end for iter=1,arguments.cfr_iters do local timer = torch.Timer() timer:reset() self:_set_opponent_starting_range(iter) timings[1] = timings[1] + timer:time().real timer:reset() self:_compute_current_strategies() timings[2] = timings[2] + timer:time().real timer:reset() self:_compute_ranges() timings[3] = timings[3] + timer:time().real timer:reset() self:_compute_update_average_strategies(iter) timings[4] = timings[4] + timer:time().real timer:reset() self:_compute_terminal_equities() timings[5] = timings[5] + timer:time().real timer:reset() self:_compute_cfvs() timings[6] = timings[6] + timer:time().real timer:reset() self:_compute_regrets() timings[7] = timings[7] + timer:time().real timer:reset() self:_compute_cumulate_average_cfvs(iter) timings[8] = timings[8] + timer:time().real timer:reset() end for i=1,8 do print(' ' .. i .. ': ' .. timings[i]) end --2.0 at the end normalize average strategy self:_compute_normalize_average_strategies() --2.1 normalize root's CFVs self:_compute_normalize_average_cfvs() end --- Uses regret matching to generate the players' current strategies. -- @local function Lookahead:_compute_current_strategies() for d=2,self.depth do self.positive_regrets_data[d]:copy(self.regrets_data[d]) self.positive_regrets_data[d]:clamp(self.regret_epsilon, tools:max_number()) --1.0 set regret of empty actions to 0 self.positive_regrets_data[d]:cmul(self.empty_action_mask[d]) --1.1 regret matching --note that the regrets as well as the CFVs have switched player indexing torch.sum(self.regrets_sum[d], self.positive_regrets_data[d], 1) local player_current_strategy = self.current_strategy_data[d] local player_regrets = self.positive_regrets_data[d] local player_regrets_sum = self.regrets_sum[d] player_current_strategy:cdiv(player_regrets, player_regrets_sum:expandAs(player_regrets)) end end --- Using the players' current strategies, computes their probabilities of -- reaching each state of the lookahead. -- @local function Lookahead:_compute_ranges() for d=1,self.depth-1 do local current_level_ranges = self.ranges_data[d] local next_level_ranges = self.ranges_data[d+1] local prev_layer_terminal_actions_count = self.terminal_actions_count[d-1] local prev_layer_actions_count = self.actions_count[d-1] local prev_layer_bets_count = self.bets_count[d-1] local gp_layer_nonallin_bets_count = self.nonallinbets_count[d-2] local gp_layer_terminal_actions_count = self.terminal_actions_count[d-2] --copy the ranges of inner nodes and transpose self.inner_nodes[d]:copy(current_level_ranges[{{prev_layer_terminal_actions_count+1, -1}, {1, gp_layer_nonallin_bets_count}, {}, {}, {}, {}}]:transpose(2,3)) local super_view = self.inner_nodes[d] super_view = super_view:view(1, prev_layer_bets_count, -1, self.batch_size, constants.players_count, game_settings.hand_count) super_view = super_view:expandAs(next_level_ranges) local next_level_strategies = self.current_strategy_data[d+1] next_level_ranges:copy(super_view) --multiply the ranges of the acting player by his strategy next_level_ranges[{{}, {}, {}, {}, self.acting_player[d], {}}]:cmul(next_level_strategies) end end --- Updates the players' average strategies with their current strategies. -- @param iter the current iteration number of re-solving -- @local function Lookahead:_compute_update_average_strategies(iter) if iter > arguments.cfr_skip_iters then --no need to go through layers since we care for the average strategy only in the first node anyway --note that if you wanted to average strategy on lower layers, you would need to weight the current strategy by the current reach probability self.average_strategies_data[2]:add(self.current_strategy_data[2]) end end --- Using the players' reach probabilities, computes their counterfactual -- values at each lookahead state which is a terminal state of the game. -- @local function Lookahead:_compute_terminal_equities_terminal_equity() -- copy in range data for d=2,self.depth do if d > 2 or self.first_call_terminal then if self.tree.street ~= constants.streets_count then self.ranges_data_call[{self.term_call_indices[d]}]:copy(self.ranges_data[d][2][-1]) else self.ranges_data_call[{self.term_call_indices[d]}]:copy(self.ranges_data[d][2]) end end self.ranges_data_fold[{self.term_fold_indices[d]}]:copy(self.ranges_data[d][1]) end self.terminal_equity:call_value(self.ranges_data_call:view(-1, game_settings.hand_count), self.cfvs_data_call:view(-1, game_settings.hand_count)) self.terminal_equity:fold_value(self.ranges_data_fold:view(-1, game_settings.hand_count), self.cfvs_data_fold:view(-1, game_settings.hand_count)) for d=2,self.depth do if self.tree.street ~= constants.streets_count then if game_settings.nl and (d>2 or self.first_call_terminal) then self.cfvs_data[d][2][-1]:copy(self.cfvs_data_call[{self.term_call_indices[d]}]) end else if d>2 or self.first_call_terminal then self.cfvs_data[d][2]:copy(self.cfvs_data_call[{self.term_call_indices[d]}]) end end self.cfvs_data[d][1]:copy(self.cfvs_data_fold[{self.term_fold_indices[d]}]) --correctly set the folded player by mutliplying by -1 local fold_mutliplier = (self.acting_player[d]*2 - 3) self.cfvs_data[d][{1, {}, {}, {}, 1, {}}]:mul(fold_mutliplier) self.cfvs_data[d][{1, {}, {}, {}, 2, {}}]:mul(-fold_mutliplier) end end --- Using the players' reach probabilities, calls the neural net to compute the -- players' counterfactual values at the depth-limited states of the lookahead. -- @local function Lookahead:_compute_terminal_equities_next_street_box() assert(self.tree.street ~= constants.streets_count) if self.num_pot_sizes == 0 then return end for d=2, self.depth do if d > 2 or self.first_call_transition then -- if there's only 1 parent, then it should've been an all in, so skip this next_street_box calculation if self.ranges_data[d][2]:size(1) > 1 or (d == 2 and self.first_call_transition) or not game_settings.nl then local parent_indices = {1, -2} if d == 2 then parent_indices = {1,1} elseif not game_settings.nl then parent_indices = {} end self.next_street_boxes_outputs[{self.indices[d], {}, {}, {}}]:copy(self.ranges_data[d][{2, parent_indices, {}, {}, {}, {}}]) end end end if self.tree.current_player == 2 then self.next_street_boxes_inputs:copy(self.next_street_boxes_outputs) else self.next_street_boxes_inputs[{{}, {}, 1, {}}]:copy(self.next_street_boxes_outputs[{{}, {}, 2, {}}]) self.next_street_boxes_inputs[{{}, {}, 2, {}}]:copy(self.next_street_boxes_outputs[{{}, {}, 1, {}}]) end if self.tree.street == 1 then self.next_street_boxes:get_value_aux( self.next_street_boxes_inputs:view(-1, constants.players_count, game_settings.hand_count), self.next_street_boxes_outputs:view(-1, constants.players_count, game_settings.hand_count), self.next_board_idx) else self.next_street_boxes:get_value( self.next_street_boxes_inputs:view(-1, constants.players_count, game_settings.hand_count), self.next_street_boxes_outputs:view(-1, constants.players_count, game_settings.hand_count)) end --now the neural net outputs for P1 and P2 respectively, so we need to swap the output values if necessary if self.tree.current_player == 2 then self.next_street_boxes_inputs:copy(self.next_street_boxes_outputs) self.next_street_boxes_outputs[{{}, {}, 1, {}}]:copy(self.next_street_boxes_inputs[{{}, {}, 2, {}}]) self.next_street_boxes_outputs[{{}, {}, 2, {}}]:copy(self.next_street_boxes_inputs[{{}, {}, 1, {}}]) end for d=2, self.depth do if d > 2 or self.first_call_transition then if self.ranges_data[d][2]:size(1) > 1 or (d == 2 and self.first_call_transition) or not game_settings.nl then local parent_indices = {1, -2} if d == 2 then parent_indices = {1,1} elseif not game_settings.nl then parent_indices = {} end self.cfvs_data[d][{2, parent_indices, {}, {}, {}, {}}]:copy(self.next_street_boxes_outputs[{self.indices[d], {}, {}, {}}]) end end end end --- Gives the average counterfactual values for the opponent during re-solving -- after a chance event (the betting round changes and more cards are dealt). -- -- Used during continual re-solving to track opponent cfvs. The lookahead must -- first be re-solved with @{resolve} or @{resolve_first_node}. -- -- @param action_index the action taken by the re-solving player at the start -- of the lookahead -- @param board a tensor of board cards, updated by the chance event -- @return a vector of cfvs function Lookahead:get_chance_action_cfv(action, board) local box_outputs = self.next_street_boxes_outputs:view(-1, constants.players_count, game_settings.hand_count) local next_street_box = self.next_street_boxes local batch_index = self.action_to_index[action] assert(batch_index ~= nil) local pot_mult = self.next_round_pot_sizes[batch_index] if box_outputs == nil then assert(false) end next_street_box:get_value_on_board(board, box_outputs) local out = box_outputs[batch_index][self.tree.current_player] out:mul(pot_mult) return out end --- Using the players' reach probabilities, computes their counterfactual -- values at all terminal states of the lookahead. -- -- These include terminal states of the game and depth-limited states. -- @local function Lookahead:_compute_terminal_equities() if self.tree.street ~= constants.streets_count then self:_compute_terminal_equities_next_street_box() end self:_compute_terminal_equities_terminal_equity() --multiply by pot scale factor for d=2,self.depth do self.cfvs_data[d]:cmul(self.pot_size[d]) end end --- Using the players' reach probabilities and terminal counterfactual -- values, computes their cfvs at all states of the lookahead. -- @local function Lookahead:_compute_cfvs() for d=self.depth,2,-1 do local gp_layer_terminal_actions_count = self.terminal_actions_count[d-2] local ggp_layer_nonallin_bets_count = self.nonallinbets_count[d-3] self.cfvs_data[d][{{}, {}, {}, {}, {1}, {}}]:cmul(self.empty_action_mask[d]) self.cfvs_data[d][{{}, {}, {}, {}, {2}, {}}]:cmul(self.empty_action_mask[d]) self.placeholder_data[d]:copy(self.cfvs_data[d]) --player indexing is swapped for cfvs self.placeholder_data[d][{{}, {}, {}, {}, self.acting_player[d], {}}]:cmul(self.current_strategy_data[d]) torch.sum(self.regrets_sum[d], self.placeholder_data[d], 1) --use a swap placeholder to change {{1,2,3}, {4,5,6}} into {{1,2}, {3,4}, {5,6}} local swap = self.swap_data[d-1] swap:copy(self.regrets_sum[d]) self.cfvs_data[d-1][{{gp_layer_terminal_actions_count+1, -1}, {1, ggp_layer_nonallin_bets_count}, {}, {}, {}, {}}]:copy(swap:transpose(2,3)) end end --- Updates the players' average counterfactual values with their cfvs from the -- current iteration. -- @param iter the current iteration number of re-solving -- @local function Lookahead:_compute_cumulate_average_cfvs(iter) if iter > arguments.cfr_skip_iters then self.average_cfvs_data[1]:add(self.cfvs_data[1]) self.average_cfvs_data[2]:add(self.cfvs_data[2]) end end --- Normalizes the players' average strategies. -- -- Used at the end of re-solving so that we can track un-normalized average -- strategies, which are simpler to compute. -- @local function Lookahead:_compute_normalize_average_strategies() --using regrets_sum as a placeholder container local player_avg_strategy = self.average_strategies_data[2] local player_avg_strategy_sum = self.regrets_sum[2] torch.sum(player_avg_strategy_sum, player_avg_strategy, 1) player_avg_strategy:cdiv(player_avg_strategy_sum:expandAs(player_avg_strategy)) --if the strategy is 'empty' (zero reach), strategy does not matter but we need to make sure --it sums to one -> now we set to always fold player_avg_strategy[1][player_avg_strategy[1]:ne(player_avg_strategy[1])] = 1 player_avg_strategy[player_avg_strategy:ne(player_avg_strategy)] = 0 end --- Normalizes the players' average counterfactual values. -- -- Used at the end of re-solving so that we can track un-normalized average -- cfvs, which are simpler to compute. -- @local function Lookahead:_compute_normalize_average_cfvs() self.average_cfvs_data[1]:div(arguments.cfr_iters - arguments.cfr_skip_iters) end --- Using the players' counterfactual values, updates their total regrets -- for every state in the lookahead. -- @local function Lookahead:_compute_regrets() for d=self.depth,2,-1 do local gp_layer_terminal_actions_count = self.terminal_actions_count[d-2] local gp_layer_bets_count = self.bets_count[d-2] local ggp_layer_nonallin_bets_count = self.nonallinbets_count[d-3] local current_regrets = self.current_regrets_data[d] current_regrets:copy(self.cfvs_data[d][{{}, {}, {}, {}, self.acting_player[d], {}}]) local next_level_cfvs = self.cfvs_data[d-1] local parent_inner_nodes = self.inner_nodes_p1[d-1] parent_inner_nodes:copy(next_level_cfvs[{{gp_layer_terminal_actions_count+1, -1}, {1, ggp_layer_nonallin_bets_count}, {}, {}, self.acting_player[d], {}}]:transpose(2,3)) parent_inner_nodes = parent_inner_nodes:view(1, gp_layer_bets_count, -1, self.batch_size, game_settings.hand_count) parent_inner_nodes = parent_inner_nodes:expandAs(current_regrets) current_regrets:csub(parent_inner_nodes) self.regrets_data[d]:add(self.regrets_data[d], current_regrets) --(CFR+) self.regrets_data[d]:clamp(0, tools:max_number()) end end --- Gets the results of re-solving the lookahead. -- -- The lookahead must first be re-solved with @{resolve} or -- @{resolve_first_node}. -- -- @return a table containing the fields: -- -- * `strategy`: an AxK tensor containing the re-solve player's strategy at the -- root of the lookahead, where A is the number of actions and K is the range size -- -- * `achieved_cfvs`: a vector of the opponent's average counterfactual values at the -- root of the lookahead -- -- * `children_cfvs`: an AxK tensor of opponent average counterfactual values after -- each action that the re-solve player can take at the root of the lookahead function Lookahead:get_results() local out = {} local actions_count = self.average_strategies_data[2]:size(1) --1.0 average strategy --[actions x range] --lookahead already computes the averate strategy we just convert the dimensions out.strategy = self.average_strategies_data[2]:view(-1, self.batch_size, game_settings.hand_count):clone() --2.0 achieved opponent's CFVs at the starting node out.achieved_cfvs = self.average_cfvs_data[1]:view(self.batch_size, constants.players_count, game_settings.hand_count)[{{},1,{}}]:clone() --3.0 CFVs for the acting player only when resolving first node if self.reconstruction_opponent_cfvs then out.root_cfvs = nil else out.root_cfvs = self.average_cfvs_data[1]:view(self.batch_size, constants.players_count, game_settings.hand_count)[{{},2,{}}]:clone() --swap cfvs indexing out.root_cfvs_both_players = self.average_cfvs_data[1]:view(self.batch_size, constants.players_count, game_settings.hand_count):clone() out.root_cfvs_both_players[{{},2,{}}]:copy(self.average_cfvs_data[1]:view(self.batch_size, constants.players_count, game_settings.hand_count)[{{},1,{}}]) out.root_cfvs_both_players[{{},1,{}}]:copy(self.average_cfvs_data[1]:view(self.batch_size, constants.players_count, game_settings.hand_count)[{{},2,{}}]) end --4.0 children CFVs --[actions x range] out.children_cfvs = self.average_cfvs_data[2][{{}, {}, {}, {}, 1, {}}]:clone():view(-1, game_settings.hand_count) --IMPORTANT divide average CFVs by average strategy in here local scaler = self.average_strategies_data[2]:view(-1, self.batch_size, game_settings.hand_count):clone() local range_mul = self.ranges_data[1][{{}, {}, {}, {}, 1, {}}]:clone():view(1, self.batch_size, game_settings.hand_count):clone() range_mul = range_mul:expandAs(scaler) scaler = scaler:cmul(range_mul) scaler = scaler:sum(3):expandAs(range_mul):clone() scaler = scaler:mul(arguments.cfr_iters - arguments.cfr_skip_iters) out.children_cfvs:cdiv(scaler) assert(out.children_cfvs) assert(out.strategy) assert(out.achieved_cfvs) return out end --- Generates the opponent's range for the current re-solve iteration using -- the @{cfrd_gadget|CFRDGadget}. -- @param iteration the current iteration number of re-solving -- @local function Lookahead:_set_opponent_starting_range(iteration) if self.reconstruction_opponent_cfvs then --note that CFVs indexing is swapped, thus the CFVs for the reconstruction player are for player '1' local opponent_range = self.reconstruction_gadget:compute_opponent_range(self.cfvs_data[1][{{}, {}, {}, {}, 1, {}}], iteration) self.ranges_data[1][{{}, {}, {}, {}, 2, {}}]:copy(opponent_range) end end ================================================ FILE: Source/Lookahead/lookahead_builder.lua ================================================ --- Builds the internal data structures of a @{lookahead|Lookahead} object. -- @classmod lookahead_builder local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local game_settings = require 'Settings.game_settings' local tools = require 'tools' require 'Tree.tree_builder' require 'Tree.tree_visualiser' require 'Nn.next_round_value' require 'Nn.next_round_value_pre' require 'Nn.value_nn' local LookaheadBuilder = torch.class('LookaheadBuilder') --used to load NNs and next_round_value_pre only once local neural_net = {} local aux_net = nil local next_round_pre = nil --- Constructor -- @param lookahead the @{lookahead|Lookahead} to generate data structures for function LookaheadBuilder:__init(lookahead) self.lookahead = lookahead self.lookahead.ccall_action_index = 1 self.lookahead.fold_action_index = 2 end --- Builds the neural net query boxes which estimate counterfactual values -- at depth-limited states of the lookahead. -- @local function LookaheadBuilder:_construct_transition_boxes() if self.lookahead.tree.street == constants.streets_count then return end --load neural nets if not already loaded local nn = neural_net[self.lookahead.tree.street] or ValueNn(self.lookahead.tree.street) neural_net[self.lookahead.tree.street] = nn if self.lookahead.tree.street == 1 and game_settings.nl then aux_net = aux_net or ValueNn(self.lookahead.tree.street, true) end self.lookahead.next_street_boxes = nil self.lookahead.indices = {} self.lookahead.num_pot_sizes = 0 if self.lookahead.tree.street == 1 then self.lookahead.next_street_boxes = next_round_pre or NextRoundValuePre(nn, aux_net, self.lookahead.terminal_equity.board) next_round_pre = self.lookahead.next_street_boxes else self.lookahead.next_street_boxes = NextRoundValue(nn, self.lookahead.terminal_equity.board) end --create the optimized data structures for batching next_round_value for d = 2,self.lookahead.depth do if d == 2 and self.lookahead.first_call_transition then local before = self.lookahead.num_pot_sizes self.lookahead.num_pot_sizes = self.lookahead.num_pot_sizes + 1 self.lookahead.indices[d] = {before + 1, self.lookahead.num_pot_sizes} elseif not game_settings.nl and (d > 2 or self.lookahead.first_call_transition) then local before = self.lookahead.num_pot_sizes self.lookahead.num_pot_sizes = self.lookahead.num_pot_sizes + (self.lookahead.pot_size[d][2]:size(1)) * self.lookahead.pot_size[d][2]:size(2) self.lookahead.indices[d] = {before + 1, self.lookahead.num_pot_sizes} elseif self.lookahead.pot_size[d][2]:size(1) > 1 then local before = self.lookahead.num_pot_sizes self.lookahead.num_pot_sizes = self.lookahead.num_pot_sizes + (self.lookahead.pot_size[d][2]:size(1) - 1) * self.lookahead.pot_size[d][2]:size(2) self.lookahead.indices[d] = {before + 1, self.lookahead.num_pot_sizes} end end if self.lookahead.num_pot_sizes == 0 then return end self.lookahead.next_round_pot_sizes = arguments.Tensor(self.lookahead.num_pot_sizes):zero() self.lookahead.action_to_index = {} for d = 2,self.lookahead.depth do local parent_indices = {1, -2} if self.lookahead.indices[d] ~= nil then if d == 2 then parent_indices = {1, 1} elseif not game_settings.nl then parent_indices = {} end self.lookahead.next_round_pot_sizes[{self.lookahead.indices[d]}]:copy(self.lookahead.pot_size[d][{2, parent_indices, {}, 1, 1, 1}]) if d <= 3 then if d == 2 then assert(self.lookahead.indices[d][1] == self.lookahead.indices[d][2]) self.lookahead.action_to_index[constants.actions.ccall] = self.lookahead.indices[d][1] else assert(self.lookahead.pot_size[d][{2, parent_indices}]:size(2) == 1, 'bad num_indices: ') for parent_action_idx = 1, self.lookahead.pot_size[d][2]:size(1) do local action_id = self.lookahead.parent_action_id[parent_action_idx] assert(self.lookahead.action_to_index[action_id] == nil) self.lookahead.action_to_index[action_id] = self.lookahead.indices[d][1] + parent_action_idx - 1 end end end end end if self.lookahead.action_to_index[constants.actions.ccall] == nil then print(self.lookahead.action_to_index) print(self.lookahead.parent_action_id) assert(false) end self.lookahead.next_street_boxes:start_computation(self.lookahead.next_round_pot_sizes, self.lookahead.batch_size) self.lookahead.next_street_boxes_inputs = arguments.Tensor(self.lookahead.num_pot_sizes, self.lookahead.batch_size, constants.players_count, game_settings.hand_count):zero() self.lookahead.next_street_boxes_outputs = self.lookahead.next_street_boxes_inputs:clone() end --- Computes the number of nodes at each depth of the tree. -- -- Used to find the size for the tensors which store lookahead data. -- @local function LookaheadBuilder:_compute_structure() assert(self.lookahead.tree.street >= 1 and self.lookahead.tree.street <= constants.streets_count) self.lookahead.regret_epsilon = 1.0 / 1000000000 --which player acts at particular depth self.lookahead.acting_player = torch.Tensor(self.lookahead.depth+1):fill(-1) self.lookahead.acting_player[1] = 1 --in lookahead, 1 does not stand for player IDs, it's just the first player to act for d=2,self.lookahead.depth+1 do self.lookahead.acting_player[d] = 3 - self.lookahead.acting_player[d-1] end self.lookahead.bets_count[-1] = 1 self.lookahead.bets_count[0] = 1 self.lookahead.nonallinbets_count[-1] = 1 self.lookahead.nonallinbets_count[0] = 1 self.lookahead.terminal_actions_count[-1] = 0 self.lookahead.terminal_actions_count[0] = 0 self.lookahead.actions_count[-1] = 1 self.lookahead.actions_count[0] = 1 --compute the node counts self.lookahead.nonterminal_nodes_count = {} self.lookahead.nonterminal_nonallin_nodes_count = {} self.lookahead.all_nodes_count = {} self.lookahead.allin_nodes_count = {} self.lookahead.inner_nodes_count = {} self.lookahead.nonterminal_nodes_count[1] = 1 self.lookahead.nonterminal_nodes_count[2] = self.lookahead.bets_count[1] self.lookahead.nonterminal_nonallin_nodes_count[0] = 1 self.lookahead.nonterminal_nonallin_nodes_count[1] = 1 self.lookahead.nonterminal_nonallin_nodes_count[2] = self.lookahead.nonterminal_nodes_count[2] if game_settings.nl then self.lookahead.nonterminal_nonallin_nodes_count[2] = self.lookahead.nonterminal_nonallin_nodes_count[2] - 1 end self.lookahead.all_nodes_count[1] = 1 self.lookahead.all_nodes_count[2] = self.lookahead.actions_count[1] self.lookahead.allin_nodes_count[1] = 0 self.lookahead.allin_nodes_count[2] = 1 self.lookahead.inner_nodes_count[1] = 1 self.lookahead.inner_nodes_count[2] = 1 for d=2,self.lookahead.depth do self.lookahead.all_nodes_count[d+1] = self.lookahead.nonterminal_nonallin_nodes_count[d-1] * self.lookahead.bets_count[d-1] * self.lookahead.actions_count[d] self.lookahead.allin_nodes_count[d+1] = self.lookahead.nonterminal_nonallin_nodes_count[d-1] * self.lookahead.bets_count[d-1] * 1 self.lookahead.nonterminal_nodes_count[d+1] = self.lookahead.nonterminal_nonallin_nodes_count[d-1] * self.lookahead.nonallinbets_count[d-1] * self.lookahead.bets_count[d] self.lookahead.nonterminal_nonallin_nodes_count[d+1] = self.lookahead.nonterminal_nonallin_nodes_count[d-1] * self.lookahead.nonallinbets_count[d-1] * self.lookahead.nonallinbets_count[d] end end --- Builds the tensors that store lookahead data during re-solving. function LookaheadBuilder:construct_data_structures() self:_compute_structure() --lookahead main data structures --all the structures are per-layer tensors, that is, each layer holds the data in n-dimensional tensors self.lookahead.pot_size = {} self.lookahead.ranges_data = {} self.lookahead.average_strategies_data = {} self.lookahead.current_strategy_data = {} self.lookahead.cfvs_data = {} self.lookahead.average_cfvs_data = {} self.lookahead.regrets_data = {} self.lookahead.current_regrets_data = {} self.lookahead.positive_regrets_data = {} self.lookahead.placeholder_data = {} self.lookahead.regrets_sum = {} self.lookahead.empty_action_mask = {} --used to mask empty actions --used to hold and swap inner (nonterminal) nodes when doing some transpose operations self.lookahead.inner_nodes = {} self.lookahead.inner_nodes_p1 = {} self.lookahead.swap_data = {} --create the data structure for the first two layers --data structures [actions x parent_action x grandparent_id x batch x players x range] self.lookahead.ranges_data[1] = arguments.Tensor(1, 1, 1, self.lookahead.batch_size, constants.players_count, game_settings.hand_count):fill(1.0 / game_settings.hand_count) self.lookahead.ranges_data[2] = arguments.Tensor(self.lookahead.actions_count[1], 1, 1, self.lookahead.batch_size, constants.players_count, game_settings.hand_count):fill(1.0 / game_settings.hand_count) self.lookahead.pot_size[1] = self.lookahead.ranges_data[1]:clone():fill(0) self.lookahead.pot_size[2] = self.lookahead.ranges_data[2]:clone():fill(0) self.lookahead.cfvs_data[1] = self.lookahead.ranges_data[1]:clone():fill(0) self.lookahead.cfvs_data[2] = self.lookahead.ranges_data[2]:clone():fill(0) self.lookahead.average_cfvs_data[1] = self.lookahead.ranges_data[1]:clone():fill(0) self.lookahead.average_cfvs_data[2] = self.lookahead.ranges_data[2]:clone():fill(0) self.lookahead.placeholder_data[1] = self.lookahead.ranges_data[1]:clone():fill(0) self.lookahead.placeholder_data[2] = self.lookahead.ranges_data[2]:clone():fill(0) --data structures for one player [actions x parent_action x grandparent_id x batch x 1 x range] self.lookahead.average_strategies_data[1] = nil self.lookahead.average_strategies_data[2] = arguments.Tensor(self.lookahead.actions_count[1], 1, 1, self.lookahead.batch_size, game_settings.hand_count):fill(0) self.lookahead.current_strategy_data[1] = nil self.lookahead.current_strategy_data[2] = self.lookahead.average_strategies_data[2]:clone():fill(0) self.lookahead.regrets_data[1] = nil self.lookahead.regrets_data[2] = self.lookahead.average_strategies_data[2]:clone():fill(0) self.lookahead.current_regrets_data[1] = nil self.lookahead.current_regrets_data[2] = self.lookahead.average_strategies_data[2]:clone():fill(0) self.lookahead.positive_regrets_data[1] = nil self.lookahead.positive_regrets_data[2] = self.lookahead.average_strategies_data[2]:clone():fill(0) self.lookahead.empty_action_mask[1] = nil self.lookahead.empty_action_mask[2] = self.lookahead.average_strategies_data[2]:clone():fill(1) --data structures for summing over the actions [1 x parent_action x grandparent_id x batch x range] self.lookahead.regrets_sum[1] = arguments.Tensor(1, 1, 1, self.lookahead.batch_size, game_settings.hand_count):fill(0) self.lookahead.regrets_sum[2] = arguments.Tensor(1, self.lookahead.bets_count[1], 1, self.lookahead.batch_size, game_settings.hand_count):fill(0) --data structures for inner nodes (not terminal nor allin) [bets_count x parent_nonallinbetscount x gp_id x batch x players x range] self.lookahead.inner_nodes[1] = arguments.Tensor(1, 1, 1, self.lookahead.batch_size, constants.players_count, game_settings.hand_count):fill(0) self.lookahead.swap_data[1] = self.lookahead.inner_nodes[1]:transpose(2,3):clone() self.lookahead.inner_nodes_p1[1] = arguments.Tensor(1, 1, 1, self.lookahead.batch_size, 1, game_settings.hand_count):fill(0) if self.lookahead.depth > 2 then self.lookahead.inner_nodes[2] = arguments.Tensor(self.lookahead.bets_count[1], 1, 1, self.lookahead.batch_size, constants.players_count, game_settings.hand_count):fill(0) self.lookahead.swap_data[2] = self.lookahead.inner_nodes[2]:transpose(2,3):clone() self.lookahead.inner_nodes_p1[2] = arguments.Tensor(self.lookahead.bets_count[1], 1, 1, self.lookahead.batch_size, 1, game_settings.hand_count):fill(0) end --create the data structures for the rest of the layers for d=3,self.lookahead.depth do --data structures [actions x parent_action x grandparent_id x batch x players x range] self.lookahead.ranges_data[d] = arguments.Tensor(self.lookahead.actions_count[d-1], self.lookahead.bets_count[d-2], self.lookahead.nonterminal_nonallin_nodes_count[d-2], self.lookahead.batch_size, constants.players_count, game_settings.hand_count):fill(0) self.lookahead.cfvs_data[d] = self.lookahead.ranges_data[d]:clone() self.lookahead.placeholder_data[d] = self.lookahead.ranges_data[d]:clone() self.lookahead.pot_size[d] = self.lookahead.ranges_data[d]:clone():fill(arguments.stack) --data structures [actions x parent_action x grandparent_id x batch x 1 x range] self.lookahead.average_strategies_data[d] = arguments.Tensor(self.lookahead.actions_count[d-1], self.lookahead.bets_count[d-2], self.lookahead.nonterminal_nonallin_nodes_count[d-2], self.lookahead.batch_size, game_settings.hand_count):fill(0) self.lookahead.current_strategy_data[d] = self.lookahead.average_strategies_data[d]:clone() self.lookahead.regrets_data[d] = self.lookahead.average_strategies_data[d]:clone():fill(self.lookahead.regret_epsilon) self.lookahead.current_regrets_data[d] = self.lookahead.average_strategies_data[d]:clone():fill(0) self.lookahead.empty_action_mask[d] = self.lookahead.average_strategies_data[d]:clone():fill(1) self.lookahead.positive_regrets_data[d] = self.lookahead.regrets_data[d]:clone() --data structures [1 x parent_action x grandparent_id x batch x players x range] self.lookahead.regrets_sum[d] = arguments.Tensor(1, self.lookahead.bets_count[d-2], self.lookahead.nonterminal_nonallin_nodes_count[d-2], self.lookahead.batch_size, constants.players_count, game_settings.hand_count):fill(0) --data structures for the layers except the last one if d < self.lookahead.depth then self.lookahead.inner_nodes[d] = arguments.Tensor(self.lookahead.bets_count[d-1], self.lookahead.nonallinbets_count[d-2], self.lookahead.nonterminal_nonallin_nodes_count[d-2], self.lookahead.batch_size, constants.players_count, game_settings.hand_count):fill(0) self.lookahead.inner_nodes_p1[d] = arguments.Tensor(self.lookahead.bets_count[d-1], self.lookahead.nonallinbets_count[d-2], self.lookahead.nonterminal_nonallin_nodes_count[d-2], self.lookahead.batch_size, 1, game_settings.hand_count):fill(0) self.lookahead.swap_data[d] = self.lookahead.inner_nodes[d]:transpose(2, 3):clone() end end --create the optimized data structures for terminal equity self.lookahead.term_call_indices = {} self.lookahead.num_term_call_nodes = 0 self.lookahead.term_fold_indices = {} self.lookahead.num_term_fold_nodes = 0 -- calculate term_call_indices for d = 2,self.lookahead.depth do if self.lookahead.tree.street ~= constants.streets_count then if game_settings.nl and (d>2 or self.lookahead.first_call_terminal) then local before = self.lookahead.num_term_call_nodes self.lookahead.num_term_call_nodes = self.lookahead.num_term_call_nodes + self.lookahead.ranges_data[d][2][-1]:size(1) self.lookahead.term_call_indices[d] = {before + 1, self.lookahead.num_term_call_nodes} end else if d>2 or self.lookahead.first_call_terminal then local before = self.lookahead.num_term_call_nodes self.lookahead.num_term_call_nodes = self.lookahead.num_term_call_nodes + self.lookahead.ranges_data[d][2]:size(1) * self.lookahead.ranges_data[d][2]:size(2) self.lookahead.term_call_indices[d] = {before + 1, self.lookahead.num_term_call_nodes} end end end -- calculate term_fold_indices for d = 2,self.lookahead.depth do local before = self.lookahead.num_term_fold_nodes self.lookahead.num_term_fold_nodes = self.lookahead.num_term_fold_nodes + self.lookahead.ranges_data[d][1]:size(1) * self.lookahead.ranges_data[d][1]:size(2) self.lookahead.term_fold_indices[d] = {before + 1, self.lookahead.num_term_fold_nodes} end self.lookahead.ranges_data_call = arguments.Tensor(self.lookahead.num_term_call_nodes, self.lookahead.batch_size, constants.players_count, game_settings.hand_count) self.lookahead.ranges_data_fold = arguments.Tensor(self.lookahead.num_term_fold_nodes, self.lookahead.batch_size, constants.players_count, game_settings.hand_count) self.lookahead.cfvs_data_call = arguments.Tensor(self.lookahead.num_term_call_nodes, self.lookahead.batch_size, constants.players_count, game_settings.hand_count) self.lookahead.cfvs_data_fold = arguments.Tensor(self.lookahead.num_term_fold_nodes, self.lookahead.batch_size, constants.players_count, game_settings.hand_count) end function LookaheadBuilder:reset() for d = 1, self.lookahead.depth do if self.lookahead.ranges_data[d] ~= nil then self.lookahead.ranges_data[d]:fill(1.0 / game_settings.hand_count) end if self.lookahead.average_strategies_data[d] ~= nil then self.lookahead.average_strategies_data[d]:fill(0) end if self.lookahead.current_strategy_data[d] ~= nil then self.lookahead.current_strategy_data[d]:fill(0) end if self.lookahead.cfvs_data[d] ~= nil then self.lookahead.cfvs_data[d]:fill(0) end if self.lookahead.average_cfvs_data[d] ~= nil then self.lookahead.average_cfvs_data[d]:fill(0) end if self.lookahead.regrets_data[d] ~= nil then self.lookahead.regrets_data[d]:fill(0) end if self.lookahead.current_regrets_data[d] ~= nil then self.lookahead.current_regrets_data[d]:fill(0) end if self.lookahead.positive_regrets_data[d] ~= nil then self.lookahead.positive_regrets_data[d]:fill(0) end if self.lookahead.placeholder_data[d] ~= nil then self.lookahead.placeholder_data[d]:fill(0) end if self.lookahead.regrets_sum[d] ~= nil then self.lookahead.regrets_sum[d]:fill(0) end if self.lookahead.inner_nodes[d] ~= nil then self.lookahead.inner_nodes[d]:fill(0) end if self.lookahead.inner_nodes_p1[d] ~= nil then self.lookahead.inner_nodes_p1[d]:fill(0) end if self.lookahead.swap_data[d] ~= nil then self.lookahead.swap_data[d]:fill(0) end end if self.lookahead.next_street_boxes ~= nil then self.lookahead.next_street_boxes.iter = 0 self.lookahead.next_street_boxes:start_computation(self.lookahead.next_round_pot_sizes, self.lookahead.batch_size) end end --- Traverses the tree to fill in lookahead data structures that summarize data -- contained in the tree. -- -- For example, saves pot sizes and numbers of actions at each lookahead state. -- -- @param node the current node of the public tree -- @param layer the depth of the current node -- @param action_id the index of the action that led to this node -- @param parent_id the index of the current node's parent -- @param gp_id the index of the current node's grandparent -- @local function LookaheadBuilder:set_datastructures_from_tree_dfs(node, layer, action_id, parent_id, gp_id, cur_action_id, parent_action_id) --fill the potsize assert(node.pot) self.lookahead.pot_size[layer][{action_id, parent_id, gp_id, {}, {}}] = node.pot if layer == 3 and cur_action_id == constants.actions.ccall then self.lookahead.parent_action_id[parent_id] = parent_action_id end node.lookahead_coordinates = arguments.Tensor({action_id, parent_id, gp_id}) --transition call cannot be allin call if node.current_player == constants.players.chance then assert(parent_id <= self.lookahead.nonallinbets_count[layer-2]) end if layer < self.lookahead.depth + 1 then local gp_nonallinbets_count = self.lookahead.nonallinbets_count[layer-2] local prev_layer_terminal_actions_count = self.lookahead.terminal_actions_count[layer-1] local gp_terminal_actions_count = self.lookahead.terminal_actions_count[layer-2] local prev_layer_bets_count = 0 prev_layer_bets_count = self.lookahead.bets_count[layer - 1] --compute next coordinates for parent and grandparent local next_parent_id = action_id - prev_layer_terminal_actions_count local next_gp_id = (gp_id - 1) * gp_nonallinbets_count + (parent_id) if (not node.terminal) and (node.current_player ~= constants.players.chance) then --parent is not an allin raise assert(parent_id <= self.lookahead.nonallinbets_count[layer-2]) --do we need to mask some actions for that node? (that is, does the node have fewer children than the max number of children for any node on this layer) local node_with_empty_actions = (#node.children < self.lookahead.actions_count[layer]) if node_with_empty_actions then --we need to mask nonexisting padded bets assert(layer > 1) local terminal_actions_count = self.lookahead.terminal_actions_count[layer] assert(terminal_actions_count == 2) local existing_bets_count = #node.children - terminal_actions_count --allin situations if existing_bets_count == 0 then assert(action_id == self.lookahead.actions_count[layer-1]) end for child_id = 1,terminal_actions_count do local child_node = node.children[child_id] --go deeper self:set_datastructures_from_tree_dfs(child_node, layer+1, child_id, next_parent_id, next_gp_id, node.actions[child_id], cur_action_id) end --we need to make sure that even though there are fewer actions, the last action/allin is has the same last index as if we had full number of actions --we manually set the action_id as the last action (allin) for b = 1, existing_bets_count do self:set_datastructures_from_tree_dfs(node.children[#node.children-b+1], layer+1, self.lookahead.actions_count[layer]-b+1, next_parent_id, next_gp_id, node.actions[#node.children-b+1], cur_action_id) end --mask out empty actions self.lookahead.empty_action_mask[layer+1][{{terminal_actions_count+1,-(existing_bets_count+1)}, next_parent_id, next_gp_id, {}}] = 0 else --node has full action count, easy to handle for child_id = 1,#node.children do local child_node = node.children[child_id] --go deeper self:set_datastructures_from_tree_dfs(child_node, layer+1, child_id, next_parent_id, next_gp_id, node.actions[child_id], cur_action_id) end end end end end --- Builds the lookahead's internal data structures using the public tree. -- @param tree the public tree used to construct the lookahead function LookaheadBuilder:build_from_tree(tree) self.lookahead.tree = tree self.lookahead.depth = tree.depth --per layer information about tree actions --per layer actions are the max number of actions for any of the nodes on the layer self.lookahead.bets_count = {} self.lookahead.nonallinbets_count = {} self.lookahead.terminal_actions_count = {} self.lookahead.actions_count = {} self.lookahead.first_call_terminal = self.lookahead.tree.children[2].terminal self.lookahead.first_call_transition = self.lookahead.tree.children[2].current_player == constants.players.chance self.lookahead.first_call_check = (not self.lookahead.first_call_terminal) and (not self.lookahead.first_call_transition) self:_compute_tree_structures({tree}, 1) --construct the initial data structures using the bet counts self:construct_data_structures() -- action ids for first self.lookahead.parent_action_id = {} --traverse the tree and fill the datastructures (pot sizes, non-existin actions, ...) --node, layer, action, parent_action, gp_id self:set_datastructures_from_tree_dfs(tree, 1, 1, 1, 1, -100) --set additional info assert(self.lookahead.terminal_actions_count[1] == 1 or self.lookahead.terminal_actions_count[1] == 2) --we mask out fold as a possible action when check is for free, due to --1) fewer actions means faster convergence --2) we need to make sure prob of free fold is zero because ACPC dealer changes such action to check if self.lookahead.tree.bets[1] == self.lookahead.tree.bets[2] then --TODO fix this self.lookahead.empty_action_mask[2][1]:fill(0) end --construct the neural net query boxes self:_construct_transition_boxes() end --- Computes the maximum number of actions at each depth of the tree. -- -- Used to find the size for the tensors which store lookahead data. The -- maximum number of actions is used so that every node at that depth can -- fit in the same tensor. -- @param current_layer a list of tree nodes at the current depth -- @param current_depth the depth of the current tree nodes -- @local function LookaheadBuilder:_compute_tree_structures(current_layer, current_depth) local layer_actions_count = 0 local layer_terminal_actions_count = 0 local next_layer = {} for n = 1,#current_layer do local node = current_layer[n] layer_actions_count = math.max(layer_actions_count, #node.children) local node_terminal_actions_count = 0 for c = 1,#current_layer[n].children do if node.children[c].terminal or node.children[c].current_player == constants.players.chance then node_terminal_actions_count = node_terminal_actions_count + 1 end end layer_terminal_actions_count = math.max(layer_terminal_actions_count, node_terminal_actions_count) --add children of the node to the next layer for later pass of BFS if not node.terminal then for c = 1,#node.children do table.insert(next_layer, node.children[c]) end end end assert((layer_actions_count == 0) == (#next_layer == 0)) assert((layer_actions_count == 0) == (current_depth == self.lookahead.depth)) --set action and bet counts self.lookahead.bets_count[current_depth] = layer_actions_count - layer_terminal_actions_count self.lookahead.nonallinbets_count[current_depth] = layer_actions_count - layer_terminal_actions_count if game_settings.nl then --remove allin self.lookahead.nonallinbets_count[current_depth] = self.lookahead.nonallinbets_count[current_depth] - 1 end --if no alllin... if layer_actions_count == 2 then assert(layer_actions_count == layer_terminal_actions_count, layer_terminal_actions_count .. " " .. current_depth) self.lookahead.nonallinbets_count[current_depth] = 0 end self.lookahead.terminal_actions_count[current_depth] = layer_terminal_actions_count self.lookahead.actions_count[current_depth] = layer_actions_count if #next_layer > 0 then assert(layer_actions_count >= 2) --go deeper self:_compute_tree_structures(next_layer, current_depth + 1) end end ================================================ FILE: Source/Lookahead/mock_resolving.lua ================================================ --- Implements the re-solving interface used by @{resolving} with functions -- that do nothing. -- -- Used for debugging. -- @classmod mock_resolving require 'Lookahead.lookahead' require 'Lookahead.cfrd_gadget' require 'Tree.tree_builder' require 'Tree.tree_visualiser' local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local tools = require 'tools' local card_tools = require 'Game.card_tools' local game_settings = require 'Settings.game_settings' local MockResolving = torch.class('MockResolving') --- Constructor function MockResolving:__init() end --- Does nothing. -- @param node the node to "re-solve" -- @param[opt] player_range not used -- @param[opt] opponent_range not used -- @see resolving.resolve_first_node function MockResolving:resolve_first_node(node, player_range, opponent_range) self.node = node self.action_count = self.node.actions:size(1) end --- Does nothing. -- @param node the node to "re-solve" -- @param[opt] player_range not used -- @param[opt] opponent_cfvs not used -- @see resolving.resolve function MockResolving:resolve(node, player_range, opponent_cfvs) self.node = node self.action_count = self.node.actions:size(1) end --- Gives the possible actions at the re-solve node. -- @return the actions that can be taken at the re-solve node -- @see resolving.get_possible_actions function MockResolving:get_possible_actions() return self.node.actions end --- Returns an arbitrary vector. -- @return a vector of 1s -- @see resolving.get_root_cfv function MockResolving:get_root_cfv() return arguments.Tensor(game_settings.card_count):fill(1) end --- Returns an arbitrary vector. -- @param[opt] action not used -- @return a vector of 1s -- @see resolving.get_action_cfv function MockResolving:get_action_cfv(action) return arguments.Tensor(game_settings.card_count):fill(1) end --- Returns an arbitrary vector. -- @param[opt] player_action not used -- @param[opt] board not used -- @return a vector of 1s -- @see resolving.get_chance_action_cfv function MockResolving:get_chance_action_cfv(player_action, board) return arguments.Tensor(game_settings.card_count):fill(1) end --- Returns an arbitrary vector. -- @param[opt] action not used -- @return a vector of 1s -- @see resolving.get_action_strategy function MockResolving:get_action_strategy(action) return arguments.Tensor(game_settings.card_count):fill(1) end ================================================ FILE: Source/Lookahead/resolving.lua ================================================ --- Implements depth-limited re-solving at a node of the game tree. -- Internally uses @{cfrd_gadget|CFRDGadget} TODO SOLVER -- @classmod resolving require 'Lookahead.lookahead' require 'Lookahead.cfrd_gadget' require 'Tree.tree_builder' require 'Tree.tree_visualiser' local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local tools = require 'tools' local card_tools = require 'Game.card_tools' local Resolving = torch.class('Resolving') --- Constructor function Resolving:__init(terminal_equity) self.tree_builder = PokerTreeBuilder() self.terminal_equity = terminal_equity end --- Builds a depth-limited public tree rooted at a given game node. -- @param node the root of the tree -- @local function Resolving:_create_lookahead_tree(node) local build_tree_params = {} build_tree_params.root_node = node build_tree_params.limit_to_street = true self.lookahead_tree = self.tree_builder:build_tree(build_tree_params) end --- Re-solves a depth-limited lookahead using input ranges. -- -- Uses the input range for the opponent instead of a gadget range, so only -- appropriate for re-solving the root node of the game tree (where ranges -- are fixed). -- -- @param node the public node at which to re-solve -- @param player_range a range vector for the re-solving player -- @param opponent_range a range vector for the opponent function Resolving:resolve_first_node(node, player_range, opponent_range) self.player_range = player_range self.opponent_range = opponent_range self.opponent_cfvs = nil self:_create_lookahead_tree(node) if player_range:dim() == 1 then player_range = player_range:view(1, player_range:size(1)) opponent_range = opponent_range:view(1, opponent_range:size(1)) end self.lookahead = Lookahead(self.terminal_equity, player_range:size(1)) local timer = torch.Timer() timer:reset() self.lookahead:build_lookahead(self.lookahead_tree) print('build time: ' .. timer:time().real) timer:reset() self.lookahead:resolve_first_node(player_range, opponent_range) print('resolve time: ' .. timer:time().real) self.resolve_results = self.lookahead:get_results() return self.resolve_results end --- Re-solves a depth-limited lookahead using an input range for the player and -- the @{cfrd_gadget|CFRDGadget} to generate ranges for the opponent. -- -- @param node the public node at which to re-solve -- @param player_range a range vector for the re-solving player -- @param opponent_cfvs a vector of cfvs achieved by the opponent -- before re-solving function Resolving:resolve(node, player_range, opponent_cfvs) assert(card_tools:is_valid_range(player_range, node.board)) local timer = torch.Timer() timer:reset() self.player_range = player_range self.opponent_cfvs = opponent_cfvs self:_create_lookahead_tree(node) if player_range:dim() == 1 then player_range = player_range:view(1, player_range:size(1)) end self.lookahead = Lookahead(self.terminal_equity, player_range:size(1)) self.lookahead:build_lookahead(self.lookahead_tree) print('build time: ' .. timer:time().real) timer:reset() self.lookahead:resolve(player_range, opponent_cfvs) print('resolve time: ' .. timer:time().real) self.resolve_results = self.lookahead:get_results() return self.resolve_results end --- Gives the index of the given action at the node being re-solved. -- -- The node must first be re-solved with @{resolve} or @{resolve_first_node}. -- @param action a legal action at the node -- @return the index of the action -- @local function Resolving:_action_to_action_id(action) local actions = self:get_possible_actions(action) local action_id = -1 for i=1,actions:size(1) do if action == actions[i] then action_id = i end end assert(action_id ~= -1) return action_id end --- Gives a list of possible actions at the node being re-solved. -- -- The node must first be re-solved with @{resolve} or @{resolve_first_node}. -- @return a list of legal actions function Resolving:get_possible_actions() return self.lookahead_tree.actions end --- Gives the average counterfactual values that the re-solve player received -- at the node during re-solving. -- -- The node must first be re-solved with @{resolve_first_node}. -- -- @return a vector of cfvs function Resolving:get_root_cfv() return self.resolve_results.root_cfvs end --- Gives the average counterfactual values that each player received -- at the node during re-solving. -- -- Usefull for data generation for neural net training -- -- The node must first be re-solved with @{resolve_first_node}. -- -- @return a 2xK tensor of cfvs, where K is the range size function Resolving:get_root_cfv_both_players() return self.resolve_results.root_cfvs_both_players end --- Gives the average counterfactual values that the opponent received -- during re-solving after the re-solve player took a given action. -- -- Used during continual re-solving to track opponent cfvs. The node must -- first be re-solved with @{resolve} or @{resolve_first_node}. -- -- @param action the action taken by the re-solve player at the node being -- re-solved -- @return a vector of cfvs function Resolving:get_action_cfv(action) local action_id = self:_action_to_action_id(action) return self.resolve_results.children_cfvs[action_id] end --- Gives the average counterfactual values that the opponent received -- during re-solving after a chance event (the betting round changes and -- more cards are dealt). -- -- Used during continual re-solving to track opponent cfvs. The node must -- first be re-solved with @{resolve} or @{resolve_first_node}. -- -- @param action the action taken by the re-solve player at the node being -- re-solved -- @param board a vector of board cards which were updated by the chance event -- @return a vector of cfvs function Resolving:get_chance_action_cfv(action, board) -- resolve to get next_board chance actions if flop if board:dim() == 1 and board:size(1) == 3 then self.lookahead:reset() local board_idx = card_tools:get_flop_board_index(board) self.lookahead.next_board_idx = board_idx if self.opponent_cfvs ~= nil then self.lookahead:resolve(self.player_range, self.opponent_cfvs) else self.lookahead:resolve_first_node(self.player_range, self.opponent_range) end self.lookahead.next_board_idx = nil end return self.lookahead:get_chance_action_cfv(action, board) end --- Gives the probability that the re-solved strategy takes a given action. -- -- The node must first be re-solved with @{resolve} or @{resolve_first_node}. -- -- @param action a legal action at the re-solve node -- @return a vector giving the probability of taking the action with each -- private hand function Resolving:get_action_strategy(action) local action_id = self:_action_to_action_id(action) return self.resolve_results.strategy[action_id][1] end ================================================ FILE: Source/Nn/Bucketing/.gitignore ================================================ unique*.dat ================================================ FILE: Source/Nn/Bucketing/flop_tools.lua ================================================ local M = {} function M:_suitcat_flop(s1,s2,s3,s4,s5) if(s1~=0) then return -1 end local ret = -1 if(s2==0) then if(s3==0) then ret = s4 * 2 + s5 elseif(s3==1) then ret = 5 + s4 * 3 + s5 end elseif(s2==1) then if(s3==0) then ret = 15 + s4 * 3 + s5 elseif(s3==1) then ret = 25 + s4 * 3 + s5 elseif(s3==2) then ret = 35 + s4 * 4 + s5 end end return ret; end function M:flopID(h, b) b = torch.sort(b) -- Get hand suits local os = {} for i = 0,4 do if i <= 1 then os[i] = (h[i+1]-1)%4 else os[i] = (b[i-1]-1)%4 end end -- Canonicalize suits local MM = 0 local s = {} for i=0,4 do local j = 0 while j < i do if os[j] == os[i] then s[i] = s[j] break end j = j + 1 end if j == i then s[i] = MM MM = MM + 1 end if i <= 1 then local suitdiff = s[i] - ((h[i+1] - 1) % 4) h[i+1] = h[i+1] + suitdiff else local suitdiff = s[i] - ((b[i-1] - 1) % 4) b[i-1] = b[i-1] + suitdiff end end b = torch.sort(b) local base = math.floor((h[1]-1)/4)*13*13*13*13 + math.floor((h[2]-1)/4)*13*13*13 + math.floor((b[1]-1)/4)*13*13 + math.floor((b[2]-1)/4)*13 + math.floor((b[3]-1)/4) for i = 0,4 do if i <= 1 then s[i] = (h[i+1]-1)%4 else s[i] = (b[i-1]-1)%4 end end local cat = self:_suitcat_flop(s[0],s[1],s[2],s[3],s[4]) if cat==-1 then print("error suit cat") io.read() end cat = cat*13*13*13*13*13 + base; return cat end return M ================================================ FILE: Source/Nn/Bucketing/river_tools.lua ================================================ local arguments = require 'Settings.arguments' local M = {} function M:_suitcat_river(s1,s2,s3,s4,s5,s6,s7) local suit = {} suit[0] = 0 suit[1] = 0 suit[2] = 0 suit[3] = 0 suit[s3] = suit[s3] + 1; suit[s4] = suit[s4] + 1; suit[s5] = suit[s5] + 1; suit[s6] = suit[s6] + 1; suit[s7] = suit[s7] + 1; if suit[0]<=2 and suit[1]<=2 and suit[2]<=2 and suit[3]<=2 then return 0 end if suit[0]==3 or suit[1]==3 or suit[2]==3 or suit[3]==3 then local thesuit = -1; for i=0,3 do if suit[i]==3 then thesuit = i end end local mask = 0 if s3==thesuit then mask = mask + 1 end if s4==thesuit then mask = mask + 2 end if s5==thesuit then mask = mask + 4 end if s6==thesuit then mask = mask + 8 end if s7==thesuit then mask = mask + 16 end local add = 0 if s1==thesuit and s2==thesuit then add = 1 elseif s1==thesuit then add = 2 elseif s2==thesuit then add = 3 end if mask==7 then return 1 + add elseif mask==11 then return 5 + add elseif mask==19 then return 9 + add elseif mask==13 then return 13 + add elseif mask==21 then return 17 + add elseif mask==25 then return 21 + add elseif mask==14 then return 25 + add elseif mask==22 then return 29 + add elseif mask==26 then return 33 + add elseif mask==28 then return 37 + add end print("bad river suits") io.read() end if suit[0]==4 or suit[1]==4 or suit[2]==4 or suit[3]==4 then local thesuit = -1; for i=0,3 do if suit[i]==4 then thesuit = i end end if s3 ~= thesuit then if s1==thesuit and s2==thesuit then return 42 end if s1==thesuit then return 43 end if s2==thesuit then return 44 end return 45 elseif s4~=thesuit then if s1==thesuit and s2==thesuit then return 46 end if s1==thesuit then return 47 end if s2==thesuit then return 48 end return 49 elseif s5~=thesuit then if s1==thesuit and s2==thesuit then return 50 end if s1==thesuit then return 51 end if s2==thesuit then return 52 end return 53 elseif s6~=thesuit then if s1==thesuit and s2==thesuit then return 54 end if s1==thesuit then return 55 end if s2==thesuit then return 56 end return 57 elseif s7~=thesuit then if s1==thesuit and s2==thesuit then return 58 end if s1==thesuit then return 59 end if s2==thesuit then return 60 end return 61; end print("bad river suits") io.read() end if suit[0]==5 or suit[1]==5 or suit[2]==5 or suit[3]==5 then local thesuit = -1; for i=0,3 do if suit[i]==5 then thesuit = i end end if s1==thesuit and s2==thesuit then return 62 end if s1==thesuit then return 63 end if s2==thesuit then return 64 end return 65 end print("bad river suits") io.read() end function M:riverID(h, b) local hole = h local board = torch.sort(b) local base = math.floor((hole[1]-1)/4)*13*13*13*13*13*13 + math.floor((hole[2]-1)/4)*13*13*13*13*13 + math.floor((board[1]-1)/4)*13*13*13*13 + math.floor((board[2]-1)/4)*13*13*13 + math.floor((board[3]-1)/4)*13*13 + math.floor((board[4]-1)/4)*13 + math.floor((board[5]-1)/4); local suitcode = self:_suitcat_river( hole[1]%4, hole[2]%4, board[1]%4, board[2]%4, board[3]%4, board[4]%4, board[5]%4); if suitcode==-1 then print("error suit cat") io.read() end suitcode = suitcode * 815730722 return suitcode+base end return M ================================================ FILE: Source/Nn/Bucketing/turn_tools.lua ================================================ local M = {} function M:_suitcat_turn(s1,s2,s3,s4,s5,s6) if s1~=0 then return -1 end local ret = -1 if s2==0 then if s3==0 then if s4==0 then ret = s5 * 2 + s6 elseif s4==1 then ret = 5 + s5 * 3 + s6 end elseif s3==1 then if s4==0 then ret = 15 + s5 * 3 + s6 elseif s4==1 then ret = 25 + s5 * 3 + s6 elseif s4==2 then ret = 35 + s5 * 4 + s6 end end elseif s2==1 then if s3==0 then if s4==0 then ret = 51 + s5 * 3 + s6 elseif s4==1 then ret = 61 + s5 * 3 + s6 elseif s4==2 then ret = 71 + s5 * 4 + s6 end elseif s3==1 then if s4==0 then ret = 87 + s5 * 3 + s6 elseif s4==1 then ret = 97 + s5 * 3 + s6 elseif s4==2 then ret = 107 + s5 * 4 + s6 end elseif s3==2 then ret = 123 + s4 * 16 + s5 * 4 + s6 end end return ret end function M:turnID(h, b) b = torch.sort(b) -- Get hand suits local os = {} for i = 0,5 do if i <= 1 then os[i] = (h[i+1]-1)%4 else os[i] = (b[i-1]-1)%4 end end -- Canonicalize suits local MM = 0 local s = {} for i=0,5 do local j = 0 while j < i do if os[j] == os[i] then s[i] = s[j] break end j = j + 1 end if j == i then s[i] = MM MM = MM + 1 end if i <= 1 then local suitdiff = s[i] - ((h[i+1] - 1) % 4) h[i+1] = h[i+1] + suitdiff else local suitdiff = s[i] - ((b[i-1] - 1) % 4) b[i-1] = b[i-1] + suitdiff end end b = torch.sort(b) local base = math.floor((h[1]-1)/4)*13*13*13*13*13 + math.floor((h[2]-1)/4)*13*13*13*13 + math.floor((b[1]-1)/4)*13*13*13 + math.floor((b[2]-1)/4)*13*13 + math.floor((b[3]-1)/4)*13 + math.floor((b[4]-1)/4) for i = 0,5 do if i <= 1 then s[i] = (h[i+1]-1)%4 else s[i] = (b[i-1]-1)%4 end end local cat = self:_suitcat_turn(s[0],s[1],s[2],s[3],s[4],s[5]) if cat==-1 then print("error suit cat") io.read() end cat = cat*13*13*13*13*13*13 + base; return cat end return M ================================================ FILE: Source/Nn/bucket_conversion.lua ================================================ --- Converts between vectors over private hands and vectors over buckets. -- @classmod bucket_conversion require 'torch' require 'math' local card_tools = require 'Game.card_tools' local arguments = require 'Settings.arguments' local game_settings = require 'Settings.game_settings' local bucketer = require 'Nn.bucketer' local tools = require 'tools' local BucketConversion = torch.class('BucketConversion') --- Constructor function BucketConversion:__init() end --- Sets the board cards for the bucketer. -- @param board a non-empty vector of board cards function BucketConversion:set_board(board, raw) if raw ~= nil then self.bucket_count = tools:choose(14, 2) + tools:choose(10, 2) else self.bucket_count = bucketer:get_bucket_count(card_tools:board_to_street(board)) end self._range_matrix = arguments.Tensor(game_settings.hand_count, self.bucket_count ):zero() local buckets = nil if raw ~= nil then buckets = bucketer:compute_rank_buckets(board) else buckets = bucketer:compute_buckets(board) end local class_ids = torch.range(1, self.bucket_count) if arguments.gpu then buckets = buckets:cuda() class_ids = class_ids:cuda() else class_ids = class_ids:float() end class_ids = class_ids:view(1, self.bucket_count):expand(game_settings.hand_count, self.bucket_count) local card_buckets = buckets:view(game_settings.hand_count, 1):expand(game_settings.hand_count, self.bucket_count) --finding all strength classes --matrix for transformation from card ranges to strength class ranges self._range_matrix[torch.eq(class_ids, card_buckets)] = 1 --matrix for transformation form class values to card values self._reverse_value_matrix = self._range_matrix:t():clone() end --- Converts a range vector over private hands to a range vector over buckets. -- -- @{set_board} must be called first. Used to create inputs to the neural net. -- @param card_range a probability vector over private hands -- @param bucket_range a vector in which to save the resulting probability -- vector over buckets function BucketConversion:card_range_to_bucket_range(card_range, bucket_range) bucket_range:mm(card_range, self._range_matrix) end function BucketConversion:hand_cfvs_to_bucket_cfvs(card_range, card_cfvs, bucket_range, bucketed_cfvs) bucketed_cfvs:mm(torch.cmul(card_range,card_cfvs), self._range_matrix) -- avoid divide by 0 bucketed_cfvs:cdiv(torch.cmax(bucket_range, 0.00001)) end --- Converts a value vector over buckets to a value vector over private hands. -- -- @{set_board} must be called first. Used to process neural net outputs. -- @param bucket_value a vector of values over buckets -- @param card_value a vector in which to save the resulting vector of values -- over private hands function BucketConversion:bucket_value_to_card_value(bucket_value, card_value) card_value:mm(bucket_value, self._reverse_value_matrix) end --- Gives a vector of possible buckets on the the board. -- -- @{set_board} must be called first. -- @return a mask vector over buckets where each entry is 1 if the bucket is -- valid, 0 if not function BucketConversion:get_possible_bucket_mask() local mask = arguments.Tensor(1, self.bucket_count) local card_indicator = arguments.Tensor(1, game_settings.hand_count):fill(1) mask:mm(card_indicator, self._range_matrix) mask[torch.gt(mask, 0)] = 1 return mask end ================================================ FILE: Source/Nn/bucketer.lua ================================================ --- Assigns hands to buckets on the given board. -- -- For the Leduc implementation, we simply assign every possible set of -- private and board cards to a unique bucket. -- @classmod bucketer local game_settings = require 'Settings.game_settings' local card_tools = require 'Game.card_tools' local card_to_string = require 'Game.card_to_string_conversion' local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local river_tools = require 'Nn.Bucketing.river_tools' local turn_tools = require 'Nn.Bucketing.turn_tools' local flop_tools = require 'Nn.Bucketing.flop_tools' local card_to_string_conversion = require 'Game.card_to_string_conversion' local evaluator = require 'Game.Evaluation.evaluator' local tools = require 'tools' local M = {} function M:_init() if self._ihr_pair_to_bucket == nil then local f = assert(io.open("./Nn/Bucketing/riverihr.dat", "rb")) local data = f:read("*all") self._river_ihr = {} for i = 1, string.len(data), 7 do local key = 0 for j = i,i+4 do key = key + data:byte(j) * (2 ^ ((4 - j + i) * 8)) end local win = data:byte(i+5) local tie = data:byte(i+6) self._river_ihr[key] = win*200 + tie end f:close() local f = assert(io.open("./Nn/Bucketing/rcats.dat", "r")) self.river_buckets = f:read("*number") self._ihr_pair_to_bucket = {} for i = 1, self.river_buckets do local win = f:read("*number") local tie = f:read("*number") self._ihr_pair_to_bucket[win * 1000 + tie] = i end f:close() end if self._turn_means == nil then self._turn_means = {} local f = assert(io.open("./Nn/Bucketing/turn_means.dat")) local num_means = f:read("*number") for i = 1,num_means do local dist = {} for j = 0,50 do dist[j] = f:read("*number") end self._turn_means[i] = dist end f:close() end if self._turn_cats == nil then self._turn_cats = {} local f = assert(io.open("./Nn/Bucketing/turn_dist_cats.dat", "rb")) local data = f:read("*all") for i = 1, string.len(data), 6 do local key = 0 for j = i,i+3 do key = key + data:byte(j) * (2 ^ ((j - i) * 8)) end local cat = data:byte(i+4) + data:byte(i+5) * (2 ^ 8) self._turn_cats[key] = cat assert(cat <= 1000 and cat >= 1, "cat = " .. cat) end f:close() end if self._flop_cats == nil then self._flop_cats = {} local f = assert(io.open("./Nn/Bucketing/flop_dist_cats.dat", "rb")) local data = f:read("*all") for i = 1, string.len(data), 6 do local key = 0 for j = i,i+3 do key = key + data:byte(j) * (2 ^ ((j - i) * 8)) end local cat = data:byte(i+4) + data:byte(i+5) * (2 ^ 8) self._flop_cats[key] = cat assert(cat <= 1000 and cat >= 1, "cat = " .. cat) end f:close() end end M:_init() --- Gives the total number of buckets across all boards. -- @return the number of buckets function M:get_bucket_count(street) if street == 4 then return self.river_buckets elseif street == 3 or street == 2 then return 1000 elseif street == 1 then return 169 end return 169 end --- Gives the maximum number of ranks across all boards. -- @return the number of buckets function M:get_rank_count() return tools:choose(14, 2) + tools:choose(10, 2) end function M:emd(a,b) local emds = {} emds[0] = 0; for i = 1, 51 do emds[i] = a[i-1] + emds[i-1] - b[i-1]; end local ret = 0; for i = 0, 51 do ret = ret + math.abs(emds[i]); end return ret end function M:_compute_turn_buckets(board) local buckets = torch.Tensor(game_settings.hand_count):fill(-1) local used = torch.ByteTensor(game_settings.card_count):fill(0) local hand = torch.ByteTensor(7) for i = 1, board:size(1) do used[board[i]] = 1 hand[i + 2] = board[i] end for card1 = 1,game_settings.card_count do if used[card1] == 0 then used[card1] = 1 hand[1] = card1 for card2 = card1+1, game_settings.card_count do if used[card2] == 0 then used[card2] = 1 hand[2] = card2 local idx = card_tools:get_hole_index({card1,card2}) --print(card_to_string_conversion:cards_to_string(hand[{{1,6}}])) local turn_code = turn_tools:turnID(hand[{{1,2}}]:clone(), hand[{{3,6}}]:clone()) local closest_mean = self._turn_cats[turn_code] buckets[idx] = closest_mean used[card2] = 0 end end used[card1] = 0 end end return buckets end function M:_compute_flop_buckets(board) local buckets = torch.Tensor(game_settings.hand_count):fill(-1) local used = torch.ByteTensor(game_settings.card_count):fill(0) local hand = torch.ByteTensor(5) for i = 1, board:size(1) do used[board[i]] = 1 hand[i + 2] = board[i] end for card1 = 1,game_settings.card_count do if used[card1] == 0 then used[card1] = 1 hand[1] = card1 for card2 = card1+1, game_settings.card_count do if used[card2] == 0 then used[card2] = 1 hand[2] = card2 local idx = card_tools:get_hole_index({card1,card2}) local flop_code = flop_tools:flopID(hand[{{1,2}}]:clone(), hand[{{3,5}}]:clone()) local closest_mean = self._flop_cats[flop_code] buckets[idx] = closest_mean used[card2] = 0 end end used[card1] = 0 end end return buckets end function M:_compute_preflop_buckets() if self._preflop_buckets == nil then self._preflop_buckets = arguments.Tensor(game_settings.hand_count):fill(-1) for card1 = 1,game_settings.card_count do for card2 = card1+1, game_settings.card_count do local idx = card_tools:get_hole_index({card1,card2}) local rank1 = math.floor((card1 - 1) / 4) local rank2 = math.floor((card2 - 1) / 4) if card1 % 4 == card2 % 4 then self._preflop_buckets[idx] = rank1 * 13 + rank2 + 1 else self._preflop_buckets[idx] = rank2 * 13 + rank1 + 1 end end end end return self._preflop_buckets end function M:_compute_river_buckets(board) local buckets = torch.Tensor(game_settings.hand_count):fill(-1) local used = torch.ByteTensor(game_settings.card_count):fill(0) local board_size = board:size(1) for i = 1, board_size do used[board[i]] = 1 end local hands = torch.ByteTensor(constants.players_count, board_size + game_settings.hand_card_count) for i = 1, constants.players_count do hands[{i,{1, - 1 - game_settings.hand_card_count}}]:copy(board) end for card1 = 1,game_settings.card_count do if used[card1] == 0 then used[card1] = 1 hands[1][-2] = card1 for card2 = card1+1, game_settings.card_count do if used[card2] == 0 then used[card2] = 1 hands[1][-1] = card2 local idx = card_tools:get_hole_index({card1,card2}) local code = river_tools:riverID(hands[1][{{6,7}}], hands[1][{{1,5}}]) local ihr = self._river_ihr[code] local win_bucket = math.floor(ihr/200) local tie_bucket = math.floor((ihr % 200)/2) local r_bucket = self._ihr_pair_to_bucket[win_bucket * 1000 + tie_bucket] assert(r_bucket ~= nil, 'bad win,tie ihr pair') buckets[idx] = r_bucket used[card2] = 0 end end used[card1] = 0 end end return buckets end --- Gives a vector which maps private hands to buckets on a given board. -- @param board a non-empty vector of board cards -- @return a vector which maps each private hand to a bucket index function M:compute_buckets(board) local street = card_tools:board_to_street(board) if street == 4 then return self:_compute_river_buckets(board) elseif street == 3 then return self:_compute_turn_buckets(board) elseif street == 2 then return self:_compute_flop_buckets(board) elseif street == 1 then return self:_compute_preflop_buckets() end end function M:compute_rank_buckets(board) local buckets = arguments.Tensor(game_settings.hand_count):fill(-1) local ranks = evaluator:batch_eval(board,-1) local sorted_ranks, _ = torch.sort(ranks) local rank_idx = 0 local rank_idxs = {} for i = 1, sorted_ranks:size(1) do if sorted_ranks[i] == -1 then break end if (i > 1 and sorted_ranks[i] ~= sorted_ranks[i-1]) or i == 1 then rank_idx = rank_idx + 1 rank_idxs[sorted_ranks[i]] = rank_idx end end for i = 1, ranks:size(1) do if ranks[i] ~= -1 then ranks[i] = rank_idxs[ranks[i]] end end return ranks end return M ================================================ FILE: Source/Nn/cpu_gpu_model_converter.lua ================================================ --- Generates a neural net model in CPU format from a neural net model saved -- in GPU format. -- @script cpu_gpu_model_converter require 'cunn' local arguments = require 'Settings.arguments' --- Generates a neural net model in CPU format from a neural net model saved -- in GPU format. -- @param gpu_model_path the prefix of the path to the gpu model, which is -- appended with `_gpu.info` and `_gpu.model` local function convert_gpu_to_cpu(gpu_model_path) local info = torch.load(gpu_model_path .. '_gpu.info') assert(info.gpu) info.gpu = false local model = torch.load(gpu_model_path .. '_gpu.model') model = model:float() torch.save(gpu_model_path .. '_cpu.info', info) torch.save(gpu_model_path .. '_cpu.model', model) end --- Generates a neural net model in GPU format from a neural net model saved -- in CPU format. -- @param cpu_model_path the prefix of the path to the cpu model, which is -- appended with `_cpu.info` and `_cpu.model` local function convert_cpu_to_gpu(cpu_model_path) local info = torch.load(cpu_model_path .. '_cpu.info') assert(not info.gpu) info.gpu = true local model = torch.load(cpu_model_path .. '_cpu.model') model = model:cuda() torch.save(cpu_model_path .. '_gpu.info', info) torch.save(cpu_model_path .. '_gpu.model', model) end convert_cpu_to_gpu('../Data/Models/NoLimit/flop/final') ================================================ FILE: Source/Nn/masked_huber_loss.lua ================================================ --- Computes a Huber loss for neural net training and evaluation. -- -- Computes the loss across buckets, but only on buckets that are -- possible on a given board. -- @classmod masked_huber_loss require 'nn' local arguments = require 'Settings.arguments' local MaskedHuberLoss = torch.class('MaskedHuberLoss') --- Constructor function MaskedHuberLoss:__init() self.criterion = nn.SmoothL1Criterion() end --- Moves the torch criterion (used for loss and gradient computation) -- to the GPU. -- @return the MaskedHuberLoss object that `cuda()` is called on function MaskedHuberLoss:cuda() self.criterion = self.criterion:cuda() return self end --- Computes the loss over a batch of neural net outputs and targets. -- -- @param outputs an NxM tensor containing N vectors of values over buckets, -- output by the neural net -- @param targets an NxM tensor containing N vectors of actual values over -- buckets, produced by @{data_generation_call} -- @param mask an NxM tensor containing N mask vectors generated with -- @{bucket_conversion.get_possible_bucket_mask} -- @return the sum of Huber loss applied elementwise on `outputs` and `targets`, -- masked so that only valid buckets are included function MaskedHuberLoss:forward(outputs, targets, mask) local batch_size = outputs:size(1) local feature_size = outputs:size(2) --1.0 zero out the outputs/target so that the error does not depend on these outputs:cmul(mask) targets:cmul(mask) local loss = self.criterion:forward(outputs, targets) --2.0 if the batch size has changed, create new storage for the sum, otherwise reuse if not self.mask_sum or (self.mask_sum:size(1) ~= batch_size) then self.mask_placeholder = arguments.Tensor(mask:size()):fill(0) self.mask_sum = arguments.Tensor(batch_size):fill(0) self.mask_multiplier = self.mask_sum:clone():fill(0):view(-1, 1) end --3.0 compute mask sum for each batch self.mask_placeholder:copy(mask) torch.sum(self.mask_sum, self.mask_placeholder, 2) --3.1 mask multiplier - note that mask is 1 for impossible features self.mask_multiplier:fill(feature_size) self.mask_multiplier:cdiv(self.mask_sum) --4.0 multiply to get a new losss --loss is not really computed batch-wise correctly, --but that does not really matter now since gradients are correct local loss_multiplier = (batch_size * feature_size) / self.mask_sum:sum() local new_loss = loss_multiplier * loss return new_loss end --- Computes the gradient of the loss function @{forward} with -- arguments `outputs`, `targets`, and `mask`. -- -- Must be called after a @{forward} call with the same arguments. -- -- @param outputs an NxM tensor containing N vectors of values over buckets, -- output by the neural net -- @param targets an NxM tensor containing N vectors of actual values over -- buckets, produced by @{data_generation_call} -- @param mask an NxM tensor containing N mask vectors generated with -- @{bucket_conversion.get_possible_bucket_mask} -- @return the gradient of @{forward} applied to the arguments function MaskedHuberLoss:backward(outputs, targets, mask) local dloss_doutput = self.criterion:backward(outputs, targets) --we use the multiplier computed with the mask during forward call dloss_doutput:cmul(self.mask_multiplier:expandAs(dloss_doutput)) return dloss_doutput end ================================================ FILE: Source/Nn/mock_nn_terminal.lua ================================================ --- Implements the same interface as @{value_nn}, but without uses terminal -- equity evaluation instead of a neural net. -- -- Can be used to replace the neural net during debugging. -- @classmod mock_nn_terminal require 'torch' require 'Nn.bucketer' require 'TerminalEquity.terminal_equity' local game_settings = require 'Settings.game_settings' local card_tools = require 'Game.card_tools' local arguments = require 'Settings.arguments' local MockNnTerminal = torch.class('MockNnTerminal') --- Constructor. Creates an equity matrix with entries for every possible -- pair of buckets. function MockNnTerminal:__init() self.bucketer = Bucketer() self.bucket_count = self.bucketer:get_bucket_count() self.equity_matrix = arguments.Tensor(self.bucket_count, self.bucket_count):zero() --filling equity matrix local boards = card_tools:get_second_round_boards() self.board_count = boards:size(1) self.terminal_equity = TerminalEquity() for i = 1, self.board_count do local board = boards[i] self.terminal_equity:set_board(board) local call_matrix = self.terminal_equity:get_call_matrix() local buckets = self.bucketer:compute_buckets(board) for c1 = 1, game_settings.card_count do for c2 = 1, game_settings.card_count do local b1 = buckets[c1] local b2 = buckets[c2] if( b1 > 0 and b2 > 0 ) then local matrix_entry = call_matrix[c1][c2] self.equity_matrix[b1][b2] = matrix_entry end end end end end --- Gives the expected showdown equity of the two players' ranges. -- @param inputs An NxI tensor containing N instances of neural net inputs. -- See @{net_builder} for details of each input. -- @param outputs An NxO tensor in which to store N sets of expected showdown -- counterfactual values for each player. function MockNnTerminal:get_value(inputs, outputs) assert(outputs:dim() == 2 ) local bucket_count = outputs:size(2) / 2 local batch_size = outputs:size(1) local player_indexes = {{1, self.bucket_count}, {self.bucket_count + 1, 2 * self.bucket_count}} local players_count = 2 for player =1, players_count do local player_idx = player_indexes[player] local opponent_idx = player_indexes[3- player] outputs[{{}, player_idx}]:mm(inputs[{{}, opponent_idx}], self.equity_matrix) end end ================================================ FILE: Source/Nn/net_builder.lua ================================================ --- Builds the neural net architecture. -- -- Uses torch's [nn package](https://github.com/torch/nn/blob/master/README.md). -- -- For M buckets, the neural net inputs have size 2*M+1, containing range -- vectors over buckets for each player, as well as a feature capturing the -- pot size. These are arranged as [{p1\_range}, {p2\_range}, pot\_size]. -- -- The neural net outputs have size 2*M, containing counterfactual value -- vectors over buckets for each player. These are arranged as -- [{p1\_cfvs}, {p2\_cfvs}]. -- @module net_builder local M = {} print "Loading Net Builder" local bucketer = require 'Nn.bucketer' local river_tools = require 'Nn.Bucketing.river_tools' require "nn" require 'torch' require 'math' local arguments = require 'Settings.arguments' local game_settings = require 'Settings.game_settings' --import GPU modules if needed if arguments.gpu then require 'cunn' require 'cutorch' end --- Builds a neural net with architecture specified by @{arguments.net}. -- @return a newly constructed neural net function M:build_net(street, raw) local bucket_count = nil if raw ~= nil then bucket_count = bucketer:get_rank_count(street) else bucket_count = bucketer:get_bucket_count(street) end local player_count = 2 local output_size = bucket_count * player_count local input_size = output_size + 1 --run the lua interpreter on the architecture from the command line to get the list of layers local layers_text = 'return ' .. arguments.net layers_text = string.gsub(layers_text, 'input_size', input_size) layers_text = string.gsub(layers_text, 'output_size', output_size) f = loadstring(layers_text) local layers = f() local feedforward_part = nn.Sequential() --build the network from the layers for _k, layer in pairs(layers) do feedforward_part:add(layer) end local right_part = nn.Sequential() right_part:add(nn.Narrow(2, 1, output_size)) local first_layer = nn.ConcatTable() first_layer:add(feedforward_part) first_layer:add(right_part) local left_part_2 = nn.Sequential() left_part_2:add(nn.SelectTable(1)) local right_part_2 = nn.Sequential() right_part_2:add(nn.DotProduct()) right_part_2:add(nn.Replicate(output_size, 2)) right_part_2:add(nn.MulConstant(-0.5)) local second_layer = nn.ConcatTable() second_layer:add(left_part_2) second_layer:add(right_part_2) local final_mlp = nn.Sequential() final_mlp:add(first_layer) final_mlp:add(second_layer) --final layer that used delta final_mlp:add(nn.CAddTable()) return final_mlp end return M ================================================ FILE: Source/Nn/next_round_value.lua ================================================ --- Uses the neural net to estimate value at the end of the first betting round. -- @classmod next_round_value require 'torch' require 'math' local bucketer = require 'Nn.bucketer' local card_tools = require 'Game.card_tools' local arguments = require 'Settings.arguments' local game_settings = require 'Settings.game_settings' local constants = require 'Settings.constants' local tools = require 'tools' local NextRoundValue = torch.class('NextRoundValue') --- Constructor. -- -- Creates a tensor that can translate hand ranges to bucket ranges -- on any board. -- @param nn the neural network function NextRoundValue:__init(nn, board, nrv) self.nn = nn if nrv == nil then self:_init_bucketing(board) else self._street = nrv._street self.bucket_count = nrv.bucket_count self.board_count = nrv.board_count self._range_matrix = nrv._range_matrix:clone() self._range_matrix_board_view = self._range_matrix:view(game_settings.hand_count, self.board_count, self.bucket_count) self._reverse_value_matrix = nrv._reverse_value_matrix:clone() end end --- Initializes the tensor that translates hand ranges to bucket ranges. -- @local function NextRoundValue:_init_bucketing(board) local timer = torch.Timer() timer:reset() local street = card_tools:board_to_street(board) self._street = street self.bucket_count = bucketer:get_bucket_count(street+1) local boards = card_tools:get_next_round_boards(board) self.board_count = boards:size(1) self._range_matrix = arguments.Tensor(game_settings.hand_count, self.board_count * self.bucket_count ):zero() self._range_matrix_board_view = self._range_matrix:view(game_settings.hand_count, self.board_count, self.bucket_count) for idx = 1, self.board_count do local board = boards[idx] local buckets = bucketer:compute_buckets(board) local class_ids = torch.range(1, self.bucket_count) if arguments.gpu then buckets = buckets:cuda() class_ids = class_ids:cuda() else class_ids = class_ids:float() end class_ids = class_ids:view(1, self.bucket_count):expand(game_settings.hand_count, self.bucket_count) local card_buckets = buckets:view(game_settings.hand_count, 1):expand(game_settings.hand_count, self.bucket_count) --finding all strength classes --matrix for transformation from card ranges to strength class ranges self._range_matrix_board_view[{{}, idx, {}}][torch.eq(class_ids, card_buckets)] = 1 end --matrix for transformation from class values to card values self._reverse_value_matrix = self._range_matrix:t():clone() --we need to div the matrix by the sum of possible boards (from point of view of each hand) local num_new_cards = game_settings.board_card_count[street+1] - game_settings.board_card_count[street] local num_cur_cards = game_settings.board_card_count[street] local den = tools:choose( game_settings.card_count - num_cur_cards - 2*game_settings.hand_card_count, num_new_cards) local weight_constant = 1/den -- count self._reverse_value_matrix:mul(weight_constant) print("nextround init_bucket time: " .. timer:time().real) end --- Converts a range vector over private hands to a range vector over buckets. -- @param card_range a probability vector over private hands -- @param bucket_range a vector in which to store the output probabilities -- over buckets -- @local function NextRoundValue:_card_range_to_bucket_range(card_range, bucket_range) bucket_range:mm(card_range, self._range_matrix) end --- Converts a value vector over buckets to a value vector over private hands. -- @param bucket_value a value vector over buckets -- @param card_value a vector in which to store the output values over -- private hands -- @local function NextRoundValue:_bucket_value_to_card_value(bucket_value, card_value) card_value:mm(bucket_value, self._reverse_value_matrix) end --- Converts a value vector over buckets to a value vector over private hands -- given a particular set of board cards. -- TODO: fix this -- @param board a non-empty vector of board cards -- @param bucket_value a value vector over buckets -- @param card_value a vector in which to store the output values over -- private hands -- @local function NextRoundValue:_bucket_value_to_card_value_on_board(board, bucket_value, card_value) local board_idx = card_tools:get_board_index(board) local board_matrix = self._range_matrix_board_view[{{}, board_idx, {}}]:t() local serialized_card_value = card_value:view(-1, game_settings.hand_count) local serialized_bucket_value = bucket_value[{{}, {}, board_idx, {}}]:clone():view(-1, self.bucket_count) serialized_card_value:mm(serialized_bucket_value, board_matrix) end --- Initializes the value calculator with the pot size of each state that -- we are going to evaluate. -- -- During continual re-solving, there is one pot size for each initial state -- of the second betting round (before board cards are dealt). -- @param pot_sizes a vector of pot sizes -- betting round ends function NextRoundValue:start_computation(pot_sizes, batch_size) self.iter = 0 self.pot_sizes = pot_sizes:view(-1, 1):clone() self.pot_sizes = self.pot_sizes:expand(self.pot_sizes:size(1),batch_size):clone() self.pot_sizes = self.pot_sizes:view(-1, 1) self.batch_size = self.pot_sizes:size(1) end --- Gives the predicted counterfactual values at each evaluated state, given -- input ranges. -- -- @{start_computation} must be called first. Each state to be evaluated must -- be given in the same order that pot sizes were given for that function. -- Keeps track of iterations internally, so should be called exactly once for -- every iteration of continual re-solving. -- -- @param ranges An Nx2xK tensor, where N is the number of states evaluated -- (must match input to @{start_computation}), 2 is the number of players, and -- K is the number of private hands. Contains N sets of 2 range vectors. -- @param values an Nx2xK tensor in which to store the N sets of 2 value vectors -- which are output function NextRoundValue:get_value(ranges, values) assert(ranges and values) assert(ranges:size(1) == self.batch_size) self.iter = self.iter + 1 if self.iter == 1 then --initializing data structures self.next_round_inputs = arguments.Tensor(self.batch_size, self.board_count, (self.bucket_count * constants.players_count + 1)):zero() self.next_round_values = arguments.Tensor(self.batch_size, self.board_count, constants.players_count, self.bucket_count ):zero() self.transposed_next_round_values = arguments.Tensor(self.batch_size, constants.players_count, self.board_count, self.bucket_count) self.next_round_extended_range = arguments.Tensor(self.batch_size, constants.players_count, self.board_count * self.bucket_count ):zero() self.next_round_serialized_range = self.next_round_extended_range:view(-1, self.bucket_count) self.range_normalization = arguments.Tensor() self.value_normalization = arguments.Tensor(self.batch_size, constants.players_count, self.board_count) --handling pot feature for the nn local den = 0 assert(self._street <= 3) if game_settings.nl then den = arguments.stack else if self._street == 4 then den = 48 elseif self._street == 3 then den = 48 elseif self._street == 2 then den = 24 elseif self._street == 1 then den = 10 else den = -1 end end local nn_bet_input = self.pot_sizes:clone():mul(1/den) nn_bet_input = nn_bet_input:view(-1, 1):expand(self.batch_size, self.board_count) self.next_round_inputs[{{}, {}, {-1}}]:copy(nn_bet_input) end --we need to find if we need remember something in this iteration local use_memory = self.iter > arguments.cfr_skip_iters if use_memory and self.iter == arguments.cfr_skip_iters + 1 then --first iter that we need to remember something - we need to init data structures self.range_normalization_memory = arguments.Tensor(self.batch_size * self.board_count * constants.players_count, 1):zero() self.counterfactual_value_memory = arguments.Tensor(self.batch_size, constants.players_count, self.board_count, self.bucket_count):zero() end --computing bucket range in next street for both players at once self:_card_range_to_bucket_range(ranges:view(self.batch_size * constants.players_count, -1), self.next_round_extended_range:view(self.batch_size * constants.players_count, -1)) self.range_normalization:sum(self.next_round_serialized_range, 2) local rn_view = self.range_normalization:view(self.batch_size, constants.players_count, self.board_count) for player = 1, constants.players_count do self.value_normalization[{{}, player, {}}]:copy(rn_view[{{}, 3 - player, {}}]) end if use_memory then self.range_normalization_memory:add(self.value_normalization) end --eliminating division by zero self.range_normalization[torch.eq(self.range_normalization, 0)] = 1 self.next_round_serialized_range:cdiv(self.range_normalization:expandAs(self.next_round_serialized_range)) local serialized_range_by_player = self.next_round_serialized_range:view(self.batch_size, constants.players_count, self.board_count, self.bucket_count) for player = 1, constants.players_count do local player_range_index = {(player -1) * self.bucket_count + 1, player * self.bucket_count} self.next_round_inputs[{{}, {}, player_range_index}]:copy(self.next_round_extended_range[{{},player, {}}]) end --using nn to compute values local serialized_inputs_view= self.next_round_inputs:view(self.batch_size * self.board_count, -1) local serialized_values_view= self.next_round_values:view(self.batch_size * self.board_count, -1) --computing value in the next round self.nn:get_value(serialized_inputs_view, serialized_values_view) --normalizing values back according to the orginal range sum local normalization_view = self.value_normalization:view(self.batch_size, constants.players_count, self.board_count, 1):transpose(2,3) self.next_round_values:cmul(normalization_view:expandAs(self.next_round_values)) self.transposed_next_round_values:copy(self.next_round_values:transpose(3,2)) --remembering the values for the next round if use_memory then self.counterfactual_value_memory:add(self.transposed_next_round_values) end --translating bucket values back to the card values self:_bucket_value_to_card_value(self.transposed_next_round_values:view(self.batch_size * constants.players_count, -1), values:view(self.batch_size * constants.players_count, -1)) end --- Gives the average counterfactual values on the given board across previous -- calls to @{get_value}. -- -- Used to update opponent counterfactual values during re-solving after board -- cards are dealt. -- @param board a non-empty vector of board cards -- @param values a tensor in which to store the values function NextRoundValue:get_value_on_board(board, values) --check if we have evaluated correct number of iterations assert(self.iter == arguments.cfr_iters ) local batch_size = values:size(1) assert(batch_size == self.batch_size) self:_prepare_next_round_values() self:_bucket_value_to_card_value_on_board(board, self.counterfactual_value_memory, values) end --- Normalizes the counterfactual values remembered between @{get_value} calls -- so that they are an average rather than a sum. -- @local function NextRoundValue:_prepare_next_round_values() assert(self.iter == arguments.cfr_iters ) --do nothing if already prepared if self._values_are_prepared then return end --eliminating division by zero self.range_normalization_memory[torch.eq(self.range_normalization_memory, 0)] = 1 local serialized_memory_view = self.counterfactual_value_memory:view(-1, self.bucket_count) serialized_memory_view:cdiv(self.range_normalization_memory:expandAs(serialized_memory_view)) self._values_are_prepared = true end ================================================ FILE: Source/Nn/next_round_value_pre.lua ================================================ --- Uses the neural net to estimate value at the end of the first betting round. -- @classmod next_round_value require 'torch' require 'math' local bucketer = require 'Nn.bucketer' local card_tools = require 'Game.card_tools' local arguments = require 'Settings.arguments' local game_settings = require 'Settings.game_settings' local constants = require 'Settings.constants' local tools = require 'tools' local NextRoundValuePre = torch.class('NextRoundValuePre') --- Constructor. -- -- Creates a tensor that can translate hand ranges to bucket ranges -- on any board. -- @param nn the neural network function NextRoundValuePre:__init(nn, aux_nn, board) self.nn = nn self.aux_nn = aux_nn self:_init_bucketing(board) end --- Initializes the tensor that translates hand ranges to bucket ranges. -- @local function NextRoundValuePre:_init_bucketing(board) local street = card_tools:board_to_street(board) self._street = street self.bucket_count = bucketer:get_bucket_count(street+1) local boards = card_tools:get_next_round_boards(board) self.boards = boards self.board_count = boards:size(1) self.board_buckets = arguments.Tensor(self.board_count, game_settings.hand_count) for idx = 1, self.board_count do if idx % 100 == 0 then print(idx) end local board = self.boards[idx] self.board_buckets[{idx,{}}]:copy(bucketer:compute_buckets(board)) end self.impossible_mask = torch.lt(self.board_buckets,0) self.board_indexes = self.board_buckets:clone() self.board_indexes:maskedFill(self.impossible_mask, 1) self.board_indexes_scatter = self.board_buckets:clone() self.board_indexes_scatter:maskedFill(self.impossible_mask, self.bucket_count+1) if arguments.gpu then self.board_indexes = self.board_indexes:cudaLong() self.board_indexes_scatter = self.board_indexes_scatter:cudaLong() else self.board_indexes = self.board_indexes:long() self.board_indexes_scatter = self.board_indexes_scatter:long() end -- compute aux variables self.bucket_count_aux = bucketer:get_bucket_count(street) local pf_buckets = bucketer:compute_buckets(arguments.Tensor({})) local class_ids = torch.range(1, self.bucket_count_aux) if arguments.gpu then class_ids = class_ids:cuda() else class_ids = class_ids:float() end class_ids = class_ids:view(1, self.bucket_count_aux):expand(game_settings.hand_count, self.bucket_count_aux) local card_buckets = pf_buckets:view(game_settings.hand_count, 1):expand(game_settings.hand_count, self.bucket_count_aux) self._range_matrix_aux = arguments.Tensor(game_settings.hand_count, self.bucket_count_aux):zero() self._range_matrix_aux[torch.eq(class_ids, card_buckets)] = 1 self._reverse_value_matrix_aux = self._range_matrix_aux:t():clone() local num_new_cards = game_settings.board_card_count[2] - game_settings.board_card_count[1] local num_cur_cards = game_settings.board_card_count[1] local den = tools:choose( game_settings.card_count - num_cur_cards - 2*game_settings.hand_card_count, num_new_cards) self.weight_constant = 1/den end --- Converts a range vector over private hands to a range vector over buckets. -- @param card_range a probability vector over private hands -- @param bucket_range a vector in which to store the output probabilities -- over buckets -- @local function NextRoundValuePre:_card_range_to_bucket_range(card_range, bucket_range) local other_bucket_range = bucket_range:view(-1,self.board_count,self.bucket_count + 1):zero() local indexes = self.board_indexes_scatter:view(1,self.board_count, game_settings.hand_count) :expand(bucket_range:size(1), self.board_count, game_settings.hand_count) other_bucket_range:scatterAdd( 3, indexes, card_range :view(-1,1,game_settings.hand_count) :expand(card_range:size(1),self.board_count, game_settings.hand_count)) end function NextRoundValuePre:_card_range_to_bucket_range_aux(card_range, bucket_range) bucket_range:mm(card_range, self._range_matrix_aux) end function NextRoundValuePre:_card_range_to_bucket_range_on_board(board_idx, card_range, bucket_range) local other_bucket_range = bucket_range:view(-1,self.bucket_count + 1):zero() local indexes = self.board_indexes_scatter:view(1,self.board_count, game_settings.hand_count)[{{},board_idx,{}}] :expand(bucket_range:size(1), game_settings.hand_count) other_bucket_range:scatterAdd( 2, indexes, card_range :view(-1,game_settings.hand_count) :expand(card_range:size(1), game_settings.hand_count)) end --- Converts a value vector over buckets to a value vector over private hands. -- @param bucket_value a value vector over buckets -- @param card_value a vector in which to store the output values over -- private hands -- @local function NextRoundValuePre:_bucket_value_to_card_value(bucket_value, card_value) local indexes = self.board_indexes:view(1,self.board_count, game_settings.hand_count) :expand(bucket_value:size(1), self.board_count, game_settings.hand_count) self.values_per_board:gather(bucket_value:view(bucket_value:size(1), self.board_count, self.bucket_count), 3, indexes) local impossible = self.impossible_mask:view(1,self.board_count, game_settings.hand_count) :expand(bucket_value:size(1), self.board_count, game_settings.hand_count) self.values_per_board:maskedFill(impossible,0) card_value:sum(self.values_per_board,2) card_value:mul(self.weight_constant) end function NextRoundValuePre:_bucket_value_to_card_value_aux(bucket_value, card_value) card_value:mm(bucket_value, self._reverse_value_matrix_aux) end --- Converts a value vector over buckets to a value vector over private hands -- given a particular set of board cards. -- TODO: fix this -- @param board a non-empty vector of board cards -- @param bucket_value a value vector over buckets -- @param card_value a vector in which to store the output values over -- private hands -- @local function NextRoundValuePre:_bucket_value_to_card_value_on_board(board, bucket_value, card_value) local board_idx = card_tools:get_flop_board_index(board) local indexes = self.board_indexes:view(1,self.board_count, game_settings.hand_count)[{{},board_idx,{}}] :expand(bucket_value:size(1), game_settings.hand_count) self.values_per_board:gather(bucket_value:view(bucket_value:size(1), self.bucket_count), 2, indexes) local impossible = self.impossible_mask:view(1,self.board_count, game_settings.hand_count)[{{},board_idx,{}}] :expand(bucket_value:size(1), game_settings.hand_count) self.values_per_board:maskedFill(impossible,0) card_value:copy(self.values_per_board) end --- Initializes the value calculator with the pot size of each state that -- we are going to evaluate. -- -- During continual re-solving, there is one pot size for each initial state -- of the second betting round (before board cards are dealt). -- @param pot_sizes a vector of pot sizes -- betting round ends function NextRoundValuePre:start_computation(pot_sizes, batch_size) self.iter = 0 if pot_sizes:dim() == 0 then return end self.pot_sizes = pot_sizes:view(-1, 1):clone() self.pot_sizes = self.pot_sizes:expand(self.pot_sizes:size(1),batch_size):clone() self.pot_sizes = self.pot_sizes:view(-1, 1) self.batch_size = self.pot_sizes:size(1) end function NextRoundValuePre:get_value_aux(ranges, values, next_board_idx) assert(ranges and values) assert(ranges:size(1) == self.batch_size, self.batch_size .. " " .. ranges:size(1)) self.iter = self.iter + 1 if self.iter == 1 then self.next_round_inputs = arguments.Tensor(self.batch_size, (self.bucket_count_aux * constants.players_count + 1)):zero() self.next_round_values = arguments.Tensor(self.batch_size, constants.players_count, self.bucket_count_aux):zero() self.next_round_extended_range = arguments.Tensor(self.batch_size, constants.players_count, self.bucket_count_aux):zero() self.next_round_serialized_range = self.next_round_extended_range:view(-1, self.bucket_count_aux) self.values_per_board = arguments.Tensor(self.batch_size * constants.players_count, game_settings.hand_count) self.range_normalization = arguments.Tensor() self.value_normalization = arguments.Tensor(self.batch_size, constants.players_count) local den = 0 assert(self._street <= 3) if game_settings.nl then den = arguments.stack else if self._street == 4 then den = 48 elseif self._street == 3 then den = 48 elseif self._street == 2 then den = 24 elseif self._street == 1 then den = 10 else den = -1 end end --handling pot feature for the nn local nn_bet_input = self.pot_sizes:clone():mul(1/den) self.next_round_inputs[{{}, {-1}}]:copy(nn_bet_input) end local use_memory = self.iter > arguments.cfr_skip_iters and next_board_idx ~= nil if use_memory and self.iter == arguments.cfr_skip_iters + 1 then --first iter that we need to remember something - we need to init data structures self.bucket_range_on_board = arguments.Tensor(self.batch_size * constants.players_count, self.bucket_count) self.range_normalization_on_board = arguments.Tensor() self.value_normalization_on_board = arguments.Tensor(self.batch_size, constants.players_count) self.range_normalization_memory = arguments.Tensor(self.batch_size * constants.players_count, 1):zero() self.counterfactual_value_memory = arguments.Tensor(self.batch_size, constants.players_count, self.bucket_count):zero() self.next_round_extended_range_on_board = arguments.Tensor(self.batch_size, constants.players_count, self.bucket_count + 1):zero() self.next_round_serialized_range_on_board = self.next_round_extended_range_on_board:view(-1, self.bucket_count + 1) self.next_round_inputs_on_board = arguments.Tensor(self.batch_size, (self.bucket_count * constants.players_count + 1)):zero() self.next_round_values_on_board = arguments.Tensor(self.batch_size, constants.players_count, self.bucket_count):zero() -- copy pot features over self.next_round_inputs_on_board[{{}, {-1}}]:copy(self.next_round_inputs[{{},{-1}}]) end --computing bucket range in next street for both players at once self:_card_range_to_bucket_range_aux( ranges:view(self.batch_size * constants.players_count, -1), self.next_round_extended_range:view(self.batch_size * constants.players_count, -1)) self.range_normalization:sum(self.next_round_serialized_range[{{},{1,self.bucket_count_aux}}], 2) local rn_view = self.range_normalization:view(self.batch_size, constants.players_count) for player = 1, constants.players_count do self.value_normalization[{{}, player}]:copy(rn_view[{{}, 3 - player}]) end if use_memory then self:_card_range_to_bucket_range_on_board( next_board_idx, ranges:view(self.batch_size * constants.players_count, -1), self.next_round_extended_range_on_board:view(self.batch_size * constants.players_count, -1)) self.range_normalization_on_board:sum(self.next_round_serialized_range_on_board[{{},{1,self.bucket_count}}], 2) local rnb_view = self.range_normalization_on_board:view(self.batch_size, constants.players_count) for player = 1, constants.players_count do self.value_normalization_on_board[{{}, player}]:copy(rnb_view[{{}, 3 - player}]) end self.range_normalization_memory:add(self.value_normalization_on_board) end --eliminating division by zero self.range_normalization[torch.eq(self.range_normalization, 0)] = 1 self.next_round_serialized_range:cdiv(self.range_normalization:expandAs(self.next_round_serialized_range)) for player = 1, constants.players_count do local player_range_index = {(player -1) * self.bucket_count_aux + 1, player * self.bucket_count_aux} self.next_round_inputs[{{}, player_range_index}]:copy(self.next_round_extended_range[{{},player, {1, self.bucket_count_aux}}]) end --using nn to compute values local serialized_inputs_view= self.next_round_inputs:view(self.batch_size, -1) local serialized_values_view= self.next_round_values:view(self.batch_size, -1) --computing value in the next round self.aux_nn:get_value(serialized_inputs_view, serialized_values_view) if use_memory then --eliminating division by zero self.range_normalization_on_board[torch.eq(self.range_normalization_on_board, 0)] = 1 self.next_round_serialized_range_on_board:cdiv(self.range_normalization_on_board:expandAs(self.next_round_serialized_range_on_board)) for player = 1, constants.players_count do local player_range_index = {(player -1) * self.bucket_count + 1, player * self.bucket_count} self.next_round_inputs_on_board[{{}, player_range_index}]:copy(self.next_round_extended_range_on_board[{{},player, {1, self.bucket_count}}]) end --using nn to compute values local serialized_inputs_view_on_board = self.next_round_inputs_on_board:view(self.batch_size, -1) local serialized_values_view_on_board = self.next_round_values_on_board:view(self.batch_size, -1) --computing value in the next round self.nn:get_value(serialized_inputs_view_on_board, serialized_values_view_on_board) end --normalizing values back according to the orginal range sum local normalization_view = self.value_normalization:view(self.batch_size, constants.players_count, 1) self.next_round_values:cmul(normalization_view:expandAs(self.next_round_values)) if use_memory then local normalization_view_on_board = self.value_normalization_on_board:view(self.batch_size, constants.players_count, 1) self.next_round_values_on_board:cmul(normalization_view_on_board:expandAs(self.next_round_values_on_board)) self.counterfactual_value_memory:add(self.next_round_values_on_board) end --remembering the values for the next round --translating bucket values back to the card values self:_bucket_value_to_card_value_aux( self.next_round_values:view(self.batch_size * constants.players_count, -1), values:view(self.batch_size * constants.players_count, -1)) end --- Gives the predicted counterfactual values at each evaluated state, given -- input ranges. -- -- @{start_computation} must be called first. Each state to be evaluated must -- be given in the same order that pot sizes were given for that function. -- Keeps track of iterations internally, so should be called exactly once for -- every iteration of continual re-solving. -- -- @param ranges An Nx2xK tensor, where N is the number of states evaluated -- (must match input to @{start_computation}), 2 is the number of players, and -- K is the number of private hands. Contains N sets of 2 range vectors. -- @param values an Nx2xK tensor in which to store the N sets of 2 value vectors -- which are output function NextRoundValuePre:get_value(ranges, values) assert(ranges and values) assert(ranges:size(1) == self.batch_size) self.iter = self.iter + 1 print(self.iter) if self.iter == 1 then self.next_round_inputs = arguments.Tensor(self.batch_size, self.board_count, (self.bucket_count * constants.players_count + 1)):zero() self.next_round_values = arguments.Tensor(self.batch_size, self.board_count, constants.players_count, self.bucket_count ):zero() self.transposed_next_round_values = arguments.Tensor(self.batch_size, constants.players_count, self.board_count, self.bucket_count) self.next_round_extended_range = arguments.Tensor(self.batch_size, constants.players_count, self.board_count, self.bucket_count + 1):zero() self.next_round_serialized_range = self.next_round_extended_range:view(-1, self.bucket_count + 1) self.values_per_board = arguments.Tensor(self.batch_size * constants.players_count, self.board_count, game_settings.hand_count) self.range_normalization = arguments.Tensor() self.value_normalization = arguments.Tensor(self.batch_size, constants.players_count, self.board_count) local den = 0 assert(self._street <= 3) if game_settings.nl then den = arguments.stack else if self._street == 4 then den = 48 elseif self._street == 3 then den = 48 elseif self._street == 2 then den = 24 elseif self._street == 1 then den = 10 else den = -1 end end --handling pot feature for the nn local nn_bet_input = self.pot_sizes:clone():mul(1/den) nn_bet_input = nn_bet_input:view(-1, 1):expand(self.batch_size, self.board_count) self.next_round_inputs[{{}, {}, {-1}}]:copy(nn_bet_input) end --computing bucket range in next street for both players at once self:_card_range_to_bucket_range( ranges:view(self.batch_size * constants.players_count, -1), self.next_round_extended_range:view(self.batch_size * constants.players_count, -1)) self.range_normalization:sum(self.next_round_serialized_range[{{},{1,self.bucket_count}}], 2) local rn_view = self.range_normalization:view(self.batch_size, constants.players_count, self.board_count) for player = 1, constants.players_count do self.value_normalization[{{}, player, {}}]:copy(rn_view[{{}, 3 - player, {}}]) end --eliminating division by zero self.range_normalization[torch.eq(self.range_normalization, 0)] = 1 self.next_round_serialized_range:cdiv(self.range_normalization:expandAs(self.next_round_serialized_range)) for player = 1, constants.players_count do local player_range_index = {(player -1) * self.bucket_count + 1, player * self.bucket_count} self.next_round_inputs[{{}, {}, player_range_index}]:copy(self.next_round_extended_range[{{},player, {}, {1, self.bucket_count}}]) end --using nn to compute values local serialized_inputs_view= self.next_round_inputs:view(self.batch_size * self.board_count, -1) local serialized_values_view= self.next_round_values:view(self.batch_size * self.board_count, -1) --computing value in the next round self.nn:get_value(serialized_inputs_view, serialized_values_view) --normalizing values back according to the orginal range sum local normalization_view = self.value_normalization:view(self.batch_size, constants.players_count, self.board_count, 1):transpose(2,3) self.next_round_values:cmul(normalization_view:expandAs(self.next_round_values)) self.transposed_next_round_values:copy(self.next_round_values:transpose(3,2)) --remembering the values for the next round --translating bucket values back to the card values self:_bucket_value_to_card_value( self.transposed_next_round_values:view(self.batch_size * constants.players_count, -1), values:view(self.batch_size * constants.players_count, -1)) end --- Gives the average counterfactual values on the given board across previous -- calls to @{get_value}. -- -- Used to update opponent counterfactual values during re-solving after board -- cards are dealt. -- @param board a non-empty vector of board cards -- @param values a tensor in which to store the values function NextRoundValuePre:get_value_on_board(board, values) --check if we have evaluated correct number of iterations assert(self.iter == arguments.cfr_iters ) local batch_size = values:size(1) assert(batch_size == self.batch_size) self.range_normalization_memory[torch.eq(self.range_normalization_memory, 0)] = 1 local serialized_memory_view = self.counterfactual_value_memory:view(-1, self.bucket_count) serialized_memory_view:cdiv(self.range_normalization_memory:expandAs(serialized_memory_view)) self:_bucket_value_to_card_value_on_board( board, self.counterfactual_value_memory:view(self.batch_size * constants.players_count, -1), values:view(self.batch_size * constants.players_count, -1)) end ================================================ FILE: Source/Nn/next_round_value_test.lua ================================================ require 'Nn.next_round_value' --require 'Nn.mock_nn' require 'Nn.mock_nn_terminal' require 'TerminalEquity.terminal_equity' require 'Nn.value_nn' local arguments = require 'Settings.arguments' local game_settings = require 'Settings.game_settings' local card_to_string = require 'Game.card_to_string_conversion' local card_tools = require 'Game.card_tools' --local next_round_value = NextRoundValue() --print(next_round_value._range_matrix) --[[ test of card to bucket range translation local range = torch.range(1, 6):float():view(1, -1) local next_round_range = arguments.Tensor(1, next_round_value.bucket_count * next_round_value.board_count) next_round_value:_card_range_to_bucket_range(range, next_round_range) print(next_round_range) ]] --test of get_value functionality local mock_nn = MockNnTerminal() --local mock_nn = ValueNn() local next_round_value = NextRoundValue(mock_nn) --local bets = torch.range(1,1):float():mul(100) local bets = torch.Tensor(1):fill(1200) next_round_value:start_computation(bets) local ranges = arguments.Tensor(1, 2, game_settings.card_count):fill(1/4) local values = arguments.Tensor(1, 2, game_settings.card_count) local x = arguments.Tensor() torch.manualSeed(0) ranges[1][1]:copy(torch.Tensor({1,1,0,0,0,0})) ranges[1][2]:copy(torch.Tensor({1,1,1,1,1,1})) next_round_value:get_value(ranges, values) print(values) --[[ local ranges_2 = ranges:view(2, game_settings.card_count):clone() local values_2 = ranges_2:clone():fill(-1) local terminal_equity = TerminalEquity() terminal_equity:set_board(torch.Tensor{}) terminal_equity:call_value(ranges_2, values_2) print('terminal_equity') print(values_2) ]] --[[ local board = card_to_string:string_to_board('Ks') local values_3 = values:clone():fill(-1) next_round_value:get_value_on_board(board, values_3) print(values_3) ]] ================================================ FILE: Source/Nn/value_nn.lua ================================================ --- Wraps the calls to the final neural net. -- @classmod value_nn require 'torch' require 'nn' local arguments = require 'Settings.arguments' local game_settings = require 'Settings.game_settings' local ValueNn = torch.class('ValueNn') --- Constructor. Loads the neural net from disk. function ValueNn:__init(street, aux) local net_file = arguments.model_path if game_settings.nl then net_file = net_file .. "NoLimit/" else net_file = net_file .. "Limit/" end assert(street <= 3) if aux then assert(street == 1) net_file = net_file .. "preflop-aux/" else if street == 3 then net_file = net_file .. "river/" elseif street == 2 then net_file = net_file .. "turn/" elseif street == 1 then net_file = net_file .. "flop/" end end net_file = net_file .. arguments.value_net_name --0.0 select the correct model cpu/gpu if arguments.gpu then net_file = net_file .. '_gpu' else net_file = net_file .. '_cpu' end --1.0 load model information local model_information = torch.load(net_file .. '.info') print('NN information:') for k, v in pairs(model_information) do print(k, v) end --import GPU modules only if needed if arguments.gpu then require 'cunn' require 'cutorch' end --2.0 try to load model if pcall(torch.load, net_file .. '.model') then self.mlp = torch.load(net_file .. '.model') self.mlp:evaluate(true) print('NN architecture:') print(self.mlp) end end --- Gives the neural net output for a batch of inputs. -- @param inputs An NxI tensor containing N instances of neural net inputs. -- See @{net_builder} for details of each input. -- @param output An NxO tensor in which to store N sets of neural net outputs. -- See @{net_builder} for details of each output. function ValueNn:get_value(inputs, output) output:copy(self.mlp:forward(inputs)) end ================================================ FILE: Source/Player/continual_resolving.lua ================================================ --- Performs the main steps of continual re-solving, tracking player range -- and opponent counterfactual values so that re-solving can be done at each -- new game state. -- @classmod continual_resolving require 'Lookahead.resolving' require 'TerminalEquity.terminal_equity' local arguments = require 'Settings.arguments' local constants = require "Settings.constants" local game_settings = require 'Settings.game_settings' local card_tools = require 'Game.card_tools' local card_to_string = require 'Game.card_to_string_conversion' local ContinualResolving = torch.class('ContinualResolving') --- Constructor. Does a depth-limited solve of the game's first node. function ContinualResolving:__init() self.starting_player_range = card_tools:get_uniform_range(arguments.Tensor{}) self.terminal_equity = TerminalEquity() self:resolve_first_node() end --- Solves a depth-limited lookahead from the first node of the game to get -- opponent counterfactual values. -- -- The cfvs are stored in the field `starting_cfvs_p1`. Because this is the -- first node of the game, exact ranges are known for both players, so -- opponent cfvs are not necessary for solving. function ContinualResolving:resolve_first_node() local first_node = {} first_node.board = arguments.Tensor{} first_node.street = 1 first_node.current_player = constants.players.P1 first_node.bets = arguments.Tensor{arguments.sb, arguments.bb} first_node.num_bets = 1 self.terminal_equity:set_board(first_node.board) --create the starting ranges local player_range = card_tools:get_uniform_range(first_node.board) local opponent_range = card_tools:get_uniform_range(first_node.board) --create re-solving and re-solve the first node self.first_node_resolving = Resolving(self.terminal_equity) self.first_node_resolving:resolve_first_node(first_node, player_range, opponent_range) --store the initial CFVs self.starting_cfvs_p1 = self.first_node_resolving:get_root_cfv() end --- Re-initializes the continual re-solving to start a new game from the root -- of the game tree. -- @param state the first state where the re-solving player acts in the new -- game (a table of the type returned by @{protocol_to_node.parse_state}) function ContinualResolving:start_new_hand(state) self.last_node = nil self.decision_id = 0 self.position = state.position self.hand_id = state.hand_id end --- Re-solves a node to choose the re-solving player's next action. -- @param node the game node where the re-solving player is to act (a table of -- the type returned by @{protocol_to_node.parsed_state_to_node}) -- @param state the game state where the re-solving player is to act -- (a table of the type returned by @{protocol_to_node.parse_state}) -- @local function ContinualResolving:_resolve_node(node, state) assert(self.decision_id) --1.0 first node and P1 position --no need to update an invariant since this is the very first situation if self.decision_id == 0 and self.position == constants.players.P1 then --the strategy computation for the first decision node has been already set up self.current_player_range = self.starting_player_range:clone() self.resolving = self.first_node_resolving --2.0 other nodes - we need to update the invariant else assert(not node.terminal) assert(node.current_player == self.position) --2.1 update the invariant based on actions we did not make self:_update_invariant(node, state) local timer = torch.Timer() timer:reset() --2.2 re-solve self.terminal_equity:set_board(node.board) print('term equity time: ' .. timer:time().real) timer:reset() self.resolving = Resolving(self.terminal_equity) self.resolving:resolve(node, self.current_player_range, self.current_opponent_cfvs_bound) end end --- Updates the player's range and the opponent's counterfactual values to be -- consistent with game actions since the last re-solved state. -- Updates it only for actions we did not make, since we update the invariant for our action as soon as we make it. -- -- @param node the game node where the re-solving player is to act (a table of -- the type returned by @{protocol_to_node.parsed_state_to_node}) -- @param state the game state where the re-solving player is to act -- (a table of the type returned by @{protocol_to_node.parse_state}) -- @local function ContinualResolving:_update_invariant(node, state) --1.0 street has changed if self.last_node and self.last_node.street ~= node.street then assert(self.last_node.street + 1 == node.street) --1.1 opponent cfvs --if the street has changed, the resonstruction API simply gives us CFVs self.current_opponent_cfvs_bound = self.resolving:get_chance_action_cfv(self.last_bet, node.board) --1.2 player range --if street has change, we have to mask out the colliding hands self.current_player_range = card_tools:normalize_range(node.board, self.current_player_range) --2.0 first decision for P2 elseif self.decision_id == 0 then assert(self.position == constants.players.P2) assert(node.street == 1) self.current_player_range = self.starting_player_range:clone() self.current_opponent_cfvs_bound = self.starting_cfvs_p1:clone() --3.0 handle game within the street else assert(self.last_node.street == node.street) end end --- Re-solves a node and chooses the re-solving player's next action. -- @param node the game node where the re-solving player is to act (a table of -- the type returned by @{protocol_to_node.parsed_state_to_node}) -- @param state the game state where the re-solving player is to act -- (a table of the type returned by @{protocol_to_node.parse_state}) -- @return an action sampled from the re-solved strategy at the given state, -- with the fields: -- -- * `action`: an element of @{constants.acpc_actions} -- -- * `raise_amount`: the number of chips to raise (if `action` is raise) function ContinualResolving:compute_action(node, state) self:_resolve_node(node, state) local sampled_bet = self:_sample_bet(node, state) self.decision_id = self.decision_id + 1 self.last_bet = sampled_bet self.last_node = node local out = self:_bet_to_action(node, sampled_bet) return out end --- Samples an action to take from the strategy at the given game state. -- @param node the game node where the re-solving player is to act (a table of -- the type returned by @{protocol_to_node.parsed_state_to_node}) -- @param state the game state where the re-solving player is to act -- (a table of the type returned by @{protocol_to_node.parse_state}) -- @return an index representing the action chosen -- @local function ContinualResolving:_sample_bet(node, state) --1.0 get the possible bets in the node local possible_bets = self.resolving:get_possible_actions() local actions_count = possible_bets:size(1) --2.0 get the strategy for the current hand since the strategy is computed for all hands local hand_strategy = arguments.Tensor(actions_count) for i = 1, actions_count do local action_bet = possible_bets[i] local action_strategy = self.resolving:get_action_strategy(action_bet) hand_strategy[i] = action_strategy[self.hand_id] end assert(math.abs(1 - hand_strategy:sum()) < 0.001) print("strategy:") print(hand_strategy) --3.0 sample the action by doing cumsum and uniform sample local hand_strategy_cumsum = torch.cumsum(hand_strategy) local r = torch.uniform() local sampled_bet = possible_bets[hand_strategy_cumsum:gt(r)][1] print("playing action that has prob: " .. hand_strategy[hand_strategy_cumsum:gt(r)][1]) --4.0 update the invariants based on our action self.current_opponent_cfvs_bound = self.resolving:get_action_cfv(sampled_bet) local strategy = self.resolving:get_action_strategy(sampled_bet) self.current_player_range:cmul(strategy) self.current_player_range = card_tools:normalize_range(node.board, self.current_player_range) return sampled_bet end --- Converts an internal action representation into a cleaner format. -- @param node the game node where the re-solving player is to act (a table of -- the type returned by @{protocol_to_node.parsed_state_to_node}) -- @param sampled_bet the index of the action to convert -- @return a table specifying the action, with the fields: -- -- * `action`: an element of @{constants.acpc_actions} -- -- * `raise_amount`: the number of chips to raise (if `action` is raise) -- @local function ContinualResolving:_bet_to_action(node, sampled_bet) if sampled_bet == constants.actions.fold then return {action = constants.acpc_actions.fold} elseif sampled_bet == constants.actions.ccall then return {action = constants.acpc_actions.ccall} else assert(sampled_bet >= 0) return {action = constants.acpc_actions.raise, raise_amount = sampled_bet} end end ================================================ FILE: Source/Player/deepstack.lua ================================================ --- Performs the main loop for DeepStack. -- @script deepstack local arguments = require 'Settings.arguments' require "ACPC.acpc_game" require "Player.continual_resolving" local port = 0 if #arg > 0 then port = tonumber(arg[1]) else print("need port") return end torch.manualSeed(0) --1.0 create the ACPC game and connect to the server local acpc_game = ACPCGame() acpc_game:connect(arguments.acpc_server, port) local continual_resolving = ContinualResolving() local last_state = nil local last_node = nil --2.0 main loop that waits for a situation where we act and then chooses an action while true do local state local node --2.1 blocks until it's our situation/turn state, node = acpc_game:get_next_situation() --did a new hand start? if not last_state or last_state.hand_number ~= state.hand_number or node.street < last_node.street then continual_resolving:start_new_hand(state) end --2.2 use continual resolving to find a strategy and make an action in the current node local adviced_action = continual_resolving:compute_action(node, state) --2.3 send the action to the dealer acpc_game:play_action(adviced_action) last_state = state last_node = node collectgarbage();collectgarbage() end ================================================ FILE: Source/Player/deepstack_server.lua ================================================ --- Performs the main loop for DeepStack. -- @script deepstack local arguments = require 'Settings.arguments' local socket = require("socket") local constants = require "Settings.constants" require "ACPC.acpc_game" require "Player.continual_resolving" --1.0 create the ACPC game and connect to the server local acpc_game = ACPCGame() local continual_resolving = ContinualResolving() local last_state = nil local last_node = nil -- load namespace -- create a TCP socket and bind it to the local host, at any port local server = assert(socket.bind("*", 0)) local ip, port = server:getsockname() print(ip .. ": " .. port) local client = server:accept() print("accepted client") while 1 do local line, err = client:receive() -- if there was no error, send it back to the client if not err then print(line) else print(err) end local state local node --2.1 blocks until it's our situation/turn state, node = acpc_game:string_to_statenode(line) --did a new hand start? if not last_state or last_state.hand_number ~= state.hand_number or node.street < last_node.street then continual_resolving:start_new_hand(state) end --2.2 use continual resolving to find a strategy and make an action in the current node local adviced_action = continual_resolving:compute_action(node, state) local action_id = adviced_action["action"] local betsize = adviced_action["raise_amount"] print(action_id) print(betsize) if betsize ~= nil then client:send(tostring(betsize)) elseif action_id == constants.acpc_actions.fold then client:send("f") elseif action_id == constants.acpc_actions.ccall then client:send("c") else client:send("WTF") end last_state = state last_node = node collectgarbage();collectgarbage() end ================================================ FILE: Source/Player/manual_player.lua ================================================ local arguments = require 'Settings.arguments' local constants = require "Settings.constants" require "ACPC.acpc_game" --1.0 create the ACPC game and connect to the server local port = 0 if #arg > 0 then port = tonumber(arg[1]) else print("need port") return end local acpc_game = ACPCGame() acpc_game:connect(arguments.acpc_server, port) --2.0 main loop that waits for a situation where we act and then chooses an action while true do local state local node --2.1 blocks until it's our situation/turn state, node = acpc_game:get_next_situation() --print(state) --io.read() --print(node) --io.read() print("input action:") local action = io.read() local acpc_action = nil if action == "f" then acpc_action = {action = constants.acpc_actions.fold} elseif action == "c" then acpc_action = {action = constants.acpc_actions.ccall} else local amount = tonumber(action) acpc_action = {action = constants.acpc_actions.raise, raise_amount = amount} end --2.3 send the action to the dealer acpc_game:play_action(acpc_action) collectgarbage();collectgarbage() end ================================================ FILE: Source/Player/slum_util.py ================================================ def acpcify_board(board): if len(board) == 6: return board if len(board) == 8: return board[:6] + "/" + board[6:] if len(board) == 10: return board[:6] + "/" + board[6:8] + "/" + board[8:] return "WTF" def acpcify_actions(actions): actions = actions.replace("b","r") actions = actions.replace("k","c") streets = actions.split("/") max_bet = 0 for i, street_actions in enumerate(streets): bets = street_actions.split("r") max_street_bet = max_bet for j, betstr in enumerate(bets): try: flag = False if len(betstr) > 1 and betstr[-1] == 'c': flag = True betstr = betstr.replace("c","") bet = int(betstr) bet += max_bet max_street_bet = max(max_street_bet, bet) bets[j] = str(bet) if flag: bets[j] += "c" bets[j] = "r" + bets[j] except ValueError: continue max_bet = max_street_bet if max_bet == 0: max_bet = 100 good_string = "".join(bets) streets[i] = good_string return "/".join(streets), max_bet ================================================ FILE: Source/Player/slumbot_player.py ================================================ from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.webdriver.common.desired_capabilities import DesiredCapabilities import os import sys import time import slum_util import socket if len(sys.argv) < 3: print("missing address port") sys.exit() client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect((sys.argv[1], int(sys.argv[2]))) chrome_options = Options() #chrome_options.add_argument("--headless") chrome_options.binary_location = '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary' d = DesiredCapabilities.CHROME d['loggingPrefs'] = { 'browser':'ALL' } driver = webdriver.Chrome(executable_path="/Users/lawson/Downloads/chromedriver", chrome_options=chrome_options, desired_capabilities=d) driver.get("http://slumbot.com") time.sleep(2) response_fun = """ response = function(data) { global_data = data // We increment actionindex even when we get an error message back. // If we didn't, then when we retried the action that triggered the error, // it would get flagged as a duplicate. ++actionindex; if ("errormsg" in data) { var errormsg = data["errormsg"]; $("#msg").text(errormsg); // Some errors end the hand (e.g., server timeout) // Would it be cleaner to treat a server timeout like a client // timeout? Return msg rather than errormsg? if ("hip" in data) { handinprogress = (data["hip"] === 1); } // Need this for server timeout. Want to enable the "Next Hand" // button and disable all the other buttons. enableactions(); return; } else if ("msg" in data) { var msg = data["msg"]; $("#msg").text(msg); } else { $("#msg").text(""); } if (actiontype === 1) { addourcheck(); } else if (actiontype === 2) { addourcall(); } else if (actiontype === 3) { addourfold(); } else if (actiontype === 4) { addourbet(); } $("#betsize").val(""); potsize = data["ps"]; ourbet = data["ourb"]; oppbet = data["oppb"]; var lastcurrentaction = currentaction; currentaction = data["action"]; var actiondisplay = currentaction; $("#currentaction").text(actiondisplay); var overlap = currentaction.substring(0, lastcurrentaction.length); if (overlap !== lastcurrentaction) { console.log("Overlap " + overlap); console.log("Last current action " + lastcurrentaction); } else { var newaction = currentaction.substring(lastcurrentaction.length, currentaction.length); oppactionmessage(newaction); } parsedata(data); drawall(aftershowdown); lifetimetotal = data["ltotal"]; lifetimeconf = data["lconf"]; lifetimebaselinetotal = data["lbtotal"]; lifetimebaselineconf = data["lbconf"]; numlifetimehands = data["lhands"]; showdowntotal = data["sdtotal"]; showdownconf = data["sdconf"]; numshowdownhands = data["sdhands"]; blbshowdowntotal = data["blbsdtotal"]; blbshowdownconf = data["blbsdconf"]; blbnumshowdownhands = data["blbsdhands"]; clbshowdowntotal = data["clbsdtotal"]; clbshowdownconf = data["clbsdconf"]; clbnumshowdownhands = data["clbsdhands"]; if (username !== "") displaystats(); if (! handinprogress) { sessiontotal = data["stotal"]; $("#sessiontotal").text(sessiontotal); numsessionhands = data["shands"]; $("#numhands").text(numsessionhands); var outcome = data["outcome"]; if (outcome > 0) { $("#outcome").text("You won a pot of " + outcome + "!"); } else if (outcome < 0) { $("#outcome").text("Slumbot won a pot of " + -outcome + "!"); } else { $("#outcome").text("You chopped!"); } } else { $("#outcome").text(""); starttimer(); } enableactions(); }; """ driver.execute_script(response_fun) hand_no = 0 while True: nexthand_button = WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.ID, "nexthand"))) nexthand_button.click() hand_no += 1 position = 0 while True: fold_button = driver.find_element_by_id("fold") nexthand_button = driver.find_element_by_id("nexthand") action_td = driver.find_element_by_id("currentaction") if fold_button.is_displayed() and fold_button.is_enabled(): if action_td.text: position = 1 break if nexthand_button.is_displayed() and nexthand_button.is_enabled(): break time.sleep(1) # new hand while True: nexthand_button = driver.find_element_by_id("nexthand") if nexthand_button.is_displayed() and nexthand_button.is_enabled(): break hole = driver.execute_script("return global_data[\"holes\"]") actions = driver.execute_script("return global_data[\"action\"]") board = driver.execute_script("return global_data[\"board\"]") fold_button = driver.find_element_by_id("fold") call_button = driver.find_element_by_id("call") check_button = driver.find_element_by_id("check") halfpot_button = driver.find_element_by_id("halfpot") pot_button = driver.find_element_by_id("pot") allin_button = driver.find_element_by_id("allin") while True: if (call_button.is_displayed() and call_button.is_enabled()) or (allin_button.is_displayed() and allin_button.is_enabled()): break time.sleep(1) # ready to make the action actions, max_bet = slum_util.acpcify_actions(actions) msg = "MATCHSTATE:" + str(position) + ":" + str(hand_no) +":" + actions + ":" if position == 0: msg += hole + "|" elif position == 1: msg += "|" + hole if len(board) > 0: msg += "/" + slum_util.acpcify_board(board) msg += "\n" client_socket.send(msg) sys.stdout.write("sent " + msg + ":") advice = client_socket.recv(100) # click button print(advice) if advice == "c": if call_button.is_displayed() and call_button.is_enabled(): call_button.click() elif check_button.is_displayed() and check_button.is_enabled(): check_button.click() elif advice == "f": fold_button.click() elif advice == "20000": allin_button.click() elif advice == str(max_bet*3): pot_button.click() else: halfpot_button.click() while True: time.sleep(1) if (call_button.is_displayed() and call_button.is_enabled()) or (allin_button.is_displayed() and allin_button.is_enabled()): break if nexthand_button.is_displayed() and nexthand_button.is_enabled(): break #signup_button = driver.find_element_by_id("login_trigger") #signup_button.click() ================================================ FILE: Source/Settings/arguments.lua ================================================ --- Parameters for DeepStack. --@module arguments local torch = require 'torch' torch.setdefaulttensortype('torch.FloatTensor') local params = {} --- whether to run on GPU params.gpu = false --- list of pot-scaled bet sizes to use in tree -- @field params.bet_sizing params.bet_sizing = {{1},{1},{1}} --- server running the ACPC dealer params.acpc_server = "localhost" --- server port running the ACPC dealer params.acpc_server_port = 16177 --- the tensor datatype used for storing DeepStack's internal data params.Tensor = torch.FloatTensor --- the directory for data files params.data_directory = '../Data/' --- the size of the game's ante, in chips params.ante = 100 params.sb = 50 params.bb = 100 --- the size of each player's stack, in chips params.stack = 20000 --- the number of iterations that DeepStack runs CFR for params.cfr_iters = 1000 --- the number of preliminary CFR iterations which DeepStack doesn't factor into the average strategy (included in cfr_iters) params.cfr_skip_iters = 500 --- how many poker situations are solved simultaneously during data generation params.gen_batch_size = 10 --- how many poker situations are used in each neural net training batch params.train_batch_size = 1000 --- path to the solved poker situation data used to train the neural net params.data_path = '../Data/TrainSamples/' --- path to the neural net model params.model_path = '../Data/Models/' --- the name of the neural net file params.value_net_name = 'final' --- the neural net architecture params.net = '{nn.Linear(input_size, 500), nn.BatchNormalization(500), nn.PReLU(), nn.Linear(500, 500), nn.BatchNormalization(500), nn.PReLU(), nn.Linear(500, 500), nn.BatchNormalization(500), nn.PReLU(), nn.Linear(500, output_size)}' --- how often to save the model during training params.save_epoch = 1 --- how many epochs to train for params.epoch_count = 200 --- how many solved poker situations are generated for use as training examples params.train_data_count = 150000 --- how many solved poker situations are generated for use as validation examples params.valid_data_count = 150000 --- learning rate for neural net training params.learning_rate = 0.001 assert(params.cfr_iters > params.cfr_skip_iters) if params.gpu then require 'cutorch' params.Tensor = torch.CudaTensor end return params ================================================ FILE: Source/Settings/constants.lua ================================================ --- Various constants used in DeepStack. -- @module constants local constants = {} --- the number of players in the game constants.players_count = 2 --- the number of betting rounds in the game constants.streets_count = 4 --- IDs for each player and chance -- @field chance `0` -- @field P1 `1` -- @field P2 `2` constants.players = {} constants.players.chance = 0 constants.players.P1 = 1 constants.players.P2 = 2 --- IDs for terminal nodes (either after a fold or call action) and nodes that follow a check action -- @field terminal_fold (terminal node following fold) `-2` -- @field terminal_call (terminal node following call) `-1` -- @field chance_node (node for the chance player) `0` -- @field check (node following check) `-1` -- @field inner_node (any other node) `1` constants.node_types = {} constants.node_types.terminal_fold = -2 constants.node_types.terminal_call = -1 constants.node_types.check = -1 constants.node_types.chance_node = 0 constants.node_types.inner_node = 1 --- IDs for fold and check/call actions -- @field fold `-2` -- @field ccall (check/call) `-1` constants.actions = {} constants.actions.fold = -2 constants.actions.ccall = -1 constants.actions.raise = -3 --- String representations for actions in the ACPC protocol -- @field fold "`fold`" -- @field ccall (check/call) "`ccall`" -- @field raise "`raise`" constants.acpc_actions = {} constants.acpc_actions.fold = "fold" constants.acpc_actions.ccall = "ccall" constants.acpc_actions.raise = "raise" return constants ================================================ FILE: Source/Settings/game_settings.lua ================================================ --- Game constants which define the game played by DeepStack. -- @module game_settings require 'torch' local tools = require 'tools' --leduc defintion local M = {} --- the number of card suits in the deck M.suit_count = 4 --- the number of card ranks in the deck M.rank_count = 13 --- the total number of cards in the deck M.card_count = M.suit_count * M.rank_count --- the number of public cards dealt in the game (revealed after the first -- betting round) M.hand_card_count = 2 M.hand_count = tools:choose(M.card_count, M.hand_card_count) M.board_card_count = {0, 3, 4, 5} M.limit_bet_sizes = {2, 2, 4, 4} M.limit_bet_cap = 4 M.nl = true return M ================================================ FILE: Source/TerminalEquity/terminal_equity.lua ================================================ --- Evaluates player equities at terminal nodes of the game's public tree. -- @classmod terminal_equity require 'torch' local evaluator = require 'Game.Evaluation.evaluator' local game_settings = require 'Settings.game_settings' local arguments = require 'Settings.arguments' local card_tools = require 'Game.card_tools' local constants = require 'Settings.constants' local card_to_string = require 'Game.card_to_string_conversion' local tools = require 'tools' local TerminalEquity = torch.class('TerminalEquity') --- Constructor function TerminalEquity:__init() self._block_matrix = arguments.Tensor(game_settings.hand_count, game_settings.hand_count):fill(1); if game_settings.hand_card_count == 2 then for i = 1, game_settings.card_count do for j = i+1, game_settings.card_count do local idx1 = card_tools:get_hole_index({i,j}) for k = 1, game_settings.card_count do for l = k+1, game_settings.card_count do local idx2 = card_tools:get_hole_index({k,l}) if i == k or i == l or j == k or j == l then self._block_matrix[idx1][idx2] = 0 self._block_matrix[idx2][idx1] = 0 end end end end end end self.matrix_mem = arguments.Tensor() if self._pf_equity == nil then local f = assert(io.open("./TerminalEquity/pf_equity.dat", "rb")) local data = f:read("*all") self._pf_equity = arguments.Tensor(game_settings.hand_count,game_settings.hand_count):fill(0) assert(string.len(data) == 1326*1326*4, 'bad length') local byteidx = 1 for i = 1, 1326 do for j = 1, 1326 do local num = 0 for k = 0,3 do num = num + data:byte(byteidx) * (2 ^ (k * 8)) byteidx = byteidx + 1 end num = (num > 2147483647) and (num - 4294967296) or num -- negative because of how equity matrix is set up self._pf_equity[i][j] = -num / 1712304 end end f:close() end self.batch_size = 10 end --- Constructs the matrix that turns player ranges into showdown equity. -- -- Gives the matrix `A` such that for player ranges `x` and `y`, `x'Ay` is the equity -- for the first player when no player folds. -- -- @param board_cards a non-empty vector of board cards -- @param call_matrix a tensor where the computed matrix is stored -- @local function TerminalEquity:get_last_round_call_matrix(board_cards, call_matrix) assert(board_cards:dim() == 0 or board_cards:size(1) == 1 or board_cards:size(1) == 2 or board_cards:size(1) == 5, 'Only Leduc, extended Leduc, and Texas Holdem are supported ' .. board_cards:size(1)) local strength = evaluator:batch_eval_fast(board_cards) --handling hand stregths (winning probs); local strength_view_1 = strength:view(game_settings.hand_count, 1):expandAs(call_matrix) local strength_view_2 = strength:view(1, game_settings.hand_count):expandAs(call_matrix) call_matrix:copy(torch.gt(strength_view_1, strength_view_2)) call_matrix:csub(torch.lt(strength_view_1, strength_view_2):typeAs(call_matrix)) self:_handle_blocking_cards(call_matrix, board_cards); end --- Constructs the matrix that turns player ranges into showdown equity. -- -- Gives the matrix `A` such that for player ranges `x` and `y`, `x'Ay` is the equity -- for the first player when no player folds. -- -- @param board_cards a non-empty vector of board cards -- @param call_matrix a tensor where the computed matrix is stored -- @local function TerminalEquity:get_inner_call_matrix(board_cards, call_matrix) assert(board_cards:dim() == 0 or board_cards:size(2) == 1 or board_cards:size(2) == 2 or board_cards:size(2) == 5, 'Only Leduc, extended Leduc, and Texas Holdem are supported ' .. board_cards:size(2)) local strength = evaluator:batch_eval_fast(board_cards) local num_boards = board_cards:size(1) --handling hand stregths (winning probs); local strength_view_1 = strength:view(num_boards, game_settings.hand_count, 1):expand(num_boards, game_settings.hand_count, game_settings.hand_count) local strength_view_2 = strength:view(num_boards, 1, game_settings.hand_count):expandAs(strength_view_1) local possible_mask = torch.lt(strength,0):typeAs(call_matrix) for i = 1, num_boards, self.batch_size do local indices = {i, i + self.batch_size - 1} local sz = self.batch_size if i + self.batch_size - 1 > num_boards then indices = {i, num_boards} sz = num_boards - i + 1 end self.matrix_mem[{{1, sz}}]:copy(torch.gt(strength_view_1[{indices}], strength_view_2[{indices}])) self.matrix_mem[{{1, sz}}]:cmul(possible_mask[{indices}]:view(sz, 1, game_settings.hand_count):expand(sz, game_settings.hand_count, game_settings.hand_count)) self.matrix_mem[{{1, sz}}]:cmul(possible_mask[{indices}]:view(sz, game_settings.hand_count, 1):expand(sz, game_settings.hand_count, game_settings.hand_count)) call_matrix:add(torch.sum(self.matrix_mem[{{1,sz}}],1)) self.matrix_mem[{{1, sz}}]:copy(torch.lt(strength_view_1[{indices}], strength_view_2[{indices}])) self.matrix_mem[{{1, sz}}]:cmul(possible_mask[{indices}]:view(sz, 1, game_settings.hand_count):expand(sz, game_settings.hand_count, game_settings.hand_count)) self.matrix_mem[{{1, sz}}]:cmul(possible_mask[{indices}]:view(sz, game_settings.hand_count, 1):expand(sz, game_settings.hand_count, game_settings.hand_count)) call_matrix:csub(torch.sum(self.matrix_mem[{{1,sz}}],1)) end self:_handle_blocking_cards(call_matrix, board_cards); end --- Zeroes entries in an equity matrix that correspond to invalid hands. -- -- A hand is invalid if it shares any cards with the board. -- -- @param equity_matrix the matrix to modify -- @param board a possibly empty vector of board cards -- @local function TerminalEquity:_handle_blocking_cards(equity_matrix, board) local possible_hand_indexes = card_tools:get_possible_hand_indexes(board); local possible_hand_matrix = possible_hand_indexes:view(1, game_settings.hand_count):expandAs(equity_matrix); equity_matrix:cmul(possible_hand_matrix); possible_hand_matrix = possible_hand_indexes:view(game_settings.hand_count,1):expandAs(equity_matrix); equity_matrix:cmul(possible_hand_matrix); if game_settings.hand_card_count == 2 then equity_matrix:cmul(self._block_matrix) elseif game_settings.hand_card_count == 1 then for i = 1, game_settings.card_count do equity_matrix[i][i] = 0 end end end --- Sets the evaluator's fold matrix, which gives the equity for terminal -- nodes where one player has folded. -- -- Creates the matrix `B` such that for player ranges `x` and `y`, `x'By` is the equity -- for the player who doesn't fold -- @param board a possibly empty vector of board cards -- @local function TerminalEquity:_set_fold_matrix(board) self.fold_matrix = arguments.Tensor(game_settings.hand_count, game_settings.hand_count); self.fold_matrix:fill(1); --setting cards that block each other to zero self:_handle_blocking_cards(self.fold_matrix, board); end --- Sets the evaluator's call matrix, which gives the equity for terminal -- nodes where no player has folded. -- -- For nodes in the last betting round, creates the matrix `A` such that for player ranges -- `x` and `y`, `x'Ay` is the equity for the first player when no player folds. For nodes -- in the first betting round, gives the weighted average of all such possible matrices. -- -- @param board a possibly empty vector of board cards -- @local -- TODO finish this function TerminalEquity:_set_call_matrix(board) local street = card_tools:board_to_street(board); self.equity_matrix = arguments.Tensor(game_settings.hand_count, game_settings.hand_count):zero(); if street == constants.streets_count then --for last round we just return the matrix self:get_last_round_call_matrix(board, self.equity_matrix); elseif street == 3 or street == 2 then --iterate through all possible next round streets --TODO(go to the last street) local next_round_boards = card_tools:get_last_round_boards(board); -- assert(false, 'hey') local boards_count = next_round_boards:size(1); if self.matrix_mem:dim() ~= 3 or self.matrix_mem:size(2) ~= game_settings.hand_count or self.matrix_mem:size(3) ~= game_settings.hand_count then self.matrix_mem = arguments.Tensor(self.batch_size, game_settings.hand_count, game_settings.hand_count) end self:get_inner_call_matrix(next_round_boards, self.equity_matrix) --averaging the values in the call matrix local cards_to_come = game_settings.board_card_count[constants.streets_count] - game_settings.board_card_count[street] local cards_left = game_settings.card_count - game_settings.hand_card_count*2 - game_settings.board_card_count[street] local den = tools:choose(cards_left, cards_to_come) local weight_constant = 1/den self.equity_matrix:mul(weight_constant); elseif street == 1 then self.equity_matrix:copy(self._pf_equity) else --impossible street assert(false, 'impossible street ' .. street); end end function TerminalEquity:get_hand_strengths() local a = arguments.Tensor(1, game_settings.hand_count):fill(1) return torch.mm(a,self.equity_matrix) end --- Sets the board cards for the evaluator and creates its internal data structures. -- @param board a possibly empty vector of board cards function TerminalEquity:set_board(board) self.board = board self:_set_call_matrix(board); self:_set_fold_matrix(board); end --- Computes (a batch of) counterfactual values that a player achieves at a terminal node -- where no player has folded. -- -- @{set_board} must be called before this function. -- -- @param ranges a batch of opponent ranges in an NxK tensor, where N is the batch size -- and K is the range size -- @param result a NxK tensor in which to save the cfvs function TerminalEquity:call_value( ranges, result ) result:mm(ranges, self.equity_matrix); end --- Computes (a batch of) counterfactual values that a player achieves at a terminal node -- where a player has folded. -- -- @{set_board} must be called before this function. -- -- @param ranges a batch of opponent ranges in an NxK tensor, where N is the batch size -- and K is the range size -- @param result A NxK tensor in which to save the cfvs. Positive cfvs are returned, and -- must be negated if the player in question folded. function TerminalEquity:fold_value( ranges, result ) result:mm(ranges, self.fold_matrix); end --- Returns the matrix which gives showdown equity for any ranges. -- -- @{set_board} must be called before this function. -- -- @return For nodes in the last betting round, the matrix `A` such that for player ranges -- `x` and `y`, `x'Ay` is the equity for the first player when no player folds. For nodes -- in the first betting round, the weighted average of all such possible matrices. function TerminalEquity:get_call_matrix() return self.equity_matrix end --- Computes the counterfactual values that both players achieve at a terminal node -- where no player has folded. -- -- @{set_board} must be called before this function. -- -- @param ranges a 2xK tensor containing ranges for each player (where K is the range size) -- @param result a 2xK tensor in which to store the cfvs for each player function TerminalEquity:tree_node_call_value( ranges, result ) assert(ranges:dim() == 2) assert(result:dim() == 2) self:call_value(ranges[1]:view(1, -1), result[2]:view(1, -1)) self:call_value(ranges[2]:view(1, -1), result[1]:view(1, -1)) end --- Computes the counterfactual values that both players achieve at a terminal node -- where either player has folded. -- -- @{set_board} must be called before this function. -- -- @param ranges a 2xK tensor containing ranges for each player (where K is the range size) -- @param result a 2xK tensor in which to store the cfvs for each player -- @param folding_player which player folded function TerminalEquity:tree_node_fold_value( ranges, result, folding_player ) assert(ranges:dim() == 2) assert(result:dim() == 2) self:fold_value(ranges[1]:view(1, -1), result[2]:view(1, -1)) self:fold_value(ranges[2]:view(1, -1), result[1]:view(1, -1)) result[folding_player]:mul(-1) end TerminalEquity:__init() --TerminalEquity:set_board(torch.Tensor({48,52,44, 1})) ================================================ FILE: Source/Training/data_stream.lua ================================================ --- Handles the data used for neural net training and validation. -- @classmod data_stream require 'torch' local arguments = require 'Settings.arguments' local game_settings = require 'Settings.game_settings' local bucketer = require 'Nn.bucketer' local constants = require 'Settings.constants' local DataStream = torch.class('DataStream') -- Lua implementation of PHP scandir function function DataStream:_scandir(directory) local i, t, popen = 0, {}, io.popen local pfile = popen('ls -a "'..directory..'"') for filename in pfile:lines() do i = i + 1 t[filename] = 1 end pfile:close() return t end --- Constructor. -- -- Reads the data from training and validation files generated with -- @{data_generation_call.generate_data}. function DataStream:__init(street) self.folder = "xxx/" if street == 4 then self.folder = "river/" elseif street == 3 then self.folder = "turn/" elseif street == 2 then self.folder = "flop/" elseif street == 1 then self.folder = "preflop-aux/" end self.path = arguments.data_path if game_settings.nl then self.path = self.path .. "NoLimit/" else self.path = self.path .. "Limit/" end self.path = self.path .. self.folder filenames = self:_scandir(self.path) local numfiles = 0 local goodfiles = {} for filename,_ in pairs(filenames) do local res = string.find(filename, ".inputs") if res ~= nil then local targetname = filename:sub(0,res) .. "targets" if filenames[targetname] ~= nil then numfiles = numfiles + 1 goodfiles[numfiles] = filename:sub(0,res) end end end self.goodfiles = goodfiles print(numfiles .. " good files") self.bucket_count = bucketer:get_bucket_count(street) self.target_size = self.bucket_count * constants.players_count self.input_size = self.bucket_count * constants.players_count + 1 local num_train = math.floor(numfiles * 0.9) local num_valid = numfiles - num_train local train_count = num_train * arguments.gen_batch_size local valid_count = num_valid * arguments.gen_batch_size self.train_data_count = train_count assert(self.train_data_count >= arguments.train_batch_size, 'Training data count has to be greater than a train batch size!') self.train_batch_count = self.train_data_count / arguments.train_batch_size self.valid_data_count = valid_count assert(self.valid_data_count >= arguments.train_batch_size, 'Validation data count has to be greater than a train batch size!') self.valid_batch_count = self.valid_data_count / arguments.train_batch_size --loading train data --transfering data to gpu if needed end --- Gives the number of batches of validation data. -- -- Batch size is defined by @{arguments.train_batch_size}. -- @return the number of batches function DataStream:get_valid_batch_count() return self.valid_batch_count end --- Gives the number of batches of training data. -- -- Batch size is defined by @{arguments.train_batch_size} -- @return the number of batches function DataStream:get_train_batch_count() return self.train_batch_count end function DataStream:shuffle(tbl, n) for i = n, 1, -1 do local rand = math.random(n) tbl[i], tbl[rand] = tbl[rand], tbl[i] end return tbl end --- Randomizes the order of training data. -- -- Done so that the data is encountered in a different order for each epoch. function DataStream:start_epoch() --data are shuffled each epoch] self:shuffle(self.goodfiles, self.train_data_count / arguments.gen_batch_size) end --- Returns a batch of data from a specified data set. -- @param inputs the inputs set for the given data set -- @param targets the targets set for the given data set -- @param mask the masks set for the given data set -- @param batch_index the index of the batch to return -- @return the inputs set for the batch -- @return the targets set for the batch -- @return the masks set for the batch -- @local function DataStream:get_batch(batch_index) local inputs = arguments.Tensor(arguments.train_batch_size, self.input_size) local targets = arguments.Tensor(arguments.train_batch_size, self.target_size) local masks = arguments.Tensor(arguments.train_batch_size, self.target_size):zero() for i = 1, arguments.train_batch_size / arguments.gen_batch_size do local idx = (batch_index - 1) * arguments.train_batch_size / arguments.gen_batch_size + i idx = math.floor(idx + 0.1) local filebase = self.goodfiles[idx] local inputname = filebase .. "inputs" local targetname = filebase .. "targets" local input_batch = torch.load(self.path .. inputname) local target_batch = torch.load(self.path .. targetname) data_index = {{(i - 1) * arguments.gen_batch_size + 1, i * arguments.gen_batch_size}, {}} inputs[data_index]:copy(input_batch) targets[data_index]:copy(target_batch) masks[data_index][torch.gt(input_batch[{{}, {1, self.bucket_count * 2}}], 0)] = 1 end if arguments.gpu then inputs = inputs:cuda() targets = targets:cuda() masks = masks:cuda() end return inputs, targets, masks end --- Returns a batch of data from the training set. -- @param batch_index the index of the batch to return -- @return the inputs set for the batch -- @return the targets set for the batch -- @return the masks set for the batch function DataStream:get_train_batch(batch_index) return self:get_batch(batch_index) end --- Returns a batch of data from the validation set. -- @param batch_index the index of the batch to return -- @return the inputs set for the batch -- @return the targets set for the batch -- @return the masks set for the batch function DataStream:get_valid_batch(batch_index) return self:get_batch(self.train_batch_count + batch_index) end ================================================ FILE: Source/Training/main_train.lua ================================================ --- Script that trains the neural network. -- -- Uses data previously generated with @{data_generation_call}. -- @script main_train local nnBuilder = require 'Nn.net_builder' require 'Training.data_stream' local train = require 'Training.train' local arguments = require 'Settings.arguments' if #arg == 0 then print("Please specify the street. 1 = preflop, 4 = river") return end local street = tonumber(arg[1]) local network = nnBuilder:build_net(street) if arguments.gpu then network = network:cuda() end local data_stream = DataStream(street) train:train(network, data_stream, arguments.epoch_count) ================================================ FILE: Source/Training/raw_converter.lua ================================================ if #arg == 0 then print("Please specify the street. 1 = preflop, 4 = river") return end require 'torch' local arguments = require 'Settings.arguments' local game_settings = require 'Settings.game_settings' local bucketer = require 'Nn.bucketer' local river_tools = require 'Nn.Bucketing.river_tools' local constants = require 'Settings.constants' local card_to_string = require 'Game.card_to_string_conversion' local card_tools = require 'Game.card_tools' local evaluator = require 'Game.Evaluation.evaluator' local threads = require 'threads' local DataStreamMem = torch.class('RawConverter') require 'Nn.bucket_conversion' -- Lua implementation of PHP scandir function function scandir(directory) local i, t, popen = 0, {}, io.popen local pfile = popen('ls -a "'..directory..'"') for filename in pfile:lines() do i = i + 1 t[filename] = 1 end pfile:close() return t end function convert(street, srcfolder, destfolder) --loadind valid data local path = arguments.data_path if game_settings.nl then path = path .. "NoLimit/" else path = path .. "Limit/" end filenames = scandir(path .. srcfolder) local numfiles = 0 local goodfiles = {} for filename,_ in pairs(filenames) do local res = string.find(filename, ".inputs") if res ~= nil then local targetname = filename:sub(0,res) .. "targets" if filenames[targetname] ~= nil then goodfiles[filename:sub(0,res)] = 1 numfiles = numfiles + 1 end end end local bucket_conversion = BucketConversion() print(numfiles .. " good files") local bucket_count = bucketer:get_bucket_count(street) local target_size = bucket_count * constants.players_count -- ranges, termvalues, potsize local input_size = bucket_count * constants.players_count + 1 local num_train = math.floor(numfiles * 0.9) local num_valid = numfiles - num_train local train_count = num_train * arguments.gen_batch_size local valid_count = num_valid * arguments.gen_batch_size local idx = 1 local fileidx = 1 local file_pattern = "[-](......" if street == 1 then file_pattern = "" elseif street == 2 then file_pattern = file_pattern .. ")" elseif street == 3 then file_pattern = file_pattern .. "..)" elseif street == 4 then file_pattern = file_pattern .. "....)" end local input_batch = arguments.Tensor(arguments.gen_batch_size, input_size) local target_batch = arguments.Tensor(arguments.gen_batch_size, target_size) for filebase, _ in pairs(goodfiles) do local inputname = filebase .. "inputs" local targetname = filebase .. "targets" if street > 1 then local board = card_to_string:string_to_board(string.match(filebase, file_pattern)) bucket_conversion:set_board(board) else bucket_conversion:set_board(arguments.Tensor()) end local raw_input_batch = torch.load(path .. srcfolder .. inputname) local raw_target_batch = torch.load(path .. srcfolder .. targetname) --input_batch idx = idx + arguments.gen_batch_size if fileidx == num_train then idx = 1 end fileidx = fileidx + 1 if fileidx % 100 == 0 then print(fileidx) end local raw_indexes = {{1, game_settings.hand_count}, {game_settings.hand_count + 1, game_settings.hand_count * 2}} local bucket_indexes = {{1, bucket_count}, {bucket_count + 1, bucket_count * 2}} --[[ print(string.match(filebase, file_pattern)) for card1 = 1,game_settings.card_count do for card2 = card1+1,game_settings.card_count do local idx = card_tools:get_hole_index({card1,card2}) print(card_to_string:card_to_string(card1) .. card_to_string:card_to_string(card2) .. ": " .. term_values1[{1,idx}]) end end io.read() ]] for player = 1, constants.players_count do local player_index = raw_indexes[player] local bucket_index = bucket_indexes[player] bucket_conversion:card_range_to_bucket_range(raw_input_batch[{{},player_index}],input_batch[{{}, bucket_index}]) end for player = 1, constants.players_count do local player_index = raw_indexes[player] local bucket_index = bucket_indexes[player] bucket_conversion:hand_cfvs_to_bucket_cfvs( raw_input_batch[{{}, player_index}], raw_target_batch[{{}, player_index}], input_batch[{{}, bucket_index}], target_batch[{{}, bucket_index}]) end input_batch[{{}, -1}]:copy(raw_input_batch[{{},-1}]) if arguments.gpu then torch.save(path .. destfolder .. targetname, target_batch:cuda()) torch.save(path .. destfolder .. inputname, input_batch:cuda()) else torch.save(path .. destfolder .. targetname, target_batch:float()) torch.save(path .. destfolder .. inputname, input_batch:float()) end end end convert(tonumber(arg[1]), "srcfolder/", "destfolder/") ================================================ FILE: Source/Training/train.lua ================================================ --- Trains the neural network. -- -- Uses data generated by @{data_generation_call}. -- @module train require 'optim' local arguments = require 'Settings.arguments' local game_settings = require 'Settings.game_settings' require 'Nn.masked_huber_loss' local M = {} --- Saves a neural net model to disk. -- -- The model is saved to `arguments.model_path` and labelled with the epoch -- number. -- @param model the neural net to save -- @param epoch the current epoch number -- @param valid_loss the validation loss of the current network -- @local function M:_save_model(model, epoch, valid_loss) local model_information = {} model_information.epoch = epoch model_information.valid_loss = valid_loss model_information.gpu = arguments.gpu local path = arguments.model_path if game_settings.nl then path = path .. "NoLimit/" else path = path .. "Limit/" end local net_type_str = arguments.gpu and '_gpu' or '_cpu' local model_file_name = path .. '/epoch_' .. epoch .. net_type_str .. '.model' local information_file_name = path .. '/epoch_' .. epoch .. net_type_str .. '.info' torch.save(model_file_name, model) torch.save(information_file_name, model_information) end --- Function passed to torch's [optim package](https://github.com/torch/optim). -- @param params_new the neural network params -- @param inputs the neural network inputs -- @param targets the neural network targets -- @param mask the mask vectors used for the loss function -- @return the masked Huber loss on `inputs` and `targets` -- @return the gradient of the loss function -- @see masked_huber_loss -- @local local function feval(params_new, inputs, targets, mask) -- set x to x_new, if different -- (in this simple implementation, x_new will typically always point to x, -- so the copy is really useless) if M.params ~= params_new then M.params:copy(params_new) end M.grads:zero() local outputs = M.network:forward(inputs) local loss = M.criterion:forward(outputs, targets, mask) -- backward local dloss_doutput = M.criterion:backward(outputs, targets) M.network:backward(inputs, dloss_doutput) return loss, M.grads end --- Trains the neural network. -- @param network the neural network (see @{net_builder}) -- @param data_stream a @{data_stream|DataStream} object which provides the -- training data -- @param epoch_count the number of epochs (passes of the training data) to train for function M:train(network, data_stream, epoch_count) M.network = network M.data_stream = data_stream M.params, M.grads = network:getParameters() M.criterion = MaskedHuberLoss() if(arguments.gpu) then M.criterion = M.criterion:cuda() end local state = {learningRate = arguments.learning_rate} local lossSum = 0 local optim_func = optim.adam -- optimization loop local timer = torch.Timer() for epoch = 1, epoch_count do timer:reset() data_stream:start_epoch(epoch) lossSum = 0 if epoch == 50 then state.learningRate = state.learningRate / 10 end M.network:evaluate(false) for i=1, data_stream:get_train_batch_count() do local inputs, targets, mask = data_stream:get_train_batch(i) assert(mask) local _, loss = optim_func(function (x) return feval(x, inputs, targets, mask) end, M.params, state) lossSum = lossSum + loss[1] end print(string.format("Training loss: %f", lossSum / data_stream.train_batch_count)) M.network:evaluate(true) --check validation loss local valid_loss_sum = 0 for i=1, data_stream:get_valid_batch_count() do local inputs, targets, mask = data_stream:get_valid_batch(i) assert(mask) local outputs = M.network:forward(inputs) local loss = M.criterion:forward(outputs, targets, mask) valid_loss_sum = valid_loss_sum + loss end local valid_loss = valid_loss_sum / data_stream.valid_batch_count print(string.format("Validation loss: %f", valid_loss)) print('Epoch took: ', timer:time().real) --saving the model print(epoch) if epoch % arguments.save_epoch == 0 then print("SAVING MODEL") self:_save_model(network, epoch, valid_loss) end end --end of train loop end return M ================================================ FILE: Source/Tree/Tests/test_tree_builder.lua ================================================ local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local card_to_string = require 'Game.card_to_string_conversion' require 'Tree.tree_builder' local builder = PokerTreeBuilder() local params = {} params.root_node = {} params.root_node.board = card_to_string:string_to_board('') params.root_node.street = 1 params.root_node.current_player = constants.players.P1 params.root_node.bets = arguments.Tensor{50, 100} params.root_node.num_bets = 1 params.limit_to_street = true local tree = builder:build_tree(params) ================================================ FILE: Source/Tree/Tests/test_tree_cfr.lua ================================================ local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local game_settings = require 'Settings.game_settings' local card_tools = require 'Game.card_tools' local card_to_string = require 'Game.card_to_string_conversion' require 'Tree.tree_builder' require 'Tree.tree_visualiser' require 'Tree.tree_values' require 'Tree.tree_cfr' local builder = PokerTreeBuilder() local params = {} params.root_node = {} params.root_node.board = card_to_string:string_to_board('6c6d2c2d3h')--card_to_string:string_to_board('Ks') params.root_node.street = 4 params.root_node.current_player = constants.players.P1 params.root_node.bets = arguments.Tensor{4, 4} params.root_node.num_bets = 0 local tree = builder:build_tree(params) local starting_ranges = arguments.Tensor(constants.players_count, game_settings.hand_count) starting_ranges[1]:copy(card_tools:get_uniform_range(params.root_node.board)) starting_ranges[2]:copy(card_tools:get_uniform_range(params.root_node.board)) --starting_ranges[1]:copy(card_tools:get_random_range(params.root_node.board, 2)) --starting_ranges[2]:copy(card_tools:get_random_range(params.root_node.board, 4)) local tree_cfr = TreeCFR() tree_cfr:run_cfr(tree, starting_ranges) print(tree.cf_values) local tree_values = TreeValues() tree_values:compute_values(tree, starting_ranges) print('Exploitability: ' .. tree.exploitability .. '[chips]' ) local visualiser = TreeVisualiser() visualiser:graphviz(tree, 'simple_tree') ================================================ FILE: Source/Tree/Tests/test_tree_strategy_fillings.lua ================================================ local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local card_tools = require 'Game.card_tools' local card_to_string = require 'Game.card_to_string_conversion' local game_settings = require 'Settings.game_settings' require 'Tree.tree_builder' require 'Tree.tree_visualiser' require 'Tree.tree_values' require 'Tree.tree_cfr' require 'Tree.tree_strategy_filling' require 'Tree.tree_visualiser' require 'Tree.tree_values' local builder = PokerTreeBuilder() local params = {} params.root_node = {} params.root_node.board = card_to_string:string_to_board('') params.root_node.street = 1 params.root_node.current_player = constants.players.P1 params.root_node.bets = arguments.Tensor{100, 100} local tree = builder:build_tree(params) local filling = TreeStrategyFilling() local range1 = card_tools:get_uniform_range(params.root_node.board) local range2 = card_tools:get_uniform_range(params.root_node.board) filling:fill_strategies(tree, 1, range1, range2) filling:fill_strategies(tree, 2, range1, range2) local starting_ranges = arguments.Tensor(constants.players_count, game_settings.card_count) starting_ranges[1]:copy(range1) starting_ranges[2]:copy(range2) local tree_values = TreeValues() tree_values:compute_values(tree, starting_ranges) print('Exploitability: ' .. tree.exploitability .. '[chips]' ) local visualiser = TreeVisualiser() visualiser:graphviz(tree) ================================================ FILE: Source/Tree/Tests/test_tree_values.lua ================================================ local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local card_to_string = require 'Game.card_to_string_conversion' require 'Tree.tree_builder' require 'Tree.tree_visualiser' require 'Tree.tree_values' local builder = PokerTreeBuilder() local params = {} params.root_node = {} params.root_node.board = card_to_string:string_to_board('') params.root_node.street = 1 params.root_node.current_player = constants.players.P1 params.root_node.bets = arguments.Tensor{100, 100} local tree = builder:build_tree(params) local tree_values = TreeValues() tree_values:compute_values(tree) print('Exploitability: ' .. tree.exploitability .. '[chips]' ) local visualiser = TreeVisualiser() visualiser:graphviz(tree) ================================================ FILE: Source/Tree/Tests/test_tree_visualiser.lua ================================================ local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local card_to_string = require 'Game.card_to_string_conversion' require 'Tree.tree_builder' require 'Tree.tree_visualiser' local builder = PokerTreeBuilder() local params = {} params.root_node = {} params.root_node.board = card_to_string:string_to_board('') params.root_node.street = 1 params.root_node.current_player = constants.players.P1 params.root_node.bets = arguments.Tensor{50, 100} params.root_node.num_bets = 0 params.limit_to_street = true local tree = builder:build_tree(params) local visualiser = TreeVisualiser() visualiser:graphviz(tree, "simple_tree") ================================================ FILE: Source/Tree/strategy_filling.lua ================================================ --- Fills a game's public tree with a uniform strategy. In particular, fills -- the chance nodes with the probability of each outcome. -- -- A strategy is represented at each public node by a NxK tensor where: -- -- * N is the number of possible child nodes. -- -- * K is the number of information sets for the active player in the public -- node. For the Leduc Hold'em variants we implement, there is one for each -- private card that the player could hold. -- -- For a player node, `strategy[i][j]` gives the probability of taking the -- action that leads to the `i`th child when the player holds the `j`th card. -- -- For a chance node, `strategy[i][j]` gives the probability of reaching the -- `i`th child for either player when that player holds the `j`th card. -- @classmod strategy_filling local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local game_settings = require 'Settings.game_settings' local math = require 'math' local card_tools = require 'Game.card_tools' local tools = require 'tools' local StrategyFilling = torch.class('StrategyFilling') --- Constructor function StrategyFilling:__init() end --- Fills a chance node with the probability of each outcome. -- @param node the chance node -- @local function StrategyFilling:_fill_chance(node) assert(not node.terminal) --filling strategy --we will fill strategy with an uniform probability, but it has to be zero for hands that are not possible on --corresponding board num_boards = tools:choose(game_settings.card_count - game_settings.hand_card_count * constants.players_count, game_settings.board_card_count[node.street+1] - game_settings.board_card_count[node.street]) node.strategy = arguments.Tensor(#node.children, game_settings.hand_count):fill(0) --setting probability of impossible hands to 0 for i = 1,#node.children do local child_node = node.children[i] local mask = card_tools:get_possible_hand_indexes(child_node.board):byte() node.strategy[i]:fill(0) --remove 2 because each player holds one card node.strategy[i][mask] = 1.0 / (game_settings.card_count - 2) end end --- Fills a player node with a uniform strategy. -- @param node the player node -- @local function StrategyFilling:_fill_uniformly(node) assert(node.current_player == constants.players.P1 or node.current_player == constants.players.P2) if(node.terminal) then return end node.strategy = arguments.Tensor(#node.children, game_settings.hand_count):fill(1.0 / #node.children) end --- Fills a node with a uniform strategy and recurses on the children. -- @param node the node -- @local function StrategyFilling:_fill_uniform_dfs(node) if node.current_player == constants.players.chance then self:_fill_chance(node) else self:_fill_uniformly(node) end for i=1,#node.children do self:_fill_uniform_dfs(node.children[i]) end end --- Fills a public tree with a uniform strategy. -- @param tree a public tree for Leduc Hold'em or variant function StrategyFilling:fill_uniform(tree) self:_fill_uniform_dfs(tree) end ================================================ FILE: Source/Tree/tree_builder.lua ================================================ --- Builds a public tree for Leduc Hold'em or variants. -- -- Each node of the tree contains the following fields: -- -- * `node_type`: an element of @{constants.node_types} (if applicable) -- -- * `street`: the current betting round -- -- * `board`: a possibly empty vector of board cards -- -- * `board_string`: a string representation of the board cards -- -- * `current_player`: the player acting at the node -- -- * `bets`: the number of chips that each player has committed to the pot -- -- * `pot`: half the pot size, equal to the smaller number in `bets` -- -- * `children`: a list of children nodes -- @classmod tree_builder local math = require 'math' local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local game_settings = require 'Settings.game_settings' local card_tools = require 'Game.card_tools' local card_to_string = require 'Game.card_to_string_conversion' require 'Tree.strategy_filling' require 'Game.bet_sizing' local PokerTreeBuilder = torch.class('PokerTreeBuilder') --- Constructor function PokerTreeBuilder:__init() end --- Creates the children nodes after a chance node. -- @param parent_node the chance node -- @return a list of children nodes -- @local function PokerTreeBuilder:_get_children_nodes_chance_node(parent_node) assert(parent_node.current_player == constants.players.chance) if self.limit_to_street then return {} end local next_boards = card_tools:get_boards(parent_node.street+1) local next_boards_count = next_boards:size(1) local subtree_height = -1 local children = {} --1.0 iterate over the next possible boards to build the corresponding subtrees for i=1,next_boards_count do local next_board = next_boards[i] local next_board_string = card_to_string:cards_to_string(next_board) local child = {} child.node_type = constants.node_types.inner_node child.parent = parent_node child.current_player = constants.players.P1 child.street = parent_node.street + 1 child.board = next_board child.board_string = next_board_string child.bets = parent_node.bets:clone() table.insert(children, child) end return children end --- Fills in additional convenience attributes which only depend on existing -- node attributes. -- @param node the node -- @local function PokerTreeBuilder:_fill_additional_attributes(node) node.pot = node.bets:min() end --- Creates the children nodes after a player node. -- @param parent_node the chance node -- @return a list of children nodes -- @local function PokerTreeBuilder:_get_children_player_node(parent_node) assert(parent_node.current_player ~= constants.players.chance) local children = {} --1.0 fold action --if parent_node.bets[1] ~= parent_node.bets[2] then local fold_node = {} fold_node.type = constants.node_types.terminal_fold fold_node.terminal = true fold_node.current_player = 3 - parent_node.current_player fold_node.street = parent_node.street fold_node.board = parent_node.board fold_node.board_string = parent_node.board_string fold_node.bets = parent_node.bets:clone() table.insert(children, fold_node) --end --2.0 check/call action if (parent_node.street == 1 and parent_node.current_player == constants.players.P1 and parent_node.num_bets == 1 and (game_settings.limit_bet_cap > 1 or game_settings.nl)) or ((parent_node.street ~= 1 or constants.streets_count == 1) and parent_node.current_player == constants.players.P2 and parent_node.bets[1] == parent_node.bets[2]) then local check_node = {} check_node.type = constants.node_types.check check_node.terminal = false check_node.current_player = 3 - parent_node.current_player check_node.street = parent_node.street check_node.board = parent_node.board check_node.board_string = parent_node.board_string check_node.bets = parent_node.bets:clone():fill(parent_node.bets:max()) check_node.num_bets = parent_node.num_bets table.insert(children, check_node) --transition check/call elseif parent_node.street ~= constants.streets_count and ( (parent_node.bets[1] == parent_node.bets[2] and ((parent_node.street == 1 and parent_node.current_player == constants.players.P2) or (parent_node.street ~= 1 and parent_node.current_player == constants.players.P1))) or (parent_node.bets[1] ~= parent_node.bets[2] and parent_node.bets:max() < arguments.stack)) then local chance_node = {} chance_node.node_type = constants.node_types.chance_node chance_node.street = parent_node.street chance_node.board = parent_node.board chance_node.board_string = parent_node.board_string chance_node.current_player = constants.players.chance chance_node.bets = parent_node.bets:clone():fill(parent_node.bets:max()) chance_node.num_bets = 0 table.insert(children, chance_node) else --2.0 terminal call - either last street or allin local terminal_call_node = {} terminal_call_node.type = constants.node_types.terminal_call terminal_call_node.terminal = true terminal_call_node.current_player = 3 - parent_node.current_player terminal_call_node.street = parent_node.street terminal_call_node.board = parent_node.board terminal_call_node.board_string = parent_node.board_string terminal_call_node.bets = parent_node.bets:clone():fill(parent_node.bets:max()) table.insert(children, terminal_call_node) end --3.0 bet actions if not game_settings.nl then if parent_node.num_bets < game_settings.limit_bet_cap then local child = {} child.parent = parent_node child.current_player = 3 - parent_node.current_player child.street = parent_node.street child.board = parent_node.board child.board_string = parent_node.board_string child.bets = parent_node.bets:clone() betsize = game_settings.limit_bet_sizes[parent_node.street] if parent_node.current_player == constants.players.P1 then child.bets[1] = child.bets[2] + betsize else child.bets[2] = child.bets[1] + betsize end child.num_bets = parent_node.num_bets + 1 table.insert(children, child) end else local possible_bets = self.bet_sizing:get_possible_bets(parent_node) if possible_bets:dim() ~= 0 then assert(possible_bets:size(2) == 2) for i=1, possible_bets:size(1) do local child = {} child.parent = parent_node child.current_player = 3 - parent_node.current_player child.street = parent_node.street child.board = parent_node.board child.board_string = parent_node.board_string child.bets = possible_bets[i] table.insert(children, child) end end end return children end --- Creates the children after a node. -- @param parent_node the node to create children for -- @return a list of children nodes -- @local function PokerTreeBuilder:_get_children_nodes(parent_node) local chance_node = parent_node.current_player == constants.players.chance --transition call -> create a chance node if parent_node.terminal then return {} --chance node elseif chance_node then return self:_get_children_nodes_chance_node(parent_node) --inner nodes -> handle bet sizes else return self:_get_children_player_node(parent_node) end assert(false) end --- Recursively build the (sub)tree rooted at the current node. -- @param current_node the root to build the (sub)tree from -- @return `current_node` after the (sub)tree has been built -- @local function PokerTreeBuilder:_build_tree_dfs(current_node) self:_fill_additional_attributes(current_node) local children = self:_get_children_nodes(current_node) current_node.children = children local depth = 0 current_node.actions = arguments.Tensor(#children) for i=1,#children do children[i].parent = current_node self:_build_tree_dfs(children[i]) depth = math.max(depth, children[i].depth) if i == 1 then current_node.actions[i] = constants.actions.fold elseif i == 2 then current_node.actions[i] = constants.actions.ccall else if not game_settings.nl then assert(i == 3, 'wtf child') current_node.actions[i] = constants.actions.raise else current_node.actions[i] = children[i].bets:max() end end end current_node.depth = depth + 1 return current_node end --- Builds the tree. -- @param params table of tree parameters, containing the following fields: -- -- * `street`: the betting round of the root node -- -- * `bets`: the number of chips committed at the root node by each player -- -- * `current_player`: the acting player at the root node -- -- * `board`: a possibly empty vector of board cards at the root node -- -- * `limit_to_street`: if `true`, only build the current betting round -- -- * `bet_sizing` (optional): a @{bet_sizing} object which gives the allowed -- bets for each player -- @return the root node of the built tree function PokerTreeBuilder:build_tree(params) local root = {} --copy necessary stuff from the root_node not to touch the input root.street = params.root_node.street root.bets = params.root_node.bets:clone() root.num_bets = params.root_node.num_bets root.current_player = params.root_node.current_player root.board = params.root_node.board:clone() params.bet_sizing = params.bet_sizing or BetSizing(arguments.bet_sizing) assert(params.bet_sizing) self.bet_sizing = params.bet_sizing self.limit_to_street = params.limit_to_street self:_build_tree_dfs(root) local strategy_filling = StrategyFilling() strategy_filling:fill_uniform(root) return root end ================================================ FILE: Source/Tree/tree_cfr.lua ================================================ --- Runs Counterfactual Regret Minimization (CFR) to approximately -- solve a game represented by a complete game tree. -- -- As this class does full solving from the root of the game with no -- limited lookahead, it is not used in continual re-solving. It is provided -- simply for convenience. -- @classmod tree_cfr local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local game_settings = require 'Settings.game_settings' local card_tools = require 'Game.card_tools' require 'TerminalEquity.terminal_equity' local TreeCFR = torch.class('TreeCFR') --- Constructor function TreeCFR:__init() --for ease of implementation, we use small epsilon rather than zero when working with regrets self.regret_epsilon = 1/1000000000 self._cached_terminal_equities = {} end --- Gets an evaluator for player equities at a terminal node. -- -- Caches the result to minimize creation of @{terminal_equity|TerminalEquity} -- objects. -- @param node the terminal node to evaluate -- @return a @{terminal_equity|TerminalEquity} evaluator for the node -- @local function TreeCFR:_get_terminal_equity(node) local cached = self._cached_terminal_equities[node.board] if cached == nil then cached = TerminalEquity() cached:set_board(node.board) self._cached_terminal_equities[node.board] = cached end return cached end --- Recursively walks the tree, applying the CFR algorithm. -- @param node the current node in the tree -- @param iter the current iteration number -- @local function TreeCFR:cfrs_iter_dfs( node, iter ) assert(node.current_player == constants.players.P1 or node.current_player == constants.players.P2 or node.current_player == constants.players.chance) local opponent_index = 3 - node.current_player --dimensions in tensor local action_dimension = 1 local card_dimension = 2 --compute values using terminal_equity in terminal nodes if(node.terminal) then local terminal_equity = self:_get_terminal_equity(node) local values = node.ranges_absolute:clone():fill(0) if(node.type == constants.node_types.terminal_fold) then terminal_equity:tree_node_fold_value(node.ranges_absolute, values, opponent_index) else terminal_equity:tree_node_call_value(node.ranges_absolute, values) end --multiply by the pot values = values * node.pot node.cf_values = values:viewAs(node.ranges_absolute) else local actions_count = #node.children local current_strategy = nil if node.current_player == constants.players.chance then current_strategy = node.strategy else --we have to compute current strategy at the beginning of each iteraton --initialize regrets in the first iteration node.regrets = node.regrets or arguments.Tensor(actions_count, game_settings.hand_count):fill(self.regret_epsilon) --[[actions_count x hand_count]] node.possitive_regrets = node.possitive_regrets or arguments.Tensor(actions_count, game_settings.hand_count):fill(self.regret_epsilon) --compute positive regrets so that we can compute the current strategy fromm them node.possitive_regrets:copy(node.regrets) node.possitive_regrets[torch.le(node.possitive_regrets, self.regret_epsilon)] = self.regret_epsilon --compute the current strategy local regrets_sum = node.possitive_regrets:sum(action_dimension) current_strategy = node.possitive_regrets:clone() current_strategy:cdiv(regrets_sum:expandAs(current_strategy)) end --current cfv [[actions, players, ranges]] local cf_values_allactions = arguments.Tensor(actions_count, constants.players_count, game_settings.hand_count):fill(0) local children_ranges_absolute = {} if node.current_player == constants.players.chance then local ranges_mul_matrix = node.ranges_absolute[1]:repeatTensor(actions_count, 1) children_ranges_absolute[1] = torch.cmul(current_strategy, ranges_mul_matrix) ranges_mul_matrix = node.ranges_absolute[2]:repeatTensor(actions_count, 1) children_ranges_absolute[2] = torch.cmul(current_strategy, ranges_mul_matrix) else local ranges_mul_matrix = node.ranges_absolute[node.current_player]:repeatTensor(actions_count, 1) children_ranges_absolute[node.current_player] = torch.cmul(current_strategy, ranges_mul_matrix) children_ranges_absolute[opponent_index] = node.ranges_absolute[opponent_index]:repeatTensor(actions_count, 1):clone() end for i = 1,#node.children do local child_node = node.children[i] --set new absolute ranges (after the action) for the child child_node.ranges_absolute = node.ranges_absolute:clone() child_node.ranges_absolute[1]:copy(children_ranges_absolute[1][{i}]) child_node.ranges_absolute[2]:copy(children_ranges_absolute[2][{i}]) self:cfrs_iter_dfs(child_node, iter, hand_count) cf_values_allactions[i] = child_node.cf_values end node.cf_values = arguments.Tensor(constants.players_count, game_settings.hand_count):fill(0) if node.current_player ~= constants.players.chance then local strategy_mul_matrix = current_strategy:viewAs(arguments.Tensor(actions_count, game_settings.hand_count)) node.cf_values[node.current_player] = torch.cmul(strategy_mul_matrix, cf_values_allactions[{{}, node.current_player, {}}]):sum(1) node.cf_values[opponent_index] = (cf_values_allactions[{{}, opponent_index, {}}]):sum(1) else node.cf_values[1] = (cf_values_allactions[{{}, 1, {}}]):sum(1) node.cf_values[2] = (cf_values_allactions[{{}, 2, {}}]):sum(1) end if node.current_player ~= constants.players.chance then --computing regrets local current_regrets = cf_values_allactions[{{}, {node.current_player}, {}}]:reshape(actions_count, game_settings.hand_count):clone() current_regrets:csub(node.cf_values[node.current_player]:view(1, game_settings.hand_count):expandAs(current_regrets)) self:update_regrets(node, current_regrets) --accumulating average strategy self:update_average_strategy(node, current_strategy, iter) end end end --- Update a node's total regrets with the current iteration regrets. -- @param node the node to update -- @param current_regrets the regrets from the current iteration of CFR -- @local function TreeCFR:update_regrets(node, current_regrets) --node.regrets:add(current_regrets) --local negative_regrets = node.regrets[node.regrets:lt(0)] --node.regrets[node.regrets:lt(0)] = negative_regrets node.regrets:add(current_regrets) node.regrets[torch.le(node.regrets, self.regret_epsilon)] = self.regret_epsilon end --- Update a node's average strategy with the current iteration strategy. -- @param node the node to update -- @param current_strategy the CFR strategy for the current iteration -- @param iter the iteration number of the current CFR iteration function TreeCFR:update_average_strategy(node, current_strategy, iter) if iter > arguments.cfr_skip_iters then node.strategy = node.strategy or arguments.Tensor(actions_count, game_settings.hand_count):fill(0) node.iter_weight_sum = node.iter_weight_sum or arguments.Tensor(game_settings.hand_count):fill(0) local iter_weight_contribution = node.ranges_absolute[node.current_player]:clone() iter_weight_contribution[torch.le(iter_weight_contribution, 0)] = self.regret_epsilon node.iter_weight_sum:add(iter_weight_contribution) local iter_weight = torch.cdiv(iter_weight_contribution, node.iter_weight_sum) local expanded_weight = iter_weight:view(1, game_settings.hand_count):expandAs(node.strategy) local old_strategy_scale = expanded_weight * (-1) + 1 --same as 1 - expanded weight node.strategy:cmul(old_strategy_scale) local strategy_addition = current_strategy:cmul(expanded_weight) node.strategy:add(strategy_addition) end end --- Run CFR to solve the given game tree. -- @param root the root node of the tree to solve. -- @param[opt] starting_ranges probability vectors over player private hands -- at the root node (default uniform) -- @param[opt] iter_count the number of iterations to run CFR for -- (default @{arguments.cfr_iters}) function TreeCFR:run_cfr( root, starting_ranges, iter_count ) assert(starting_ranges) local iter_count = iter_count or arguments.cfr_iters root.ranges_absolute = starting_ranges for iter = 1,iter_count do self:cfrs_iter_dfs(root, iter) end end ================================================ FILE: Source/Tree/tree_strategy_filling.lua ================================================ --- Recursively performs continual re-solving at every node of a public tree to -- generate the DeepStack strategy for the entire game. -- -- A strategy is represented at each public node by a NxK tensor where: -- -- * N is the number of possible child nodes. -- -- * K is the number of information sets for the active player in the public -- node. For the Leduc Hold'em variants we implement, there is one for each -- private card that the player could hold. -- -- For a player node, `strategy[i][j]` gives the probability of taking the -- action that leads to the `i`th child when the player holds the `j`th card. -- -- For a chance node, `strategy[i][j]` gives the probability of reaching the -- `i`th child for either player when that player holds the `j`th card. -- @classmod tree_strategy_filling require 'math' local arguments = require 'Settings.arguments' local card_tools = require 'Game.card_tools' local constants = require 'Settings.constants' local game_settings = require 'Settings.game_settings' require 'Lookahead.mock_resolving' require 'Lookahead.resolving' local TreeStrategyFilling = torch.class('TreeStrategyFilling') --- Constructor function TreeStrategyFilling:__init() self.board_count = card_tools:get_boards_count() end --- Fills all chance nodes of a subtree with the probability of each outcome. -- @param node the root of the subtree -- @local function TreeStrategyFilling:_fill_chance(node) if(node.terminal) then return end if node.current_player == constants.players.chance then --chance node, we will fill uniform strategy --works only for chance node at start of second round assert(#node.children == self.board_count) --filling strategy --we will fill strategy with an uniform probability, but it has to be zero for hands that are not possible on --corresponding board node.strategy = arguments.Tensor(#node.children, game_settings.card_count):fill(0) --setting strategy for impossible hands to 0 for i = 1,#node.children do local child_node = node.children[i] local mask = card_tools:get_possible_hand_indexes(child_node.board):byte() node.strategy[i][mask] = 1.0/(self.board_count - 2) end end for i = 1,#node.children do local child_node = node.children[i] self:_fill_chance(child_node) end end --- Recursively fills a subtree with a uniform random strategy for the given -- player. -- -- Used in sections of the game to which the player doesn't play. -- -- @param node the root of the subtree -- @param player the player which is given the uniform random strategy -- @local function TreeStrategyFilling:_fill_uniformly(node, player) if(node.terminal) then return end if node.current_player == player then --fill uniform strategy node.strategy = arguments.Tensor(#node.children, game_settings.card_count):fill(1.0 / #node.children) end for i = 1,#node.children do local child_node = node.children[i] self:_fill_uniformly(child_node, player) end end --- Recursively fills a player's strategy for the subtree rooted at an -- opponent node. -- -- @param params tree walk parameters (see @{_fill_strategies_dfs}) -- @local function TreeStrategyFilling:_process_opponent_node(params) -- node, player, range, cf_values, strategy_computation, our_last_action local node = params.node local player = params.player local range = params.range local cf_values = params.cf_values local resolving = params.resolving local our_last_action = params.our_last_action assert(not node.terminal and node.current_player ~= player) --when opponent plays, we will do nothing except sending cf_values to the child nodes for i = 1,#node.children do local child_node = node.children[i] if not child_node.terminal then local child_params = {} child_params.node = child_node child_params.range = range child_params.player = player child_params.cf_values = cf_values child_params.resolving = params.resolving child_params.our_last_action = our_last_action self:_fill_strategies_dfs(child_params) end end end --- Recursively fills a player's strategy in a tree. -- -- @param node the root of the tree -- @param player the player to calculate a strategy for -- @param p1_range a probability vector of the first player's private hand -- at the root -- @param p2_range a probability vector of the second player's private hand -- at the root -- @local function TreeStrategyFilling:_fill_starting_node(node, player, p1_range, p2_range) assert(not node.terminal) assert(node.current_player == constants.players.P1) --re-solving the node local resolving = Resolving() resolving:resolve_first_node(node, p1_range, p2_range) --check which player plays first if node.current_player == player then self:_fill_computed_node(node, player, p1_range, resolving) else --opponent plays in this node. we need only cf-values at the beginning and we will just copy them local cf_values = resolving:get_root_cfv() local child_params = {} child_params.node = node child_params.range = p2_range child_params.player = player child_params.cf_values = cf_values self:_process_opponent_node(child_params) end end --- Recursively fills a player's strategy for the subtree rooted at a -- player node. -- -- Re-solves to generate a strategy for the player node. -- -- @param params tree walk parameters (see @{_fill_strategies_dfs}) -- @local function TreeStrategyFilling:_fill_player_node(params) local node = params.node local player = params.player local range = params.range local cf_values = params.cf_values local opponent_range = params.opponent_range assert(not node.terminal and node.current_player == player) --now player plays, we have to compute his strategy local resolving = Resolving() resolving:resolve(node, range, cf_values) --we will send opponent range to adjust range also in our second action in the street self:_fill_computed_node(node, player, range, resolving) end --- Recursively fills a player's strategy for the subtree rooted at a -- player node. -- -- @param node the player node -- @param player the player to fill the strategy for -- @param range a probability vector giving the player's range at the node -- @param resolving a @{resolving|Resolving} object which has been used to -- re-solve the node -- @local function TreeStrategyFilling:_fill_computed_node(node, player, range, resolving) assert(resolving) assert(node.current_player == player) local player_actions = resolving:get_possible_actions() local actions_count = #node.children assert(actions_count == node.actions:size(1)) --find which bets are used by player local used_bets = torch.ByteTensor(actions_count):zero() for i = 1, player_actions:size(1) do local player_action = player_actions[i] local bet_indicator = torch.eq(node.actions, player_action) --there has to be exactly one equivalent bet assert(bet_indicator:sum(1)[1] == 1) used_bets:add(bet_indicator:typeAs(used_bets)) end --check if terminal actions are used and if all player bets are used assert(used_bets[1] == 1 and used_bets[2] == 1) assert(used_bets:sum(1)[1] == player_actions:size(1)) --fill the strategy node.strategy = arguments.Tensor(actions_count, game_settings.card_count):zero() local cf_values = arguments.Tensor(actions_count, game_settings.card_count):zero() --we need to compute all values and ranges before dfs call, becasue --re-solving will be built from different node in the recursion --in first cycle, fill nodes we do not play in and fill strategies and cf-values for i=1, actions_count do local child_node = node.children[i] --check if the bet is possible if used_bets[i] == 0 then self:_fill_uniformly(child_node, player) else local action = node.actions[i] local values_after_action = resolving:get_action_cfv(action) cf_values[i]:copy(values_after_action) node.strategy[i] = resolving:get_action_strategy(action) end end --compute ranges for each action local range_after_action = node.strategy:clone() range_after_action:cmul(range:view(1, game_settings.card_count):expandAs(range_after_action)) -- new range = range * strategy --normalize the ranges local normalization_factor = range_after_action:sum(2) normalization_factor[torch.eq(normalization_factor, 0)] = 1 range_after_action:cdiv(normalization_factor:expandAs(range_after_action)) --in second cycle, run dfs computation for action = 1, actions_count do local child_node = node.children[action] if used_bets[action] ~= 0 then if not (math.abs(range_after_action[action]:sum(1)[1] - 1) < 0.001) then assert(range_after_action[action]:sum() == 0, range_after_action[action]:sum()) self:_fill_uniformly(child_node, player) else assert(math.abs(range_after_action[action]:sum(1)[1] - 1) < 0.001) local params = {} params.node = child_node params.range = range_after_action[action] params.player = player params.cf_values = cf_values[action] params.resolving = resolving params.our_last_action = node.actions[action] params.opponent_range = opponent_range self:_fill_strategies_dfs(params) end end end end --- Recursively fills a player's strategy for the subtree rooted at a -- chance node. -- -- @param params tree walk parameters (see @{_fill_strategies_dfs}) -- @local function TreeStrategyFilling:_process_chance_node(params) local resolving = params.resolving local node = params.node local player = params.player local range = params.range local cf_values = params.cf_values local our_last_action = params.our_last_action assert(resolving) assert(our_last_action) assert(not node.terminal and node.current_player == constants.players.chance) --on chance node we need to recompute values in next round for i = 1,#node.children do local child_node = node.children[i] assert(child_node.current_player == constants.players.P1) assert(not child_node.terminal) --computing cf_values for the child node local child_cf_values = resolving:get_chance_action_cfv(our_last_action, child_node.board) --we need to remove impossible hands from the range and then renormalize it local child_range = range:clone() local mask = card_tools:get_possible_hand_indexes(child_node.board) child_range:cmul(mask) local range_weight = child_range:sum(1)[1] --weight should be single number child_range:mul(1/range_weight) --we should never touch same re-solving again after the chance action, set it to nil local params = {} params.node = child_node params.range = child_range params.player = player params.cf_values = child_cf_values params.resolving = nil params.our_last_action = nil self:_fill_strategies_dfs(params) end end --- Recursively fills a player's strategy for a subtree. -- -- @param params a table of tree walk parameters with the following fields: -- -- * `node`: the root of the subtree -- -- * `player`: the player to fill the strategy for -- -- * `range`: a probability vector over the player's private hands at the node -- -- * `cf_values`: a vector of opponent counterfactual values at the node -- -- * `resolving`: a @{resolving|Resolving} object which was used to -- re-solve the last player node -- -- * `our_last_action`: the action taken by the player at their last node -- @local function TreeStrategyFilling:_fill_strategies_dfs(params) assert(params.player == constants.players.chance or params.player == constants.players.P1 or params.player == constants.players.P2) if(params.node.terminal) then return elseif(params.node.current_player == constants.players.chance) then --chance node self:_process_chance_node(params) elseif(params.node.current_player == params.player ) then self:_fill_player_node(params) else self:_process_opponent_node(params) end end --- Fills a tree with a player's strategy generated with continual re-solving. -- -- Recursively does continual re-solving on every node of the tree to generate -- the strategy for that node. -- -- @param root the root of the tree -- @param player the player to fill the strategy for -- @param p1_range a probability vector over the first player's private hands -- at the root of the tree -- @param p2_range a probability vector over the second player's private hands -- at the root of the tree function TreeStrategyFilling:fill_strategies( root, player, p1_range, p2_range ) self.current_filling_player = player if player == constants.players.chance then self:_fill_chance(root) else assert(player == constants.players.P1 or player == constants.players.P2) self:_fill_starting_node(root, player, p1_range, p2_range) end end --- Fills a tree with uniform random strategies for both players. -- @param root the root of the tree function TreeStrategyFilling:fill_uniform_strategy(root) self:_fill_uniformly(root, constants.players.P1) self:_fill_uniformly(root, constants.players.P2) end ================================================ FILE: Source/Tree/tree_values.lua ================================================ --- Computes the expected value of a strategy profile on a game's public tree, -- as well as the value of a best response against the profile. -- @classmod tree_values local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local game_settings = require 'Settings.game_settings' local card_tools = require 'Game.card_tools' require 'TerminalEquity.terminal_equity' local TreeValues = torch.class('TreeValues') --- Constructor function TreeValues:__init() self.terminal_equity = TerminalEquity() end --- Recursively walk the tree and calculate the probability of reaching each -- node using the saved strategy profile. -- -- The reach probabilities are saved in the `ranges_absolute` field of each -- node. -- @param node the current node of the tree -- @param ranges_absolute a 2xK tensor containing the probabilities of each -- player reaching the current node with each private hand -- @local function TreeValues:_fill_ranges_dfs(node, ranges_absolute) node.ranges_absolute = ranges_absolute:clone() if(node.terminal) then return end assert(node.strategy) local actions_count = #node.children --check that it's a legal strategy local strategy_to_check = node.strategy local hands_mask = card_tools:get_possible_hand_indexes(node.board) if node.current_player ~= constants.players.chance then local checksum = strategy_to_check:sum(1) assert(not torch.any(strategy_to_check:lt(0))) assert(not torch.any(checksum:gt(1.001))) assert(not torch.any(checksum:lt(0.999))) assert(not torch.any(checksum:ne(checksum))) end assert(node.ranges_absolute:lt(0):sum() == 0) assert(node.ranges_absolute:gt(1):sum() == 0) --check if the range consists only of cards that don't overlap with the board local impossible_hands_mask = hands_mask:clone():fill(1) - hands_mask local impossible_range_sum = node.ranges_absolute:clone():cmul(impossible_hands_mask:view(1, game_settings.card_count):expandAs(node.ranges_absolute)):sum() assert(impossible_range_sum == 0, impossible_range_sum) local children_ranges_absolute = arguments.Tensor(#node.children, constants.players_count, game_settings.card_count) --chance player if node.current_player == constants.players.chance then --multiply ranges of both players by the chance prob children_ranges_absolute[{{}, constants.players.P1, {}}]:copy(node.ranges_absolute[constants.players.P1]:repeatTensor(actions_count, 1)) children_ranges_absolute[{{}, constants.players.P2, {}}]:copy(node.ranges_absolute[constants.players.P2]:repeatTensor(actions_count, 1)) children_ranges_absolute[{{}, constants.players.P1, {}}]:cmul(node.strategy) children_ranges_absolute[{{}, constants.players.P2, {}}]:cmul(node.strategy) --player else --copy the range for the non-acting player children_ranges_absolute[{{}, 3-node.current_player, {}}] = node.ranges_absolute[3-node.current_player]:clone():repeatTensor(actions_count, 1) --multiply the range for the acting player using his strategy local ranges_mul_matrix = node.ranges_absolute[node.current_player]:repeatTensor(actions_count, 1) children_ranges_absolute[{{}, node.current_player, {}}] = torch.cmul(node.strategy, ranges_mul_matrix) end --fill the ranges for the children for i = 1,#node.children do local child_node = node.children[i] local child_range = children_ranges_absolute[i] --go deeper self:_fill_ranges_dfs(child_node, child_range) end end --- Recursively calculate the counterfactual values for each player at each -- node of the tree using the saved strategy profile. -- -- The cfvs for each player in the given strategy profile when playing against -- each other is stored in the `cf_values` field for each node. The cfvs for -- a best response against each player in the profile are stored in the -- `cf_values_br` field for each node. -- @param node the current node -- @local function TreeValues:_compute_values_dfs(node) --compute values using terminal_equity in terminal nodes if(node.terminal) then assert(node.type == constants.node_types.terminal_fold or node.type == constants.node_types.terminal_call) self.terminal_equity:set_board(node.board) local values = node.ranges_absolute:clone():fill(0) if(node.type == constants.node_types.terminal_fold) then self.terminal_equity:tree_node_fold_value(node.ranges_absolute, values, 3-node.current_player) else self.terminal_equity:tree_node_call_value(node.ranges_absolute, values) end --multiply by the pot values = values * node.pot node.cf_values = values:viewAs(node.ranges_absolute) node.cf_values_br = values:viewAs(node.ranges_absolute) else local actions_count = #node.children local ranges_size = node.ranges_absolute:size(2) --[[actions, players, ranges]] local cf_values_allactions = arguments.Tensor(#node.children, 2, ranges_size):fill(0) local cf_values_br_allactions = arguments.Tensor(#node.children, 2, ranges_size):fill(0) for i = 1,#node.children do local child_node = node.children[i] self:_compute_values_dfs(child_node) cf_values_allactions[i] = child_node.cf_values cf_values_br_allactions[i] = child_node.cf_values_br end node.cf_values = arguments.Tensor(2, ranges_size):fill(0) node.cf_values_br = arguments.Tensor(2, ranges_size):fill(0) --strategy = [[actions x range]] local strategy_mul_matrix = node.strategy:viewAs(arguments.Tensor(actions_count, ranges_size)) --compute CFVs given the current strategy for this node if node.current_player == constants.players.chance then node.cf_values = cf_values_allactions:sum(1)[1] node.cf_values_br = cf_values_br_allactions:sum(1)[1] else node.cf_values[node.current_player] = torch.cmul(strategy_mul_matrix, cf_values_allactions[{{}, node.current_player, {}}]):sum(1) node.cf_values[3-node.current_player] = (cf_values_allactions[{{}, 3-node.current_player, {}}]):sum(1) --compute CFVs given the BR strategy for this node node.cf_values_br[3 - node.current_player] = cf_values_br_allactions[{{}, 3 - node.current_player, {}}]:sum(1) node.cf_values_br[node.current_player] = cf_values_br_allactions[{{}, node.current_player, {}}]:max(1) end end --counterfactual values weighted by the reach prob node.cfv_infset = arguments.Tensor(2) node.cfv_infset[1] = node.cf_values[1] * node.ranges_absolute[1] node.cfv_infset[2] = node.cf_values[2] * node.ranges_absolute[2] --compute CFV-BR values weighted by the reach prob node.cfv_br_infset = arguments.Tensor(2) node.cfv_br_infset[1] = node.cf_values_br[1] * node.ranges_absolute[1] node.cfv_br_infset[2] = node.cf_values_br[2] * node.ranges_absolute[2] node.epsilon = node.cfv_br_infset - node.cfv_infset node.exploitability = node.epsilon:mean() end --- Compute the self play and best response values of a strategy profile on -- the given game tree. -- -- The cfvs for each player in the given strategy profile when playing against -- each other is stored in the `cf_values` field for each node. The cfvs for -- a best response against each player in the profile are stored in the -- `cf_values_br` field for each node. -- -- @param root The root of the game tree. Each node of the tree is assumed to -- have a strategy saved in the `strategy` field. -- @param[opt] starting_ranges probability vectors over player private hands -- at the root node (default uniform) function TreeValues:compute_values( root, starting_ranges ) --1.0 set the starting range local uniform_ranges = arguments.Tensor(constants.players_count, game_settings.card_count):fill(1.0/game_settings.card_count) local starting_ranges = starting_ranges or uniform_ranges --2.0 check the starting ranges local checksum = starting_ranges:sum(2)[{{}, 1}] assert(math.abs(checksum[1] - 1) < 0.0001, 'starting range does not sum to 1') assert(math.abs(checksum[2] - 1) < 0.0001, 'starting range does not sum to 1') assert(starting_ranges:lt(0):sum() == 0) --3.0 compute the values self:_fill_ranges_dfs(root, starting_ranges) self:_compute_values_dfs(root) end ================================================ FILE: Source/Tree/tree_visualiser.lua ================================================ --- Generates visual representations of game trees. -- @classmod tree_visualiser local TreeVisualiser = torch.class('TreeVisualiser') local arguments = require 'Settings.arguments' local constants = require 'Settings.constants' local card_to_string = require 'Game.card_to_string_conversion' --TODO: README --dot tree_2.dot -Tpng -O --- Constructor function TreeVisualiser:__init() self.node_to_graphviz_counter = 0 self.edge_to_graphviz_counter = 0 end --- Generates a string representation of a tensor. -- @param tensor a tensor -- @param[opt] name a name for the tensor -- @param[opt] format a format string to use with @{string.format} for each -- element of the tensor -- @param[opt] labels a list of labels for the elements of the tensor -- @return a string representation of the tensor -- @local function TreeVisualiser:add_tensor(tensor, name, format, labels) local out = '' if name then out = '| ' .. name .. ': ' end if not format then format = "%.3f" end out = out .. " | " .. "" .. tensor:size(1) .. " hands | " for i = 1, --[[tensor:size(1) do -- ]]math.min(2,tensor:size(1)) do if labels then out = out .. labels[i] .. ":" end out = out .. string.format(format, tensor[i]) .. ", " end out = out .. ".." return out end --- Generates a string representation of any range or value fields that are set -- for the given tree node. -- @param node the node -- @return a string containing concatenated representations of any tensors -- stored in the `ranges_absolute`, `cf_values`, or `cf_values_br` fields of -- the node. -- @local function TreeVisualiser:add_range_info(node) local out = "" if(node.ranges_absolute) then out = out .. self:add_tensor(node.ranges_absolute[1], 'abs_range1') out = out .. self:add_tensor(node.ranges_absolute[2], 'abs_range2') end if(node.cf_values) then --cf values computed by real tree dfs out = out .. self:add_tensor(node.cf_values[1], 'cf_values1') out = out .. self:add_tensor(node.cf_values[2], 'cf_values2') end if(node.cf_values_br) then --cf values that br has in real tree out = out .. self:add_tensor(node.cf_values_br[1], 'cf_values_br1') out = out .. self:add_tensor(node.cf_values_br[2], 'cf_values_br2') end return out end --- Generates data for a graphical representation of a node in a public tree. -- @param node the node to generate data for -- @return a table containing `name`, `label`, and `shape` fields for graphviz -- @local function TreeVisualiser:node_to_graphviz(node) local out = {} --1.0 label out.label = '"' .. node.current_player if node.terminal then if node.type == constants.node_types.terminal_fold then out.label = out.label .. '| TERMINAL FOLD' elseif node.type == constants.node_types.terminal_call then out.label = out.label .. '| TERMINAL CALL' else assert('unknown terminal node type') end else out.label = out.label .. '| bet1: ' .. node.bets[constants.players.P1] .. '| bet2: ' .. node.bets[constants.players.P2] if node.street then out.label = out.label .. '| street: ' .. node.street out.label = out.label .. '| board: ' .. card_to_string:cards_to_string(node.board) out.label = out.label .. '| depth: ' .. node.depth end end if node.margin then out.label = out.label .. '| margin: ' .. node.margin end out.label = out.label .. self:add_range_info(node) if(node.cfv_infset) then out.label = out.label .. '| cfv1: ' .. node.cfv_infset[1] out.label = out.label .. '| cfv2: ' .. node.cfv_infset[2] out.label = out.label .. '| cfv_br1: ' .. node.cfv_br_infset[1] out.label = out.label .. '| cfv_br2: ' .. node.cfv_br_infset[2] out.label = out.label .. '| epsilon1: ' .. node.epsilon[1] out.label = out.label .. '| epsilon2: ' .. node.epsilon[2] end if node.lookahead_coordinates then out.label = out.label .. '| COORDINATES ' out.label = out.label .. '| action_id: ' .. node.lookahead_coordinates[1] out.label = out.label .. '| parent_action_id: ' .. node.lookahead_coordinates[2] out.label = out.label .. '| gp_id: ' .. node.lookahead_coordinates[3] end out.label = out.label .. '"' --2.0 name out.name = '"node' .. self.node_to_graphviz_counter .. '"' --3.0 shape out.shape = '"record"' self.node_to_graphviz_counter = self.node_to_graphviz_counter + 1 return out end --- Generates data for graphical representation of a public tree action as an -- edge in a tree. -- @param from the graphical node the edge comes from -- @param to the graphical node the edge goes to -- @param node the public tree node before at which the action is taken -- @param child_node the public tree node that results from taking the action -- @return a table containing fields `id_from`, `id_to`, `id` for graphviz and -- a `strategy` field to use as a label for the edge -- @local function TreeVisualiser:nodes_to_graphviz_edge(from, to, node, child_node) local out = {} out.id_from = from.name out.id_to = to.name out.id = self.edge_to_graphviz_counter --get the child id of the child node local child_id = -1 for i=1,#node.children do if node.children[i] == child_node then child_id = i end end assert(child_id ~= -1) out.strategy = self:add_tensor(node.strategy[child_id], nil, "%.2f") self.edge_to_graphviz_counter = self.edge_to_graphviz_counter + 1 return out end --- Recursively generates graphviz data from a public tree. -- @param node the current node in the public tree -- @param nodes a table of graphical nodes generated so far -- @param edges a table of graphical edges generated so far -- @local function TreeVisualiser:graphviz_dfs(node, nodes, edges) local gv_node = self:node_to_graphviz(node) table.insert(nodes, gv_node) for i = 1,#node.children do local child_node = node.children[i] local gv_node_child = self:graphviz_dfs(child_node, nodes, edges) local gv_edge = self:nodes_to_graphviz_edge(gv_node, gv_node_child, node, child_node) table.insert(edges, gv_edge) end return gv_node end --- Generates `.dot` and `.svg` image files which graphically represent -- a game's public tree. -- -- Each node in the image lists the acting player, the number of chips -- committed by each player, the current betting round, public cards, -- and the depth of the subtree after the node, as well as any probabilities -- or values stored in the `ranges_absolute`, `cf_values`, or `cf_values_br` -- fields of the node. -- -- Each edge in the image lists the probability of the action being taken -- with each private card. -- -- @param root the root of the game's public tree -- @param filename a name used for the output files function TreeVisualiser:graphviz(root, filename) filename = filename or 'tree_2.dot' local out = 'digraph g { graph [ rankdir = "LR"];node [fontsize = "16" shape = "ellipse"]; edge [];' local nodes = {} local edges = {} self:graphviz_dfs(root, nodes, edges) for i = 1, #nodes do local node = nodes[i] local node_text = node.name .. '[' .. 'label=' .. node.label .. ' shape = ' .. node.shape .. '];' out = out .. node_text end for i = 1, #edges do local edge = edges[i] local edge_text = edge.id_from .. ':f0 -> ' .. edge.id_to .. ':f0 [ id = ' .. edge.id .. ' label = "' .. edge.strategy .. '"];' out = out .. edge_text end out = out .. '}' --write into dot file local file = io.open (arguments.data_directory .. 'Dot/' .. filename, 'w') file:write(out) file:close() --run graphviz program to generate image os.execute( 'dot ' .. arguments.data_directory .. 'Dot/' .. filename .. ' -Tsvg -O') end ================================================ FILE: Source/tools.lua ================================================ --- Assorted tools. --@module tools local M = {C = {}, max_choose = 55} --- Generates a string representation of a table. --@param table the table --@return the string function M:table_to_string(table) local out = "{" for key,value in pairs(table) do local val_string = '' if type(value) == 'table' then val_string = self:table_to_string(value) else val_string = tostring(value) end out = out .. tostring(key) .. ":" .. val_string .. ", " end out = out .. "}" return out end --- An arbitrarily large number used for clamping regrets. --@return the number function M:max_number() return 999999 end --- Initializes the choose table. -- @local function M:_init_choose() for i = 0,self.max_choose do for j = 0,self.max_choose do self.C[i*self.max_choose + j] = 0 end end for i = 0,self.max_choose do self.C[i*self.max_choose] = 1 self.C[i*self.max_choose + i] = 1 end for i = 1,self.max_choose do for j = 1,i do self.C[i*self.max_choose + j] = self.C[(i-1)*self.max_choose + j-1] + self.C[(i-1)*self.max_choose + j] end end end M:_init_choose() function M:choose(n, k) return self.C[n*self.max_choose + k] end return M ================================================ FILE: readme.md ================================================ # DeepHoldem This is an implementation of [DeepStack](https://www.deepstack.ai/s/DeepStack.pdf) for No Limit Texas Hold'em, extended from [DeepStack-Leduc](https://github.com/lifrordi/DeepStack-Leduc). ## Setup Running any of the DeepHoldem code requires [Lua](https://www.lua.org/) and [torch](http://torch.ch/). Please install torch with lua version 5.2 instead of LuaJIT. Torch is only officially supported for \*NIX based systems (i.e. Linux and Mac OS X). Connecting DeepHoldem to a server or running DeepHoldem on a server will require the [luasocket](http://w3.impa.br/~diego/software/luasocket/) package. This can be installed with [luarocks](https://luarocks.org/) (which is installed as part of the standard torch distribution) using the command `luarocks install luasocket`. Visualising the trees produced by DeepHoldem requires the [graphviz](http://graphviz.org/) package, which can be installed with `luarocks install graphviz`. Running the code on the GPU requires [cutorch](https://github.com/torch/cutorch) which can be installed with `luarocks install cutorch`. The HandRanks file was too big for github, so you will need to unzip it: `cd Source/Game/Evaluation && unzip HandRanks.zip` #### scatterAdd When you try to run DeepHoldem, you will eventually run into a problem where `scatterAdd` is not defined. Torch7 actually includes a C++ implementation of scatterAdd but for whatever reason, doesn't include a lua wrapper for it. I've included `TensorMath.lua` files in the torch folder of this repository that include the wrapper functions for both CPU and GPU. Copy them to their corresponding torch installation folders. Now, from your torch installation directory, run: ./clean.sh TORCH_LUA_VERSION=LUA52 ./install.sh and you should be good to go. ## Performance This implementation was tested against Slumbot 2017, the only publicly playable bot as of June 2018. The action abstraction used was half pot, pot and all in for first action, pot and all in for second action onwards. It achieved a baseline winrate of **42bb/100** after 2616 hands (equivalent to ~5232 duplicate hands). Notably, it achieved this playing inside of Slumbot's action abstraction space. ![](Data/Images/slumbot_stats.png) A comparison of preflop ranges was also done against [DeepStack's hand history](https://www.deepstack.ai/s/DeepStack_vs_IFP_pros.zip), showing similar results. | |DeepStack | DeepHoldem| |--- |--- | ---| |Open fold |![](Data/Images/deepstack_folds.png) | ![](Data/Images/my_folds.png)| |Open pot |![](Data/Images/deepstack_pots.png) | ![](Data/Images/my_pots.png)| |3bet pot after pot open |![](Data/Images/deepstack_3bets.png) | ![](Data/Images/my_3bets.png)| Average thinking times on NVIDIA Tesla P100: Street | Thinking Speed (s) --- | --- Preflop | 2.69 Flop | 12.42 Turn | 7.57 River | 3.33 Training details: ||# samples | Validation huber loss| | --- | --- | --- | |River network|1,000,000| 0.0415| |Turn network|1,000,000| 0.045| |Flop network|1,000,000| 0.013| |Preflop aux network|1,000,000| 0.0017| ## Creating your own models Other than the preflop auxiliary network, the counterfactual value networks are not included as part of this release, you will need to generate them yourself. The model generation pipeline is a bit different from the Leduc-Holdem implementation in that the data generated is saved to disk as raw solutions rather than bucketed solutions. This makes it easier to experiment with different bucketing methods. Here's a step by step guide to creating models: 1. `cd Source && th DataGeneration/main_data_generation.lua 4` 2. Wait for enough data to be generated. 3. Modify the last line of `Training/raw_converter.lua` to specify the folders where the raw training data (source folder) you got from step 1 is and where you want the bucketed training data (dest folder) to be. 4. `th Training/raw_converter.lua 4` 5. `th Training/main_train.lua 4` 6. Models will be generated under `Data/Models/NoLimit`. Pick the model you like best and place it inside `Data/Models/NoLimit/river` along with its .info file. Rename them to `final_cpu.info` and `final_cpu.model`. Please refer to the [DeepStack-Leduc](https://github.com/lifrordi/DeepStack-Leduc/blob/master/doc/manual/tutorial.md) tutorial if you want to convert them to GPU models. 7. Repeat steps 1-6 for turn and flop by replacing `4` with `3` or `2` and placing the models under the turn and flop folders. If you want to speed up data generation with a GPU, make sure to modify `Settings/arguments.lua` so that `params.gpu = true` ## Playing against DeepHoldem `Player/manual_player.lua` is supplied so you can play against DeepHoldem for preflop situations. If you want DeepHoldem to work for flop, turn and river, you will need to create your own models. 1. `cd ACPCServer && make` 2. `./dealer testMatch holdem.nolimit.2p.reverse_blinds.game 1000 0 Alice Bob` 3. 2 ports will be output, note them down as port1 and port2 4. Open a second terminal and `cd Source && th Player/manual_player.lua ` 5. Open a third terminal and `cd Source && th Player/deepstack.lua `. It will take about 20 minutes to load all the flop buckets, but this is actually not necessary until you've created your own flop model. You can skip the flop bucket computation by commenting out line 44 of `Source/Nn/next_round_value_pre.lua`. 6. Once the deepstack player is done loading, you can play against it using manual_player terminal. `f` = fold, `c` = check/call, `450` = raise my total pot commitment to 450 chips. ## Differences from the original paper - A river model was used instead of solving directly from the turn - Different neural net architecture - Batch normalization layers were added in between hidden layers because they were found to improve huber loss - Only 3 hidden layers were used. Additional layers didn't improve huber loss, in agreement with the paper. - Preflop solving was done with auxiliary network only, whereas paper used 20 iterations of flop network - Because of this, the cfvs for a given flop must be calculated after seeing it by solving the preflop again with the current flop in mind - During re-solving, the opponent ranges were not warm started ## Future work - Warm start opponent ranges for re-solving - Cache flop buckets so initializing next_round_value_pre doesn't take 20 minutes - Speed up flop solving (use flop network during preflop solving?) - Support LuaJIT - C++ implementation? ================================================ FILE: torch/extra/cutorch/TensorMath.lua ================================================ local wrap = require 'cwrap' local interface = wrap.CInterface.new() local method = wrap.CInterface.new() local argtypes = wrap.CInterface.argtypes argtypes['ptrdiff_t'] = { helpname = function(arg) return 'ptrdiff_t' end, declare = function(arg) -- if it is a number we initialize here local default = tonumber(tostring(arg.default)) or 0 return string.format("%s arg%d = %g;", 'ptrdiff_t', arg.i, default) end, check = function(arg, idx) return string.format("lua_isinteger(L, %d)", idx) end, read = function(arg, idx) return string.format("arg%d = (%s)lua_tointeger(L, %d);", arg.i, 'ptrdiff_t', idx) end, init = function(arg) -- otherwise do it here if arg.default then local default = tostring(arg.default) if not tonumber(default) then return string.format("arg%d = %s;", arg.i, default) end end end, carg = function(arg) return string.format('arg%d', arg.i) end, creturn = function(arg) return string.format('arg%d', arg.i) end, precall = function(arg) if arg.returned then return string.format('lua_pushinteger(L, (lua_Integer)arg%d);', arg.i) end end, postcall = function(arg) if arg.creturned then return string.format('lua_pushinteger(L, (lua_Integer)arg%d);', arg.i) end end } interface:print('/* WARNING: autogenerated file */') interface:print('') interface:print('#include "THC.h"') interface:print('#include "luaT.h"') interface:print('#include "torch/utils.h"') interface:print('') interface:print('') interface:print([[ static int torch_isnonemptytable(lua_State *L, int idx) { int empty; if (!lua_istable(L, idx)) return 0; lua_rawgeti(L, idx, 1); empty = lua_isnil(L, -1); lua_pop(L, 1); return !empty; } ]]) -- Lua 5.2 compatibility local unpack = unpack or table.unpack -- specific to CUDA local typenames = { 'CudaByteTensor', 'CudaCharTensor', 'CudaShortTensor', 'CudaIntTensor', 'CudaLongTensor', 'CudaTensor', 'CudaDoubleTensor', 'CudaHalfTensor' } for _, typename in ipairs(typenames) do -- cut and paste from wrap/types.lua wrap.types[typename] = { helpname = function(arg) if arg.dim then return string.format('%s~%dD', typename, arg.dim) else return typename end end, declare = function(arg) local txt = {} table.insert(txt, string.format("TH%s *arg%d = NULL;", typename, arg.i)) if arg.returned then table.insert(txt, string.format("int arg%d_idx = 0;", arg.i)); end return table.concat(txt, '\n') end, check = function(arg, idx) if arg.dim then return string.format('(arg%d = luaT_toudata(L, %d, "torch.%s")) && (arg%d->nDimension == %d)', arg.i, idx, typename, arg.i, arg.dim) else return string.format('(arg%d = luaT_toudata(L, %d, "torch.%s"))', arg.i, idx, typename) end end, read = function(arg, idx) if arg.returned then return string.format("arg%d_idx = %d;", arg.i, idx) end end, init = function(arg) if type(arg.default) == 'boolean' then return string.format('arg%d = TH%s_new(cutorch_getstate(L));', arg.i, typename) elseif type(arg.default) == 'number' then return string.format('arg%d = %s;', arg.i, arg.args[arg.default]:carg()) else error('unknown default tensor type value') end end, carg = function(arg) return string.format('arg%d', arg.i) end, creturn = function(arg) return string.format('arg%d', arg.i) end, precall = function(arg) local txt = {} if arg.default and arg.returned then table.insert(txt, string.format('if(arg%d_idx)', arg.i)) -- means it was passed as arg table.insert(txt, string.format('lua_pushvalue(L, arg%d_idx);', arg.i)) table.insert(txt, string.format('else')) if type(arg.default) == 'boolean' then -- boolean: we did a new() table.insert(txt, string.format('luaT_pushudata(L, arg%d, "torch.%s");', arg.i, typename)) else -- otherwise: point on default tensor --> retain table.insert(txt, string.format('{')) table.insert(txt, string.format('TH%s_retain(arg%d);', typename, arg.i)) -- so we need a retain table.insert(txt, string.format('luaT_pushudata(L, arg%d, "torch.%s");', arg.i, typename)) table.insert(txt, string.format('}')) end elseif arg.default then -- we would have to deallocate the beast later if we did a new -- unlikely anyways, so i do not support it for now if type(arg.default) == 'boolean' then error('a tensor cannot be optional if not returned') end elseif arg.returned then table.insert(txt, string.format('lua_pushvalue(L, arg%d_idx);', arg.i)) end return table.concat(txt, '\n') end, postcall = function(arg) local txt = {} if arg.creturned then -- if a tensor is returned by a wrapped C function, the refcount semantics -- are ambiguous (transfer ownership vs. shared ownership). -- We never actually do this, so lets just not allow it. error('a tensor cannot be creturned') end return table.concat(txt, '\n') end } wrap.types[typename .. 'Array'] = { helpname = function(arg) return string.format('{%s+}', typename) end, declare = function(arg) local txt = {} table.insert(txt, string.format('TH%s **arg%d_data = NULL;', typename, arg.i)) table.insert(txt, string.format('long arg%d_size = 0;', arg.i)) table.insert(txt, string.format('int arg%d_i = 0;', arg.i)) return table.concat(txt, '\n') end, check = function(arg, idx) return string.format('torch_isnonemptytable(L, %d)', idx) end, read = function(arg, idx) local txt = {} -- Iterate over the array to find its length, leave elements on stack. table.insert(txt, string.format('do')) table.insert(txt, string.format('{')) table.insert(txt, string.format(' arg%d_size++;', arg.i)) table.insert(txt, string.format(' lua_checkstack(L, 1);')) table.insert(txt, string.format(' lua_rawgeti(L, %d, arg%d_size);', idx, arg.i)) table.insert(txt, string.format('}')) table.insert(txt, string.format('while (!lua_isnil(L, -1));')) table.insert(txt, string.format('arg%d_size--;', arg.i)) -- Pop nil element from stack. table.insert(txt, string.format('lua_pop(L, 1);')) -- Allocate tensor pointers and read values from stack backwards. table.insert(txt, string.format('arg%d_data = (TH%s**)THAlloc(arg%d_size * sizeof(TH%s*));', arg.i, typename, arg.i, typename)) table.insert(txt, string.format('for (arg%d_i = arg%d_size - 1; arg%d_i >= 0; arg%d_i--)', arg.i, arg.i, arg.i, arg.i)) table.insert(txt, string.format('{')) table.insert(txt, string.format(' if (!(arg%d_data[arg%d_i] = luaT_toudata(L, -1, "torch.%s")))', arg.i, arg.i, typename)) table.insert(txt, string.format(' luaL_error(L, "expected %s in tensor array");', typename)) table.insert(txt, string.format(' lua_pop(L, 1);')) table.insert(txt, string.format('}')) table.insert(txt, string.format('')) return table.concat(txt, '\n') end, init = function(arg) end, carg = function(arg) return string.format('arg%d_data,arg%d_size', arg.i, arg.i) end, creturn = function(arg) error('TensorArray cannot be returned.') end, precall = function(arg) end, postcall = function(arg) return string.format('THFree(arg%d_data);', arg.i) end } end local function interpretdefaultvalue(arg) local default = arg.default if type(default) == 'boolean' then if default then return '1' else return '0' end elseif type(default) == 'number' then return tostring(default) elseif type(default) == 'string' then return default elseif type(default) == 'function' then default = default(arg) assert(type(default) == 'string', 'a default function must return a string') return default elseif type(default) == 'nil' then return nil else error('unknown default type value') end end wrap.types.half = { helpname = function(arg) return "half" end, declare = function(arg) -- if it is a number we initialize here local default = tonumber(interpretdefaultvalue(arg)) or 0 return string.format("half arg%d = THC_float2half((float) %d);", arg.i, tonumber(default)) end, check = function(arg, idx) return string.format("lua_isnumber(L, %d)", idx) end, read = function(arg, idx) return string.format("arg%d = THC_float2half((float) lua_tonumber(L, %d));", arg.i, idx) end, init = function(arg) -- otherwise do it here if arg.default then local default = interpretdefaultvalue(arg) if not tonumber(default) then return string.format("arg%d = THC_float2half((float) %s);", arg.i, default) end end end, carg = function(arg) return string.format('arg%d', arg.i) end, creturn = function(arg) return string.format('arg%d', arg.i) end, precall = function(arg) if arg.returned then return string.format('lua_pushnumber(L, (lua_Number) THC_half2float(arg%d));', arg.i) end end, postcall = function(arg) if arg.creturned then return string.format('lua_pushnumber(L, (lua_Number) THC_half2float(arg%d));', arg.i) end end } wrap.types.LongArg = { vararg = true, helpname = function(arg) return "(LongStorage | dim1 [dim2...])" end, declare = function(arg) return string.format("THLongStorage *arg%d = NULL;", arg.i) end, init = function(arg) if arg.default then error('LongArg cannot have a default value') end end, check = function(arg, idx) return string.format("cutorch_islongargs(L, %d)", idx) end, read = function(arg, idx) return string.format("arg%d = cutorch_checklongargs(L, %d);", arg.i, idx) end, carg = function(arg, idx) return string.format('arg%d', arg.i) end, creturn = function(arg, idx) return string.format('arg%d', arg.i) end, precall = function(arg) local txt = {} if arg.returned then table.insert(txt, string.format('luaT_pushudata(L, arg%d, "torch.LongStorage");', arg.i)) end return table.concat(txt, '\n') end, postcall = function(arg) local txt = {} if arg.creturned then -- this next line is actually debatable table.insert(txt, string.format('THLongStorage_retain(arg%d);', arg.i)) table.insert(txt, string.format('luaT_pushudata(L, arg%d, "torch.LongStorage");', arg.i)) end if not arg.returned and not arg.creturned then table.insert(txt, string.format('THLongStorage_free(arg%d);', arg.i)) end return table.concat(txt, '\n') end } wrap.types.charoption = { helpname = function(arg) if arg.values then return "(" .. table.concat(arg.values, '|') .. ")" end end, declare = function(arg) local txt = {} table.insert(txt, string.format("const char *arg%d = NULL;", arg.i)) if arg.default then table.insert(txt, string.format("char arg%d_default = '%s';", arg.i, arg.default)) end return table.concat(txt, '\n') end, init = function(arg) return string.format("arg%d = &arg%d_default;", arg.i, arg.i) end, check = function(arg, idx) local txt = {} local txtv = {} table.insert(txt, string.format('(arg%d = lua_tostring(L, %d)) && (', arg.i, idx)) for _,value in ipairs(arg.values) do table.insert(txtv, string.format("*arg%d == '%s'", arg.i, value)) end table.insert(txt, table.concat(txtv, ' || ')) table.insert(txt, ')') return table.concat(txt, '') end, read = function(arg, idx) end, carg = function(arg, idx) return string.format('arg%d', arg.i) end, creturn = function(arg, idx) end, precall = function(arg) end, postcall = function(arg) end } cutorch_state_code = function(varname) local txt = {} table.insert(txt, 'lua_getglobal(L, "cutorch");') table.insert(txt, 'lua_getfield(L, -1, "_state");') table.insert(txt, string.format('THCState *%s = lua_touserdata(L, -1);', varname)) table.insert(txt, 'lua_pop(L, 2);') return table.concat(txt, '\n'); end interface:registerDefaultArgument(cutorch_state_code) method:registerDefaultArgument(cutorch_state_code) local function wrap(...) local args = {...} -- interface interface:wrap(...) -- method: we override things possibly in method table field for _,x in ipairs(args) do if type(x) == 'table' then -- ok, now we have a list of args for _, arg in ipairs(x) do if arg.method then for k,v in pairs(arg.method) do if v == 'nil' then -- special case, we erase the field arg[k] = nil else arg[k] = v end end end end end end method:wrap(unpack(args)) end local Tensor -- functions to help take in arguments that are Tensor or CudaLongTensor (for backward compatibility) -- used in scatter / gather for example local function TensorToCudaLong_declare(dummy) return function(arg) local txt = {} table.insert(txt, string.format("THCudaLongTensor *arg%d = NULL;", arg.i)) if dummy then table.insert(txt, string.format("THCudaLongTensor *indexLongTensor = NULL;")) table.insert(txt, string.format("TH%s *dummyIndexTensor = NULL;", Tensor)) end return table.concat(txt, '\n') end end local function TensorToCudaLong_check(arg, idx) return string.format('(dummyIndexTensor = luaT_toudata(L, %d, "torch.%s"))', idx, Tensor) end local function TensorToCudaLong_read(arg, idx) local copyname = Tensor:match("(%a+)Tensor") if copyname == 'Cuda' then copyname = 'CudaFloat' end local txt = {} table.insert(txt, string.format('arg%d = THCudaLongTensor_new(default_arg1);', arg.i)) table.insert(txt, string.format('THLongStorage *indexSize = TH%s_newSizeOf(default_arg1, dummyIndexTensor);', Tensor)) table.insert(txt, string.format('THCudaLongTensor_resize(default_arg1, arg%d, indexSize, NULL);', arg.i)) table.insert(txt, string.format('THLongStorage_free(indexSize);')) table.insert(txt, string.format('THCudaLongTensor_copy%s(default_arg1, arg%d, dummyIndexTensor);', copyname, arg.i)) table.insert(txt, string.format('indexLongTensor = arg%d;', arg.i)) return table.concat(txt, '\n') end local function TensorToCudaLong_postcall(arg) return "if (indexLongTensor != NULL) THCudaLongTensor_free(default_arg1, indexLongTensor);\n" end -- function to initialize the gather call local function gatherInit(arg) return table.concat( { arg.__metatable.init(arg), string.format("TH%s_checkGPU(cutorch_getstate(L), 1, %s);", Tensor, arg.args[4]:carg()), string.format( [[ THCState *state = cutorch_getstate(L); THLongStorage *indicesSize = THCudaLongTensor_newSizeOf(state, %s); TH%s_resize(state, %s, indicesSize, NULL); THLongStorage_free(indicesSize); ]], arg.args[4]:carg(), Tensor, arg:carg()), }, '\n') end -- -- Non-CudaTensor type math, since these are less fully implemented than -- CudaTensor -- local handledTypenames = { 'CudaByteTensor', 'CudaCharTensor', 'CudaShortTensor', 'CudaIntTensor', 'CudaLongTensor', 'CudaDoubleTensor', 'CudaHalfTensor', } local handledTypereals = { 'unsigned char', 'char', 'short', 'int', 'long', 'double', 'half' } local handledTypeaccreals = { 'long', 'long', 'long', 'long', 'long', 'double', 'float' } for k, Tensor_ in pairs(handledTypenames) do Tensor = Tensor_ if Tensor == 'CudaHalfTensor' then interface:print("#ifdef CUDA_HALF_TENSOR") end local real = handledTypereals[k] local accreal = handledTypeaccreals[k] function interface.luaname2wrapname(self, name) return string.format('cutorch_%s_%s', Tensor, name) end function method.luaname2wrapname(self, name) return string.format('m_cutorch_%s_%s', Tensor, name) end local function cname(name) return string.format('TH%s_%s', Tensor, name) end local function lastdim(argn) return function(arg) return string.format('TH%s_nDimension(cutorch_getstate(L), %s)', Tensor, arg.args[argn]:carg()) end end local function lastdimarray(argn) return function(arg) return string.format('TH%s_nDimension(cutorch_getstate(L), arg%d_data[0])', Tensor, arg.args[argn].i) end end wrap("fill", cname("fill"), {{name=Tensor, returned=true}, {name=real}}) wrap("zero", cname("zero"), {{name=Tensor, returned=true}}) wrap("zeros", cname("zeros"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name="LongArg"}}) wrap("ones", cname("ones"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name="LongArg"}}) wrap("reshape", cname("reshape"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="LongArg"}}) wrap("numel", cname("numel"), {{name=Tensor}, {name="ptrdiff_t", creturned=true}}) wrap("add", cname("add"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}, cname("cadd"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real, default=1}, {name=Tensor}}) wrap("csub", cname("sub"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}, cname("csub"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real, default=1}, {name=Tensor}}) wrap("mul", cname("mul"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("clamp", cname("clamp"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}, {name=real}}) wrap("cross", cname("cross"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name=Tensor}, {name="index", default=0}}) wrap("div", cname("div"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("lshift", cname("lshift"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("rshift", cname("rshift"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("fmod", cname("fmod"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("remainder", cname("remainder"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("bitand", cname("bitand"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("bitor", cname("bitor"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("bitxor", cname("bitxor"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("equal", cname("equal"), {{name=Tensor}, {name=Tensor}, {name="boolean", creturned=true}}) local cfuncs = {"cmul", "cpow", "cdiv", "cremainder", "cfmod", "clshift", "crshift", "cbitand", "cbitor", "cbitxor"} for _, name in ipairs(cfuncs) do wrap(name, cname(name), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}}) end wrap("addcmul", cname("addcmul"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real, default=1}, {name=Tensor}, {name=Tensor}}) wrap("addcdiv", cname("addcdiv"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real, default=1}, {name=Tensor}, {name=Tensor}}) for _,name in ipairs({"min", "max"}) do wrap(name, cname(name .. "all"), {{name=Tensor}, {name=real, creturned=true}}, cname(name), {{name=Tensor, default=true, returned=true}, {name='CudaLongTensor', default=true, returned=true}, {name=Tensor}, {name="index"}, {name="boolean", default=true, invisible=true}}) end for _,name in ipairs({"cmin", "cmax"}) do wrap(name, cname(name), {{name=Tensor, default=true, returned=true}, {name=Tensor, method={default=1}}, {name=Tensor}}, cname(name .. "Value"), {{name=Tensor, default=true, returned=true}, {name=Tensor, method={default=1}}, {name=real}}) end if Tensor == 'CudaByteTensor' then for _,name in pairs({'all', 'any'}) do wrap(name, cname('logical' .. name), {{name=Tensor}, {name="boolean", creturned=true}}) end end for _,name in pairs({'lt','gt','le','ge','eq','ne'}) do wrap(name, cname(name .. 'Value'), {{name='CudaByteTensor',default=true, returned=true}, {name=Tensor}, {name=real}}, cname(name .. 'ValueT'), {{name=Tensor, returned=true}, {name=Tensor}, {name=real}}, cname(name .. 'Tensor'), {{name='CudaByteTensor',default=true, returned=true}, {name=Tensor}, {name=Tensor}}, cname(name .. 'TensorT'), {{name=Tensor, returned=true}, {name=Tensor}, {name=Tensor}}) end wrap("sum", cname("sumall"), {{name=Tensor}, {name=accreal, creturned=true}}, cname("sum"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="index"}, {name="boolean", default=true, invisible=true}}) for _, name in ipairs({"cumsum", "cumprod"}) do wrap(name, cname(name), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="index", default=1}}) end wrap("prod", cname("prodall"), {{name=Tensor}, {name=accreal, creturned=true}}, cname("prod"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="index"}, {name="boolean", default=true, invisible=true}}) wrap("mean", cname("meanall"), {{name=Tensor}, {name=accreal, creturned=true}}, cname("mean"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="index"}, {name="boolean", default=true, invisible=true}}) wrap("maskedFill", cname("maskedFill"), {{name=Tensor, returned=true, method={default='nil'}}, {name='CudaByteTensor'}, {name=real}}) wrap("maskedCopy", cname("maskedCopy"), {{name=Tensor, returned=true, method={default='nil'}}, {name='CudaByteTensor'}, {name=Tensor}}) wrap("maskedSelect", cname("maskedSelect"), {{name=Tensor, returned=true, default=true}, {name=Tensor}, {name='CudaByteTensor'}}) wrap("gather", cname("gather"), {{name=Tensor, default=true, returned=true, init=gatherInit}, {name=Tensor}, {name="index"}, {name='CudaLongTensor'}}, cname("gather"), -- this is for backward-compatibility, and takes in "Tensor" as the indexing tensor {{name=Tensor, default=true, returned=true, init=gatherInit}, {name=Tensor}, {name="index"}, {name=Tensor, declare=TensorToCudaLong_declare(true), check=TensorToCudaLong_check, read=TensorToCudaLong_read, postcall=TensorToCudaLong_postcall}}) wrap("scatter", cname("scatter"), {{name=Tensor, returned=true}, {name="index"}, {name='CudaLongTensor'}, {name=Tensor}}, cname("scatter"), -- this is for backward-compatibility, and takes in "Tensor" as the indexing tensor {{name=Tensor, returned=true}, {name="index"}, {name=Tensor, declare=TensorToCudaLong_declare(true), check=TensorToCudaLong_check, read=TensorToCudaLong_read, postcall=TensorToCudaLong_postcall}, {name=Tensor}}, cname("scatterFill"), {{name=Tensor, returned=true}, {name="index"}, {name='CudaLongTensor'}, {name=real}}, cname("scatterFill"), -- this is for backward-compatibility, and takes in "Tensor" as the indexing tensor {{name=Tensor, returned=true}, {name="index"}, {name=Tensor, declare=TensorToCudaLong_declare(false), check=TensorToCudaLong_check, read=TensorToCudaLong_read, postcall=TensorToCudaLong_postcall}, {name=real}} ) wrap("scatterAdd", cname("scatterAdd"), {{name=Tensor, returned=true}, {name="index"}, {name='CudaLongTensor'}, {name=Tensor}}) wrap("sort", cname("sort"), {{name=Tensor, default=true, returned=true}, {name="CudaLongTensor", default=true, returned=true, noreadadd=true}, {name=Tensor}, {name="index", default=lastdim(3)}, {name="boolean", default=0}} ) wrap("topk", cname("topk"), {{name=Tensor, default=true, returned=true}, {name="CudaLongTensor", default=true, returned=true, noreadadd=true}, {name=Tensor}, {name="long", default=1}, {name="index", default=lastdim(3)}, {name="boolean", default=0}, {name="boolean", default=0}}) wrap("mode", cname("mode"), {{name=Tensor, default=true, returned=true, noreadadd=true}, {name="CudaLongTensor", default=true, returned=true, noreadadd=true}, {name=Tensor}, {name="index", default=lastdim(3)}, {name="boolean", default=true, invisible=true}}) wrap("squeeze", cname("squeeze"), {{name=Tensor, default=true, returned=true, postcall=function(arg) local txt = {} if arg.returned then table.insert(txt, string.format('if(arg%d->nDimension == 1 && arg%d->size[0] == 1)', arg.i, arg.i)) -- number if Tensor == 'CudaHalfTensor' then table.insert(txt, string.format('lua_pushnumber(L, (lua_Number)THC_half2float(TH%s_get1d(cutorch_getstate(L), arg%d, 0)));', Tensor, arg.i)) else table.insert(txt, string.format('lua_pushnumber(L, (lua_Number)(TH%s_get1d(cutorch_getstate(L), arg%d, 0)));', Tensor, arg.i)) end end return table.concat(txt, '\n') end}, {name=Tensor}}, cname("squeeze1d"), {{name=Tensor, default=true, returned=true, postcall= function(arg) local txt = {} if arg.returned then table.insert(txt, string.format('if(!hasdims && arg%d->nDimension == 1 && arg%d->size[0] == 1)', arg.i, arg.i)) -- number if Tensor == 'CudaHalfTensor' then table.insert(txt, string.format('lua_pushnumber(L, (lua_Number)THC_half2float(TH%s_get1d(cutorch_getstate(L), arg%d, 0)));}', Tensor, arg.i)) else table.insert(txt, string.format('lua_pushnumber(L, (lua_Number)(TH%s_get1d(cutorch_getstate(L), arg%d, 0)));}', Tensor, arg.i)) end end return table.concat(txt, '\n') end}, {name=Tensor, precall= function(arg) return string.format('{int hasdims = arg%d->nDimension > 1;', arg.i) end}, {name="index"}}) wrap("abs", cname("abs"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}}) wrap("sign", cname("sign"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}}) wrap("cat", cname("cat"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name=Tensor}, {name="index", default=-1}}, cname("catArray"), {{name=Tensor, default=true, returned=true}, {name=Tensor .. "Array"}, {name="index", default=-1}}) wrap("geometric", cname("geometric"), {{name=Tensor, returned=true}, {name='double'}}) wrap("bernoulli", cname("bernoulli"), {{name=Tensor, returned=true}, {name='double', default=0.5}}, cname("bernoulli_FloatTensor"), {{name=Tensor, returned=true}, {name="CudaTensor"}}, cname("bernoulli_DoubleTensor"), {{name=Tensor, returned=true}, {name="CudaDoubleTensor"}}) wrap("nonzero", cname("nonzero"), {{name="CudaLongTensor", default=true, returned=true}, {name=Tensor}}) wrap("range", cname("range"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=accreal}, {name=accreal}, {name=accreal, default=1}}) if real == 'float' or real == 'double' or real == 'half' then for _,name in ipairs({"log", "log1p", "exp", "cos", "acos", "cosh", "sin", "asin", "sinh", "tan", "atan", "tanh", "sqrt", "rsqrt", "sigmoid", "cinv", "ceil", "floor", "neg", "round", "trunc", "frac"}) do wrap(name, cname(name), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}}) end wrap("linspace", cname("linspace"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=real}, {name=real}, {name="long", default=100}}) wrap("logspace", cname("logspace"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=real}, {name=real}, {name="long", default=100}}) wrap("pow", cname("pow"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}, cname("tpow"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name = real}, {name=Tensor, method={default=1}}}) wrap("rand", cname("rand"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name="LongArg"}}) wrap("randn", cname("randn"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name="LongArg"}}) wrap("multinomial", cname("multinomial"), {{name='CudaLongTensor', default=true, returned=true, method={default='nil'}}, {name=Tensor}, {name="int"}, {name="boolean", default=false}}) wrap("multinomialAliasSetup_", cname("multinomialAliasSetup"), {{name=Tensor}, {name='CudaLongTensor', default=true, returned=true, method={default='nil'}}, {name=Tensor, default=true, returned=true, method={default='nil'}}}) wrap("multinomialAlias_", cname("multinomialAliasDraw"), {{name="CudaLongTensor", default=true, returned=true, method={default='nil'}}, {name="CudaLongTensor"}, {name=Tensor} }) for _,f in ipairs({{name='uniform', a=0, b=1}, {name='cauchy', a=0, b=1}, {name='normal', a=0, b=1}, {name='logNormal', a=1, b=2}}) do wrap(f.name, cname(f.name), {{name=Tensor, returned=true}, {name='double', default=f.a}, {name='double', default=f.b}}) end wrap('exponential', cname('exponential'), {{name=Tensor, returned=true}, {name='double', default=nil}}) wrap("norm", cname("normall"), {{name=Tensor}, {name=real, default=2}, {name=accreal, creturned=true}}, cname("norm"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name=real}, {name="index"}, {name="boolean", default=true, invisible=true}}) wrap("renorm", cname("renorm"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}, {name="index"}, {name=real}}) wrap("dist", cname("dist"), {{name=Tensor}, {name=Tensor}, {name=real, default=2}, {name=accreal, creturned=true}}) for _,name in ipairs({"var", "std"}) do wrap(name, cname(name .. "all"), {{name=Tensor}, {name="boolean", default=false}, {name=accreal, creturned=true}}, cname(name), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="index"}, {name="boolean", default=false}, {name="boolean", default=true, invisible=true}}) end wrap("tril", cname("tril"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="int", default=0}}) wrap("triu", cname("triu"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="int", default=0}}) wrap("diag", cname("diag"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="int", default=0}}) wrap("trace", cname("trace"), {{name=Tensor}, {name=accreal, creturned=true}}) wrap("lerp", cname("lerp"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}, {name=real}}) -- BLAS functions wrap("mv", cname("addmv"), {{name=Tensor, default=true, returned=true, method={default='nil'}, init=function(arg) return table.concat( { arg.__metatable.init(arg), string.format("TH%s_checkGPU(cutorch_getstate(L), 1, %s);", Tensor, arg.args[5]:carg()), string.format("TH%s_resize1d(cutorch_getstate(L), %s, %s->size[0]);", Tensor, arg:carg(), arg.args[5]:carg()) }, '\n') end, precall=function(arg) return table.concat( { string.format("TH%s_zero(cutorch_getstate(L), %s);", Tensor, arg:carg()), arg.__metatable.precall(arg) }, '\n') end }, {name=real, default=1, invisible=true}, {name=Tensor, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=2}, {name=Tensor, dim=1}} ) wrap("mm", cname("addmm"), {{name=Tensor, default=true, returned=true, method={default='nil'}, init=function(arg) return table.concat( { arg.__metatable.init(arg), string.format("TH%s_checkGPU(cutorch_getstate(L), 2, %s, %s);", Tensor, arg.args[5]:carg(), arg.args[6]:carg()), string.format("TH%s_resize2d(cutorch_getstate(L), %s, %s->size[0], %s->size[1]);", Tensor, arg:carg(), arg.args[5]:carg(), arg.args[6]:carg()) }, '\n') end, }, {name=real, default=0, invisible=true}, {name=Tensor, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=2}, {name=Tensor, dim=2}} ) wrap("bmm", cname("baddbmm"), {{name=Tensor, default=true, returned=true, method={default='nil'}, init=function(arg) return table.concat( { arg.__metatable.init(arg), string.format("TH%s_checkGPU(cutorch_getstate(L), 2, %s, %s);", Tensor, arg.args[5]:carg(), arg.args[6]:carg()), string.format("TH%s_resize3d(cutorch_getstate(L), %s, %s->size[0], %s->size[1], %s->size[2]);", Tensor, arg:carg(), arg.args[5]:carg(), arg.args[5]:carg(), arg.args[6]:carg()) }, '\n') end, }, {name=real, default=0, invisible=true}, {name=Tensor, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=3}, {name=Tensor, dim=3}} ) wrap("ger", cname("addr"), {{name=Tensor, default=true, returned=true, method={default='nil'}, init=function(arg) return table.concat( { arg.__metatable.init(arg), string.format("TH%s_checkGPU(cutorch_getstate(L), 2, %s, %s);", Tensor, arg.args[5]:carg(), arg.args[6]:carg()), string.format("TH%s_resize2d(cutorch_getstate(L), %s, %s->size[0], %s->size[0]);", Tensor, arg:carg(), arg.args[5]:carg(), arg.args[6]:carg()) }, '\n') end, precall=function(arg) return table.concat( { string.format("TH%s_zero(cutorch_getstate(L), %s);", Tensor, arg:carg()), arg.__metatable.precall(arg) }, '\n') end }, {name=real, default=1, invisible=true}, {name=Tensor, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=1}, {name=Tensor, dim=1}} ) for _,f in ipairs({ {name="addmv", dim1=1, dim2=2, dim3=1}, {name="addmm", dim1=2, dim2=2, dim3=2}, {name="addr", dim1=2, dim2=1, dim3=1}, {name="baddbmm", dim1=3, dim2=3, dim3=3}, {name="addbmm", dim1=2, dim2=3, dim3=3}, } ) do interface:wrap(f.name, cname(f.name), {{name=Tensor, default=true, returned=true}, {name=real, default=1}, {name=Tensor, dim=f.dim1}, {name=real, default=1}, {name=Tensor, dim=f.dim2}, {name=Tensor, dim=f.dim3}}) -- there is an ambiguity here, hence the more complicated setup method:wrap(f.name, cname(f.name), {{name=Tensor, returned=true, dim=f.dim1}, {name=real, default=1, invisible=true}, {name=Tensor, default=1, dim=f.dim1}, {name=real, default=1}, {name=Tensor, dim=f.dim2}, {name=Tensor, dim=f.dim3}}, cname(f.name), {{name=Tensor, returned=true, dim=f.dim1}, {name=real}, {name=Tensor, default=1, dim=f.dim1}, {name=real}, {name=Tensor, dim=f.dim2}, {name=Tensor, dim=f.dim3}}) end end if real == 'float' or real == 'double' then for _,name in ipairs({"gesv", "gels"}) do wrap(name, cname(name), {{name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor}, {name=Tensor}}, cname(name), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name=Tensor}}) end wrap("symeig", cname("syev"), {{name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor}, {name='charoption', values={'N', 'V'}, default='N'}, {name='charoption', values={'U', 'L'}, default='U'}}, cname("syev"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name='charoption', values={'N', 'V'}, default='N'}, {name='charoption', values={'U', 'L'}, default='U'}}) wrap("eig", cname("geev"), {{name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor}, {name='charoption', values={'N', 'V'}, default='N'}}, cname("geev"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name='charoption', values={'N', 'V'}, default='N'}}) wrap("svd", cname("gesvd"), {{name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor}, {name='charoption', values={'A', 'S'}, default='S'}}, cname("gesvd"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name='charoption', values={'A', 'S'}, default='S'}}) wrap("inverse", cname("getri"), {{name=Tensor, returned=true}, {name=Tensor}}, cname("getri"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}}) wrap("potri", cname("potri"), {{name=Tensor, returned=true}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}}, cname("potri"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}}) wrap("potrf", cname("potrf"), {{name=Tensor, returned=true}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}}, cname("potrf"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}}) wrap("potrs", cname("potrs"), {{name=Tensor, returned=true}, {name=Tensor}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}}, cname("potrs"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}}) wrap("qr", cname("qr"), {{name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor}}, cname("qr"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}}) end wrap("dot", cname("dot"), {{name=Tensor}, {name=Tensor}, {name=accreal, creturned=true}}) method:register("m_cutorch_" .. Tensor .. "Math__") interface:print(method:tostring()) method:clearhistory() method:registerDefaultArgument(cutorch_state_code) interface:register("cutorch_" .. Tensor .. "Math__") interface:print(string.format([[ void cutorch_%sMath_init(lua_State *L) { luaT_pushmetatable(L, "torch.%s"); /* register methods */ luaL_setfuncs(L, m_cutorch_%sMath__, 0); /* register functions into the "torch" field of the tensor metaclass */ lua_pushstring(L, "torch"); lua_newtable(L); luaL_setfuncs(L, cutorch_%sMath__, 0); lua_rawset(L, -3); lua_pop(L, 1); } ]], Tensor, Tensor, Tensor, Tensor)) if Tensor == 'CudaHalfTensor' then interface:print("#endif") end end -- -- CudaTensor special handling, since it is more fully implemented -- Tensor = "CudaTensor" local real = "float" function interface.luaname2wrapname(self, name) return string.format('cutorch_%s_%s', Tensor, name) end function method.luaname2wrapname(self, name) return string.format('m_cutorch_%s_%s', Tensor, name) end local function cname(name) return string.format('TH%s_%s', Tensor, name) end local function lastdim(argn) return function(arg) return string.format('TH%s_nDimension(cutorch_getstate(L), %s)', Tensor, arg.args[argn]:carg()) end end local function lastdimarray(argn) return function(arg) return string.format('TH%s_nDimension(cutorch_getstate(L), arg%d_data[0])', Tensor, arg.args[argn].i) end end wrap("zero", cname("zero"), {{name=Tensor, returned=true}}) wrap("fill", cname("fill"), {{name=Tensor, returned=true}, {name=real}}) wrap("zeros", cname("zeros"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name="LongArg"}}) wrap("ones", cname("ones"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name="LongArg"}}) wrap("linspace", cname("linspace"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=real}, {name=real}, {name="long", default=100}}) wrap("logspace", cname("logspace"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=real}, {name=real}, {name="long", default=100}}) wrap("reshape", cname("reshape"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="LongArg"}}) wrap("numel", cname("numel"), {{name=Tensor}, {name="long", creturned=true}}) wrap("add", cname("add"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}, cname("cadd"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real, default=1}, {name=Tensor}}) wrap("csub", cname("sub"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}, cname("csub"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real, default=1}, {name=Tensor}}) wrap("mul", cname("mul"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("div", cname("div"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("fmod", cname("fmod"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("remainder", cname("remainder"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("equal", cname("equal"), {{name=Tensor}, {name=Tensor}, {name="boolean", creturned=true}}) local cfuncs = {"cmul", "cpow", "cdiv", "cremainder", "cfmod", "clshift", "crshift", "cbitand", "cbitor", "cbitxor"} for _, name in ipairs(cfuncs) do wrap(name, cname(name), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}}) end wrap("addcmul", cname("addcmul"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real, default=1}, {name=Tensor}, {name=Tensor}}) wrap("addcdiv", cname("addcdiv"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real, default=1}, {name=Tensor}, {name=Tensor}}) wrap("maskedFill", cname("maskedFill"), {{name=Tensor, returned=true, method={default='nil'}}, {name='CudaByteTensor'}, {name=real}}) wrap("maskedCopy", cname("maskedCopy"), {{name=Tensor, returned=true, method={default='nil'}}, {name='CudaByteTensor'}, {name=Tensor}}) wrap("maskedSelect", cname("maskedSelect"), {{name=Tensor, returned=true, default=true}, {name=Tensor}, {name='CudaByteTensor'}}) wrap("gather", cname("gather"), {{name=Tensor, default=true, returned=true, init=gatherInit}, {name=Tensor}, {name="index"}, {name='CudaLongTensor'}}, cname("gather"), -- this is for backward-compatibility, and takes in "Tensor" as the indexing tensor {{name=Tensor, default=true, returned=true, init=gatherInit}, {name=Tensor}, {name="index"}, {name=Tensor, declare=TensorToCudaLong_declare(true), check=TensorToCudaLong_check, read=TensorToCudaLong_read, postcall=TensorToCudaLong_postcall}}) wrap("scatter", cname("scatter"), {{name=Tensor, returned=true}, {name="index"}, {name='CudaLongTensor'}, {name=Tensor}}, cname("scatter"), -- this is for backward-compatibility, and takes in "Tensor" as the indexing tensor {{name=Tensor, returned=true}, {name="index"}, {name=Tensor, declare=TensorToCudaLong_declare(true), check=TensorToCudaLong_check, read=TensorToCudaLong_read, postcall=TensorToCudaLong_postcall}, {name=Tensor}}, cname("scatterFill"), {{name=Tensor, returned=true}, {name="index"}, {name='CudaLongTensor'}, {name=real}}, cname("scatterFill"), -- this is for backward-compatibility, and takes in "Tensor" as the indexing tensor {{name=Tensor, returned=true}, {name="index"}, {name=Tensor, declare=TensorToCudaLong_declare(false), check=TensorToCudaLong_check, read=TensorToCudaLong_read, postcall=TensorToCudaLong_postcall}, {name=real}} ) wrap("scatterAdd", cname("scatterAdd"), {{name=Tensor, returned=true}, {name="index"}, {name='CudaLongTensor'}, {name=Tensor}}) wrap("sort", cname("sort"), {{name=Tensor, default=true, returned=true}, {name="CudaLongTensor", default=true, returned=true, noreadadd=true}, {name=Tensor}, {name="index", default=lastdim(3)}, {name="boolean", default=0}}) wrap("topk", cname("topk"), {{name=Tensor, default=true, returned=true}, {name="CudaLongTensor", default=true, returned=true, noreadadd=true}, {name=Tensor}, {name="long", default=1}, {name="index", default=lastdim(3)}, {name="boolean", default=0}, {name="boolean", default=0}}) wrap("mode", cname("mode"), {{name=Tensor, default=true, returned=true, noreadadd=true}, {name="CudaLongTensor", default=true, returned=true, noreadadd=true}, {name=Tensor}, {name="index", default=lastdim(3)}, {name="boolean", default=true, invisible=true}}) do local Tensor = Tensor local real = real wrap("mv", cname("addmv"), {{name=Tensor, default=true, returned=true, method={default='nil'}, init=function(arg) return table.concat( { arg.__metatable.init(arg), string.format("TH%s_checkGPU(cutorch_getstate(L), 1, %s);", Tensor, arg.args[5]:carg()), string.format("TH%s_resize1d(cutorch_getstate(L), %s, %s->size[0]);", Tensor, arg:carg(), arg.args[5]:carg()) }, '\n') end, precall=function(arg) return table.concat( { string.format("TH%s_zero(cutorch_getstate(L), %s);", Tensor, arg:carg()), arg.__metatable.precall(arg) }, '\n') end }, {name=real, default=1, invisible=true}, {name=Tensor, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=2}, {name=Tensor, dim=1}} ) wrap("mm", cname("addmm"), {{name=Tensor, default=true, returned=true, method={default='nil'}, init=function(arg) return table.concat( { arg.__metatable.init(arg), string.format("TH%s_checkGPU(cutorch_getstate(L), 2, %s, %s);", Tensor, arg.args[5]:carg(), arg.args[6]:carg()), string.format("TH%s_resize2d(cutorch_getstate(L), %s, %s->size[0], %s->size[1]);", Tensor, arg:carg(), arg.args[5]:carg(), arg.args[6]:carg()) }, '\n') end, }, {name=real, default=0, invisible=true}, {name=Tensor, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=2}, {name=Tensor, dim=2}} ) wrap("bmm", cname("baddbmm"), {{name=Tensor, default=true, returned=true, method={default='nil'}, init=function(arg) return table.concat( { arg.__metatable.init(arg), string.format("TH%s_checkGPU(cutorch_getstate(L), 2, %s, %s);", Tensor, arg.args[5]:carg(), arg.args[6]:carg()), string.format("TH%s_resize3d(cutorch_getstate(L), %s, %s->size[0], %s->size[1], %s->size[2]);", Tensor, arg:carg(), arg.args[5]:carg(), arg.args[5]:carg(), arg.args[6]:carg()) }, '\n') end, }, {name=real, default=0, invisible=true}, {name=Tensor, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=3}, {name=Tensor, dim=3}} ) wrap("ger", cname("addr"), {{name=Tensor, default=true, returned=true, method={default='nil'}, init=function(arg) return table.concat( { arg.__metatable.init(arg), string.format("TH%s_checkGPU(cutorch_getstate(L), 2, %s, %s);", Tensor, arg.args[5]:carg(), arg.args[6]:carg()), string.format("TH%s_resize2d(cutorch_getstate(L), %s, %s->size[0], %s->size[0]);", Tensor, arg:carg(), arg.args[5]:carg(), arg.args[6]:carg()) }, '\n') end, precall=function(arg) return table.concat( { string.format("TH%s_zero(cutorch_getstate(L), %s);", Tensor, arg:carg()), arg.__metatable.precall(arg) }, '\n') end }, {name=real, default=1, invisible=true}, {name=Tensor, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=1}, {name=Tensor, dim=1}} ) for _,f in ipairs({ {name="addmv", dim1=1, dim2=2, dim3=1}, {name="addmm", dim1=2, dim2=2, dim3=2}, {name="addr", dim1=2, dim2=1, dim3=1}, {name="baddbmm", dim1=3, dim2=3, dim3=3}, {name="addbmm", dim1=2, dim2=3, dim3=3}, } ) do interface:wrap(f.name, cname(f.name), {{name=Tensor, default=true, returned=true}, {name=real, default=1}, {name=Tensor, dim=f.dim1}, {name=real, default=1}, {name=Tensor, dim=f.dim2}, {name=Tensor, dim=f.dim3}}) -- there is an ambiguity here, hence the more complicated setup method:wrap(f.name, cname(f.name), {{name=Tensor, returned=true, dim=f.dim1}, {name=real, default=1, invisible=true}, {name=Tensor, default=1, dim=f.dim1}, {name=real, default=1}, {name=Tensor, dim=f.dim2}, {name=Tensor, dim=f.dim3}}, cname(f.name), {{name=Tensor, returned=true, dim=f.dim1}, {name=real}, {name=Tensor, default=1, dim=f.dim1}, {name=real}, {name=Tensor, dim=f.dim2}, {name=Tensor, dim=f.dim3}}) end end wrap("dot", cname("dot"), {{name=Tensor}, {name=Tensor}, {name=real, creturned=true}}) wrap("sum", cname("sumall"), {{name=Tensor}, {name=real, creturned=true}}, cname("sum"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="index"}, {name="boolean", default=true, invisible=true}}) for _, name in ipairs({"cumsum", "cumprod"}) do wrap(name, cname(name), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="index", default=1}}) end wrap("prod", cname("prodall"), {{name=Tensor}, {name=real, creturned=true}}, cname("prod"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="index"}, {name="boolean", default=true, invisible=true}}) for _,name in ipairs({"min", "max"}) do wrap(name, cname(name .. "all"), {{name=Tensor}, {name=real, creturned=true}}, cname(name), {{name=Tensor, default=true, returned=true}, {name='CudaLongTensor', default=true, returned=true}, {name=Tensor}, {name="index"}, {name="boolean", default=true, invisible=true}}) end for _,name in ipairs({"cmin", "cmax"}) do wrap(name, cname(name), {{name=Tensor, default=true, returned=true}, {name=Tensor, method={default=1}}, {name=Tensor}}, cname(name .. "Value"), {{name=Tensor, default=true, returned=true}, {name=Tensor, method={default=1}}, {name=real}}) end wrap("cross", cname("cross"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name=Tensor}, {name="index", default=0}}) wrap("tril", cname("tril"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="int", default=0}}) wrap("triu", cname("triu"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="int", default=0}}) wrap("diag", cname("diag"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="int", default=0}}) wrap("trace", cname("trace"), {{name=Tensor}, {name=real, creturned=true}}) for _,name in ipairs({"log", "log1p", "exp", "cos", "acos", "cosh", "sin", "asin", "sinh", "tan", "atan", "tanh", "sqrt", "rsqrt", "sigmoid", "cinv", "ceil", "floor", "neg", "abs", "sign", "round", "trunc", "frac"}) do wrap(name, cname(name), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}}) end wrap("atan2", cname("atan2"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}} ) wrap("lerp", cname("lerp"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}, {name=real}} ) wrap("pow", cname("pow"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}, cname("tpow"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name = real}, {name=Tensor, method={default=1}}}) wrap("rand", cname("rand"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name="LongArg"}}) wrap("randn", cname("randn"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name="LongArg"}}) wrap("multinomial", cname("multinomial"), {{name='CudaLongTensor', default=true, returned=true, method={default='nil'}}, {name=Tensor}, {name="int"}, {name="boolean", default=false}}) wrap("multinomialAliasSetup_", cname("multinomialAliasSetup"), {{name=Tensor}, {name='CudaLongTensor', default=true, returned=true, method={default='nil'}}, {name=Tensor, default=true, returned=true, method={default='nil'}}}) wrap("multinomialAlias_", cname("multinomialAliasDraw"), {{name="CudaLongTensor", default=true, returned=true, method={default='nil'}}, {name="CudaLongTensor"}, {name=Tensor} }) wrap("clamp", cname("clamp"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}, {name=real}}) for _,name in pairs({'lt','gt','le','ge','eq','ne'}) do wrap(name, cname(name .. 'Value'), {{name='CudaByteTensor',default=true, returned=true}, {name=Tensor}, {name=real}}, cname(name .. 'ValueT'), {{name=Tensor, returned=true}, {name=Tensor}, {name=real}}, cname(name .. 'Tensor'), {{name='CudaByteTensor',default=true, returned=true}, {name=Tensor}, {name=Tensor}}, cname(name .. 'TensorT'), {{name=Tensor, returned=true}, {name=Tensor}, {name=Tensor}}) end wrap("cat", cname("cat"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name=Tensor}, {name="index", default=-1}}, cname("catArray"), {{name=Tensor, default=true, returned=true}, {name=Tensor .. "Array"}, {name="index", default=-1}}) wrap("nonzero", cname("nonzero"), {{name="CudaLongTensor", default=true, returned=true}, {name=Tensor}}) wrap("range", cname("range"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=real}, {name=real}, {name=real, default=1}}) wrap("geometric", cname("geometric"), {{name=Tensor, returned=true}, {name='double'}}) wrap("bernoulli", cname("bernoulli"), {{name=Tensor, returned=true}, {name='double', default=0.5}}, cname("bernoulli_FloatTensor"), {{name=Tensor, returned=true}, {name="CudaTensor"}}, cname("bernoulli_DoubleTensor"), {{name=Tensor, returned=true}, {name="CudaDoubleTensor"}}) for _,f in ipairs({{name='uniform', a=0, b=1}, {name='normal', a=0, b=1}, {name='cauchy', a=0, b=1}, {name='logNormal', a=1, b=2}}) do wrap(f.name, cname(f.name), {{name=Tensor, returned=true}, {name=real, default=f.a}, {name=real, default=f.b}}) end for _,f in ipairs({{name='exponential'}}) do wrap(f.name, cname(f.name), {{name=Tensor, returned=true}, {name=real, default=f.a}}) end for _,name in ipairs({"gesv","gels"}) do wrap(name, cname(name), {{name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor}, {name=Tensor}}, cname(name), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name=Tensor}}) end wrap("symeig", cname("syev"), {{name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor}, {name='charoption', values={'N', 'V'}, default='N'}, {name='charoption', values={'U', 'L'}, default='U'}}, cname("syev"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name='charoption', values={'N', 'V'}, default='N'}, {name='charoption', values={'U', 'L'}, default='U'}}) wrap("eig", cname("geev"), {{name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor}, {name='charoption', values={'N', 'V'}, default='N'}}, cname("geev"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name='charoption', values={'N', 'V'}, default='N'}}) wrap("svd", cname("gesvd"), {{name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor}, {name='charoption', values={'A', 'S'}, default='S'}}, cname("gesvd"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name='charoption', values={'A', 'S'}, default='S'}}) wrap("inverse", cname("getri"), {{name=Tensor, returned=true}, {name=Tensor}}, cname("getri"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}}) wrap("potri", cname("potri"), {{name=Tensor, returned=true}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}}, cname("potri"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}}) wrap("potrf", cname("potrf"), {{name=Tensor, returned=true}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}}, cname("potrf"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}}) wrap("potrs", cname("potrs"), {{name=Tensor, returned=true}, {name=Tensor}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}}, cname("potrs"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}}) wrap("qr", cname("qr"), {{name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor}}, cname("qr"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}}) wrap("mean", cname("meanall"), {{name=Tensor}, {name=real, creturned=true}}, cname("mean"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="index"}, {name="boolean", default=true, invisible=true}}) for _,name in ipairs({"var", "std"}) do wrap(name, cname(name .. "all"), {{name=Tensor}, {name="boolean", default=false}, {name=real, creturned=true}}, cname(name), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="index"}, {name="boolean", default=false}, {name="boolean", default=true, invisible=true}}) end wrap("norm", cname("normall"), {{name=Tensor}, {name=real, default=2}, {name=real, creturned=true}}, cname("norm"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name=real}, {name="index"}, {name="boolean", default=true, invisible=true}}) wrap("renorm", cname("renorm"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}, {name="index"}, {name=real}}) wrap("dist", cname("dist"), {{name=Tensor}, {name=Tensor}, {name=real, default=2}, {name=real, creturned=true}}) wrap("squeeze", cname("squeeze"), {{name=Tensor, default=true, returned=true, postcall=function(arg) local txt = {} if arg.returned then table.insert(txt, string.format('if(arg%d->nDimension == 1 && arg%d->size[0] == 1)', arg.i, arg.i)) -- number table.insert(txt, string.format('lua_pushnumber(L, (lua_Number)(THCudaTensor_get1d(cutorch_getstate(L), arg%d, 0)));', arg.i)) end return table.concat(txt, '\n') end}, {name=Tensor}}, cname("squeeze1d"), {{name=Tensor, default=true, returned=true, postcall= function(arg) local txt = {} if arg.returned then table.insert(txt, string.format('if(!hasdims && arg%d->nDimension == 1 && arg%d->size[0] == 1)', arg.i, arg.i)) -- number table.insert(txt, string.format('lua_pushnumber(L, (lua_Number)(THCudaTensor_get1d(cutorch_getstate(L), arg%d, 0)));}', arg.i)) end return table.concat(txt, '\n') end}, {name=Tensor, precall= function(arg) return string.format('{int hasdims = arg%d->nDimension > 1;', arg.i) end}, {name="index"}}) method:register("m_cutorch_" .. Tensor .. "Math__") interface:print(method:tostring()) method:clearhistory() interface:register("cutorch_" .. Tensor .. "Math__") interface:print(string.format([[ void cutorch_%sMath_init(lua_State *L) { luaT_pushmetatable(L, "torch.%s"); /* register methods */ luaL_setfuncs(L, m_cutorch_%sMath__, 0); /* register functions into the "torch" field of the tensor metaclass */ lua_pushstring(L, "torch"); lua_newtable(L); luaL_setfuncs(L, cutorch_%sMath__, 0); lua_rawset(L, -3); lua_pop(L, 1); } ]], Tensor, Tensor, Tensor, Tensor)) interface:tofile(arg[1]) ================================================ FILE: torch/pkg/torch/TensorMath.lua ================================================ local wrap = require 'cwrap' require 'torchcwrap' local interface = wrap.CInterface.new() local method = wrap.CInterface.new() local argtypes = wrap.CInterface.argtypes argtypes['ptrdiff_t'] = wrap.types.ptrdiff_t interface:print([[ #include "TH.h" #include "THMath.h" #include "luaT.h" #include "utils.h" ]]) -- specific to torch: we generate a 'dispatch' function -- first we create a helper function -- note that it let the "torch" table on the stack interface:print([[ static const void* torch_istensortype(lua_State *L, const char *tname) { if(!tname) return NULL; if(!luaT_pushmetatable(L, tname)) return NULL; lua_pushstring(L, "torch"); lua_rawget(L, -2); if(lua_istable(L, -1)) return tname; else { lua_pop(L, 2); return NULL; } return NULL; } ]]) interface:print([[ static int torch_isnonemptytable(lua_State *L, int idx) { int empty; if (!lua_istable(L, idx)) return 0; lua_rawgeti(L, idx, 1); empty = lua_isnil(L, -1); lua_pop(L, 1); return !empty; } ]]) interface:print([[ static const void* torch_istensorarray(lua_State *L, int idx) { const char* tname; int tensor_idx; if (!torch_isnonemptytable(L, idx)) return 0; lua_checkstack(L, 3); lua_rawgeti(L, idx, 1); tensor_idx = lua_gettop(L); tname = (torch_istensortype(L, luaT_typename(L, -1))); lua_remove(L, tensor_idx); return tname; } ]]) interface.dispatchregistry = {} function interface:wrap(name, ...) -- usual stuff wrap.CInterface.wrap(self, name, ...) -- dispatch function if not interface.dispatchregistry[name] then interface.dispatchregistry[name] = true table.insert(interface.dispatchregistry, {name=name, wrapname=string.format("torch_%s", name)}) interface:print(string.gsub([[ static int torch_NAME(lua_State *L) { int narg = lua_gettop(L); const void *tname; if(narg >= 1 && (tname = torch_istensortype(L, luaT_typename(L, 1)))) /* first argument is tensor? */ { } else if(narg >= 2 && (tname = torch_istensortype(L, luaT_typename(L, 2)))) /* second? */ { } else if(narg >= 1 && (tname = torch_istensorarray(L, 1))) /* torch table argument? */ { } else if(narg >= 1 && lua_type(L, narg) == LUA_TSTRING && (tname = torch_istensortype(L, lua_tostring(L, narg)))) /* do we have a valid tensor type string then? */ { lua_remove(L, -2); } else if(!(tname = torch_istensortype(L, torch_getdefaulttensortype(L)))) luaL_error(L, "internal error: the default tensor type does not seem to be an actual tensor"); lua_pushstring(L, "NAME"); lua_rawget(L, -2); if(lua_isfunction(L, -1)) { lua_insert(L, 1); lua_pop(L, 2); /* the two tables we put on the stack above */ lua_call(L, lua_gettop(L)-1, LUA_MULTRET); } else return luaL_error(L, "%s does not implement the torch.NAME() function", tname); return lua_gettop(L); } ]], 'NAME', name)) end end function interface:dispatchregister(name) local txt = self.txt table.insert(txt, string.format('static const struct luaL_Reg %s [] = {', name)) for _,reg in ipairs(self.dispatchregistry) do table.insert(txt, string.format('{"%s", %s},', reg.name, reg.wrapname)) end table.insert(txt, '{NULL, NULL}') table.insert(txt, '};') table.insert(txt, '') self.dispatchregistry = {} end interface:print('/* WARNING: autogenerated file */') interface:print('') local function wrap(...) local args = {...} -- interface interface:wrap(...) -- method: we override things possibly in method table field for _,x in ipairs(args) do if type(x) == 'table' then -- ok, now we have a list of args for _, arg in ipairs(x) do if arg.method then for k,v in pairs(arg.method) do if v == 'nil' then -- special case, we erase the field arg[k] = nil else arg[k] = v end end end end end end local unpack = unpack or table.unpack method:wrap(unpack(args)) end local reals = {ByteTensor='unsigned char', CharTensor='char', ShortTensor='short', IntTensor='int', LongTensor='long', FloatTensor='float', HalfTensor='half', DoubleTensor='double'} local accreals = {ByteTensor='long', CharTensor='long', ShortTensor='long', IntTensor='long', LongTensor='long', FloatTensor='double', HalfTensor='float', DoubleTensor='double'} for _,Tensor in ipairs({"ByteTensor", "CharTensor", "ShortTensor", "IntTensor", "LongTensor", "FloatTensor", "HalfTensor", "DoubleTensor"}) do local real = reals[Tensor] local accreal = accreals[Tensor] function interface.luaname2wrapname(self, name) return string.format('torch_%s_%s', Tensor, name) end function method.luaname2wrapname(self, name) return string.format('m_torch_%s_%s', Tensor, name) end local function cname(name) return string.format('TH%s_%s', Tensor, name) end local function lastdim(argn) return function(arg) return string.format("TH%s_nDimension(%s)", Tensor, arg.args[argn]:carg()) end end local function lastdimarray(argn) return function(arg) return string.format("TH%s_nDimension(arg%d_data[0])", Tensor, arg.args[argn].i) end end if Tensor ~= 'HalfTensor' then wrap("zero", cname("zero"), {{name=Tensor, returned=true}}) wrap("fill", cname("fill"), {{name=Tensor, returned=true}, {name=real}}) wrap("zeros", cname("zeros"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name="LongArg"}}) wrap("ones", cname("ones"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name="LongArg"}}) wrap("reshape", cname("reshape"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="LongArg"}}) wrap("gather", cname("gather"), {{name=Tensor, default=true, returned=true, init=function(arg) return table.concat( { arg.__metatable.init(arg), string.format("THLongStorage* %s_size = THLongTensor_newSizeOf(%s);", arg:carg(), arg.args[4]:carg()), string.format("TH%s_resize(%s, %s_size, NULL);", Tensor, arg:carg(), arg:carg()), string.format("THLongStorage_free(%s_size);", arg:carg()) }, '\n') end }, {name=Tensor}, {name="index"}, {name="IndexTensor", noreadadd=true}}) wrap("scatter", cname("scatter"), {{name=Tensor, returned=true}, {name="index"}, {name="IndexTensor", noreadadd=true}, {name=Tensor}}, cname("scatterFill"), {{name=Tensor, returned=true}, {name="index"}, {name="IndexTensor", noreadadd=true}, {name=real}}) wrap("scatterAdd", cname("scatterAdd"), {{name=Tensor, returned=true}, {name="index"}, {name="IndexTensor", noreadadd=true}, {name=Tensor}}) wrap("dot", cname("dot"), {{name=Tensor}, {name=Tensor}, {name=accreal, creturned=true}}) wrap("equal", cname("equal"), {{name=Tensor}, {name=Tensor}, {name="boolean", creturned=true}}) wrap("add", cname("add"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}, cname("cadd"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real, default=1}, {name=Tensor}}) wrap("csub", cname("sub"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}, cname("csub"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real, default=1}, {name=Tensor}}) wrap("mul", cname("mul"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("div", cname("div"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("lshift", cname("lshift"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("rshift", cname("rshift"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("fmod", cname("fmod"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("remainder", cname("remainder"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("bitand", cname("bitand"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("bitor", cname("bitor"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("bitxor", cname("bitxor"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) -- mod alias wrap("mod", cname("fmod"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}) wrap("clamp", cname("clamp"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}, {name=real}}) wrap("match", cname("match"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor}, {name=Tensor}, {name=real, default=1} }) wrap("cmul", cname("cmul"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}}) wrap("cpow", cname("cpow"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}}) wrap("cdiv", cname("cdiv"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}}) wrap("clshift", cname("clshift"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}}) wrap("crshift", cname("crshift"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}}) wrap("cfmod", cname("cfmod"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}}) wrap("cremainder", cname("cremainder"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}}) wrap("cbitand", cname("cbitand"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}}) wrap("cbitor", cname("cbitor"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}}) wrap("cbitxor", cname("cbitxor"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}}) -- cmod alias wrap("cmod", cname("cfmod"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}}) wrap("addcmul", cname("addcmul"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real, default=1}, {name=Tensor}, {name=Tensor}}) wrap("addcdiv", cname("addcdiv"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real, default=1}, {name=Tensor}, {name=Tensor}}) wrap("mv", cname("addmv"), {{name=Tensor, default=true, returned=true, method={default='nil'}, init=function(arg) return table.concat( { arg.__metatable.init(arg), string.format("TH%s_resize1d(%s, %s->size[0]);", Tensor, arg:carg(), arg.args[5]:carg()) }, '\n') end, precall=function(arg) return table.concat( { string.format("TH%s_zero(%s);", Tensor, arg:carg()), arg.__metatable.precall(arg) }, '\n') end, }, {name=real, default=0, invisible=true}, {name=Tensor, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=2}, {name=Tensor, dim=1}} ) wrap("mm", cname("addmm"), {{name=Tensor, default=true, returned=true, method={default='nil'}, init=function(arg) return table.concat( { arg.__metatable.init(arg), string.format("TH%s_resize2d(%s, %s->size[0], %s->size[1]);", Tensor, arg:carg(), arg.args[5]:carg(), arg.args[6]:carg()) }, '\n') end, precall=function(arg) return table.concat( { string.format("TH%s_zero(%s);", Tensor, arg:carg()), arg.__metatable.precall(arg) }, '\n') end, }, {name=real, default=0, invisible=true}, {name=Tensor, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=2}, {name=Tensor, dim=2}} ) wrap("bmm", cname("baddbmm"), {{name=Tensor, default=true, returned=true, method={default='nil'}, init=function(arg) return table.concat( { arg.__metatable.init(arg), string.format("TH%s_resize3d(%s, %s->size[0], %s->size[1], %s->size[2]);", Tensor, arg:carg(), arg.args[5]:carg(), arg.args[5]:carg(), arg.args[6]:carg()) }, '\n') end, precall=function(arg) return table.concat( { string.format("TH%s_zero(%s);", Tensor, arg:carg()), arg.__metatable.precall(arg) }, '\n') end, }, {name=real, default=0, invisible=true}, {name=Tensor, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=3}, {name=Tensor, dim=3}} ) wrap("ger", cname("addr"), {{name=Tensor, default=true, returned=true, method={default='nil'}, init=function(arg) return table.concat( { arg.__metatable.init(arg), string.format("TH%s_resize2d(%s, %s->size[0], %s->size[0]);", Tensor, arg:carg(), arg.args[5]:carg(), arg.args[6]:carg()) }, '\n') end, precall=function(arg) return table.concat( { string.format("TH%s_zero(%s);", Tensor, arg:carg()), arg.__metatable.precall(arg) }, '\n') end }, {name=real, default=1, invisible=true}, {name=Tensor, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=1}, {name=Tensor, dim=1}} ) for _,f in ipairs({ {name="addmv", dim1=1, dim2=2, dim3=1}, {name="addmm", dim1=2, dim2=2, dim3=2}, {name="addr", dim1=2, dim2=1, dim3=1}, {name="addbmm", dim1=2, dim2=3, dim3=3}, {name="baddbmm", dim1=3, dim2=3, dim3=3}, } ) do interface:wrap(f.name, cname(f.name), {{name=Tensor, default=true, returned=true}, {name=real, default=1}, {name=Tensor, dim=f.dim1}, {name=real, default=1}, {name=Tensor, dim=f.dim2}, {name=Tensor, dim=f.dim3}}) -- there is an ambiguity here, hence the more complicated setup method:wrap(f.name, cname(f.name), {{name=Tensor, returned=true, dim=f.dim1}, {name=real, default=1, invisible=true}, {name=Tensor, default=1, dim=f.dim1}, {name=real, default=1}, {name=Tensor, dim=f.dim2}, {name=Tensor, dim=f.dim3}}, cname(f.name), {{name=Tensor, returned=true, dim=f.dim1}, {name=real}, {name=Tensor, default=1, dim=f.dim1}, {name=real}, {name=Tensor, dim=f.dim2}, {name=Tensor, dim=f.dim3}}) end wrap("numel", cname("numel"), {{name=Tensor}, {name="ptrdiff_t", creturned=true}}) for _,name in ipairs({"cumsum", "cumprod"}) do wrap(name, cname(name), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="index", default=1}}) end wrap("sum", cname("sumall"), {{name=Tensor}, {name=accreal, creturned=true}}, cname("sum"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="index"}, {name="boolean", default=true, invisible=true}}) wrap("prod", cname("prodall"), {{name=Tensor}, {name=accreal, creturned=true}}, cname("prod"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="index"}, {name="boolean", default=true, invisible=true}}) for _,name in ipairs({"min", "max"}) do wrap(name, cname(name .. "all"), {{name=Tensor}, {name=real, creturned=true}}, cname(name), {{name=Tensor, default=true, returned=true}, {name="IndexTensor", default=true, returned=true, noreadadd=true}, {name=Tensor}, {name="index"}, {name="boolean", default=true, invisible=true}}) end for _,name in ipairs({"cmin", "cmax"}) do wrap(name, cname(name), {{name=Tensor, default=true, returned=true}, {name=Tensor, method={default=1}}, {name=Tensor}}, cname(name .. "Value"), {{name=Tensor, default=true, returned=true}, {name=Tensor, method={default=1}}, {name=real}}) end wrap("trace", cname("trace"), {{name=Tensor}, {name=accreal, creturned=true}}) wrap("cross", cname("cross"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name=Tensor}, {name="index", default=0}}) wrap("diag", cname("diag"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="long", default=0}}) wrap("eye", cname("eye"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name="long"}, {name="long", default=0}}) wrap("range", cname("range"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=accreal}, {name=accreal}, {name=accreal, default=1}}) wrap("randperm", cname("randperm"), {{name=Tensor, default=true, returned=true, method={default='nil'}, postcall=function(arg) return table.concat( { arg.__metatable.postcall(arg), string.format("TH%s_add(%s, %s, 1);", Tensor, arg:carg(), arg:carg()) }, '\n') end}, {name="Generator", default=true}, {name="long"}}) wrap("sort", cname("sort"), {{name=Tensor, default=true, returned=true}, {name="IndexTensor", default=true, returned=true, noreadadd=true}, {name=Tensor}, {name="index", default=lastdim(3)}, {name="boolean", default=0}}) wrap("topk", cname("topk"), {{name=Tensor, default=true, returned=true}, {name="IndexTensor", default=true, returned=true, noreadadd=true}, {name=Tensor}, {name="long", default=1}, {name="index", default=lastdim(3)}, {name="boolean", default=0}, {name="boolean", default=0}}) wrap("kthvalue", cname("kthvalue"), {{name=Tensor, default=true, returned=true}, {name="IndexTensor", default=true, returned=true, noreadadd=true}, {name=Tensor}, {name="long"}, {name="index", default=lastdim(3)}, {name="boolean", default=true, invisible=true}}) wrap("mode", cname("mode"), {{name=Tensor, default=true, returned=true}, {name="IndexTensor", default=true, returned=true, noreadadd=true}, {name=Tensor}, {name="index", default=lastdim(3)}, {name="boolean", default=true, invisible=true}}) wrap("median", cname("median"), {{name=Tensor, default=true, returned=true}, {name="IndexTensor", default=true, returned=true, noreadadd=true}, {name=Tensor}, {name="index", default=lastdim(3)}, {name="boolean", default=true, invisible=true}}) wrap("tril", cname("tril"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="int", default=0}}) wrap("triu", cname("triu"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="int", default=0}}) wrap("cat", cname("cat"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name=Tensor}, {name="index", default=-1}}, cname("catArray"), {{name=Tensor, default=true, returned=true}, {name=Tensor .. "Array"}, {name="index", default=-1}}) if Tensor == 'ByteTensor' then -- we declare this only once interface:print( [[ static long THRandom_random2__(THGenerator *gen, long a, long b) { THArgCheck(b >= a, 2, "upper bound must be larger than lower bound"); return((THRandom_random(gen) % (b+1-a)) + a); } static long THRandom_random1__(THGenerator *gen, long b) { THArgCheck(b > 0, 1, "upper bound must be strictly positive"); return(THRandom_random(gen) % b + 1); } ]]) end interface:print(string.gsub( [[ static void THTensor_random2__(THTensor *self, THGenerator *gen, long a, long b) { THArgCheck(b >= a, 2, "upper bound must be larger than lower bound"); TH_TENSOR_APPLY(real, self, *self_data = ((THRandom_random(gen) % (b+1-a)) + a);) } static void THTensor_random1__(THTensor *self, THGenerator *gen, long b) { THArgCheck(b > 0, 1, "upper bound must be strictly positive"); TH_TENSOR_APPLY(real, self, *self_data = (THRandom_random(gen) % b + 1);) } ]], 'Tensor', Tensor):gsub('real', real)) wrap('random', 'THRandom_random2__', {{name='Generator', default=true}, {name='long'}, {name='long'}, {name='long', creturned=true}}, 'THRandom_random1__', {{name='Generator', default=true}, {name='long'}, {name='long', creturned=true}}, 'THRandom_random', {{name='Generator', default=true}, {name='long', creturned=true}}, cname("random2__"), {{name=Tensor, returned=true}, {name='Generator', default=true}, {name='long'}, {name='long'}}, cname("random1__"), {{name=Tensor, returned=true}, {name='Generator', default=true}, {name='long'}}, cname("random"), {{name=Tensor, returned=true}, {name='Generator', default=true}}) wrap("geometric", "THRandom_geometric", {{name="Generator", default=true}, {name="double"}, {name="double", creturned=true}}, cname("geometric"), {{name=Tensor, returned=true}, {name="Generator", default=true}, {name="double"}}) wrap("bernoulli", "THRandom_bernoulli", {{name="Generator", default=true}, {name="double", default=0.5}, {name="double", creturned=true}}, cname("bernoulli"), {{name=Tensor, returned=true}, {name="Generator", default=true}, {name="double", default=0.5}}, cname("bernoulli_FloatTensor"), {{name=Tensor, returned=true}, {name="Generator", default=true}, {name="FloatTensor"}}, cname("bernoulli_DoubleTensor"), {{name=Tensor, returned=true}, {name="Generator", default=true}, {name="DoubleTensor"}}) wrap("squeeze", cname("squeeze"), {{name=Tensor, default=true, returned=true, postcall=function(arg) local txt = {} if arg.returned then table.insert(txt, string.format('if(arg%d->nDimension == 1 && arg%d->size[0] == 1)', arg.i, arg.i)) -- number table.insert(txt, string.format('lua_pushnumber(L, (lua_Number)(*TH%s_data(arg%d)));', Tensor, arg.i)) end return table.concat(txt, '\n') end}, {name=Tensor}}, cname("squeeze1d"), {{name=Tensor, default=true, returned=true, postcall= function(arg) local txt = {} if arg.returned then table.insert(txt, string.format('if(!hasdims && arg%d->nDimension == 1 && arg%d->size[0] == 1)', arg.i, arg.i)) -- number table.insert(txt, string.format('lua_pushnumber(L, (lua_Number)(*TH%s_data(arg%d)));}', Tensor, arg.i)) end return table.concat(txt, '\n') end}, {name=Tensor, precall= function(arg) return string.format('{int hasdims = arg%d->nDimension > 1;', arg.i) end}, {name="index"}}) wrap("sign", cname("sign"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}}) wrap("conv2", cname("conv2Dmul"), {{name=Tensor, default=true, returned=true}, {name=real, default=0, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=2}, {name=Tensor, dim=2}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name='charoption', values={'V', 'F'}, default='V'}, {name='charoption', default="C", invisible=true}}, cname("conv2Dcmul"), {{name=Tensor, default=true, returned=true}, {name=real, default=0, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=3}, {name=Tensor, dim=3}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name='charoption', values={'V', 'F'}, default='V'}, {name='charoption', default="C", invisible=true}}, cname("conv2Dmv"), {{name=Tensor, default=true, returned=true}, {name=real, default=0, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=3}, {name=Tensor, dim=4}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name='charoption', values={'V', 'F'}, default='V'}, {name='charoption', default="C", invisible=true}} ) wrap("xcorr2", cname("conv2Dmul"), {{name=Tensor, default=true, returned=true}, {name=real, default=0, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=2}, {name=Tensor, dim=2}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name='charoption', values={'V', 'F'}, default='V'}, {name='charoption', default="X", invisible=true}}, cname("conv2Dcmul"), {{name=Tensor, default=true, returned=true}, {name=real, default=0, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=3}, {name=Tensor, dim=3}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name='charoption', values={'V', 'F'}, default='V'}, {name='charoption', default="X", invisible=true}}, cname("conv2Dmv"), {{name=Tensor, default=true, returned=true}, {name=real, default=0, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=3}, {name=Tensor, dim=4}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name='charoption', values={'V', 'F'}, default='V'}, {name='charoption', default="X", invisible=true}} ) wrap("conv3", cname("conv3Dmul"), {{name=Tensor, default=true, returned=true}, {name=real, default=0, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=3}, {name=Tensor, dim=3}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name='charoption', values={'V', 'F'}, default='V'}, {name='charoption', default="C", invisible=true}}, cname("conv3Dcmul"), {{name=Tensor, default=true, returned=true}, {name=real, default=0, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=4}, {name=Tensor, dim=4}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name='charoption', values={'V', 'F'}, default='V'}, {name='charoption', default="C", invisible=true}}, cname("conv3Dmv"), {{name=Tensor, default=true, returned=true}, {name=real, default=0, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=4}, {name=Tensor, dim=5}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name='charoption', values={'V', 'F'}, default='V'}, {name='charoption', default="C", invisible=true}} ) wrap("xcorr3", cname("conv3Dmul"), {{name=Tensor, default=true, returned=true}, {name=real, default=0, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=3}, {name=Tensor, dim=3}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name='charoption', values={'V', 'F'}, default='V'}, {name='charoption', default="X", invisible=true}}, cname("conv3Dcmul"), {{name=Tensor, default=true, returned=true}, {name=real, default=0, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=4}, {name=Tensor, dim=4}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name='charoption', values={'V', 'F'}, default='V'}, {name='charoption', default="X", invisible=true}}, cname("conv3Dmv"), {{name=Tensor, default=true, returned=true}, {name=real, default=0, invisible=true}, {name=real, default=1, invisible=true}, {name=Tensor, dim=4}, {name=Tensor, dim=5}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name=real, default=1, invisible=true}, {name='charoption', values={'V', 'F'}, default='V'}, {name='charoption', default="X", invisible=true}} ) for _,name in pairs({'lt','gt','le','ge','eq','ne'}) do wrap(name, cname(name .. 'Value'), {{name='ByteTensor',default=true, returned=true}, {name=Tensor}, {name=real}}, cname(name .. 'ValueT'), {{name=Tensor, returned=true}, {name=Tensor}, {name=real}}, cname(name .. 'Tensor'), {{name='ByteTensor',default=true, returned=true}, {name=Tensor}, {name=Tensor}}, cname(name .. 'TensorT'), {{name=Tensor, returned=true}, {name=Tensor}, {name=Tensor}}) end wrap("nonzero", cname("nonzero"), {{name="IndexTensor", default=true, returned=true}, {name=Tensor}}) end -- ~= HalfTensor if Tensor == 'ByteTensor' then -- Logical accumulators only apply to ByteTensor for _,name in ipairs({'all', 'any'}) do wrap(name, cname('logical' .. name), {{name=Tensor}, {name="boolean", creturned=true}}) end end if Tensor == 'IntTensor' then wrap("abs", cname("abs"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}}, "abs", {{name=real}, {name=real, creturned=true}}) elseif Tensor == 'LongTensor' then wrap("abs", cname("abs"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}}, "labs", {{name=real}, {name=real, creturned=true}}) end if Tensor == 'FloatTensor' or Tensor == 'DoubleTensor' then wrap("mean", cname("meanall"), {{name=Tensor}, {name=accreal, creturned=true}}, cname("mean"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="index"}, {name="boolean", default=true, invisible=true}}) for _,name in ipairs({"var", "std"}) do wrap(name, cname(name .. "all"), {{name=Tensor}, {name="boolean", default=false}, {name=accreal, creturned=true} }, cname(name), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="index"}, {name="boolean", default=false}, {name="boolean", default=true, invisible=true}}) end wrap("histc", cname("histc"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="long",default=100}, {name="double",default=0}, {name="double",default=0}}) wrap("bhistc", cname("bhistc"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name="long",default=100}, {name="double",default=0}, {name="double",default=0}}) wrap("norm", cname("normall"), {{name=Tensor}, {name=real, default=2}, {name=accreal, creturned=true}}, cname("norm"), {{name=Tensor, default=true, returned=true}, {name=Tensor}, {name=real}, {name="index"}, {name="boolean", default=true, invisible=true}}) wrap("renorm", cname("renorm"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}, {name="index"}, {name=real}}) wrap("dist", cname("dist"), {{name=Tensor}, {name=Tensor}, {name=real, default=2}, {name=accreal, creturned=true}}) wrap("linspace", cname("linspace"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=real}, {name=real}, {name="long", default=100}}) wrap("logspace", cname("logspace"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=real}, {name=real}, {name="long", default=100}}) for _,name in ipairs({"log", "log1p", "exp", "cos", "acos", "cosh", "sin", "asin", "sinh", "tan", "atan", "tanh", "sqrt", "round", "ceil", "floor", "trunc", }) do wrap(name, cname(name), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}}, name, {{name=real}, {name=real, creturned=true}}) end wrap("abs", cname("abs"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}}, "fabs", {{name=real}, {name=real, creturned=true}}) wrap("frac", cname("frac"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}}, "TH_frac", {{name=real}, {name=real, creturned=true}}) wrap("rsqrt", cname("rsqrt"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}}, "TH_rsqrt", {{name=real}, {name=real, creturned=true}}) wrap("sigmoid", cname("sigmoid"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}}, "TH_sigmoid", {{name=real}, {name=real, creturned=true}}) wrap("neg", cname("neg"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}}) wrap("cinv", cname("cinv"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}}) wrap("lerp", cname("lerp"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}, {name=real}}, "TH_lerp", {{name=real}, {name=real}, {name=real}, {name=real, creturned=true}}) wrap("atan2", cname("atan2"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=Tensor}}, "atan2", {{name=real}, {name=real}, {name=real, creturned=true}}) wrap("pow", cname("pow"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=Tensor, method={default=1}}, {name=real}}, cname("tpow"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name=real}, {name=Tensor, method={default=1}}}, "pow", {{name=real}, {name=real}, {name=real, creturned=true}}) wrap("rand", cname("rand"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name='Generator', default=true}, {name="LongArg"}}) wrap("randn", cname("randn"), {{name=Tensor, default=true, returned=true, method={default='nil'}}, {name='Generator', default=true}, {name="LongArg"}}) wrap("multinomial", cname("multinomial"), {{name="IndexTensor", default=true, returned=true, method={default='nil'}}, {name='Generator', default=true}, {name=Tensor}, {name="int"}, {name="boolean", default=false}}) wrap("multinomialAliasSetup_", cname("multinomialAliasSetup"), {{name=Tensor}, {name="IndexTensor", default=true, returned=true, method={default='nil'}}, {name=Tensor, default=true, returned=true, method={default='nil'}}}) wrap("multinomialAlias_", cname("multinomialAliasDraw"), {{name="IndexTensor", default=true, returned=true, method={default='nil'}}, {name='Generator', default=true}, {name="IndexTensor"}, {name=Tensor} }) for _,f in ipairs({{name='uniform', a=0, b=1}, {name='normal', a=0, b=1}, {name='cauchy', a=0, b=1}, {name='logNormal', a=1, b=2}}) do wrap(f.name, string.format("THRandom_%s", f.name), {{name='Generator', default=true}, {name="double", default=f.a}, {name="double", default=f.b}, {name="double", creturned=true}}, cname(f.name), {{name=Tensor, returned=true}, {name='Generator', default=true}, {name=real, default=f.a}, {name=real, default=f.b}}) end for _,f in ipairs({{name='exponential'}}) do wrap(f.name, string.format("THRandom_%s", f.name), {{name='Generator', default=true}, {name="double", default=f.a}, {name="double", creturned=true}}, cname(f.name), {{name=Tensor, returned=true}, {name='Generator', default=true}, {name=real, default=f.a}}) end for _,name in ipairs({"gesv","gels"}) do interface:wrap(name, cname(name), {{name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor}, {name=Tensor}}, cname(name), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name=Tensor}} ) end interface:wrap("trtrs", cname("trtrs"), {{name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}, -- uplo {name='charoption', values={'N', 'T'}, default='N'}, -- trans {name='charoption', values={'N', 'U'}, default='N'}}, -- diag cname("trtrs"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}, -- uplo {name='charoption', values={'N', 'T'}, default='N'}, -- trans {name='charoption', values={'N', 'U'}, default='N'}} -- diag ) interface:wrap("symeig", cname("syev"), {{name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor}, {name='charoption', values={'N', 'V'}, default='N'}, {name='charoption', values={'U', 'L'}, default='U'}}, cname("syev"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name='charoption', values={'N', 'V'}, default='N'}, {name='charoption', values={'U', 'L'}, default='U'}} ) interface:wrap("eig", cname("geev"), {{name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor}, {name='charoption', values={'N', 'V'}, default='N'}}, cname("geev"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name='charoption', values={'N', 'V'}, default='N'}} ) interface:wrap("svd", cname("gesvd"), {{name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor}, {name='charoption', values={'A', 'S'}, default='S'}}, cname("gesvd"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name='charoption', values={'A', 'S'}, default='S'}} ) interface:wrap("inverse", cname("getri"), {{name=Tensor, returned=true}, {name=Tensor}}, cname("getri"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}} ) interface:wrap("potrf", cname("potrf"), {{name=Tensor, returned=true}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}}, -- uplo cname("potrf"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}} ) interface:wrap("potrs", cname("potrs"), {{name=Tensor, returned=true}, {name=Tensor}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}}, -- uplo cname("potrs"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}} ) interface:wrap("potri", cname("potri"), {{name=Tensor, returned=true}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}}, -- uplo cname("potri"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}} -- uplo ) interface:wrap("pstrf", cname("pstrf"), {{name=Tensor, returned=true}, {name='IntTensor', returned=true}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}, -- uplo {name=real, default=-1}}, cname("pstrf"), {{name=Tensor, default=true, returned=true, invisible=true}, {name='IntTensor', default=true, returned=true, invisible=true}, {name=Tensor}, {name='charoption', values={'U', 'L'}, default='U'}, -- uplo {name=real, default=-1}} ) interface:wrap("qr", cname("qr"), {{name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor}}, cname("qr"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}} ) interface:wrap("geqrf", cname("geqrf"), {{name=Tensor, returned=true}, {name=Tensor, returned=true}, {name=Tensor}}, cname("geqrf"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}} ) interface:wrap("orgqr", cname("orgqr"), {{name=Tensor, returned=true}, {name=Tensor}, {name=Tensor}}, cname("orgqr"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name=Tensor}} ) interface:wrap("ormqr", cname("ormqr"), {{name=Tensor, returned=true}, {name=Tensor}, {name=Tensor}, {name=Tensor}, {name='charoption', values={'L', 'R'}, default='L'}, {name='charoption', values={'N', 'T'}, default='N'}}, cname("ormqr"), {{name=Tensor, default=true, returned=true, invisible=true}, {name=Tensor}, {name=Tensor}, {name=Tensor}, {name='charoption', values={'L', 'R'}, default='L'}, {name='charoption', values={'N', 'T'}, default='N'}} ) end method:register(string.format("m_torch_%sMath__", Tensor)) interface:print(method:tostring()) method:clearhistory() interface:register(string.format("torch_%sMath__", Tensor)) interface:print(string.gsub([[ static void torch_TensorMath_init(lua_State *L) { luaT_pushmetatable(L, "torch.Tensor"); /* register methods */ luaT_setfuncs(L, m_torch_TensorMath__, 0); /* register functions into the "torch" field of the tensor metaclass */ lua_pushstring(L, "torch"); lua_newtable(L); luaT_setfuncs(L, torch_TensorMath__, 0); lua_rawset(L, -3); lua_pop(L, 1); } ]], 'Tensor', Tensor)) end interface:dispatchregister("torch_TensorMath__") interface:print([[ void torch_TensorMath_init(lua_State *L) { torch_ByteTensorMath_init(L); torch_CharTensorMath_init(L); torch_ShortTensorMath_init(L); torch_IntTensorMath_init(L); torch_LongTensorMath_init(L); torch_FloatTensorMath_init(L); torch_DoubleTensorMath_init(L); luaT_setfuncs(L, torch_TensorMath__, 0); } ]]) if arg[1] then interface:tofile(arg[1]) else print(interface:tostring()) end