您现在的位置是:首页 > 技术教程 正文

Python 一步一步教你用pyglet制作汉诺塔游戏(终篇)

admin 阅读: 2024-03-19
后台-插件-广告管理-内容页头部广告(手机)

 

目录

汉诺塔游戏

完整游戏

演示回放

完整代码


汉诺塔游戏

汉诺塔(Tower of Hanoi),是一个源于印度古老传说的益智玩具。这个传说讲述了大梵天创造世界的时候,他做了三根金刚石柱子,并在其中一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门将这些圆盘从下面开始按大小顺序重新摆放在另一根柱子上,并规定在小圆盘上不能放大圆盘,同时在三根柱子之间一次只能移动一个圆盘。当盘子的数量增加时,移动步骤的数量会呈指数级增长,圆盘数为n时,总步骤数steps为2^n - 1。

n = 64, steps = 2^64 - 1 = 18446744073709551616 ≈ 1.845 x 10^19

汉诺塔问题是一个递归问题,也可以使用非递归法来解决,例如使用栈来模拟递归过程。这个问题不仅是一个数学和逻辑问题,也是一个很好的教学工具,可以用来教授递归、算法和逻辑思考等概念。同时,汉诺塔游戏也具有一定的娱乐性,人们可以通过解决不同规模的汉诺塔问题来挑战自己的智力和耐心。

本篇接着上期讲下去,前2篇的链接地址:

Python 一步一步教你用pyglet制作汉诺塔游戏(续)-CSDN博客

Python 一步一步教你用pyglet制作汉诺塔游戏-CSDN博客


完整游戏

前2期代码的基础上,添加了完整的提示功能,一个汉诺塔游戏作品终于完工了,效果如下:

信息提示功能都放在了鼠标事件中:

@window.event
def on_mouse_press(x, y, dx, dy):
    global xy, hanns, gamecompleted
    if not hanns.success():
        pole = hanns.on_mouse_over(x, y)
        if pole is not None:
            xy.append(pole)
            if len(xy)==1:
                hanns.setdiskcolor(xy[0], (255,0,0))
                if not hanns.array[pole]:
                    hanns.setdiskcolor(xy[0])
                    xy.pop()
                    return
        if len(xy)==2:
            if xy[0]!=xy[1]:
                info = hanns.move(*xy)
                hanns.setdiskcolor(xy[0])
                if info is False:
                    info1.text = '起始圆盘大于目标位置的圆盘'
                elif info is None:
                    info1.text = '所选起始位置的塔架不能为空'
                else:
                    info1.text = f'{hanns.order-hanns.array[xy[1]][-1]}号圆盘从{xy[0]+1}号塔架移动到{xy[1]+1}号塔架'
            hanns.setdiskcolor(xy[0])
            xy.clear()
            info2.text = f'当前层数:{hanns.order}\t最佳步数:{2**hanns.order-1}\t当前步数:{hanns.steps}'
        if hanns.success():
            if hanns.order<24:
                info1.text = f'恭喜您完成 {hanns.order} 层汉诺塔!任意点击层数加一!'
            else:
                info1.text = f'太棒了!您已完成 {hanns.order} 层汉诺塔,游戏结束!'
                gamecompleted = True
                return
    elif not gamecompleted:
        hanns = Hann(window.width/2, 120, hanns.order+1)
        info1.text = f' {hanns.order} 层汉诺塔,游戏开始!'
        info2.text = f'当前层数:{hanns.order}\t最佳步数:{2**hanns.order-1}\t当前步数:{hanns.steps}'

Hann 类中增加一个改色的方法,用于标注被点击的要移动的源塔架:

  def setdiskcolor(self, n, color=Color[0]):
        self.disk[n].cir1.color = color
        self.disk[n].cir2.color = color
        self.disk[n].rect.color = color

