python学习心得之:生命游戏

 
  之所有会选择写一个生命游戏,一方面是为了学习python,另一方面,可能也是某种情怀之类的东西,第一次接触“生命游戏这”个概念,是在霍金的《大设计》一书中,有关于类似“对于任何一个无序的世界在一套规则的作用下必然会发展成为一个高度有序的世界”这样的理论真的让人很震撼,于是想自己动手,去见证一下,这样的世界。

1.游戏规则

 
  生命游戏其实不算是游戏,这个过程中,你只需要写好代码,然后静静的看着你的世界的发展即可,所以我们的游戏不会触发点击事件,这里游戏的趣味性,只在于,你的世界会按照你既定的规则去发展,这里我借用《大设计》一文中的规则来设计我的世界。
 
  在这个世界中,是有 一个个方块组成的,每一个方块周围的八个方块我们称之为“邻居”,白色的方块是“活的”,黑色的则是“死的”,这些方块的生死规则如下:
  1.对于一个死方块,如果他的邻居中刚好有三个活方块,则他复活。
  2.对于一个活方块:
    * 如果他的邻居中有两个或者三个活方块,则他存活
    * 如果他的邻居中只有一个或者超过三个,则他会死
 
  在这个世界中,时间是不连续的,这一点很重要,我们举个例子,如下图是三个连续的活方块,其中的数字代表着他们此刻邻居中的活方块数量:

屏幕快照 2019-01-25 下午2.10.30

 
  我们的时间是不连续的,所以,在这一秒,我按照规则判定,第一个方块与第三个方块只有一个活邻居,所以他们会在下一秒死去,第二个方块有两个活邻居,所以他不会死,然而到了第二秒,中间的方块就没有活邻居了,所以他会在第三秒死去。
 
  如果时间是连续的,那么,在第一秒时,我判定方块1死去,因为方块1的死,导致方块2只剩下一个活邻居了,所以方块2也会死,同理,方块三也死了,所以,如果时间是连续的,这三个方块都会在1秒内死去。
 
  在我们的游戏中,我们规定时间是不连续的,这没有为什么,你也可以选择让时间连续,这样也会产生不同的结果。

2.游戏设计

 
  在这个游戏中,我们首先构造一个“世界”,这个世界是二维的,我们可以使用坐标(x,y)来指向这个世界的每一个角落,我们使用pygame来构造这个世界。

import pygame as py
width = 2500
lengh = 1300
py.init()
screen = py.display.set_mode((width,lengh))

 
  这段代码很好理解,我们构造了一个2500*1300的世界,如果你直接执行了这段代码,你会发现屏幕一闪而过,什么都没有,那是因为你的主线程已经结束了,打开的界面自然关闭了,我们尝试在后面加上一个循环,来确保我们创造的“世界”可以被看到。

while(True):
    for event in py.event.get():
        if event.type == py.QUIT:
            py.quit()
            exit()

 
  这个循环中,我们判断是否有点击事件发生,没有则不执行操作,在本文中,我们没有用到点击事件,所有这里不做介绍。
  这里的2500和1300的单位都是像素,这一点与c的图形化界面不同,在c中,你可以在(1,1)的位置上输出一个字符,然而在这里(1,1)所在是一个长宽都只有1个像素的“点”,可以说太小了,我们不能直接在这个位置上输出字符,否则什么都看不清楚。
 
  在第一部分中,我们说到,我们的世界是由方块组成的,所有我将这个世界重置,每十个像素为一个单位,一个方块的大小是1010像素,所有我们的世界坐标便要重置为250 * 130,也就是250 * 130个方块,同时我加载了两张1010像素的图片作为活方块和死方块的标签。

image = py.image.load(r"图片路径")
image_bg = py.image.load(r"图片路径")

 
  那么看到这里,我们知道,这个世界上,每一个方块内都至少包含了两个信息——背景图片和邻居的数量,事实上,我们的,每一个方块包含的信息还有,,这方块的坐标,重置后的坐标(这里成为id),方块是存活还是死亡?等,这么多的信息如何存储?这里我们定义一个类(Node),我们这个世界的每一个方块,都是这个Node的实例化对象。

class Node:
    def __init__(self,pos):
        self.pos = pos								#方块的左上角的坐标
        self.id = (int(pos[0]/10),int(pos[1]/10))	#重置后的坐标
        self.bg = image_bg							#方块的背景
        self.alive = False							#方块是否存活
        self.value = 0								#方块的活邻居数

 
  有了这些信息,我们便可以开始初始化世界了,我从(0,0)开始,以10为步长,将这个世界划分为250 * 130 个方块,这个数字是可以改的,取决于你的世界的大小,和你选取的步长。

for x in range(0,width,10):
    for y in range(0,lengh,10):
        node = Node((x,y))
        all[node.id] = node

 
  我们重置坐标为id其实是为了方便的找到每一个方块,例如,我想找到第二行第三个方块只需要借助字典all,all[(2,3)]即使我们的要的,否则,我需要将坐标乘以步长all[(20,30)]才可以。
 
  初始化的方块全是死方块,接下来我们随机激活一些方块。

