Skip to content

Commit

Permalink
Add an interactive, manual control mode.
Browse files Browse the repository at this point in the history
This is useful for e.g. if you just want some advice and are playing on a
platform where automatic control isn't available (e.g. on a phone).
  • Loading branch information
nneonneo committed Oct 12, 2024
1 parent 5b89217 commit 7fa523a
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 35 deletions.
2 changes: 1 addition & 1 deletion 2048.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ static inline board_t execute_move_3(board_t board) {
}

/* Execute a move. */
static inline board_t execute_move(int move, board_t board) {
board_t execute_move(int move, board_t board) {
switch(move) {
case 0: // up
return execute_move_0(board);
Expand Down
1 change: 1 addition & 0 deletions 2048.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ extern "C" {
#endif

DLL_PUBLIC void init_tables();
DLL_PUBLIC board_t execute_move(int move, board_t board);

typedef int (*get_move_func_t)(board_t);
DLL_PUBLIC float score_toplevel_move(board_t board, int move);
Expand Down
42 changes: 8 additions & 34 deletions 2048.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,21 @@
''' Help the user achieve a high score in a real game of 2048 by using a move searcher. '''

from __future__ import print_function
import ctypes
import time
import os

from ailib import ailib, to_c_board, from_c_index

# Enable multithreading?
MULTITHREAD = True

for suffix in ['so', 'dll', 'dylib']:
dllfn = 'bin/2048.' + suffix
if not os.path.isfile(dllfn):
continue
ailib = ctypes.CDLL(dllfn)
break
else:
print("Couldn't find 2048 library bin/2048.{so,dll,dylib}! Make sure to build it first.")
exit()

ailib.init_tables()

ailib.find_best_move.argtypes = [ctypes.c_uint64]
ailib.score_toplevel_move.argtypes = [ctypes.c_uint64, ctypes.c_int]
ailib.score_toplevel_move.restype = ctypes.c_float

def to_c_board(m):
board = 0
i = 0
for row in m:
for c in row:
board |= int(c) << (4*i)
i += 1
return board

def print_board(m):
for row in m:
for c in row:
print('%8d' % c, end=' ')
print()

def _to_val(c):
if c == 0: return 0
return 2**c

def to_val(m):
return [[_to_val(c) for c in row] for row in m]
return [[from_c_index(c) for c in row] for row in m]

def _to_score(c):
if c <= 1:
Expand Down Expand Up @@ -110,7 +81,7 @@ def parse_args(argv):

parser = argparse.ArgumentParser(description="Use the AI to play 2048 via browser control")
parser.add_argument('-p', '--port', help="Port number to control on (default: 32000 for Firefox, 9222 for Chrome)", type=int)
parser.add_argument('-b', '--browser', help="Browser you're using. Only Firefox with remote debugging, Firefox with the Remote Control extension (deprecated), and Chrome with remote debugging, are supported right now.", default='firefox', choices=('firefox', 'firefox-rc', 'chrome'))
parser.add_argument('-b', '--browser', help="Browser you're using. Only Firefox with remote debugging, Firefox with the Remote Control extension (deprecated), and Chrome with remote debugging, are supported right now.", default='firefox', choices=('firefox', 'firefox-rc', 'chrome', 'manual'))
parser.add_argument('-k', '--ctrlmode', help="Control mode to use. If the browser control doesn't seem to work, try changing this.", default='hybrid', choices=('keyboard', 'fast', 'hybrid'))

return parser.parse_args(argv)
Expand All @@ -134,7 +105,10 @@ def main(argv):
args.port = 9222
ctrl = ChromeDebuggerControl(args.port)

if args.ctrlmode == 'keyboard':
if args.browser == 'manual':
from manualctrl import ManualControl
gamectrl = ManualControl()
elif args.ctrlmode == 'keyboard':
from gamectrl import Keyboard2048Control
gamectrl = Keyboard2048Control(ctrl)
elif args.ctrlmode == 'fast':
Expand Down
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,57 @@ Open the game in a new tab, then run `2048.py -b firefox` and watch the game! Th
Enable Chrome remote debugging by quitting it and then restarting it with the `remote-debugging-port` command-line switch (e.g. `google-chrome --remote-debugging-port=9222`).

Open the game in a new tab, then run `2048.py -b chrome` and watch the game! The `-p` option can be used to set the port to connect to.

## Using the AI interactively

You can also use `2048.py` interactively using `2048.py -b manual`. In this mode, you'll be asked to input the board, after which the AI will give its suggested move. This might be useful for getting hints while playing the game on a platform without autoplay (e.g. on a phone), or for getting the AI's analysis of a given situation.

After each recommendation, the AI will assume you make that recommended move, and then prompt you to make any necessary adjustments to the board (usually, the location and value of the newly spawned tile) before giving its next suggestion.

Sample run:

```
Enter board one row at a time, with entries separated by spaces
Row 1: 16 128 256 1024
Row 2: 16 8 2 0
Row 3: 8 2 0 0
Row 4: 0 4 0 0
Current board:
16 128 256 1024
16 8 2 0
8 2 0 0
0 4 0 0
Enter updates in the form r,c,n (1-indexed row/column), separated by spaces:
16 128 256 1024
16 8 2 0
8 2 0 0
0 4 0 0
005.030340: Score 0, Move 1: up
EXECUTE MOVE: up
Current board:
32 128 256 1024
8 8 2 0
0 2 0 0
0 4 0 0
Enter updates in the form r,c,n (1-indexed row/column), separated by spaces: 3,1,4
32 128 256 1024
8 8 2 0
4 2 0 0
0 4 0 0
035.648508: Score 0, Move 2: left
EXECUTE MOVE: left
Current board:
32 128 256 1024
16 2 0 0
4 2 0 0
4 0 0 0
Enter updates in the form r,c,n (1-indexed row/column), separated by spaces: 4,3,2
32 128 256 1024
16 2 0 0
4 2 0 0
4 0 2 0
058.927319: Score 0, Move 3: left
EXECUTE MOVE: left
```

This tells the bot that after the first move, a 4 spawned in the 3rd row, 1st column, and after the second move, a 2 spawned in the 4th row, 3rd column.
47 changes: 47 additions & 0 deletions ailib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import ctypes
import os

for suffix in ['so', 'dll', 'dylib']:
dllfn = 'bin/2048.' + suffix
if not os.path.isfile(dllfn):
continue
ailib = ctypes.CDLL(dllfn)
break
else:
print("Couldn't find 2048 library bin/2048.{so,dll,dylib}! Make sure to build it first.")
exit()

ailib.init_tables()

ailib.find_best_move.argtypes = [ctypes.c_uint64]
ailib.score_toplevel_move.argtypes = [ctypes.c_uint64, ctypes.c_int]
ailib.score_toplevel_move.restype = ctypes.c_float
ailib.execute_move.argtypes = [ctypes.c_int, ctypes.c_uint64]
ailib.execute_move.restype = ctypes.c_uint64

def to_c_board(m):
board = 0
i = 0
for row in m:
for c in row:
board |= int(c) << (4*i)
i += 1
return board

def from_c_board(n):
board = []
i = 0
for ri in range(4):
row = []
for ci in range(4):
row.append((n >> (4 * i)) & 0xf)
i += 1
board.append(row)
return board

def to_c_index(n):
return [0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768].index(n)

def from_c_index(c):
if c == 0: return 0
return 2**c
44 changes: 44 additions & 0 deletions manualctrl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from ailib import ailib, to_c_board, from_c_board, to_c_index, from_c_index
from gamectrl import Generic2048Control

def print_board(m):
for row in m:
for c in row:
print('%8d' % from_c_index(c), end=' ')
print()

class ManualControl(Generic2048Control):
def __init__(self):
print("Enter board one row at a time, with entries separated by spaces")
board = []
for ri in range(4):
board.append([to_c_index(int(c)) for c in input("Row %d: " % (ri + 1)).split()])
self.cur_board = board

def get_status(self):
return "running"

def restart_game(self):
print("Game over - time to restart!")

def continue_game(self):
pass

def get_score(self):
# don't care
return 0

def get_board(self):
print("Current board:")
print_board(self.cur_board)

updates = input("Enter updates in the form r,c,n (1-indexed row/column), separated by spaces: ")
for item in updates.split():
r, c, n = map(int, item.split(","))
self.cur_board[r-1][c-1] = to_c_index(n)

return self.cur_board

def execute_move(self, move):
print("EXECUTE MOVE:", ["up", "down", "left", "right"][move])
self.cur_board = from_c_board(ailib.execute_move(move, to_c_board(self.cur_board)))

0 comments on commit 7fa523a

Please sign in to comment.