├── autogen.sh ├── make-msvc.bat ├── .gitignore ├── qlearn.py ├── Makefile.in ├── configure.ac ├── README.md ├── 2048.h ├── config.h.in ├── platdefs.h ├── config.h ├── 2048.py ├── m4 └── ax_cxx_compile_stdcxx_11.m4 ├── 2048.cpp └── install-sh /autogen.sh: -------------------------------------------------------------------------------- 1 | autoreconf -fiv 2 | -------------------------------------------------------------------------------- /make-msvc.bat: -------------------------------------------------------------------------------- 1 | @mkdir bin 2 | cl /W1 /O2 /Gd /MD /D _WINDLL /EHsc /nologo 2048.cpp /Fobin\2048.obj /link /OUT:bin\2048.exe 3 | cl /nologo bin\2048.obj /link /DLL /OUT:bin\2048.dll 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | *.pyc 3 | *~ 4 | 5 | # autoconf intermediates 6 | aclocal.m4 7 | autom4te.cache/ 8 | 9 | # configure script output 10 | config.h 11 | config.log 12 | config.status 13 | Makefile 14 | -------------------------------------------------------------------------------- /qlearn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Help the user achieve a high score in a real game of threes by using a move searcher. ''' 5 | 6 | from __future__ import print_function 7 | import ctypes 8 | import time 9 | import os 10 | 11 | for suffix in ['so', 'dll', 'dylib']: 12 | dllfn = 'bin/2048.' + suffix 13 | if not os.path.isfile(dllfn): 14 | continue 15 | gamelib = ctypes.CDLL(dllfn) 16 | break 17 | else: 18 | print("Couldn't find 2048 library bin/2048.{so,dll,dylib}! Make sure to build it first.") 19 | exit() 20 | 21 | def main(): 22 | gamelib.init_tables() 23 | 24 | result = gamelib.play_game_randomly() 25 | print(result) 26 | 27 | if __name__ == '__main__': 28 | main() 29 | -------------------------------------------------------------------------------- /Makefile.in: -------------------------------------------------------------------------------- 1 | # Makefile.in generated by hand. 2 | # @configure_input@ 3 | 4 | CC = @CC@ 5 | CFLAGS = @CFLAGS@ 6 | CPP = @CPP@ 7 | CPPFLAGS = @CPPFLAGS@ 8 | CXX = @CXX@ 9 | CXXLD = $(CXX) 10 | CXXCPP = @CXXCPP@ 11 | CXXFLAGS = @CXXFLAGS@ -O3 -Wall -Wextra -fPIC -I/usr/local/python_2.7/include 12 | LDFLAGS = @LDFLAGS@ -L/usr/local/python_2.7/lib 13 | LDLIBS = @LIBS@ -lpython2.7 14 | MKDIR_P = @MKDIR_P@ 15 | 16 | EXEEXT = @EXEEXT@ 17 | OBJEXT = @OBJEXT@ 18 | 19 | $(shell $(MKDIR_P) bin) 20 | 21 | all: bin/2048$(EXEEXT) bin/2048.so 22 | 23 | bin/%$(EXEEXT): bin/%.$(OBJEXT) 24 | $(CXXLD) $(CXXFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@ 25 | 26 | bin/%.so: bin/%.$(OBJEXT) 27 | $(CXXLD) $(CXXFLAGS) -shared $(LDFLAGS) $^ $(LDLIBS) -o $@ 28 | 29 | bin/%.$(OBJEXT) : %.cpp 30 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< 31 | 32 | clean: 33 | $(RM) -rf bin/* 34 | 35 | .PHONY: all clean 36 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # -*- Autoconf -*- 2 | # Process this file with autoconf to produce a configure script. 3 | 4 | AC_CONFIG_MACRO_DIR([m4]) 5 | 6 | AC_PREREQ([2.69]) 7 | AC_INIT([2048-AI], [0.1], [https://github.com/nneonneo/2048-ai/issues]) 8 | AC_CONFIG_SRCDIR([2048.cpp]) 9 | AC_CONFIG_HEADERS([config.h]) 10 | 11 | AC_LANG([C++]) 12 | AX_CXX_COMPILE_STDCXX_11([], [optional]) 13 | 14 | # Checks for programs. 15 | AC_PROG_CXX 16 | AC_PROG_CPP 17 | AC_PROG_CC 18 | AC_PROG_MKDIR_P 19 | 20 | # Checks for header files. 21 | AC_CHECK_HEADERS([fcntl.h stdint.h stdlib.h string.h sys/time.h unistd.h unordered_map tr1/unordered_map]) 22 | 23 | # Checks for typedefs, structures, and compiler characteristics. 24 | AC_C_INLINE 25 | AC_TYPE_UINT16_T 26 | AC_TYPE_UINT64_T 27 | 28 | # Checks for library functions. 29 | AC_CHECK_FUNCS([gettimeofday memset strchr arc4random_uniform drand48]) 30 | 31 | AC_OUTPUT(Makefile) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AI for the [2048 game](http://gabrielecirulli.github.io/2048/). This uses *expectimax optimization*, along with a highly-efficient bitboard representation to search upwards of 10 million moves per second on recent hardware. Heuristics used include bonuses for empty squares and bonuses for placing large values near edges and corners. 2 | 3 | ## Building 4 | 5 | ### Unix/Linux/OS X 6 | 7 | Execute 8 | 9 | ./configure 10 | make 11 | 12 | in a terminal. Any relatively recent C++ compiler should be able to build the output. 13 | 14 | Note that you don't do `make install`; this program is meant to be run from this directory. 15 | 16 | ### Windows 17 | 18 | You have a few options, depending on what you have installed. 19 | 20 | - Pure Cygwin: follow the Unix/Linux/OS X instructions above. The resulting DLL can *only* be used with Cygwin programs, so 21 | to run the browser control version, you must use the Cygwin Python (not the python.org Python). For step-by-step instructions, courtesy Tamas Szell (@matukaa), see [this document](https://github.com/nneonneo/2048-ai/wiki/CygwinStepByStep.pdf). 22 | - Cygwin with MinGW: run 23 | 24 | CXX=x86_64-w64-mingw32-g++ CXXFLAGS='-static-libstdc++ -static-libgcc -D_WINDLL -D_GNU_SOURCE=1' ./configure ; make 25 | 26 | in a MinGW or Cygwin shell to build. The resultant DLL can be used with non-Cygwin programs. 27 | - Visual Studio: open a Visual Studio command prompt, `cd` to the 2048-ai directory, and run `make-msvc.bat`. 28 | 29 | ## Running the command-line version 30 | 31 | Run `bin/2048` if you want to see the AI by itself in action. 32 | 33 | ## Running the browser-control version 34 | 35 | Install [Remote Control for Firefox](https://addons.mozilla.org/en-US/firefox/addon/remote-control/). 36 | 37 | Open up the [2048 game](http://gabrielecirulli.github.io/2048/) or any compatible clone and start remote control. 38 | 39 | Run `2048.py` and watch the game! 40 | -------------------------------------------------------------------------------- /2048.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "platdefs.h" 4 | 5 | /* The fundamental trick: the 4x4 board is represented as a 64-bit word, 6 | * with each board square packed into a single 4-bit nibble. 7 | * 8 | * The maximum possible board value that can be supported is 32768 (2^15), but 9 | * this is a minor limitation as achieving 65536 is highly unlikely under normal circumstances. 10 | * 11 | * The space and computation savings from using this representation should be significant. 12 | * 13 | * The nibble shift can be computed as (r,c) -> shift (4*r + c). That is, (0,0) is the LSB. 14 | */ 15 | 16 | typedef uint64_t board_t; 17 | typedef uint16_t row_t; 18 | 19 | static const board_t ROW_MASK = 0xFFFFULL; 20 | static const board_t COL_MASK = 0x000F000F000F000FULL; 21 | 22 | static inline void print_board(board_t board) { 23 | int i,j; 24 | for(i=0; i<4; i++) { 25 | for(j=0; j<4; j++) { 26 | int value = (board) & 0xf; 27 | printf("%8d ", 1 << value); 28 | board >>= 4; 29 | } 30 | printf("\n"); 31 | } 32 | printf("\n"); 33 | } 34 | 35 | static inline board_t unpack_col(row_t row) { 36 | board_t tmp = row; 37 | return (tmp | (tmp << 12ULL) | (tmp << 24ULL) | (tmp << 36ULL)) & COL_MASK; 38 | } 39 | 40 | static inline row_t reverse_row(row_t row) { 41 | return (row >> 12) | ((row >> 4) & 0x00F0) | ((row << 4) & 0x0F00) | (row << 12); 42 | } 43 | 44 | /* Functions */ 45 | #ifdef __cplusplus 46 | extern "C" { 47 | #endif 48 | 49 | DLL_PUBLIC void init_tables(); 50 | 51 | typedef int (*get_move_func_t)(board_t); 52 | DLL_PUBLIC float score_toplevel_move(board_t board, int move); 53 | DLL_PUBLIC int find_best_move(board_t board); 54 | DLL_PUBLIC int ask_for_move(board_t board); 55 | DLL_PUBLIC PyObject* play_game(get_move_func_t get_move); 56 | DLL_PUBLIC PyObject* play_game_randomly(); 57 | 58 | #ifdef __cplusplus 59 | } 60 | #endif 61 | -------------------------------------------------------------------------------- /config.h.in: -------------------------------------------------------------------------------- 1 | /* config.h.in. Generated from configure.ac by autoheader. */ 2 | 3 | /* Define to 1 if you have the `arc4random_uniform' function. */ 4 | #undef HAVE_ARC4RANDOM_UNIFORM 5 | 6 | /* define if the compiler supports basic C++11 syntax */ 7 | #undef HAVE_CXX11 8 | 9 | /* Define to 1 if you have the `drand48' function. */ 10 | #undef HAVE_DRAND48 11 | 12 | /* Define to 1 if you have the header file. */ 13 | #undef HAVE_FCNTL_H 14 | 15 | /* Define to 1 if you have the `gettimeofday' function. */ 16 | #undef HAVE_GETTIMEOFDAY 17 | 18 | /* Define to 1 if you have the header file. */ 19 | #undef HAVE_INTTYPES_H 20 | 21 | /* Define to 1 if you have the header file. */ 22 | #undef HAVE_MEMORY_H 23 | 24 | /* Define to 1 if you have the `memset' function. */ 25 | #undef HAVE_MEMSET 26 | 27 | /* Define to 1 if you have the header file. */ 28 | #undef HAVE_STDINT_H 29 | 30 | /* Define to 1 if you have the header file. */ 31 | #undef HAVE_STDLIB_H 32 | 33 | /* Define to 1 if you have the `strchr' function. */ 34 | #undef HAVE_STRCHR 35 | 36 | /* Define to 1 if you have the header file. */ 37 | #undef HAVE_STRINGS_H 38 | 39 | /* Define to 1 if you have the header file. */ 40 | #undef HAVE_STRING_H 41 | 42 | /* Define to 1 if you have the header file. */ 43 | #undef HAVE_SYS_STAT_H 44 | 45 | /* Define to 1 if you have the header file. */ 46 | #undef HAVE_SYS_TIME_H 47 | 48 | /* Define to 1 if you have the header file. */ 49 | #undef HAVE_SYS_TYPES_H 50 | 51 | /* Define to 1 if you have the header file. */ 52 | #undef HAVE_TR1_UNORDERED_MAP 53 | 54 | /* Define to 1 if you have the header file. */ 55 | #undef HAVE_UNISTD_H 56 | 57 | /* Define to 1 if you have the header file. */ 58 | #undef HAVE_UNORDERED_MAP 59 | 60 | /* Define to the address where bug reports for this package should be sent. */ 61 | #undef PACKAGE_BUGREPORT 62 | 63 | /* Define to the full name of this package. */ 64 | #undef PACKAGE_NAME 65 | 66 | /* Define to the full name and version of this package. */ 67 | #undef PACKAGE_STRING 68 | 69 | /* Define to the one symbol short name of this package. */ 70 | #undef PACKAGE_TARNAME 71 | 72 | /* Define to the home page for this package. */ 73 | #undef PACKAGE_URL 74 | 75 | /* Define to the version of this package. */ 76 | #undef PACKAGE_VERSION 77 | 78 | /* Define to 1 if you have the ANSI C header files. */ 79 | #undef STDC_HEADERS 80 | 81 | /* Define for Solaris 2.5.1 so the uint64_t typedef from , 82 | , or is not used. If the typedef were allowed, the 83 | #define below would cause a syntax error. */ 84 | #undef _UINT64_T 85 | 86 | /* Define to `__inline__' or `__inline' if that's what the C compiler 87 | calls it, or to nothing if 'inline' is not supported under any name. */ 88 | #ifndef __cplusplus 89 | #undef inline 90 | #endif 91 | 92 | /* Define to the type of an unsigned integer type of width exactly 16 bits if 93 | such a type exists and the standard includes do not define it. */ 94 | #undef uint16_t 95 | 96 | /* Define to the type of an unsigned integer type of width exactly 64 bits if 97 | such a type exists and the standard includes do not define it. */ 98 | #undef uint64_t 99 | -------------------------------------------------------------------------------- /platdefs.h: -------------------------------------------------------------------------------- 1 | #ifndef PLATDEFS_H 2 | #define PLATDEFS_H 3 | 4 | #include "config.h" 5 | 6 | #include 7 | 8 | /** unif_random */ 9 | /* unif_random is defined as a random number generator returning a value in [0..n-1]. */ 10 | #if defined(HAVE_ARC4RANDOM_UNIFORM) 11 | static inline unsigned unif_random(unsigned n) { 12 | return arc4random_uniform(n); 13 | } 14 | #elif defined(HAVE_DRAND48) 15 | // Warning: This is a slightly biased RNG. 16 | #include 17 | #include 18 | #include 19 | static inline unsigned unif_random(unsigned n) { 20 | static int seeded = 0; 21 | 22 | if(!seeded) { 23 | int fd = open("/dev/urandom", O_RDONLY); 24 | unsigned short seed[3]; 25 | if(fd < 0 || read(fd, seed, sizeof(seed)) < (int)sizeof(seed)) { 26 | srand48(time(NULL)); 27 | } else { 28 | seed48(seed); 29 | } 30 | if(fd >= 0) 31 | close(fd); 32 | 33 | seeded = 1; 34 | } 35 | 36 | return (int)(drand48() * n); 37 | } 38 | #else 39 | // Warning: This is a slightly biased RNG. 40 | #include 41 | static inline unsigned unif_random(unsigned n) { 42 | static int seeded = 0; 43 | 44 | if(!seeded) { 45 | srand(time(NULL)); 46 | seeded = 1; 47 | } 48 | 49 | return rand() % n; 50 | } 51 | #endif 52 | 53 | /** DLL_PUBLIC */ 54 | /* DLL_PUBLIC definition from http://gcc.gnu.org/wiki/Visibility */ 55 | #if defined _WIN32 || defined __CYGWIN__ 56 | #if defined(_WINDLL) 57 | #define BUILDING_DLL 58 | #endif 59 | #ifdef BUILDING_DLL 60 | #ifdef __GNUC__ 61 | #define DLL_PUBLIC __attribute__ ((dllexport)) 62 | #else 63 | #define DLL_PUBLIC __declspec(dllexport) // Note: actually gcc seems to also supports this syntax. 64 | #endif 65 | #else 66 | #ifdef __GNUC__ 67 | #define DLL_PUBLIC __attribute__ ((dllimport)) 68 | #else 69 | #define DLL_PUBLIC __declspec(dllimport) // Note: actually gcc seems to also supports this syntax. 70 | #endif 71 | #endif 72 | #else 73 | #if __GNUC__ >= 4 74 | #define DLL_PUBLIC __attribute__ ((visibility ("default"))) 75 | #else 76 | #define DLL_PUBLIC 77 | #endif 78 | #endif 79 | 80 | /** gettimeofday */ 81 | /* Win32 gettimeofday implementation from 82 | http://social.msdn.microsoft.com/Forums/vstudio/en-US/430449b3-f6dd-4e18-84de-eebd26a8d668/gettimeofday 83 | with a missing "0" added to DELTA_EPOCH_IN_MICROSECS */ 84 | #if defined(_WIN32) && (!defined(HAVE_GETTIMEOFDAY) || !defined(HAVE_SYS_TIME_H)) 85 | #include 86 | #include 87 | #if defined(_MSC_VER) || defined(_MSC_EXTENSIONS) 88 | #define DELTA_EPOCH_IN_MICROSECS 116444736000000000Ui64 89 | #else 90 | #define DELTA_EPOCH_IN_MICROSECS 116444736000000000ULL 91 | #endif 92 | 93 | struct timezone; 94 | 95 | int gettimeofday(struct timeval *tv, struct timezone *tz) 96 | { 97 | FILETIME ft; 98 | unsigned __int64 tmpres = 0; 99 | 100 | (void)tz; 101 | 102 | if (NULL != tv) 103 | { 104 | GetSystemTimeAsFileTime(&ft); 105 | 106 | tmpres |= ft.dwHighDateTime; 107 | tmpres <<= 32; 108 | tmpres |= ft.dwLowDateTime; 109 | 110 | /*converting file time to unix epoch*/ 111 | tmpres -= DELTA_EPOCH_IN_MICROSECS; 112 | tmpres /= 10; /*convert into microseconds*/ 113 | tv->tv_sec = (long)(tmpres / 1000000UL); 114 | tv->tv_usec = (long)(tmpres % 1000000UL); 115 | } 116 | 117 | return 0; 118 | } 119 | #else 120 | #include 121 | #endif 122 | 123 | #endif /* PLATDEFS_H */ 124 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* config.h. Generated from config.h.in by configure. */ 2 | /* config.h.in. Generated from configure.ac by autoheader. */ 3 | 4 | /* Define to 1 if you have the `arc4random_uniform' function. */ 5 | /* #undef HAVE_ARC4RANDOM_UNIFORM */ 6 | 7 | /* define if the compiler supports basic C++11 syntax */ 8 | /* #undef HAVE_CXX11 */ 9 | 10 | /* Define to 1 if you have the `drand48' function. */ 11 | #define HAVE_DRAND48 1 12 | 13 | /* Define to 1 if you have the header file. */ 14 | #define HAVE_FCNTL_H 1 15 | 16 | /* Define to 1 if you have the `gettimeofday' function. */ 17 | #define HAVE_GETTIMEOFDAY 1 18 | 19 | /* Define to 1 if you have the header file. */ 20 | #define HAVE_INTTYPES_H 1 21 | 22 | /* Define to 1 if you have the header file. */ 23 | #define HAVE_MEMORY_H 1 24 | 25 | /* Define to 1 if you have the `memset' function. */ 26 | #define HAVE_MEMSET 1 27 | 28 | /* Define to 1 if you have the header file. */ 29 | #define HAVE_STDINT_H 1 30 | 31 | /* Define to 1 if you have the header file. */ 32 | #define HAVE_STDLIB_H 1 33 | 34 | /* Define to 1 if you have the `strchr' function. */ 35 | #define HAVE_STRCHR 1 36 | 37 | /* Define to 1 if you have the header file. */ 38 | #define HAVE_STRINGS_H 1 39 | 40 | /* Define to 1 if you have the header file. */ 41 | #define HAVE_STRING_H 1 42 | 43 | /* Define to 1 if you have the header file. */ 44 | #define HAVE_SYS_STAT_H 1 45 | 46 | /* Define to 1 if you have the header file. */ 47 | #define HAVE_SYS_TIME_H 1 48 | 49 | /* Define to 1 if you have the header file. */ 50 | #define HAVE_SYS_TYPES_H 1 51 | 52 | /* Define to 1 if you have the header file. */ 53 | #define HAVE_TR1_UNORDERED_MAP 1 54 | 55 | /* Define to 1 if you have the header file. */ 56 | #define HAVE_UNISTD_H 1 57 | 58 | /* Define to 1 if you have the header file. */ 59 | /* #undef HAVE_UNORDERED_MAP */ 60 | 61 | /* Define to the address where bug reports for this package should be sent. */ 62 | #define PACKAGE_BUGREPORT "https://github.com/nneonneo/2048-ai/issues" 63 | 64 | /* Define to the full name of this package. */ 65 | #define PACKAGE_NAME "2048-AI" 66 | 67 | /* Define to the full name and version of this package. */ 68 | #define PACKAGE_STRING "2048-AI 0.1" 69 | 70 | /* Define to the one symbol short name of this package. */ 71 | #define PACKAGE_TARNAME "2048-ai" 72 | 73 | /* Define to the home page for this package. */ 74 | #define PACKAGE_URL "" 75 | 76 | /* Define to the version of this package. */ 77 | #define PACKAGE_VERSION "0.1" 78 | 79 | /* Define to 1 if you have the ANSI C header files. */ 80 | #define STDC_HEADERS 1 81 | 82 | /* Define for Solaris 2.5.1 so the uint64_t typedef from , 83 | , or is not used. If the typedef were allowed, the 84 | #define below would cause a syntax error. */ 85 | /* #undef _UINT64_T */ 86 | 87 | /* Define to `__inline__' or `__inline' if that's what the C compiler 88 | calls it, or to nothing if 'inline' is not supported under any name. */ 89 | #ifndef __cplusplus 90 | /* #undef inline */ 91 | #endif 92 | 93 | /* Define to the type of an unsigned integer type of width exactly 16 bits if 94 | such a type exists and the standard includes do not define it. */ 95 | /* #undef uint16_t */ 96 | 97 | /* Define to the type of an unsigned integer type of width exactly 64 bits if 98 | such a type exists and the standard includes do not define it. */ 99 | /* #undef uint64_t */ 100 | -------------------------------------------------------------------------------- /2048.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' Help the user achieve a high score in a real game of threes by using a move searcher. ''' 5 | 6 | from __future__ import print_function 7 | import ctypes 8 | import time 9 | import os 10 | 11 | # Enable multithreading? 12 | MULTITHREAD = True 13 | 14 | for suffix in ['so', 'dll', 'dylib']: 15 | dllfn = 'bin/2048.' + suffix 16 | if not os.path.isfile(dllfn): 17 | continue 18 | ailib = ctypes.CDLL(dllfn) 19 | break 20 | else: 21 | print("Couldn't find 2048 library bin/2048.{so,dll,dylib}! Make sure to build it first.") 22 | exit() 23 | 24 | ailib.init_tables() 25 | 26 | ailib.find_best_move.argtypes = [ctypes.c_uint64] 27 | ailib.score_toplevel_move.argtypes = [ctypes.c_uint64, ctypes.c_int] 28 | ailib.score_toplevel_move.restype = ctypes.c_float 29 | 30 | def to_c_board(m): 31 | board = 0 32 | i = 0 33 | for row in m: 34 | for c in row: 35 | board |= c << (4*i) 36 | i += 1 37 | return board 38 | 39 | def print_board(m): 40 | for row in m: 41 | for c in row: 42 | print('%8d' % c, end=' ') 43 | print() 44 | 45 | def _to_val(c): 46 | if c == 0: return 0 47 | return 2**c 48 | 49 | def to_val(m): 50 | return [[_to_val(c) for c in row] for row in m] 51 | 52 | def _to_score(c): 53 | if c <= 1: 54 | return 0 55 | return (c-1) * (2**c) 56 | 57 | def to_score(m): 58 | return [[_to_score(c) for c in row] for row in m] 59 | 60 | if MULTITHREAD: 61 | from multiprocessing.pool import ThreadPool 62 | pool = ThreadPool(4) 63 | def score_toplevel_move(args): 64 | return ailib.score_toplevel_move(*args) 65 | 66 | def find_best_move(m): 67 | board = to_c_board(m) 68 | 69 | print_board(to_val(m)) 70 | 71 | scores = pool.map(score_toplevel_move, [(board, move) for move in range(4)]) 72 | bestmove, bestscore = max(enumerate(scores), key=lambda x:x[1]) 73 | if bestscore == 0: 74 | return -1 75 | return bestmove 76 | else: 77 | def find_best_move(m): 78 | board = to_c_board(m) 79 | return ailib.find_best_move(board) 80 | 81 | def movename(move): 82 | return ['up', 'down', 'left', 'right'][move] 83 | 84 | def rungame(args): 85 | from gamectrl import BrowserRemoteControl, Fast2048Control, Keyboard2048Control 86 | 87 | if len(args) == 1: 88 | port = int(args[0]) 89 | else: 90 | port = 32000 91 | 92 | ctrl = BrowserRemoteControl(port) 93 | # Use Keyboard2048Control if Fast2048Control doesn't seem to be working. 94 | gamectrl = Fast2048Control(ctrl) 95 | # gamectrl = Keyboard2048Control(ctrl) 96 | 97 | if gamectrl.get_status() == 'ended': 98 | gamectrl.restart_game() 99 | 100 | moveno = 0 101 | start = time.time() 102 | while 1: 103 | state = gamectrl.get_status() 104 | if state == 'ended': 105 | break 106 | elif state == 'won': 107 | time.sleep(0.75) 108 | gamectrl.continue_game() 109 | 110 | moveno += 1 111 | board = gamectrl.get_board() 112 | move = find_best_move(board) 113 | if move < 0: 114 | break 115 | print("%010.6f: Score %d, Move %d: %s" % (time.time() - start, gamectrl.get_score(), moveno, movename(move))) 116 | gamectrl.execute_move(move) 117 | 118 | score = gamectrl.get_score() 119 | board = gamectrl.get_board() 120 | maxval = max(max(row) for row in to_val(board)) 121 | print("Game over. Final score %d; highest tile %d." % (score, maxval)) 122 | 123 | if __name__ == '__main__': 124 | import sys 125 | rungame(sys.argv[1:]) 126 | -------------------------------------------------------------------------------- /m4/ax_cxx_compile_stdcxx_11.m4: -------------------------------------------------------------------------------- 1 | # ============================================================================ 2 | # http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html 3 | # ============================================================================ 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_CXX_COMPILE_STDCXX_11([ext|noext],[mandatory|optional]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # Check for baseline language coverage in the compiler for the C++11 12 | # standard; if necessary, add switches to CXXFLAGS to enable support. 13 | # 14 | # The first argument, if specified, indicates whether you insist on an 15 | # extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. 16 | # -std=c++11). If neither is specified, you get whatever works, with 17 | # preference for an extended mode. 18 | # 19 | # The second argument, if specified 'mandatory' or if left unspecified, 20 | # indicates that baseline C++11 support is required and that the macro 21 | # should error out if no mode with that support is found. If specified 22 | # 'optional', then configuration proceeds regardless, after defining 23 | # HAVE_CXX11 if and only if a supporting mode is found. 24 | # 25 | # LICENSE 26 | # 27 | # Copyright (c) 2008 Benjamin Kosnik 28 | # Copyright (c) 2012 Zack Weinberg 29 | # Copyright (c) 2013 Roy Stogner 30 | # Copyright (c) 2014 Alexey Sokolov 31 | # 32 | # Copying and distribution of this file, with or without modification, are 33 | # permitted in any medium without royalty provided the copyright notice 34 | # and this notice are preserved. This file is offered as-is, without any 35 | # warranty. 36 | 37 | #serial 4 38 | 39 | m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody], [[ 40 | template 41 | struct check 42 | { 43 | static_assert(sizeof(int) <= sizeof(T), "not big enough"); 44 | }; 45 | 46 | struct Base { 47 | virtual void f() {} 48 | }; 49 | struct Child : public Base { 50 | virtual void f() override {} 51 | }; 52 | 53 | typedef check> right_angle_brackets; 54 | 55 | int a; 56 | decltype(a) b; 57 | 58 | typedef check check_type; 59 | check_type c; 60 | check_type&& cr = static_cast(c); 61 | 62 | auto d = a; 63 | auto l = [](){}; 64 | ]]) 65 | 66 | AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [dnl 67 | m4_if([$1], [], [], 68 | [$1], [ext], [], 69 | [$1], [noext], [], 70 | [m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_11])])dnl 71 | m4_if([$2], [], [ax_cxx_compile_cxx11_required=true], 72 | [$2], [mandatory], [ax_cxx_compile_cxx11_required=true], 73 | [$2], [optional], [ax_cxx_compile_cxx11_required=false], 74 | [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX_11])]) 75 | AC_LANG_PUSH([C++])dnl 76 | ac_success=no 77 | AC_CACHE_CHECK(whether $CXX supports C++11 features by default, 78 | ax_cv_cxx_compile_cxx11, 79 | [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], 80 | [ax_cv_cxx_compile_cxx11=yes], 81 | [ax_cv_cxx_compile_cxx11=no])]) 82 | if test x$ax_cv_cxx_compile_cxx11 = xyes; then 83 | ac_success=yes 84 | fi 85 | 86 | m4_if([$1], [noext], [], [dnl 87 | if test x$ac_success = xno; then 88 | for switch in -std=gnu++11 -std=gnu++0x; do 89 | cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) 90 | AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, 91 | $cachevar, 92 | [ac_save_CXXFLAGS="$CXXFLAGS" 93 | CXXFLAGS="$CXXFLAGS $switch" 94 | AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], 95 | [eval $cachevar=yes], 96 | [eval $cachevar=no]) 97 | CXXFLAGS="$ac_save_CXXFLAGS"]) 98 | if eval test x\$$cachevar = xyes; then 99 | CXXFLAGS="$CXXFLAGS $switch" 100 | ac_success=yes 101 | break 102 | fi 103 | done 104 | fi]) 105 | 106 | m4_if([$1], [ext], [], [dnl 107 | if test x$ac_success = xno; then 108 | for switch in -std=c++11 -std=c++0x; do 109 | cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) 110 | AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, 111 | $cachevar, 112 | [ac_save_CXXFLAGS="$CXXFLAGS" 113 | CXXFLAGS="$CXXFLAGS $switch" 114 | AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], 115 | [eval $cachevar=yes], 116 | [eval $cachevar=no]) 117 | CXXFLAGS="$ac_save_CXXFLAGS"]) 118 | if eval test x\$$cachevar = xyes; then 119 | CXXFLAGS="$CXXFLAGS $switch" 120 | ac_success=yes 121 | break 122 | fi 123 | done 124 | fi]) 125 | AC_LANG_POP([C++]) 126 | if test x$ax_cxx_compile_cxx11_required = xtrue; then 127 | if test x$ac_success = xno; then 128 | AC_MSG_ERROR([*** A compiler with support for C++11 language features is required.]) 129 | fi 130 | else 131 | if test x$ac_success = xno; then 132 | HAVE_CXX11=0 133 | AC_MSG_NOTICE([No compiler with C++11 support was found]) 134 | else 135 | HAVE_CXX11=1 136 | AC_DEFINE(HAVE_CXX11,1, 137 | [define if the compiler supports basic C++11 syntax]) 138 | fi 139 | 140 | AC_SUBST(HAVE_CXX11) 141 | fi 142 | ]) 143 | -------------------------------------------------------------------------------- /2048.cpp: -------------------------------------------------------------------------------- 1 | #include "2048.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | #include "config.h" 13 | #if defined(HAVE_UNORDERED_MAP) 14 | #include 15 | typedef std::unordered_map trans_table_t; 16 | #elif defined(HAVE_TR1_UNORDERED_MAP) 17 | #include 18 | typedef std::tr1::unordered_map trans_table_t; 19 | #else 20 | #include 21 | typedef std::map trans_table_t; 22 | #endif 23 | 24 | /* MSVC compatibility: undefine max and min macros */ 25 | #if defined(max) 26 | #undef max 27 | #endif 28 | 29 | #if defined(min) 30 | #undef min 31 | #endif 32 | 33 | // Transpose rows/columns in a board: 34 | // 0123 048c 35 | // 4567 --> 159d 36 | // 89ab 26ae 37 | // cdef 37bf 38 | static inline board_t transpose(board_t x) 39 | { 40 | board_t a1 = x & 0xF0F00F0FF0F00F0FULL; 41 | board_t a2 = x & 0x0000F0F00000F0F0ULL; 42 | board_t a3 = x & 0x0F0F00000F0F0000ULL; 43 | board_t a = a1 | (a2 << 12) | (a3 >> 12); 44 | board_t b1 = a & 0xFF00FF0000FF00FFULL; 45 | board_t b2 = a & 0x00FF00FF00000000ULL; 46 | board_t b3 = a & 0x00000000FF00FF00ULL; 47 | return b1 | (b2 >> 24) | (b3 << 24); 48 | } 49 | 50 | // Count the number of empty positions (= zero nibbles) in a board. 51 | // Precondition: the board cannot be fully empty. 52 | static int count_empty(uint64_t x) 53 | { 54 | x |= (x >> 2) & 0x3333333333333333ULL; 55 | x |= (x >> 1); 56 | x = ~x & 0x1111111111111111ULL; 57 | // At this point each nibble is: 58 | // 0 if the original nibble was non-zero 59 | // 1 if the original nibble was zero 60 | // Next sum them all 61 | x += x >> 32; 62 | x += x >> 16; 63 | x += x >> 8; 64 | x += x >> 4; // this can overflow to the next nibble if there were 16 empty positions 65 | return x & 0xf; 66 | } 67 | 68 | /* We can perform state lookups one row at a time by using arrays with 65536 entries. */ 69 | 70 | /* Move tables. Each row or compressed column is mapped to (oldrow^newrow) assuming row/col 0. 71 | * 72 | * Thus, the value is 0 if there is no move, and otherwise equals a value that can easily be 73 | * xor'ed into the current board state to update the board. */ 74 | static row_t row_left_table [65536]; 75 | static row_t row_right_table[65536]; 76 | static board_t col_up_table[65536]; 77 | static board_t col_down_table[65536]; 78 | static float heur_score_table[65536]; 79 | static float score_table[65536]; 80 | 81 | void init_tables() { 82 | for (unsigned row = 0; row < 65536; ++row) { 83 | unsigned line[4] = { 84 | (row >> 0) & 0xf, 85 | (row >> 4) & 0xf, 86 | (row >> 8) & 0xf, 87 | (row >> 12) & 0xf 88 | }; 89 | 90 | float heur_score = 0.0f; 91 | float score = 0.0f; 92 | for (int i = 0; i < 4; ++i) { 93 | int rank = line[i]; 94 | if (rank == 0) { 95 | heur_score += 10000.0f; 96 | } else if (rank >= 2) { 97 | // the score is the total sum of the tile and all intermediate merged tiles 98 | score += (rank - 1) * (1 << rank); 99 | } 100 | } 101 | score_table[row] = score; 102 | 103 | int maxi = 0; 104 | for (int i = 1; i < 4; ++i) { 105 | if (line[i] > line[maxi]) maxi = i; 106 | } 107 | 108 | if (maxi == 0 || maxi == 3) heur_score += 20000.0f; 109 | 110 | // Check if maxi's are close to each other, and of diff ranks (eg 128 256) 111 | for (int i = 1; i < 4; ++i) { 112 | if ((line[i] == line[i - 1] + 1) || (line[i] == line[i - 1] - 1)) heur_score += 1000.0f; 113 | } 114 | 115 | // Check if the values are ordered: 116 | if ((line[0] < line[1]) && (line[1] < line[2]) && (line[2] < line[3])) heur_score += 10000.0f; 117 | if ((line[0] > line[1]) && (line[1] > line[2]) && (line[2] > line[3])) heur_score += 10000.0f; 118 | 119 | heur_score_table[row] = heur_score; 120 | 121 | 122 | // execute a move to the left 123 | for (int i = 0; i < 3; ++i) { 124 | int j; 125 | for (j = i + 1; j < 4; ++j) { 126 | if (line[j] != 0) break; 127 | } 128 | if (j == 4) break; // no more tiles to the right 129 | 130 | if (line[i] == 0) { 131 | line[i] = line[j]; 132 | line[j] = 0; 133 | i--; // retry this entry 134 | } else if (line[i] == line[j] && line[i] != 0xf) { 135 | line[i]++; 136 | line[j] = 0; 137 | } 138 | } 139 | 140 | row_t result = (line[0] << 0) | 141 | (line[1] << 4) | 142 | (line[2] << 8) | 143 | (line[3] << 12); 144 | row_t rev_result = reverse_row(result); 145 | unsigned rev_row = reverse_row(row); 146 | 147 | row_left_table [ row] = row ^ result; 148 | row_right_table[rev_row] = rev_row ^ rev_result; 149 | col_up_table [ row] = unpack_col( row) ^ unpack_col( result); 150 | col_down_table [rev_row] = unpack_col(rev_row) ^ unpack_col(rev_result); 151 | } 152 | } 153 | 154 | static inline board_t execute_move_0(board_t board) { 155 | board_t ret = board; 156 | board_t t = transpose(board); 157 | ret ^= col_up_table[(t >> 0) & ROW_MASK] << 0; 158 | ret ^= col_up_table[(t >> 16) & ROW_MASK] << 4; 159 | ret ^= col_up_table[(t >> 32) & ROW_MASK] << 8; 160 | ret ^= col_up_table[(t >> 48) & ROW_MASK] << 12; 161 | return ret; 162 | } 163 | 164 | static inline board_t execute_move_1(board_t board) { 165 | board_t ret = board; 166 | board_t t = transpose(board); 167 | ret ^= col_down_table[(t >> 0) & ROW_MASK] << 0; 168 | ret ^= col_down_table[(t >> 16) & ROW_MASK] << 4; 169 | ret ^= col_down_table[(t >> 32) & ROW_MASK] << 8; 170 | ret ^= col_down_table[(t >> 48) & ROW_MASK] << 12; 171 | return ret; 172 | } 173 | 174 | static inline board_t execute_move_2(board_t board) { 175 | board_t ret = board; 176 | ret ^= board_t(row_left_table[(board >> 0) & ROW_MASK]) << 0; 177 | ret ^= board_t(row_left_table[(board >> 16) & ROW_MASK]) << 16; 178 | ret ^= board_t(row_left_table[(board >> 32) & ROW_MASK]) << 32; 179 | ret ^= board_t(row_left_table[(board >> 48) & ROW_MASK]) << 48; 180 | return ret; 181 | } 182 | 183 | static inline board_t execute_move_3(board_t board) { 184 | board_t ret = board; 185 | ret ^= board_t(row_right_table[(board >> 0) & ROW_MASK]) << 0; 186 | ret ^= board_t(row_right_table[(board >> 16) & ROW_MASK]) << 16; 187 | ret ^= board_t(row_right_table[(board >> 32) & ROW_MASK]) << 32; 188 | ret ^= board_t(row_right_table[(board >> 48) & ROW_MASK]) << 48; 189 | return ret; 190 | } 191 | 192 | /* Execute a move. */ 193 | static inline board_t execute_move(int move, board_t board) { 194 | switch(move) { 195 | case 0: // up 196 | return execute_move_0(board); 197 | case 1: // down 198 | return execute_move_1(board); 199 | case 2: // left 200 | return execute_move_2(board); 201 | case 3: // right 202 | return execute_move_3(board); 203 | default: 204 | return ~0ULL; 205 | } 206 | } 207 | 208 | static inline int get_max_rank(board_t board) { 209 | int maxrank = 0; 210 | while (board) { 211 | maxrank = std::max(maxrank, int(board & 0xf)); 212 | board >>= 4; 213 | } 214 | return maxrank; 215 | } 216 | 217 | /* Optimizing the game */ 218 | 219 | struct eval_state { 220 | trans_table_t trans_table; // transposition table, to cache previously-seen moves 221 | float cprob_thresh; 222 | int maxdepth; 223 | int curdepth; 224 | int cachehits; 225 | int moves_evaled; 226 | 227 | eval_state() : cprob_thresh(0), maxdepth(0), curdepth(0), cachehits(0), moves_evaled(0) { 228 | } 229 | }; 230 | 231 | // score a single board heuristically 232 | static float score_heur_board(board_t board); 233 | // score a single board actually (adding in the score from spawned 4 tiles) 234 | static float score_board(board_t board); 235 | // score over all possible moves 236 | static float score_move_node(eval_state &state, board_t board, float cprob); 237 | // score over all possible tile choices and placements 238 | static float score_tilechoose_node(eval_state &state, board_t board, float cprob); 239 | 240 | 241 | static float score_helper(board_t board, const float* table) { 242 | return table[(board >> 0) & ROW_MASK] + 243 | table[(board >> 16) & ROW_MASK] + 244 | table[(board >> 32) & ROW_MASK] + 245 | table[(board >> 48) & ROW_MASK]; 246 | } 247 | 248 | static float score_heur_board(board_t board) { 249 | return score_helper( board , heur_score_table) + 250 | score_helper(transpose(board), heur_score_table) + 251 | 100000.0f; 252 | } 253 | 254 | static float score_board(board_t board) { 255 | return score_helper(board, score_table); 256 | } 257 | 258 | static float score_tilechoose_node(eval_state &state, board_t board, float cprob) { 259 | int num_open = count_empty(board); 260 | cprob /= num_open; 261 | 262 | float res = 0.0f; 263 | board_t tmp = board; 264 | board_t tile_2 = 1; 265 | while (tile_2) { 266 | if ((tmp & 0xf) == 0) { 267 | res += score_move_node(state, board | tile_2 , cprob * 0.9f) * 0.9f; 268 | res += score_move_node(state, board | (tile_2 << 1), cprob * 0.1f) * 0.1f; 269 | } 270 | tmp >>= 4; 271 | tile_2 <<= 4; 272 | } 273 | return res / num_open; 274 | } 275 | 276 | // Statistics and controls 277 | // cprob: cumulative probability 278 | // don't recurse into a node with a cprob less than this threshold 279 | static const float CPROB_THRESH_BASE = 0.0001f; 280 | static const int CACHE_DEPTH_LIMIT = 6; 281 | static const int SEARCH_DEPTH_LIMIT = 8; 282 | 283 | static float score_move_node(eval_state &state, board_t board, float cprob) { 284 | if (cprob < state.cprob_thresh || state.curdepth >= SEARCH_DEPTH_LIMIT) { 285 | if(state.curdepth > state.maxdepth) 286 | state.maxdepth = state.curdepth; 287 | return score_heur_board(board); 288 | } 289 | 290 | if(state.curdepth < CACHE_DEPTH_LIMIT) { 291 | const trans_table_t::iterator &i = state.trans_table.find(board); 292 | if(i != state.trans_table.end()) { 293 | state.cachehits++; 294 | return i->second; 295 | } 296 | } 297 | 298 | float best = 0.0f; 299 | state.curdepth++; 300 | for (int move = 0; move < 4; ++move) { 301 | board_t newboard = execute_move(move, board); 302 | state.moves_evaled++; 303 | 304 | if (board != newboard) { 305 | best = std::max(best, score_tilechoose_node(state, newboard, cprob)); 306 | } 307 | } 308 | state.curdepth--; 309 | 310 | if (state.curdepth < CACHE_DEPTH_LIMIT) { 311 | state.trans_table[board] = best; 312 | } 313 | 314 | return best; 315 | } 316 | 317 | static float _score_toplevel_move(eval_state &state, board_t board, int move) { 318 | //int maxrank = get_max_rank(board); 319 | board_t newboard = execute_move(move, board); 320 | 321 | if(board == newboard) 322 | return 0; 323 | 324 | state.cprob_thresh = CPROB_THRESH_BASE; 325 | 326 | return score_tilechoose_node(state, newboard, 1.0f) + 1e-6; 327 | } 328 | 329 | float score_toplevel_move(board_t board, int move) { 330 | float res; 331 | struct timeval start, finish; 332 | double elapsed; 333 | eval_state state; 334 | 335 | gettimeofday(&start, NULL); 336 | res = _score_toplevel_move(state, board, move); 337 | gettimeofday(&finish, NULL); 338 | 339 | elapsed = (finish.tv_sec - start.tv_sec); 340 | elapsed += (finish.tv_usec - start.tv_usec) / 1000000.0; 341 | 342 | //printf("Move %d: result %f: eval'd %d moves (%d cache hits, %d cache size) in %.2f seconds (maxdepth=%d)\n", move, res, 343 | //state.moves_evaled, state.cachehits, (int)state.trans_table.size(), elapsed, state.maxdepth); 344 | 345 | return res; 346 | } 347 | 348 | /* Find the best move for a given board. */ 349 | int find_best_move(board_t board) { 350 | int move; 351 | float best = 0; 352 | int bestmove = -1; 353 | 354 | for(move=0; move<4; move++) { 355 | float res = score_toplevel_move(board, move); 356 | 357 | if(res > best) { 358 | best = res; 359 | bestmove = move; 360 | } 361 | } 362 | 363 | return bestmove; 364 | } 365 | 366 | int ask_for_move(board_t board) { 367 | int move; 368 | char validstr[5]; 369 | char *validpos = validstr; 370 | 371 | print_board(board); 372 | 373 | for(move=0; move<4; move++) { 374 | if(execute_move(move, board) != board) 375 | *validpos++ = "UDLR"[move]; 376 | } 377 | *validpos = 0; 378 | if(validpos == validstr) 379 | return -1; 380 | 381 | while(1) { 382 | char movestr[64]; 383 | const char *allmoves = "UDLR"; 384 | 385 | printf("Move [%s]? ", validstr); 386 | 387 | if(!fgets(movestr, sizeof(movestr)-1, stdin)) 388 | return -1; 389 | 390 | if(!strchr(validstr, toupper(movestr[0]))) { 391 | printf("Invalid move.\n"); 392 | continue; 393 | } 394 | 395 | return strchr(allmoves, toupper(movestr[0])) - allmoves; 396 | } 397 | } 398 | 399 | /* Playing the game */ 400 | static board_t draw_tile() { 401 | return (unif_random(10) < 9) ? 1 : 2; 402 | } 403 | 404 | static board_t insert_tile_rand(board_t board, board_t tile) { 405 | int index = unif_random(count_empty(board)); 406 | board_t tmp = board; 407 | while (true) { 408 | while ((tmp & 0xf) != 0) { 409 | tmp >>= 4; 410 | tile <<= 4; 411 | } 412 | if (index == 0) break; 413 | --index; 414 | tmp >>= 4; 415 | tile <<= 4; 416 | } 417 | return board | tile; 418 | } 419 | 420 | static board_t initial_board() { 421 | board_t board = draw_tile() << (4 * unif_random(16)); 422 | return insert_tile_rand(board, draw_tile()); 423 | } 424 | 425 | int get_random_move(board_t board) { 426 | return unif_random(4); 427 | } 428 | 429 | PyObject* play_game_randomly() { 430 | return play_game(get_random_move); 431 | } 432 | 433 | PyObject* play_game(get_move_func_t get_move) { 434 | board_t board = initial_board(); 435 | PyObject* result = PyList_New(0); 436 | 437 | int moveno = 0; 438 | int scorepenalty = 0; // "penalty" for obtaining free 4 tiles 439 | 440 | int moves = 0; 441 | 442 | while(1) { 443 | int move; 444 | board_t newboard; 445 | 446 | for(move = 0; move < 4; move++) { 447 | if(execute_move(move, board) != board) 448 | break; 449 | } 450 | if(move == 4) 451 | break; // no legal moves 452 | 453 | //printf("\nMove #%d, current score=%.0f\n", ++moveno, score_board(board) - scorepenalty); 454 | //print_board(board); 455 | 456 | PyObject *val = PyFloat_FromDouble(score_board(board)); 457 | PyObject *key = PyLong_FromLongLong(board); 458 | 459 | PyObject *item = PyTuple_New(2); 460 | PyTuple_SetItem(item, 0, key); 461 | PyTuple_SetItem(item, 1, val); 462 | 463 | PyList_Append(result, item); 464 | 465 | move = get_move(board); 466 | if(move < 0) 467 | break; 468 | 469 | newboard = execute_move(move, board); 470 | if(newboard == board) { 471 | moves -= 1; 472 | continue; 473 | } 474 | 475 | board_t tile = draw_tile(); 476 | if (tile == 2) scorepenalty += 4; 477 | board = insert_tile_rand(newboard, tile); 478 | 479 | int max_value = get_max_rank(board); 480 | 481 | if (max_value == 11) 482 | break; 483 | 484 | moves += 1; 485 | } 486 | 487 | return result; 488 | } 489 | 490 | int main() { 491 | init_tables(); 492 | //play_game(find_best_move); 493 | play_game_randomly(); 494 | } 495 | -------------------------------------------------------------------------------- /install-sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # install - install a program, script, or datafile 3 | 4 | scriptversion=2011-11-20.07; # UTC 5 | 6 | # This originates from X11R5 (mit/util/scripts/install.sh), which was 7 | # later released in X11R6 (xc/config/util/install.sh) with the 8 | # following copyright and license. 9 | # 10 | # Copyright (C) 1994 X Consortium 11 | # 12 | # Permission is hereby granted, free of charge, to any person obtaining a copy 13 | # of this software and associated documentation files (the "Software"), to 14 | # deal in the Software without restriction, including without limitation the 15 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 16 | # sell copies of the Software, and to permit persons to whom the Software is 17 | # furnished to do so, subject to the following conditions: 18 | # 19 | # The above copyright notice and this permission notice shall be included in 20 | # all copies or substantial portions of the Software. 21 | # 22 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | # X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 26 | # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- 27 | # TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | # 29 | # Except as contained in this notice, the name of the X Consortium shall not 30 | # be used in advertising or otherwise to promote the sale, use or other deal- 31 | # ings in this Software without prior written authorization from the X Consor- 32 | # tium. 33 | # 34 | # 35 | # FSF changes to this file are in the public domain. 36 | # 37 | # Calling this script install-sh is preferred over install.sh, to prevent 38 | # 'make' implicit rules from creating a file called install from it 39 | # when there is no Makefile. 40 | # 41 | # This script is compatible with the BSD install script, but was written 42 | # from scratch. 43 | 44 | nl=' 45 | ' 46 | IFS=" "" $nl" 47 | 48 | # set DOITPROG to echo to test this script 49 | 50 | # Don't use :- since 4.3BSD and earlier shells don't like it. 51 | doit=${DOITPROG-} 52 | if test -z "$doit"; then 53 | doit_exec=exec 54 | else 55 | doit_exec=$doit 56 | fi 57 | 58 | # Put in absolute file names if you don't have them in your path; 59 | # or use environment vars. 60 | 61 | chgrpprog=${CHGRPPROG-chgrp} 62 | chmodprog=${CHMODPROG-chmod} 63 | chownprog=${CHOWNPROG-chown} 64 | cmpprog=${CMPPROG-cmp} 65 | cpprog=${CPPROG-cp} 66 | mkdirprog=${MKDIRPROG-mkdir} 67 | mvprog=${MVPROG-mv} 68 | rmprog=${RMPROG-rm} 69 | stripprog=${STRIPPROG-strip} 70 | 71 | posix_glob='?' 72 | initialize_posix_glob=' 73 | test "$posix_glob" != "?" || { 74 | if (set -f) 2>/dev/null; then 75 | posix_glob= 76 | else 77 | posix_glob=: 78 | fi 79 | } 80 | ' 81 | 82 | posix_mkdir= 83 | 84 | # Desired mode of installed file. 85 | mode=0755 86 | 87 | chgrpcmd= 88 | chmodcmd=$chmodprog 89 | chowncmd= 90 | mvcmd=$mvprog 91 | rmcmd="$rmprog -f" 92 | stripcmd= 93 | 94 | src= 95 | dst= 96 | dir_arg= 97 | dst_arg= 98 | 99 | copy_on_change=false 100 | no_target_directory= 101 | 102 | usage="\ 103 | Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE 104 | or: $0 [OPTION]... SRCFILES... DIRECTORY 105 | or: $0 [OPTION]... -t DIRECTORY SRCFILES... 106 | or: $0 [OPTION]... -d DIRECTORIES... 107 | 108 | In the 1st form, copy SRCFILE to DSTFILE. 109 | In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. 110 | In the 4th, create DIRECTORIES. 111 | 112 | Options: 113 | --help display this help and exit. 114 | --version display version info and exit. 115 | 116 | -c (ignored) 117 | -C install only if different (preserve the last data modification time) 118 | -d create directories instead of installing files. 119 | -g GROUP $chgrpprog installed files to GROUP. 120 | -m MODE $chmodprog installed files to MODE. 121 | -o USER $chownprog installed files to USER. 122 | -s $stripprog installed files. 123 | -t DIRECTORY install into DIRECTORY. 124 | -T report an error if DSTFILE is a directory. 125 | 126 | Environment variables override the default commands: 127 | CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG 128 | RMPROG STRIPPROG 129 | " 130 | 131 | while test $# -ne 0; do 132 | case $1 in 133 | -c) ;; 134 | 135 | -C) copy_on_change=true;; 136 | 137 | -d) dir_arg=true;; 138 | 139 | -g) chgrpcmd="$chgrpprog $2" 140 | shift;; 141 | 142 | --help) echo "$usage"; exit $?;; 143 | 144 | -m) mode=$2 145 | case $mode in 146 | *' '* | *' '* | *' 147 | '* | *'*'* | *'?'* | *'['*) 148 | echo "$0: invalid mode: $mode" >&2 149 | exit 1;; 150 | esac 151 | shift;; 152 | 153 | -o) chowncmd="$chownprog $2" 154 | shift;; 155 | 156 | -s) stripcmd=$stripprog;; 157 | 158 | -t) dst_arg=$2 159 | # Protect names problematic for 'test' and other utilities. 160 | case $dst_arg in 161 | -* | [=\(\)!]) dst_arg=./$dst_arg;; 162 | esac 163 | shift;; 164 | 165 | -T) no_target_directory=true;; 166 | 167 | --version) echo "$0 $scriptversion"; exit $?;; 168 | 169 | --) shift 170 | break;; 171 | 172 | -*) echo "$0: invalid option: $1" >&2 173 | exit 1;; 174 | 175 | *) break;; 176 | esac 177 | shift 178 | done 179 | 180 | if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then 181 | # When -d is used, all remaining arguments are directories to create. 182 | # When -t is used, the destination is already specified. 183 | # Otherwise, the last argument is the destination. Remove it from $@. 184 | for arg 185 | do 186 | if test -n "$dst_arg"; then 187 | # $@ is not empty: it contains at least $arg. 188 | set fnord "$@" "$dst_arg" 189 | shift # fnord 190 | fi 191 | shift # arg 192 | dst_arg=$arg 193 | # Protect names problematic for 'test' and other utilities. 194 | case $dst_arg in 195 | -* | [=\(\)!]) dst_arg=./$dst_arg;; 196 | esac 197 | done 198 | fi 199 | 200 | if test $# -eq 0; then 201 | if test -z "$dir_arg"; then 202 | echo "$0: no input file specified." >&2 203 | exit 1 204 | fi 205 | # It's OK to call 'install-sh -d' without argument. 206 | # This can happen when creating conditional directories. 207 | exit 0 208 | fi 209 | 210 | if test -z "$dir_arg"; then 211 | do_exit='(exit $ret); exit $ret' 212 | trap "ret=129; $do_exit" 1 213 | trap "ret=130; $do_exit" 2 214 | trap "ret=141; $do_exit" 13 215 | trap "ret=143; $do_exit" 15 216 | 217 | # Set umask so as not to create temps with too-generous modes. 218 | # However, 'strip' requires both read and write access to temps. 219 | case $mode in 220 | # Optimize common cases. 221 | *644) cp_umask=133;; 222 | *755) cp_umask=22;; 223 | 224 | *[0-7]) 225 | if test -z "$stripcmd"; then 226 | u_plus_rw= 227 | else 228 | u_plus_rw='% 200' 229 | fi 230 | cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; 231 | *) 232 | if test -z "$stripcmd"; then 233 | u_plus_rw= 234 | else 235 | u_plus_rw=,u+rw 236 | fi 237 | cp_umask=$mode$u_plus_rw;; 238 | esac 239 | fi 240 | 241 | for src 242 | do 243 | # Protect names problematic for 'test' and other utilities. 244 | case $src in 245 | -* | [=\(\)!]) src=./$src;; 246 | esac 247 | 248 | if test -n "$dir_arg"; then 249 | dst=$src 250 | dstdir=$dst 251 | test -d "$dstdir" 252 | dstdir_status=$? 253 | else 254 | 255 | # Waiting for this to be detected by the "$cpprog $src $dsttmp" command 256 | # might cause directories to be created, which would be especially bad 257 | # if $src (and thus $dsttmp) contains '*'. 258 | if test ! -f "$src" && test ! -d "$src"; then 259 | echo "$0: $src does not exist." >&2 260 | exit 1 261 | fi 262 | 263 | if test -z "$dst_arg"; then 264 | echo "$0: no destination specified." >&2 265 | exit 1 266 | fi 267 | dst=$dst_arg 268 | 269 | # If destination is a directory, append the input filename; won't work 270 | # if double slashes aren't ignored. 271 | if test -d "$dst"; then 272 | if test -n "$no_target_directory"; then 273 | echo "$0: $dst_arg: Is a directory" >&2 274 | exit 1 275 | fi 276 | dstdir=$dst 277 | dst=$dstdir/`basename "$src"` 278 | dstdir_status=0 279 | else 280 | # Prefer dirname, but fall back on a substitute if dirname fails. 281 | dstdir=` 282 | (dirname "$dst") 2>/dev/null || 283 | expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ 284 | X"$dst" : 'X\(//\)[^/]' \| \ 285 | X"$dst" : 'X\(//\)$' \| \ 286 | X"$dst" : 'X\(/\)' \| . 2>/dev/null || 287 | echo X"$dst" | 288 | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ 289 | s//\1/ 290 | q 291 | } 292 | /^X\(\/\/\)[^/].*/{ 293 | s//\1/ 294 | q 295 | } 296 | /^X\(\/\/\)$/{ 297 | s//\1/ 298 | q 299 | } 300 | /^X\(\/\).*/{ 301 | s//\1/ 302 | q 303 | } 304 | s/.*/./; q' 305 | ` 306 | 307 | test -d "$dstdir" 308 | dstdir_status=$? 309 | fi 310 | fi 311 | 312 | obsolete_mkdir_used=false 313 | 314 | if test $dstdir_status != 0; then 315 | case $posix_mkdir in 316 | '') 317 | # Create intermediate dirs using mode 755 as modified by the umask. 318 | # This is like FreeBSD 'install' as of 1997-10-28. 319 | umask=`umask` 320 | case $stripcmd.$umask in 321 | # Optimize common cases. 322 | *[2367][2367]) mkdir_umask=$umask;; 323 | .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; 324 | 325 | *[0-7]) 326 | mkdir_umask=`expr $umask + 22 \ 327 | - $umask % 100 % 40 + $umask % 20 \ 328 | - $umask % 10 % 4 + $umask % 2 329 | `;; 330 | *) mkdir_umask=$umask,go-w;; 331 | esac 332 | 333 | # With -d, create the new directory with the user-specified mode. 334 | # Otherwise, rely on $mkdir_umask. 335 | if test -n "$dir_arg"; then 336 | mkdir_mode=-m$mode 337 | else 338 | mkdir_mode= 339 | fi 340 | 341 | posix_mkdir=false 342 | case $umask in 343 | *[123567][0-7][0-7]) 344 | # POSIX mkdir -p sets u+wx bits regardless of umask, which 345 | # is incompatible with FreeBSD 'install' when (umask & 300) != 0. 346 | ;; 347 | *) 348 | tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ 349 | trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0 350 | 351 | if (umask $mkdir_umask && 352 | exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1 353 | then 354 | if test -z "$dir_arg" || { 355 | # Check for POSIX incompatibilities with -m. 356 | # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or 357 | # other-writable bit of parent directory when it shouldn't. 358 | # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. 359 | ls_ld_tmpdir=`ls -ld "$tmpdir"` 360 | case $ls_ld_tmpdir in 361 | d????-?r-*) different_mode=700;; 362 | d????-?--*) different_mode=755;; 363 | *) false;; 364 | esac && 365 | $mkdirprog -m$different_mode -p -- "$tmpdir" && { 366 | ls_ld_tmpdir_1=`ls -ld "$tmpdir"` 367 | test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" 368 | } 369 | } 370 | then posix_mkdir=: 371 | fi 372 | rmdir "$tmpdir/d" "$tmpdir" 373 | else 374 | # Remove any dirs left behind by ancient mkdir implementations. 375 | rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null 376 | fi 377 | trap '' 0;; 378 | esac;; 379 | esac 380 | 381 | if 382 | $posix_mkdir && ( 383 | umask $mkdir_umask && 384 | $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" 385 | ) 386 | then : 387 | else 388 | 389 | # The umask is ridiculous, or mkdir does not conform to POSIX, 390 | # or it failed possibly due to a race condition. Create the 391 | # directory the slow way, step by step, checking for races as we go. 392 | 393 | case $dstdir in 394 | /*) prefix='/';; 395 | [-=\(\)!]*) prefix='./';; 396 | *) prefix='';; 397 | esac 398 | 399 | eval "$initialize_posix_glob" 400 | 401 | oIFS=$IFS 402 | IFS=/ 403 | $posix_glob set -f 404 | set fnord $dstdir 405 | shift 406 | $posix_glob set +f 407 | IFS=$oIFS 408 | 409 | prefixes= 410 | 411 | for d 412 | do 413 | test X"$d" = X && continue 414 | 415 | prefix=$prefix$d 416 | if test -d "$prefix"; then 417 | prefixes= 418 | else 419 | if $posix_mkdir; then 420 | (umask=$mkdir_umask && 421 | $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break 422 | # Don't fail if two instances are running concurrently. 423 | test -d "$prefix" || exit 1 424 | else 425 | case $prefix in 426 | *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; 427 | *) qprefix=$prefix;; 428 | esac 429 | prefixes="$prefixes '$qprefix'" 430 | fi 431 | fi 432 | prefix=$prefix/ 433 | done 434 | 435 | if test -n "$prefixes"; then 436 | # Don't fail if two instances are running concurrently. 437 | (umask $mkdir_umask && 438 | eval "\$doit_exec \$mkdirprog $prefixes") || 439 | test -d "$dstdir" || exit 1 440 | obsolete_mkdir_used=true 441 | fi 442 | fi 443 | fi 444 | 445 | if test -n "$dir_arg"; then 446 | { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && 447 | { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && 448 | { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || 449 | test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 450 | else 451 | 452 | # Make a couple of temp file names in the proper directory. 453 | dsttmp=$dstdir/_inst.$$_ 454 | rmtmp=$dstdir/_rm.$$_ 455 | 456 | # Trap to clean up those temp files at exit. 457 | trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 458 | 459 | # Copy the file name to the temp name. 460 | (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && 461 | 462 | # and set any options; do chmod last to preserve setuid bits. 463 | # 464 | # If any of these fail, we abort the whole thing. If we want to 465 | # ignore errors from any of these, just make sure not to ignore 466 | # errors from the above "$doit $cpprog $src $dsttmp" command. 467 | # 468 | { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && 469 | { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && 470 | { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && 471 | { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && 472 | 473 | # If -C, don't bother to copy if it wouldn't change the file. 474 | if $copy_on_change && 475 | old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && 476 | new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && 477 | 478 | eval "$initialize_posix_glob" && 479 | $posix_glob set -f && 480 | set X $old && old=:$2:$4:$5:$6 && 481 | set X $new && new=:$2:$4:$5:$6 && 482 | $posix_glob set +f && 483 | 484 | test "$old" = "$new" && 485 | $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 486 | then 487 | rm -f "$dsttmp" 488 | else 489 | # Rename the file to the real destination. 490 | $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || 491 | 492 | # The rename failed, perhaps because mv can't rename something else 493 | # to itself, or perhaps because mv is so ancient that it does not 494 | # support -f. 495 | { 496 | # Now remove or move aside any old file at destination location. 497 | # We try this two ways since rm can't unlink itself on some 498 | # systems and the destination file might be busy for other 499 | # reasons. In this case, the final cleanup might fail but the new 500 | # file should still install successfully. 501 | { 502 | test ! -f "$dst" || 503 | $doit $rmcmd -f "$dst" 2>/dev/null || 504 | { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && 505 | { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } 506 | } || 507 | { echo "$0: cannot unlink or rename $dst" >&2 508 | (exit 1); exit 1 509 | } 510 | } && 511 | 512 | # Now rename the file to the real destination. 513 | $doit $mvcmd "$dsttmp" "$dst" 514 | } 515 | fi || exit 1 516 | 517 | trap '' 0 518 | fi 519 | done 520 | 521 | # Local variables: 522 | # eval: (add-hook 'write-file-hooks 'time-stamp) 523 | # time-stamp-start: "scriptversion=" 524 | # time-stamp-format: "%:y-%02m-%02d.%02H" 525 | # time-stamp-time-zone: "UTC" 526 | # time-stamp-end: "; # UTC" 527 | # End: 528 | --------------------------------------------------------------------------------