def start(self):
        self.tileset = Tilesheet("tileset.png", 24, 24) #Tileset
        self.tilemap = Tilemap(self.tileset, 32, 32) #On-screen tiles

        self._generate_map()

        self.turn = 0
        self.turn_player = 0
        self.victorious_side = None
        self.quit_popup = False

        self.red_cursor = [ 0, 19]
        self.blu_cursor = [13, 31]

        self.red_charge_wall = 0
        self.red_charge_floor = 0
        self.blu_charge_wall = 0
        self.blu_charge_floor = 0
        self.red_points = 0
        self.blu_points = 0

        self.red_image = pygame.Surface((8 * 24 + 3, 4 * 24 + 3))
        self.blu_image = pygame.Surface((8 * 24 + 3, 4 * 24 + 3))

        self._prerender_popups()

        self._update_status()
class PegMode(Mode):
    def start(self):
        self.tileset = Tilesheet("tileset.png", 24, 24) #Tileset
        self.tilemap = Tilemap(self.tileset, 32, 32) #On-screen tiles

        self._generate_map()

        self.turn = 0
        self.turn_player = 0
        self.victorious_side = None
        self.quit_popup = False

        self.red_cursor = [ 0, 19]
        self.blu_cursor = [13, 31]

        self.red_charge_wall = 0
        self.red_charge_floor = 0
        self.blu_charge_wall = 0
        self.blu_charge_floor = 0
        self.red_points = 0
        self.blu_points = 0

        self.red_image = pygame.Surface((8 * 24 + 3, 4 * 24 + 3))
        self.blu_image = pygame.Surface((8 * 24 + 3, 4 * 24 + 3))

        self._prerender_popups()

        self._update_status()

    def _prerender_popups(self):
        font = pygame.font.Font(None, 32)

        text = font.render("Really quit (Y/Esc)?", 1, (230, 230, 50))
        textpos = text.get_rect()
        self.quit_image = pygame.Surface((textpos[2], textpos[3]))
        self.quit_image.blit(text, textpos)

        text1 = font.render("Red player wins!", 1, RED_RED )
        text2 = font.render("Blue player wins!", 1, BLU_BLUE)
        textpos = text1.get_rect(left=3, top=3)
        self.victory_image_red = pygame.Surface((textpos[2] + 6, textpos[3] + 6))
        self.victory_image_blu = pygame.Surface((textpos[2] + 6, textpos[3] + 6))
        self.victory_image_red.fill(RED_RED)
        self.victory_image_red.fill((0,0,0), textpos)
        self.victory_image_red.blit(text1, textpos)

        self.victory_image_blu.fill(BLU_BLUE)
        self.victory_image_blu.fill((0,0,0), textpos)
        self.victory_image_blu.blit(text2, textpos)

    def _generate_map(self, mode = MAP_MODE_ASYMMETRICAL):
        tiles = gen_list2d(32, 32)

        density_sectors = [
            [10, 10, 10,  9,  6,  4,  3,  1],
            [10, 10,  7,  6,  8,  3,  2],
            [10,  9,  3,  5,  2,  3],
            [ 9,  7,  8,  3,  2],
            [ 4,  3,  2,  1],
            [ 0,  1,  6],
            [ 1,  4],
            [ 8]
        ]
        #Mirror the density sectors
        for y, row in enumerate(density_sectors):
            for x in range(len(row), 8):
                d = density_sectors[7-x][7-y]
                density_sectors[y] += [d]

        self.goals = (
            (30,  1),
            (16, 15),
            (10,  8),
            (31 - 8, 31 - 10),
            (18,  1),
            (31 - 1, 31 - 18)
        )
        RED_BASE_POS = (0, 21)
        BLU_BASE_POS = (31 - 21, 31)

        for x in range(32):
            for y in range(32):
                #Upper left and bottom right fills
                if (x + y <= 15) or ((31-x) + (31-y) <= 15):
                    tiles[x][y] = T_OBSTACLE
                #Lower left corner fill
                elif (x + (32-y) <= 5):
                    tiles[x][y] = T_OBSTACLE
                elif (x, y) == RED_BASE_POS:
                    tiles[x][y] = T_RED_BASE
                elif (x, y) == BLU_BASE_POS:
                    tiles[x][y] = T_BLU_BASE
                elif (x, y) in self.goals:
                    tiles[x][y] = T_GOAL
                elif (x > 1) and (y < 30):
                    d = density_sectors[y // 4][x // 4]
                    if random.randint(0, 15) <= d:
                        tiles[x][y] = T_OBSTACLE
                    else:
                        tiles[x][y] = T_FLOOR

        #Some simple cellular automata magic to hopefully make the map nicer
        oldmap = [x[:] for x in tiles]
        for x in range(1, 30):
            for y in range(1, 30):
                if (x + y <= 15) or ((32-x) + (32-y) <= 15): continue
                elif (x + (32-y) <= 5): continue

                obstacle_count = 0
                for (mx, my) in [(1, -1), (1, 0), (1, 1), (0, -1), (0, 1), (-1, -1), (-1, 0), (-1, 1)]:
                    obstacle_count += int(oldmap[x+mx][y+my] == T_OBSTACLE)
                floor_count = 8 - obstacle_count

                d = density_sectors[y // 4][x // 4]

                if oldmap[x][y] == T_OBSTACLE:
                    if random.randint(0, max(0, floor_count + d)) <= 3:
                        tiles[x][y] = T_FLOOR
                elif oldmap[x][y] == T_FLOOR:
                    if random.randint(0, obstacle_count + 10 - d) == 0:
                        tiles[x][y] = T_OBSTACLE

        if mode == MAP_MODE_SYMMETRICAL:
            #Make the map symmetrical
            for x in range(0, 32):
                for y in range(0, 32 - x):
                    if tiles[x][y] in [T_OBSTACLE, T_FLOOR, T_FLOOR2]:
                        tiles[31 - y][31 - x] = tiles[x][y]

        #Surround the goals with immutable ground tiles
        for goal in self.goals:
            gx = goal[0]
            gy = goal[1]
            for x in range(gx - PROTECTED_RADIUS, gx + PROTECTED_RADIUS + 1):
                for y in range(gy - PROTECTED_RADIUS, gy + PROTECTED_RADIUS + 1):
                    if (x < 0) or (x > 31) or (y < 0) or (y > 31):
                        continue
                    elif tiles[x][y] == T_FLOOR:
                        tiles[x][y] = T_FLOOR2

        self.tilemap.from_list(tiles)

        self._cast_paths()

    def _cast_paths(self):
        #Clear paths
        for x in range(32):
            for y in range(32):
                if self.tilemap.get_tile(x, y) in [T_BLU_PATH, T_RED_PATH, T_MIX_PATH]:
                    self.tilemap.set_tile(x, y, T_FLOOR)
                if self.tilemap.get_tile(x, y) in [T_BLU_PATH2, T_RED_PATH2, T_MIX_PATH2]:
                    self.tilemap.set_tile(x, y, T_FLOOR2)
        #Recalculate paths
        for x in range(32):
            for y in range(32):
                d = [
                    {
                        "own_pegs":  [T_BLU_BASE, T_BLU_PEG],
                        "enemy_path":  [T_RED_PATH, T_MIX_PATH],
                        "enemy_path2": [T_RED_PATH2, T_MIX_PATH2],
                        "own_path": T_BLU_PATH,
                        "own_path2": T_BLU_PATH2,
                    },
                    {
                        "own_pegs":  [T_RED_BASE, T_RED_PEG],
                        "enemy_path":  [T_BLU_PATH, T_MIX_PATH],
                        "enemy_path2": [T_BLU_PATH2, T_MIX_PATH2],
                        "own_path": T_RED_PATH,
                        "own_path2": T_RED_PATH2,
                    }
                ]
                for dataset in d:
                    if self.tilemap.get_tile(x, y) in dataset["own_pegs"]:
                        for direction in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
                            x2 = x + direction[0]
                            y2 = y + direction[1]
                            f_passable_tiles = lambda x, y: self.tilemap.get_tile(x2, y2) in PASSABLE_TILES
                            while (x2 >= 0) and (x2 < 32) and (y2 >= 0) and (y2 < 32) and f_passable_tiles(x2, y2):
                                if   self.tilemap.get_tile(x2, y2) in dataset["enemy_path"]:  ttype = T_MIX_PATH
                                elif self.tilemap.get_tile(x2, y2) in dataset["enemy_path2"]: ttype = T_MIX_PATH2
                                elif self.tilemap.get_tile(x2, y2) == T_FLOOR2: ttype = dataset["own_path2"]
                                elif self.tilemap.get_tile(x2, y2) == dataset["own_path2"]: ttype = dataset["own_path2"]
                                else: ttype = dataset["own_path"]

                                self.tilemap.set_tile(x2, y2, ttype)
                                x2 += direction[0]
                                y2 += direction[1]

    def place_peg(self, player, x, y):
        paths = (
            [T_RED_PATH, T_RED_PATH2],
            [T_BLU_PATH, T_BLU_PATH2]
        )[player] + [T_MIX_PATH, T_MIX_PATH2]
        peg = (T_RED_PEG, T_BLU_PEG)[player]

        if self.tilemap.get_tile(x, y) in paths:
            self.tilemap.set_tile(x, y, peg)
            self._end_turn()
            return True

        return False

    def place_obstacle(self, player, x, y):
        for goal in self.goals:
            gx = goal[0]
            gy = goal[1]
            if x in range(gx - PROTECTED_RADIUS, gx + PROTECTED_RADIUS + 1):
                if y in range(gy - PROTECTED_RADIUS, gy + PROTECTED_RADIUS + 1):
                    return False

        charge = (self.red_charge_wall, self.blu_charge_wall)[player]
        if charge == CHARGE_OBSTACLE:
            for d in [(-1, 0), ( 1, 0), ( 0,-1), ( 0, 1)]:
                x2, y2 = x + d[0], y + d[1]
                if x2 < 0 or x2 > 31 or y2 < 0 or y2 > 31: continue

                enemy_pegs = (
                    [T_RED_PEG, T_RED_BASE],
                    [T_BLU_PEG, T_BLU_BASE],
                )[player ^ 1]

                if self.tilemap.get_tile(x2, y2) in enemy_pegs:
                    return False

            if self.tilemap.get_tile(x, y) not in IMMUTABLE_TILES:
                self.tilemap.set_tile(x, y, T_OBSTACLE2)
                if player == P_RED: self.red_charge_wall = -1
                else: self.blu_charge_wall = -1

                self._end_turn()
                return True

        return False

    def place_floor(self, player, x, y):
        charge = (self.red_charge_floor, self.blu_charge_floor)[player]
        if charge == CHARGE_FLOOR:
            if self.tilemap.get_tile(x, y) not in IMMUTABLE_TILES:
                for x2 in range(x - 1, x + 2):
                    for y2 in range(y - 1, y + 2):
                        if x2 < 0 or x2 > 31 or y2 < 0 or y2 > 31: continue
                        if self.tilemap.get_tile(x2, y2) == T_FLOOR:
                            self.tilemap.set_tile(x2, y2, T_FLOOR2)

                self.tilemap.set_tile(x, y, T_FLOOR2)
                if player == P_RED: self.red_charge_floor = -1
                else: self.blu_charge_floor = -1

                self._end_turn()
                return True

        return False

    def _update_status(self):
        self.red_points = 0
        self.blu_points = 0
        for goal_pos in self.goals:
            red_scored = False
            blu_scored = False
            for direction in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
                x = goal_pos[0] + direction[0]
                y = goal_pos[1] + direction[1]
                tile = self.tilemap.get_tile(x, y)
                if   tile == T_RED_PEG and not red_scored:
                    self.red_points += 1
                    red_scored = True
                elif tile == T_BLU_PEG and not blu_scored:
                    self.blu_points += 1
                    blu_scored = True

        icon_y = int(2.5 * 24)
        icon1pos = int(3.5 * 24)
        icon2pos = int(5.0 * 24)
        icon3pos = int(6.5 * 24)

        black = (0, 0, 0)
        gray75 = (192, 192, 192)
        green = (0, 192, 0)

        color = RED_RED if (self.turn_player == 0) else gray75
        self.red_image.fill(color)
        color = BLU_BLUE if (self.turn_player == 1) else gray75
        self.blu_image.fill(color)

        self.red_image.fill(black, (0, 0, 8*24, 4*24))
        self.blu_image.fill(black, (3, 3, 8*24, 4*24))

        icons = [
            [icon1pos, 1, 1, 1, T_ICON_RED_PEG, T_ICON_BLU_PEG],
            [icon2pos, self.red_charge_wall, self.blu_charge_wall, CHARGE_OBSTACLE, T_ICON_OBSTACLE, T_ICON_OBSTACLE],
            [icon3pos, self.red_charge_floor, self.blu_charge_floor, CHARGE_FLOOR, T_ICON_FLOOR, T_ICON_FLOOR],
        ]

        for icon in icons:
            red_charge = 1.0 - 1.0 * icon[1] / icon[3]
            blu_charge = 1.0 - 1.0 * icon[2] / icon[3]

            red_color = green if (self.turn_player == 0) else gray75
            blu_color = green if (self.turn_player == 1) else gray75

            red_y = icon_y - 2 + int(red_charge * 28)
            red_h = 28 - int(red_charge * 28)

            blu_y = icon_y - 2 + int(blu_charge * 28)
            blu_h = 28 - int(blu_charge * 28)

            self.red_image.fill(red_color, (icon[0] - 2, red_y, 28, red_h))
            self.red_image.fill(black, (icon[0], icon_y, 24, 24))
            self.blu_image.fill(blu_color, (icon[0] - 2 + 3, blu_y + 3, 28, blu_h))
            self.blu_image.fill(black, (icon[0] + 3, icon_y + 3, 24, 24))

            rect = self.tileset.get_tile_rect(icon[4])
            self.red_image.blit(self.tileset.image, (icon[0], icon_y) , rect)
            rect = self.tileset.get_tile_rect(icon[5])
            self.blu_image.blit(self.tileset.image, (icon[0]+3, icon_y+3) , rect)

        font = pygame.font.Font(None, 32)

        red_text = font.render("Player 1 - %ipts" % self.red_points, 1, RED_RED)
        red_textpos = red_text.get_rect(left=10, top=10)

        blu_text = font.render("Player 2 - %ipts" % self.blu_points, 1, BLU_BLUE)
        blu_textpos = blu_text.get_rect(left=13, top=13)

        self.red_image.blit(red_text, red_textpos)
        self.blu_image.blit(blu_text, blu_textpos)

        #TODO: Add key captions

    def _end_turn(self):
        self.turn += 1
        self.turn_player ^= 1

        if self.turn_player == 0:
            self.red_charge_wall  = min(self.red_charge_wall  + 1, CHARGE_OBSTACLE )
            self.red_charge_floor = min(self.red_charge_floor + 1, CHARGE_FLOOR)

            self.blu_charge_wall  = min(self.blu_charge_wall  + 1, CHARGE_OBSTACLE )
            self.blu_charge_floor = min(self.blu_charge_floor + 1, CHARGE_FLOOR)

        self._cast_paths()
        self._update_status()

    def _handle_input(self):
        pass

    def run(self, delta_time):
        if self.red_points > 3: self.victorious_side = P_RED
        if self.blu_points > 3: self.victorious_side = P_BLU

        if self.victorious_side is not None:
            if keyboard.just_pressed("escape"): self.parent.set_mode(MenuMode())
            elif keyboard.just_pressed("enter"): self.parent.set_mode(MenuMode())
            return

        if self.quit_popup:
            if   keyboard.just_pressed("escape"): self.quit_popup = False
            elif keyboard.just_pressed("y"): self.parent.set_mode(MenuMode())
            #elif keyboard.just_pressed("y"): self.parent.running = False
            return

        if keyboard.just_pressed("escape"):
            self.quit_popup = True
            return

        if   keyboard.just_pressed("r") and self.turn == 0:
            self._generate_map()
        elif keyboard.just_pressed("t") and self.turn == 0:
            self._generate_map(mode=MAP_MODE_SYMMETRICAL)

        self._handle_input()

    def render(self, surface):
        surface.blit(self.tilemap.image, (0, 0))

        color = (255, 0, 255)
        interval = 768 / 8

        #Render cursors
        pos1 = (self.red_cursor[0] * 24, self.red_cursor[1] * 24)
        pos2 = (self.blu_cursor[0] * 24, self.blu_cursor[1] * 24)
        rect1 = self.tileset.get_tile_rect(T_CURSOR_RED)
        rect2 = self.tileset.get_tile_rect(T_CURSOR_BLU)
        rect3 = self.tileset.get_tile_rect(T_CURSOR_MIX)
        if pos1 == pos2:
            surface.blit(self.tileset.image, pos1, rect3)
        else:
            #Render RED cursor
            surface.blit(self.tileset.image, pos1, rect1)
            #Render BLU cursor
            surface.blit(self.tileset.image, pos2, rect2)

        surface.blit(self.red_image, (0, 0))
        blu_status_pos = (
            768 - self.blu_image.get_width(),
            768 - self.blu_image.get_height()
        )
        surface.blit(self.blu_image, blu_status_pos)

        if self.victorious_side is not None:
            pos = (
                (surface.get_width() - self.victory_image_red.get_width()) / 2,
                (surface.get_height() - self.victory_image_red.get_height()) / 2,
            )
            if self.victorious_side == P_RED:
                surface.blit(self.victory_image_red, pos)
            else:
                surface.blit(self.victory_image_blu, pos)
        elif self.quit_popup:
            pos = (
                (surface.get_width() - self.quit_image.get_width()) / 2,
                (surface.get_height() - self.quit_image.get_height()) / 2,
            )
            surface.blit(self.quit_image, pos)