Support for multi-touch stroke drawing in tuioinput.py
[numtypysics.git] / Game.cpp
blob753073e9512bd690273fef10dfbd27599bc85dc4
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];
51 struct DemoEntry {
52 DemoEntry( int _t, int _o, SDL_Event& _e ) : t(_t), o(_o), e(_e) {}
53 int t,o;
54 SDL_Event e;
57 class DemoLog : public Array<DemoEntry>
59 public:
60 std::string asString( int i )
62 if ( i < size() ) {
63 DemoEntry& e = at(i);
64 stringstream s;
65 s << "E:" << e.t << " ";
66 switch( e.e.type ) {
67 case SDL_KEYDOWN:
68 s << "K" << e.e.key.keysym.sym;
69 break;
70 case SDL_KEYUP:
71 s << "k" << e.e.key.keysym.sym;
72 break;
73 case SDL_MOUSEBUTTONDOWN:
74 s << "B" << '0'+e.e.button.button;
75 s << "," << e.e.button.x << "," << e.e.button.y;
76 break;
77 case SDL_MOUSEBUTTONUP:
78 s << "b" << '0'+e.e.button.button;
79 s << "," << e.e.button.x << "," << e.e.button.y;
80 break;
81 case SDL_MOUSEMOTION:
82 s << "M" << e.e.button.x << "," << e.e.button.y;
83 break;
84 default:
85 return std::string();
87 return s.str();
89 return std::string();
92 void append( int tick, int offset, SDL_Event& ev )
94 Array<DemoEntry>::append( DemoEntry( tick, offset, ev ) );
97 void appendFromString( const std::string& str )
99 const char *s = str.c_str();
100 int t, o, v1, v2, v3;
101 char c;
102 SDL_Event ev = {0};
103 ev.type = 0xff;
104 if ( sscanf(s,"E:%d %c%d",&t,&c,&v1)==3 ) { //1 arg
105 switch ( c ) {
106 case 'K': ev.type = SDL_KEYDOWN; break;
107 case 'k': ev.type = SDL_KEYUP; break;
109 ev.key.keysym.sym = (SDLKey)v1;
110 } else if ( sscanf(s,"E:%d %c%d,%d",&t,&c,&v1,&v2)==4 ) { //2 args
111 switch ( c ) {
112 case 'M': ev.type = SDL_MOUSEMOTION; break;
114 ev.button.x = v1;
115 ev.button.y = v2;
116 } else if ( sscanf(s,"E:%d %c%d,%d,%d",&t,&c,&v1,&v2,&v3)==5 ) { //3 args
117 switch ( c ) {
118 case 'B': ev.type = SDL_MOUSEBUTTONDOWN; break;
119 case 'b': ev.type = SDL_MOUSEBUTTONUP; break;
121 ev.button.button = v1;
122 ev.button.x = v2;
123 ev.button.y = v3;
125 if ( ev.type != 0xff ) {
126 append( t, o, ev );
131 class DemoRecorder
133 public:
135 void start()
137 m_running = true;
138 m_log.empty();
139 m_log.capacity(512);
140 m_lastTick = 0;
141 m_lastTickTime = SDL_GetTicks();
142 m_pressed = 0;
145 void stop()
147 printf("stop recording: %d events:\n", m_log.size());
148 for ( int i=0; i<m_log.size(); i++ ) {
149 std::string e = m_log.asString(i);
150 if ( e.length() > 0 ) {
151 printf(" %s\n",e.c_str());
154 m_running = false;
157 void tick()
159 if ( m_running ) {
160 m_lastTick++;
161 m_lastTickTime = SDL_GetTicks();
165 void record( SDL_Event& ev )
167 if ( m_running ) {
168 switch( ev.type ) {
169 case SDL_MOUSEBUTTONDOWN:
170 m_pressed |= 1<<ev.button.button;
171 break;
172 case SDL_MOUSEBUTTONUP:
173 m_pressed &= ~(1<<ev.button.button);
174 break;
175 case SDL_MOUSEMOTION:
176 if ( m_pressed == 0 ) {
177 return;
180 m_log.append( m_lastTick, SDL_GetTicks()-m_lastTickTime, ev );
184 DemoLog& getLog() { return m_log; }
186 private:
187 bool m_running;
188 DemoLog m_log;
189 int m_lastTick;
190 int m_lastTickTime;
191 int m_pressed;
195 class DemoPlayer
197 public:
199 void start( const DemoLog* log )
201 m_playing = true;
202 m_log = log;
203 m_index = 0;
204 m_lastTick = 0;
205 printf("start playback: %d events\n",m_log->size());
208 bool isRunning() { return m_playing; }
210 void stop()
212 m_playing = false;
213 m_log = NULL;
216 void tick()
218 if ( m_playing ) {
219 m_lastTick++;
223 bool fetchEvent( SDL_Event& ev )
225 if ( m_playing ) {
226 if ( m_index < m_log->size()
227 && m_log->at(m_index).t <= m_lastTick ) {
228 printf("demo event at t=%d\n",m_lastTick);
229 ev = m_log->at(m_index).e;
230 m_index++;
231 return true;
234 return false;
237 private:
238 bool m_playing;
239 const DemoLog* m_log;
240 int m_index;
241 int m_lastTick;
245 class CollectionSelector : public ListProvider
247 Overlay* m_list;
248 public:
249 CollectionSelector( GameControl& game )
251 m_list = createListOverlay( game, this );
253 Overlay* overlay() { return m_list; }
255 virtual int countItems() {
256 return 58;
258 virtual Canvas* provideItem( int i, Canvas* old ) {
259 delete old;
260 char buf[18];
261 sprintf(buf,"%d. Item",i);
262 Canvas* c = new Canvas( 100, 32 );
263 c->setBackground(0xffffff);
264 c->clear();
265 Font::headingFont()->drawLeft( c, Vec2(3,3), buf, i<<3 );
266 return c;
268 virtual void releaseItem( Canvas* old ) {
269 delete old;
271 virtual void onSelection( int i, int ix, int iy ) {
272 printf("Selected: %d (%d,%d)\n",i,ix,iy);
277 struct GameStats
279 int startTime;
280 int endTime;
281 int strokeCount;
282 int pausedStrokes;
283 int undoCount;
284 void reset() {
285 startTime = SDL_GetTicks();
286 strokeCount = 0;
287 pausedStrokes = 0;
288 undoCount = 0;
292 class Game : public GameControl, public Widget
294 Scene m_scene;
295 Stroke *m_createStrokes[MT_MAX_CURSORS];
296 Stroke *m_moveStrokes[MT_MAX_CURSORS];
297 Array<Overlay*> m_overlays;
298 Window m_window;
299 Overlay *m_pauseOverlay;
300 Overlay *m_editOverlay;
301 Overlay *m_completedOverlay;
302 // DemoOverlay m_demoOverlay;
303 DemoRecorder m_recorder;
304 DemoPlayer m_player;
305 CollectionSelector m_cselector;
306 Os *m_os;
307 GameStats m_stats;
308 bool m_isCompleted;
309 bool m_paused;
310 public:
311 Game( Levels* levels, int width, int height )
312 : m_window(width,height,"Numpty Physics","NPhysics"),
313 m_pauseOverlay( NULL ),
314 m_editOverlay( NULL ),
315 m_completedOverlay( NULL ),
316 m_isCompleted(false),
317 m_cselector( *this ),
318 m_os( Os::get() ),
319 m_paused( false )
320 //,m_demoOverlay( *this )
322 for(int n=0; n<MT_MAX_CURSORS; n++) {
323 m_createStrokes[n] = NULL;
324 m_moveStrokes[n] = NULL;
326 configureScreenTransform( m_window.width(), m_window.height() );
327 m_levels = levels;
328 gotoLevel(0);
332 virtual bool renderScene( Canvas& c, int level )
334 Scene scene( true );
335 int size = m_levels->load( level, levelbuf, sizeof(levelbuf) );
336 if ( size && scene.load( levelbuf, size ) ) {
337 scene.draw( c, FULLSCREEN_RECT );
338 return true;
340 return false;
343 void gotoLevel( int level, bool replay=false )
345 if ( level >= 0 && level < m_levels->numLevels() ) {
346 int size = m_levels->load( level, levelbuf, sizeof(levelbuf) );
347 if ( size && m_scene.load( levelbuf, size ) ) {
348 m_scene.activateAll();
349 //m_window.setSubName( file );
350 m_refresh = true;
351 if ( m_edit ) {
352 m_scene.protect(0);
354 m_recorder.stop();
355 m_player.stop();
356 if ( replay ) {
357 m_player.start( &m_recorder.getLog() );
358 } else {
359 m_recorder.start();
361 m_level = level;
362 m_stats.reset();
368 bool save( const char *file=NULL )
370 string p;
371 if ( file ) {
372 p = file;
373 } else {
374 p = Config::userDataDir() + Os::pathSep + "L99_saved.nph";
376 if ( m_scene.save( p ) ) {
377 m_levels->addPath( p.c_str() );
378 int l = m_levels->findLevel( p.c_str() );
379 if ( l >= 0 ) {
380 m_level = l;
381 m_window.setSubName( p.c_str() );
383 return true;
385 return false;
388 bool send()
390 if ( save( SEND_TEMP_FILE ) ) {
391 Http h;
392 if ( h.post( Config::planetRoot().c_str(),
393 "upload", SEND_TEMP_FILE, "type=level" ) ) {
394 std::string id = h.getHeader("NP-Upload-Id");
395 if ( id.length() > 0 ) {
396 printf("uploaded as id %s\n",id.c_str());
397 if ( !m_os->openBrowser((Config::planetRoot()+"?level="+id).c_str()) ) {
398 showMessage("Unable to launch browser");
400 } else {
401 showMessage("UploadFailed: unknown error");
403 } else {
404 showMessage(std::string("UploadFailed: ")+h.errorMessage());
407 return false;
410 void setTool( int t )
412 m_colour = t;
415 void editMode( bool set )
417 m_edit = set;
420 void showMessage( const std::string& msg )
422 //todo
423 printf("showMessage \"%s\"\n",msg.c_str());
426 void showOverlay( Overlay* o )
428 parent()->add( o );
429 o->onShow();
432 void hideOverlay( Overlay* o )
434 parent()->remove( o );
435 o->onHide();
436 m_refresh = true;
439 void togglePause()
441 if ( !m_paused ) {
442 if ( !m_pauseOverlay ) {
443 m_pauseOverlay = createIconOverlay( *this, "pause.png", 50, 50 );
445 showOverlay( m_pauseOverlay );
446 m_paused = true;
447 } else {
448 hideOverlay( m_pauseOverlay );
449 m_paused = false;
453 bool isPaused()
455 return m_paused;
458 void edit( bool doEdit )
460 if ( m_edit != doEdit ) {
461 m_edit = doEdit;
462 if ( m_edit ) {
463 if ( !m_editOverlay ) {
464 m_editOverlay = createEditOverlay(*this);
466 showOverlay( m_editOverlay );
467 m_scene.protect(0);
468 } else {
469 hideOverlay( m_editOverlay );
470 m_strokeFixed = false;
471 m_strokeSleep = false;
472 m_strokeDecor = false;
473 if ( m_colour < 2 ) m_colour = 2;
474 m_scene.protect();
479 Vec2 cursorPoint(Uint16 x, Uint16 y)
481 Vec2 pt( x, y );
482 worldToScreen.inverseTransform( pt );
483 return pt;
486 Vec2 mousePoint( SDL_Event ev )
488 Vec2 pt( ev.button.x, ev.button.y );
489 worldToScreen.inverseTransform( pt );
490 return pt;
493 bool handleGameEvent( SDL_Event &ev )
495 switch( ev.type ) {
496 case SDL_KEYDOWN:
497 switch ( ev.key.keysym.sym ) {
498 case SDLK_SPACE:
499 case SDLK_KP_ENTER:
500 case SDLK_RETURN:
501 togglePause();
502 break;
503 case SDLK_s:
504 save();
505 break;
506 case SDLK_F4:
507 showOverlay( createMenuOverlay( *this ) );
508 break;
509 case SDLK_c:
510 showOverlay( m_cselector.overlay() );
511 break;
512 case SDLK_e:
513 case SDLK_F6:
514 edit( !m_edit );
515 break;
516 case SDLK_d:
517 //toggleOverlay( m_demoOverlay );
518 break;
519 case SDLK_r:
520 case SDLK_UP:
521 gotoLevel( m_level );
522 break;
523 case SDLK_n:
524 case SDLK_RIGHT:
525 gotoLevel( m_level+1 );
526 break;
527 case SDLK_p:
528 case SDLK_LEFT:
529 gotoLevel( m_level-1 );
530 break;
531 case SDLK_v:
532 gotoLevel( m_level, true );
533 break;
534 default:
535 break;
537 break;
538 default:
539 break;
541 return false;
544 bool handleModEvent( SDL_Event &ev )
546 static int mod=0;
547 //printf("mod=%d\n",ev.key.keysym.sym,mod);
548 switch( ev.type ) {
549 case SDL_KEYDOWN:
550 //printf("mod key=%x mod=%d\n",ev.key.keysym.sym,mod);
551 if ( ev.key.keysym.sym == SDLK_F8 ) {
552 mod = 1; //zoom- == middle (delete)
553 return true;
554 } else if ( ev.key.keysym.sym == SDLK_F7 ) {
555 mod = 2; //zoom+ == right (move)
556 return true;
558 break;
559 case SDL_KEYUP:
560 if ( ev.key.keysym.sym == SDLK_F7
561 || ev.key.keysym.sym == SDLK_F8 ) {
562 mod = 0;
563 return true;
565 break;
566 case SDL_MOUSEBUTTONDOWN:
567 case SDL_MOUSEBUTTONUP:
568 if ( ev.button.button == SDL_BUTTON_LEFT && mod != 0 ) {
569 ev.button.button = ((mod==1) ? SDL_BUTTON_MIDDLE : SDL_BUTTON_RIGHT);
571 break;
573 return false;
576 bool handlePlayEvent( SDL_Event &ev )
578 switch( ev.type ) {
579 case SDL_MOUSEBUTTONDOWN:
580 if ( ev.button.button == SDL_BUTTON_LEFT ) {
581 if (startStroke(0, ev.button.x, ev.button.y))
583 return true;
586 break;
587 case SDL_MOUSEBUTTONUP:
588 if ( ev.button.button == SDL_BUTTON_LEFT) {
589 if (finishStroke(0)) {
590 return true;
593 break;
594 case SDL_MOUSEMOTION:
595 if (extendStroke(0, ev.button.x, ev.button.y)) {
596 return true;
598 break;
599 case SDL_KEYDOWN:
600 if ( ev.key.keysym.sym == SDLK_ESCAPE ) {
601 if ( m_createStroke ) {
602 m_scene.deleteStroke( m_createStroke );
603 m_createStroke = NULL;
604 } else if ( m_scene.deleteStroke( m_scene.strokes().at(m_scene.strokes().size()-1) ) ) {
605 m_stats.undoCount++;
607 m_refresh = true;
608 return true;
610 break;
611 default:
612 break;
614 return false;
617 bool startStroke(int index, Uint16 x, Uint16 y)
619 if (m_createStrokes[index])
621 return false;
624 int attrib;
625 if ( m_strokeFixed ) {
626 attrib |= ATTRIB_GROUND;
628 if ( m_strokeSleep ) {
629 attrib |= ATTRIB_SLEEPING;
631 if ( m_strokeDecor ) {
632 attrib |= ATTRIB_DECOR;
634 m_createStrokes[index] = m_scene.newStroke( Path()&cursorPoint(x, y), m_colour, attrib );
636 return true;
639 bool extendStroke(int index, Uint16 x, Uint16 y)
641 if ( m_createStrokes[index] ) {
642 m_scene.extendStroke( m_createStrokes[index], cursorPoint(x, y) );
643 return true;
646 return false;
649 bool finishStroke(int index)
651 if (!m_createStrokes[index]) {
652 return false;
655 if ( m_scene.activate( m_createStrokes[index] ) ) {
656 m_stats.strokeCount++;
657 if ( isPaused() ) {
658 m_stats.pausedStrokes++;
660 } else {
661 m_scene.deleteStroke( m_createStrokes[index] );
663 m_createStrokes[index] = NULL;
665 return true;
668 bool handleEditEvent( SDL_Event &ev )
670 //allow move/delete in normal play!!
671 //if ( !m_edit ) return false;
673 switch( ev.type ) {
674 case SDL_MOUSEBUTTONDOWN:
675 if ( ev.button.button == SDL_BUTTON_RIGHT ) {
677 if (startMoveStroke(0, ev.button.x, ev.button.y)) {
678 return true;
681 if ( ev.button.button == SDL_BUTTON_MIDDLE ) {
682 if (deleteStroke(ev.button.x, ev.button.y)) {
683 return true;
686 break;
687 case SDL_MOUSEBUTTONUP:
688 if ( ev.button.button == SDL_BUTTON_RIGHT ) {
689 if (endMoveStroke(0)) {
690 return true;
693 break;
694 case SDL_MOUSEMOTION:
695 if (moveStroke(0, ev.button.x, ev.button.y)) {
696 return true;
698 break;
699 default:
700 break;
702 return false;
705 bool startMoveStroke(int index, Uint16 x, Uint16 y)
707 if (m_moveStrokes[index]) {
708 return false;
711 m_moveStrokes[index] = m_scene.strokeAtPoint( cursorPoint(x, y),
712 SELECT_TOLERANCE );
714 return true;
717 bool endMoveStroke(int index)
719 if (!m_moveStrokes[index]) {
720 return false;
723 m_moveStrokes[index] = NULL;
724 return true;
727 bool moveStroke(int index, Uint16 x, Uint16 y)
729 if (!m_moveStrokes[index]) {
730 return false;
733 m_scene.moveStroke( m_moveStrokes[index], cursorPoint(x, y) );
734 return true;
738 bool deleteStroke(Uint16 x, Uint16 y)
740 m_scene.deleteStroke( m_scene.strokeAtPoint( cursorPoint(x, y),
741 SELECT_TOLERANCE ) );
742 m_refresh = true;
746 bool handleMultitouchEvent ( SDL_Event &ev)
748 switch( ev.type ) {
749 case SDL_NP_START_STROKE: {
750 DrawEvent* de = (DrawEvent*)ev.user.data1;
751 startStroke(de->cursor_id, de->x, de->y);
753 break;
755 case SDL_NP_APPEND_STROKE: {
756 DrawEvent* de = (DrawEvent*)ev.user.data1;
757 extendStroke(de->cursor_id, de->x, de->y);
759 break;
761 case SDL_NP_FINISH_STROKE: {
762 DrawEvent* de = (DrawEvent*)ev.user.data1;
763 finishStroke(de->cursor_id);
765 break;
767 case SDL_NP_START_ROPE:
768 break;
770 case SDL_NP_APPEND_ROPE:
771 break;
773 case SDL_NP_FINISH_ROPE:
774 break;
776 case SDL_NP_START_DRAG: {
777 DragEvent* de = (DragEvent*)ev.user.data1;
778 startMoveStroke(de->cursor_id, de->x, de->y);
780 break;
782 case SDL_NP_DRAG: {
783 DragEvent* de = (DragEvent*)ev.user.data1;
784 moveStroke(de->cursor_id, de->x, de->y);
786 break;
788 case SDL_NP_END_DRAG: {
789 DragEvent* de = (DragEvent*)ev.user.data1;
790 endMoveStroke(de->cursor_id);
792 break;
794 case SDL_NP_PAN:
795 break;
797 case SDL_NP_ZOOM:
798 break;
800 case SDL_NP_DELETE: {
801 DeleteEvent* de = (DeleteEvent*)ev.user.data1;
802 deleteStroke(de->x, de->y);
804 break;
808 return false;
812 ////////////////////////////////////////////////////////////////
813 // layer interface
814 ////////////////////////////////////////////////////////////////
816 virtual bool isDirty()
818 //TODO this can be a bit heavyweight
819 return !dirtyArea().isEmpty();
822 virtual Rect dirtyArea()
824 if ( m_refresh ) {
825 m_refresh = false;
826 return FULLSCREEN_RECT;
827 } else {
828 return m_scene.dirtyArea();
832 virtual void onTick( int tick )
834 if ( !isPaused() ) {
835 m_scene.step();
836 m_recorder.tick();
837 m_player.tick();
840 SDL_Event ev;
841 while ( m_player.fetchEvent(ev) ) {
842 // todo only dispatch play events?
843 handleModEvent(ev)
844 || handleGameEvent(ev)
845 || handleEditEvent(ev)
846 || handlePlayEvent(ev)
847 || handleMultitouchEvent(ev);
850 if ( m_isCompleted && m_edit ) {
851 hideOverlay( m_completedOverlay );
852 m_isCompleted = false;
854 if ( m_scene.isCompleted() != m_isCompleted && !m_edit ) {
855 m_isCompleted = m_scene.isCompleted();
856 if ( m_isCompleted ) {
857 m_stats.endTime = SDL_GetTicks();
858 m_player.stop();
859 m_recorder.stop();
860 m_completedOverlay = createNextLevelOverlay(*this, m_scene.getWinner());
861 showOverlay( m_completedOverlay );
862 } else {
863 hideOverlay( m_completedOverlay );
867 if ( m_os ) {
868 m_os->poll();
869 for ( char *f = m_os->getLaunchFile(); f; f=m_os->getLaunchFile() ) {
870 if ( strstr(f,".npz") ) {
871 //m_levels->empty();
873 m_levels->addPath( f );
874 int l = m_levels->findLevel( f );
875 if ( l >= 0 ) {
876 gotoLevel( l );
877 m_window.raise();
883 virtual void draw( Canvas& screen, const Rect& area )
885 m_scene.draw( screen, area );
886 if ( m_fade ) {
887 screen.fade( area );
891 virtual bool handleEvent( SDL_Event& ev )
893 if ( m_player.isRunning()
894 && ( ev.type==SDL_MOUSEMOTION ||
895 ev.type==SDL_MOUSEBUTTONDOWN ||
896 ev.type==SDL_MOUSEBUTTONUP ) ) {
897 return false;
898 } else if (handleModEvent(ev)
899 || handleGameEvent(ev)
900 || handleEditEvent(ev)
901 || handlePlayEvent(ev)
902 || handleMultitouchEvent(ev)) {
903 //\TODO only record edit,play events etc
904 m_recorder.record( ev );
905 return true;
907 return false;
913 Widget* createGameLayer( Levels* levels, int width, int height )
915 return new Game(levels,width,height);