1 /*
2  * Pong Clock - A clock that plays pong.
3  *             See http://mocoloco.com/archives/001766.php for the inspiration.
4  *
5  * Copyright (C) 2005 Matthew Allum
6  *
7  * Author: Matthew Allum mallum@openedhand.com
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22  *
23  */
24 
25 #include <stdlib.h>
26 #include <time.h>
27 #include <sys/time.h>
28 #include <sys/types.h>
29 #include <string.h>
30 #include <stdio.h>
31 #include <unistd.h>
32 #include <signal.h>
33 
34 #include <X11/Xlib.h>
35 #include <X11/Xutil.h>
36 #include <X11/Xatom.h>
37 
38 /* Tweak values for different hw setups */
39 
40 #define FPS          50
41 #define RESX         40
42 #define RESY         40
43 #define TO_MISS_SECS 55
44 #define BALLDX       16
45 #define BALLDY       4
46 
47 
48 typedef struct PongClock
49 {
50   Display *xdpy;
51   int      xscreen;
52   Window   xwin, xwin_root;
53   Pixmap   backbuffer;
54   GC       xgc;
55   int      xwin_width, xwin_height;
56   int      pixelw, pixelh;
57 
58   int      ball_x, ball_y, ball_dx, ball_dy;
59   int      bata_y, batb_y;
60   Bool     bata_to_miss, batb_to_miss;
61 
62 }
63 PongClock;
64 
65 void
get_time(int * hour,int * min,int * sec)66 get_time(int *hour, int *min, int *sec)
67 {
68   struct timeval   tv;
69   struct tm       *localTime = NULL;
70   time_t           actualTime;
71 
72   gettimeofday(&tv, 0);
73   actualTime = tv.tv_sec;
74   localTime = localtime(&actualTime);
75 
76   if (hour)
77     *hour = localTime->tm_hour;
78 
79   if (min)
80     *min  = localTime->tm_min;
81 
82   if (sec)
83     *sec  = localTime->tm_sec;
84 }
85 
86 void
draw_rect(PongClock * pong_clock,int x,int y,int width,int height)87 draw_rect (PongClock *pong_clock,
88 	   int        x,
89 	   int        y,
90 	   int        width,
91 	   int        height)
92 {
93   XFillRectangle (pong_clock->xdpy,
94 		  pong_clock->backbuffer,
95 		  pong_clock->xgc,
96 		  x * pong_clock->pixelw,
97 		  y * pong_clock->pixelh,
98 		  width * pong_clock->pixelw,
99 		  height * pong_clock->pixelh);
100 }
101 
102 void
draw_field(PongClock * pong_clock)103 draw_field (PongClock *pong_clock)
104 {
105   int i;
106 
107   draw_rect (pong_clock, 0, 0, RESX+1, 1);
108   draw_rect (pong_clock, 0, RESY-1, RESX+1, 1);
109 
110   for (i=0; i < RESY/2; i++)
111     draw_rect (pong_clock, (RESX/2)-1, i*2, 2, 1);
112 }
113 
114 void
draw_digit(PongClock * pong_clock,int x,int y,int digit)115 draw_digit (PongClock *pong_clock,
116 	    int        x,
117 	    int        y,
118 	    int        digit)
119 {
120   int digits[] = { 0x1f8c63f, 0x1f21086, 0x1f0fe1f, 0x1f87e1f, 0x1087e31,
121 		   0x1f87c3f, 0x1f8fc3f, 0x84421f,  0x1f8fe3f, 0x1087e3f };
122 
123   XRectangle rects[5*5];
124   int i,j,k;
125 
126   i = 0;
127 
128   for (k=0; k<5; k++)
129     for (j=0; j<5; j++)
130       if (digits[digit] & (1 << ((k*5)+j)))
131 	{
132 	  rects[i].x      = (x + j) * pong_clock->pixelw;
133 	  rects[i].y      = (y + k) * pong_clock->pixelh;
134 	  rects[i].width  = pong_clock->pixelw;
135 	  rects[i].height = pong_clock->pixelh;
136 	  i++;
137 	}
138 
139   XFillRectangles (pong_clock->xdpy,
140 		   pong_clock->backbuffer,
141 		   pong_clock->xgc,
142 		   rects, i);
143 }
144 
145 void
draw_time(PongClock * pong_clock)146 draw_time (PongClock *pong_clock)
147 {
148   int hour, min;
149 
150   get_time(&hour, &min, NULL);
151 
152   draw_digit (pong_clock,
153 	      (RESX/2) - 14,
154 	      5,
155 	      hour / 10 );
156 
157   draw_digit (pong_clock,
158 	      (RESX/2) - 8,
159 	      5,
160 	      hour % 10 );
161 
162   draw_digit (pong_clock,
163 	      (RESX/2) + 3,
164 	      5,
165 	      min / 10 );
166 
167   draw_digit (pong_clock,
168 	      (RESX/2) + 9,
169 	      5,
170 	      min % 10 );
171 }
172 
173 void
draw_bat_and_ball(PongClock * pong_clock)174 draw_bat_and_ball (PongClock *pong_clock)
175 {
176   /* ball */
177 
178   XFillRectangle (pong_clock->xdpy,
179 		  pong_clock->backbuffer,
180 		  pong_clock->xgc,
181 		  pong_clock->ball_x,
182 		  pong_clock->ball_y,
183 		  pong_clock->pixelw,
184 		  pong_clock->pixelh);
185 
186   /* bat a */
187 
188   XFillRectangle (pong_clock->xdpy,
189 		  pong_clock->backbuffer,
190 		  pong_clock->xgc,
191 		  0,
192 		  pong_clock->bata_y - (2 * pong_clock->pixelh),
193 		  pong_clock->pixelw,
194 		  pong_clock->pixelh * 5);
195 
196   /* bat b */
197 
198   XFillRectangle (pong_clock->xdpy,
199 		  pong_clock->backbuffer,
200 		  pong_clock->xgc,
201 		  (pong_clock->xwin_width - pong_clock->pixelw),
202 		  pong_clock->batb_y - (2 * pong_clock->pixelh),
203 		  pong_clock->pixelw,
204 		  pong_clock->pixelh * 5);
205 
206 }
207 
208 void
update_state(PongClock * pong_clock)209 update_state (PongClock *pong_clock)
210 {
211   int sec, min, hour;
212 
213   get_time(&hour, &min, &sec);
214 
215   /* Check ball is on field and no ones dues to miss a shot.
216   */
217   if ( (pong_clock->ball_x < 0 && !pong_clock->bata_to_miss)
218       || (pong_clock->ball_x > (pong_clock->xwin_width - pong_clock->pixelw)
219 	  && !pong_clock->batb_to_miss) )
220     pong_clock->ball_dx *= -1;
221 
222   if ((pong_clock->ball_y < pong_clock->pixelh)
223       || pong_clock->ball_y > (pong_clock->xwin_height - (2*pong_clock->pixelh)))
224     pong_clock->ball_dy *= -1;
225 
226   pong_clock->ball_x += pong_clock->ball_dx;
227   pong_clock->ball_y += pong_clock->ball_dy;
228 
229   /* Set up someone to miss if we getting close to an hour or min.
230    */
231   if (sec > TO_MISS_SECS)
232     {
233       if (min == 59)
234 	pong_clock->batb_to_miss = True;
235       else
236 	pong_clock->bata_to_miss = True;
237     }
238   else
239     {
240       /* Reset the game */
241       if (pong_clock->bata_to_miss)
242 	{
243 	  pong_clock->bata_to_miss = False;
244 	  pong_clock->ball_y = pong_clock->bata_y;
245 	  pong_clock->ball_x = pong_clock->pixelw;
246 	  pong_clock->ball_dx *= -1;
247 	}
248 
249       if (pong_clock->batb_to_miss)
250 	{
251 	  pong_clock->batb_to_miss = False;
252 	  pong_clock->ball_y = pong_clock->batb_y;
253 	  pong_clock->ball_x = pong_clock->xwin_width - pong_clock->pixelw;
254 	  pong_clock->ball_dx *= -1;
255 	}
256     }
257 
258   /* Keep bats on field and only move in not setup to miss */
259   if (pong_clock->ball_y >= (3*pong_clock->pixelh)
260       && pong_clock->ball_y <=  (pong_clock->xwin_height - (5*pong_clock->pixelh)))
261   {
262     if (!pong_clock->batb_to_miss)
263       pong_clock->batb_y = pong_clock->ball_y;
264 
265     if (!pong_clock->bata_to_miss)
266       pong_clock->bata_y = pong_clock->ball_y;
267   }
268 }
269 
270 void
draw_frame(PongClock * pong_clock)271 draw_frame (PongClock *pong_clock)
272 {
273   update_state (pong_clock);
274 
275   /* Clear playfield */
276   XSetForeground (pong_clock->xdpy,
277 		  pong_clock->xgc,
278 		  BlackPixel(pong_clock->xdpy,
279 			     pong_clock->xscreen));
280 
281   XFillRectangle (pong_clock->xdpy,
282 		  pong_clock->backbuffer,
283 		  pong_clock->xgc,
284 		  0, 0,
285 		  pong_clock->xwin_width,
286 		  pong_clock->xwin_height);
287 
288   XSetForeground (pong_clock->xdpy,
289 		  pong_clock->xgc,
290 		  WhitePixel(pong_clock->xdpy,
291 			     pong_clock->xscreen));
292 
293   draw_field (pong_clock);
294 
295   draw_time (pong_clock);
296 
297   draw_bat_and_ball (pong_clock);
298 
299   /* flip 'backbuffer' */
300   XSetWindowBackgroundPixmap (pong_clock->xdpy,
301 			      pong_clock->xwin,
302 			      pong_clock->backbuffer);
303   XClearWindow(pong_clock->xdpy, pong_clock->xwin);
304 
305   XSync(pong_clock->xdpy, False);
306 }
307 
308 int
main(int argc,char ** argv)309 main (int argc, char **argv)
310 {
311   XGCValues  gcv;
312   Atom       atoms_WINDOW_STATE, atoms_WINDOW_STATE_FULLSCREEN;
313   PongClock *pong_clock;
314 
315   pong_clock = malloc(sizeof(PongClock));
316   memset(pong_clock, 0, sizeof(PongClock));
317 
318   if ((pong_clock->xdpy = XOpenDisplay(getenv("DISPLAY"))) == NULL) {
319     fprintf(stderr, "Cannot connect to X server on display %s.",
320 	    getenv("DISPLAY"));
321     exit(-1);
322   }
323 
324   pong_clock->xscreen     = DefaultScreen(pong_clock->xdpy);
325   pong_clock->xwin_root   = DefaultRootWindow(pong_clock->xdpy);
326   pong_clock->xwin_width  = DisplayWidth(pong_clock->xdpy,
327 					 pong_clock->xscreen);
328   pong_clock->xwin_height = DisplayHeight(pong_clock->xdpy,
329 					  pong_clock->xscreen);
330 
331   pong_clock->pixelw  = pong_clock->xwin_width  / RESX;
332   pong_clock->pixelh  = pong_clock->xwin_height / RESY;
333 
334   pong_clock->ball_x = 0;
335   pong_clock->ball_y = pong_clock->xwin_height / 2;
336 
337   pong_clock->ball_dx = BALLDX;
338   pong_clock->ball_dy = BALLDY;
339 
340   pong_clock->batb_y = pong_clock->bata_y = pong_clock->ball_y;
341 
342   gcv.background = BlackPixel(pong_clock->xdpy,
343 			      pong_clock->xscreen);
344   gcv.foreground = WhitePixel(pong_clock->xdpy,
345 			      pong_clock->xscreen);
346   gcv.graphics_exposures = False;
347 
348   pong_clock->xgc = XCreateGC (pong_clock->xdpy, pong_clock->xwin_root,
349 			       GCForeground|GCBackground|GCGraphicsExposures,
350 			       &gcv);
351 
352   atoms_WINDOW_STATE
353     = XInternAtom(pong_clock->xdpy, "_NET_WM_STATE",False);
354   atoms_WINDOW_STATE_FULLSCREEN
355     = XInternAtom(pong_clock->xdpy, "_NET_WM_STATE_FULLSCREEN",False);
356 
357   pong_clock->xwin = XCreateSimpleWindow(pong_clock->xdpy,
358 					 pong_clock->xwin_root,
359 					 0, 0,
360 					 pong_clock->xwin_width,
361 					 pong_clock->xwin_height,
362 					 0,
363 					 WhitePixel(pong_clock->xdpy,
364 						    pong_clock->xscreen),
365 					 BlackPixel(pong_clock->xdpy,
366 						    pong_clock->xscreen));
367 
368   pong_clock->backbuffer = XCreatePixmap(pong_clock->xdpy,
369 					 pong_clock->xwin_root,
370 					 pong_clock->xwin_width,
371 					 pong_clock->xwin_height,
372 					 DefaultDepth(pong_clock->xdpy,
373 						      pong_clock->xscreen));
374 
375   XSelectInput(pong_clock->xdpy, pong_clock->xwin, KeyPressMask);
376 
377 
378   /* Set the hints for fullscreen */
379   XChangeProperty(pong_clock->xdpy,
380 		  pong_clock->xwin,
381 		  atoms_WINDOW_STATE,
382 		  XA_ATOM,
383 		  32,
384 		  PropModeReplace,
385 		  (unsigned char *) &atoms_WINDOW_STATE_FULLSCREEN, 1);
386 
387   XMapWindow(pong_clock->xdpy, pong_clock->xwin);
388 
389   while (True)
390     {
391       struct timeval timeout;
392       XEvent         xev;
393 
394       timeout.tv_sec  = 0;
395       timeout.tv_usec = 1000000 / FPS;
396       select (0, NULL, NULL, NULL, &timeout);
397 
398       draw_frame (pong_clock);
399 
400       XFlush(pong_clock->xdpy);
401 
402       if (XPending(pong_clock->xdpy))
403 	{
404 	  if (XCheckMaskEvent(pong_clock->xdpy,
405 			      KeyPressMask,
406 			      &xev))
407 	    exit(-1);
408 	}
409     }
410 }
411