-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.cpp
More file actions
1381 lines (1201 loc) · 55.7 KB
/
main.cpp
File metadata and controls
1381 lines (1201 loc) · 55.7 KB
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
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Dragon Flight by Mingyeol Kim, Sujung Lee
===! 주의 !===
이 파일은 UTF-8로 인코딩 되어있어 DEV-C++로 열 수 없습니다.
컴파일 시에는 C++11 이상을 사용하여야 합니다.
다음의 옵션으로 컴파일 하는것을 추천합니다.
"command": "g++",
"args": [
"-static-libgcc",
"-static-libstdc++",
"-Wl,-Bstatic",
"-lstdc++",
"-lpthread",
"-Wl,-Bstatic,--whole-archive",
"-lwinpthread",
"-Wl,-Bdynamic",
"-Wl,--no-whole-archive",
"-g",
"${file}",
"-o",
"${fileDirname}/${fileBasenameNoExtension}.exe"
]
=== 게임 설명 ===
이 게임은 라인 게임즈의 드레곤 플라이트를 콘솔 버전으로 모작한 게임입니다.
[Dragons, 용]
- 흰색 : 0m 이후에서 나옵니다. (50점)
- 노란색 : 500m 이후에서 나옵니다. (100점)
- 초록색 : 1000m 이후에서 나옵니다. (200점)
- 빨간색 : 1500m 이후에서 나옵니다. (300점)
- 보라색 : 2000m 이후에서 나옵니다. (500점)
[Meteorite, 운석]
빨간색 느낌표로 줄이 생성되며 운석이 떨어질 것을 예고합니다.
운석은 매우 빠른 속력으로 내려오며 1x1 크기입니다.
=== 클래스 설명 ===
class Frame
-> 게임의 화면을 출력하기 위한 클래스입니다.
-> 매 프레임마다 패치 및 출력을 합니다.
class Game
-> 게임의 논리 구현부 입니다.
-> 매 클럭마다 플레이어의 위치를 연산하며, 배열을 출력합니다.
-> 매 프레임 마다 게임 구성 요소(몬스터, 운석, 플레이어와의 충돌 등)를 움직이는 연산을 합니다.
*/
//IO 컨트롤
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <thread>
#include <future>
#include <chrono>
#include <fstream>
#include <sstream>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include <Windows.h>
#include <tchar.h>
#include <shlobj.h>
#include <shellapi.h>
#include <exdisp.h>
using namespace std;
namespace Startup{
const wchar_t* RELAUNCH_GUARD = L"DRAGON_FLIGHT_CONHOST_RELAUNCHED";
wstring quoteArg(const wstring& arg){
wstring quoted = L"\"";
int backslashCount = 0;
for(wchar_t ch : arg){
if(ch == L'\\'){
backslashCount++;
}
else if(ch == L'\"'){
quoted.append(backslashCount * 2 + 1, L'\\');
quoted += ch;
backslashCount = 0;
}
else{
if(backslashCount > 0){
quoted.append(backslashCount, L'\\');
backslashCount = 0;
}
quoted += ch;
}
}
if(backslashCount > 0) quoted.append(backslashCount * 2, L'\\');
quoted += L"\"";
return quoted;
}
bool shouldRelaunchInClassicConsole(){
if(GetEnvironmentVariableW(RELAUNCH_GUARD, NULL, 0) > 0) return false;
return GetEnvironmentVariableW(L"WT_SESSION", NULL, 0) > 0;
}
bool relaunchInClassicConsole(){
int argc = 0;
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
if(argv == NULL) return false;
wchar_t exePath[MAX_PATH];
if(GetModuleFileNameW(NULL, exePath, MAX_PATH) == 0){
LocalFree(argv);
return false;
}
wstring command = L"conhost.exe -- ";
command += quoteArg(exePath);
for(int i = 1; i < argc; i++){
command += L" ";
command += quoteArg(argv[i]);
}
LocalFree(argv);
SetEnvironmentVariableW(RELAUNCH_GUARD, L"1");
STARTUPINFOW si = { 0, };
PROCESS_INFORMATION pi = { 0, };
si.cb = sizeof(si);
vector<wchar_t> mutableCommand(command.begin(), command.end());
mutableCommand.push_back(L'\0');
BOOL created = CreateProcessW(
NULL,
mutableCommand.data(),
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi
);
if(created == TRUE){
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return true;
}
SetEnvironmentVariableW(RELAUNCH_GUARD, NULL);
return false;
}
}
//색깔 정의
#define BLACK 0 //어두움
#define BLUE 1
#define GREEN 2
#define BLUEGREEN 3
#define RED 4
#define PURPLE 5
#define YELLOW 6
#define WHITE 7
#define GRAY 8
#define SKYBLUE 9
#define B_GREEN 10 //밝음
#define B_BLUEGREEN 11
#define B_RED 12
#define B_PURPLE 13
#define B_YELLOW 14
#define B_WHITE 15
//오브젝트 정의
#define NONE 0
#define PLAYER 1
#define BULLET 2
#define WHITE_DRAGON 3
#define YELLOW_DRAGON 4
#define GREEN_DRAGON 5
#define RED_DRAGON 6
#define PURPLE_DRAGON 7
#define METEOR 8
//체력 정의
#define H_NONE 0
#define H_PLAYER 3
#define H_BULLET 3
#define H_WHITE_DRAGON 1
#define H_YELLOW_DRAGON 2
#define H_GREEN_DRAGON 3
#define H_RED_DRAGON 4
#define H_PURPLE_DRAGON 5
#define H_METEOR 9
//점수 정의
#define S_WHITE_DRAGON 50
#define S_YELLOW_DRAGON 150
#define S_GREEN_DRAGON 200
#define S_RED_DRAGON 300
#define S_PURPLE_DRAGON 500
//이벤트 정의
#define E_KEY_EVENT 1
#define E_MOUSE_EVENT 2
#define PAUSE_KEY 119
#define E_MOUSE_LEFT 1
#define E_MOUSE_RIGHT 2
#define E_Q_KEY 113
#define E_W_KEY 119
#define E_E_KEY 101
typedef struct Element{
int object; //자신의 오브젝트 번호
int health = H_NONE; //자신의 체력
Element* back; //오브젝트의 뒤에 중첩된 오브젝트
} Element;
//콘솔창 제어 함수
namespace Console{ //콘솔을 제어할 함수들을 모아놓은 이름 공간
HANDLE hStdin;
DWORD fdwSaveOldMode;
DWORD cNumRead, fdwMode, i;
INPUT_RECORD irInBuf;
int counter = 0;
typedef struct xy{ //좌표 반환을 위해 설계한 구조체
int x;
int y;
} xy;
typedef struct eventStruct{ //마우스와 키보드 이벤트 반환을 위해 설계한 구조체
int eventType;
int key;
bool keyPressed;
bool Clicked;
bool ClickKey;
xy coordinate;
} eventStruct;
void init(){ //인코딩을 UTF-8로 바꾸고, 콘솔창 제목을 설정
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
SetConsoleTitle(TEXT("dragon flight"));
}
void sleep(float sec){ //sec초 만큼 정지
Sleep(sec * 1000.0);
}
void cls(){ //화면을 초기화
system("cls");
}
void windowSize(int x, int y){ //윈도우 사이즈를 바꿈
string query = "mode con cols=" + to_string(x) + " lines=" + to_string(y);
system(&query[0]);
}
void cursorVisible(bool status){ //커서의 표시 여부
CONSOLE_CURSOR_INFO cursorInfo = { 0, };
cursorInfo.dwSize = 100;
if(status == true) cursorInfo.bVisible = TRUE;
else cursorInfo.bVisible = FALSE;
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursorInfo);
}
void gotoxy(int x, int y){ //커서를 (x, y)로 이동
COORD Pos;
Pos.X = x;
Pos.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), Pos);
}
void setColor(int text, int back){ //글자색과 배경색을 바꿈
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), text | (back << 4));
}
void ErrorExit(LPCSTR lpszMessage){ //lpszMessage를 출력 후 프로그램 종료
fprintf(stderr, "%s\n", lpszMessage);
SetConsoleMode(hStdin, fdwSaveOldMode);
system("pause > nul");
ExitProcess(0);
}
void useEventInput(bool status){ //이벤트 입력(마우스 / 키보드)을 사용할지 결정
if(status == true){
hStdin = GetStdHandle(STD_INPUT_HANDLE);
if (hStdin == INVALID_HANDLE_VALUE)
ErrorExit("GetStdHandle");
if (!GetConsoleMode(hStdin, &fdwSaveOldMode))
ErrorExit("GetConsoleMode");
fdwMode = ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT | ENABLE_INSERT_MODE | ENABLE_EXTENDED_FLAGS;
if (!SetConsoleMode(hStdin, fdwMode))
ErrorExit("SetConsoleMode");
}else{
SetConsoleMode(hStdin, fdwSaveOldMode);
}
}
void getEvent(eventStruct* event){ //발생한 이벤트를 event에 저장
if (!ReadConsoleInput(hStdin, &irInBuf, 1, &cNumRead))
ErrorExit("ReadConsoleInput");
switch (irInBuf.EventType){
case KEY_EVENT: {// keyboard 인풋일때
char keyStr[5];
sprintf(keyStr, "%d", irInBuf.Event.KeyEvent.uChar);
event->key = atoi(keyStr);
event->keyPressed = irInBuf.Event.KeyEvent.bKeyDown;
event->eventType = E_KEY_EVENT;
break;
}
case MOUSE_EVENT: {// mouse 인풋일때
if(irInBuf.Event.MouseEvent.dwEventFlags == 0){
if (irInBuf.Event.MouseEvent.dwButtonState == FROM_LEFT_1ST_BUTTON_PRESSED){
event->Clicked = true;
event->ClickKey = E_MOUSE_LEFT;
}
else if (irInBuf.Event.MouseEvent.dwButtonState == RIGHTMOST_BUTTON_PRESSED){
event->Clicked = true;
event->ClickKey = E_MOUSE_RIGHT;
}else event->Clicked = false;
}
int mouse_x = irInBuf.Event.MouseEvent.dwMousePosition.X;
int mouse_y = irInBuf.Event.MouseEvent.dwMousePosition.Y;
event->eventType = E_MOUSE_EVENT;
event->coordinate.x = mouse_x;
event->coordinate.y = mouse_y;
break;
}
}
}
void waitEvent(promise<eventStruct> *p){ //이벤트를 기다릴 스레드 함수
eventStruct event;
event.eventType = NONE;
while(event.eventType == NONE){
getEvent(&event);
}
p->set_value(event);
}
void moveWindowCenter(){ //콘솔창을 화면 정가운데로 옮김
HWND hwndmoveWindow = GetConsoleWindow();
RECT consoleWindow;
::GetWindowRect(hwndmoveWindow, &consoleWindow);
xy consoleWindowSize = {consoleWindow.right - consoleWindow.left, consoleWindow.bottom - consoleWindow.top};
xy screenSize = {GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)};
::SetWindowPos(hwndmoveWindow, HWND_TOPMOST, (screenSize.x - consoleWindowSize.x)/2, (screenSize.y - consoleWindowSize.y)/2, 0, 0, SWP_NOSIZE | SWP_NOREDRAW );
}
void moveWindowCoordinate(int x, int y){ //콘솔창을 죄측 상단을 기준으로 (x, y)로 이동
HWND hwndmoveWindow = GetConsoleWindow();
::SetWindowPos(hwndmoveWindow, HWND_TOPMOST, x, y, 0, 0, SWP_NOSIZE | SWP_NOREDRAW );
}
}
//=================================== 메인 코드 ===================================
class Frame{
public:
//frame 설정
int fps; //초당 프레임
int horizontal; //가로
int vertical; //세로
Element **frame; //frame 포인터
double interval; //fps에 따른 frame갱신 시간
int consolehorizontal; //콘솔창 가로
int consolevertical; //콘솔창 세로
void print(); //게임 화면 프린트
void printAlert(int alertcode); //인내 메세지 프린트
void printIntro(); //인트로 프린트
void printMain(); //메인 화면 프린트
void printPause(); //일시정지 화면 프린트
void printBlank(); //빈 틀을 프린트
void printLogo(); //로고 프린트
void printColorLine(int textcolor, int backcolor, int horizontal); //컬러 라인을 프린트
void printScore(int score, int distance, int level, int levelCriteria, int PlayerHealth); //점수 프린트
void printScoreframe(); //점수 프레임 프린트
void printGameOver(int score, int distance, int level); //게임 오버 화면 프린트
int SkipFramePer = 1; //출력할 프레임 배수(나머지 프레임은 출력을 하지 않음)
int LogoVertical = 3; //로고 세로 길이
int LeftSpace = 6; //게임 배열 좌측 공간
int UpperSpace = 4; //게임 배열 위쪽 공간
int ScoreboardHeight = 4; //점수 프레임을 프린트할 때 위의 공간
int alertcode; //알림 코드
Frame(int fps, int horizontal, int vertical); //생성자
};
Frame::Frame(int fps, int horizontal, int vertical){
this->fps = fps;
this->horizontal = horizontal;
this->vertical = vertical;
this->interval = 1.0/(float)(this->fps);
this->alertcode = 0;
this->frame = new Element *[this->vertical];
for(int i=0;i < this->vertical;i++){
this->frame[i] = new Element [this->horizontal];
}
Console::init();
}
/*
[Frame::print()]
다음의 알고리즘을 시행합니다.
1. frame 배열을 프린트합니다. (배경색은 Element, 숫자는 체력을 의미한다.)
*/
void Frame::print(){ //게임중 배열을 프린트
Console::gotoxy(0, this->UpperSpace);
if(this->alertcode == 2) Console::setColor(B_RED, BLACK);
else Console::setColor(B_WHITE, BLACK);
Console::gotoxy(this->LeftSpace, this->UpperSpace);
printf("┌");
for(int v=0;v<this->horizontal;v++) printf("─");
printf("┐\n");
int v;
for(v=0;v<this->vertical;v++){
Console::gotoxy(this->LeftSpace, this->UpperSpace+v+1);
if(this->alertcode == 2) Console::setColor(B_RED, BLACK);
else Console::setColor(B_WHITE, BLACK);
printf("│");
Console::setColor(B_WHITE, BLACK);
for(int h=0;h<this->horizontal;h++){
if(this->frame[v][h].object == NONE){
Console::setColor(B_WHITE, BLACK);
printf(" ");
}
else if(this->frame[v][h].object == METEOR){
Console::setColor(B_RED, B_RED);
printf(" ");
}
else if(this->frame[v][h].object == BULLET){
Console::setColor(B_WHITE, GRAY);
printf("%d", this->frame[v][h].health);
}
else if(this->frame[v][h].object < BULLET){
Console::setColor(B_WHITE, BLACK);
printf("%d", this->frame[v][h].object);
}
else if(this->frame[v][h].object > BULLET){
if(this->frame[v][h].object == WHITE_DRAGON) Console::setColor(BLACK, B_WHITE);
else if(this->frame[v][h].object == YELLOW_DRAGON) Console::setColor(BLACK, B_YELLOW);
else if(this->frame[v][h].object == GREEN_DRAGON) Console::setColor(BLACK, B_GREEN);
else if(this->frame[v][h].object == RED_DRAGON) Console::setColor(BLACK, B_RED);
else if(this->frame[v][h].object == PURPLE_DRAGON) Console::setColor(BLACK, B_PURPLE);
else Console::setColor(B_WHITE, BLACK);
printf("%d", this->frame[v][h].health);
}
}
if(this->alertcode == 2) Console::setColor(B_RED, BLACK);
else Console::setColor(B_WHITE, BLACK);
printf("│");
Console::setColor(B_WHITE, BLACK);
}
Console::gotoxy(this->LeftSpace, this->UpperSpace+v+1);
if(this->alertcode == 2) Console::setColor(B_RED, BLACK);
else Console::setColor(B_WHITE, BLACK);
printf("└");
for(int v=0;v<this->horizontal;v++) printf("─");
printf("┘\n");
Console::setColor(B_WHITE, BLACK);
}
void Frame::printAlert(int alertcode){
switch(alertcode){
case 0:
Console::gotoxy((this->consolehorizontal - 30)/2, this->consolevertical - 1);
cout << " ";
this->alertcode = 0;
break;
case 1:
Console::gotoxy((this->consolehorizontal - 30)/2, this->consolevertical - 1);
Console::setColor(B_WHITE, BLACK);
cout << "[W]를 눌러 일지정지 할 수 있습니다. ";
this->alertcode = 1;
break;
case 2:
Console::gotoxy((this->consolehorizontal - 32)/2, this->consolevertical - 1);
Console::setColor(B_RED, BLACK);
cout << "마우스를 플레이 범위 안으로 옮겨주세요! ";
Console::setColor(B_WHITE, BLACK);
this->alertcode = 2;
break;
default:
break;
}
}
void Frame::printLogo(){ //게임중 로고를 프린트
int nowline = 0;
string line;
fstream logo;
logo.open("assets/logo/MAINLOGO", fstream::in);
while (getline(logo, line))
{
Console::gotoxy((this->consolehorizontal - 150) * 2, nowline++);
cout << line << endl;
}
}
void Frame::printScoreframe(){ //게임중 스코어보드 틀을 프린트
Console::gotoxy(this->horizontal * 2, this->ScoreboardHeight);
printf("┌");
for(int h=0;h<22;h++) printf("─");
printf("┐");
int v;
for(v=0;v<7;v++){
Console::gotoxy(this->horizontal * 2, this->ScoreboardHeight + 1 + v);
printf("│");
for(int h=0;h<22;h++) printf(" ");
printf("│");
}
Console::gotoxy(this->horizontal * 2, this->ScoreboardHeight + 1 + v);
printf("└");
for(int v=0;v<22;v++) printf("─");
printf("┘\n");
}
void Frame::printScore(int score, int distance, int level, int levelCriteria, int PlayerHealth){ //게임중 스코어보드 내에 내용을 출력
Console::gotoxy((this->horizontal + 1) * 2, this->ScoreboardHeight + 2);
printf("점수 : %d점", score);
Console::gotoxy((this->horizontal + 1) * 2, this->ScoreboardHeight + 3);
printf("거리 : %dm", distance);
Console::gotoxy((this->horizontal + 1) * 2, this->ScoreboardHeight + 4);
printf("체력 : ");
Console::setColor(B_RED, BLACK);
for(int i=0;i<PlayerHealth;i++) printf("❤ ");
for(int i=0;i<H_PLAYER - PlayerHealth;i++) printf(" ");
Console::setColor(B_WHITE, BLACK);
Console::gotoxy((this->horizontal + 1) * 2, this->ScoreboardHeight + 5);
printf("페이즈 : %d번째 ", level + 1);
Console::gotoxy((this->horizontal + 1) * 2, this->ScoreboardHeight + 6);
printf("현재 페이즈 [%.1lf%%] ", ((double)(distance % levelCriteria)/(double)levelCriteria)*(double)100);
}
void Frame::printMain(){ //메인 화면을 출력
Console::windowSize(this->consolehorizontal, this->consolevertical + 20);
Console::moveWindowCenter();
Console::gotoxy(0, 0);
printf("┌");
for(int i=0;i<this->consolehorizontal - 3;i++) printf("─");
printf("┐");
for(int i=0;i<this->consolevertical + 18;i++){
Console::gotoxy(0, i+1);
printf("│");
for(int j=0;j<this->consolehorizontal - 3;j++) printf(" ");
printf("│");
}
Console::gotoxy(0, this->consolevertical + 19);
printf("└");
for(int i=0;i<this->consolehorizontal - 3;i++) printf("─");
printf("┘");
int nowline = 0;
string line;
fstream logo;
logo.open("assets/logo/MAINLOGO", fstream::in);
while (getline(logo, line))
{
Console::gotoxy((this->consolehorizontal - 100) / 2, 4 + nowline++);
cout << line << endl;
}
Console::gotoxy((this->consolehorizontal - 14) / 2, this->consolevertical + 1);
printf("시작하기 [Q]");
Console::gotoxy((this->consolehorizontal - 14) / 2, this->consolevertical + 5);
printf("종료하기 [W]");
Console::gotoxy((this->consolehorizontal - 14) / 2, this->consolevertical + 9);
printf("튜토리얼 [E]");
}
void Frame::printPause(){ //일시정지 화면을 출력
Console::moveWindowCenter();
Console::gotoxy(0, 0);
printf("┌");
for(int i=0;i<this->consolehorizontal - 3;i++) printf("─");
printf("┐");
for(int i=0;i<this->consolevertical - 2;i++){
Console::gotoxy(0, i+1);
printf("│");
for(int j=0;j<this->consolehorizontal - 3;j++) printf(" ");
printf("│");
}
Console::gotoxy(0, this->consolevertical - 1);
printf("└");
for(int i=0;i<this->consolehorizontal - 3;i++) printf("─");
printf("┘");
int nowline = 0;
string line;
fstream logo;
logo.open("assets/logo/PAUSELOGO", fstream::in);
while (getline(logo, line))
{
Console::gotoxy((this->consolehorizontal - 42) / 2, 4 + nowline++);
cout << line << endl;
}
Console::gotoxy((this->consolehorizontal - 14) / 2, 15);
printf("종료하기 [Q]");
Console::gotoxy((this->consolehorizontal - 14) / 2, 19);
printf("복귀하기 [W]");
Console::gotoxy((this->consolehorizontal - 14) / 2, 23);
printf("다시시작 [E]");
}
void Frame::printBlank(){ //아무것도 없는 화면을 출력
Console::gotoxy(0, 0);
for(int i=0;i<this->consolevertical;i++){
for(int j=0;j<this->consolehorizontal - 1;j++) printf(" ");
printf("\n");
}
}
void Frame::printColorLine(int textcolor, int backcolor, int horizontal){ //운석이 떨어질 것을 알리는 선을 출력
Console::setColor(textcolor, backcolor);
for(int i=0;i<this->vertical - 1;i++){
Console::gotoxy(this->LeftSpace + 1 + horizontal, this->UpperSpace + 1 + i);
printf(" ! ");
}
Console::setColor(B_WHITE, BLACK);
}
void Frame::printGameOver(int score, int distance, int level){ //게임 오버 화면을 출력
Console::moveWindowCenter();
Console::gotoxy(0, 0);
printf("┌");
for(int i=0;i<this->consolehorizontal - 3;i++) printf("─");
printf("┐");
for(int i=0;i<this->consolevertical - 2;i++){
Console::gotoxy(0, i+1);
printf("│");
for(int j=0;j<this->consolehorizontal - 3;j++) printf(" ");
printf("│");
}
Console::gotoxy(0, this->consolevertical - 1);
printf("└");
for(int i=0;i<this->consolehorizontal - 3;i++) printf("─");
printf("┘");
int nowline = 0;
string line;
fstream logo;
logo.open("assets/logo/GAMEOVERLOGO", fstream::in);
while (getline(logo, line))
{
Console::gotoxy((this->consolehorizontal - 74) / 2, 4 + nowline++);
cout << line << endl;
}
Console::gotoxy((this->consolehorizontal - 14) / 2, 15);
printf("다시시작 [Q]");
Console::gotoxy((this->consolehorizontal - 14) / 2, 19);
printf("종료하기 [W]");
Console::gotoxy((this->consolehorizontal - 14) / 2, 23);
printf("메인화면 [E]");
Console::gotoxy((this->consolehorizontal - 14) / 2, 25);
printf("간 거리 : %dm", distance);
Console::gotoxy((this->consolehorizontal - 14) / 2, 27);
printf("총 점수 : %d점", score);
Console::gotoxy((this->consolehorizontal - 14) / 2, 29);
printf("%d 페이즈 달성!", level);
}
void Frame::printIntro(){ //게임 시작시 인트로 출력
Console::windowSize(166, 47);
Console::moveWindowCenter();
for(int i=82;i>0;i--){
int nowline = 0;
string line;
fstream logo;
string filename = "assets/intro/" + to_string(i) + ".txt";
Console::gotoxy(0, 0);
logo.open(&filename[0], fstream::in);
while (getline(logo, line)){
cout << line << endl;
}
}
int nowline = 0;
string line;
fstream logo;
logo.open("assets/logo/MAINTAINERLOGO", fstream::in);
while (getline(logo, line))
{
Console::gotoxy((166 - 75) / 2, 47/2 + nowline++);
cout << line << endl;
}
Console::sleep(2.5);
}
class Game{
public:
Frame *printframe; //Frame 클래스 포인터
Element **frame; //frame 포인터
int distance; //현재 거리
int level; //현재 레벨(levelCriteria의 배수마다 1 증가)
int levelCriteria; //한 레벨을 올리는 데의 기준
int SCREENmain(); //메인 화면이 호출될때 실행되는 함수
int SCREENpause(); //게임을 일시정지할 때 실행되는 함수
int SCREENover(); //게임 오버시 실행되는 함수
int nowAlertcode; //현재 Alertcode
int PlayerHorizontal; //플레이어의 가로 위치
int meteorHorizontal; //운석의 가로 위치
int FrameClock; //프레임을 갱신할 클럭 배수
int patchMonsterClock; //몬스터를 패치할 클럭 배수
int bulletClock; //총알을 패치할 클럭 배수
int meteorClock; //운석을 패치할 클럭 배수
int makeClock(); //연산 클럭을 생성함
bool updateFrame(); //배열을 조작함
void patchPlayer(Console::xy coor); //플레이어의 가로 위치를 프레임에 패치
void patchMonster(); //프레임의 맨 윗줄에 몬스터를 패치
bool shiftFrame(); //맨 윗줄부터 플레이어 이전 줄을 한 칸 아래로 민다.
void printFrame(); //매 클럭당 출력
void addScore(int target); //몬스터에 따라 score변수에 점수 추가
Element randomMonster(int from, int to); //from부터 to까지 범위에 있는 몬스터를 랜덤으로 뽑아 구조체를 반환
int score; //플레이어의 점수
int PlayerHealth; //플레이어의 체력
void init(); //새 게임 시작 전 초기화자
Game(); //생성자
};
Game::Game(){ //생성자 : 메인 함수에서 클래스를 선언할 때 선언하자마자 호출없이 바로 살행되는 함수
this->printframe = new Frame(2000, 15, 25); //frame 배열을 프린트하고, 관리할 Frame 클래스를 printframe이라는 이름으로 선언
this->frame = this->printframe->frame; //game의 frame과 printframe의 frame이 같은 배열을 가르키도록 주소를 복사
this->levelCriteria = 500; //한 레벨을 올리는 데의 기준
this->FrameClock = 10; //FrameClock의 배수 클럭마다 프레임이 갱신된다.
this->patchMonsterClock = 120; //patchMonsterClock의 배수 클럭마다 몬스터가 맨 윗줄에 패치된다.
this->bulletClock = 4; //bulletClock의 배수 클럭마다 플레이어 바로 윗줄에 bullet이 생성이 된다.
this->meteorClock = 100;
this->printframe->consolevertical = this->printframe->vertical + 10; //콘솔창의 세로 길이
this->printframe->consolehorizontal = this->printframe->horizontal + 170; //콘솔창의 가로 길이
Console::useEventInput(true); //마우스 사용을 선언한다.
}
void Game::init(){ //게임을 새로 시작할 때 마다 게임 상황을 초기화해주는 함수
for(int v=0;v<this->printframe->vertical;v++){
for(int h=0;h<this->printframe->horizontal;h++){
this->frame[v][h].object = NONE; //frame 배열의 모든 원소를 NONE으로 초기화한다.
this->frame[v][h].health = H_NONE;
this->frame[v][h].back = new Element;
this->frame[v][h].back->object = NONE;
this->frame[v][h].back->health = H_NONE;
}
}
this->PlayerHealth = H_PLAYER;
this->PlayerHorizontal = this->printframe->horizontal/2; //플레이어의 초기 좌표를 설정한다. [맨 밑줄 (가로길이/2)번째 칸을 지정]
this->meteorHorizontal = this->PlayerHorizontal;
this->frame[this->printframe->vertical-1][this->PlayerHorizontal].object = PLAYER; //PlayerHorizontal 칸을 PLAYER로 지정한다.
this->frame[this->printframe->vertical-1][this->PlayerHorizontal].health = this->PlayerHealth; //플레이어의 체력을 설정한다.
this->distance = 0; //현재 거리
this->level = 0; //현재 난이도
this->score = 0; //점수
this->printframe->SkipFramePer = 1;
srand(time(NULL)); //난수 시드 설정
Console::windowSize(this->printframe->consolehorizontal, this->printframe->consolevertical); //윈도우 사이즈를 바꾼다.
Console::moveWindowCenter();
Console::cls(); //화면을 초기화
Console::cursorVisible(false); //커서를 보이지 않게 한다.
this->printframe->printLogo(); //로고를 프린트한다.
this->printframe->printAlert(1); //안내 메시지를 프린트한다.
this->nowAlertcode = 1;
this->printframe->printScoreframe(); //점수판 위치에 틀을 프린트한다.
Console::useEventInput(true); //마우스 사용을 선언한다.
}
/*
[Game::makeClock()]
다음의 알고리즘을 반복합니다.
1. 마우스의 움직임을 감지할 waitEvent() 스레드를 생성한다.
2. 만약 이벤트가 감지되어 좌표가 반환되면 (2-1) 아니면 (3)
2-1. patchPlayer() 함수를 이용하여 플레이어의 좌표를 패치한다.
2-2. 스레드를 다시 join시킨다.
3. updateFrame() 함수를 이용하여 특정 클럭 마다 프레임을 업데이트 한다.
4. printFrame() 함수를 이용하여 매 클럭마다 프레임을 출력한다.
5. 만약) 스레드가 join 되었다면 1로, 아니라면 2로 간다.
*/
int Game::makeClock(){
bool gameStatus = true; //gameStatus을 true로 초기화
while(1){ //게임이 종료될 때 까지 반복
promise<Console::eventStruct> p; //p를 받겠다고 약속한다.
future<Console::eventStruct> coor = p.get_future(); //coor을 통해 미래에 p를 받겠다고 선언한다.
thread t(Console::waitEvent, &p); //waitEvent를 실행해 p에 받겠다는 약속을 하고 t라는 스레드를 생성한다.
while (gameStatus) { //gameStatus가 false가 아니면 계속 반복한다.
Console::eventStruct Event; //이벤트를 받을 구조체
future_status status = coor.wait_for(std::chrono::milliseconds((int)(this->printframe->interval * 1000))); //미래에 받겠다고 한 coor이 완료가 되었는지 interval초 동안 물어본다.
if(this->distance % this->levelCriteria == 0){ //만약 distance가 levelCriteria의 배수라면
this->level++; //level을 1 증가시킨다.
if(this->level % 3 == 0) this->printframe->SkipFramePer++; //만약 level이 3의 배수면 SkipFramePer을 1 증가시킨다. (체감 속도 증가)
}
this->distance++; //distance을 1 증가시킨다.
if (status == future_status::timeout){ //만약 물어본지 1초가 지나 timeout되었다면(시간초과 되었다면)
gameStatus = this->updateFrame(); //프레임을 업데이트한다.
this->printFrame(); //프레임을 프린트한다.
if(gameStatus == false) break; //만약 프레임을 업데이트 할 때 false가 반환이 되었으면 while문을 나간다.
}
else if (status == future_status::ready){ //만약 물어봤을때 함수의 반환이 준비가 되었다면
Event = coor.get(); //미래에 받겠다고 한 정보를 반환받는다.
if(Event.eventType == E_MOUSE_EVENT){ //만약 마우스 이벤트가 발생하였다면
this->patchPlayer(Event.coordinate); //플레어의 위치를 패치한다.
}else if(Event.eventType == E_KEY_EVENT){ //만약 키보드 이벤트가 발생하였다면
if(Event.keyPressed == true && Event.key == PAUSE_KEY){ //만약 정지 키(defined by PAUSE_KEY)가 눌렸다면
int todo = this->SCREENpause(); //정지 화면을 출력하고, 반환값을 todo에 저장한다.
if(todo == 1){ //만약 todo가 1 이라면(게임 종료)
gameStatus = false;
break;
}
else if(todo == 2){ //만약 todo가 2 라면(게임 계속하기)
this->printframe->printBlank(); //blank 출력
this->printframe->printLogo(); //로고 출력
this->printframe->printAlert(1); //알림 출력
this->nowAlertcode = 1; //알림 코드 설정
this->printframe->printScoreframe(); //점수 프레임 출력
}
else if(todo == 3) this->init(); //만약 todo가 3이라면(게임 다시시작) -> init()을 통해 배열이나 체력등을 초기화 한 후 진행
}
}
this->updateFrame(); //프레임을 업데이트한다.
this->printFrame(); //프레임을 출력한다.
Console::sleep(this->printframe->interval); //interval초 동안 정지한다.
break; //while문을 나간다.
}
}
t.join(); //스레드 t를 종료한다.
if(gameStatus == false) break;
}
return this->SCREENover(); //만약 계속 반복되던 while문이 break되어 종료되면 게임 오버로 간다.
}
/*
[Game::printFrame()]
다음의 알고리즘을 시행합니다.
1. 만약 클럭이 SkipFramePer의 배수라면 frame을 출력한다.
2. frame 밑에 distance과 levelCriteria을 출력한다.
*/
void Game::printFrame(){
if(this->distance % this->printframe->SkipFramePer == 0){
this->printframe->print();
this->printframe->printScore(this->score, this->distance, this->level, this->levelCriteria, this->PlayerHealth);
}
}
/*
[Game::updateFrame()]
다음의 알고리즘을 시행합니다.
1. 만약 클럭이 bulletClock의 배수라면 bullet을 플레이어 바로 윗 줄 같은 collum에 생성한다.
2. 만약 shiftFrame을 실행하다가 false가 반환되면 바로 updateFrame에서 false를 반환한다. (바로 게임 종료를 위함)
3. 만약 클럭이 FrameClock의 배수라면 몬스터를 패치한다.
4. true를 반환한다.
*/
bool Game::updateFrame(){
if(this->distance % this->bulletClock == 0){
if(this->frame[this->printframe->vertical-2][this->PlayerHorizontal].object > 2){
this->frame[this->printframe->vertical-2][this->PlayerHorizontal].back->object = BULLET;
this->frame[this->printframe->vertical-2][this->PlayerHorizontal].back->health = H_BULLET;
}else{
this->frame[this->printframe->vertical-2][this->PlayerHorizontal].object = BULLET;
this->frame[this->printframe->vertical-2][this->PlayerHorizontal].health = H_BULLET;
}
}
if(this->distance % this->meteorClock == 0){
if(this->frame[0][this->meteorHorizontal].object > 2){
this->frame[0][this->meteorHorizontal].back->object = METEOR;
this->frame[0][this->meteorHorizontal].back->health = H_METEOR;
}else{
this->frame[0][this->meteorHorizontal].object = METEOR;
this->frame[0][this->meteorHorizontal].health = H_METEOR;
}
}else if(this->distance % this->meteorClock == this->meteorClock-21) this->meteorHorizontal = this->PlayerHorizontal;
else if(this->distance % this->meteorClock >= this->meteorClock-20 && this->distance % this->meteorClock <= this->meteorClock-16)
this->printframe->printColorLine(B_WHITE, B_RED, this->meteorHorizontal);
else if(this->distance % this->meteorClock >= this->meteorClock-15 && this->distance % this->meteorClock <= this->meteorClock-11)
this->printframe->printColorLine(B_WHITE, B_PURPLE, this->meteorHorizontal);
else if(this->distance % this->meteorClock >= this->meteorClock-10 && this->distance % this->meteorClock <= this->meteorClock-6)
this->printframe->printColorLine(B_WHITE, B_RED, this->meteorHorizontal);
else if(this->distance % this->meteorClock >= this->meteorClock-5 && this->distance % this->meteorClock <= this->meteorClock-1)
this->printframe->printColorLine(B_WHITE, B_PURPLE, this->meteorHorizontal);
if(this->shiftFrame() == false) return false;
if(this->distance % this->FrameClock == 0) this->patchMonster();
return true;
}
/*
[Game::patchPlayer()]
다음의 알고리즘을 시행합니다.
1. 만약 마우스의 x좌표가 출력되는 배열 내에 있다면 플레이어의 이전 죄표를 0으로 만들고 현재 좌표를 1도 만든다.
*/
void Game::patchPlayer(Console::xy coor){
if(coor.x > this->printframe->LeftSpace && coor.x < this->printframe->horizontal + 1 + this->printframe->LeftSpace){
Console::gotoxy(0, this->printframe->vertical+5);
if(this->nowAlertcode != 1){
this->printframe->printAlert(1);
this->nowAlertcode = 1;
}
this->frame[this->printframe->vertical-1][this->PlayerHorizontal].object = NONE;
this->frame[this->printframe->vertical-1][this->PlayerHorizontal].health = H_NONE;
this->PlayerHorizontal = coor.x - 1 - this->printframe->LeftSpace;
this->frame[this->printframe->vertical-1][this->PlayerHorizontal].object = PLAYER;
this->frame[this->printframe->vertical-1][this->PlayerHorizontal].health = this->PlayerHealth;
}else{
Console::gotoxy(0, this->printframe->vertical+5);
if(this->nowAlertcode != 2){
this->printframe->printAlert(2);
this->nowAlertcode = 2;
}
}
}
/*
[Game::randomMonster()]
다음의 알고리즘을 시행합니다.
1. 랜덤으로 생성된 값을 (to - from)으로 나눈 나머지를 구한다.
2. form에 [1]에서 구한 값을 더하면 반환할 몬스터의 번호가 나온다.
3. [2]에 맞도록 switch-case문을 써서 반환 값을 설정한다.
*/
Element Game::randomMonster(int from, int to){
Element tmp;
int target = from + rand() % (to - from + 1);
switch(target){
case WHITE_DRAGON:
tmp.object = WHITE_DRAGON;
tmp.health = H_WHITE_DRAGON;
break;
case YELLOW_DRAGON:
tmp.object = YELLOW_DRAGON;
tmp.health = H_YELLOW_DRAGON;
break;
case GREEN_DRAGON:
tmp.object = GREEN_DRAGON;