---
# System prepended metadata

title: Snake_C++ Final Project
tags: [交大課業]

---

# Snake_C++ Final Project
## 題目整理
### 說明
交大電機「計算機概論於程式設計」110學年度期末專題，
雖然為簡單的程式練習，希望同時增進專案開發通整的能力以及研究紀錄的撰寫。

[Snake_Final_Project [GitHub]](https://github.com/william0503tw/Snake_Final_Project)

### :mountain: 目標

1. 玩家須控制貪吃蛇在地圖上行走。
2. 地圖需隨機產生多個食物，蛇吃掉後則長度增加1個單位。
3. 地圖需包含邊界以及障礙物，若蛇撞到邊界/障礙物，或是吃到自己的身體，則遊戲結束。
4. 需記錄遊戲過程中的得分於地圖上方顯示欄中。



### :book: 規則
- [x] 鍵盤W、S、A、D代表蛇上下左右的移動。
- [x] 遊戲地圖長寬 50*25 (不包含邊界)。
- [x] 初始畫面中，需顯示蛇、食物、障礙物、邊界、記分板、等...。
- [x] 食物被吃掉後，在地圖中任意位置重生。
- [x] 記分板需能「及時」刷新分數，顯示當前蛇的身長。
- [x] 蛇撞到邊界、障礙物、本身則死亡，地圖下方顯示遊戲結束特效。


### :robot_face: 評分項目


- [x]  蛇能夠正確上下左右操控，不可穿越障礙物、邊界。
- [x] 食物能更新
- [x] 記分板能即時更新
- [x] 蛇的死亡條件正確
- [x] 蛇死亡後，地圖下方能正確顯示特效
 
==加分項目==
- [x] 邊界上出現一對傳送門，從任一方進入，可從另一方出來。
- [ ] 食物具備保存期限，過期的話，食物閃爍三秒後消失。
- [ ] 遊戲結束時詢問是否完下一場遊戲。
- [x] 隨著時間增加，蛇行走速度不斷上升。
- [x] 吃到特殊食物後蛇立刻變型 ex: 原本用*表示蛇，吃到食物候用@表示蛇。
- [ ] 蛇能夠發射子彈破壞障礙物(限定2發)。
- [ ] 蛇經過一段時間沒吃到食物(15秒)，則餓死，遊戲結束。
- [ ] 遊戲開始前可以自訂遊戲地圖長寬其他，視難易程度加分。
- [x] 新增顏色
- [x] 自行設定移動速度、顏色、是否要障礙物、障礙物是否隨機



### :file_folder: 繳交格式

1. 原始檔案，包含CPP檔(請詳細註解、縮排)、執行檔..等等。
2. 流程圖(用圖表表示或文字敘述均可)。
3. 心得報告。

---
## 心得報告
### :book: 筆記
1. 用到的函式庫
```cpp=
#include <iostream>
#include <ctime>      //srand
#include <cstdlib>    //srand、rand
#include <windows.h>  //SetConsoleCursorPosition()
#include <thread.h>   //readInput
#include <conio.h>    //kbhit()、getch()
```
2. readInput
  - kbhit(): 檢查鍵盤是否有鍵按下
  - getch(): 從鍵盤讀入一個字元，並回傳其ASCII值
  - 兩者皆須#include <conio.h>
```cpp=
void readInput(void* id) //判斷使用者的input
{
    char c;
    do{
        c = getch(); //從鍵盤讀入任意字元
        switch(c){
            case 'w': direction = UP; break;
            case 'a': direction = LEFT; break;
            case 's': direction = DOWN; break ;
            case 'd': direction = RIGHT; break ;
        }
    }while( (c != 'q') || c != (char)27);
    
    _endthread();  //终止由 _beginthread所創建的線程
    return;
    
    //cout << direction << endl ; 檢查判斷是否正確
}
```
3. 利用srand去改變食物的位置
  - srand、rand概念
```cpp=
#include <iostream>
#include <cstdlib> //亂數相關函數
#include <ctime>   //時間相關函數

int main()
{
  srand( time(NULL) );//利用時間作亂數種子
  int x = rand();//產生亂數

  return 0;
}
```
  - 隨機決定食物的初始位置座標
```cpp=
int temp ;
srand((unsigned int)time(NULL));
for(int i = 1 ; i <= 7 ; i++)
{
    if(temp != rand())
    {
        int x = rand() % (x_size - 2) + 1 ;//x範圍為1~50
        int y = rand() % (y_size - 2) + 1 ;//y範圍為1~25
        ground[x][y] = FOOD ;//食物位置座標
    }
    else
    {
        srand(0);
        int x = rand() % (x_size - 2) + 1 ;
        int y = rand() % (y_size - 2) + 1 ;
        ground[x][y] = FOOD ;
    }
    temp = rand();
}
```
4. Sleep
  - 讓程式暫停執行一段時間，單位為「毫秒」，1秒 = 1000毫秒
  - 須#include<windows.h>
```cpp=
Sleep(1000) // 暫停程式1秒(1000毫秒)
```

5. system("cls")
  - 把已經輸出的文字清掉，然後再把游標移回到第一行最前面的位置。

6. 設定顏色
  - 利用SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),a);

