effb1cda9e4856578b9791e95985acbf99a152a1
[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
59 # container for all game control and configuration items
60 class Config:
61  config = dict()
62  soundtrack = Channel(channel_id=-1)
63  sound_effects = Channel(channel_id=0)
64  def __init__(self, width=0, height=0, title=""):
65   self.config.update({'width':width, 'height':height})
66   self.config.update({'title':title})
67   self.config.update({'sound':1})
68   self.play = False
69   self.resolution = width, height
70   self.debug = DEBUG
71
72 class SeamlessBackground:
73  image_path = None
74  image = None
75  offset_x = 0
76  width = None
77  def __init__(self, filename, offset=0):
78   if os.path.exists(filename):
79    self.image_path = filename
80    self.image = pygame.image.load(self.image_path) #.convert()
81    self.offset = offset
82    self.width = self.image.get_size()[0]
83   if self.image == None:
84    print("Failed to load image", filename)
85
86  def draw(self, screen, x_pos, step):
87   """ x_pos == -1 requests the full screen be painted, not just the damaged left/right margins
88   """
89   if DEBUG: print("SeamlessBackground.draw(screen, %d, %d)" % (x_pos, step))
90   if self.image != None:
91    resolution = screen.get_size()
92    # adjust x_pos to be x coord of damaged rectangle (left or right side of screen)
93    if step > 0:
94     x_pos += resolution[0] - step
95    elif step < 0:
96     x_pos -= step
97    elif step == 0 and x_pos >= 0:
98     # nothing to do if not drawing the initial background
99     return
100
101    bk_x_start = x_pos % self.width if x_pos != -1 else 0
102    bk_rect = pygame.Rect(bk_x_start, 0, resolution[0] if x_pos == -1 else abs(step), resolution[1])
103    patch = pygame.Surface((bk_rect.width, bk_rect.height))
104    patch.blit(self.image, (0, 0), bk_rect)
105    remainder = self.width - bk_x_start
106    if remainder < abs(step):
107     # need more pixels because we're just wrapped around the background image
108     patch.blit(self.image, (4,0), pygame.Rect(0, 0, remainder, resolution[1]))
109     if DEBUG: print("Wrap-around, remainder=%d" % remainder)
110
111    scr_x_dest = resolution[0] - step if step > 0 else 0
112    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))
113    screen.blit(patch, (scr_x_dest, 0))
114   else:
115    print("No image to draw")
116
117 myGame = Config(1500, 1000)
118 flags = pygame.OPENGL & pygame.DOUBLEBUF
119 depth = 0
120
121 # main loop sleep (100 milliseconds)
122 delay = 0.01
123
124 pygame.display.set_caption("TJ's platform scroller")
125
126 # background colour
127 stepping = 8 # pixels scrolled each flip
128 background_colour = (20, 20, 64)
129 screen = pygame.display.set_mode(myGame.resolution, flags, depth)
130 resolution = screen.get_size()
131 screen.fill(background_colour)
132
133 # background image
134 background_image = SeamlessBackground(os.path.join("resources", "binary.jpg"))
135 background_image.draw(screen, -1, 0) # -1 flags initial background drawing
136 pygame.display.flip()
137
138 # scrolling object
139 blip = pygame.Surface((abs(stepping), abs(stepping)), pygame.HWSURFACE)
140 blip_colour = (128, 128, 128)
141 blip.fill(blip_colour)
142
143 # starting position
144 y = resolution[1] / 2
145
146 if myGame.config['sound'] == 1:
147  myGame.soundtrack.play(os.path.join("resources","Music","Rolemusic_-_Savage_Steel_Fun_Club.mp3"), loops=-1)
148
149 auto_scroll = False
150 movement = 0
151 x_pos = 0
152 myGame.play = True
153 while (myGame.play):
154  # update game state
155  if x_pos + movement >= 0:
156   x_pos += movement
157  else:
158   movement = 0
159 # print(movement)
160
161  # change colour randomly
162  d_red = random.randrange(-1,2) *8
163  d_green = random.randrange(-1,2) *8
164  d_blue = random.randrange(-1,2) *8
165  # build a new tuple, ensuring all values are valid
166  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)) )
167  blip.fill(blip_colour)
168
169  # change position up or down randomly by one step
170  dy = random.randrange(-1,2) * abs(stepping)
171  if((y + dy) < resolution[1] and (y + dy) >= 0):
172   # screen.fill(background_colour, pygame.Rect(resolution[0] - abs(stepping), y, abs(stepping), abs(stepping) ))
173   y += dy
174
175  # debug info
176  #if myGame.debug:
177   #print(y, blip_colour)
178
179  # redraw the display
180  screen.scroll(-movement, 0)
181  background_image.draw(screen, x_pos, movement)
182  screen.blit(blip, (resolution[0] - abs(stepping), y))
183  pygame.display.flip()
184  
185  # manage the frame-rate
186  time.sleep(delay)
187
188  # process events
189  for event in pygame.event.get():
190   if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_q):
191    myGame.play = False
192   if event.type == pygame.KEYDOWN:
193    k = event.key
194    if k == pygame.K_p:
195     time.sleep(10)
196  if not auto_scroll:
197    movement = 0
198  keys_pressed = pygame.key.get_pressed()
199  if sum(keys_pressed):
200   if keys_pressed[pygame.K_6]:
201    # volume down
202    myGame.soundtrack.down()
203   elif keys_pressed[pygame.K_7]:
204    # volume up
205    myGame.soundtrack.up()
206   elif keys_pressed[pygame.K_d]:
207    movement = stepping
208   elif keys_pressed[pygame.K_a]:
209    movement = -stepping
210   elif keys_pressed[pygame.K_l]:
211    auto_scroll = True if auto_scroll == False else False
212
213 myGame.soundtrack.fadeout(10000)
214 time.sleep(10)
215 pygame.display.quit()
216 exit(0)