当前位置:   article > 正文

Python 一步一步教你用pyglet制作“彩色方块连连看”游戏

Python 一步一步教你用pyglet制作“彩色方块连连看”游戏

目录

彩色方块连连看

第一步

第二步

第三步

第四步

第五步

第六步

第七步

动态效果展示

小结


彩色方块连连看

本篇除了介绍怎样用pyglet制作连连看游戏,还将介绍如果使用自定义库colorlib,用它来描绘游戏中多种颜色的彩色方块。自定义库colorlib的由来,另请阅读《python 教你如何创建一个自定义库 colorlib.py》,链接:

https://hannyang.blog.csdn.net/article/details/136861675

第一步

见原文中,有一个创建随机彩色霓虹方块的代码:

  1. import pyglet
  2. from colorlib import randcolorTuples as ctpl
  3. window = pyglet.window.Window(800, 600, caption='色块展示')
  4. color = ctpl(16)
  5. batch = pyglet.graphics.Batch()
  6. shape = [pyglet.shapes.Rectangle(180+i%4*120, 120+i//4*100, 100, 80, color=color[i], batch=batch) for i in range(len(color))]
  7. @window.event
  8. def on_draw():
  9. window.clear()
  10. batch.draw()
  11. def change_color(e):
  12. for i,c in enumerate(ctpl(16)):
  13. shape[i].color = c
  14. pyglet.clock.schedule_interval(change_color, 0.1)
  15. pyglet.app.run()

运行效果:

第二步

创建更多方块,方块大小由参数决定;随机色由colorlib.randcolorTuples函数产生。

  1. from pyglet import *
  2. from colorlib import randcolorTuples as ctpl
  3. W, H = 800, 600
  4. window = window.Window(W, H, caption='色块展示')
  5. batch = graphics.Batch()
  6. row, col, space = 8, 10, 5
  7. width = w = (W-space)//col-space
  8. height = h = (H-space)//row-space
  9. x0, y0 = (W-(w+space)*col+space)//2, (H-(h+space)*row+space)//2
  10. color = ctpl(row*col)
  11. shape = [[1]*col for _ in range(row)]
  12. for r,arr in enumerate(shape):
  13. for c,num in enumerate(arr):
  14. shape[r][c] = shapes.Rectangle(x0+c*(w+space), y0+r*(h+space), w, h, color=color[c+r*len(arr)], batch=batch)
  15. @window.event
  16. def on_draw():
  17. window.clear()
  18. batch.draw()
  19. def change_color(e):
  20. for i,c in enumerate(ctpl(row*col)):
  21. shape[i//col][i%col].color = c
  22. clock.schedule_interval(change_color, 0.1)
  23. app.run()

第三步

取消“闪烁”,方块增加边框并改写为Box类、增加一个对应存放方块状态的列表Array。

  1. from pyglet import *
  2. from colorlib import *
  3. ctpl = randcolorTuples
  4. W, H = 800, 600
  5. window = window.Window(W, H, caption='色块展示')
  6. gl.glClearColor(*Color('lightblue3').decimal)
  7. batch = graphics.Batch()
  8. row, col, space = 8, 10, 5
  9. width = w = (W-space)//col-space
  10. height = h = (H-space)//row-space
  11. x0, y0 = (W-(w+space)*col+space)//2, (H-(h+space)*row+space)//2
  12. color = ctpl(row*col)
  13. Array = [[1]*col for _ in range(row)] # 用于控制方块的状态
  14. class Box:
  15. def __init__(self, x, y, w, h, color, batch=batch):
  16. self.rect = shapes.Rectangle(x, y, w, h, color=color, batch=batch)
  17. self.box = shapes.Box(x, y, w, h, color=(255,255,255), thickness=3, batch=batch)
  18. def visible(self, visible=True):
  19. self.box.batch = self.rect.batch = batch if visible else None
  20. Array[0][0] = Array[2][2] = Array[5][7] = 0 # 设置三个方块消失
  21. for r,arr in enumerate(Boxes:=[_[:] for _ in Array]):
  22. for c,_ in enumerate(arr):
  23. Boxes[r][c] = Box(x0+c*(w+space), y0+r*(h+space), w, h, color[c+r*len(arr)])
  24. Boxes[r][c].visible(Array[r][c])
  25. @window.event
  26. def on_draw():
  27. window.clear()
  28. batch.draw()
  29. app.run()

运行效果:

注:顺带测试一下在for...in...语句enumerate()函数中使用海象操作符:=。

for r,arr in enumerate(Boxes:=[_[:] for _ in Array]):

第四步

限制生产颜色时,每种颜色只能出现4次,即2组连连看。

COLOR = []
while len(COLOR)<row*col//4:
    if (c:=randcolorTuple()) not in COLOR:
        COLOR.append(c)
COLOR = sample(COLOR*4, row*col

  1. from pyglet import *
  2. from colorlib import *
  3. ctpl = randcolorTuples
  4. W, H = 800, 600
  5. window = window.Window(W, H, caption='色块展示')
  6. gl.glClearColor(*Color('lightblue3').decimal)
  7. batch = graphics.Batch()
  8. row, col, space = 8, 10, 5
  9. width = w = (W-space)//col-space
  10. height = h = (H-space)//row-space
  11. x0, y0 = (W-(w+space)*col+space)//2, (H-(h+space)*row+space)//2
  12. COLOR = []
  13. while len(COLOR)<row*col//4:
  14. if (c:=randcolorTuple()) not in COLOR:
  15. COLOR.append(c)
  16. COLOR = sample(COLOR*4, row*col)
  17. class Box:
  18. def __init__(self, x, y, w, h, color, batch=batch):
  19. self.rect = shapes.Rectangle(x, y, w, h, color=color, batch=batch)
  20. self.box = shapes.Box(x, y, w, h, color=(255,255,255), thickness=3, batch=batch)
  21. self.visible = True
  22. def visible(self, visible=True):
  23. self.box.batch = self.rect.batch = batch if visible else None
  24. Array, Boxes = [[[1]*col for _ in range(row)] for _ in range(2)]
  25. for r,arr in enumerate(Boxes):
  26. for c,_ in enumerate(arr):
  27. Boxes[r][c] = Box(x0+c*(w+space), y0+r*(h+space), w, h, COLOR[c+r*len(arr)])
  28. @window.event
  29. def on_draw():
  30. window.clear()
  31. batch.draw()
  32. app.run()

运行效果: 

第五步

增加Game类,并加入点击坐标和状态,可以记录方块的位置和状态。

方块的行列坐标与窗口中实际坐标的转换:

r, c = (y-y0)//(h+space), (x-x0)//(w+space)

  1. from pyglet import *
  2. from colorlib import *
  3. W, H = 800, 600
  4. window = window.Window(W, H, caption='色块展示')
  5. gl.glClearColor(*Color('lightblue3').decimal)
  6. batch = graphics.Batch()
  7. row, col, space = 8, 10, 5
  8. width = w = (W-space)//col-space
  9. height = h = (H-space)//row-space
  10. x0, y0 = (W-(w+space)*col+space)//2, (H-(h+space)*row+space)//2
  11. COLOR = []
  12. while len(COLOR)<row*col//4:
  13. if (c:=randcolorTuple()) not in COLOR:
  14. COLOR.append(c)
  15. COLOR = sample(COLOR*4, row*col)
  16. Array, Boxes = [[[[11]]*col for _ in range(row)] for _ in range(2)]
  17. class Box:
  18. def __init__(self, x, y, w, h, color, batch=batch):
  19. self.x, self.y = x, y
  20. self.w, self.h = w, h
  21. self.visible, self.pressed = True, False
  22. self.rect = shapes.Rectangle(x, y, w, h, color=color, batch=batch)
  23. self.box = shapes.Box(x, y, w, h, color=(255,255,255), thickness=3, batch=batch)
  24. def show(self, visible=True):
  25. self.visible, self.pressed = visible, False
  26. self.box.batch = self.rect.batch = batch if visible else None
  27. def hide(self):
  28. self.show(False)
  29. def on_mouse_over(self, x, y):
  30. return self.x<=x<=self.x+self.w and self.y<=y<=self.y+self.h
  31. def on_mouse_click(self):
  32. if self.visible:
  33. self.pressed = True
  34. if Color('RED').equal(self.box.color):
  35. self.hide()
  36. else:
  37. self.pressed = False
  38. self.show()
  39. self.box.color = Color('RED' if self.pressed else 'WHITE').rgba
  40. for r,arr in enumerate(Boxes):
  41. for c,_ in enumerate(arr):
  42. Boxes[r][c] = Box(x0+c*(w+space), y0+r*(h+space), w, h, COLOR[c+r*len(arr)])
  43. class Game:
  44. def __init__(self):
  45. self.array = Array
  46. self.boxes = Boxes
  47. def on_mouse_click(self, x, y):
  48. r, c = (y - y0)//(h+space), (x - x0)//(w+space)
  49. if r in range(row) and c in range(col) and self.boxes[r][c].on_mouse_over(x, y):
  50. self.boxes[r][c].on_mouse_click()
  51. self.array[r][c] = self.boxes[r][c].visible*10+self.boxes[r][c].pressed
  52. return r, c, self.array[r][c]
  53. game = Game()
  54. @window.event
  55. def on_mouse_press(x, y, dx, dy):
  56. window.set_caption(f'色块展示——坐标和状态:{game.on_mouse_click(x, y)}')
  57. @window.event
  58. def on_draw():
  59. window.clear()
  60. batch.draw()
  61. app.run()

运行效果: 

第六步

增加连线功能,用shapes.Line画一根淡金色的直线来表示:

self.line = shapes.Line(0, 0, 0, 0, width=5, color=Color('light gold').rgba)

再增加一个属性self.click来存储一组连线方块的坐标;增加方法getxy(),以获取方块在窗口中的实际坐标:

    def getxy(self, row, col):
        return x0+col*(w+space)+w//2, y0+row*(h+space)+h//2

  1. from pyglet import *
  2. from colorlib import *
  3. W, H = 800, 600
  4. window = window.Window(W, H, caption='彩色色块连连看')
  5. gl.glClearColor(*Color('lightblue3').decimal)
  6. batch = graphics.Batch()
  7. group = graphics.Group()
  8. row, col, space = 8, 10, 5
  9. width = w = (W-space)//col-space
  10. height = h = (H-space)//row-space
  11. x0, y0 = (W-(w+space)*col+space)//2, (H-(h+space)*row+space)//2
  12. COLOR = []
  13. while len(COLOR)<row*col//4:
  14. if (c:=randcolorTuple()) not in COLOR:
  15. COLOR.append(c)
  16. COLOR = sample(COLOR*4, row*col)
  17. Array, Boxes = [[[1]*col for _ in range(row)] for _ in range(2)]
  18. class Box:
  19. def __init__(self, x, y, w, h, color, batch=batch):
  20. self.x, self.y = x, y
  21. self.w, self.h = w, h
  22. self.pressed = False
  23. self.rect = shapes.Rectangle(x, y, w, h, color=color, batch=batch)
  24. self.box = shapes.Box(x, y, w, h, color=Color('WHITE').rgba, thickness=3, batch=batch)
  25. self.box.group = group
  26. def hide(self):
  27. self.box.batch = self.rect.batch = None
  28. def on_mouse_over(self, x, y):
  29. return self.x<=x<=self.x+self.w and self.y<=y<=self.y+self.h
  30. for r,arr in enumerate(Boxes):
  31. for c,_ in enumerate(arr):
  32. Boxes[r][c] = Box(x0+c*(w+space), y0+r*(h+space), w, h, COLOR[c+r*len(arr)])
  33. class Game:
  34. def __init__(self):
  35. self.array = Array
  36. self.boxes = Boxes
  37. self.click = []
  38. self.last = None
  39. self.row, self.col = 0, 0
  40. self.line = shapes.Line(0, 0, 0, 0, width=5, color=Color('light gold').rgba)
  41. self.line.batch = batch
  42. self.line.group = group
  43. self.line.visible = False
  44. def on_mouse_click(self, x, y):
  45. if self.line.visible: return
  46. r, c = (y-y0)//(h+space), (x-x0)//(w+space)
  47. if r in range(row) and c in range(col) and self.boxes[r][c].on_mouse_over(x, y) and self.array[r][c]:
  48. self.row, self.col = r, c
  49. if len(self.click)==0:
  50. self.click.append((r,c))
  51. self.last = r, c, self.boxes[r][c]
  52. self.boxes[r][c].box.color = Color('RED').rgba
  53. elif len(self.click)==1:
  54. if (r,c) in self.click:
  55. r,c = self.click.pop()
  56. self.boxes[r][c].box.color = Color('WHITE').rgba
  57. else:
  58. self.click.append((r,c))
  59. self.boxes[r][c].box.color = Color('RED').rgba
  60. r2, c2 = self.click[0][0], self.click[0][1]
  61. self.line.x, self.line.y = self.getxy(r, c)
  62. self.line.x2, self.line.y2 = self.getxy(r2, c2)
  63. self.array[r2][c2] = self.array[r][c] = 0
  64. self.line.visible = True
  65. self.click.clear()
  66. clock.schedule_interval(self.update, 0.3)
  67. return r, c, self.array[r][c]
  68. def getxy(self, row, col):
  69. return x0+col*(w+space)+w//2, y0+row*(h+space)+h//2
  70. def update(self, event):
  71. self.line.visible = False
  72. one, another = self.last, self.boxes[self.row][self.col]
  73. if one[-1].rect.color==another.rect.color:
  74. one[-1].hide(); another.hide()
  75. self.last = None
  76. else:
  77. self.array[self.row][self.col] = self.array[one[0]][one[1]] = 1
  78. one[-1].box.color = another.box.color = Color('WHITE').rgba
  79. clock.unschedule(self.update)
  80. @window.event
  81. def on_mouse_press(x, y, dx, dy):
  82. window.set_caption(f'彩色色块连连看——坐标和状态:{game.on_mouse_click(x, y)}')
  83. @window.event
  84. def on_draw():
  85. window.clear()
  86. batch.draw()
  87. game = Game()
  88. app.run()

运行效果:

第七步

继续优化代码,放弃self.click记录方块的坐标,改为直接用self.last和self.last2来记录一组方块,同样也能操作它们的位置和状态;增加判断任务完成的方法。

    def success(self):
        return sum(sum(self.array,[]))==0 

再增加一个记录行列坐标的类,方便代码的书写:

class RC:
    def __init__(self, x=0, y=0):
        self.r, self.c = x, y
    def __eq__(self, other):
        return self.rc == other.rc
    @property
    def rc(self):
        return self.r, self.c

  1. from pyglet import *
  2. from colorlib import *
  3. W, H = 800, 600
  4. window = window.Window(W, H, caption='彩色色块连连看')
  5. gl.glClearColor(*Color('lightblue3').decimal)
  6. batch, group = graphics.Batch(),graphics.Group()
  7. row, col, space = 8, 10, 5
  8. w, h = W//col-space*2, H//row-space*2
  9. x0, y0 = (W-(w+space)*col+space)//2, (H-(h+space)*row+space)//2
  10. COLOR = []
  11. while len(COLOR)<row*col//4:
  12. if (c:=randcolorTuple()) not in COLOR:
  13. COLOR.append(c)
  14. COLOR = sample(COLOR*4, row*col)
  15. Array, Boxes = [[[1]*col for _ in range(row)] for _ in range(2)]
  16. class Box:
  17. def __init__(self, x, y, w, h, color, batch=batch):
  18. self.x, self.y, self.w, self.h = x, y, w, h
  19. self.rect = shapes.Rectangle(x, y, w, h, color=color, batch=batch)
  20. self.box = shapes.Box(x, y, w, h, color=Color('WHITE').rgba, thickness=3, batch=batch)
  21. self.box.group = group
  22. def hide(self):
  23. self.box.batch = self.rect.batch = None
  24. def on_mouse_over(self, x, y):
  25. return self.x<=x<=self.x+self.w and self.y<=y<=self.y+self.h
  26. for r,arr in enumerate(Boxes):
  27. for c,_ in enumerate(arr):
  28. Boxes[r][c] = Box(x0+c*(w+space), y0+r*(h+space), w, h, COLOR[c+r*len(arr)])
  29. class RC:
  30. def __init__(self, x=0, y=0):
  31. self.x, self.y = x, y
  32. def __eq__(self, other):
  33. return self.rc == other.rc
  34. @property
  35. def rc(self):
  36. return self.x, self.y
  37. class Game:
  38. def __init__(self):
  39. self.array = Array
  40. self.boxes = Boxes
  41. self.rc, self.rc2 = RC(), RC()
  42. self.last, self.last2 = None, None
  43. self.line = shapes.Line(0, 0, 0, 0, width=5, color=Color('light gold').rgba, batch=batch, group=group)
  44. self.line.visible = False
  45. def on_mouse_click(self, x, y):
  46. if self.line.visible or self.success(): return
  47. r, c = (y-y0)//(h+space), (x-x0)//(w+space)
  48. if r in range(row) and c in range(col) and self.boxes[r][c].on_mouse_over(x, y) and self.array[r][c]:
  49. if self.last is None and self.last2 is None:
  50. self.rc, self.last = RC(r, c), self.boxes[r][c]
  51. self.last.box.color = Color('RED').rgba
  52. elif self.last is not None and self.last2 is None:
  53. self.rc2, self.last2 = RC(r, c), self.boxes[r][c]
  54. self.last2.box.color = Color('RED').rgba
  55. if self.rc == self.rc2:
  56. self.last.box.color = Color('WHITE').rgba
  57. self.last, self.last2 = None, None
  58. else:
  59. self.line.x, self.line.y = self.getxy(r, c)
  60. self.line.x2, self.line.y2 = self.getxy(self.rc.x, self.rc.y)
  61. self.line.visible = True
  62. clock.schedule_interval(self.update, 0.3)
  63. return (r, c), Color(self.boxes[r][c].rect.color).name
  64. def getxy(self, row, col):
  65. return x0+col*(w+space)+w//2, y0+row*(h+space)+h//2
  66. def update(self, event):
  67. self.line.visible = False
  68. clock.unschedule(self.update)
  69. if self.last.rect.color==self.last2.rect.color:
  70. self.last.hide(); self.last2.hide()
  71. self.array[self.rc.x][self.rc.y] = self.array[self.rc2.x][self.rc2.y] = 0
  72. else:
  73. self.last.box.color = self.last2.box.color = Color('WHITE').rgba
  74. self.last, self.last2 = None, None
  75. if game.success():
  76. window.set_caption('彩色色块连连看——任务完成!')
  77. def success(self):
  78. return sum(sum(self.array,[]))==0
  79. @window.event
  80. def on_draw():
  81. window.clear()
  82. batch.draw()
  83. @window.event
  84. def on_mouse_press(x, y, dx, dy):
  85. ret = game.on_mouse_click(x, y)
  86. if ret and not game.success():
  87. window.set_caption(f'彩色色块连连看——坐标:{ret[0]} 颜色:{ret[1]}')
  88. game = Game()
  89. app.run()

运行效果:

动态效果展示

为节省截图时间把第9行代码的8行10列暂改为4行5列。

小结

至此,一个代码不到100行的简单的“彩色方块连连看”游戏完成了。下一期文章将继续探讨“连连看”的直线规则,即不能有斜线连接方块,只能由水平和垂直的直线相连方块且直线最多转弯2次。

待续......


附录

colorlib库Color类的主代码介绍

class Color:
    '''
    Color(3-tuple: tuple) -> Color # 3-tuple as (r, g, b)
    Color(4-tuple: tuple) -> Color # 4-tuple as (r, g, b, a)
    Color(color_name: str) -> Color # color_name as 'Red','Blue',...
    Color(color_string: str) -> Color # color_string as '#rrggbb'
    Object for color representations.
    '''
    def __init__(self, r=0, g=0, b=0, a=None): 
        self.__alpha = (a is not None) and isinstance(a, (int, float))
        if all(map(lambda r:isinstance(r, (int, float)),(r,g,b))):
            self.r, self.g, self.b = map(lambda n:int(n)%256,(r,g,b))
        elif isinstance(r, (tuple, list)) and len(r) in (3, 4):
            self.r, self.g, self.b, self.a = *[int(c)%256 for c in r[:3]], 255
            if len(r)==4: a, self.__alpha = int(r[3])%256, True 
        elif isinstance(r, str) and len(r)==7 and r.startswith('#'):  
            self.r, self.g, self.b = str2tuple(r)  
        elif isinstance(r, str):
            if (rgb := ColorDict.get(r, None)) is None:
                raise ValueError("Invalid Color Name")
            self.r, self.g, self.b, a = *rgb, 255
        else:
            raise ValueError("Invalid argument for class Color")
        self.a = a if self.__alpha else 255
        self.rgb = self.r, self.g, self.b
        self.rgba = self.r, self.g, self.b, self.a
        self.value = self.rgba if self.__alpha else self.rgb
        self.string = tuple2str(self.value[:3])
        self.decimal = tuple(map(lambda x:x/255, self.rgba))
        self.name = {v:k for k,v in ColorDict.items()}.get(self.rgb, 'Noname')
    def __repr__(self):
        rgba = 'RGBA(' if self.__alpha else 'RGB('
        return ', '.join(map(lambda x:str(x).rjust(3),self.value)).join((rgba,')'))
    def randcolor(self):
        '''Convert the Color to any random color in ColorDict.keys().'''
        rgb = randcolorTuple()
        return Color(*rgb, self.a) if self.__alpha else Color(rgb)
    def random(self):
        '''Convert rgb to a 3-tuple of random integer between 0 and 255.'''
        rgb = randint(0,255), randint(0,255), randint(0,255)
        return Color(*rgb, self.a) if self.__alpha else Color(rgb)
    def alpha(self, a=255):
        '''Set alpha value of the Color, or change RGB to RGBA.'''
        self.__init__(*self.rgb, a)
    def equal(self, other):
        '''Compare self.rgba with another color's RGBA tuple.'''
        return self.rgba == other

其中,属性 self.decimal 用在了窗口背景设置的代码上:

gl.glClearColor(*Color('lightblue3').decimal)

游戏代码中还用到以下属性:

self.rgba = self.r, self.g, self.b, self.a
self.name = {v:k for k,v in ColorDict.items()}.get(self.rgb, 'Noname') 


完 

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

闽ICP备14008679号