changeset 389:463802281182

Add basic level support (level choosing needs work)
author Neil Muller <drnlmuller@gmail.com>
date Thu, 29 Oct 2009 20:55:37 +0000
parents c6f0e3e72e86
children 2bcfccb8288e
files gamelib/animal.py gamelib/constants.py gamelib/engine.py gamelib/gameboard.py gamelib/gameover.py gamelib/level.py gamelib/main.py gamelib/mainmenu.py
diffstat 8 files changed, 141 insertions(+), 71 deletions(-) [+]
line wrap: on
line diff
--- a/gamelib/animal.py	Thu Oct 29 20:54:32 2009 +0000
+++ b/gamelib/animal.py	Thu Oct 29 20:55:37 2009 +0000
@@ -243,6 +243,7 @@
     IMAGE_FILE = 'sprites/fox.png'
     DEATH_ANIMATION = animations.FoxDeath
     DEATH_SOUND = 'kill-fox.ogg'
+    CONFIG_NAME = 'fox'
 
     costs = {
             # weighting for movement calculation
@@ -459,12 +460,14 @@
 
     STEALTH = 60
     IMAGE_FILE = 'sprites/ninja_fox.png'
+    CONFIG_NAME = 'ninja fox'
 
 class DemoFox(Fox):
     """Demolition Foxes destroy fences easily"""
 
     DIG_ANIMATION = animations.FenceExplosion
     IMAGE_FILE = 'sprites/sapper_fox.png'
+    CONFIG_NAME = 'sapper fox'
 
     def __init__(self, pos):
         Fox.__init__(self, pos)
@@ -479,6 +482,7 @@
 
 class GreedyFox(Fox):
     """Greedy foxes eat more chickens"""
+    CONFIG_NAME = 'greedy fox'
 
     def __init__(self, pos):
         Fox.__init__(self, pos)
@@ -496,6 +500,7 @@
     """The Rinkhals has eclectic tastes"""
     STEALTH = 80
     IMAGE_FILE = 'sprites/rinkhals.png'
+    CONFIG_NAME = 'rinkhals'
 
     def _catch_chicken(self, chicken, gameboard):
         """The Rinkhals hunts for sport, catch and release style"""
@@ -527,3 +532,14 @@
     distance = watcher.pos.dist(watchee.pos) - 1
     roll = random.randint(1, 100)
     return roll > watchee.STEALTH - vision_bonus + range_penalty*distance
+
+# These don't have to add up to 100, but it's easier to think
+# about them if they do.
+DEFAULT_FOX_WEIGHTINGS = (
+    (Fox, 59),
+    (GreedyFox, 30),
+    (NinjaFox, 5),
+    (DemoFox, 5),
+    (Rinkhals, 1),
+    )
+
--- a/gamelib/constants.py	Thu Oct 29 20:54:32 2009 +0000
+++ b/gamelib/constants.py	Thu Oct 29 20:55:37 2009 +0000
@@ -24,18 +24,27 @@
 BUFFER = 1024  # audio buffer size in no. of samples
 FRAMERATE = 30 # how often to check if playback has finished
 
-# Game constants
+# Default values that can be overridden by the levels
 
-STARTING_CASH = 1000
-SELL_PRICE_CHICKEN = 10
-SELL_PRICE_EGG = 5
-SELL_PRICE_DEAD_FOX = 15
+DEFAULT_STARTING_CASH = 1000
+DEFAULT_SELL_PRICE_CHICKEN = 10
+DEFAULT_SELL_PRICE_EGG = 5
+DEFAULT_SELL_PRICE_DEAD_FOX = 15
+DEFAULT_TURN_LIMIT = 14
+DEFAULT_GOAL_DESC = 'Survive for 2 weeks'
+
+DEFAULT_MAX_FOXES = 50
+
+# Game constants, still to be made configurable
+
 LOGGING_PRICE = 50
 BUY_PRICE_FENCE = 50
 SELL_PRICE_FENCE = 25
 REPAIR_PRICE_FENCE = 25
 SELL_PRICE_BROKEN_FENCE = 5
 
+# Toolbar constants
+
 TOOL_SELL_CHICKEN = 1
 TOOL_SELL_EGG = 2
 TOOL_SELL_BUILDING = 3
@@ -46,13 +55,4 @@
 
 NIGHT_LENGTH = 150
 
-TURN_LIMITS = {
-        'Two weeks' : 14,
-        'Three months' : 90,
-        'Unlimited' : 0,
-        }
 
-DEFAULT_MODE = 'Two weeks'
-
-ABS_MAX_NUM_FOXES = 50 # Limit possible uppoer number of foxes, due to concerns
-                        # about performance, etc.
--- a/gamelib/engine.py	Thu Oct 29 20:54:32 2009 +0000
+++ b/gamelib/engine.py	Thu Oct 29 20:55:37 2009 +0000
@@ -9,15 +9,17 @@
 import constants
 import mainmenu
 import helpscreen
+import level
 from misc import check_exit
 
 class Engine(Game):
-    def __init__(self, main_app):
+    def __init__(self, main_app, level_name):
         self.main_app = main_app
+        self.level = level.Level(level_name)
         self.clock = pygame.time.Clock()
-        self.main_menu = mainmenu.make_main_menu()
+        self.main_menu = mainmenu.make_main_menu(self.level)
         self._open_window = None
-        self.scoreboard = gameover.ScoreTable()
+        self.scoreboard = gameover.ScoreTable(self.level)
         self.gameboard = None
 
     def tick(self):
@@ -33,11 +35,8 @@
 
     def create_game_board(self):
         """Create and open a gameboard window."""
-        self.mode = self.main_menu.get_mode()
-        if not self.mode:
-            self.mode = constants.DEFAULT_MODE
         self.gameboard = gameboard.GameBoard(self.main_app,
-                constants.TURN_LIMITS[self.mode])
+                self.level)
         self.open_window(self.gameboard.get_top_widget())
 
     def set_main_menu(self):
