当前位置:   article > 正文

python开发的连连看小游戏_连连看源代码 python

连连看源代码 python

说明:

1.在网上找了一个基础版本改进而来,大概增加了200行代码,相对原版要复杂一些;

2.界面采用tkinter开发,比较简单;

3.新增了连接线功能;

4.新增了积分功能;

5.新增了陷入死局时,重新打散功能;

6.新增了关卡功能,目前设置了5关;

7.新增了计算当前地图有多少可连通图标功能。

陷入死局时,效果如下:

打散后,重新排列,然后聚在一起:

 进入下一关界面:

 进入第二关效果如下:

 后面的关卡与上面类似。

源代码只有一个文件,用到的库比较少、比较小,安装起来很容易。代码如下:

  1. from tkinter import BOTH,FIRST,LAST,GROOVE,FLAT
  2. import os, random
  3. import tkinter as tk
  4. import tkinter.messagebox
  5. import numpy as np
  6. from PIL import Image, ImageTk
  7. import time
  8. from playsound import playsound
  9. class MainWindow():
  10. __gameTitle = "连连看游戏"
  11. __windowWidth = 600
  12. __windowHeigth = 500
  13. __gameSize = 10 # 游戏尺寸
  14. __iconKind = __gameSize * __gameSize / 4 # 小图片种类数量
  15. __iconWidth = 40
  16. __iconHeight = 40
  17. __map = [] # 游戏地图
  18. __delta = 25
  19. __isFirst = True
  20. __isGameStart = False
  21. __formerPoint = None
  22. EMPTY = -1
  23. NONE_LINK = 0
  24. STRAIGHT_LINK = 1
  25. ONE_CORNER_LINK = 2
  26. TWO_CORNER_LINK = 3
  27. __images = []#蒙板阴影图像
  28. __level = 1#游戏关卡
  29. __point_position = {}
  30. __score = 0
  31. def __init__(self):
  32. self.__root = tk.Tk()
  33. self.__root.title(self.__gameTitle)
  34. self.centerWindow(self.__windowWidth, self.__windowHeigth)
  35. self.__root.minsize(460, 460)
  36. self.__addComponets()
  37. self.new_game()
  38. self.__root.mainloop()
  39. #菜单栏中添加一个叫“游戏”的菜单项
  40. def __addComponets(self):
  41. #菜单栏
  42. self.menubar = tk.Menu(self.__root, bg="lightgrey", fg="black")
  43. #子菜单
  44. self.file_menu = tk.Menu(self.menubar, tearoff=0, bg="lightgrey", fg="black")
  45. self.file_menu.add_command(label="新游戏", command=self.new_game, accelerator="Ctrl+N")
  46. self.menubar.add_cascade(label="游戏", menu=self.file_menu)
  47. self.__root.configure(menu=self.menubar)
  48. self.__root.bind('<Control-n>',self.new_game)#实现快捷键功能ctrl+N
  49. #
  50. self.canvas = tk.Canvas(self.__root, bg = '#D3D3D3', width = 450, height = 450,cursor="hand2")
  51. self.canvas.grid(row=0,column=0,sticky='N',pady = 5,rowspan=2,padx=(50,0))
  52. self.canvas.bind('<Button-1>', self.clickCanvas)
  53. #分数面板
  54. self.label_score = tkinter.Label(self.__root,width=10,height=2,font=('黑体',13,'bold'),fg="#802A2A",bg="#F5DEB3")
  55. self.label_score.grid(row=0,column=1)
  56. #显示当前可连接数
  57. self.label_linknums = tkinter.Label(self.__root,width=10,height=2,font=('黑体',13,'bold'),fg="#228B22")
  58. self.label_linknums.grid(row=1,column=1)
  59. '''
  60. 判断两个接点之间是否能连通
  61. '''
  62. def __isLink(self,fromPoint,point):
  63. return self.isStraightLink(fromPoint,point) or self.isOneCornerLink(fromPoint,point) or self.isTwoCornerLink(fromPoint,point)
  64. '''
  65. 获取提示,逻辑有点复杂,主要解决以下问题:
  66. 1.获取当前地图上所有的图标;
  67. 2.然后遍历,计算有多少对可连通的图标;
  68. 3.如果有三个或者四个相同的图标可以相互连通,要进行判断,以勉出现重复计算。
  69. '''
  70. def getPromptPoint(self):
  71. _link_point = []
  72. _icons_arr = self.__map.flatten()#先转化为一维数组,便于操作
  73. for i in set(_icons_arr):
  74. if i == self.EMPTY: continue #忽略为空的坐标
  75. _arr = np.where(_icons_arr==i)[0]#元组的第一个元素,是由i元素的索引组成的数组
  76. if _arr.size == 4:#剩下4个相同图标
  77. _point1 = Point(int(_arr[0]%10),int(_arr[0]/10))# 计算x,y坐标
  78. _point2 = Point(int(_arr[1]%10),int(_arr[1]/10))
  79. _point3 = Point(int(_arr[2]%10),int(_arr[2]/10))
  80. _point4 = Point(int(_arr[3]%10),int(_arr[3]/10))
  81. #4个接点6条线,但最多只能计算2条线,再多就会出现重复计数
  82. if self.__isLink(_point1,_point2):
  83. _link_point.append((_point1,_point2))
  84. if self.__isLink(_point3,_point4):
  85. _link_point.append((_point3,_point4))
  86. elif self.__isLink(_point1,_point3):
  87. _link_point.append((_point1,_point3))
  88. if self.__isLink(_point2,_point4):
  89. _link_point.append((_point2,_point4))
  90. elif self.__isLink(_point1,_point4):
  91. _link_point.append((_point1,_point4))
  92. if self.__isLink(_point2,_point3):
  93. _link_point.append((_point2,_point3))
  94. elif self.__isLink(_point2,_point3):
  95. _link_point.append((_point2,_point3))
  96. elif self.__isLink(_point2,_point4):
  97. _link_point.append((_point2,_point4))
  98. elif self.__isLink(_point3,_point4):
  99. _link_point.append((_point3,_point4))
  100. elif _arr.size == 2:#剩下2个相同图标
  101. _point1 = Point(int(_arr[0]%10),int(_arr[0]/10))# 计算x,y坐标
  102. _point2 = Point(int(_arr[1]%10),int(_arr[1]/10))
  103. if self.__isLink(_point1,_point2):
  104. _link_point.append((_point1,_point2))
  105. self.label_linknums['text'] = f"{len(_link_point)}连"
  106. print("_link_point",_link_point)
  107. return _link_point
  108. def centerWindow(self, width, height):#700 500
  109. screenwidth = self.__root.winfo_screenwidth()#窗口距离屏幕左边的宽
  110. screenheight = self.__root.winfo_screenheight()#窗口距离屏幕顶部的高
  111. size = '%dx%d+%d+%d' % (width, height, (screenwidth - width)/2, (screenheight - height)/2)
  112. self.__root.geometry(size)
  113. def add_score(self):
  114. self.__score += 2 #消除一对图标,分数加2
  115. self.set_label_text()
  116. def set_label_text(self):
  117. self.label_score['text'] = f"{self.__score}分"
  118. '''
  119. 这个方法需要注意的积分问题:
  120. 1.开始新游戏时,将积分清零;
  121. 2.开始下一关时,将前一关的积分累积起来。
  122. '''
  123. def new_game(self, event=None,level=1,score = 0):
  124. self.__score = score
  125. self.set_label_text()
  126. self.__level = level
  127. self.extractSmallIconList()
  128. self.iniMap()
  129. self.drawMap()
  130. self.getPromptPoint()
  131. self.__isGameStart = True
  132. def clickCanvas(self, event):
  133. if not self.__isGameStart:
  134. return
  135. # 确认有效点击坐标
  136. point = self.getInnerPoint(Point(event.x, event.y))
  137. if not point.isUserful() or self.isEmptyInMap(point):
  138. return
  139. #__isFirst在三种情况下为True,1.游戏开始的时候;2.同一个图标点击了两次;3.成功消除一组图标。
  140. if self.__isFirst:
  141. self.drawSelectedArea(point)
  142. self.__isFirst= False
  143. self.__formerPoint = point
  144. else:
  145. if self.__formerPoint.isEqual(point):#两次点击的是同一个图标
  146. self.__isFirst = True
  147. self.canvas.delete("rectRedOne")
  148. self.canvas.delete('image_mask')
  149. else:
  150. linkType = self.getLinkType(self.__formerPoint, point)
  151. if linkType['type'] != self.NONE_LINK:
  152. #画连接线
  153. self.draw_link_line(self.__formerPoint,point,linkType)
  154. playsound("music2.mp3")
  155. time.sleep(.5)# 显示画线的延迟
  156. self.ClearLinkedBlocks(self.__formerPoint, point)
  157. self.canvas.delete("rectRedOne")
  158. self.canvas.delete("linkline")
  159. self.canvas.delete('image_mask')
  160. #增加分数
  161. self.add_score()
  162. _link_point = self.getPromptPoint()
  163. self.__isFirst = True
  164. if len(_link_point) == 0:
  165. if self.isGameEnd():
  166. if tk.messagebox.askokcancel('确认操作', '是否进入下一关?'):
  167. self.__level = self.__level+1
  168. self.new_game(level = self.__level,score = self.__score)
  169. else:
  170. self.__isGameStart = False
  171. else: #没有可连通图标,且当前地图上还有图标存在,游戏未结束时,打散当前地图
  172. tk.messagebox.showinfo("提示", "已没有可连通图标,需重新打散!")
  173. self.shuffleMap()
  174. self.drawMap()
  175. self.getPromptPoint()
  176. else:
  177. self.__formerPoint = point
  178. self.canvas.delete("rectRedOne")
  179. self.drawSelectedArea(point)
  180. # 判断游戏是否结束
  181. def isGameEnd(self):
  182. for y in range(0, self.__gameSize):
  183. for x in range(0, self.__gameSize):
  184. if self.__map[y][x] != self.EMPTY:
  185. return False
  186. return True
  187. '''
  188. 消除图像前,把两个图像之间的连接线画出来。
  189. '''
  190. def draw_link_line(self,formerPoint,point,linkType):
  191. if linkType['type'] == self.STRAIGHT_LINK:
  192. p1 = self.getOuterCenterPoint(formerPoint)
  193. p2 = self.getOuterCenterPoint(point)
  194. #arrow表示线的箭头样式,默认不带箭头,参数值 FIRST表示添加箭头带线段开始位置,LAST表示到末尾占位置,BOTH表示两端均添加
  195. self.canvas.create_line((p1.x, p1.y),(p2.x, p2.y),fill = 'red',tags = 'linkline',width=3, arrow=LAST)
  196. if linkType['type'] == self.ONE_CORNER_LINK:
  197. corner1 = self.getOuterCenterPoint(linkType["p1"])
  198. p1 = self.getOuterFitCenterPoint(formerPoint,corner1)
  199. p2 = self.getOuterFitCenterPoint(point,corner1)
  200. self.canvas.create_line((p1.x, p1.y),(corner1.x, corner1.y),fill = 'red',tags = 'linkline',width=3)
  201. self.canvas.create_line((p2.x, p2.y),(corner1.x, corner1.y),fill = 'red',tags = 'linkline',width=3, arrow=FIRST)
  202. elif linkType['type'] == self.TWO_CORNER_LINK:
  203. corner1 = self.getOuterCenterPoint(linkType["p1"])
  204. corner2 = self.getOuterCenterPoint(linkType["p2"])
  205. p1 = self.getOuterFitCenterPoint(formerPoint,corner1)
  206. p2 = self.getOuterFitCenterPoint(point,corner2)
  207. self.canvas.create_line((p1.x, p1.y),(corner1.x, corner1.y),fill = 'red',tags = 'linkline',width=3)
  208. self.canvas.create_line((p2.x, p2.y),(corner2.x, corner2.y),fill = 'red',tags = 'linkline',width=3, arrow=FIRST)
  209. self.canvas.create_line((corner1.x, corner1.y),(corner2.x, corner2.y),fill = 'red',tags = 'linkline',width=3)
  210. self.canvas.update()
  211. '''
  212. 提取小头像数组
  213. '''
  214. def extractSmallIconList(self):
  215. self.__icons = []
  216. imageSouce = Image.open(f"图片/new{self.__level}.png")
  217. for index in range(0, int(self.__iconKind)):#0-24
  218. region = imageSouce.crop((self.__iconWidth * index, 0,
  219. self.__iconWidth * index + self.__iconWidth - 1, self.__iconHeight - 1))
  220. self.__icons.append(ImageTk.PhotoImage(region))
  221. '''
  222. 初始化地图 存值为0-24
  223. '''
  224. def iniMap(self):
  225. self.__map = [] # 重置地图
  226. _tmpRecords = []
  227. # 0-24,一共25个图标,每个图标出现4次,总共出现100次
  228. _total = self.__gameSize * self.__gameSize
  229. _tmpRecords = np.linspace(0, self.__iconKind, _total, endpoint = False,dtype=int)
  230. np.random.shuffle(_tmpRecords)#重新打散洗牌
  231. self.__map = _tmpRecords.reshape((10,10))#将一维数组100,转化为二维10*10
  232. '''
  233. 初始化地图 存值为0-24,当前地图中,如果没有可连通路线、进入死局时,将现有的图标进行打散重新洗牌。
  234. '''
  235. def shuffleMap(self):
  236. _icons_arr = self.__map.flatten()#先转化为一维数组,便于操作
  237. self.__map = []#重置地图
  238. for i in set(_icons_arr):
  239. if i == self.EMPTY: continue
  240. _tuple = np.where(_icons_arr==i)[0]#元组的第一个元素,是由i元素的索引组成的数组
  241. self.__map.extend([i]* _tuple.size)#第i个图标出现的次数
  242. np.random.shuffle(self.__map)#洗牌
  243. _len = len(self.__map)
  244. _total = self.__gameSize * self.__gameSize
  245. self.__map[_len:_total] = [self.EMPTY]*(_total-_len)#剩余元素置为空(-1),填满至整个地图0-99
  246. self.__map = np.array(self.__map).reshape((self.__gameSize,self.__gameSize))#转化为二维数组10*10
  247. '''
  248. 根据地图绘制图像
  249. 1.在使用create_image()函数时,需要先通过Pillow库(或其他图片处理库)读取图片文件并生成图片对象,然后再将这个图片对象传递给create_image()函数,在指定的坐标位置将图片添加到画布上。
  250. 2.canvas.create_image(x, y, image=图像对象, anchor=定位点),x和y表示图像锚点在画布上的位置,即图像在画布上的左上角坐标。image参数是一个tkinter中的PhotoImage()对象,它可以指定要加载的图像文件。anchor参数是一个字符串,用于指定图像锚点的位置,可以是"nw"(左上角)、"n"(上)、"ne"(右上角)、"w"(左)、"center"(中心)、"e"(右)、"sw"(左下角)或"s"(下)。如果不指定anchor参数,则默认为“center”。
  251. '''
  252. def drawMap(self):
  253. self.canvas.delete("all")#字符串"all""是代表画布上所有项目的特殊标记
  254. for y in range(0, self.__gameSize):
  255. for x in range(0, self.__gameSize):
  256. point = self.getOuterLeftTopPoint(Point(x, y))
  257. if self.__map[y][x] != self.EMPTY:#打散重绘时,不再置空的图标
  258. self.canvas.create_image((point.x, point.y),image=self.__icons[self.__map[y][x]], anchor='nw', tags = 'im%d%d' % (x, y))
  259. '''
  260. 根据两点不同的位置,获取对应边的中心连接点。例如目标图标在左边,就取靠左的一边的中点。如果连接目标在右边,就取靠右一边的中点坐标。
  261. '''
  262. def getOuterFitCenterPoint(self, formerPoint, corner):
  263. _formerPoint = self.getOuterCenterPoint(formerPoint)
  264. if _formerPoint.y>corner.y:
  265. fitCenterPoint = Point(self.getX(formerPoint.x) + int(self.__iconWidth / 2),self.getY(formerPoint.y))#顶边
  266. elif _formerPoint.y<corner.y:
  267. fitCenterPoint = Point(self.getX(formerPoint.x) + int(self.__iconWidth / 2),self.getY(formerPoint.y) + int(self.__iconHeight))#底边
  268. elif _formerPoint.y == corner.y:
  269. if _formerPoint.x > corner.x:
  270. fitCenterPoint = Point(self.getX(formerPoint.x),self.getY(formerPoint.y)+int(self.__iconHeight / 2))#左边
  271. elif _formerPoint.x < corner.x:
  272. fitCenterPoint = Point(self.getX(formerPoint.x) + int(self.__iconWidth),self.getY(formerPoint.y)+int(self.__iconHeight / 2))#右边
  273. return fitCenterPoint
  274. '''
  275. 获取内部坐标对应矩形左上角顶点坐标
  276. '''
  277. def getOuterLeftTopPoint(self, point):
  278. return Point(self.getX(point.x), self.getY(point.y))
  279. '''
  280. 获取内部坐标对应矩形中心坐标
  281. '''
  282. def getOuterCenterPoint(self, point):
  283. return Point(self.getX(point.x) + int(self.__iconWidth / 2),
  284. self.getY(point.y) + int(self.__iconHeight / 2))
  285. def getX(self, x):# x * 40 + 25
  286. return x * self.__iconWidth + self.__delta
  287. def getY(self, y):
  288. return y * self.__iconHeight + self.__delta
  289. '''
  290. 获取内部坐标
  291. '''
  292. def getInnerPoint(self, point):
  293. x = -1
  294. y = -1
  295. for i in range(0, self.__gameSize):
  296. x1 = self.getX(i)
  297. x2 = self.getX(i + 1)
  298. if point.x >= x1 and point.x < x2:
  299. x = i
  300. for j in range(0, self.__gameSize):
  301. j1 = self.getY(j)
  302. j2 = self.getY(j + 1)
  303. if point.y >= j1 and point.y < j2:
  304. y = j
  305. return Point(x, y)
  306. '''
  307. 创建一块蒙板,覆盖到选中的图形上
  308. '''
  309. def create_mask(self,x1, y1, x2, y2, **kwargs):
  310. #(0,0,0)代表黑色的RGB,127代表alpha透明度,(x2-x1, y2-y1)指长宽
  311. image = Image.new('RGBA', (x2-x1, y2-y1), (0,0,0,127))
  312. self.__images = [ImageTk.PhotoImage(image)]#这里一定要用一个实例变量存储,局部变量没有效果,原因不清楚
  313. self.canvas.create_image(x1, y1, image=self.__images[0], anchor='nw',tags = "image_mask")
  314. '''
  315. 选择的区域变红,point为内部坐标
  316. '''
  317. def drawSelectedArea(self, point):
  318. pointLT = self.getOuterLeftTopPoint(point)
  319. pointRB = self.getOuterLeftTopPoint(Point(point.x + 1, point.y + 1))
  320. self.canvas.create_rectangle(pointLT.x, pointLT.y, pointRB.x - 1, pointRB.y - 1, outline = 'red', tags = "rectRedOne", width=2)
  321. #蒙板
  322. self.create_mask(pointLT.x, pointLT.y, pointRB.x - 1, pointRB.y - 1, fill='skyblue', alpha=.5,width=0)
  323. '''
  324. 消除连通的两个块
  325. '''
  326. def ClearLinkedBlocks(self, p1, p2):
  327. self.__map[p1.y][p1.x] = self.EMPTY
  328. self.__map[p2.y][p2.x] = self.EMPTY
  329. self.canvas.delete('im%d%d' % (p1.x, p1.y))
  330. self.canvas.delete('im%d%d' % (p2.x, p2.y))
  331. '''
  332. 地图上该点是否为空
  333. '''
  334. def isEmptyInMap(self, point):
  335. if self.__map[point.y][point.x] == self.EMPTY:
  336. return True
  337. else:
  338. return False
  339. '''
  340. 获取两个点连通类型
  341. '''
  342. def getLinkType(self, p1, p2):
  343. # 首先判断两个方块中图片是否相同
  344. if self.__map[p1.y][p1.x] != self.__map[p2.y][p2.x]:
  345. return { 'type': self.NONE_LINK }
  346. if self.isStraightLink(p1, p2):
  347. return {
  348. 'type': self.STRAIGHT_LINK
  349. }
  350. res = self.isOneCornerLink(p1, p2)
  351. if res:
  352. return {
  353. 'type': self.ONE_CORNER_LINK,
  354. 'p1': res
  355. }
  356. res = self.isTwoCornerLink(p1, p2)
  357. if res:
  358. return {
  359. 'type': self.TWO_CORNER_LINK,
  360. 'p1': res['p1'],
  361. 'p2': res['p2']
  362. }
  363. return {
  364. 'type': self.NONE_LINK
  365. }
  366. '''
  367. 直连
  368. '''
  369. def isStraightLink(self, p1, p2):
  370. start = -1
  371. end = -1
  372. # 水平
  373. if p1.y == p2.y:
  374. # 大小判断
  375. if p2.x < p1.x:
  376. start = p2.x
  377. end = p1.x
  378. else:
  379. start = p1.x
  380. end = p2.x
  381. for x in range(start + 1, end):
  382. if self.__map[p1.y][x] != self.EMPTY:
  383. return False
  384. return True
  385. elif p1.x == p2.x:
  386. if p1.y > p2.y:
  387. start = p2.y
  388. end = p1.y
  389. else:
  390. start = p1.y
  391. end = p2.y
  392. for y in range(start + 1, end):
  393. if self.__map[y][p1.x] != self.EMPTY:
  394. return False
  395. return True
  396. return False
  397. def isOneCornerLink(self, p1, p2):
  398. pointCorner = Point(p1.x, p2.y)
  399. if self.isStraightLink(p1, pointCorner) and self.isStraightLink(pointCorner, p2) and self.isEmptyInMap(pointCorner):
  400. return pointCorner
  401. pointCorner = Point(p2.x, p1.y)
  402. if self.isStraightLink(p1, pointCorner) and self.isStraightLink(pointCorner, p2) and self.isEmptyInMap(pointCorner):
  403. return pointCorner
  404. def isTwoCornerLink(self, p1, p2):
  405. for y in range(-1, self.__gameSize + 1):
  406. pointCorner1 = Point(p1.x, y)
  407. pointCorner2 = Point(p2.x, y)
  408. if y == p1.y or y == p2.y:
  409. continue
  410. if y == -1 or y == self.__gameSize:
  411. if self.isStraightLink(p1, pointCorner1) and self.isStraightLink(pointCorner2, p2):
  412. return {'p1': pointCorner1, 'p2': pointCorner2}
  413. else:
  414. if self.isStraightLink(p1, pointCorner1) and self.isStraightLink(pointCorner1, pointCorner2) and self.isStraightLink(pointCorner2, p2) and self.isEmptyInMap(pointCorner1) and self.isEmptyInMap(pointCorner2):
  415. return {'p1': pointCorner1, 'p2': pointCorner2}
  416. # 横向判断
  417. for x in range(-1, self.__gameSize + 1):
  418. pointCorner1 = Point(x, p1.y)
  419. pointCorner2 = Point(x, p2.y)
  420. if x == p1.x or x == p2.x:
  421. continue
  422. if x == -1 or x == self.__gameSize:
  423. if self.isStraightLink(p1, pointCorner1) and self.isStraightLink(pointCorner2, p2):
  424. return {'p1': pointCorner1, 'p2': pointCorner2}
  425. else:
  426. if self.isStraightLink(p1, pointCorner1) and self.isStraightLink(pointCorner1, pointCorner2) and self.isStraightLink(pointCorner2, p2) and self.isEmptyInMap(pointCorner1) and self.isEmptyInMap(pointCorner2):
  427. return {'p1': pointCorner1, 'p2': pointCorner2}
  428. class Point():
  429. def __init__(self, x, y):
  430. self.x = x
  431. self.y = y
  432. def isUserful(self):
  433. if self.x >= 0 and self.y >= 0:
  434. return True
  435. else:
  436. return False
  437. '''
  438. 判断两个点是否相同
  439. '''
  440. def isEqual(self, point):
  441. if self.x == point.x and self.y == point.y:
  442. return True
  443. else:
  444. return False
  445. '''
  446. 克隆一份对象
  447. '''
  448. def clone(self):
  449. return Point(self.x, self.y)
  450. '''
  451. 改为另一个对象
  452. '''
  453. def changeTo(self, point):
  454. self.x = point.x
  455. self.y = point.y
  456. '''
  457. 显示坐标
  458. '''
  459. def __repr__(self):
  460. return f"x={self.x},y={self.y}"
  461. MainWindow()