7. 游標移動函式
  - 利用設定游標位置 SetConsoleCursorPosition()，記得#include <windows.h>
  - HANDLE : 從一個標準設備中取得句炳
  - COORD : 一个字符在螢幕上的座標
```cpp=
void gotoxy(short x,short y)//游標移動函式
{
    HANDLE hStdOut;
    hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);//從一個標準設備中取得句炳
    if (hStdOut == INVALID_HANDLE_VALUE) return; //調用失敗則返回
    
    COORD pos={x,y};
    //pos.x = x;
    //pos.y = y;
   
    SetConsoleCursorPosition(hStdOut,pos);//設定游標位置
}
```
8. 設定視窗大小 setWindowSize
  - HWND : Handle of WiNDow = 視窗的句柄
  - GetConsoleWindow() : 取得視窗的句炳
  - RECT : 儲存一個矩形左上角的横座標、縱座標以及矩形的高度、宽度
  - GetWindowRect(width , height) : 取得RECT的指定尺寸 
  - LPRECT : 指向一個RECT結構的指標，該結構接收視窗的左上角和右下角的螢幕座標
- MoveWindow : 改變視窗的位置和尺寸，結構如下
```cpp=
void MoveWindow(
	int x,       //視窗左上角的橫坐標
	int y,       //視窗左上角的縱座標
	int nWidth,  //視窗寬度
	int nHeight, //視窗高度
	BOOL bRepaint = TRUE  //TRUE => 重畫視窗
	);
void MoveWindow(
	LPCRECT lpRect, //接收視窗的左上角和右下角的螢幕座標。
	BOOL bRepaint = TRUE 
	);
```
  - setWindowSize的完整程式 : 
```cpp=
void setWindowSize(int width, int height)
{
    HWND console = GetConsoleWindow(); //取得視窗的句炳
    RECT ConsoleRect;
    GetWindowRect(console, &ConsoleRect);
    //取得RECT的指定尺寸(console讀入width、&ConsoleRect讀入height)

    MoveWindow(console, ConsoleRect.left, ConsoleRect.top, width, height, TRUE);
    //改變指定視窗的位置和大小
}
```  
9. 更新屏幕 ( void clearScreen() )
  - CONSOLE_SCREEN_BUFFER_INFO : 包含主控台螢幕緩衝區的資訊，可利用此資訊來決定資料在視窗中的顯示方式 
  - CONSOLE_SCREEN_BUFFER_INFO 結構如下
