[pygame] pygame设计联机对战桌游(三)

2023-12-07 12:10

本文主要是介绍[pygame] pygame设计联机对战桌游(三),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

[pygame] pygame设计联机对战桌游(4-1)

    • 内容概要
    • 昨日问题
    • 卡牌展示器
      • 0. 成果总结
        • 1. 卡牌显示器
        • 2. 精灵类及其容器
        • 3. 事件循环
      • 1. 游戏对象类
        • 1.1. 卡牌Card
        • 1.2. 卡堆CardHeap
        • 1.3. 所有卡堆list
      • 2. 处理鼠标事件
      • 3. 出牌
      • 4. 全屏模式(不稳定,暂时删除)
      • 5. 打包发行
      • 6. 全部代码
    • python’新手笔记
      • 1. 类的实例化
      • 2. 私有与公开
      • 3. 继承,方法重写
      • 4. 多继承
      • 5. 命名空间和作用域

本系列总目录:https://blog.csdn.net/wxlxy316/article/details/104246724

内容概要

四、分析桌游需求,制作类图,开始开发桌游(1)

昨日问题

在学习了一定的知识后——实际上是看了太久繁杂的英文教程,感到了倦怠和恶心——我决心为自己做一份甜点休憩一下:从设计简单的卡片展示器着手,先制作出一个简单的雏形来,验证自己的学习成果,再继续我的学习路径

先提几个昨日遇到的问题

python 获取某个对象的引用地址
https://blog.csdn.net/cpongo6/article/details/89249293

出现UnboundLocalError: local variable ‘a’ referenced before assignment异常的情况与解决方法
https://blog.csdn.net/DansonC/article/details/88860885

pygame.event类别汇总
https://blog.csdn.net/qq_41556318/article/details/86303039

这些问题使我感到了深深的沮丧和挫败,并且让我发现我对python对象的有关概念的理解实在是烂透了,因此我打开了菜鸟教程恶补了几天python class的知识:https://www.runoob.com/python3/python3-class.html
,关于学习这些内容时的笔记和感悟我放在文章后部了,现在,我们先讨论一下目前的工作吧!

卡牌展示器

0. 成果总结

1. 卡牌显示器

最终我厘清了一些思路,制作了一份简单的卡牌显示器,它具有如下特性:

  • 定义了两个类:卡堆CardHeap、卡牌Card
  • 卡牌若干,分布在卡堆中,卡牌和卡堆一一对应
  • 卡牌拥有正反两面,可以实现单击翻面特性
  • 卡牌在卡堆中一字排开,存在次序,紧邻排布(未完成)
  • 可以通过鼠标调整卡牌在卡堆中的顺序,也可以调整卡牌移动到不同的卡堆中

代码和素材移步github地址:https://github.com/bridgeL/card_show

2. 精灵类及其容器

学习了pygame.sprite.Sprite类,pygame.sprite.Group类,pygame.sprite.OrderedUpdates类,简单来说,
后两个类是第一个类的容器,且较前者为无序容器,较后者为有序容器,一般来说有序容器较慢,应根据实际情形使用不同的容器打包pygame.sprite.Sprite对象

3. 事件循环

深化了对于Event循环的理解:

  • clock模块,提供适当延时,以保证阵列稳定为预设值
  • 处理event队列,将每一帧间积攒的多个操作,依次处理
  • 获取上一帧中的精灵对象的矩阵坐标,用底图的对应部分覆盖 screen.blit(background,sprite.rect),对于对于组内的精灵可用group.clear(screen,background),会自动迭代处理每一个精灵
  • 运行游戏逻辑,计算这一帧精灵对象的矩阵坐标sprite.update(),对于组内的精灵可用group.update(),会自动迭代处理每一个精灵
  • 绘制组内精灵对象到这一帧group.draw(screen)
  • 应用更改到显示器pygame.display.flip()

1. 游戏对象类

1.1. 卡牌Card

2020/2/11
今日实在太匆忙,代码只做了一个雏形,也来不及写完第三期的官方教程学习笔记,这一期也漏了一些很坑的问题,我明天再来补充

卡牌类继承自pygame.sprite.Sprite类,新增属性imghimgbidlabelheadupmovepos
提供方法overturnisclickmove,重写了方法update,最值得一提的是,我在卡牌类中封装一个公共属性imgb

