import pygame import sys import random import math class CUtils: """ A função GetRandomCoordinate(min_x, max_x, min_y, max_y) recebe quatro argumentos: min_x, max_x, min_y e max_y, que representam as faixas de valores para as coordenadas x e y. A função retorna um tuple de números inteiros aleatórios. """ @staticmethod def GetRandomCoordinate(min_x, max_x, min_y, max_y): return ( random.randint(min_x, max_x), random.randint(min_y, max_y), ) """ A função GetRandomFloatNumber(min, max) retorna um número aleatório de ponto flutuante dentro de uma faixa específica. Os argumentos min e max definem a faixa de valores para o número aleatório. O método retorna um número de ponto flutuante aleatório. """ @staticmethod def GetRandomFloatNumber(min, max): return random.uniform(float(min), float(max)) """ A função GetRandomIntegerNumber(min, max) retorna um número inteiro aleatório dentro de uma faixa específica. Os argumentos min e max definem a faixa de valores para o número aleatório. A função retorna um número inteiro aleatório. """ @staticmethod def GetRandomIntegerNumber(min, max): return random.randint(int(min), int(max)) class CElasticCollision: _BACKGROUND_COLOR = (255, 255, 255) _BORDER_PX_LENGHT = 10 _BORDER_COLOR = (0, 0, 0) _RED_COLOR = (255, 0, 0) _BLUE_COLOR = (0, 0, 255) _WHITE_COLOR = (255, 255, 255) # CElasticCollision Construtor def __init__(self, *args): self.__Initialize(*args) # Inicializa a biblioteca Pygame e configura a janela da simulação. self.__InitializePyGame() # Inicializa os objetos de círculo da simulação. self.__InitializeCirclesObjects() # Inicializa a simulação self.__RunSimulation() # CElasticCollision Destrutor def __del__(self): self.__Initialize('', 0, 0, 0, 0, 0) ################## #@ Private Methods ################## """ A função __Initialize() é chamada para inicializar as variáveis necessárias para o funcionamento da simulação. Esta recebe os seguintes argumentos: -> args[0]: Título da janela da simulação (convertido para uma string). -> args[1]: Largura da janela da simulação (convertida para um número inteiro). -> args[2]: Altura da janela da simulação (convertida para um número inteiro). -> args[3]: Raio dos círculos da simulação (convertido para um número inteiro). -> args[4]: Número de círculos na simulação (convertido para um número inteiro). -> args[5]: Velocidade máxima dos círculos (convertida para um número inteiro). Dentro da função, são inicializadas as seguintes variáveis: -> self.window_pygame: Inicializada como None. Será preenchida posteriormente com a janela da simulação criada pelo Pygame. -> self.window_title: Armazena o título da janela da simulação. -> self.window_width: Armazena a largura da janela da simulação. -> self.window_height: Armazena a altura da janela da simulação. -> self.circle_objects: Lista vazia que armazenará os objetos dos círculos da simulação. -> self.circle_radius: Armazena o raio dos círculos da simulação. -> self.circles_count: Armazena o número de círculos na simulação. -> self.circles_max_velocity: Armazena a velocidade máxima dos círculos. Esta função é responsável por preparar e inicializar todas as variáveis necessárias para a simulação antes de começar a executá-la. """ def __Initialize(self, *args): self.window_pygame = None self.window_title = str(args[0]) self.window_width = int(args[1]) self.window_height = int(args[2]) self.circle_objects = [] self.circle_radius = int(args[3]) self.circles_count = int(args[4]) self.circles_max_velocity = int(args[5]) """ A função __InitializePyGame() é responsável por inicializar a biblioteca Pygame e configurar a janela da simulação. A primeira verificação self.IsPyGameInitialized() verifica se o Pygame já foi inicializado anteriormente. Se sim, a função retorna imediatamente sem fazer nada. Caso o Pygame não tenha sido inicializado, a função chama pygame.init() para inicializar a biblioteca. Em seguida, ela cria uma janela com as dimensões especificadas em self.window_width e self.window_height usando pygame.display.set_mode(). O título da janela é definido usando pygame.display.set_caption() com o valor armazenado em self.window_title. Desta forma, esta função garante que o Pygame seja inicializado apenas uma vez e que a janela de simulação seja configurada corretamente. """ def __InitializePyGame(self): if self.IsPyGameInitialized(): return pygame.init() self.window_pygame = pygame.display.set_mode((self.window_width, self.window_height)) pygame.display.set_caption(self.window_title) """ A função __InitializeCirclesObjects(self) é responsável por inicializar os círculos na simulação. Ela verifica se os objetos de círculo já foram inicializados anteriormente e, se sim, retorna imediatamente. A função possui uma função interna __IsPositionUnique(new_circle_position) que verifica se uma nova posição do círculo é única, ou seja, se não há sobreposição com outros círculos já existentes. Essa verificação é feita calculando a distância entre a nova posição e as posições dos círculos existentes. Se a distância for menor ou igual ao raio do círculo, significa que há sobreposição. Dentro de um loop for circle_index in range(self.circles_count), são inicializados os círculos um por um. Para cada círculo, é gerada uma posição aleatória dentro dos limites da janela da simulação, garantindo que o círculo não fique muito próximo das bordas. A função CUtils.GetRandomCoordinate() é usada para gerar uma posição aleatória dentro desses limites. Em seguida, é verificado se a posição gerada é única usando a função __IsPositionUnique(). Se a posição não for única (ou seja, houver sobreposição), um novo conjunto de coordenadas é gerado até que uma posição única seja encontrada. Depois de uma posição única ser encontrada, um objeto de círculo é adicionado à lista self.circle_objects. Esse objeto contém informações como posição, velocidade, cor e uma lista de círculos com os quais ele já colidiu. O loop continua até que todos os círculos sejam inicializados e adicionados à lista self.circle_objects. """ def __InitializeCirclesObjects(self): if self.IsCircleObjectsInitialized(): return """ A função __IsPositionUnique(new_circle_position) verifica se uma nova posição de círculo é única, ou seja, se não há sobreposição com outros círculos já existentes. A função itera sobre cada objeto de círculo na lista self.circle_objects. Para cada círculo, calcula a distância entre a posição do círculo existente e a nova posição do círculo utilizando o Teorema de Pitágoras. A fórmula utilizada é a distância euclidiana entre dois pontos no plano: distance = math.sqrt((circle['position'][0] - new_circle_position[0]) ** 2 + (circle['position'][1] - new_circle_position[1]) ** 2) Se a distância calculada for menor ou igual ao raio do círculo (self.circle_radius), significa que há uma sobreposição entre os círculos e a função retorna False. Se a função percorrer todos os círculos existentes e não encontrar nenhuma sobreposição, ela retorna True, indicando que a nova posição é única e não há sobreposição com outros círculos. Esta função é usada na inicialização dos objetos de círculo para garantir que cada novo círculo seja posicionado em uma localização única, evitando colisões imediatas entre os círculos. """ def __IsPositionUnique(new_circle_position): for circle in self.circle_objects: distance = math.sqrt( (circle['position'][0] - new_circle_position[0]) ** 2 + (circle['position'][1] - new_circle_position[1]) ** 2 ) if distance <= self.circle_radius: return False return True for circle_index in range(self.circles_count): while True: positi self._BORDER_PX_LENGHT + self.circle_radius, self.window_width - self._BORDER_PX_LENGHT - self.circle_radius, self._BORDER_PX_LENGHT + self.circle_radius, self.window_height - self._BORDER_PX_LENGHT - self.circle_radius ) if __IsPositionUnique(position): break self.circle_objects.append({ 'position' : position, 'velocity' : [ CUtils.GetRandomFloatNumber(1, self.circles_max_velocity), CUtils.GetRandomFloatNumber(1, self.circles_max_velocity) ], 'color' : self._RED_COLOR if circle_index < int(self.circles_count * 0.1) else self._BLUE_COLOR, 'collided_balls' : [] }) def __RunSimulation(self): # Verifica se o PyGame foi inicializado corretamente if not self.IsPyGameInitialized(): return """ A função __CalculateDistance(pt1, pt2) é usada para calcular a distância entre dois pontos no plano implementando a fórmula da distância euclidiana. As coordenadas dos pontos pt1 e pt2 são extraídas, representadas como (x1, y1) e (x2, y2) respectivamente. A diferença entre as coordenadas x (dx = pt1[0] - pt2[0]) e y (dy = pt1[1] - pt2[1]) é calculada. A linha return (dx ** 2 + dy ** 2) ** 0.5 calcula a distância euclidiana entre os pontos usando a fórmula matemática d = sqrt(dx^2 + dy^2). O resultado é retornado como a distância entre os pontos pt1 e pt2. Esta função é utilizada em outros lugares do código para calcular a distância entre os centros dos círculos e determinar se eles colidiram com base nessa distância. """ def __CalculateDistance(pt1, pt2): dx = pt1[0] - pt2[0] dy = pt1[1] - pt2[1] return (dx ** 2 + dy ** 2) ** 0.5 """ A função __CheckCollision(circle, other_circle) é responsável por verificar se dois círculos colidiram com base nas suas posições e raios. As variáveis (x1, y1) e (x2, y2) são extraídas das informações dos círculos circle e other_circle, representando suas posições. A função __CalculateDistance((x1, y1), (x2, y2)) é chamada para calcular a distância entre os centros dos dois círculos. Essa função deve estar definida em algum lugar no código e não foi fornecida. A linha return __CalculateDistance((x1, y1), (x2, y2)) <= (2 * self.circle_radius) verifica se a distância entre os centros dos círculos é menor ou igual à soma dos raios dos círculos. Se isso for verdadeiro, significa que os círculos estão se sobrepondo e, portanto, ocorreu uma colisão. Em resumo, esta função verifica se dois círculos colidiram verificando se a distância entre os seus centros é menor ou igual à soma de seus raios. """ def __CheckCollision(circle, other_circle): (x1, y1) = circle['position'] (x2, y2) = other_circle['position'] return __CalculateDistance((x1, y1), (x2, y2)) <= (2 * self.circle_radius) """ A função __CalculateNewVelocities(velocity1, velocity2, mass1=1, mass2=1) é responsável por calcular as novas velocidades dos círculos após uma colisão. As variáveis multi1 e multi2 são calculadas para determinar as proporções das massas dos círculos na colisão. Essas proporções são usadas para calcular a contribuição relativa de cada círculo na nova velocidade resultante. A linha delta_v2 = (multi1 * velocity1[0] - multi2 * velocity2[0], multi1 * velocity1[1] - multi2 * velocity2[1]) calcula a diferença nas velocidades nos eixos x e y entre os círculos, com base nas proporções das massas. Essa diferença é o quanto a velocidade de cada círculo será alterada após a colisão. A linha delta_v1 = (multi2 * velocity2[0] - multi1 * velocity1[0], multi2 * velocity2[1] - multi1 * velocity1[1]) calcula a diferença nas velocidades nos eixos x e y, mas com a ordem dos círculos invertida. Isso é necessário para atualizar corretamente as velocidades de ambos os círculos. A função retorna a soma das diferenças nas velocidades (delta_v1 + delta_v2), que são as novas velocidades para cada círculo após a colisão. Em resumo, esta função utiliza as massas e as velocidades dos círculos envolvidos em uma colisão para calcular as novas velocidades deles após a colisão. """ def __CalculateNewVelocities(velocity1, velocity2, mass1 = 1, mass2 = 1): multi1 = mass1 / (mass1 + mass2) multi2 = mass2 / (mass1 + mass2) delta_v2 = (multi1 * velocity1[0] - multi2 * velocity2[0], multi1 * velocity1[1] - multi2 * velocity2[1]) delta_v1 = (multi2 * velocity2[0] - multi1 * velocity1[0], multi2 * velocity2[1] - multi1 * velocity1[1]) return delta_v1 + delta_v2 """ A função __UpdateCircles() é responsável por atualizar a posição dos círculos na simulação e lidar com as colisões entre eles. O primeiro loop for circle in self.circle_objects: itera sobre todos os círculos presentes em self.circle_objects e atualiza as posições com base na sua velocidades. Ele também verifica se os círculos atingiram as bordas da janela da simulação e inverte a direção da velocidade caso tenham atingido alguma das bordas. O segundo loop for circle in self.circle_objects: é usado para verificar as colisões entre os círculos. Ele itera sobre todos os círculos presentes na lista self.circle_objects e verifica se houve uma colisão com qualquer outro círculo. Dentro do segundo loop, a função __CheckCollision(circle, other_circle) é chamada para verificar se ocorreu uma colisão entre circle e other_circle. Se ocorrer uma colisão e other_circle não estiver na lista collided_balls de circle, as velocidades dos círculos são atualizadas usando a função __CalculateNewVelocities((x_vel, y_vel), (other_x_vel, other_y_vel)). Os círculos também são adicionados às listas collided_balls um do outro para evitar colisões repetidas. Se não houver uma colisão entre circle e other_circle, e circle estiver na lista collided_balls de other_circle, as listas collided_balls são atualizadas para remover a referência ao outro círculo. Isso garante que a lista collided_balls esteja sempre atualizada com as colisões atuais. Em resumo, esta função atualiza as posições dos círculos, trata colisões entre eles e atualiza as velocidades com base nas colisões ocorridas. """ def __UpdateCircles(): for circle in self.circle_objects: (x, y) = circle['position'] (x_vel, y_vel) = circle['velocity'] if (x + self.circle_radius <= (self.window_width - self._BORDER_PX_LENGHT)) and (x - self.circle_radius) >= self._BORDER_PX_LENGHT: x += x_vel else: x_vel *= -1 x += x_vel if (y + self.circle_radius <= (self.window_height - self._BORDER_PX_LENGHT)) and (y - self.circle_radius) >= self._BORDER_PX_LENGHT: y += y_vel else: y_vel *= -1 y += y_vel circle['position'] = [x, y] circle['velocity'] = [x_vel, y_vel] for circle in self.circle_objects: for other_circle in self.circle_objects: exist_collision = __CheckCollision(circle, other_circle) if other_circle not in circle['collided_balls'] and circle != other_circle and exist_collision: (x_vel, y_vel) = circle['velocity'] (other_x_vel, other_y_vel) = other_circle['velocity'] new_velocities = __CalculateNewVelocities((x_vel, y_vel), (other_x_vel, other_y_vel)) circle['velocity'] = [new_velocities[0], new_velocities[1]] circle['collided_balls'].append(other_circle) other_circle['velocity'] = [new_velocities[2], new_velocities[3]] other_circle['collided_balls'].append(circle) elif not exist_collision: if circle in other_circle['collided_balls']: other_circle['collided_balls'].remove(circle) circle['collided_balls'].remove(other_circle) """ A função __DrawWindow() é responsável por desenhar a janela da simulação com o fundo preenchido e as bordas retangulares. self.window_pygame.fill(self._BACKGROUND_COLOR): Preenche a janela da simulação com a cor de fundo definida em _BACKGROUND_COLOR. Essa linha garante que a janela seja limpa e preenchida com a cor correta antes de desenhar os retângulos das bordas. pygame.draw.rect(self.window_pygame, self._BORDER_COLOR, (0, 0, self.window_width, self._BORDER_PX_LENGHT)): Desenha um retângulo na parte superior da janela da simulação. O retângulo tem a cor definida em _BORDER_COLOR e as coordenadas (0, 0, self.window_width, self._BORDER_PX_LENGHT) especificam a posição e as dimensões do retângulo. Nesse caso, o retângulo é desenhado na parte superior da janela com a largura da janela (self.window_width) e a espessura das bordas (self._BORDER_PX_LENGHT). pygame.draw.rect(self.window_pygame, self._BORDER_COLOR, (0, self.window_height - self._BORDER_PX_LENGHT, self.window_width, self._BORDER_PX_LENGHT)): Desenha um retângulo na parte inferior da janela da simulação. O retângulo tem a cor definida em _BORDER_COLOR e as coordenadas (0, self.window_height - self._BORDER_PX_LENGHT, self.window_width, self._BORDER_PX_LENGHT) especificam a posição e as dimensões do retângulo. Nesse caso, o retângulo é desenhado na parte inferior da janela com a largura da janela (self.window_width) e a espessura das bordas (self._BORDER_PX_LENGHT). pygame.draw.rect(self.window_pygame, self._BORDER_COLOR, (0, 0, self._BORDER_PX_LENGHT, self.window_height)): Desenha um retângulo na parte esquerda da janela da simulação. O retângulo tem a cor definida em _BORDER_COLOR e as coordenadas (0, 0, self._BORDER_PX_LENGHT, self.window_height) especificam a posição e as dimensões do retângulo. Nesse caso, o retângulo é desenhado na parte esquerda da janela com a altura da janela (self.window_height) e a espessura das bordas (self._BORDER_PX_LENGHT). pygame.draw.rect(self.window_pygame, self._BORDER_COLOR, (self.window_width - self._BORDER_PX_LENGHT, 0, self._BORDER_PX_LENGHT, self.window_height)): Desenha um retângulo na parte direita da janela da simulação. O retângulo tem a cor definida em _BORDER_COLOR e as coordenadas (self.window_width - self._BORDER_PX_LENGHT, 0, self._BORDER_PX_LENGHT, self.window_height) especificam a posição e as dimensões do retângulo. Nesse caso, o retângulo é desenhado na parte direita """ def __DrawWindow(): self.window_pygame.fill(self._BACKGROUND_COLOR) pygame.draw.rect(self.window_pygame, self._BORDER_COLOR, (0, 0, self.window_width, self._BORDER_PX_LENGHT)) pygame.draw.rect(self.window_pygame, self._BORDER_COLOR, (0, self.window_height - self._BORDER_PX_LENGHT, self.window_width, self._BORDER_PX_LENGHT)) pygame.draw.rect(self.window_pygame, self._BORDER_COLOR, (0, 0, self._BORDER_PX_LENGHT, self.window_height)) pygame.draw.rect(self.window_pygame, self._BORDER_COLOR, (self.window_width - self._BORDER_PX_LENGHT, 0, self._BORDER_PX_LENGHT, self.window_height)) """ A função __DrawCircles() é responsável por desenhar os círculos na janela da simulação. Ela percorre a lista de objetos dos círculos (circle_objects) e desenha cada círculo utilizando a função pygame.draw.circle(). Dentro do loop, para cada objeto de círculo, a função pygame.draw.circle() é chamada com os seguintes parâmetros: self.window_pygame: A janela da simulação onde o círculo será desenhado. circle_information.get('color', self._WHITE_COLOR): A cor do círculo. É obtida a partir do dicionário circle_information, onde o valor correspondente à chave 'color' é retornado. Se a chave 'color' não existir no dicionário, o valor padrão self._WHITE_COLOR (branco) é utilizado. circle_information.get('position', (0, 0)): A posição do centro do círculo. É obtida do dicionário circle_information, onde o valor correspondente à chave 'position' é retornado. Se a chave 'position' não existir no dicionário, o valor padrão (0, 0) é utilizado. self.circle_radius: O raio do círculo. Desta forma, a função percorre todos os círculos e os desenha na janela da simulação com as cores e posições corretas. """ def __DrawCircles(): for circle_information in self.circle_objects: pygame.draw.circle( self.window_pygame, circle_information.get('color', self._WHITE_COLOR), circle_information.get('position', (0, 0)), self.circle_radius ) """ A função __Refresh() é responsável por atualizar a janela da simulação e adicionar um pequeno atraso de 10 milissegundos antes de atualizar novamente. pygame.display.update() atualiza a janela da simulação, mostrando as alterações feitas nos objetos. pygame.time.delay(10) adiciona um atraso de 10 milissegundos, permitindo controlar a taxa de atualização da janela. Isso evita que a janela seja atualizada muito rapidamente, tornando a simulação mais suave e reduzindo o consumo de recursos do sistema. """ def __Refresh(): pygame.display.update() pygame.time.delay(10) while True: """ O loop for event in pygame.event.get() captura todos os eventos que ocorrem na simulação. Ele percorre a lista de eventos retornada pela função pygame.event.get(). Para cada evento capturado, é verificado se o tipo de evento é pygame.QUIT, que indica que o usuário solicitou o fecho da janela da simulação. Neste caso, as funções pygame.quit() e sys.exit() são chamadas para encerrar a simulação corretamente. """ for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() # Atualiza a posição e velocidade dos círculos. __UpdateCircles() # Desenha a janela da simulação, preenchendo o fundo com a cor de fundo definida e desenhando os retângulos que representam as bordas da janela. __DrawWindow() # Desenhar os círculos na janela da simulação. __DrawCircles() # Atualiza a exibição da janela da simulação, mostrando todas as alterações feitas nos passos anteriores. __Refresh() ################## #@ Public Methods ################## def IsPyGameInitialized(self): # Verifica se o Pygame foi inicializado return self.window_pygame != None def IsCircleObjectsInitialized(self): # Verifica se os objetos de circulo foram inicializados return len(self.circle_objects) != 0 def GetPyGameWindowWidth(self): # Retorna a largura da janela do Pygame return self.window_width def GetPyGameWindowHeight(self): # Retorna a altura da janela do Pygame return self.window_height if __name__ == '__main__': # Cria uma instância do gerenciador da simulação collision_manager = CElasticCollision( "Colisao Elastica", # Título da janela 800, # Largura da janela 600, # Altura da janela 15, # Raio do círculo 20, # Quantidade de círculos 5.0 # Velocidade inicial maxima dos círculos )