/
pentomas
/
volley
Обзор
Документация
Войти
/
pentomas
/
volley
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
model.py
574 строки
19 KB
pentomas
create: IMG_1551.PNG, мяч.png, controller.py, main.py, model.py, player.py, view.py
08 май 2026, 06:57
Верифицирован
08 май 2026, 06:57
9c0e4d3
Код
Авторство
О чём код?
from enum import Enum from math import sqrt from constants import ( AI_SERVE_DELAY, BALL_RADIUS, BALL_SPEED_X, BALL_SPEED_Y, COURT_LEFT, COURT_RIGHT, FLOOR_Y, GRAVITY, MAX_TEAM_TOUCHES, NET_BOUNCE, NET_HEIGHT, NET_WIDTH, NET_X, RECEIVE_HEIGHT, RECEIVE_WIDTH, WIDTH, ) from player import OutsideHitter, Setter class GameState(Enum): SERVE = "serve" PLAYING = "playing" ROUND_OVER = "round_over" GAME_OVER = "game_over" class VolleyballModel: def __init__(self): self.ball_x = 450 self.ball_y = 150 self.ball_vx = BALL_SPEED_X self.ball_vy = BALL_SPEED_Y self.left_score = 0 self.right_score = 0 self.winner_text = "" self.round_message = "" self.state = GameState.SERVE self.serving_team = "left" self.last_touch_team = None self.last_contact_team = None self.last_touch_player = None self.team_touches = 0 self.ai_attack_delay = {"left": 0, "right": 0} self.serve_timer = 0 self.left_player = OutsideHitter("left", COURT_LEFT + 60) self.left_setter = Setter("left", COURT_LEFT + 150) self.right_player = OutsideHitter("right", COURT_RIGHT - 90) self.right_setter = Setter("right", COURT_RIGHT - 180) self.left_player.control = "human" self.left_setter.control = "ai" self.right_player.control = "ai" self.right_setter.control = "ai" for player in (self.left_setter, self.right_player, self.right_setter): player.speed *= 1.8 self.players = [self.left_player, self.left_setter, self.right_player, self.right_setter] self.hit_cooldown = 0 self.attack_feedback_text = "" self.attack_feedback_timer = 0 self.attack_feedback_x = 0 self.attack_feedback_y = 0 self._start_serve() @property def is_round_over(self): return self.state == GameState.ROUND_OVER @property def game_over(self): return self.state == GameState.GAME_OVER @property def player_x(self): return self.left_player.x @property def player_y(self): return self.left_player.y @property def player_w(self): return self.left_player.w @property def player_h(self): return self.left_player.h @property def player_2_x(self): return self.right_player.x @property def player_2_y(self): return self.right_player.y @property def player_2_w(self): return self.right_player.w @property def player_2_h(self): return self.right_player.h @property def setter_left_x(self): return self.left_setter.x @property def setter_left_y(self): return self.left_setter.y @property def setter_left_w(self): return self.left_setter.w @property def setter_left_h(self): return self.left_setter.h @property def setter_right_x(self): return self.right_setter.x @property def setter_right_y(self): return self.right_setter.y @property def setter_right_w(self): return self.right_setter.w @property def setter_right_h(self): return self.right_setter.h def update(self, input_state): if self.hit_cooldown > 0: self.hit_cooldown -= 1 if self.attack_feedback_timer > 0: self.attack_feedback_timer -= 1 self._update_ai_attack_delay() if self.state == GameState.GAME_OVER: if input_state["reset"]: self.left_score = 0 self.right_score = 0 self.winner_text = "" self.round_message = "" self.serving_team = "left" self._start_serve() return if self.state == GameState.ROUND_OVER: if input_state["reset"]: self._start_serve() return if self.state == GameState.SERVE: self._update_players(input_state, serving=True) self._place_ball_for_serve() if self.serving_team == "right": self._update_ai_serve() elif input_state["serve"] and self._server_can_serve(): self._serve_ball() return self._update_players(input_state) self._handle_attack_hits() if self.state != GameState.PLAYING: return self._handle_receive_hits() if self.state != GameState.PLAYING: return self._update_ball() self._handle_round_end() def _start_serve(self): self.state = GameState.SERVE self.round_message = f"{self.serving_team.title()} serve" self.last_touch_team = None self.last_contact_team = None self.last_touch_player = None self.team_touches = 0 self.ai_attack_delay = {"left": 0, "right": 0} self.serve_timer = AI_SERVE_DELAY self.ball_vx = 0 self.ball_vy = 0 for player in self.players: player.reset_vertical_position() self._move_server_to_service_zone() self._place_ball_for_serve() def _move_server_to_service_zone(self): server = self._get_server() if server.team == "left": server.x = COURT_LEFT - server.w else: server.x = COURT_RIGHT def _place_ball_for_serve(self): server = self._get_server() self.ball_x = server.x + server.w / 2 self.ball_y = server.y - BALL_RADIUS - 6 def _serve_ball(self): self.state = GameState.PLAYING self.round_message = "" self.last_touch_team = None self.last_contact_team = self.serving_team self.last_touch_player = None self.team_touches = 0 direction = 1 if self.serving_team == "left" else -1 self.ball_vx = direction * 7.2 self.ball_vy = -12.0 def _update_ai_serve(self): if not self._server_can_serve(): return if self.serve_timer > 0: self.serve_timer -= 1 return self._serve_ball() def _server_can_serve(self): server = self._get_server() if server.team == "left": return server.x + server.w <= COURT_LEFT return server.x >= COURT_RIGHT def _get_server(self): return self.left_player if self.serving_team == "left" else self.right_player def _update_players(self, input_state, serving=False): left_bounds = self._get_player_bounds(self.left_player, serving) self.left_player.update( input_state["p1_left"], input_state["p1_right"], False if serving else input_state["p1_action"], False if serving else input_state["p1_receive"], left_bounds, ) for player in (self.left_setter, self.right_player, self.right_setter): move_left, move_right, action, receive = self._get_ai_input(player) player.update(move_left, move_right, action, receive, self._get_player_bounds(player, serving)) def _get_player_bounds(self, player, serving=False): if serving and player is self._get_server(): if player.team == "left": return (0, COURT_LEFT) return (COURT_RIGHT, WIDTH) if player.team == "left": return (COURT_LEFT, NET_X) return (NET_X, COURT_RIGHT) def _get_ai_input(self, player): side_left = COURT_LEFT if player.team == "left" else WIDTH // 2 side_right = WIDTH // 2 if player.team == "left" else COURT_RIGHT ball_on_side = side_left <= self.ball_x <= side_right ball_approaching_side = self._is_ball_approaching_side(player.team) should_track_ball = ball_on_side or ball_approaching_side home_x = self._get_ai_home_x(player, side_left, side_right) target_ball_x = self._predict_ball_x_for_ai(player) if should_track_ball else self.ball_x target_player_x = self._get_ai_receive_x(player, target_ball_x) if should_track_ball else home_x - player.w / 2 move_left = player.x > target_player_x + 8 move_right = player.x < target_player_x - 8 receive_center_x = self._get_receive_center_x(player) receive_center_y = player.y + player.h * 0.48 + RECEIVE_HEIGHT / 2 ball_close_x = abs(receive_center_x - self.ball_x) < RECEIVE_WIDTH / 2 + BALL_RADIUS ball_reachable_y = abs(receive_center_y - self.ball_y) < RECEIVE_HEIGHT / 2 + BALL_RADIUS ball_near_receive_lane = abs(receive_center_x - self.ball_x) < RECEIVE_WIDTH + BALL_RADIUS * 2 ball_can_reach_receive = player.y - 105 <= self.ball_y <= player.y + player.h + 25 receive = should_track_ball and ball_near_receive_lane and ball_can_reach_receive action = False return move_left, move_right, action, receive def _is_ball_approaching_side(self, team): if team == "left": return self.ball_x > NET_X and self.ball_vx < 0 return self.ball_x < NET_X and self.ball_vx > 0 def _predict_ball_x_for_ai(self, player): receive_center_y = player.y + player.h * 0.48 + RECEIVE_HEIGHT / 2 predicted_time = self._predict_time_to_y(receive_center_y) predicted_x = self.ball_x + self.ball_vx * predicted_time side_left = COURT_LEFT if player.team == "left" else NET_X side_right = NET_X if player.team == "left" else COURT_RIGHT return max(side_left, min(side_right, predicted_x)) def _predict_time_to_y(self, target_y): a = 0.5 * GRAVITY b = self.ball_vy c = self.ball_y - target_y discriminant = b * b - 4 * a * c if discriminant < 0: return 18 root = sqrt(discriminant) times = [(-b - root) / (2 * a), (-b + root) / (2 * a)] future_times = [time for time in times if time >= 0] if not future_times: return 0 return min(90, max(0, min(future_times))) def _get_ai_receive_x(self, player, ball_x=None): if ball_x is None: ball_x = self.ball_x if player.team == "left": return ball_x - player.w - RECEIVE_WIDTH / 2 return ball_x + RECEIVE_WIDTH / 2 def _update_ai_attack_delay(self): for team in self.ai_attack_delay: if self.ai_attack_delay[team] > 0: self.ai_attack_delay[team] -= 1 def _get_ai_home_x(self, player, side_left, side_right): if player.role == "setter": return NET_X - 70 if player.team == "left" else NET_X + 70 return side_left + (side_right - side_left) * (0.68 if player.team == "left" else 0.32) def _get_receive_center_x(self, player): if player.team == "left": return player.x + player.w + RECEIVE_WIDTH / 2 return player.x - RECEIVE_WIDTH / 2 def _handle_attack_hits(self): for player in self.players: if self._try_attack_hit(player): break def _try_attack_hit(self, player): contact_segment = player.get_attack_contact_segment() if self.hit_cooldown > 0 or contact_segment is None or not self._ball_hits_segment(contact_segment, 10): return False if not self._register_touch(player): return True self.ball_vx, self.ball_vy = player.get_attack_velocity(self.ball_y) self._show_attack_feedback(player) self.last_contact_team = player.team self.hit_cooldown = 10 return True def _show_attack_feedback(self, player): self.attack_feedback_text = player.attack_timing_label self.attack_feedback_timer = 30 self.attack_feedback_x = player.x + player.w / 2 self.attack_feedback_y = player.y - 18 def _handle_receive_hits(self): for player in self.players: if self._try_receive_hit(player): break def _try_receive_hit(self, player): hitbox = player.get_receive_hitbox() if self.hit_cooldown > 0 or hitbox is None or not self._ball_hits_rect(hitbox): return False if not self._register_touch(player): return True target_x, target_y, flight_time = self._get_receive_target(player) self.ball_vx, self.ball_vy = self._calculate_arc_velocity(target_x, target_y, flight_time) self.last_contact_team = player.team self.hit_cooldown = 8 return True def _get_receive_target(self, player): if player.team == "right": return self._get_free_ball_target(player) if self.last_touch_team == player.team and self.team_touches >= MAX_TEAM_TOUCHES: return self._get_free_ball_target(player) teammate = self._get_teammate_for_receive(player) if player.role == "setter": attack_x = NET_X - 55 if player.team == "left" else NET_X + 55 target_x = attack_x target_y = teammate.y - 225 flight_time = 62 self.ai_attack_delay[player.team] = 18 else: target_x = teammate.x + teammate.w / 2 target_y = teammate.y - 65 flight_time = 36 return target_x, target_y, flight_time def _get_free_ball_target(self, player): if player.team == "left": target_x = (NET_X + COURT_RIGHT) / 2 else: target_x = (COURT_LEFT + NET_X) / 2 return target_x, FLOOR_Y - 150, 46 def _get_teammate_for_receive(self, player): if player.team == "left": return self.left_player if player.role == "setter" else self.left_setter return self.right_player if player.role == "setter" else self.right_setter def _calculate_arc_velocity(self, target_x, target_y, flight_time): vx = (target_x - self.ball_x) / flight_time vy = (target_y - self.ball_y - 0.5 * GRAVITY * flight_time * flight_time) / flight_time return vx, vy def _register_touch(self, player): team = player.team if self.last_touch_player is player: scoring_team = "right" if team == "left" else "left" self._award_point(scoring_team, f"{team.title()} same player twice") return False if self.last_touch_team == team: self.team_touches += 1 else: self.last_touch_team = team self.team_touches = 1 self.last_contact_team = team self.last_touch_player = player if self.team_touches <= MAX_TEAM_TOUCHES: return True scoring_team = "right" if team == "left" else "left" self._award_point(scoring_team, f"{team.title()} made 4 touches") return False def _update_ball(self): self.ball_x += self.ball_vx self.ball_y += self.ball_vy self.ball_vy += GRAVITY self.ball_vx = max(-9, min(9, self.ball_vx)) self.ball_vy = max(-16, min(16, self.ball_vy)) if self.ball_x <= BALL_RADIUS or self.ball_x >= WIDTH - BALL_RADIUS: self.ball_vx *= -1 self._handle_net_collision() def _handle_net_collision(self): net_left = NET_X - NET_WIDTH / 2 net_right = NET_X + NET_WIDTH / 2 net_top = FLOOR_Y - NET_HEIGHT net_bottom = FLOOR_Y closest_x = max(net_left, min(self.ball_x, net_right)) closest_y = max(net_top, min(self.ball_y, net_bottom)) dx = self.ball_x - closest_x dy = self.ball_y - closest_y if dx * dx + dy * dy > BALL_RADIUS * BALL_RADIUS: return if abs(dx) > abs(dy): if self.ball_x < NET_X: self.ball_x = net_left - BALL_RADIUS self.ball_vx = -abs(self.ball_vx) * NET_BOUNCE else: self.ball_x = net_right + BALL_RADIUS self.ball_vx = abs(self.ball_vx) * NET_BOUNCE else: self.ball_y = net_top - BALL_RADIUS self.ball_vy = -abs(self.ball_vy) * NET_BOUNCE def _handle_round_end(self): if self.ball_y < FLOOR_Y - BALL_RADIUS: return self.ball_y = FLOOR_Y - BALL_RADIUS if self.ball_x < COURT_LEFT or self.ball_x > COURT_RIGHT: self._handle_out_ball() elif self.ball_x <= WIDTH // 2: self._award_point("right", "Right scores") else: self._award_point("left", "Left scores") def _handle_out_ball(self): if self.last_contact_team is None: scoring_team = "right" if self.ball_x < COURT_LEFT else "left" message = f"{scoring_team.title()} scores" else: scoring_team = "right" if self.last_contact_team == "left" else "left" message = f"{self.last_contact_team.title()} hits out" self._award_point(scoring_team, message) def _award_point(self, team, message): if team == "left": self.left_score += 1 else: self.right_score += 1 self.serving_team = team self.round_message = message self.last_touch_team = None self.last_contact_team = None self.last_touch_player = None self.team_touches = 0 if self.left_score >= 5: self.state = GameState.GAME_OVER self.winner_text = "Left Player Wins!" elif self.right_score >= 5: self.state = GameState.GAME_OVER self.winner_text = "Right Player Wins!" else: self.state = GameState.ROUND_OVER def _ball_hits_rect(self, rect): rect_x, rect_y, rect_w, rect_h = rect closest_x = max(rect_x, min(self.ball_x, rect_x + rect_w)) closest_y = max(rect_y, min(self.ball_y, rect_y + rect_h)) dx = self.ball_x - closest_x dy = self.ball_y - closest_y return dx * dx + dy * dy <= BALL_RADIUS * BALL_RADIUS def _ball_hits_segment(self, segment, radius): start, end = segment start_x, start_y = start end_x, end_y = end segment_x = end_x - start_x segment_y = end_y - start_y segment_length_squared = segment_x * segment_x + segment_y * segment_y if segment_length_squared == 0: closest_x = start_x closest_y = start_y else: ball_to_start_x = self.ball_x - start_x ball_to_start_y = self.ball_y - start_y position_on_segment = ( ball_to_start_x * segment_x + ball_to_start_y * segment_y ) / segment_length_squared position_on_segment = max(0, min(1, position_on_segment)) closest_x = start_x + segment_x * position_on_segment closest_y = start_y + segment_y * position_on_segment dx = self.ball_x - closest_x dy = self.ball_y - closest_y max_distance = BALL_RADIUS + radius return dx * dx + dy * dy <= max_distance * max_distance