```cpp=
typedef struct _CONSOLE_SCREEN_BUFFER_INFO
{
    COORD      dwSize; //目前主控台螢幕緩衝區的大小
    COORD      dwCursorPosition; //主控台螢幕緩衝區中資料指標的資料行和資料列座標
    WORD       wAttributes;
    SMALL_RECT srWindow; //視窗左上角和右下角的主控台螢幕緩衝區座標
    COORD      dwMaximumWindowSize; //指定目前螢幕緩衝區大小、目前的字型大小和螢幕視窗的大小上限
}
```
  - DWORD : 相當於 unsigned long，一般用於回傳值不會有負數的情況
  - FillConsoleOutputCharacter : 從指定的座標開始，將字元從主控台螢幕緩衝區寫入指定的次數。寫法如下
```cpp=
BOOL WINAPI FillConsoleOutputCharacter
(
  _In_  HANDLE  hConsoleOutput, //螢幕緩衝區的句炳
  _In_  TCHAR   cCharacter,     //寫入緩衝區的字元
  _In_  DWORD   nLength,        //寫入字元的數量
  _In_  COORD   dwWriteCoord,   //給定要寫入的資料格的座標
  _Out_ LPDWORD lpNumberOfCharsWritten //接收實際寫入螢幕緩衝區的字元
    
  // _In_  表一個輸入的參數
  // _Out_ 表一個輸出的參數
);
```
  - FillConsoleOutputAttribute : 設定從螢幕緩衝區中的指定座標開始的字元屬性。寫法如下
```cpp=
BOOL WINAPI FillConsoleOutputAttribute(
  _In_  HANDLE  hConsoleOutput,//螢幕緩衝區的句炳
  _In_  WORD    wAttribute,    //字元的屬性
  _In_  DWORD   nLength,       //要設定為指定屬性的儲存格數
  _In_  COORD   dwWriteCoord,  //首個設定屬性的字元位置座標
  _Out_ LPDWORD lpNumberOfAttrsWritten  //接收寫入的字元數
);
```
  - void clearScreen()的完整程式 : 
```cpp=
void clearScreen()
{
    HANDLE                     hStdOut;  //螢幕緩衝區句炳
    CONSOLE_SCREEN_BUFFER_INFO csbi;     //螢幕緩衝區資訊
    DWORD                      count;    //接收的字元數量
    DWORD                      cellCount;//寫入的字元數量
    COORD                      homeCoords = { 0, 0 }; 

    //取得螢幕緩衝區的句炳
    hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hStdOut == INVALID_HANDLE_VALUE) return;
    if (!GetConsoleScreenBufferInfo(hStdOut, &csbi)) return;
    
    //寫入的字元數量 = 視窗大小
    cellCount = csbi.dwSize.X *csbi.dwSize.Y;

    //用空格填充整個緩衝區
    if (!FillConsoleOutputCharacter(
    hStdOut,           //螢幕緩衝區的句炳
    (TCHAR) ' ',       //填入空格
    cellCount,         //填入空格的數量
    homeCoords,        //填入第一個空格的座標
    &count             //接收寫入的字元
    )) return;

    //用當前的顏色和屬性填充整個緩衝區
    if (!FillConsoleOutputAttribute(
    hStdOut,            //螢幕緩衝區的句炳
    csbi.wAttributes,   //字元的屬性
    cellCount,          //要設定為指定屬性的儲存格數
    homeCoords,         //第一個設定屬性的字元位置座標
    &count              //接收寫入的字元數
    )) return;

    //將游標移回原點
    SetConsoleCursorPosition(hStdOut, homeCoords);
}
```

更/ 讚讚 : )






### :pencil: 紀錄

邱[2021/11/30] 
: - 完成期末專題HackMd共筆
  - 確認題目需求

邱、林[2021/12/2]
: - 使用Clion協作
  - 訂定目標：自修預習到Class (CH10) 
  - Host端在邱，林欲編輯時，邱開
  - 圖形介面直接參考範例（如果OK，上顏色）
  - 完成時間規劃（如下）

