Initial Import
[profile/ivi/clutter-toys.git] / pong / pong2.c
1 #include <clutter/clutter.h>
2 #include <cairo/cairo.h>
3 #include <math.h>
4
5 #define PADDLE_SIZE 48.0
6 #define PADDLE_THICKNESS 8.0
7 #define PADDLE_SPEED 4
8 #define BALL_SIZE 12.0
9 #define DASH_LENGTH 12.0
10 #define ARENA_WIDTH 320.0
11 #define ARENA_HEIGHT 240.0
12 #define FPS 60.0
13 #define MINPOS (PADDLE_THICKNESS * 2)
14 #define MAXPOS (ARENA_HEIGHT - PADDLE_SIZE - (PADDLE_THICKNESS * 2))
15
16 /*
17  * NOTE: This is a completely brain-dead way to implement pong, but helped
18  *       me familiarise with Clutter paths and such.
19  */
20
21 static const ClutterColor green = { 0x0, 0xff, 0x0, 0xff };
22
23 typedef struct {
24         /* First paddle */
25         gint score1;
26         gint position1;
27         ClutterActor *paddle1;
28         gboolean up1;
29         gboolean down1;
30         
31         /* Second paddle */
32         gint score2;
33         gint position2;
34         ClutterActor *paddle2;
35         gboolean up2;
36         gboolean down2;
37         
38         /* Paddle independent */
39         gdouble angle;
40         gdouble speed;
41         ClutterActor *ball;
42         ClutterActor *arena;
43         ClutterTimeline *timeline;
44         ClutterAlpha *alpha;
45         ClutterBehaviour *behaviour;
46         ClutterPath *path;
47         ClutterKnot start;
48         ClutterKnot end;
49         gboolean pause;
50 } PongData;
51
52 static ClutterActor *
53 pong_arena_actor_create (PongData *data)
54 {
55         ClutterActor *group, *actor;
56         ClutterGeometry geom;
57         gint i;
58         
59         group = clutter_group_new ();
60         
61         /* Top border */
62         actor = clutter_rectangle_new_with_color (&green);
63         geom.x = 0; geom.y = 0;
64         geom.width = ARENA_WIDTH; geom.height = PADDLE_THICKNESS;
65         clutter_actor_set_geometry (actor, &geom);
66         clutter_actor_show (actor);
67         clutter_group_add (CLUTTER_GROUP (group), actor);
68         
69         /* Bottom border */
70         actor = clutter_rectangle_new_with_color (&green);
71         geom.y = ARENA_HEIGHT - PADDLE_THICKNESS;
72         clutter_actor_set_geometry (actor, &geom);
73         clutter_actor_show (actor);
74         clutter_group_add (CLUTTER_GROUP (group), actor);
75         
76         /* Dotted line down the middle */
77         geom.x = (ARENA_WIDTH / 2) - (PADDLE_THICKNESS / 2);
78         geom.width = PADDLE_THICKNESS;
79         geom.height = PADDLE_THICKNESS * 2;
80         for (i = 0; i < ARENA_HEIGHT / (PADDLE_THICKNESS * 2); i+= 2) {
81                 geom.y = i * PADDLE_THICKNESS * 2;
82                 actor = clutter_rectangle_new_with_color (&green);
83                 clutter_actor_set_geometry (actor, &geom);
84                 clutter_actor_show (actor);
85                 clutter_group_add (CLUTTER_GROUP (group), actor);
86         }
87         
88         return group;
89 }
90
91 static ClutterActor *
92 pong_ball_actor_create (PongData *data)
93 {
94         ClutterActor *actor, *group;
95         cairo_t *cr;
96         
97         actor = clutter_cairo_texture_new (BALL_SIZE, BALL_SIZE);
98         cr = clutter_cairo_texture_create (CLUTTER_CAIRO_TEXTURE (actor));
99         
100         /* Clear */
101         cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
102         cairo_paint(cr);
103         
104         cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
105         cairo_set_source_rgba (cr, 0, 1.0, 0, 1.0);
106         
107         cairo_new_path (cr);
108         cairo_arc (cr, BALL_SIZE/2.0, BALL_SIZE/2.0,
109                 BALL_SIZE/2.0, 0, 2*M_PI);
110         cairo_fill (cr);
111         
112         cairo_destroy (cr);
113
114         group = clutter_group_new ();
115         clutter_group_add (CLUTTER_GROUP (group), actor);
116         clutter_actor_set_position (actor, -BALL_SIZE/2, -BALL_SIZE/2);
117         clutter_actor_show (actor);
118
119         return group;
120 }
121
122 static ClutterActor *
123 pong_paddle_actor_create (PongData *data)
124 {
125         ClutterGeometry geom;
126         ClutterActor *actor;
127
128         actor = clutter_rectangle_new_with_color (&green);
129         geom.x = 0; geom.y = 0;
130         geom.width = PADDLE_THICKNESS;
131         geom.height = PADDLE_SIZE;
132         clutter_actor_set_geometry (actor, &geom);
133         
134         return actor;
135 }
136
137 static void
138 pong_ball_path_calculate (PongData *data, gdouble ax, gdouble ay, gdouble w,
139                           gdouble h)
140 {
141         gdouble x, y, dx, dy, angle;
142         
143         x = clutter_actor_get_x (data->ball);
144         y = clutter_actor_get_y (data->ball);
145         
146         data->start.x = x;
147         data->start.y = y;
148
149         /* Work out destination */
150         if (data->angle < M_PI * 0.5) {
151                 /* Travelling up-right */
152                 dx = x + (tan (data->angle) * (y - ay - (BALL_SIZE/2)));
153                 if (dx > w - (BALL_SIZE/2)) {
154                         dx = w - (BALL_SIZE/2);
155                         dy = y - ((dx - x) / tan (data->angle));
156                 } else {
157                         dy = ay + (BALL_SIZE / 2);
158                 }
159         } else if (data->angle < M_PI) {
160                 /* Travelling down-right */
161                 angle = M_PI - data->angle;
162                 dx = x + (tan (angle) * (h - y));
163                 if (dx > w - (BALL_SIZE/2)) {
164                         dx = w - (BALL_SIZE/2);
165                         dy = y + ((dx - x) / tan (angle));
166                 } else {
167                         dy = h - (BALL_SIZE/2);
168                 }
169         } else if (data->angle < M_PI * 1.5) {
170                 /* Travelling down-left */
171                 angle = data->angle - M_PI;
172                 dx = x - (tan (angle) * (h - y));
173                 if (dx < (BALL_SIZE/2)) {
174                         dx = (BALL_SIZE/2);
175                         dy = y + ((x - ax) / tan (angle));
176                 } else {
177                         dy = h - (BALL_SIZE/2);
178                 }
179         } else {
180                 /* Travelling up-left */
181                 angle = (M_PI * 2) - data->angle;
182                 dx = x - (tan (angle) * (y - ay - (BALL_SIZE/2)));
183                 if (dx <  (BALL_SIZE/2)) {
184                         dx = (BALL_SIZE/2);
185                         dy = y - ((x - ax) / tan (angle));
186                 } else {
187                         dy = ay + (BALL_SIZE / 2);
188                 }
189         }
190         
191         clutter_timeline_set_duration (data->timeline, MAX (1000/FPS,
192                 (guint)(1000 * (ABS (dx - x)/w) * data->speed)));
193         data->end.x = (gint)dx;
194         data->end.y = (gint)dy;
195 }
196
197 static void
198 pong_path_end_cb (ClutterTimeline *timeline,
199                       PongData *data)
200 {
201         /* Figure out the new angle of the ball after a collision */
202                 gint x, y;
203
204                 x = clutter_actor_get_x (data->ball);
205                 y = clutter_actor_get_y (data->ball);
206                 
207                 /*g_debug ("%d, %d, %lf", x, y, data->angle);*/
208                 
209                 /* Work out new travel angle after collisions */
210                 if ((x >= (ARENA_WIDTH - (BALL_SIZE/2))) ||
211                     (x <= (BALL_SIZE/2)))
212                         data->angle = -data->angle;
213
214                 while (data->angle > M_PI*2) data->angle -= M_PI*2;
215                 while (data->angle < 0) data->angle += M_PI*2;
216
217                 if (y <= PADDLE_THICKNESS + (BALL_SIZE/2)) {
218                         if (data->angle < M_PI * 0.5) {
219                                 data->angle = M_PI - data->angle;
220                         } else if (data->angle > M_PI * 1.5) {
221                                 data->angle = M_PI +
222                                         ((M_PI * 2.0) - data->angle);
223                         }
224                 } else if (y >= ARENA_HEIGHT - PADDLE_THICKNESS -
225                            (BALL_SIZE/2)) {
226                         if (data->angle < M_PI) {
227                                 data->angle = M_PI - data->angle;
228                         } else if (data->angle < M_PI * 1.5) {
229                                 data->angle = (M_PI * 2.0) -
230                                         (data->angle - M_PI);
231                         }
232                 }
233                 
234                 while (data->angle > M_PI*2) { data->angle -= M_PI*2; }
235                 while (data->angle < 0) { data->angle += M_PI*2; }
236                 
237                 pong_ball_path_calculate (data,
238                         0, PADDLE_THICKNESS,
239                         ARENA_WIDTH,
240                         ARENA_HEIGHT - PADDLE_THICKNESS);
241                 
242         clutter_path_clear (data->path);
243         clutter_path_add_move_to (data->path, data->start.x, data->start.y);
244         clutter_path_add_line_to (data->path, data->end.x,   data->end.y  );
245
246         clutter_timeline_start (data->timeline);
247
248                 /*g_debug ("%d, %d, %lf", data->end.x,
249                         data->end.y, data->angle);*/
250 }
251
252 static void
253 pong_key_press_event_cb (ClutterStage *stage, ClutterEvent *event,
254                          PongData *data)
255 {
256         guint key_symbol = clutter_event_get_key_symbol (event);
257
258         if ((key_symbol != CLUTTER_p) && (data->pause)) {
259                 data->pause = FALSE;
260                 clutter_timeline_start (data->timeline);
261         }
262                 
263         switch (key_symbol)
264         {
265             case CLUTTER_Escape:
266             case CLUTTER_q:
267                 clutter_main_quit ();
268                 break;
269             case CLUTTER_a:
270                 data->up1 = TRUE;
271                 break;
272             case CLUTTER_z:
273                 data->down1 = TRUE;
274                 break;
275             case CLUTTER_k:
276                 data->up2 = TRUE;
277                 break;
278             case CLUTTER_m:
279                 data->down2 = TRUE;
280                 break;
281             case CLUTTER_p:
282                 data->pause = !data->pause;
283                 if (data->pause) {
284                         clutter_timeline_pause (data->timeline);
285                 } else
286                         clutter_timeline_start (data->timeline);
287             default:
288                 break;
289         }
290 }
291
292 static void
293 pong_key_release_event_cb (ClutterStage *stage, ClutterEvent *event,
294                            PongData *data)
295 {
296         guint key_symbol = clutter_event_get_key_symbol (event);
297
298         switch (key_symbol)
299         {
300             case CLUTTER_a:
301                 data->up1 = FALSE;
302                 break;
303             case CLUTTER_z:
304                 data->down1 = FALSE;
305                 break;
306             case CLUTTER_k:
307                 data->up2 = FALSE;
308                 break;
309             case CLUTTER_m:
310                 data->down2 = FALSE;
311                 break;
312             default:
313                 break;
314         }
315 }
316
317 static void
318 pong_new_frame_cb (ClutterTimeline *timeline, gint frame_num,
319                                PongData *data)
320 {
321         if (data->up1 ^ data->down1) {
322                 data->position1 = MAX (MINPOS, MIN (MAXPOS, data->position1 +
323                         (data->down1 ? PADDLE_SPEED : 0) -
324                         (data->up1 ? PADDLE_SPEED : 0)));
325                 clutter_actor_set_position (data->paddle1, PADDLE_THICKNESS,
326                         data->position1);
327         }
328
329         if (data->up2 ^ data->down2) {
330                 data->position2 = MAX (MINPOS, MIN (MAXPOS, data->position2 +
331                         (data->down2 ? PADDLE_SPEED : 0) -
332                         (data->up2 ? PADDLE_SPEED : 0)));
333                 clutter_actor_set_position (data->paddle2, ARENA_WIDTH -
334                         (PADDLE_THICKNESS * 2), data->position2);
335         }
336 }
337
338 int
339 main (int argc, char **argv)
340 {
341         PongData data;
342         ClutterActor *stage;
343         const ClutterColor stage_color = { 0x00, 0x00, 0x00, 0xff };
344         
345         clutter_init (&argc, &argv);
346         
347         data.arena = pong_arena_actor_create (&data);
348         data.paddle1 = pong_paddle_actor_create (&data);
349         data.paddle2 = pong_paddle_actor_create (&data);
350         data.ball = pong_ball_actor_create (&data);
351         
352         clutter_actor_set_position (data.paddle1, PADDLE_THICKNESS,
353                 PADDLE_THICKNESS * 2);
354         clutter_actor_set_position (data.paddle2,
355                 ARENA_WIDTH - (PADDLE_THICKNESS * 2),
356                 PADDLE_THICKNESS * 2);
357         
358         data.up1 = FALSE;
359         data.down1 = FALSE;
360         data.up2 = FALSE;
361         data.down2 = FALSE;
362         data.pause = TRUE;
363         data.position1 = 0;
364         data.position2 = 0;
365         
366         data.timeline = clutter_timeline_new (2000);
367         data.alpha = clutter_alpha_new_full (data.timeline, CLUTTER_LINEAR);
368         data.path = clutter_path_new();
369         data.behaviour = clutter_behaviour_path_new (data.alpha, data.path);
370         
371         data.angle = ((M_PI * 1.8));
372         data.speed = 2;
373         
374         clutter_actor_set_position (data.ball, ARENA_WIDTH/2, ARENA_HEIGHT/2);
375         
376         stage = clutter_stage_get_default ();
377         clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color);
378         
379         clutter_group_add (CLUTTER_GROUP (stage), data.arena);
380         clutter_group_add (CLUTTER_GROUP (stage), data.paddle1);
381         clutter_group_add (CLUTTER_GROUP (stage), data.paddle2);
382         clutter_group_add (CLUTTER_GROUP (stage), data.ball);
383         
384         clutter_actor_show_all (CLUTTER_ACTOR (stage));
385         clutter_actor_set_scale (CLUTTER_ACTOR (stage),
386                 CLUTTER_STAGE_WIDTH () / ARENA_WIDTH,
387                 CLUTTER_STAGE_HEIGHT () / ARENA_HEIGHT);
388         
389         pong_ball_path_calculate (&data,
390                         0, PADDLE_THICKNESS,
391                         ARENA_WIDTH,
392                         ARENA_HEIGHT - PADDLE_THICKNESS);
393         clutter_behaviour_apply (data.behaviour, data.ball);
394         
395         clutter_path_add_move_to (data.path, data.start.x, data.start.y);
396         clutter_path_add_line_to (data.path, data.end.x,   data.end.y  );
397
398         g_signal_connect_after (data.timeline, "completed",
399                 G_CALLBACK (pong_path_end_cb), &data);
400         g_signal_connect (data.timeline, "new_frame",
401                 G_CALLBACK (pong_new_frame_cb), &data);
402
403         g_signal_connect (stage, "key-press-event",
404                 G_CALLBACK (pong_key_press_event_cb), &data);
405         g_signal_connect (stage, "key-release-event",
406                 G_CALLBACK (pong_key_release_event_cb), &data);
407
408         clutter_main ();
409
410         return 0;
411 }
412