test puzzle
authorFrank DeMarco <if.self.end@gmail.com>
Thu, 21 Feb 2019 04:02:37 +0000 (23:02 -0500)
committerFrank DeMarco <if.self.end@gmail.com>
Thu, 21 Feb 2019 04:02:37 +0000 (23:02 -0500)
.gitignore
PictureProcessing.py
config
resource/progress

index 2dd628f..5f15053 100644 (file)
@@ -5,3 +5,4 @@ MANIFEST
 sdl/build/
 *.o
 main
+resource/editor/levels/new/
index 0e964d7..f71ab0e 100644 (file)
@@ -1,3 +1,4 @@
+from os import makedirs, listdir
 from os.path import basename, join, exists
 from random import random, randrange, choice, randint
 from glob import glob
@@ -113,9 +114,9 @@ class Editor(GameChild):
     LABEL_EDITOR_HEADING = "EDITOR"
     LABEL_EXIT = "ESC: EXIT", "B: EXIT"
     LABEL_EDIT = "ENTER: EDIT", "A: EDIT"
-    LABEL_TILES = "\\24 TILES"
+    LABEL_TILES = "\\24 EDIT TILES"
     LABEL_PLAY = "TEST \\26"
-    LABEL_PAINT_ARROW = "\\25 PAINT"
+    LABEL_PAINT_ARROW = "\\25 PLACE TILES"
     LABEL_MUSIC = "\\27 MUSIC"
     LABEL_SAVE_EXIT = "ESC: SAVE", "B: SAVE"
     LABEL_HIDE_MENU = "ENTER: HIDE", "A: HIDE"
@@ -123,6 +124,11 @@ class Editor(GameChild):
     LABEL_PAINT = "Z: PAINT", "A: PAINT"
     LABEL_NEW = "ENTER: NEW", "A: NEW"
     LABEL_SELECT = "ENTER: SELECT", "A: SELECT"
+    LABEL_DRAG = "Z: DRAG BOX", "A: DRAG BOX"
+    LABEL_SWAP = "ENTER: SWAP", "A: SWAP"
+    LABEL_SOLVED = "SOLVED"
+    LABEL_LEFT = "\\27"
+    LABEL_RIGHT = "\\26"
     HEADING_MARGIN = 60
     BUTTONS_MARGIN = 60
     VIEW_MARGIN = 120
@@ -132,6 +138,7 @@ class Editor(GameChild):
     TILE_BAR_BACKGROUND = 0, 0, 0
     TILE_BAR_SCALE = 3
     PALETTE_WIDTH = 100
+    ZFILL_FILE = 5
 
     def __init__(self, parent):
         GameChild.__init__(self, parent)
@@ -147,6 +154,7 @@ class Editor(GameChild):
         self.at_tile_edit = False
         self.at_palette = False
         self.at_box = False
+        self.at_test = False
         self.level_index = 0
         self.tile_index = 0
         self.palette_index = 0
@@ -186,6 +194,9 @@ class Editor(GameChild):
         self.label_back = self.get_label(
             self.LABEL_BACK, align=self.ALIGN_ONE_THIRD_BOTTOM,
             margin=self.VIEW_MARGIN)
+        self.label_back_box = self.get_label(
+            self.LABEL_BACK, align=self.ALIGN_ONE_THIRD_BOTTOM,
+            margin=self.VIEW_MARGIN)
         self.label_paint = self.get_label(
             self.LABEL_PAINT, align=self.ALIGN_TWO_THIRDS_BOTTOM,
             margin=self.VIEW_MARGIN)
@@ -198,6 +209,18 @@ class Editor(GameChild):
         self.label_select = self.get_label(
             self.LABEL_SELECT, align=self.ALIGN_TWO_THIRDS_BOTTOM,
             margin=self.VIEW_MARGIN)
