Fix (lots of) memory leaks in Game.cpp
[numtypysics.git] / Game.cpp
blob4327a51b0891190844ca8b498e47acdd23584b25
1 /*
2 * This file is part of NumptyPhysics
3 * Copyright (C) 2008 Tim Edmonds
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
17 #include "Common.h"
18 #include "Array.h"
19 #include "Config.h"
20 #include "Game.h"
21 #include "Overlay.h"
22 #include "Path.h"
23 #include "Canvas.h"
24 #include "Font.h"
25 #include "Levels.h"
26 #include "Http.h"
27 #include "Os.h"
28 #include "Scene.h"
30 #include "Multitouch.h"
32 #include <SDL/SDL.h>
33 #include <SDL/SDL_image.h>
35 #include <cstdio>
36 #include <iostream>
37 #include <sstream>
38 #include <fstream>
39 #include <memory.h>
40 #include <errno.h>
41 #include <sys/stat.h>
43 using namespace std;
45 unsigned char levelbuf[64*1024];
49 struct DemoEntry {
50 DemoEntry( int _t, int _o, SDL_Event& _e ) : t(_t), o(_o), e(_e) {}
51 int t,o;
52 SDL_Event e;
55 class DemoLog : public Array<DemoEntry>
57 public:
58 std::string asString( int i )
60 if ( i < size() ) {
61 DemoEntry& e = at(i);
62 stringstream s;
63 s << "E:" << e.t << " ";
64 switch( e.e.type ) {
65 case SDL_KEYDOWN:
66 s << "K" << e.e.key.keysym.sym;
67 break;
68 case SDL_KEYUP:
69 s << "k" << e.e.key.keysym.sym;
70 break;
71 case SDL_MOUSEBUTTONDOWN:
72 s << "B" << '0'+e.e.button.button;
73 s << "," << e.e.button.x << "," << e.e.button.y;
74 break;
75 case SDL_MOUSEBUTTONUP:
76 s << "b" << '0'+e.e.button.button;
77 s << "," << e.e.button.x << "," << e.e.button.y;
78 break;
79 case SDL_MOUSEMOTION:
80 s << "M" << e.e.button.x << "," << e.e.button.y;
81 break;
82 default:
83 return std::string();
85 return s.str();
87 return std::string();
90 void append( int tick, int offset, SDL_Event& ev )
92 Array<DemoEntry>::append( DemoEntry( tick, offset, ev ) );
95 void appendFromString( const std::string& str )
97 const char *s = str.c_str();
98 int t, o, v1, v2, v3;
99 char c;
100 SDL_Event ev = {0};
101 ev.type = 0xff;
102 if ( sscanf(s,"E:%d %c%d",&t,&c,&v1)==3 ) { //1 arg
103 switch ( c ) {
104 case 'K': ev.type = SDL_KEYDOWN; break;
105 case 'k': ev.type = SDL_KEYUP; break;
107 ev.key.keysym.sym = (SDLKey)v1;
108 } else if ( sscanf(s,"E:%d %c%d,%d",&t,&c,&v1,&v2)==4 ) { //2 args
109 switch ( c ) {
110 case 'M': ev.type = SDL_MOUSEMOTION; break;
112 ev.button.x = v1;
113 ev.button.y = v2;
114 } else if ( sscanf(s,"E:%d %c%d,%d,%d",&t,&c,&v1,&v2,&v3)==5 ) { //3 args
115 switch ( c ) {
116 case 'B': ev.type = SDL_MOUSEBUTTONDOWN; break;
117 case 'b': ev.type = SDL_MOUSEBUTTONUP; break;
119 ev.button.button = v1;
120 ev.button.x = v2;
121 ev.button.y = v3;
123 if ( ev.type != 0xff ) {
124 append( t, o, ev );
129 class DemoRecorder
131 public:
133 void start()
135 m_running = true;
136 m_log.empty();
137 m_log.capacity(512);
138 m_lastTick = 0;
139 m_lastTickTime = SDL_GetTicks();
140 m_pressed = 0;
143 void stop()
145 printf("stop recording: %d events:\n", m_log.size());
146 for ( int i=0; i<m_log.size(); i++ ) {
147 std::string e = m_log.asString(i);
148 if ( e.length() > 0 ) {
149 printf(" %s\n",e.c_str());
152 m_running = false;
155 void tick()
157 if ( m_running ) {
158 m_lastTick++;
159 m_lastTickTime = SDL_GetTicks();
163 void record( SDL_Event& ev )
165 if ( m_running ) {
166 switch( ev.type ) {
167 case SDL_MOUSEBUTTONDOWN:
168 m_pressed |= 1<<ev.button.button;
169 break;
170 case SDL_MOUSEBUTTONUP:
171 m_pressed &= ~(1<<ev.button.button);
172 break;
173 case SDL_MOUSEMOTION:
174 if ( m_pressed == 0 ) {
175 return;
178 m_log.append( m_lastTick, SDL_GetTicks()-m_lastTickTime, ev );
182 DemoLog& getLog() { return m_log; }
184 private:
185 bool m_running;
186 DemoLog m_log;
187 int m_lastTick;
188 int m_lastTickTime;
189 int m_pressed;
193 class DemoPlayer
195 public:
197 void start( const DemoLog* log )
199 m_playing = true;
200 m_log = log;
201 m_index = 0;
202 m_lastTick = 0;
203 printf("start playback: %d events\n",m_log->size());
206 bool isRunning() { return m_playing; }
208 void stop()
210 m_playing = false;
211 m_log = NULL;
214 void tick()
216 if ( m_playing ) {
217 m_lastTick++;
221 bool fetchEvent( SDL_Event& ev )
223 if ( m_playing ) {
224 if ( m_index < m_log->size()
225 && m_log->at(m_index).t <= m_lastTick ) {
226 printf("demo event at t=%d\n",m_lastTick);
227 ev = m_log->at(m_index).e;
228 m_index++;
229 return true;
232 return false;
235 private:
236 bool m_playing;
237 const DemoLog* m_log;
238 int m_index;
239 int m_lastTick;
243 class CollectionSelector : public ListProvider
245 Overlay* m_list;
246 public:
247 CollectionSelector( GameControl& game )
249 m_list = createListOverlay( game, this );
251 Overlay* overlay() { return m_list; }
253 virtual int countItems() {
254 return 58;
256 virtual Canvas* provideItem( int i, Canvas* old ) {
257 delete old;
258 char buf[18];
259 sprintf(buf,"%d. Item",i);
260 Canvas* c = new Canvas( 100, 32 );
261 c->setBackground(0xffffff);
262 c->clear();
263 Font::headingFont()->drawLeft( c, Vec2(3,3), buf, i<<3 );
264 return c;
266 virtual void releaseItem( Canvas* old ) {
267 delete old;
269 virtual void onSelection( int i, int ix, int iy ) {
270 printf("Selected: %d (%d,%d)\n",i,ix,iy);
275 struct GameStats
277 int startTime;
278 int endTime;
279 int strokeCount;
280 int pausedStrokes;
281 int undoCount;
282 void reset() {
283 startTime = SDL_GetTicks();
284 strokeCount = 0;
285 pausedStrokes = 0;
286 undoCount = 0;
290 class Game : public GameControl, public Widget
292 Scene m_scene;
293 Stroke *m_createStrokes[MT_MAX_CURSORS];
294 Stroke *m_moveStrokes[MT_MAX_CURSORS];
295 Array<Overlay*> m_overlays;
296 Window m_window;
297 Overlay *m_pauseOverlay;
298 Overlay *m_editOverlay;
299 Overlay *m_completedOverlay;
300 // DemoOverlay m_demoOverlay;
301 DemoRecorder m_recorder;
302 DemoPlayer m_player;
303 CollectionSelector m_cselector;
304 Os *m_os;
305 GameStats m_stats;
306 bool m_isCompleted;
307 bool m_paused;
308 public:
309 Game( Levels* levels, int width, int height )
310 : m_window(width,height,"Numpty Physics","NPhysics"),
311 m_pauseOverlay( NULL ),
312 m_editOverlay( NULL ),
313 m_completedOverlay( NULL ),
314 m_isCompleted(false),
315 m_cselector( *this ),
316 m_os( Os::get() ),
317 m_paused( false )
318 //,m_demoOverlay( *this )
320 for(int n=0; n<MT_MAX_CURSORS; n++) {
321 m_createStrokes[n] = NULL;
322 m_moveStrokes[n] = NULL;
324 configureScreenTransform( m_window.width(), m_window.height() );
325 m_levels = levels;
326 gotoLevel(0);
330 virtual bool renderScene( Canvas& c, int level )
332 Scene scene( true );
333 int size = m_levels->load( level, levelbuf, sizeof(levelbuf) );
334 if ( size && scene.load( levelbuf, size ) ) {
335 scene.draw( c, FULLSCREEN_RECT );
336 return true;
338 return false;
341 void gotoLevel( int level, bool replay=false )
343 if ( level >= 0 && level < m_levels->numLevels() ) {
344 int size = m_levels->load( level, levelbuf, sizeof(levelbuf) );
345 if ( size && m_scene.load( levelbuf, size ) ) {
346 m_scene.activateAll();
347 //m_window.setSubName( file );
348 m_refresh = true;
349 if ( m_edit ) {
350 m_scene.protect(0);
352 m_recorder.stop();
353 m_player.stop();
354 if ( replay ) {
355 m_player.start( &m_recorder.getLog() );
356 } else {
357 m_recorder.start();
359 m_level = level;
360 m_stats.reset();
366 bool save( const char *file=NULL )
368 string p;
369 if ( file ) {
370 p = file;
371 } else {
372 p = Config::userDataDir() + Os::pathSep + "L99_saved.nph";
374 if ( m_scene.save( p ) ) {
375 m_levels->addPath( p.c_str() );
376 int l = m_levels->findLevel( p.c_str() );
377 if ( l >= 0 ) {
378 m_level = l;
379 m_window.setSubName( p.c_str() );
381 return true;
383 return false;
386 bool send()
388 if ( save( SEND_TEMP_FILE ) ) {
389 Http h;
390 if ( h.post( Config::planetRoot().c_str(),
391 "upload", SEND_TEMP_FILE, "type=level" ) ) {
392 std::string id = h.getHeader("NP-Upload-Id");
393 if ( id.length() > 0 ) {
394 printf("uploaded as id %s\n",id.c_str());
395 if ( !m_os->openBrowser((Config::planetRoot()+"?level="+id).c_str()) ) {
396 showMessage("Unable to launch browser");
398 } else {
399 showMessage("UploadFailed: unknown error");
401 } else {
402 showMessage(std::string("UploadFailed: ")+h.errorMessage());
405 return false;
408 void setTool( int t )
410 m_colour = t;
413 void editMode( bool set )
415 m_edit = set;
418 void showMessage( const std::string& msg )
420 //todo
421 printf("showMessage \"%s\"\n",msg.c_str());
424 void showOverlay( Overlay* o )
426 parent()->add( o );
427 o->onShow();
430 void hideOverlay( Overlay* o )
432 parent()->remove( o );
433 o->onHide();
434 m_refresh = true;
437 void togglePause()
439 if ( !m_paused ) {
440 if ( !m_pauseOverlay ) {
441 m_pauseOverlay = createIconOverlay( *this, "pause.png", 50, 50 );
443 showOverlay( m_pauseOverlay );
444 m_paused = true;
445 } else {
446 hideOverlay( m_pauseOverlay );
447 m_paused = false;
451 bool isPaused()
453 return m_paused;
456 void edit( bool doEdit )
458 if ( m_edit != doEdit ) {
459 m_edit = doEdit;
460 if ( m_edit ) {
461 if ( !m_editOverlay ) {
462 m_editOverlay = createEditOverlay(*this);
464 showOverlay( m_editOverlay );
465 m_scene.protect(0);
466 } else {
467 hideOverlay( m_editOverlay );
468 m_strokeFixed = false;
469 m_strokeSleep = false;
470 m_strokeDecor = false;
471 if ( m_colour < 2 ) m_colour = 2;
472 m_scene.protect();
477 Vec2 cursorPoint(Uint16 x, Uint16 y)
479 Vec2 pt( x, y );
480 worldToScreen.inverseTransform( pt );
481 return pt;
484 Vec2 mousePoint( SDL_Event ev )
486 Vec2 pt( ev.button.x, ev.button.y );
487 worldToScreen.inverseTransform( pt );
488 return pt;
491 bool handleGameEvent( SDL_Event &ev )
493 switch( ev.type ) {
494 case SDL_KEYDOWN:
495 switch ( ev.key.keysym.sym ) {
496 case SDLK_SPACE:
497 case SDLK_KP_ENTER:
498 case SDLK_RETURN:
499 togglePause();
500 break;
501 case SDLK_s:
502 save();
503 break;
504 case SDLK_F4:
505 showOverlay( createMenuOverlay( *this ) );
506 break;
507 case SDLK_c:
508 showOverlay( m_cselector.overlay() );
509 break;
510 case SDLK_e:
511 case SDLK_F6:
512 edit( !m_edit );
513 break;
514 case SDLK_d:
515 //toggleOverlay( m_demoOverlay );
516 break;
517 case SDLK_r:
518 case SDLK_UP:
519 gotoLevel( m_level );
520 break;
521 case SDLK_n:
522 case SDLK_RIGHT:
523 gotoLevel( m_level+1 );
524 break;
525 case SDLK_p:
526 case SDLK_LEFT:
527 gotoLevel( m_level-1 );
528 break;
529 case SDLK_v:
530 gotoLevel( m_level, true );
531 break;
532 default:
533 break;
535 break;
536 default:
537 break;
539 return false;
542 bool handleModEvent( SDL_Event &ev )
544 static int mod=0;
545 //printf("mod=%d\n",ev.key.keysym.sym,mod);
546 switch( ev.type ) {
547 case SDL_KEYDOWN:
548 //printf("mod key=%x mod=%d\n",ev.key.keysym.sym,mod);
549 if ( ev.key.keysym.sym == SDLK_F8 ) {
550 mod = 1; //zoom- == middle (delete)
551 return true;
552 } else if ( ev.key.keysym.sym == SDLK_F7 ) {
553 mod = 2; //zoom+ == right (move)
554 return true;
556 break;
557 case SDL_KEYUP:
558 if ( ev.key.keysym.sym == SDLK_F7
559 || ev.key.keysym.sym == SDLK_F8 ) {
560 mod = 0;
561 return true;
563 break;
564 case SDL_MOUSEBUTTONDOWN:
565 case SDL_MOUSEBUTTONUP:
566 if ( ev.button.button == SDL_BUTTON_LEFT && mod != 0 ) {
567 ev.button.button = ((mod==1) ? SDL_BUTTON_MIDDLE : SDL_BUTTON_RIGHT);
569 break;
571 return false;
574 bool handlePlayEvent( SDL_Event &ev )
576 switch( ev.type ) {
577 case SDL_MOUSEBUTTONDOWN:
578 if ( ev.button.button == SDL_BUTTON_LEFT ) {
579 if (startStroke(0, ev.button.x, ev.button.y))
581 return true;
584 break;
585 case SDL_MOUSEBUTTONUP:
586 if ( ev.button.button == SDL_BUTTON_LEFT) {
587 if (finishStroke(0)) {
588 return true;
591 break;
592 case SDL_MOUSEMOTION:
593 if (extendStroke(0, ev.button.x, ev.button.y)) {
594 return true;
596 break;
597 case SDL_KEYDOWN:
598 if ( ev.key.keysym.sym == SDLK_ESCAPE ) {
599 if ( m_createStroke ) {
600 m_scene.deleteStroke( m_createStroke );
601 m_createStroke = NULL;
602 } else if ( m_scene.deleteStroke( m_scene.strokes().at(m_scene.strokes().size()-1) ) ) {
603 m_stats.undoCount++;
605 m_refresh = true;
606 return true;
608 break;
609 default:
610 break;
612 return false;
615 bool startStroke(int index, Uint16 x, Uint16 y)
617 if (m_createStrokes[index])
619 return false;
622 int attrib;
623 if ( m_strokeFixed ) {
624 attrib |= ATTRIB_GROUND;
626 if ( m_strokeSleep ) {
627 attrib |= ATTRIB_SLEEPING;
629 if ( m_strokeDecor ) {
630 attrib |= ATTRIB_DECOR;
632 m_createStrokes[index] = m_scene.newStroke( Path()&cursorPoint(x, y), m_colour, attrib );
634 return true;
637 bool extendStroke(int index, Uint16 x, Uint16 y)
639 if ( m_createStrokes[index] ) {
640 m_scene.extendStroke( m_createStrokes[index], cursorPoint(x, y) );
641 return true;
644 return false;
647 bool finishStroke(int index)
649 if (!m_createStrokes[index]) {
650 return false;
653 if ( m_scene.activate( m_createStrokes[index] ) ) {
654 m_stats.strokeCount++;
655 if ( isPaused() ) {
656 m_stats.pausedStrokes++;
658 } else {
659 m_scene.deleteStroke( m_createStrokes[index] );
661 m_createStrokes[index] = NULL;
663 return true;
666 bool handleEditEvent( SDL_Event &ev )
668 //allow move/delete in normal play!!
669 //if ( !m_edit ) return false;
671 switch( ev.type ) {
672 case SDL_MOUSEBUTTONDOWN:
673 if ( ev.button.button == SDL_BUTTON_RIGHT ) {
675 if (startMoveStroke(0, ev.button.x, ev.button.y)) {
676 return true;
679 if ( ev.button.button == SDL_BUTTON_MIDDLE ) {
680 if (deleteStroke(ev.button.x, ev.button.y)) {
681 return true;
684 break;
685 case SDL_MOUSEBUTTONUP:
686 if ( ev.button.button == SDL_BUTTON_RIGHT ) {
687 if (endMoveStroke(0)) {
688 return true;
691 break;
692 case SDL_MOUSEMOTION:
693 if (moveStroke(0, ev.button.x, ev.button.y)) {
694 return true;
696 break;
697 default:
698 break;
700 return false;
703 bool startMoveStroke(int index, Uint16 x, Uint16 y)
705 if (m_moveStrokes[index]) {
706 return false;
709 m_moveStrokes[index] = m_scene.strokeAtPoint( cursorPoint(x, y),
710 SELECT_TOLERANCE );
712 if (m_moveStrokes[index]) {
713 m_scene.setNoMass(m_moveStrokes[index]);
716 return true;
719 bool endMoveStroke(int index)
721 if (!m_moveStrokes[index]) {
722 return false;
723 } else {
724 m_scene.setDefaultMass(m_moveStrokes[index]);
728 m_moveStrokes[index] = NULL;
729 return true;
732 bool moveStroke(int index, Uint16 x, Uint16 y)
734 if (!m_moveStrokes[index]) {
735 return false;
738 m_scene.moveStroke( m_moveStrokes[index], cursorPoint(x, y) );
739 return true;
743 bool deleteStroke(Uint16 x, Uint16 y)
745 m_scene.deleteStroke( m_scene.strokeAtPoint( cursorPoint(x, y),
746 SELECT_TOLERANCE ) );
747 m_refresh = true;
751 bool handleMultitouchEvent ( SDL_Event &ev)
753 switch( ev.type ) {
754 case SDL_NP_START_STROKE: {
755 DrawEvent* de = (DrawEvent*)ev.user.data1;
756 startStroke(de->cursor_id, de->x, de->y);
757 free(de);
759 break;
761 case SDL_NP_APPEND_STROKE: {
762 DrawEvent* de = (DrawEvent*)ev.user.data1;
763 extendStroke(de->cursor_id, de->x, de->y);
764 free(de);
766 break;
768 case SDL_NP_FINISH_STROKE: {
769 DrawEvent* de = (DrawEvent*)ev.user.data1;
770 finishStroke(de->cursor_id);
771 free(de);
773 break;
775 case SDL_NP_START_ROPE:
776 break;
778 case SDL_NP_APPEND_ROPE:
779 break;
781 case SDL_NP_FINISH_ROPE:
782 break;
784 case SDL_NP_START_DRAG: {
785 DragEvent* de = (DragEvent*)ev.user.data1;
786 startMoveStroke(de->cursor_id, de->x, de->y);
787 free(de);
789 break;
791 case SDL_NP_DRAG: {
792 DragEvent* de = (DragEvent*)ev.user.data1;
793 moveStroke(de->cursor_id, de->x, de->y);
794 free(de);
796 break;
798 case SDL_NP_END_DRAG: {
799 DragEvent* de = (DragEvent*)ev.user.data1;
800 endMoveStroke(de->cursor_id);
801 free(de);
803 break;
805 case SDL_NP_PAN:
806 break;
808 case SDL_NP_ZOOM:
809 break;
811 case SDL_NP_DELETE: {
812 DeleteEvent* de = (DeleteEvent*)ev.user.data1;
813 deleteStroke(de->x, de->y);
814 free(de);
816 break;
820 return false;
824 ////////////////////////////////////////////////////////////////
825 // layer interface
826 ////////////////////////////////////////////////////////////////
828 virtual bool isDirty()
830 //TODO this can be a bit heavyweight
831 return !dirtyArea().isEmpty();
834 virtual Rect dirtyArea()
836 if ( m_refresh ) {
837 m_refresh = false;
838 return FULLSCREEN_RECT;
839 } else {
840 return m_scene.dirtyArea();
844 virtual void onTick( int tick )
846 if ( !isPaused() ) {
847 m_scene.step();
848 m_recorder.tick();
849 m_player.tick();
852 SDL_Event ev;
853 while ( m_player.fetchEvent(ev) ) {
854 // todo only dispatch play events?
855 handleModEvent(ev)
856 || handleGameEvent(ev)
857 || handleEditEvent(ev)
858 || handlePlayEvent(ev)
859 || handleMultitouchEvent(ev);
862 if ( m_isCompleted && m_edit ) {
863 hideOverlay( m_completedOverlay );
864 m_isCompleted = false;
866 if ( m_scene.isCompleted() != m_isCompleted && !m_edit ) {
867 m_isCompleted = m_scene.isCompleted();
868 if ( m_isCompleted ) {
869 m_stats.endTime = SDL_GetTicks();
870 m_player.stop();
871 m_recorder.stop();
872 m_completedOverlay = createNextLevelOverlay(*this, m_scene.getWinner());
873 showOverlay( m_completedOverlay );
874 } else {
875 hideOverlay( m_completedOverlay );
879 if ( m_os ) {
880 m_os->poll();
881 for ( char *f = m_os->getLaunchFile(); f; f=m_os->getLaunchFile() ) {
882 if ( strstr(f,".npz") ) {
883 //m_levels->empty();
885 m_levels->addPath( f );
886 int l = m_levels->findLevel( f );
887 if ( l >= 0 ) {
888 gotoLevel( l );
889 m_window.raise();
895 virtual void draw( Canvas& screen, const Rect& area )
897 m_scene.draw( screen, area );
898 if ( m_fade ) {
899 screen.fade( area );
903 virtual bool handleEvent( SDL_Event& ev )
905 if ( m_player.isRunning()
906 && ( ev.type==SDL_MOUSEMOTION ||
907 ev.type==SDL_MOUSEBUTTONDOWN ||
908 ev.type==SDL_MOUSEBUTTONUP ) ) {
909 return false;
910 } else if (handleModEvent(ev)
911 || handleGameEvent(ev)
912 || handleEditEvent(ev)
913 || handlePlayEvent(ev)
914 || handleMultitouchEvent(ev)) {
915 //\TODO only record edit,play events etc
916 m_recorder.record( ev );
917 return true;
919 return false;
925 Widget* createGameLayer( Levels* levels, int width, int height )
927 return new Game(levels,width,height);