@@ -52,7 +51,7 @@
     def create_game_over(self):
         """Create and open the Game Over window"""
         game_over = gameover.create_game_over(self.gameboard,
-                self.scoreboard[self.mode], self.mode)
+                self.scoreboard[self.level.level_name], self.level)
         self.gameboard = None
         self.open_window(game_over)
 
--- a/gamelib/gameboard.py	Thu Oct 29 20:54:32 2009 +0000
+++ b/gamelib/gameboard.py	Thu Oct 29 20:55:37 2009 +0000
@@ -58,7 +58,7 @@
     return update_counter
 
 class ToolBar(gui.Table):
-    def __init__(self, gameboard, **params):
+    def __init__(self, gameboard, level, **params):
         gui.Table.__init__(self, **params)
         self.group = gui.Group(name='toolbar', value=None)
         self._next_tool_value = 0
@@ -87,9 +87,9 @@
 
         self.add_heading("Sell ...")
         self.add_tool_button("Chicken", constants.TOOL_SELL_CHICKEN,
-                constants.SELL_PRICE_CHICKEN, cursors.cursors['sell'])
+                level.sell_price_chicken, cursors.cursors['sell'])
         self.add_tool_button("Egg", constants.TOOL_SELL_EGG,
-                constants.SELL_PRICE_EGG, cursors.cursors['sell'])
+                level.sell_price_egg, cursors.cursors['sell'])
         self.add_tool_button("Building", constants.TOOL_SELL_BUILDING,
                 None, cursors.cursors['sell'])
         self.add_tool_button("Equipment", constants.TOOL_SELL_EQUIPMENT,
@@ -275,25 +275,15 @@
     WOODLAND = tiles.REVERSE_TILE_MAP['woodland']
     BROKEN_FENCE = tiles.REVERSE_TILE_MAP['broken fence']
 
-    # These don't have to add up to 100, but it's easier to think
-    # about them if they do.
-    FOX_WEIGHTINGS = (
-        (animal.Fox, 59),
-        (animal.GreedyFox, 30),
-        (animal.NinjaFox, 5),
-        (animal.DemoFox, 5),
-        (animal.Rinkhals, 1),
-        )
-
-    def __init__(self, main_app, max_turns):
+    def __init__(self, main_app, level):
         self.disp = main_app
+        self.level = level
         self.tv = tiles.FarmVid()
         self.tv.png_folder_load_tiles('tiles')
-        self.tv.tga_load_level(data.filepath('levels/farm.tga'))
+        self.tv.tga_load_level(level.map)
         width, height = self.tv.size
         # Ensure we don't every try to create more foxes then is sane
-        self.max_foxes = min(height+width-15, constants.ABS_MAX_NUM_FOXES)
-        self.max_turns = max_turns
+        self.max_foxes = min(height+width-15, level.max_foxes)
         self.create_display()
 
         self.selected_tool = None
@@ -306,7 +296,7 @@
         self.eggs = 0
         self.days = 0
         self.killed_foxes = 0
-        self.add_cash(constants.STARTING_CASH)
+        self.add_cash(level.starting_cash)
         self.day, self.night = True, False
 
         self.fix_buildings()
@@ -324,7 +314,7 @@
         width, height = self.disp.rect.w, self.disp.rect.h
         tbl = gui.Table()
         tbl.tr()
-        self.toolbar = ToolBar(self, width=self.TOOLBAR_WIDTH)
+        self.toolbar = ToolBar(self, self.level, width=self.TOOLBAR_WIDTH)
         tbl.td(self.toolbar, valign=-1)
         self.tvw = VidWidget(self, self.tv, width=width-self.TOOLBAR_WIDTH, height=height)
         tbl.td(self.tvw)
@@ -443,7 +433,7 @@
             for item in list(chicken.equipment):
                 self.add_cash(item.sell_price())
                 chicken.unequip(item)
-            self.add_cash(constants.SELL_PRICE_CHICKEN)
+            self.add_cash(self.level.sell_price_chicken)
             sound.play_sound("sell-chicken.ogg")
             if update_button:
                 update_button(chicken, empty=True)
@@ -460,7 +450,7 @@
 
     def sell_one_egg(self, chicken):
         if chicken.eggs:
-            self.add_cash(constants.SELL_PRICE_EGG)
+            self.add_cash(self.level.sell_price_egg)
             chicken.remove_one_egg()
             self.eggs -= 1
             self.toolbar.update_egg_counter(self.eggs)
@@ -777,10 +767,10 @@
 
     def advance_day(self):
         self.days += 1
-        if self.days == self.max_turns:
+        if self.days == self.level.turn_limit:
             self.toolbar.day_counter.style.color = (255, 0, 0)
         self.toolbar.update_day_counter("%s/%s" % (self.days,
-            self.max_turns if self.max_turns > 0 else "-"))
+            self.level.turn_limit if self.level.turn_limit > 0 else "-"))
 
     def clear_foxes(self):
         for fox in self.foxes.copy():
