import random
import curses
# 一开始生成两个方块
STARTUP_TILES = 2
# 随机生成的方块中出现4的概率
FOUR_POSSIBILITY = 0.1
# 游戏板
BOARD = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
# 游戏分数
SCORE = 0
# 是否有2048
HAS_2048 = False
# 是否已经不能动了
IS_STUCK = False
# 是否已经问过是否需要继续游戏了
ASKED_FOR_CONTINUE = False
# 方向
LEFT, RIGHT, UP, DOWN = 0, 1, 2, 3
# 用来代表是否可以结束游戏的常量
CAN_END = True
VECTOR = [[-1, 0], [1, 0], [0, -1], [0, 1]]
SUCCESS = True
FAILED = False
# curses相关
SCREEN = None
def clip(num, lowerbound, upperbound):
if num < lowerbound:
return lowerbound
elif num > upperbound:
return upperbound
else:
return num
def print_score():
global SCREEN
SCREEN.addstr(9, 0, ''.join(['本场游戏结束,得分:', str(SCORE), '。']))
def print_prompt(prompt):
global SCREEN
SCREEN.addstr(10, 0, prompt)
def get_user_input(prompt, requested_input):
global SCREEN
error_prompt_str = ''.join(
['请输入', ','.join([str(x) for x in requested_input]), '的其中之一。'])
print_prompt(prompt)
user_input = SCREEN.getkey()
while user_input not in requested_input:
print_prompt(error_prompt_str)
user_input = SCREEN.getkey()
return user_input
def get_random_tile_number():
return 4 if random.random() <= FOUR_POSSIBILITY else 2
def get_empty_pos():
result = []
for y, row in enumerate(BOARD):
for x, _ in enumerate(row):
if BOARD[y][x] == 0:
result.Append((x, y))
return result
def get_random_empty_pos():
# 因为get_empty_pos返回的是(x, y),对应横坐标(也就是列数)和横坐标(行数)
try:
col, row = random.choice(get_empty_pos())
return row, col
except IndexError:
return None
def gen_tile():
pos = get_random_empty_pos()
if pos is None:
return FAILED
row, col = pos
number = get_random_tile_number()
BOARD[row][col] = number
return SUCCESS
# 开始新游戏
def new_game():
global BOARD, SCORE, HAS_2048, IS_STUCK, ASKED_FOR_CONTINUE
# 将板和分数清空
BOARD = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
SCORE = 0
HAS_2048 = False
IS_STUCK = False
ASKED_FOR_CONTINUE = False
# 随机生成起始方块
for _ in range(STARTUP_TILES):
gen_tile()
while True:
print_board()
check_board()
if HAS_2048 and not ASKED_FOR_CONTINUE:
user_choice = get_user_input(
'你合并出了2048!还要继续吗?(输入Y继续,输入Q结束这盘游戏)[Y/Q]:',
['Y', 'Q', 'y', 'q']
)
if user_choice in 'yY':
print_prompt('好的,继续游戏……')
ASKED_FOR_CONTINUE = True
elif user_choice in 'qQ':
print_prompt('好的,结束游戏……')
break
elif IS_STUCK:
break
# 取用户输入
direction = get_user_input(
'请按方向键移动方块。(按Q放弃本盘游戏)',
['KEY_UP', 'KEY_DOWN', 'KEY_LEFT', 'KEY_RIGHT', 'Q', 'q']
)
if direction in 'qQ':
break
elif direction == 'KEY_LEFT':
direction = LEFT
elif direction == 'KEY_RIGHT':
direction = RIGHT
elif direction == 'KEY_UP':
direction = UP
elif direction == 'KEY_DOWN':
direction = DOWN
moved_result = move_tile(direction)
if moved_result:
gen_tile()
print_score()
# 这里是这样:整块板分为(1)顶部的边框和(2)数字和数字下面的边框。
# 横向同理,分为(1)左边的边框和(2)数字和数字右边的边框。
def print_board():
# 顶部边框
SCREEN.addstr(0, 0, '+----+----+----+----+')
for y, row in enumerate(BOARD):
# 左边边框
SCREEN.addstr(y * 2 + 1, 0, '|')
# the number
for x, num in enumerate(row):
# 我们用0表示当前位置没有方块
numstr = str(num) if num != 0 else ''
SCREEN.addstr(y * 2 + 1, x * 5 + 1, numstr +
(' ' * (4 - len(numstr))) + '|')
# 数字下面的边框
SCREEN.addstr(y * 2 + 2, 0, '+----+----+----+----+')
def move_tile(direction):
global SCORE
def get_line(offset: int, direction: int):
'''
取direction上offset一行/列的所有方块。例如,当需要将第2行左移时,用
get_line(1, LEFT)获得这一行。
'''
global BOARD
if direction == LEFT:
return BOARD[offset]
elif direction == RIGHT:
return list(reversed(BOARD[offset]))
elif direction == UP:
return [BOARD[y][offset] for y in range(4)]
elif direction == DOWN:
return [BOARD[y][offset] for y in range(3, -1, -1)]
def put_line(line: list, offset: int, direction: int):
'''
将一条方块按照direction所指的方向放进游戏板中。
'''
global BOARD
if direction == LEFT:
BOARD[offset] = line
elif direction == RIGHT:
BOARD[offset] = list(reversed(line))
elif direction == UP:
for y in range(4):
BOARD[y][offset] = line[y]
elif direction == DOWN:
for y in range(4):
BOARD[y][offset] = line[3 - y]
def move(line: list):
'''
移动一条方块。
'''
new_line = []
gained_score = 0
i = 0
while i < 4:
if line[i] == 0:
i += 1
else:
old_tile = line[i]
i += 1
while i < 4 and line[i] == 0:
i += 1
if i >= 4 or line[i] != old_tile:
new_line.append(old_tile)
else:
gained_score += line[i] + old_tile
new_line.append(line[i] + old_tile)
i += 1
while len(new_line) < 4:
new_line.append(0)
# 这里有三种情况:
# 1. 移动不了。
# 2. 移动了,但是没有得分。
# 3. 移动了,也得分了。
# 在这里,出现第一种情况时返回None,第二/三种情况返回移动好的方块和
# 本次移动获得的分。
if new_line == line:
return None
else:
return new_line, gained_score
board_moved = False
for offset in range(4):
line = get_line(offset, direction)
moved_line = move(line)
if moved_line is not None:
board_moved = True
line, gained_score = moved_line
put_line(line, offset, direction)
SCORE += gained_score
put_line(line, offset, direction)
# 在这里,对于整个游戏板,有两种情况:
# 1. 有至少一行/一列移动了。
# 2. 没有。全部都没有移动。
# 在第二种下是不应该生成新的方块的。在这里返回游戏板有没有移动:
return board_moved
def get_neighbour(x, y, width, height):
global VECTOR
result = []
for vec in VECTOR:
new_x = x + vec[0]
new_y = y + vec[1]
if 0 <= new_x < width and 0 <= new_y < height:
result.append((new_x, new_y))
return result
def check_board():
global BOARD, HAS_2048, IS_STUCK
for y, row in enumerate(BOARD):
for x, num in enumerate(row):
# 如果有2048,那么就记住有2048。
if num == 2048:
HAS_2048 = True
return
# 如果有空位,那么就可以继续移动。
elif num == 0:
IS_STUCK = False
return
else:
for new_x, new_y in get_neighbour(x, y, 4, 4):
if BOARD[new_y][new_x] == num:
IS_STUCK = False
return
IS_STUCK = True
return
def main(stdscr):
global SCREEN
SCREEN = stdscr
while True:
SCREEN.clear()
new_game()
user_choice = get_user_input(
'是否开始下一盘?(输入Y开始下一盘,输入Q退出)[Y/Q]', ['Y', 'Q', 'y', 'q'])
if user_choice in 'qQ':
print_prompt('正在退出……')
break
elif user_choice in 'yY':
print_prompt('开始下一盘……')
continue
# don't start the thing when loading in interactive mode.
if __name__ == '__main__':
curses.wrapper(main)