完整代码: 

  1. import pyglet
  2. window = pyglet.window.Window(800, 500, caption='汉诺塔')
  3. pyglet.gl.glClearColor(1, 1, 1, 1)
  4. batch = pyglet.graphics.Batch()
  5. Color = (182,128,18),(25,65,160),(56,170,210),(16,188,78),(20,240,20),(240,240,20),(255,128,20),(240,20,20),(245,60,138)
  6. class Disk:
  7. def __init__(self, x, y, color=(0,0,0), width=200, height=20):
  8. self.cir1 = pyglet.shapes.Circle(x+width/2-height/2, y, radius=height/2, color=color, batch=batch)
  9. self.cir2 = pyglet.shapes.Circle(x-width/2+height/2, y, radius=height/2, color=color, batch=batch)
  10. self.rect = pyglet.shapes.Rectangle(x-width/2+height/2, y-height/2, width-height, height, color=color, batch=batch)
  11. def move(self, dx, dy):
  12. self.cir1.x += dx; self.cir1.y += dy
  13. self.cir2.x += dx; self.cir2.y += dy
  14. self.rect.x += dx; self.rect.y += dy
  15. class Hann:
  16. def __init__(self, x, y, order=2, space=250, thickness=20, width=200, height=300):
  17. assert(order>1)
  18. self.pole = [pyglet.shapes.Line(x-i*space, y, x-i*space, y+height, width=thickness, color=Color[0], batch=batch) for i in range(-1,2)]
  19. self.disk = [Disk(x+i*space, y, color=Color[0], width=width+thickness, height=thickness) for i in range(-1,2)]
  20. self.x, self.y = x, y
  21. self.order = order
  22. self.space = space
  23. self.thickness = thickness
  24. self.width = width
  25. self.poleheight = height
  26. self.beadheight = (height-thickness*2)/order
  27. self.step = (width-thickness)/(order+1)
  28. self.steps = 0
  29. self.macro = []
  30. coordinates = [(self.x-space, self.y+(i+1)*self.beadheight-(self.beadheight-thickness)/2) for i in range(order)]
  31. self.beads = [Disk(*xy, Color[i%8+1], width=self.width-i*self.step, height=self.beadheight) for i,xy in enumerate(coordinates)]
  32. self.array = [[*range(order)], [], []]
  33. def move(self, pole1, pole2):
  34. if self.array[pole1]:
  35. bead = self.array[pole1].pop()
  36. if self.array[pole2] and bead1]:
  37. self.array[pole1].append(bead)
  38. return False
  39. else:
  40. return None
  41. self.beads[bead].move((pole2-pole1)*self.space, (len(self.array[pole2])-len(self.array[pole1]))*self.beadheight)
  42. self.array[pole2].append(bead)
  43. self.steps += 1
  44. self.macro.append((pole1, pole2))
  45. return True
  46. def setdiskcolor(self, n, color=Color[0]):
  47. self.disk[n].cir1.color = color
  48. self.disk[n].cir2.color = color
  49. self.disk[n].rect.color = color
  50. def on_mouse_over(self, x, y):
  51. for i in range(-1,2):
  52. if hanns.x-hanns.width/2 < x-i*hanns.space < hanns.x+hanns.width/2 and hanns.y-hanns.thickness/2 < y < hanns.y+hanns.poleheight:
  53. return i+1
  54. def success(self):
  55. return len(self.array[2]) == self.order
  56. @window.event
  57. def on_draw():
  58. window.clear()
  59. batch.draw()
  60. @window.event
  61. def on_mouse_press(x, y, dx, dy):
  62. global xy, hanns, gamecompleted
  63. if not hanns.success():
  64. pole = hanns.on_mouse_over(x, y)
  65. if pole is not None:
  66. xy.append(pole)
  67. if len(xy)==1:
  68. hanns.setdiskcolor(xy[0], (255,0,0))
  69. if not hanns.array[pole]:
  70. hanns.setdiskcolor(xy[0])
  71. xy.pop()
  72. return
  73. if len(xy)==2:
  74. if xy[0]!=xy[1]:
  75. info = hanns.move(*xy)
  76. hanns.setdiskcolor(xy[0])
  77. if info is False:
  78. info1.text = '起始圆盘大于目标位置的圆盘'
  79. elif info is None:
  80. info1.text = '所选起始位置的塔架不能为空'
  81. else:
  82. info1.text = f'{hanns.order-hanns.array[xy[1]][-1]}号圆盘从{xy[0]+1}号塔架移动到{xy[1]+1}号塔架'
  83. hanns.setdiskcolor(xy[0])
  84. xy.clear()
  85. info2.text = f'当前层数:{hanns.order}\t最佳步数:{2**hanns.order-1}\t当前步数:{hanns.steps}'
  86. if hanns.success():
  87. if hanns.order<24:
  88. info1.text = f'恭喜您完成 {hanns.order} 层汉诺塔!任意点击层数加一!'
  89. else:
  90. info1.text = f'太棒了!您已完成 {hanns.order} 层汉诺塔,游戏结束!'
  91. gamecompleted = True
  92. return
  93. elif not gamecompleted:
  94. hanns = Hann(window.width/2, 120, hanns.order+1)
  95. info1.text = f' {hanns.order} 层汉诺塔,游戏开始!'
  96. info2.text = f'当前层数:{hanns.order}\t最佳步数:{2**hanns.order-1}\t当前步数:{hanns.steps}'
  97. xy = []
  98. order = 2
  99. hanns = Hann(window.width/2, 120, order)
  100. info1 = pyglet.text.Label('操作方法:鼠标先后点击起始和目标位置就能移动圆盘', font_size=21, color=(0,0,0,255), x=window.width/2, y=50, anchor_x='center', batch=batch)
  101. info2 = pyglet.text.Label(f'当前层数:{order}\t最佳步数:{2**order-1}\t当前步数:0', font_size=18, color=(0,0,0,255), x=80, y=450, batch=batch)
  102. gamecompleted = False
  103. pyglet.app.run()

