Created
December 25, 2023 11:18
-
-
Save mafshin/fb4d0ea8aefa806a24d0d5110303a265 to your computer and use it in GitHub Desktop.
Interactive Solution Drawing of Problem 40 with NiceGUI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from enum import Enum | |
from nicegui import ui | |
from nicegui.elements.html import Html | |
from nicegui.functions.timer import Timer | |
Direction = Enum('Direction', ['Up', 'Down', 'Right', 'Left']) | |
class Position(): | |
def __init__ (self, x, y): | |
self.x = x | |
self.y = y | |
class State(): | |
def __init__(self): | |
self.iteration = 0 | |
self.robot_pos = Position(0, 0) | |
self.ball_pos = Position(0, 0) | |
self.grid_size = 0 | |
self.cell_size = 0 | |
self.solution_path = '' | |
self.start_pos = Position(0, 0) | |
self.gate = (Position(0,0), Position(0, 0)) | |
self.ball_color = 'black' | |
self.robot_color = 'black' | |
self.path_color = 'black' | |
self.gate_color = 'black' | |
def build_svg( state: State) -> str: | |
cell_size = state.cell_size | |
grid_size = state.grid_size | |
width = cell_size * grid_size + 1 | |
height = cell_size * grid_size + 1 | |
current_path = state.solution_path.split(' ')[0:state.iteration] | |
# draw solution path | |
path = draw_path(state.start_pos.x, state.start_pos.y, current_path, grid_size, cell_size, state.path_color) | |
# draw gate | |
if(state.gate[0].x == state.gate[1].x): | |
gate_start_x = state.gate[0].x | |
gate_start_y = min(state.gate[0].y, state.gate[1].y) | |
gate_path = 'U' * abs(state.gate[0].y - state.gate[1].y) | |
else: | |
gate_start_x = min(state.gate[0].x, state.gate[1].x) | |
gate_start_y = state.gate[0].y | |
gate_path = 'R' * abs(state.gate[0].x - state.gate[1].x) | |
gate = draw_path(gate_start_x, gate_start_y, gate_path, grid_size, cell_size, state.gate_color) | |
# draw ball | |
ball_x = get_x_position(state.ball_pos.x, grid_size, cell_size) | |
ball_y = get_y_position(state.ball_pos.y, grid_size, cell_size) | |
ball = f'<circle cx="{ball_x}" cy="{ball_y}" r="10" fill="{state.ball_color}" />' | |
last_movement = state.solution_path.split(' ')[state.iteration] | |
last_movement_direction = get_direction(last_movement) | |
# draw robot | |
robot_pos_x = get_x_position(state.robot_pos.x, grid_size, cell_size) | |
robot_pos_y = get_y_position(state.robot_pos.y, grid_size, cell_size) | |
robot = f'<circle cx="{robot_pos_x}" cy="{robot_pos_y}" r="20" fill="{state.robot_color}" />' | |
robot_next_pos = calculate_next_position(state.robot_pos, last_movement_direction) | |
state.robot_pos = robot_next_pos # update robot position | |
if(state.robot_pos.x == state.ball_pos.x and state.robot_pos.y == state.ball_pos.y): | |
ball_next_pos = calculate_next_position(state.ball_pos, last_movement_direction) | |
state.ball_pos = ball_next_pos | |
# draw game board | |
return f''' | |
<svg id='patternId' width='{width}' height='{height}' xmlns='http://www.w3.org/2000/svg'> | |
<defs><pattern id='a' patternUnits='userSpaceOnUse' width='{cell_size}' height='{cell_size}' patternTransform='rotate(0)'> | |
<rect x='0' y='0' width='100%' height='100%' fill='hsla(0,0%,100%,1)'/> | |
<path d='M 0,0 V {cell_size} Z M 0,0 H {cell_size} Z' stroke-width='1' stroke='hsla(133, 35%, 32%, 1)' fill='none'/> | |
</pattern></defs> | |
<rect width='100%' height='100%' fill='url(#a)'/> | |
{path} | |
{gate} | |
{ball} | |
{robot} | |
</svg> | |
''' | |
def get_x_position(pos, grid_size, cell_size): | |
return pos * cell_size | |
def get_y_position(pos, grid_size, cell_size): | |
return (grid_size - pos) * cell_size | |
def draw_path(x, y, path, grid_size, cell_size, color): | |
lines = [] | |
next_x = x | |
next_y = y | |
for dir in path: | |
direction: Direction = get_direction(dir) | |
lines.append(generate_line(next_x, next_y, direction, 1, grid_size, cell_size, color)) | |
next_x, next_y = calculate_next_position_xy(next_x, next_y, direction) | |
return str.join(" ", lines) | |
def calculate_next_position(pos: Position, direction: Direction): | |
(next_x, next_y) = calculate_next_position_xy(pos.x, pos.y, direction) | |
return Position(next_x, next_y) | |
def calculate_next_position_xy(x, y, direction: Direction): | |
match direction: | |
case Direction.Down: | |
return (x, y - 1) | |
case Direction.Up: | |
return (x, y + 1) | |
case Direction.Left: | |
return (x - 1, y) | |
case Direction.Right: | |
return (x + 1, y) | |
def get_direction(dir): | |
match dir: | |
case 'D': | |
return Direction.Down | |
case 'U': | |
return Direction.Up | |
case 'L': | |
return Direction.Left | |
case 'R': | |
return Direction.Right | |
def generate_line(x, y, direction: Direction, length, grid_size, cell_size, color): | |
target_x = x | |
target_y = (grid_size) - y | |
match direction: | |
case Direction.Down: | |
target_y = target_y + length # SVG grid is top to bottom | |
case Direction.Up: | |
target_y = target_y - length # SVG grid is top to bottom | |
case Direction.Right: | |
target_x = target_x + length | |
case Direction.Left: | |
target_x = target_x - length | |
scaled_x = x * cell_size | |
scaled_y = ((grid_size) - y) * cell_size | |
scaled_target_x = target_x * cell_size | |
scaled_target_y = target_y * cell_size | |
return f''' | |
<path d='M {scaled_x} {scaled_y} L {scaled_target_x} {scaled_target_y}' stroke-width='3' stroke='{color}'/> | |
''' | |
def next_frame(body: Html, state: State): | |
body.set_content(build_svg(state)) | |
state.iteration = state.iteration + 1 | |
def toggle_timer(timer: Timer): | |
if(timer.active): | |
timer.deactivate() | |
else: | |
timer.activate() | |
def reset(state: State): | |
state.ball_pos = Position(2, 1) | |
state.robot_pos = Position(7, 4) | |
state.grid_size = 10 | |
state.cell_size = 50 | |
state.iteration = 0 | |
state.solution_path = 'D D D D L L L L L U U U U U U L U R R R R R R R R R' | |
state.start_pos = state.robot_pos | |
state.gate = (Position(10, 5), Position(10, 8)) | |
state.ball_color = 'red' | |
state.robot_color = 'green' | |
state.path_color = 'orange' | |
state.gate_color = 'blue' | |
interval = 1.0 # seconds | |
timer = ui.timer(interval, lambda: next_frame(body, state)) | |
timer.deactivate() | |
state = State() | |
with ui.row(): | |
ui.button('Play / Pause', on_click= lambda: toggle_timer(timer)) | |
ui.button('Reset', on_click= lambda: reset(state)) | |
body = ui.html().classes('self-center') | |
reset(state) | |
ui.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment