parent
ccfe1604ea
commit
052b5fbcb8
@ -0,0 +1,13 @@
|
||||
from common.game.billiards.baseBilliard import BaseBilliard
|
||||
from enums.color import WHITE
|
||||
from enums.category import CUE_BALL
|
||||
|
||||
|
||||
class ColorBall(BaseBilliard):
|
||||
def __init__(self, score: int, category: int, color: tuple[int, int, int], pos=(0, 0)):
|
||||
super().__init__(score, category, color, pos)
|
||||
self.__start_pos = pos
|
||||
|
||||
def reset(self):
|
||||
self.pos = self.__start_pos
|
||||
self.activity = True
|
@ -0,0 +1,8 @@
|
||||
from common.game.billiards.baseBilliard import BaseBilliard
|
||||
from enums.color import WHITE
|
||||
from enums.category import CUE_BALL
|
||||
|
||||
|
||||
class CueBall(BaseBilliard):
|
||||
def __init__(self, pos=(0, 0)):
|
||||
super().__init__(-1, CUE_BALL, WHITE, pos)
|
@ -0,0 +1,15 @@
|
||||
import pygame
|
||||
|
||||
from common.drawableItem import DrawableItem
|
||||
from config.sys import BALL_RADIUS
|
||||
from enums.color import WHITE, BLACK
|
||||
|
||||
|
||||
class FakeCueBall(DrawableItem):
|
||||
def __init__(self, pos):
|
||||
self.pos = pos
|
||||
|
||||
def draw(self, window: pygame.Surface):
|
||||
pygame.draw.circle(window, WHITE, self.pos, BALL_RADIUS)
|
||||
pygame.draw.circle(window, BLACK, self.pos, BALL_RADIUS, 1)
|
||||
pass
|
@ -0,0 +1,11 @@
|
||||
from common.game.billiards.baseBilliard import BaseBilliard
|
||||
from enums.color import RED
|
||||
from enums.category import RED_BALL
|
||||
|
||||
|
||||
class RedBall(BaseBilliard):
|
||||
def __init__(self, pos):
|
||||
super().__init__(1, RED_BALL, RED, pos)
|
||||
|
||||
def reset(self):
|
||||
pass
|
@ -0,0 +1,167 @@
|
||||
import sys
|
||||
|
||||
import pygame
|
||||
import pymunk
|
||||
|
||||
from common.game.billiards.baseBilliard import BaseBilliard
|
||||
from common.game.billiards.colorBall import ColorBall
|
||||
from common.game.billiards.cueBall import CueBall
|
||||
from common.game.billiards.fakeCueball import FakeCueBall
|
||||
from common.game.billiards.redBall import RedBall
|
||||
from common.game.cue import Cue
|
||||
from common.game.gameData import GameData
|
||||
from common.game.ruleManager import RuleManager
|
||||
from common.game.table import Table
|
||||
from config.sys import *
|
||||
from enums.color import *
|
||||
from enums.category import *
|
||||
|
||||
|
||||
class Game(object):
|
||||
def __init__(self):
|
||||
self.__game_data = GameData()
|
||||
self.__table = Table(self.__game_data)
|
||||
self.__cue_ball = CueBall((TABLE_WIDTH * 7 / 8, TABLE_HEIGHT / 4))
|
||||
self.__game_data.balls.append(self.__cue_ball)
|
||||
self.__cue = Cue(self.__cue_ball, self.__game_data)
|
||||
self.__space = pymunk.Space()
|
||||
self.__clock = pygame.time.Clock()
|
||||
self.__space.gravity = (0, 0)
|
||||
self.__rule = RuleManager(self.__game_data)
|
||||
self.__hitting = False
|
||||
self.__window = pygame.display.set_mode(SCREEN_SIZE)
|
||||
self.__fake_ball = FakeCueBall((99999, 99999))
|
||||
pass
|
||||
|
||||
def add_ball(self, ball: BaseBilliard):
|
||||
self.__game_data.balls.append(ball)
|
||||
self.__space.add(ball.rigid_body, ball.shape)
|
||||
|
||||
def init_table(self):
|
||||
self.add_ball(RedBall((TABLE_WIDTH / 4 - BALL_RADIUS, TABLE_HEIGHT / 2)))
|
||||
self.add_ball(RedBall((TABLE_WIDTH / 4 - BALL_RADIUS * 2, TABLE_HEIGHT / 2 - BALL_RADIUS / 2)))
|
||||
self.add_ball(RedBall((TABLE_WIDTH / 4 - BALL_RADIUS * 2, TABLE_HEIGHT / 2 + BALL_RADIUS / 2)))
|
||||
self.add_ball(RedBall((TABLE_WIDTH / 4 - BALL_RADIUS * 3, TABLE_HEIGHT / 2)))
|
||||
self.add_ball(RedBall((TABLE_WIDTH / 4 - BALL_RADIUS * 3, TABLE_HEIGHT / 2 - BALL_RADIUS)))
|
||||
self.add_ball(RedBall((TABLE_WIDTH / 4 - BALL_RADIUS * 3, TABLE_HEIGHT / 2 + BALL_RADIUS)))
|
||||
self.add_ball(RedBall((TABLE_WIDTH / 4 - BALL_RADIUS * 4, TABLE_HEIGHT / 2 - BALL_RADIUS / 2)))
|
||||
self.add_ball(RedBall((TABLE_WIDTH / 4 - BALL_RADIUS * 4, TABLE_HEIGHT / 2 + BALL_RADIUS / 2)))
|
||||
self.add_ball(RedBall((TABLE_WIDTH / 4 - BALL_RADIUS * 4, TABLE_HEIGHT / 2 - BALL_RADIUS)))
|
||||
self.add_ball(RedBall((TABLE_WIDTH / 4 - BALL_RADIUS * 4, TABLE_HEIGHT / 2 + BALL_RADIUS)))
|
||||
self.add_ball(RedBall((TABLE_WIDTH / 4 - BALL_RADIUS * 5, TABLE_HEIGHT / 2)))
|
||||
self.add_ball(RedBall((TABLE_WIDTH / 4 - BALL_RADIUS * 5, TABLE_HEIGHT / 2 - BALL_RADIUS)))
|
||||
self.add_ball(RedBall((TABLE_WIDTH / 4 - BALL_RADIUS * 5, TABLE_HEIGHT / 2 + BALL_RADIUS)))
|
||||
self.add_ball(RedBall((TABLE_WIDTH / 4 - BALL_RADIUS * 5, TABLE_HEIGHT / 2 - BALL_RADIUS * 2)))
|
||||
self.add_ball(RedBall((TABLE_WIDTH / 4 - BALL_RADIUS * 5, TABLE_HEIGHT / 2 + BALL_RADIUS * 2)))
|
||||
self.add_ball(ColorBall(2, YELLOW_BALL, YELLOW, (TABLE_WIDTH * 3 / 4, TABLE_HEIGHT / 3)))
|
||||
self.add_ball(ColorBall(3, COFFEE_BALL, COFFEE, (TABLE_WIDTH * 3 / 4, TABLE_HEIGHT / 2)))
|
||||
self.add_ball(ColorBall(4, CLAN_BALL, CLAN, (TABLE_WIDTH * 3 / 4, TABLE_HEIGHT * 2 / 3)))
|
||||
self.add_ball(ColorBall(5, BLUE_BALL, BLUE, (TABLE_WIDTH / 2, TABLE_HEIGHT / 2)))
|
||||
self.add_ball(ColorBall(6, PINK_BALL, PINK, (TABLE_WIDTH / 4 + BALL_RADIUS * 3, TABLE_HEIGHT / 2)))
|
||||
self.add_ball(ColorBall(7, BLACK_BALL, BLACK, (TABLE_WIDTH / 8, TABLE_HEIGHT / 2)))
|
||||
pass
|
||||
|
||||
def print_score(self, left_score, right_score):
|
||||
font = pygame.font.Font("./src/fonts/PingFang.ttc", 24)
|
||||
left_surface = font.render("left: " + str(left_score), True, BLACK, None)
|
||||
right_surface = font.render("right: " + str(right_score), True, BLACK, None)
|
||||
round_surface = font.render("round: " + ("left" if self.__rule.round == 1 else "right"), True, BLACK, None)
|
||||
self.__window.blit(left_surface, (SCREEN_WIDTH / 8, SCREEN_HEIGHT * 7 / 8))
|
||||
self.__window.blit(right_surface, (SCREEN_WIDTH * 7 / 8 - right_surface.get_width(), SCREEN_HEIGHT * 7 / 8))
|
||||
self.__window.blit(round_surface, (SCREEN_WIDTH / 2 - round_surface.get_width() / 2, SCREEN_HEIGHT * 7 / 8))
|
||||
|
||||
def hit(self, x, y):
|
||||
self.__hitting = True
|
||||
self.__game_data.last_goal_balls = [_ for _ in self.__game_data.goal_balls]
|
||||
self.__rule.get_should_hit()
|
||||
self.__game_data.collide_log.clear()
|
||||
self.__game_data.goal_balls.clear()
|
||||
pos_x = x - (SCREEN_WIDTH - TABLE_WIDTH) / 2
|
||||
pos_y = y - (SCREEN_HEIGHT - TABLE_HEIGHT) / 2
|
||||
self.__cue_ball.rigid_body.velocity = pymunk.Vec2d(
|
||||
pos_x - self.__game_data.balls[0].rigid_body.position.x,
|
||||
pos_y - self.__game_data.balls[0].rigid_body.position.y).scale_to_length(512)
|
||||
|
||||
def start(self):
|
||||
def new_ball(space, pos):
|
||||
ball = RedBall(pos)
|
||||
body = ball.rigid_body
|
||||
shape = ball.shape
|
||||
space.add(body, shape)
|
||||
self.__game_data.balls.append(ball)
|
||||
return shape
|
||||
|
||||
def post_collision_ball_ball(arbiter, space, data):
|
||||
self.__game_data.collide_log.append(arbiter.shapes[0].collision_type)
|
||||
|
||||
pygame.init()
|
||||
pygame.display.set_caption(TITLE)
|
||||
|
||||
self.init_table()
|
||||
|
||||
for _ in range(1, 9):
|
||||
self.__space.add_collision_handler(_, CUE_BALL).post_solve = post_collision_ball_ball
|
||||
|
||||
for _ in range(6):
|
||||
self.__space.add(self.__table.cushing_body[_], self.__table.cushing_shape[_])
|
||||
self.__space.add(self.__game_data.balls[0].rigid_body, self.__game_data.balls[0].shape)
|
||||
|
||||
force = 5
|
||||
add_force = False
|
||||
|
||||
while True:
|
||||
if self.__rule.settlement:
|
||||
self.__rule.judge()
|
||||
if self.__game_data.is_stable() and self.__hitting:
|
||||
self.__rule.settlement = True
|
||||
self.__hitting = False
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
pygame.quit()
|
||||
exit()
|
||||
if not self.__game_data.is_stable():
|
||||
continue
|
||||
if event.type == pygame.MOUSEMOTION:
|
||||
self.__cue.mouse_pos = event.pos
|
||||
if self.__rule.need_reset_cue_ball:
|
||||
self.__fake_ball.pos = event.pos
|
||||
|
||||
if event.type == pygame.MOUSEBUTTONDOWN:
|
||||
if event.button == 1:
|
||||
add_force = True
|
||||
if event.type == pygame.MOUSEBUTTONUP:
|
||||
if event.button == 1:
|
||||
if self.__rule.need_reset_cue_ball:
|
||||
pos_x = - (SCREEN_WIDTH - TABLE_WIDTH) / 2 + event.pos[0]
|
||||
pos_y = - (SCREEN_HEIGHT - TABLE_HEIGHT) / 2 + event.pos[1]
|
||||
self.__cue_ball.pos = (pos_x + 1, pos_y + 1)
|
||||
self.__fake_ball.pos = (99999, 99999)
|
||||
self.__rule.need_reset_cue_ball = False
|
||||
else:
|
||||
self.hit(event.pos[0], event.pos[1])
|
||||
add_force = False
|
||||
force = 5
|
||||
if event.button == 3:
|
||||
pos_x = event.pos[0] - (SCREEN_WIDTH - TABLE_WIDTH) / 2
|
||||
pos_y = event.pos[1] - (SCREEN_HEIGHT - TABLE_HEIGHT) / 2
|
||||
new_ball(self.__space, (pos_x, pos_y))
|
||||
if add_force:
|
||||
force += 5
|
||||
if force >= 512:
|
||||
force = 512
|
||||
self.__window.fill(WHITE)
|
||||
self.__table.goal(self.__space, self.__game_data.balls)
|
||||
self.__table.draw(self.__window)
|
||||
self.__fake_ball.draw(self.__window)
|
||||
if not self.__rule.need_reset_cue_ball:
|
||||
self.__cue.draw(self.__window)
|
||||
for _ in self.__game_data.balls:
|
||||
_.resistance()
|
||||
_.draw(self.__window)
|
||||
self.print_score(self.__game_data.left_score, self.__game_data.right_score)
|
||||
self.__space.step(1 / FPS)
|
||||
pygame.display.update()
|
||||
self.__clock.tick(FPS)
|
||||
pass
|
||||
|
||||
pass
|
@ -0,0 +1,24 @@
|
||||
from common.game.billiards.colorBall import ColorBall
|
||||
from common.game.billiards.redBall import RedBall
|
||||
from common.game.billiards.cueBall import CueBall
|
||||
|
||||
|
||||
class GameData(object):
|
||||
def __init__(self):
|
||||
self.left_score = 0
|
||||
self.right_score = 0
|
||||
self.balls: list[ColorBall | RedBall | CueBall] = []
|
||||
self.last_balls: list[ColorBall | RedBall | CueBall] = []
|
||||
self.goal_balls: list[int] = []
|
||||
self.last_goal_balls: list[int] = []
|
||||
self.collide_log: list[int] = []
|
||||
pass
|
||||
|
||||
def collide_ball(self, ball):
|
||||
self.collide_log.append(ball)
|
||||
|
||||
def is_stable(self) -> bool:
|
||||
for _ in self.balls:
|
||||
if not _.is_stable():
|
||||
return False
|
||||
return True
|
@ -0,0 +1,124 @@
|
||||
from common.game.gameData import GameData
|
||||
from enums.category import *
|
||||
|
||||
|
||||
class RuleManager(object):
|
||||
def __init__(self, game_data: GameData):
|
||||
self.__game_data = game_data
|
||||
self.__should_hit = []
|
||||
self.need_reset_cue_ball = False
|
||||
self.settlement = False
|
||||
self.round = 1
|
||||
|
||||
def judge(self):
|
||||
self.print_log()
|
||||
self.do_reset()
|
||||
self.cal_score()
|
||||
self.try_turn_round()
|
||||
self.settlement = False
|
||||
|
||||
def print_log(self):
|
||||
print('round:' + str(self.round))
|
||||
print('collide: ' + str(self.__game_data.collide_log))
|
||||
print('collision_flag: ' + str(self.collision_legal()))
|
||||
print('goal: ' + str(self.__game_data.goal_balls))
|
||||
print('goal_flag: ' + str(self.goal_legal()))
|
||||
|
||||
def check_legality(self):
|
||||
return self.collision_legal() and self.goal_legal() and self.cue_legal()
|
||||
|
||||
def do_reset(self):
|
||||
if not self.goal_legal():
|
||||
for _ in self.__game_data.balls:
|
||||
if _.category in COLORED_BALLS and _.category in self.__game_data.goal_balls:
|
||||
_.reset()
|
||||
if not self.cue_legal():
|
||||
self.need_reset_cue_ball = True
|
||||
for _ in self.__game_data.balls:
|
||||
if _.category == RED_BALL and _.activity:
|
||||
for __ in self.__game_data.balls:
|
||||
if __.category in COLORED_BALLS and __.category in self.__game_data.goal_balls:
|
||||
__.reset()
|
||||
break
|
||||
|
||||
def get_legality(self):
|
||||
legality = []
|
||||
if not self.collision_legal():
|
||||
legality.append(legality)
|
||||
if not self.goal_legal():
|
||||
legality.append(legality)
|
||||
if not self.cue_legal():
|
||||
legality.append(legality)
|
||||
return legality
|
||||
|
||||
def try_turn_round(self):
|
||||
if len(self.__game_data.goal_balls) != 0 and self.check_legality():
|
||||
return
|
||||
self.__game_data.goal_balls.clear()
|
||||
self.__game_data.last_goal_balls.clear()
|
||||
self.round *= -1
|
||||
|
||||
def get_should_hit(self):
|
||||
self.__should_hit.clear()
|
||||
have_red_ball = False
|
||||
for _ in self.__game_data.balls:
|
||||
if _.category == RED_BALL and _.activity:
|
||||
have_red_ball = True
|
||||
break
|
||||
if have_red_ball:
|
||||
if len(self.__game_data.last_goal_balls) == 0:
|
||||
return self.__should_hit.append(RED_BALL)
|
||||
if self.__game_data.last_goal_balls[0] in COLORED_BALLS:
|
||||
return self.__should_hit.append(RED_BALL)
|
||||
self.__should_hit = [_ for _ in COLORED_BALLS]
|
||||
return
|
||||
should_hit = 7
|
||||
for _ in self.__game_data.balls:
|
||||
if _.category < should_hit and _.activity:
|
||||
should_hit = _.category
|
||||
return self.__should_hit.append(should_hit)
|
||||
|
||||
def collision_legal(self):
|
||||
if len(self.__game_data.collide_log) == 0:
|
||||
return False
|
||||
if self.__game_data.collide_log[0] in self.__should_hit:
|
||||
return True
|
||||
return False
|
||||
|
||||
def goal_legal(self):
|
||||
without_cue_list = [_ for _ in self.__game_data.goal_balls]
|
||||
if CUE_BALL in without_cue_list:
|
||||
without_cue_list.remove(CUE_BALL)
|
||||
if len(without_cue_list) == 0:
|
||||
return True
|
||||
if len(list(set(without_cue_list) & set(self.__should_hit))) == 0:
|
||||
return False
|
||||
if RED_BALL not in self.__should_hit and len(list(set(without_cue_list) & set(self.__should_hit))) != 1:
|
||||
return True
|
||||
return True
|
||||
|
||||
def cue_legal(self):
|
||||
return CUE_BALL not in self.__game_data.goal_balls
|
||||
|
||||
def cal_score(self):
|
||||
if self.round == 1:
|
||||
if self.check_legality():
|
||||
for _ in self.__game_data.goal_balls:
|
||||
self.__game_data.left_score += _
|
||||
return
|
||||
if len(self.__game_data.collide_log) == 0:
|
||||
self.__game_data.right_score += 4
|
||||
return
|
||||
first_collide = self.__game_data.collide_log[0]
|
||||
self.__game_data.right_score += first_collide if first_collide > 4 else 4
|
||||
else:
|
||||
if self.check_legality():
|
||||
for _ in self.__game_data.goal_balls:
|
||||
self.__game_data.right_score += _
|
||||
return
|
||||
if len(self.__game_data.collide_log) == 0:
|
||||
self.__game_data.left_score += 4
|
||||
return
|
||||
first_collide = self.__game_data.collide_log[0]
|
||||
self.__game_data.left_score += first_collide if first_collide > 4 else 4
|
||||
pass
|
@ -0,0 +1,2 @@
|
||||
ILLEGAL_COLLISION = 1
|
||||
ILLEGAL_GOAL = 2
|
Binary file not shown.
Loading…
Reference in new issue