for i in range(0,1500):
    new = [random.randint(0,width-1),random.randint(0,lengh-1)]
    alive.append((int(new[0]/10), int(new[1]/10)))
alive = list(set(alive))

 
  我们在我们的地图范围(2500,1300)内随机选择了1500个坐标,然后将这些坐标转为id,这些id所对应的方块便是我们选中的方块,需要注意的是,我们这里的1500个不是方块数量,例如坐标(101,101)的id是(10,10),坐标(108,108)的id也是(10,10),他们对着同一个方块,所以,我们用set(list)可以对列表去重,然后再list(set),将集合转回列表。所以最后的被选中的方块数很大的可能是小于1500的,所有被选中的id都在alive里了。现在我们激活这些方块。

def reBorn(id):
    all[id].aliveChange(True)
    
def neighbor(id):
    n = []
    x = id[0]
    y = id[1]
    neig = [(x-1,y-1),(x,y-1),(x+1,y-1),(x-1,y),(x+1,y),(x-1,y+1),(x,y+1),(x+1,y+1)]
    for id in neig:
        r_id = rePos(id)
        n.append(r_id)
    return(n)

def rePos(x):
    x_new = x[0]
    y_new = x[1]
    x_new = x_new + wordPos[0] if x_new < 0 else x_new
    x_new = x_new - wordPos[0] if x_new > wordPos[0]-1 else x_new
    y_new = y_new + wordPos[1] if y_new < 0 else y_new
    y_new = y_new - wordPos[1] if y_new > wordPos[1]-1 else y_new
    return(x_new,y_new)
    
class Node:
    #global image
    def __init__(self,pos):
        self.pos = pos
        self.id = (int(pos[0]/10),int(pos[1]/10))
        self.bg = image_bg
        self.alive = False
        self.value = 0

    def aliveChange(self,alive):
        plus = 1 if alive == True else -1
        neig = neighbor(self.id)
        for id in neig:
            node = all[id]
            node.value = node.value + plus
        self.alive = alive
        self.bg = image_bg if self.alive==False else image
        screen.blit(self.bg,self.pos)

for id in alive:
    reBorn(id)

这一段中的代码比较长,其实都是一些方法定义,我们从调用方法的地方开始看,即地23~243行。

23~24行的循环很好理解,我们将alive中的id送入 reBorn()去激活, 在reBorn()中,我们通过id找到了这个方块,然后调用方块(类)自己的方法aliveChange(self,alive),在这个方法中,我们又调用了neighbor(id)这个方法,5~8行比较好理解,我们通过传入的id,找到了这个方块的8个邻居的id,9 ~11行中,我们对这些邻居的id做了一个处理。举个例子,id(0,0)是第一行的第一个方块,他的邻居只有三个,但是neighbor(id)方法才不管这些,他依旧生成了8个id,其中便有(-1,-1)等这些在世界之外的id,所以我们构造了rePos(x)方法,将这个平面世界的上下左右“连了起来,当向左穿过边界时便会从右边界出现,当然你也可以舍弃这些”出格“的id,同样的,这也会产生完全不同的结果。

所谓激活或是复活方块,其实很简单,我们将对象中的,alive参数设置为True,并将其邻居的value参数加1。死亡则反之,将alive设置为False,然后将其邻居的value减一。

所有的准备工作都已经就绪了,现在,让我们的世界自己动起来。

while(True):
    for id in all:
        node = all[id]
        if node.alive  == False:
            if node.value == 3:
                alive.append(id)
                #reBorn(id)
        elif node.alive  == True:
            if node.value <= 1 or node.value >= 4:
                die_.append(id)
                #die(id)
    for i in alive:
        reBorn(i)
    for i in die_:
        die(i)

    alive = []
    die_ = []
    py.display.update()

 
  我们构造了一个循环,循环中,我们遍历所有的方块,把应当死去的方块放入die_中,把应当复活的方块放入alive中,在遍历结束之后统一去处理,这里遍历结束之后很重要,他决定了你的世界中时间是不是连续的。
 
  遍历结束后,我们清空alive和die——为下一次遍历做准备,然后,刷新界面,测试一下,我们的世界动起来了。

3测试结果

 
  在我们的设定中,所有的东西都是随机生成的,所以你每次运行,都会得到不同的结果,你修改。其中的任意一个参数都会有不同结果,以下是我的一次结果。(原谅我不会上动图)

在这里插入图片描述
 
  我们可以看到有一些呈稳定状态,有一些处于不停地震荡中,还有一些会沿着一定的方向一直运动下去,事实上,当地图足够大,方块基数足够多时,你能够看到更多不可思议的画面,详情可以百度或者知乎。

4,源码

import pygame as py
import random
import math

