2018-08-06 day16 面向对象贪吃蛇游戏实现

GreedSnake_v2.0.py

思路概述: 本次实例用面向对象思想写一个贪吃蛇小游戏.这个游戏总共分为三个大类,蛇类、石头和食物类、按键类. 蛇类又分为蛇头和蛇身两个类继承蛇类,蛇类定义类字段速度字段,控制蛇整体速度,对象属性位置,方向,以及方向属性,拐点列表,蛇头增加蛇头颜色和半径
本次项目难点在于蛇身需要跟随蛇头到指定的拐点再向指定方向转弯,所以拐点列表中的拐点字典存储了该点的位置,以及应该转的方向,因为存在拐点遍历,每一次走到相应拐点都会存在转弯,致使蛇身,脱离蛇头,最后考虑在拐点字典上加了'count'键用来记录改拐点走过的次数,每走过一个身体就+1,如果经过的次数等于蛇身长度,说明整条蛇已经走过这个拐点,于是把这个拐点字典从列表中删除,还可以节约主机的内存
另外就是蛇头吃食物,食物对象是存储在列表中,如果被吃掉时直接删除会导致遍历出问题,所以我使用的办法是先将已经被吃掉的食物半径置为0,然后完成这次遍历,在下次刷新屏幕时再次遍历食物列表,将所有半径为0的食物对象删除,这样就不会影响到比较位置,其余部分逻辑都很简单,不做阐述

import pygame
from random import randint as ri
from math import sqrt as sq

screenWid = 1200  # 窗口大小
screenHet = 800
speed = 1


def randpos():  # 封装产生随机点方法
    return (ri(14, screenWid), ri(14, screenHet))


def getDistance(pos1, pos2):  # 封装计算两点距离算法
    x1, y1 = pos1
    x2, y2 = pos2
    return sq((x1 - x2)**2 + (y1 - y2)**2)


def showInfo(sc, score, level):  # 展示得分和速度等级
    font = pygame.font.SysFont('Times', 30)
    sInfo = 'Score:' + str(score) + '  Level:' + str(level)
    info = font.render(sInfo, True, (0, 0, 0))
    sc.blit(info, (800, 2))


class FoodNRock(object):  # 食物和石头类(蛇头碰到石头会结束游戏)
    def __init__(self, sc, pos):
        self.pos = pos
        self.xSpd = ri(1, 4)
        self.ySpd = ri(1, 4)
        radius = ri(1, 4)  # 有四种种类的食物
        if radius == 1:
            self.radius = 8
            self.color = (206, 145, 120)
            pygame.draw.circle(sc, (206, 145, 120), self.pos, self.radius)
        elif radius == 2:
            self.radius = 10
            self.color = (240, 162, 27)
            pygame.draw.circle(sc, (240, 162, 27), self.pos, self.radius)
        elif radius == 3:
            self.radius = 12
            self.color = (197, 34, 192)
            pygame.draw.circle(sc, (197, 34, 192), self.pos, self.radius)
        else:
            self.radius = 14
            self.color = (86, 156, 214)
            pygame.draw.circle(sc, (86, 156, 214), self.pos, self.radius)

    def drawFood(self, sc):
        pygame.draw.circle(sc, self.color, self.pos, self.radius)

    def foodMove(self):  # 食物移动方法
        fX, fY = self.pos
        if (fX - self.radius) < 0:
            fX = self.radius
            self.xSpd *= -1
        if (fX + self.radius) > screenWid:
            fX = screenWid - self.radius
            self.xSpd *= -1
        if (fY - self.radius) < 0:
            fY = self.radius
            self.ySpd *= -1
        if (fY + self.radius) > screenHet:
            fY = screenHet - self.radius
            self.ySpd *= -1
        fX += self.xSpd
        fY += self.ySpd
        self.pos = fX, fY


class Snake(object):  # 蛇类
    speed = 1  # 总体速度字段

    def __init__(self, screen):
        self.screen = screen
        self.pos = (150, 400)
        self.xSpd = 1
        self.ySpd = 0
        self.direction = 'right'
        self.turns = []

    def move(self):  # 蛇的运动方法
        pX, pY = self.pos
        pX += Snake.speed * self.xSpd
        pY += Snake.speed * self.ySpd
        self.pos = (pX, pY)

    def gameOver(sc):  # 游戏结束方法
        sc.fill((100, 200, 200))
        font = pygame.font.SysFont('Times', 100, bold=1)
        title = font.render('Game Over', True, (255, 0, 0))
        sc.blit(title, (360, 320))
        pygame.display.update()
        pygame.time.delay(3000)
        exit()


