├── .gitignore ├── 2_players.py ├── 4_players.py ├── LICENSE ├── README.md └── pics ├── 2_players.gif └── 4_players.gif /.gitignore: -------------------------------------------------------------------------------- 1 | /frames 2 | -------------------------------------------------------------------------------- /2_players.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import random 3 | import math 4 | import argparse 5 | import os 6 | 7 | 8 | # Constants 9 | WIDTH, HEIGHT = 800, 800 10 | SQUARE_SIZE = 25 11 | DAY_COLOR = (51, 255, 255) 12 | NIGHT_COLOR = (255, 153, 255) 13 | DAY_BALL_COLOR = (200, 255, 255) 14 | NIGHT_BALL_COLOR = (255, 0, 255) 15 | DX = 14 16 | DY = 14 17 | 18 | def calculate_scores(squares): 19 | scores = {DAY_COLOR: 0, NIGHT_COLOR: 0} 20 | for row in squares: 21 | for color in row: 22 | if color in scores: 23 | scores[color] += 1 24 | return scores 25 | 26 | 27 | def draw_score_panel(screen, scores, font): 28 | panel_height = 40 29 | panel_color = (50, 50, 50) # Dark gray background for score panel 30 | 31 | # Draw the background panel at the bottom 32 | pygame.draw.rect(screen, panel_color, (0, HEIGHT - panel_height, WIDTH, panel_height)) 33 | 34 | # Calculate the total width of the score texts for 2 players 35 | player_colors = [DAY_COLOR, NIGHT_COLOR] 36 | total_width = 0 37 | score_surfaces = [] 38 | for color in player_colors: 39 | score_text = str(scores[color]) 40 | score_surface = font.render(score_text, True, color) 41 | score_surfaces.append(score_surface) 42 | total_width += score_surface.get_width() + 30 # Include spacing 43 | 44 | # Start position for the first score text to center the block 45 | text_x = (WIDTH - total_width) // 2 46 | text_y = HEIGHT - panel_height + (panel_height - font.get_height()) // 2 47 | 48 | # Draw each score text 49 | for score_surface in score_surfaces: 50 | screen.blit(score_surface, (text_x, text_y)) 51 | text_x += score_surface.get_width() + 30 # Adjust spacing between scores 52 | 53 | 54 | 55 | def create_squares(): 56 | squares = [] 57 | for i in range(int(WIDTH / SQUARE_SIZE)): 58 | row = [] 59 | for j in range(int(HEIGHT / SQUARE_SIZE)): 60 | color = DAY_COLOR if i < WIDTH / SQUARE_SIZE / 2 else NIGHT_COLOR 61 | row.append(color) 62 | squares.append(row) 63 | return squares 64 | 65 | def draw_squares(squares, screen): 66 | for i in range(len(squares)): 67 | for j in range(len(squares[i])): 68 | color = squares[i][j] 69 | pygame.draw.rect(screen, color, (i * SQUARE_SIZE, j * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)) 70 | 71 | 72 | def draw_ball(x, y, color, screen): 73 | pygame.draw.circle(screen, color, (int(x), int(y)), SQUARE_SIZE // 2) 74 | 75 | 76 | def update_square_and_bounce(x, y, dx, dy, color, squares): 77 | updated_dx, updated_dy = dx, dy 78 | for angle in range(0, 360, 45): 79 | rad = math.radians(angle) 80 | check_x = x + math.cos(rad) * (SQUARE_SIZE // 2) 81 | check_y = y + math.sin(rad) * (SQUARE_SIZE // 2) 82 | i, j = int(check_x // SQUARE_SIZE), int(check_y // SQUARE_SIZE) 83 | if 0 <= i < len(squares) and 0 <= j < len(squares[i]): 84 | if squares[i][j] != color: 85 | squares[i][j] = color 86 | if abs(math.cos(rad)) > abs(math.sin(rad)): 87 | updated_dx = -updated_dx 88 | else: 89 | updated_dy = -updated_dy 90 | updated_dx += random.uniform(-0.01, 0.01) 91 | updated_dy += random.uniform(-0.01, 0.01) 92 | return updated_dx, updated_dy 93 | 94 | 95 | def check_boundary_collision(x, y, dx, dy): 96 | if x + dx > WIDTH - SQUARE_SIZE // 2 or x + dx < SQUARE_SIZE // 2: 97 | dx = -dx 98 | if y + dy > HEIGHT - SQUARE_SIZE // 2 or y + dy < SQUARE_SIZE // 2: 99 | dy = -dy 100 | return dx, dy 101 | 102 | 103 | def make_gif(frames_dir, delete_frames=True): 104 | from moviepy.editor import ImageSequenceClip 105 | from natsort import natsorted 106 | import glob 107 | frame_files = natsorted(glob.glob(os.path.join(frames_dir, "*.png"))) 108 | 109 | clip = ImageSequenceClip(frame_files, fps=60) 110 | pics_dir = "./pics" 111 | os.makedirs(pics_dir, exist_ok=True) 112 | clip.write_gif(os.path.join(pics_dir, "2_players.gif")) 113 | # delete frames 114 | if delete_frames: 115 | # remove frames folder 116 | import shutil 117 | shutil.rmtree(frames_dir) 118 | 119 | 120 | 121 | def main(args): 122 | if args.seed: 123 | random.seed(args.seed) 124 | if args.record_frames: 125 | frame_dir = "frames" 126 | os.makedirs(frame_dir, exist_ok=True) 127 | frame_num = 0 128 | pygame.init() 129 | pygame.font.init() # Initialize the font module 130 | 131 | font = pygame.font.SysFont('Consolas', 18) # Or any other preferred font 132 | # Set up the display 133 | screen = pygame.display.set_mode((WIDTH, HEIGHT)) 134 | pygame.display.set_caption("Pong Wars") 135 | 136 | clock = pygame.time.Clock() 137 | squares = create_squares() 138 | x1, y1 = WIDTH / 4, HEIGHT / 2 139 | dx1, dy1 = DX, DY 140 | x2, y2 = WIDTH * 3 / 4, HEIGHT / 2 141 | dx2, dy2 = -DX, -DY 142 | 143 | running = True 144 | while running: 145 | for event in pygame.event.get(): 146 | if event.type == pygame.QUIT: 147 | running = False 148 | 149 | dx1, dy1 = update_square_and_bounce(x1, y1, dx1, dy1, DAY_COLOR, squares) 150 | dx2, dy2 = update_square_and_bounce(x2, y2, dx2, dy2, NIGHT_COLOR, squares) 151 | 152 | dx1, dy1 = check_boundary_collision(x1, y1, dx1, dy1) 153 | dx2, dy2 = check_boundary_collision(x2, y2, dx2, dy2) 154 | 155 | x1 += dx1 156 | y1 += dy1 157 | x2 += dx2 158 | y2 += dy2 159 | 160 | screen.fill((0, 0, 0)) 161 | draw_squares(squares, screen) 162 | draw_ball(x1, y1, DAY_BALL_COLOR, screen) 163 | draw_ball(x2, y2, NIGHT_BALL_COLOR, screen) 164 | 165 | # Display scores 166 | scores = calculate_scores(squares) 167 | draw_score_panel(screen, scores, font) 168 | if args.record_frames: 169 | if frame_num%3 == 0: 170 | pygame.image.save(screen, os.path.join(frame_dir, f"frame_{frame_num}.png")) 171 | frame_num += 1 172 | 173 | pygame.display.flip() 174 | clock.tick(60) 175 | 176 | pygame.quit() 177 | if args.record_frames: 178 | make_gif(frame_dir) 179 | 180 | if __name__ == "__main__": 181 | args = argparse.ArgumentParser() 182 | args.add_argument("--record_frames", action="store_true", help="Record frames for making a movie", default=False) 183 | args.add_argument("--seed", type=int, help="Seed for random number generator", default=0) 184 | args = args.parse_args() 185 | main(args) 186 | -------------------------------------------------------------------------------- /4_players.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import random 3 | import math 4 | import argparse 5 | import os 6 | 7 | 8 | # Constants 9 | WIDTH, HEIGHT = 800, 800 10 | SQUARE_SIZE = 25 11 | PLAYER1_COLOR = (255, 153, 153) 12 | PLAYER2_COLOR = (255, 255, 153) 13 | PLAYER3_COLOR = (153, 255, 153) 14 | PLAYER4_COLOR = (153, 153, 255) 15 | 16 | # Ball colors for each player, contrast is important 17 | 18 | BALL_COLOR1 = (255, 0, 0) # Red 19 | BALL_COLOR2 = (255, 255, 0) # Yellow 20 | BALL_COLOR3 = (0, 255, 0) # Green 21 | BALL_COLOR4 = (0, 0, 255) # Blue 22 | 23 | DX = 14 24 | DY = 14 25 | 26 | 27 | def calculate_scores(squares): 28 | scores = {PLAYER1_COLOR: 0, PLAYER2_COLOR: 0, PLAYER3_COLOR: 0, PLAYER4_COLOR: 0} 29 | for row in squares: 30 | for color in row: 31 | if color in scores: 32 | scores[color] += 1 33 | return scores 34 | 35 | def draw_score_panel(screen, scores, font): 36 | panel_height = 40 37 | panel_color = (50, 50, 50) # Dark gray background for score panel 38 | 39 | # Draw the background panel at the bottom 40 | pygame.draw.rect(screen, panel_color, (0, HEIGHT - panel_height, WIDTH, panel_height)) 41 | 42 | # Calculate the total width of the score texts 43 | player_colors = [PLAYER1_COLOR, PLAYER2_COLOR, PLAYER3_COLOR, PLAYER4_COLOR] 44 | total_width = 0 45 | score_surfaces = [] 46 | for color in player_colors: 47 | score_text = str(scores[color]) 48 | score_surface = font.render(score_text, True, color) 49 | score_surfaces.append(score_surface) 50 | total_width += score_surface.get_width() + 30 # Include spacing 51 | 52 | # Start position for the first score text to center the block 53 | text_x = (WIDTH - total_width) // 2 54 | text_y = HEIGHT - panel_height + (panel_height - font.get_height()) // 2 55 | 56 | # Draw each score text 57 | for score_surface in score_surfaces: 58 | screen.blit(score_surface, (text_x, text_y)) 59 | text_x += score_surface.get_width() + 30 # Adjust spacing between scores 60 | 61 | 62 | 63 | def create_squares(): 64 | squares = [] 65 | for i in range(int(WIDTH / SQUARE_SIZE)): 66 | row = [] 67 | for j in range(int(HEIGHT / SQUARE_SIZE)): 68 | if i < WIDTH / SQUARE_SIZE / 2: 69 | color = PLAYER1_COLOR if j < HEIGHT / SQUARE_SIZE / 2 else PLAYER3_COLOR 70 | else: 71 | color = PLAYER2_COLOR if j < HEIGHT / SQUARE_SIZE / 2 else PLAYER4_COLOR 72 | row.append(color) 73 | squares.append(row) 74 | return squares 75 | 76 | def draw_squares(squares, screen): 77 | for i in range(len(squares)): 78 | for j in range(len(squares[i])): 79 | color = squares[i][j] 80 | pygame.draw.rect(screen, color, (i * SQUARE_SIZE, j * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)) 81 | 82 | 83 | def draw_ball(x, y, color, screen): 84 | pygame.draw.circle(screen, color, (int(x), int(y)), SQUARE_SIZE // 2) 85 | 86 | 87 | def update_square_and_bounce(x, y, dx, dy, color, squares): 88 | updated_dx, updated_dy = dx, dy 89 | for angle in range(0, 360, 45): 90 | rad = math.radians(angle) 91 | check_x = x + math.cos(rad) * (SQUARE_SIZE // 2) 92 | check_y = y + math.sin(rad) * (SQUARE_SIZE // 2) 93 | i, j = int(check_x // SQUARE_SIZE), int(check_y // SQUARE_SIZE) 94 | if 0 <= i < len(squares) and 0 <= j < len(squares[i]): 95 | if squares[i][j] != color: 96 | squares[i][j] = color 97 | if abs(math.cos(rad)) > abs(math.sin(rad)): 98 | updated_dx = -updated_dx 99 | else: 100 | updated_dy = -updated_dy 101 | updated_dx += random.uniform(-0.01, 0.01) 102 | updated_dy += random.uniform(-0.01, 0.01) 103 | return updated_dx, updated_dy 104 | 105 | 106 | def check_boundary_collision(x, y, dx, dy): 107 | if x + dx > WIDTH - SQUARE_SIZE // 2 or x + dx < SQUARE_SIZE // 2: 108 | dx = -dx 109 | if y + dy > HEIGHT - SQUARE_SIZE // 2 or y + dy < SQUARE_SIZE // 2: 110 | dy = -dy 111 | return dx, dy 112 | 113 | 114 | def make_gif(frames_dir, delete_frames=True): 115 | from moviepy.editor import ImageSequenceClip 116 | from natsort import natsorted 117 | import glob 118 | frame_files = natsorted(glob.glob(os.path.join(frames_dir, "*.png"))) 119 | 120 | clip = ImageSequenceClip(frame_files, fps=60) 121 | pics_dir = "./pics" 122 | clip.write_gif(os.path.join(pics_dir, "4_players.gif")) 123 | # delete frames 124 | if delete_frames: 125 | # remove frames folder 126 | import shutil 127 | shutil.rmtree(frames_dir) 128 | 129 | 130 | def main(args): 131 | if args.seed: 132 | random.seed(args.seed) 133 | if args.record_frames: 134 | frame_dir = "frames" 135 | os.makedirs(frame_dir, exist_ok=True) 136 | frame_num = 0 137 | pygame.init() 138 | pygame.font.init() # Initialize the font module 139 | 140 | font = pygame.font.SysFont('Consolas', 18) # Or any other preferred font 141 | 142 | # Set up the display 143 | screen = pygame.display.set_mode((WIDTH, HEIGHT)) 144 | pygame.display.set_caption("Pong Wars") 145 | 146 | clock = pygame.time.Clock() 147 | squares = create_squares() 148 | x1, y1 = WIDTH / 4, HEIGHT / 4 149 | x2, y2 = 3 * WIDTH / 4, HEIGHT / 4 150 | x3, y3 = WIDTH / 4, 3 * HEIGHT / 4 151 | x4, y4 = 3 * WIDTH / 4, 3 * HEIGHT / 4 152 | 153 | # Initial directions for four players 154 | dx1, dy1 = DX, DY 155 | dx2, dy2 = -DX, DY 156 | dx3, dy3 = DX, -DY 157 | dx4, dy4 = -DX, -DY 158 | 159 | running = True 160 | while running: 161 | for event in pygame.event.get(): 162 | if event.type == pygame.QUIT: 163 | running = False 164 | 165 | dx1, dy1 = update_square_and_bounce(x1, y1, dx1, dy1, PLAYER1_COLOR, squares) 166 | dx2, dy2 = update_square_and_bounce(x2, y2, dx2, dy2, PLAYER2_COLOR, squares) 167 | dx3, dy3 = update_square_and_bounce(x3, y3, dx3, dy3, PLAYER3_COLOR, squares) 168 | dx4, dy4 = update_square_and_bounce(x4, y4, dx4, dy4, PLAYER4_COLOR, squares) 169 | 170 | # Check boundary collisions for players 171 | dx1, dy1 = check_boundary_collision(x1, y1, dx1, dy1) 172 | dx2, dy2 = check_boundary_collision(x2, y2, dx2, dy2) 173 | dx3, dy3 = check_boundary_collision(x3, y3, dx3, dy3) 174 | dx4, dy4 = check_boundary_collision(x4, y4, dx4, dy4) 175 | 176 | x1 += dx1 177 | y1 += dy1 178 | x2 += dx2 179 | y2 += dy2 180 | x3 += dx3 181 | y3 += dy3 182 | x4 += dx4 183 | y4 += dy4 184 | 185 | 186 | draw_squares(squares, screen) 187 | draw_ball(x1, y1, BALL_COLOR1, screen) 188 | draw_ball(x2, y2, BALL_COLOR2, screen) 189 | draw_ball(x3, y3, BALL_COLOR3, screen) 190 | draw_ball(x4, y4, BALL_COLOR4, screen) 191 | 192 | # Display scores 193 | scores = calculate_scores(squares) 194 | draw_score_panel(screen, scores, font) 195 | 196 | if args.record_frames: 197 | if frame_num%3 == 0: 198 | pygame.image.save(screen, os.path.join(frame_dir, f"frame_{frame_num}.png")) 199 | frame_num += 1 200 | 201 | pygame.display.flip() 202 | clock.tick(60) 203 | 204 | pygame.quit() 205 | if args.record_frames: 206 | make_gif(frame_dir) 207 | 208 | 209 | 210 | if __name__ == "__main__": 211 | args = argparse.ArgumentParser() 212 | args.add_argument("--record_frames", action="store_true", help="Record frames for making a movie", default=False) 213 | args.add_argument("--seed", type=int, help="Seed for random number generator", default=0) 214 | args = args.parse_args() 215 | main(args) 216 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Shukrullo Nazirjonov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pong Wars 2 |