Browse Source

Add different balls and targets

master
Lev 3 years ago
parent
commit
ea1a6ce2a3
  1. 262
      lab5/gun.py

262
lab5/gun.py

@ -1,9 +1,12 @@
from __future__ import annotations
import math import math
from random import choice, randrange as rnd from copy import deepcopy
from random import choice, uniform, randrange as rnd
from dataclasses import dataclass, field
import pygame import pygame
import typing import typing
import numpy as np
FPS = 30 FPS = 30
@ -20,26 +23,16 @@ GAME_COLORS = [RED, BLUE, YELLOW, GREEN, MAGENTA, CYAN]
WIDTH = 800 WIDTH = 800
HEIGHT = 600 HEIGHT = 600
global balls, bullet
balls, bullet = [], 0
@dataclass
class Ball: class Ball:
def __init__(self, screen: pygame.Surface, x: int = 40, y: int = 450): position: np.array
""" Конструктор класса ball r: float = 10
v: np.array = field(default_factory=lambda: np.array([0., 0.]))
Args: color: int = field(default_factory=lambda: choice(GAME_COLORS))
x - начальное положение мяча по горизонтали time_to_split: typing.Optional[int] = field(default_factory=lambda: choice([None, rnd(5, 25)]))
y - начальное положение мяча по вертикали live: int = 30
"""
self.screen = screen
self.x = x
self.y = y
self.r = 10
self.vx = 0
self.vy = 0
self.color = choice(GAME_COLORS)
self.live = 30
def move(self): def move(self):
"""Переместить мяч по прошествии единицы времени. """Переместить мяч по прошествии единицы времени.
@ -48,52 +41,58 @@ class Ball:
self.x и self.y с учетом скоростей self.vx и self.vy, силы гравитации, действующей на мяч, self.x и self.y с учетом скоростей self.vx и self.vy, силы гравитации, действующей на мяч,
и стен по краям окна (размер окна 800х600). и стен по краям окна (размер окна 800х600).
""" """
if self.y <= 500: if self.time_to_split is not None:
self.vy -= 1.2 self.time_to_split -= 1
self.y -= self.vy if self.position[1] <= 500:
self.x += self.vx self.v[1] -= 1.2
self.vx *= 0.99 self.position += self.v * np.array([1, -1])
else: self.v[0] *= 0.99
if self.vx ** 2 + self.vy ** 2 > 10:
self.vy =- self.vy / 2
self.vx = self.vx / 2
self.y = 499
if self.live < 0:
balls.pop(balls.index(self))
else: else:
if np.linalg.norm(self.v) > 10 ** 0.5:
self.v *= np.array([0.5, -0.5])
self.position[1] = 499
self.live -= 1 self.live -= 1
if self.x > 780: if self.position[0] > 780:
self.vx =- self.vx / 2 self.v[0] *= -0.5
self.x = 779 self.position[0] = 779
def draw(self): def draw(self, screen):
pygame.draw.circle( pygame.draw.circle(
self.screen, screen,
self.color, self.color,
(self.x, self.y), self.position,
self.r self.r
) )
def hittest(self, obj): def hittest(self, obj):
"""Функция проверяет сталкивалкивается ли данный обьект с целью, описываемой в обьекте obj. """Функция проверяет, сталкивается ли данный объект с целью, описываемой в объекте obj.
Args: Args:
obj: Обьект, с которым проверяется столкновение. obj: Объект, с которым проверяется столкновение.
Returns: Returns:
Возвращает True в случае столкновения мяча и цели. В противном случае возвращает False. Возвращает True в случае столкновения мяча и цели. В противном случае возвращает False.
""" """
if abs(obj.x - self.x) <= (self.r + obj.r) and abs(obj.y - self.y) <= (self.r + obj.r): if np.linalg.norm(self.position - obj.position) < self.r + obj.r:
return True return True
else: else:
return False return False
def get_split(self) -> typing.Tuple[Ball, Ball]:
ball1, ball2 = deepcopy(self), deepcopy(self)
ball1.r /= 2 ** 0.5
ball2.r /= 2 ** 0.5
ball1.time_to_split = choice([None, rnd(5, 25)])
ball2.time_to_split = choice([None, rnd(5, 25)])
v_angle = np.arctan2(*self.v)
delta_angle = 0.5
ball1.v = np.linalg.norm(self.v) * np.array([np.sin(v_angle - delta_angle), np.cos(v_angle - delta_angle)])
ball2.v = np.linalg.norm(self.v) * np.array([np.sin(v_angle + delta_angle), np.cos(v_angle + delta_angle)])
return ball1, ball2
class Gun: class Gun:
def __init__(self, screen): def __init__(self):
global bullet self.bullet = 0
bullet = 0
self.screen = screen
self.f2_power = 10 self.f2_power = 10
self.f2_on = 0 self.f2_on = 0
self.an = 1 self.an = 1
@ -101,23 +100,22 @@ class Gun:
self.x0 = 40 self.x0 = 40
self.y0 = 450 self.y0 = 450
def fire2_start(self, event): def fire2_start(self, _event):
self.f2_on = 1 self.f2_on = 1
def fire2_end(self, event): def fire2_end(self, event, game: Game):
"""Выстрел мячом. """Выстрел мячом.
Происходит при отпускании кнопки мыши. Происходит при отпускании кнопки мыши.
Начальные значения компонент скорости мяча vx и vy зависят от положения мыши. Начальные значения компонент скорости мяча vx и vy зависят от положения мыши.
""" """
global bullet self.bullet += 1
bullet += 1 new_ball = Ball(position=np.array([self.x0 + self.deltas()[0], self.y0 + self.deltas()[1]]))
new_ball = Ball(self.screen, x=self.x0 + self.deltas()[0], y=self.y0 + self.deltas()[1])
new_ball.r += 5 new_ball.r += 5
self.an = math.atan2((event.pos[1] - new_ball.y), (event.pos[0] - new_ball.x)) self.an = math.atan2((event.pos[1] - new_ball.position[1]), (event.pos[0] - new_ball.position[0]))
new_ball.vx = self.f2_power * math.cos(self.an) new_ball.v[0] = self.f2_power * math.cos(self.an)
new_ball.vy = - self.f2_power * math.sin(self.an) new_ball.v[1] = - self.f2_power * math.sin(self.an)
balls.append(new_ball) game.balls.append(new_ball)
self.f2_on = 0 self.f2_on = 0
self.f2_power = 10 self.f2_power = 10
@ -130,18 +128,18 @@ class Gun:
else: else:
self.color = GREY self.color = GREY
def deltas(self) -> typing.Union[float, float]: def deltas(self) -> typing.Tuple[float, float]:
length = 50 + self.f2_power length = 50 + self.f2_power
return length * math.cos(self.an), length * math.sin(self.an) return length * math.cos(self.an), length * math.sin(self.an)
def draw(self): def draw(self, screen):
r, length = 10, 50 + self.f2_power r, length = 10, 50 + self.f2_power
dx, dy = r * math.sin(self.an), -r * math.cos(self.an) dx, dy = r * math.sin(self.an), -r * math.cos(self.an)
delta_x, delta_y = self.deltas() delta_x, delta_y = self.deltas()
points = [ points = [
(self.x0 + dx, self.y0 + dy), (self.x0 - dx, self.y0 - dy), (self.x0 + dx, self.y0 + dy), (self.x0 - dx, self.y0 - dy),
(self.x0 + delta_x - dx, self.y0 + delta_y - dy), (self.x0 + delta_x + dx, self.y0 + delta_y + dy)] (self.x0 + delta_x - dx, self.y0 + delta_y - dy), (self.x0 + delta_x + dx, self.y0 + delta_y + dy)]
pygame.draw.polygon(self.screen, self.color, points) pygame.draw.polygon(screen, self.color, points)
def power_up(self): def power_up(self):
if self.f2_on: if self.f2_on:
@ -152,30 +150,46 @@ class Gun:
self.color = GREY self.color = GREY
@dataclass
class Target: class Target:
def __init__(self, screen): x: float = field(default_factory=lambda: rnd(600, 780))
"""Инициализация новой цели. """ y: float = field(default_factory=lambda: rnd(300, 550))
x = self.x = rnd(600, 780) r: float = field(default_factory=lambda: rnd(9, 50))
y = self.y = rnd(300, 550) vx: float = field(default_factory=lambda: rnd(-4, 6))
r = self.r = rnd(2, 50) vy: float = field(default_factory=lambda: rnd(-4, 6))
self.vx = rnd(2, 6) color: int = RED
self.vy = rnd(2, 6) points: int = 0
color = self.color = RED live: int = 1
self.screen = screen ax: float = 0
self.points = 0 ay: float = 0
self.live = 1 randomness_ampl: float = field(default_factory=lambda: uniform(0, 5) * choice([0, 0, 1]))
oscillation_freq: float = field(default_factory=lambda: 0.05 * choice([-1, 1]))
oscillation_ampl: float = field(default_factory=lambda: rnd(0, 6) * choice([0, 0, 1]))
oscillation_phase: float = field(default_factory=lambda: uniform(0, 2 * np.pi))
def hit(self, points=1): def hit(self, points=1):
"""Попадание шарика в цель.""" """Попадание шарика в цель."""
self.points += points self.points += points
def draw(self): def draw(self, screen):
pygame.draw.circle( pygame.draw.circle(
self.screen, screen,
self.color, self.color,
(self.x, self.y), (self.x, self.y),
self.r self.r
) )
pygame.draw.circle(
screen,
WHITE,
(self.x, self.y),
self.r * 0.7
)
pygame.draw.circle(
screen,
self.color if not self.oscillation_ampl and not self.randomness_ampl else BLUE,
(self.x, self.y),
self.r * 0.45
)
def move(self): def move(self):
"""Переместить мяч по прошествии единицы времени. """Переместить мяч по прошествии единицы времени.
@ -185,60 +199,90 @@ class Target:
и стен по краям окна (размер окна 800х600). и стен по краям окна (размер окна 800х600).
""" """
if self.y < 0: if self.y < 0:
self.vy = -abs(self.vy) self.vy = -abs(self.vy) * 0.5 + self.ay
if self.x < 0:
self.vx = abs(self.vx) * 0.75 + self.ax
if self.y <= 500: if self.y <= 500:
self.y -= self.vy self.y -= self.vy + self.oscillation_ampl * np.sin(self.oscillation_phase)
self.x += self.vx self.x += self.vx + self.oscillation_ampl * np.cos(self.oscillation_phase)
self.vx *= 0.99 self.vx *= 0.98
else: else:
if self.vx ** 2 + self.vy ** 2 > 10: # if self.vx ** 2 + self.vy ** 2 > 10:
self.vy =- self.vy / 2 # self.vy = -self.vy / 2
self.vx = self.vx / 2 # self.vx = self.vx / 2
self.vy = 0.9 * abs(self.vy)
self.y = 499 self.y = 499
if self.x > 780: if self.x > 780:
self.vx =- self.vx / 2 self.vx = -abs(self.vx) / 2
self.x = 779 self.x = 779
self.oscillation_phase += self.oscillation_freq
self.ax += self.randomness_ampl * uniform(-1, 1)
self.ay += self.randomness_ampl * uniform(-1, 1)
@property
def position(self):
return np.array([self.x, self.y])
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
bullet = 0
balls = []
clock = pygame.time.Clock() @dataclass
gun = Gun(screen) class Game:
targets = [Target(screen), Target(screen)] balls: typing.List[Ball] = field(default_factory=list)
finished = False targets: typing.List[Target] = field(default_factory=lambda: [Target(), Target()])
gun: Gun = field(default_factory=Gun)
finished: bool = False
clock: pygame.time.Clock = field(default_factory=pygame.time.Clock)
fps: int = FPS
while not finished: def move(self):
screen.fill(WHITE) for target in self.targets:
gun.draw()
for target in targets:
target.draw()
target.move() target.move()
for b in balls: for ball in self.balls:
b.draw() ball.move()
if ball.live < 0:
self.balls.pop([i for i in range(len(self.balls)) if self.balls[i].position is ball.position][0])
elif ball.time_to_split is not None and ball.time_to_split < 0:
ball1, ball2 = ball.get_split()
self.balls.pop([i for i in range(len(self.balls)) if self.balls[i].position is ball.position][0])
self.balls.extend([ball1, ball2])
for target in self.targets:
if ball.hittest(target) and target.live:
target.live = 0
ball.live = -1
target.hit()
self.targets.remove(target)
self.targets.append(Target())
self.gun.power_up()
def draw(self, screen):
screen.fill(WHITE)
self.gun.draw(screen)
for target in self.targets:
target.draw(screen)
for ball in self.balls:
ball.draw(screen)
pygame.display.update() pygame.display.update()
clock.tick(FPS) def process_event(self, event):
for event in pygame.event.get():
if event.type == pygame.QUIT: if event.type == pygame.QUIT:
finished = True self.finished = True
elif event.type == pygame.MOUSEBUTTONDOWN: elif event.type == pygame.MOUSEBUTTONDOWN:
gun.fire2_start(event) self.gun.fire2_start(event)
elif event.type == pygame.MOUSEBUTTONUP: elif event.type == pygame.MOUSEBUTTONUP:
gun.fire2_end(event) self.gun.fire2_end(event, self)
elif event.type == pygame.MOUSEMOTION: elif event.type == pygame.MOUSEMOTION:
gun.targetting(event) self.gun.targetting(event)
def main_loop(self):
screen = pygame.display.set_mode((WIDTH, HEIGHT))
while not self.finished:
self.draw(screen)
self.clock.tick(self.fps)
for event in pygame.event.get():
self.process_event(event)
self.move()
pygame.quit()
for b in balls:
b.move()
for target in targets:
if b.hittest(target) and target.live:
target.live = 0
target.hit()
targets.remove(target)
targets.append(Target(screen))
gun.power_up()
pygame.quit() pygame.init()
game = Game()
game.main_loop()

Loading…
Cancel
Save