360 lines
18 KiB
Python
360 lines
18 KiB
Python
"""
|
||
This code is supported by the website: https://www.guanjihuan.com
|
||
The newest version of this code is on the web page: https://www.guanjihuan.com/archives/703
|
||
"""
|
||
|
||
import pygame
|
||
import random
|
||
import math
|
||
import numpy as np
|
||
|
||
# 参数
|
||
screen_width = 1500 # 屏幕宽度
|
||
screen_height = 900 # 屏幕高度
|
||
map_width = screen_width*4 # 地图的大小
|
||
map_height = screen_height*4 # 地图的大小
|
||
number_enemy = map_width*map_height/500000 # 敌人的数量
|
||
number_dots = map_width * map_height / 50 # 点点的数量
|
||
max_show_size = 100 # 球显示的最大半径(屏幕有限,球再增大时,改变的地图比例尺寸)
|
||
|
||
my_value = 1000 # 我的初始值
|
||
enemy_value_low = 500 # 敌人的初始值(最低)
|
||
enemy_value_high = 1500 # 敌人的初始值(最高)
|
||
dot_value = 30 # 点点的值(地上的豆豆/食物值)
|
||
my_speed = 10 # 我的球运动的速度
|
||
speed_up = 20 # 按下鼠标时加速
|
||
speed_enemy = 10 # 敌人球正常运动速度
|
||
speed_enemy_anomaly = 20 # 敌人突然加速时的速度(速度异常时的速度)
|
||
anomaly_pro = 0.5 # 敌人加速的概率
|
||
change_pro = 0.05 # 敌人移动路径变化的概率,也就是1/change_pro左右会变化一次
|
||
eat_percent = 0.9 # 吃掉敌人的球,按多少比例并入自己的体积,1对应的是100%
|
||
loss = 0.001 # 按比例减小体重(此外越重的减少越多,10万体积损失值为loss的一倍)
|
||
enemy_bigger_pro = 0.0005 # 敌人的值增加了我的球的值的enemy_bigger_rate倍的几率
|
||
enemy_bigger_rate = 0.1 # 增加我的球的体积的enemy_bigger_rate倍
|
||
|
||
|
||
class Color(object): # 定义颜色的类
|
||
@classmethod # 加了这个可以不需要把实例化,能直接调用类的方法
|
||
def random_color(cls): # cls, 即class,表示可以通过类名直接调用
|
||
red = random.randint(0, 255)
|
||
green = random.randint(0, 255)
|
||
blue = random.randint(0, 255)
|
||
return red, green, blue
|
||
|
||
|
||
class Ball(object): # 定义球
|
||
def __init__(self, x, y, sx, sy, color, value): # 初始化
|
||
self.x = x # 球的地图位置参数
|
||
self.y = y
|
||
self.sx = sx # 速度参数
|
||
self.sy = sy
|
||
self.color = color # 颜色
|
||
self.value = value # 球的值,也就是球的大小(不是显示的大小)
|
||
self.is_alive = True # 球默认是存活状态
|
||
|
||
|
||
class My_Ball(Ball): # 定义我的球,继承了Ball类的方法
|
||
def __init__(self, x, y, sx, sy, color, value):
|
||
# 注意:如果重写了__init__() 时,实例化子类,就不会调用父类已经定义的__init__()
|
||
# 如果子类不重写__init__()方法,实例化子类后,会自动调用父类的__init__()的方法
|
||
# 如果子类重写__init__()方法又需要调用父类的方法,则要使用super关键词。
|
||
super().__init__(x, y, sx, sy, color, value) # 调用父类Ball的初始化方法__init__()
|
||
self.radius = int(self.value**0.5) # 我的球的半径(不考虑系数pi)
|
||
if self.radius >= max_show_size: # 如果半径比规定的最大半径还大,则显示最大半径
|
||
self.show_radius = max_show_size # 我的球显示的半径
|
||
else:
|
||
self.show_radius = self.radius # 如果半径没有超过规定最大的半径,则显示原来实际大小的半径
|
||
self.position_x = int(screen_width/2) # 把我的球固定在屏幕中间position_x,是屏幕显示的位置
|
||
self.position_y = int(screen_height/2) # 把我的球固定在屏幕中间position_y,是屏幕显示的位置
|
||
|
||
def draw(self, window): # 把我的球画出来
|
||
self.radius = int(self.value ** 0.5) # 这里重复上面的,因为除了初始化之后,还要更新
|
||
if self.radius >= max_show_size:
|
||
self.show_radius = max_show_size
|
||
else:
|
||
self.show_radius = self.radius
|
||
self.position_x = int(screen_width / 2)
|
||
self.position_y = int(screen_height / 2)
|
||
pygame.draw.circle(window, self.color, (self.position_x , self.position_y), self.show_radius)
|
||
|
||
def eat_ball(self, other): # 吃别的球(包括小点点和敌人)
|
||
if self != other and self.is_alive and other.is_alive: # 如果other不是自身,自身和对方也都是存活状态,则执行下面动作
|
||
distance = ((self.position_x - other.position_x) ** 2 + (self.position_y - other.position_y) ** 2) ** 0.5 # 两个球之间的距离
|
||
if distance < self.show_radius and (self.show_radius > other.show_radius or (self.show_radius == other.show_radius and self.value > other.value)): # 如果自身半径比别人大,而且两者距离小于自身半径,那么可以吃掉。
|
||
other.is_alive = False # 吃球(敌方已死)
|
||
self.value += other.value*eat_percent # 自己的值增大(体量增大)
|
||
self.radius = int(self.value ** 0.5) # 计算出半径
|
||
if self.radius >= max_show_size: # 我的球的显示半径
|
||
self.show_radius = max_show_size
|
||
else:
|
||
self.show_radius = self.radius
|
||
|
||
def move(self): # 移动规则
|
||
self.x += self.sx # 地图位置加上速度
|
||
self.y += self.sy
|
||
# 横向出界
|
||
if self.x < 0: # 离开了地图左边
|
||
self.x = 0
|
||
if self.x > map_width: # 离开了地图右边
|
||
self.x = map_width
|
||
# 纵向出界
|
||
if self.y <= 0: # 离开了地图下边
|
||
self.y = 0
|
||
if self.y >= map_height: # 离开了地图上边
|
||
self.y = map_height
|
||
|
||
|
||
class Enemy_Ball(Ball): # 定义敌人的球,继承了Ball类的方法
|
||
def __init__(self, x, y, sx, sy, color, value, host_ball): # 初始化带上host_ball,也就是我的球
|
||
super().__init__(x, y, sx, sy, color, value)
|
||
self.host_ball = host_ball
|
||
self.radius = int(self.value**0.5)
|
||
if self.host_ball.radius >= max_show_size: # 如果我的球比规定的最大尺寸还大,则敌人的球显示的比例要减小
|
||
self.show_radius = max(10, int(self.radius/(self.host_ball.radius/max_show_size))) # 敌人的球也不能太小,最小半径为10
|
||
self.position_x = int((self.x - self.host_ball.x) / (self.host_ball.radius / max_show_size)) + int(
|
||
screen_width / 2) # 计算出敌人的球和我的球的相对位置,并且按比例减小
|
||
self.position_y = int((self.y - self.host_ball.y) / (self.host_ball.radius / max_show_size)) + int(
|
||
screen_height / 2) # 计算出敌人的球和我的球的相对位置,并且按比例减小
|
||
else:
|
||
self.show_radius = self.radius # 正常显示
|
||
self.position_x = (self.x - self.host_ball.x) + int(screen_width / 2) # 敌人和我的球的相对位置
|
||
self.position_y = (self.y - self.host_ball.y) + int(screen_height / 2) # 敌人和我的球的相对位置
|
||
|
||
# 画出球
|
||
def draw(self, window):
|
||
self.radius = int(self.value ** 0.5)
|
||
if self.host_ball.radius >= max_show_size: # 这边把初始化的内容再写一遍,因为敌人的球初始化之后还要根据我的球而动态改变
|
||
self.show_radius = max(10, int(self.radius/(self.host_ball.radius/max_show_size)))
|
||
self.position_x = int((self.x - self.host_ball.x) / (self.host_ball.radius / max_show_size)) + int(
|
||
screen_width / 2)
|
||
self.position_y = int((self.y - self.host_ball.y) / (self.host_ball.radius / max_show_size)) + int(
|
||
screen_height / 2)
|
||
else:
|
||
self.show_radius = self.radius
|
||
self.position_x = (self.x - self.host_ball.x) + int(screen_width / 2)
|
||
self.position_y = (self.y - self.host_ball.y) + int(screen_height / 2)
|
||
pygame.draw.circle(window, self.color, (self.position_x, self.position_y), self.show_radius)
|
||
|
||
def eat_ball(self, other):
|
||
if self != other and self.is_alive and other.is_alive:
|
||
distance = ((self.position_x - other.position_x) ** 2 + (self.position_y - other.position_y) ** 2) ** 0.5
|
||
if distance < self.show_radius and (self.show_radius > other.show_radius or (self.show_radius == other.show_radius and self.value > other.value)):
|
||
other.is_alive = False # 吃球
|
||
self.value += other.value*eat_percent
|
||
self.radius = int(self.value ** 0.5)
|
||
|
||
def move(self): # 移动规则
|
||
self.x += self.sx # 地图位置加上速度
|
||
self.y += self.sy
|
||
# 横向出界
|
||
if self.x < 0: # 离开了地图左边
|
||
self.sx = -self.sx
|
||
self.x = 0
|
||
if self.x > map_width: # 离开了地图右边
|
||
self.sx = -self.sx
|
||
self.x = map_width
|
||
# 纵向出界
|
||
if self.y <= 0: # 离开了地图下边
|
||
self.sy = -self.sy
|
||
self.y = 0
|
||
if self.y >= map_height: # 离开了地图上边
|
||
self.sy = -self.sy
|
||
self.y = map_height
|
||
|
||
|
||
class Dot_Ball(Ball): # 定义地上的小点点,供自己的球和敌人的球吃,继承了Ball类的方法
|
||
def __init__(self, x, y, sx, sy, color, value, host_ball):
|
||
super().__init__(x, y, sx, sy, color, value)
|
||
self.host_ball = host_ball
|
||
self.radius = 8 # 初始小点点大小
|
||
if self.host_ball.radius >= max_show_size:
|
||
self.show_radius = max(3, int(self.radius/(self.host_ball.radius/max_show_size))) # 小点点显示也不能太小,最小显示半径为3
|
||
self.position_x = int((self.x - self.host_ball.x) / (self.host_ball.radius / max_show_size)) + int(
|
||
screen_width / 2)
|
||
self.position_y = int((self.y - self.host_ball.y) / (self.host_ball.radius / max_show_size)) + int(
|
||
screen_height / 2)
|
||
else:
|
||
self.show_radius = self.radius
|
||
self.position_x = (self.x - self.host_ball.x) + int(screen_width / 2)
|
||
self.position_y = (self.y - self.host_ball.y) + int(screen_height / 2)
|
||
|
||
# 画出球
|
||
def draw(self, window):
|
||
if self.host_ball.radius >= max_show_size: # 这边把初始化的内容再写一遍,因为小点点初始化之后还要根据我的球而动态改变
|
||
self.show_radius = max(3, int(self.radius/(self.host_ball.radius/max_show_size)))
|
||
self.position_x = int((self.x - self.host_ball.x) / (self.host_ball.radius / max_show_size)) + int(
|
||
screen_width / 2)
|
||
self.position_y = int((self.y - self.host_ball.y) / (self.host_ball.radius / max_show_size)) + int(
|
||
screen_height / 2)
|
||
else:
|
||
self.show_radius = self.radius
|
||
self.position_x = (self.x - self.host_ball.x) + int(screen_width / 2)
|
||
self.position_y = (self.y - self.host_ball.y) + int(screen_height / 2)
|
||
pygame.draw.circle(window, self.color, (self.position_x, self.position_y) , self.show_radius)
|
||
|
||
|
||
def creat_my_ball(): # 产生我的球
|
||
x = random.randint(0, map_width) # 我的球在地图中的位置,随机生成
|
||
y = random.randint(0, map_height)
|
||
value = my_value # 我的球的初始值
|
||
color = 255, 255, 255 # 我的球的颜色
|
||
sx = 0 # 速度默认为0
|
||
sy = 0
|
||
host_ball = My_Ball(x, y, sx, sy, color, value) # 调用My_Ball类
|
||
return host_ball # 返回我的球
|
||
|
||
|
||
def auto_creat_ball(balls, host_ball): # 自动产生敌人的球
|
||
if len(balls) <= number_enemy: # 控制敌人的数量,如果个数够了,就不再生成
|
||
x = random.randint(0, map_width) # 敌人球在地图中的位置,随机生成
|
||
y = random.randint(0, map_height)
|
||
value = random.randint(enemy_value_low, enemy_value_high) # 敌人的球初始值
|
||
sx = random.randint(-speed_enemy, speed_enemy) # 敌人的球移动速度
|
||
i2 = random.randint(0, 1) # y的移动方向
|
||
if i2 == 0:
|
||
sy = int((speed_enemy**2 - sx**2) ** 0.5)
|
||
else:
|
||
sy = -int((speed_enemy ** 2 - sx ** 2) ** 0.5)
|
||
color = Color.random_color() # 敌人的颜色随机生成
|
||
enemy = Enemy_Ball(x, y, sx, sy, color, value, host_ball)
|
||
balls.append(enemy)
|
||
|
||
|
||
def auto_creat_dots(dots, host_ball): # 自动生成点点
|
||
if len(dots) <= number_dots: # 控制点点的数量
|
||
x = random.randint(0, map_width) # 随机生成点点的位置
|
||
y = random.randint(0, map_height)
|
||
value = dot_value # 点点的值
|
||
sx = 0 # 点点速度为0
|
||
sy = 0
|
||
color = Color.random_color() # 颜色
|
||
dot = Dot_Ball(x, y, sx, sy, color, value, host_ball)
|
||
dots.append(dot)
|
||
|
||
|
||
def control_my_ball(host_ball): # 控制我的球
|
||
host_ball.move()
|
||
host_ball.value = host_ball.value*(1-loss*host_ball.value/100000)
|
||
for event in pygame.event.get(): # 监控事件(鼠标移动)
|
||
# print(event)
|
||
if event.type == pygame.MOUSEBUTTONDOWN:
|
||
pos = event.pos
|
||
speed = speed_up
|
||
elif event.type == pygame.MOUSEMOTION:
|
||
pos = event.pos
|
||
if event.buttons[0] == 1:
|
||
speed = speed_up
|
||
if event.buttons[0] == 0:
|
||
speed = my_speed
|
||
elif event.type == pygame.MOUSEBUTTONUP:
|
||
pos = event.pos
|
||
speed = my_speed
|
||
else:
|
||
pos = [screen_width/2, screen_height/2]
|
||
speed = my_speed
|
||
if abs(pos[0] - screen_width/2) < 30 and abs(pos[1] - screen_height/2) < 30:
|
||
host_ball.sx = 0
|
||
host_ball.sy = 0
|
||
elif pos[0] > screen_width/2 and pos[1] >= screen_height/2:
|
||
angle = abs(math.atan((pos[1] - screen_height/2) / (pos[0] - screen_width/2)))
|
||
host_ball.sx = int(speed * math.cos(angle))
|
||
host_ball.sy = int(speed * math.sin(angle))
|
||
elif pos[0] > screen_width/2 and pos[1] < screen_height/2:
|
||
angle = abs(math.atan((pos[1] - screen_height/2) / (pos[0] - screen_width/2)))
|
||
host_ball.sx = int(speed * math.cos(angle))
|
||
host_ball.sy = -int(speed * math.sin(angle))
|
||
elif pos[0] < screen_width/2 and pos[1] >= screen_height/2:
|
||
angle = abs(math.atan((pos[1] - screen_height/2) / (pos[0] - screen_width/2)))
|
||
host_ball.sx = -int(speed * math.cos(angle))
|
||
host_ball.sy = int(speed * math.sin(angle))
|
||
elif pos[0] < screen_width/2 and pos[1] < screen_height/2:
|
||
angle = abs(math.atan((pos[1] - screen_height/2) / (pos[0] - screen_width/2)))
|
||
host_ball.sx = -int(speed * math.cos(angle))
|
||
host_ball.sy = -int(speed * math.sin(angle))
|
||
elif pos[0] == screen_width/2:
|
||
host_ball.sx = 0
|
||
if pos[1] >= 0:
|
||
host_ball.sy = speed
|
||
else:
|
||
host.ball.sy = -speed
|
||
|
||
|
||
def enemy_move(balls, host_ball): # 敌人移动
|
||
for enemy in balls:
|
||
enemy.move() # 移动
|
||
enemy.value = enemy.value*(1-loss*enemy.value/100000)
|
||
if random.randint(1, int(1/enemy_bigger_pro)) == 1:
|
||
enemy.value += host_ball.value*enemy_bigger_rate
|
||
if random.randint(1, int(1/anomaly_pro)) == 1:
|
||
speed_enemy0 = speed_enemy_anomaly # 敌人异常速度
|
||
else:
|
||
speed_enemy0 = speed_enemy # 敌人正常速度
|
||
i = random.randint(1, int(1/change_pro)) # 一定的概率改变轨迹
|
||
if i == 1:
|
||
enemy.sx = random.randint(-speed_enemy0, speed_enemy0)
|
||
i2 = random.randint(0, 1)
|
||
if i2 == 0:
|
||
enemy.sy = int((speed_enemy0 ** 2 - enemy.sx ** 2) ** 0.5)
|
||
else:
|
||
enemy.sy = -int((speed_enemy0 ** 2 - enemy.sx ** 2) ** 0.5)
|
||
|
||
|
||
def eat_each_other(host_ball, balls, dots): # 吃球
|
||
for enemy in balls:
|
||
for enemy2 in balls:
|
||
enemy.eat_ball(enemy2) # 敌人互吃
|
||
for food in dots:
|
||
enemy.eat_ball(food) # 敌人吃点点
|
||
for enemy in balls:
|
||
host_ball.eat_ball(enemy) # 我吃敌人
|
||
enemy.eat_ball(host_ball) # 敌人吃我
|
||
for food in dots:
|
||
host_ball.eat_ball(food) # 我吃点点
|
||
|
||
|
||
def paint(host_ball, balls, dots, screen):
|
||
screen.fill((0, 0, 0)) # 刷漆
|
||
if host_ball.is_alive:
|
||
host_ball.draw(screen)
|
||
for enemy in balls: # 遍历容器
|
||
if enemy.is_alive:
|
||
enemy.draw(screen)
|
||
else:
|
||
balls.remove(enemy)
|
||
for food in dots: # 遍历容器
|
||
if food.is_alive:
|
||
food.draw(screen)
|
||
else:
|
||
dots.remove(food)
|
||
|
||
|
||
def main():
|
||
pygame.init() # 初始化
|
||
screen = pygame.display.set_mode((screen_width, screen_height)) # 设置屏幕
|
||
pygame.display.set_caption("球球大作战") # 设置屏幕标题
|
||
balls = [] # 定义一容器 存放所有的敌方球
|
||
dots = [] # 定义一容器 存放所有的点点
|
||
is_running = True # 默认运行状态
|
||
host_ball = creat_my_ball() # 产生我的球
|
||
i00 = 0 # 一个参数
|
||
while is_running:
|
||
for event in pygame.event.get():
|
||
if event.type == pygame.QUIT:
|
||
is_running = False
|
||
auto_creat_dots(dots, host_ball) # 自动生成点点
|
||
auto_creat_ball(balls, host_ball) # 自动生成敌人
|
||
paint(host_ball, balls, dots, screen) # 把所有的都画出来 调用draw方法
|
||
pygame.display.flip() # 渲染
|
||
pygame.time.delay(30) # 设置动画的时间延迟
|
||
|
||
control_my_ball(host_ball) # 移动我的球
|
||
enemy_move(balls, host_ball) # 敌人的球随机运动
|
||
eat_each_other(host_ball, balls, dots) # 吃球 调用eat_ball方法
|
||
i00 += 1
|
||
if np.mod(i00, 50) == 0:
|
||
print(host_ball.value)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|