accelerometer should be optional
[sandcastle] / sand.c
1 /**
2  * Sandcastle - Falling sand game for Maemo 5
3  * Copyright (c) 2009 Thomas Thurman <thomas.thurman@collabora.co.uk>
4  * This is a demonstration of the accelerometer.
5  * It's only a few hours' work and could stand to be improved in
6  * several ways:
7  *  - use a sorted list of grains to speed up sand processing
8  *  - add different colours of sand?
9  *  - add various other stuff like plants, water, explosives...
10  *    (as in the web game "Hell of Sand")
11  *
12  *  I also haven't tracked down why it crashes sometimes when
13  *  you run it from the launcher.  Run it from a terminal as
14  *  /usr/bin/sandcastle to get around this.
15  */
16
17 /**
18  * This program is free software; you can redistribute it and/or
19  * modify it under the terms of the GNU General Public License as
20  * published by the Free Software Foundation; either version 2 of the
21  * License, or (at your option) any later version.
22  *
23  * This program is distributed in the hope that it will be useful, but
24  * WITHOUT ANY WARRANTY; without even the implied warranty of
25  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
26  * General Public License for more details.
27  * 
28  * You should have received a copy of the GNU General Public License
29  * along with this program; if not, write to the Free Software
30  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
31  * 02111-1307, USA.
32  */
33
34 #include <gtk/gtk.h>
35 #include <gdk-pixbuf/gdk-pixbuf.h>
36 #include <mce/mode-names.h>
37 #include <mce/dbus-names.h>
38 #include <hildon/hildon.h>
39 #include <dbus/dbus-glib.h>
40
41 GdkPixbuf *pixbuf;
42 GtkWidget *window, *area;
43 int width, height, rowstride;
44 int minx, maxx, miny, maxy;
45 DBusGProxy *dbus_proxy = NULL;
46 DBusGProxyCall *dbus_call = NULL;
47 guint32 *pixels = NULL;
48 GList *grains = NULL;
49
50 const guint32 SAND  = 0xff00ffff;
51 const guint32 WALL  = 0xffffffff;
52 const guint32 EMPTY = 0xff000000;
53
54 enum {
55   UP = 0,
56   UP_RIGHT = 1,
57   RIGHT = 2,
58   DOWN_RIGHT = 3,
59   DOWN = 4,
60   DOWN_LEFT = 5,
61   LEFT = 6,
62   UP_LEFT = 7
63 };
64
65 int gravity = DOWN;
66 int there_is_gravity = TRUE;
67
68 static inline guint32*
69 translate_pointer_by_direction(guint32 *src, int direction, int x, int y)
70 {
71   static guint32 edge = WALL;
72
73   switch (direction%8) {
74   case UP:
75     if (y<=0) return &edge;
76     return src-rowstride;
77   case UP_RIGHT:
78     if (y<=0 || x>=width) return &edge;
79     return (src-rowstride)+1;
80   case RIGHT:
81     if (x>=width) return &edge;
82     return src+1;
83   case DOWN_RIGHT:
84     if (x>=width || y>=height) return &edge;
85     return (src+rowstride)+1;
86   case DOWN:
87     if (y>=height) return &edge;
88     return src+rowstride;
89   case DOWN_LEFT:
90     if (x<=0 || y>=height) return &edge;
91     return (src+rowstride)-1;
92   case LEFT:
93     if (x<=0) return &edge;
94     return src-1;
95   case UP_LEFT:
96     if (x<=0 || y<=0) return &edge;
97     return (src-rowstride)-1;
98   }
99 }
100
101 static gint
102 expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer data)
103 {
104   gdk_draw_pixbuf (GDK_DRAWABLE(widget->window),
105                    widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
106                    pixbuf, event->area.x, event->area.y,
107                    event->area.x, event->area.y,
108                    event->area.width, event->area.height,
109                    GDK_RGB_DITHER_NONE, 0, 0);
110   return TRUE;
111 }
112
113 static gboolean
114 tap_event (GtkWidget      *widget,
115            GdkEventMotion *event,
116            gpointer        user_data)
117 {
118   int rx, ry;
119
120   for (rx=-5; rx<=5; rx++)
121     for (ry=-5; ry<=5; ry++)
122       {
123         guint32 *p = pixels + (ry+(int)event->y)*rowstride + (rx+(int)event->x);
124
125         if (*p==EMPTY) *p=SAND;
126       }
127
128   gtk_widget_queue_draw_area (area, ((int)event->x)-5, ((int)event->y)-5, 10, 10);
129   minx=miny=0;
130   maxx=width; maxy=height;
131 }
132
133 static void
134 orientation_callback (DBusGProxy *proxy, DBusGProxyCall *call, void *data)
135 {
136   gchar *s1, *s2, *s3;
137   gint x, y, z;
138   gchar facing = 0;
139
140   if (dbus_g_proxy_end_call (proxy, call, NULL,
141                              G_TYPE_STRING, &s1,
142                              G_TYPE_STRING, &s2,
143                              G_TYPE_STRING, &s3,
144                              G_TYPE_INT, &x,
145                              G_TYPE_INT, &y,
146                              G_TYPE_INT, &z,
147                              G_TYPE_INVALID)) {
148
149     if (x<=-500) facing |= 0x10;
150     else if (x<=0) facing |= 0x20;
151     else if (x<=500) facing |= 0x30;
152     else facing |= 0x40;
153
154     if (y<=-500) facing |= 0x1;
155     else if (y<=0) facing |= 0x2;
156     else if (y<=500) facing |= 0x3;
157     else facing |= 0x4;
158
159     g_free(s1); g_free(s2); g_free(s3);
160
161     there_is_gravity = TRUE;
162
163     switch (facing) {
164     case 0x34: case 0x36: case 0x24: gravity=UP; break;
165     case 0x13: case 0x14: gravity=UP_RIGHT; break;
166     case 0x12: gravity=RIGHT; break;
167     case 0x11: gravity=DOWN_RIGHT; break;
168     case 0x21: case 0x31: gravity=DOWN; break;
169     case 0x41: gravity=DOWN_LEFT; break;
170     case 0x42: case 0x43: gravity=LEFT; break;
171     case 0x44: gravity=UP_LEFT; break;
172     default:
173       there_is_gravity=FALSE;
174     }
175
176   } else {
177     g_warning ("Couldn't end the call!\n");
178   }
179   dbus_call = NULL;
180 }
181
182 gint
183 sand_sort (gpointer sand1, gpointer sand2)
184 {
185   if (sand1<sand2)
186     return -1;
187   else if (sand1==sand1)
188     return 0;
189   else
190     return 1;
191 }
192
193 static gboolean
194 update_screen (gpointer data)
195 {
196   int x, y;
197   int newminx=width, newminy=height, newmaxx=0, newmaxy=0;
198
199   int xstart, xend, xdelta, ystart, yend, ydelta;
200
201   if (!dbus_call && dbus_proxy)
202     dbus_call = dbus_g_proxy_begin_call (
203                                          dbus_proxy,
204                                          "get_device_orientation",
205                                          orientation_callback,
206                                          NULL, NULL,
207                                          G_TYPE_INVALID);
208
209   if (!there_is_gravity) return TRUE;
210
211   switch(gravity)
212     {
213     case UP:
214     case UP_RIGHT:
215     case DOWN_RIGHT:
216     case DOWN:
217     case RIGHT:
218       xstart=maxx; xend=minx; xdelta=-1;
219       break;
220     case DOWN_LEFT:
221     case LEFT:
222     case UP_LEFT:
223       xstart=minx; xend=maxx; xdelta=1;
224       break;
225
226     }
227
228   switch(gravity)
229     {
230     case UP:
231     case UP_LEFT:
232     case UP_RIGHT:
233     case LEFT:
234       ystart=miny; yend=maxy; ydelta=1;
235       break;
236
237     case DOWN:
238     case DOWN_LEFT:
239     case DOWN_RIGHT:
240     case RIGHT:
241       ystart=maxy; yend=miny; ydelta=-1;
242       break;
243
244     }
245
246   g_assert( (xstart-xend)*xdelta < 0);
247   g_assert( (ystart-yend)*ydelta < 0);
248
249   for (y=ystart; y!=yend; y+=ydelta) {
250
251     guint32 *p = pixels + y*rowstride + xstart;
252
253     for (x=xstart; x!=xend; x+=xdelta) {
254
255       if (*p==SAND) {
256         const int directions[] = {0, 1, -1};
257         int i;
258         for (i=0; i<3; i++) {
259           guint32 *below = translate_pointer_by_direction(p, gravity+directions[i], x, y);
260           if (*below==EMPTY) {
261             *below=SAND;
262             *p=EMPTY;
263             break;
264           }
265         }
266
267         if (x<newminx) newminx=x;
268         if (x>newmaxx) newmaxx=x;
269         if (y<newminy) newminy=y;
270         if (y>newmaxy) newmaxy=y;
271       }
272       p += xdelta;
273     }
274   }
275
276   minx=newminx; if (minx>2) minx--; else minx=0;
277   maxx=newmaxx; if (maxx<width-2) maxx++; else maxx=width;
278   miny=newminy; if (miny>2) miny--; else miny=0;
279   maxy=newmaxy; if (maxy<height-2) maxy++; else maxy=height-2;
280
281   gtk_widget_queue_draw_area (area, minx, miny, maxx-minx, maxy-miny);
282
283   return TRUE;
284 }
285
286 void
287 count_grains (void)
288 {
289   int x, y;
290
291   for (y=0; y<=height; y++) {
292
293     guint32 *p = pixels + y*rowstride;
294
295     for (x=0; x<width; x++) {
296       grains = g_list_append(grains, p+x);
297     }
298   }
299
300   g_warning ("There are %d grains.\n", g_list_length (grains));
301 }
302
303 int
304 main(int argc, char **argv)
305 {
306   hildon_gtk_init (&argc, &argv);
307
308   dbus_proxy = dbus_g_proxy_new_for_name_owner (
309                                                 dbus_g_bus_get (DBUS_BUS_SYSTEM, NULL),
310                                                 "com.nokia.mce",
311                                                 "/com/nokia/mce/request",
312                                                 "com.nokia.mce.request", NULL);
313
314   if (!dbus_proxy)
315     {
316       g_warning ("You have no accelerometer installed.  Gravity will always be downwards.\n");
317     }
318
319   pixbuf = gdk_pixbuf_new_from_file ("/usr/share/sandcastle/sandcastle.png", NULL);
320   pixels = (guint32*) gdk_pixbuf_get_pixels (pixbuf);
321   minx = miny = 0;
322   maxx = width = gdk_pixbuf_get_width (pixbuf);
323   maxy = height = gdk_pixbuf_get_height (pixbuf);
324   maxx-=2; maxy-=2;
325   rowstride = gdk_pixbuf_get_rowstride (pixbuf)/4;
326
327   count_grains ();
328   
329   window = hildon_stackable_window_new ();
330   gtk_window_set_title (GTK_WINDOW (window), "Sandcastle");
331
332   area = gtk_drawing_area_new ();
333   gtk_widget_add_events (area, GDK_BUTTON_MOTION_MASK|GDK_BUTTON_PRESS_MASK);
334
335   gtk_container_add (GTK_CONTAINER (window), area);
336   g_signal_connect (G_OBJECT (area), "expose_event", G_CALLBACK (expose_event), NULL);
337   g_signal_connect (G_OBJECT (area), "motion-notify-event", G_CALLBACK (tap_event), NULL);
338   g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL);
339
340   gtk_widget_show_all (window);
341
342   g_timeout_add (20, update_screen, NULL);
343
344   gtk_main ();
345
346   return 0;
347 }
348