Created
October 24, 2023 13:46
-
-
Save MathisHammel/ef90a4e10fda96b391702abc4178a0d1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pygame # pip install pygame-ce | |
import random | |
from pygame import gfxdraw | |
FPS = 60 | |
fpsClock = pygame.time.Clock() | |
WINDOW_WIDTH = 600 | |
WINDOW_HEIGHT = 800 | |
BOX_OFFSET_TOP = 100 | |
BOX_OFFSET_HORIZ = 50 | |
BOX_OFFSET_BOTTOM = 100 | |
BOX_POSITION_LEFT = BOX_OFFSET_HORIZ | |
BOX_POSITION_RIGHT = WINDOW_WIDTH - BOX_OFFSET_HORIZ | |
CURSOR_Y = 50 | |
CURSOR_SPEED = 200 | |
BOX_POSITION_BOTTOM = WINDOW_HEIGHT - BOX_OFFSET_BOTTOM | |
BOX_THICKNESS = 20 | |
BOX_COLOR = 'black' | |
BACKGROUND_COLOR = '#16a085' | |
TARGET_LINE_COLOR = 'white' | |
GRAVITY = 2000 | |
FRUIT_COLLISION_STIFFNESS = 1000 | |
FRICTION_FACTOR = 8.0 | |
MAX_FRUIT_SPEED = 200 | |
MAX_FRUIT_LEVEL = 11 | |
MIN_RANDOM_LEVEL = 1 | |
MAX_RANDOM_LEVEL = 5 | |
RESPAWN_TIME = 500 | |
FRUIT_RADIUS = {i:13 * (2**(1/3))**i for i in range(1, MAX_FRUIT_LEVEL + 1)} | |
FRUIT_COLOR = { | |
#i: tuple(random.randint(50,255) for _ in range(3)) for i in range(1, MAX_FRUIT_LEVEL + 1) | |
1: '#f00608', | |
2: '#f4674b', | |
3: '#8f60e7', | |
4: '#db9901', | |
5: '#ce7126', | |
6: '#c01010', | |
7: '#cbc165', | |
8: '#d1aca5', | |
9: '#f5e105', | |
10: '#91d00f', | |
11: '#087806' | |
} | |
def color_hex2tup(hexcolor): | |
assert len(hexcolor) == 7 and hexcolor.startswith('#') | |
return tuple(int(hexcolor[i:i+2], 16) for i in (1,3,5)) | |
class Fruit(): | |
def __init__(self, level, x, y): | |
# TODO : Add rotation and friction? | |
assert 1 <= level <= MAX_FRUIT_LEVEL | |
self.level = level | |
self.radius = FRUIT_RADIUS[level] | |
self.mass = 0.1 * level | |
self.color = color_hex2tup(FRUIT_COLOR[level]) | |
self.is_merging = False | |
self.position = pygame.math.Vector2(x, y) | |
self.speed = pygame.math.Vector2() | |
self.acceleration = pygame.math.Vector2() | |
def draw(self, surf): | |
gfxdraw.aacircle(surf, | |
int(self.position.x), | |
int(self.position.y), | |
int(self.radius), | |
self.color) | |
gfxdraw.filled_circle(surf, | |
int(self.position.x), | |
int(self.position.y), | |
int(self.radius), | |
self.color) | |
# pygame.draw.circle(surf, | |
# self.color, | |
# self.position, | |
# self.radius) | |
def update(self, dt): | |
#print(self.acceleration, self.speed, self.position) | |
self.acceleration += (-self.speed * FRICTION_FACTOR) | |
self.speed += self.acceleration * dt | |
if self.speed.magnitude() > 0: | |
self.speed = self.speed.clamp_magnitude(MAX_FRUIT_SPEED) | |
self.position += self.speed * dt | |
class GameState(): | |
def __init__(self): | |
self.fruits = [] | |
self.score = 0 | |
def update(self, dt): | |
# Wall collision + gravity | |
for fruit in self.fruits: | |
fruit.acceleration = pygame.math.Vector2(0, GRAVITY) | |
# Bottom wall | |
#print(BOX_POSITION_BOTTOM - fruit.position.y, fruit.radius) | |
if BOX_POSITION_BOTTOM - fruit.position.y < fruit.radius: | |
fruit.speed.y = -abs(fruit.speed.y) | |
fruit.position.y = BOX_POSITION_BOTTOM - fruit.radius | |
# Left wall | |
if fruit.position.x - BOX_POSITION_LEFT < fruit.radius: | |
fruit.speed.x = abs(fruit.speed.x) | |
fruit.position.x = BOX_POSITION_LEFT + fruit.radius | |
# Right wall | |
if BOX_POSITION_RIGHT - fruit.position.x < fruit.radius: | |
fruit.speed.x = -abs(fruit.speed.x) | |
fruit.position.x = BOX_POSITION_RIGHT - fruit.radius | |
# Inter-fruit collision | |
fruits_created = [] | |
for index1, fruit1 in enumerate(self.fruits): | |
for index2, fruit2 in enumerate(self.fruits[:index1]): | |
f1f2 = fruit2.position - fruit1.position | |
if f1f2.magnitude() < fruit1.radius + fruit2.radius: | |
# Collision found! | |
if (fruit1.level == fruit2.level | |
and not fruit1.is_merging | |
and not fruit2.is_merging): | |
self.score += (fruit1.level * (fruit1.level + 1)) // 2 | |
fruit1.is_merging = True | |
fruit2.is_merging = True | |
if fruit1.level < MAX_FRUIT_LEVEL: | |
new_fruit_pos = (fruit1.position + fruit2.position) / 2 | |
new_fruit = { | |
'level' : fruit1.level + 1, | |
'position' : new_fruit_pos, | |
'speed' : (fruit1.speed + fruit2.speed) / 2 | |
} | |
fruits_created.append(new_fruit) | |
else: | |
force_magnitude = abs(f1f2.magnitude() - (fruit1.radius + fruit2.radius)) * FRUIT_COLLISION_STIFFNESS | |
fruit1.acceleration += (f1f2.normalize() * -force_magnitude) / fruit1.mass | |
fruit2.acceleration += (f1f2.normalize() * force_magnitude) / fruit2.mass | |
next_fruits = [] | |
for fruit in self.fruits: | |
if not fruit.is_merging: | |
fruit.update(dt) | |
next_fruits.append(fruit) | |
self.fruits = next_fruits | |
for new_fruit in fruits_created: | |
f = self.dropFruit(new_fruit['level'], new_fruit['position'].x, new_fruit['position'].y) | |
f.speed = new_fruit['speed'] | |
def drawBackground(self, surf): | |
screen.fill(BACKGROUND_COLOR) | |
# Left wall | |
pygame.draw.rect(surf, BOX_COLOR, (BOX_POSITION_LEFT - BOX_THICKNESS, | |
BOX_OFFSET_TOP, | |
BOX_THICKNESS, | |
WINDOW_HEIGHT - BOX_OFFSET_TOP - BOX_OFFSET_BOTTOM)) | |
# Right wall | |
pygame.draw.rect(surf, BOX_COLOR, (BOX_POSITION_RIGHT, | |
BOX_OFFSET_TOP, | |
BOX_THICKNESS, | |
WINDOW_HEIGHT - BOX_OFFSET_TOP - BOX_OFFSET_BOTTOM)) | |
# Floor (bottom wall) | |
pygame.draw.rect(surf, BOX_COLOR, (BOX_POSITION_LEFT - BOX_THICKNESS, | |
BOX_POSITION_BOTTOM, | |
BOX_POSITION_RIGHT - BOX_POSITION_LEFT + 2 * BOX_THICKNESS, | |
BOX_THICKNESS)) | |
def drawFruits(self, surf): | |
for fruit in self.fruits: | |
fruit.draw(surf) | |
def draw(self, surf): | |
self.drawBackground(surf) | |
self.drawFruits(surf) | |
def dropFruit(self, level, x, y): | |
new_fruit = Fruit(level, x, y) | |
self.fruits.append(new_fruit) | |
return new_fruit | |
# pygame setup | |
pygame.init() | |
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) | |
clock = pygame.time.Clock() | |
running = True | |
dt = 0 | |
game_state = GameState() | |
cursor_x = WINDOW_WIDTH / 2 | |
current_fruit = Fruit(random.randint(MIN_RANDOM_LEVEL, MAX_RANDOM_LEVEL), cursor_x, CURSOR_Y) | |
# Game loop | |
while running: | |
for event in pygame.event.get(): | |
if event.type == pygame.QUIT: | |
running = False | |
if event.type == pygame.KEYDOWN: | |
if event.key == pygame.K_SPACE and current_fruit is not None: | |
game_state.dropFruit(current_fruit.level, current_fruit.position.x + random.random(), current_fruit.position.y) | |
current_fruit = None | |
drop_time = pygame.time.get_ticks() | |
game_state.update(dt) | |
game_state.drawBackground(screen) | |
if current_fruit is not None: | |
endPos = pygame.math.Vector2(current_fruit.position.x, BOX_POSITION_BOTTOM) | |
pygame.draw.line( | |
screen, | |
TARGET_LINE_COLOR, | |
current_fruit.position, | |
endPos, | |
3 | |
) | |
game_state.drawFruits(screen) | |
keys_pressed = pygame.key.get_pressed() | |
if keys_pressed[pygame.K_LEFT]: | |
cursor_x -= CURSOR_SPEED * dt | |
if keys_pressed[pygame.K_RIGHT]: | |
cursor_x += CURSOR_SPEED * dt | |
if current_fruit is None: | |
if pygame.time.get_ticks() - drop_time > RESPAWN_TIME: | |
current_fruit = Fruit(random.randint(MIN_RANDOM_LEVEL, MAX_RANDOM_LEVEL), cursor_x, CURSOR_Y) | |
else: | |
cursor_x = min(cursor_x, BOX_POSITION_RIGHT - current_fruit.radius) | |
cursor_x = max(cursor_x, BOX_POSITION_LEFT + current_fruit.radius) | |
current_fruit.position.x = cursor_x | |
current_fruit.draw(screen) | |
font = pygame.font.SysFont("Arial", 50) | |
text_render = font.render(f'{game_state.score}', True, 'white') | |
screen.blit(text_render, (50, BOX_POSITION_BOTTOM + 33)) | |
pygame.display.flip() | |
dt = clock.tick(60) / 1000 | |
pygame.quit() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment