2048小游戏【python】

 截图:【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()

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值