-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain_web.cpp
More file actions
1919 lines (1671 loc) · 75.7 KB
/
Copy pathmain_web.cpp
File metadata and controls
1919 lines (1671 loc) · 75.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 <algorithm>
#include <thread>
#include <future>
#include <chrono>
#include <fstream>
#include <sstream>
#include <vector>
#include <deque>
#include <mutex>
#include <streambuf>
#include <cstdarg>
#include <cctype>
#include <cstdint>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/html5.h>
#endif
using namespace std;
typedef const char* LPCSTR;
//색깔 정의
#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{ //콘솔을 제어할 함수들을 모아놓은 이름 공간
int counter = 0;
typedef struct xy{ //좌표 반환을 위해 설계한 구조체
int x;
int y;
} xy;
typedef struct eventStruct{ //마우스와 키보드 이벤트 반환을 위해 설계한 구조체
int eventType;
int key;
bool keyPressed;
bool Clicked;
int ClickKey;
xy coordinate;
} eventStruct;
typedef struct Cell{
uint32_t ch;
unsigned char text;
unsigned char back;
bool continuation;
} Cell;
static int consoleWidth = 180;
static int consoleHeight = 55;
static int cursorX = 0;
static int cursorY = 0;
static int currentText = B_WHITE;
static int currentBack = BLACK;
static bool visibleCursor = true;
static bool eventEnabled = false;
static bool initialized = false;
static bool needRender = true;
static vector<Cell> cells;
static deque<eventStruct> eventQueue;
static mutex eventLock;
static int clampInt(int value, int lower, int upper){
if(upper < lower) return lower;
if(lower == upper) return lower;
if(value < lower) return lower;
if(value > upper) return upper;
return value;
}
static int frameLeft = 6;
static int frameTop = 4;
static int frameWidth = 15;
static int frameHeight = 25;
static bool isWithin(int value, int minInclusive, int maxExclusive){
return value >= minInclusive && value < maxExclusive;
}
static int mapRange(int value, int srcMin, int srcMaxExclusive, int dstMin, int dstMaxExclusive){
int srcRange = srcMaxExclusive - srcMin;
int dstRange = dstMaxExclusive - dstMin;
if(srcRange <= 0 || dstRange <= 0) return dstMin;
int rel = value - srcMin;
if(rel < 0) rel = 0;
if(rel >= srcRange) rel = srcRange - 1;
int mapped = dstMin + (rel * dstRange) / srcRange;
if(mapped < dstMin) mapped = dstMin;
if(mapped >= dstMaxExclusive) mapped = dstMaxExclusive - 1;
return mapped;
}
static xy mapCoordinateToConsole(int px, int py, int canvasW, int canvasH){
if(canvasW <= 0) canvasW = 1;
if(canvasH <= 0) canvasH = 1;
xy result;
int boardStartX = frameLeft + 1;
int boardEndX = boardStartX + frameWidth;
int boardStartY = frameTop + 1;
int boardEndY = boardStartY + frameHeight;
if(isWithin(px, boardStartX, boardEndX)){
result.x = mapRange(px, boardStartX, boardEndX, 0, canvasW);
}else if(isWithin(px, 83, 97)){
result.x = mapRange(px, 83, 97, 0, canvasW);
}else if(isWithin(px, 0, consoleWidth)){
result.x = mapRange(px, 0, consoleWidth, 0, canvasW);
}else{
result.x = clampInt((int)((double)px * (double)canvasW / (double)max(1, consoleWidth)), 0, canvasW - 1);
}
if(isWithin(py, boardStartY, boardEndY)){
result.y = mapRange(py, boardStartY, boardEndY, 0, canvasH);
}else if(isWithin(py, 14, 17)){
result.y = mapRange(py, 14, 17, 0, canvasH);
}else if(isWithin(py, 18, 21)){
result.y = mapRange(py, 18, 21, 0, canvasH);
}else if(isWithin(py, 22, 25)){
result.y = mapRange(py, 22, 25, 0, canvasH);
}else if(isWithin(py, 35, 38)){
result.y = mapRange(py, 35, 38, 0, canvasH);
}else if(isWithin(py, 39, 42)){
result.y = mapRange(py, 39, 42, 0, canvasH);
}else if(isWithin(py, 0, consoleHeight)){
result.y = mapRange(py, 0, consoleHeight, 0, canvasH);
}else{
result.y = clampInt((int)((double)py * (double)canvasH / (double)max(1, consoleHeight)), 0, canvasH - 1);
}
return result;
}
static xy mapCoordinateFromCanvas(int cx, int cy, int canvasW, int canvasH){
if(canvasW <= 0) canvasW = 1;
if(canvasH <= 0) canvasH = 1;
xy result;
result.x = clampInt(mapRange(cx, 0, canvasW, 0, consoleWidth), 0, consoleWidth - 1);
result.y = clampInt(mapRange(cy, 0, canvasH, 0, consoleHeight), 0, consoleHeight - 1);
return result;
}
static void ensureCellBuffer(){
if(consoleWidth < 1) consoleWidth = 1;
if(consoleHeight < 1) consoleHeight = 1;
cells.assign(consoleWidth * consoleHeight, Cell{(uint32_t)' ', (unsigned char)B_WHITE, (unsigned char)BLACK, false});
cursorX = clampInt(cursorX, 0, consoleWidth - 1);
cursorY = clampInt(cursorY, 0, consoleHeight - 1);
needRender = true;
}
static void clearEvents(){
lock_guard<mutex> lock(eventLock);
eventQueue.clear();
}
static void pushEvent(const eventStruct& event){
lock_guard<mutex> lock(eventLock);
eventQueue.push_back(event);
}
static bool popEvent(eventStruct* event){
lock_guard<mutex> lock(eventLock);
if(eventQueue.empty()) return false;
*event = eventQueue.front();
eventQueue.pop_front();
return true;
}
#ifdef __EMSCRIPTEN__
EM_JS(void, webConsoleSetup, (), {
var canvas = Module['canvas'];
if (!canvas) {
canvas = document.createElement('canvas');
canvas.id = 'canvas';
document.body.appendChild(canvas);
Module['canvas'] = canvas;
}
canvas.width = 1600;
canvas.height = 900;
canvas.style.width = '100vw';
canvas.style.maxWidth = '1600px';
canvas.style.height = 'auto';
canvas.style.display = 'block';
canvas.style.margin = '0 auto';
canvas.style.background = '#000000';
canvas.style.imageRendering = 'pixelated';
canvas.style.outline = 'none';
canvas.style.boxShadow = 'none';
canvas.tabIndex = -1;
document.body.style.margin = '0';
document.body.style.background = '#000000';
var notice = document.getElementById('dragon-flight-web-notice');
if (!notice) {
notice = document.createElement('div');
notice.id = 'dragon-flight-web-notice';
notice.style.color = '#c9d1d9';
notice.style.fontFamily = 'monospace';
notice.style.fontSize = '14px';
notice.style.maxWidth = '1600px';
notice.style.margin = '12px auto';
notice.style.padding = '0 10px';
notice.textContent = 'Dragon Flight Web - 마우스로 이동, Q/W/E 키 입력';
var parent = canvas.parentNode || document.body;
parent.insertBefore(notice, canvas);
}
});
EM_JS(int, webCanvasClientWidth, (), {
var canvas = Module['canvas'];
if (!canvas) return 0;
var rect = canvas.getBoundingClientRect();
return Math.max(1, Math.round(rect.width || canvas.clientWidth || canvas.width || 0));
});
EM_JS(int, webCanvasClientHeight, (), {
var canvas = Module['canvas'];
if (!canvas) return 0;
var rect = canvas.getBoundingClientRect();
return Math.max(1, Math.round(rect.height || canvas.clientHeight || canvas.height || 0));
});
EM_JS(void, webConsoleRender,
(int width, int height, const uint32_t* chars, const unsigned char* text, const unsigned char* back,
const unsigned char* continuation, int cursorVisible, int cx, int cy), {
var canvas = Module['canvas'];
if (!canvas) return;
var ctx = canvas.getContext('2d');
if (!ctx) return;
var total = width * height;
var heapChar = HEAPU32.subarray(chars >> 2, (chars >> 2) + total);
var heapText = HEAPU8.subarray(text, text + total);
var heapBack = HEAPU8.subarray(back, back + total);
var heapContinuation = HEAPU8.subarray(continuation, continuation + total);
var palette = [
'#000000', '#000080', '#008000', '#008080', '#800000', '#800080', '#808000', '#c0c0c0',
'#808080', '#5555ff', '#55ff55', '#55ffff', '#ff5555', '#ff55ff', '#ffff55', '#ffffff'
];
var cw = canvas.width / width;
var ch = canvas.height / height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.textBaseline = 'top';
ctx.textAlign = 'left';
var fontPx = Math.max(8, Math.min(ch - 2, cw * 1.6));
ctx.font = fontPx + 'px "Nanum Gothic Coding Web", "Nanum Gothic Coding", "D2Coding", "Noto Sans Mono CJK KR", "Malgun Gothic", "Apple SD Gothic Neo", "Courier New", Consolas, monospace';
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var idx = y * width + x;
var px = x * cw;
var py = y * ch;
ctx.fillStyle = palette[heapBack[idx] & 15];
ctx.fillRect(px, py, Math.ceil(cw), Math.ceil(ch));
}
}
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var idx = y * width + x;
var px = x * cw;
var py = y * ch;
if (heapContinuation[idx]) {
continue;
}
var codepoint = heapChar[idx];
if (codepoint !== 0 && codepoint !== 32) {
ctx.fillStyle = palette[heapText[idx] & 15];
var isWide = codepoint >= 0x1100 && (
(codepoint <= 0x115F) ||
(codepoint >= 0x2329 && codepoint <= 0x232A) ||
(codepoint >= 0x2E80 && codepoint <= 0xA4CF) ||
(codepoint >= 0xAC00 && codepoint <= 0xD7A3) ||
(codepoint >= 0xF900 && codepoint <= 0xFAFF) ||
(codepoint >= 0xFE10 && codepoint <= 0xFE19) ||
(codepoint >= 0xFE30 && codepoint <= 0xFE6F) ||
(codepoint >= 0xFF01 && codepoint <= 0xFF60) ||
(codepoint >= 0xFFE0 && codepoint <= 0xFFE6)
);
var drawText = String.fromCodePoint(codepoint);
var drawWidth = isWide ? cw * 2 : cw;
var measured = ctx.measureText(drawText).width || drawWidth;
var scaleX = measured > 0 ? Math.min(1, drawWidth / measured) : 1;
var offsetX = Math.max(0, (drawWidth - measured * scaleX) / 2);
ctx.save();
ctx.translate(px + offsetX, py);
ctx.scale(scaleX, 1);
ctx.fillText(drawText, 0, 0);
ctx.restore();
}
}
}
if (cursorVisible) {
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 1;
ctx.strokeRect(cx * cw + 1, cy * ch + 1, Math.max(1, cw - 2), Math.max(1, ch - 2));
}
});
static xy mapMouseCoordinate(int x, int y){
int canvasW = 1600;
int canvasH = 900;
int clientW = 0;
int clientH = 0;
emscripten_get_canvas_element_size("#canvas", &canvasW, &canvasH);
clientW = webCanvasClientWidth();
clientH = webCanvasClientHeight();
if(clientW > 0) canvasW = clientW;
if(clientH > 0) canvasH = clientH;
if(canvasW < 1) canvasW = 1;
if(canvasH < 1) canvasH = 1;
return mapCoordinateFromCanvas(x, y, canvasW, canvasH);
}
static EM_BOOL mouseMoveCallback(int, const EmscriptenMouseEvent* e, void*){
if(eventEnabled == false) return EM_TRUE;
eventStruct event = {E_MOUSE_EVENT, 0, false, false, false, {0, 0}};
int mouseX = (e->targetX != 0 || e->targetY != 0) ? e->targetX : e->canvasX;
int mouseY = (e->targetX != 0 || e->targetY != 0) ? e->targetY : e->canvasY;
event.coordinate = mapMouseCoordinate(mouseX, mouseY);
pushEvent(event);
return EM_TRUE;
}
static EM_BOOL mouseDownCallback(int, const EmscriptenMouseEvent* e, void*){
if(eventEnabled == false) return EM_TRUE;
eventStruct event = {E_MOUSE_EVENT, 0, false, true, false, {0, 0}};
int mouseX = (e->targetX != 0 || e->targetY != 0) ? e->targetX : e->canvasX;
int mouseY = (e->targetX != 0 || e->targetY != 0) ? e->targetY : e->canvasY;
event.coordinate = mapMouseCoordinate(mouseX, mouseY);
event.ClickKey = (e->button == 2) ? E_MOUSE_RIGHT : E_MOUSE_LEFT;
pushEvent(event);
return EM_TRUE;
}
static EM_BOOL keyDownCallback(int, const EmscriptenKeyboardEvent* e, void*){
if(eventEnabled == false) return EM_TRUE;
if(e->repeat) return EM_TRUE;
int key = e->which ? e->which : e->keyCode;
if(key >= 65 && key <= 90) key += 32;
eventStruct event = {E_KEY_EVENT, key, true, false, false, {0, 0}};
pushEvent(event);
return EM_TRUE;
}
static EM_BOOL keyUpCallback(int, const EmscriptenKeyboardEvent* e, void*){
if(eventEnabled == false) return EM_TRUE;
int key = e->which ? e->which : e->keyCode;
if(key >= 65 && key <= 90) key += 32;
eventStruct event = {E_KEY_EVENT, key, false, false, false, {0, 0}};
pushEvent(event);
return EM_TRUE;
}
#endif
static void scrollUp(){
for(int y=1;y<consoleHeight;y++){
for(int x=0;x<consoleWidth;x++){
cells[(y-1) * consoleWidth + x] = cells[y * consoleWidth + x];
}
}
for(int x=0;x<consoleWidth;x++){
cells[(consoleHeight - 1) * consoleWidth + x] = Cell{(uint32_t)' ', (unsigned char)currentText, (unsigned char)currentBack, false};
}
cursorY = consoleHeight - 1;
}
static bool isWideCodepoint(uint32_t cp){
return
(cp >= 0x1100 && cp <= 0x115F) ||
(cp >= 0x2329 && cp <= 0x232A) ||
(cp >= 0x2E80 && cp <= 0xA4CF) ||
(cp >= 0xAC00 && cp <= 0xD7A3) ||
(cp >= 0xF900 && cp <= 0xFAFF) ||
(cp >= 0xFE10 && cp <= 0xFE19) ||
(cp >= 0xFE30 && cp <= 0xFE6F) ||
(cp >= 0xFF01 && cp <= 0xFF60) ||
(cp >= 0xFFE0 && cp <= 0xFFE6) ||
(cp >= 0x1F300 && cp <= 0x1FAFF);
}
static void putCodepoint(uint32_t cp){
if(cp == '\r'){
cursorX = 0;
return;
}
if(cp == '\n'){
cursorX = 0;
cursorY++;
if(cursorY >= consoleHeight) scrollUp();
needRender = true;
return;
}
if(cp == '\t'){
for(int i=0;i<4;i++) putCodepoint(' ');
return;
}
if(cp < 32) return;
int cellWidth = isWideCodepoint(cp) ? 2 : 1;
if(cursorX >= consoleWidth || (cellWidth == 2 && cursorX >= consoleWidth - 1)){
cursorX = 0;
cursorY++;
if(cursorY >= consoleHeight) scrollUp();
}
if(cursorX >= 0 && cursorX < consoleWidth && cursorY >= 0 && cursorY < consoleHeight){
Cell& cell = cells[cursorY * consoleWidth + cursorX];
cell.ch = cp;
cell.text = (unsigned char)(currentText & 15);
cell.back = (unsigned char)(currentBack & 15);
cell.continuation = false;
needRender = true;
if(cellWidth == 2 && cursorX + 1 < consoleWidth){
Cell& nextCell = cells[cursorY * consoleWidth + cursorX + 1];
nextCell.ch = (uint32_t)' ';
nextCell.text = (unsigned char)(currentText & 15);
nextCell.back = (unsigned char)(currentBack & 15);
nextCell.continuation = true;
}
}
cursorX += cellWidth;
}
static void putUtf8(const char* data, size_t len){
size_t i = 0;
while(i < len){
unsigned char c0 = (unsigned char)data[i];
if(c0 < 0x80){
putCodepoint(c0);
i++;
continue;
}
uint32_t cp = '?';
int extra = 0;
if((c0 & 0xE0) == 0xC0 && i + 1 < len){
cp = ((uint32_t)(c0 & 0x1F) << 6) |
((uint32_t)((unsigned char)data[i+1] & 0x3F));
extra = 1;
}else if((c0 & 0xF0) == 0xE0 && i + 2 < len){
cp = ((uint32_t)(c0 & 0x0F) << 12) |
((uint32_t)((unsigned char)data[i+1] & 0x3F) << 6) |
((uint32_t)((unsigned char)data[i+2] & 0x3F));
extra = 2;
}else if((c0 & 0xF8) == 0xF0 && i + 3 < len){
cp = ((uint32_t)(c0 & 0x07) << 18) |
((uint32_t)((unsigned char)data[i+1] & 0x3F) << 12) |
((uint32_t)((unsigned char)data[i+2] & 0x3F) << 6) |
((uint32_t)((unsigned char)data[i+3] & 0x3F));
extra = 3;
}
putCodepoint(cp);
i += (size_t)extra + 1;
}
}
class ConsoleStreamBuf : public std::streambuf{
protected:
int_type overflow(int_type ch) override{
if(ch != traits_type::eof()){
char c = (char)ch;
putUtf8(&c, 1);
}
return ch;
}
std::streamsize xsputn(const char* s, std::streamsize n) override{
if(n > 0) putUtf8(s, (size_t)n);
return n;
}
};
static ConsoleStreamBuf consoleStreamBuf;
static std::streambuf* originalCoutBuf = nullptr;
#ifdef __EMSCRIPTEN__
static vector<uint32_t> renderChars;
static vector<unsigned char> renderText;
static vector<unsigned char> renderBack;
static vector<unsigned char> renderContinuation;
#endif
void present(){
if(initialized == false || needRender == false) return;
#ifdef __EMSCRIPTEN__
size_t total = cells.size();
if(total > 0){
renderChars.resize(total);
renderText.resize(total);
renderBack.resize(total);
renderContinuation.resize(total);
for(size_t i=0;i<total;i++){
renderChars[i] = cells[i].ch;
renderText[i] = cells[i].text;
renderBack[i] = cells[i].back;
renderContinuation[i] = cells[i].continuation ? 1 : 0;
}
webConsoleRender(consoleWidth, consoleHeight,
&renderChars[0],
&renderText[0],
&renderBack[0],
&renderContinuation[0],
visibleCursor ? 1 : 0,
cursorX, cursorY);
}
#else
cout.flush();
#endif
needRender = false;
}
void cls();
int printfCompat(const char* format, ...){
char local[4096];
va_list args;
va_start(args, format);
int count = vsnprintf(local, sizeof(local), format, args);
va_end(args);
if(count < 0) return count;
if((size_t)count >= sizeof(local)){
vector<char> dynamicBuffer((size_t)count + 1);
va_start(args, format);
vsnprintf(&dynamicBuffer[0], dynamicBuffer.size(), format, args);
va_end(args);
putUtf8(&dynamicBuffer[0], (size_t)count);
}else{
putUtf8(local, (size_t)count);
}
return count;
}
void init(){ //인코딩을 UTF-8로 바꾸고, 콘솔창 제목을 설정
if(initialized == true) return;
ensureCellBuffer();
originalCoutBuf = cout.rdbuf(&consoleStreamBuf);
#ifdef __EMSCRIPTEN__
webConsoleSetup();
emscripten_set_mousemove_callback("#canvas", nullptr, EM_TRUE, mouseMoveCallback);
emscripten_set_mousedown_callback("#canvas", nullptr, EM_TRUE, mouseDownCallback);
emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, keyDownCallback);
emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, keyUpCallback);
#endif
initialized = true;
cls();
present();
}
void configureFrameRegion(int left, int top, int width, int height){
frameLeft = left;
frameTop = top;
frameWidth = (width > 0) ? width : 1;
frameHeight = (height > 0) ? height : 1;
}
xy toCanvasCoordinate(xy consoleCoordinate){
int canvasW = 1600;
int canvasH = 900;
#ifdef __EMSCRIPTEN__
emscripten_get_canvas_element_size("#canvas", &canvasW, &canvasH);
#endif
return mapCoordinateToConsole(consoleCoordinate.x, consoleCoordinate.y, canvasW, canvasH);
}
void sleep(float sec){ //sec초 만큼 정지
present();
if(sec < 0.0f) sec = 0.0f;
int ms = (int)(sec * 1000.0f);
if(ms < 1) ms = 1;
#ifdef __EMSCRIPTEN__
emscripten_sleep(ms);
#else
this_thread::sleep_for(chrono::milliseconds(ms));
#endif
}
void cls(){ //화면을 초기화
for(size_t i=0;i<cells.size();i++){
cells[i].ch = ' ';
cells[i].text = (unsigned char)currentText;
cells[i].back = (unsigned char)currentBack;
}
cursorX = 0;
cursorY = 0;
needRender = true;
}
void windowSize(int x, int y){ //윈도우 사이즈를 바꿈
consoleWidth = (x > 1) ? x : 1;
consoleHeight = (y > 1) ? y : 1;
ensureCellBuffer();
cls();
}
void cursorVisible(bool status){ //커서의 표시 여부
visibleCursor = status;
needRender = true;
}
void gotoxy(int x, int y){ //커서를 (x, y)로 이동
cursorX = clampInt(x, 0, consoleWidth - 1);
cursorY = clampInt(y, 0, consoleHeight - 1);
}
void setColor(int text, int back){ //글자색과 배경색을 바꿈
currentText = text;
currentBack = back;
}
void ErrorExit(LPCSTR lpszMessage){ //lpszMessage를 출력 후 프로그램 종료
fprintf(stderr, "%s\n", lpszMessage);
present();
exit(0);
}
void useEventInput(bool status){ //이벤트 입력(마우스 / 키보드)을 사용할지 결정
eventEnabled = status;
if(eventEnabled == false) clearEvents();
}
bool pollEvent(eventStruct* event){
event->eventType = NONE;
event->key = 0;
event->keyPressed = false;
event->Clicked = false;
event->ClickKey = false;
event->coordinate = {0, 0};
if(eventEnabled == false) return false;
return popEvent(event);
}
void getEvent(eventStruct* event){ //발생한 이벤트를 event에 저장
event->eventType = NONE;
event->key = 0;
event->keyPressed = false;
event->Clicked = false;
event->ClickKey = false;
event->coordinate = {0, 0};
if(eventEnabled == false) return;
while(popEvent(event) == false){
present();
#ifdef __EMSCRIPTEN__
emscripten_sleep(1);
#else
this_thread::sleep_for(chrono::milliseconds(1));
#endif
}
}
void waitEvent(promise<eventStruct> *p){ //이벤트를 기다릴 스레드 함수
eventStruct event;
event.eventType = NONE;
while(event.eventType == NONE){
getEvent(&event);
}
p->set_value(event);
}
void moveWindowCenter(){ //콘솔창을 화면 정가운데로 옮김
}
void moveWindowCoordinate(int x, int y){ //콘솔창을 죄측 상단을 기준으로 (x, y)로 이동
(void)x;
(void)y;
}
}
#define printf Console::printfCompat
//=================================== 메인 코드 ===================================
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");