@@ -864,7 +854,7 @@
     def kill_fox(self, fox):
         self.killed_foxes += 1
         self.toolbar.update_fox_counter(self.killed_foxes)
-        self.add_cash(constants.SELL_PRICE_DEAD_FOX)
+        self.add_cash(self.level.sell_price_dead_fox)
         self.remove_fox(fox)
 
     def remove_fox(self, fox):
@@ -899,7 +889,7 @@
         self.add_chicken(chick)
 
     def _choose_fox(self, (x, y)):
-        fox_cls = misc.WeightedSelection(self.FOX_WEIGHTINGS).choose()
+        fox_cls = misc.WeightedSelection(self.level.fox_weightings).choose()
         return fox_cls((x, y))
 
     def spawn_foxes(self):
@@ -907,7 +897,7 @@
         # Foxes spawn just outside the map
         x, y = 0, 0
         width, height = self.tv.size
-        min_foxes = (self.days+3)/2 # always more than one fox
+        min_foxes = max(self.level.min_foxes, (self.days+3)/2) # always more than one fox
         new_foxes = min(random.randint(min_foxes, min_foxes*2), self.max_foxes)
         while len(self.foxes) < new_foxes:
             side = random.randint(0, 3)
@@ -977,7 +967,7 @@
         """Return true if we're complete"""
         if self.trees_left() == 0:
             return True
