Skip to content

Commit bbb2da3

Browse files
committed
v1 release
1 parent 4b891f7 commit bbb2da3

21 files changed

Lines changed: 1587 additions & 0 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.swp
2+
*__pycache__

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Starting-Programming
2+
3+
Leif was here
4+
me too!
5+
Let`s make a conflict
6+

core/display_high_score.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import pygame
2+
from enum import Enum
3+
4+
from core import score
5+
6+
7+
white = (255, 255, 255)
8+
green = (0, 255, 0)
9+
blue = (0, 0, 128)
10+
11+
12+
class Alignment(Enum):
13+
CENTER = 0
14+
RIGHT = 1
15+
16+
class TextArea:
17+
'''Draws a bounding box around several text rects.'''
18+
19+
def __init__(self, screen, y):
20+
self.screen = screen
21+
self.screen_size = screen.get_size()
22+
self.rects = []
23+
self.y = y
24+
25+
26+
def add_text(self, text_string, fsize, align=Alignment.CENTER, offset=0):
27+
font = pygame.font.Font('freesansbold.ttf', fsize)
28+
text = font.render(text_string, True, white, blue)
29+
30+
self.y += 0.5*fsize
31+
textRect = text.get_rect()
32+
if align == Alignment.CENTER:
33+
textRect.center = (self.screen_size[0] // 2, self.y)
34+
elif align == Alignment.RIGHT:
35+
textRect.top = self.y
36+
textRect.right = offset
37+
else:
38+
raise ValueError(f'Alignment not implemented: {align}')
39+
40+
self.y += 0.5*fsize + 5
41+
self.rects.append((text, textRect))
42+
return textRect
43+
44+
45+
def add_row(self, fields, fsize, offset=0):
46+
'''Aligns each column on RIGHT.'''
47+
y = self.y
48+
# middle = self.screen_size[0] // 2
49+
right = offset
50+
# Referencing other rects for distancing easily produces bad results, since
51+
# prevous rect size differs depending on its content. For now hardcode
52+
# difference. A proper solution would be to make a grid layout or so.
53+
for f in reversed(fields):
54+
self.y = y
55+
rect = self.add_text(str(f), fsize, align=Alignment.RIGHT, offset=right)
56+
right -= 140
57+
58+
return rect
59+
60+
61+
def bounding_box(self):
62+
universe = self.rects[0][1]
63+
for (text, rec) in self.rects[1:]:
64+
universe = universe.union(rec)
65+
return universe.inflate(50, 50)
66+
67+
68+
def draw(self):
69+
box = self.bounding_box()
70+
pygame.draw.rect(self.screen, blue, box)
71+
pygame.draw.rect(self.screen, white, box, width=2)
72+
for (text, textRect) in self.rects:
73+
self.screen.blit(text, textRect)
74+
pygame.display.update()
75+
76+
77+
def high_score_screen(screen, score, resources):
78+
pygame.mixer.music.load(resources.sad_music)
79+
pygame.mixer.music.play()
80+
81+
area = TextArea(screen, 200)
82+
83+
title = area.add_text('HighScore', 64)
84+
area.add_text('', 16)
85+
86+
offset = title.right + 30
87+
area.add_row(('Name', 'Level', 'Score'), 24, offset=offset)
88+
high_score = score.get_unique_scores()
89+
for row in high_score[:10]:
90+
area.add_row(row, 24, offset=offset)
91+
92+
area.add_text('', 16)
93+
area.add_row(score.last, 32, offset=offset)
94+
area.draw()
95+
96+
y = 100 + area.bounding_box().bottom
97+
bottom = TextArea(screen, y)
98+
bottom.add_text('Press ESC to quit. Press ENTER to try again.', 24)
99+
bottom.draw()

core/game.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import pygame
2+
import time
3+
4+
from core import level
5+
6+
class Game:
7+
'''Runs one game instance.
8+
9+
Sums player score.
10+
'''
11+
def __init__(self, screen, resources):
12+
self.screen = screen
13+
self.resources = resources
14+
self.score = 0
15+
self.highest_level = 0
16+
self.cheat = False
17+
18+
19+
def run(self):
20+
alive = True
21+
num_level = 0
22+
while alive:
23+
num_level += 1
24+
alive = self.run_level(num_level)
25+
self.highest_level = num_level
26+
27+
28+
def play_music(self, num_level):
29+
change_each = 5
30+
if (num_level % 5 != 1):
31+
# continue with previous music without changing
32+
return
33+
track = (num_level // 5) % len(self.resources.music)
34+
# pygame.mixer.music.stop()
35+
pygame.mixer.music.load(self.resources.music[track])
36+
pygame.mixer.music.play()
37+
38+
39+
def run_level(self, num_level):
40+
'''Runs one level of the game.
41+
42+
Returns whether the player cleared the level.
43+
Difficulty increases with `num_level`.
44+
'''
45+
instance = level.Level(num_level, self.screen, self.resources)
46+
self.play_music(num_level)
47+
48+
running = True
49+
while running:
50+
# 50 fps = 1/50 sec = 20 ms
51+
# Sleep for 20 ms
52+
time.sleep(0.020)
53+
if instance.won or instance.lost:
54+
running = False
55+
56+
instance.tick()
57+
instance.draw()
58+
59+
for event in pygame.event.get():
60+
if event.type == pygame.QUIT:
61+
running = False
62+
if event.type == pygame.KEYUP:
63+
if event.key == pygame.K_9:
64+
print('You cheated!')
65+
instance.won = True
66+
self.cheat = True
67+
instance.reset_direction(event.key)
68+
if event.type == pygame.KEYDOWN:
69+
if event.key == pygame.K_ESCAPE:
70+
running = False
71+
instance.add_direction(event.key)
72+
73+
self.score += instance.compute_score()
74+
time.sleep(1)
75+
return instance.won

core/level.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import math
2+
import random
3+
import pygame
4+
5+
from core import update
6+
7+
BACKGROUND=(40, 110, 150)
8+
VICTORY_BG=(0xff, 0xd3, 0x00)
9+
DEFEAT_BG=(106, 40, 126)
10+
11+
START_ALPHAS=7
12+
GOAL_RADIUS=50
13+
ALPHA_RADIUS=update.ALPHA_RADIUS # TODO refactor
14+
MAX_SPEED=10
15+
16+
17+
def direction(key, speed=MAX_SPEED):
18+
if key == pygame.K_DOWN:
19+
return (0,speed)
20+
if key == pygame.K_UP:
21+
return (0,0-speed)
22+
if key == pygame.K_RIGHT:
23+
return (speed,0)
24+
if key == pygame.K_LEFT:
25+
return (0-speed,0)
26+
return (0,0)
27+
28+
29+
def reset_velocity(velocity, key):
30+
vx, vy = velocity
31+
if key in [pygame.K_DOWN, pygame.K_UP]:
32+
vy = 0
33+
if key in [pygame.K_LEFT, pygame.K_RIGHT]:
34+
vx = 0
35+
return (vx, vy)
36+
37+
38+
def get_background(num):
39+
min = [30, 100, 160]
40+
max = [85, 240, 255]
41+
def update_color(i, c):
42+
return random.randrange(min[i], max[i])
43+
return [update_color(i,c) for (i,c) in enumerate(BACKGROUND)]
44+
45+
46+
def generate_position(screen_size):
47+
max_x, max_y = screen_size
48+
x = random.uniform(0, max_x)
49+
y = random.uniform(0, max_y)
50+
return (x,y)
51+
52+
53+
def generate_position_with_distance(player, image_size, distance, screen_size):
54+
pos = generate_position(screen_size)
55+
while update.touches_circle(player, image_size, pos, distance):
56+
pos = generate_position(screen_size)
57+
return pos
58+
59+
60+
def generate_alphas(player, number, image_size, screen_size):
61+
alphas = []
62+
alpha_velocities = []
63+
for i in range(number):
64+
alphas.append(generate_position_with_distance(player, image_size, 200, screen_size))
65+
alpha_velocities.append((0,0))
66+
return alphas, alpha_velocities
67+
68+
69+
class Level:
70+
71+
def __init__(self, num_level, screen, resources):
72+
self.num_level = num_level
73+
self.screen = screen
74+
self.resources = resources
75+
self.survived_iterations = 0
76+
77+
self.screen_size = self.screen.get_size()
78+
self.image_size = self.resources.player_img.get_rect().size
79+
80+
self.won = False
81+
self.lost = False
82+
83+
self.player = (0,0)
84+
self.velocity = (0,0)
85+
86+
self.goal = generate_position_with_distance(self.player, self.image_size, 350, self.screen_size)
87+
num_deltas = 0
88+
if num_level >= 3:
89+
num_deltas = num_level - 2
90+
num_alphas = START_ALPHAS + 3 * (self.num_level - 1) - 2*num_deltas
91+
self.alphas, self.alpha_velocities = generate_alphas(self.player, num_alphas, self.image_size, self.screen_size)
92+
self.background = get_background(self.num_level)
93+
94+
self.delta_alphas, self.delta_velocities = generate_alphas(self.player, num_deltas, self.image_size, self.screen_size)
95+
96+
97+
def draw_img(self, img, center_pos):
98+
w = img.get_width()
99+
h = img.get_height()
100+
(x,y) = center_pos
101+
self.screen.blit(img, (x - w/2, y - h/2))
102+
103+
104+
def draw(self):
105+
'''Draws the current state.'''
106+
if self.won:
107+
background = VICTORY_BG
108+
elif self.lost:
109+
background = DEFEAT_BG
110+
else:
111+
background = self.background
112+
113+
self.screen.fill(background)
114+
self.draw_img(self.resources.goal_img, self.goal)
115+
116+
self.draw_img(self.resources.player_img, self.player)
117+
for alpha in self.alphas:
118+
self.draw_img(self.resources.alpha_img, alpha)
119+
120+
for delta in self.delta_alphas:
121+
self.draw_img(self.resources.delta_img, delta)
122+
123+
pygame.display.flip()
124+
125+
126+
def tick(self):
127+
'''Updates the model one iteration.
128+
129+
If already won or lost, changes nothing.
130+
'''
131+
if self.won or self.lost:
132+
return
133+
134+
self.survived_iterations += 1
135+
self.player = update.add(self.player, self.velocity)
136+
self.player = update.bounds(self.player, self.image_size, self.screen_size)
137+
self.alphas, self.alpha_velocities = update.move_alphas(self.alphas, self.alpha_velocities, self.screen_size)
138+
139+
self.delta_alphas, self.delta_velocities = update.move_deltas(self.delta_alphas, self.delta_velocities, self.screen_size, self.player)
140+
141+
if update.touches_circle(self.player, self.image_size, self.goal, GOAL_RADIUS):
142+
pygame.mixer.Sound.play(self.resources.door_sound)
143+
self.won = True
144+
145+
for alpha in (self.alphas + self.delta_alphas):
146+
if not self.won and update.touches_circle(self.player, self.image_size, alpha, ALPHA_RADIUS):
147+
pygame.mixer.music.stop()
148+
pygame.mixer.Sound.play(self.resources.explosion_sound)
149+
self.lost = True
150+
break
151+
152+
153+
def compute_score(self):
154+
'''If won, returns max score.
155+
156+
Otherwise return asymptotically approaching max score based on survived_iterations.
157+
'''
158+
max_score = self.num_level * 1000
159+
if self.won: return max_score
160+
161+
k = 1e-3
162+
progression = 1 - math.exp(-k*self.survived_iterations)
163+
return math.floor(max_score * progression)
164+
165+
166+
def reset_direction(self, key):
167+
self.velocity = reset_velocity(self.velocity, key)
168+
169+
170+
def add_direction(self, key):
171+
d = direction(key)
172+
self.velocity = update.add(self.velocity, d)

0 commit comments

Comments
 (0)