<style> .red{ color : #f15b6c; } .black{ color : 000000; } .blue{ color : #87ceeb; } </style> # 泰坦號 🏄‍♂️ ![titan_pic](https://hackmd.io/_uploads/B1oB1HwC3.jpg) ## 教學前的提醒 看不懂,聽不懂的部分可以直接提出,要改進也可以寫在留言區 著重目標在讓未學習過程式的同學,透過printf學習一些基礎的程式概念 ## 新聞 📰 [新聞網址](https://tw.stock.yahoo.com/news/%E6%B3%B0%E5%9D%A6%E8%99%9F%E7%B6%93%E6%AD%B7-%E5%9E%82%E7%9B%B4%E4%B8%8B%E5%A2%9C71%E7%A7%92-%E5%B0%88%E5%AE%B6-5%E4%BA%BA%E6%84%8F%E8%AD%98%E5%88%B0%E5%8D%B3%E5%B0%87%E5%96%AA%E5%91%BD-073409567.html) ![](https://hackmd.io/_uploads/rJRqfRa0h.png =30%x) ## 遊戲介紹 * ### 遊玩方式 利用鍵盤上的方向鍵,將一個指定字串(hello),移動到另個指定字串(,Titanic)的旁邊 成功後會再中間跳出success * ### 畫面 左上角可移動字串,右下角目標字串、有2隻小魚隨著hello走,還有海草、海浪 ![](https://hackmd.io/_uploads/rkf0g06Rn.png =80%x) ## 前置⚙️ 1. 在桌面新增資料夾 ![](https://hackmd.io/_uploads/HJ46kuo03.png) 2. 資料夾名稱不可以是中文且中間不可以有空格 ![](https://hackmd.io/_uploads/SyXmxdiCh.png) 3. 開啟 VSCode 並開啟剛剛新增的資料夾 ![](https://hackmd.io/_uploads/SyOgXujCn.png) ![](https://hackmd.io/_uploads/ByDHX_s02.png) 4. 選擇是,我信任作者 ![](https://hackmd.io/_uploads/rJzt7_sR3.png) 5. 點擊新增檔案 ![](https://hackmd.io/_uploads/BygYrdoRh.png) 6. 取檔名且後面要是.c檔,按 enter 就可以新增檔案了 ![](https://hackmd.io/_uploads/S1GaBuiCn.png) ## **程式碼剖析**🔍 > 3個大綱 函式庫->背景->可移動字串 ,在三個大綱中再分細項去講解 1. ### 函式庫的引入 * ### code示範 如果只打上 ```c= int main() { printf("hello,Titanic\n"); return 0; } ``` ![](https://hackmd.io/_uploads/B1UOUQ0A2.png) 系統會告訴你無法編譯與執行 那加上 printf() 所屬的標頭檔 <stdio.h> 再試試 ```c= #include<stdio.h> int main() { printf("hello,Titanic\n"); return 0; } ``` ![](https://hackmd.io/_uploads/SkCYLmAA3.png) 發現可以正常執行了!! 因為在C語言中,當你使用某個函數(比如 printf )時,你需要在程式的開頭引入相應函式所屬的標頭檔(例如 <stdio.h>)。這樣做可以讓編譯器知道這是一個合法的函式,並且了解它的返回類型以及參數的類型。如果你沒有包括相應的標頭檔,程式在預處理的時候就找不到相對應函式,因此在後續編譯時編譯器會報錯 2. ### 加入背景(海水、海草、魚) 讓大家先體驗printf、for、函式基礎、while、_getch() * #### 背景的設定 background() * 製作海的方法 把海的樣,想像成 ~ 的延長版 - 第一種、可以透過一次 printf 大量的 \~\~\~\~\~\~\~ 來完成,但這樣程式碼會看起來不簡潔,所以我們這邊學習 - 第二種、for迴圈 * 體驗用一次 printf 完跟 for 迴圈的差異👉 ```c= #include<stdio.h> int main() { printf("~\n");//一個~ printf("~~~~~~~~~~~~~~~~~~~~\n");//20個~ for(int i=0;i<20;i++){//20個~ printf("~"); } printf("\n"); return 0; } ``` * **<span class ="red">Q :</span> [for迴圈](https://openhome.cc/Gossip/CGossip/forStatement.html)是甚麼?迴圈感覺就很難** **<span class ="red">ANS :</span> 其實不會** 日後很常會用到,經過長期的練習,for迴圈就像呼吸一樣簡單理解 這邊先簡單講我們這邊的for再幹嘛 for()裡面通常會有兩個;三樣東西,但不是一定要三樣!! 但先以正常版去講解 ``` for (初始變數; 判斷式; 遞增式) { 陳述句一; 陳述句二; } ``` 那以這個為例 ```C= for(int i = 0; i < 20; i++){ printf("~"); } ``` 我們的初始變數是從 0 開始,那遞增式是 +1,判斷式是到第 20 次就要停 判斷的方式是,i = 0 開始,接著跑到判斷式看i有沒有小於20有的話 執行printf,再去做 i++ 的動作 * background() 示範code 👉 ```c= #include<stdio.h> void background() { int lim = 4; for (int k = 0; k < 2; k++) { for (int i = 0; i < lim * 9; i++) printf("~");//輸出海浪 printf("\n"); } for (int i = 0; i < lim*2; i++) printf("\n"); //海底的樣 printf("\n"); for (int i = 0; i < lim*3; i++) printf("\\|/");//輸出海草 printf("\n"); } int main() { background(); return 0; } ``` * **完成方式** 基礎的 printf 出想要的畫面,像是海浪的部分,~ 用這個去思考,搭配上一個lim去限制我們 printf 出的次數,那中間我們就當作海底不加上東西,所以 \n 換行,一直到海草的部分 \\|/,用上述的相同概念完成 * [函式](https://openhome.cc/Gossip/CppGossip/FunctionABC.html) 可能有人發現前面的background <span class ="red">為什麼程式碼沒在main裡面</span> 理解: main 就像主程式一樣,我們 run_code 時就會運作,但函式就像副程式一樣,我們不去呼叫他,就不會去運行 ![](https://hackmd.io/_uploads/B1IBcjCC3.png =50%x) > 魚的部分有點多了,就跳過[color=#E1F1E7] 3. ### 基本畫面(讓 hello 跟 ,Titanic 能正常操作) * ### draw 🖼 > 將指定的字串放在指定的位置上[color=#E1F1E7] * #### 單純 printf 的作法 * 我們一個一個去 printf 出來 * 作法 ```c= #include<stdio.h> int main() { printf("\n\n\n\n\n\n\n"); printf("hello,Titanic");//游標此時會在hello,Titanic的後方 return 0; } ``` * 不過這樣就會有一個問題點,就是無法回到起點去printf,因為游標在後方 ![](https://hackmd.io/_uploads/By7pUVk16.png) * #### 首先運用 gotoxy 去將游標指向指定的 x,y * #### 再來去 printf (該字串) * #### 示範 code 👉 ```c= #include<stdio.h> #include<windows.h> #define TARGET ",Titanic" #define MOVER "Hello" void gotoxy(int x, int y) { COORD coord; coord.X = x; coord.Y = y; SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord); } void draw(int y, int x, const char* str) { gotoxy(x, y); printf("%s", str); } int main() { printf("\n\n\n\n\n\n\n"); draw(10, 8, MOVER); draw(10, 13, TARGET); return 0; } ``` ![](https://hackmd.io/_uploads/SyDUsaJkp.png =50%x) 我們的輸出會在指定的y與x上,而非最左上角 > 值得注意的是 draw() 是先 y 再來 x 的喔 [color=#E1F1E7] > 也運用到了前面所講的定義與標頭檔!! * ### go to xy >將游標移到,指定的 x,y [color=#E1F1E7] 1. `COORD`代表著 x,y 的座標 ![](https://hackmd.io/_uploads/rkviaRTCh.png =40%x) 2. 因為上述的[SetConsoleCursorPosition](https://learn.microsoft.com/zh-tw/windows/console/setconsolecursorposition)跟[GetStdHandle](https://learn.microsoft.com/zh-tw/windows/console/getstdhandle)、[COORD](https://learn.microsoft.com/zh-tw/windows/console/coord-str)我們會運用到 `<windows.h>` 3. 示範code ``` c= #include<stdio.h> #include<windows.h> void gotoxy(int x, int y) { COORD coord; coord.X = x; coord.Y = y; SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord); } int main() { printf("hello,Titanic\n"); gotoxy(15, 10); printf("hello,Titanic\n"); return 0; } ``` ![](https://hackmd.io/_uploads/SydV6Lk16.png =50%x) 會發現到,第二次的 Hello,World 並非出現在第一次的正下方,而是在 x = 15, y = 10 的位置 * ### main * #### **變數定義** * `maxY`、`maxX`:定義整個邊界大小。 * `len_mover`、`len_target`:用於判斷這兩個字串的長度。 * `targetY`、`targetX`:判斷指定的答案位置。 * `moverY`、`moverX`:控制要移動的字串的位置。 <br/> ```c= int maxY = 25, maxX = 80; // Assume a default console size int len_mover = strlen(MOVER); int len_target = strlen(TARGET); int targetY = maxY - 1, targetX = maxX - len_target; int moverY = 0, moverX = (maxX - len_mover) / 2; int ch; ``` * 示範[strlen](https://cplusplus.com/reference/cstring/strlen/)的功能👉 >簡單來說,就是用來計算字串的長度 [color=#E1F1E7] ```c= #include<stdio.h> #include<stdlib.h> #include<string.h> #define TARGET ",Titanic" #define MOVER "Hello" int main() { int len_target = strlen(TARGET); int len_mover = strlen(MOVER); printf("%d\n", len_target); // 9 printf("%d\n", len_mover); // 5 return 0; } ``` ![](https://hackmd.io/_uploads/SyNfpHykp.png) * **draw(target)、draw(mover)** * 先將 MOVER 的位置放到畫面上的原因,以保持游標始終在移動中的字串後面 <br/> ```c= draw(targetY, targetX, TARGET); draw(moverY, moverX, MOVER); ``` * **if** * 判斷是否到達指定的位置,如果是,則在中間顯示 "Success" 並離開 while 迴圈。 ```c= if (moverY == targetY && moverX >= (targetX - 6) && moverX <= targetX + len_target - len_mover) { gotoxy(maxX / 2 - 3, maxY / 2); printf("Success"); break; } ``` * 示範if的code👉 ```C= #include<stdio.h> int main() { int value = 72; if (value == 72) { printf("hello,Titanic\n"); } else { printf("Not match\n"); } value = 30; if (value == 72) { printf("hello,Titanic\n"); } else { printf("Not match\n"); } return 0; } ``` ![](https://hackmd.io/_uploads/rJP49p1k6.png) 當 if() 內的事件成立時<span class ="red">(TRUE)</span>,就會執行 if{} 起來的,反之,則會執行 else{} 起來的 * 示範&&的code👉 ```c= #include<stdio.h> int main() { int value1 = 5; int value2 = 10; printf("%d\n",value1 > 0 && value2 > 0);//兩者成立 printf("%d\n",value1 < 0 && value2 > 0);//其一不成立 printf("%d\n",value1 < 0 && value2 < 0);//都不成立 if (value1 > 0 && value2 > 0) { printf("It match!!\n"); } else { printf("Not match QAQ\n"); } value1 = 0; //更改成不符合其中一個條件 if (value1 > 0 && value2 > 0) { printf("It match!!\n"); } else { printf("Not match QAQ\n"); } return 0; } ``` ![](https://hackmd.io/_uploads/HJmKwX0R2.png) > && 是 C 語言中的邏輯運算符號,當左右兩邊成立時會回傳 TRUE,反之為 FALSE[color=#E1F1E7] 第一次的 if 我們兩邊的值都有成立,所以 printf 出 It match,但當第二次其中一個不成立時,就 printf 出 Not match * **[_getch()](https://learn.microsoft.com/zh-tw/cpp/c-runtime-library/reference/getch-getwch?view=msvc-170)** * 用來判斷鍵盤輸入的一個函式。 `ch = _getch();` * getch()示範code👉 這段code是用來測試,_getch()他會輸出按下去的按鈕 ```c= #include<stdio.h> #include<conio.h> #include<windows.h> int main() { int number = 0; number = _getch();//先得到224這個key number = _getch(); printf("%d\n", number); return 0; } ``` > 值得注意的是,當按下方向鍵時,會先輸出一個 224 > _getch() 會首先返回 224,表示這是一個特殊的鍵盤輸入(功能鍵、方向鍵) > 接著才有上方向鍵 72、下方向鍵 80、左方向鍵 75、右方向鍵77[color=#E1F1E7] </br> > <span class = "red">但這樣的 CODE 會有問題,沒有辦法持續輸入方向鍵 > 所以要來搭配個 while</span> * **[while](https://openhome.cc/Gossip/CppGossip/whileStatement.html)** while 也是一種迴圈,不過他的寫法跟 for 不太一樣 ``` while(條件式) { 陳述句一; 陳述句二; } ``` 那如果條件式是一個 while(1) 的話就會形成無限迴圈,因為 1 在布林值中也代表著 TRUE,條件式一直成立的情況下,就會一直執行陳述句 步驟: 條件式(成立) -> 陳述句1 -> 陳述句2 -> 條件式(成立).... ![](https://hackmd.io/_uploads/H1S-fLky6.png =70%x) * while 跟 _getch 搭配 code ```c= #include<stdio.h> #include<conio.h> #include<windows.h> int main() { int number = 0; while(1) { number = _getch(); printf("%d\n", number); Sleep(1000); } return 0; } ``` * 不過就會發現到畫面一堆數字,所以會運用到`system("cls")` `system("cls")` 會把已經輸出的文字清掉,然後再把游標移回到第一行最前面的位置 * 搭上 system("cls") code ```c= #include<stdio.h> #include<conio.h> #include<windows.h> int main() { int number = 0; while(1) { system("cls"); number = _getch(); number = _getch(); printf("%d\n", number); Sleep(1000); } return 0; } ``` <br/> ## 程式碼玩看看 🎮 * **程式碼** ```c= //引入標頭檔 #include<stdio.h> #include<string.h> #include<windows.h> #include<conio.h> //定義字串 #define TARGET ",Titanic" #define MOVER "hello" #define FISH "><(((º>" //讓游標到指定位置 void gotoxy(int x, int y) { COORD coord; coord.X = x; coord.Y = y; SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord); } //將字串printf到指定位置 void draw(int y, int x, const char* str) { gotoxy(x, y); printf("%s", str); } //畫面載入(魚、海草、海水) void background() { int lim = 4; for (int k = 0; k < 2; k++) { for (int i = 0; i < lim * 9; i++) printf("\033[5m\033[34m~\033[0m");//輸出海浪 printf("\n"); } for (int i = 0; i < lim*2; i++) printf("\n"); //海底的樣 printf("\n"); for (int i = 0; i < lim*3; i++) printf("\033[32m\033[5m\\|/\033[0m"); //輸出海草 printf("\n"); } //魚的移動控制 void fishmove(int *fishx,int *fishy,int maxX) { *fishx += 1; if (maxX < *fishx) { *fishx = 0; } } int main() { int maxY = 10, maxX = 30; //邊界大小 int len_mover = strlen(MOVER); //各字串長度 int len_target = strlen(TARGET); int targetY = maxY - 1, targetX = maxX - len_target;//目標座標 int moverY = 3, moverX = 0; //移動字串座標 int ch; //方向鍵的 int fishX = 10,fishY=5; //魚的座標 int fish2X = 10, fish2Y = 10; while (1) { system("cls"); background();//海底畫面 draw(fishY, fishX, FISH); draw(fish2Y, fish2X, FISH); draw(targetY, targetX, TARGET); draw(moverY, moverX, MOVER); fishmove(&fishX,&fishY,maxX); fishmove(&fish2X, &fish2Y, maxX); if (moverY == targetY && moverX >= (targetX - 5) && moverX <= targetX + len_target - len_mover) {//達成畫面 gotoxy(maxX / 2 - 3, maxY / 2); printf("Success"); break; } ch = _getch(); switch (ch) { //選擇移動方式 case 72: // Up if (moverY > 0) moverY--; break; case 80: // Down if (moverY < maxY - 1) moverY++; break; case 75: // Left if (moverX > 0) moverX--; break; case 77: // Right if (moverX < maxX - len_mover) moverX++; break; case 'q': return 0; default: break; } } while (_getch() != 'q'); //按下q離開 return 0; } ``` > **提示若不能用,邊界的大小(maxX、maxY)記得要做更改喔!!!** [color=#E1F1E7] </br></br> * 參考 * [格式指令字](https://ithelp.ithome.com.tw/articles/10282210) * [特殊字元](https://openhome.cc/Gossip/CGossip/PrintfScanf.html) * [更改顏色](https://blog.51cto.com/u_15333014/3879949) * [GetStdHandle](https://learn.microsoft.com/zh-tw/windows/console/getstdhandle) * [getch_](https://learn.microsoft.com/zh-tw/cpp/c-runtime-library/reference/getch?view=msvc-170) * [setconsolecursorposition](https://learn.microsoft.com/zh-tw/windows/console/setconsolecursorposition) * [HTML_emoji](https://www.w3schools.com/charsets/ref_emoji_smileys.asp) * [什麼是函式](http://program-lover.blogspot.com/2008/11/what-is-function_25.html) * [for](https://openhome.cc/Gossip/CGossip/forStatement.html)