截图:【ai功能暂时搁浅】
# -*- coding:utf-8 -*-
import random
from tkinter import Tk
from tkinter import Canvas
from tkinter import Button
import numpy as np
import math
import time
class Game(object):
def __init__(self):
# 创建数组,初始全为0
self.NumMap = np.zeros((4, 4), dtype=int)
self.is_game_over = False
self.vectors = [[0, -1], # up
[1, 0], # right
[0, 1], # down
[-1, 0]] # left
self.marked = np.zeros((4, 4), dtype=bool)
self.playerTurn = True # 玩家操作
self.random_add_item()
self.random_add_item()
# UI
self.root = Tk()
self.root.title('2048')
self.root.geometry("360x360+300+0")
self.canvas = Canvas(self.root, width=360, height=300, bg="white")
self.canvas.focus_set() # 让画布获得焦点
self.canvas.pack()
self.draw_map()
self.canvas.bind(sequence='<Key>', func=self.key_s)
self.btn_restart = Button(self.root, text="重新开始", command=self.restart)
self.btn_restart.pack()
self.btn_ai = Button(self.root, text="ai", command=self.ai)
self.btn_ai.pack()
self.root.mainloop()
def restart(self):
# 创建数组,初始全为0
self.NumMap = np.zeros((4, 4), dtype=int)
self.is_game_over = False
self.random_add_item()
self.random_add_item()
self.draw_map()
def ai(self):
print('ren')
def draw_map(self):
self.canvas.delete('all')
# 画宫格
for i in range(1, 6):
self.canvas.create_line(60, 60 * i, 300, 60 * i) # 横线
self.canvas.create_line(60 * i, 60, 60 * i, 300) # 竖线
# 画数字
for i in range(len(self.NumMap)):
for j in range(len(self.NumMap[i])):
text_temp = self.NumMap[i][j]
if text_temp != 0:
self.canvas.create_text(60 * j + 90, 60 * i + 90, text=str(text_temp), font={'50'})
# 随机生成数字(2或4)添加到NumMap
def random_add_item(self):
# 找出所有的空位
empty_cells = [(i, j) for i in range(4) for j in range(4) if self.NumMap[i][j] == 0]
if len(empty_cells) == 0:
self.is_game_over = True
else:
(i, j) = random.choice(empty_cells)
new_item = 4 if random.randrange(100) >= 90 else 2
self.NumMap[i][j] = new_item
# 将列作为元素
def transpose(self, nummap):
return [list(row) for row in zip(*nummap)]
# 将行反转,并将行作为元素
def invert(self, nummap):
return [row[::-1] for row in nummap]
@staticmethod
def move(row):
# 将非 0 往前凑
def tighten(row1):
new_row = [i for i in row1 if i != 0]
new_row += [0 for i in range(len(row1) - len(new_row))] #
return new_row
# 合并
def merge(row2):
pair = False
new_row = []
for i in range(len(row2)):
if pair:
new_row.append(2 * row2[i])
# self.score += 2 * row[i]
pair = False
else:
if i + 1 < len(row2) and row2[i] == row2[i + 1]:
pair = True
new_row.append(0)
else:
new_row.append(row2[i])
assert len(new_row) == len(row2), '行的长度得跟原来一样'
return new_row
return tighten(merge(tighten(row)))
def move_left(self, num_map):
return [self.move(row) for row in num_map]
def move_right(self, num_map):
return self.invert(self.move_left(self.invert(num_map)))
def move_up(self, num_map):
return self.transpose(self.move_left(self.transpose(num_map)))
def move_down(self, num_map):
return self.transpose(self.move_right(self.transpose(num_map)))
def key_s(self, ke):
if ke.keysym == 'W' or ke.keysym == 'w' or ke.keysym == 'Up':
self.NumMap = self.move_up(self.NumMap)
elif ke.keysym == 'S' or ke.keysym == 's' or ke.keysym == 'Down':
self.NumMap = self.move_down(self.NumMap)
elif ke.keysym == 'A' or ke.keysym == 'a' or ke.keysym == 'Left':
self.NumMap = self.move_left(self.NumMap)
elif ke.keysym == 'D' or ke.keysym == 'd' or ke.keysym == 'Right':
self.NumMap = self.move_right(self.NumMap)
if ke.keysym in 'WSADwsad' or ke.keysym in ['Up', 'Down', 'Left', 'Right']:
self.random_add_item()
self.is_win()
self.is_game_over_f()
self.draw_map()
def is_win(self):
if max(max(row) for row in self.NumMap) >= 2048:
print('you win')
def is_game_over_f(self):
temp = self.NumMap.copy()
a = self.move_right(temp)
b = self.move_left(temp)
c = self.move_down(temp)
d = self.move_up(temp)
if (a == self.NumMap) and (b == self.NumMap) and (c == self.NumMap) and (d == self.NumMap):
print('gameOver')
# 判断某个方向是否可以移动 1-up 2-down 3-left 4-right
def can_move_direction(self, direction):
temp_num = self.NumMap.copy()
if direction == 1:
if self.move_up(temp_num) != temp_num:
return True
elif direction == 2:
if self.move_down(temp_num) != temp_num:
return True
elif direction == 3:
if self.move_left(temp_num) != temp_num:
return True
elif direction == 4:
if self.move_right(temp_num) != temp_num:
return True
return False
def move_direction(self, direction, temp_num):
if direction == 1:
return self.move_up(temp_num)
elif direction == 2:
return self.move_down(temp_num)
elif direction == 3:
return
elif direction == 4:
return self.move_right(temp_num)
def isInBounds(self, x, y):
return 0 <= x < 4 and 0 <= y < 4
def isCellAvailable(self, x, y):
return self.NumMap[x][y] == 0
# 平滑性
def smoothess(self, num_map):
smoothness = 0
for x in range(4):
for y in range(4):
if num_map[x][y] != 0:
value = math.log(num_map[x][y]) / math.log(2)
# 水平方向和垂直方向的平滑性评估值
for direction in range(1, 3):
vector = self.vectors[direction]
cnt_x, cnt_y = x, y
while True:
cnt_x += vector[0]
cnt_y += vector[1]
if not (self.isInBounds(cnt_x, cnt_y) and self.isCellAvailable(cnt_x, cnt_y)):
break
if self.isInBounds(cnt_x, cnt_y):
if num_map[cnt_x][cnt_y] != 0:
target_value = math.log(num_map[cnt_y][cnt_y]) / math.log(2)
smoothness = math.fabs(value - target_value)
return smoothness
# 空格数
def get_empty_cell_count(self, num_map):
empty_cells = [(i, j) for i in range(4) for j in range(4) if num_map[i][j] == 0]
return len(empty_cells)
# 获得最大数
def get_max(self, num_map):
max_num = max(max(row) for row in num_map)
return math.log(max_num) / math.log(2)
# 单调性
def monotonicity(self, num_map):
totals = [0, 0, 0, 0] # 四个方向格局单调的评估值
# 左右方向
for x in range(4):
current = 0
next_index = current + 1
while next_index < 4:
while next_index < 4 and num_map[x][next_index] == 0:
next_index += 1
if next_index >= 4: next_index -= 1
current_value = math.log(num_map[x][current]) / math.log(2) if num_map[x][current] != 0 else 0
next_value = math.log(num_map[x][next_index]) / math.log(2) if num_map[x][
next_index] != 0 else 0
if current_value > next_value:
totals[0] += next_value - current_value
elif current_value < next_value:
totals[1] += current_value - next_value
current = next_index
next_index += 1
# 上 / 下方向
for y in range(4):
current = 0
next_index = current + 1
while next_index < 4:
while next_index < 4 and num_map[next_index][y] == 0:
next_index += 1
if next_index >= 4: next_index -= 1
current_value = math.log(num_map[current][y]) / math.log(2) if (num_map[current][y] != 0) else 0
next_value = math.log(num_map[next_index][y]) / math.log(2) if num_map[next_index][
y] != 0 else 0
if current_value > next_value:
totals[2] += next_value - current_value
elif next_value > current_value:
totals[3] += current_value - next_value
current = next_index
next_index += 1
return max(totals[0], totals[1]) + max(totals[2], totals[3])
def evaluate(self, num_map):
smoothWeight = 0.1 # 平滑性权重系数
monoWeight = 1.3 # 单调性权重系数
emptyWeight = 2.7 # 空格数权重系数
maxWeight = 1.0 # 最大数权重系数
return self.smoothess(num_map) * smoothWeight + self.monotonicity(num_map) * monoWeight + math.log(
self.get_empty_cell_count(num_map)) * emptyWeight + self.get_max(num_map) * maxWeight
def islands(self, num_map):
islands = 0
for x in range(4):
for y in range(4):
if num_map[x][y] != 0:
self.marked[x][y] = False
for x in range(4):
for y in range(4):
if num_map[x][y] != 0 and (not self.marked[x][y]):
islands += 1;
self.mark(x, y, self.NumMap[x][y]);
return islands;
def mark(self, x, y, value):
if (0 <= x <= 3 and 0 <= y <= 3 and (self.NumMap[x][y] != 0)
and (self.NumMap[x][y] == value) and (not self.marked[x][y])):
self.marked[x][y] = True
for direction in range(4):
vector = self.vectors[direction];
self.mark(x + vector[0], y + vector[1], value);
# 执行搜索操作,返回最好的移动方向
def getBestMove(self):
return self.iterativeDeep(100);
# 基于alpha - beta的Minimax搜索,进行迭代深搜,搜索时间设定为0.1秒,即决策的思考时间为0.1秒
def iterativeDeep(self, minSearchTime):
start = int(time.time() * 1000)
depth = 0
best = -1
while True:
newBest = self.search(depth, -10000, 10000, 0, 0);
if newBest.move == -1:
break
else:
best = newBest.move;
depth += 1
if int(time.time() * 1000) - start < minSearchTime:
break
return best
def search(self, depth, alpha, beta, max_min_map):
best_move = -1
directions = np.array([1, 2, 3, 4], dtype=int)
if self.playerTurn: # Max节点,玩家进行上下左右移动操作,alpha选最大
best_score = alpha
for direction in directions:
if self.can_move_direction(direction): # 某个方向可以进行移动,即该方向可作为子节点拓展
temp_num_map = self.move_direction(direction, max_min_map)
self.playerTurn = False
if depth == 0: # 不需要继续搜索
print('end-------------')
searchresult = SearchResult(direction, self.evaluate(temp_num_map))
else: # 继续往下搜索
searchresult = self.search(depth - 1, best_score, beta, temp_num_map)
if searchresult.score > best_score:
best_score = searchresult.score
best_move = direction
# 如果当前bestScore也即alpha > beta时,表明这个节点下不会再有更好解,于是剪枝
if best_score > beta:
# 剪枝
print('jianzhi')
else: # Min层
best_score = beta
# 给所有的空格填进{2或4}
empty_cells = [(i, j) for i in range(4) for j in range(4) if max_min_map[i][j] == 0]
score_2 = np.array()
score_4 = np.array()
if len(empty_cells) != 0:
print("有空格")
fill_in_empty_cell = np.array([2, 4])
for value in fill_in_empty_cell:
for (i, j) in empty_cells:
max_min_map[i][j] = value
if value == 2:
score_2.append(self.islands(max_min_map) - self.smoothess(max_min_map))
if value == 4:
score_4.append(self.islands(max_min_map) - self.smoothess(max_min_map))
max_min_map[i][j] = 0
candidates = [] # 记录每一个最坏的操作 所填空格的索引,以及所填的值
max_score = max(max(score_2), max(score_4))
for fitness in score_2:
if fitness == max_score:
index = np.where(score_2 == max(score_2))
for i in index:
candidates.append((empty_cells[i], 2))
for fitness in score_4:
if fitness == max_score:
index = np.where(score_4 == max(score_4))
for i in index:
candidates.append((empty_cells[i], 4))
class SearchResult(object):
def __init__(self, move, score):
self.move = move
self.score = score
# self.positions = positions
# self.cutoffs = cutoffs
if __name__ == '__main__':
Game()