class Shead(Snake):  # 蛇头类
    def __init__(self, screen):
        super().__init__(screen)
        self.pos = (150, 400)
        self.headCol = (63, 140, 162)
        self.radius = 14

    def drawShead(self, sc):
        pygame.draw.circle(sc, self.headCol, self.pos, self.radius)

    def eat(self, food):  # 如果两点距离小于半径之和则可以吃掉该食物
        if (self.radius + food.radius) > getDistance(self.pos, food.pos):
            return True
        return False


class Sbody(Snake):  # 蛇身类
    def __init__(self, screen, lastbody=None, pos=(124, 400)):
        super().__init__(screen)
        self.bodyCol = (124, 220, 254)
        self.radius = 12
        self.direction = 'right'
        if lastbody:  # 判断蛇身最后一个对象的位置和方向,吃掉食物之后新增加的身体在蛇身最后面
            if lastbody.direction == 'up':
                self.direction = 'up'
                pos = (lastbody.pos[0], lastbody.pos[1] + 2 * self.radius)
            elif lastbody.direction == 'down':
                self.direction = 'down'
                pos = (lastbody.pos[0], lastbody.pos[1] - 2 * self.radius)
            elif lastbody.direction == 'left':
                self.direction = 'left'
                pos = (lastbody.pos[0] + 2 * self.radius, lastbody.pos[1])
            elif lastbody.direction == 'right':
                self.direction = 'right'
                pos = (lastbody.pos[0] - 2 * self.radius, lastbody.pos[1])
        self.pos = pos

    def bodyMove(self):  # 身体移动方法
        if self.direction == 'up':
            self.xSpd = 0
            self.ySpd = -1
            bX, bY = self.pos
            bX += self.xSpd * Snake.speed
            bY += self.ySpd * Snake.speed
            self.pos = (bX, bY)
        elif self.direction == 'down':
            self.xSpd = 0
            self.ySpd = 1
            bX, bY = self.pos
            bX += self.xSpd * Snake.speed
            bY += self.ySpd * Snake.speed
            self.pos = (bX, bY)
        elif self.direction == 'left':
            self.xSpd = -1
            self.ySpd = 0
            bX, bY = self.pos
            bX += self.xSpd * Snake.speed
            bY += self.ySpd * Snake.speed
            self.pos = (bX, bY)
        elif self.direction == 'right':
            self.xSpd = 1
            self.ySpd = 0
            bX, bY = self.pos
            bX += self.xSpd * Snake.speed
            bY += self.ySpd * Snake.speed
            self.pos = (bX, bY)

    def drawbody(self, sc, head=0):
        pygame.draw.circle(sc, self.bodyCol, self.pos, self.radius)

    @staticmethod
    def bodyReSort(bodys, lastbody):  # 刷新蛇身方法  未启用
        for i in range(len(bodys)):
            if i == 0:
                if lastbody.direction == 'up':
                    bodys[i].pos = (
                        lastbody.pos[0],
                        lastbody.pos[1] + bodys[i].radius + lastbody.radius)
                elif lastbody.direction == 'down':
                    bodys[i].pos = (
                        lastbody.pos[0],
                        lastbody.pos[1] - bodys[i].radius - lastbody.radius)
                elif lastbody.direction == 'left':
                    bodys[i].pos = (
                        lastbody.pos[0] + bodys[i].radius + lastbody.radius,
                        lastbody.pos[1])
                elif lastbody.direction == 'right':
                    bodys[i].pos = (
                        lastbody.pos[0] - bodys[i].radius - lastbody.radius,
                        lastbody.pos[1])
            else:
                if lastbody.direction == 'up':
                    bodys[i].pos = (lastbody.pos[0],
                                    lastbody.pos[1] + 2 * lastbody.radius)
                elif lastbody.direction == 'down':
                    bodys[i].pos = (lastbody.pos[0],
                                    lastbody.pos[1] - 2 * lastbody.radius)
                elif lastbody.direction == 'left':
                    bodys[i].pos = (lastbody.pos[0] + 2 * lastbody.radius,
                                    lastbody.pos[1])
                elif lastbody.direction == 'right':
                    bodys[i].pos = (lastbody.pos[0] - 2 * lastbody.radius,
                                    lastbody.pos[1])
            lastbody = bodys[i]