再提一点,就是游戏地图的制作,原理就是找25张图片,把它们压缩成40*40像素的图标,然后横排合成一张图。在网上找了很久,也没发现靠谱的软件,其实用python程序就能实现这个功能。

代码来源:

使用Python批量拼接图片_python多图合并成一张图_谢欣桁的博客-CSDN博客

稍微改了下:

  1. import os
  2. import math
  3. from PIL import Image
  4. def merge_images(image_folder, output_file, n, m):
  5. # 获取所有图像文件的列表
  6. image_files = [os.path.join(image_folder, f) for f in os.listdir(image_folder) if f.endswith('.png')]
  7. # 计算每个小图像的大小和大图像的大小
  8. image_count = len(image_files)
  9. if image_count == 0:
  10. print('No image files found in the directory:', image_folder)
  11. return
  12. # 计算小图像的大小以及大图像的大小
  13. img = Image.open(image_files[0])
  14. # img_size0 = img.size[0]
  15. # img_size1 = img.size[1]
  16. img_size0 = 40
  17. img_size1 = 40
  18. new_img_size0 = img_size0 * n
  19. new_img_size1 = img_size1 * m
  20. # 创建一个新的大图像
  21. new_img = Image.new('RGB', (new_img_size0, new_img_size1), 'white')
  22. # 将所有小图像粘贴到新图像的正确位置
  23. for i, f in enumerate(image_files):
  24. row = int(i / n)
  25. col = i % n
  26. img = Image.open(f)
  27. img = img.resize((img_size0, img_size1))
  28. new_img.paste(img, (col * img_size0, row * img_size1))
  29. # 保存大图像
  30. new_img.save(output_file)
  31. # 用法示例
  32. image_folder = 'C:/Users/Administrator/Desktop/图标/卡通' #目录下放25张png图片
  33. output_file = 'C:/Users/Administrator/Desktop/图标/卡通/new5.png'#运行程序合成一张图
  34. n = 25 # 每行显示的图像数
  35. m = 1 # 每列显示的图像数
  36. merge_images(image_folder, output_file, n, m)

