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