总所周知,一套卡牌的正面图案各不相同,但是背面图是一致的,如果我们为每个卡牌都加载两份图片(正反两面)的内存空间,那么至少有一半的内存空间是无意义消耗的,因此我为卡牌封装了一个公共属性imgb,存放反面图片,同时设计了属性self.headup和方法Card.overturn(self)这个方法将在翻转卡牌时起作用,它负责将属性self.headup翻转,同时修改self.image指向另一面,self.imghimgb


class Card(pygame.sprite.Sprite):imgb = 0def __init__(self, id, lable):super(Card, self).__init__()self.imgh, self.rect = load_img(lable+'.jpg')self.image = self.imghself.id = idself.lable = lableself.headup = Trueself.movepos = [0, 0]def update(self):if self.movepos[0] != 0 or self.movepos[1] != 0:self.rect.move_ip(self.movepos)self.movepos = [0, 0]def overturn(self):self.headup = not self.headupif self.headup:self.image = self.imghelse:self.image = Card.imgbdef isclick(self, pos):return self.rect.collidepoint(pos)def move(self, rel):self.movepos[0] += rel[0]self.movepos[1] += rel[1]

比较坑的一点在于,我在摸索这一模式时,经常遇到了

UnboundLocalError: local variable '<module_name>' referenced before assignment

类似这种错误,这是为什么呢?

原代码如下:

class Card(pygame.sprite.Sprite):imgb = pygame.image.load('data/back.jpg')def __init__(self, id, lable):super(Card, self).__init__()self.imgh, self.rect = load_img(lable+'.jpg')self.image = self.imgh......

原来,这份代码在编译class Card类时——尽管此时还没有任何一个card对象实例化——调用了还没有初始化的pygame.image模块的函数,而pygame的初始化函数在main函数里,

经修改如下:

class Card(pygame.sprite.Sprite):imgb = 0def __init__(self, id, lable):super(Card, self).__init__()self.imgh, self.rect = load_img(lable+'.jpg')self.image = self.imgh......
def cardinit():Card.imgb = pygame.image.load('data/back.jpg')...
def main():pygame.init()...cardinit()...

问题解决√√√

1.2. 卡堆CardHeap

需要设计卡堆存储一定数量的卡牌。

可以继承pygame.sprite.Group类,但没必要,pygame提供了Sprite的容器类,但是没提供Group的容器类,所以就没通过继承模式设计

class CardHeap():def __init__(self, rect):self.items = pygame.sprite.Group()self.count = 0self.area = rectdef add(self, card):print('-----------------------------', card.rect, self.area.topleft)card.rect.move_ip(self.area.topleft[0] - card.rect.topleft[0], self.area.topleft[1] - card.rect.topleft[1])print('-----------------------------', card.rect)self.items.add(card)self.count += 1def remove(self, card):self.items.remove(card)self.count -= 1def clear(self, screen, background):self.items.clear(screen, background)def update(self):self.items.update()def draw(self, screen):self.items.draw(screen)def isclick(self, pos):for card in reversed(self.items.sprites()):if card.isclick(pos):return True, cardreturn False, 0def setarea(self, rect):self.area = rectfor card in self.items.sprites():card.rect.move_ip(rect.topleft)

每个卡堆里的卡牌都被限制在卡堆的area里,不能通过鼠标拖动到其他地方

1.3. 所有卡堆list
allcardheaps = [CardHeap(pygame.Rect((50, 100), (100, 200))), CardHeap(pygame.Rect((120, 200), (200, 300))), CardHeap(pygame.Rect((30, 30), (100, 100)))]
CH = dynamic({'player0': 0, 'player1': 1, 'table': 2})

CH这种操作可以说是想了很久,实现了类似c的枚举效果

当然也有更简单粗暴的

class CH():player0 = 0player1 = 1table = 2

调用的时候是这样的

allcardheaps[CH.player0].update()

上面这句代码的效果是更新player0的卡堆的所有卡牌

关于dynamic这种类,是找到的一个大神的文章里的,他的定义的dynamic类最终实现了类似event类的效果——将字典通过属性方式调用,而传统的字典是这样的:

CH['player0'] = 0

dynamic类原文地址
https://www.jianshu.com/p/edfa99a72a02

class dynamic(dict):def __init__(self, d=None):if d is not None:for k, v in d.items():self[k] = vreturn super().__init__()def __key(self, key):return "" if key is None else key.lower()def __str__(self):import jsonreturn json.dumps(self)def __setattr__(self, key, value):self[self.__key(key)] = valuedef __getattr__(self, key):return self.get(self.__key(key))def __getitem__(self, key):return super().get(self.__key(key))def __setitem__(self, key, value):return super().__setitem__(self.__key(key), value)