2023.6.16日更新:通过chatGPT对部分代码进行了改进,代码精简了一些。

精简版代码如下:

  1. # -*- coding: utf-8 -*-
  2. from tkinter import BOTH,FIRST,LAST,GROOVE,FLAT
  3. import os, random
  4. import tkinter as tk
  5. import tkinter.messagebox
  6. import numpy as np
  7. from PIL import Image, ImageTk
  8. import time
  9. from playsound import playsound
  10. class MainWindow():
  11. __gameTitle = "连连看游戏"
  12. __windowWidth = 600
  13. __windowHeigth = 500
  14. __gameSize = 10 # 游戏尺寸
  15. __iconKind = __gameSize * __gameSize / 4 # 小图片种类数量
  16. __iconWidth = 40
  17. __iconHeight = 40
  18. __map = [] # 游戏地图
  19. __delta = 25
  20. __isFirst = True
  21. __isGameStart = False
  22. __formerPoint = None
  23. EMPTY = -1
  24. NONE_LINK = 0
  25. STRAIGHT_LINK = 1
  26. ONE_CORNER_LINK = 2
  27. TWO_CORNER_LINK = 3
  28. __images = []#蒙板阴影图像
  29. __level = 1#游戏关卡
  30. __point_position = {}
  31. __score = 0
  32. def __init__(self):
  33. self.__root = tk.Tk()
  34. self.__root.title(self.__gameTitle)
  35. self.centerWindow(self.__windowWidth, self.__windowHeigth)
  36. self.__root.minsize(460, 460)
  37. self.__addComponets()
  38. self.new_game()
  39. self.__root.mainloop()
  40. #菜单栏中添加一个叫“游戏”的菜单项
  41. def __addComponets(self):
  42. #菜单栏
  43. self.menubar = tk.Menu(self.__root, bg="lightgrey", fg="black")
  44. #子菜单
  45. self.file_menu = tk.Menu(self.menubar, tearoff=0, bg="lightgrey", fg="black")
  46. self.file_menu.add_command(label="新游戏", command=self.new_game, accelerator="Ctrl+N")
  47. self.menubar.add_cascade(label="游戏", menu=self.file_menu)
  48. self.__root.configure(menu=self.menubar)
  49. self.__root.bind('<Control-n>',self.new_game)#实现快捷键功能ctrl+N
  50. #
  51. self.canvas = tk.Canvas(self.__root, bg = '#D3D3D3', width = 450, height = 450,cursor="hand2")
  52. self.canvas.grid(row=0,column=0,sticky='N',pady = 5,rowspan=2,padx=(50,0))
  53. self.canvas.bind('<Button-1>', self.clickCanvas)
  54. #分数面板
  55. self.label_score = tkinter.Label(self.__root,width=10,height=2,font=('黑体',13,'bold'),fg="#802A2A",bg="#F5DEB3")
  56. self.label_score.grid(row=0,column=1)
  57. #显示当前可连接数
  58. self.label_linknums = tkinter.Label(self.__root,width=10,height=2,font=('黑体',13,'bold'),fg="#228B22")
  59. self.label_linknums.grid(row=1,column=1)
  60. '''
  61. 判断两个接点之间是否能连通
  62. '''
  63. def __isLink(self,fromPoint,point):
  64. return self.isStraightLink(fromPoint,point) or self.isOneCornerLink(fromPoint,point) or self.isTwoCornerLink(fromPoint,point)
  65. '''
  66. 获取提示,逻辑有点复杂,主要解决以下问题:
  67. 1.获取当前地图上所有的图标;
  68. 2.然后遍历,计算有多少对可连通的图标;
  69. 3.如果有三个或者四个相同的图标可以相互连通,要进行判断,以勉出现重复计算。
  70. '''
  71. def getPromptPoint(self):
  72. _link_point = []
  73. _icons_arr = self.__map.flatten()#先转化为一维数组,便于操作
  74. for i in set(_icons_arr):
  75. if i == self.EMPTY: continue #忽略为空的坐标
  76. _arr = np.where(_icons_arr==i)[0]#元组的第一个元素,是由i元素的索引组成的数组
  77. if _arr.size == 4:#剩下4个相同图标
  78. _point1 = Point(int(_arr[0]%10),int(_arr[0]/10))# 计算x,y坐标
  79. _point2 = Point(int(_arr[1]%10),int(_arr[1]/10))
  80. _point3 = Point(int(_arr[2]%10),int(_arr[2]/10))
  81. _point4 = Point(int(_arr[3]%10),int(_arr[3]/10))
  82. #4个接点6条线,但最多只能计算2条线,再多就会出现重复计数
  83. if self.__isLink(_point1,_point2):
  84. _link_point.append((_point1,_point2))
  85. if self.__isLink(_point3,_point4):
  86. _link_point.append((_point3,_point4))
  87. elif self.__isLink(_point1,_point3):
  88. _link_point.append((_point1,_point3))
  89. if self.__isLink(_point2,_point4):
  90. _link_point.append((_point2,_point4))
  91. elif self.__isLink(_point1,_point4):
  92. _link_point.append((_point1,_point4))
  93. if self.__isLink(_point2,_point3):
  94. _link_point.append((_point2,_point3))
  95. elif self.__isLink(_point2,_point3):
  96. _link_point.append((_point2,_point3))
  97. elif self.__isLink(_point2,_point4):
  98. _link_point.append((_point2,_point4))
  99. elif self.__isLink(_point3,_point4):
  100. _link_point.append((_point3,_point4))
  101. elif _arr.size == 2:#剩下2个相同图标
  102. _point1 = Point(int(_arr[0]%10),int(_arr[0]/10))# 计算x,y坐标
  103. _point2 = Point(int(_arr[1]%10),int(_arr[1]/10))
  104. if self.__isLink(_point1,_point2):
  105. _link_point.append((_point1,_point2))
  106. self.label_linknums['text'] = f"{len(_link_point)}连"
  107. print("_link_point",_link_point)
  108. return _link_point
  109. def centerWindow(self, width, height):#700 500
  110. screenwidth = self.__root.winfo_screenwidth()#窗口距离屏幕左边的宽
  111. screenheight = self.__root.winfo_screenheight()#窗口距离屏幕顶部的高
  112. size = '%dx%d+%d+%d' % (width, height, (screenwidth - width)/2, (screenheight - height)/2)
  113. self.__root.geometry(size)
  114. def add_score(self):
  115. self.__score += 2 #消除一对图标,分数加2
  116. self.set_label_text()
  117. def set_label_text(self):
  118. self.label_score['text'] = f"{self.__score}分"
  119. '''
  120. 这个方法需要注意的积分问题:
  121. 1.开始新游戏时,将积分清零;
  122. 2.开始下一关时,将前一关的积分累积起来。
  123. '''
  124. def new_game(self, event=None,level=1,score = 0):
  125. self.__score = score
  126. self.set_label_text()
  127. self.__level = level
  128. self.extractSmallIconList()
  129. self.iniMap()
  130. self.drawMap()
  131. self.getPromptPoint()
  132. self.__isGameStart = True
  133. def clickCanvas(self, event):
  134. if not self.__isGameStart:
  135. return
  136. # 确认有效点击坐标
  137. point = self.getInnerPoint(Point(event.x, event.y))
  138. if not point.isUserful() or self.isEmptyInMap(point):
  139. return
  140. #__isFirst在三种情况下为True,1.游戏开始的时候;2.同一个图标点击了两次;3.成功消除一组图标。
  141. if self.__isFirst:
  142. self.drawSelectedArea(point)
  143. self.__isFirst= False
  144. self.__formerPoint = point
  145. else:
  146. if self.__formerPoint.isEqual(point):#两次点击的是同一个图标
  147. self.__isFirst = True
  148. self.canvas.delete("rectRedOne")
  149. self.canvas.delete('image_mask')
  150. else:
  151. linkType = self.getLinkType(self.__formerPoint, point)
  152. if linkType['type'] != self.NONE_LINK:
  153. #画连接线
  154. self.draw_link_line(self.__formerPoint,point,linkType)
  155. playsound("music2.mp3")
  156. time.sleep(.5)# 显示画线的延迟
  157. self.ClearLinkedBlocks(self.__formerPoint, point)
  158. self.canvas.delete("rectRedOne")
  159. self.canvas.delete("linkline")
  160. self.canvas.delete('image_mask')
  161. #增加分数
  162. self.add_score()
  163. _link_point = self.getPromptPoint()
  164. self.__isFirst = True
  165. if len(_link_point) == 0:
  166. if self.isGameEnd():
  167. if tk.messagebox.askokcancel('确认操作', '是否进入下一关?'):
  168. self.__level = self.__level+1
  169. self.new_game(level = self.__level,score = self.__score)
  170. else:
  171. self.__isGameStart = False
  172. else: #没有可连通图标,且当前地图上还有图标存在,游戏未结束时,打散当前地图
  173. tk.messagebox.showinfo("提示", "已没有可连通图标,需重新打散!")
  174. self.shuffleMap()
  175. self.drawMap()
  176. self.getPromptPoint()
  177. else:
  178. self.__formerPoint = point
  179. self.canvas.delete("rectRedOne")
  180. self.drawSelectedArea(point)
  181. # 判断游戏是否结束
  182. def isGameEnd(self):
  183. return np.all(self.__map == self.EMPTY)
  184. '''
  185. 消除图像前,把两个图像之间的连接线画出来。
  186. '''
  187. def draw_link_line(self,formerPoint,point,linkType):
  188. if linkType['type'] == self.STRAIGHT_LINK:
  189. p1 = self.getOuterCenterPoint(formerPoint)
  190. p2 = self.getOuterCenterPoint(point)
  191. #arrow表示线的箭头样式,默认不带箭头,参数值 FIRST表示添加箭头带线段开始位置,LAST表示到末尾占位置,BOTH表示两端均添加
  192. self.canvas.create_line((p1.x, p1.y),(p2.x, p2.y),fill = 'red',tags = 'linkline',width=3, arrow=LAST)
  193. if linkType['type'] == self.ONE_CORNER_LINK:
  194. corner1 = self.getOuterCenterPoint(linkType["p1"])
  195. p1 = self.getOuterFitCenterPoint(formerPoint,corner1)
  196. p2 = self.getOuterFitCenterPoint(point,corner1)
  197. self.canvas.create_line((p1.x, p1.y),(corner1.x, corner1.y),fill = 'red',tags = 'linkline',width=3)
  198. self.canvas.create_line((p2.x, p2.y),(corner1.x, corner1.y),fill = 'red',tags = 'linkline',width=3, arrow=FIRST)
  199. elif linkType['type'] == self.TWO_CORNER_LINK:
  200. corner1 = self.getOuterCenterPoint(linkType["p1"])
  201. corner2 = self.getOuterCenterPoint(linkType["p2"])
  202. p1 = self.getOuterFitCenterPoint(formerPoint,corner1)
  203. p2 = self.getOuterFitCenterPoint(point,corner2)
  204. self.canvas.create_line((p1.x, p1.y),(corner1.x, corner1.y),fill = 'red',tags = 'linkline',width=3)
  205. self.canvas.create_line((p2.x, p2.y),(corner2.x, corner2.y),fill = 'red',tags = 'linkline',width=3, arrow=FIRST)
  206. self.canvas.create_line((corner1.x, corner1.y),(corner2.x, corner2.y),fill = 'red',tags = 'linkline',width=3)
  207. self.canvas.update()
  208. '''
  209. 提取小头像数组
  210. '''
  211. def extractSmallIconList(self):
  212. self.__icons = []
  213. imageSouce = Image.open(f"图片/new{self.__level}.png")
  214. for index in range(0, int(self.__iconKind)):#0-24
  215. region = imageSouce.crop((self.__iconWidth * index, 0,
  216. self.__iconWidth * index + self.__iconWidth - 1, self.__iconHeight - 1))
  217. self.__icons.append(ImageTk.PhotoImage(region))
  218. '''
  219. 初始化地图 存值为0-24
  220. '''
  221. def iniMap(self):
  222. self.__map = [] # 重置地图
  223. _tmpRecords = []
  224. # 0-24,一共25个图标,每个图标出现4次,总共出现100次
  225. _total = self.__gameSize * self.__gameSize
  226. _tmpRecords = np.linspace(0, self.__iconKind, _total, endpoint = False,dtype=int) #用25个图标构建100个位置
  227. np.random.shuffle(_tmpRecords)#重新打散洗牌
  228. self.__map = _tmpRecords.reshape((10,10))#将一维数组100,转化为二维10*10
  229. '''
  230. 初始化地图 存值为0-24,当前地图中,如果没有可连通路线、进入死局时,将现有的图标进行打散重新洗牌。
  231. '''
  232. def shuffleMap(self):
  233. _icons_arr = self.__map.flatten()#先转化为一维数组,便于操作
  234. self.__map = np.array([i for i in _icons_arr if i != self.EMPTY])
  235. np.random.shuffle(self.__map)#洗牌
  236. self.__map = np.pad(self.__map, (0, self.__gameSize**2 - len(self.__map)), 'constant', constant_values=self.EMPTY)#补齐剩下地图的值-1
  237. self.__map = self.__map.reshape((self.__gameSize, self.__gameSize))
  238. '''
  239. 根据地图绘制图像
  240. 1.在使用create_image()函数时,需要先通过Pillow库(或其他图片处理库)读取图片文件并生成图片对象,然后再将这个图片对象传递给create_image()函数,在指定的坐标位置将图片添加到画布上。
  241. 2.canvas.create_image(x, y, image=图像对象, anchor=定位点),x和y表示图像锚点在画布上的位置,即图像在画布上的左上角坐标。image参数是一个tkinter中的PhotoImage()对象,它可以指定要加载的图像文件。anchor参数是一个字符串,用于指定图像锚点的位置,可以是"nw"(左上角)、"n"(上)、"ne"(右上角)、"w"(左)、"center"(中心)、"e"(右)、"sw"(左下角)或"s"(下)。如果不指定anchor参数,则默认为“center”。
  242. '''
  243. def drawMap(self):
  244. self.canvas.delete("all")
  245. y, x = np.where(self.__map != self.EMPTY)
  246. for i in range(len(x)):
  247. point = self.getOuterLeftTopPoint(Point(x[i], y[i]))
  248. self.canvas.create_image((point.x, point.y), image=self.__icons[self.__map[y[i]][x[i]]], anchor='nw', tags='im%d%d' % (x[i], y[i]))
  249. '''
  250. 根据两点不同的位置,获取对应边的中心连接点。例如目标图标在左边,就取靠左的一边的中点。如果连接目标在右边,就取靠右一边的中点坐标。
  251. '''
  252. def getOuterFitCenterPoint(self,former_point, corner):
  253. _former_point = self.getOuterCenterPoint(former_point)
  254. mapping = {
  255. _former_point.y > corner.y: (self.getX(former_point.x) + int(self.__iconWidth / 2), self.getY(former_point.y)),
  256. _former_point.y < corner.y: (self.getX(former_point.x) + int(self.__iconWidth / 2), self.getY(former_point.y) + int(self.__iconHeight)),
  257. _former_point.y == corner.y and _former_point.x > corner.x: (self.getX(former_point.x), self.getY(former_point.y) + int(self.__iconHeight / 2)),
  258. _former_point.y == corner.y and _former_point.x < corner.x: (self.getX(former_point.x) + int(self.__iconWidth),self.getY(former_point.y) + int(self.__iconHeight / 2))
  259. }
  260. x, y = mapping[True]
  261. return Point(x, y)
  262. '''
  263. 获取内部坐标对应矩形左上角顶点坐标
  264. '''
  265. def getOuterLeftTopPoint(self, point):
  266. return Point(self.getX(point.x), self.getY(point.y))
  267. '''
  268. 获取内部坐标对应矩形中心坐标
  269. '''
  270. def getOuterCenterPoint(self, point):
  271. return Point(self.getX(point.x) + int(self.__iconWidth / 2),
  272. self.getY(point.y) + int(self.__iconHeight / 2))
  273. def getX(self, x):# x * 40 + 25
  274. return x * self.__iconWidth + self.__delta
  275. def getY(self, y):
  276. return y * self.__iconHeight + self.__delta
  277. '''
  278. 获取内部坐标,将点击的像素坐标转换为point坐标,即icon位置坐标
  279. '''
  280. def getInnerPoint(self, point):
  281. x = -1
  282. y = -1
  283. for i in range(0, self.__gameSize):
  284. x1 = self.getX(i)
  285. x2 = self.getX(i + 1)
  286. if point.x >= x1 and point.x < x2:
  287. x = i
  288. for j in range(0, self.__gameSize):
  289. j1 = self.getY(j)
  290. j2 = self.getY(j + 1)
  291. if point.y >= j1 and point.y < j2:
  292. y = j
  293. return Point(x, y)
  294. '''
  295. 创建一块蒙板,覆盖到选中的图形上
  296. '''
  297. def create_mask(self,x1, y1, x2, y2, **kwargs):
  298. #(0,0,0)代表黑色的RGB,127代表alpha透明度,(x2-x1, y2-y1)指长宽
  299. image = Image.new('RGBA', (x2-x1, y2-y1), (0,0,0,127))
  300. self.__images = [ImageTk.PhotoImage(image)]#这里一定要用一个实例变量存储,局部变量没有效果,原因不清楚
  301. self.canvas.create_image(x1, y1, image=self.__images[0], anchor='nw',tags = "image_mask")
  302. '''
  303. 选择的区域变红,point为内部坐标
  304. '''
  305. def drawSelectedArea(self, point):
  306. pointLT = self.getOuterLeftTopPoint(point)
  307. pointRB = self.getOuterLeftTopPoint(Point(point.x + 1, point.y + 1))
  308. self.canvas.create_rectangle(pointLT.x, pointLT.y, pointRB.x - 1, pointRB.y - 1, outline = 'red', tags = "rectRedOne", width=2)
  309. #蒙板
  310. self.create_mask(pointLT.x, pointLT.y, pointRB.x - 1, pointRB.y - 1, fill='skyblue', alpha=.5,width=0)
  311. '''
  312. 消除连通的两个块
  313. '''
  314. def ClearLinkedBlocks(self, p1, p2):
  315. self.__map[p1.y][p1.x] = self.EMPTY
  316. self.__map[p2.y][p2.x] = self.EMPTY
  317. self.canvas.delete('im%d%d' % (p1.x, p1.y))
  318. self.canvas.delete('im%d%d' % (p2.x, p2.y))
  319. '''
  320. 地图上该点是否为空
  321. '''
  322. def isEmptyInMap(self, point):
  323. return self.__map[point.y][point.x] == self.EMPTY
  324. '''
  325. 获取两个点连通类型
  326. '''
  327. def getLinkType(self, p1, p2):
  328. # 首先判断两个方块中图片是否相同
  329. if self.__map[p1.y][p1.x] != self.__map[p2.y][p2.x]:
  330. return { 'type': self.NONE_LINK }
  331. if self.isStraightLink(p1, p2):
  332. return {'type': self.STRAIGHT_LINK}
  333. res = self.isOneCornerLink(p1, p2)
  334. if res:
  335. return {'type': self.ONE_CORNER_LINK,'p1': res}
  336. res = self.isTwoCornerLink(p1, p2)
  337. if res:
  338. return {'type': self.TWO_CORNER_LINK,'p1': res['p1'],'p2': res['p2']}
  339. return {'type': self.NONE_LINK}
  340. '''
  341. 直连
  342. '''
  343. def isStraightLink(self, p1, p2):
  344. if p1.y == p2.y:
  345. start, end = sorted([p1.x, p2.x])
  346. for x in range(start + 1, end):
  347. if self.__map[p1.y][x] != self.EMPTY:
  348. return False
  349. elif p1.x == p2.x:
  350. start, end = sorted([p1.y, p2.y])
  351. for y in range(start + 1, end):
  352. if self.__map[y][p1.x] != self.EMPTY:
  353. return False
  354. else:
  355. return False
  356. return True
  357. def isOneCornerLink(self, p1, p2):
  358. pointCorner = Point(p1.x, p2.y)
  359. if self.isStraightLink(p1, pointCorner) and self.isStraightLink(pointCorner, p2) and self.isEmptyInMap(pointCorner):
  360. return pointCorner
  361. pointCorner = Point(p2.x, p1.y)
  362. if self.isStraightLink(p1, pointCorner) and self.isStraightLink(pointCorner, p2) and self.isEmptyInMap(pointCorner):
  363. return pointCorner
  364. '''
  365. 这可能是此文件最复杂的一个函数,for循环中的第一个数组是纵向比较(x不变),第二个是横向比较(y不变),合在一起可以减少代码量
  366. '''
  367. def isTwoCornerLink(self, p1, p2):
  368. for x1,y1,x2,y2 in [(p1.x, y, p2.x, y) for y in range(-1, self.__gameSize + 1)] + [(x, p1.y, x, p2.y) for x in range(-1, self.__gameSize + 1)]:
  369. pointCorner1 = Point(x1, y1)
  370. pointCorner2 = Point(x2, y2)
  371. if pointCorner1==p1 or pointCorner2==p2:continue #排除p1与p2两个点
  372. if y1 == -1 or y1 == self.__gameSize or x2 == -1 or x2 == self.__gameSize: #处理最外四条边上的双拐点
  373. if self.isStraightLink(p1, pointCorner1) and self.isStraightLink(pointCorner2, p2):
  374. return {'p1': pointCorner1, 'p2': pointCorner2}
  375. elif self.isStraightLink(p1, pointCorner1) and self.isStraightLink(pointCorner1, pointCorner2) and self.isStraightLink(pointCorner2, p2) and self.isEmptyInMap(pointCorner1) and self.isEmptyInMap(pointCorner2):
  376. return {'p1': pointCorner1, 'p2': pointCorner2}
  377. class Point():
  378. def __init__(self, x, y):
  379. self.x = x
  380. self.y = y
  381. def isUserful(self):
  382. return self.x >= 0 and self.y >= 0
  383. '''
  384. 判断两个点是否相同
  385. '''
  386. def isEqual(self, point):
  387. return self.x == point.x and self.y == point.y
  388. '''
  389. 克隆一份对象
  390. '''
  391. def clone(self):
  392. return Point(self.x, self.y)
  393. '''
  394. 改为另一个对象
  395. '''
  396. def changeTo(self, point):
  397. self.x = point.x
  398. self.y = point.y
  399. '''
  400. 显示坐标
  401. '''
  402. def __repr__(self):
  403. return f"x={self.x},y={self.y}"
  404. MainWindow()

改进版尽管功能更加强大,但有点复杂,如果初学,可以先看基础版。

增强版代码包(含图片、音效文件):

https://download.csdn.net/download/qiuqiuit/87895753

基础版代码包(包含图片): 

https://download.csdn.net/download/qiuqiuit/87895720

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

闽ICP备14008679号