Skip to content

Instantly share code, notes, and snippets.

@MathisHammel
Created October 24, 2023 13:46
Show Gist options
  • Save MathisHammel/ef90a4e10fda96b391702abc4178a0d1 to your computer and use it in GitHub Desktop.
Save MathisHammel/ef90a4e10fda96b391702abc4178a0d1 to your computer and use it in GitHub Desktop.
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