+        self.label_drag = self.get_label(
+            self.LABEL_DRAG, align=self.ALIGN_TWO_THIRDS_BOTTOM,
+            margin=self.VIEW_MARGIN)
+        self.label_left = self.get_label(self.LABEL_LEFT, True, red=200)
+        self.label_left.location.midleft = dsr.left + self.BUTTONS_MARGIN, dsr.centery
+        self.label_right = self.get_label(self.LABEL_RIGHT, True, red=200)
+        self.label_right.location.midright = dsr.right - self.BUTTONS_MARGIN, dsr.centery
+        self.label_swap = self.get_label(
+            self.LABEL_SWAP, align=self.ALIGN_TWO_THIRDS_BOTTOM,
+            margin=self.VIEW_MARGIN)
+        self.label_solved = self.get_label(self.LABEL_SOLVED)
+        self.label_solved.location.bottomright = dsr.bottomright
         self.arrow_tiles = self.get_label(self.LABEL_TILES)
         self.arrow_tiles.location.midtop = dsr.midtop
         self.arrow_play = self.get_label(self.LABEL_PLAY)
@@ -231,6 +254,7 @@ class Editor(GameChild):
         self.build_default_palette()
         self.box = FlashingCursor(self, *([self.get_level_tile_size()] * 2))
         self.loaded = True
+        self.current_level_is_default = False
 
     def get_level_tile_size(self):
         return self.TILE_SIZE * PictureProcessing.SCALE
@@ -243,11 +267,11 @@ class Editor(GameChild):
                 color.hsla = hue, 100, lightness, 100
                 self.default_palette.append(color)
 
-    def get_label(self, text, rainbow=False, align=None, margin=0):
+    def get_label(self, text, rainbow=False, align=None, margin=0, red=80):
         if isinstance(text, tuple):
             text = self.get_game().select_text(text)
         surface = self.get_game().glyphs.get_surface_from_text(
-            text, background=([0, 80][rainbow], 0, 0))
+            text, background=([0, red][rainbow], 0, 0))
         if rainbow:
             label = RainbowSprite(self, surface)
         else:
@@ -264,12 +288,15 @@ class Editor(GameChild):
 
     def read_levels(self):
         levels = self.levels = []
-        for path in glob(join(self.get_new_levels_path(), "*")):
+        for path in sorted(glob(join(self.get_new_levels_path(), "*"))):
             level = Level(self)
-            if level.load_grid_file(path):
+            if level.load(Level.LOAD_GRID_FILE, path):
                 levels.append(level)
             else:
                 print "Failed to load level %s" % path
+        self.add_default_level()
+
+    def add_default_level(self):
         self.levels.append(self.get_default_level())
 
     def get_default_level(self):
@@ -302,6 +329,8 @@ class Editor(GameChild):
                     self.at_level_select = False
                     self.at_view = True
                     self.menu_hidden = False
+                    if self.level_index == len(self.levels) - 1:
+                        self.current_level_is_default = True
                 elif self.at_view:
                     self.menu_hidden = not self.menu_hidden
                 elif self.at_tile_bar:
@@ -309,6 +338,9 @@ class Editor(GameChild):
                         tile = Surface([self.TILE_SIZE] * 2)
                         tile.fill((0, 0, 0))
                         self.get_current_level().add_tile(tile)
+                        if self.current_level_is_default:
+                            self.current_level_is_default = False
+                            self.add_default_level()
                     self.at_tile_edit = True
                     self.at_tile_bar = False
                 elif self.at_palette:
@@ -320,6 +352,8 @@ class Editor(GameChild):
                 elif self.at_view:
                     self.at_view = False
                     self.at_level_select = True
+                    self.tile_index = 0
+                    self.palette_index = 0
                 elif self.at_tile_bar:
                     self.at_tile_bar = False
                     self.at_view = True
@@ -332,7 +366,15 @@ class Editor(GameChild):
                 elif self.at_box:
                     self.at_box = False
                     self.at_view = True
+                elif self.at_test and self.get_game().interface.closed:
+                    self.at_test = False
+                    self.at_view = True
+                    self.get_current_level().set_swap_status()
             if compare(event, "right"):
+                if self.at_level_select:
+                    self.level_index += 1
+                    if self.level_index >= len(self.levels):
+                        self.level_index = 0
                 if self.at_tile_bar:
                     self.tile_index += 1
                     if self.tile_index == Interface.MAX_TILE_COUNT or \
@@ -352,6 +394,11 @@ class Editor(GameChild):
                         self.at_tile_edit = True
                     else:
                         self.wrap_palette_index()
