2 Unstar Star 24 Fork 2

Wings / SnaguePythonMulanPSL-2.0

Create your Gitee Account
Explore and code with more than 5 million developers,Free private repositories !:)
Sign up
Clone or download
snague.py 57.43 KB
Copy Edit Web IDE Raw Blame History
WingsZeng authored 2020-08-06 13:43 . add code and release!
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754
'''
@Author: Wings
@Date: 2020-07-16 13:44:34
LastEditTime: 2020-08-06 12:13:51
LastEditors: Please set LastEditors
@Description: Release 1.0.0
@FilePath: \snague\snague.py
'''
#-*-coding:utf-8-*-
import sys
import random
import pygame
# 游戏名
GAME_NAME = "Snague - 地牢贪吃蛇"
GAME_VERSION = "Release 1.0.0"
ICON_FILENAME = 'snague.ico'
# 图片文件名
IMAGE_BACKGROUND_FILENAME = 'img\\background.jpg'
IMAGE_HOME_FILENAME = 'img\\home.jpg'
IMAGE_GAMEOVER_FILENAME = 'img\\gameover.jpg'
IMAGE_SNAKE_HEAD_FILENAME = ['img\\snake_head_1.png', 'img\\snake_head_2.png',
'img\\snake_head_3.png', 'img\\snake_head_4.png',
'img\\snake_head_5.png', 'img\\snake_head_6.png',
'img\\snake_head_7.png', 'img\\snake_head_8.png']
IMAGE_SNAKE_BODY_FILENAME = 'img\\snake_body.png'
IMAGE_SNAKE_TAIL_FILENAME = 'img\\snake_tail.png'
IMAGE_FOOD_FILENAME = 'img\\food.png'
IMAGE_MEDICINE_FILENAME = 'img\\medicine.png'
IMAGE_PILL_FILENAME = 'img\\pill.png'
IMAGE_STONE_FILENAME = 'img\\stone.png'
IMAGE_ARROW_FILENAME = 'img\\arrow.png'
IMAGE_BOMB_FILENAME = ['img\\bomb_1.png', 'img\\bomb_2.png', 'img\\bomb_3.png', 'img\\bomb_4.png',
'img\\bomb_5.png', 'img\\bomb_6.png', 'img\\bomb_7.png', 'img\\bomb_8.png',
'img\\bomb_9.png', 'img\\bomb_10.png']
IMAGE_SPARK_FILENAME = ['img\\spark_1.png', 'img\\spark_2.png', 'img\\spark_3.png',
'img\\spark_4.png', 'img\\spark_5.png']
IMAGE_HEART_FILENAME = 'img\\heart.png'
IMAGE_HEART_SORT_FILENAME = 'img\\heart_sort.png'
IMAGE_MONEY_FILENAME = 'img\\money.png'
IMAGE_GHAST_FILENAME = 'img\\ghost.png'
IMAGE_BUFF_FILENAME = ['img\\helmet.png', 'img\\great_helmet.png', 'img\\heal.png',
'img\\cautious.png', 'img\\diet.png', 'img\\brave.png',
'img\\suit.png', 'img\\tool.png', 'img\\hurry.png', 'img\\dizzy.png']
IMAGE_GOOD_FILENAME = ['', 'img\\heart.png', 'img\\heal.png', 'img\\aid.png', 'img\\cautious.png',
'img\\helmet.png', 'img\\great_helmet.png', 'img\\tool.png', 'img\\suit.png']
IMAGE_ENERGY_FILENAME = 'img\\energy.png'
# 音乐音效文件名
MUSIC_BGM_FILENAME = ['music\\home.wav', 'music\\level0.wav', 'music\\level1.wav',
'music\\level2.wav', 'music\\level3.wav', 'music\\level4.wav',
'music\\level5.wav', 'music\\gameover.wav', 'music\\prize.wav', 'music\\shop.wav']
SOUND_EAT_FILENAME = 'music\\eat.wav'
SOUND_HURT_FILENAME = 'music\\hurt.wav'
SOUND_HEAL_FILENAME = 'music\\heal.wav'
SOUND_HIT_FILENAME = 'music\\hit.wav'
SOUND_EXPLODE_FILENAME = 'music\\explode.wav'
SOUND_HIT_WITH_HELMET_FILENAME = 'music\\hit_with_helmet.wav'
SOUND_CATCH_GHAST_FILENAME = 'music\\catch_ghast.wav'
SOUND_GET_BUFF_FILENAME = 'music\\get_buff.wav'
SOUND_GET_DEBUFF_FILENAME = 'music\\get_debuff.wav'
SOUND_SUIT_FILENAME = 'music\\suit.wav'
# 屏幕大小
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 800
# 屏幕刷新率
MAX_FPS = 30
# 格子大小
BLOCK_WIDTH = 50
# 每行每列格子个数
BLOCK_NUM = 16
UI_ITEM_WIDTH = 30 # 图标宽度
UI_ITEM_INTERVAL = 5 # 图标间隔
UI_ENERGY_WIDTH = 10 # 能量图标宽度
# 方向标识
DIR_NORTH = 1
DIR_SOUTH = 3
DIR_EAST = 4
DIR_WEST = 2
# 方向键
ARROWKEY = [pygame.K_UP, pygame.K_DOWN, pygame.K_LEFT, pygame.K_RIGHT]
# 关卡
LEVEL = []
# 占据一个点的实体
class Entity(object):
def __init__(self, x, y):
self.x = x
self.y = y
def get_pos(self):
return (self.x, self.y)
# 场景关卡
class Scene(object):
# 同等级的关卡重复次数
REPEAT = 3
# 药的频率(的倒数)
MEDICINE_FREQUENCY = 10
# 随机药丸频率(的倒数)
PILL_FREQUENCY = 3
# progress的返回值代表的意思
FLAG_ALIVE = 0
FLAG_DEAD = 1
FLAG_WIN = 2
# 关卡代号
SCENE_PRIZE = -2
SCENE_SHOP = -1
# level = 0
SCENE_PLAIN = 0
LEVEL_0 = [0]
# level = 1
SCENE_STONE_EASY = 1
SCENE_BOMB_EASY = 2
SCENE_BOMB_EASY_2 = 21
SCENE_GHAST_EASY = 3
LEVEL_1 = [1, 2, 21, 3, -1]
# level = 2
SCENE_GHAST_TWO_SLOW = 8
SCENE_STONE_MEDIUM = 4
SCENE_BOMB_TWO = 5
SCENE_BOMB_MEDIUM = 6
LEVEL_2 = [8, 4, 5, 6, -1]
# level = 3
SCENE_GHAST_MEDIUM = 7
SCENE_GHAST_TWO = 9
SCENE_STONE_HARD = 10
SCENE_BOMB_POWERFUL = 11
SCENE_BOMB_THREE = 12
LEVEL_3 = [7, 9, 10, 11, 12, -1, -1]
# level = 4
SCENE_STONE_IMP = 13
SCENE_GHAST_HARD = 14
SCENE_GHAST_TWO_HARD = 15
SCENE_BOMB_IMP = 16
SCENE_BOMB_FREQUENT = 17
LEVEL_4 = [13, 14, 15, 16, 17, -1, -1]
# level = 5
SCENE_GHAST_FAST = 18
SCENE_GHAST_IMP = 19
SCENE_BOMB_INSIDE = 20
LEVEL_5 = [13, 18, 19, 20, -1]
def __init__(self, snake, target):
self.foods = [] # 食物
self.snake = snake # 蛇
self.foods.append(Food())
self.foods[0].put_food(self.get_food_cant_in())
# 概率生成随机药丸
if random.randint(1, Scene.PILL_FREQUENCY) == 1:
self.foods.append(Pill())
self.foods[1].put_food(self.get_food_cant_in())
self.is_win = False # 是否获胜
self.target = target # 获胜目标食物数量
self.image_background = pygame.image.load(IMAGE_BACKGROUND_FILENAME).convert() # 加载关卡背景图片
self.image_arrow = pygame.image.load(IMAGE_ARROW_FILENAME).convert_alpha() # 加载箭头图片
# 是否碰到食物, 返回碰到的食物
def is_hit_food(self):
for food in self.foods:
if self.snake.body[0].x == food.x and self.snake.body[0].y == food.y:
return food
return None
# 是否进门
def is_in_door(self):
head = self.snake.body[0]
return head.x == BLOCK_NUM and head.y == BLOCK_NUM / 2
# 获取障碍物
def get_obs(self):
return self.snake.get_body_pos() # 蛇身是障碍物
# 获取食物不能放的地方
def get_food_cant_in(self):
return self.get_obs()
# 吃食物, 返回吃了的食物, 供重新投放用
def eat_food(self, food):
print("Eat food")
# 药丸吃完了就没了
if food.typ == Food.FOOD_TYPE_PILL:
food.effect(self.snake, True)
self.foods.remove(food)
return
# 还需要吃的食物数量减少
self.target -= 1
# 前面没有障碍物才可以生长
obstacle = self.get_obs()
is_in_obs = (self.snake.advance() in obstacle) # 头是否在障碍物里
cantgrow = is_in_obs
# 节食buff, 50%的概率不生长
if self.snake.is_buffed(Buff.BUFF_DIET):
print("Buff diet")
cantgrow |= random.randint(0, 1)
# 食物效果
food.effect(self.snake, cantgrow)
# 如果吃完以后生长的头在障碍物里, 则受伤, 删除一个点
if food.typ == Food.FOOD_TYPE_NORMAL and is_in_obs:
self.snake.hurt()
if random.randint(1, Scene.MEDICINE_FREQUENCY) == 1: # 随机放药
print("Medicine!")
if food.typ != Food.FOOD_TYPE_MEDICINE:
self.foods.remove(food)
self.foods.append(Medicine())
food = self.foods[-1]
elif food.typ == Food.FOOD_TYPE_MEDICINE:
self.foods.remove(food)
self.foods.append(Food())
food = self.foods[-1]
return food
# 画场景里的所有东西
def draw(self, screen):
self.draw_background(screen) # 背景
self.draw_foods(screen) # 食物
if self.is_win:
self.draw_arrow(screen) # 过关的箭头
self.draw_snake(screen) # 蛇
# 画血量
def draw_hp(self, screen):
# 先画血槽
for hp_sort in self.snake.hp_sort:
screen.blit(self.snake.image_heart_sort, hp_sort)
# 然后画血就可以覆盖上去了
for hp in self.snake.hp:
screen.blit(self.snake.image_heart, hp)
# 画钱
def draw_money(self, screen):
screen.blit(self.snake.image_money, (UI_ITEM_INTERVAL, BLOCK_WIDTH))
pos = (UI_ITEM_INTERVAL*2 + UI_ITEM_WIDTH, BLOCK_WIDTH)
show_text(screen, pos, "X %d" % self.snake.money)
# 画buff图标
def draw_buff(self, screen):
for i in range(len(self.snake.buffs)):
buff = self.snake.buffs[i]
pos = (SCREEN_WIDTH - (i+1) * (UI_ITEM_WIDTH + UI_ITEM_INTERVAL), BLOCK_WIDTH)
screen.blit(buff.image, pos)
# 画能量
def draw_energy(self, screen):
y = 0
x = SCREEN_WIDTH - UI_ITEM_INTERVAL
for i in range(self.snake.energy):
x -= UI_ENERGY_WIDTH
screen.blit(self.snake.image_energy, (x, y))
# 画背景
def draw_background(self, screen):
screen.blit(self.image_background, (0, 0))
# 画食物
def draw_foods(self, screen):
for food in self.foods:
food.draw(screen)
# 画蛇
def draw_snake(self, screen):
self.snake.draw(screen)
# 画过关箭头
def draw_arrow(self, screen):
screen.blit(self.image_arrow, get_real_pos(BLOCK_NUM-1, BLOCK_NUM/2))
# 画UI
def draw_ui(self, screen):
self.draw_hp(screen)
self.draw_money(screen)
self.draw_buff(screen)
self.draw_energy(screen)
# 场景基础过程, 返回值是判断是否通过或者死亡用的
def progress(self, direction):
# 如果受伤闪烁达到显示时间, 则停止闪烁
if self.snake.is_hurt + Snake.HURT_PULSE_TIME == frame_no:
self.snake.is_hurt = 0
# 没血了或者比4短
if len(self.snake.hp) <= 0 or self.snake.len < Snake.MIN_LEN:
self.snake.die()
return Scene.FLAG_DEAD
self.progress_snake_move()
# 因为有按前进的方向直接向前一步,所以condition必须每时每刻都判断
if self.condition_snake_move():
return Scene.FLAG_WIN
# 其他过程
self.progress_other_move()
self.condition_other_move()
# 如果dir不为0(即按下了方向键), 则改变方向
if direction:
self.snake.change_dir(direction)
# 每1s增加1点能量值
if frame_no % Snake.ENERGY_HEAL_TIME == 0 and self.snake.energy < Snake.MAX_ENERGY:
self.snake.energy += 1
return Scene.FLAG_ALIVE
# 蛇移动的过程
def progress_snake_move(self):
# 帧数当做时间, 每隔一定帧数再移动
# 速度增减的buff
speed = self.snake.speed
if self.snake.is_buffed(Buff.BUFF_CAUTIOUS):
print("Buff cautious, slow done")
speed *= 0.75
speed = int(speed)
if self.snake.is_buffed(Buff.DEBUFF_HURRY):
print("Buff hurry, accelerate")
speed += int((MAX_FPS - speed)*0.3)
if frame_no % (MAX_FPS - speed) == 0:
self.snake.move()
# 父类无其他过程, 子类重写即可
def progress_other_move(self):
pass
# 蛇移动后的判断条件
def condition_snake_move(self):
# 再判断是否撞击了什么东西
food = self.is_hit_food()
# 是否获胜并进门
if self.is_win and self.is_in_door():
print("Is in door")
return Scene.FLAG_WIN
# 撞墙
if self.snake.is_hit_wall(self.is_win):
print("hit wall")
self.snake.hit()
# 撞击音效
sound_hit.play()
# 撞自己
elif self.snake.is_hit_snake():
print("hit snake itself")
self.snake.hit()
# 撞食物
elif food is not None:
food = self.eat_food(food)
# 如果是随机药丸的话返回值是None
if food:
# 吃够了, 不再出现食物
if self.is_win is False and self.target <= 0:
self.win()
# 重新投放食物
if not self.is_win:
food.put_food(self.get_food_cant_in())
# 如果过关了就不需要投放
else:
self.foods.clear()
return Scene.FLAG_ALIVE
# 父类无其他过程判断, 子类重写即可
def condition_other_move(self):
pass
# 胜利
def win(self):
print("Win!")
self.is_win = True
# 获得食物的坐标
def get_food_pos(self):
pos = []
for food in self.foods:
pos.append(food.get_pos())
return pos
# 商店
class SceneShop(Scene):
SHOP_POS_X = [BLOCK_NUM/2, BLOCK_NUM/4, int(BLOCK_NUM/4*3)] # 商店格子
SHOP_POS_Y = BLOCK_NUM/2
def __init__(self, snake, level, num=3):
super().__init__(snake, 0)
self.win() # 不需要吃任何食物就可以下一关
self.foods.clear()
self.goods = []
# 随机放置商品
for i in range(num):
print("good %d : " % i)
self.put_goods(i, level)
# 添加商品
def put_goods(self, id, level):
total = Good.GOOD_TOTAL+1
# 用每个商品的概率来判断出现什么样的商品
sum = 0
for i in range(total):
sum += Good.GOOD_APPEAR_PROB[i]
x = random.randint(1, Good.GOOD_SUM)
if x <= sum:
if i == 0: # 0表示没有商品
print("No good")
break
# 其他表示商品代号
name = i
# 计算价格
price_level_appendix = int(level/Scene.REPEAT)*2+level-Scene.REPEAT+1
price_value_appendix = int(level/Scene.REPEAT+1) * Good.GOOD_VALUE[i]
price = price_level_appendix + price_value_appendix
self.goods.append(Good(SceneShop.SHOP_POS_X[id], SceneShop.SHOP_POS_Y, name, price))
print("Good %d , price %d" % (name, price))
break
def draw(self, screen):
super().draw(screen)
# 商品画在顶层
for good in self.goods:
good.draw(screen)
# 是否撞击商品, 返回商品或者none
def is_hit_good(self):
head = self.snake.body[0]
for good in self.goods:
if head.get_pos() == good.get_pos():
return good
return None
def condition_snake_move(self):
# 买商品
good = self.is_hit_good()
if good:
if self.snake.money >= good.price:
self.snake.money -= good.price
good.effect(self.snake)
self.goods.remove(good)
print("Purchase!")
return super().condition_snake_move()
# 奖励场景, 在商店场景基础上修改即可
class ScenePrize(SceneShop):
def __init__(self, snake):
super().__init__(snake, 1, 1) # 1个物品
def put_goods(self, id, level):
# 奖励是随机的buff
name = random.randint(0, Buff.BUFF_TOTAL-1)
prize = Good(ScenePrize.SHOP_POS_X[0], ScenePrize.SHOP_POS_Y, name)
prize.image = pygame.image.load(IMAGE_BUFF_FILENAME[name]) # 改一下图片
self.goods.append(prize)
def condition_snake_move(self):
prize = self.is_hit_good()
if prize:
self.snake.add_buff(prize.name)
self.goods.pop()
return super().condition_snake_move()
# 商品
class Good(Entity):
GOOD_TOTAL = 9
GOOD_HP = 1 # 血
GOOD_HP_SORT = 2 # 血量上限
GOOD_AID = 3 # 血包
GOOD_CAUTIOUS = 4 # 速度下降
GOOD_HELMET = 5 # 头盔
GOOD_GREAT_HELMET = 6 # 钢制头盔
GOOD_TOOL = 7 # 捉鬼工具: 用头撞鬼鬼会死
GOOD_SUIT = 8 # 防护服: 抵御一次炸弹伤害
GOOD_APPEAR_PROB = [10, 10, 5, 1, 7, 13, 7, 10, 15, 15] # 出现概率
GOOD_SUM = 93
GOOD_VALUE = [0, 1, 2, 6, 4, 4, 10, 10, 10, 20] # 商品价值
def __init__(self, x, y, name, price=0):
super().__init__(x, y)
self.price = price
self.name = name
self.init_image()
def init_image(self):
# 因为IMAGE_GOOD_FILENAME第0个是没有, 此时不需要加载图片, 不会调用到它
if self.name:
self.image = pygame.image.load(IMAGE_GOOD_FILENAME[self.name]).convert_alpha()
# 商品效果
def effect(self, snake):
# 加血
if self.name == Good.GOOD_HP:
# 播放治愈音效
sound_heal.play()
snake.add_hp()
# 加血槽
elif self.name == Good.GOOD_HP_SORT:
# getbuff音效
sound_get_buff.play()
snake.add_hp_sort()
snake.add_hp()
# 加满血
elif self.name == Good.GOOD_AID:
# 播放治愈音效
sound_heal.play()
for i in range(len(snake.hp_sort)):
snake.add_hp()
# 获得一系列的buff
elif self.name == Good.GOOD_CAUTIOUS:
snake.add_buff(Buff.BUFF_CAUTIOUS)
elif self.name == Good.GOOD_HELMET:
snake.add_buff(Buff.BUFF_HELMET)
elif self.name == Good.GOOD_GREAT_HELMET:
snake.add_buff(Buff.BUFF_GREAT_HELMET)
elif self.name == Good.GOOD_TOOL:
snake.add_buff(Buff.BUFF_TOOL)
elif self.name == Good.GOOD_SUIT:
snake.add_buff(Buff.BUFF_SUIT)
# 画商品并在下面一个格子里显示价格
def draw(self, screen):
pos_good = get_real_pos(self.x, self.y)
screen.blit(self.image, pos_good)
if self.price:
pos_text = get_real_pos(self.x, self.y+1)
show_text(screen, pos_text, "%d" % self.price)
# 石头阵
class SceneStone(Scene):
def __init__(self, snake, num, target):
self.target = target # 目标食物数
self.num = num # 石头数量
self.stones = [] # 石头
# 加载石头图片
self.image_stone = pygame.image.load(IMAGE_STONE_FILENAME).convert_alpha()
# 创建石头
self.put_stones()
super().__init__(snake, target)
# 创建石头
def put_stones(self):
stonepos = [] # 已经出现石头的位置
for i in range(self.num):
self.stones.append(Stone(stonepos)) # 不能放在已经出现过石头的位置
stonepos.append((self.stones[i].x, self.stones[i].y))
# 石头阵判断
def condition_snake_move(self):
# 撞石头
if self.is_hit_stone():
print("hit stone")
self.snake.hit()
# 播放音效
sound_hit.play()
return super().condition_snake_move()
# 移除位置为p的石头
def remove_stone(self, p):
print("Remove stone (%d, %d)" % p)
for stone in self.stones:
if p == stone.get_pos():
self.stones.remove(stone)
return
# 判断是否撞击了石头
def is_hit_stone(self):
head = self.snake.body[0]
if head.get_pos() in self.get_stones_pos():
# 头盔buff
if self.snake.is_buffed(Buff.BUFF_HELMET):
print("Buff helmet")
self.snake.use_buff(Buff.BUFF_HELMET)
self.remove_stone(self.snake.body[0].get_pos())
# 头盔撞击
sound_hit_with_helmet.play()
# 高级头盔buff
elif self.snake.is_buffed(Buff.BUFF_GREAT_HELMET):
print("Buff great-helmet")
self.snake.use_buff(Buff.BUFF_GREAT_HELMET)
self.remove_stone(self.snake.body[0].get_pos())
# 头盔撞击
sound_hit_with_helmet.play()
else:
return True
return False
# 食物不能放的地方
def get_food_cant_in(self):
cantin = super().get_food_cant_in()
cantin.extend(self.get_stone_around_cant_in()) # 被2个石头包围的位置
return cantin
# 障碍物
def get_obs(self):
obs = super().get_obs()
obs.extend(self.get_stones_pos())
return obs
# 获取石头坐标
def get_stones_pos(self):
return get_entity_pos(self.stones)
# 获取因石头包围而不能放的地方
def get_stone_around_cant_in(self):
cantin = []
# 用来计算周围有几个石头
cnt = [[0 for i in range(BLOCK_NUM)] for j in range(BLOCK_NUM)]
for stone in self.stones:
x, y = stone.x, stone.y
if x+1 in range(BLOCK_NUM):
cnt[x+1][y] += 1
if x-1 in range(BLOCK_NUM):
cnt[x-1][y] += 1
if y+1 in range(BLOCK_NUM):
cnt[x][y+1] += 1
if y-1 in range(BLOCK_NUM):
cnt[x][y-1] += 1
# 周围石头 >= 2 的不能投放
for x in range(BLOCK_NUM):
for y in range(BLOCK_NUM):
if cnt[x][y] >= 2:
cantin.append((x, y))
return cantin
# 画图
def draw(self, screen):
# 要先画背景
super().draw(screen)
# 然后再画石头
for stone in self.stones:
x, y = stone.get_pos()
screen.blit(self.image_stone, get_real_pos(x, y))
# 鬼石头
class SceneStoneGhast(SceneStone):
def __init__(self, snake, stone_num, target, ghost_num, speed):
self.ghost_num = ghost_num # 鬼数量
self.ghosts = []
super().__init__(snake, stone_num, target)
self.put_ghosts(ghost_num, speed)
# 创建鬼
def put_ghosts(self, num, speed):
cantin = self.get_obs()
for i in range(num):
ghost = Ghast(speed)
ghost.appear(cantin)
self.ghosts.append(ghost)
# 鬼不能放在同一个位置
cantin.append(ghost.get_pos())
# 鬼是否撞到了蛇, 撞到则返回撞蛇的鬼
def is_ghost_hit(self, ghost):
body = self.snake.get_body_pos()
return (ghost.x, ghost.y) in body and not ghost.is_hurt_snake
def draw(self, screen):
super().draw(screen)
# 鬼魂在最顶层
for ghost in self.ghosts:
ghost.draw(screen)
# 获取鬼坐标
def get_ghosts_pos(self):
return get_entity_pos(self.ghosts)
# 鬼的移动
def progress_other_move(self):
for ghost in self.ghosts:
if frame_no % (MAX_FPS - ghost.speed) == 0:
ghost.move()
return super().progress_other_move()
# 判断鬼有没有撞到蛇
def condition_other_move(self):
for ghost in self.ghosts:
if self.is_ghost_hit(ghost):
print("Ghast (%d, %d) hit snake" % (ghost.x, ghost.y))
# 如果有捉鬼工具且鬼在蛇头的位置, 则删除鬼
if (ghost.get_pos() == self.snake.body[0].get_pos() and
self.snake.is_buffed(Buff.BUFF_TOOL)):
print("Buff tool")
# 抓鬼音效
sound_catch_ghast.play()
self.snake.use_buff(Buff.BUFF_TOOL)
# 删除鬼
self.ghosts.remove(ghost)
continue
# 如果有防护服则抵挡一次伤害
if self.snake.is_buffed(Buff.BUFF_BRAVE):
print("Buff brave")
self.snake.use_buff(Buff.BUFF_BRAVE)
else:
self.snake.hit()
ghost.is_hurt_snake = True
return super().condition_other_move()
# 炸弹场景
class SceneBomb(Scene):
BOMB_TIME = MAX_FPS # 炸弹爆炸时间
SPARK_TIME = MAX_FPS / 2 # 火花产生时间
def __init__(self, snake, num, once, frequency, power, target=float("inf")):
self.bombs = []
self.power = power # 炸弹爆炸的范围
self.frequency = frequency # 炸弹间隔时间
self.target = target
self.num = num * once # 炸弹波数
self.once = once # 一次出几个
self.sparks = [] # 爆炸后的火花
self.init_image() # 加载图片
super().__init__(snake, target)
# 加载图片
def init_image(self):
self.image_bomb = []
for i in range(len(IMAGE_BOMB_FILENAME)):
self.image_bomb.append(pygame.image.load(IMAGE_BOMB_FILENAME[i]).convert_alpha())
self.image_spark = []
for i in range(len(IMAGE_SPARK_FILENAME)):
self.image_spark.append(pygame.image.load(IMAGE_SPARK_FILENAME[i]).convert_alpha())
def get_obs(self):
obs = super().get_obs()
obs.extend(get_entity_pos(self.bombs)) # 炸弹是障碍物
return obs
def draw(self, screen):
super().draw(screen)
# 画炸弹
for bomb in self.bombs:
last = frame_no - bomb.start_frame
if last >= SceneBomb.BOMB_TIME:
continue
id = get_pic_id(last, SceneBomb.BOMB_TIME, len(IMAGE_BOMB_FILENAME))
x, y = bomb.get_pos()
screen.blit(self.image_bomb[id], get_real_pos(x, y))
# 画火花
for spark in self.sparks:
last = frame_no - spark.start_frame
if last >= SceneBomb.SPARK_TIME:
continue
id = get_pic_id(last, SceneBomb.SPARK_TIME, len(IMAGE_SPARK_FILENAME))
x, y = spark.get_pos()
screen.blit(self.image_spark[id], get_real_pos(x, y))
def condition_snake_move(self):
# 撞炸弹
if self.is_hit_bomb():
print("hit bomb")
self.snake.hit()
return super().condition_snake_move()
# 判断是否撞到了炸弹
def is_hit_bomb(self):
head = self.snake.body[0]
return head.get_pos() in get_entity_pos(self.bombs)
# 炸弹炸到了蛇
def is_bomb_hit(self, sparkpos):
for spark in self.sparks:
for body in self.snake.body:
if body.get_pos() in sparkpos:
return True
return False
# 放炸弹,需要重写游戏过程
def progress(self, direction):
if frame_no % self.frequency == 0 and self.num > 0:
for i in range(self.once):
self.put_bomb()
# 炸弹放完了才赢
if self.num <= 0:
self.win()
for bomb in self.bombs:
# 炸弹到了爆炸时间
if frame_no - bomb.start_frame >= SceneBomb.BOMB_TIME:
self.explode(bomb)
for spark in self.sparks:
# 火花到了消失时间
if frame_no - spark.start_frame >= SceneBomb.SPARK_TIME:
self.sparks.remove(spark)
return super().progress(direction)
# 炸弹爆炸
def explode(self, bomb):
print("Bomb explode (%d, %d)" % bomb.get_pos())
# 炸弹个数减少
self.num -= 1
# 音效
sound_explode.play()
self.bombs.remove(bomb) # 删除爆炸的炸弹
x, y = bomb.get_pos()
# 在爆炸范围内产生火花
sparkpos = []
self.put_spark(x, y)
for i in range(self.power):
# 将此次爆炸的伤害范围存储
sparkpos.extend([(x+i+1, y), (x-i-1, y), (x, y+i+1), (x, y-i-1)])
self.put_spark(x+i+1, y)
self.put_spark(x-i-1, y)
self.put_spark(x, y+i+1)
self.put_spark(x, y-i-1)
if self.is_bomb_hit(sparkpos): # 判断蛇身体是否在伤害范围内
# 防弹衣buff
if self.snake.is_buffed(Buff.BUFF_SUIT):
# 播放防护服音效
sound_suit.play()
self.snake.use_buff(Buff.BUFF_SUIT)
else:
self.snake.hit()
# 放置新的炸弹
def put_bomb(self):
cantin = self.get_obs()
cantin.extend(self.get_food_pos())
self.bombs.append(Bomb(cantin))
# 产生火花
def put_spark(self, x, y):
self.sparks.append(Spark(x, y))
# 炸弹
class Bomb(Entity):
def __init__(self, cantin):
self.start_frame = frame_no # 放置炸弹时的帧数
cantin.append((-1, -1))
x, y = -1, -1
while (x, y) in cantin:
x, y = random_pos() # 创建的时候就放置好了
self.x, self.y = x, y
print("Put bomb at (%d, %d)" % (x, y))
# 火花
class Spark(Entity):
def __init__(self, x, y):
super().__init__(x, y)
self.start_frame = frame_no
# 食物
class Food(Entity):
# 类型标志
FOOD_TYPE_NORMAL = 0
FOOD_TYPE_MEDICINE = 1
FOOD_TYPE_PILL = 2
def __init__(self, x=0, y=0):
super().__init__(x, y)
self.image = pygame.image.load(IMAGE_FOOD_FILENAME).convert_alpha()
self.typ = Food.FOOD_TYPE_NORMAL
# 投放食物
def put_food(self, cantin=[]):
x, y = -1, -1
cantin.append((-1, -1))
while (x, y) in cantin:
x, y = random_pos(2, BLOCK_NUM-3) # 食物不能离樯太近, 否则会一头扎进樯里
self.x, self.y = x, y
print("Put food at (%d, %d)" % (x, y))
# 食物效果
def effect(self, snake, cantgrow):
if not cantgrow:
snake.add_node() # 增长
snake.score += 1 # 加分
snake.money += 1 # 加钱
# 播放音效
sound_eat.play()
def draw(self, screen):
screen.blit(self.image, get_real_pos(self.x, self.y))
# 药
class Medicine(Food):
def __init__(self, x=0, y=0):
super().__init__(x=x, y=y)
self.image = pygame.image.load(IMAGE_MEDICINE_FILENAME).convert_alpha()
self.typ = Food.FOOD_TYPE_MEDICINE
def effect(self, snake, cantgrow):
# 吃药不加钱
snake.add_hp()
snake.score += 1
# 播放治愈音效
sound_heal.play()
# 随机药丸
class Pill(Food):
def __init__(self, x=0, y=0):
super().__init__(x=x, y=y)
self.image = pygame.image.load(IMAGE_PILL_FILENAME).convert_alpha()
self.typ = Food.FOOD_TYPE_PILL
def effect(self, snake, cantgrow):
# 1/15 钱5倍
if random.randint(1, 15) == 15:
# 播放音效
sound_get_buff.play()
snake.money *= 5
# 1/15 受伤
elif random.randint(2, 15) == 15:
snake.hurt()
# 1/15 消除身上所有buff和debuff
elif random.randint(3, 15) == 15:
# 播放音效
sound_get_debuff.play()
snake.buffs.clear()
# 1/15 单次(de)buff使用次数加一
elif random.randint(4, 15) == 15:
# 播放音效
sound_get_buff.play()
for buff in snake.buffs:
buff.usage += 1
# /15 随机buff/debuff
else:
snake.add_buff(random.randint(0, Buff.BUFF_TOTAL + Buff.DEBUFF_TOTAL - 1))
# 石头
class Stone(Entity):
def __init__(self, stones, x=0, y=0):
super().__init__(x, y)
x, y = 0, BLOCK_NUM/2
# 进门头两个块不能是石头, 出门的门口不能是石头
cantin = [(0, BLOCK_NUM/2), (1, BLOCK_NUM/2), (BLOCK_NUM-1, BLOCK_NUM/2)]
cantin.extend(stones)
while (x, y) in cantin:
x, y = random_pos()
self.x, self.y = x, y
print("Place a stone in (%d, %d)" % (x, y))
# 蛇身结点
class Node(Entity):
# 蛇块种类标识
NODE_TYPE_HEAD = 1
NODE_TYPE_BODY = 2
NODE_TYPE_TAIL = 3
def __init__(self, typ, x=0, y=0, direction=0):
super().__init__(x, y)
self.typ = typ # 种类
self.direction = direction # 朝向
# 增(减)益效果
class Buff(object):
BUFF_TOTAL = 8
BUFF_HELMET = 0 # 抵御1次撞击石头并把石头击碎
BUFF_GREAT_HELMET = 1 # 抵御3次撞击石头并把石头击碎
BUFF_HEAL = 2 # 过关恢复1滴血
BUFF_CAUTIOUS = 3 # 速度减慢
BUFF_DIET = 4 # 50%几率吃食物但不变长
BUFF_BRAVE = 5 # 抵御1次鬼魂伤害
BUFF_SUIT = 6 # 抵御1次炸弹伤害
BUFF_TOOL = 7 # 用头撞鬼鬼会死
DEBUFF_TOTAL = 2
DEBUFF_HURRY = 8 # 速度增加
DEBUFF_DIZZY = 9 # 按键对调
def __init__(self, name, image):
super().__init__()
self.name = name
self.image = image
self.usage = 0
# 蛇
class Snake(object):
MIN_LEN = 4 # 最小长度, 蛇的初始长度
INITIAL_SPEED = 20 # 初始速度
INITIAL_DIR = DIR_EAST # 初始方向
INITIAL_HP = 10 # 初始血量(上限)
EYE_TIME = MAX_FPS # 眼珠子转一圈的周期
HURT_PULSE_TIME = MAX_FPS / 2 # 受伤的闪烁时间
MAX_ENERGY = 10 # 能量上限
ENERGY_HEAL_TIME = MAX_FPS # 能量恢复速率
def __init__(self):
self.direction = self.INITIAL_DIR # 初始方向默认向东
self.body = [] # 蛇身
self.speed = Snake.INITIAL_SPEED # 速度 MAX_FPS - speed
self.len = Snake.MIN_LEN # 长度, 起始为4
self.is_hurt = 0 # 受伤的判断标志, 0表示没有受伤, 其他数字表示受伤的时间点(帧数)
self.money = 0 # 钱
self.score = 0 # 分数
# 按下键盘后再按另一个键会处理出错,用标识来表示是否已经处理了按键,如果没有处理,则按键无效
self.is_turn_but_wating_moving = False
# 按下方向键以后不能立马把头的方向改变,否则会画出已经改变方向的图,但实际上还没有移动,用标识传递在move里修改方向的消息
self.is_change_in_moving = 0
self.energy = 0 # 能量值, 加速用
self.buffs = [] # buff
self.image_buff = []
self.init_image()
# 血槽和血
self.hp = []
self.hp_sort = []
for i in range(Snake.INITIAL_HP):
self.add_hp_sort()
self.add_hp()
# 创建一个蛇
self.build()
# 加载图片
def init_image(self):
# 加载心图片
self.image_heart = pygame.image.load(IMAGE_HEART_FILENAME).convert_alpha()
self.image_heart_sort = pygame.image.load(IMAGE_HEART_SORT_FILENAME).convert_alpha()
# 加载蛇图片
# 头
self.image_snake_head = []
for j in range(4):
tmp = []
for i in range(len(IMAGE_SNAKE_HEAD_FILENAME)):
t = pygame.transform.rotate(pygame.image.load(IMAGE_SNAKE_HEAD_FILENAME[i]).convert_alpha(), 90 * j)
tmp.append(t)
self.image_snake_head.append(tmp)
# 身体
self.image_snake_body = []
for i in range(4):
t = pygame.transform.rotate(pygame.image.load(IMAGE_SNAKE_BODY_FILENAME).convert_alpha(), 90 * i)
self.image_snake_body.append(t)
# 尾
self.image_snake_tail = []
for i in range(4):
t = pygame.transform.rotate(pygame.image.load(IMAGE_SNAKE_TAIL_FILENAME).convert_alpha(), 90 * i)
self.image_snake_tail.append(t)
self.image_money = pygame.image.load(IMAGE_MONEY_FILENAME).convert_alpha()
# buff图标
for i in range(len(IMAGE_BUFF_FILENAME)):
self.image_buff.append(pygame.image.load(IMAGE_BUFF_FILENAME[i]).convert_alpha())
# 能量图标
self.image_energy = pygame.image.load(IMAGE_ENERGY_FILENAME).convert_alpha()
# 创建一个蛇
def build(self):
print("build a snake")
# 依次构造头, 身体(们), 尾
self.body.append(Node(Node.NODE_TYPE_HEAD))
for i in range(self.len-2):
self.body.append(Node(Node.NODE_TYPE_BODY))
self.body.append(Node(Node.NODE_TYPE_TAIL))
# 设置各个部位的位置和朝向
self.reset()
# 获取前一个格子的坐标
def advance(self):
x, y = self.body[0].x, self.body[0].y
if self.direction == DIR_EAST:
x += 1
elif self.direction == DIR_WEST:
x -= 1
elif self.direction == DIR_SOUTH:
y += 1
elif self.direction == DIR_NORTH:
y -= 1
return x, y
# 向前方增加一个结点
def add_node(self):
print("Add a node and turn to head")
self.len += 1
# 获取下一个点的坐标
x, y = self.advance()
node = Node(Node.NODE_TYPE_HEAD, x, y, self.direction)
self.body[0].typ = Node.NODE_TYPE_BODY # 他不是头了应该是身体了
self.body.insert(0, node) # 插入到首端并改成头
return self.body[0]
# 删除最后一个结点
def del_node(self):
print("Delete last node and turn to tail")
self.len -= 1
self.body[-2].typ = Node.NODE_TYPE_TAIL # 最后一个改成尾巴
return self.body.pop()
# 是否撞墙
def is_hit_wall(self, is_win):
is_hit = False
# 头是否在范围内
head = self.body[0]
is_hit = head.x not in range(BLOCK_NUM) or head.y not in range(BLOCK_NUM)
# 如果获胜, 那么门开了, 那么撞门的地方不算撞墙
if is_win and head.x == BLOCK_NUM-1 and head.y == BLOCK_NUM/2:
is_hit = False
return is_hit
# 是否撞自己
def is_hit_snake(self):
# 除去头的身体的坐标
bodies = self.get_body_pos()
bodies.remove((self.body[0].x, self.body[0].y))
# 判断头是否在身体里
return (self.body[0].x, self.body[0].y) in bodies
# 撞击, 把头向后一格
def go_back(self):
print("Head can't move and delete")
self.len -= 1
direction = self.body[0].direction
del self.body[0] # 删除头
# 把第一个身体改成头
self.body[0].typ = Node.NODE_TYPE_HEAD
self.body[0].direction = direction
# 获取身体结点坐标
def get_body_pos(self):
return get_entity_pos(self.body)
# 转向
def turn_snake(self):
print("Turn")
if self.is_change_in_moving: # 按了方向键,但是不能马上改变方向,否则画图时会画到已经变了的方向
print("Turn to direction %d" % self.direction)
self.body[0].direction = self.direction
self.body[1].direction = self.direction # 身体的方向也必须转过来
# 移动
def move(self):
print("move")
# 如果转向后受伤,那么整体的方向可能会朝着身体内部,在这里判断并改变方向
if (self.direction - self.body[1].direction + 4) % 4 == 2:
self.direction = self.body[0].direction = self.body[1].direction
# 在前端新增结点, 末端删除结点
self.add_node()
self.del_node()
# 改变方向
self.turn_snake()
# 移动了, 下次转向有效
self.is_turn_but_wating_moving = False
# 死了
def die(self):
print("GAME OVER")
return True
# 受伤
def hurt(self):
print("Hurt")
# 播放音效
sound_hurt.play()
self.is_hurt = frame_no
if len(self.hp): # 防止同时两个伤害而pop空了出错
self.hp.pop()
# 改变方向
def change_dir(self, direction):
# 头晕buff, 方向相反
if self.is_buffed(Buff.DEBUFF_DIZZY):
print("Debuff dizzy")
direction = (direction + 2) % 4
if direction == 0:
direction = 4
# 按下前进的方向键, 进行冲刺
if direction == self.direction:
self.dash()
# 如果还没有移动,则此次改变方向无效
if self.is_turn_but_wating_moving is True:
return
self.is_turn_but_wating_moving = True
# 不能走相对(身体)方向
if (direction - self.body[1].direction + 4) % 4 == 2:
return
# 可以先改变蛇整体的方向
self.direction = direction
print("Change direction to direction %d" % direction)
self.is_change_in_moving = direction # 在下一步move里改变node的方向
# 冲刺
def dash(self):
# 消耗能量进行快速移动
if self.energy:
print("Dash")
self.energy -= 1
self.move()
# 紧跟移动后的撞击判断, 否则当自动移动和dash同时出发会出现跨过现象
data.scene.condition_snake_move()
# 撞击
def hit(self):
print("Hit")
self.hurt() # 受伤
self.go_back() # 删除头结点
# 将蛇身的位置重置
def reset(self):
print("Reset snake")
self.direction = self.INITIAL_DIR
for i in range(self.len):
self.body[i].direction = self.INITIAL_DIR
self.body[i].y = BLOCK_NUM/2
self.body[i].x = -i
# 增加血量上限
def add_hp_sort(self):
print("Add HP sort")
i = len(self.hp_sort)
x = i * (UI_ITEM_WIDTH + UI_ITEM_INTERVAL) + UI_ITEM_INTERVAL
self.hp_sort.append((x, 0))
# 增加血量
def add_hp(self):
i = len(self.hp)
# 如果超过了上限就不加
if i >= len(self.hp_sort):
return
x = i * (UI_ITEM_WIDTH + UI_ITEM_INTERVAL) + UI_ITEM_INTERVAL
print("Add HP")
self.hp.append((x, 0))
# 添加buff
def add_buff(self, name):
print("Add buff %d" % name)
if name < Buff.DEBUFF_HURRY:
sound_get_buff.play()
else:
sound_get_debuff.play()
buff = Buff(name, self.image_buff[name])
# 高级头盔能用3次
if name == Buff.BUFF_GREAT_HELMET:
buff.usage = 3
else:
buff.usage = 1
self.buffs.append(buff)
# 是否具有该buff
def is_buffed(self, name):
for buff in self.buffs:
if buff.name == name:
return True
return False
# 通过名字获取buff实例
def get_buff(self, name):
for buff in self.buffs:
if buff.name == name:
return buff
return None
# 触发一次性的buff(工具等)
def use_buff(self, name):
buff = self.get_buff(name)
buff.usage -= 1
if buff.usage == 0:
self.buffs.remove(buff)
# 画蛇
def draw(self, screen):
for node in self.body:
x, y, direction = node.x, node.y, node.direction % 4
pos = get_real_pos(x, y)
# 受伤的时候闪烁
if self.is_hurt and (frame_no-self.is_hurt) % 6 in [1, 2, 3]:
return
# 没有出来的块不用画
if node.x in range(BLOCK_NUM) and node.y in range(BLOCK_NUM):
if node.typ == Node.NODE_TYPE_HEAD:
# 眼珠转
last = frame_no % Snake.EYE_TIME
id = get_pic_id(last, Snake.EYE_TIME, len(IMAGE_SNAKE_HEAD_FILENAME))
# id = int(frame_no / 3) % len(IMAGE_SNAKE_HEAD_FILENAME)
# 画蛇头
screen.blit(self.image_snake_head[direction][id], pos)
# 画身子
elif node.typ == Node.NODE_TYPE_BODY:
screen.blit(self.image_snake_body[direction], pos)
# 画尾巴
elif node.typ == Node.NODE_TYPE_TAIL:
screen.blit(self.image_snake_tail[direction], pos)
# 怪物总类
class Sprite(Entity):
# 标志
SPRITE_TYPE_GHAST = 0
def __init__(self, speed=0, x=-1, y=-1):
super().__init__(x, y)
self.typ = -1
self.image = None # 子类补
self.speed = speed
# 生成
def appear(self, cantin):
cantin.append((-1, -1))
x, y = -1, -1
while (x, y) in cantin:
x, y = random_pos(1, BLOCK_NUM-2)
self.x, self.y = x, y
print("Sprite appear at (%d, %d)" % (x, y))
def draw(self, screen):
screen.blit(self.image, get_real_pos(self.x, self.y))
# 留给子类写
def move(self):
pass
# 鬼
class Ghast(Sprite):
# 鬼的移动方向
DX = [1, 1, -1, -1]
DY = [1, -1, 1, -1]
def __init__(self, speed=0, direction=0, x=-1, y=-1):
super().__init__(speed, x, y)
self.typ = Sprite.SPRITE_TYPE_GHAST
self.image = pygame.image.load(IMAGE_GHAST_FILENAME).convert_alpha()
self.direction = direction
self.kx = 1 # 碰撞系数(撞墙反弹用)
self.ky = 1
self.is_hurt_snake = False # 因为判断是否碰到蛇是每时每刻都在进行的,而真正的伤害是移动的一次
# 所以用一个标记来判断是不是应该造成伤害
def appear(self, cantin):
return super().appear(cantin)
self.direction = random.randint(0, 3) # 随机方向
# 鬼的移动
def move(self):
print("Ghast move (%d, %d)" % (self.x, self.y))
# 碰到墙反弹
if self.x == 0 or self.x == BLOCK_NUM-1:
self.kx *= -1
if self.y == 0 or self.y == BLOCK_NUM-1:
self.ky *= -1
self.x += self.kx * Ghast.DX[self.direction]
self.y += self.ky * Ghast.DY[self.direction]
# 移动了, 可以再次造成伤害
self.is_hurt_snake = False
# 游戏数据
class GameData(object):
# 游戏状态标识
STATE_HOME = 0
STATE_GAME = 1
STATE_OVER = 3
def __init__(self):
super().__init__()
self.snake = None
self.level = Scene.REPEAT - 1 # 保证平板只有一次
self.scene = None
self.state = GameData.STATE_HOME
self.screen = None
self.prize = 0
self.image_home = None
self.image_gameover = None
self.game_bgm = 0
# 帧数
frame_no = 0
# 音效
sound_eat = None
sound_hurt = None
sound_heal = None
sound_hit = None
sound_explode = None
sound_hit_with_helmet = None
sound_catch_ghast = None
sound_get_buff = None
sound_get_debuff = None
sound_suit = None
data = GameData()
# 获取一个(子)实体列表的所有坐标
def get_entity_pos(es):
pos = []
for e in es:
pos.append((e.x, e.y))
return pos
# 通过坐标得到格子的实际位置
def get_real_pos(x, y):
return (x * BLOCK_WIDTH, y * BLOCK_WIDTH)
# 随机位置
def random_pos(left=0, right=BLOCK_NUM-1):
return random.randint(left, right), random.randint(left, right)
# 通过持续时间, 显示周期, 图片数量获取当前应该显示的是动画中的哪一张
def get_pic_id(last, T, num):
part = int(T / num)
id = int(last / part)
id %= num
return id
def play_bgm(bgm_id, loop=-1):
print("play bgm %d" % bgm_id)
# 如果正在播放则停止
if pygame.mixer.music.get_busy():
pygame.mixer.music.stop()
# 重新加载
pygame.mixer.music.load(MUSIC_BGM_FILENAME[bgm_id])
pygame.mixer.music.play(loop)
data.game_bgm = bgm_id
def show_text(screen, pos, text, color=(255, 255, 255), font_size=26):
cur_font = pygame.font.SysFont('arial', font_size)
text_fmt = cur_font.render(text, 1, color)
screen.blit(text_fmt, pos)
# 根据按键返回方向标识
def get_dir_by_key(key):
if key == pygame.K_RIGHT:
return DIR_EAST
elif key == pygame.K_UP:
return DIR_NORTH
elif key == pygame.K_LEFT:
return DIR_WEST
elif key == pygame.K_DOWN:
return DIR_SOUTH
else:
return
# (重新)开始游戏
def start_game(snake):
print("Game start")
# 初始化游戏数
data.level = Scene.REPEAT-1
data.snake = snake
data.state = GameData.STATE_GAME
data.score = 0
data.prize = 1
next_level(snake)
# 返回首页
def back_to_home():
print("Back to home page")
play_bgm(0)
data.state = GameData.STATE_HOME
# 首页过程
def home_progress(key):
data.screen.blit(data.image_home, (0, 0))
# 左下角显示版本号
font_size = 14
pos = (0, SCREEN_HEIGHT - font_size)
black = (0, 0, 0)
show_text(data.screen, pos, GAME_VERSION, black, font_size)
if key:
if key == pygame.K_SPACE:
start_game(snake=Snake()) # 重首页开始游戏的蛇要新建
# 下一关
def next_level(snake):
level = int(data.level / Scene.REPEAT)
print("Next level, level = %d" % data.level)
# 重置蛇到开始的位置
snake.reset()
# 恢复buff:
if snake.is_buffed(Buff.BUFF_HEAL):
snake.add_hp()
# 眩晕debuff维持一个关卡(否则太难了T_T, 反正我就没过过关T_T)
if snake.is_buffed(Buff.DEBUFF_DIZZY) and data.level > 2:
snake.use_buff(Buff.DEBUFF_DIZZY)
# 根据等级随机关卡
rd = random.randint(0, len(LEVEL[level]) - 1)
id = LEVEL[level][rd]
# 确认过第一次过了3个同等级的关(或者第0关), 则进入奖励房间
if data.level % Scene.REPEAT == 0 and level == data.prize:
data.prize += 1
data.level -= 1
id = Scene.SCENE_PRIZE
# 到顶就不能加了
if level < len(LEVEL):
data.level += 1
# 根据id改变关卡
scene = None
if id == Scene.SCENE_PLAIN:
print("Scene plain")
# 平板, 吃5个食物过关
scene = Scene(snake, 5)
elif id == Scene.SCENE_STONE_EASY:
print("Scene stone easy")
# 石头阵, 20个石头, 吃5个食物过关
scene = SceneStone(snake, 20, 5)
elif id == Scene.SCENE_GHAST_EASY:
print("Scene ghost_easy")
# 1个鬼, 速度正常(10), 无石头, 吃5个食物过关
scene = SceneStoneGhast(snake, 0, 5, 1, 10)
elif id == Scene.SCENE_BOMB_EASY:
print("Scene bomb easy")
# 炸弹, 一次1个, 范围1, 间隔3s, 吃10个食物过关
scene = SceneBomb(snake, float("inf"), 1, MAX_FPS*3, 1, 10)
elif id == Scene.SCENE_STONE_MEDIUM:
print("Scene stone medium")
# 石头阵, 30个石头, 吃8个食物过关
scene = SceneStone(snake, 30, 8)
elif id == Scene.SCENE_GHAST_MEDIUM:
print("Scene ghost medium")
# 1个鬼, 速度正常(10), 10个石头, 吃5个食物过关
scene = SceneStoneGhast(snake, 10, 5, 1, 10)
elif id == Scene.SCENE_GHAST_TWO_SLOW:
print("Scene ghost two slow")
# 2个鬼, 速度慢(8), 5个石头, 吃5个食物过关
scene = SceneStoneGhast(snake, 5, 5, 2, 8)
elif id == Scene.SCENE_GHAST_TWO:
print("Scene ghost two")
# 2个鬼, 速度正常(10), 无石头, 吃5个食物过关
scene = SceneStoneGhast(snake, 0, 5, 2, 10)
elif id == Scene.SCENE_BOMB_TWO:
print("Scene bomb two")
# 炸弹, 一次2个, 范围1, 间隔2s, 共10波, 过关
scene = SceneBomb(snake, 10, 2, MAX_FPS*2, 1)
elif id == Scene.SCENE_BOMB_MEDIUM:
print("Scene bomb medium")
# 炸弹, 一次1个, 范围2, 间隔2s, 共10波, 过关
scene = SceneBomb(snake, 10, 1, MAX_FPS*2, 2)
elif id == Scene.SCENE_STONE_HARD:
print("Scene stone hard")
# 石头阵, 50个石头, 吃10个食物过关
scene = SceneStone(snake, 50, 10)
elif id == Scene.SCENE_GHAST_HARD:
print("Scene ghost hard")
# 1个鬼, 速度正常(10), 30个石头, 吃5个食物过关
scene = SceneStoneGhast(snake, 30, 5, 1, 10)
elif id == Scene.SCENE_GHAST_TWO_HARD:
print("Scene ghost two hard")
# 2个鬼, 速度正常(10), 5个石头, 吃10个食物过关
scene = SceneStoneGhast(snake, 5, 10, 2, 10)
elif id == Scene.SCENE_BOMB_POWERFUL:
print("Scene bomb powerful")
# 炸弹, 一次1个, 范围3, 间隔5s, 吃5个食物过关
scene = SceneBomb(snake, float("inf"), 1, MAX_FPS*5, 3, 5)
elif id == Scene.SCENE_BOMB_THREE:
print("Scene bomb three")
# 炸弹, 一次3个, 范围1, 间隔5s, 共5波, 过关
scene = SceneBomb(snake, 5, 3, 1, MAX_FPS*5)
elif id == Scene.SCENE_STONE_IMP:
print("Scene stone impossible")
# 石头阵, 60个石头, 吃10个食物过关
scene = SceneStone(snake, 60, 10)
elif id == Scene.SCENE_GHAST_FAST:
print("Scene ghost fast")
# 1个鬼, 速度快(20), 无石头, 吃10个食物过关
scene = SceneStoneGhast(snake, 0, 10, 1, 20)
elif id == Scene.SCENE_GHAST_IMP:
print("Scene ghost impossible")
# 1个鬼, 速度正常(10), 30个石头, 吃5个食物过关
scene = SceneStoneGhast(snake, 30, 5, 1, 10)
elif id == Scene.SCENE_BOMB_IMP:
print("Scene bomb impossible")
# 炸弹, 一次5个, 范围3, 间隔1s, 共20波, 过关
scene = SceneBomb(snake, 20, 5, MAX_FPS, 3)
elif id == Scene.SCENE_BOMB_FREQUENT:
print("Scene bomb frequent")
# 炸弹, 一次1个, 范围1, 间隔0.2s, 共50波, 过关
scene = SceneBomb(snake, 50, 1, MAX_FPS/5, 1)
elif id == Scene.SCENE_BOMB_EASY_2:
print("Scene bomb easy 2")
# 炸弹, 一次1个, 范围1, 间隔3s, 共10波, 过关
scene = SceneBomb(snake, 10, 1, MAX_FPS*3, 1)
elif id == Scene.SCENE_BOMB_INSIDE:
print("Scene bomb inside")
# 炸弹, 一次2个, 范围5, 间隔1s, 吃5个食物过关
scene = SceneBomb(snake, float("inf"), 2, MAX_FPS, 5, 5)
elif id == Scene.SCENE_SHOP:
print("Scene shop!")
scene = SceneShop(snake, data.level)
data.level -= 1 # 商店不算关卡
elif id == Scene.SCENE_PRIZE:
print("Scene Prize!")
scene = ScenePrize(snake)
data.scene = scene
# 更换背景音乐
# 商店
if id == Scene.SCENE_SHOP:
if data.game_bgm != id:
play_bgm(id)
# 奖励
elif id == Scene.SCENE_PRIZE:
if data.game_bgm != id:
play_bgm(id)
else:
# 每个level的音乐不一样
for i in range(len(LEVEL)):
if id in LEVEL[i]:
if data.game_bgm != i + 1:
play_bgm(i + 1)
else:
break
# 游戏过程
def game_progress(key):
direction = 0
if key == pygame.K_q:
back_to_home()
elif key in ARROWKEY:
direction = get_dir_by_key(key)
# 调用场景过程
flag = data.scene.progress(direction)
# 死了计算最终分数并显示死亡界面
if flag == Scene.FLAG_DEAD:
data.state = GameData.STATE_OVER
data.snake.score += (data.level - Scene.REPEAT) * 100
data.snake.score += (data.snake.len - Snake.MIN_LEN + 1) * 10
play_bgm(-3, 1)
# 赢了就下一关
elif flag == Scene.FLAG_WIN:
next_level(data.scene.snake)
# 绘图
data.scene.draw(data.screen)
# UI在游戏图层之上
data.scene.draw_ui(data.screen)
# 死亡画面(过程)
def gameover_progress(key):
data.screen.blit(data.image_gameover, (0, 0))
pos = (SCREEN_WIDTH/2 - 150, SCREEN_HEIGHT/2)
black = (0, 0, 0)
# 显示分数
show_text(data.screen, pos, "Score: %d" % data.snake.score, black, 60)
if key == pygame.K_SPACE:
start_game(Snake())
# 初始化pygame
def init_pygame():
pygame.init()
# 设置标题
data.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption(GAME_NAME + " - " + GAME_VERSION)
# 设置图标
icon = pygame.image.load(ICON_FILENAME).convert_alpha()
pygame.display.set_icon(icon)
return pygame.time.Clock()
# 初始化音效
def init_sounds():
global sound_eat, sound_catch_ghast, sound_explode, sound_get_buff
global sound_get_debuff, sound_heal, sound_hit, sound_hit_with_helmet
global sound_hurt, sound_suit
sound_eat = pygame.mixer.Sound(SOUND_EAT_FILENAME)
sound_hurt = pygame.mixer.Sound(SOUND_HURT_FILENAME)
sound_explode = pygame.mixer.Sound(SOUND_EXPLODE_FILENAME)
sound_get_buff = pygame.mixer.Sound(SOUND_GET_BUFF_FILENAME)
sound_get_debuff = pygame.mixer.Sound(SOUND_GET_DEBUFF_FILENAME)
sound_heal = pygame.mixer.Sound(SOUND_HEAL_FILENAME)
sound_hit = pygame.mixer.Sound(SOUND_HIT_FILENAME)
sound_hit_with_helmet = pygame.mixer.Sound(SOUND_HIT_WITH_HELMET_FILENAME)
sound_suit = pygame.mixer.Sound(SOUND_SUIT_FILENAME)
sound_catch_ghast = pygame.mixer.Sound(SOUND_CATCH_GHAST_FILENAME)
# 初始化游戏数据
def init_game_data():
# 加载数据
# 初始在首页
data.state = GameData.STATE_HOME
# 载入首页图片
data.image_home = pygame.image.load(IMAGE_HOME_FILENAME).convert()
# 载入死亡界面图片
data.image_gameover = pygame.image.load(IMAGE_GAMEOVER_FILENAME).convert()
# 载入首页的背景音乐
play_bgm(0)
# 载入所有音效
init_sounds()
# 加载关卡信息
LEVEL.append(Scene.LEVEL_0)
LEVEL.append(Scene.LEVEL_1)
LEVEL.append(Scene.LEVEL_2)
LEVEL.append(Scene.LEVEL_3)
LEVEL.append(Scene.LEVEL_4)
LEVEL.append(Scene.LEVEL_5)
def main():
global frame_no
clock_refresh = init_pygame() # 屏幕刷新用的计时器
init_game_data()
while True:
key = None
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
key = event.key
if data.state == GameData.STATE_HOME:
home_progress(key)
elif data.state == GameData.STATE_GAME:
if data.scene:
game_progress(key)
elif data.state == GameData.STATE_OVER:
gameover_progress(key)
pygame.display.update()
clock_refresh.tick(MAX_FPS) # 1s进行MAX_FPS次循环
frame_no += 1 # 统计帧数
if __name__ == "__main__":
main()

Comment ( 0 )

Sign in for post a comment

Python
1
https://gitee.com/wingszeng/Adapted-game-Snague.git
git@gitee.com:wingszeng/Adapted-game-Snague.git
wingszeng
Adapted-game-Snague
Snague
master

Search

132457 8cb2edc1 1899542 131848 70c8d3a4 1899542