当前位置:   article > 正文

【完整源码】2048及其AI模式实现(底层逻辑及算法优化、GUI界面编程、Minimax算法实现AI)_2048ai

2048ai

目录

基于Python完成的三个任务

2048底层逻辑

GUI界面编程

AI模式的实现


基于Python完成的三个任务

  1. 实现2048底层逻辑

  2. 完成GUI设计并实现

  3. 让机器学会玩2048,并获得游戏胜利(最大方块达到2048)

2048底层逻辑

自顶向下拆分各个功能板块:

  1. 随机在空白位置生成两个方块;

  2. 刷新界面并展示矩阵;

  3. 接收玩家操作;

  4. 计算玩家操作后得到的新矩阵,并计算该步得分;

  5. 判断游戏结束。

各个功能间逻辑关系:

游戏开始->

1->2->5->3->4->

1->2->5->3->4->...

1->2->5->3->4->游戏结束->给出分数

算法优化:

考虑到玩家向上、向下、向左、向右的操作在逻辑上具有高度一致性,可以考虑只实现一个方向的功能,比如左。其他的方向可以利用矩阵旋转,转化为左方向。

比如,程序接收到用户向上操作的指令时,可以将矩阵逆时针旋转90°,转化为向左操作。

综上,详细介绍各个功能模块的实现细节:

  1. 展示矩阵:

    1.  def show(M):
    2.      print("\n" + "使用W A S D控制方向", end="")
    3.      print("\n" + " " * 25 + "Score: " + "{:}".format(score), end="")
    4.      for i in range(SIZE):
    5.          print("\n")
    6.          for j in range(SIZE):
    7.              print("{: >6}".format(M[i][j]), end="")
    8.      print("\n")

  2. 随机位置生成两个方块:

    1.  def add(M):
    2.      numOfZero = 0
    3.      cnt = 0
    4.      for r in range(SIZE):
    5.          for c in range(SIZE):
    6.              if M[r][c] == 0:
    7.                  numOfZero += 1
    8.      if numOfZero >= 2:
    9.          numOfZero = 2
    10.      while True:
    11.          p = random.randint(0, SIZE * SIZE - 1)
    12.          if M[p // SIZE][p % SIZE] == 0:
    13.              x = random.choice([2, 2, 2, 4])
    14.              M[p // SIZE][p % SIZE] = x
    15.              cnt += 1
    16.          if cnt == numOfZero:
    17.              break
    18.      return M

  3. 接收玩家操作并将该操作转化为向左:

    1.  def moveUp():
    2.      global map
    3.      map = rotate90(map)  # 先逆时针旋转90度,原本向上的操作变成了向左操作,这时就可以调用caculate函数。
    4.      if caculate(map)[1]:
    5.          map = add(map)
    6.      map = rotate90(map)
    7.      map = rotate90(map)
    8.      map = rotate90(map)  # 操作完成后再旋转3个90度,总共旋转360度,矩阵方向不变。
    9.  ​
    10.  ​
    11.  def moveRight():
    12.      global map
    13.      map = rotate90(map)
    14.      map = rotate90(map)
    15.      if caculate(map)[1]:
    16.          map = add(map)
    17.      map = rotate90(map)
    18.      map = rotate90(map)
    19.  ​
    20.  ​
    21.  def moveDown():
    22.      global map
    23.      map = rotate90(map)
    24.      map = rotate90(map)
    25.      map = rotate90(map)
    26.      if caculate(map)[1]:
    27.          map = add(map)
    28.      map = rotate90(map)
    29.  ​
    30.  ​
    31.  def moveLeft():
    32.      global map
    33.      if caculate(map)[1]:
    34.          map = add(map)

  4. 矩阵逆时针旋转90°:

    1.  def rotate90(M):
    2.      M = [[M[c][r] for c in range(SIZE)] for r in reversed(range(SIZE))]
    3.      return M

  5. 计算玩家操作后的矩阵状态(仅支持向左)并更新分数:

    1.  def caculate(M):
    2.      global score
    3.      changed = False
    4.      for a in M:
    5.          b = []
    6.          last = 0
    7.          for v in a:
    8.              if v != 0:
    9.                  if v == last:
    10.                      s = b.pop() * 2
    11.                      b.append(s)
    12.                      score += s
    13.                      last = 0
    14.                  else:
    15.                      b.append(v)
    16.                      last = v
    17.          b += [0] * (SIZE - len(b))  # 弥补本行剩下的元素
    18.          for i in range(SIZE):
    19.              if a[i] != b[i]:
    20.                  changed = True
    21.          a[:] = b
    22.      return M, changed

  6. 判断游戏结束:

    1.  def over(M):
    2.      for r in range(SIZE):
    3.          for c in range(SIZE):
    4.              if M[r][c] == 0:
    5.                  return False
    6.      for r in range(SIZE):
    7.          for c in range(SIZE - 1):
    8.              if M[r][c] == M[r][c + 1]:
    9.                  return False
    10.      for r in range(SIZE - 1):
    11.          for c in range(SIZE):
    12.              if M[r][c] == M[r + 1][c]:
    13.                  return False
    14.      return True

    初始化和主函数:

    1.  import random
    2.  ​
    3.  # 矩阵大小(游戏难度)
    4.  SIZE = 4
    5.  ​
    6.  # map存储矩阵中的数字
    7.  map = [[0 for i in range(SIZE)] for i in range(SIZE)]
    8.  ​
    9.  # 游戏分数
    10.  score = 0
    11.  ​
    12.  map = add(map)
    13.  show(map)
    14.  ​
    15.  while not over(map):
    16.      cmd = input()
    17.      if cmd == "w":
    18.          moveUp()
    19.      if cmd == 's':
    20.          moveDown()
    21.      if cmd == 'a':
    22.          moveLeft()
    23.      if cmd == 'd':
    24.          moveRight()
    25.      show(map)
    26.      
    27.  print("Game Over!\nYour score is: ", end='')
    28.  print(score)

运行界面:

GUI界面编程

为2048封装GUI界面,不涉及非常困难的技术问题,参照GUI文档完成任务。

  1.  # main.py
  2.  # main file
  3.  import random
  4.  import tkinter as tk
  5.  import colors as c
  6.  ​
  7.  ​
  8.  class Game(tk.Frame):
  9.      def __init__(self):
  10.          tk.Frame.__init__(self)
  11.          self.grid()
  12.          self.master.title("2048")
  13.  ​
  14.          self.main_grid = tk.Frame(
  15.              self, bg=c.GRID_COLOR, bd=3, width=600, height=600
  16.         )
  17.          self.main_grid.grid(pady=(100, 0))
  18.          self.make_GUI()
  19.          self.start_game()
  20.          self.master.bind("<Left>", self.left)
  21.          self.master.bind("<Right>", self.right)
  22.          self.master.bind("<Up>", self.up)
  23.          self.master.bind("<Down>", self.down)
  24.          self.mainloop()
  25.  ​
  26.      def make_GUI(self):
  27.          # make grid
  28.          self.cells = []
  29.          for i in range(4):
  30.              row = []
  31.              for j in range(4):
  32.                  cell_frame = tk.Frame(
  33.                      self.main_grid,
  34.                      bg=c.EMPTY_CELL_COLOR,
  35.                      width=150,
  36.                      height=150
  37.                 )
  38.                  cell_frame.grid(row=i, column=j, padx=5, pady=5)
  39.                  cell_number = tk.Label(self.main_grid, bg=c.EMPTY_CELL_COLOR)
  40.                  cell_number.grid(row=i, column=j)
  41.                  cell_data = {"frame": cell_frame, "number": cell_number}
  42.                  row.append(cell_data)
  43.              self.cells.append(row)
  44.  ​
  45.          score_frame = tk.Frame(self)
  46.          score_frame.place(relx=0.5, y=45, anchor="center")
  47.          tk.Label(
  48.              score_frame,
  49.              text="Score",
  50.              font=c.SCORE_LABEL_FONT
  51.         ).grid(row=0)
  52.          self.score_label = tk.Label(score_frame, text="0", font=c.SCORE_FONT)
  53.          self.score_label.grid(row=1)
  54.  ​
  55.      def start_game(self):
  56.          self.matrix = [[0] * 4 for _ in range(4)]
  57.  ​
  58.          row = random.randint(0, 3)
  59.          col = random.randint(0, 3)
  60.          self.matrix[row][col] = 2
  61.          self.cells[row][col]["frame"].configure(bg=c.CELL_COLORS[2])
  62.          self.cells[row][col]["number"].configure(
  63.              bg=c.CELL_COLORS[2],
  64.              fg=c.CELL_NUMBER_COLORS[2],
  65.              font=c.CELL_NUMBER_FONTS[2],
  66.              text="2"
  67.         )
  68.          while (self.matrix[row][col] != 0):
  69.              row = random.randint(0, 3)
  70.              col = random.randint(0, 3)
  71.          self.matrix[row][col] = 2
  72.          self.cells[row][col]["frame"].configure(bg=c.CELL_COLORS[2])
  73.          self.cells[row][col]["number"].configure(
  74.              bg=c.CELL_COLORS[2],
  75.              fg=c.CELL_NUMBER_COLORS[2],
  76.              font=c.CELL_NUMBER_FONTS[2],
  77.              text="2"
  78.         )
  79.  ​
  80.          self.score = 0
  81.  ​
  82.      def stack(self):
  83.          new_matrix = [[0] * 4 for _ in range(4)]
  84.          for i in range(4):
  85.              fill_position = 0
  86.              for j in range(4):
  87.                  if self.matrix[i][j] != 0:
  88.                      new_matrix[i][fill_position] = self.matrix[i][j]
  89.                      fill_position += 1
  90.          self.matrix = new_matrix
  91.  ​
  92.      def combine(self):
  93.          for i in range(4):
  94.              for j in range(3):
  95.                  if self.matrix[i][j] != 0 and self.matrix[i][j] == self.matrix[i][j + 1]:
  96.                      self.matrix[i][j] *= 2
  97.                      self.matrix[i][j + 1] = 0
  98.                      self.score += self.matrix[i][j]
  99.  ​
  100.      def reverse(self):
  101.          new_matrix = []
  102.          for i in range(4):
  103.              new_matrix.append([])
  104.              for j in range(4):
  105.                  new_matrix[i].append(self.matrix[i][3 - j])
  106.          self.matrix = new_matrix
  107.  ​
  108.      def transpose(self):
  109.          new_matrix = [[0] * 4 for _ in range(4)]
  110.          for i in range(4):
  111.              for j in range(4):
  112.                  new_matrix[i][j] = self.matrix[j][i]
  113.          self.matrix = new_matrix
  114.  ​
  115.      def add_new_tile(self):
  116.          row = random.randint(0, 3)
  117.          col = random.randint(0, 3)
  118.          while (self.matrix[row][col] != 0):
  119.              row = random.randint(0, 3)
  120.              col = random.randint(0, 3)
  121.          self.matrix[row][col] = random.choice([2, 4])
  122.  ​
  123.      def update_GUI(self):
  124.          for i in range(4):
  125.              for j in range(4):
  126.                  cell_value = self.matrix[i][j]
  127.                  if cell_value == 0:
  128.                      self.cells[i][j]["frame"].configure(bg=c.EMPTY_CELL_COLOR)
  129.                      self.cells[i][j]["number"].configure(bg=c.EMPTY_CELL_COLOR, text="")
  130.                  else:
  131.                      self.cells[i][j]["frame"].configure(bg=c.CELL_COLORS[cell_value])
  132.                      self.cells[i][j]["number"].configure(
  133.                          bg=c.CELL_COLORS[cell_value],
  134.                          fg=c.CELL_NUMBER_COLORS[cell_value],
  135.                          font=c.CELL_NUMBER_FONTS[cell_value],
  136.                          text=str(cell_value)
  137.                     )
  138.          self.score_label.configure(text=self.score)
  139.          self.update_idletasks()
  140.  ​
  141.      # These code for ordinary mode
  142.      def left(self, event):
  143.          self.stack()
  144.          self.combine()
  145.          self.stack()
  146.          self.add_new_tile()
  147.          self.update_GUI()
  148.          self.game_over()
  149.  ​
  150.      def right(self, event):
  151.          self.reverse()
  152.          self.stack()
  153.          self.combine()
  154.          self.stack()
  155.          self.reverse()
  156.          self.add_new_tile()
  157.          self.update_GUI()
  158.          self.game_over()
  159.  ​
  160.      def up(self, event):
  161.          self.transpose()
  162.          self.stack()
  163.          self.combine()
  164.          self.stack()
  165.          self.transpose()
  166.          self.add_new_tile()
  167.          self.update_GUI()
  168.          self.game_over()
  169.  ​
  170.      def down(self, event):
  171.          self.transpose()
  172.          self.reverse()
  173.          self.stack()
  174.          self.combine()
  175.          self.stack()
  176.          self.reverse()
  177.          self.transpose()
  178.          self.add_new_tile()
  179.          self.update_GUI()
  180.          self.game_over()
  181.  ​
  182.      def horizontal_move_exists(self):
  183.          for i in range(4):
  184.              for j in range(3):
  185.                  if self.matrix[i][j] == self.matrix[i][j + 1]:
  186.                      return True
  187.          return False
  188.  ​
  189.      def vertical_move_exists(self):
  190.          for i in range(3):
  191.              for j in range(4):
  192.                  if self.matrix[i][j] == self.matrix[i + 1][j]:
  193.                      return True
  194.          return False
  195.  ​
  196.      def game_over(self):
  197.          if any(2048 in row for row in self.matrix):
  198.              game_over_frame = tk.Frame(self.main_grid, borderwidth=2)
  199.              game_over_frame.place(relx=0.5, rely=0.5, anchor="center")
  200.              tk.Label(
  201.                  game_over_frame,
  202.                  text="You win!",
  203.                  bg=c.WINNER_BG,
  204.                  fg=c.GAME_OVER_FONT_COLOR,
  205.                  font=c.GAME_OVER_FONT
  206.             ).pack()
  207.          elif not any(0 in row for row in self.matrix) \
  208.                  and not self.horizontal_move_exists() \
  209.                  and not self.vertical_move_exists():
  210.              game_over_frame = tk.Frame(self.main_grid, borderwidth=2)
  211.              game_over_frame.place(relx=0.5, rely=0.5, anchor="center")
  212.              tk.Label(
  213.                  game_over_frame,
  214.                  text="Game over!",
  215.                  bg=c.LOSER_BG,
  216.                  fg=c.GAME_OVER_FONT_COLOR,
  217.                  font=c.GAME_OVER_FONT
  218.             ).pack()
  219.  ​
  220.  def main():
  221.      Game()
  222.  ​
  223.  if __name__ == "__main__":
  224.      main()
 
  1. # color.py
  2.  # config file
  3.  GRID_COLOR = "#a39489"
  4.  EMPTY_CELL_COLOR = "#c2b3a9"
  5.  SCORE_LABEL_FONT = ("Verdana", 24)
  6.  SCORE_FONT = ("Helvetica", 36, "bold")
  7.  GAME_OVER_FONT = ("Helvetica", 48, "bold")
  8.  GAME_OVER_FONT_COLOR = "#ffffff"
  9.  WINNER_BG = "#ffcc00"
  10.  LOSER_BG = "#a39489"
  11.  ​
  12.  CELL_COLORS = {
  13.      2:"#fcefe6",
  14.      4:"#f2e8cb",
  15.      8:"#f5b682",
  16.      16:"#f29446",
  17.      32:"#ff775c",
  18.      64:"#e64ce2",
  19.      128:"#ede291",
  20.      256:"#fce130",
  21.      512:"#ffdb4a",
  22.      1024:"#f0b922",
  23.      2048:"#fad74d"
  24.  }
  25.  ​
  26.  CELL_NUMBER_COLORS = {
  27.      2:"#695c57",
  28.      4:"#695c57",
  29.      8:"#ffffff",
  30.      16:"#ffffff",
  31.      32:"#ffffff",
  32.      64:"#ffffff",
  33.      128:"#ffffff",
  34.      256:"#ffffff",
  35.      512:"#ffffff",
  36.      1024:"#ffffff",
  37.      2048:"#ffffff"
  38.  }
  39.  ​
  40.  CELL_NUMBER_FONTS = {
  41.      2:("Helvetica", 55, "bold"),
  42.      4:("Helvetica", 55, "bold"),
  43.      8:("Helvetica", 55, "bold"),
  44.      16:("Helvetica", 50, "bold"),
  45.      32:("Helvetica", 50, "bold"),
  46.      64:("Helvetica", 50, "bold"),
  47.      128:("Helvetica", 45, "bold"),
  48.      256:("Helvetica", 45, "bold"),
  49.      512:("Helvetica", 45, "bold"),
  50.      1024:("Helvetica", 40, "bold"),
  51.      2048:("Helvetica", 40, "bold")
  52.  }

运行界面:

AI模式的实现

下面讨论使用Minimax算法完成人工智能模式,使得机器能够自我运行2048.

对于Minimax算法,其他文章有详细介绍。

Minimax是双人对弈模型中的经典算法。而2048可以看做玩家和计算机之间的博弈。玩家希望找出四个方向中的最佳方向,使得格局尽可能最优;而计算机希望在某特定位置生成随机方块,使得玩家格局尽可能最差。并且,玩家和计算机轮流行动。所以2048可以抽象为双人对弈模型。

在本项目中,影响格局的因素有如下几点:

  1. 同行、同列内方块保持递增或者递减(单调性);

  2. 相邻方块数值差异小(平滑性);

  3. 矩阵中的空格数量(空格数);

  4. 矩阵中的最大值(最大数)。

在设计估值函数时,需要考虑以上要素,并经过多次调试,确定最佳权重参数,完成对估值函数的设计。

在程序运行过程中,利用估值函数,构建格局树,并通过DFS找到最优格局。

从理论上说,只要估值函数设计合理,程序可以一直运行下去,游戏很难结束。但是,实际中由于对格局的影响因素考虑不全、权重参数并非最佳等原因,导致估值函数并非理想。

但是运行到2048问题不大。程序运行结果如下:

已经到2048了,但是可以看出来格局依然很好,跑到4096也是轻轻松松。

仔细回顾AI的思路,发现和我们平时玩2048的高分思路一致,都是把最大数放到一角,次大数放在旁边。

源代码如下:

  1.  # BaseDisplayer.py
  2.  class BaseDisplayer:
  3.      def __init__(self):
  4.          pass
  5.  ​
  6.      def display(self, grid):
  7.          pass
 ​
  1.  # BaseAI.py
  2.  class BaseDisplayer:
  3.      def __init__(self):
  4.          pass
  5.  ​
  6.      def display(self, grid):
  7.          pass
 ​
  1.  # ComputerAI.py
  2.  from random import randint
  3.  from BaseAI import BaseAI
  4.  ​
  5.  class ComputerAI(BaseAI):
  6.      def getMove(self, grid):
  7.          cells = grid.getAvailableCells()
  8.  ​
  9.          return cells[randint(0, len(cells) - 1)] if cells else None
 ​
  1.  # PlayerAI.py
  2.  from random import randint
  3.  from BaseAI import BaseAI
  4.  from pdb import set_trace
  5.  import time
  6.  ​
  7.  timeLimit = 0.2
  8.  ​
  9.  def h(grid):
  10.      return hash(tuple([tuple(x) for x in grid.map]))
  11.      
  12.  class PlayerAI(BaseAI):
  13.      
  14.      def getMove(self, grid):
  15.          '''
  16.             UP 0
  17.             DOWN 1
  18.             LEFT 2
  19.             RIGHT 3
  20.         '''
  21.          self.startTime = time.clock()
  22.          self.depthLimit = 6
  23.          self.heuristics = dict()
  24.          self.childrenMax = dict()
  25.          self.childrenMin = dict()
  26.          bestMove = None
  27.          while not self.timeOver():
  28.              move, _, _ = self.maximize(grid, float('-inf'), float('inf'), 1)
  29.              self.depthLimit +=1
  30.              if not self.timeOver() or bestMove is None:
  31.                  bestMove = move
  32.          return bestMove
  33.          
  34.      def maximize(self, grid, alpha, beta, depth):
  35.          if self.timeOver() or depth > self.depthLimit:
  36.                  return None, None, self.evaluation(grid)
  37.  ​
  38.          key = h(grid)
  39.          if key not in self.childrenMax:
  40.              moves = grid.getAvailableMoves()
  41.              if not moves:
  42.                  return None, None, self.evaluation(grid)
  43.              children = []
  44.              for move in moves:
  45.                  child = grid.clone()
  46.                  child.move(move)
  47.                  children.append([child, move])
  48.              self.childrenMax[key] = children
  49.          maxChild, maxUtility = None, float('-inf')
  50.  ​
  51.          for child in self.childrenMax[key]:
  52.              _, _, utility = self.minimize(child[0], alpha, beta, depth + 1)
  53.  ​
  54.              if utility > maxUtility:
  55.                  bestMove, maxChild, maxUtility = child[1], child[0], utility
  56.  ​
  57.              if maxUtility >= beta:
  58.                  break
  59.  ​
  60.              if maxUtility > alpha:
  61.                  alpha = maxUtility
  62.  ​
  63.          return bestMove, maxChild, maxUtility
  64.  ​
  65.      def minimize(self, grid, alpha, beta, depth):
  66.          if self.timeOver() or depth > self.depthLimit:
  67.              return None, None, self.evaluation(grid)
  68.  ​
  69.          key = h(grid)
  70.          if key not in self.childrenMin:
  71.              cells = grid.getAvailableCells()
  72.              if not cells:
  73.                  return None, None, self.evaluation(grid)
  74.              children = []
  75.              for cell in cells:
  76.                  child = grid.clone()
  77.                  child.insertTile(cell, 2)
  78.                  children.append(child)
  79.              for cell in cells:
  80.                  child = grid.clone()
  81.                  child.insertTile(cell, 4)
  82.                  children.append(child)
  83.              self.childrenMin[key] = children
  84.  ​
  85.          minChild, minUtility = None, float('inf')
  86.  ​
  87.          for child in self.childrenMin[key]:
  88.              _, _, utility = self.maximize(child, alpha, beta, depth + 1)
  89.  ​
  90.              if utility < minUtility:
  91.                  _, minChild, minUtility = _, child, utility
  92.  ​
  93.              if minUtility <= alpha:
  94.                  break
  95.  ​
  96.              if minUtility < beta:
  97.                  beta = minUtility
  98.  ​
  99.          return _, minChild, minUtility
  100.  ​
  101.      def evaluation(self, grid):
  102.          key = h(grid)
  103.          if key in self.heuristics:
  104.              return self.heuristics[key]
  105.  ​
  106.          score = 0
  107.          for i in range(4):
  108.              for j in range(4):
  109.                  score += grid.map[i][j] * (6-i-j)
  110.          #freeTiles = len(grid.getAvailableCells())
  111.          #score -= score/(freeTiles+1)
  112.  ​
  113.          if not grid.canMove():
  114.              lostPenalty = 2 * grid.getMaxTile()
  115.              score -= lostPenalty
  116.  ​
  117.          self.heuristics[key] = score
  118.          return score
  119.          
  120.      def timeOver(self):
  121.          return self.startTime + timeLimit <= time.clock()
  1.  # Displayer.py
  2.  from BaseDisplayer import BaseDisplayer
  3.  import platform
  4.  import os
  5.  ​
  6.  colorMap = {
  7.      0 : 97 ,
  8.      2     : 40 ,
  9.      4     : 100,
  10.      8     : 47 ,
  11.      16   : 107,
  12.      32   : 46 ,
  13.      64   : 106,
  14.      128   : 44 ,
  15.      256   : 104,
  16.      512   : 42 ,
  17.      1024 : 102,
  18.      2048 : 43 ,
  19.      4096 : 103,
  20.      8192 : 45 ,
  21.      16384 : 105,
  22.      32768 : 41 ,
  23.      65536 : 101,
  24.  }
  25.  ​
  26.  cTemp = "\x1b[%dm%7s\x1b[0m "
  27.  ​
  28.  class Displayer(BaseDisplayer):
  29.      def __init__(self):
  30.          if "Windows" == platform.system():
  31.              self.display = self.winDisplay
  32.          else:
  33.              self.display = self.unixDisplay
  34.  ​
  35.      def display(self, grid):
  36.          pass
  37.  ​
  38.      def winDisplay(self, grid):
  39.          for i in range(grid.size):
  40.              for j in range(grid.size):
  41.                  print("%6d " % grid.map[i][j], end="")
  42.              print("")
  43.          print("")
  44.  ​
  45.      def unixDisplay(self, grid):
  46.          for i in range(3 * grid.size):
  47.              for j in range(grid.size):
  48.                  v = grid.map[int(i / 3)][j]
  49.  ​
  50.                  if i % 3 == 1:
  51.                      string = str(v).center(7, " ")
  52.                  else:
  53.                      string = " "
  54.  ​
  55.                  print(cTemp % (colorMap[v], string), end="")
  56.              print("")
  57.  ​
  58.              if i % 3 == 2:
  59.                  print("")
  1.  # Grid.py
  2.  from copy import deepcopy
  3.  ​
  4.  directionVectors = (UP_VEC, DOWN_VEC, LEFT_VEC, RIGHT_VEC) = ((-1, 0), (1, 0), (0, -1), (0, 1))
  5.  vecIndex = [UP, DOWN, LEFT, RIGHT] = range(4)
  6.  ​
  7.  class Grid:
  8.      def __init__(self, size = 4):
  9.          self.size = size
  10.          self.map = [[0] * self.size for i in range(self.size)]
  11.  ​
  12.      # Make a Deep Copy of This Object
  13.      def clone(self):
  14.          gridCopy = Grid()
  15.          gridCopy.map = deepcopy(self.map)
  16.          gridCopy.size = self.size
  17.  ​
  18.          return gridCopy
  19.  ​
  20.      # Insert a Tile in an Empty Cell
  21.      def insertTile(self, pos, value):
  22.          self.setCellValue(pos, value)
  23.  ​
  24.      def setCellValue(self, pos, value):
  25.          self.map[pos[0]][pos[1]] = value
  26.  ​
  27.      # Return All the Empty c\Cells
  28.      def getAvailableCells(self):
  29.          cells = []
  30.  ​
  31.          for x in range(self.size):
  32.              for y in range(self.size):
  33.                  if self.map[x][y] == 0:
  34.                      cells.append((x,y))
  35.  ​
  36.          return cells
  37.  ​
  38.      # Return the Tile with Maximum Value
  39.      def getMaxTile(self):
  40.          maxTile = 0
  41.  ​
  42.          for x in range(self.size):
  43.              for y in range(self.size):
  44.                  maxTile = max(maxTile, self.map[x][y])
  45.  ​
  46.          return maxTile
  47.  ​
  48.      # Check If Able to Insert a Tile in Position
  49.      def canInsert(self, pos):
  50.          return self.getCellValue(pos) == 0
  51.  ​
  52.      # Move the Grid
  53.      def move(self, dir):
  54.          dir = int(dir)
  55.  ​
  56.          if dir == UP:
  57.              return self.moveUD(False)
  58.          if dir == DOWN:
  59.              return self.moveUD(True)
  60.          if dir == LEFT:
  61.              return self.moveLR(False)
  62.          if dir == RIGHT:
  63.              return self.moveLR(True)
  64.  ​
  65.      # Move Up or Down
  66.      def moveUD(self, down):
  67.          r = range(self.size -1, -1, -1) if down else range(self.size)
  68.  ​
  69.          moved = False
  70.  ​
  71.          for j in range(self.size):
  72.              cells = []
  73.  ​
  74.              for i in r:
  75.                  cell = self.map[i][j]
  76.  ​
  77.                  if cell != 0:
  78.                      cells.append(cell)
  79.  ​
  80.              self.merge(cells)
  81.  ​
  82.              for i in r:
  83.                  value = cells.pop(0) if cells else 0
  84.  ​
  85.                  if self.map[i][j] != value:
  86.                      moved = True
  87.  ​
  88.                  self.map[i][j] = value
  89.  ​
  90.          return moved
  91.  ​
  92.      # move left or right
  93.      def moveLR(self, right):
  94.          r = range(self.size - 1, -1, -1) if right else range(self.size)
  95.  ​
  96.          moved = False
  97.  ​
  98.          for i in range(self.size):
  99.              cells = []
  100.  ​
  101.              for j in r:
  102.                  cell = self.map[i][j]
  103.  ​
  104.                  if cell != 0:
  105.                      cells.append(cell)
  106.  ​
  107.              self.merge(cells)
  108.  ​
  109.              for j in r:
  110.                  value = cells.pop(0) if cells else 0
  111.  ​
  112.                  if self.map[i][j] != value:
  113.                      moved = True
  114.  ​
  115.                  self.map[i][j] = value
  116.  ​
  117.          return moved
  118.  ​
  119.      # Merge Tiles
  120.      def merge(self, cells):
  121.          if len(cells) <= 1:
  122.              return cells
  123.  ​
  124.          i = 0
  125.  ​
  126.          while i < len(cells) - 1:
  127.              if cells[i] == cells[i+1]:
  128.                  cells[i] *= 2
  129.  ​
  130.                  del cells[i+1]
  131.  ​
  132.              i += 1
  133.  ​
  134.      def canMove(self, dirs = vecIndex):
  135.  ​
  136.          # Init Moves to be Checked
  137.          checkingMoves = set(dirs)
  138.  ​
  139.          for x in range(self.size):
  140.              for y in range(self.size):
  141.  ​
  142.                  # If Current Cell is Filled
  143.                  if self.map[x][y]:
  144.  ​
  145.                      # Look Ajacent Cell Value
  146.                      for i in checkingMoves:
  147.                          move = directionVectors[i]
  148.  ​
  149.                          adjCellValue = self.getCellValue((x + move[0], y + move[1]))
  150.  ​
  151.                          # If Value is the Same or Adjacent Cell is Empty
  152.                          if adjCellValue == self.map[x][y] or adjCellValue == 0:
  153.                              return True
  154.  ​
  155.                  # Else if Current Cell is Empty
  156.                  elif self.map[x][y] == 0:
  157.                      return True
  158.  ​
  159.          return False
  160.  ​
  161.      # Return All Available Moves
  162.      def getAvailableMoves(self, dirs = vecIndex):
  163.          availableMoves = []
  164.  ​
  165.          for x in dirs:
  166.              gridCopy = self.clone()
  167.  ​
  168.              if gridCopy.move(x):
  169.                  availableMoves.append(x)
  170.  ​
  171.          return availableMoves
  172.  ​
  173.      def crossBound(self, pos):
  174.          return pos[0] < 0 or pos[0] >= self.size or pos[1] < 0 or pos[1] >= self.size
  175.  ​
  176.      def getCellValue(self, pos):
  177.          if not self.crossBound(pos):
  178.              return self.map[pos[0]][pos[1]]
  179.          else:
  180.              return None
  181.  ​
  182.  if __name__ == '__main__':
  183.      g = Grid()
  184.      g.map[0][0] = 2
  185.      g.map[1][0] = 2
  186.      g.map[3][0] = 4
  187.  ​
  188.      while True:
  189.          for i in g.map:
  190.              print(i)
  191.  ​
  192.          print(g.getAvailableMoves())
  193.  ​
  194.          v = input()
  195.  ​
  196.          g.move(v)
  1.  # GameManager.py
  2.  from Grid import Grid
  3.  from ComputerAI import ComputerAI
  4.  from PlayerAI import PlayerAI
  5.  from Displayer import Displayer
  6.  from random import randint
  7.  import time
  8.  ​
  9.  defaultInitialTiles = 2
  10.  defaultProbability = 0.9
  11.  ​
  12.  actionDic = {
  13.      0: "UP",
  14.      1: "DOWN",
  15.      2: "LEFT",
  16.      3: "RIGHT"
  17.  }
  18.  ​
  19.  (PLAYER_TURN, COMPUTER_TURN) = (0, 1)
  20.  ​
  21.  # Time Limit
  22.  timeLimit = 0.3
  23.  allowance = 0.05
  24.  ​
  25.  ​
  26.  class GameManager:
  27.      def __init__(self, size=4):
  28.          self.grid = Grid(size)
  29.          self.possibleNewTiles = [2, 4]
  30.          self.probability = defaultProbability
  31.          self.initTiles = defaultInitialTiles
  32.          self.computerAI = None
  33.          self.playerAI = None
  34.          self.displayer = None
  35.          self.over = False
  36.  ​
  37.      def setComputerAI(self, computerAI):
  38.          self.computerAI = computerAI
  39.  ​
  40.      def setPlayerAI(self, playerAI):
  41.          self.playerAI = playerAI
  42.  ​
  43.      def setDisplayer(self, displayer):
  44.          self.displayer = displayer
  45.  ​
  46.      def updateAlarm(self, currTime):
  47.          if currTime - self.prevTime > timeLimit + allowance:
  48.              self.over = True
  49.          else:
  50.              while time.clock() - self.prevTime < timeLimit + allowance:
  51.                  pass
  52.  ​
  53.              self.prevTime = time.clock()
  54.  ​
  55.      def start(self):
  56.          for i in range(self.initTiles):
  57.              self.insertRandonTile()
  58.  ​
  59.          self.displayer.display(self.grid)
  60.  ​
  61.          # Player AI Goes First
  62.          turn = PLAYER_TURN
  63.          maxTile = 0
  64.  ​
  65.          self.prevTime = time.clock()
  66.  ​
  67.          while not self.isGameOver() and not self.over:
  68.              # Copy to Ensure AI Cannot Change the Real Grid to Cheat
  69.              gridCopy = self.grid.clone()
  70.  ​
  71.              move = None
  72.  ​
  73.              if turn == PLAYER_TURN:
  74.                  print("Player's Turn:", end="")
  75.                  move = self.playerAI.getMove(gridCopy)
  76.                  print(actionDic[move])
  77.  ​
  78.                  # Validate Move
  79.                  if move != None and move >= 0 and move < 4:
  80.                      if self.grid.canMove([move]):
  81.                          self.grid.move(move)
  82.  ​
  83.                          # Update maxTile
  84.                          maxTile = self.grid.getMaxTile()
  85.                      else:
  86.                          print("Invalid PlayerAI Move")
  87.                          self.over = True
  88.                  else:
  89.                      print("Invalid PlayerAI Move - 1")
  90.                      self.over = True
  91.              else:
  92.                  print("Computer's turn:")
  93.                  move = self.computerAI.getMove(gridCopy)
  94.  ​
  95.                  # Validate Move
  96.                  if move and self.grid.canInsert(move):
  97.                      self.grid.setCellValue(move, self.getNewTileValue())
  98.                  else:
  99.                      print("Invalid Computer AI Move")
  100.                      self.over = True
  101.  ​
  102.              if not self.over:
  103.                  self.displayer.display(self.grid)
  104.  ​
  105.              # Exceeding the Time Allotted for Any Turn Terminates the Game
  106.              self.updateAlarm(time.clock())
  107.  ​
  108.              turn = 1 - turn
  109.          print(maxTile)
  110.  ​
  111.      def isGameOver(self):
  112.          return not self.grid.canMove()
  113.  ​
  114.      def getNewTileValue(self):
  115.          if randint(0, 99) < 100 * self.probability:
  116.              return self.possibleNewTiles[0]
  117.          else:
  118.              return self.possibleNewTiles[1]
  119.  ​
  120.      def insertRandonTile(self):
  121.          tileValue = self.getNewTileValue()
  122.          cells = self.grid.getAvailableCells()
  123.          cell = cells[randint(0, len(cells) - 1)]
  124.          self.grid.setCellValue(cell, tileValue)
  125.  ​
  126.  ​
  127.  def main():
  128.      gameManager = GameManager()
  129.      playerAI = PlayerAI()
  130.      computerAI = ComputerAI()
  131.      displayer = Displayer()
  132.  ​
  133.      gameManager.setDisplayer(displayer)
  134.      gameManager.setPlayerAI(playerAI)
  135.      gameManager.setComputerAI(computerAI)
  136.  ​
  137.      gameManager.start()
  138.  ​
  139.  ​
  140.  if __name__ == '__main__':
  141.      main()

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/649078
推荐阅读
相关标签
  

闽ICP备14008679号