+                if self.at_view:
+                    self.at_view = False
+                    self.at_test = True
+                    self.get_current_level().set_swap_status(True)
+                    self.get_game().interface.setup()
             elif compare(event, "down"):
                 if self.at_tile_edit or self.at_box:
                     dy = 1
@@ -361,6 +408,10 @@ class Editor(GameChild):
                     self.at_view = False
                     self.at_box = True
             elif compare(event, "left"):
+                if self.at_level_select:
+                    self.level_index -= 1
+                    if self.level_index < 0:
+                        self.level_index = len(self.levels) - 1
                 if self.at_tile_bar:
                     self.tile_index -= 1
                     if self.tile_index < 0:
@@ -424,6 +475,38 @@ class Editor(GameChild):
         for xi in xrange(x, x + w):
             for yi in xrange(y, y + h):
                 self.get_current_level().grid[xi][yi] = self.tile_index
+        self.box.location.topleft = [size * coordinate for coordinate in self.box_corner]
+        self.get_current_level().set_preview()
+        self.save_level()
+
+    def save_level(self):
+        config = self.get_configuration("editor")
+        new_levels_directory = self.get_new_levels_path()
+        if not exists(new_levels_directory):
+            makedirs(new_levels_directory)
+        grid = self.get_current_level().grid
+        level = self.get_current_level()
+        if level.path is None:
+            directories = sorted(listdir(new_levels_directory))
+            index = len(directories) - 1
+            while True:
+                if index < 0:
+                    level.path = join(new_levels_directory, "0" * self.ZFILL_FILE)
+                    break
+                elif directories[index].isdigit():
+                    level.path = join(
+                        new_levels_directory,
+                        str(int(directories[index]) + 1).zfill(self.ZFILL_FILE))
+                    makedirs(level.path)
+                    break
+                index -= 1
+        level_file = open(join(level.path, self.get_configuration("editor", "level-file-name")), "w")
+        for jj in xrange(len(grid[0])):
+            for ii in xrange(len(grid)):
+                level_file.write(str(hex(grid[ii][jj]))[-1])
+            level_file.write("\n")
+        for ii, tile in enumerate(level.original_tiles):
+            save(tile, join(level.path, "%s.png" % str(ii).zfill(2)))
 
     def set_box_anchor(self):
         size = self.get_level_tile_size()
@@ -449,7 +532,8 @@ class Editor(GameChild):
         self.box.location.topleft = \
             min(self.box_corner.x, self.box_anchor.x) * size, \
             min(self.box_corner.y, self.box_anchor.y) * size
-
+        self.box.update()
+        
     def move_box(self, x=0, y=0):
         size = self.get_level_tile_size()
         dsr = self.get_display_surface().get_rect()
@@ -466,10 +550,15 @@ class Editor(GameChild):
         scaled = self.get_current_level().tiles[self.tile_index]
         x, y = self.brush_position
         color = self.get_full_palette()[self.palette_index]
-        original.set_at((x, y), color)
-        scale = PictureProcessing.SCALE
-        scaled.fill(color, (x * scale, y * scale, scale, scale))
-        self.get_current_level().set_preview()
+        if original.get_at((x, y)) != color:
+            if self.current_level_is_default:
+                self.current_level_is_default = False
+                self.add_default_level()
+            original.set_at((x, y), color)
+            scale = PictureProcessing.SCALE
+            scaled.fill(color, (x * scale, y * scale, scale, scale))
+            self.get_current_level().set_preview()
+            self.save_level()
 
     def wrap_palette_index(self):
         count = self.get_palette_cell_count()
@@ -520,6 +609,9 @@ class Editor(GameChild):
                 rect.center = ds.get_rect().center
                 ds.blit(preview, rect)
                 self.level_select_cursor.update()
+                if len(self.levels) > 1:
+                    self.label_left.update()
+                    self.label_right.update()
             else:
                 self.get_current_level().update()
             if self.at_view:
@@ -621,7 +713,26 @@ class Editor(GameChild):
                     self.palette_cursor.location.topleft = left - 1, palette_rect.top + top - 1
                     self.palette_cursor.update()
             if self.at_box:
+                if self.box.location.centery > ds.get_rect().centery:
+                    self.label_drag.location.top = self.VIEW_MARGIN
+                    self.label_back_box.location.top = self.VIEW_MARGIN
+                else:
+                    self.label_drag.location.bottom = ds.get_height() - self.VIEW_MARGIN
+                    self.label_back_box.location.bottom = ds.get_height() - self.VIEW_MARGIN
+                self.box.move(-1, -1)
                 self.box.update()
+                self.box.move(1, 1)
+                self.label_drag.update()
+                self.label_back_box.update()
+            if self.at_test:
+                interface = self.get_game().interface
+                if interface.closed:
+                    self.label_back.update()
+                    self.label_swap.update()
+                else:
+                    if self.get_current_level().is_solved():
+                        self.label_solved.update()
+                interface.update()
 
     def get_full_palette(self):
         return self.default_palette + self.get_current_colors()
@@ -864,23 +975,29 @@ class Level(Animation):
         self.clip_audio = None
         self.full_audio = None
         self.title_plate = None
+        self.path = None
 
     def load(self, method=LOAD_DEFAULT, directory=None, title=None):
+        self.tiles = []
+        self.original_tiles = []
+        self.colored_tiles = {}
         if method == self.LOAD_GRID_FILE:
-            self.load_grid_file()
+            loaded = self.load_grid_file(directory)
         elif method == self.LOAD_MAP_FILE:
-            self.load_map_file(directory, title)
+            loaded = self.load_map_file(directory, title)
         else:
-            self.load_default()
-        self.set_preview()
+            loaded = self.load_default()
+        if loaded:
+            self.set_preview()
+        return loaded
 
     def load_map_file(self, directory, title):
+        tiles = self.tiles
+        original_tiles = self.original_tiles
         ds = self.get_display_surface()
-        tiles = self.tiles = []
-        original = self.original_tiles = []
         for path in sorted(glob(join(directory, "*.png"))):
             self.add_tile(load(path).convert_alpha())
-        colored = self.colored_tiles = {}
+        colored = self.colored_tiles
         self.set_entire_grid()
         block = 0
         for line in open(join(directory, "map")):
@@ -934,13 +1051,22 @@ class Level(Animation):
         self.play(self.rotate_title_border)
         self.subscribe(self.respond)
         self.set_swap_status()
+        self.path = directory
+        return True
 
     def add_tile(self, surface):
         self.original_tiles.append(surface)
-        self.tiles.append(scale(surface, [surface.get_width() * \
-                                          PictureProcessing.SCALE] * 2))
+        scaled = scale(surface, [surface.get_width() * PictureProcessing.SCALE] * 2)
+        self.tiles.append(scaled)
         self.set_swap_status()
 
+    def get_visible_tile_indicies(self):
+        indicies = set()
+        for row in self.grid:
+            for index in row:
+                indicies.add(index)
+        return indicies
+
     def set_entire_grid(self, index=None):
         self.grid = []
         ds = self.get_display_surface()
@@ -948,12 +1074,27 @@ class Level(Animation):
             self.grid.append([index] *
                              (ds.get_height() / self.tiles[0].get_height()))
 
-    def load_grid_file(self):
-        return False
+    def load_grid_file(self, directory):
+        for path in sorted(glob(join(directory, "*.png"))):
+            self.add_tile(load(path).convert())
+        if not self.tiles:
+            return False
+        level_path = join(
+            directory, self.get_configuration("editor", "level-file-name"))
+        if not exists(level_path):
+            return False
+        level_file = open(level_path, "r")
+        self.set_entire_grid()
+        for jj, line in enumerate(level_file):
+            for ii, character in enumerate(line.strip()):
+                self.grid[ii][jj] = int(character, 16)
+                if self.grid[ii][jj] >= len(self.tiles):
+                    return False
+        self.set_swap_status()
+        self.path = directory
+        return True
 
     def load_default(self):
-        self.tiles = []
-        self.original_tiles = []
         path = self.get_game().editor.get_default_tile_path()
         if exists(path):
             default = load(path).convert()