class Key(object):  # 按键类
    def __init__(self, keyVle, isBan=False):
        self.keyVle = keyVle
        self.isBan = isBan

    def kUPress(self, head, snake, kU, kD, kL, kR):  # 按下按键之后关闭朝前和朝后的按键事件触发
        head.xSpd = 0
        head.ySpd = -1
        head.direction = 'up'
        snake.turns.append({'pos': head.pos, 'direction': 'up', 'count': 0})
        kD.isBan = True
        kU.isBan = True
        kL.isBan = False
        kR.isBan = False

    def kDPress(self, head, snake, kU, kD, kL, kR):
        head.xSpd = 0
        head.ySpd = 1
        head.direction = 'down'
        snake.turns.append({'pos': head.pos, 'direction': 'down', 'count': 0})
        kD.isBan = True
        kU.isBan = True
        kL.isBan = False
        kR.isBan = False

    def kLPress(self, head, snake, kU, kD, kL, kR):
        head.xSpd = -1
        head.ySpd = 0
        head.direction = 'left'
        snake.turns.append({'pos': head.pos, 'direction': 'left', 'count': 0})
        kD.isBan = False
        kU.isBan = False
        kL.isBan = True
        kR.isBan = True

    def kRPress(self, head, snake, kU, kD, kL, kR):
        head.xSpd = 1
        head.ySpd = 0
        head.direction = 'right'
        snake.turns.append({'pos': head.pos, 'direction': 'right', 'count': 0})
        kD.isBan = False
        kU.isBan = False
        kL.isBan = True
        kR.isBan = True


def main():  # 主方法
    score = 0
    level = 0
    pygame.init()
    screen = pygame.display.set_caption('GreedsHead')
    screen = pygame.display.set_mode((screenWid, screenHet))
    screen.fill((255, 255, 255))
    kU = Key(273, False)
    kD = Key(274, False)
    kR = Key(275, False)
    kL = Key(276, True)
    snake = Snake(screen)
    head = Shead(screen)
    bodys = [Sbody(screen)]
    head.drawShead(screen)
    bodys[0].drawbody(screen)
    foods = [FoodNRock(screen, randpos())]
    pygame.display.flip()
    while True:
        if score // 25 > level:  # 最大等级为5级,每25分升一级,前四级加速
            if Snake.speed <= 4:
                level += 1
                Snake.speed += 1
            else:
                level = 5
        for f in foods:  # 删除半径为0的食物
            if f.radius == 0:
                foods.remove(f)
        if len(foods) < 2:  # 保持屏幕中有2个食物存在
            foods.append(FoodNRock(screen, randpos()))
        for evt in pygame.event.get():
            if evt.type == pygame.QUIT:  # 退出事件
                exit()
            if evt.type == pygame.KEYDOWN:  # 按键事件
                if evt.key == kU.keyVle:
                    if not kU.isBan:
                        kU.kUPress(head, snake, kU, kD, kL, kR)
                        break
                if evt.key == kD.keyVle:
                    if not kD.isBan:
                        kD.kDPress(head, snake, kU, kD, kL, kR)
                        break
                if evt.key == kL.keyVle:
                    if not kL.isBan:
                        kL.kLPress(head, snake, kU, kD, kL, kR)
                        break
                if evt.key == kR.keyVle:
                    if not kR.isBan:
                        kR.kRPress(head, snake, kU, kD, kL, kR)
                        break
        screen.fill((255, 255, 255))
        showInfo(screen, score, level)  # 展示得分等信息
        for body in bodys:
            for bTurn in snake.turns:  # 如果哪个身体遇到了拐点就拐弯
                if getDistance(body.pos, bTurn['pos']) < Snake.speed:  # 调整因为速度增加无法正好到达拐点的情况
                    body.pos = bTurn['pos']  # 强制更新身体的位置
                    body.direction = bTurn['direction']
                    bTurn['count'] += 1  # 计数+1
                if bTurn['count'] == len(bodys):  # 如果所有身体的点都经过过一次这个拐点,则删除这个拐点
                    snake.turns.remove(bTurn)
            body.bodyMove()
            body.drawbody(screen)
            lastBody = body  # 保留最后一个身体对象

        for food in foods:
            if level == 5:  # 如果等级为5 ,则食物开始移动
                food.foodMove()
            food.drawFood(screen)
            if head.eat(food):  # 判断食物是否被吃
                score += food.radius // 3  # 食物半径的1/3作为得分
                food.radius = 0  # 被吃掉的食物半径为0
                bodys.append(Sbody(screen, lastBody))  # 增加一个身体对象
        head.move()
        head.drawShead(screen)
        if head.pos[0] + head.radius > screenWid or head.pos[0] - head.radius < 0 or head.pos[1] - head.radius < 0 or head.pos[1] + head.radius > screenHet:  # 判断舌头是否触碰边界,触碰则结束游戏
            Snake.gameOver(screen)
        pygame.display.update()


if __name__ == '__main__':
    main()

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343