用Python Tkinter打造的精彩连连看小游戏【附源码】

简介: 用Python Tkinter打造的精彩连连看小游戏【附源码】


连连看小游戏:用Python Tkinter打造的精彩游戏体验

在丰富多彩的游戏世界中,连连看作为一种经典的解谜游戏一直备受欢迎。连连看的玩法简单却富有挑战性,通过消除相同图标的配对,玩家可以不断地挑战自己的观察力和反应速度。

游戏简介

连连看是一种考验玩家眼力和逻辑思维的益智游戏,也是一种常见的休闲娱乐方式。游戏的基本规则是通过连接两个相同图标的路径,消除所有的图标。路径连接可以是直线、一次拐弯或两次拐弯。只有在路径不被其他图标遮挡的情况下,连接才会有效。

技术背景

本次实现采用了Python编程语言,并利用Tkinter库作为图形用户界面的构建工具。Tkinter是Python内建的GUI工具包,它提供了创建窗口、按钮、菜单等常见GUI元素的功能,极大地简化了图形界面的开发过程。通过结合Python的易读性和Tkinter的灵活性,我们可以迅速实现一个简单而有趣的连连看小游戏。

MainWindow类:

职责:

  • 代表游戏的主窗口。
  • 使用Tkinter处理GUI元素。
  • 管理游戏的初始化、地图绘制、处理用户点击以及检查游戏结束条件。

代码示例:

class MainWindow():
    # ... (其他代码)
    def __init__(self):
        self.root = tk.Tk()
        # ... (其他初始化代码)
    def __addComponets(self):
        self.menubar = tk.Menu(self.root, bg="lightgrey", fg="black")
        # ... (其他菜单初始化代码)
        self.canvas = tk.Canvas(self.root, bg='white', width=450, height=450)
        self.canvas.pack(side=tk.TOP, pady=5)
        self.canvas.bind('<Button-1>', self.clickCanvas)
        # ... (其他画布初始化代码)
    def centerWindow(self, width, height):
        # ... (屏幕居中代码)
    def file_new(self, event=None):
        # ... (新游戏初始化代码)
    def clickCanvas(self, event):
        # ... (处理画布点击事件的代码)
    # ... (其他方法)

方法:

  • __init__: 初始化主窗口并设置组件。
  • __addComponets: 添加组件,如菜单和画布到窗口。
  • centerWindow: 将窗口居中于屏幕。
  • file_new: 初始化并开始新游戏。
  • clickCanvas: 处理游戏画布上的鼠标点击。
  • isGameEnd: 检查游戏是否结束。
  • extractSmallIconList: 从较大的图像中加载并提取小图标。
  • iniMap: 使用随机图标初始化游戏地图。
  • drawMap: 在画布上绘制游戏地图。

包含用于坐标计算、绘制选定区域、清除连接块以及检查地图中空白区域的各种辅助方法。

Point类:

  • 表示游戏网格上的二维点。
  • 提供用于检查有用性、相等性、克隆和更改坐标的方法。
class Point():
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def isUserful(self):
        return self.x >= 0 and self.y >= 0
    def isEqual(self, point):
        return self.x == point.x and self.y == point.y
    def clone(self):
        return Point(self.x, self.y)
    def changeTo(self, point):
        self.x = point.x
        self.y = point.y

方法:

  • __init__: 使用x和y坐标初始化点。
  • isUserful: 检查点的坐标是否为非负数。
  • isEqual: 检查两个点是否相等。
  • clone: 创建点的克隆。
  • changeTo: 更改点的坐标以匹配另一个点。

主执行部分:

创建MainWindow类的实例以启动游戏。

MainWindow()

