dtwm: basic multihead(xinerama only) support
[oweals/cde.git] / cde / programs / dtscreen / life.c
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
6  * These libraries and programs are free software; you can
7  * redistribute them and/or modify them under the terms of the GNU
8  * Lesser General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * These libraries and programs are distributed in the hope that
13  * they will be useful, but WITHOUT ANY WARRANTY; without even the
14  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with these librararies and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 /* $XConsortium: life.c /main/3 1995/11/02 16:07:44 rswiston $ */
24 /*                                                                      *
25  * (c) Copyright 1993, 1994 Hewlett-Packard Company                     *
26  * (c) Copyright 1993, 1994 International Business Machines Corp.       *
27  * (c) Copyright 1993, 1994 Sun Microsystems, Inc.                      *
28  * (c) Copyright 1993, 1994 Novell, Inc.                                *
29  */
30 /*-
31  * life.c - Conway's game of Life for dtscreen, the X Window System lockscreen.
32  *
33  * Copyright (c) 1991 by Patrick J. Naughton.
34  *
35  * See dtscreen.c for copying information.
36  *
37  * Revision History:
38  * 24-May-91: Added wraparound code from johnson@bugs.comm.mot.com.
39  *            Made old cells stay blue.
40  *            Made batchcount control the number of generations till restart.
41  * 29-Jul-90: support for multiple screens.
42  * 07-Feb-90: remove bogus semi-colon after #include line.
43  * 15-Dec-89: Fix for proper skipping of {White,Black}Pixel() in colors.
44  * 08-Oct-89: Moved seconds() to an extern.
45  * 20-Sep-89: Written (life algorithm courtesy of Jim Graham, flar@sun.com).
46  */
47
48 #include "dtscreen.h"
49 #include "lifeicon.bit"
50 #include <stdlib.h>
51
52 static XImage logo = {
53     0, 0,                       /* width, height */
54     0, XYBitmap, 0,             /* xoffset, format, data */
55     LSBFirst, 8,                /* byte-order, bitmap-unit */
56     LSBFirst, 8, 1              /* bitmap-bit-order, bitmap-pad, depth */
57 };
58 #define min(a, b) ((a)<(b)?(a):(b))
59 #define MAXROWS 155
60 #define MAXCOLS 144
61 #define TIMEOUT 30
62
63 typedef struct {
64     int         pixelmode;
65     int         xs;
66     int         ys;
67     int         xb;
68     int         yb;
69     int         generation;
70     long        shooterTime;
71     int         nrows;
72     int         ncols;
73     int         width;
74     int         height;
75     unsigned char buffer[(MAXROWS + 2) * (MAXCOLS + 2) + 2];
76     unsigned char tempbuf[MAXCOLS * 2];
77     unsigned char lastbuf[MAXCOLS];
78     unsigned char agebuf[(MAXROWS + 2) * (MAXCOLS + 2)];
79 }           lifestruct;
80
81 static int  icon_width, icon_height;
82
83 /* Buffer stores the data for each cell. Each cell is stored as
84  * 8 bits representing the presence of a critter in each of it's
85  * surrounding 8 cells. There is an empty row and column around
86  * the whole array to allow stores without bounds checking as well
87  * as an extra row at the end for the fetches into tempbuf.
88  *
89  * Tempbuf stores the data for the next two rows so that we know
90  * the state of those critter before he was modified by the fate
91  * of the critters that have already been processed.
92  *
93  * Agebuf stores the age of each critter.
94  */
95
96 #define UPLT    0x01
97 #define UP      0x02
98 #define UPRT    0x04
99 #define LT      0x08
100 #define RT      0x10
101 #define DNLT    0x20
102 #define DN      0x40
103 #define DNRT    0x80
104
105 /* Fates is a lookup table for the fate of a critter. The 256
106  * entries represent the 256 possible combinations of the 8
107  * neighbor cells. Each entry is one of BIRTH (create a cell
108  * or leave one alive), SAME (leave the cell alive or dead),
109  * or DEATH (kill anything in the cell).
110  */
111 #define BIRTH   0
112 #define SAME    1
113 #define DEATH   2
114 static unsigned char fates[256];
115 static int  initialized = 0;
116
117 const int  patterns[][128] = {
118     {                           /* EIGHT */
119         -3, -3, -2, -3, -1, -3,
120         -3, -2, -2, -2, -1, -2,
121         -3, -1, -2, -1, -1, -1,
122         0, 0, 1, 0, 2, 0,
123         0, 1, 1, 1, 2, 1,
124         0, 2, 1, 2, 2, 2,
125         99
126     },
127     {                           /* PULSAR */
128         1, 1, 2, 1, 3, 1, 4, 1, 5, 1,
129         1, 2, 5, 2,
130         99
131     },
132     {                           /* BARBER */
133         -7, -7, -6, -7,
134         -7, -6, -5, -6,
135         -5, -4, -3, -4,
136         -3, -2, -1, -2,
137         -1, 0, 1, 0,
138         1, 2, 3, 2,
139         3, 4, 5, 4,
140         4, 5, 5, 5,
141         99
142     },
143     {                           /* HERTZ */
144         -2, -6, -1, -6,
145         -2, -5, -1, -5,
146         -7, -3, -6, -3, -2, -3, -1, -3, 0, -3, 1, -3, 5, -3, 6, -3,
147         -7, -2, -5, -2, -3, -2, 2, -2, 4, -2, 6, -2,
148         -5, -1, -3, -1, -2, -1, 2, -1, 4, -1,
149         -7, 0, -5, 0, -3, 0, 2, 0, 4, 0, 6, 0,
150         -7, 1, -6, 1, -2, 1, -1, 1, 0, 1, 1, 1, 5, 1, 6, 1,
151         -2, 3, -1, 3,
152         -2, 4, -1, 4,
153         99
154     },
155     {                           /* TUMBLER */
156         -6, -6, -5, -6, 6, -6, 7, -6,
157         -6, -5, -5, -5, 6, -5, 7, -5,
158         -5, 5, 6, 5,
159         -7, 6, -5, 6, 6, 6, 8, 6,
160         -7, 7, -5, 7, 6, 7, 8, 7,
161         -7, 8, -6, 8, 7, 8, 8, 8,
162         99
163     },
164     {                           /* PERIOD4 */
165         -5, -8, -4, -8,
166         -7, -7, -5, -7,
167         -8, -6, -2, -6,
168         -7, -5, -3, -5, -2, -5,
169         -5, -3, -3, -3,
170         -4, -2,
171         99
172     },
173     {                           /* PERIOD5 */
174         -5, -8, -4, -8,
175         -6, -7, -3, -7,
176         -7, -6, -2, -6,
177         -8, -5, -1, -5,
178         -8, -4, -1, -4,
179         -7, -3, -2, -3,
180         -6, -2, -3, -2,
181         -5, -1, -4, -1,
182         99
183     },
184     {                           /* PERIOD6 */
185         -4, -8, -3, -8,
186         -8, -7, -7, -7, -5, -7,
187         -8, -6, -7, -6, -4, -6, -1, -6,
188         -3, -5, -1, -5,
189         -2, -4,
190         -3, -2, -2, -2,
191         -3, -1, -2, -1,
192         99
193     },
194     {                           /* PINWHEEL */
195         -4, -8, -3, -8,
196         -4, -7, -3, -7,
197         -4, -5, -3, -5, -2, -5, -1, -5,
198         -5, -4, -3, -4, 0, -4, 2, -4, 3, -4,
199         -5, -3, -1, -3, 0, -3, 2, -3, 3, -3,
200         -8, -2, -7, -2, -5, -2, -2, -2, 0, -2,
201         -8, -1, -7, -1, -5, -1, 0, -1,
202         -4, 0, -3, 0, -2, 0, -1, 0,
203         -2, 2, -1, 2,
204         -2, 3, -1, 3,
205         99
206     },
207     {                           /* ] */
208         -1, -1, 0, -1, 1, -1,
209         0, 0, 1, 0,
210         -1, 1, 0, 1, 1, 1,
211         99
212     },
213     {                           /* cc: */
214         -3, -1, -2, -1, -1, -1, 1, -1, 2, -1, 3, -1,
215         -3, 0, -2, 0, 1, 0, 2, 0,
216         -3, 1, -2, 1, -1, 1, 1, 1, 2, 1, 3, 1,
217         99
218     },
219     {                           /* DOLBY */
220         -3, -1, -2, -1, -1, -1, 1, -1, 2, -1, 3, -1,
221         -3, 0, -2, 0, 2, 0, 3, 0,
222         -3, 1, -2, 1, -1, 1, 1, 1, 2, 1, 3, 1,
223         99
224     },
225     {                           /* HORIZON */
226         -15, 0, -14, 0, -13, 0, -12, 0, -11, 0,
227         -10, 0, -9, 0, -8, 0, -7, 0, -6, 0,
228         -5, 0, -4, 0, -3, 0, -2, 0, -1, 0,
229         4, 0, 3, 0, 2, 0, 1, 0, 0, 0,
230         9, 0, 8, 0, 7, 0, 6, 0, 5, 0,
231         14, 0, 13, 0, 12, 0, 11, 0, 10, 0,
232         99
233     },
234     {                           /* SHEAR */
235         -7, -2, -6, -2, -5, -2, -4, -2, -3, -2,
236         -2, -2, -1, -2, 0, -2, 1, -2, 2, -2,
237         -5, -1, -4, -1, -3, -1, -2, -1, -1, -1,
238         0, -1, 1, -1, 2, -1, 3, -1, 4, -1,
239         -3, 0, -2, 0, -1, 0, 0, 0, 1, 0,
240         2, 0, 3, 0, 4, 0, 5, 0, 6, 0,
241         -10, 1, -9, 1, -8, 1, -7, 1, -6, 1,
242         -5, 1, -4, 1, -3, 1, -2, 1, -1, 1,
243         -10, 2, -9, 2, -8, 2, -7, 2, -6, 2,
244         -5, 2, -4, 2, -3, 2, -2, 2, -1, 2,
245         99
246     },
247     {                           /* VERTIGO */
248         0, -7,
249         0, -6,
250         0, -5,
251         0, -4,
252         0, -3,
253         0, -2,
254         0, -1,
255         0, 0,
256         0, 7,
257         0, 6,
258         0, 5,
259         0, 4,
260         0, 3,
261         0, 2,
262         0, 1,
263         99
264     },
265     {                           /* CROSSBAR */
266         -5, 0, -4, 0, -3, 0, -2, 0, -1, 0, 4, 0, 3, 0, 2, 0, 1, 0, 0, 0,
267         99
268     },
269     {                           /* GOALPOSTS */
270         -8, -7, 8, -7,
271         -8, -6, 8, -6,
272         -8, -5, 8, -5,
273         -8, -4, 8, -4,
274         -8, -3, 8, -3,
275         -8, -2, 8, -2,
276         -8, -1, 8, -1,
277         -8, 0, 8, 0,
278         -8, 1, 8, 1,
279         -8, 2, 8, 2,
280         -8, 3, 8, 3,
281         -8, 4, 8, 4,
282         -8, 5, 8, 5,
283         -8, 6, 8, 6,
284         -8, 7, 8, 7,
285         99
286     },
287     {                           /* \ */
288         -8, -8, -7, -8,
289         -7, -7, -6, -7,
290         -6, -6, -5, -6,
291         -5, -5, -4, -5,
292         -4, -4, -3, -4,
293         -3, -3, -2, -3,
294         -2, -2, -1, -2,
295         -1, -1, 0, -1,
296         0, 0, 1, 0,
297         1, 1, 2, 1,
298         2, 2, 3, 2,
299         3, 3, 4, 3,
300         4, 4, 5, 4,
301         5, 5, 6, 5,
302         6, 6, 7, 6,
303         7, 7, 8, 7,
304         99
305     },
306     {                           /* LABYRINTH */
307         -4, -4, -3, -4, -2, -4, -1, -4, 0, -4, 1, -4, 2, -4, 3, -4, 4, -4,
308         -4, -3, 0, -3, 4, -3,
309         -4, -2, -2, -2, -1, -2, 0, -2, 1, -2, 2, -2, 4, -2,
310         -4, -1, -2, -1, 2, -1, 4, -1,
311         -4, 0, -2, 0, -1, 0, 0, 0, 1, 0, 2, 0, 4, 0,
312         -4, 1, -2, 1, 2, 1, 4, 1,
313         -4, 2, -2, 2, -1, 2, 0, 2, 1, 2, 2, 2, 4, 2,
314         -4, 3, 0, 3, 4, 3,
315         -4, 4, -3, 4, -2, 4, -1, 4, 0, 4, 1, 4, 2, 4, 3, 4, 4, 4,
316         99
317     }
318 };
319
320 #define NPATS   (sizeof patterns / sizeof patterns[0])
321
322
323 static void
324 drawcell(perwindow *pwin, int row, int col)
325 {
326     lifestruct *lp;
327
328     lp = (lifestruct *)pwin->data;
329     XSetForeground(dsp, pwin->gc, WhitePixelOfScreen(pwin->perscreen->screen));
330     if (!mono && pwin->perscreen->npixels > 2) {
331         unsigned char *loc = lp->buffer + ((row + 1) * (lp->ncols + 2)) + col + 1;
332         unsigned char *ageptr = lp->agebuf + (loc - lp->buffer);
333         unsigned char age = *ageptr;
334
335         /* if we aren't up to blue yet, then keep aging the cell. */
336         if (age < (unsigned char) (pwin->perscreen->npixels * 0.7))
337             ++age;
338
339         XSetForeground(dsp, pwin->gc, pwin->perscreen->pixels[age]);
340         *ageptr = age;
341     }
342     if (lp->pixelmode)
343         XFillRectangle(dsp, pwin->w, pwin->gc,
344                lp->xb + lp->xs * col, lp->yb + lp->ys * row, lp->xs, lp->ys);
345     else
346         XPutImage(dsp, pwin->w, pwin->gc, &logo,
347                   0, 0, lp->xb + lp->xs * col, lp->yb + lp->ys * row,
348                   icon_width, icon_height);
349 }
350
351
352 static void
353 erasecell(perwindow *pwin, int row, int col)
354 {
355     lifestruct *lp = (lifestruct *)pwin->data;
356     XSetForeground(dsp, pwin->gc, BlackPixelOfScreen(pwin->perscreen->screen));
357     XFillRectangle(dsp, pwin->w, pwin->gc,
358                lp->xb + lp->xs * col, lp->yb + lp->ys * row, lp->xs, lp->ys);
359 }
360
361
362 static void
363 spawn(perwindow *pwin, unsigned char *loc)
364 {
365     lifestruct *lp = (lifestruct *)pwin->data;
366     unsigned char *ulloc, *ucloc, *urloc, *clloc, *crloc, *llloc, *lcloc, *lrloc,
367                *arloc;
368     int         off, row, col, lastrow;
369
370     lastrow = (lp->nrows) * (lp->ncols + 2);
371     off = loc - lp->buffer;
372     col = off % (lp->ncols + 2);
373     row = (off - col) / (lp->ncols + 2);
374     ulloc = loc - lp->ncols - 3;
375     ucloc = loc - lp->ncols - 2;
376     urloc = loc - lp->ncols - 1;
377     clloc = loc - 1;
378     crloc = loc + 1;
379     arloc = loc + 1;
380     llloc = loc + lp->ncols + 1;
381     lcloc = loc + lp->ncols + 2;
382     lrloc = loc + lp->ncols + 3;
383     if (row == 1) {
384         ulloc += lastrow;
385         ucloc += lastrow;
386         urloc += lastrow;
387     }
388     if (row == lp->nrows) {
389         llloc -= lastrow;
390         lcloc -= lastrow;
391         lrloc -= lastrow;
392     }
393     if (col == 1) {
394         ulloc += lp->ncols;
395         clloc += lp->ncols;
396         llloc += lp->ncols;
397     }
398     if (col == lp->ncols) {
399         urloc -= lp->ncols;
400         crloc -= lp->ncols;
401         lrloc -= lp->ncols;
402     }
403     *ulloc |= UPLT;
404     *ucloc |= UP;
405     *urloc |= UPRT;
406     *clloc |= LT;
407     *crloc |= RT;
408     *arloc |= RT;
409     *llloc |= DNLT;
410     *lcloc |= DN;
411     *lrloc |= DNRT;
412
413     *(lp->agebuf + (loc - lp->buffer)) = 0;
414 }
415
416
417 static void
418 life_kill(perwindow *pwin, unsigned char *loc)
419 {
420     lifestruct *lp = (lifestruct *)pwin->data;
421
422     unsigned char *ulloc, *ucloc, *urloc, *clloc, *crloc, *llloc, *lcloc,
423                *lrloc, *arloc;
424     int         off, row, col, lastrow;
425
426     lastrow = (lp->nrows) * (lp->ncols + 2);
427     off = loc - lp->buffer;
428     row = off / (lp->ncols + 2);
429     col = off % (lp->ncols + 2);
430     row = (off - col) / (lp->ncols + 2);
431     ulloc = loc - lp->ncols - 3;
432     ucloc = loc - lp->ncols - 2;
433     urloc = loc - lp->ncols - 1;
434     clloc = loc - 1;
435     crloc = loc + 1;
436     arloc = loc + 1;
437     llloc = loc + lp->ncols + 1;
438     lcloc = loc + lp->ncols + 2;
439     lrloc = loc + lp->ncols + 3;
440     if (row == 1) {
441         ulloc += lastrow;
442         ucloc += lastrow;
443         urloc += lastrow;
444     }
445     if (row == lp->nrows) {
446         llloc -= lastrow;
447         lcloc -= lastrow;
448         lrloc -= lastrow;
449     }
450     if (col == 1) {
451         ulloc += lp->ncols;
452         clloc += lp->ncols;
453         llloc += lp->ncols;
454     }
455     if (col == lp->ncols) {
456         urloc -= lp->ncols;
457         crloc -= lp->ncols;
458         lrloc -= lp->ncols;
459     }
460     *ulloc &= ~UPLT;
461     *ucloc &= ~UP;
462     *urloc &= ~UPRT;
463     *clloc &= ~LT;
464     *crloc &= ~RT;
465     *arloc &= ~RT;
466     *llloc &= ~DNLT;
467     *lcloc &= ~DN;
468     *lrloc &= ~DNRT;
469 }
470
471
472 static void
473 setcell(perwindow *pwin, int row, int col)
474 {
475     lifestruct *lp = (lifestruct *)pwin->data;
476     unsigned char *loc;
477
478     loc = lp->buffer + ((row + 1) * (lp->ncols + 2)) + col + 1;
479     spawn(pwin, loc);
480     drawcell(pwin, row, col);
481 }
482
483
484 static void
485 init_fates(void)
486 {
487     int         i, bits, neighbors;
488
489     for (i = 0; i < 256; i++) {
490         neighbors = 0;
491         for (bits = i; bits; bits &= (bits - 1))
492             neighbors++;
493         if (neighbors == 3)
494             fates[i] = BIRTH;
495         else if (neighbors == 2)
496             fates[i] = SAME;
497         else
498             fates[i] = DEATH;
499     }
500 }
501
502
503 void
504 initlife(perwindow *pwin)
505 {
506     int         row, col;
507     int        *patptr;
508     XWindowAttributes xgwa;
509     lifestruct *lp;
510
511     if (pwin->data) free(pwin->data);
512     pwin->data = (void *)malloc(sizeof(lifestruct));
513     memset(pwin->data, '\0', sizeof(lifestruct));
514     lp = (lifestruct *)pwin->data;
515     lp->generation = 0;
516     lp->shooterTime = seconds();
517     icon_width = lifeicon_width;
518     icon_height = lifeicon_height;
519
520     if (!initialized) {
521         initialized = 1;
522         init_fates();
523         logo.data = (char *) lifeicon_bits;
524         logo.width = icon_width;
525         logo.height = icon_height;
526         logo.bytes_per_line = (icon_width + 7) / 8;
527     }
528     XGetWindowAttributes(dsp, pwin->w, &xgwa);
529     lp->width = xgwa.width;
530     lp->height = xgwa.height;
531     lp->pixelmode = (lp->width < 4 * icon_width);
532     if (lp->pixelmode) {
533         lp->ncols = 32;
534         lp->nrows = 32;
535     } else {
536         lp->ncols = min(lp->width / icon_width, MAXCOLS);
537         lp->nrows = min(lp->height / icon_height, MAXROWS);
538     }
539 /* For the dtstyle preview screen, the rows and columns can 
540  * be less than 32 regardless of the pixelmode calculation.
541  * This can cause the row/column calculations below to go 
542  * negative, which causes very bad things to happen. Until we
543  * get an official fix, this will keep life from core dumping.
544  */
545     if ((lp->ncols < 32) || (lp->nrows < 32)) {
546         lp->pixelmode = 1;
547         lp->ncols = 32;
548         lp->nrows = 32;
549     }
550     lp->xs = lp->width / lp->ncols;
551     lp->ys = lp->height / lp->nrows;
552     lp->xb = (lp->width - lp->xs * lp->ncols) / 2;
553     lp->yb = (lp->height - lp->ys * lp->nrows) / 2;
554
555     XSetForeground(dsp, pwin->gc, BlackPixelOfScreen(pwin->perscreen->screen));
556     XFillRectangle(dsp, pwin->w, pwin->gc, 0, 0, lp->width, lp->height);
557
558     memset(lp->buffer, '\0', sizeof(lp->buffer));
559     patptr = (int *)&patterns[random() % NPATS][0];
560     while ((col = *patptr++) != 99) {
561         row = *patptr++;
562         col += lp->ncols / 2;
563         row += lp->nrows / 2;
564         if ((row >= 0) && (row < lp->nrows) && (col >= 0) && (col < lp->ncols))
565             setcell(pwin, row, col);
566     }
567 }
568
569
570 void
571 drawlife(perwindow *pwin)
572 {
573     unsigned char *loc, *temploc, *lastloc;
574     int         row, col;
575     unsigned char fate;
576     lifestruct *lp = (lifestruct *)pwin->data;
577
578     loc = lp->buffer + lp->ncols + 2 + 1;
579     temploc = lp->tempbuf;
580     /* copy the first 2 rows to the tempbuf */
581     memcpy(temploc, loc, lp->ncols);
582     memcpy(temploc + lp->ncols, loc + lp->ncols + 2, lp->ncols);
583
584     lastloc = lp->lastbuf;
585     /* copy the last row to another buffer for wraparound */
586     memcpy(lastloc, loc + ((lp->nrows - 1) * (lp->ncols + 2)), lp->ncols);
587
588     for (row = 0; row < lp->nrows; ++row) {
589         for (col = 0; col < lp->ncols; ++col) {
590             fate = fates[*temploc];
591             *temploc = (row == (lp->nrows - 3)) ?
592                 *(lastloc + col) :
593                 *(loc + (lp->ncols + 2) * 2);
594             switch (fate) {
595             case BIRTH:
596                 if (!(*(loc + 1) & RT)) {
597                     spawn(pwin, loc);
598                 }
599                 /* NO BREAK */
600             case SAME:
601                 if (*(loc + 1) & RT) {
602                     drawcell(pwin, row, col);
603                 }
604                 break;
605             case DEATH:
606                 if (*(loc + 1) & RT) {
607                     life_kill(pwin, loc);
608                     erasecell(pwin, row, col);
609                 }
610                 break;
611             }
612             loc++;
613             temploc++;
614         }
615         loc += 2;
616         if (temploc >= lp->tempbuf + lp->ncols * 2)
617             temploc = lp->tempbuf;
618     }
619
620     if (++lp->generation > batchcount)
621         initlife(pwin);
622
623     /*
624      * generate a randomized shooter aimed roughly toward the center of the
625      * screen after timeout.
626      */
627
628     if (seconds() - lp->shooterTime > TIMEOUT) {
629         int         hsp = random() % (lp->ncols - 5) + 3;
630         int         vsp = random() % (lp->nrows - 5) + 3;
631         int         hoff = 1;
632         int         voff = 1;
633         if (vsp > lp->nrows / 2)
634             voff = -1;
635         if (hsp > lp->ncols / 2)
636             hoff = -1;
637         setcell(pwin, vsp + 0 * voff, hsp + 2 * hoff);
638         setcell(pwin, vsp + 1 * voff, hsp + 2 * hoff);
639         setcell(pwin, vsp + 2 * voff, hsp + 2 * hoff);
640         setcell(pwin, vsp + 2 * voff, hsp + 1 * hoff);
641         setcell(pwin, vsp + 1 * voff, hsp + 0 * hoff);
642         lp->shooterTime = seconds();
643     }
644 }