all = {}
die_ = []
alive = []
width = 2500
lengh = 1300
wordPos = [int(width/10),int(lengh/10)]

py.init()
screen = py.display.set_mode((width,lengh))
image = py.image.load(r"/Users/yangbiao/Documents/python/生存游戏/1.png")
image_bg = py.image.load(r"/Users/yangbiao/Documents/python/生存游戏/2.png")
for i in range(0,2000):
    new = [random.randint(0,width-1),random.randint(0,lengh-1)]
    alive.append((int(new[0]/10), int(new[1]/10)))
alive = list(set(alive))
#py.display.set_caption("生存游戏")
def rePos(x):
    x_new = x[0]
    y_new = x[1]
    x_new = x_new + wordPos[0] if x_new < 0 else x_new
    x_new = x_new - wordPos[0] if x_new > wordPos[0]-1 else x_new
    y_new = y_new + wordPos[1] if y_new < 0 else y_new
    y_new = y_new - wordPos[1] if y_new > wordPos[1]-1 else y_new
    return(x_new,y_new)

def neighbor(id):
    n = []
    x = id[0]
    y = id[1]
    neig = [(x-1,y-1),(x,y-1),(x+1,y-1),(x-1,y),(x+1,y),(x-1,y+1),(x,y+1),(x+1,y+1)]
    for id in neig:
        r_id = rePos(id)
        n.append(r_id)
    return(n)

def die(id):
    all[id].aliveChange(False)

def reBorn(id):
    all[id].aliveChange(True)

#定义节点
class Node:
    #global image
    def __init__(self,pos):
        self.pos = pos
        self.id = (int(pos[0]/10),int(pos[1]/10))
        self.bg = image_bg
        self.alive = False
        self.value = 0

    def aliveChange(self,alive):
        plus = 1 if alive == True else -1
        neig = neighbor(self.id)
        for id in neig:
            node = all[id]
            node.value = node.value + plus
        self.alive = alive
        self.bg = image_bg if self.alive==False else image
        screen.blit(self.bg,self.pos)

for x in range(0,width,10):
    for y in range(0,lengh,10):
        node = Node((x,y))
        all[node.id] = node

for id in alive:
    reBorn(id)
alive = []

py.display.update()

while(True):
    for id in all:
        node = all[id]
        if node.alive  == False:
            if node.value == 3:
                alive.append(id)
                #reBorn(id)
        elif node.alive  == True:
            if node.value <= 1 or node.value >= 4:
                die_.append(id)
                #die(id)
    for i in alive:
        reBorn(i)
    for i in die_:
        die(i)

    alive = []
    die_ = []
    py.display.update()

    for event in py.event.get():
        if event.type == py.QUIT:
            py.quit()
            exit()

5.小结

 
 本次学习自然不会为了看一个红火热闹,这里对一些用到的知识点做个总结。

1.类

class name:
	n = 1
    
    def __init__(self,x):
        self.id = x
        
    def idChange(self,x):
		self.id = x

 
  以上是定义一个类的基本方式,第一行的name是你自己的类的名字,下面定义的参数你、是属于整个类的,你之后将类实例化之后,若是更改了这个n,所有的对象中n都会改变,事实上,这个n在内存中只保存一次,所有的对象共用。

第一个方法def __ init __(self,x),要注意init前后都是两个下划线,第一个self是必须的,第二个x是你自己的参数,数量不限。实例化创建了一个对象后,首先会执行此方法,例如:

new = name(3)
print(new.id)

 
  输出的结果为3。
 
  def idChange(self,x)是你自己定义的方法,可以跟在对象那个后面使用,例如

new.idChange(2)
print(new.id)

 
  输出结果为2.当然,若只是简单的想更改id,你可以直接:

  new.id = 2

 
  这里只是举个例子。

2.pygame的一些操作

import pygame as py		#不解释了,必须操作
py.init()				#初始化
screen = py.display.set_mode((width,lengh))		#生成地图,参数是地图的长和宽
image = py.image.load(r"图路径")			#加载图片
screen.blit(image,pos)		#将图片贴在pos(坐标)处
py.display.update()			#刷新

3.循环创建变量

 
  这个问题本次demo没有用到,是在摸索过程成遇到的,一并写在这里。
 
  举个例子,如果我想把1~100存起来,又不想用list,需要用100个变量,代码如下:

for i in range(0,10):
    locals["n"+str(i)] = i

 
  这样便创建了100个变量,名字是n0~n99。

4.随机数的产生

import random
n = random.random()					#返回一个随机浮点型
n = random.uniform(0,100)			#返回一个0~100之间浮点型
n = random.randint(0,100)			#返回一个0~100之间的整型
n = random.sample(list,num)			#从list中随机挑选num个,返回值是list

5.list去重

num = [1,2,3,3,4,5,6,5]
num_1 =  set(num)		#将列表转为集合,这个过程中去掉了重复元素
num = list(num_1)		#将集合转回列表
  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值