目次
config.py
#! usr/bin/env/ Python3
# config.py
# coded by Saito-Saito-Saito
# last edited: 25 September 2020
# explained on https://Saito-Saito-Saito.github.io/2048
from logging import getLogger, Formatter, StreamHandler, FileHandler, DEBUG, INFO, WARNING, ERROR, CRITICAL
### LOG SET UP
DEFAULT_LOG_FILE_NAME = 'log.txt'
DEFAULT_LOG_FORMAT = Formatter('%(asctime)s - %(levelname)s - logger:%(name)s - %(filename)s - L%(lineno)d - %(funcName)s - %(message)s')
# file handler
DEFAULT_FHANDLER = FileHandler(DEFAULT_LOG_FILE_NAME, mode='w')
DEFAULT_FHANDLER.setFormatter(DEFAULT_LOG_FORMAT)
DEFAULT_FHANDLER.setLevel(DEBUG)
# stream handler
DEFAULT_SHANDLER = StreamHandler()
DEFAULT_SHANDLER.setFormatter(DEFAULT_LOG_FORMAT)
DEFAULT_SHANDLER.setLevel(WARNING)
# logger set up function
def logger_setup(logger_name='default', level=DEBUG, *, fhandler=None, fhandler_level=DEBUG, filename=DEFAULT_LOG_FILE_NAME, filemode='w', fileformat=DEFAULT_LOG_FORMAT, shandler=None, shandler_level=WARNING, streamformat=DEFAULT_LOG_FORMAT):
logger = getLogger(logger_name)
logger.setLevel(level)
# file handler
fhandler = fhandler or FileHandler(filename, mode=filemode)
fhandler.setLevel(fhandler_level)
fhandler.setFormatter(fileformat)
logger.addHandler(fhandler)
# stream handler
shandler = shandler or StreamHandler()
shandler.setLevel(shandler_level)
shandler.setFormatter(streamformat)
logger.addHandler(shandler)
return logger
### INDEXES
ROW = 0
COL = 1
### SQUARES STATUS
EMPTY = 0
### DIRECTIONS
UP = [-1, 0]
DOWN = [+1, 0]
LEFT = [0, -1]
RIGHT = [0, +1]
### RETURN VALUES
SUCCEEDED = True
FAILED = False
### DEFAULT VALUES
DEFAULT_PROB4 = 1 / 8
DEFAULT_SIZE = 4 # board size = 4 * 4
DEFAULT_GOAL = 2048
MAX_GOAL = 65536
board.py
#! usr/bin/env/ Python3
# board.py
# coded by Saito-Saito-Saito
# last edited: 25 September 2020
# explained on https://Saito-Saito-Saito.github.io/2048
from config import *
import copy
import random
local_logger = logger_setup(__name__, level=INFO)
class Board:
def insert(self, *, logger=None):
logger = logger or self.logger
# searching for empty square
emptied = []
for row in range(self.size):
for col in range(self.size):
if self.board[row][col] == EMPTY:
emptied.append([row, col])
# when there is no empty square, you cannot insert a number
if emptied == []:
logger.info('there is no empty square')
return FAILED
# selecting an empty square randomly
target = random.choice(emptied)
# inserting 4 if random No < prob4
if random.random() < self.prob4:
self.board[target[ROW]][target[COL]] = 4
else:
self.board[target[ROW]][target[COL]] = 2
logger.info('target {} <- No. {}'.format(target, self.board[target[ROW]][target[COL]]))
return SUCCEEDED
def __init__(self, *, size=DEFAULT_SIZE, board=[], goal=DEFAULT_GOAL, logger=local_logger, prob4=DEFAULT_PROB4):
# copying the parameters
self.size = size
self.prob4 = prob4
self.logger = logger
if goal > MAX_GOAL:
self.goal = MAX_GOAL
else:
self.goal = goal
# copying the board
if len(board) == self.size:
self.board = copy.deepcopy(board)
else:
self.board = [[EMPTY for col in range(self.size)] for row in range(self.size)]
self.insert(logger=logger)
self.insert(logger=logger)
logger.debug('in {}, board is {}'.format(self, self.board))
# checking whether index is in the board
def isInBoard(self, index:int):
if 0 <= index < self.size:
return True
else:
return False
def print(self):
print('\n-', end='')
for row in range(self.size):
print('--------', end='')
print('') # to a new line
for row in range(self.size):
print('|', end='')
for col in range(self.size):
if self.board[row][col] == EMPTY:
print(' |', end='')
else:
print(' {} '.format(self.board[row][col]).center(7) + '|', end='')
print('\n-', end='') # to new line
for col in range(self.size):
print('--------', end='')
print('') # to a new line
print('') # to a new line
return SUCCEEDED
def move(self, direction: list, *, logger=None):
logger = logger or self.logger
ever_moved = False
start = [0, self.size - 1] # UP&LEFT: 0, DOWN&RIGHT: size - 1
step = [1, -1] # UP&LEFT: 1, DOWN&RIGHT: -1
stop = [self.size, -1] # UP&LEFT: size, DOWN&RIGHT: -1
switch = (direction in [DOWN, RIGHT]) # UP&LEFT: False, DOWN&RIGHT: True
# cf. move.py and https://Saito-Saito-Saito.github.io/2048/stage4
root = [0, 0]
for root[COL] in range(start[switch], stop[switch], step[switch]):
for root[ROW] in range(start[switch], stop[switch], step[switch]):
focused = [root[ROW] - direction[ROW], root[COL] - direction[COL]]
while self.isInBoard(focused[ROW]) and self.isInBoard(focused[COL]):
# in case root == EMPTY
if self.board[root[ROW]][root[COL]] == EMPTY != self.board[focused[ROW]][focused[COL]]:
logger.info('{}, {} <- {}'.format(root[ROW], root[COL], self.board[focused[ROW]][focused[COL]]))
self.board[root[ROW]][root[COL]] = self.board[focused[ROW]][focused[COL]]
self.board[focused[ROW]][focused[COL]] = EMPTY
ever_moved = True
# in case root == focused != EMPTY
elif self.board[root[ROW]][root[COL]] == self.board[focused[ROW]][focused[COL]] != EMPTY:
logger.info('{}, {} <- {}'.format(root[ROW], root[COL], self.board[focused[ROW]][focused[COL]]))
self.board[root[ROW]][root[COL]] *= 2
self.board[focused[ROW]][focused[COL]] = EMPTY
ever_moved = True
break
# in case EMPTY != root != focuseed != EMPTY
elif self.board[focused[ROW]][focused[COL]] != EMPTY:
break
# either is zero
focused[ROW] -= direction[ROW]
focused[COL] -= direction[COL]
if ever_moved == True:
return SUCCEEDED
else:
# if no number was moved, it is game over
logger.debug('ever_moved == False')
return FAILED
def isGoal(self, *, logger=None):
logger = logger or self.logger
for row in range(self.size):
for col in range(self.size):
if self.board[row][col] >= self.goal:
logger.info('{} is goaled'.format([row, col]))
return True
# there is no goaled square
return False
def isOver(self, *, logger=None):
logger = logger or self.logger
local_board = Board(size=self.size, board=self.board, goal=self.goal, logger=self.logger, prob4=self.prob4)
for direction in [UP, DOWN, LEFT, RIGHT]:
if local_board.move(direction):
logger.info('direction {} is available'.format(direction))
return False
# here, no move is available
return True
if __name__ == "__main__":
test_board = Board(logger=local_logger)
test_board.print()
# for count in range(16):
# test_board.move(random.choice([UP, DOWN, LEFT, RIGHT]))
# test_board.print()
test_board.move(random.choice([UP, DOWN, LEFT, RIGHT]))
test_board.insert()
test_board.print()
main.py
#! usr/bin/env/ Python3
# main.py
# coded by Saito-Saito-Saito
# last edited: 25 September 2020
# explained on https://Saito-Saito-Saito.github.io/2048
from config import *
import board
import random
### LOG SET UP
logger = logger_setup(__name__, level=DEBUG)
logger.info('start')
parameters = {'size': DEFAULT_SIZE, 'goal': DEFAULT_GOAL}
### SET UP
def settings(command: str): # when command is X, change the parameters
while command in ['x', 'X']:
setcommand = input('ENTER THE COMMAND (BOARD SIZE: S, GOAL: G, EXIT: E) ---')
while setcommand in ['s', 'S']:
size_command = input('NEW SIZE --- ')
if size_command.isdecimal() == True and int(size_command) >= 2:
parameters['size'] = int(size_command)
break
else:
print('INVALID INPUT')
continue
while setcommand in ['G', 'g']:
goal_command = input('NEW GOAL (8 ~ {}) ---'.format(MAX_GOAL))
if goal_command.isdecimal() == True:
if 8 <= int(goal_command) < MAX_GOAL:
parameters['goal'] = int(goal_command)
break
else:
print('INVALID VALUE')
continue
else:
print('INVALID INPUT')
continue
if setcommand in ['E', 'e']:
return
print('''SETTINGS
BOARD SIZE {} * {}
GOAL {} '''.format(parameters['size'],parameters['size'],parameters['goal']))
def startmenu():
command = input('''
2048
SETTINGS
BOARD SIZE {} * {}
GOAL {}
ENTER TO SART(X TO CHANGE THE SETTINGS) - - - '''.format(parameters['size'],parameters['size'],parameters['goal']))
settings(command)
if __name__ == "__main__":
startmenu()
main_board = board.Board(size=parameters['size'], goal=parameters['goal'])
main_board.print()
while True:
input_d = input('DIRECTION: UP-1, DOWN-2, LEFT-3, RIGHT-4 : ')
logger.debug('input_d = {}'.format(input_d))
if input_d in ['1', 'w', 'W']:
direction = UP
elif input_d in ['2', 's', 'S']:
direction = DOWN
elif input_d in ['3', 'a', 'A']:
direction = LEFT
elif input_d in ['4', 'd', 'D']:
direction = RIGHT
else:
print('INVALID INPUT')
continue
if main_board.move(direction) == FAILED:
print('INVALID DIRECTION')
continue
if main_board.insert() == FAILED or main_board.isOver():
main_board.print()
print('GAME OVER...')
break
if main_board.isGoal() == True:
main_board.print()
print('You WIN!')
break
main_board.print()
logger.info('end')
move.py
#! usr/bin/env/ Python3
# move.py
# coded by Saito-Saito-Saito
# last edited: 25 September 2020
# CAUTION: THIS CODE IS ONLY FOR MEMORANDOM OF move METHOD. YOU CAN RUN main.py WITHOUT THIS MODULE.
# explained on https://Saito-Saito-Saito.github.io/2048
import board
from config import *
class Move(board.Board):
def board_move(self, direction: list, *, logger=None):
logger = logger or self.logger
ever_moved = False
# up
if direction == UP:
logger.debug('direction is UP')
root = [0, 0]
for root[COL] in range(self.size):
for root[ROW] in range(self.size):
focused = [root[ROW] + 1, root[COL]]
while self.isInBoard(focused[ROW]):
# in case root == EMPTY
if self.board[root[ROW]][root[COL]] == EMPTY != self.board[focused[ROW]][focused[COL]]:
logger.info('{}, {} <- {}'.format(root[ROW], root[COL], self.board[focused[ROW]][focused[COL]]))
self.board[root[ROW]][root[COL]] = self.board[focused[ROW]][focused[COL]]
self.board[focused[ROW]][focused[COL]] = EMPTY
ever_moved = True
# in case board[root] == board[focused] (!= 0)
elif self.board[root[ROW]][root[COL]] == self.board[focused[ROW]][focused[COL]] != 0:
logger.info('{}, {} <- {}'.format(root[ROW], root[COL], self.board[focused[ROW]][focused[COL]]))
self.board[root[ROW]][root[COL]] *= 2
self.board[focused[ROW]][focused[COL]] = EMPTY
ever_moved = True
break
elif self.board[focused[ROW]][focused[COL]] != EMPTY:
break
focused[ROW] += 1
elif direction == DOWN:
logger.debug('direction is DOWN')
root = [0, 0]
for root[COL] in range(self.size):
for root[ROW] in range(self.size - 1, -1, -1):
focused = [root[ROW] - 1, root[COL]]
while self.isInBoard(focused[ROW]):
# in case root == EMPTY
if self.board[root[ROW]][root[COL]] == EMPTY != self.board[focused[ROW]][focused[COL]]:
logger.info('{}, {} <- {}'.format(root[ROW], root[COL], self.board[focused[ROW]][focused[COL]]))
self.board[root[ROW]][root[COL]] = self.board[focused[ROW]][focused[COL]]
self.board[focused[ROW]][focused[COL]] = EMPTY
ever_moved = True
# in case board[root] == board[focused] (!= 0)
elif self.board[root[ROW]][root[COL]] == self.board[focused[ROW]][focused[COL]] != 0:
logger.info('{}, {} <- {}'.format(root[ROW], root[COL], self.board[focused[ROW]][focused[COL]]))
self.board[root[ROW]][root[COL]] *= 2
self.board[focused[ROW]][focused[COL]] = EMPTY
ever_moved = True
break
elif self.board[focused[ROW]][focused[COL]] != EMPTY:
break
focused[ROW] -= 1
elif direction == LEFT:
logger.debug('direction is LEFT')
root = [0, 0]
for root[ROW] in range(self.size):
for root[COL] in range(self.size):
focused = [root[ROW], root[COL]+1]
while self.isInBoard(focused[COL]):
# in case root == EMPTY
if self.board[root[ROW]][root[COL]] == EMPTY != self.board[focused[ROW]][focused[COL]]:
logger.info('{}, {} <- {}'.format(root[ROW], root[COL], self.board[focused[ROW]][focused[COL]]))
self.board[root[ROW]][root[COL]] = self.board[focused[ROW]][focused[COL]]
self.board[focused[ROW]][focused[COL]] = EMPTY
ever_moved = True
# in case board[root] == board[focused] (!= 0)
elif self.board[root[ROW]][root[COL]] == self.board[focused[ROW]][focused[COL]] != 0:
logger.info('{}, {} <- {}'.format(root[ROW], root[COL], self.board[focused[ROW]][focused[COL]]))
self.board[root[ROW]][root[COL]] *= 2
self.board[focused[ROW]][focused[COL]] = EMPTY
ever_moved = True
break
elif self.board[focused[ROW]][focused[COL]] != EMPTY:
break
focused[COL] += 1
elif direction == RIGHT:
logger.debug('direction is RIGHT')
root = [0, 0]
for root[ROW] in range(self.size):
for root[COL] in range(self.size - 1, -1, -1):
focused = [root[ROW], root[COL] - 1]
while self.isInBoard(focused[COL]):
# in case root == EMPTY
if self.board[root[ROW]][root[COL]] == EMPTY != self.board[focused[ROW]][focused[COL]]:
logger.info('{}, {} <- {}'.format(root[ROW], root[COL], self.board[focused[ROW]][focused[COL]]))
self.board[root[ROW]][root[COL]] = self.board[focused[ROW]][focused[COL]]
self.board[focused[ROW]][focused[COL]] = EMPTY
ever_moved = True
# in case board[root]==board[focused] (!= 0)
elif self.board[root[ROW]][root[COL]] == self.board[focused[ROW]][focused[COL]] != 0:
logger.info('{}, {} <- {}'.format(root[ROW], root[COL], self.board[focused[ROW]][focused[COL]]))
self.board[root[ROW]][root[COL]] *= 2
self.board[focused[ROW]][focused[COL]] = EMPTY
ever_moved = True
break
elif self.board[focused[ROW]][focused[COL]] != EMPTY:
break
focused[COL] -= 1
else:
logger.error('INPUT DIRECTION is INVALID VALUE')
return FAILED
if ever_moved == True:
return SUCCEEDED
else:
# if no number was moved, it is game over
logger.debug('ever_moved == False')
return FAILED
if __name__ == "__main__":
test_board = Move()
test_board.print()
test_board.board_move(UP)
test_board.insert()
test_board.print()
test_board.board_move(DOWN)
test_board.insert()
test_board.print()
test_board.board_move(LEFT)
test_board.insert()
test_board.print()
test_board.board_move(RIGHT)
test_board.insert()
test_board.print()