all repos — Legends-RPG @ a5b828ace1209769c68ffed3ad2178da480599a4

A fantasy mini-RPG built with Python and Pygame.

data/menugui.py (view raw)

  1# -*- coding: utf-8 -*-
  2
  3"""
  4This class controls all the GUI for the player
  5menu screen.
  6"""
  7import sys
  8import pygame as pg
  9from . import setup, observer
 10from . import constants as c
 11from . import tools
 12
 13#Python 2/3 compatibility.
 14if sys.version_info[0] == 2:
 15    range = xrange
 16
 17
 18class SmallArrow(pg.sprite.Sprite):
 19    """
 20    Small arrow for menu.
 21    """
 22    def __init__(self, info_box):
 23        super(SmallArrow, self).__init__()
 24        self.image = setup.GFX['smallarrow']
 25        self.rect = self.image.get_rect()
 26        self.state = 'selectmenu'
 27        self.state_dict = self.make_state_dict()
 28        self.slots = info_box.slots
 29        self.pos_list = []
 30
 31    def make_state_dict(self):
 32        """
 33        Make state dictionary.
 34        """
 35        state_dict = {'selectmenu': self.navigate_select_menu,
 36                      'itemsubmenu': self.navigate_item_submenu,
 37                      'magicsubmenu': self.navigate_magic_submenu}
 38
 39        return state_dict
 40
 41    def navigate_select_menu(self, pos_index):
 42        """
 43        Nav the select menu.
 44        """
 45        self.pos_list = self.make_select_menu_pos_list()
 46        self.rect.topleft = self.pos_list[pos_index]
 47
 48    def navigate_item_submenu(self, pos_index):
 49        """Nav the item submenu"""
 50        self.pos_list = self.make_item_menu_pos_list()
 51        self.rect.topleft = self.pos_list[pos_index]
 52
 53    def navigate_magic_submenu(self, pos_index):
 54        """
 55        Nav the magic submenu.
 56        """
 57        self.pos_list = self.make_magic_menu_pos_list()
 58        self.rect.topleft = self.pos_list[pos_index]
 59
 60    def make_magic_menu_pos_list(self):
 61        """
 62        Make the list of possible arrow positions for magic submenu.
 63        """
 64        pos_list = [(310, 119),
 65                    (310, 169)]
 66
 67        return pos_list
 68
 69    def make_select_menu_pos_list(self):
 70        """
 71        Make the list of possible arrow positions.
 72        """
 73        pos_list = []
 74
 75        for i in range(3):
 76            pos = (35, 443 + (i * 45))
 77            pos_list.append(pos)
 78
 79        return pos_list
 80
 81    def make_item_menu_pos_list(self):
 82        """
 83        Make the list of arrow positions in the item submenu.
 84        """
 85        pos_list = [(300, 173),
 86                    (300, 223),
 87                    (300, 323),
 88                    (300, 373),
 89                    (300, 478),
 90                    (300, 528),
 91                    (535, 478),
 92                    (535, 528)]
 93
 94        return pos_list
 95
 96    def update(self, pos_index):
 97        """
 98        Update arrow position.
 99        """