完整代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2017-10-02 15:19:24
# @Author  : Salamander (1906747819@qq.com)
# @Link    : http://blog.51lucy.com
import os, random
import tkinter as tk
import tkinter.messagebox
from PIL import Image, ImageTk
class MainWindow():
  __gameTitle = "连连看游戏"
  __windowWidth = 700
  __windowHeigth = 500
  __icons = []
  __gameSize = 10 # 游戏尺寸
  __iconKind = __gameSize * __gameSize / 4 # 小图片种类数量
  __iconWidth = 40
  __iconHeight = 40
  __map = [] # 游戏地图
  __delta = 25
  __isFirst = True
  __isGameStart = False
  __formerPoint = None
  EMPTY = -1
  NONE_LINK = 0
  STRAIGHT_LINK = 1
  ONE_CORNER_LINK = 2
  TWO_CORNER_LINK = 3
  def __init__(self):
    self.root = tk.Tk()
    self.root.title(self.__gameTitle)
    self.centerWindow(self.__windowWidth, self.__windowHeigth)
    self.root.minsize(460, 460)
    self.__addComponets()
    self.extractSmallIconList()
    self.root.mainloop()
  def __addComponets(self):
    self.menubar = tk.Menu(self.root, bg="lightgrey", fg="black")
    self.file_menu = tk.Menu(self.menubar, tearoff=0, bg="lightgrey", fg="black")
    self.file_menu.add_command(label="新游戏", command=self.file_new, accelerator="Ctrl+N")
    self.menubar.add_cascade(label="游戏", menu=self.file_menu)
    self.root.configure(menu=self.menubar)
    self.canvas = tk.Canvas(self.root, bg = 'white', width = 450, height = 450)
    self.canvas.pack(side=tk.TOP, pady = 5)
    self.canvas.bind('<Button-1>', self.clickCanvas)
        
  def centerWindow(self, width, height):
    screenwidth = self.root.winfo_screenwidth()
    screenheight = self.root.winfo_screenheight()
    size = '%dx%d+%d+%d' % (width, height, (screenwidth - width)/2, (screenheight - height)/2)
    self.root.geometry(size)
  def file_new(self, event=None):
    self.iniMap()
    self.drawMap()
    self.__isGameStart = True
  def clickCanvas(self, event):
    if self.__isGameStart:
      point = self.getInnerPoint(Point(event.x, event.y))
      # 有效点击坐标
      if point.isUserful() and not self.isEmptyInMap(point):
        if self.__isFirst:
          self.drawSelectedArea(point)
          self.__isFirst= False
          self.__formerPoint = point
        else:
          if self.__formerPoint.isEqual(point):
            self.__isFirst = True
            self.canvas.delete("rectRedOne")
          else:
            linkType = self.getLinkType(self.__formerPoint, point)
            if linkType['type'] != self.NONE_LINK:
              # TODO Animation
              self.ClearLinkedBlocks(self.__formerPoint, point)
              self.canvas.delete("rectRedOne")
              self.__isFirst = True
              if self.isGameEnd():
                tk.messagebox.showinfo("You Win!", "Tip")
                self.__isGameStart = False
            else:
              self.__formerPoint = point
              self.canvas.delete("rectRedOne")
              self.drawSelectedArea(point)
  # 判断游戏是否结束
  def isGameEnd(self):
    for y in range(0, self.__gameSize):
      for x in range(0, self.__gameSize):
        if self.__map[y][x] != self.EMPTY:
          return False
    return True
              
  '''
  提取小头像数组
  '''
  def extractSmallIconList(self):
    root_dir = os.getcwd()
    imagePath = os.path.join(root_dir, 'images', 'NARUTO.png')
    imageSouce = Image.open(imagePath)
    for index in range(0, int(self.__iconKind)):
      region = imageSouce.crop((self.__iconWidth * index, 0, 
          self.__iconWidth * index + self.__iconWidth - 1, self.__iconHeight - 1))
      self.__icons.append(ImageTk.PhotoImage(region))
  '''
  初始化地图 存值为0-24
  '''
  def iniMap(self):
    self.__map = [] # 重置地图
    tmpRecords = []
    records = []
    for i in range(0, int(self.__iconKind)):
      for j in range(0, 4):
        tmpRecords.append(i)
    total = self.__gameSize * self.__gameSize
    for x in range(0, total):
      index = random.randint(0, total - x - 1)
      records.append(tmpRecords[index])
      del tmpRecords[index]
    # 一维数组转为二维,y为高维度
    for y in range(0, self.__gameSize):
      for x in range(0, self.__gameSize):
        if x == 0:
          self.__map.append([])
        self.__map[y].append(records[x + y * self.__gameSize])
  '''
  根据地图绘制图像
  '''
  def drawMap(self):
    self.canvas.delete("all")
    for y in range(0, self.__gameSize):
      for x in range(0, self.__gameSize):
        point = self.getOuterLeftTopPoint(Point(x, y))
        im = self.canvas.create_image((point.x, point.y), 
          image=self.__icons[self.__map[y][x]], anchor='nw', tags = 'im%d%d' % (x, y))
  '''
  获取内部坐标对应矩形左上角顶点坐标
  '''
  def getOuterLeftTopPoint(self, point):
    return Point(self.getX(point.x), self.getY(point.y))
  '''
  获取内部坐标对应矩形中心坐标
  '''
  def getOuterCenterPoint(self, point):
    return Point(self.getX(point.x) + int(self.__iconWidth / 2), 
        self.getY(point.y) + int(self.__iconHeight / 2))
    
  def getX(self, x):
    return x * self.__iconWidth + self.__delta
  def getY(self, y):
    return y * self.__iconHeight + self.__delta
  '''
  获取内部坐标
  '''
  def getInnerPoint(self, point):
    x = -1
    y = -1
    for i in range(0, self.__gameSize):
      x1 = self.getX(i)
      x2 = self.getX(i + 1)
      if point.x >= x1 and point.x < x2:
        x = i
    for j in range(0, self.__gameSize):
      j1 = self.getY(j)
      j2 = self.getY(j + 1)
      if point.y >= j1 and point.y < j2:
        y = j
    return Point(x, y)
  '''
  选择的区域变红,point为内部坐标
  '''
  def drawSelectedArea(self, point):
    pointLT = self.getOuterLeftTopPoint(point)
    pointRB = self.getOuterLeftTopPoint(Point(point.x + 1, point.y + 1))
    self.canvas.create_rectangle(pointLT.x, pointLT.y, 
        pointRB.x - 1, pointRB.y - 1, outline = 'red', tags = "rectRedOne")
  '''
  消除连通的两个块
  '''
  def ClearLinkedBlocks(self, p1, p2):
    self.__map[p1.y][p1.x] = self.EMPTY
    self.__map[p2.y][p2.x] = self.EMPTY
    self.canvas.delete('im%d%d' % (p1.x, p1.y))
    self.canvas.delete('im%d%d' % (p2.x, p2.y))
  '''
  地图上该点是否为空
  '''
  def isEmptyInMap(self, point):
    if self.__map[point.y][point.x] == self.EMPTY:
      return True
    else:
      return False
  '''
  获取两个点连通类型
  '''
  def getLinkType(self, p1, p2):
    # 首先判断两个方块中图片是否相同
    if self.__map[p1.y][p1.x] != self.__map[p2.y][p2.x]:
      return { 'type': self.NONE_LINK }
    if self.isStraightLink(p1, p2):
      return {
        'type': self.STRAIGHT_LINK
      }
    res = self.isOneCornerLink(p1, p2)
    if res:
      return {
        'type': self.ONE_CORNER_LINK,
        'p1': res
      }
    res = self.isTwoCornerLink(p1, p2)
    if res:
      return {
        'type': self.TWO_CORNER_LINK,
        'p1': res['p1'],
        'p2': res['p2']
      }
    return {
      'type': self.NONE_LINK
    }
  '''
  直连
  '''
  def isStraightLink(self, p1, p2):
    start = -1
    end = -1
    # 水平
    if p1.y == p2.y:
      # 大小判断
      if p2.x < p1.x:
        start = p2.x
        end = p1.x
      else:
        start = p1.x
        end = p2.x
      for x in range(start + 1, end):
        if self.__map[p1.y][x] != self.EMPTY:
          return False
      return True
    elif p1.x == p2.x:
      if p1.y > p2.y:
        start = p2.y
        end = p1.y
      else:
        start = p1.y
        end = p2.y
      for y in range(start + 1, end):
        if self.__map[y][p1.x] != self.EMPTY:
          return False
      return True
    return False
  def isOneCornerLink(self, p1, p2):
    pointCorner = Point(p1.x, p2.y)
    if self.isStraightLink(p1, pointCorner) and self.isStraightLink(pointCorner, p2) and self.isEmptyInMap(pointCorner):
      return pointCorner
    pointCorner = Point(p2.x, p1.y)
    if self.isStraightLink(p1, pointCorner) and self.isStraightLink(pointCorner, p2) and self.isEmptyInMap(pointCorner):
      return pointCorner
  def isTwoCornerLink(self, p1, p2):
    for y in range(-1, self.__gameSize + 1):
      pointCorner1 = Point(p1.x, y)
      pointCorner2 = Point(p2.x, y)
      if y == p1.y or y == p2.y:
        continue
      if y == -1 or y == self.__gameSize:
        if self.isStraightLink(p1, pointCorner1) and self.isStraightLink(pointCorner2, p2):
          return {'p1': pointCorner1, 'p2': pointCorner2}
      else:
        if self.isStraightLink(p1, pointCorner1) and self.isStraightLink(pointCorner1, pointCorner2) and self.isStraightLink(pointCorner2, p2) and self.isEmptyInMap(pointCorner1) and self.isEmptyInMap(pointCorner2):
          return {'p1': pointCorner1, 'p2': pointCorner2}
    # 横向判断
    for x in range(-1, self.__gameSize + 1):
      pointCorner1 = Point(x, p1.y)
      pointCorner2 = Point(x, p2.y)
      if x == p1.x or x == p2.x:
        continue
      if x == -1 or x == self.__gameSize:
        if self.isStraightLink(p1, pointCorner1) and self.isStraightLink(pointCorner2, p2):
          return {'p1': pointCorner1, 'p2': pointCorner2}
      else:
        if self.isStraightLink(p1, pointCorner1) and self.isStraightLink(pointCorner1, pointCorner2) and self.isStraightLink(pointCorner2, p2) and self.isEmptyInMap(pointCorner1) and self.isEmptyInMap(pointCorner2):
          return {'p1': pointCorner1, 'p2': pointCorner2}