邱[2021/12/3]
: - snake::initPlayground()
  - 新增updateScreen(), updateScreen::drawScore() 
  - 新增class "snake"
  - 場地畫面截圖
  - 
      ![場地圖片](https://i.imgur.com/GZProWq.png =75%x)

邱[2021/12/4]
: - snake::onTouch() //tells what thing does the snake's head touched

邱、林[2021/12/5]
: - 蛇的初始狀態先設定為■，之後再想辦法加長
  - 新增七個食物的初始位置(使用srand+rand)
  - updateScreen()修正
  - 邏設加加油！ XD 12/7更 邏設大爆炸
  - 12/7更 我邏設也大爆炸哭哭

邱[2021/12/7]
: - func gotoXY，左上為(1,7) 右下為(50,31)可增加「偏移量」使(0,0開始)
  - setWindowSize : 打開時視窗大小才不會跑掉
  - readInput完成
  - direction 移至class snake variables裡面
  - 最下面那一行(場地正下方) y=33
  - 經過偏移，左上笑臉(1,1) 右下笑臉(50,25)，使用時以左上為(1,1)
  - xy向右向下為正
  - ![](https://i.imgur.com/g37i15u.png =50%x)
  - ㄟ不是，到底要怎麼順toggole direction，網路上都是用螢幕刷新 嗚嗚
  - 林 : 你太快了哭哭，這樣我好像小廢廢，你要不要分配一點東東給我做

邱[2021/12/8]
: - readInput成功了 哈哈哈
  - random不再是同sequence
  - [Moving Test 1](https://youtu.be/JAo3QygDOzA)

邱、林[2021/12/8]
: - 生成障礙物，但非隨機，若有時間再研究如何隨機產生障礙物
  - 新增recreateFood，但是失敗ㄌ => 再修正
  

邱[2021/12/9]
: - 為了確認符合程式整體邏輯，cout障礙物程式碼放在[這裡](https://hackmd.io/J91IKjL9QBS5NTBWJuGYiQ)
  - 印出所有index(Debug且可判斷位置)
  - ![](https://i.imgur.com/uPkGxQ2.png =50%x)
  - 食物隨機生成不會卡牆(recreate food, init food)
  - [GamePlay Video](https://youtu.be/VAo5DUKyNwM)
  - main裡面可以觸發gmaneOver條件
  - 把障礙物index進ground[][]裡面
  - 把所有snake class的變數移到public
  - 每吃5個食物，速度會增加
  - 輸出障礙物不是用gotoXY，而是丟到ground[][]裡面，但日後不易更改樣式
  - 更改score的方式是用gotoXY
  - food initialize時從5個更改為3個
  - 最後，尾巴到底怎麼用==

邱[2021/12/14]
: - 哇! 全部完成
  - 碰到食物時不pop_front，所以在下一次更新時長度就會變長
  - 使用deque來implement身體的東西
  - 要注意drawBody在updateSnake中的位置，會影響到x,y值，這就是之前一直失敗的原因。
  - 此外，之前失敗也是因為許多先後呼叫順序影響的，可以跟上次的commit來比較
  - [完成影片](https://youtu.be/CSZxJPydoRk)

邱、林[2021/12/15]
: - 可以重新開始遊戲
  - 每吃到6個食物就新增一個 Special Food
  - 吃到一個 Special Food 就改變身體符號及顏色 
  - 研究Ctime
  - 完成若user的輸入與蛇現在的行進方向相反，則維持原本的方向行進

林[2021/12/17]
: - 顯示現在速度並即時更新
 (之前速度沒有正確的被增加，因為吃到Special Food時的food count沒有
 ++)
  - 先用cin >> skin，去改變初始顏色，看之後能不能改成讓使用者用選單的方式改顏色
  - 但是body的顏色還沒調整好QQ (調整好了!)

林[2021/12/20]
: - 用cin >> speed，改變初始速度
  - 哭哭我不知道怎麼用選單的方式來讓user調整啦 -> 超難的啦
  - 今天會搞定食物計時，先在這裡打下目標 :angry: (好好笑但不知道做不做得到)
  - 已經隨機建立出傳送門，但還在想到底要怎麼傳送 :(
  - 發現輸入y重新開始遊戲會有點bug

邱[2021/12/20]
: - 新增共用[心得報告Google Docs](https://docs.google.com/document/d/1YewclVUv1ugUc74W5dVGUulyxIV8o6P1-1EVpfLdfUo/edit?usp=sharing)

林[2021/12/20]
: - 可以隨機建立障礙物了
  - 用cin讓使用者選擇是否要障礙物以及障礙物是否隨機 (不隨機障礙物還沒好哭哭)
  - 重新開始遊戲還是有點怪怪的 (輸入及分數顯示)
  - 目前剩下不隨機障礙物跟傳送門還沒完成 (不隨機障礙物完成！)
  - 我真的不會寫計時器QQ

林[2021/12/30]
: - 遊戲初始介面完成
  - 哭哭剩下"再玩一次"和"傳送門"沒有搞定好
  - 完成傳送門！
 
邱、林[2022/1/8]
: - 完成流程圖
![](https://i.imgur.com/L23mdXY.png)



### :heavy_check_mark: TODO List (待做事項)
- [x] 研究Github如何協作，並寫出簡短教學 (順便練習Git)
- [x] 確認兩人各使用何種IDE
- [x] 完成場地印出
- [x] 研究需要哪些Library
- [x] 讀懂網路上的範例(Wrote By Poor Engineer)
- [x] 流程圖產出
- [x] 研究gotoxy(Window.h)
- [x] Auto set console window size
- [x] fix random 在同個位置
- [x] 研究Sleep
- [x] 碰到食物增加分數
- [x] 碰到食物增加身長
- [ ] 吃宵夜睡覺
- [x] 判斷死亡(碰到障礙物、蛇身)
- [x] 死亡產生GAME OVER
- [x] 自行新增一些加分條件(做得出來的XD)
- [x] Function新增註解
- [ ] 心得報告


## :clock10: 時間規劃
:heavy_check_mark: **[Week1]** [11/29~12/5] 完成流程圖以及撰寫想法

:heavy_check_mark: **[Week2]** [12/6~12/12] 寫Code

:heavy_check_mark: **[Week3]** [12/12~12/19] 寫Code(加分題如果有空), 心得 (12/14完成)

**[Week4]** [12/20~12/26] 時間緩衝＋檢查

P.S. 最好狀況Week2, Week3完成

---

## 網路資源
[Snake Game/PoorEnginerr](https://thepoorengineer.com/en/snake-cplusplus/)

[Snake Cpp/Cplusplus.com](http://www.cplusplus.com/articles/3U75fSEw/)

[如何設定顏色 ?](https://www.youtube.com/watch?v=E_-lMZDi7Uw&list=PLrjEQvEart7dPMSJiVVwTDZIHYq6eEbeL)

[Youtube上的流程教學](https://www.youtube.com/watch?v=PSoLD9mVXTA)

[不用執行緒的讀入keyboard方法](https://www.google.com/amp/s/www.geeksforgeeks.org/kbhit-c-language/amp/)

[中文貪吃蛇範例](https://www.796t.com/article.php?id=15871)

[遊戲開始前可自行設定顏色、選擇是否要障礙物、初始移動速度、是否開啟加速](https://github.com/TreeYeah/Snake)

[貪吃蛇](https://www.bilibili.com/read/cv6183110) 

[Timer](https://crmne0707.pixnet.net/blog/post/316734972-c%2B%2B-%E8%A8%88%E6%99%82%E5%99%A8)

[限時食物](https://iter01.com/146949.html)

[介面](https://www.twblogs.net/a/5c2692bdbd9eee16b3dba17a)

## 備忘
[期末分組名單](https://docs.google.com/spreadsheets/d/1lYH35O7wEUSYWoxB0fEOuOG3gaYE-x_o4revlsdCJVA/edit#gid=0)

[流程圖](https://drive.google.com/file/d/1dx8aroci1TWFalambsf7X0qTSc0fQhDf/view?usp=sharing)

###### tags: `交大課業`