# HG changeset patch # User Neil Muller # Date 1316607418 -7200 # Node ID 9d3ed4d05f550a8e0dda5b86f56ea5ab1b1f24d4 # Parent 91e1a95343b2e4a16acb039d3dae0500837081e0 Hack in test level mode to editor diff -r 91e1a95343b2 -r 9d3ed4d05f55 mamba/habitats/editor.py --- a/mamba/habitats/editor.py Tue Sep 20 17:29:38 2011 +0200 +++ b/mamba/habitats/editor.py Wed Sep 21 14:16:58 2011 +0200 @@ -1,14 +1,16 @@ """Habitat for editing levels.""" import pygame.display -from pygame.locals import SWSURFACE, KEYDOWN, K_1, K_2, MOUSEBUTTONDOWN +from pygame.locals import (SWSURFACE, KEYDOWN, K_1, K_2, MOUSEBUTTONDOWN, + K_LEFT, K_RIGHT, K_DOWN, K_UP, K_SPACE) import sys import traceback from StringIO import StringIO import urllib import urllib2 -from mamba.engine import Habitat, NewHabitatEvent +from mamba.engine import (Habitat, NewHabitatEvent, SnakeDiedEvent, + LevelCompletedEvent) from mamba.sprites import find_special_sprites from mamba.widgets.level import EditLevelWidget from mamba.widgets.text import TextWidget, TextButton @@ -23,7 +25,7 @@ from mamba.data import (check_level_exists, get_level_list, load_file, load_image, load_tile_image) from mamba.constants import (SCREEN, EDIT_SCREEN, NAME, ESCAPE_KEYS, - RESERVED_NAMES, WINDOW_ICON, LEVEL_SERVER) + RESERVED_NAMES, WINDOW_ICON, LEVEL_SERVER, UP, DOWN, LEFT, RIGHT) MAX_TOOLS = 6 MODE_HEIGHT = 370 @@ -37,19 +39,20 @@ A new level can be created from one of the existing templates using "New". Level properties, such as the name, filename and background music track can be changed using the "Edit Level Metadata" button. -The level editor has 3 modes: placing tiles, placing things and editing sprites. +The level editor has 4 modes: placing tiles, placing things, editing sprites and testing the level. Tiles are level geography, such as floors, walls and so forth. Things are items that the snake can eat, such as rats. Sprites are special elements with more complex items which require extra configuration information, such as tunnels. -To place tiles and things, select the desired element and click on the map to place the tile or thing. -Deleting anything replaces the space with a floor tile (Right click also deletes). The selection of -tiles can be scrolled using the arrow buttons. +To place tiles and things, select the desired element and click on the map to place the tile or thing. Deleting anything +replaces the space with a floor tile (Right click also deletes). The selection of tiles can be scrolled using the arrow buttons. -Sprites can be placed as well. Sprites require parameters. Each sprite needs a unique id tag, which can -be any string without spaces, and then additional parameters that depend on the type of sprite, usually -the id of another sprite. Sprites can be added, edited and deleted. Look at existing maps to see how -the parameters can be used. +Sprites can be placed as well. Sprites require parameters. Each sprite needs a unique id tag, which can be any string +without spaces, and then additional parameters that depend on the type of sprite, usually the id of another sprite. +Sprites can be added, edited and deleted. Look at existing maps to see how the parameters can be used. + +Test mode allows you to test the level without any clock running. The snake is moved purely using the cursor keys, but +interacts with the level as in the actual game. Space will also advance the snake one step, useful for testing arrows. Levels can be saved, and uploaded to be shared with other people. Shared levels only become available after the levels have been curated by the Mamba team.""") @@ -65,9 +68,19 @@ self.container.add(self.edit_widget) self.container.add_callback(KEYDOWN, self.keydown_event) self.container.add_callback(MOUSEBUTTONDOWN, self.mouse_event) + self.edit_widget.add_callback(SnakeDiedEvent, self.halt_test) + self.edit_widget.add_callback(LevelCompletedEvent, self.halt_test) self.mode = 'Tiles' self.sprite_mode = 'Add' self.sprite_cls = None + # Map for test mode lookups + self.action_map = { + K_UP: UP, + K_DOWN: DOWN, + K_LEFT: LEFT, + K_RIGHT: RIGHT, + K_SPACE: None, + } def on_enter(self): # We need to juggle the display to the correct size @@ -99,6 +112,26 @@ # Activate floor button self.floor_button.forced_click() return True + elif self.mode == 'Test' and ev.key in self.action_map: + self.edit_widget.apply_action(self.action_map[ev.key]) + return True + + def halt_test(self, ev, widget): + if SnakeDiedEvent.matches(ev): + text = 'Snake died: %s' % ev.reason + else: + text = 'Level completed' + if self.edit_widget.snake_alive: + self.edit_widget.kill_snake() + message = MessageBox((300, 200), text, + post_callback=self.test_restart) + self.display_dialog(message) + return True + + def test_restart(self): + self.unpause() + self.edit_widget.restart() + return True def setup_toolbar(self): """Draw the editor toolbar""" @@ -199,8 +232,12 @@ 'Sprites') sprite_button.add_callback('clicked', self.change_toolbar, 'Sprites') - help_button = TextButton((sprite_button.rect.right + button_padding, + test_button = TextButton((sprite_button.rect.right + button_padding, thing_button.rect.bottom + button_padding), + 'Test') + test_button.add_callback('clicked', self.change_toolbar, 'Test') + help_button = TextButton((button_left, + test_button.rect.bottom + button_padding), 'Help') help_button.add_callback('clicked', self.show_help) @@ -210,9 +247,12 @@ thing_button.disable() elif self.mode == "Sprites": sprite_button.disable() + elif self.mode == 'Test': + test_button.disable() self.container.add(tile_button) self.container.add(thing_button) self.container.add(sprite_button) + self.container.add(test_button) self.container.add(help_button) button_height = LOAD_SAVE_HEIGHT @@ -242,6 +282,11 @@ save.add_callback('clicked', self.save) self.container.add(save) + if self.mode == 'Test': + save.disable() + load.disable() + new.disable() + def change_tool(self, ev, widget, new_tool, text): self.edit_widget.set_tool(new_tool) self.current_tool.text = 'Tool: %s' % text @@ -249,7 +294,7 @@ return True def show_help(self, ev, widget): - message = MessageBox((50, 50), HELP_MSG, color="black", + message = MessageBox((20, 20), HELP_MSG, color="black", fontsize=12) self.display_dialog(message) return True @@ -349,16 +394,30 @@ self.level.level_name = '' # Special case for new level self.container.paused = False self.edit_widget = EditLevelWidget(self.level) + self.edit_widget.add_callback(SnakeDiedEvent, self.halt_test) + self.edit_widget.add_callback(LevelCompletedEvent, self.halt_test) self.container.add(self.edit_widget) self.clear_toolbar() self.setup_toolbar() return True def change_toolbar(self, ev, widget, new_mode): + if new_mode == 'Test': + try: + self.level.validate_level() + except InvalidMapError, error: + # Fail to change mode on invalid maps + message = MessageBox((300, 300), "Map isn't valid\n%s" % error) + self.display_dialog(message) + return True self.mode = new_mode - self.edit_widget.tile_mode = (self.mode != 'Sprites') + self.edit_widget.tile_mode = (self.mode in ('Tiles', 'Sprites')) self.clear_toolbar() self.setup_toolbar() + if self.mode == 'Test': + self.edit_widget.start_test() + else: + self.edit_widget.stop_test() return True def clear_toolbar(self): diff -r 91e1a95343b2 -r 9d3ed4d05f55 mamba/widgets/level.py --- a/mamba/widgets/level.py Tue Sep 20 17:29:38 2011 +0200 +++ b/mamba/widgets/level.py Wed Sep 21 14:16:58 2011 +0200 @@ -1,8 +1,11 @@ from pygame.rect import Rect from pygame.locals import MOUSEBUTTONDOWN, MOUSEBUTTONUP, MOUSEMOTION +from pygame.key import set_repeat from mamba.widgets.base import Widget from mamba.constants import TILE_SIZE +from mamba.snake import Snake +from mamba.engine import FlipArrowsEvent class EditLevelWidget(Widget): @@ -18,9 +21,55 @@ self.add_callback(MOUSEBUTTONDOWN, self.place_tile) self.add_callback(MOUSEBUTTONUP, self.end_draw) self.add_callback(MOUSEMOTION, self.draw_tiles) + self.add_callback(FlipArrowsEvent, self.flip_arrows) + self.snake = None + self.snake_alive = False + + def start_test(self): + self.level.restart() + tile_pos, orientation = self.level.get_entry() + self.snake = Snake(tile_pos, orientation) + set_repeat(40, 100) + self.snake_alive = True + + def stop_test(self): + self.snake = None + self.snake_alive = False + self.level.restart() + set_repeat(0, 0) def draw(self, surface): self.level.draw(surface) + if self.snake: + self.snake.draw(surface) + + def kill_snake(self): + """Prevent user interaction while snake is dead""" + self.snake_alive = False + + def restart(self): + self.start_test() + + def interact(self, segment): + if not self.snake or not self.snake_alive: + return + tiles = segment.filter_collisions(self.level.sprites) + for tile in tiles: + tile.interact(self, segment) + + def get_sprite(self, sprite_id): + return self.level.extra_sprites[sprite_id] + + def apply_action(self, orientation): + if not self.snake or not self.snake_alive: + return + # We choose numbers that are close to, but not exactly, move 0.5 tiles + # to avoid a couple of rounding corner cases in the snake code + if orientation is None or orientation == self.snake.orientation: + self.snake.update(9.99 / self.snake.speed, self) + else: + self.snake.send_new_direction(orientation) + self.snake.update(9.99 / self.snake.speed, self) def set_tool(self, new_tool): self.main_tool = new_tool @@ -34,6 +83,9 @@ # FIXME: Need to consider leaving and re-entering the widget self.update_tile(self.convert_pos(event.pos)) + def flip_arrows(self, ev, widget): + self.level.flip_arrows() + def place_tile(self, event, widget): if self.tile_mode: if event.button == 1: # Left button