make corners straight use straight lines instead of arcs, add randomized level generator finally, make default difficulties bigger, make main menu buttons smaller, other fixes and improvements
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 309 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 334 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 301 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 322 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 295 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 321 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 287 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 305 B |
@@ -15,9 +15,9 @@ def get_opposite(direction):
|
||||
|
||||
class Cell(arcade.gui.UITextureButton):
|
||||
def __init__(self, cell_type, left_neighbour, top_neighbour):
|
||||
super().__init__(texture=TEXTURE_MAP[cell_type, ROTATIONS[cell_type][0], cell_type == "power_source"])
|
||||
super().__init__(texture=TEXTURE_MAP[cell_type, ROTATIONS[cell_type][0] if cell_type in ROTATIONS else "cross", cell_type == "power_source"])
|
||||
|
||||
self.rotation = ROTATIONS[cell_type][0]
|
||||
self.rotation = ROTATIONS[cell_type][0] if cell_type in ROTATIONS else "cross"
|
||||
self.cell_type = cell_type
|
||||
self.powered = False
|
||||
self.left_neighbour, self.top_neighbour = left_neighbour, top_neighbour
|
||||
@@ -38,13 +38,11 @@ class Cell(arcade.gui.UITextureButton):
|
||||
self.get_neighbour(neighbour_direction) for neighbour_direction in NEIGHBOURS[self.rotation]
|
||||
if (
|
||||
self.get_neighbour(neighbour_direction) and
|
||||
self.get_neighbour(neighbour_direction).cell_type != "house" and
|
||||
get_opposite(neighbour_direction) in NEIGHBOURS[self.get_neighbour(neighbour_direction).rotation]
|
||||
)
|
||||
]
|
||||
|
||||
def update_value(self):
|
||||
self.powered = any([neighbour.powered for neighbour in self.get_connected_neighbours()])
|
||||
|
||||
def update_visual(self):
|
||||
self.texture = TEXTURE_MAP[(self.cell_type, self.rotation, self.powered)]
|
||||
self.texture_hovered = TEXTURE_MAP[(self.cell_type, self.rotation, self.powered)]
|
||||
|
||||
129
game/level_generator.py
Normal file
@@ -0,0 +1,129 @@
|
||||
import random
|
||||
from utils.constants import ROTATIONS, NEIGHBOURS, DIRECTIONS
|
||||
from collections import deque
|
||||
|
||||
def in_bounds(x, y, size):
|
||||
return 0 <= x < size and 0 <= y < size
|
||||
|
||||
def classify_tile(conns):
|
||||
for rotation, connections in NEIGHBOURS.items():
|
||||
if conns == connections:
|
||||
for cell_type, rotations in ROTATIONS.items():
|
||||
if rotation in rotations:
|
||||
return cell_type
|
||||
|
||||
print(f"Unknown: {conns}")
|
||||
return "cross"
|
||||
|
||||
def add_cycles(conns, num_cycles):
|
||||
size = len(conns)
|
||||
added = 0
|
||||
attempts = 0
|
||||
max_attempts = num_cycles * 20
|
||||
|
||||
while added < num_cycles and attempts < max_attempts:
|
||||
attempts += 1
|
||||
x, y = random.randint(0, size-1), random.randint(0, size-1)
|
||||
|
||||
dirs = list(DIRECTIONS.items())
|
||||
random.shuffle(dirs)
|
||||
|
||||
for d, (dx, dy, opposite) in dirs:
|
||||
nx, ny = x + dx, y + dy
|
||||
|
||||
if in_bounds(nx, ny, size) and d not in conns[y][x]:
|
||||
conns[y][x].add(d)
|
||||
conns[ny][nx].add(opposite)
|
||||
added += 1
|
||||
break
|
||||
|
||||
return conns
|
||||
|
||||
def pick_random_cells(size, count, avoid=None):
|
||||
all_cells = [(x, y) for y in range(size) for x in range(size)]
|
||||
if avoid:
|
||||
all_cells = [c for c in all_cells if c not in avoid]
|
||||
random.shuffle(all_cells)
|
||||
return all_cells[:count]
|
||||
|
||||
def generate_spanning_tree_with_dead_ends(size, num_dead_ends):
|
||||
if num_dead_ends > size * size - 1:
|
||||
num_dead_ends = size * size - 1
|
||||
|
||||
grid = [[set() for _ in range(size)] for _ in range(size)]
|
||||
all_cells = [(x, y) for y in range(size) for x in range(size)]
|
||||
random.shuffle(all_cells)
|
||||
|
||||
start = all_cells[0]
|
||||
stack = [start]
|
||||
visited = {start}
|
||||
leaf_candidates = []
|
||||
|
||||
while len(visited) < size * size:
|
||||
if not stack:
|
||||
unvisited = [c for c in all_cells if c not in visited]
|
||||
if unvisited:
|
||||
stack.append(unvisited[0])
|
||||
visited.add(unvisited[0])
|
||||
|
||||
x, y = stack[-1]
|
||||
dirs = list(DIRECTIONS.items())
|
||||
random.shuffle(dirs)
|
||||
|
||||
found = False
|
||||
for d, (dx, dy, opposite) in dirs:
|
||||
nx, ny = x + dx, y + dy
|
||||
if in_bounds(nx, ny, size) and (nx, ny) not in visited:
|
||||
grid[y][x].add(d)
|
||||
grid[ny][nx].add(opposite)
|
||||
visited.add((nx, ny))
|
||||
stack.append((nx, ny))
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
if len(grid[y][x]) == 1:
|
||||
leaf_candidates.append((x, y))
|
||||
stack.pop()
|
||||
|
||||
leaf_nodes = leaf_candidates[:num_dead_ends]
|
||||
|
||||
for y in range(size):
|
||||
for x in range(size):
|
||||
if (x, y) not in leaf_nodes and len(grid[y][x]) < 2:
|
||||
dirs = list(DIRECTIONS.items())
|
||||
random.shuffle(dirs)
|
||||
|
||||
for d, (dx, dy, opposite) in dirs:
|
||||
nx, ny = x + dx, y + dy
|
||||
|
||||
if in_bounds(nx, ny, size) and d not in grid[y][x]:
|
||||
grid[y][x].add(d)
|
||||
grid[ny][nx].add(opposite)
|
||||
if len(grid[y][x]) >= 2:
|
||||
break
|
||||
|
||||
return grid, leaf_nodes
|
||||
|
||||
def generate_map(size, source_count, house_count, cycles=15):
|
||||
conns, dead_ends = generate_spanning_tree_with_dead_ends(size, house_count)
|
||||
conns = add_cycles(conns, cycles)
|
||||
|
||||
houses = dead_ends[:house_count]
|
||||
available_cells = [(x, y) for y in range(size) for x in range(size)
|
||||
if (x, y) not in houses]
|
||||
random.shuffle(available_cells)
|
||||
sources = available_cells[:source_count]
|
||||
|
||||
grid = []
|
||||
for y in range(size):
|
||||
grid.append([])
|
||||
for x in range(size):
|
||||
if (x, y) in sources:
|
||||
grid[-1].append("power_source")
|
||||
elif (x, y) in houses:
|
||||
grid[-1].append("house")
|
||||
else:
|
||||
grid[-1].append(classify_tile(conns[y][x]))
|
||||
|
||||
return grid
|
||||
18
game/play.py
@@ -5,6 +5,7 @@ from utils.preload import button_texture, button_hovered_texture
|
||||
|
||||
from collections import deque
|
||||
|
||||
from game.level_generator import generate_map
|
||||
from game.cells import *
|
||||
|
||||
class Game(arcade.gui.UIView):
|
||||
@@ -20,8 +21,10 @@ class Game(arcade.gui.UIView):
|
||||
self.houses = []
|
||||
|
||||
self.anchor = self.add_widget(arcade.gui.UIAnchorLayout(size_hint=(1, 1)))
|
||||
self.grid_size = list(map(int, difficulty.split("x")))
|
||||
self.power_grid = self.anchor.add(arcade.gui.UIGridLayout(horizontal_spacing=0, vertical_spacing=0, row_count=self.grid_size[0], column_count=self.grid_size[1]))
|
||||
self.grid_size = int(difficulty.split("x")[0])
|
||||
self.grid = generate_map(self.grid_size, int((self.grid_size * self.grid_size) / 10), int((self.grid_size * self.grid_size) / 5))
|
||||
|
||||
self.power_grid = self.anchor.add(arcade.gui.UIGridLayout(horizontal_spacing=0, vertical_spacing=0, row_count=self.grid_size, column_count=self.grid_size))
|
||||
|
||||
def on_show_view(self):
|
||||
super().on_show_view()
|
||||
@@ -30,13 +33,13 @@ class Game(arcade.gui.UIView):
|
||||
self.back_button.on_click = lambda event: self.main_exit()
|
||||
self.anchor.add(self.back_button, anchor_x="left", anchor_y="top", align_x=5, align_y=-5)
|
||||
|
||||
for row in range(self.grid_size[0]):
|
||||
for row in range(self.grid_size):
|
||||
self.cells.append([])
|
||||
for col in range(self.grid_size[1]):
|
||||
for col in range(self.grid_size):
|
||||
left_neighbour = self.cells[row][col - 1] if col > 0 else None
|
||||
top_neighbour = self.cells[row - 1][col] if row > 0 else None
|
||||
|
||||
cell_type = random.choice(["line", "corner", "t_junction", "cross", "power_source", "house"])
|
||||
cell_type = self.grid[row][col]
|
||||
|
||||
if cell_type in ["line", "corner", "t_junction", "cross"]:
|
||||
cell = PowerLine(cell_type, left_neighbour, top_neighbour)
|
||||
@@ -81,8 +84,9 @@ class Game(arcade.gui.UIView):
|
||||
queue.append(connected_neighbour)
|
||||
|
||||
for row in self.cells:
|
||||
for power_line in row:
|
||||
power_line.update_visual()
|
||||
for cell in row:
|
||||
cell.update_visual()
|
||||
|
||||
|
||||
def main_exit(self):
|
||||
from menus.main import Main
|
||||
|
||||
@@ -22,7 +22,7 @@ class DifficultySelector(arcade.gui.UIView):
|
||||
|
||||
self.box.add(arcade.gui.UILabel(text="Difficulty Selector", font_size=32))
|
||||
|
||||
for difficulty in ["3x3", "4x4", "5x5", "6x6", "9x9"]:
|
||||
for difficulty in ["7x7", "8x8", "9x9", "10x10", "12x12"]:
|
||||
button = self.box.add(arcade.gui.UITextureButton(text=difficulty, width=self.window.width / 2, height=self.window.height / 10, texture=button_texture, texture_hovered=button_hovered_texture, style=big_button_style))
|
||||
button.on_click = lambda e, difficulty=difficulty: self.play(difficulty)
|
||||
|
||||
|
||||
@@ -52,10 +52,10 @@ class Main(arcade.gui.UIView):
|
||||
|
||||
self.title_label = self.box.add(arcade.gui.UILabel(text="Connect the Current", font_name="Roboto", font_size=48))
|
||||
|
||||
self.play_button = self.box.add(arcade.gui.UITextureButton(text="Play", texture=button_texture, texture_hovered=button_hovered_texture, width=self.window.width / 2, height=150, style=big_button_style))
|
||||
self.play_button = self.box.add(arcade.gui.UITextureButton(text="Play", texture=button_texture, texture_hovered=button_hovered_texture, width=self.window.width / 2, height=self.window.height / 10, style=big_button_style))
|
||||
self.play_button.on_click = lambda event: self.play()
|
||||
|
||||
self.settings_button = self.box.add(arcade.gui.UITextureButton(text="Settings", texture=button_texture, texture_hovered=button_hovered_texture, width=self.window.width / 2, height=150, style=big_button_style))
|
||||
self.settings_button = self.box.add(arcade.gui.UITextureButton(text="Settings", texture=button_texture, texture_hovered=button_hovered_texture, width=self.window.width / 2, height=self.window.height / 10, style=big_button_style))
|
||||
self.settings_button.on_click = lambda event: self.settings()
|
||||
|
||||
def play(self):
|
||||
|
||||
2
run.py
@@ -18,8 +18,6 @@ from arcade.experimental.controller_window import ControllerWindow
|
||||
|
||||
sys.excepthook = on_exception
|
||||
|
||||
__builtins__.print = lambda *args, **kwargs: logging.debug(" ".join(map(str, args)))
|
||||
|
||||
if not log_dir in os.listdir():
|
||||
os.makedirs(log_dir)
|
||||
|
||||
|
||||
@@ -7,25 +7,24 @@ ROTATIONS = {
|
||||
"line": ["vertical", "horizontal"],
|
||||
"corner": ["right_bottom", "left_bottom", "left_top", "right_top"],
|
||||
"t_junction": ["top_bottom_right", "left_right_bottom", "top_bottom_left", "left_right_top"],
|
||||
"cross": ["cross"],
|
||||
"power_source": ["cross"],
|
||||
"house": ["cross"]
|
||||
"cross": ["cross"]
|
||||
}
|
||||
|
||||
NEIGHBOURS = {
|
||||
"vertical": ["b", "t"],
|
||||
"horizontal": ["l", "r"],
|
||||
"left_bottom": ["l", "b"],
|
||||
"right_bottom": ["r", "b"],
|
||||
"left_top": ["l", "t"],
|
||||
"right_top": ["r", "t"],
|
||||
"top_bottom_right": ["t", "b", "r"],
|
||||
"top_bottom_left": ["t", "b", "l"],
|
||||
"left_right_bottom": ["l", "r", "b"],
|
||||
"left_right_top": ["l", "r", "t"],
|
||||
"cross": ["l", "r", "t", "b"]
|
||||
"vertical": {"b", "t"},
|
||||
"horizontal": {"l", "r"},
|
||||
"left_bottom": {"l", "b"},
|
||||
"right_bottom": {"r", "b"},
|
||||
"left_top": {"l", "t"},
|
||||
"right_top": {"r", "t"},
|
||||
"top_bottom_right": {"t", "b", "r"},
|
||||
"top_bottom_left": {"t", "b", "l"},
|
||||
"left_right_bottom": {"l", "r", "b"},
|
||||
"left_right_top": {"l", "r", "t"},
|
||||
"cross": {"l", "r", "t", "b"}
|
||||
}
|
||||
|
||||
DIRECTIONS = {"t": (0, -1, "b"), "b": (0, 1, "t"), "l": (-1, 0, "r"), "r": (1, 0, "l")}
|
||||
|
||||
menu_background_color = (30, 30, 47)
|
||||
log_dir = 'logs'
|
||||
|
||||