Added chest interaction
[butaba-adventures.git] / maingame.py
1 import pygame
2 import sys
3 import random
4 import os.path
5 import cPickle
6
7 import level
8 import butaba
9 import utility
10 import gameobjects
11 import constants
12
13 class MainGame:
14
15 # initialize the game
16 def __init__ (self):
17 pygame.init ()
18 self.screen = pygame.display.set_mode ((720, 512))
19 pygame.display.set_caption ("The Adventures of Butaba")
20
21 # initalize background graphics
22 self.img_tileset = pygame.image.load (os.path.join ("background", "tileset.png")).convert ()
23
24 self.img_menu = pygame.image.load (os.path.join ("background", "menu_screen.png")).convert ()
25
26 self.img_inventory = pygame.image.load (os.path.join ("background", "inventory.png")).convert ()
27
28 self.img_chestbg = pygame.image.load (os.path.join ("background", "chestcontent.png")).convert ()
29
30
31 # initialize object graphics
32 self.img_redpotion = pygame.image.load (os.path.join ("objects", "red-potion.png")).convert ()
33 self.img_redpotion.set_colorkey (pygame.Color (0, 255, 0))
34 self.img_goldcoins = pygame.image.load (os.path.join ("objects", "gold-coins.png")).convert ()
35 self.img_goldcoins.set_colorkey (pygame.Color (0, 255, 0))
36 self.img_wand = pygame.image.load (os.path.join ("objects", "wand.png")).convert ()
37 self.img_wand.set_colorkey (pygame.Color (0, 255, 0))
38 self.img_bulb = pygame.image.load (os.path.join ("objects", "bulb.png")).convert ()
39 self.img_bulb.set_colorkey (pygame.Color (0, 255, 0))
40 self.img_lightning = pygame.image.load (os.path.join ("objects", "lightning.png")).convert ()
41 self.img_lightning.set_colorkey (pygame.Color (0, 255, 0))
42 self.img_key = pygame.image.load (os.path.join ("objects", "key.png")).convert ()
43 self.img_key.set_colorkey (pygame.Color (0, 255, 0))
44 self.img_key2 = pygame.image.load (os.path.join ("objects", "key2.png")).convert ()
45 self.img_key2.set_colorkey (pygame.Color (0, 255, 0))
46 self.img_chest = pygame.image.load (os.path.join ("objects", "chest.png")).convert ()
47 self.img_chest.set_colorkey (pygame.Color (0, 255, 0))
48
49 # initialize player graphics
50 self.img_butabafront = pygame.image.load (os.path.join ("sprite", "butaba-front.png")).convert ()
51 self.img_butabafront.set_colorkey (pygame.Color (0, 255, 0))
52 self.img_butababack = pygame.image.load (os.path.join ("sprite", "butaba-back.png")).convert ()
53 self.img_butababack.set_colorkey (pygame.Color (0, 255, 0))
54 self.img_butabaleft = pygame.image.load (os.path.join ("sprite", "butaba-left.png")).convert ()
55 self.img_butabaleft.set_colorkey (pygame.Color (0, 255, 0))
56 self.img_butabaright = pygame.image.load (os.path.join ("sprite", "butaba-right.png")).convert ()
57 self.img_butabaright.set_colorkey (pygame.Color (0, 255, 0))
58
59 # set level data
60 self.setup_levels ()
61 # set current level and position of our character
62 self.currentlevel = self.level1
63
64 # set the status message
65 self.status_message = "Game started"
66
67 self.butaba = butaba.Butaba (5,0, butaba.Butaba.RIGHT)
68
69 # set up the levels and their interactions
70 def setup_levels (self):
71 # set up the objects first
72 chest1 = gameobjects.Chest (2, 6, "chest", self.img_chest, constants.KEY_CHEST1, True)
73 chest2 = gameobjects.Chest (6, 6, "chest", self.img_chest, constants.KEY_CHEST2, True)
74 key1 = gameobjects.Key (5, 3, "a chest key", self.img_key2, constants.KEY_CHEST1)
75 key2 = gameobjects.Key (5, 3, "a chest key", self.img_key, constants.KEY_CHEST2)
76 potion = gameobjects.HealthPotion (5, 2, self.img_redpotion)
77 gold50 = gameobjects.GoldCoins (6, 2, self.img_goldcoins, 50)
78 gold25 = gameobjects.GoldCoins (6, 2, self.img_goldcoins, 25)
79 gold10 = gameobjects.GoldCoins (6, 2, self.img_goldcoins, 10)
80 potion2 = gameobjects.HealthPotion (5, 2, self.img_redpotion)
81 potion3 = gameobjects.HealthPotion (5, 2, self.img_redpotion)
82
83 chest1.objects = [ gold50, gold25, potion2, potion3, key2, gold10 ]
84
85 # create the levels
86 self.level1 = level.Level (cPickle.load (file ("levels/level1.dat")),
87 objects = [ chest1 ] )
88
89 self.level1w = level.Level (cPickle.load (file ("levels/level1w.dat")))
90
91 self.level1e = level.Level (cPickle.load (file ("levels/level1e.dat")),
92 objects = [ key1, potion, chest2 ])
93
94 # set up the interaction between levels
95 self.level1.levelright = self.level1e
96 self.level1.levelleft = self.level1w
97 self.level1e.levelleft = self.level1
98 self.level1w.levelright = self.level1
99
100 def main_loop (self):
101 # main game loop
102 while 1:
103 # clear screen
104 self.screen.fill (pygame.Color (0,0,0))
105 # draw the level
106 self.draw_level_background (self.currentlevel)
107 # draw level objects
108 self.draw_level_objects (self.currentlevel)
109 # draw our character
110 self.draw_butaba ()
111 # display the character's inventory
112 self.draw_inventory ()
113 # draw the status info
114 self.draw_status ()
115 # update the display
116 pygame.display.update ()
117
118 # get keyboard events
119 for event in pygame.event.get ():
120 if event.type == pygame.QUIT:
121 sys.exit (0)
122 # if keyboard event
123 if event.type == pygame.KEYDOWN:
124 if event.key == pygame.K_UP:
125 self.move_butaba_up ()
126 elif event.key == pygame.K_DOWN:
127 self.move_butaba_down ()
128 elif event.key == pygame.K_LEFT:
129 self.move_butaba_left ()
130 elif event.key == pygame.K_RIGHT:
131 self.move_butaba_right ()
132 # drinking health potion in inventory
133 elif event.key == ord ("h") or event.key == ord ("H"):
134 self.inventory_drink_health_potion ()
135 # quit the game
136 elif event.key == ord ("q") or event.key == ord ("Q"):
137 sys.exit (0)
138
139 # drink a health potion if it is in the player's inventory
140 def inventory_drink_health_potion (self):
141 # look for a health potion
142 for item in self.butaba.objects:
143 if isinstance (item, gameobjects.HealthPotion) is True:
144 self.use_object (self.butaba, item)
145 break
146
147 def move_butaba_up (self):
148 # clear any status messages
149 self.status_message = None
150 # first if butaba is not facing up, make him face up
151 if self.butaba.position <> butaba.Butaba.BACK:
152 self.butaba.position = butaba.Butaba.BACK
153 return
154
155 # if butaba is trying to move off the top of the screen
156 if self.butaba.row <= 0:
157 # if there is a level above set current level to that one
158 if self.currentlevel.leveltop is not None:
159 lastrow = len (self.currentlevel.leveltop.background) - 1
160 # interact with objects
161 if self.level_interact (self.currentlevel.leveltop, lastrow, self.butaba.col) is False:
162 return
163
164 # make sure there is no obstacle
165 if self.check_background_obstacle (self.currentlevel.leveltop, lastrow, self.butaba.col) is False:
166 self.currentlevel = self.currentlevel.leveltop
167 self.butaba.row = lastrow
168 # normal upward movement
169 else:
170 # if there is any object in that place interact with it
171 # if any object is a blocking object then avoid movement
172 if self.level_interact (self.currentlevel, self.butaba.row-1, self.butaba.col) is False:
173 return
174
175 if self.check_background_obstacle (self.currentlevel, self.butaba.row-1, self.butaba.col) is False:
176 self.butaba.row -= 1
177
178 def move_butaba_down (self):
179 # clear any status messages
180 self.status_message = None
181 # first if butaba is not facing forward, make him face forward/down
182 if self.butaba.position <> butaba.Butaba.FRONT:
183 self.butaba.position = butaba.Butaba.FRONT
184 return
185
186 # if butaba is trying to move off the bottom of the screen
187 if self.butaba.row >= len (self.currentlevel.background)-1:
188 # if there is a level below set current level to that one
189 if self.currentlevel.levelbottom is not None:
190 # interact with objects if any
191 # if any object is a blocking object then avoid movement
192 if self.level_interact (self.currentlevel.levelbottom, 0, self.butaba.col) is False:
193 return
194 # make sure there is no obstacle at that position
195 if self.check_background_obstacle (self.currentlevel.levelbottom, 0, self.butaba.col) is False:
196 self.currentlevel = self.currentlevel.levelbottom
197 self.butaba.row = 0
198 # normal downward movement
199 else:
200 # interact with objects if any
201 # if any object is a blocking object then avoid movement
202 if self.level_interact (self.currentlevel, self.butaba.row+1, self.butaba.col) is False:
203 return
204 if self.check_background_obstacle (self.currentlevel, self.butaba.row+1, self.butaba.col) is False:
205 self.butaba.row += 1
206
207 # check if a background tile is an obstacle
208 def check_background_obstacle (self, level, row, col):
209 if (level.background[row][col][2] == 1):
210 return True
211 else:
212 return False
213
214 # get and interact with objects and characters if present in a particular row/col
215 def level_interact (self, level, row, col):
216 objs = []
217 # get list of objects at current location
218 for obj in level.objects:
219 if obj.row == row and obj.col == col:
220 objs.append (obj)
221
222 notblock = self.interact_objects (level, objs)
223
224 return notblock
225
226
227 # interaction with objects
228 def interact_objects (self, container, objs):
229 # overall flag for blocking/non-blocking objects
230 notblock = True
231
232 # now perform interaction
233 for obj in objs:
234 # run the object interact function
235 if obj.interact () is False:
236 notblock = False
237 # if object can be picked up ask
238 if obj.can_pickup is True:
239 ans = utility.ask_question (self.screen, "Found %s." % obj.text, ["Pick up", "Use", "Ignore"], self.img_menu)
240 # if the answer is "pick up"
241 if ans == 1:
242 self.pickup_object (container, obj)
243 elif ans == 2:
244 # use the object according to its type
245 self.use_object (container, obj)
246 # if it cannot be picked up, try to use it anyway
247 else:
248 ans = utility.ask_question (self.screen, "Found %s." % obj.text, ["Use", "Ignore"], self.img_menu)
249 if ans == 1:
250 self.use_object (container, obj)
251
252 return notblock
253
254 # picking up an object
255 def pickup_object (self, container, obj):
256 # only if object can be picked up, pick it up or use it
257 if obj.can_pickup is True:
258 # check if the inventory is full
259 if len (self.butaba.objects) >= butaba.Butaba.MAXITEMS:
260 self.status_message = "Cannot pick up item. Inventory full"
261 else:
262 # add item to inventory
263 self.butaba.objects.append (obj)
264 # remove from container
265 container.objects.remove (obj)
266
267 self.status_message = "You picked up %s" % obj.text
268
269 # this method uses the object first by calling the object use () method
270 # and then performing specific actions as necessary
271 def use_object (self, container, obj):
272 # if the object is a health potion
273 if isinstance (obj, gameobjects.HealthPotion) is True:
274 if self.butaba.health < butaba.Butaba.MAXHEALTH:
275 obj.use (self.butaba)
276 container.objects.remove (obj)
277 self.status_message = "You gained health"
278 else:
279 self.status_message = "You already have maximum health!"
280 # if the object is a chest
281 elif isinstance (obj, gameobjects.Chest) is True:
282 # if chest is locked, try to open it
283 if obj.locked is True:
284 # try opening the chest with every item 9the use () function
285 # of the chest determines if item is a key anyway
286 fittedkey = None
287 for invobj in self.butaba.objects:
288 fittedkey = obj.use (invobj)
289 # if a key fits
290 if fittedkey is not None:
291 break
292 # if no key found
293 if fittedkey is None:
294 self.status_message = "No key found to open %s" % obj.text
295 # chest successfully unlocked
296 else:
297 self.status_message = "You unlocked the %s" % obj.text
298 # remove the key from Butaba
299 self.butaba.objects.remove (fittedkey)
300 # add an experience point for unlocking chest subject
301 # to a limit of KNOWLEDGEMAX_CHEST_UNLOCK
302 if self.butaba.experience < constants.KNOWLEDGEMAX_CHEST_UNLOCK:
303 self.butaba.experience += 1
304 self.status_message += " and gained experience!"
305 # display the contents of the chest
306 else:
307 item = utility.get_container_object (self.screen, obj, self.img_chestbg, 30)
308 if item is not None:
309 self.interact_objects (obj, [ item, ])
310
311 # if the object is gold coins
312 elif isinstance (obj, gameobjects.GoldCoins) is True:
313 obj.use (self.butaba)
314 self.status_message = "You picked up %d gold." % obj.value
315 # remove the gold coins after adding it to Butaba's gold
316 container.objects.remove (obj)
317
318 def move_butaba_left (self):
319 # clear any status messages
320 self.status_message = None
321
322 # first if Butaba is not facing left, make him face left
323 if self.butaba.position <> butaba.Butaba.LEFT:
324 self.butaba.position = butaba.Butaba.LEFT
325 return
326
327 # if butaba is trying to move off the left edge
328 if self.butaba.col <= 0:
329 # if there is a level to the right set current level to that one
330 if self.currentlevel.levelleft is not None:
331 # get the last column of the previous level
332 lastcol = len (self.currentlevel.levelleft.background[0]) - 1
333 # interact with objects if any
334 # if any object is a blocking object then avoid movement
335 if self.level_interact (self.currentlevel.levelleft, self.butaba.row, lastcol) is False:
336 return
337 # make sure there is no obstacle at that position of movement
338 if self.check_background_obstacle (self.currentlevel.levelleft, self.butaba.row, lastcol) is False:
339 self.currentlevel = self.currentlevel.levelleft
340 self.butaba.col = lastcol
341 # normal left movement
342 else:
343 # interact with objects if any
344 # if any object is a blocking object then avoid movement
345 if self.level_interact (self.currentlevel, self.butaba.row, self.butaba.col-1) is False:
346 return
347 if self.check_background_obstacle (self.currentlevel, self.butaba.row, self.butaba.col-1) is False:
348 self.butaba.col -= 1
349
350 def move_butaba_right (self):
351 # clear any status messages
352 self.status_message = None
353
354 # First if Butaba is not facing right make him face right
355 if self.butaba.position <> butaba.Butaba.RIGHT:
356 self.butaba.position = butaba.Butaba.RIGHT
357 return
358
359 # if butaba is trying to move off the right edge
360 if self.butaba.col >= len (self.currentlevel.background[0])-1:
361 # if there is a level to the right swap current level with that one
362 if self.currentlevel.levelright is not None:
363 # interact with objects if any
364 # if any object is a blocking object then avoid movement
365 if self.level_interact (self.currentlevel.levelright, self.butaba.row, 0) is False:
366 return
367
368 # make sure there is no obstacle at that position of movement
369 # get the last column of the previous level
370 if self.check_background_obstacle (self.currentlevel.levelright, self.butaba.row, 0) is False:
371 self.currentlevel = self.currentlevel.levelright
372 self.butaba.col = 0
373 # normal right movement
374 else:
375 # interact with objects if any
376 # if any object is a blocking object then avoid moving
377 if self.level_interact (self.currentlevel, self.butaba.row, self.butaba.col + 1) is False:
378 return
379 if self.check_background_obstacle (self.currentlevel, self.butaba.row, self.butaba.col + 1) is False:
380 self.butaba.col += 1
381
382 def draw_butaba (self):
383 if self.butaba.position == butaba.Butaba.FRONT:
384 self.screen.blit (self.img_butabafront, (self.butaba.col*48, self.butaba.row*48))
385 elif self.butaba.position == butaba.Butaba.BACK:
386 self.screen.blit (self.img_butababack, (self.butaba.col*48, self.butaba.row*48))
387 elif self.butaba.position == butaba.Butaba.LEFT:
388 self.screen.blit (self.img_butabaleft, (self.butaba.col*48, self.butaba.row*48))
389 elif self.butaba.position == butaba.Butaba.RIGHT:
390 self.screen.blit (self.img_butabaright, (self.butaba.col*48, self.butaba.row*48))
391
392
393 # Draw the status infodisplay
394 def draw_status (self):
395 self.screen.blit (self.img_redpotion, (485, 10))
396 utility.put_text (self.screen, 550, 25, 20, (255, 0, 0), "%d" % self.butaba.health)
397
398 self.screen.blit (self.img_lightning, (620, 10))
399 utility.put_text (self.screen, 660, 25, 20, (255,255,255), "%d" % self.butaba.strength)
400
401 self.screen.blit (self.img_wand, (485, 65))
402 utility.put_text (self.screen, 550, 75, 20, (0, 0, 255), "%d" % self.butaba.magic)
403
404 self.screen.blit (self.img_bulb, (620, 65))
405 utility.put_text (self.screen, 660, 75, 20, (0, 255, 0), "%d" % self.butaba.experience)
406
407 self.screen.blit (self.img_goldcoins, (485, 110))
408 utility.put_text (self.screen, 550, 130, 20, (255, 255, 0), "%d" % self.butaba.gold)
409
410 if self.status_message is not None:
411 utility.put_text (self.screen, 10, 485, 10, (255,255, 0), "%s" % self.status_message)
412
413 # display the inventory of the player
414 def draw_inventory (self):
415 # draw the inventory slots
416 r = 1
417 c = 1
418 utility.put_text (self.screen, 490, 170, 16, (255,255 , 0), "Inventory")
419 for i in range (butaba.Butaba.MAXITEMS):
420 self.screen.blit (self.img_inventory, (440+c*54, 150+r*54))
421 if c % 4 == 0:
422 r += 1
423 c = 1
424 else:
425 c += 1
426
427 r = 1
428 c = 1
429 for obj in self.butaba.objects:
430 self.screen.blit (obj.image, (440+c*54+2, 150+r*54+2))
431 if c % 4 == 0:
432 r += 1
433 c = 1
434 else:
435 c += 1
436
437 # Draw the level background tiles on surface
438 def draw_level_background (self, level):
439 i = 0
440 for row in level.background:
441 j = 0
442 for tilerow, tilecol, is_solid in row:
443 tilex = tilecol * 48
444 tiley = tilerow * 48
445 self.screen.blit (self.img_tileset, (j*48, i*48), pygame.Rect (tilex, tiley, 48, 48))
446
447 j += 1
448 i += 1
449
450 # Draw the level objects
451 def draw_level_objects (self, level):
452 for obj in level.objects:
453 if obj.image is not None:
454 self.screen.blit (obj.image, (obj.col*48, obj.row*48))