-
Notifications
You must be signed in to change notification settings - Fork 8
/
game.py
1555 lines (1214 loc) · 55.4 KB
/
game.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*-
## A duck game.
#
#
#
# Miloslav 'tastyfish' Číž, 2015, FIT VUT Brno
# For Python 2.7.
import pygame
import sys
import math
import random
import os
# time of the last frame in milliseconds
frame_time = 0.0
# after how many frames the player state will be updated (this is
# only a graphics thing)
#-----------------------------------------------------------------------
def text_to_fixed_width(text, width):
if len(text) > width:
return text[:width]
return text + " " * (width - len(text))
#-----------------------------------------------------------------------
## Prepares image after loading for its use.
#
# @param image image to be prepared (pygame.Surface)
# @param transparent_color color that should be transparent, if not
# given, no color will be transparent (pygame.Color)
# @param transparency_mask image to be used as a transparency map, it
# should be the same size as the image and should be gray scale,
# 0 meaning fully transparent, 255 fully non-transparent
# (pygame.Surface)
# @return prepared image
def prepare_image(image, transparent_color = None, transparency_mask = None):
result = pygame.Surface.convert(image)
if transparency_mask != None:
result = pygame.Surface.convert_alpha(result)
for y in range(image.get_height()):
for x in range(image.get_width()):
color = image.get_at((x,y))
alpha = transparency_mask.get_at((x,y))
color.a = alpha.r
result.set_at((x,y),color)
elif transparent_color != None:
result = pygame.Surface.convert_alpha(result)
for y in range(image.get_height()):
for x in range(image.get_width()):
color = image.get_at((x,y))
if color == transparent_color:
color.a = 0
result.set_at((x,y),color)
return result
#-----------------------------------------------------------------------
class MapGridObject:
OBJECT_TILE = 0
OBJECT_FINISH = 1
OBJECT_TRAMPOLINE = 2
OBJECT_COIN = 3
OBJECT_EGG = 4
OBJECT_ENEMY_FLYING = 5
OBJECT_ENEMY_GROUND = 6
OBJECT_PLAYER = 7
OBJECT_SPIKES = 8
## Checks if the argument is a tile.
#
# @return True if what is a tile, False otherwise
@staticmethod
def is_tile(what):
return what != None and (what.object_type == MapGridObject.OBJECT_TILE or what.object_type == MapGridObject.OBJECT_TRAMPOLINE)
## Makes an instance of MapGridObject based on provided string.
#
# @param object_string string representing the object
# @return MapGridObject instance or None (if the string represented
# no object)
@staticmethod
def get_instance_from_string(object_string):
if object_string == ".":
return None
result = MapGridObject()
if object_string[0] == "X":
result.object_type = MapGridObject.OBJECT_FINISH
elif object_string[0] == "E":
result.object_type = MapGridObject.OBJECT_EGG
elif object_string[0] == "C":
result.object_type = MapGridObject.OBJECT_COIN
elif object_string[0] == "P":
result.object_type = MapGridObject.OBJECT_PLAYER
elif object_string[0] == "T":
result.object_type = MapGridObject.OBJECT_TRAMPOLINE
elif object_string[0] == "S":
result.object_type = MapGridObject.OBJECT_SPIKES
elif object_string[0] == "F":
result.object_type = MapGridObject.OBJECT_ENEMY_FLYING
result.enemy_id = int(object_string[2:])
elif object_string[0] == "G":
result.object_type = MapGridObject.OBJECT_ENEMY_GROUND
result.enemy_id = int(object_string[2:])
else: # tile
result.object_type = MapGridObject.OBJECT_TILE
helper_list = object_string.split(";")
result.tile_id = int(helper_list[0])
result.tile_variant = int(helper_list[1])
return result
def __init_attributes(self):
self.object_type = MapGridObject.OBJECT_TILE
self.tile_id = 0
self.tile_variant = 1
self.enemy_id = 0
def __str__(self):
if self.object_type == MapGridObject.OBJECT_TILE:
return "t"
if self.object_type == MapGridObject.OBJECT_COIN:
return "C"
if self.object_type == MapGridObject.OBJECT_EGG:
return "E"
if self.object_type == MapGridObject.OBJECT_PLAYER:
return "P"
if self.object_type == MapGridObject.OBJECT_ENEMY_FLYING:
return "F"
if self.object_type == MapGridObject.OBJECT_ENEMY_GROUND:
return "G"
if self.object_type == MapGridObject.OBJECT_FINISH:
return "F"
return "?"
def __init__(self):
self.__init_attributes()
return
#-----------------------------------------------------------------------
class Level:
STATE_PLAYING = 0
STATE_WON = 1
STATE_LOST = 2
## Loads the level from given file.
#
# @param filename file to be loaded
def load_from_file(self,filename):
self.filename = filename
with open(filename) as input_file:
content = input_file.readlines()
for i in range(len(content)): # get rid of newlines and spaces
content[i] = ((content[i])[:-1]).rstrip()
line_number = 0
while line_number < len(content):
if content[line_number] == "name:":
line_number += 1
self.name = content[line_number]
elif content[line_number] == "background:":
line_number += 1
self.background_name = content[line_number]
line_number += 1
self.background_color = pygame.Color(content[line_number])
elif content[line_number].rstrip() == "tiles:":
while True:
line_number += 1
if line_number >= len(content) or len(content[line_number]) == 0:
break
helper_list = content[line_number].split()
self.tiles.append((int(helper_list[0]),helper_list[1],int(helper_list[2])))
elif content[line_number] == "outside:":
line_number += 1
self.outside_tile = MapGridObject()
self.outside_tile.object_type = MapGridObject.OBJECT_TILE
self.outside_tile.tile_id = int(content[line_number])
self.outside_tile.tile_variant = 1
elif content[line_number] == "scores:":
line_number += 1
while True:
if line_number >= len(content):
break
split_line = content[line_number].split()
if len(split_line) != 3:
break
self.scores.append((split_line[0],int(split_line[1]),int(split_line[2])))
line_number += 1
line_number -= 1
self._sort_scores()
elif content[line_number] == "map:":
line_number += 1
helper_list = content[line_number].split() # map size
self.width = int(helper_list[0])
self.height = int(helper_list[1])
self.map_array = [[None] * self.height for item in range(self.width)]
pos_y = 0
while True: # load the map grid
line_number += 1
if line_number >= len(content) or len(content[line_number]) == 0:
break
helper_list = content[line_number].split()
for pos_x in range(len(helper_list)):
helper_object = MapGridObject.get_instance_from_string(helper_list[pos_x])
if helper_object == None:
self.map_array[pos_x][pos_y] = helper_object
elif helper_object.object_type == MapGridObject.OBJECT_PLAYER:
self.player = Player(self)
self.player.position_x = pos_x + 0.5
self.player.position_y = pos_y + 0.5
elif helper_object.object_type == MapGridObject.OBJECT_ENEMY_FLYING:
self.enemies.append(Enemy(self,Enemy.ENEMY_FLYING))
self.enemies[-1].position_x = pos_x + 0.5
self.enemies[-1].position_y = pos_y + 0.5
elif helper_object.object_type == MapGridObject.OBJECT_ENEMY_GROUND:
self.enemies.append(Enemy(self,Enemy.ENEMY_GROUND))
self.enemies[-1].position_x = pos_x + 0.5
self.enemies[-1].position_y = pos_y + 0.5
else:
if helper_object.object_type == MapGridObject.OBJECT_EGG:
self.eggs_left += 1
elif helper_object.object_type == MapGridObject.OBJECT_COIN:
self.coins_total += 1
self.map_array[pos_x][pos_y] = helper_object
pos_y += 1
line_number += 1
## Saves the scores into a file that's associated with the level
# (the one that's been passed to load_from_file method).
def save_scores(self):
if len(self.filename) == 0:
return
output_lines = []
input_file = open(self.filename)
for line in input_file:
if line.lstrip().rstrip() == "scores:":
break
else:
output_lines.append(line)
input_file.close()
output_file = open(self.filename,"w")
for line in output_lines:
output_file.write(line)
output_file.write("scores:\n")
for score in self.scores:
output_file.write(score[0] + " " + str(score[1]) + " " + str(score[2]) + "\n")
output_file.close()
## Says to add a new score entry. The entry will be added if it will
# be among the top scores.
#
# @param name player name (string)
# @param time time in milliseconds (int)
# @param score player score
def add_score(self, name, time, score):
if len(self.scores) < 20: # record 20 highest scores
self.scores.append((name,score,time))
else:
minimum_index = 0
i = 0
while len(self.scores):
if self.scores[i][1] < self.scores[minimum_index][1]:
minimum_index = i
i += 1
if self.scores[minimum_index][1] < score:
del self.scores[minimum_index]
self.scores.append((name,score,time))
self._sort_scores()
def _sort_scores(self):
self.scores.sort(key = lambda item: item[1],reverse = True)
## Checks the game state and updates it acoordingly, for example if
# a player is standing on an egg, they will take it.
def update(self):
player_tile_x = int(self.player.position_x)
player_tile_y = int(self.player.position_y)
self.time = pygame.time.get_ticks() - self._time_start
object_at_player_tile = self.get_at(player_tile_x,player_tile_y)
object_under_player_tile = self.get_at(player_tile_x,player_tile_y + 1)
if object_at_player_tile != None:
if object_at_player_tile.object_type == MapGridObject.OBJECT_COIN:
self.sound_player.play_coin()
self.map_array[player_tile_x][player_tile_y] = None
self.coins_collected += 1
elif object_at_player_tile.object_type == MapGridObject.OBJECT_EGG:
self.sound_player.play_click()
self.map_array[player_tile_x][player_tile_y] = None
self.eggs_left -= 1
elif object_at_player_tile.object_type == MapGridObject.OBJECT_FINISH:
if self.eggs_left <= 0:
self.state = Level.STATE_WON
self.add_score(self.game.name,pygame.time.get_ticks() - self._time_start,self.score)
self.save_scores()
self.player.force_computer.velocity_vector[0] = 0
self.sound_player.play_win()
elif object_at_player_tile.object_type == MapGridObject.OBJECT_SPIKES:
self.set_lost()
return
if object_under_player_tile != None and object_under_player_tile.object_type == MapGridObject.OBJECT_TRAMPOLINE and not self.player.is_in_air():
self.player.force_computer.velocity_vector[1] = -10
self.sound_player.play_trampoline()
# compute the score:
self.score = int(20000000.0 / (pygame.time.get_ticks() - self._time_start + 20000)) + self.coins_collected * 200
# check colissions of player with enemies:
for enemy in self.enemies:
if self.player.collides(enemy):
self.set_lost()
## Sets the game state to lost and takes appropriate actions.
def set_lost(self):
if self.state == Level.STATE_LOST:
return
self.player.last_quack_time = -99999 # to allow the player to make quack
self.player.quack()
self.state = Level.STATE_LOST
self.player.solid = False
self.player.force_computer.velocity_vector[0] = -1
self.player.force_computer.velocity_vector[1] = -4
self.player.force_computer.acceleration_vector[0] = 0
self.player.force_computer.ground_friction = 0
def __init_attributes(self):
## this will contain the name of the file associated with the level
self.filename = ""
## game to which the level belongs
self.game = None
## the level name
self.name = ""
## current score
self.score = 0
## holds the level scores, the items of the list are tuples
# (name, score, time in ms)
self.scores = []
## state of the game
self.state = Level.STATE_PLAYING
## total number of coins in the level, this doesn not decrease as
# the player takes them
self.coins_total = 0
## how many coins the player has collected in the level so far
self.coins_collected = 0
## how many eggs are there left in the level
self.eggs_left = 0
## the level background name
self.background_name = ""
## background color (pygame.Color)
self.background_color = None
## list of tile types - dicts in format [id (int), name (str), number of variants (int)]
self.tiles = []
## map width in tiles
self.width = 0
## map height in tiles
self.height = 0
## 2D list of map grid objects representing the map, each item can
# be None (representing nothing) or a MapGridObject
self.map_array = None
## contains a MapGridObject representing a tile with which the area
# outside of the level is filled
self.outside_tile = None
## the player object
self.player = None
## contains enemies
self.enemies = []
## plays the sounds in the game
self.sound_player = None
## gravity force
self.gravity = 4.7
## time from the level start in miliseconds
self.time = 0
## time at which the level was created
self._time_start = pygame.time.get_ticks()
## Gets the MapGridObject at given position in the map with map
# boundary check.
#
# @param x x position
# @param y y position
# @return MapGridObject at given position (can be also None), if the
# position provided is outside the map area, the
# MapGridObject representing the outside tile is returned
def get_at(self, x, y):
if x < 0 or x >= self.width or y < 0 or y >= self.height:
return self.outside_tile
return self.map_array[x][y]
def __init__(self, game):
self.__init_attributes()
self.sound_player = game.sound_player
self.game = game
#-----------------------------------------------------------------------
## Represents an object that has a position and a rectangular shape. The
# object can be moved with collision detections.
class Movable(object):
def __init_attributes(self):
## x position of the center in tiles (float)
self.position_x = 0.0
## y position of the center in tiles (float)
self.position_y = 0.0
## object width in tiles (float)
self.width = 0.4
## object height in tiles (float)
self.height = 0.8
## reference to a level in which the object is placed (for colision
# detection)
self.level = None
## says if collisions are applied when moving
self.solid = True
## Check if the object collides with another object.
#
# @param with_what object to check the collision with (Movable)
# @return True if the objects collide, otherwise False
def collides(self, with_what):
if ((self.position_x < with_what.position_x and
self.position_x < with_what.position_x + with_what.width and
self.position_x + self.width < with_what.position_x and
self.position_x + self.width < with_what.position_x + with_what.width)
or
(self.position_x > with_what.position_x and
self.position_x > with_what.position_x + with_what.width and
self.position_x + self.width > with_what.position_x and
self.position_x + self.width > with_what.position_x + with_what.width)):
return False
if ((self.position_y < with_what.position_y and
self.position_y < with_what.position_y + with_what.height and
self.position_y + self.height < with_what.position_y and
self.position_y + self.height < with_what.position_y + with_what.height)
or
(self.position_y > with_what.position_y and
self.position_y > with_what.position_y + with_what.height and
self.position_y + self.height > with_what.position_y and
self.position_y + self.height > with_what.position_y + with_what.height)):
return False
return True
## Checks if the object is in the air (i.e. there is no tile right
# below it)
#
# @return True if the object is in the air, False otherwise
def is_in_air(self):
distance_to_ground = 99999
lower_border = self.position_y + self.height / 2.0
tile_y = int(lower_border) + 1
if MapGridObject.is_tile(self.level.get_at(int(self.position_x),tile_y)):
distance_to_ground = tile_y - lower_border
return distance_to_ground > 0.1
## Moves the object by given position difference with colission
# detections.
#
# @param dx position difference in x, in tiles (float)
# @param dy position difference in y, in tiles (float)
def move_by(self, dx, dy):
if not self.solid:
self.position_x += dx
self.position_y += dy
return
half_width = self.width / 2.0
half_height = self.height / 2.0
# occupied cells in format (x1,y1,x2,y2)
occupied_cells = (int(self.position_x - half_width),int(self.position_y - half_height),int(self.position_x + half_width),int(self.position_y + half_height))
# distances to nearest obstacles:
distance_x = 0
distance_y = 0
if dx > 0:
minimum = 65536
for i in range(occupied_cells[1],occupied_cells[3] + 1):
value = 65536
for j in range(occupied_cells[2] + 1,occupied_cells[2] + 3): # checks the following two cells
if MapGridObject.is_tile(self.level.get_at(j,i)):
value = j
break
if value < minimum:
minimum = value
distance_x = minimum - (self.position_x + half_width)
elif dx < 0:
maximum = -2048
for i in range(occupied_cells[1],occupied_cells[3] + 1):
value = -2048
for j in range(occupied_cells[0] - 1,occupied_cells[0] - 3,-1):
if MapGridObject.is_tile(self.level.get_at(j,i)):
value = j
break
if value > maximum:
maximum = value
distance_x = (maximum + 1) - (self.position_x - half_width)
if dy > 0:
minimum = 65536
for i in range(occupied_cells[0],occupied_cells[2] + 1):
value = 65536
for j in range(occupied_cells[3] + 1,occupied_cells[3] + 3): # checks the following two cells
if MapGridObject.is_tile(self.level.get_at(i,j)):
value = j
break
if value < minimum:
minimum = value
distance_y = minimum - (self.position_y + half_height)
elif dy < 0:
maximum = -2048
for i in range(occupied_cells[0],occupied_cells[2] + 1):
value = -2048
for j in range(occupied_cells[1] - 1,occupied_cells[1] - 3,-1):
if MapGridObject.is_tile(self.level.get_at(i,j)):
value = j
break
if value > maximum:
maximum = value
distance_y = (maximum + 1) - (self.position_y - half_height)
if abs(distance_x) > abs(dx):
self.position_x += dx
if abs(distance_y) > abs(dy):
self.position_y += dy
def __init__(self, level):
self.__init_attributes()
self.level = level
return
#-----------------------------------------------------------------------
class Player(Movable):
PLAYER_STATE_STANDING = 0
PLAYER_STATE_WALKING = 1
PLAYER_STATE_JUMPING_UP = 2
PLAYER_STATE_JUMPING_DOWN = 3
QUACK_COOLDOWN = 5000 # quack cooldown time in milliseconds
QUACK_DURATION = 2500 # for how long the quack immobilises the enemies
def __init_attributes(self):
## basic player state
self.state = Player.PLAYER_STATE_STANDING
## whether the player is facing right or left
self.facing_right = True
## whether the player is flapping its wings
self.flapping_wings = False
self.last_quack_time = -999999
## force computer of the player
self.force_computer = ForceComputer(self)
def jump(self):
self.force_computer.velocity_vector[1] = -3.7
## Makes the player quack and takes appropriate actions (tells the
# level about it etc).
def quack(self):
if pygame.time.get_ticks() < self.last_quack_time + Player.QUACK_COOLDOWN:
return
self.last_quack_time = pygame.time.get_ticks()
self.level.sound_player.play_quack()
def __init__(self, level):
super(Player,self).__init__(level)
self.__init_attributes()
self.force_computer.acceleration_vector[0] = self.level.gravity # set the gravity
self.force_computer.acceleration_vector[1] = 0
#-----------------------------------------------------------------------
class Enemy(Movable):
ENEMY_FLYING = 0
ENEMY_GROUND = 1
## Makes the enemy move accoording to its AI. The step length is
# computed out of a global
# variable frame_time.
def ai_move(self):
self.force_computer.execute_step()
if pygame.time.get_ticks() < self.level.player.last_quack_time + Player.QUACK_DURATION: # quack is active => monsters don't move
self.force_computer.velocity_vector[0] = 0
if self.enemy_type == Enemy.ENEMY_FLYING:
self.force_computer.velocity_vector[1] = 0
return
if pygame.time.get_ticks() >= self.next_direction_change:
self.next_direction_change = pygame.time.get_ticks() + random.randint(500,2000)
self.__recompute_direction()
## Private method, recomputes the direction of movement to a new
# direction and remembers it as a velocity vector in force computer.
def __recompute_direction(self):
self.force_computer.velocity_vector[0] = 1.0 - random.random() * 2.0
self.force_computer.velocity_vector[1] = 1.0 - random.random() * 2.0
def __init__(self, level, enemy_type = ENEMY_GROUND):
super(Enemy,self).__init__(level)
self.enemy_type = enemy_type
self.force_computer = ForceComputer(self)
if self.enemy_type == Enemy.ENEMY_GROUND: # apply gravity to the ground robot
self.force_computer.acceleration_vector[0] = 0
self.force_computer.acceleration_vector[1] = level.gravity
self.force_computer.ground_friction = 0
## time of next direction change
self.next_direction_change = 0
self.enemy_type = enemy_type
return
#-----------------------------------------------------------------------
## Tile top layer image container.
class TileTopImageContainer:
def __init__(self):
## full image
self.image = None
## subimage - left
self.left = None
## subimage - center
self.center = None
## subimage - right
self.right = None
#-----------------------------------------------------------------------
## Character (player, enemy, ...) image container.
class CharacterImageContainer:
def __init__(self):
self.standing = []
self.moving_right = []
self.moving_left = []
self.jumping = []
self.special = []
#-----------------------------------------------------------------------
class SoundPlayer:
## Initialises the sound player.
#
# @param allow whether the sound is allowed or not (boolean)
def __init__(self, allow):
self.allowed = allow
if not self.allowed:
return
pygame.mixer.init()
if not pygame.mixer.get_init:
return
self.sound_quack = pygame.mixer.Sound("resources/quack.wav")
self.sound_trampoline = pygame.mixer.Sound("resources/trampoline.wav")
self.sound_coin = pygame.mixer.Sound("resources/coin.wav")
self.sound_click = pygame.mixer.Sound("resources/click.wav")
self.sound_flap = pygame.mixer.Sound("resources/flapping.wav")
self.sound_win = pygame.mixer.Sound("resources/win.wav")
pygame.mixer.music.load("resources/blue_dot_session.wav")
pygame.mixer.music.set_volume(0.5)
pygame.mixer.music.play()
def play_quack(self):
if self.allowed:
self.sound_quack.play()
def play_trampoline(self):
if self.allowed:
self.sound_trampoline.play()
def play_coin(self):
if self.allowed:
self.sound_coin.play()
def play_click(self):
if self.allowed:
self.sound_click.play()
def play_flap(self):
if self.allowed:
self.sound_flap.play()
def play_win(self):
if self.allowed:
self.sound_win.play()
#-----------------------------------------------------------------------
class Renderer:
TILE_WIDTH = 200
TILE_HEIGHT = 200
TOP_LAYER_OFFSET = 10
TOP_LAYER_LEFT_WIDTH = 23
QUACK_LENGTH = 350
def __init_attributes(self):
## normal sized font
self.font_normal = pygame.font.Font("resources/Folktale.ttf",28)
## small sized font
self.font_small = pygame.font.Font("resources/larabiefont.ttf",20)
## the text color
self.font_color = (100,50,0)
## reference to a level being rendered
self._level = None
## screen width in pixel
self.screen_width = 640
## screen height in pixel
self.screen_height = 480
## screen width in tiles (rounded up)
self.screen_width_tiles = 1
## screen height in tiles (rounded up)
self.screen_height_tiles = 1
## camera top left corner x offset from the origin in pixels
self._camera_x = 0
## camera top left corner y offset from the origin in pixels
self._camera_y = 0
## contains images of tiles indexed by tile id, each item is a list
# where each item contains an image of one tile variant starting
# from 1, index 0 contains a TileTopImageContainer
self.tile_images = {}
## contains the level background image
self.background_image = None
## contains prerendered image of high score text
self.scores_image = None
arrow_mask = pygame.image.load("resources/arrow_mask.bmp")
self.arrow_image = prepare_image(pygame.image.load("resources/arrow.bmp"),transparency_mask = arrow_mask)
## contains flying enemy images
self.enemy_flying_images = []
enemy_flying_mask = pygame.image.load("resources/robot_flying_1_mask.bmp")
self.enemy_flying_images.append(prepare_image(pygame.image.load("resources/robot_flying_1.bmp"),transparency_mask = enemy_flying_mask))
enemy_flying_mask = pygame.image.load("resources/robot_flying_2_mask.bmp")
self.enemy_flying_images.append(prepare_image(pygame.image.load("resources/robot_flying_2.bmp"),transparency_mask = enemy_flying_mask))
enemy_flying_mask = pygame.image.load("resources/robot_flying_3_mask.bmp")
self.enemy_flying_images.append(prepare_image(pygame.image.load("resources/robot_flying_3.bmp"),transparency_mask = enemy_flying_mask))
## contains ground enemy images
self.enemy_ground_images = []
enemy_ground_mask = pygame.image.load("resources/robot_ground_stand_mask.bmp")
self.enemy_ground_images.append(prepare_image(pygame.image.load("resources/robot_ground_stand.bmp"),transparency_mask = enemy_ground_mask))
enemy_ground_mask = pygame.image.load("resources/robot_ground_right_mask.bmp")
self.enemy_ground_images.append(prepare_image(pygame.image.load("resources/robot_ground_right.bmp"),transparency_mask = enemy_ground_mask))
enemy_ground_mask = pygame.image.load("resources/robot_ground_left_mask.bmp")
self.enemy_ground_images.append(prepare_image(pygame.image.load("resources/robot_ground_left.bmp"),transparency_mask = enemy_ground_mask))
## contains teleport image
teleport_mask = pygame.image.load("resources/teleport_mask.bmp")
self.teleport_inactive_image = prepare_image(pygame.image.load("resources/teleport_1.bmp"),transparency_mask = teleport_mask)
self.teleport_active_image = prepare_image(pygame.image.load("resources/teleport_2.bmp"),transparency_mask = teleport_mask)
self.logo_image = prepare_image(pygame.image.load("resources/logo.bmp"))
## contains coin animation images
self.coin_images = []
score_bar_mask = pygame.image.load("resources/score_bar_mask.bmp")
self.score_bar_image = prepare_image(pygame.image.load("resources/score_bar.bmp"),transparency_mask = score_bar_mask)
for i in range(1,7):
coin_mask = pygame.image.load("resources/coin_" + str(i) + "_mask.bmp")
self.coin_images.append(prepare_image(pygame.image.load("resources/coin_" + str(i) + ".bmp"),transparency_mask = coin_mask))
egg_mask = pygame.image.load("resources/egg_mask.bmp")
## contains egg image
self.egg_image = prepare_image(pygame.image.load("resources/egg.bmp"),transparency_mask = egg_mask)
## how many times the background should be repeated in x direction
self.background_repeat_times = 1
## Says which part of the map array is visible in format
# (x1,y1,x2,y2)
self.visible_tile_area = (0,0,0,0)
spikes_mask = pygame.image.load("resources/spikes_mask.bmp")
## contains the spikes image
self.spikes_image = prepare_image(pygame.image.load("resources/spikes.bmp"),transparency_mask = spikes_mask)
## contains the trampoline image
self.trampoline_image = prepare_image(pygame.image.load("resources/trampoline.bmp"))
## contains images of the player (the duck)
self.player_images = CharacterImageContainer()
self.player_images.standing.append(prepare_image(pygame.image.load("resources/duck_right_stand.bmp"),transparency_mask = pygame.image.load("resources/duck_right_stand_mask.bmp")))
self.player_images.standing.append(pygame.transform.flip(self.player_images.standing[0],True,False))
for i in range(1,7):
self.player_images.moving_right.append(prepare_image(pygame.image.load("resources/duck_right_walk_" + str(i) + ".bmp"),transparency_mask = pygame.image.load("resources/duck_right_walk_" + str(i) + "_mask.bmp")))
self.player_images.moving_left.append(pygame.transform.flip(self.player_images.moving_right[-1],True,False))
self.player_images.jumping.append(prepare_image(pygame.image.load("resources/duck_right_jump_up_1.bmp"),transparency_mask = pygame.image.load("resources/duck_right_jump_up_1_mask.bmp")))
self.player_images.jumping.append(prepare_image(pygame.image.load("resources/duck_right_jump_up_2.bmp"),transparency_mask = pygame.image.load("resources/duck_right_jump_up_2_mask.bmp")))
self.player_images.jumping.append(prepare_image(pygame.image.load("resources/duck_right_jump_down_1.bmp"),transparency_mask = pygame.image.load("resources/duck_right_jump_down_1_mask.bmp")))
self.player_images.jumping.append(prepare_image(pygame.image.load("resources/duck_right_jump_down_2.bmp"),transparency_mask = pygame.image.load("resources/duck_right_jump_down_2_mask.bmp")))
self.player_images.jumping.append(pygame.transform.flip(self.player_images.jumping[0],True,False))
self.player_images.jumping.append(pygame.transform.flip(self.player_images.jumping[1],True,False))
self.player_images.jumping.append(pygame.transform.flip(self.player_images.jumping[2],True,False))
self.player_images.jumping.append(pygame.transform.flip(self.player_images.jumping[3],True,False))
self.player_images.special.append(prepare_image(pygame.image.load("resources/duck_right_quack.bmp"),transparency_mask = pygame.image.load("resources/duck_right_quack_mask.bmp")))
self.player_images.special.append(pygame.transform.flip(self.player_images.special[0],True,False))
## Converts number of milliseconds to a string in format:
# ss:m.
#
# @param value number of milliseconds
# @return string in format described above
def __milliseconds_to_time(self, value):
seconds = int(value / 1000)
tenths = int((value % 1000) / 100)
return str(seconds) + ":" + str(tenths)
## Sets the level to be rendered.
#
# @param level Level object
def set_level(self, level):
self._level = level
# load the level background image:
self.background_image = prepare_image(pygame.image.load("resources/background_" + self._level.background_name + ".bmp"))
self.background_repeat_times = int(math.ceil(self.screen_width / float(self.background_image.get_width())))
# load the tile images:
for tile in level.tiles:
self.tile_images[tile[0]] = []
# tile top:
top_mask = pygame.image.load(("resources/tile_" + tile[1] + "_top_mask.bmp"))
top_image = prepare_image(pygame.image.load(("resources/tile_" + tile[1] + "_top.bmp")),transparency_mask = top_mask)
self.tile_images[tile[0]].append(TileTopImageContainer())
self.tile_images[tile[0]][0].image = top_image
self.tile_images[tile[0]][0].left = top_image.subsurface(pygame.Rect(0,0,23,56)) # left
self.tile_images[tile[0]][0].center = top_image.subsurface(pygame.Rect(24,0,201,56)) # center
self.tile_images[tile[0]][0].right = top_image.subsurface(pygame.Rect(225,0,21,56)) # right
# tile variants:
for variant_number in range(tile[2]):
self.tile_images[tile[0]].append(prepare_image(pygame.image.load("resources/tile_" + tile[1] + "_" + str(variant_number + 1) + ".bmp")))
# make the score image:
self.scores_image = prepare_image(pygame.Surface((250,200)),pygame.Color(0,0,0))
text_image = self.font_normal.render("top scores:",1,self.font_color)
self.scores_image.blit(text_image,(0,0))
for i in range(min(3,len(self._level.scores))):
text_image = self.font_small.render(text_to_fixed_width(self._level.scores[i][0],10) + " " + text_to_fixed_width(str(self._level.scores[i][1]),6) + " " + text_to_fixed_width(self.__milliseconds_to_time(self._level.scores[i][2]),6),1,self.font_color)
self.scores_image.blit(text_image,(0,30 + (i + 1) * 20))
## Private method, checks if the tile at given position in the level
# has a top layer (i.e. there is no other tile above it) and what
# type.
#
# @param x x position of the tile
# @param y y position of the tile
# @return a tree item tuple with boolean values (left, center, right)
def __get_top_layer(self, x, y):
center = not MapGridObject.is_tile(self._level.get_at(x, y - 1))
left = not MapGridObject.is_tile(self._level.get_at(x, y - 1)) and not MapGridObject.is_tile(self._level.get_at(x - 1, y)) and not MapGridObject.is_tile(self._level.get_at(x - 1, y - 1))
right = not MapGridObject.is_tile(self._level.get_at(x, y - 1)) and not MapGridObject.is_tile(self._level.get_at(x + 1, y)) and not MapGridObject.is_tile(self._level.get_at(x + 1, y - 1))
return (left,center,right)
## Private method, computes the screen pixel coordinates out of given
# map square coordinates (float) taking camera position into account.
#
# @param x x map position in tiles (double)
# @param y y map position in tiles (double)
# @return (x,y) tuple of pixel screen coordinates
def __map_position_to_screen_position(self, x, y):
return (x * Renderer.TILE_WIDTH - self._camera_x,y * Renderer.TILE_HEIGHT - self._camera_y)
## Renders given menu.
#
# @param menu menu screen to be rendered (Menu)
# @return image (Surface) with the menu rendered
def render_menu(self, menu):
result = pygame.Surface((self.screen_width,self.screen_height))
result.fill((255,255,255))
result.blit(self.logo_image,(self.screen_width / 2 - self.logo_image.get_width() / 2,self.screen_height / 2 - self.logo_image.get_height() / 2))
i = 0
while i < len(menu.items):
text_image = self.font_normal.render(menu.items[i],1,(0,0,0))
result.blit(text_image,(100,100 + i * 40))
if i == menu.selected_item:
result.blit(self.arrow_image,(50,95 + i * 40))