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