class Point():
  def __init__(self, x, y):
    self.x = x
    self.y = y
  def isUserful(self):
    if self.x >= 0 and self.y >= 0:
      return True
    else:
      return False
          
  '''
  判断两个点是否相同
  '''
  def isEqual(self, point):
    if self.x == point.x and self.y == point.y:
      return True
    else:
      return False
  '''
  克隆一份对象
  '''
  def clone(self):
    return Point(self.x, self.y)
  '''
  改为另一个对象
  '''
  def changeTo(self, point):
    self.x = point.x
    self.y = point.y
MainWindow()

总结:

在这个充满创造力和乐趣的项目中,我们不仅实现了一个简单的连连看小游戏,更体验了Python和Tkinter在游戏开发领域的强大潜力。可以作为初学者学习Python GUI编程和游戏开发的绝佳实例。希望这个项目能够激发更多人对编程和游戏开发的兴趣,开启一个全新的学习之旅。

目录
相关文章
|
9月前
|
人工智能 数据安全/隐私保护 异构计算
桌面版exe安装和Python命令行安装2种方法详细讲解图片去水印AI源码私有化部署Lama-Cleaner安装使用方法-优雅草卓伊凡
桌面版exe安装和Python命令行安装2种方法详细讲解图片去水印AI源码私有化部署Lama-Cleaner安装使用方法-优雅草卓伊凡
1500 8
桌面版exe安装和Python命令行安装2种方法详细讲解图片去水印AI源码私有化部署Lama-Cleaner安装使用方法-优雅草卓伊凡
|
9月前
|
机器学习/深度学习 数据采集 算法
基于mediapipe深度学习的运动人体姿态提取系统python源码
本内容介绍了基于Mediapipe的人体姿态提取算法。包含算法运行效果图、软件版本说明、核心代码及详细理论解析。Mediapipe通过预训练模型检测人体关键点,并利用部分亲和场(PAFs)构建姿态骨架,具有模块化架构,支持高效灵活的数据处理流程。
|
9月前
|
小程序 PHP 图形学
热门小游戏源码(Python+PHP)下载-微信小程序游戏源码Unity发实战指南​
本文详解如何结合Python、PHP与Unity开发并部署小游戏至微信小程序。涵盖技术选型、Pygame实战、PHP后端对接、Unity转换适配及性能优化,提供从原型到发布的完整指南,助力开发者快速上手并发布游戏。
|
11月前
|
算法 数据可视化 数据挖掘
基于EM期望最大化算法的GMM参数估计与三维数据分类系统python源码
本内容展示了基于EM算法的高斯混合模型(GMM)聚类实现,包含完整Python代码、运行效果图及理论解析。程序使用三维数据进行演示,涵盖误差计算、模型参数更新、结果可视化等关键步骤,并附有详细注释与操作视频,适合学习EM算法与GMM模型的原理及应用。
|
11月前
|
API 数据安全/隐私保护 开发者
企业微信自动加好友软件,导入手机号批量添加微信好友,python版本源码分享
代码展示了企业微信官方API的合规使用方式,包括获取access_token、查询部门列表和创建用户等功能
|
10月前
|
并行计算 算法 Java
Python3解释器深度解析与实战教程:从源码到性能优化的全路径探索
Python解释器不止CPython,还包括PyPy、MicroPython、GraalVM等,各具特色,适用于不同场景。本文深入解析Python解释器的工作原理、内存管理机制、GIL限制及其优化策略,并介绍性能调优工具链及未来发展方向,助力开发者提升Python应用性能。
577 0
|
11月前
|
机器人 API 数据安全/隐私保护
QQ机器人插件源码,自动回复聊天机器人,python源码分享
消息接收处理:通过Flask搭建HTTP服务接收go-cqhttp推送的QQ消息47 智能回复逻辑
|
9月前
|
数据采集 机器学习/深度学习 人工智能
Python:现代编程的首选语言
Python:现代编程的首选语言
1533 102
|
9月前
|
数据采集 机器学习/深度学习 算法框架/工具
Python:现代编程的瑞士军刀
Python:现代编程的瑞士军刀
493 104
|
9月前
|
人工智能 自然语言处理 算法框架/工具
Python:现代编程的首选语言
Python:现代编程的首选语言
387 103

推荐镜像

更多