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
    评论
### 回答1: 2048是一款非常有趣的数字游戏,可以通过Python编程实现。 首先,我们需要安装pygame模块来实现游戏的图形化界面。可以使用以下命令来安装pygame: ``` pip install pygame ``` 然后,我们可以根据2048游戏规则,设计游戏的逻辑。我们需要一个4x4的方格来存储数字,并可以进行上下左右四个方向的移动。每次移动时,相邻的相同数字会合并成一个数字,并在空白的方格上生成一个新的数字。当方格被填满,无法继续移动时,游戏结束。 接下来是代码实现,以下是一个简单的2048游戏实现代码: ```python import pygame import random # 初始化游戏 pygame.init() # 游戏界面大小 screen_width = 400 screen_height = 400 # 方格大小 grid_size = 100 # 方格间隔 grid_gap = 10 # 游戏界面 screen = pygame.display.set_mode([screen_width, screen_height]) # 游戏标题 pygame.display.set_caption("2048") # 游戏颜色 background_color = (250, 248, 239) grid_color = (187, 173, 160) text_color = (119, 110, 101) # 字体 font = pygame.font.SysFont("Arial", 36) # 数字颜色 number_color = { 0: (204, 192, 179), 2: (238, 228, 218), 4: (237, 224, 200), 8: (242, 177, 121), 16: (245, 149, 99), 32: (246, 124, 95), 64: (246, 95, 59), 128: (237, 207, 114), 256: (237, 204, 97), 512: (237, 200, 80), 1024: (237, 197, 63), 2048: (237, 194, 46), } # 初始化方格 grid = [[0 for i in range(4)] for j in range(4)] # 随机生成一个数字 def generate_number(): x = random.randint(0, 3) y = random.randint(0, 3) while grid[x][y] != 0: x = random.randint(0, 3) y = random.randint(0, 3) grid[x][y] = 2 # 绘制方格 def draw_grid(): for i in range(4): for j in range(4): pygame.draw.rect(screen, grid_color, (i * (grid_size + grid_gap) + grid_gap, j * (grid_size + grid_gap) + grid_gap, grid_size, grid_size)) if grid[i][j] != 0: text = font.render(str(grid[i][j]), True, number_color[grid[i][j]]) text_rect = text.get_rect(center=(i * (grid_size + grid_gap) + grid_size / 2 + grid_gap, j * (grid_size + grid_gap) + grid_size / 2 + grid_gap)) screen.blit(text, text_rect) # 判断游戏是否结束 def is_game_over(): for i in range(4): for j in range(4): if grid[i][j] == 0: return False if i > 0 and grid[i][j] == grid[i - 1][j]: return False if i < 3 and grid[i][j] == grid[i + 1][j]: return False if j > 0 and grid[i][j] == grid[i][j - 1]: return False if j < 3 and grid[i][j] == grid[i][j + 1]: return False return True # 移动方格 def move(key): if key == pygame.K_UP: for j in range(4): for i in range(1, 4): if grid[i][j] != 0: k = i - 1 while k >= 0 and grid[k][j] == 0: k -= 1 if k >= 0 and grid[k][j] == grid[i][j]: grid[k][j] *= 2 grid[i][j] = 0 elif k < i - 1: grid[k + 1][j] = grid[i][j] grid[i][j] = 0 elif key == pygame.K_DOWN: for j in range(4): for i in range(2, -1, -1): if grid[i][j] != 0: k = i + 1 while k <= 3 and grid[k][j] == 0: k += 1 if k <= 3 and grid[k][j] == grid[i][j]: grid[k][j] *= 2 grid[i][j] = 0 elif k > i + 1: grid[k - 1][j] = grid[i][j] grid[i][j] = 0 elif key == pygame.K_LEFT: for i in range(4): for j in range(1, 4): if grid[i][j] != 0: k = j - 1 while k >= 0 and grid[i][k] == 0: k -= 1 if k >= 0 and grid[i][k] == grid[i][j]: grid[i][k] *= 2 grid[i][j] = 0 elif k < j - 1: grid[i][k + 1] = grid[i][j] grid[i][j] = 0 elif key == pygame.K_RIGHT: for i in range(4): for j in range(2, -1, -1): if grid[i][j] != 0: k = j + 1 while k <= 3 and grid[i][k] == 0: k += 1 if k <= 3 and grid[i][k] == grid[i][j]: grid[i][k] *= 2 grid[i][j] = 0 elif k > j + 1: grid[i][k - 1] = grid[i][j] grid[i][j] = 0 # 游戏主循环 generate_number() while True: # 处理事件 for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() exit() elif event.type == pygame.KEYDOWN: if event.key == pygame.K_UP or event.key == pygame.K_DOWN or event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT: move(event.key) if not is_game_over(): generate_number() else: print("Game Over!") # 绘制界面 screen.fill(background_color) draw_grid() pygame.display.flip() ``` 运行代码,即可开始游戏。通过上下左右方向键来移动方格,合并相同数字。当方格被填满,无法继续移动时,游戏结束。 ### 回答2: 2048小游戏是一款非常流行的益智游戏,玩家需要通过合并相同数字的方块,最终得到2048这个数字方块。 在Python中编写这个游戏,可以使用面向对象的思想来实现。首先,我们需要创建一个2048的游戏类,其中包含游戏界面的初始化、方块的生成与移动等方法。 游戏开始时,我们可以使用一个二维数组来表示游戏界面,每个位置的值代表方块上的数字。我们可以在游戏开始时随机生成两个数字方块,然后玩家可以通过键盘输入来移动方块。移动的过程中,如果两个相邻方块的数字相同,它们会合并为一个新方块。每次移动,都要随机生成一个新的数字方块。 在创建游戏类的同时,我们可以定义一个打印游戏界面的方法,用来显示当前方块的位置和数字。我们还可以编写一个判断游戏是否结束的方法,如果界面上没有可以移动的方块或者已经达到2048,则游戏结束。 总的来说,编写2048小游戏需要考虑游戏界面的初始化和刷新、方块的生成和移动、游戏结束的判断等方面。通过使用Python面向对象的编程思想,我们可以比较方便地实现这个小游戏。 ### 回答3: 2048是一款流行的数字益智游戏,它的目标是通过在一个4x4的方格上移动并合并相同数字的方块来达到数字2048。 首先,我们需要导入必要的库。在Python中,我们可以使用pygame库来创建游戏界面并处理用户的输入。 然后,我们需要定义一个方格的类,用于存储每个方格的数字和位置。该类应包括以下方法:初始化方格,绘制方格和移动方格。 接下来,我们需要定义一个游戏板的类,用于存储所有方格,并处理方格的移动和合并。该类应包括以下方法:初始化游戏板,生成新的方格,移动方格,合并方格和检查游戏是否结束等。 在游戏循环中,我们首先创建一个游戏板的实例,并生成初始方格。然后,我们使用pygame库创建游戏窗口,并进入游戏循环,等待用户的输入。 当用户按下方向键时,我们将调用游戏板的移动方格和合并方格的方法来处理方格的移动和合并。如果方格成功移动或合并,我们将生成一个新的方格。然后,我们将重新绘制游戏界面,显示更新后的方格。 游戏循环会一直进行,直到出现某个方块的数字达到2048,或者没有可以移动的方格时,游戏结束。在游戏结束时,我们将显示游戏结束的信息,并提供重新开始游戏或退出游戏的选项。 以上就是使用Python编写2048小游戏的大致流程。实际编写代码时,还需要注意处理边界条件、用户输入的验证以及游戏结束的判断等细节。希望这个回答对您有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值