@@ -964,9 +1105,9 @@ class Level(Animation):
                 for x in xrange([1, 2, 2, 2, 2, 2, 7, 8][y]):
                     default.set_at((x, y), (200, 200, 200))
         self.add_tile(default)
-        self.colored_tiles = {}
         self.set_entire_grid(0)
         self.set_swap_status()
+        return True
 
     def set_preview(self):
         tw, th = self.original_tiles[0].get_size()
@@ -1034,7 +1175,7 @@ class Level(Animation):
         swap_status = self.swap_status = {}
         if randomize and len(self.tiles) > 1:
             swapped = False
-            indicies = set(range(len(self.tiles)))
+            indicies = self.get_visible_tile_indicies()
             while not swapped:
                 available_swap_positions = indicies.copy()
                 for index in indicies:
@@ -1205,9 +1346,16 @@ class Interface(Animation):
             self.open_plate.toggle_hidden()
         if not self.closed and self.swapping_index is not None:
             self.swap_indicator.toggle_hidden()
-        if self.solved and not self.suppressing_commands and not self.get_game().levels.current_level.at_title:
+        if self.solved and not self.suppressing_commands and not self.get_level().at_title:
             self.advance_plate.toggle_hidden()
 
+    def get_level(self):
+        if self.get_game().editor.active:
+            level = self.get_game().editor.get_current_level()
+        else:
+            level = self.get_game().levels.current_level
+        return level
+
     def blink_countdown(self):
         if self.is_countdown_active():
             self.countdown_plate.toggle_hidden()
@@ -1225,10 +1373,11 @@ class Interface(Animation):
                 title_plate.hide()
 
     def is_countdown_active(self):
-        return 0 < self.get_game().levels.current_level.get_remaining_swap_count() <= self.COUNTDOWN_THRESHOLD
+        return 0 < self.get_level().get_remaining_swap_count() <= self.COUNTDOWN_THRESHOLD
 
     def respond(self, event, suppress_sound=False):
-        if self.active and not self.suppressing_commands and not self.get_game().editor.active:
+        if self.active and not self.suppressing_commands and \
+           (not self.get_game().editor.active or self.get_game().editor.at_test):
             delegate = self.get_game().delegate
             effects = self.get_game().sound_effects
             is_pad_mode = self.get_game().is_gamepad_mode()
@@ -1237,13 +1386,14 @@ class Interface(Animation):
                 self.unclose()
             elif self.closed and self.solved and (delegate.compare(event, "advance") or \
                (not is_pad_mode and delegate.compare(event, "action"))):
-                effects.play("go")
-                self.advance_plate.hide()
-                if self.get_game().levels.is_final_level():
-                    self.get_game().ending.activate()
-                    self.get_game().levels.deactivate()
-                else:
-                    self.get_game().levels.begin_next_level()
+                if not self.get_game().editor.active:
+                    effects.play("go")
+                    self.advance_plate.hide()
+                    if self.get_game().levels.is_final_level():
+                        self.get_game().ending.activate()
+                        self.get_game().levels.deactivate()
+                    else:
+                        self.get_game().levels.begin_next_level()
             elif not self.closed:
                 if delegate.compare(event, "cancel"):
                     effects.play("cancel")
@@ -1254,19 +1404,20 @@ class Interface(Animation):
                 elif delegate.compare(event, "action"):
                     if self.swapping_index is None:
                         effects.play("start")
-                        self.swapping_index = self.cell_index
+                        self.swapping_index = self.get_selected_tile_index()
                         self.set_indicator_position(False)
                         self.respond(Event(delegate.command_event_id,
                                            {"command": "down", "cancel": False}), True)
                     else:
-                        if self.cell_index == self.swapping_index:
+                        if self.get_selected_tile_index() == self.swapping_index:
                             effects.play("cancel")
                         else:
-                            level = self.get_game().levels.current_level
+                            level = self.get_level()
                             status = level.swap_status
                             temp = status[self.swapping_index]
-                            status[self.swapping_index] = status[self.cell_index]
-                            status[self.cell_index] = temp
+                            selected_index = self.get_selected_tile_index()
+                            status[self.swapping_index] = status[selected_index]
+                            status[selected_index] = temp
                             self.swap_count += 1
                             if not level.is_solved():
                                 effects.play("swap")