2. 处理鼠标事件

pygame的鼠标处理很强大,细节面很广,譬如button属性

button事件
1鼠标左键
2鼠标中键
3鼠标右键
1滚轮向上滚动
1滚轮向下滚动

而event.type又有三种,对应鼠标按下、鼠标移动、鼠标抬起,十分人性化

这里我又增加了一个全局鼠标姿态记录变量mouse_gesture

MG = dynamic({'no_action': 0, 'click_l': 1, 'click_r': 2, 'move': 3})
# Initialise mouse_gesture
mouse_gesture = MG.no_action

分别枚举了鼠标无有意义动作、鼠标左键点击了卡牌、鼠标右键点击了卡牌、鼠标正在拖动卡牌四个状态

    # Event loopwhile True:for event in pygame.event.get():print(event)if event.type == QUIT:returnelif event.type == MOUSEBUTTONDOWN:#如果有卡牌被选中if event.button == 1:mouse_gesture = MG.click_lbreakelif event.button == 3:mouse_gesture = MG.click_rbreakelif event.type == MOUSEMOTION:if mouse_gesture == MG.click_l:# 如果左键拖动卡牌,移动距离足够远,说明用户希望移动卡牌而不是左键单击它mouse_gesture = MG.move# do somethingelif mouse_gesture == MG.move:# do somethingelif event.type == MOUSEBUTTONUP:if mouse_gesture == MG.click_l and event.button == 1:# 用户左键单击了某张卡牌elif mouse_gesture == MG.click_r and event.button == 3:# 用户右键单击了某张卡牌mouse_gesture = MG.no_action

这里关于用户拖动操作的模拟搞了很久,一度我以为应该根据用户鼠标按住的时长来判断是否应该移动卡牌,但是这种设计手感很差,最终替换成了如果左键拖动卡牌,移动距离足够远,说明用户希望移动卡牌而不是左键单击它

3. 出牌

将player牌组中的牌移动到桌面牌组里

def moveCH2T(ch_remove, card):ch_remove.remove(card)allcardheaps[CH.table].add(card)

player0牌组中的牌移到player1中

def moveCH2CH(ch_remove, ch_add, card):ch_remove.remove(card)ch_add.add(card)

4. 全屏模式(不稳定,暂时删除)

按F1切换窗口模式/全屏模式
https://blog.csdn.net/qq_38526635/article/details/84307903

5. 打包发行

推荐使用pyinstaller
http://c.biancheng.net/view/2690.html

cmd在对应目录下执行指令

pyinstaller -F app.py

有个问题是,pyinstaller不能将图片等资源文件也打包到一个包里,因此要记得把exe文件和资源文件放到一起运行

6. 全部代码

代码和素材移步github地址:https://github.com/bridgeL/card_show


python’新手笔记

python的类相比c++的类而言,依旧是体现了python的简洁特性,但若不了解一定的规则,使用起来则会造成一定的混乱和麻烦

1. 类的实例化

c++通过和类同名的构造函数实例化类为对象,python则通过__init__()方法实现,同样的,__init__()函数也可以有参数

菜鸟教程里举了这样一个例子:

#!/usr/bin/python3class Complex:def __init__(self, realpart, imagpart):self.r = realpartself.i = imagpart
x = Complex(3.0, -4.5)
print(x.r, x.i)   # 输出结果:3.0 -4.5

self是python里对类的一个独特处理,它代表类的实例,而非类。这句话似乎难以理解,但是站在编译器的角度思考,似乎可以很容易理解。

对于一个类来说,它的每一个对象都拥有的个人财产(相对于公共财产)是一堆变量/属性Attributesname, age, height, weight,diet…,而公共财产则是一系列的函数/方法Functionsget_name_length()judge_health()…,当一个对象请求调用一个公共函数judge_health(),输入自己的年龄身高体重来计算它的健康指数并指导就餐计划(diet)时,它显然要将自己的引用地址告知公共函数,以帮助它访问和修改自己的各项属性,而这一切,仅需要在设计函数时

class Person():...def judge_health(self): self.diet = self.age...self.height...self.weight

之后函数内便可以愉快地调用self的属性,访问或者修改它们。对象使用函数时,self将被自动调用,如下

person = Person(...)
person.judge_health()

这相当于

person = Person(...)
Person.judge_health(person)

