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)