Brython <<
Previous Next >> Robot_ex
Brython_ex
基本動態模擬
機器人繞圈
機器人自由行
brython_robot.py
導入模組繞圈
ijkm 控制
OOP 機器人右轉
brython_robot_extended.py
多機器人模擬
10x10 three robots
請修改上列程式,可以讓三個 robot 快速通過 20x20 的模擬世界中的每一格。
brython_robot2.py
加入採收作物
可執行採收
指定位置採收
按鍵採收 (按下 i 鍵左轉,按下 j 鍵向前走一步,按下 p 鍵可以採收,每次最多採收 5 個農作物)
頁面 button 採收 (含載入 json 格式附加作物配置檔案)
手動裝盒採收 (每 5 個紅蘿蔔裝一盒,裝盒前個數標示在右上方,採收盒數標示在左上方)
brython_robot3.py
手動裝盒採收 (含判斷內建場景或由 world URL 變數載入 json 檔案場景)
自動巡迴裝盒採收 (滿載後回到原點)
自動巡迴裝盒採收 (滿載後停止)
碰牆之前直走並採收 (while, front_is_clear, move, object_here, pick_carrot 等函式應用)
# 使用一個非同步迴圈,等待每個動作完成
def check_and_move():
if robot.front_is_clear():
robot.move(1)
if robot.object_here():
robot.pick_carrot()
# 延遲執行,給予機器人時間移動
timer.set_timeout(check_and_move, 500)
# 延遲啟動迴圈,確保前面的動作有足夠時間被排入佇列
timer.set_timeout(check_and_move, 1000)
為何需要「非同步迴圈」?
簡單來說,你需要一個非同步迴圈來讓程式「等待」機器人完成動作,而不是一口氣全部執行完。
這段程式碼的邏輯之所以需要這樣設計,是因為 Brython 機器人程式庫中的方法(如 robot.move()、robot.turn_left() 和 robot.pick_carrot())都是非同步的。
讓我們分解一下這背後的原因:
1. 動作是有延遲的 (Actions Have Delay)
機器人程式庫為了模擬真實的動作過程,例如移動、轉彎,會設定一個時間延遲(例如 200 或 300 毫秒)。當你呼叫 robot.move(1) 時,這個指令並不會立即完成,而是會被加入一個「待辦事項」佇列 (queue) 中,然後由一個定時器 (timer) 在稍後執行。
2. while 迴圈是同步的 (A while Loop is Synchronous)
與機器人的非同步動作不同,Python 的 while 迴圈是同步且立即執行的。當你寫下:
Python
while robot.front_is_clear():
robot.move(1)
程式碼會:
立即檢查 robot.front_is_clear()。
如果為 True,立即呼叫 robot.move(1)。這個呼叫只會把「移動」指令放到佇列中,不會等待它完成。
然後,程式會立即回到第一步,再次檢查 robot.front_is_clear()。
因為機器人還沒來得及移動(它的狀態沒有改變),所以 robot.front_is_clear() 會在原地連續被檢查數百甚至數千次,直到程式結束或卡死。這會導致以下問題:
無限迴圈: 如果前方始終暢通,程式會陷入無限迴圈,因為機器人的位置從未改變,無法觸發迴圈終止條件。
不正確的行為: 機器人可能會忽略它前方的牆壁或物件,因為它在原地就已經把所有指令都發送出去了。
3. 非同步迴圈如何解決問題?
你所提供的 check_and_move() 函式就是一個解決方案。它的運作方式是:
檢查條件: if robot.front_is_clear():
執行動作: 如果條件為真,執行 robot.move(1)。
設定延遲: 呼叫 timer.set_timeout(check_and_move, 500)。這個指令告訴瀏覽器:「請在 500 毫秒後,再次呼叫 check_and_move() 這個函式。」
這樣做的好處是,程式執行完 check_and_move() 函式後就結束了,不會陷入迴圈。它將控制權交還給瀏覽器,讓定時器在未來重新觸發。在這 500 毫秒的延遲期間,機器人有足夠的時間完成它在佇列中的移動動作,並更新它在世界中的位置。當 check_and_move() 再次被呼叫時,機器人的狀態已經是最新的了。
結論是,這種非同步設計是為了讓同步的程式邏輯能夠與非同步的視覺化動作完美地協同工作,避免程式陷入僵局或產生錯誤的行為。
brython_robot4.py
brython_robot4_ex1.py
這套程式庫的設計目標是提供一個可視化的機器人模擬環境,讓使用者可以用類似 Reeborg 的「同步」方式來控制機器人,但底層實際上是基於瀏覽器的非同步機制 browser.aio 實現的。
程式庫主要由以下幾個類別和函式組成:
1. World 類別
這是整個模擬環境的核心。它負責世界的繪圖和狀態管理,但本身不處理機器人的動作。
__init__(self, width, height, ...): 初始化一個指定寬度和高度的世界,並建立多個 html.CANVAS 圖層。每個圖層負責繪製不同的元素(例如:網格、背景、牆壁、物件、機器人)。
_create_layers(): 建立並返回一個字典,包含各種不同用途的 CANVAS 圖層。
_init_html(): 將這些圖層以絕對定位的方式堆疊到一個主容器中,並將其插入到網頁的指定 div 元素(brython_div1)裡。
繪圖方法 (_draw_grid, _draw_walls, _draw_background, draw_objects): 這些方法負責在對應的 CANVAS 圖層上繪製靜態或動態的場景元素。
2. AnimatedRobot 類別
這是最底層的機器人動畫引擎。它直接處理機器人的位置和朝向變化,並負責將動畫「畫」出來。
__init__(self, world, x, y, ...): 初始化機器人,設定其在世界中的位置 (x, y)、朝向 (facing),並獲得用於繪製的 CANVAS 繪圖上下文 (ctx)。
async move(self, steps=1): 這是關鍵的 async 方法。它會一個一個步驟地移動機器人。
在每次移動前,它會檢查前方是否有牆壁阻擋。如果有,會印出錯誤訊息並停止。
沒有阻擋時,它會更新機器人的位置,並呼叫 _draw_trace 和 _draw_robot 進行繪圖。
最重要的是,它使用 await aio.sleep(0.2) 來暫停程式的執行,等待 200 毫秒。這就是讓動作看起來像動畫的原因。程式會在此處「掛起」,直到動畫時間過去,然後才會繼續執行。
async turn_left(self): 類似 move,這是一個 async 方法。它會更新機器人的朝向,重新繪製機器人圖片,然後 await aio.sleep(0.3) 來等待轉向動畫完成。
3. SmartRobot 類別
這個類別是使用者與機器人互動的主要介面。它將 AnimatedRobot 的基本動作進行包裝,並提供更進階、更人性化的功能,如 turn_right、move_backward 和 pick_carrot。
__init__(self, base_robot): 接受一個 AnimatedRobot 實例,作為其動作的執行基礎。它還會追蹤機器人收集到的胡蘿蔔數量。
async move(self, steps=1): 這個方法會呼叫 await self.base.move(steps)。使用者在這裡呼叫 robot.move(2) 時,程式會在這裡等待,直到底層 AnimatedRobot 的兩個單步移動都完成,才會繼續執行下一行程式碼。
pick_carrot(): 這是一個同步方法,因為採集胡蘿蔔的動作不需要動畫延遲。它會檢查當前位置是否有胡蘿蔔,並更新內部狀態和世界繪圖。
wall_in_front() 和 front_is_clear(): 這些是同步的狀態檢查方法。它們直接返回當前位置是否有牆壁的布林值,不需要等待,因為它們不涉及動畫。
4. 輔助函式
async load_scene_from_url(url): 這個函式將原本的回調函式 ajax 請求,包裝成一個 async 函式。它使用 aio.Future() 來等待 ajax 請求完成,然後返回場景數據。這使得應用程式可以像同步呼叫一樣寫 await load_scene_from_url(...)。
create_buttons(robot) 和 on_key(robot, evt): 這兩個函式負責建立網頁上的按鈕和鍵盤事件監聽。當使用者點擊按鈕或按鍵時,它會呼叫 aio.run() 來啟動一個新的協程,執行對應的機器人動作。
呼叫應用程式 (main.py) 的內容
應用程式的程式碼非常簡潔,完美展示了如何利用程式庫的非同步特性,寫出像同步一樣直觀的程式碼。
1. 程式結構
應用程式的主體包含兩個 async 函式:robot_actions 和 main。
整個程式的執行從 aio.run(main()) 開始。
2. async def main()
這是程式的入口點。
它首先檢查網址參數,以決定是從遠端載入場景還是使用預設場景。
關鍵在於 await load_scene_from_url(world_url)。這行程式碼會暫停 main() 函式的執行,直到遠端場景數據完全下載並解析完成。
接著,它使用載入的場景數據呼叫 init() 來初始化世界和機器人。
最後,它呼叫 await robot_actions(robot),將控制權交給機器人的行為邏輯。
3. async def robot_actions(robot)
這就是使用者撰寫的機器人行為腳本。
它使用 await 關鍵字來呼叫 robot 物件的 async 方法,例如 await robot.move(2) 和 await robot.turn_left()。
程式會逐行執行,並在每個 await 點暫停,直到對應的動畫完成。這正是 Reeborg 帶給使用者的「同步」感覺。
while robot.front_is_clear() 迴圈也完美地體現了這一點。程式碼會先判斷前方是否暢通,如果暢通,就執行 await robot.move(1),然後再次回到迴圈開頭進行檢查。這個過程是自動化的,使用者不需要關心延遲或回調。
總結來說,這套程式庫與應用程式的設計模式,成功地將複雜的瀏覽器非同步操作封裝起來,透過 Brython 的 browser.aio 協程框架,為使用者提供了一個直觀、易於理解且類似同步的編程環境,非常適合程式教育。
brython_robot3.py 程式庫的核心是手動的佇列和回調函式。
執行流程:當呼叫 robot.move() 時,程式會把這個動作加到一個內部佇列 (self.queue) 裡。一個獨立的執行器會從佇列中取出動作,然後使用 timer.set_timeout() 來安排動畫。
同步錯覺:使用者在 robot_actions 函式中,必須手動使用 timer.set_timeout() 來安排下一個動作的執行,並給予足夠的延遲時間(例如 timer.set_timeout(check_and_move, 1000))。這種方法非常脆弱,如果延遲時間設定不當,機器人動作就會出錯。
優點:不依賴任何特定的非同步框架。
缺點:
難以維護:程式碼會變得非常複雜,容易陷入俗稱的「回調地獄」。
不夠靈活:無法在一個動作完成後立即執行條件判斷(例如 if robot.object_here()),因為判斷程式碼必須被包裝在回調函式裡,並等待非同步動作完成。
不夠直觀:使用者必須理解 timer.set_timeout() 的非同步行為,這與 Reeborg 的同步直觀感相去甚遠。
brython_robot4.py 程式庫的核心是 Brython 的 browser.aio 協程框架。
執行流程:當呼叫 await robot.move() 時,Brython 會暫停這個協程的執行,並將控制權交還給瀏覽器的事件迴圈。底層的 AnimatedRobot.move() 方法會執行動畫,並在動畫完成後,使用 await aio.sleep() 重新啟動協程。
同步錯覺:await 關鍵字讓程式碼看起來像是同步執行。當程式碼遇到 await 時,它會等待非同步操作完成,然後再繼續執行下一行程式碼。這讓使用者可以直觀地將動作串聯在一起,例如 await robot.move(); await robot.turn_left();。
優點:
高度直觀:使用者程式碼簡單且易於閱讀,完全符合 Reeborg 帶來的「同步」體驗。
強大穩定:由 browser.aio 框架管理執行順序,避免了手動設定延遲時間的錯誤,確保了動作的正確性和順序性。
更接近原生 Python:async/await 是 Python 3.5+ 的標準語法,讓使用者學到的技能更具通用性。
總結
特性 brython_robot3.py brython_robot4.py)
核心技術 手動實現的佇列和回調函式 browser.aio 協程框架
使用者程式 必須使用 timer.set_timeout 來安排動作 使用 await 關鍵字,程式碼看起來是同步的
穩定性 易因手動延遲時間設定不當而發生錯誤 穩定可靠,由框架保證執行順序
程式碼複雜度 較高,容易產生回調地獄 較低,程式碼線性直觀
結論: brython_robot3.py 是土法煉鋼,而 brython_robot4.py 則是採用了現代化的、更強大和更優雅的程式設計模式。新版讓使用者可以專注於機器人的邏輯,而不需要擔心底層的非同步細節。
Optimization:
一位製造商想設計一個開口式的盒子,其底部為長方形,寬為 x、長為 y,且總表面積為 80 平方公分。 請問要使盒子的體積達到最大,應該選用哪些尺寸?
直接利用 Python 以 Differential Evoluation 運算: de_volume_max.py
參考:
de_volume_max.c
也可以採網頁前端使用 Brython,後端採 Python 執行運算後將資料傳回網頁: brython_w_flask.7z
Brython <<
Previous Next >> Robot_ex