演示回放

优化代码,布局稍作调整,并加入帮助、演示和回放等功能。演示和回放的区别:演示功能是提供如何移动的教学帮助;回放功能则是展示用户自己的移动过程。

运行效果如下:

完整代码

  1. import pyglet
  2. from pyglet.window import key
  3. Width, Height = 800, 500
  4. window = pyglet.window.Window(Width, Height, caption='Tower of Hanoi')
  5. screen = pyglet.canvas.Display().get_default_screen()
  6. window.set_location((screen.width-Width)//2, (screen.height-Height)//2)
  7. pyglet.gl.glClearColor(0.78, 0.88, 0.98, 1)
  8. batch = pyglet.graphics.Batch()
  9. group = pyglet.graphics.Group()
  10. Color = (182,128,18),(25,65,160),(56,170,210),(16,188,78),(20,240,20),(240,240,20),(255,128,20),(240,20,20),(245,60,138)
  11. class Disk:
  12. def __init__(self, x, y, color=(0,0,0), width=200, height=20):
  13. self.cir1 = pyglet.shapes.Circle(x+width/2-height/2, y, radius=height/2, color=color, batch=batch)
  14. self.cir2 = pyglet.shapes.Circle(x-width/2+height/2, y, radius=height/2, color=color, batch=batch)
  15. self.rect = pyglet.shapes.Rectangle(x-width/2+height/2, y-height/2, width-height, height, color=color, batch=batch)
  16. def move(self, dx, dy):
  17. self.cir1.x += dx; self.cir1.y += dy
  18. self.cir2.x += dx; self.cir2.y += dy
  19. self.rect.x += dx; self.rect.y += dy
  20. class Hann:
  21. def __init__(self, order=2, x=window.width/2, y=110, space=250, thickness=20, width=200, height=300):
  22. assert(order>1)
  23. self.pole = [pyglet.shapes.Line(x+i*space, y, x+i*space, y+height, width=thickness, color=Color[0], batch=batch) for i in range(-1,2)]
  24. self.disk = [Disk(x+i*space, y, color=Color[0], width=width+thickness, height=thickness) for i in range(-1,2)]
  25. self.x, self.y = x, y
  26. self.order = order
  27. self.ordermax = 24
  28. self.space = space
  29. self.thickness = thickness
  30. self.width = width
  31. self.poleheight = height
  32. self.beadheight = (height-thickness*2)/order
  33. self.step = (width-thickness)/(order+1)
  34. self.steps = 0
  35. self.macro = []
  36. self.posxy = []
  37. self.click = False
  38. self.ctrlz = False
  39. self.playback = False
  40. coordinates = [(self.x-space, self.y+(i+1)*self.beadheight-(self.beadheight-thickness)/2) for i in range(order)]
  41. self.beads = [Disk(*pos, Color[i%8+1], width=self.width-i*self.step, height=self.beadheight) for i,pos in enumerate(coordinates)]
  42. self.array = [[*range(order)], [], []]
  43. self.arraymacro = []
  44. def move(self, pole1, pole2):
  45. if self.array[pole1]:
  46. bead = self.array[pole1].pop()
  47. if self.array[pole2] and bead1]:
  48. self.array[pole1].append(bead)
  49. return False
  50. else:
  51. return None
  52. self.beads[bead].move((pole2-pole1)*self.space, (len(self.array[pole2])-len(self.array[pole1]))*self.beadheight)
  53. self.array[pole2].append(bead)
  54. self.steps += 1
  55. self.click = True
  56. self.macro.append((pole1, pole2))
  57. self.arraymacro.append([array[:] for array in self.array])
  58. return True
  59. def set_color(self, n, color=Color[0]):
  60. self.click = color==Color[0]
  61. self.disk[n].cir1.color = color
  62. self.disk[n].cir2.color = color
  63. self.disk[n].rect.color = color
  64. self.pole[n].color = color
  65. def on_mouse_over(self, x, y):
  66. for i in range(-1,2):
  67. if hanns.x-hanns.width/2 < x-i*hanns.space < hanns.x+hanns.width/2 and hanns.y-hanns.thickness/2 < y < hanns.y+hanns.poleheight:
  68. return i+1
  69. def success(self):
  70. return len(self.array[2]) == self.order
  71. def hanoi(n, start=0, mid=1, end=2, moves=None):
  72. if moves is None:
  73. moves = []
  74. if n == 1:
  75. moves.append((start, end))
  76. else:
  77. hanoi(n-1, start, end, mid, moves)
  78. moves.append((start, end))
  79. hanoi(n-1, mid, start, end, moves)
  80. return moves
  81. def hanoimacro(n, start=0, mid=1, end=2, moves=None):
  82. global macro, array
  83. if moves is None:
  84. moves = []
  85. array = [[*range(n)],[],[]]
  86. macro = [[[*range(n)],[],[]]]
  87. if n == 1:
  88. moves.append((start, end))
  89. array[end].append(array[start].pop())
  90. macro.append([disk[:] for disk in array])
  91. else:
  92. hanoimacro(n-1, start, end, mid, moves)
  93. moves.append((start, end))
  94. array[end].append(array[start].pop())
  95. macro.append([disk[:] for disk in array])
  96. hanoimacro(n-1, mid, start, end, moves)
  97. return macro
  98. @window.event
  99. def on_draw():
  100. window.clear()
  101. batch.draw()
  102. @window.event
  103. def on_mouse_press(x, y, dx, dy):
  104. global hanns, gamestarting, gamecompleted
  105. if not gamestarting:
  106. gamestarting = True
  107. show_message(False)
  108. return
  109. elif hanns.playback:
  110. return
  111. if not hanns.success():
  112. pole = hanns.on_mouse_over(x, y)
  113. if pole is not None:
  114. hanns.posxy.append(pole)
  115. if len(hanns.posxy)==1:
  116. hanns.set_color(hanns.posxy[0], (200,150,0))
  117. if not hanns.array[pole]:
  118. hanns.set_color(hanns.posxy[0])
  119. hanns.posxy.pop()
  120. return
  121. if len(hanns.posxy)==2:
  122. if hanns.posxy[0]!=hanns.posxy[1]:
  123. info = hanns.move(*hanns.posxy)
  124. hanns.set_color(hanns.posxy[0])
  125. if info:
  126. info1.text = information(3)
  127. else:
  128. info1.text = information(6)
  129. hanns.set_color(hanns.posxy[0])
  130. hanns.posxy.clear()
  131. info2.text = information()
  132. if hanns.success():
  133. if hanns.order<24:
  134. info1.text = information(4)
  135. else:
  136. info1.text = information(5)
  137. gamecompleted = True
  138. return
  139. elif not gamecompleted:
  140. hanns = Hann(hanns.order+1)
  141. info1.text = information(0)
  142. info2.text = information(2)
  143. @window.event
  144. def on_key_press(symbol, modifiers):
  145. global hanns, macro, gamestarting
  146. def show_how(event):
  147. if macro:
  148. hanns.move(*macro.pop(0))
  149. info2.text = information()
  150. else:
  151. info1.text = info1.text.replace('中......', '完成!')
  152. hanns.playback = False
  153. pyglet.clock.unschedule(show_how)
  154. if not gamestarting:
  155. gamestarting = True
  156. show_message(False)
  157. return
  158. if hanns.playback:
  159. if symbol in (key.B, key.BREAK) and modifiers & key.MOD_CTRL:
  160. macro.clear()
  161. info1.text = info1.text.replace('中......', '终止,')+'游戏继续!'
  162. return
  163. if hanns.macro and hanns.click and not hanns.success() and symbol==key.Z and modifiers & key.MOD_CTRL:
  164. hanns.ctrlz = not hanns.ctrlz
  165. hanns.posxy = hanns.macro.pop()[::-1]
  166. info = hanns.move(*hanns.posxy)
  167. hanns.steps -= 2*hanns.ctrlz
  168. hanns.posxy = []
  169. elif symbol == key.H and modifiers & key.MOD_CTRL:
  170. gamestarting = False
  171. show_message(True)
  172. elif symbol == key.R and modifiers & key.MOD_CTRL:
  173. if hanns.steps:
  174. hanns = Hann(hanns.order)
  175. info1.text = information(0)
  176. elif symbol == key.N and modifiers & (key.MOD_CTRL | key.MOD_SHIFT) and modifiers & key.MOD_SHIFT:
  177. if hanns.order>2:
  178. hanns = Hann(hanns.order-1)
  179. info1.text = information(0)
  180. else:
  181. info1.text = information(7)
  182. elif symbol == key.N and modifiers & key.MOD_CTRL and not modifiers & key.MOD_SHIFT:
  183. if hanns.order<24:
  184. hanns = Hann(hanns.order+1)
  185. info1.text = information(0)
  186. else:
  187. info1.text = information(8)
  188. elif symbol in (key.S, key.P) and modifiers & key.MOD_CTRL:
  189. macro = hanns.macro
  190. hanns = Hann(hanns.order)
  191. hanns.playback = True
  192. if symbol==key.S:
  193. task = '演示中......'
  194. macro = hanoi(hanns.order)
  195. elif symbol==key.P:
  196. task = '回放中......'
  197. info1.text = f'{hanns.order} 层汉诺塔{task}'
  198. pyglet.clock.schedule_interval(show_how, 9/(hanns.order**2+9))
  199. info2.text = information()
  200. def show_message(visible=True):
  201. box1.visible = box2.visible = html.visible = visible
  202. info1.text = information(0)
  203. def messagebox():
  204. global box1, box2, html
  205. rect = 150, 90, 500, 330
  206. box1 = pyglet.shapes.Rectangle(*rect, color=(255,255,255,200), batch=batch, group=group)
  207. box2 = pyglet.shapes.Box(*rect, color=(255,0,0,255), thickness=3, batch=batch, group=group)
  208. html = pyglet.text.HTMLLabel(text=hypertext, multiline=True, width=Width//2, x=Width//2, y=240, batch=batch, group=group)
  209. html.anchor_x = html.anchor_y = 'center'
  210. def information(info=2):
  211. if info==0:
  212. return f'{hanns.order} 层汉诺塔,游戏开始!'
  213. elif info==1:
  214. return '点击任意键开始......'
  215. elif info==2:
  216. return f'当前层数:{hanns.order:<12}最佳步数:{2**hanns.order-1:<12}当前步数:{hanns.steps:>9}'
  217. elif info==3:
  218. return f'{hanns.order-hanns.array[hanns.posxy[1]][-1]}号圆盘从{hanns.posxy[0]+1}号塔架移动到{hanns.posxy[1]+1}号塔架'
  219. elif info==4:
  220. return f'恭喜您完成 {hanns.order} 层汉诺塔!任意点击层数加一!'
  221. elif info==5:
  222. return f'太棒了!您已完成 {hanns.order} 层汉诺塔,游戏全部通关!'
  223. elif info==6:
  224. return '起始圆盘大于目标位置的圆盘'
  225. elif info==7:
  226. return f'程序设置的最小层数是 2'
  227. elif info==8:
  228. return f'程序设置的最大层数是 {hanns.ordermax}'
  229. hanns = Hann(order=2)
  230. hypertext = '''汉诺塔 Tower of Hanoi

  231. 汉诺塔是一个源于印度的古老传说的益智玩具。这个传说
    讲述了大梵天创造世界的时候做了三根金刚石柱子,并在
  232. 一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大
    梵天命令婆罗门将这些圆盘按照大小顺序重新摆放到另一
  233. 根柱子上(左边搬到右边,中间临时存放),规定大圆盘不
    能放在小圆盘上,且一次只能移动一个圆盘。


  234. 操作方法:先后点击塔架,就能移动塔架上的圆盘

  235. Ctrl+Z 取消操作    Ctrl+R 重新开始
    Ctrl+S 自动演示    Ctrl+P 操作回放
  236. Ctrl+B 终止操作    Ctrl+H 帮助消息
    Ctrl+N 增减层数(同时按Shift键为减)
    '''
  237. info1 = pyglet.text.Label(information(1), font_size=21, color=(0,0,0,255), x=400, y=40, anchor_x='center', batch=batch)
  238. info2 = pyglet.text.Label(information(2), font_size=16, font_name='宋体', color=(0,0,0,255), x=70, y=450, batch=batch)
  239. gamestarting, gamecompleted = False, False
  240. messagebox()
  241. pyglet.app.run()

其中记录移动的宏暂未用到,进一步开发会用到:

def hanoimacro(n, start=0, mid=1, end=2, moves=None):
    global macro, array
    if moves is None:
        moves = []
        array = [[*range(n)],[],[]]
        macro = [[[*range(n)],[],[]]]
    if n == 1:
        moves.append((start, end))
        array[end].append(array[start].pop())
        macro.append([disk[:] for disk in array])
    else:
        hanoimacro(n-1, start, end, mid, moves)
        moves.append((start, end))
        array[end].append(array[start].pop())
        macro.append([disk[:] for disk in array])
        hanoimacro(n-1, mid, start, end, moves)
    return macro

标签:
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

在线投稿:投稿 站长QQ:1888636

后台-插件-广告管理-内容页尾部广告(手机)
关注我们

扫一扫关注我们,了解最新精彩内容

搜索
排行榜