1 /*****************************************************************************
2  *                                                                           *
3  *                         B l u e   M o o n                                 *
4  *                         =================                                 *
5  *                               V2.2                                        *
6  *                   A patience game by T.A.Lister                           *
7  *            Integral screen support by Eric S. Raymond                     *
8  *                                                                           *
9  *****************************************************************************/
10 
11 /*
12  * Compile this with the command `cc -O blue.c -lcurses -o blue'. For best
13  * results, use the portable freeware ncurses(3) library.  On non-Intel
14  * machines, SVr4 curses is just as good.
15  */
16 
17 #include <stdlib.h>
18 #include <string.h>
19 #include <unistd.h>
20 #include <signal.h>
21 #include <time.h>
22 
23 #if HAVE_TERMIOS_H
24 #include <sys/termios.h>
25 #endif
26 
27 #include <curses.h>
28 #ifndef SLANG
29 #include <term.h>
30 #endif
31 
32 #define NOCARD		(-1)
33 
34 #define ACE		0
35 #define KING		12
36 #define SUIT_LENGTH	13
37 
38 #define HEARTS		0
39 #define SPADES		1
40 #define DIAMONDS	2
41 #define CLUBS		3
42 #define NSUITS		4
43 
44 #define GRID_WIDTH	14	/*    13+1  */
45 #define GRID_LENGTH	56	/* 4*(13+1) */
46 #define PACK_SIZE	52
47 
48 #define BASEROW		1
49 #define PROMPTROW	11
50 
51 static int deck_size = PACK_SIZE;	/* initial deck */
52 static int deck[PACK_SIZE];
53 
54 static int grid[GRID_LENGTH];	/* card layout grid */
55 static int freeptr[4];		/* free card space pointers */
56 
57 static int deal_number=0;
58 
59 static chtype ranks[SUIT_LENGTH][2] =
60 {
61     {' ', 'A'},
62     {' ', '2'},
63     {' ', '3'},
64     {' ', '4'},
65     {' ', '5'},
66     {' ', '6'},
67     {' ', '7'},
68     {' ', '8'},
69     {' ', '9'},
70     {'1', '0'},
71     {' ', 'J'},
72     {' ', 'Q'},
73     {' ', 'K'}
74 };
75 
76 static chtype letters[] =
77 {
78     'h' | COLOR_PAIR(COLOR_RED),	/* hearts */
79     's' | COLOR_PAIR(COLOR_GREEN),	/* spades */
80     'd' | COLOR_PAIR(COLOR_RED),	/* diamonds */
81     'c' | COLOR_PAIR(COLOR_GREEN),	/* clubs */
82 };
83 
84 #if defined(__i386__)
85 static chtype glyphs[] =
86 {
87     '\003' | A_ALTCHARSET | COLOR_PAIR(COLOR_RED),	/* hearts */
88     '\006' | A_ALTCHARSET | COLOR_PAIR(COLOR_GREEN),	/* spades */
89     '\004' | A_ALTCHARSET | COLOR_PAIR(COLOR_RED),	/* diamonds */
90     '\005' | A_ALTCHARSET | COLOR_PAIR(COLOR_GREEN),	/* clubs */
91 };
92 #endif /* __i386__ */
93 
94 static chtype *suits = letters;	/* this may change to glyphs below */
95 
die(int onsig)96 static void die(int onsig)
97 {
98     signal(onsig, SIG_IGN);
99     endwin();
100     exit(0);
101 }
102 
init_vars(void)103 static void init_vars(void)
104 {
105     int i;
106 
107     deck_size = PACK_SIZE;
108     for (i=0; i < PACK_SIZE; i++)
109 	deck[i]=i;
110     for (i = 0; i < 4; i++)
111 	freeptr[i]=i * GRID_WIDTH;
112 }
113 
shuffle(int size)114 static void shuffle(int size)
115 {
116     int i,j,numswaps,swapnum,temp;
117 
118     numswaps=size*10;		/* an arbitrary figure */
119 
120     for (swapnum=0;swapnum<numswaps;swapnum++)
121     {
122 	i=rand() % size;
123 	j=rand() % size;
124 	temp=deck[i];
125 	deck[i]=deck[j];
126 	deck[j]=temp;
127     }
128 }
129 
deal_cards(void)130 static void deal_cards(void)
131 {
132     int ptr, card=0, value, csuit, crank, suit, aces[4];
133 
134     for (suit=HEARTS;suit<=CLUBS;suit++)
135     {
136 	ptr=freeptr[suit];
137 	grid[ptr++]=NOCARD;	/* 1st card space is blank */
138 	while ((ptr % GRID_WIDTH) != 0)
139 	{
140 	    value=deck[card++];
141 	    crank=value % SUIT_LENGTH;
142 	    csuit=value / SUIT_LENGTH;
143 	    if (crank==ACE)
144 		aces[csuit]=ptr;
145 	    grid[ptr++]=value;
146 	}
147     }
148 
149     if (deal_number==1)		/* shift the aces down to the 1st column */
150 	for (suit=HEARTS;suit<=CLUBS;suit++)
151 	{
152 	    grid[suit * GRID_WIDTH] = suit * SUIT_LENGTH;
153 	    grid[aces[suit]]=NOCARD;
154 	    freeptr[suit]=aces[suit];
155 	}
156 }
157 
printcard(int value)158 static void printcard(int value)
159 {
160     (void) addch(' ');
161     if (value == NOCARD)
162 	(void) addstr("   ");
163     else
164     {
165 	addch(ranks[value % SUIT_LENGTH][0] | COLOR_PAIR(COLOR_BLUE));
166 	addch(ranks[value % SUIT_LENGTH][1] | COLOR_PAIR(COLOR_BLUE));
167 	addch(suits[value / SUIT_LENGTH]);
168     }
169     (void) addch(' ');
170 }
171 
display_cards(int deal)172 static void display_cards(int deal)
173 {
174     int row, card;
175 
176     clear();
177     (void)printw(
178 		 "Blue Moon 2.1 - by Tim Lister & Eric Raymond - Deal %d.\n",
179 		 deal);
180     for(row=HEARTS;row<=CLUBS;row++)
181     {
182 	move(BASEROW + row + row + 2, 1);
183 	for(card=0;card<GRID_WIDTH;card++)
184 	    printcard(grid[row * GRID_WIDTH + card]);
185     }
186 
187     move(PROMPTROW + 2, 0); refresh();
188 #define P(x)	(void)printw("%s\n", x)
189 P("   This 52-card solitaire starts with  the entire deck shuffled and dealt");
190 P("out in four rows.  The aces are then moved to the left end of the layout,");
191 P("making 4 initial free spaces.  You may move to a space only the card that");
192 P("matches the left neighbor in suit, and is one greater in rank.  Kings are");
193 P("high, so no cards may be placed to their right (they create dead spaces).");
194 P("  When no moves can be made,  cards still out of sequence are  reshuffled");
195 P("and dealt face up after the ends of the partial sequences, leaving a card");
196 P("space after each sequence, so that each row looks like a partial sequence");
197 P("followed by a space, followed by enough cards to make a row of 14.       ");
198 P("  A moment's reflection will show that this game cannot take more than 13");
199 P("deals. A good score is 1-3 deals, 4-7 is average, 8 or more is poor.     ");
200 #undef P
201     refresh();
202 }
203 
find(int card)204 static int find(int card)
205 {
206     int i;
207 
208     if ((card<0) || (card>=PACK_SIZE))
209 	return(NOCARD);
210     for(i = 0; i < GRID_LENGTH; i++)
211 	if (grid[i] == card)
212 	    return i;
213     return(NOCARD);
214 }
215 
movecard(int src,int dst)216 static void movecard(int src, int dst)
217 {
218     grid[dst]=grid[src];
219     grid[src]=NOCARD;
220 
221     move( BASEROW + (dst / GRID_WIDTH)*2+2, (dst % GRID_WIDTH)*5 + 1);
222     printcard(grid[dst]);
223 
224     move( BASEROW + (src / GRID_WIDTH)*2+2, (src % GRID_WIDTH)*5 + 1);
225     printcard(grid[src]);
226 
227     refresh();
228 }
229 
play_game(void)230 static void play_game(void)
231 {
232     int dead=0, i, j;
233     char c;
234     int select[4], card;
235 
236     while (dead<4)
237     {
238 	dead=0;
239 	for (i=0;i<4;i++)
240 	{
241 	    card=grid[freeptr[i]-1];
242 
243 	    if (	((card % SUIT_LENGTH)==KING)
244 		||
245 		(card==NOCARD)	)
246 		select[i]=NOCARD;
247 	    else
248 		select[i]=find(card+1);
249 
250 	    if (select[i]==NOCARD)
251 		dead++;
252 	};
253 
254 	if (dead < 4)
255 	{
256 	    char	live[NSUITS+1], *lp = live;
257 
258 	    for (i=0;i<4;i++)
259 	    {
260 		if (select[i] != NOCARD)
261 		{
262 		    move(BASEROW + (select[i] / GRID_WIDTH)*2+3,
263 			 (select[i] % GRID_WIDTH)*5);
264 		    (void)printw("   %c ", *lp++ = 'a' + i);
265 		}
266 	    };
267 	    *lp = '\0';
268 
269 	    if (strlen(live) == 1)
270 	    {
271 		move(PROMPTROW,0);
272 		(void)printw(
273 		    "Making forced moves...                                 ");
274 		refresh();
275 		(void) sleep(1);
276 		c = live[0];
277 	    }
278 	    else
279 	    {
280 		char	buf[BUFSIZ];
281 
282 		(void)sprintf(buf,
283 			"Type [%s] to move, r to redraw, q or INTR to quit: ",
284 			live);
285 
286 		do {
287 		    move(PROMPTROW,0);
288 		    (void) addstr(buf);
289 		    move(PROMPTROW, (int)strlen(buf));
290 		    clrtoeol();
291 		    (void) addch(' ');
292 		} while
293 		    (((c = getch())<'a' || c>'d') && (c!='r') && (c!='q'));
294 	    }
295 
296 	    for (j = 0; j < 4; j++)
297 		if (select[j]!=NOCARD)
298 		{
299 		    move(BASEROW + (select[j] / GRID_WIDTH)*2+3,
300 			 (select[j] % GRID_WIDTH)*5);
301 		    (void)printw("     ");
302 		}
303 
304 	    if (c == 'r')
305 		display_cards(deal_number);
306 	    else if (c == 'q')
307 		die(SIGINT);
308 	    else
309 	    {
310 		i = c-'a';
311 		if (select[i] == NOCARD)
312 		    beep();
313 		else
314 		{
315 		    movecard(select[i], freeptr[i]);
316 		    freeptr[i]=select[i];
317 		}
318 	    }
319 	}
320     }
321 
322     move(PROMPTROW, 0);
323     standout();
324     (void)printw("Finished deal %d - type any character to continue...", deal_number);
325     standend();
326     (void) getch();
327 }
328 
collect_discards(void)329 static int collect_discards(void)
330 {
331     int row, col, cardno=0, finish, gridno;
332 
333     for (row=HEARTS;row<=CLUBS;row++)
334     {
335 	finish=0;
336 	for (col=1;col<GRID_WIDTH;col++)
337 	{
338 	    gridno=row * GRID_WIDTH + col;
339 
340 	    if ((grid[gridno]!=(grid[gridno-1]+1))&&(finish==0))
341 	    {
342 		finish=1;
343 		freeptr[row]=gridno;
344 	    };
345 
346 	    if ((finish!=0)&&(grid[gridno]!=NOCARD))
347 		deck[cardno++]=grid[gridno];
348 	}
349     }
350     return cardno;
351 }
352 
game_finished(int deal)353 static void game_finished(int deal)
354 {
355     clear();
356     (void)printw("You finished the game in %d deals. This is ",deal);
357     standout();
358     if (deal<2)
359 	(void)addstr("excellent");
360     else if (deal<4)
361 	(void)addstr("good");
362     else if (deal<8)
363 	(void)addstr("average");
364     else
365 	(void)addstr("poor");
366     standend();
367     (void) addstr(".         ");
368     refresh();
369 }
370 
main(int argc,char * argv[])371 int main(int argc, char *argv[])
372 {
373     (void) signal(SIGINT, die);
374     initscr();
375 
376     /*
377      * We use COLOR_GREEN because COLOR_BLACK is wired to the wrong thing.
378      */
379     start_color();
380     init_pair(COLOR_RED,     COLOR_RED,   COLOR_WHITE);
381     init_pair(COLOR_BLUE,    COLOR_BLUE,  COLOR_WHITE);
382     init_pair(COLOR_GREEN,   COLOR_BLACK, COLOR_WHITE);
383 
384 #if defined(__i386__) && defined(A_ALTCHARSET)
385     if (tigetstr("smpch"))
386 	suits = glyphs;
387 #endif /* __i386__ && A_ALTCHARSET */
388 
389     cbreak();
390 
391     if (argc == 2)
392 	srand((unsigned)atoi(argv[1]));
393     else
394 	srand((unsigned)time((time_t *)0));
395 
396     init_vars();
397 
398     do{
399 	deal_number++;
400 	shuffle(deck_size);
401 	deal_cards();
402 	display_cards(deal_number);
403 	play_game();
404     }
405     while
406 	((deck_size=collect_discards()) != 0);
407 
408     game_finished(deal_number);
409 
410     die(SIGINT);
411     /*NOTREACHED*/
412    return 1;
413 }
414 
415 /* blue.c ends here */
416