--- tags: Old Version --- :::info Ch.3: Infinite loops (https://gitlab.com/aesthetic-programming/book/-/tree/master/source/3-InfiniteLoops) ::: # Ch.3: 無窮迴圈(Infinite loops) ![](https://aesthetic-programming.net/pages/ch3_0.svg) --- ## setup() <!-- ![](https://aesthetic-programming.net/pages/ch3_0.png) --> <div> <img style="animation: spin 4s linear infinite;" id="ouruboros" src="https://aesthetic-programming.net/pages/ch3_0.png" /> <style> #ouruboros { mix-blend-mode: multiply; } #ouruboros:hover { filter: invert(1); transform: scale(1.2) rotate(-1turn); transition: 2s; hue-rotate(90deg); } body:has(#ouruboros:hover) #doc { background-color: black; } </style> </div> > 圖 3.1:早期煉金術插圖裡的銜尾蛇,其中附帶煉金術士克利奧帕特拉(Cleopatra the Alchemist)(10 世紀)的文字「萬物一體」(ἓν τὸ πᾶν)。圖片來自維基百科 迴圈(loop)提供了另一種想像,像是蟒蛇啃食尾巴的古老圖騰。在希臘傳說中,銜尾蛇(Ouroborous)代表了出生與死亡的無限循環,也因此表現出一種無限自我更新的處理能力。迴圈除了讓人聯想起自我吞噬和煉金術之外,也和控制與自動化步驟有關,還有那些日常生活中的重複程序,例如在音樂中聽到聲音素材的重複片段。[^1] 在程式設計裡,一個迴圈能夠讓原始碼的片段重複執行,一直持續到滿足特定的條件(例如 true 或 false)為止。實際上,如果條件永遠不會變為假,那麼迴圈就會變成無限的(或沒有終點的)。 數學家和作家奧古斯塔・愛達・拜倫・勒芙蕾絲(Augusta Ada Byron Lovelace)是 19 世紀早期首位描述及導入程式迴圈概念的人。她意識到有史以來第一台自動通用計算機——查爾斯・巴貝奇分析引擎(Charles Babbage’s Analytical Engine)的設計中,可以有重複性操作的概念。迴圈出現在她在針對分析引擎所寫的「G筆記」中[^2],以一個「週期」(cycle)來描述所謂的伯努利數字程式(Bernoulli numbers program),如插圖所示。它利用兩個迴圈來示意一系列帶條件指令的重複,[^3]從而大幅減化了重複編輯與操作需要耗費的精力。因此,迴圈為重複操作節省了時間。 ![](https://aesthetic-programming.net/pages/ch3_1.jpg) > 圖 3.2:伯努利數字引擎計算圖,來自愛達・拜倫・勒芙蕾絲的「G筆記」(1843)。圖片來自維基共享資源 迴圈在當代程式編輯中深受這些早期以圖表形式表示、處理機器重複操作的洞見的影響。高階程式語言如 p5.js 包含這個迴圈概念,允許重複執行原始碼片段,例如 `draw()` 函式將持續運行直到程式停止或使用語法 `noLoop()` 來停止迴圈。迴圈是寫程式時最基本及強大的部分概念。 本章的主要範例是有「跳動者」(throbber)[^4] 之稱的圓形旋轉載入圖標,它代表一個程式正在進行下載或大量計算。我們認為這個符號令人玩味,因為它顯示了隱藏的機器勞動,它的差異介於我們知道與不知道之間,在任何已知的電腦運行期間存有多種複雜的時間性共同進行。[^5] 它是一個說明迴圈是如何運作的絕佳標誌,讓我們能夠思考可感知串流與電腦邏輯的糾纏,以及我們如何經由數位媒體來體驗逝去的當下。[^6] 當我們從靜態的對象轉向為動態時,跳動者的動畫能夠引導程式編輯與思考轉換(例如旋轉和平移)的相關任務,也將做為一個適合的分析對象,讓我們思考迴圈、相關的時間元素和概念化地看待與時間相關的語法。 --- ## start() 我們通常在下載或串流時遇到的旋轉圖標無所不在。它表明某個操作正在進行中,但究竟發生了什麼、以及需要多長時間,不完全清楚。沒有進度或狀態的指示,像是進度條。我們看到圖標在旋轉,但它對背景中發生的事情或時間跨度幾乎沒有解釋。我們會學習寫「跳動者」的程式,然後再來看約翰・P・貝爾(John P. Bell)的〈星號繪畫〉——它藉由重複在畫布上留下繼打開程式以來經過的毫秒數來創立一系列星號——幫助你深入了解寫程式使用動態轉換和迴圈結構的方式,同時深入了解計算過程的時間操作。 --- ## 課堂練習(解碼) > >如前所述,本章寫的程式從靜態對象轉變為混合靜態與動態的對象。我們的例子是圓形及旋轉的,好像它正在吃自己的尾巴。 > >![](https://aesthetic-programming.net/pages/ch3_2.png) > 圖 3.3:範例碼的執行檔——草圖 3_1 > >RunMe, https://aesthetic-programming.gitlab.io/book/p5_SampleCode/ch3_InfiniteLoops/sketch3_1/ > >你能用自己的話來描述執行檔裡的各種元素以及它們是如何在電腦上運行的嗎? > >1. 推測 >>- 根據您在螢幕上看到/感知到的東西,請試著描述: >>>- 有哪些元素?列出一份清單描述它所具備的特色。 >>>>- 有哪些東西在動,哪些不動? >>>>- 中間有多少個橢圓? >>>>- 試著調整視窗大小,看看會發生什麼事。 >>>> >>-更進一步的問題: >>>- 如何設定背景顏色? >>>- 橢圓如何旋轉? >>>- 如何讓橢圓淡出並旋轉到下一個位置? >>>- 如何在同一個草圖中同時定位靜態的黃線以及動態的橢圓? >>> >2. 實驗 >>- 隨意更改一下原始碼 >>- 嘗試改變部分的參數,例如`background()`, `framerate()`, 以及`drawElements()` 裡面的變數 >>- 在 `p5.js` 網站參考文獻查看一些新的函式說明,例如 `push()`、`pop()`、`translate()`、`rotate()` >3. 對應 >>- 將你的推測發現/特色對應到原始碼。哪個區塊的程式碼與你的發現相關? >>- 你能否辨識出哪些部分的程式碼反應了你所推測的元素? >4. 技術問題/議題 >>- `let cir = 360/num*(frameCount%num)`; (請參見第 21 行) > 使用除法後餘數的「模運算子」[^7] 運算方式,試著解釋這一行的用意及作用? >5. 其他概念性問題 >>- 你經常在哪裡看到此圖標?你的經驗是什麼? >>- 如果此圖標與等待(或浪費)時間有關,那麼你認識多少與機器相關的時間呢? >>- 同步是機器時間的其中一個重要層面,你能描述同步過程方面的經驗嗎? ## 原始碼 ```javascript= //跳動者 function setup() { //建立一個繪圖畫布 createCanvas(windowWidth, windowHeight); frameRate (8); //試著調整這項參數 } function draw() { background(70, 80); //請參見這個語法設定透明度的方式 drawElements(); } function drawElements() { let num = 9; push(); //把物件移到中央 translate(width/2, height/2); /* 360/num >> 每個橢圓形移動的角度; frameCount%num >> 取得餘數來決定橢圓應該落在八個位置上的其中哪一個位置。*/ let cir = 360/num*(frameCount%num); rotate(radians(cir)); noStroke(); fill(255, 255, 0); // x 參數代表了橢圓距離中心點的位置。 ellipse(35, 0, 22, 22); pop(); stroke(255, 255, 0, 18); //靜態的線條 line(60, 0, 60, height); line(width-60, 0, width-60, height); line(0, 60, width, 60); line(0, height-60, width, height-60); } function windowResized() { resizeCanvas(windowWidth, windowHeight); } ``` --- ## Function p5.js 函式以 `function() {}` 語法開頭,包含執行特定任務「自身所擁有的一段程式碼」[^8]。 p5.js 中最基本的內建函式是 `setup()` 和 `draw()` ,它們與在程式運作環境裡設定特定目的程式碼有關,有時也隨著時間的推移來做事。範例中提供的其他內建函式,例如 `windowResized()` 的功用,僅用於如果有任何調整視窗的事件,則讀取畫布大小。畫布大小未設定為固定尺寸,而是按照程式碼跟著視窗大小調整。在前面的章節中討論過:`createCanvas(windowWidth, windowHeight)`;。函式 `windowResized()` 代表了一個「事件聆聽器」——在電腦程式中偵測一個事件發生的過程或函式——在程式碼的啟動層級,不僅運行一次,而是「持續性的」。它專門「聆聽」視窗尺寸調整的事件,類似於上一章介紹的 `mouseIsPressed()` 等其他聆聽事件。 `windowResized()` 函式被認為是異步的(asynchronous),這意味著其他的事件與程式也可以與主要的流程同時發生,例如繪製形狀。 除了內建函式,範例中還包含自定義函式 `function drawElements()`;第 13 行在 `draw()` 函式中調用了: `drawElements()`;。在 JavaScript 中定義函式相對簡單。鍵入關鍵字「function」,然後在後面加上你要為函式命名的名稱。函式名稱「drawElements」讓你了解該函式的作用,即繪製橢圓、特定大小、位置和顏色的線,以及繪製順時針旋轉或靜態保持原位的橢圓和線。有很多方法可以得出相同的結果,但由於我們仍處於學習寫程式的早期階段,因此我們將研究一個可以做同樣事情但更符合學習進度的案例。考慮到這點,為了闡明一些關鍵概念特意以較低效率的方式編寫程式碼。 程式設計師喜歡將大的任務拆分成更小的動作及步驟,因此它們更易於構建、管理、除錯、閱讀,並且有助於多個程式設計師協作。在範例中 `drawElements()`; 函式與 `draw()` 函式分開,清楚表明這一部分的程式碼與繪製畫面上的各種元素有關。當然,您也可以將橢圓和直線的繪製分開,如何妥善地劃分不同任務是主觀的並且取決於情境。 還有另一種類型的函式,你可以用參數來指定特定的任務,傳到函數中並接收傳回來的值。請參閱下面的範例: ```javascript= let x = sum(4, 3, 2); print(x); //在function sum的函式中,把4的值賦予a、3的值賦予b、2的值賦予c function sum(a, b, c) { return a + b + c; //回傳結果 } ``` > Output:“9” --- ## 課堂練習 > >嘗試將上述程式碼輸入/複製到自己的草稿,它在 web console 的區域將輸出數字 9,因為這是值 4、3 和 2 的總和結果。這些值被稱為傳遞給函式(即 `sum()`)的「參數」(arguments)。在範例中,做為變數的 a、b 和 c 同等於做為參數的實際值 4、3 和 2,但變數的值可以更改。「sum」的函式可以重複使用,你也可以賦予它其他參數/值,例如另一行程式碼 `let y = sum(5,6,7)`; y 傳回來的值將會是 18。你可以嘗試提出自己的函式和參數。 > --- ## 轉化 通常,與轉化相關的函式[^9]應用於變化二維或三維的元素或對象。在「跳動者」的程式碼範例中,使用了兩個特定的轉化函式來移動畫布並創建轉換對象的錯覺。 (重要的是知道轉換是在畫布的背景層級完成的,而不是在單個形狀/對象的層級。) ![](https://aesthetic-programming.net/pages/ch3_3.png) >圖 3.4:在畫布層級移動坐標系。 圖片來自 processing.org 1. `translate()`:此函式在視窗內顯示/移動對象。例如,將畫布移動到中心也會將整個草稿定位在中心(`translate(width/2, height/2);`)。橢圓以 `ellipse(35, 0, 22, 22)` 繪製,以 (35, 0) 做為 x 和 y 坐標,「22」做為大小。如果我們前面沒有 `translate()` 函式,橢圓將被放置在左上角(因為 x 坐標值「35」是旋轉橢圓距中心位置的距離)。通過使用 `translate()` 函式將坐標原點移動到中間,橢圓被放置在畫布的中間,因為坐標原點 (0,0) 已經移動到了螢幕的中心。在上一章關於坐標系的空間維度基礎上,「平移」(translate)為移動畫布和定位對象增添了另一個層次的思考。 2. `rotate()`:跳動者的程式碼使用函式 `rotate()` 讓橢圓旋轉了特定的度數。旋轉函示的初始設定單位是弧度。因此,程式碼編寫為`rotate(radians(cir));`。函式`rotate()` 在其初始設定下採用弧度,但如果你想更改為度數,只需添加程式碼`angleMode(DEGREES)`。 為了繼續擴展空間關係,在此範例藉由使用 `draw()` 裡面的其他時間相關語法一起運行的 `rotate()` 函式,使時間和空間的糾纏變得明顯。總共有 9 個橢圓(表示為 `let num=9;`),每個橢圓與下一個橢圓相距 40 度(即 0.968 弧度),這源自“360/9”。一個圓有 360 度,要隨著時間旋轉橢圓,它需要時間元素來計算何時、如何移動以及移動到哪裡。這就是函式 `frameCount()` 的工作原理,因為它計算了程式自啟動以來畫面影格的幀數。[^10] 第 21 行 `let cir = 360/num*(frameCount%num);`說明如何使用「模」運算來查找餘數,或除以另一個值後剩下的數字。因此,變數 `cir` 的值限制為 40 的倍數:「0、40、80、120、160、240、280 和 320」。在 `cir` 值的基礎上,程式按照這樣的順序隨著時間的推移,根據原始位置一個接一個地旋轉,然後不斷重複。 --- ## push() and pop() `push()` 和 `pop()` 函式通常分別用於保存當前樣式和恢復原來設置。樣式如顏色,設置如旋轉和平移。在範例中,旋轉僅應用於居中的橢圓,而四邊的線條維持固定。 用以下程式碼幫助解釋: ```javascript= function drawElements() { let num = 9; push(); //把物件的定位移到畫面中央 … pop(); stroke(255, 255, 0,18); //靜態線條 line(60, 0, 60, height); line(width-60, 0, width-60, height); line(0, 60, width, 60); line(0, height-60, width, height-60); } ``` 最後四行繪製了四條靜態黃線。 從邏輯上來講,translate 和rotate 函式也該應用在這些線上,但因為繪製完所有橢圓後立刻放置了 `pop()` 函式,所以它不會影響這些線(見圖 3.5)。 但是如果你將 `pop()` 移到尾端,那麼這些線也會旋轉及平移(見圖 3.6)。 這說明了如何使用 `push()` 和 `pop()` 來保存和恢復樣式,以及它們的位置如何重要。[^11] ![](https://aesthetic-programming.net/pages/ch3_6.png) >圖 3.5:pop() 函式的不同位置 - 四個靜態黃線 ![](https://aesthetic-programming.net/pages/ch3_7.png) >圖 3.6:pop() 函式的不同位置 - 四個旋轉的黃線 --- ## 課堂練習 >1. 更改參數/值,以及範例程式碼的位置/順序,來了解函式和語法,例如 `num` 變數、平移 `translate()` 與旋轉 `rotate()` 等轉化函式,以及保存 `push()` 和恢復 `pop()` 樣式。 >2. 我們已經解釋瞭如何使用旋轉 `rotate()` 來顯示不同旋轉度數的橢圓,但是草圖中每個橢圓的淡入淡出呢? (提示:因為這個淡入淡出是反覆性的,`draw()` 函式下的 `background()` 語法是產生這種效果的關鍵。) >3. 這個練習是關於構建程式碼。你將如何重構範例的程式碼,以便其他人更容易理解,但仍保持相同的視覺效果?沒有正確或錯誤的答案,但下面的一些提示可能有助於討論: >4. 你可以重命名函式和/或添加新函式 >5. 除了 `drawElements()`,是否也可能有 `drawThrobber()` 和 `drawLines()` 之類的東西? --- ## 星號繪畫 下一節將從重複和規律,轉向重複和差異。 藝術家及軟體開發人員約翰・P・貝爾製作了一幅名為〈星號繪畫〉的作品,[^12] 由許多類似跳動者的旋轉圖案所組成,但是每個跳動者(或他所謂的「星號」)以各不相同的顏色和質地旋轉。貝爾使用的許多語法都與時間相關,例如計時器的設置、以毫秒為單位的計算、旋轉速度、開始新迴圈之前的等待時間等,藉由寫程式操縱與時間有關的功能來重新-協商時間、來「開啟另一種實踐和體驗時間的可能性」。 [^13] 此外,仔細觀察,星號不是幾何形狀而是由一系列數字構成,這些數字是排成一直線的毫秒計數器。 ![](https://aesthetic-programming.net/pages/Asterisk_Painting.gif) >圖 3.7:約翰・P・貝爾的〈星號繪畫〉(2014 年)。 由藝術家提供 根據貝爾的說法, >「星號繪畫的程式被設計為重複印出自繪畫開始以來經過的毫秒數來創建一系列星號。 如果讓它自己運行,它會這樣做; 然而,當在真實系統上啟動時,外部延遲可能會使我的作品星號看起來更像點(⋯)」 --- ## 原始碼 原來的作品是用 Processing 編寫的,經過修改,移植到 p5.js。 這個程式比第一個複雜得多,但我們仍然希望做為本章的補充,因為它有助於示範在草稿中進一步開發迴圈的潛力,並更深入地思考無窮迴圈,以及使用與時間相關的語法。 ```javascript= /* 星號繪畫 by John P.Bell (http://www.johnpbell.com/asterisk-painting/) 使用 Processing 製作的原始碼: https://tinyurl.com/AsteriskPainting 引入p5js、改寫與增加註記 by Winnie Soon, 最後更新: 25 Aug 2020 修改紀錄: 1. 顏色模式改為由一個變數來決定 因為 push/pop 函式會復原之前的色彩填充方式。 2. 移除字體 3. 修改背景顏色 4. 增加文字尺寸 5. 移除載入代表圖像 6. 修改畫布尺寸並反映到星號數量 7. 顯示一個黑色的計數器在左下角 8. 增加更完整的註釋 9. 在 integer 回傳一個數值 */ let xDim = 1000; //畫布尺寸-寬 let yDim = 600; //布尺寸-高 let timer = 0; let speed = 100; //旋轉的速度,初始設定為100 let maxSentences = 77; //最多的句數,初始值為: 77 let sentences = 0; let xPos = [1, 2, 3, 4, 5]; //初始值為: 8 欄 let yPos = [1, 2, 3, 4]; //初始值為: 5 列 let xCtr = 0; let yCtr = 0; let waitTime = 10000; let itr = 0; // 重複的次數 let milliStart = 0; let currentMillis; let fillColor; function setup(){ createCanvas(xDim, yDim); background(240); /*以一個xPos[]陣列來計算每個星號的 x 座標,陣列的開始為 index[0]*/ for(let i = 0; i < xPos.length; i++) { xPos[i] = xPos[i] * (xDim / (xPos.length+1)); } /*以一個yPos[]陣列來計算每個星號的 y 座標,陣列的開始為 index[0]*/ for(let i = 0; i < yPos.length; i++) { yPos[i] = yPos[i] * (yDim / (yPos.length+1)); } fill(0); //左下角計數器的顏色 textAlign(LEFT, CENTER); text(itr, 10, yDim-30); //顯示計數器 fillColor = color( floor(random(0, 255)),floor(random(0, 255)),floor(random(0, 255)) ); } function draw(){ //millis 的意思是自程式開啟以來的毫秒數,類似每秒的畫面幀數 frameCount currentMillis = floor(millis() - milliStart); if(currentMillis > timer){ push() translate(xPos[xCtr], yPos[yCtr]); //列與行 rotate(radians((360/8)* (millis()/speed))); //自行旋轉 timer = currentMillis + speed; //到下一個迴圈的時間 textSize(12); fill(fillColor); /* 這一段是關於把時間字串以星號的方式排列, 而它總是從0開始。 nf():將數字的格式轉化成字串並增加一個零在前面 [https://p5js.org/reference/#/p5/nf] 小數點前有三位數,小數點後沒有數字。*/ text(nf(currentMillis, 6), 3, 0); sentences++; if(sentences >= maxSentences){ //每個星號達到最大數量的句數時 xCtr++; //移至下一個陣列 //到達最大的欄數之後,需要換到下一行 if(xCtr >= xPos.length) { xCtr = 0; yCtr++; //換到下一行 /* 當程式達到了畫面上所允許的最多行數 (例如:在達到最多欄數之後); 畫面被填滿 > 重置一切並更新計數器 */ if(yCtr >= yPos.length){ yCtr = 0; background(240); //增加計數器(迭代) itr++; pop(); //計數器的顏色 fill(0); //再次更改計數器的顯示 text(itr, 10, yDim-30); //在到下一輪開始第一個星號之前的等待 let wait = floor(millis() + waitTime); while(millis() < wait){} //重置開始的時間 milliStart = millis(); //重置計時器 timer = 0; push(); } } sentences = 0; fillColor = color( floor(random(0,255)),floor(random(0,255)),floor(random(0,255))); } pop(); //回復到原有的狀態 } } ``` ## 課堂練習 >RunMe,https://aesthetic-programming.gitlab.io/book/p5_SampleCode/ch3_InfiniteLoops/ > >1.閱讀上面的原始碼。程式碼註釋提醒: >>1. // 表示單行註釋 >>2. /* ... */ 表示多行註釋 >2. 使用本章前面介紹的解碼方法,嘗試推測、實驗並將您的想法對照到原始碼。 > >- 推測:描述您在螢幕上看到/體驗到的內容。 >>- 螢幕上有什麼元素? >>- 螢幕上有多少個星號,它們是如何排列的? >>- 什麼在移動,它是如何移動的? >>- 是什麼讓星號旋轉,它何時停止創造新的星號? >>- 你能在這個草稿中找到與時間相關的語法嗎? > >- 實驗:更改一些程式碼的參數 >>- 嘗試更改一些值,例如全域變數的值 >>- 有哪些語法和函示是你不知道的? (在 p5.js 參考文獻中查看它們。) > >- 對照:將推測中的元素對照到原始碼 ## Arrays 為了能更深入了解原始碼,你只需要再多學幾個基本的程式設計概念。 第一個是「陣列」,通常被認為是一串的資料,與之前的變數和資料類型等概念有關。 如果我們需要處理一大塊數據,比如説一組的字詞,你可以使用陣列而不用分別地創造變數。 例如: ```javascript= //範例 let words = [] //陣列 -> 從0開始 words[0] = "what"; words[1] = "are"; words[2] = "arrays"; console.log(words[2]); //顯示出:arrays console.log(words.length); //顯示出:3 ``` 我們可以根據之前使用變數的方法及類似的結構: 1. **宣示(Declare)**:想出一個你想用來存儲值的陣列名稱。 `let words = [] `中的符號 [] 表示「words」被建構為一個陣列,但裡面是有多少值是未知的,而且這行程式碼也尚未指定。 2. **初始化/賦值(Initialize / Assign)**:根據上面的範例,有三個文字的值要存儲在引號中(這表明它們是「字串」(String)的資料類型):「what」、「are」和「arrays」。由於陣列是一個值的列表,需要被各別標識,所以用方括號內的「陣列索引」來表示每筆資料在陣列中的位置。它以 [0] 做為第一項開始,然後是 [1] 做為第二項,依此類推。因此 `words[0] ="what"` 表示陣列 words 的第一個索引項目的資料類型是字串,值為「what」。 3. **重複(使用)**:`console.log()` 函式是一個說明如何檢索和使用資料的範例、如何在 web console 區域顯示資料或如何在畫布上繪圖。 語法 `arrayname.length` 用於詢問陣列中有多少物件。 讓我們來看看下面星號繪畫的範例: ```javascript= let xPos = [1, 2, 3, 4, 5]; let yPos = [1, 2, 3, 4]; ``` 這和宣示一個陣列的方式稍微不一樣。它將宣示、初始化/賦值合併到同一行,用讓(let)這個詞來宣示 xPos 和 yPos 為陣列的名稱,然後將數值分配給陣列來做索引,兩個索引分別指的是第幾列和第幾行。可以這樣想:程式需要知道在移動到下一行之前應該在螢幕上繪製多少個星號以及何時重新啟動(星號會以達到最大行數和列數的方式來填滿整個畫面。) 由於陣列索引以 `[0]` 開頭,因此每個索引的值都以這種方式來對應: `let xPos = [1,2,3,4,5];` → `xPos.length` 為 5,這表明該陣列中存儲了 5 個值:xPos[0] = 1, xPos[1] = 2, xPos[2] = 3, xPos[3] = 4, xPos[4 ] = 5. `let yPos = [1,2,3,4];` → `yPos.length` 為 4,這表示該陣列中存儲了 4 個值:ypos[0] = 1, yPos[1] = 2, yPos[2] = 3, yPos[3] = 4. 上述兩個陣列以 x 和 y 坐標的形式來存儲每個星號的中心位置。 還有一些添加或刪除陣列索引的方法: `array.push(value)`[^14] → 在陣列尾端添加一個值。例如:`xPos.push(6)` 將索引擴展為 `xPos[5] = 6`。 `array.splice()`[^15] → 這將從陣列索引中刪除一個範圍,或刪除現有索引,並用新索引替換它其他值。 ## 條件語句 上一章對條件語句的討論會讓你更容易理解〈星號繪畫〉的程式碼。 我們依循程式中內建的條件結構(if-then),以便知道何時從一個星號移動到下一個星號。 ```javascript= if(sentences >= maxSentences){ //達到每個星號所含句數的最大值 //移動到下一個星號來繼續; } ``` 變數 `maxSentences` 的值為 77(請參閱原始碼中的第 5 行),因此每個星號至多包含 77 個句子(或是說以句子的形式來包含的一串數字)。 另外一個 `sentences` 變數則用來計算句子數量,程式會檢查當前句子的數量是否已達到最大值。 「如果」(if)星號達到 77 個句子「則」(then)移動到下一個星號,`sentences` 計數器將重置為零(第 84 行)並再次開始計數。 該邏輯重複運作在 `draw()` 函式中的所有星號。 --- ## 迴圈(Loops) 迴圈的核心概念是讓你多次執行一個區塊的程式碼。例如,如果你需要繪製一百條垂直的線,一條接著一條垂直,你當然可以使用:`line()` 語法來編寫一百行程式碼。 “for-loop”允許重複執行程式碼,提供一種繪製一百次線的有效方法,藉由設置條件結構,計算已繪製的行數,並計算行數的最大值。同樣,在這幅圖畫中,有一些元素需要重複運行,但這裡有一個終點,例如以畫布的寬和高為基礎,使用 x 和 y 坐標精確計算每個星號的中心點。知道畫布有多少列和行可以讓我們知道繪製每個星號中心點的值。 要構建 for-loop,你需要問自己: >- 有哪些事情/動作是你想按某種順序或模式來重複的? >- 更具體地想,什麼是條件式的結構,什麼時候可以停止迴圈? >- 當此條件被滿足或不被滿足時,你想要做什麼? 以下摘自〈星號繪畫〉(第 20-29 行): ```javascript= /*以一個xPos[]陣列來計算每個星號的 x 座標, 陣列的開始為 index[0]*/ for(let i = 0; i < xPos.length; i++) { xPos[i] = xPos[i] * (xDim / (xPos.length+1)); } /*以一個yPos[]陣列來計算每個星號的 y 座標, 陣列的開始為 index[0]*/ for(let i = 0; i < yPos.length; i++) { yPos[i] = yPos[i] * (yDim / (yPos.length+1)); } ``` 細看 for-loop 的結構: ——————————————————————————— |    如果條件不符合,迴圈就會停止    | |          ↑            | | 宣示+初始化  條件  遞增/遞減     | |    <-><-----><->      | | for(let i=0; i < xPos.length; I++) {        | |  xPos[i] = xPos[I]*(xDim / (xPos.length+1)); | | }                       | |   <-----------------> | |            動作:         | |           ↓           | |    當條件成立時需執行的步驟/計算    | ——————————————————————————— >圖 3.8 一個 for-loop 圖 3.8 顯示了 for-loop 的組成包含了: 1. **一個變數宣示和初始化**:通常以0開頭 2. **特定條件**:滿足條件的標準 3. **動作**:當條件滿足時你想要讓什麼事情發生 4. **循環到下一個**:用於下一次迭代(通常是遞增/遞減)。 上面這段程式碼示範了如何根據(每個星號的中心點 [x, y]的)x 和 y 坐標來設定星號的位置。由於在全域變數中定義了 5 列 (xPos) 和 4 行 (yPos),因此程式需要精確地知道坐標。定位位置的整體公式,例如xPos,就是將畫布的寬度除以水平方向的星號數,再加一個星星(見圖3.9)。因此,程式碼可以理解為:以0為起點計算每次重複 `xPos[i]` 的時候。另外,每次重複都會將 count 加 1,直到達到一行中的最大星號數(`i < xPos.length`)。 ![](https://aesthetic-programming.net/pages/ch3_5.png) >圖 3.9 每個星號的 xPos 在我們的教學中,我們描述了另一個使用 for-loop 的例子,以進一步闡明其用途並示範重複繪製物件。這個例子(見圖 3.10)繪製了 20 個橢圓,每個橢圓的距離為 20 個像素。 ![](https://aesthetic-programming.net/pages/ch3_8.png) >圖 3.10 使用 for-loop 在畫布上繪製 20 個橢圓 ```javascript= let x = 20; function setup() { createCanvas(420, 100); background(200); for (let i = 0; i < 20; i++) { ellipse(x, 45, 15, 15); x += 20; } } ``` 在簡單繪製的這個橢圓中,關鍵是區域變數 i(見上面第六行,用於開始計算橢圓:`let i = 0;`,以及設置應該畫多少橢圓:`i < 20`,並計算每次橢圓增加的數量:`i++`)。全域變數 x 用來確定每個橢圓的位置(根據 x 軸或可以描述為距離的位置),並確保程式每次迭代都會增加 20 個像素:`x+=20`。透過這種方式,我們使用 for-loop 來繪製多個橢圓,而不是使用有固定 x 和 y 坐標的 20 行程式碼。 「while loop」是另一種用於執行迭代的迴圈。語句一直執行到條件為真,一旦為假就停止。 例如,〈星號繪畫〉第 76 行中的 `while(millis() < wait){}` 告訴電腦如果 `millis()`[^16] 的值小於 `wait` 變數的值,則什麼也不做。 `millis()` 是一種與時間相關的語法,它會回傳自程式啟動以來的毫秒數,這使得它類似 `frameCount()`。一旦條件為假(即`millis()`不再小於 `wait`),迴圈將結束,程式可以繼續下一行。當所有星號都繪製完畢後,在此範例的程式尾端,程式需要等待一段時間才重置(清除)畫布並重新開始。因此,這個 while-loop 有暫停、凍結程式運行的作用,因為在開始和結束括號之間實際上沒有任何指定。 --- ## While() 如前所述,只要指定的條件為真,迴圈就會持續執行一段程式碼。在本章的結尾部分,似乎應該強調 while-loop 和 for-loop 允許基於已設定條件重複執行程式碼。這個迴圈可以被認為是一個重複的「if」語句,它提供了一種挑戰線性時間傳統結構的好方法,並展現了電腦如何以不同的方式利用時間。程式設計挑戰了許多我們對時間先入為主的看法,包括它是如何組織的、如何使用各種特定於時間的參數和條件,來呈現當下的樣貌,就像跳動者的情況一樣。我們希望能清楚呈現,機器時間與人類時間在不同的範疇中運行,全球網絡基礎設施和即時電腦計算的概念將這些時間更進一步複雜化。 開始和結束一個給定的過程,意味著什麼,成為一個哲學問題。在《電腦做為時間關鍵的媒介》(The Computer as Time-Critical Medium)[^17],沃爾夫岡・恩斯特(Wolfgang Ernst)釐清了時間對於電腦操作和任務執行的存有論的重要性。他指出程式設計語言層面上,諸如可程式設計性、反饋和遞歸等等的關鍵問題,而我們希望本章提供的範例也能產生類似的共鳴。[^18] 精確的技術細節對討論很重要,他的範例是關於「時間如何在電腦中以不同的方式計算,例如時鐘的訊號。 恩斯特的「微時間性」(micro-temporality)概念很有用,因為他對程式設計的時間性問題提高了關注,在討論軟體的時候,時間性經常被忽略,此外,也進一步檢視在時間哲學中,技術或數學的層面如何被當作已決定的(deterministic)而被忽視。[^19] 迴圈提供了另一種的時間想像。在一場會議發表的論文「⋯⋯另一種迴圈的永恆」(...... Else Loop Forever)中,恩斯特將這個討論與「不及時性」(untimeliness:過早的、不合時宜的、不朽的)相關聯。[^20] 他利用了支持圖靈計算、惡名昭彰的「停機問題」來談一個電腦程式在給定所有可能輸入條件下,是否會結束運行或永遠繼續運行下去。在他 1936/37 年的文章「關於可計算的數字,以及對可判定性的應用」中,圖靈斷言解決停機問題的通用算法是不可能的,這導致了圖靈機器的數學定義。 [^21] 這個「可判定性」,或者恩斯特所說的「結束」,強調了更廣泛的時間運算概念,以及電腦在無窮迴圈中永遠期待自己是「永無止境」的。也許「跳動者」 圖標是一個很好的比喻,就無法預測傳輸條件的質量而言,動畫圖形以這種方式描繪了一種不確定感,這是微時間性深層步驟的基礎。[^22] 與任何傳統的敘事相反——有開頭、中間和結尾——恩斯特指出,電腦的記錄可以無止盡地重演:「沒有內在的結局感」,做為一種「思考時間的狀態」。[^23] 「沒有幸福的結局」也讓恩斯特詳細闡述新的時間結構,這些結構不再與傳統敘事結構或「歷史終結」的終結邏輯一致。[^24] 我們的第一個跳動者例子暗示了這種開始和結束的模糊。時間複雜性透過回顧圖靈對人工智慧的推測而得到進一步發展,無論一台機器的有限狀態是否能在制定的時間內意識到它的「有意識」狀態,以及它是否需要一種結束感才能具備功能性。很明顯,有限狀態的機器是有步驟的,因為它們像發條一樣在時間上操作序列的、線性的、個別的事件,但正如恩斯特提醒我們的:「沒有自動步驟可以為任何程式做決定,無論是它是否包含一個無窮迴圈。」[^25] 借鏡馬丁・海德格(Martin Heidegger)的「存在與時間」(being-in-time)[^26] 以及人類對生命終結的認識,這些銘刻了做為人類意味著什麼時間意義的相關知識,恩斯特說:「人類生活在隱含的意識中,即他們的死亡已經是過去的未來了。」[^27] 這種迴圈延遲的終點在存有論上隨著電腦運作而加劇,將存在的終點做為對人類和機器展開的關鍵時間條件。撇開對海德格哲學的深入討論不談,這對於迴圈討論的重要性似乎反映了生活過的時間的複雜性。程式設計設法在這裡提供洞察力和創造性的機會,就像即時寫程式碼的情況一樣,在此期間,程式設計師與正在運行的系統進行互動,而這個系統在等待新程式語句發揮效令前後都不會停止。[^28] 我們甚至可以開始推測軟體如何不僅僅與生活時間即時同步,甚至是製造了它,而我們希望本章中的兩個例子能幫助從概念和技術上思考無止盡的、循環的、條件的和時間性的的交集。我們甚至可以說,程式設計允許對技術如何在我們的時間體驗中發揮關鍵作用的時間關鍵的理解,不僅是我們如何建置模型,而且在於我們如何能夠創造新的開始和結束。 --- ## MiniX:設計一個跳動者 **目標:** > - 反思數位文化中的時間性,以此設計一個跳動者的標誌。 > - 實驗各種電腦語法、動畫效果以及轉換。 **更多靈感:** 看看其他關於跳動者的作品以及其他人如何將他們的想法置於語境中。 > - 〈七夕〉 by Yurika Sayo (n.d.) ,含原始碼,https://www.openprocessing.org/sketch/926326。 > - LOADING (THE BEAST 6:66/20:09) by Gordan Savičić (2009),https://www.yugo.at/processing/archive/index.php?what=loading。 > - 最好的尚未到來 by Silvio Lorusso (2012),下載圖標一個接著一個隨機無止盡地出現,https://silviolorusso.com/work/the-best-is-yet-to-come/。 > - DVD 人 by Constant Dullaart (2009),https://www.youtube.com/playlist?list=PLCUGKK4FUkbMdnNii8qoRy9_tMvqE8XHB,包含創作脈絡說明,柏林 Panke 畫廊,http://www.upstreamgallery.nl/news/545/constant- dulaart-solo-show-nein-gag-at-panke-gallery-berlin。 > - Throb by Winnie Soon (2018-19),http://siusoon.net/throb/。 **Task (RunMe):** 使用迴圈和任何一種轉化功能來重新設計跳動者的「動畫」。 **問題思考(ReadME):** **描述**你的跳動者設計概念與技術。 > - 你想探索和/或表達什麼? > - 你在程式中使用了哪些與時間相關的語法/函式,為什麼要以這種方式使用它們?時間是如何在計算中構建的(包含參考閱讀材料和你的程式碼)? > - 想一想你在數位文化中遇到過的一個跳動者圖標,例如用在 YouTube 上的串流影片或是在 Facebook 上載入最新提要,或等待付款交易,並思考一個跳動者所傳遞的/或隱藏的意義?我們如何以不同的方式描述這個圖標? --- ## 必讀 > - Hans Lammerant,「人類和機器如何協商時間經驗」,《技術銀河軟體觀察指南》,88-98,(2018 年),https://www.books.constantvzw.org/nl/home/tgso。 > - Daniel Shiffman,課程 3.1、3.2、3.3、3.4、4.1、4.2、5.1、5.2、5.3、7.1、7.2,程式碼! 使用 p5.js 程式設計,https://www.youtube.com/watch?v=1Osb_iGDdjk(2018)。 (包括條件語句、迴圈、函式和陣列的實際用法。) > - Wolfgang Ernst,「『……另一種迴圈的永恆』。媒體的不及時性」,(2009 年),https://www.medienwissenschaft.hu-berlin.de /de/medienwissenschaft/medientheorien/downloads/publikationen/ernst-else-loop-forever.pdf。 --- ## 進一步閱讀 > -Wolfgang Ernst, Chronopoetics: The Temporal Being and Operativity of Technological Media (London: Rowman & Littlefield International, 2016), 63-95. > Winnie Soon, “Throbber: Executing Micro-temporal Streams,” Computational Culture 7, October 21 (2019), http://computationalculture.net/throbber-executing-micro-temporal-streams/. > - Wilfried Hou Je Bek, “Loop,” in Fuller, ed., Software Studies.。 > - Derek Robinson, “Function,” in Fuller, ed., Software Studies. --- ## Notes [^1]: 迴圈背後的邏輯可以通過以下自相矛盾的文字遊戲來證明:「下一句為真。前一句為假。」更多關於悖論、遞歸和奇怪的迴圈例子可以在侯世達(Douglas R. Hofstadter)的《哥德爾、埃舍爾、巴赫:一條永恆的金帶》(Gödel, Escher, Bach: An Eternal Golden Braid, New York: Basic Books,1999 )中找到。 [^2]: 有關「G筆記」一種詮釋,請參閱喬亞西亞・克萊薩(Joasia Krysa)的《愛達・勒芙蕾絲:十三屆文件展,一百個筆記、一百個想法》(Ada Lovelace 100 Notes-100 Thoughts Documenta 13, Berlin: Hatje Cantz Verlag, 2011)。 [^3]: 尤金・艾瑞克・金(Eugene Eric Kim)和貝蒂・亞歷山大・圖爾(Betty Alexandra Toole)「愛達和第一台計算機」,1999年《科學美國人》280期第五部78頁。 [^4]: 有趣的是「跳動者」一詞源自帶有嘲弄陰莖勃起的貶義,如開頭第一章描述的「git」(原有混帳的貶義)有相似的語意演化。 [^5]: 在生產力勞動時間與注意力經濟等更廣泛的脈絡下,我們可以在此補充做為文化形式的螢幕保護程式。 亞歷山德拉・阿尼基納(Alexandra Anikina)在程式電影的博士論文(倫敦大學金史密斯學院,2020 年)有一章節討論螢幕保護程式的美學形式與空閒時間/睡眠及有關勞動認知;她的《慣性電影》(2017)演講表演可以在 http://en.mieff.com/2017/alexandra_anikina 上看到。另見拉斐爾・羅森達爾(Rafaël Rozendaal)的睡眠模式裝置:荷蘭新研究所(Het Nieuwe Instituut)藝術螢幕保護程式(2017),https://hetnieuweinstituut.nl/en/press-releases/sleep-mode-art-screensaver。 [^6]: 孫詠怡《跳動者:微時串流的執行》,電腦文化第七期(2019 年 10 月 21 日),http://computationalculture.net/throbber-executing-micro-temporal-streams/。 [^7]: 藝術家戈蘭・萊文(Golan Levin)提供了一個關於模運算子的線上課程做為 The Coding Train 系列的一部分,請參見:https://www.youtube.com/watch?v=r5Iy3v1co0A。 [^8]: 德里克・羅賓遜,「功能」,在馬修・富勒(Matthew Fuller)編輯的《軟體研究》(Software Studies)第101期。 [^9]: 為了貼近範例的討論,我們僅提供了兩種轉化相關的語法。除了 translate() 和 rotate(),還有其他與轉化相關的函式,例如 scale()、shearX()、shearY()。請參閱 https://p5js.org/reference/#group-Transform。 [^10]: https://p5js.org/reference/#/p5/frameCount。 [^11]: https://p5js.org/reference/#/p5/push。 [^12]: http://www.johnpbell.com/asterisk-painting/。 [^13]: 漢斯・拉默蘭特(Hans Lammerant),「人類和機器如何協商時間經驗」,《技術銀河軟體觀察指南》( The Techno-Galactic Guide to Software Observation, Brussels: Constant, 2018, 88-98頁)。 [^14]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push 。 [^15]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice。 [^16]: millis() 是 p5.js 語法,回傳自啟動程式以來的毫秒數,類似 frameCount 但以毫秒為單位,請參見 https://p5js.org/reference/#/p5/millis。 [^17]: 沃爾夫岡・恩斯特,2016,《時間詩學:技術性媒體的時間存有與運行》,倫敦:Rowan & Littlefield International,63-95頁。 [^18]: 恩斯特《時間詩學》,63頁。 [^19]: 例如,哲學家亨利・柏格森(Henri Bergson)在生活的「持續」時間與粗俗或時鐘時間之間進行了質性的區分,這使時間體驗變得扁平和消沉。請參見亨利・柏格森《物質與記憶》原出版 1896年,紐約:Zone Books 1990重新出版。(Matter and Memory [1896](New York:Zone Books,1990)。 [^20]: 沃爾夫岡・恩斯特,「『……另一種迴圈的永恆』。媒體的不及時性」(2009 年)。可在此閱覽 https://www.medienwissenschaft.hu-berlin.de/de/medienwissenschaft/medientheorien/downloads/publikationen/ernst-else-loop-forever.pdf。 [^21]: 艾倫・圖靈(Alan M. Turing),「關於可計算的數字,以及對可判定性的應用」(On Computable Numbers, with an Application to the Entscheidungs problem),倫敦數學會會刊 42(1936/1937):230-265。 [^22]: 孫詠怡「跳動者」。 [^23]: 恩斯特「……另一種迴圈的永恆。」 [^24]: 「歷史的終結」是指弗朗西斯・福山的《歷史的終結與最後的人》(紐約:自由出版社,1992 年),它提出了 1989 年後蘇聯解體後西方自由民主的崛起。 [^25]: 恩斯特「……另一種迴圈的永恆。」 [^26]: 馬丁・海德格,《存在與時間》(1927)。有用的摘要,請參閱 https://plato.stanford.edu/entries/heidegger/#BeiTim。 [^27]: 恩斯特「……另一種迴圈的永恆。」 [^28]: 請參閱即將出版的 Alan Blackwell、Emma Cocker、Geoff Cox、Thor Magnussen、Alex McLean,《即時編碼:一個使用者的指南》Live Coding: A User's Manual(出版商和日期未知)。