-        if self.max_turns > 0 and self.days >= self.max_turns:
+        if self.level.turn_limit > 0 and self.days >= self.level.turn_limit:
             return True
         if len(self.chickens) == 0:
             return True
--- a/gamelib/gameover.py	Thu Oct 29 20:54:32 2009 +0000
+++ b/gamelib/gameover.py	Thu Oct 29 20:55:37 2009 +0000
@@ -32,19 +32,21 @@
     "What will your chickens do now?",
     ]
 
-def ScoreTable():
+def ScoreTable(level):
     """Create and initialise a score table"""
     # We need a true file, so load will work, but, as we never save,
     # the deletion doesn't bother us.
     our_scores = Highs(tempfile.NamedTemporaryFile(), 4)
-    for mode in constants.TURN_LIMITS:
-        for score in range(700,1000,100):
-            our_scores[mode].submit(score, 'No-one', None)
+    #for mode in constants.TURN_LIMITS:
+    #    for score in range(700,1000,100):
+    #        our_scores[mode].submit(score, 'No-one', None)
+    for score in range(700,1000,100):
+        our_scores[level.level_name].submit(score, 'No-one', None)
     return our_scores
 
-def create_game_over(gameboard, scores, mode):
+def create_game_over(gameboard, scores, level):
     """Create a game over screen"""
-    game_over = GameOver(gameboard, scores, mode)
+    game_over = GameOver(gameboard, scores, level)
     return GameOverContainer(game_over, align=0, valign=0)
 
 class GameOverContainer(gui.Container):
@@ -68,7 +70,7 @@
 
 class GameOver(gui.Table):
 
-    def __init__(self, gameboard, scoreboard, mode, **params):
+    def __init__(self, gameboard, scoreboard, level, **params):
         gui.Table.__init__(self, **params)
 
         def return_pressed():
@@ -78,8 +80,8 @@
             pygame.event.post(engine.QUIT)
 
         score = gameboard.cash + \
-                constants.SELL_PRICE_CHICKEN * len(gameboard.chickens) + \
-                constants.SELL_PRICE_EGG * gameboard.eggs
+                level.sell_price_chicken * len(gameboard.chickens) + \
+                level.sell_price_egg * gameboard.eggs
 
         self.tr()
         made_list = scoreboard.check(score) is not None
@@ -100,7 +102,7 @@
         # show the scoreboard
 
         self.tr()