self代表的是类的实例,代表当前对象的地址,self.__class__指向类,可以利用self.__class__进行类名检查,提高代码安全性。

在c++的类中,函数往往是复杂烦人的,你需要不厌其烦地告诉公共函数该对象的每一属性的拷贝或者地址,最终的函数往往变成这样

void judge_health(const int& age, const int& height, const int& weight, Diet& diet)
{...
}

当然,这是安全妥当的做法,但是在开发效率上…

或者是这样,

class Person
{
private:int age,height,weight;Diet diet;
public:void judge_health(){diet = age... height...weight;}
}

冒出来一些让人摸不着头脑的变量,看起来它们也许是对象的变量,也可能是全局变量…

2. 私有与公开

双下划线前缀代表私有属性,私有方法
默认为公开属性,公开方法

#!/usr/bin/python3#类定义
class people:#定义基本属性name = ''age = 0#定义私有属性,私有属性在类外部无法直接进行访问__weight = 0#定义构造方法def __init__(self,n,a,w):self.name = nself.age = aself.__weight = wdef __prt(self):print("%s 说: 我 %d 岁。" %(self.name,self.age))def speak(self):self.__prt()# 实例化类
p = people('runoob',10,30)
p.speak()

3. 继承,方法重写

派生类的定义如下所示:

class DerivedClassName(BaseClassName1):...

BaseClassName(示例中的基类名)必须与派生类定义在一个作用域内。除了类,还可以用表达式,基类定义在另一个模块中时这一点非常有用:

class DerivedClassName(modname.BaseClassName):...

python中子类与父类__init__函数的关系:

  • 子类不重写 __init__,实例化子类时,会自动调用父类定义的 __init__
  • 如果重写了__init__时,实例化子类,就不会调用父类已经定义的__init__,因此需要重新编写内容实例化继承自父类的属性,或者如下操作
class DerivedClassName(BaseClassName1):...def __init__(self, 参数1,参数2....):BaseClassName1.__init__(self, 参数1,参数2....)...

或者使用super继承父类的构造方法

class DerivedClassName(BaseClassName1):...def __init__(self, 参数1,参数2....):super(DerivedClassName,self).__init__(参数1,参数2....)...

方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。

#!/usr/bin/python3#类定义
class people:#定义基本属性name = ''age = 0#定义私有属性,私有属性在类外部无法直接进行访问__weight = 0#定义构造方法def __init__(self,n,a,w):self.name = nself.age = aself.__weight = wdef speak(self):print("%s 说: 我 %d 岁。" %(self.name,self.age))#单继承示例
class student(people):grade = ''def __init__(self,n,a,w,g):#调用父类的构函people.__init__(self,n,a,w)self.grade = g#覆写父类的方法def speak(self):print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))s = student('ken',10,60,3)
s.speak()

没什么好说的,代码就是教程

4. 多继承

class DerivedClassName(Base1, Base2, Base3):...
#!/usr/bin/python3#类定义
class people:#定义基本属性name = ''age = 0#定义私有属性,私有属性在类外部无法直接进行访问__weight = 0#定义构造方法def __init__(self,n,a,w):self.name = nself.age = aself.__weight = wdef speak(self):print("%s 说: 我 %d 岁。" %(self.name,self.age))#单继承示例
class student(people):grade = ''def __init__(self,n,a,w,g):#调用父类的构函people.__init__(self,n,a,w)self.grade = g#覆写父类的方法def speak(self):print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))#另一个类,多重继承之前的准备
class speaker():topic = ''name = ''def __init__(self,n,t):self.name = nself.topic = tdef speak(self):print("我叫 %s,我是一个演说家,我演讲的主题是 %s"%(self.name,self.topic))#多重继承
class sample(speaker,student):a =''def __init__(self,n,a,w,g,t):student.__init__(self,n,a,w,g)speaker.__init__(self,n,t)test = sample("Tim",25,80,4,"Python")
test.speak()   #方法名相同,默认调用的是在括号中排前的父类的方法

test.speak() #方法名相同,默认调用的是在括号中排前的父类的方法

5. 命名空间和作用域

1.png

  • 内置名称(built-in names), Python 语言内置的名称,比如函数名 abs、char 和异常名称 BaseException、Exception 等等。
  • 全局名称(global names),模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
  • 局部名称(local names),函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是)