@@ -1278,7 +1429,7 @@ class Interface(Animation):
                                 else:
                                     self.set_title_plate_visibility(False)
                                     self.countdown_plate.hide()
-                            else:
+                            elif not self.get_game().editor.active:
                                 effects.play("clear")
                                 if not self.get_game().levels.is_final_level():
                                     self.get_game().write_progress(self.get_game().levels.\
@@ -1330,9 +1481,14 @@ class Interface(Animation):
                             self.cell_index -= bp
                     self.set_indicator_position()
 
+    def get_selected_tile_index(self):
+        for ii, index in enumerate(sorted(self.get_level().swap_status.keys())):
+            if ii == self.cell_index:
+                return index
+                    
     def play_song(self):
         self.close()
-        level = self.get_game().levels.current_level
+        level = self.get_level()
         level.clip_audio.stop()
         level.full_audio.play(-1)
         self.play(self.unsuppress_advance, delay=self.UNSUPPRESS_ADVANCE_DELAY,
@@ -1352,7 +1508,7 @@ class Interface(Animation):
         indicator.location.left += self.CELL_INDICATOR_OFFSET
 
     def get_cell_count(self):
-        return len(self.get_game().levels.current_level.swap_status)
+        return len(self.get_level().swap_status)
 
     def reset(self):
         self.deactivate()
@@ -1398,11 +1554,11 @@ class Interface(Animation):
         self.countdown_plate.hide()
 
     def get_title_plate(self):
-        return self.get_game().levels.current_level.title_plate
+        return self.get_level().title_plate
 
     def set_countdown_plate(self):
         countdown_plate = self.countdown_plate = Sprite(self)
-        count = self.get_game().levels.current_level.get_remaining_swap_count()
+        count = self.get_level().get_remaining_swap_count()
         swap_text = "SWAPS" if count > 1 else "SWAP"
         text = "%i %s UNTIL FIXED!" % (count, swap_text)
         countdown_plate.add_frame(self.get_game().glyphs.
@@ -1424,7 +1580,8 @@ class Interface(Animation):
     def update(self):
         Animation.update(self)
         if self.active:
-            self.open_plate.update()
+            if not self.get_game().editor.active:
+                self.open_plate.update()
             self.advance_plate.update()
             if not self.closed:
                 for frame in self.cell_frames[:self.get_cell_count()]:
@@ -1432,15 +1589,16 @@ class Interface(Animation):
                 self.cell_indicator.update()
                 if self.swapping_index is not None:
                     self.swap_indicator.update()
-                level = self.get_game().levels.current_level
-                for index, position in level.swap_status.iteritems():
+                level = self.get_level()
+                for ii, index in enumerate(sorted(level.swap_status.keys())):
+                    position = level.swap_status[index]
                     if position in level.colored_tiles.keys():
                         tile = level.colored_tiles[position]
                     else:
                         tile = level.tiles[position]
                     zoomed = scale(tile, [tile.get_width() * self.TILE_ZOOM_SCALE] * 2)
                     rect = zoomed.get_rect()
-                    frame = self.cell_frames[index]
+                    frame = self.cell_frames[ii]
                     rect.midright = frame.location.right - self.CELL_INDENT, \
                                     frame.location.centery
                     self.display_surface.blit(self.tile_shadow, rect.move(self.SHADOW_OFFSET))
@@ -1451,7 +1609,8 @@ class Interface(Animation):
                 title_plate = self.get_title_plate()
                 if title_plate is not None:
                     title_plate.update()
-                self.countdown_plate.update()
+                if not self.get_game().editor.active:
+                    self.countdown_plate.update()
 
 
 class Title(Animation):
diff --git a/config b/config
index e584d0b..287a61b 100644 (file)
--- a/config
+++ b/config
@@ -39,9 +39,10 @@ framerate = 100
 temp-directory = /tmp/
 
 [editor]
-root-directory = editor
+root-directory = resource/editor
 levels-directory = levels
 original-directory = original
 new-directory = new
 tiles-directory = tiles
 default-tile-file-name = default.png
+level-file-name = level
index b748e2d..feba237 100644 (file)
@@ -1 +1 @@
-0 0
+1 27