-
Notifications
You must be signed in to change notification settings - Fork 1
/
SnakeGame.cpp
414 lines (325 loc) · 14.6 KB
/
SnakeGame.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
#include <iostream>
#include <memory.h>
#include <string>
#include "kmu_curses.h"
#include "SnakeGame.h"
#define DEBUG false
using namespace std;
SnakeGame::SnakeGame(): snake(1, MAP_SIZE / 2), wall(MAP_SIZE, 1) {
// 시작 상태 초기화(스테이지, 아이템)
currStage = 1;
totalCnt = MissionCnt(snake.get_snake_length());
currCnt = MissionCnt(snake.get_snake_length());
mapUpate();
}
bool SnakeGame::getNextGateShowFlag() {
return this->nextGateShowFlag;
}
void SnakeGame::setNextGateShowFlag(bool nextGateShowFlag) {
this->nextGateShowFlag = nextGateShowFlag;
}
void SnakeGame::init() {
// Screen 초기화
initscr();
// CMD 색상 사용
start_color();
noecho();
nodelay(stdscr, TRUE);
// 칸 별 색상 정의
// 그런데, pair는 1부터 시작할 수 있기 때문에 + 1해서 증가
// 이제 굳이 board 전체를 순회해서 1로 만들어줄 필요는 없기 때문
init_pair(ELEMENT_KIND::BOARD + 1, COLOR_WHITE, COLOR_WHITE);
init_pair(ELEMENT_KIND::IMMU_WALL + 1, COLOR_BLUE, COLOR_BLUE);
init_pair(ELEMENT_KIND::WALL + 1, COLOR_CYAN, COLOR_CYAN);
init_pair(ELEMENT_KIND::SNAKE_HEAD + 1, COLOR_GREEN, COLOR_GREEN);
init_pair(ELEMENT_KIND::SNAKE_BODY + 1, COLOR_YELLOW, COLOR_YELLOW);
init_pair(ELEMENT_KIND::GATE + 1, COLOR_MAGENTA, COLOR_MAGENTA);
init_pair(ELEMENT_KIND::NEXT_GATE + 1, COLOR_YELLOW, COLOR_YELLOW);
init_pair(ELEMENT_KIND::GROWTH_ITEM + 1, COLOR_GREEN, COLOR_GREEN);
init_pair(ELEMENT_KIND::POISON_ITEM + 1, COLOR_RED, COLOR_RED);
init_pair(ELEMENT_KIND::REVERSE_ITEM + 1, COLOR_BLACK, COLOR_BLACK);
init_pair(11, COLOR_RED, COLOR_WHITE);
this->scoreBoard = newwin(15, 25, 5, 75);
this->missionBoard = newwin(9, 25, 20, 75);
this->gameBoard = newwin(31, 62, 5, 10);
this->noticeText = newwin(1, 70, 3, 15);
}
void SnakeGame::draw(const string& msg, bool clear) {
for(int i = 0; i < 100 && clear; i++){
for(int j = 0; j < 130; j++){
mvprintw(i, j, " ");
}
}
if(msg.compare("") != 0){
wattr_on(this->gameBoard, COLOR_PAIR(10), NULL);
if(msg.find("change") == string::npos){
mvprintw(10, 0, msg.c_str());
mvprintw(20, 50, "Press Any Key");
refresh();
} else {
wattr_on(this->gameBoard, COLOR_PAIR(11), NULL);
mvwprintw(this->gameBoard, 13, 24, "Mission Clear!");
mvwprintw(this->gameBoard, 15, 10, msg.c_str());
wrefresh(this->gameBoard);
}
} else {
// 반각문자를 출력하기 때문에
// 가로의 좌표를 출력할 때는 2칸씩 건너뛰어야 함
for(int i = 0; i < MAP_SIZE; i++){
for(int j = 0; j < MAP_SIZE * 2; j += 2){
// 2칸씩 좌표를 건너뛰지만, map의 길이는 MAP_SIZE 만큼이기 때문에
// j / 2 를 해서 올바른 접근을 하게 함
if(!this->getNextGateShowFlag() && this->map[i][j / 2] == ELEMENT_KIND::NEXT_GATE)
wattr_on(this->gameBoard, COLOR_PAIR(ELEMENT_KIND::WALL + 1), NULL);
else
wattr_on(this->gameBoard, COLOR_PAIR(this->getElement(i, j / 2) + 1), NULL);
// 반각문자이기 때문에 공백 2칸 출력
mvwprintw(this->gameBoard, i, j, DEBUG ? "a " : " ");
}
}
}
// ncurses 함수 / 화면 갱신
wrefresh(this->gameBoard);
}
void SnakeGame::drawScoreBoard(int64_t time) {
box(this->scoreBoard, 0, 0);
box(this->missionBoard, 0, 0);
mvwprintw(this->scoreBoard, 1, 7, "Score");
mvwprintw(this->missionBoard, 1, 7, "Mission");
mvwprintw(this->scoreBoard, 3, 3, "Time(mm:ss) : %02lld:%02lld", time / 60LL, time % 60LL);
mvwprintw(this->scoreBoard, 5, 8, "HP : %d", this->mission[this->currStage - 1].poisonItem - currCnt.poisonItem);
mvwprintw(this->scoreBoard, 7, 8, " B : %d / %d", this->snake.get_snake_length(), this->totalCnt.maxSnakeLength);
mvwprintw(this->scoreBoard, 9, 8, "GI : %d", this->totalCnt.growthItem);
mvwprintw(this->scoreBoard, 11, 8, " G : %d", this->totalCnt.gate);
mvwprintw(this->missionBoard, 3, 8, " B : %d / %d", this->snake.get_snake_length(), this->mission[this->currStage - 1].maxSnakeLength);
mvwprintw(this->missionBoard, 5, 8, "GI : %d / %d", this->currCnt.growthItem, this->mission[this->currStage - 1].growthItem);
mvwprintw(this->missionBoard, 7, 8, " G : %d / %d", this->currCnt.gate, this->mission[this->currStage - 1].gate);
wrefresh(this->scoreBoard);
wrefresh(this->missionBoard);
}
void SnakeGame::changeGate() {
setElement(this->wall.getNowGate1(), ELEMENT_KIND::WALL);
setElement(this->wall.getNowGate2(), ELEMENT_KIND::WALL);
this->wall.initGate();
setElement(this->wall.getNowGate1(), ELEMENT_KIND::GATE);
setElement(this->wall.getNowGate2(), ELEMENT_KIND::GATE);
setElement(this->wall.getNextGate1(), ELEMENT_KIND::NEXT_GATE);
setElement(this->wall.getNextGate2(), ELEMENT_KIND::NEXT_GATE);
}
void SnakeGame::changeMap(){
// 게임 상태 업데이트 작업
// 미션 달성 체크 및 스테이지 변경 로직 구현
// 스테이지 넘어가는 로직
currStage++;
SnakeGame::mapUpate(); // 맵 업데이트 호출
// 다음 스테이지 초기화 작업 등을 수행
}
bool SnakeGame::isMissionClear(){
bool ret = this->snake.get_snake_length() >= this->mission[this->currStage - 1].maxSnakeLength;
ret &= this->currCnt.growthItem >= this->mission[this->currStage - 1].growthItem;
ret &= this->currCnt.gate >= this->mission[this->currStage - 1].gate;
return ret;
}
void SnakeGame::changeNoticeMessage(const char* msg){
mvwprintw(this->noticeText, 0, 0, " ");
mvwprintw(this->noticeText, 0, 17, msg);
wrefresh(this->noticeText);
}
void SnakeGame::mapUpate(){
if(currStage > 5){
this->setGameStatus(GAME_STATUS::WIN);
return;
}
this->currCnt = MissionCnt();
// map 초기화
memset(map, ELEMENT_KIND::BOARD, sizeof(map));
this->snake = Snake(1, MAP_SIZE / 2);
// Snake 시작 위치 초기화
this->map[1][MAP_SIZE / 2] = ELEMENT_KIND::SNAKE_HEAD;
for(int i = 1; i < 4; i++){
this->map[1][MAP_SIZE / 2 + i] = ELEMENT_KIND::SNAKE_BODY;
}
this->wall = Wall(MAP_SIZE, currStage);
// map 외곽 초기화(WALL)
for(pos p: this->wall.get_wall_info())
setElement(p, ELEMENT_KIND::WALL);
this->changeGate();
// map 외곽 초기화(IMMU_WALL)
this->map[0][0] = this->map[0][MAP_SIZE - 1] = ELEMENT_KIND::IMMU_WALL;
this->map[MAP_SIZE - 1][0] = this->map[MAP_SIZE - 1][MAP_SIZE - 1] = ELEMENT_KIND::IMMU_WALL;
if(currStage == 5){
this->map[12][4] = this->map[12][8] = this->map[12][16] = ELEMENT_KIND::IMMU_WALL;
this->map[18][14] = this->map[18][22] = this->map[18][26] = ELEMENT_KIND::IMMU_WALL;
for(int i = 11; i < 20; i++){
this->map[i][3] = ELEMENT_KIND::IMMU_WALL;
}
for(int i = 3; i < 10; i++){
this->map[11][i] = ELEMENT_KIND::IMMU_WALL;
}
for(int i = 11; i < 16; i++){
this->map[i][9] = ELEMENT_KIND::IMMU_WALL;
}
for(int i = 9; i < 16; i++){
this->map[19][i] = ELEMENT_KIND::IMMU_WALL;
}
for(int i = 11; i < 19; i++){
this->map[i][15] = ELEMENT_KIND::IMMU_WALL;
}
for(int i = 15; i < 22; i++){
this->map[11][i] = ELEMENT_KIND::IMMU_WALL;
}
for(int i = 15; i < 19; i++){
this->map[i][21] = ELEMENT_KIND::IMMU_WALL;
}
for(int i = 21; i < 28; i++){
this->map[19][i] = ELEMENT_KIND::IMMU_WALL;
}
for(int i = 11; i < 19; i++){
this->map[i][27] = ELEMENT_KIND::IMMU_WALL;
}
}
}
void SnakeGame::update(int64_t time){
if(this->wall.isUsed()) this->wall.update_remain_length();
switch(this->getElement(this->snake.new_head())){
// 앞으로 이동
case ELEMENT_KIND::BOARD:{
// 현재 머리 좌표는 SNAKE::BODY로 변경, 새로운 머리 좌표는 SNAKE_HEAD로 변경
if(this->getElement(this->snake.head()) != ELEMENT_KIND::GATE){
this->setElement(this->snake.head(), ELEMENT_KIND::SNAKE_BODY);
// 현재 꼬리는 ELEMENT_KIND::BOARD로 변경하고,
this->setElement(this->snake.tail(), ELEMENT_KIND::BOARD);
this->snake.shrink();
this->snake.grow();
this->setElement(this->snake.head(), ELEMENT_KIND::SNAKE_HEAD);
} else {
this->snake.move_head(this->snake.new_head());
this->setElement(this->snake.head(), ELEMENT_KIND::SNAKE_HEAD);
}
// 새로운 머리의 위치를 맨 앞에 삽입
break;
}
case ELEMENT_KIND::GROWTH_ITEM:{
this->changeNoticeMessage("Eat Growth Item!, Snake Length + 1");
this->totalCnt.growthItem += 1;
this->currCnt.growthItem += 1;
this->currCnt.poisonItem = (this->currCnt.poisonItem - 1 < 0) ? 0 : (this->currCnt.poisonItem - 1);
this->setElement(this->snake.head(), ELEMENT_KIND::SNAKE_BODY);
this->snake.grow();
this->setElement(this->snake.head(), ELEMENT_KIND::SNAKE_HEAD);
this->totalCnt.maxSnakeLength = max(this->totalCnt.maxSnakeLength, this->snake.get_snake_length());
break;
}
case ELEMENT_KIND::POISON_ITEM:{
this->changeNoticeMessage("Eat Poison Item..., HP - 1");
this->totalCnt.poisonItem += 1;
this->currCnt.poisonItem += 1;
if(this->mission[currStage - 1].poisonItem == this->currCnt.poisonItem) {
this->setGameStatus(GAME_STATUS::LOSE);
return;
}
this->setElement(this->snake.head(), ELEMENT_KIND::SNAKE_BODY);
// 현재 꼬리는 ELEMENT_KIND::BOARD로 변경하고,
this->setElement(this->snake.tail(), ELEMENT_KIND::BOARD);
this->snake.shrink();
this->snake.grow();
this->setElement(this->snake.head(), ELEMENT_KIND::SNAKE_HEAD);
this->setElement(this->snake.tail(), ELEMENT_KIND::BOARD);
this->snake.shrink();
if(snake.get_snake_length() < 3) this->setGameStatus(GAME_STATUS::LOSE);
break;
}
case ELEMENT_KIND::REVERSE_ITEM:{
this->setElement(this->snake.new_head(), ELEMENT_KIND::BOARD);
this->changeNoticeMessage("Snake Reversed!");
this->setElement(this->snake.tail(), ELEMENT_KIND::SNAKE_HEAD);
this->setElement(this->snake.head(), ELEMENT_KIND::SNAKE_BODY);
this->snake.set_head_direction((this->snake.get_head_direction() + 2) % 4);
this->snake.reverse();
break;
}
case ELEMENT_KIND::GATE:{
this->changeNoticeMessage(" Teleport! ");
this->totalCnt.gate += 1;
this->currCnt.gate += 1;
this->wall.setUsed(this->snake.get_snake_length());
const pos& exit = this->snake.new_head() != this->wall.getNowGate1() ? this->wall.getNowGate1() : this->wall.getNowGate2();
// 포탈을 통해 이동한 것과 같으니까 꼬리 1칸 줄이기
this->setElement(this->snake.tail(), ELEMENT_KIND::BOARD);
this->snake.shrink();
// 원래 머리는 SNAKE_BODY로 변경, 탈출구 위치에 머리 놓기 ==> 만약에 포탈 앞에 아이템이 놓여있으면 적용 X
this->setElement(this->snake.head(), ELEMENT_KIND::SNAKE_BODY);
// 탈출구 쪽으로 머리 늘리기
this->snake.grow(exit);
// 포탈에서 나갈 방향 정하기
if(exit.Y == 0) this->snake.set_head_direction(SNAKE_HEAD_DIRECTION::DOWN);
else if(exit.Y == MAP_SIZE - 1) this->snake.set_head_direction(SNAKE_HEAD_DIRECTION::UP);
else if(exit.X == 0) this->snake.set_head_direction(SNAKE_HEAD_DIRECTION::RIGHT);
else if(exit.X == MAP_SIZE - 1) this->snake.set_head_direction(SNAKE_HEAD_DIRECTION::LEFT);
else {
int direction = this->snake.get_head_direction();
do {
if(this->getElement(this->snake.new_head()) != ELEMENT_KIND::WALL
&& this->getElement(this->snake.new_head()) != ELEMENT_KIND::IMMU_WALL
&& this->getElement(this->snake.new_head()) != ELEMENT_KIND::NEXT_GATE)
break;
this->snake.set_head_direction((this->snake.get_head_direction() + 1) % 4);
} while(direction != this->snake.get_head_direction());
}
// 새롭게 업데이트
update(time);
return;
}
case ELEMENT_KIND::NEXT_GATE:
case ELEMENT_KIND::SNAKE_BODY:
case ELEMENT_KIND::IMMU_WALL:
case ELEMENT_KIND::WALL:
this->setGameStatus(GAME_STATUS::LOSE);
break;
}
this->draw();
this->drawScoreBoard(time);
}
void SnakeGame::createItems() {
srand(time(NULL));
// random 공간에 아이템 지정하기 (growth, poison)
for (int i = 0; i < NUM_ITEMS; i++) {
pos itemPosition;
do {
// map에서 빈 공간 중 random 으로 하나 지정
itemPosition = findRandomEmptySpace();
} while (getElement(itemPosition) != ELEMENT_KIND::BOARD); // 빈 공간이 없으면 다시 시도 -> 빈 공간 찾을때 까지
// 아이템 종류 지정
int rNum = rand() % 3;
int itemType = (rNum == 0) ? ELEMENT_KIND::GROWTH_ITEM : (rNum == 1) ? ELEMENT_KIND::POISON_ITEM : ELEMENT_KIND::REVERSE_ITEM;
//아이템 map에 지정
setElement(itemPosition, itemType);
}
}
void SnakeGame::removeExpiredItems() {
// 시간이 만료된 아이템 지우기
for (int i = 1; i < MAP_SIZE - 1; i++) {
for (int j = 1; j < MAP_SIZE - 1; j++) {
if (this->map[i][j] >= ELEMENT_KIND::GROWTH_ITEM) {
setElement(pos(i, j), ELEMENT_KIND::BOARD);
}
}
}
}
pos SnakeGame::findRandomEmptySpace() {
pos emptySpace = {-1, -1};
//임의의 시작점을 찾고 random으로 출현
int startX = rand() % (MAP_SIZE - 2) + 1;
int startY = rand() % (MAP_SIZE - 2) + 1;
for (int i = 0; i < MAP_SIZE - 2; i++) {
int x = (startX + i) % (MAP_SIZE - 2) + 1;
for (int j = 0; j < MAP_SIZE - 2; j++) {
int y = (startY + j) % (MAP_SIZE - 2) + 1;
if (this->map[y][x] == ELEMENT_KIND::BOARD) {
emptySpace = {y, x};
return emptySpace;
}
}
}
return emptySpace;
}