-        self.td(gui.Label('Game Mode: %s' % mode, color=constants.FG_COLOR),
+        self.td(gui.Label('Level: %s' % level.level_name, color=constants.FG_COLOR),
                 colspan=3)
 
         for highscore in scoreboard:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gamelib/level.py	Thu Oct 29 20:55:37 2009 +0000
@@ -0,0 +1,51 @@
+# level.py
+
+import constants
+import data
+from animal import DEFAULT_FOX_WEIGHTINGS
+from ConfigParser import RawConfigParser
+
+class Level(object):
+   """Container for level details"""
+
+   def __init__(self, level_name):
+      default_map = '%s.tga' % level_name
+      level_info = data.filepath('levels/%s.conf' % level_name)
+      # Load the level info file
+      # setup defaults
+      defaults = {
+              'map' : default_map,
+              'level name' : level_name,
+              'sell price chicken' : constants.DEFAULT_SELL_PRICE_CHICKEN,
+              'sell price egg' : constants.DEFAULT_SELL_PRICE_EGG,
+              'sell price dead fox' : constants.DEFAULT_SELL_PRICE_DEAD_FOX,
+              'turn limit' : constants.DEFAULT_TURN_LIMIT,
+              'goal' : constants.DEFAULT_GOAL_DESC,
+              'max foxes' : constants.DEFAULT_MAX_FOXES,
+              'min foxes' : 0,
+              'starting cash' : constants.DEFAULT_STARTING_CASH,
+              }
+      # Add default fox weightings
+      for animal, prob in DEFAULT_FOX_WEIGHTINGS:
+          defaults[animal.CONFIG_NAME] = prob
+      config = RawConfigParser(defaults)
+      config.read(level_info)
+      # NB. This assumes the level file is correctly formatted. No provision
+      # is made for missing sections or incorrectly specified values.
+      # i.e. Things may blow up
+      map_file = config.get('Level', 'map')
+      self.map = data.filepath('levels/%s' % map_file)
+      self.level_name = config.get('Level', 'level name')
+      self.goal = config.get('Level', 'goal')
+      self.turn_limit = config.getint('Level', 'turn limit')
+      self.max_foxes = config.getint('Game values', 'max foxes')
+      self.min_foxes = config.getint('Game values', 'min foxes')
+      self.sell_price_chicken = config.getint('Game values', 'sell price chicken')
+      self.sell_price_egg = config.getint('Game values', 'sell price egg')
+      self.sell_price_dead_fox = config.getint('Game values',
+              'sell price dead fox')
+      self.starting_cash = config.getint('Game values', 'starting cash')
+      self.fox_weightings = []
+      for animal, _prob in DEFAULT_FOX_WEIGHTINGS:
+          self.fox_weightings.append((animal, config.getint('Fox probablities',
+                  animal.CONFIG_NAME)))
--- a/gamelib/main.py	Thu Oct 29 20:54:32 2009 +0000
+++ b/gamelib/main.py	Thu Oct 29 20:55:37 2009 +0000
@@ -14,6 +14,7 @@
 from sound import init_sound
 import constants
 import data
+import sys
 
 def create_main_app(screen):
     """Create an app with a background widget."""
@@ -33,7 +34,12 @@
 
     from engine import Engine, MainMenuState
 
-    engine = Engine(main_app)
+    if len(sys.argv) > 1:
+        level_name = sys.argv[1]
+    else:
+        level_name = 'two_weeks'
+
+    engine = Engine(main_app, level_name)
     try:
         engine.run(MainMenuState(engine), screen)
     except KeyboardInterrupt:
--- a/gamelib/mainmenu.py	Thu Oct 29 20:54:32 2009 +0000
+++ b/gamelib/mainmenu.py	Thu Oct 29 20:55:37 2009 +0000
@@ -6,9 +6,9 @@
 import engine
 import imagecache
 
-def make_main_menu():
+def make_main_menu(level):
     """Create a main menu"""
-    main_menu = MainMenu()
+    main_menu = MainMenu(level)
 
     c = MenuContainer(align=0, valign=0)
     c.add(main_menu, 0, 0)
@@ -26,7 +26,7 @@
         return self.widgets[0].mode
 
 class MainMenu(gui.Table):
-    def __init__(self, **params):
+    def __init__(self, level, **params):
         gui.Table.__init__(self, **params)
         self.mode = None
 
@@ -36,10 +36,12 @@
         def quit_pressed():
             pygame.event.post(engine.QUIT)
 
-        def start_game(mode):
-            self.mode = mode
+        def start_game():
             pygame.event.post(engine.START_DAY)
 
+        def choose_level():
+            print 'Needs to be implemented. Specify the level name as a parameter'
+
         def help_pressed():
             pygame.event.post(engine.GO_HELP_SCREEN)
 
@@ -50,12 +52,16 @@
             "align": 0,
             "style": style,
         }
- 
-        for mode in constants.TURN_LIMITS:
-            button = gui.Button(mode)
-            button.connect(gui.CLICK, start_game, mode)
-            self.tr()
-            self.td(button, **td_kwargs)
+
+        change_button = gui.Button('Choose level')
+        change_button.connect(gui.CLICK, choose_level)
+        self.tr()
+        self.td(change_button, **td_kwargs)
+
+        start_button = gui.Button(level.level_name)
+        start_button.connect(gui.CLICK, start_game)
+        self.tr()
+        self.td(start_button, **td_kwargs)
 
         quit_button = gui.Button("Quit")
         quit_button.connect(gui.CLICK, quit_pressed)