100        state_function = self.state_dict[self.state]
101        state_function(pos_index)
102
103    def draw(self, surface):
104        """
105        Draw to surface"""
106        surface.blit(self.image, self.rect)
107
108
109class QuickStats(pg.sprite.Sprite):
110    def __init__(self, game_data):
111        self.inventory = game_data['player inventory']
112        self.game_data = game_data
113        self.health = game_data['player stats']['health']
114        self.stats = self.game_data['player stats']
115        self.font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 22)
116        self.small_font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 18)
117        self.image, self.rect = self.make_image()
118
119    def make_image(self):
120        """
121        Make the surface for the gold box.
122        """
123        stat_list = ['GOLD', 'health', 'magic'] 
124        magic_health_list  = ['health', 'magic']
125        image = setup.GFX['goldbox']
126        rect = image.get_rect(left=10, top=244)
127
128        surface = pg.Surface(rect.size)
129        surface.set_colorkey(c.BLACK)
130        surface.blit(image, (0, 0))
131
132        for i, stat in enumerate(stat_list):
133            first_letter = stat[0].upper()
134            rest_of_letters = stat[1:]
135            if stat in magic_health_list:
136                current = self.stats[stat]['current']
137                max = self.stats[stat]['maximum']
138                text = "{}{}: {}/{}".format(first_letter, rest_of_letters, current, max)
139            elif stat == 'GOLD':
140                text = "Gold: {}".format(self.inventory[stat]['quantity'])
141            render = self.small_font.render(text, True, c.NEAR_BLACK)
142            x = 26
143            y = 45 + (i*30)
144            text_rect = render.get_rect(x=x,
145                                        centery=y)
146            surface.blit(render, text_rect)
147        
148        return surface, rect
149
150    def update(self):
151        """
152        Update gold.
153        """
154        self.image, self.rect = self.make_image()
155
156    def draw(self, surface):
157        """
158        Draw to surface.
159        """
160        surface.blit(self.image, self.rect)
161
162
163class InfoBox(pg.sprite.Sprite):
164    def __init__(self, inventory, player_stats):
165        super(InfoBox, self).__init__()
166        self.inventory = inventory
167        self.player_stats = player_stats
168        self.attack_power = self.get_attack_power()
169        self.defense_power = self.get_defense_power()
170        self.font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 22)
171        self.big_font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 24)
172        self.title_font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 28)
173        self.title_font.set_underline(True)
174        self.get_tile = tools.get_tile
175        self.sword = self.get_tile(48, 0, setup.GFX['shopsigns'], 16, 16, 2)
176        self.shield = self.get_tile(32, 0, setup.GFX['shopsigns'], 16, 16, 2)
177        self.potion = self.get_tile(16, 0, setup.GFX['shopsigns'], 16, 16, 2)
178        self.possible_potions = ['Healing Potion', 'ELIXIR', 'Ether Potion']
179        self.possible_armor = ['Wooden Shield', 'Chain Mail']
180        self.possible_weapons = ['Long Sword', 'Rapier']
181        self.possible_magic = ['Fire Blast', 'Cure']
182        self.quantity_items = ['Healing Potion', 'ELIXIR', 'Ether Potion']
183        self.slots = {}
184        self.state = 'invisible'
185        self.state_dict = self.make_state_dict()
186        self.print_slots = True
187
188    def get_attack_power(self):
189        """
190        Calculate the current attack power based on equipped weapons.
191        """
192        weapon = self.inventory['equipped weapon']
193        return self.inventory[weapon]['power']
194
195    def get_defense_power(self):
196        """
197        Calculate the current defense power based on equipped weapons.
198        """
199        defense_power = 0
200        for armor in self.inventory['equipped armor']:
201            defense_power += self.inventory[armor]['power']
202
203        return defense_power
204
205    def make_state_dict(self):
206        """Make the dictionary of state methods"""
207        state_dict = {'stats': self.show_player_stats,
208                      'items': self.show_items,
209                      'magic': self.show_magic,
210                      'invisible': self.show_nothing}
211
212        return state_dict
213
214
215    def show_player_stats(self):
216        """Show the player's main stats"""
217        title = 'STATS'
218        stat_list = ['Level', 'experience to next level',
219                     'health', 'magic', 'Attack Power', 
220                     'Defense Power', 'gold']
221        attack_power = 5
222        surface, rect = self.make_blank_info_box(title)
223
224        for i, stat in enumerate(stat_list):
225            if stat == 'health' or stat == 'magic':
226                text = "{}{}: {} / {}".format(stat[0].upper(),
227                                              stat[1:],
228                                              str(self.player_stats[stat]['current']),
229                                              str(self.player_stats[stat]['maximum']))
230            elif stat == 'experience to next level':
231                text = "{}{}: {}".format(stat[0].upper(),
232                                         stat[1:],
233                                         self.player_stats[stat])
234            elif stat == 'Attack Power':
235				text = "{}: {}".format(stat, self.get_attack_power()) 
236            elif stat == 'Defense Power':
237                text = "{}: {}".format(stat, self.get_defense_power())
238            elif stat == 'gold':
239                text = "Gold: {}".format(self.inventory['GOLD']['quantity'])
240            else:
241                text = "{}: {}".format(stat, str(self.player_stats[stat]))
242            text_image = self.font.render(text, True, c.NEAR_BLACK)
243            text_rect = text_image.get_rect(x=50, y=80+(i*50))
244            surface.blit(text_image, text_rect)
245
246        self.image = surface
247        self.rect = rect
248
249
250    def show_items(self):
251        """Show list of items the player has"""
252        title = 'ITEMS'
253        potions = ['POTIONS']
254        weapons = ['WEAPONS']
255        armor = ['ARMOR']
256        for i, item in enumerate(self.inventory):
257            if item in self.possible_weapons:
258                if item == self.inventory['equipped weapon']:
259                    item += " (E)"
260                weapons.append(item)
261            elif item in self.possible_armor:
262                if item in self.inventory['equipped armor']:
263                    item += " (E)"
264                armor.append(item)
265            elif item in self.possible_potions:
266                potions.append(item)
267
268        self.slots = {}
269        self.assign_slots(weapons, 85)
270        self.assign_slots(armor, 235)
271        self.assign_slots(potions, 390)
272
273        surface, rect = self.make_blank_info_box(title)
274
275        self.blit_item_lists(surface)
276
277        self.sword['rect'].topleft = 40, 80
278        self.shield['rect'].topleft = 40, 230
279        self.potion['rect'].topleft = 40, 385
280        surface.blit(self.sword['surface'], self.sword['rect'])
281        surface.blit(self.shield['surface'], self.shield['rect'])
282        surface.blit(self.potion['surface'], self.potion['rect'])
283
284        self.image = surface
285        self.rect = rect
286
287
288    def assign_slots(self, item_list, starty, weapon_or_armor=False):
289        """Assign each item to a slot in the menu"""
290        if len(item_list) > 3:
291            for i, item in enumerate(item_list[:3]):
292                posx = 80
293                posy = starty + (i * 50)
294                self.slots[(posx, posy)] = item
295            for i, item in enumerate(item_list[3:]):
296                posx = 315
297                posy = (starty + 50) + (i * 5)
298                self.slots[(posx, posy)] = item
299        else:
300            for i, item in enumerate(item_list):
301                posx = 80
302                posy = starty + (i * 50)
303                self.slots[(posx, posy)] = item
304
305    def assign_magic_slots(self, magic_list, starty):
306        """
307        Assign each magic spell to a slot in the menu.
308        """
309        for i, spell in enumerate(magic_list):
310            posx = 120
311            posy = starty + (i * 50)
312            self.slots[(posx, posy)] = spell
313
314    def blit_item_lists(self, surface):
315        """Blit item list to info box surface"""
316        for coord in self.slots:
317            item = self.slots[coord]
318
319            if item in self.possible_potions:
320                text = "{}: {}".format(self.slots[coord],
321                                       self.inventory[item]['quantity'])
322            else:
323                text = "{}".format(self.slots[coord])
324            text_image = self.font.render(text, True, c.NEAR_BLACK)
325            text_rect = text_image.get_rect(topleft=coord)
326            surface.blit(text_image, text_rect)
327
328    def show_magic(self):
329        """Show list of magic spells the player knows"""
330        title = 'MAGIC'
331        item_list = []
332        for item in self.inventory:
333            if item in self.possible_magic:
334                item_list.append(item)
335                item_list = sorted(item_list)
336
337        self.slots = {}
338        self.assign_magic_slots(item_list, 80)
339
340        surface, rect = self.make_blank_info_box(title)
341
342        for i, item in enumerate(item_list):
343            text_image = self.font.render(item, True, c.NEAR_BLACK)
344            text_rect = text_image.get_rect(x=100, y=80+(i*50))
345            surface.blit(text_image, text_rect)
346
347        self.image = surface
348        self.rect = rect
349
350    def show_nothing(self):
351        """
352        Show nothing when the menu is opened from a level.
353        """
354        self.image = pg.Surface((1, 1))
355        self.rect = self.image.get_rect()
356        self.image.fill(c.BLACK_BLUE)
357
358    def make_blank_info_box(self, title):
359        """Make an info box with title, otherwise blank"""
360        image = setup.GFX['playerstatsbox']
361        rect = image.get_rect(left=285, top=35)
362        centerx = rect.width / 2
363
364        surface = pg.Surface(rect.size)
365        surface.set_colorkey(c.BLACK)
366        surface.blit(image, (0,0))
367
368        title_image = self.title_font.render(title, True, c.NEAR_BLACK)
369        title_rect = title_image.get_rect(centerx=centerx, y=30)
370        surface.blit(title_image, title_rect)
371
372        return surface, rect
373
374
375    def update(self):
376        state_function = self.state_dict[self.state]
377        state_function()
378
379
380    def draw(self, surface):
381        """Draw to surface"""
382        surface.blit(self.image, self.rect)
383
384
385class SelectionBox(pg.sprite.Sprite):
386    def __init__(self):
387        self.font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 22)
388        self.image, self.rect = self.make_image()
389
390    def make_image(self):
391        choices = ['Items', 'Magic', 'Stats']
392        image = setup.GFX['goldbox']
393        rect = image.get_rect(left=10, top=425)
394
395        surface = pg.Surface(rect.size)
396        surface.set_colorkey(c.BLACK)
397        surface.blit(image, (0, 0))
398
399        for i, choice in enumerate(choices):
400            choice_image = self.font.render(choice, True, c.NEAR_BLACK)
401            choice_rect = choice_image.get_rect(x=100, y=(15 + (i * 45)))
402            surface.blit(choice_image, choice_rect)
403
404        return surface, rect
405
406    def draw(self, surface):
407        """Draw to surface"""
408        surface.blit(self.image, self.rect)
409
410
411class MenuGui(object):
412    def __init__(self, level, inventory, stats):
413        self.level = level
414        self.game_data = self.level.game_data
415        self.sfx_observer = observer.SoundEffects()
416        self.observers = [self.sfx_observer]
417        self.inventory = inventory
418        self.stats = stats
419        self.info_box = InfoBox(inventory, stats)
420        self.gold_box = QuickStats(self.game_data)
421        self.selection_box = SelectionBox()
422        self.arrow = SmallArrow(self.info_box)
423        self.arrow_index = 0
424        self.allow_input = False
425
426    def check_for_input(self, keys):
427        """Check for input"""
428        if self.allow_input:
429            if keys[pg.K_DOWN]:
430                if self.arrow_index < len(self.arrow.pos_list) - 1:
431                    self.notify(c.CLICK)
432                    self.arrow_index += 1
433                    self.allow_input = False
434            elif keys[pg.K_UP]:
435                if self.arrow_index > 0:
436                    self.notify(c.CLICK)
437                    self.arrow_index -= 1
438                    self.allow_input = False
439            elif keys[pg.K_RIGHT]:
440                if self.info_box.state == 'items':
441                    if not self.arrow.state == 'itemsubmenu':
442                        self.notify(c.CLICK)
443                        self.arrow_index = 0
444                    self.arrow.state = 'itemsubmenu'
445                elif self.info_box.state == 'magic':
446                    if not self.arrow.state == 'magicsubmenu':
447                        self.notify(c.CLICK)
448                        self.arrow_index = 0
449                    self.arrow.state = 'magicsubmenu'
450                self.allow_input = False
451
452            elif keys[pg.K_LEFT]:
453                self.notify(c.CLICK)
454                self.arrow.state = 'selectmenu'
455                self.arrow_index = 0
456                self.allow_input = False
457            elif keys[pg.K_SPACE]:
458                self.notify(c.CLICK2)
459                if self.arrow.state == 'selectmenu':
460                    if self.arrow_index == 0:
461                        self.info_box.state = 'items'
462                        self.arrow.state = 'itemsubmenu'
463                        self.arrow_index = 0
464                    elif self.arrow_index == 1:
465                        self.info_box.state = 'magic'
466                        self.arrow.state = 'magicsubmenu'
467                        self.arrow_index = 0
468                    elif self.arrow_index == 2:
469                        self.info_box.state = 'stats'
470                elif self.arrow.state == 'itemsubmenu':
471                    self.select_item()
472                elif self.arrow.state == 'magicsubmenu':
473                    self.select_magic()
474
475                self.allow_input = False
476            elif keys[pg.K_RETURN]:
477                self.level.state = 'normal'
478                self.info_box.state = 'invisible'
479                self.allow_input = False
480                self.arrow_index = 0
481                self.arrow.state = 'selectmenu'
482
483        if (not keys[pg.K_DOWN]
484                and not keys[pg.K_UP]
485                and not keys[pg.K_RETURN]
486                and not keys[pg.K_SPACE]
487                and not keys[pg.K_RIGHT]
488                and not keys[pg.K_LEFT]):
489            self.allow_input = True
490
491    def notify(self, event):
492        """
493        Notify all observers of event.
494        """
495        for observer in self.observers:
496            observer.on_notify(event)
497
498    def select_item(self):
499        """
500        Select item from item menu.
501        """
502        health = self.game_data['player stats']['health']
503        posx = self.arrow.rect.x - 220
504        posy = self.arrow.rect.y - 38
505
506        if (posx, posy) in self.info_box.slots:
507            if self.info_box.slots[(posx, posy)][:7] == 'Healing':
508                potion = 'Healing Potion'
509                value = 30
510                self.drink_potion(potion, health, value)
511            elif self.info_box.slots[(posx, posy)][:5] == 'Ether':
512                potion = 'Ether Potion'
513                stat = self.game_data['player stats']['magic']
514                value = 30
515                self.drink_potion(potion, stat, value)
516            elif self.info_box.slots[(posx, posy)][:10] == 'Long Sword':
517                self.inventory['equipped weapon'] = 'Long Sword'
518            elif self.info_box.slots[(posx, posy)][:6] == 'Rapier':
519                self.inventory['equipped weapon'] = 'Rapier'
520            elif self.info_box.slots[(posx, posy)][:13] == 'Wooden Shield':
521                if 'Wooden Shield' in self.inventory['equipped armor']:
522                    self.inventory['equipped armor'].remove('Wooden Shield')
523                else:
524                    self.inventory['equipped armor'].append('Wooden Shield')
525            elif self.info_box.slots[(posx, posy)][:10] == 'Chain Mail':
526                if 'Chain Mail' in self.inventory['equipped armor']:
527                    self.inventory['equipped armor'].remove('Chain Mail')
528                else:
529                    self.inventory['equipped armor'].append('Chain Mail')
530
531    def select_magic(self):
532        """
533        Select spell from magic menu.
534        """
535        health = self.game_data['player stats']['health']
536        magic = self.game_data['player stats']['magic']
537        posx = self.arrow.rect.x - 190
538        posy = self.arrow.rect.y - 39
539
540        if (posx, posy) in self.info_box.slots:
541            if self.info_box.slots[(posx, posy)][:4] == 'Cure':
542               self.use_cure_spell()
543
544    def use_cure_spell(self):
545        """
546        Use cure spell to heal player.
547        """
548        health = self.game_data['player stats']['health']
549        magic = self.game_data['player stats']['magic']
550        inventory = self.game_data['player inventory']
551
552        if health['current'] != health['maximum']:
553            if magic['current'] >= inventory['Cure']['magic points']:
554                self.notify(c.POWERUP)
555                magic['current'] -= inventory['Cure']['magic points']
556                health['current'] += inventory['Cure']['power']
557                if health['current'] > health['maximum']:
558                    health['current'] = health['maximum']
559
560    def drink_potion(self, potion, stat, value):
561        """
562        Drink potion and change player stats.
563        """
564        if stat['current'] != stat['maximum']:
565            self.notify(c.POWERUP)
566            self.inventory[potion]['quantity'] -= 1
567            stat['current'] += value
568            if stat['current'] > stat['maximum']:
569                stat['current'] = stat['maximum']
570            if not self.inventory[potion]['quantity']:
571                del self.inventory[potion]
572
573    def update(self, keys):
574        self.info_box.update()
575        self.gold_box.update()
576        self.arrow.update(self.arrow_index)
577        self.check_for_input(keys)
578
579
580    def draw(self, surface):
581        self.gold_box.draw(surface)
582        self.info_box.draw(surface)
583        self.selection_box.draw(surface)
584        self.arrow.draw(surface)