Clean up code a bit
[numtypysics.git] / Game.cpp
blob08464abee164dec3d26e6527b85849f15e18919e
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 <SDL/SDL.h>
18 #include <SDL/SDL_image.h>
20 #include <cstdio>
21 #include <iostream>
22 #include <sstream>
23 #include <fstream>
24 #include <memory.h>
25 #include <errno.h>
26 #include <sys/stat.h>
28 #include "Common.h"
29 #include "Array.h"
30 #include "Config.h"
31 #include "Path.h"
32 #include "Canvas.h"
33 #include "Levels.h"
34 #ifdef USE_HILDON
35 # include "Hildon.h"
36 #endif //USE_HILDON
38 using namespace std;
40 const Rect FULLSCREEN_RECT( 0, 0, CANVAS_WIDTH-1, CANVAS_HEIGHT-1 );
41 const Rect BOUNDS_RECT( -CANVAS_WIDTH/4, -CANVAS_HEIGHT,
42 CANVAS_WIDTH*5/4, CANVAS_HEIGHT );
44 const int brush_colours[] = {
45 0xb80000, //red
46 0xeec900, //yellow
47 0x000077, //blue
48 0x108710, //green
49 0x101010, //black
50 0x8b4513, //brown
51 0x87cefa, //lightblue
52 0xee6aa7, //pink
53 0xb23aee, //purple
54 0x00fa9a, //lightgreen
55 0xff7f00, //orange
56 0x6c7b8b, //grey
58 #define NUM_COLOURS (sizeof(brush_colours)/sizeof(brush_colours[0]))
60 class Stroke
62 public:
63 typedef enum {
64 ATTRIB_DUMMY = 0,
65 ATTRIB_GROUND = 1,
66 ATTRIB_TOKEN = 2,
67 ATTRIB_GOAL = 4,
68 ATTRIB_DECOR = 8,
69 ATTRIB_SLEEPING = 16,
70 ATTRIB_CLASSBITS = ATTRIB_TOKEN | ATTRIB_GOAL
71 } Attribute;
73 private:
74 struct JointDef : public b2RevoluteJointDef
76 JointDef( b2Body* b1, b2Body* b2, const b2Vec2& pt )
78 body1 = b1;
79 body2 = b2;
80 anchorPoint = pt;
81 motorTorque = 10.0f;
82 motorSpeed = 0.0f;
83 enableMotor = true;
87 struct BoxDef : public b2BoxDef
89 void init( const Vec2& p1, const Vec2& p2, int attr )
91 b2Vec2 barOrigin = p1;
92 b2Vec2 bar = p2 - p1;
93 bar *= 1.0f/PIXELS_PER_METREf;
94 barOrigin *= 1.0f/PIXELS_PER_METREf;;
95 extents.Set( bar.Length()/2.0f, 0.1f );
96 localPosition = 0.5f*bar + barOrigin;
97 localRotation = vec2Angle( bar );
98 friction = 0.3f;
99 if ( attr & ATTRIB_GROUND ) {
100 density = 0.0f;
101 } else if ( attr & ATTRIB_GOAL ) {
102 density = 100.0f;
103 } else if ( attr & ATTRIB_TOKEN ) {
104 density = 3.0f;
105 friction = 0.1f;
106 } else {
107 density = 5.0f;
109 restitution = 0.2f;
113 public:
114 Stroke( const Path& path )
115 : m_rawPath(path)
117 m_colour = COLOUR_BLUE;
118 m_attributes = 0;
119 m_origin = m_rawPath.point(0);
120 m_rawPath.translate( -m_origin );
121 reset();
124 Stroke( const string& str )
126 int col = 0;
127 m_colour = brush_colours[2];
128 m_attributes = 0;
129 m_origin = Vec2(400,240);
130 reset();
131 const char *s = str.c_str();
132 while ( *s && *s!=':' && *s!='\n' ) {
133 switch ( *s ) {
134 case 't': setAttribute( ATTRIB_TOKEN ); break;
135 case 'g': setAttribute( ATTRIB_GOAL ); break;
136 case 'f': setAttribute( ATTRIB_GROUND ); break;
137 case 's': setAttribute( ATTRIB_SLEEPING ); break;
138 case 'd': setAttribute( ATTRIB_DECOR ); break;
139 default:
140 if ( *s >= '0' && *s <= '9' ) {
141 col = col*10 + *s -'0';
143 break;
145 s++;
147 if ( col >= 0 && col < NUM_COLOURS ) {
148 m_colour = brush_colours[col];
150 if ( *s++ == ':' ) {
151 float x,y;
152 while ( sscanf( s, "%f,%f", &x, &y )==2) {
153 m_rawPath.append( Vec2((int)x,(int)y) );
154 while ( *s && *s!=' ' && *s!='\t' ) s++;
155 while ( *s==' ' || *s=='\t' ) s++;
158 if ( m_rawPath.size() < 2 ) {
159 throw "invalid stroke def";
161 m_origin = m_rawPath.point(0);
162 m_rawPath.translate( -m_origin );
163 setAttribute( ATTRIB_DUMMY );
166 void reset( b2World* world=NULL )
168 if (m_body && world) {
169 world->DestroyBody( m_body );
171 m_body = NULL;
172 m_xformAngle = 7.0f;
173 m_drawnBbox.tl = m_origin;
174 m_drawnBbox.br = m_origin;
175 m_jointed[0] = m_jointed[1] = false;
176 m_shapePath = m_rawPath;
177 m_hide = 0;
178 m_drawn = false;
181 string asString()
183 stringstream s;
184 s << 'S';
185 if ( hasAttribute(ATTRIB_TOKEN) ) s<<'t';
186 if ( hasAttribute(ATTRIB_GOAL) ) s<<'g';
187 if ( hasAttribute(ATTRIB_GROUND) ) s<<'f';
188 if ( hasAttribute(ATTRIB_SLEEPING) ) s<<'s';
189 if ( hasAttribute(ATTRIB_DECOR) ) s<<'d';
190 for ( int i=0; i<NUM_COLOURS; i++ ) {
191 if ( m_colour==brush_colours[i] ) s<<i;
193 s << ":";
194 transform();
195 for ( int i=0; i<m_xformedPath.size(); i++ ) {
196 const Vec2& p = m_xformedPath.point(i);
197 s <<' '<< p.x << ',' << p.y;
199 s << endl;
200 return s.str();
203 void setAttribute( Stroke::Attribute a )
205 m_attributes |= a;
206 if ( m_attributes & ATTRIB_TOKEN ) m_colour = COLOUR_RED;
207 else if ( m_attributes & ATTRIB_GOAL ) m_colour = COLOUR_YELLOW;
210 bool hasAttribute( Stroke::Attribute a )
212 return (m_attributes&a) != 0;
214 void setColour( int c )
216 m_colour = c;
219 void createBodies( b2World& world )
221 process();
222 if ( hasAttribute( ATTRIB_DECOR ) ){
223 return; //decorators have no physical embodiment
225 int n = m_shapePath.numPoints();
226 if ( n > 1 ) {
227 BoxDef boxDef[n];
228 b2BodyDef bodyDef;
229 for ( int i=1; i<n; i++ ) {
230 boxDef[i].init( m_shapePath.point(i-1),
231 m_shapePath.point(i),
232 m_attributes );
233 bodyDef.AddShape( &boxDef[i] );
235 bodyDef.position = m_origin;
236 bodyDef.position *= 1.0f/PIXELS_PER_METREf;
237 bodyDef.userData = this;
238 if ( m_attributes & ATTRIB_SLEEPING ) {
239 bodyDef.isSleeping = true;
241 m_body = world.CreateBody( &bodyDef );
243 transform();
246 bool maybeCreateJoint( b2World& world, Stroke* other )
248 if ( (m_attributes&ATTRIB_CLASSBITS)
249 != (other->m_attributes&ATTRIB_CLASSBITS) ) {
250 return false; // can only joint matching classes
251 } else if ( hasAttribute(ATTRIB_GROUND) ) {
252 return true; // no point jointing grounds
253 } else if ( m_body && other->body() ) {
254 transform();
255 int n = m_xformedPath.numPoints();
256 for ( int end=0; end<2; end++ ) {
257 if ( !m_jointed[end] ) {
258 const Vec2& p = m_xformedPath.point( end ? n-1 : 0 );
259 if ( other->distanceTo( p ) <= JOINT_TOLERANCE ) {
260 //printf("jointed end %d d=%f\n",end,other->distanceTo( p ));
261 b2Vec2 pw = p;
262 pw *= 1.0f/PIXELS_PER_METREf;
263 JointDef j( m_body, other->m_body, pw );
264 world.CreateJoint( &j );
265 m_jointed[end] = true;
270 if ( m_body ) {
271 return m_jointed[0] && m_jointed[1];
273 return true; ///nothing to do
276 void draw( Canvas& canvas )
278 if ( m_hide < HIDE_STEPS ) {
279 transform();
280 canvas.drawPath( m_xformedPath, canvas.makeColour(m_colour), true );
281 m_drawn = true;
283 m_drawnBbox = m_xformBbox;
286 void addPoint( const Vec2& pp )
288 Vec2 p = pp; p -= m_origin;
289 if ( p == m_rawPath.point( m_rawPath.numPoints()-1 ) ) {
290 } else {
291 m_rawPath.append( p );
292 m_drawn = false;
296 void origin( const Vec2& p )
298 // todo
299 if ( m_body ) {
300 b2Vec2 pw = p;
301 pw *= 1.0f/PIXELS_PER_METREf;
302 m_body->SetOriginPosition( pw, m_body->GetRotation() );
304 m_origin = p;
307 b2Body* body() { return m_body; }
309 float distanceTo( const Vec2& pt )
311 float best = 100000.0;
312 transform();
313 for ( int i=1; i<m_xformedPath.numPoints(); i++ ) {
314 Segment s( m_xformedPath.point(i-1), m_xformedPath.point(i) );
315 float d = s.distanceTo( pt );
316 //printf(" d[%d]=%f %d,%d\n",i,d,m_rawPath.point(i-1).x,m_rawPath.point(i-1).y);
317 if ( d < best ) {
318 best = d;
321 return best;
324 Rect bbox()
326 transform();
327 return m_xformBbox;
330 Rect lastDrawnBbox()
332 return m_drawnBbox;
335 bool isDirty()
337 return !m_drawn || transform();
340 void hide()
342 if ( m_hide==0 ) {
343 m_hide = 1;
345 if (m_body) {
346 // stash the body where no-one will find it
347 m_body->SetCenterPosition( b2Vec2(0.0f,CANVAS_HEIGHTf*2.0f), 0.0f );
348 m_body->SetLinearVelocity( b2Vec2(0.0f,0.0f) );
349 m_body->SetAngularVelocity( 0.0f );
354 bool hidden()
356 return m_hide >= HIDE_STEPS;
359 int numPoints()
361 return m_rawPath.numPoints();
364 private:
365 static float vec2Angle( b2Vec2 v )
367 float a=atan(v.y/v.x);
368 return v.y>0?a:a+b2_pi;
371 void process()
373 float thresh = 0.1*SIMPLIFY_THRESHOLDf;
374 m_rawPath.simplify( thresh );
375 m_shapePath = m_rawPath;
377 while ( m_shapePath.numPoints() > MULTI_VERTEX_LIMIT ) {
378 thresh += SIMPLIFY_THRESHOLDf;
379 m_shapePath.simplify( thresh );
383 bool transform()
385 // distinguish between xformed raw and shape path as needed
386 if ( m_hide ) {
387 if ( m_hide < HIDE_STEPS ) {
388 //printf("hide %d\n",m_hide);
389 Vec2 o = m_xformBbox.centroid();
390 m_xformedPath -= o;
391 m_xformedPath.scale( 0.99 );
392 m_xformedPath += o;
393 m_xformBbox = m_xformedPath.bbox();
394 m_hide++;
395 return true;
397 } else if ( m_body ) {
398 if ( hasAttribute( ATTRIB_DECOR ) ) {
399 return false; // decor never moves
400 } else if ( hasAttribute( ATTRIB_GROUND )
401 && m_xformAngle == m_body->GetRotation() ) {
402 return false; // ground strokes never move.
403 } else if ( m_xformAngle != m_body->GetRotation()
404 || ! (m_xformPos == m_body->GetOriginPosition()) ) {
405 //printf("transform stroke - rot or pos\n");
406 b2Mat22 rot( m_body->GetRotation() );
407 b2Vec2 orig = PIXELS_PER_METREf * m_body->GetOriginPosition();
408 m_xformedPath = m_rawPath;
409 m_xformedPath.rotate( rot );
410 m_xformedPath.translate( Vec2(orig) );
411 m_xformAngle = m_body->GetRotation();
412 m_xformPos = m_body->GetOriginPosition();
413 m_xformBbox = m_xformedPath.bbox();
414 } else if ( ! (m_xformPos == m_body->GetOriginPosition() ) ) {
415 //NOT WORKING printf("transform stroke - pos\n");
416 b2Vec2 move = m_body->GetOriginPosition() - m_xformPos;
417 move *= PIXELS_PER_METREf;
418 m_xformedPath.translate( Vec2(move) );
419 m_xformPos = m_body->GetOriginPosition();
420 m_xformBbox = m_xformedPath.bbox();//TODO translate instead of recalc
421 } else {
422 //printf("transform none\n");
423 return false;
425 } else {
426 //printf("transform no body\n");
427 m_xformedPath = m_rawPath;
428 m_xformedPath.translate( m_origin );
429 m_xformBbox = m_xformedPath.bbox();
430 return false;
432 return true;
435 Path m_rawPath;
436 int m_colour;
437 int m_attributes;
438 Vec2 m_origin;
439 Path m_shapePath;
440 Path m_xformedPath;
441 float m_xformAngle;
442 b2Vec2 m_xformPos;
443 Rect m_xformBbox;
444 Rect m_drawnBbox;
445 bool m_drawn;
446 b2Body* m_body;
447 bool m_jointed[2];
448 int m_hide;
452 class Scene
454 public:
456 Scene( bool noWorld=false )
457 : m_world( NULL ),
458 m_bgImage( NULL ),
459 m_protect( 0 )
461 if ( !noWorld ) {
462 b2AABB worldAABB;
463 worldAABB.minVertex.Set(-100.0f, -100.0f);
464 worldAABB.maxVertex.Set(100.0f, 100.0f);
466 b2Vec2 gravity(0.0f, 10.0f);
467 bool doSleep = true;
468 m_world = new b2World(worldAABB, gravity, doSleep);
472 ~Scene()
474 clear();
475 delete m_world;
478 int numStrokes()
480 return m_strokes.size();
483 Stroke* newStroke( const Path& p ) {
484 Stroke *s = new Stroke(p);
485 m_strokes.append( s );
486 return s;
489 void deleteStroke( Stroke *s ) {
490 //printf("delete stroke %p\n",s);
491 if ( s ) {
492 int i = m_strokes.indexOf(s);
493 if ( i >= m_protect ) {
494 reset(s);
495 m_strokes.erase( m_strokes.indexOf(s) );
501 void activate( Stroke *s )
503 //printf("activate stroke\n");
504 s->createBodies( *m_world );
505 createJoints( s );
508 void activateAll()
510 for ( int i=0; i < m_strokes.size(); i++ ) {
511 m_strokes[i]->createBodies( *m_world );
513 for ( int i=0; i < m_strokes.size(); i++ ) {
514 createJoints( m_strokes[i] );
518 void createJoints( Stroke *s )
520 for ( int j=m_strokes.size()-1; j>=0; j-- ) {
521 if ( s != m_strokes[j] ) {
522 //printf("try join to %d\n",j);
523 s->maybeCreateJoint( *m_world, m_strokes[j] );
524 m_strokes[j]->maybeCreateJoint( *m_world, s );
529 void step()
531 m_world->Step( ITERATION_TIMESTEPf, SOLVER_ITERATIONS );
533 // check for completion
534 for (b2Contact* c = m_world->GetContactList(); c; c = c->GetNext()) {
535 if (c->GetManifoldCount() > 0) {
536 Stroke* s1 = (Stroke*)c->GetShape1()->GetBody()->GetUserData();
537 Stroke* s2 = (Stroke*)c->GetShape2()->GetBody()->GetUserData();
538 if ( s1 && s2 ) {
539 if ( s2->hasAttribute(Stroke::ATTRIB_TOKEN) ) {
540 b2Swap( s1, s2 );
542 if ( s1->hasAttribute(Stroke::ATTRIB_TOKEN)
543 && s2->hasAttribute(Stroke::ATTRIB_GOAL) ) {
544 s2->hide();
545 //printf("SUCCESS!! level complete\n");
551 // check for token respawn
552 for ( int i=0; i < m_strokes.size(); i++ ) {
553 if ( m_strokes[i]->hasAttribute( Stroke::ATTRIB_TOKEN )
554 && !BOUNDS_RECT.intersects( m_strokes[i]->lastDrawnBbox() ) ) {
555 printf("RESPAWN token %d\n",i);
556 reset( m_strokes[i] );
557 activate( m_strokes[i] );
562 bool isCompleted()
564 for ( int i=0; i < m_strokes.size(); i++ ) {
565 if ( m_strokes[i]->hasAttribute( Stroke::ATTRIB_GOAL )
566 && !m_strokes[i]->hidden() ) {
567 return false;
570 //printf("completed!\n");
571 return true;
574 Rect dirtyArea()
576 Rect r(0,0,0,0),temp;
577 int numDirty = 0;
578 for ( int i=0; i<m_strokes.size(); i++ ) {
579 if ( m_strokes[i]->isDirty() ) {
580 // acumulate new areas to draw
581 temp = m_strokes[i]->bbox();
582 if ( !temp.isEmpty() ) {
583 if ( numDirty==0 ) {
584 r = temp;
585 } else {
586 r.expand( m_strokes[i]->bbox() );
588 // plus prev areas to erase
589 r.expand( m_strokes[i]->lastDrawnBbox() );
590 numDirty++;
594 if ( !r.isEmpty() ) {
595 // expand to allow for thick lines
596 r.tl.x--; r.tl.y--;
597 r.br.x++; r.br.y++;
599 return r;
602 void draw( Canvas& canvas, const Rect& area )
604 if ( m_bgImage ) {
605 canvas.setBackground( m_bgImage );
606 } else {
607 canvas.setBackground( 0 );
609 canvas.clear( area );
610 Rect clipArea = area;
611 clipArea.tl.x--;
612 clipArea.tl.y--;
613 clipArea.br.x++;
614 clipArea.br.y++;
615 for ( int i=0; i<m_strokes.size(); i++ ) {
616 if ( area.intersects( m_strokes[i]->bbox() ) ) {
617 m_strokes[i]->draw( canvas );
620 //canvas.drawRect( area, 0xffff0000, false );
623 void reset( Stroke* s=NULL )
625 for ( int i=0; i<m_strokes.size(); i++ ) {
626 if (s==NULL || s==m_strokes[i]) {
627 m_strokes[i]->reset(m_world);
632 Stroke* strokeAtPoint( const Vec2 pt, float max )
634 Stroke* best = NULL;
635 for ( int i=0; i<m_strokes.size(); i++ ) {
636 float d = m_strokes[i]->distanceTo( pt );
637 //printf("stroke %d dist %f\n",i,d);
638 if ( d < max ) {
639 max = d;
640 best = m_strokes[i];
643 return best;
646 void clear()
648 reset();
649 while ( m_strokes.size() ) {
650 delete m_strokes[0];
651 m_strokes.erase(0);
653 if ( m_world ) {
654 while ( m_world->GetBodyList() ) {
655 printf("body left over %p\n",m_world->GetBodyList());
656 m_world->DestroyBody( m_world->GetBodyList() );
658 //step is required to actually destroy bodies and joints
659 m_world->Step( ITERATION_TIMESTEPf, SOLVER_ITERATIONS );
663 bool load( const string& file )
665 clear();
666 if ( g_bgImage==NULL ) {
667 g_bgImage = new Image("paper.jpg");
669 m_bgImage = g_bgImage;
670 string line;
671 ifstream i( file.c_str(), ios::in );
672 while ( i.is_open() && !i.eof() ) {
673 getline( i, line );
674 //cout << "read: " << line << endl;;
675 switch( line[0] ) {
676 case 'T': m_title = line.substr(line.find(':')+1); break;
677 case 'B': m_bg = line.substr(line.find(':')+1); break;
678 case 'A': m_author = line.substr(line.find(':')+1); break;
679 case 'S': m_strokes.append( new Stroke(line) ); break;
682 i.close();
683 protect();
684 return true;
687 void protect( int n=-1 )
689 m_protect = (n==-1 ? m_strokes.size() : n );
692 bool save( const std::string& file )
694 printf("saving to %s\n",file.c_str());
695 ofstream o( file.c_str(), ios::out );
696 if ( o.is_open() ) {
697 o << "Title: "<<m_title<<endl;
698 o << "Author: "<<m_author<<endl;
699 o << "Background: "<<m_bg<<endl;
700 for ( int i=0; i<m_strokes.size(); i++ ) {
701 o << m_strokes[i]->asString();
703 o.close();
704 return true;
705 } else {
706 return false;
710 Array<Stroke*>& strokes()
712 return m_strokes;
715 private:
716 b2World *m_world;
717 Array<Stroke*> m_strokes;
718 string m_title, m_author, m_bg;
719 Image *m_bgImage;
720 static Image *g_bgImage;
721 int m_protect;
724 Image *Scene::g_bgImage = NULL;
727 struct GameParams
729 GameParams() : m_quit(false),
730 m_pause(false),
731 m_edit( false ),
732 m_refresh( true ),
733 m_colour( 2 ),
734 m_strokeFixed( false ),
735 m_strokeSleep( false ),
736 m_strokeDecor( false ),
737 m_levels(),
738 m_level(0)
740 virtual ~GameParams() {}
741 virtual bool save( char *file=NULL ) { return false; }
742 virtual bool send() { return false; }
743 virtual void gotoLevel( int l ) {}
744 bool m_quit;
745 bool m_pause;
746 bool m_edit;
747 bool m_refresh;
748 int m_colour;
749 bool m_strokeFixed;
750 bool m_strokeSleep;
751 bool m_strokeDecor;
752 Levels m_levels;
753 int m_level;
757 class Overlay
759 public:
760 Overlay( GameParams& game, int x=10, int y=10 )
761 : m_game(game),
762 m_x(x), m_y(y),
763 m_canvas(NULL),
764 m_dragging(false),
765 m_buttonDown(false)
767 virtual ~Overlay() {}
769 Rect dirtyArea()
771 return Rect(m_x,m_y,m_x+m_canvas->width()-1,m_y+m_canvas->height()-1);
774 virtual void onShow() {}
775 virtual void onHide() {}
776 virtual void onTick( int tick ) {}
778 virtual void draw( Canvas& screen )
780 if ( m_canvas ) {
781 screen.drawImage( m_canvas, m_x, m_y );
785 virtual bool handleEvent( SDL_Event& ev )
787 switch( ev.type ) {
788 case SDL_MOUSEBUTTONDOWN:
789 //printf("overlay click\n");
790 if ( ev.button.button == SDL_BUTTON_LEFT
791 && ev.button.x >= m_x && ev.button.x <= m_x + m_canvas->width()
792 && ev.button.y >= m_y && ev.button.y <= m_y + m_canvas->height() ) {
793 m_orgx = ev.button.x;
794 m_orgy = ev.button.y;
795 m_prevx = ev.button.x;
796 m_prevy = ev.button.y;
797 m_buttonDown = true;
798 return true;
800 break;
801 case SDL_MOUSEBUTTONUP:
802 if ( ev.button.button == SDL_BUTTON_LEFT ) {
803 if ( m_dragging ) {
804 m_dragging = false;
805 } else if ( ABS(ev.button.x-m_orgx) < CLICK_TOLERANCE
806 && ABS(ev.button.y-m_orgy) < CLICK_TOLERANCE ){
807 onClick( m_orgx-m_x, m_orgy-m_y );
809 m_buttonDown = false;
811 break;
812 case SDL_MOUSEMOTION:
813 if ( !m_dragging
814 && m_buttonDown
815 && ( ABS(ev.button.x-m_orgx) >= CLICK_TOLERANCE
816 || ABS(ev.button.y-m_orgy) >= CLICK_TOLERANCE ) ) {
817 m_dragging = true;
819 if ( m_dragging ) {
820 m_x += ev.button.x - m_prevx;
821 m_y += ev.button.y - m_prevy;
822 m_prevx = ev.button.x;
823 m_prevy = ev.button.y;
824 m_game.m_refresh = true;
826 break;
827 default:
828 break;
830 return false;
833 virtual bool onClick( int x, int y ) { return false; }
835 protected:
836 GameParams& m_game;
837 int m_x, m_y;
838 Canvas *m_canvas;
839 private:
840 int m_orgx, m_orgy;
841 int m_prevx, m_prevy;
842 bool m_dragging;
843 bool m_buttonDown;
847 class IconOverlay: public Overlay
849 public:
850 IconOverlay(GameParams& game, const char* file, int x=100,int y=20)
851 : Overlay(game,x,y)
853 m_canvas = new Image(file);
858 class NextLevelOverlay : public IconOverlay
860 public:
861 NextLevelOverlay( GameParams& game )
862 : IconOverlay(game,"next.png",CANVAS_WIDTH/2-200,100),
863 m_levelIcon(-2),
864 m_icon(NULL)
866 ~NextLevelOverlay()
868 delete m_icon;
870 virtual void onShow()
872 m_selectedLevel = m_game.m_level+1;
874 virtual void draw( Canvas& screen )
876 screen.fade();
877 IconOverlay::draw( screen );
878 if ( genIcon() ) {
879 screen.drawImage( m_icon, m_x+100, m_y+75 );
882 virtual bool onClick( int x, int y )
884 if ( y > 180 ) {
885 m_game.gotoLevel( m_selectedLevel );
886 } else if ( x > 300 ) {
887 m_selectedLevel++;
888 printf("NextLevel++ = %d\n",m_selectedLevel);
889 } else if ( x < 100 && m_selectedLevel > 0 ) {
890 m_selectedLevel--;
891 printf("NextLevel-- = %d\n",m_selectedLevel);
893 return true;
895 private:
896 bool genIcon()
898 if ( m_icon==NULL || m_levelIcon != m_selectedLevel ) {
899 printf("new thumbnail required\n");
900 delete m_icon;
901 m_icon = NULL;
902 if ( m_selectedLevel < m_game.m_levels.numLevels() ) {
903 Scene scene( true );
904 if ( scene.load( m_game.m_levels.levelFile(m_selectedLevel) ) ) {
905 printf("generating thumbnail %s\n",
906 m_game.m_levels.levelFile(m_selectedLevel).c_str());
907 Canvas temp( CANVAS_WIDTH, CANVAS_HEIGHT );
908 scene.draw( temp, FULLSCREEN_RECT );
909 m_icon = temp.scale( ICON_SCALE_FACTOR );
910 printf("generating thumbnail %s done\n",
911 m_game.m_levels.levelFile(m_selectedLevel).c_str());
912 } else {
913 printf("failed to gen scene thumbnail %s\n",
914 m_game.m_levels.levelFile(m_selectedLevel).c_str());
916 } else {
917 m_icon = new Image("theend.png");
918 m_caption = "no more levels!";
919 m_selectedLevel = m_game.m_levels.numLevels();
921 m_levelIcon = m_selectedLevel;
923 return m_icon;
925 int m_selectedLevel;
926 int m_levelIcon;
927 Canvas* m_icon;
928 string m_caption;
932 class DemoOverlay : public IconOverlay
934 struct EvEntry {
935 int t;
936 SDL_Event e;
938 typedef enum {
939 STARTING,
940 RUNNING,
941 PLAYING,
942 STOPPED
943 } State;
945 public:
946 DemoOverlay( GameParams& game )
947 : IconOverlay( game, "pause.png" ),
948 m_state( STARTING ),
949 m_log( 512 )
952 virtual void onTick( int tick )
954 switch ( m_state ) {
955 case STARTING:
956 m_game.save( DEMO_TEMP_FILE );
957 //todo reset motion: m_game.load( DEMO_TEMP_FILE );
958 m_t0 = tick;
959 m_state = RUNNING;
960 m_log.empty();
961 break;
962 case RUNNING:
963 m_tnow = tick;
964 break;
965 case PLAYING:
966 while ( m_playidx < m_log.size()
967 && m_log[m_playidx].t < tick-m_t0 ) {
968 SDL_PushEvent( &m_log[m_playidx++].e );
970 break;
973 virtual bool onClick( int x, int y )
975 switch ( m_state ) {
976 case STOPPED:
977 case RUNNING:
978 break;
979 case PLAYING:
980 m_state = STOPPED;
981 break;
983 return true;
985 virtual void draw( Canvas& screen )
987 IconOverlay::draw( screen );
988 switch ( m_state ) {
989 case STOPPED:
990 case RUNNING:
991 case PLAYING:
992 break;
995 virtual bool handleEvent( SDL_Event& ev )
997 if ( IconOverlay::handleEvent(ev) ) {
998 return true;
999 } else if ( m_state == RUNNING ) {
1000 EvEntry e = { m_tnow - m_t0, ev };
1001 m_log.append( e );
1003 return false;
1006 private:
1007 State m_state;
1008 int m_t0, m_tnow;
1009 int m_playidx;
1010 Array<EvEntry> m_log;
1014 class EditOverlay : public IconOverlay
1016 int m_saving, m_sending;
1017 public:
1018 EditOverlay( GameParams& game )
1019 : IconOverlay(game,"edit.png"),
1020 m_saving(0), m_sending(0)
1023 for ( int i=0; i<NUM_COLOURS; i++ ) {
1024 m_canvas->drawRect( pos(i), m_canvas->makeColour(brush_colours[i]), true );
1027 Rect pos( int i )
1029 int c = i%3, r = i/3;
1030 return Rect( c*28+13, r*28+33, c*28+33, r*28+53 );
1032 int index( int x, int y )
1034 int r = (y-33)/28;
1035 int c = (x-13)/28;
1036 if ( r<0 || c<0 || c>2 ) return -1;
1037 return r*3+c;
1039 void outline( Canvas& screen, int i, int c )
1041 Rect r = pos(i) + Vec2(m_x,m_y);
1042 r.tl.x-=2; r.tl.y-=2;
1043 r.br.x+=2; r.br.y+=2;
1044 screen.drawRect( r, c, false );
1046 virtual void draw( Canvas& screen )
1048 IconOverlay::draw( screen );
1049 outline( screen, m_game.m_colour, 0 );
1050 if ( m_game.m_strokeFixed ) outline( screen, 12, 0 );
1051 if ( m_game.m_strokeSleep ) outline( screen, 13, 0 );
1052 if ( m_game.m_strokeDecor ) outline( screen, 14, 0 );
1053 if ( m_sending ) outline( screen, 16, screen.makeColour((m_sending--)<<9) );
1054 if ( m_saving ) outline( screen, 17, screen.makeColour((m_saving--)<<9) );
1056 virtual bool onClick( int x, int y )
1058 int i = index(x,y);
1059 switch (i) {
1060 case -1: return false;
1061 case 12: m_game.m_strokeFixed = ! m_game.m_strokeFixed; break;
1062 case 13: m_game.m_strokeSleep = ! m_game.m_strokeSleep; break;
1063 case 14: m_game.m_strokeDecor = ! m_game.m_strokeDecor; break;
1064 case 16: if ( m_game.send() ) m_sending=10; break;
1065 case 17: if ( m_game.save() ) m_saving=10; break;
1066 default: if (i<NUM_COLOURS) m_game.m_colour = i; break;
1068 return true;
1073 class Game : public GameParams
1075 Scene m_scene;
1076 Stroke *m_createStroke;
1077 Stroke *m_moveStroke;
1078 Array<Overlay*> m_overlays;
1079 Window m_window;
1080 IconOverlay m_pauseOverlay;
1081 EditOverlay m_editOverlay;
1082 #ifdef USE_HILDON
1083 Hildon m_hildon;
1084 #endif //USE_HILDON
1085 public:
1086 Game( int argc, const char** argv )
1087 : m_createStroke(NULL),
1088 m_moveStroke(NULL),
1089 m_window(800,480,"Numpty Physics","NPhysics"),
1090 m_pauseOverlay( *this, "pause.png",50,50),
1091 m_editOverlay( *this)
1093 if ( argc<=1 ) {
1094 FILE *f = fopen("Game.cpp","rt");
1095 if ( f ){
1096 m_levels.addPath( "." );
1097 fclose(f);
1098 } else {
1099 m_levels.addPath( DEFAULT_LEVEL_PATH );
1101 std::string u( getenv("HOME") );
1102 m_levels.addPath( (u + "/" USER_LEVEL_PATH).c_str() );
1103 } else {
1104 for ( int i=1;i<argc;i++ ) {
1105 m_levels.addPath( argv[i] );
1108 gotoLevel( 0 );
1111 void gotoLevel( int l )
1113 if ( l >= 0 && l < m_levels.numLevels() ) {
1114 printf("loading from %s\n",m_levels.levelFile(l).c_str());
1115 m_scene.load( m_levels.levelFile(l).c_str() );
1116 m_scene.activateAll();
1117 m_level = l;
1118 m_window.setSubName( m_levels.levelFile(l).c_str() );
1119 m_refresh = true;
1120 if ( m_edit ) {
1121 m_scene.protect(0);
1126 bool save( char *file=NULL )
1128 string p;
1129 if ( file ) {
1130 p = file;
1131 } else {
1132 p = getenv("HOME"); p+="/"USER_BASE_PATH"/L99_saved.nph";
1134 if ( m_scene.save( p ) ) {
1135 m_levels.addPath( p.c_str() );
1136 int l = m_levels.findLevel( p.c_str() );
1137 if ( l >= 0 ) {
1138 m_level = l;
1139 m_window.setSubName( p.c_str() );
1141 return true;
1143 return false;
1146 bool send()
1148 return save( SEND_TEMP_FILE )
1149 #ifdef USE_HILDON
1150 && m_hildon.sendFile( "nobody@", SEND_TEMP_FILE )
1151 #endif
1155 void setTool( int t )
1157 m_colour = t;
1160 void editMode( bool set )
1162 m_edit = set;
1165 void showOverlay( Overlay& o )
1167 printf("show overlay\n");
1168 m_overlays.append( &o );
1169 o.onShow();
1172 void hideOverlay( Overlay& o )
1174 printf("hide overlay\n");
1175 o.onHide();
1176 m_overlays.erase( m_overlays.indexOf(&o) );
1177 m_refresh = true;
1180 void pause( bool doPause )
1182 if ( m_pause != doPause ) {
1183 m_pause = doPause;
1184 if ( m_pause ) {
1185 showOverlay( m_pauseOverlay );
1186 } else {
1187 hideOverlay( m_pauseOverlay );
1192 void edit( bool doEdit )
1194 if ( m_edit != doEdit ) {
1195 m_edit = doEdit;
1196 if ( m_edit ) {
1197 showOverlay( m_editOverlay );
1198 m_scene.protect(0);
1199 } else {
1200 hideOverlay( m_editOverlay );
1201 m_strokeFixed = false;
1202 m_strokeSleep = false;
1203 m_strokeDecor = false;
1204 if ( m_colour < 2 ) m_colour = 2;
1205 m_scene.protect();
1210 Vec2 mousePoint( SDL_Event ev )
1212 return Vec2( ev.button.x, ev.button.y );
1215 bool handleGameEvent( SDL_Event &ev )
1217 switch( ev.type ) {
1218 case SDL_QUIT:
1219 m_quit = true;
1220 break;
1221 case SDL_KEYDOWN:
1222 switch ( ev.key.keysym.sym ) {
1223 case SDLK_q:
1224 m_quit = true;
1225 break;
1226 case SDLK_SPACE:
1227 case SDLK_KP_ENTER:
1228 case SDLK_RETURN:
1229 pause( !m_pause );
1230 break;
1231 case SDLK_s:
1232 case SDLK_F4:
1233 save();
1234 break;
1235 case SDLK_e:
1236 case SDLK_F6:
1237 edit( !m_edit );
1238 break;
1239 case SDLK_r:
1240 case SDLK_UP:
1241 gotoLevel( m_level );
1242 break;
1243 case SDLK_n:
1244 case SDLK_RIGHT:
1245 gotoLevel( m_level+1 );
1246 break;
1247 case SDLK_p:
1248 case SDLK_LEFT:
1249 gotoLevel( m_level-1 );
1250 break;
1251 case SDLK_x:
1252 //record();
1253 default:
1254 break;
1256 break;
1257 default:
1258 break;
1260 return false;
1264 bool handleModEvent( SDL_Event &ev )
1266 static int mod=0;
1267 //printf("mod=%d\n",ev.key.keysym.sym,mod);
1268 switch( ev.type ) {
1269 case SDL_KEYDOWN:
1270 //printf("mod key=%x mod=%d\n",ev.key.keysym.sym,mod);
1271 if ( ev.key.keysym.sym == SDLK_F8 ) {
1272 mod = 1; //zoom- == middle (delete)
1273 return true;
1274 } else if ( ev.key.keysym.sym == SDLK_F7 ) {
1275 mod = 2; //zoom+ == right (move)
1276 return true;
1278 break;
1279 case SDL_KEYUP:
1280 if ( ev.key.keysym.sym == SDLK_F7
1281 || ev.key.keysym.sym == SDLK_F8 ) {
1282 mod = 0;
1283 return true;
1285 break;
1286 case SDL_MOUSEBUTTONDOWN:
1287 case SDL_MOUSEBUTTONUP:
1288 if ( ev.button.button == SDL_BUTTON_LEFT && mod != 0 ) {
1289 ev.button.button = ((mod==1) ? SDL_BUTTON_MIDDLE : SDL_BUTTON_RIGHT);
1291 break;
1293 return false;
1296 bool handlePlayEvent( SDL_Event &ev )
1298 switch( ev.type ) {
1299 case SDL_MOUSEBUTTONDOWN:
1300 if ( ev.button.button == SDL_BUTTON_LEFT
1301 && !m_createStroke ) {
1302 m_createStroke = m_scene.newStroke( Path()&mousePoint(ev) );
1303 if ( m_createStroke ) {
1304 switch ( m_colour ) {
1305 case 0: m_createStroke->setAttribute( Stroke::ATTRIB_TOKEN ); break;
1306 case 1: m_createStroke->setAttribute( Stroke::ATTRIB_GOAL ); break;
1307 default: m_createStroke->setColour( brush_colours[m_colour] ); break;
1309 if ( m_strokeFixed ) {
1310 m_createStroke->setAttribute( Stroke::ATTRIB_GROUND );
1312 if ( m_strokeSleep ) {
1313 m_createStroke->setAttribute( Stroke::ATTRIB_SLEEPING );
1315 if ( m_strokeDecor ) {
1316 m_createStroke->setAttribute( Stroke::ATTRIB_DECOR );
1320 break;
1321 case SDL_MOUSEBUTTONUP:
1322 if ( ev.button.button == SDL_BUTTON_LEFT
1323 && m_createStroke ) {
1324 if ( m_createStroke->numPoints() > 1 ) {
1325 m_scene.activate( m_createStroke );
1326 } else {
1327 m_scene.deleteStroke( m_createStroke );
1329 m_createStroke = NULL;
1331 break;
1332 case SDL_MOUSEMOTION:
1333 if ( m_createStroke ) {
1334 m_createStroke->addPoint( mousePoint(ev) );
1336 break;
1337 case SDL_KEYDOWN:
1338 if ( ev.key.keysym.sym == SDLK_ESCAPE ) {
1339 if ( m_createStroke ) {
1340 m_scene.deleteStroke( m_createStroke );
1341 m_createStroke = NULL;
1342 } else {
1343 m_scene.deleteStroke( m_scene.strokes().at(m_scene.strokes().size()-1) );
1345 m_refresh = true;
1347 break;
1348 default:
1349 break;
1351 return false;
1354 bool handleEditEvent( SDL_Event &ev )
1356 if ( !m_edit ) return false;
1358 switch( ev.type ) {
1359 case SDL_MOUSEBUTTONDOWN:
1360 if ( ev.button.button == SDL_BUTTON_RIGHT
1361 && !m_moveStroke ) {
1362 m_moveStroke = m_scene.strokeAtPoint( mousePoint(ev),
1363 SELECT_TOLERANCE );
1364 } else if ( ev.button.button == SDL_BUTTON_MIDDLE ) {
1365 m_scene.deleteStroke( m_scene.strokeAtPoint( mousePoint(ev),
1366 SELECT_TOLERANCE ) );
1367 m_refresh = true;
1369 break;
1370 case SDL_MOUSEBUTTONUP:
1371 if ( ev.button.button == SDL_BUTTON_RIGHT
1372 && m_moveStroke ) {
1373 m_moveStroke = NULL;
1375 break;
1376 case SDL_MOUSEMOTION:
1377 if ( m_moveStroke ) {
1378 m_moveStroke->origin( mousePoint(ev) );
1380 break;
1381 default:
1382 break;
1384 return false;
1387 void run()
1389 NextLevelOverlay completedOverlay(*this);
1391 m_scene.draw( m_window, FULLSCREEN_RECT );
1392 m_window.update( FULLSCREEN_RECT );
1394 int iterateCounter = 0;
1395 int lastTick = SDL_GetTicks();
1396 bool isComplete = false;
1398 while ( !m_quit ) {
1399 for ( int i=0; i<m_overlays.size(); i++ ) {
1400 m_overlays[i]->onTick( lastTick );
1403 SDL_Event ev;
1404 while ( SDL_PollEvent(&ev) ) {
1405 bool handled = false;
1406 for ( int i=m_overlays.size()-1; i>=0 && !handled; i-- ) {
1407 handled = m_overlays[i]->handleEvent(ev);
1409 if ( !handled ) {
1410 handled = false
1411 || handleModEvent(ev)
1412 || handleGameEvent(ev)
1413 || handleEditEvent(ev)
1414 || handlePlayEvent(ev);
1418 if ( isComplete && m_edit ) {
1419 hideOverlay( completedOverlay );
1420 isComplete = false;
1422 if ( m_scene.isCompleted() != isComplete && !m_edit ) {
1423 isComplete = m_scene.isCompleted();
1424 if ( isComplete ) {
1425 showOverlay( completedOverlay );
1426 } else {
1427 hideOverlay( completedOverlay );
1431 Rect r = m_scene.dirtyArea();
1432 if ( m_refresh || isComplete ) {
1433 r = FULLSCREEN_RECT;
1436 if ( !r.isEmpty() ) {
1437 m_scene.draw( m_window, r );
1440 for ( int i=0; i<m_overlays.size(); i++ ) {
1441 m_overlays[i]->draw( m_window );
1442 r.expand( m_overlays[i]->dirtyArea() );
1445 //temp
1446 if ( m_refresh ) {
1447 m_window.update( FULLSCREEN_RECT );
1448 m_refresh = false;
1449 } else {
1450 r.br.x++; r.br.y++;
1451 m_window.update( r );
1455 if ( !m_pause ) {
1456 //assumes RENDER_RATE <= ITERATION_RATE
1457 while ( iterateCounter < ITERATION_RATE ) {
1458 m_scene.step();
1459 iterateCounter += RENDER_RATE;
1461 iterateCounter -= ITERATION_RATE;
1464 #ifdef USE_HILDON
1465 m_hildon.poll();
1466 for ( char *f = m_hildon.getFile(); f; f=m_hildon.getFile() ) {
1467 if ( f ) {
1468 m_levels.addPath( f );
1469 int l = m_levels.findLevel( f );
1470 if ( l >= 0 ) {
1471 gotoLevel( l );
1472 m_window.raise();
1476 #endif //USE_HILDON
1477 int sleepMs = lastTick + RENDER_INTERVAL - SDL_GetTicks();
1478 if ( sleepMs > 0 ) {
1479 SDL_Delay( sleepMs );
1480 //printf("sleep %dms\n",sleepMs);
1481 } else {
1482 printf("overrun %dms\n",-sleepMs);
1484 lastTick = SDL_GetTicks();
1490 int main(int argc, char** argv)
1492 try {
1493 putenv("SDL_VIDEO_X11_WMCLASS=NPhysics");
1494 if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER) < 0) {
1495 throw "Couldn't initialize SDL";
1498 if ( mkdir( (string(getenv("HOME"))+"/"USER_BASE_PATH).c_str(),
1499 0755)==0 ) {
1500 printf("created user dir\n");
1501 } else if ( errno==EEXIST ){
1502 printf("user dir ok\n");
1503 } else {
1504 printf("failed to create user dir\n");
1508 if ( argc > 2 && strcmp(argv[1],"-bmp")==0 ) {
1509 for ( int i=2; i<argc; i++ ) {
1510 Scene scene( true );
1511 if ( scene.load( argv[i] ) ) {
1512 printf("generating bmp %s\n", argv[i]);
1513 Canvas temp( CANVAS_WIDTH, CANVAS_HEIGHT );
1514 scene.draw( temp, FULLSCREEN_RECT );
1515 string bmp( argv[i] );
1516 bmp += ".bmp";
1517 temp.writeBMP( bmp.c_str() );
1520 } else {
1521 Game game( argc, (const char**)argv );
1522 game.run();
1524 } catch ( const char* e ) {
1525 cout <<"*** CAUGHT: "<<e<<endl;
1527 return 0;