即使是像口袋里的手機這樣小型的電腦,也可以在短短幾秒鐘內(nèi)完成超過數(shù)千次的操作。更令人驚奇的是,它們可以基于內(nèi)存中的數(shù)據(jù)以及程序員編寫的邏輯進行決策。這種決策能力在人們所思考的人工智能問題中是極為關(guān)鍵的要素,當(dāng)然也是創(chuàng)建有趣的智能應(yīng)用的重要組成部分。本章將探索如何在應(yīng)用中編寫判斷選擇邏輯。
正如我們在第14章所討論的,應(yīng)用的行為由一系列的事件處理程序所定義。每個事件處理程序針對某個特定事件進行響應(yīng),并實現(xiàn)特定的功能。然而,這種響應(yīng)的過程未必是按線性順序來實現(xiàn)各項功能,有些功能只能在一定條件下才能執(zhí)行。像游戲類的應(yīng)用可能就會判斷分數(shù)是否已經(jīng)達到了100,而位置感知類的應(yīng)用可能會問“某個手機是否在某個建筑物的范圍之內(nèi)”。你的應(yīng)用也可以詢問類似的問題,然后根據(jù)答案,繼續(xù)執(zhí)行不同的程序分支。
如圖18-1,當(dāng)事件(Event1)發(fā)生時,無論如何A功能都會被執(zhí)行;然后進行一個檢測判斷:如果檢測結(jié)果為真,則執(zhí)行B1分支;如果結(jié)果為假,則執(zhí)行B2分支;無論執(zhí)行哪個分支,該事件處理程序的其余部分(C)都將被執(zhí)行。
由于像圖18-1這樣的決策圖看起來像一棵樹,因此通常會將這種根據(jù)判斷結(jié)果而選擇執(zhí)行的一段程序稱為“分支”。在這種情況下,你會說, “如果測試結(jié)果為真,則執(zhí)行包含B1的分支?!?/p>
圖 18-1 事件處理程序中,根據(jù)條件測試的結(jié)果執(zhí)行不同分支
App Inventor提供了兩類條件塊(如圖18-2):if塊和ifelse塊。可以從Control抽屜里拖出一個if塊,然后點擊上面的藍色圖標,彈出可擴充的塊,可以根據(jù)需要添加任意多個“else”分支。
圖 18-2 條件塊if及ifelse
可以將任何邏輯表達式(Boolean)插入到if右側(cè)的測試插槽中。邏輯表達式是一個用數(shù)學(xué)等式,它的返回值要么是真(true),要么是假(false)。如圖18-3,邏輯表達式使用關(guān)系運算符(藍色)以及邏輯運算符(綠色),對屬性值或變量值進行檢測。
圖 18-3 用于條件判斷的關(guān)系及邏輯運算符
無論是if塊還是ifelse塊,只有“if”后面的測試結(jié)果為真時,將執(zhí)行“then”右側(cè)插槽中的塊。對于if塊,如果測試結(jié)果為假,程序?qū)⑻鰅f塊,繼續(xù)執(zhí)行if后面的塊;而對于ifelse塊,如果測試結(jié)果為假,將執(zhí)行“else”右側(cè)插槽中的塊。
因此,對于一個游戲來說,可能會插入一個與成績有關(guān)的邏輯表達式,如圖18-4所示。
圖 18-4 用于測試成績值的邏輯表達式
在本例中,如果成績到達100,則播放一個聲音文件。注意,如果測試結(jié)果為假,不執(zhí)行任何塊。如果需要在測試結(jié)果為假時執(zhí)行某些操作,可以使用ifelse塊。
考慮這樣一個應(yīng)用,無聊的時候也許會用到它:在手機上點擊一個按鈕,就可以隨機地撥打一個朋友的電話。如圖18-5,使用一個random integer(隨機整數(shù))塊來生成一個數(shù)字,然后用ifelse對生成的數(shù)字進行判斷,來決定即將撥打的電話號碼。
圖 18-5 用ifelse塊判斷隨機生成的整數(shù)來選擇要撥打的號碼
在這個例子中,random integer的參數(shù)為1和2,意味著將以相等的幾率產(chǎn)生1或2,所產(chǎn)生的隨機數(shù)保存在變量randomNum中。
一旦取得了變量randomNum的值,在ifelse塊中將變量值與1進行比較:如果randomNum的值為1,程序?qū)?zhí)行第一個分支(then),將電話號碼設(shè)置為“111-1111”;如果變量值不為1,測試結(jié)果為假,程序執(zhí)行第二個分支(else),電話號碼被設(shè)置為“222-2222”。無論測試結(jié)果如何,程序都將拔打電話,因為是在整個ifelse塊的下面調(diào)用了MakePhoneCall過程。
許多情況下不只是雙重選擇,即,可選擇的結(jié)果不僅僅是兩個。例如,也許你希望可以給更多的朋友隨機撥打電話,因此就需要在原來的else分支中,再加入一個ifelse,如圖18-6所示。
圖 18-6 外層條件判斷的else分支中加入另一個ifelse條件判斷
在這些塊中,如果第一個檢測條件結(jié)果為真,程序?qū)?zhí)行第一個“then”分支并撥打號碼“111-1111”;如果第一個測試結(jié)果為假,則執(zhí)行外層的else分支,此時將立即進行另一個測試。因此,如果第一個測試結(jié)果(randomNum=1)為假,而第二個測試結(jié)果(randomNum=2)為真,則執(zhí)行第二個(內(nèi)層的)“then”分支,并撥打號碼“222-2222”;如果前面兩個測試的結(jié)果都為假,則執(zhí)行最下面的內(nèi)層的else分支,并撥打第三個號碼333-3333。
注意,在修改過的程序中,隨機整數(shù)生成器(random integer)中的參數(shù)2變成了3,因此,將以相等的幾率生成結(jié)果1、2或3。
這種在一個條件判斷中加入另一個判斷的方式稱為“嵌套”,在本例中,可以稱為“嵌套的if-else塊”,使用這種嵌套的邏輯,可以為隨機撥打電話的程序提供更多的選擇。一般來說,任何程序中都可以使用任意多層的嵌套。
除了嵌套,還可以設(shè)定更為復(fù)雜的檢測條件,即,多于一個等式的檢測條件。例如這樣一個應(yīng)用,當(dāng)你(或你的手機)離開某棟建筑或某個邊界時,手機會發(fā)出震動。這樣的應(yīng)用適用于那些受控人員,警告他們不要遠離法定的邊界;也可以用于家長監(jiān)視孩子們的行蹤;教師可以用它來做自動點名(條件是學(xué)生們都配有Android手機?。?。
例如,我們提出這樣的問題:手機是否在“舊金山大學(xué)哈尼科學(xué)中心”范圍內(nèi)?這樣的應(yīng)用要對4個不同的問題進行一個復(fù)雜的檢測:
手機所在的緯度低于邊界緯度的最大值(37.78034)嗎?
手機所在的經(jīng)度低于邊界經(jīng)度的最大值(-122.45027)嗎?
手機所在的緯度高于邊界緯度的最小值(37.78016)嗎?
本例中使用了位置傳感器(LocatinSensor)組件,即便你沒用過這個組件,也能夠理解這些程序,在第23章中將有更多講解。
使用邏輯運算符and、or及not可以構(gòu)造出更為復(fù)雜的測試條件,可以從Logic抽屜中找到它們。在本例中,先拖出一個if塊以及三個and塊,并將and塊放在if塊的測試插槽中,如圖18-7所示。
圖 18-7 放在if塊測試插槽中的“and”塊(選擇“External Input/外展式輸入”以免塊的排列過寬)
然后拖出幾個塊來組成第一個測試問題,并將其放在and塊的第一個測試插槽中,如圖18-8所示。
圖 18-8 and塊中放入了第一個測試問題塊
如法炮制出其他幾個測試條件,填入其他幾個and的測試插槽中,并將整個if塊放入事件處理程序LocationSensor.LocationChanged中,這樣就寫成了一個檢測邊界的程序,如圖18-9所示。
圖 18-9 每次位置更新時,觸發(fā)該事件處理程序,來檢測是否在邊界之內(nèi)
這些塊的功能是,在每次位置傳感器讀數(shù)更新時做出判斷,如果手機的位置在邊界之內(nèi),則發(fā)出震動。
OK,到目前為止,應(yīng)用已經(jīng)相當(dāng)酷了,但現(xiàn)在我們來嘗試更為復(fù)雜的功能,以便你能充分地了解程序中決策的威力。如何才能讓手機僅在越出邊界時才發(fā)出震動呢?繼續(xù)學(xué)習(xí)之前,自己先想想如何來寫這樣的程序。
我們的方法是定義一個變量withinBoundary,目的是記住傳感器上一次的讀數(shù)是否在邊界內(nèi),并根據(jù)每一次后續(xù)讀數(shù)的測試結(jié)果對變量值進行修改。withinBoundary是一個布爾(Boolean)類型的變量,與保存數(shù)字或文本的變量相比,它保存的值為true(真)或false(假)。舉例來說,如果將變量初始值設(shè)為false,如圖18-10所示,這意味著設(shè)備不在舊金山大學(xué)的哈尼科學(xué)中心范圍內(nèi)。
圖 18-10 變量withinBoundary為初始化為false
對塊做出修改,以便在每次位置信息變化時,對變量withinBoundary進行設(shè)置,并且只有當(dāng)手機越出邊界時,才會發(fā)出震動。說的更明確一些,手機產(chǎn)生震動的必備條件是(1)變量withinBoundary的值為真,即意味著上一次讀數(shù)還在邊界內(nèi);(2)新的傳感器讀數(shù)超出了邊界。圖18-11中是修改后的塊。
圖 18-11 這些塊的功能是:只有當(dāng)手機從界內(nèi)移動到界外時,手機才會震動
我們來仔細地分析一下。當(dāng)位置傳感器(LocationSensor)獲得讀數(shù)時,首先判斷讀數(shù)是否在邊界內(nèi),如果是,將withinBoundary設(shè)置為true。由于我們希望只有在手機越出邊界時才震動,因此在第一個分支中不發(fā)生震動。
如果執(zhí)行的是else分支,我們知道新的讀數(shù)已經(jīng)超出了邊界。此時,我們需要檢查上一次的讀數(shù):盡管這次讀數(shù)超出了邊界,但我們希望僅當(dāng)上次讀數(shù)在邊界內(nèi)時,才讓手機發(fā)出震動。withinBoundary變量會告訴我們上一次的讀數(shù),因此我們會檢查這個變量,如果檢查結(jié)果為真,則讓手機震動。
一旦確認手機從界內(nèi)移動到了界外,還有一件事必須要做,你能猜到是什么嗎?對,需要重新設(shè)置withinBoundary為false,這樣,在下一次收到傳感器讀數(shù)時,手機才不會再次震動。
關(guān)于布爾型變量,還有一點需要提示:檢查一下這兩個if測試,如圖18-12,它們的效果一樣嗎?
圖 18-12 你能說出這兩個if測試的結(jié)果一樣嗎?
答案是“一樣”!唯一的差別在于下邊的提問方式實際上更加老練,而上邊的測試還要將一個布爾型的變量(其值只能是true或false)與true進行比較。如果withinBoundary的值為true,將true與true比較,結(jié)果一定是true;如果變量值為false,將false與true比較,結(jié)果為false。因此,只需要對withinBoundary的值進行檢測,像右邊那樣,其結(jié)果相同,而且編碼更加簡潔。
頭暈了嗎?尤其是最后的部分相當(dāng)復(fù)雜!但這類決策方法是高級應(yīng)用中必須具備的。如果你能一步一步(或者說一個分支一個分支)地實現(xiàn)這些行為,并做到邊做邊測試,我們敢斷言,你會發(fā)現(xiàn),即便是人工智能也不是不可能的。它讓你頭疼,也讓你的大腦獲得了些許邏輯思維的鍛煉,但無疑也是充滿樂趣的。
更多建議: