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

news2025/1/18 10:07:54

文章目录

  • 连连看小游戏:用Python Tkinter打造的精彩游戏体验
    • 游戏简介
    • 技术背景
    • MainWindow类:
      • 职责:
      • 方法:
      • Point类:
    • 主执行部分:
    • 完整代码:
    • 总结:

连连看小游戏:用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编程和游戏开发的绝佳实例。希望这个项目能够激发更多人对编程和游戏开发的兴趣,开启一个全新的学习之旅。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1432260.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python 数据分析(PYDA)第三版(一)

原文&#xff1a;wesmckinney.com/book/ 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 关于开放版本 第 3 版的《Python 数据分析》现在作为“开放获取”HTML 版本在此网站wesmckinney.com/book上提供&#xff0c;除了通常的印刷和电子书格式。该版本最初于 2022 年…

逆向基础-破解密码

1.通过study PE查看.exe程序的位数 打开 x32dbg 从暂停到运行程序 原理&#xff1a;软件算出的密码与用户输入的密码作比较 破解流程&#xff1a;查信息 --> 找内存关键数据 --> 测试

STM32--SPI通信协议(1)SPI基础知识总结

前言 I2C (Inter-Integrated Circuit)和SPI (Serial Peripheral Interface)是两种常见的串行通信协议&#xff0c;用于连接集成电路芯片之间的通信&#xff0c;选择I2C或SPI取决于具体的应用需求。如果需要较高的传输速度和简单的接口&#xff0c;可以选择SPI。如果需要连接多…

基于NSGA-II的深度迁移学习

深度迁移学习 迁移学习是一种机器学习技术&#xff0c;它允许一个预训练的模型被用作起点&#xff0c;在此基础上进行微调以适应新的任务或数据。其核心思想是利用从一个任务中学到的知识来帮助解决另一个相关的任务&#xff0c;即使这两个任务的数据分布不完全相同。这种方法…

幻兽帕鲁服务器搭建记录,小白成功分享

玩转幻兽帕鲁服务器&#xff0c;阿里云推出新手0基础一键部署幻兽帕鲁服务器教程&#xff0c;傻瓜式一键部署&#xff0c;3分钟即可成功创建一台Palworld专属服务器&#xff0c;成本仅需26元&#xff0c;阿里云服务器网aliyunfuwuqi.com分享2024年新版基于阿里云搭建幻兽帕鲁服…

重磅发布!使用 LangGraph 创建一个超级AI Agent

几天前&#xff0c;LangChain 正式宣布了名为 LangGraph 的新库&#xff0c;LangGraph 建立在 LangChain 之上&#xff0c;简化了创建和管理Agent及其运行时的过程。 在这篇文章中&#xff0c;我们将全面介绍 langGraph&#xff0c;什么是代理和代理运行时&#xff1f;Langgra…

【数据结构】排序---C语言版

七大排序算法 一、对于排序的分类&#xff1a;二、插入排序1、直接插入排序&#xff08;1&#xff09;基本思想&#xff1a;&#xff08;2&#xff09;直接插入排序&#xff1a;&#xff08;3&#xff09;代码实现&#xff1a;&#xff08;4&#xff09;总结&#xff1a; 2、希…

Postgresql体系结构

client连接PostgreSQL过程&#xff1a; 1、客户端发起请求 2、主服务postmaster进程负责服务器是否接受客户端的host通信认证&#xff0c;服务器对客户端进行身份鉴别 3、主服务进程为该客户端单独fork一个客户端工作进程postgres 4、客户端与postgres进程建立通信连接&#xf…

【小白开服日记】幻兽帕鲁服务器如何搭建?

玩转幻兽帕鲁服务器&#xff0c;阿里云推出新手0基础一键部署幻兽帕鲁服务器教程&#xff0c;傻瓜式一键部署&#xff0c;3分钟即可成功创建一台Palworld专属服务器&#xff0c;成本仅需26元&#xff0c;阿里云服务器网aliyunfuwuqi.com分享2024年新版基于阿里云搭建幻兽帕鲁服…

二分查找第二弹

目录 力扣852.山脉数组的峰顶索引 力扣162.寻找峰值 力扣153.寻找旋转排序数组中的最小值 力扣剑指Offer53.0-n-1缺失的数字 力扣852.山脉数组的峰顶索引 峰顶之前的全部比他小&#xff0c;峰顶之后的也比他小&#xff0c;把小于等于和大于分成两段 class Solution {publi…

java仓库进销存商品库存管理系统springboot+vue

库存管理信息系统研究的内容涉及库存管理的全过程&#xff0c;包括入库、出库、退 货、订货、库存统计查询等等。 根据上述工作流程&#xff0c;库存管理系统将包含以下内容 1&#xff09;登录信息的输入&#xff0c;密码的修改。 2&#xff09;基本信息的输入&#xff0c;包括…

一篇文章搞懂CNN(卷积神经网络)及其所含概念

目录 1. 什么是卷积神经网络&#xff1a;2. 应用领域&#xff1a;3. 架构&#xff1a;4. 卷积层的参数和名词参数&#xff1a;名词&#xff1a; 5. 注意&#xff1a;6. 经典网络&#xff1a;小结&#xff1a; 当下&#xff0c;计算机视觉在人工智能领域中扮演着至关重要的角色。…

【数据分享】1929-2023年全球站点的逐日降雪深度数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、能见度等指标&#xff0c;说到气象数据&#xff0c;最详细的气象数据是具体到气象监测站点的数据&#xff01; 之前我们分享过1929-2023年全球气象站点的逐日平均气温数据、逐日最高气温数据…

基于OpenCV灰度图像转GCode的双向扫描实现

基于OpenCV灰度图像转GCode的双向扫描实现 引言激光雕刻简介OpenCV简介实现步骤 1.导入必要的库2. 读取灰度图像3. 图像预处理4. 生成GCode 1. 简化版的双向扫描2. 优化版的双向扫描 5. 保存生成的GCode6. 灰度图像双向扫描代码示例 总结 系列文章 ⭐深入理解G0和G1指令&…

freeswitch对接FunASR实时语音听写

1、镜像启动 通过下述命令拉取并启动FunASR软件包的docker镜像&#xff1a; sudo docker pull \registry.cn-hangzhou.aliyuncs.com/funasr_repo/funasr:funasr-runtime-sdk-online-cpu-0.1.7 mkdir -p ./funasr-runtime-resources/models sudo docker run -p 10096:10095 -i…

Eclipse 安装使用ABAPGit

Eclipse->Help->Install New software 添加地址 https://eclipse.abapgit.org/updatesite/ 安装完成打开 选择abapGit repositories,先添加仓库 点下图添加自己仓库 如图添加仓库地址 添加完仓库后&#xff0c;点击我的仓库 右键选中行&#xff0c;可以进行push和pu…

WPS Office18.7软件日常更新

【应用名称】&#xff1a;WPS Office 【适用平台】&#xff1a;#Android 【软件标签】&#xff1a;#WPS 【应用版本】&#xff1a;18.6.1➡18.7 【应用大小】&#xff1a;160MB 【软件说明】&#xff1a;软件日常更新。WPS Office是使用人数最多的移动办公软件。独有手机阅读模…

打榜,一个新人是如何做到一天推荐小报童7人的?

这是第一次参加打榜活动&#xff0c;有点小激动&#xff0c;虽然GMV不高&#xff0c;和那些动辄几千上万的大佬没法比&#xff0c;但是其实内在玩法还是相通的 下面就记录一下个人在此次打榜活动中的一些感悟&#xff0c;与君共勉吧~ 先来看下当时的成绩 最后时刻被反超 本…

Python算法100例-1.3 牛顿迭代法求方程根

完整源代码项目地址&#xff0c;关注博主私信’源代码’后可获取 1&#xff0e;问题描述 编写用牛顿迭代法求方程根的函数。方程为 a x 3 b x 2 c x d 0 ax^3bx^2cxd0 ax3bx2cxd0&#xff0c;系数a、b、c、d由主函数输入&#xff0c;求x在1附近的一个实根。求出根后&…

C++STL之容器

STL的概述 STL(Standard Template Library,标准模板库) STL的6大组件&#xff1a;容器、算法、迭代器、适配器、仿函数、空间配置 容器&#xff1a;存放数据 算法&#xff1a;操作数据 迭代器&#xff1a;算法 通过迭代器 操作 容器 适配器&#xff1a;为算法 提供更多的接口 …