11<!DOCTYPE html>
22< html lang ="en-us ">
33 < head >
4- < title > Websockets Doom</ title >
4+ < title > Websockets Doom - TERMINAL </ title >
55 < link rel ="shortcut icon " href ="favicon.ico " type ="image/x-icon " />
66 < style >
77 @font-face {
1818
1919 html {
2020 font-family : "VT323" , monospace;
21- font-size : 12px ;
21+ font-size : 16px ;
22+ height : 100% ;
23+ overflow : hidden;
2224 }
2325
2426 body {
25- background-color : # 111 ;
27+ background-color : # 050505 ;
28+ background-image :
29+ linear-gradient (rgba (18 , 16 , 16 , 0 ) 50% , rgba (0 , 0 , 0 , 0.25 ) 50% ),
30+ linear-gradient (90deg , rgba (255 , 0 , 0 , 0.06 ), rgba (255 , 0 , 0 , 0.02 ));
31+ background-size : 100% 2px , 3px 100% ;
2632 margin : 0 ;
27- padding : 2 ch ;
33+ padding : 0 ;
2834 display : flex;
2935 justify-content : center;
3036 align-items : center;
3137 min-height : 100vh ;
38+ color : # d32f2f ;
3239 }
3340
41+ /* 모니터 전체 프레임 */
3442 # container {
35- max-width : 100vw ;
36- max-height : 95vh ;
37- margin : auto;
43+ position : relative;
44+ width : 95vw ;
45+ max-width : 1600px ;
46+ height : 90vh ; /* 높이를 화면의 90%로 고정 */
47+ aspect-ratio : 4 / 3 ;
48+ border : 4px solid # 444 ;
49+ border-radius : 20px ;
50+ background : # 000 ;
51+ box-shadow :
52+ 0 0 0 4px # 1a1a1a,
53+ 0 0 50px rgba (220 , 20 , 60 , 0.4 ),
54+ inset 0 0 100px rgba (0 , 0 , 0 , 0.9 );
55+ padding : 30px ; /* 패딩을 조금 줄임 */
3856 display : flex;
39- justify-content : center;
57+ flex-direction : column; /* 세로 정렬 */
58+ justify-content : space-between; /* 위아래 요소 분배 */
59+ align-items : center;
60+ overflow : hidden; /* 밖으로 나가는 것 방지 */
4061 }
4162
42- .frame {
43- padding-right : 0 ;
44- margin-left : auto;
45- margin-right : auto;
46- display : block;
63+ /* 상단 장식용 헤더 */
64+ .monitor-header {
65+ width : 100% ;
66+ display : flex;
67+ justify-content : space-between;
68+ padding : 0 10px 10px 10px ;
69+ border-bottom : 2px solid # 333 ;
70+ margin-bottom : 10px ;
71+ color : # ff3333 ;
72+ text-shadow : 0 0 5px # ff3333 ;
73+ font-size : 1.2rem ;
74+ letter-spacing : 2px ;
75+ flex-shrink : 0 ; /* 크기 줄어들지 않게 고정 */
76+ }
77+
78+ .blink {
79+ animation : blinker 1s linear infinite;
80+ }
81+ @keyframes blinker {
82+ 50% { opacity : 0 ; }
83+ }
84+
85+ /* 캔버스 래퍼 (화면 안쪽) - 여기가 핵심 수정 */
86+ .screen-wrapper {
87+ position : relative;
88+ width : 100% ;
89+ flex : 1 ; /* 남은 공간을 모두 차지하도록 설정 */
90+ min-height : 0 ; /* Flex item이 내용물보다 작아질 수 있게 허용 */
91+ overflow : hidden;
92+ border : 2px solid # 222 ;
93+ border-radius : 4px ;
94+ box-shadow : inset 0 0 20px rgba (0 , 0 , 0 , 1 );
95+ background-color : # 000 ;
4796 }
4897
4998 canvas .frame {
50- background-color : black ;
99+ background-color : # 000 ;
51100 width : 100% ;
52- height : auto;
53- object-fit : contain;
54- image-rendering : -moz-crisp-edges;
55- image-rendering : -webkit-crisp-edges;
101+ height : 100% ;
102+ object-fit : contain; /* 비율 유지하며 짤리지 않게 */
56103 image-rendering : pixelated;
57- image-rendering : crisp-edges;
104+ display : block;
105+ opacity : 0.9 ;
106+ }
107+
108+ /* CRT 스캔라인 효과 */
109+ .crt-overlay {
110+ position : absolute;
111+ top : 0 ;
112+ left : 0 ;
113+ width : 100% ;
114+ height : 100% ;
115+ pointer-events : none;
116+ background : linear-gradient (
117+ to bottom,
118+ rgba (255 , 255 , 255 , 0 ),
119+ rgba (255 , 255 , 255 , 0 ) 50% ,
120+ rgba (0 , 0 , 0 , 0.2 ) 50% ,
121+ rgba (0 , 0 , 0 , 0.2 )
122+ );
123+ background-size : 100% 4px ;
124+ z-index : 10 ;
125+ box-shadow : inset 0 0 80px rgba (0 , 0 , 0 , 0.7 );
126+ }
127+
128+ /* 화면 반사광 효과 */
129+ .crt-glare {
130+ position : absolute;
131+ top : 0 ;
132+ left : 0 ;
133+ width : 100% ;
134+ height : 100% ;
135+ pointer-events : none;
136+ background : linear-gradient (135deg , rgba (255 , 255 , 255 , 0.03 ) 0% , rgba (255 , 255 , 255 , 0 ) 40% );
137+ z-index : 11 ;
138+ border-radius : 4px ;
58139 }
59140
60141 # ascii-output {
61142 display : none;
62143 }
144+
145+ /* 하단 장식 텍스트 */
146+ .monitor-footer {
147+ margin-top : 10px ;
148+ width : 100% ;
149+ text-align : right;
150+ color : # 555 ;
151+ font-size : 0.9rem ;
152+ flex-shrink : 0 ; /* 크기 줄어들지 않게 고정 */
153+ }
154+
155+ .noselect {
156+ user-select : none;
157+ }
63158 </ style >
64159 </ head >
65160 < body >
66161 < div id ="container " class ="noselect ">
67- <!-- Hidden canvas for SDL (required for initialization) -->
68- < canvas id ="sdl-canvas " style ="display: none; width: 320px; height: 200px; "> </ canvas >
69- <!-- Visible canvas for ASCII rendering -->
70- < canvas class ="frame " id ="canvas " oncontextmenu ="event.preventDefault() " tabindex ="-1 "> </ canvas >
162+ < div class ="monitor-header ">
163+ < span > DOOM_OS // VER 6.6.6</ span >
164+ < span > < span class ="blink "> ●</ span > REC</ span >
165+ </ div >
166+
167+ < div class ="screen-wrapper ">
168+ < canvas id ="sdl-canvas " style ="display: none; width: 320px; height: 200px; "> </ canvas >
169+
170+ < canvas class ="frame " id ="canvas " oncontextmenu ="event.preventDefault() " tabindex ="-1 "> </ canvas >
171+
172+ < div class ="crt-overlay "> </ div >
173+ < div class ="crt-glare "> </ div >
174+ </ div >
175+
71176 < pre id ="ascii-output "> </ pre >
177+
178+ < div class ="monitor-footer ">
179+ SYSTEM: ONLINE | RENDERER: ASCII_WASM
180+ </ div >
72181 </ div >
182+
73183 < script >
74184 // 공통적으로 Doom 실행 시에 사용할 인자들
75- // - doom1.wad: 기본 WAD 파일
76- // - -window, -nogui, -nomusic 등: 웹 환경에 맞게 최소 옵션으로 실행
77- // - -force_software_renderer 1: WebGL 대신 소프트웨어 렌더러 사용 강제
78185 var commonArgs = [ "-iwad" , "doom1.wad" , "-window" , "-nogui" , "-nomusic" , "-config" , "default.cfg" , "-servername" , "doomflare" , "-force_software_renderer" , "1" ] ;
79186
80187 var Module = {
107214 if ( arguments . length > 1 ) text = Array . prototype . slice . call ( arguments ) . join ( " " ) ;
108215 console . error ( text ) ;
109216 } ,
110- // SDL이 사용할 실제 캔버스 (숨겨져 있음)
111217 canvas : document . getElementById ( "sdl-canvas" ) ,
112218 print : function ( text ) {
113219 console . log ( text ) ;
118224 } ;
119225
120226 function setupRenderer ( ) {
121- // WASM에서 export 한 함수들을 JS에서 호출하기 위해 cwrap 사용
122- // ascii_get_buffer : AsciiCell 버퍼의 포인터를 반환
123- // ascii_get_buffer_size: AsciiCell 버퍼의 총 바이트 크기 반환
124227 const getBufferPtr = Module . cwrap ( 'ascii_get_buffer' , 'number' , [ ] ) ;
125228 const getBufferSize = Module . cwrap ( 'ascii_get_buffer_size' , 'number' , [ ] ) ;
126229
132235 const canvas = document . getElementById ( 'canvas' ) ;
133236 const ctx = canvas . getContext ( '2d' ) ;
134237
135- // C 쪽에서 정의된 ASCII 해상도 (i_ascii.h 의 ASCII_WIDTH/HEIGHT)
136238 const ASCII_WIDTH = 480 ;
137239 const ASCII_HEIGHT = 160 ;
138240
139- // 사용자 요청에 따라 최종 캔버스 해상도 설정
140241 const TARGET_CANVAS_WIDTH = 1600 ;
141242 const TARGET_CANVAS_HEIGHT = 1200 ;
142243
143- // 각 아스키 문자 셀이 차지할 픽셀 크기 계산
144244 const charWidth = TARGET_CANVAS_WIDTH / ASCII_WIDTH ;
145245 const charHeight = TARGET_CANVAS_HEIGHT / ASCII_HEIGHT ;
146246
147- // 폰트 크기는 charHeight에 맞춰 설정 (소수점 폰트 크기는 일부 브라우저에서 렌더링 문제가 있을 수 있으므로 반올림)
148247 const FONT_SIZE = Math . round ( charHeight ) ;
149248 const FONT = `${ FONT_SIZE } px "Courier New", monospace` ;
150249
151- ctx . font = FONT ; // 폰트 적용 (measureText는 이제 사용하지 않음)
250+ ctx . font = FONT ;
152251
153252 canvas . width = TARGET_CANVAS_WIDTH ;
154253 canvas . height = TARGET_CANVAS_HEIGHT ;
155254
156255 console . log ( `Canvas initialized: ${ canvas . width } x${ canvas . height } ` ) ;
157256 console . log ( `Character cell size: ${ charWidth } x${ charHeight } ` ) ;
158257
159- // 현재 WASM 메모리 버퍼를 얻는 함수
160- // Emscripten 설정에 따라 HEAPU8 이나 wasmMemory 등 접근 방법이 달라질 수 있음
161258 function getMemoryBuffer ( ) {
162259 if ( typeof HEAPU8 !== 'undefined' && HEAPU8 && HEAPU8 . buffer ) {
163260 return HEAPU8 . buffer ;
171268 return null ;
172269 }
173270
174- // Test if we can access memory
175271 const testBuffer = getMemoryBuffer ( ) ;
176272 if ( ! testBuffer ) {
177- console . error ( "Cannot access WASM memory buffer. Available properties:" , Object . keys ( Module ) ) ;
273+ console . error ( "Cannot access WASM memory buffer." ) ;
178274 return ;
179275 }
180276 console . log ( "Memory buffer access confirmed" ) ;
181277
182- // 렌더링 프레임 수 및 디버깅용 타임스탬프
183278 let frameCount = 0 ;
184279 let lastLogTime = Date . now ( ) ;
185280
195290 return ;
196291 }
197292
198- // Get fresh memory buffer each frame (may have been reallocated)
199293 const heapBuffer = getMemoryBuffer ( ) ;
200294 if ( ! heapBuffer ) {
201295 console . error ( "Cannot access memory buffer" ) ;
202296 return ;
203297 }
204298
205- // C++에서 만든 AsciiCell 배열(연속된 구조체 메모리)을 JS에서
206- // 그대로 Uint8Array 로 바라본 뒤, 4바이트 단위로 파싱한다.
207299 const buffer = new Uint8Array ( heapBuffer , bufferPtr , bufferSize ) ;
208300
209- // Debug: Check if buffer has data
210301 if ( frameCount === 0 || Date . now ( ) - lastLogTime > 2000 ) {
211302 const sampleData = Array . from ( buffer . slice ( 0 , 20 ) ) ;
212303 console . log ( "Buffer sample:" , sampleData , "Size:" , bufferSize , "Expected:" , ASCII_WIDTH * ASCII_HEIGHT * 4 ) ;
217308 ctx . fillRect ( 0 , 0 , canvas . width , canvas . height ) ;
218309 ctx . font = FONT ;
219310
220- // AsciiCell 구조체 레이아웃:
221- // char character (1 byte)
222- // uint8_t r (1 byte)
223- // uint8_t g (1 byte)
224- // uint8_t b (1 byte)
225- // → 총 4 바이트가 한 글자 셀을 표현
226- // 따라서 전체 버퍼 크기는 ASCII_WIDTH * ASCII_HEIGHT * 4 바이트가 되어야 함
227311 const expectedSize = ASCII_WIDTH * ASCII_HEIGHT * 4 ;
228312 if ( buffer . length < expectedSize ) {
229313 if ( frameCount % 60 === 0 ) {
230314 console . warn ( `Buffer size mismatch: got ${ buffer . length } , expected ${ expectedSize } ` ) ;
231315 }
232316 }
233317
234- let i = 0 ;
318+ let i = 0 ;
235319 let hasData = false ;
236320 let nonZeroCount = 0 ;
237321
238322 for ( let y = 0 ; y < ASCII_HEIGHT ; y ++ ) {
239323 for ( let x = 0 ; x < ASCII_WIDTH ; x ++ ) {
240324 if ( i + 3 >= buffer . length ) break ;
241325
242- // Read AsciiCell: character, r, g, b
243326 const charCode = buffer [ i ] ;
244327 const r = buffer [ i + 1 ] ;
245328 const g = buffer [ i + 2 ] ;
246329 const b = buffer [ i + 3 ] ;
247330
248- // Check if this cell has any data
249331 if ( charCode !== 0 || r !== 0 || g !== 0 || b !== 0 ) {
250332 hasData = true ;
251333 nonZeroCount ++ ;
252334 }
253335
254- // Only render non-empty cells for better performance
255336 if ( charCode !== 0 || r !== 0 || g !== 0 || b !== 0 ) {
256337 const char = charCode > 0 && charCode < 256 ? String . fromCharCode ( charCode ) : ' ' ;
257338
278359 }
279360 }
280361
281- const renderInterval = setInterval ( renderFrame , 16 ) ; // ~60 FPS
362+ const renderInterval = setInterval ( renderFrame , 16 ) ;
282363
283364 callMain ( commonArgs ) ;
284365 }
289370 </ script >
290371 < script async type ="text/javascript " src ="chocolate-doom.js "> </ script >
291372 </ body >
292- </ html >
373+ </ html >
0 commit comments