要点:

  • 命名空间查找顺序: 局部的命名空间去 -> 全局命名空间 -> 内置命名空间
  • 命名空间的生命周期:取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束
  • 相同的对象名称可以存在于多个不同的命名空间中
  • 有四种作用域:L(Local,局部变量),E(Enclosing,闭包函数外的函数),G(Global,当前模块的全局变量),B(Built-in,内建的变量/关键字)
g_count = 0  # 全局作用域
def outer():o_count = 1  # 闭包函数外的函数中def inner():i_count = 2  # 局部作用域
  • 局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问
  • 当内部作用域想修改外部作用域的变量时,需要依靠global和nonlocal关键字
  • 全局变量使用global,嵌套作用域(enclosing 作用域,非全局作用域)变量使用 nonlocal
num = 1
def fun():# global numnum = 2
fun()
print(num)

没有声明global,fun中调用的num为新建的局部变量,输出结果为1

num = 1
def fun():global numnum = 2
fun()
print(num)

输出结果为2

本系列总目录:https://blog.csdn.net/wxlxy316/article/details/104246724

这篇关于[pygame] pygame设计联机对战桌游(三)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/465823

相关文章

Python中的可视化设计与UI界面实现

《Python中的可视化设计与UI界面实现》本文介绍了如何使用Python创建用户界面(UI),包括使用Tkinter、PyQt、Kivy等库进行基本窗口、动态图表和动画效果的实现,通过示例代码,展示... 目录从像素到界面:python带你玩转UI设计示例:使用Tkinter创建一个简单的窗口绘图魔法:用

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

怎么让1台电脑共享给7人同时流畅设计

在当今的创意设计与数字内容生产领域,图形工作站以其强大的计算能力、专业的图形处理能力和稳定的系统性能,成为了众多设计师、动画师、视频编辑师等创意工作者的必备工具。 设计团队面临资源有限,比如只有一台高性能电脑时,如何高效地让七人同时流畅地进行设计工作,便成为了一个亟待解决的问题。 一、硬件升级与配置 1.高性能处理器(CPU):选择多核、高线程的处理器,例如Intel的至强系列或AMD的Ry

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机

SprinBoot+Vue网络商城海鲜市场的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍:CSDN认证博客专家,CSDN平台Java领域优质创作者,全网30w+

单片机毕业设计基于单片机的智能门禁系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍程序代码部分参考 设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订

Spring的设计⽬标——《Spring技术内幕》

读《Spring技术内幕》第二版,计文柯著。 如果我们要简要地描述Spring的设计⽬标,可以这么说,Spring为开发者提供的是⼀个⼀站式的轻量级应⽤开发框架(平台)。 作为平台,Spring抽象了我们在 许多应⽤开发中遇到的共性问题;同时,作为⼀个轻量级的应⽤开发框架,Spring和传统的J2EE开发相⽐,有其⾃⾝的特点。 通过这些⾃⾝的特点,Spring充分体现了它的设计理念:在

开题报告中的研究方法设计:AI能帮你做什么?

AIPaperGPT,论文写作神器~ https://www.aipapergpt.com/ 大家都准备开题报告了吗?研究方法部分是不是已经让你头疼到抓狂? 别急,这可是大多数人都会遇到的难题!尤其是研究方法设计这一块,选定性还是定量,怎么搞才能符合老师的要求? 每次到这儿,头脑一片空白。 好消息是,现在AI工具火得一塌糊涂,比如ChatGPT,居然能帮你在研究方法这块儿上出点主意。是不

创业者该如何设计公司的股权架构

本文来自七八点联合IT橘子和车库咖啡的一系列关于设计公司股权结构的讲座。 主讲人何德文: 在公司发展的不同阶段,创业者都会面临公司股权架构设计问题: 1.合伙人合伙创业第一天,就会面临股权架构设计问题(合伙人股权设计); 2.公司早期要引入天使资金,会面临股权架构设计问题(天使融资); 3.公司有三五十号人,要激励中层管理与重要技术人员和公司长期走下去,会面临股权架构设计问题(员工股权激

分布式文件系统设计

分布式文件系统是分布式领域的一个基础应用,其中最著名的毫无疑问是 HDFS/GFS。如今该领域已经趋向于成熟,但了解它的设计要点和思想,对我们将来面临类似场景 / 问题时,具有借鉴意义。并且,分布式文件系统并非只有 HDFS/GFS 这一种形态,在它之外,还有其他形态各异、各有千秋的产品形态,对它们的了解,也对扩展我们的视野有所俾益。本文试图分析和思考,在分布式文件系统领域,我们要解决哪些问题、有