# 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"
- 場地畫面截圖
-

邱[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向右向下為正
- 
- ㄟ不是,到底要怎麼順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且可判斷位置)
- 
- 食物隨機生成不會卡牆(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]
: - 完成流程圖

### :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: `交大課業`