Add more music tracks from Free Music Archive "CHIPMUSIC" classification"
[base2-runner.git] / pygame-scroller.py
1 #!/usr/bin/python3
2
3 import sys, os, time, random, pygame
4
5 DEBUG = True
6
7 pygame.init()
8
9 class Channel:
10  # use integers to control volume which avoids float rounding error issues
11  MAX = 100
12  MIN = 0
13  step = 5
14  def __init__(self, volume=1.0, channel_id=0): 
15   self.volume = int(volume*100)
16   self.channel_id = 0
17   if channel_id == -1:
18    self.channel_id = channel_id
19    self.channel = None # flag to show this uses pygame.mixer.music 
20   elif channel_id < pygame.mixer.get_num_channels():
21    self.channel_id = channel_id
22    self.channel = pygame.mixer.Channel(self.channel_id)
23
24  def set_volume(self, new_level):
25   if new_level >= Channel.MIN and new_level <= Channel.MAX:
26    self.volume = new_level
27    if self.channel == None:
28     pygame.mixer.music.set_volume(self.volume/100)
29    else:
30     self.channel.set_volume(self.volume/100)
31    if DEBUG: print("Channel.set_volume(", self.volume, ")")
32  def up(self):
33   if self.volume+self.step <= Channel.MAX:
34    self.set_volume(self.volume + self.step)
35  def down(self):
36   if self.volume-self.step >= Channel.MIN:
37    self.set_volume(self.volume - self.step)
38  def get(self):
39   return self.volume
40  def queue(self, sound):
41   if self.channel != None:
42    self.channel.queue(sound)
43  def get_queue(self):
44   return self.channel.get_queue() if self.channel != None else None
45  def fadeout(self, time):
46   if self.channel == None:
47    pygame.mixer.music.fadeout(time)
48   else:
49    self.channel.fadeout(time)
50  def play(self, sound, loops=0, maxtime=0, fade_ms=0):
51   if self.channel != None:
52    # allow 'sound' to be a path string or an object
53    sound = pygame.mixer.Sound(sound)
54    self.channel.play(sound, loops, maxtime, fade_ms)
55   else:
56    pygame.mixer.music.load(sound)
57    pygame.mixer.music.play(loops)
58  def playlist(self, path):
59   self.music_path = path
60   self.playlist = [ f for f in os.listdir(self.music_path) if os.path.isfile(os.path.join(self.music_path, f)) ]
61   random.shuffle(self.playlist)
62   if DEBUG: print ("playlist=", self.playlist)
63   self.playlist_index = -1 # flag for 'start from the beginning of list'
64   self.playlist_next()
65   pygame.mixer.music.set_endevent(pygame.USEREVENT)
66  def playlist_next(self):
67   self.playlist_index = self.playlist_index + 1 if self.playlist_index < len(self.playlist)-1 else 0
68   if DEBUG: print("Channel.playlist_next() = [%d] %s" %(self.playlist_index, self.playlist[self.playlist_index] ))
69   pygame.mixer.music.load(os.path.join(self.music_path, self.playlist[self.playlist_index]))
70   pygame.mixer.music.play()
71
72 # container for all game control and configuration items
73 class Game:
74  config = dict()
75  soundtrack = Channel(channel_id=-1)
76  sound_effects = Channel(channel_id=0)
77  def __init__(self, width=0, height=0, title=""):
78   self.config.update({'width':width, 'height':height})
79   self.config.update({'title':title})
80   self.config.update({'sound':1})
81   self.play = False
82   self.resolution = width, height
83   self.debug = DEBUG
84
85 class SeamlessBackground:
86  image_path = None
87  image = None
88  offset_x = 0
89  width = None
90  def __init__(self, filename, offset=0):
91   if os.path.exists(filename):
92    self.image_path = filename
93    self.image = pygame.image.load(self.image_path) #.convert()
94    self.offset = offset
95    self.width = self.image.get_size()[0]
96   if self.image == None:
97    print("Failed to load image", filename)
98
99  def draw(self, screen, x_pos, step):
100   """ x_pos == -1 requests the full screen be painted, not just the damaged left/right margins
101   """
102   #if DEBUG: print("SeamlessBackground.draw(screen, %d, %d)" % (x_pos, step))
103   if self.image != None:
104    resolution = screen.get_size()
105    # adjust x_pos to be x coord of damaged rectangle (left or right side of screen)
106    if step > 0:
107     x_pos += resolution[0] - step
108    elif step < 0:
109     x_pos -= step
110    elif step == 0 and x_pos >= 0:
111     # nothing to do if not drawing the initial background
112     return
113
114    bk_x_start = x_pos % self.width if x_pos != -1 else 0
115    bk_rect = pygame.Rect(bk_x_start, 0, resolution[0] if x_pos == -1 else abs(step), resolution[1])
116    patch = pygame.Surface((bk_rect.width, bk_rect.height))
117    patch.blit(self.image, (0, 0), bk_rect)
118    remainder = self.width - bk_x_start
119    if remainder < abs(step):
120     # need more pixels because we're just wrapped around the background image
121     patch.blit(self.image, (4,0), pygame.Rect(0, 0, remainder, resolution[1]))
122     #if DEBUG: print("Wrap-around, remainder=%d" % remainder)
123
124    scr_x_dest = resolution[0] - step if step > 0 else 0
125    #if DEBUG: print("x_pos=%d, scr_x_dest=%d, bk_x_start=%d, bk_rect=%s" % (x_pos, scr_x_dest, bk_x_start, bk_rect))
126    screen.blit(patch, (scr_x_dest, 0))
127   else:
128    print("No image to draw")
129
130
131 def main():
132  myGame = Game(900, 600)
133  flags = pygame.OPENGL & pygame.DOUBLEBUF
134  depth = 0
135
136  # main loop sleep (100 milliseconds)
137  delay = 0.01
138
139  pygame.display.set_caption("TJ's platform scroller")
140
141  # background colour
142  stepping = 8 # pixels scrolled each flip
143  background_colour = (20, 20, 64)
144  screen = pygame.display.set_mode(myGame.resolution, flags, depth)
145  resolution = screen.get_size()
146  screen.fill(background_colour)
147
148  # background image
149  background_image = SeamlessBackground(os.path.join("resources", "binary.jpg"))
150  background_image.draw(screen, -1, 0) # -1 flags initial background drawing
151  pygame.display.flip()
152
153  # scrolling object
154  blip = pygame.Surface((abs(stepping), abs(stepping)), pygame.HWSURFACE)
155  blip_colour = (128, 128, 128)
156  blip.fill(blip_colour)
157
158  # starting position
159  y = resolution[1] / 2
160
161  if myGame.config['sound'] == 1:
162   myGame.soundtrack.playlist(os.path.join(os.getcwd(), "resources", "Music"))
163
164  auto_scroll = False
165  movement = 0
166  x_pos = 0
167  myGame.play = True
168  while (myGame.play):
169   # update game state
170   if x_pos + movement >= 0:
171    x_pos += movement
172   else:
173    movement = 0
174  # print(movement)
175
176   # change colour randomly
177   d_red = random.randrange(-1,2) *8
178   d_green = random.randrange(-1,2) *8
179   d_blue = random.randrange(-1,2) *8
180   # build a new tuple, ensuring all values are valid
181   blip_colour = tuple( b+d if b+d < 256 and b+d >=0 else b for b, d in zip(blip_colour, (d_red, d_green, d_blue)) )
182   blip.fill(blip_colour)
183
184   # change position up or down randomly by one step
185   dy = random.randrange(-1,2) * abs(stepping)
186   if((y + dy) < resolution[1] and (y + dy) >= 0):
187    # screen.fill(background_colour, pygame.Rect(resolution[0] - abs(stepping), y, abs(stepping), abs(stepping) ))
188    y += dy
189
190   # debug info
191   #if myGame.debug:
192    #print(y, blip_colour)
193
194   # redraw the display
195   screen.scroll(-movement, 0)
196   background_image.draw(screen, x_pos, movement)
197   screen.blit(blip, (resolution[0] - abs(stepping), y))
198   pygame.display.flip()
199   
200   # manage the frame-rate
201   time.sleep(delay)
202
203   # process events
204   for event in pygame.event.get():
205    if event.type == pygame.QUIT or (event.type == pygame.KEYUP and event.key == pygame.K_q):
206     myGame.play = False
207    if event.type == pygame.KEYUP:
208     k = event.key
209     if k == pygame.K_p:
210      time.sleep(10)
211    if event.type == pygame.USEREVENT:
212     if DEBUG: print("pygame.USEREVENT received; calling playlist_next()")
213     myGame.soundtrack.playlist_next()
214
215   if not auto_scroll:
216     movement = 0
217   keys_pressed = pygame.key.get_pressed()
218   if sum(keys_pressed):
219    if keys_pressed[pygame.K_6]:
220     # volume down
221     myGame.soundtrack.down()
222    elif keys_pressed[pygame.K_7]:
223     # volume up
224     myGame.soundtrack.up()
225    elif keys_pressed[pygame.K_d]:
226     movement = stepping
227    elif keys_pressed[pygame.K_a]:
228     movement = -stepping
229    elif keys_pressed[pygame.K_l]:
230     auto_scroll = True if auto_scroll == False else False
231
232  myGame.soundtrack.fadeout(5000)
233  time.sleep(5)
234  pygame.display.quit()
235  exit(0)
236
237 if __name__ == '__main__':
238  main()