--- image: https://i.imgur.com/P8EHAcA.png --- # 資訊之芽一階大作業練習解說 For 2021 Py 語法班 [toc] Reference: [大作業規格說明](https://hackmd.io/J8l8vuQKSqSBpXJidpM0sA?view) ## 基礎說明 我們從[投影片](https://docs.google.com/presentation/d/1bYe5lW9eU_jDoh1m3Wj8Qnryd9ysFfuwxRrX1btjqH0/edit)開始。 ![](https://i.imgur.com/P8EHAcA.png =500x) - 大作業練習跟實際的大作業使用的檔案是相對的: - 主程式`main.py` $\longrightarrow$<small>(呼叫)</small>`Handler.py` - 主程式`practice.py` $\longrightarrow$<small>(呼叫)</small>`Handler_practice.py` - 其他的檔案都是共用的,除了修改`config`裡面的尺寸之外,**基本上一路到寫完大作業,完全不需要去改到其他檔案**。 > **為什麼❓** 因為兩個`Handler`負責的其實就是遊戲中各種操作及邏輯判斷的部分,我們把這些從主程式`main.py`獨立出來,讓大家可以專注在寫出遊戲的這些功能上。 **Tips:** 完成練習之後,應該可以發現有很多的功能跟[大作業的各項 Tasks](https://hackmd.io/J8l8vuQKSqSBpXJidpM0sA?view#Tasks) 是接近的,只要參考練習的程式碼,依照各種情況寫出能判斷、執行功能的 function,就可以輕易寫好大作業的幾個功能囉。 同一個功能可以多試試不同的寫法,對於理解 python 的語法、體會寫程式的感覺很有幫助。 ## part 1 ![](https://i.imgur.com/3pQfPcJ.png =500x) 這邊要做的事情是處理移動這件事情,我們已經知道移動其實就是去修改`piece.x`跟`piece.y`這兩個值,可是除了單純的移動之外,我們還需要去判斷有沒有碰到邊界。 怎樣是「碰到邊界」呢?如果動了之後會撞到邊界,那就不能移動才行。 再來,透過一些嘗試大家應該可以知道,`piece.x`跟`piece.y`兩個座標值其實是一整個方塊中的**參考座標**,我們是以每個方格「**跟`piece.x`、`piece.y`的相對位置**」作為顯示方塊的基準。 所以要判斷整個方塊移動之後會不會撞到邊界,我們必須要去看其中**每一個方格**移動之後會不會撞到邊界才行! 那我們要怎麼取得「其中每一個方格」的座標呢? 回到鍵盤配置那邊,這其實就是我們已經寫好的空格鍵,也就是檔案裡最上面的`printPiece`這個 function 的功能:![](https://i.imgur.com/mDzjKXm.png =x20) ```python=7 # 印出目前方塊的所有方格的座標 def printPiece(shot, piece): print('目前方塊:', getCellsAbsolutePosition(piece)) ``` 只要使用`getCellsAbsolutePosition(piece)`就可以得到一個由方塊中所有方格座標 $(y, x)$ 所組成的 list 了。 ```python def moveLeft(shot, piece): move = True # 大部分的情況應該是可以動的 for y, x in getCellsAbsolutePosition(piece): if x == 0: move = False # 如果有方格現在已經在最左位置,那就不能移動 if move: piece.x -= 1 ``` 不過這樣寫其實比較冗長,而且你也會發現 for 跑過那幾個座標的時候,只要有一個`x == 0`,那就已經可以「宣判」不能移動了。 因為「不移動」其實就代表「什麼都不用做」的意思,我們可以利用`return`改寫: ```python def moveLeft(shot, piece): for y, x in getCellsAbsolutePosition(piece): if x == 0: return piece.x -= 1 ``` 在 function 裡面如果使用`return`,可以直接結束函數(並回傳值),我們可以當作「跳到整個函數的結尾」的意思。 所以這樣一來的話,`piece.x -= 1`這行只有在整個 for 跑完都「安全無虞」之後,才會被執行。 --- 再來只要把同樣的作法推導到整個上、下、左、右的邊界,我們就完成了。 整個 part 1 寫完的 code 如下: ```python=12 def moveLeft(shot, piece): for y, x in getCellsAbsolutePosition(piece): if x == 0: return piece.x -= 1 def moveRight(shot, piece): for y, x in getCellsAbsolutePosition(piece): if x + 1 == config.columns: return piece.x += 1 def moveUp(shot, piece): for y, x in getCellsAbsolutePosition(piece): if y == 0: return piece.y -= 1 def moveDown(shot, piece): for y, x in getCellsAbsolutePosition(piece): if y + 1 == config.rows: return piece.y += 1 ``` **Note:** $(`config.row`,`config.columns`)=(20,10)$,其實把`if`裡面判斷的條件換成數字沒有關係,但是使用設定裡面指定的函數是好習慣。[為什麼❓](https://zh.wikipedia.org/zh-tw/%E9%AD%94%E8%A1%93%E6%95%B8%E5%AD%97_(%E7%A8%8B%E5%BC%8F%E8%A8%AD%E8%A8%88)) ## part 2 ![](https://i.imgur.com/NGyIDKN.png =500x) 這個應該是很簡單的,只要把整個`shot.status`跑過一遍就好了。 依照大家過去寫下來的經驗,這邊標準的寫法就是: ```python=36 def printMap(shot, piece): print('Print Map:') print('---'*4) for y in range(config.rows): for x in range(config.columns): print(shot.status[y][x], end='') print() print('---'*4) ``` **[Some extra notes for part 2](#notes-for-part-2)** ## part 3 ![](https://i.imgur.com/LusBLRW.png =500x) 這邊要做的事情是,在處理移動的 code 裡面多加上一個判斷障礙物的功能,具體來說跟 part 1 其實很接近,我們只要在 if 中加上一個判斷`shot.status`上有沒有障礙物的條件就 ok 了。 ```python=12 def moveLeft(shot, piece): for y, x in getCellsAbsolutePosition(piece): if x == 0 or shot.status[y][x - 1] == 2: return piece.x -= 1 def moveRight(shot, piece): for y, x in getCellsAbsolutePosition(piece): if x + 1 == config.columns or shot.status[y][x + 1] == 2: return piece.x += 1 def moveUp(shot, piece): for y, x in getCellsAbsolutePosition(piece): if y == 0 or shot.status[y - 1][x] == 2: return piece.y -= 1 def moveDown(shot, piece): for y, x in getCellsAbsolutePosition(piece): if y + 1 == config.rows or shot.status[y + 1][x] == 2: return piece.y += 1 ``` (注意每個`if`條件裡面`or`後面的部分) ## notes for part 2 **Note:** 你可能會聽說在 python 裡面連續呼叫`print()`會花掉一些程式的效能,關於這個跑過整個 map 的 for 迴圈,我們有沒有什麼簡化的方式呢? ```python=39 for y in range(config.rows): for x in range(config.columns): print(shot.status[y][x], end='') print() ``` 利用 python 的字串處理,這個 loop 其實也可以寫成: ```python=39 for y in range(config.rows): print(''.join(map(str, shot.status[y]))) ``` 如果輸出的格式不一定要一樣的話,我們甚至可以直接這樣: ```python=39 for y in range(config.rows): print(shot.status[y]) ``` 這麼寫的話 print 出來會變成這樣: ![](https://i.imgur.com/5sSATiw.png =200x) 畢竟程式的輸出只是給我們人類看的!一般寫程式不是在解 judge 題的時候,功能要怎麼寫可以依照你的需求調整。 有時候,寫起來簡單、比較清爽的寫法,就是好寫法。 ## all codes 這裡可以下載完成 part 1+2 的範例檔:[Handler_practice.py](https://gist.github.com/pcchou/d02652925a56710ab143fa88b4c0b1b7/raw/8812eb1d4c464b1b1cf16d5e7d11ecb1c5da3933/Handler_practice.py) 這裡可以下載完成 part 1+2+3 的範例檔:[Handler_practice.py](https://gist.githubusercontent.com/pcchou/94ef5fd617e7d48765f259eaabb32ee0/raw/8120e9cc9feea79f72ce2bc15e57d4de7c379b2d/Handler_practice.py)