Id: | To index | |
Original: | Legend | |
Status: | ||
Mutant: | Show |
Testcases to display
Filter by kind
Filter by status
1: # include "game.h" |
2: |
3: # include "event.h" |
4: |
5: # include < iostream > |
6: |
7: Game :: Game ( Window & window ) |
8: : window ( window ) , mobSystem_ ( * this ) , physicsSystem_ ( * this ) , renderSystem_ ( * this ) , |
9: groundTiles_ ( worldBounds . width , worldBounds . height , '.' ) { } |
10: |
11: void Game :: setup ( ) { |
12: const auto & b = worldBounds ; |
13: |
14: // Create player |
15: auto & playerMob = createMob ( MobType :: Player , { 0 , 0 } ) ; |
16: player = playerMob . entity ; |
17: cameraTarget = playerMob . position ; |
18: cameraPosition = playerMob . position ; |
19: |
20: // Setup terrain |
21: groundTiles_ . fill ( '.' ) ; |
22: for ( int x = b . left ; x < b . left + b . width ; x ++ ) { |
23: for ( int y = b . top - b . height + 1 ; y <= b . top ; y ++ ) { |
24: if ( randInt ( 0 , 6 ) == 0 ) { |
25: groundTile ( { x , y } ) = choose ( { ',' , '_' , ' ' } ) ; |
26: } |
27: } |
28: } |
29: |
30: // Populate world with mobs |
31: int numMobs = ( int ) ( 0.5 * sqrt ( b . width * b . height ) ) ; |
32: for ( int i = 0 ; i < numMobs ; i ++ ) { |
33: MobType type = choose ( { MobType :: Rabbit , MobType :: OrcStrong , MobType :: Snake } ) ; |
34: vec2i pos { randInt ( b . left , b . left + b . width - 1 ) , randInt ( b . top - b . height + 1 , b . top ) } ; |
35: createMob ( type , pos ) ; |
36: |
37: if ( i % 32 == 0 ) |
38: sync ( ) ; |
39: } |
40: |
41: // Mob-less sprites |
42: for ( int i = 0 ; i < numMobs / 2 ; i ++ ) { |
43: vec2i pos { randInt ( b . left , b . left + b . width - 1 ) , randInt ( b . top - b . height + 1 , b . top ) } ; |
44: if ( randInt ( 0 , 2 ) != 0 ) { |
45: createSprite ( "vV" , true , 6 , TB_MAGENTA , TB_BLACK , pos , RenderLayer :: GroundCover ) ; |
46: } else if ( randInt ( 0 , 1 ) == 0 ) { |
47: createSprite ( "|/-\\" , true , 2 , TB_YELLOW , TB_BLACK , pos , RenderLayer :: GroundCover ) ; |
48: } else { |
49: createSprite ( "Xx" , true , 1 , TB_BLUE , TB_BLACK , pos , RenderLayer :: GroundCover ) ; |
50: } |
51: |
52: if ( i % 32 == 0 ) |
53: sync ( ) ; |
54: } |
55: |
56: sync ( ) ; |
57: } |
58: |
59: bool Game :: update ( ) { |
60: handleInput ( ) ; |
61: updateCamera ( ) ; // NB: Outside of world update |
62: |
63: // Use this to slow down the world update |
64: const int subTicksPerTick = 2 ; |
65: if ( -- subTick_ <= 0 ) { |
66: subTick_ = subTicksPerTick ; |
67: |
68: if ( freezeTimer > 0 ) |
69: freezeTimer -- ; |
70: bool updateWorld = ( freezeTimer == 0 ) ; |
71: |
72: if ( updateWorld ) { |
73: updatePlayer ( ) ; |
74: |
75: for ( auto * sys : systems_ ) { |
76: sys -> update ( ) ; |
77: } |
78: |
79: for ( auto & e : entities . values ( ) ) { |
80: e . age ++ ; |
81: if e . life > 0 e . age >= e . life ) { |
82: queueEvent ( EvRemove { e . id } ) ; |
83: } |
84: } |
85: |
86: // Dirt system |
87: for ( auto & c : groundTiles_ . data ( ) ) { |
88: // Roughen flat ground |
89: if c == '_' randInt ( 0 , 60 ) == 0 ) |
90: c = '.' ; |
91: } |
92: } |
93: |
94: // Events |
95: auto & events = events_ [ eventsIndex_ ] ; |
96: eventsIndex_ = 1 - eventsIndex_ ; // toggle buffer |
97: std :: vector < ident > remove ; |
98: |
99: for ( const EvAny & any : events ) { |
100: const bool logEvents = false ; |
101: if ( logEvents ) { |
102: log ( to_string ( any ) ) ; |
103: } |
104: |
105: if ( any . is < EvRemove > ( ) ) { |
106: const auto & ev = any . get < EvRemove > ( ) ; |
107: remove . push_back ( { ev . entity } ) ; |
108: } else if ( any . is < EvKillMob > ( ) ) { |
109: const auto & ev = any . get < EvKillMob > ( ) ; |
110: Mob & mob = mobs [ ev . who ] ; |
111: auto & e = entities [ mob . entity ] ; |
112: auto & sprite = sprites [ e . sprite ] ; |
113: queueEvent ( EvRemove { mob . entity } ) ; |
114: |
115: if ( onScreen ( mob . position ) ) { |
116: cameraShake = true ; |
117: cameraShakeTimer = 0 ; |
118: cameraShakeStrength = 2 ; |
119: freezeTimer = 1 ; |
120: } |
121: |
122: createBloodSplatter ( mob . position ) ; |
123: createBones ( sprite . frames [ sprite . frame ] , mob . position ) ; |
124: } else if ( any . is < EvSpawnMob > ( ) ) { |
125: const auto & ev = any . get < EvSpawnMob > ( ) ; |
126: createMob ( ev . type , ev . position ) ; |
127: } else if ( any . is < EvTryWalk > ( ) ) { |
128: // const auto& ev = any.get<EvTryWalk>(); |
129: // ... |
130: } else if ( any . is < EvWalked > ( ) ) { |
131: const auto & ev = any . get < EvWalked > ( ) ; |
132: if ( ev . mob == entities [ player ] . mob ) { |
133: // Camera tracks player |
134: const vec2i margin { 8 , 4 } ; |
135: vec2i newScreenPos = screenCoord ( ev . to ) ; |
136: if ( ( window . width ( ) - newScreenPos . x ) < margin . x ) { |
137: cameraTarget . x += margin . x ; |
138: } else if ( newScreenPos . x < margin . x ) { |
139: cameraTarget . x -= margin . x ; |
140: } else if ( ( window . height ( ) - newScreenPos . y ) < margin . y ) { |
141: cameraTarget . y -= margin . y ; |
142: } else if ( newScreenPos . y < margin . y ) { |
143: cameraTarget . y += margin . y ; |
144: } |
145: } |
146: } else if ( any . is < EvAttack > ( ) ) { |
147: const auto & ev = any . get < EvAttack > ( ) ; |
148: if ( onScreen ( mobs [ ev . target ] . position ) ) { |
149: cameraShake = true ; |
150: cameraShakeTimer = 0 ; |
151: cameraShakeStrength = 1 ; |
152: } |
153: } |
154: |
155: for ( auto * sys : systems_ ) { |
156: sys -> handleEvent ( any ) ; |
157: } |
158: } |
159: events . clear ( ) ; |
160: |
161: auto it = remove . begin ( ) ; |
162: while ( it != remove . end ( ) ) { |
163: const ident & id = * it ; |
164: Entity & e = entities [ id ] ; |
165: if ( ! e ) { |
166: ++ it ; |
167: continue ; // Already removed |
168: } |
169: |
170: using cid = std :: pair < ComponentType , ident > ; |
171: auto comps = { cid { ComponentType :: Mob , e . mob } , cid { ComponentType :: Sprite , e . sprite } , |
172: cid { ComponentType :: Physics , e . physics } } ; |
173: |
174: for ( auto pair : comps ) { |
175: ident component = pair . second ; |
176: if ( component ) { |
177: ComponentType type = pair . first ; |
178: |
179: switch ( type ) { |
180: default : |
181: break ; |
182: case ComponentType :: Mob : { |
183: mobs . remove ( component ) ; |
184: break ; |
185: } |
186: case ComponentType :: Sprite : { |
187: sprites . remove ( component ) ; |
188: break ; |
189: } |
190: case ComponentType :: Physics : { |
191: physics . remove ( component ) ; |
192: break ; |
193: } |
194: } |
195: } |
196: } |
197: |
198: for ( const auto & ch : e . children ) { |
199: queueEvent ( EvRemove { ch } ) ; |
200: } |
201: e . children . clear ( ) ; |
202: |
203: entities . remove ( id ) ; |
204: it ++ ; |
205: } |
206: |
207: sync ( ) ; |
208: tick_ ++ ; |
209: |
210: while ( ! log_ . empty ( ) ) { |
211: const auto & pair = log_ . front ( ) ; |
212: if ( tick_ > pair . second + 20 ) { |
213: log_ . pop_front ( ) ; |
214: } else |
215: break ; |
216: } |
217: } |
218: |
219: return true ; |
220: } |
221: |
222: void Game :: render ( ) { |
223: window . clear ( ) ; |
224: renderSystem_ . render ( ) ; |
225: |
226: const bool showLog = true ; |
227: if ( showLog ) { |
228: const int maxMessages = 10 ; |
229: int y = 0 ; |
230: for ( const auto & message : log_ ) { |
231: auto tick = std :: to_string ( message . second ) ; |
232: for ( int x = 0 ; x < ( int ) tick . size ( ) ; x ++ ) { |
233: window . set ( x , y , tick [ x ] , TB_WHITE , TB_BLUE ) ; |
234: } |
235: for ( int x = ( int ) tick . size ( ) ; x < 6 ; x ++ ) { |
236: window . set ( x , y , ' ' , TB_WHITE , TB_BLUE ) ; |
237: } |
238: for ( int x = 0 ; x < ( int ) message . first . size ( ) ; x ++ ) { |
239: window . set ( 6 + x , y , message . first [ x ] , TB_WHITE , TB_BLUE ) ; |
240: } |
241: y ++ ; |
242: if ( y > maxMessages ) |
243: break ; |
244: } |
245: } |
246: |
247: std :: string header = "Some Roguelike Thing" ; |
248: for ( int x = 0 ; x < ( int ) header . size ( ) ; x ++ ) { |
249: window . set ( x , 0 , header [ x ] , TB_WHITE , TB_BLUE ) ; |
250: } |
251: for ( int x = ( int ) header . size ( ) ; x < window . width ( ) ; x ++ ) { |
252: window . set ( x , 0 , ' ' , TB_WHITE , TB_BLUE ) ; |
253: } |
254: |
255: # ifdef __EMSCRIPTEN__ |
256: std :: string footer = "Arrows: Move. Code: https://github.com/eigenbom/game-example." ; |
257: # else |
258: std :: string footer = "ESC: Exit. Arrows: Move." ; |
259: # endif |
260: |
261: for ( int x = 0 ; x < ( int ) footer . size ( ) ; x ++ ) { |
262: window . set ( x , window . height ( ) - 1 , footer [ x ] , TB_WHITE , TB_BLUE ) ; |
263: } |
264: for ( int x = ( int ) footer . size ( ) ; x < window . width ( ) ; x ++ ) { |
265: window . set ( x , 0 , ' ' , TB_WHITE , TB_BLUE ) ; |
266: } |
267: } |
268: |
269: vec2i Game :: worldCoord ( vec2i screenCoord ) const { |
270: const vec2i ws { window . width ( ) , window . height ( ) } ; |
271: vec2i q = screenCoord - ws / 2 ; |
272: vec2i camFinal = cameraPosition + ( cameraShake ? cameraShakeOffset : vec2i { 0 , 0 } ) ; |
273: return { q . x + camFinal . x , - ( q . y - camFinal . y ) } ; |
274: } |
275: |
276: vec2i Game :: screenCoord ( vec2i worldCoord ) const { |
277: const vec2i ws { window . width ( ) , window . height ( ) } ; |
278: vec2i wc = worldCoord ; |
279: vec2i camFinal = cameraPosition + ( cameraShake ? cameraShakeOffset : vec2i { 0 , 0 } ) ; |
280: return vec2i { wc . x - camFinal . x , camFinal . y - wc . y } + ws / 2 ; |
281: } |
282: |
283: bool Game :: onScreen ( vec2i worldCoord ) const { |
284: vec2i sc = screenCoord ( worldCoord ) ; |
285: recti windowBounds { 0 , window . height ( ) - 1 , window . width ( ) , window . height ( ) } ; |
286: return windowBounds . contains ( sc ) ; |
287: } |
288: |
289: void Game :: queueEvent ( const EvAny & ev ) { events_ [ eventsIndex_ ] . push_back ( ev ) ; } |
290: |
291: void Game :: sync ( ) { |
292: entities . sync ( ) ; |
293: mobs . sync ( ) ; |
294: sprites . sync ( ) ; |
295: physics . sync ( ) ; |
296: } |
297: |
298: void Game :: log ( const std :: string message ) { log_ . push_back ( { message , tick_ } ) ; } |
299: |
300: Sprite & Game :: createSprite ( std :: string frames , bool animated , int frameRate , uint16_t fg , |
301: uint16_t bg , vec2i position , RenderLayer renderLayer ) { |
302: auto & e = entities . add ( ) ; |
303: |
304: auto & spr = sprites . add ( Sprite { frames , animated , frameRate , fg , bg , position , renderLayer } ) ; |
305: spr . entity = e . id ; |
306: e . sprite = spr . id ; |
307: return spr ; |
308: } |
309: |
310: Mob & Game :: createMob ( MobType type , vec2i position ) { |
311: auto & e = entities . add ( ) ; |
312: |
313: auto & info = MobDatabase . at ( type ) ; |
314: Mob & mob = mobs . add ( Mob { & info } ) ; |
315: e . mob = mob . id ; |
316: mob . entity = e . id ; |
317: |
318: mob . health = info . health ; |
319: mob . position = position ; |
320: |
321: const char * frames = "?!" ; |
322: int frameRate = 1 ; |
323: auto fg = TB_WHITE ; |
324: auto bg = TB_BLACK ; |
325: switch ( mob . info -> category ) { |
326: case MobCategory :: Rabbit : |
327: frames = "r" ; |
328: frameRate = 1 ; |
329: fg = TB_YELLOW ; |
330: break ; |
331: case MobCategory :: Snake : |
332: frames = "i!~~" ; |
333: frameRate = 0 ; |
334: fg = TB_GREEN ; |
335: break ; |
336: case MobCategory :: Orc : |
337: frames = "oO" ; |
338: frameRate = 3 ; |
339: fg = TB_GREEN ; |
340: bg = TB_BLACK ; |
341: break ; |
342: case MobCategory :: Player : |
343: frames = "@" ; |
344: break ; |
345: default : |
346: frames = "?!" ; |
347: break ; |
348: } |
349: auto & spr = |
350: sprites . add ( Sprite ( frames , frameRate > 0 , frameRate , fg , bg , position , RenderLayer :: Mob ) ) ; |
351: e . sprite = spr . id ; |
352: spr . entity = e . id ; |
353: |
354: switch ( mob . info -> category ) { |
355: case MobCategory :: Snake : { |
356: auto spr = createSprite ( "oo" , false , 0 , TB_GREEN , TB_BLACK , mob . position + mob . dir , |
357: RenderLayer :: Mob ) ; |
358: auto & child = entities [ spr . entity ] ; |
359: e . addChild ( child ) ; |
360: |
361: mob . extraSprite = spr . id ; |
362: break ; |
363: } |
364: case MobCategory :: Orc : { |
365: auto & spr1 = createSprite ( "\\|" , true , 6 , TB_GREEN , TB_BLACK , mob . position + vec2i { - 1 , 1 } , |
366: RenderLayer :: MobBelow ) ; |
367: auto & child1 = entities [ spr1 . entity ] ; |
368: e . addChild ( child1 ) ; |
369: mob . extraSprite = spr1 . id ; |
370: |
371: auto & spr2 = createSprite ( "/|" , true , 6 , TB_GREEN , TB_BLACK , mob . position + vec2i { 1 , 1 } , |
372: RenderLayer :: MobBelow ) ; |
373: auto & child2 = entities [ spr2 . entity ] ; |
374: e . addChild ( child2 ) ; |
375: mob . extraSprite2 = spr2 . id ; |
376: break ; |
377: } |
378: default : |
379: break ; |
380: } |
381: |
382: return mob ; |
383: } |
384: |
385: void Game :: createBloodSplatter ( vec2i position ) { |
386: if ( sprites . size ( ) >= sprites . max_size ( ) / 2 ) |
387: return ; |
388: |
389: const int radius = 3 ; |
390: const int sqradius = radius * radius ; |
391: for ( int dx = - radius ; dx <= radius ; dx ++ ) { |
392: for ( int dy = - radius ; dy <= radius ; dy ++ ) { |
393: if ( ( dx * dx + dy * dy ) <= sqradius ) { |
394: if ( randInt ( 0 , 4 ) != 0 ) { |
395: auto & spr = createSprite ( "." , false , 0 , TB_RED , TB_BLACK , |
396: position + vec2i { dx , dy } , RenderLayer :: Ground ) ; |
397: auto & e = entities [ spr . entity ] ; |
398: e . life = randInt ( 200 , 300 ) ; |
399: } |
400: } |
401: } |
402: } |
403: |
404: int numBloodParticles = randInt ( 10 , 40 ) ; |
405: for ( int i = 0 ; i < numBloodParticles ; i ++ ) { |
406: auto & spr = createSprite ( "o" , false , 0 , TB_RED , TB_BLACK , position , RenderLayer :: Particles ) ; |
407: auto & e = entities [ spr . entity ] ; |
408: e . life = randInt ( 6 , 12 ) ; |
409: |
410: double vel = random ( 0.4 , 0.6 ) ; |
411: |
412: Physics & ph = physics . add ( ) ; |
413: ph . type = PhysicsType :: Projectile ; |
414: ph . position = ( vec2d ) position ; |
415: double th = random ( - M_PI , M_PI ) ; |
416: ph . velocity . x = vel * cos ( th ) ; |
417: ph . velocity . y = vel * sin ( th ) ; |
418: |
419: e . physics = ph . id ; |
420: ph . entity = e . id ; |
421: } |
422: } |
423: |
424: void Game :: createBones ( char c , vec2i position ) { |
425: auto & spr = |
426: createSprite ( std :: string ( 1 , c ) , false , 0 , TB_RED , TB_BLACK , position , RenderLayer :: Ground ) ; |
427: auto & e = entities [ spr . entity ] ; |
428: e . life = randInt ( 100 , 110 ) ; |
429: } |
430: |
431: void Game :: handleInput ( ) { |
432: for ( auto ev : window . events ( ) ) { |
433: bool isPlayerMove = [ ev ] ( ) { |
434: switch ( ev ) { |
435: case WindowEvent :: ArrowUp : |
436: case WindowEvent :: ArrowDown : |
437: case WindowEvent :: ArrowLeft : |
438: case WindowEvent :: ArrowRight : |
439: return true ; |
440: default : |
441: return false ; |
442: } |
443: } ( ) ; |
444: |
445: if ! isPlayerMove windowEvents_ . size ( ) < 1 ) { |
446: // log(to_string(ev)); |
447: windowEvents_ . push_back ( ev ) ; |
448: } |
449: } |
450: } |
451: |
452: void Game :: updatePlayer ( ) { |
453: auto & entity = entities [ player ] ; |
454: auto & mob = mobs [ entity . mob ] ; |
455: |
456: mob . tick += mob . info -> speed ; |
457: mob . tick = std :: min ( mob . tick , 2 * Mob :: TicksPerAction - 1 ) ; |
458: |
459: // Map input to player commands |
460: vec2i movePlayer { 0 , 0 } ; |
461: |
462: while ( ! windowEvents_ . empty ( ) ) { |
463: const auto & ev = windowEvents_ . front ( ) ; |
464: switch ( ev ) { |
465: default : |
466: break ; |
467: case WindowEvent :: ArrowUp : |
468: movePlayer = vec2i { 0 , 1 } ; |
469: break ; |
470: case WindowEvent :: ArrowDown : |
471: movePlayer = vec2i { 0 , - 1 } ; |
472: break ; |
473: case WindowEvent :: ArrowLeft : |
474: movePlayer = vec2i { - 1 , 0 } ; |
475: break ; |
476: case WindowEvent :: ArrowRight : |
477: movePlayer = vec2i { 1 , 0 } ; |
478: break ; |
479: } |
480: |
481: // A move requires a full action |
482: if ( movePlayer != vec2i { 0 , 0 } ) { |
483: if ( mob . tick >= Mob :: TicksPerAction ) { |
484: windowEvents_ . pop_front ( ) ; |
485: mob . tick -= Mob :: TicksPerAction ; |
486: break ; |
487: } else { |
488: // Can't move yet |
489: return ; |
490: } |
491: } |
492: } |
493: |
494: if ( movePlayer != vec2i { 0 , 0 } ) { |
495: vec2i oldPos = mob . position ; |
496: vec2i newPos = oldPos + movePlayer ; |
497: |
498: ident target = invalid_id ; |
499: for ( auto & other : mobs . values ( ) ) { |
500: if other . id != mob . id other . position == newPos ) { |
501: target = other . id ; |
502: break ; |
503: } |
504: } |
505: |
506: if ( target ) { |
507: queueEvent ( EvAttack { mob . id , target } ) ; |
508: } else { |
509: queueEvent ( EvTryWalk { mob . id , oldPos , newPos } ) ; |
510: } |
511: } |
512: } |
513: |
514: void Game :: updateCamera ( ) { |
515: if ( cameraShake ) { |
516: cameraShakeTimer ++ ; |
517: if ( cameraShakeStrength == 1 ) |
518: cameraShakeTimer ++ ; |
519: |
520: if ( cameraShakeTimer > 7 ) { |
521: cameraShake = false ; |
522: cameraShakeOffset = vec2i { 0 , 0 } ; |
523: cameraShakeTimer = 0 ; |
524: } else if ( cameraShakeTimer % 2 == 0 ) { |
525: if ( cameraShakeStrength == 1 ) { |
526: if ( randInt ( 0 , 1 ) == 0 ) { |
527: cameraShakeOffset = vec2i { randInt ( - 1 , 1 ) , 0 } ; |
528: } else { |
529: cameraShakeOffset = vec2i { 0 , randInt ( - 1 , 1 ) } ; |
530: } |
531: } else { |
532: cameraShakeOffset = vec2i { randInt ( - 1 , 1 ) , randInt ( - 1 , 1 ) } ; |
533: } |
534: } |
535: } |
536: |
537: if ( cameraPosition != cameraTarget ) { |
538: static int cameraTick_ = 0 ; |
539: if ( cameraTick_ ++ >= 1 ) { |
540: cameraTick_ = 0 ; |
541: vec2i dc = cameraTarget - cameraPosition ; |
542: int dx = sign ( dc . x ) ; |
543: int dy = sign ( dc . y ) ; |
544: cameraPosition += vec2i { dx , dy } ; |
545: } |
546: } |
547: } |