A card solitaire game for the TI-84 Plus/83 Premium CE graphing calculators.
1#include "drawing.h"
2#include <graphx.h>
3#include <time.h>
4#include "card.h"
5#include "state.h"
6
7#include <debug.h>
8
9#define COLOR_CARD 0
10#define COLOR_BLACK 1
11#define COLOR_TABLE 2
12#define COLOR_RED 3
13#define COLOR_GREEN 4
14#define COLOR_BLUE 5
15#define COLOR_ORANGE 6
16#define COLOR_DIM 0x80
17
18#define CARD_WIDTH 25
19#define CARD_HEIGHT 51
20#define CARD_SPACING_X 4
21
22#define CARD_NUMERAL_DX 1
23#define CARD_NUMERAL_DY 1
24#define CARD_SUIT_DX 6
25#define CARD_SUIT_DY 1
26
27#define TABLEAU_DX (CARD_WIDTH + CARD_SPACING_X)
28#define TABLEAU_WIDTH ((TABLEAU_NUM_PILES * TABLEAU_DX) - CARD_SPACING_X)
29#define TABLEAU_MARGIN ((GFX_LCD_WIDTH - TABLEAU_WIDTH) / 2)
30#define TABLEAU_X0 TABLEAU_MARGIN
31#define TABLEAU_Y0 (GFX_LCD_HEIGHT / 3)
32#define TABLEAU_DY 8
33
34#define FOUNDATIONS_DX TABLEAU_DX
35#define FOUNDATIONS_X0 (GFX_LCD_WIDTH - FOUNDATIONS_DX * 4 - TABLEAU_MARGIN)
36#define FOUNDATIONS_Y (TABLEAU_Y0 - CARD_HEIGHT - CARD_SPACING_X * 2)
37#define DIM_AMOUNT 128
38
39#define TRUMPS_X0 (TABLEAU_MARGIN + CARD_SPACING_X)
40#define TRUMPS_DX CARD_SPACING_X
41#define TRUMPS_X1 (TRUMPS_X0 + (TRUMPS_DX * CARD_NUM_TRUMPS) + CARD_WIDTH)
42#define TRUMPS_Y FOUNDATIONS_Y
43
44#define HELD_CARD_X ((GFX_LCD_WIDTH - CARD_WIDTH) / 2)
45#define HELD_CARD_Y (GFX_LCD_HEIGHT - CARD_HEIGHT)
46
47#define MOVE_ANIM_LENGTH 40
48#define DEAL_ANIM_DECK_X (TABLEAU_X0 + TABLEAU_DX * 5)
49#define DEAL_ANIM_DECK_Y TABLEAU_Y0
50#define DEAL_ANIM_TOTAL_TIME 100000
51#define DEAL_ANIM_TRAVEL_TIME 6000
52#define DEAL_ANIM_LAUNCH_INTERVAL ((DEAL_ANIM_TOTAL_TIME - DEAL_ANIM_TRAVEL_TIME) / NUM_CARDS)
53
54#define DRAW_MODE 0x00
55#define DRAW_MODE_PARTIAL 0x01
56#define DRAW_MODE_DIMMABLE 0x02
57
58#include <time.h>
59
60// sprite references
61#include "gfx/gfx.h"
62const unsigned char *numerals_data[] = {
63 _1_data, _2_data, _3_data, _4_data, _5_data, _6_data, _7_data,
64 _8_data, _9_data, _10_data, J_data, Q_data, K_data
65};
66const unsigned char *small_suits_data[] = {
67 clubs_data, swords_data, cups_data, coins_data
68};
69const unsigned char *large_suits_data[] = {
70 large_clubs_data, large_swords_data, large_cups_data, large_coins_data
71};
72
73// black magic for placing pips on cards
74const unsigned char segments[] = {
75 0x01, 0x24, 0x25, 0x48, 0x49, 0x4a, 0xa8, 0x90, 0x91, 0xb4
76};
77const unsigned char pip_code[] = {
78 0x04, 06, 07, 14, 07, 06, 17, 14, 17,
79 0x02, 06, 12, 14, 12,
80 0x01, 10, 12,
81 0x84, 11, 34, 19, 34, 11, 44, 19, 44,
82 0x82, 11, 39, 19, 39,
83 0x81, 15, 39,
84 0x02, 06, 22, 14, 22,
85 0x01, 10, 22
86};
87
88// roman numeral display widths
89const unsigned char roman_widths[] = {
90 3, 5, 7, 7, 5, 7, 9, 11, 7, 5, 7, // i - xi
91 9, 11, 11, 9, 11, 13, 15, 11, 9, 11, 13 // xii - xxii
92};
93
94void draw_setup()
95{
96 gfx_Begin();
97 gfx_SetDrawBuffer();
98 gfx_SetPalette(global_palette, sizeof_global_palette, 0);
99 gfx_SetPalette(global_palette, sizeof_global_palette, COLOR_DIM);
100}
101
102void darken_dim()
103{
104 for (unsigned char i = COLOR_DIM; i < (sizeof_global_palette | COLOR_DIM); i++)
105 {
106 gfx_palette[i] = gfx_Darken(gfx_palette[i], DIM_AMOUNT);
107 }
108}
109
110void restore_dim()
111{
112 gfx_SetPalette(gfx_palette, sizeof_global_palette, COLOR_DIM);
113}
114
115void draw_mask(const unsigned char *data,
116 unsigned char num_rows, unsigned int x, unsigned char y)
117{
118 unsigned char yy = y;
119
120 for (unsigned char row = 0; row < num_rows; row++)
121 {
122 unsigned char row_data = data[row];
123
124 for (unsigned int xx = x; xx < x + 8; xx++)
125 {
126 if (row_data & 0x80) gfx_SetPixel(xx, yy);
127
128 row_data <<= 1;
129 }
130
131 yy++;
132 }
133}
134
135void draw_mask_inverted(const unsigned char *data,
136 unsigned char num_rows, unsigned int x, unsigned char y)
137{
138 unsigned char yy = y - num_rows;
139
140 for (unsigned char row = 1; row <= num_rows; row++)
141 {
142 unsigned char row_data = data[num_rows - row];
143
144 for (unsigned int xx = x - 8; xx < x; xx++)
145 {
146 if (row_data & 0x01) gfx_SetPixel(xx, yy);
147
148 row_data >>= 1;
149 }
150
151 yy++;
152 }
153}
154
155void draw_card(const unsigned int x, const unsigned char y, card_t card, unsigned char draw_mode)
156{
157 // check clipping
158 if (x > GFX_LCD_WIDTH - CARD_WIDTH || y > GFX_LCD_HEIGHT - CARD_HEIGHT) return;
159
160 // draw blank card
161 if (draw_mode & DRAW_MODE_DIMMABLE) gfx_SetColor(COLOR_CARD | COLOR_DIM);
162 else gfx_SetColor(COLOR_CARD);
163 gfx_HorizLine_NoClip(x + 1, y, CARD_WIDTH - 2);
164 gfx_FillRectangle_NoClip(x, y + 1, CARD_WIDTH, CARD_HEIGHT - 1);
165
166 // draw card border
167 gfx_SetColor(COLOR_BLACK);
168 gfx_SetPixel(x, y);
169 gfx_SetPixel(x + CARD_WIDTH - 1, y);
170 gfx_SetPixel(x, y + CARD_HEIGHT - 1);
171 gfx_SetPixel(x + CARD_WIDTH - 1, y + CARD_HEIGHT - 1);
172 gfx_HorizLine_NoClip(x + 1, y - 1, CARD_WIDTH - 2);
173 gfx_HorizLine_NoClip(x + 1, y + CARD_HEIGHT, CARD_WIDTH - 2);
174
175 if (card_is_trump(card))
176 {
177 // draw card number as roman numeral
178 unsigned char remaining_value = card_get_value(card) + 1;
179 unsigned int xx = x + (CARD_WIDTH - roman_widths[remaining_value - 1]) / 2;
180
181 do {
182 if (remaining_value >= 10) {
183 draw_mask(x_data, 5, xx, y + CARD_NUMERAL_DY);
184 xx += 4;
185 remaining_value -= 10;
186 }
187 else if (remaining_value >= 9) {
188 draw_mask(i_data, 5, xx, y + CARD_NUMERAL_DY);
189 draw_mask(x_data, 5, xx + 2, y + CARD_NUMERAL_DY);
190 xx += 6;
191 remaining_value -= 9;
192 }
193 else if (remaining_value >= 5) {
194 draw_mask(v_data, 5, xx, y + CARD_NUMERAL_DY);
195 xx += 4;
196 remaining_value -= 5;
197 }
198 else if (remaining_value >= 4) {
199 draw_mask(i_data, 5, xx, y + CARD_NUMERAL_DY);
200 draw_mask(v_data, 5, xx + 2, y + CARD_NUMERAL_DY);
201 xx += 6;
202 remaining_value -= 4;
203 }
204 else {
205 draw_mask(i_data, 5, xx, y + CARD_NUMERAL_DY);
206 xx += 2;
207 remaining_value -= 1;
208 }
209 } while (remaining_value > 0);
210 }
211 else
212 {
213 // draw card number and suit
214 if (draw_mode & DRAW_MODE_DIMMABLE)
215 gfx_SetColor((card_get_suit(card) + COLOR_RED) | COLOR_DIM);
216 else gfx_SetColor(card_get_suit(card) + COLOR_RED);
217 draw_mask(numerals_data[card_get_value(card)], 5,
218 x + CARD_NUMERAL_DX, y + CARD_NUMERAL_DY);
219 draw_mask(small_suits_data[card_get_suit(card)], 5,
220 x + CARD_SUIT_DX, y + CARD_SUIT_DY);
221 draw_mask_inverted(numerals_data[card_get_value(card)], 5,
222 x + CARD_WIDTH - CARD_NUMERAL_DX, y + CARD_HEIGHT - CARD_NUMERAL_DY);
223 draw_mask_inverted(small_suits_data[card_get_suit(card)], 5,
224 x + CARD_WIDTH - CARD_SUIT_DX, y + CARD_HEIGHT - CARD_SUIT_DY);
225 }
226
227 if (draw_mode & DRAW_MODE_PARTIAL) return;
228
229 if (card_is_face(card) || card_is_trump(card)) return; // TODO
230
231 // otherwise, draw pips
232 const unsigned char *pip_mask = large_suits_data[card_get_suit(card)];
233 const unsigned char *code_ptr = pip_code;
234 for (unsigned char pip_map = segments[card_get_value(card)];
235 pip_map != 0x00; pip_map <<= 1)
236 {
237 unsigned char counter = *code_ptr & 0x0f;
238 if (pip_map & 0x80)
239 {
240 const bool is_negative = *code_ptr & 0x80;
241 code_ptr++;
242
243 while (counter-- > 0)
244 {
245 if (is_negative) draw_mask_inverted(pip_mask, 6,
246 x + code_ptr[0], y + code_ptr[1]);
247 else draw_mask(pip_mask, 6, x + code_ptr[0], y + code_ptr[1]);
248
249 code_ptr += 2;
250 }
251 }
252 else
253 {
254 code_ptr += counter * 2 + 1;
255 }
256 }
257}
258
259void draw_frame()
260{
261 gfx_FillScreen(COLOR_TABLE);
262
263 // draw tableau
264 unsigned int x = TABLEAU_X0;
265 for (unsigned char i = 0; i < TABLEAU_NUM_PILES; i++)
266 {
267 unsigned char y = TABLEAU_Y0;
268 unsigned char j;
269
270 for (j = 0; j < TABLEAU_PILE_SIZE; j++)
271 {
272 if (!card_exists(tableau[i][j])) break;
273
274 draw_card(x, y, tableau[i][j], card_exists(tableau[i][j + 1]));
275
276 y += TABLEAU_DY;
277 }
278
279 if (cursor_stack == i)
280 {
281 if (j > 0) y -= TABLEAU_DY;
282
283 if (card_exists(held_card))
284 {
285 if (j > 0) y += TABLEAU_DY;
286 gfx_SetColor(COLOR_RED);
287 } else {
288 gfx_SetColor(COLOR_BLACK);
289 }
290 draw_mask(selcorner_left_data, 6,
291 x - 2, y - 2);
292 draw_mask(selcorner_right_data, 6,
293 x + CARD_WIDTH - 4, y - 2);
294 draw_mask_inverted(selcorner_right_data, 6,
295 x + 4, y + CARD_HEIGHT + 2);
296 draw_mask_inverted(selcorner_left_data, 6,
297 x + CARD_WIDTH + 2, y + CARD_HEIGHT + 2);
298 }
299
300 x += TABLEAU_DX;
301 }
302
303 // draw foundations
304 x = FOUNDATIONS_X0;
305 for (unsigned char i = 0; i < FOUNDATIONS_NUM; i++)
306 {
307 draw_card(x, FOUNDATIONS_Y, foundations[i], DRAW_MODE_DIMMABLE);
308 x += FOUNDATIONS_DX;
309 }
310
311 // draw trumps
312 x = TRUMPS_X0;
313 for (unsigned char i = 0; i < next_low_trump; i++)
314 {
315 draw_card(x, TRUMPS_Y, CARD_SUIT_TRUMP | i, 0);
316 x += TRUMPS_DX;
317 }
318 x = TRUMPS_X1;
319 for (unsigned char i = CARD_NUM_TRUMPS - 1; i > next_high_trump; i--)
320 {
321 draw_card(x, TRUMPS_Y, CARD_SUIT_TRUMP | i, 0);
322 x -= TRUMPS_DX;
323 }
324
325 // draw held card
326 if (card_exists(held_card))
327 {
328 draw_card(HELD_CARD_X, HELD_CARD_Y, held_card, 0);
329 }
330
331 gfx_BlitBuffer();
332}
333
334void move_card(unsigned int x0, unsigned char y0, unsigned int x1,
335 unsigned char y1, card_t card, unsigned char no_redraw)
336{
337 if (y0 == 0)
338 {
339 x0 = x1;
340 y0 = y1;
341 }
342
343 gfx_TempSprite(sprite_buffer, CARD_WIDTH, CARD_HEIGHT + 2);
344 if (!no_redraw) gfx_GetSprite(sprite_buffer, x1, y1 - 1);
345
346 draw_card(x1, y1, card, 0);
347
348 gfx_BlitRectangle(gfx_buffer, x0, y0 - 1, CARD_WIDTH, CARD_HEIGHT + 2);
349 gfx_BlitRectangle(gfx_buffer, x1, y1 - 1, CARD_WIDTH, CARD_HEIGHT + 2);
350
351 if (!no_redraw) gfx_Sprite(sprite_buffer, x1, y1 - 1);
352}
353
354void animate_move(unsigned int x0, unsigned char y0,
355 unsigned int x1, unsigned char y1, card_t card)
356{
357 const unsigned char flip_x = x0 > x1;
358 const unsigned char flip_y = y0 > y1;
359
360 if (flip_x && x0 != x1)
361 {
362 x0 ^= x1;
363 x1 ^= x0;
364 x0 ^= x1;
365 }
366
367 if (flip_y && y0 != y1)
368 {
369 y0 ^= y1;
370 y1 ^= y0;
371 y0 ^= y1;
372 }
373
374 draw_frame();
375
376 const unsigned int Dx = x1 - x0;
377 const unsigned char Dy = y1 - y0;
378 const clock_t duration = MOVE_ANIM_LENGTH * (Dy + Dx);
379 const clock_t start_time = clock();
380 unsigned int last_x = 0;
381 unsigned char last_y = 0;
382 while (true)
383 {
384 const clock_t now_time = clock();
385 const clock_t elapsed = now_time - start_time;
386
387 const unsigned char last_iteration = now_time - start_time > duration;
388
389 const unsigned int dx = last_iteration ? Dx : Dx * elapsed / duration;
390 const unsigned char dy = last_iteration ? Dy : Dy * elapsed / duration;
391
392 const unsigned int card_x = flip_x ? x1 - dx : x0 + dx;
393 const unsigned char card_y = flip_y ? y1 - dy : y0 + dy;
394
395 move_card(last_x, last_y, card_x, card_y, card, 0);
396
397 if (last_iteration) break;
398
399 last_x = card_x;
400 last_y = card_y;
401 }
402}
403
404void animate_build(unsigned char tableau_stack, unsigned char tableau_index)
405{
406 const card_t card = tableau[tableau_stack][tableau_index];
407 tableau[tableau_stack][tableau_index] = empty_card();
408
409 const unsigned int x0 = TABLEAU_X0 + tableau_stack * TABLEAU_DX;
410 const unsigned char y0 = TABLEAU_Y0 + tableau_index * TABLEAU_DY;
411
412 if (card_is_trump(card))
413 {
414 unsigned int x1 = TRUMPS_X0 + TRUMPS_DX * card_get_value(card);
415 if (card_get_value(card) != next_low_trump)
416 {
417 x1 += CARD_WIDTH + TRUMPS_DX;
418 }
419
420 animate_move(x0, y0, x1, TRUMPS_Y, card);
421 }
422 else
423 {
424 unsigned int x1 = FOUNDATIONS_X0 + card_get_suit(card) * FOUNDATIONS_DX;
425 animate_move(x0, y0, x1, FOUNDATIONS_Y, card);
426 }
427}
428
429void animate_grab(card_t card, unsigned char tableau_stack,
430 unsigned char tableau_index)
431{
432 const unsigned int x0 = TABLEAU_X0 + tableau_stack * TABLEAU_DX;
433 const unsigned char y0 = TABLEAU_Y0 + tableau_index * TABLEAU_DY;
434
435 animate_move(x0, y0, HELD_CARD_X, HELD_CARD_Y, card);
436}
437
438void animate_drop(card_t card, unsigned char tableau_stack,
439 unsigned char tableau_index)
440{
441 const unsigned char x1 = TABLEAU_X0 + tableau_stack * TABLEAU_DX;
442 const unsigned char y1 = TABLEAU_Y0 + tableau_index * TABLEAU_DY;
443
444 animate_move(HELD_CARD_X, HELD_CARD_Y, x1, y1, card);
445}
446
447void animate_deal(card_t *deck)
448{
449 gfx_FillScreen(COLOR_TABLE);
450 gfx_BlitBuffer();
451
452 const clock_t main_timer = clock();
453 clock_t last_dt = 0;
454 while (clock() - main_timer < DEAL_ANIM_TOTAL_TIME)
455 {
456 const clock_t frame_dt = clock() - main_timer;
457
458 unsigned char tableau_stack = 0;
459 unsigned char tableau_index = 0;
460 for (unsigned char i = 0; i < NUM_CARDS; i++)
461 {
462 // should this card have launched yet?
463 const unsigned int delay = i * DEAL_ANIM_LAUNCH_INTERVAL;
464 if (frame_dt <= delay) continue;
465
466 // if last card launched, deck sprite disappears
467 // TODO
468
469 // where is the card going?
470 const card_t card = deck[i];
471 unsigned int x1;
472 unsigned char y1;
473 if (!card_is_trump(card) && card_get_value(card) == 0)
474 {
475 // aces go to foundations
476 x1 = FOUNDATIONS_X0 + card_get_suit(card) * FOUNDATIONS_DX;
477 y1 = FOUNDATIONS_Y;
478 }
479 else
480 {
481 // other cards go to the tableau
482 x1 = TABLEAU_X0 + tableau_stack * TABLEAU_DX;
483 y1 = TABLEAU_Y0 + tableau_index * TABLEAU_DY;
484 do { tableau_stack++; } while (tableau_stack * 2 == TABLEAU_NUM_PILES - 1);
485 if (tableau_stack >= TABLEAU_NUM_PILES)
486 {
487 tableau_stack = 0;
488 tableau_index++;
489 }
490 }
491
492 // where was this card last time?
493 unsigned int prev_x;
494 unsigned char prev_y;
495 if (last_dt >= delay + DEAL_ANIM_TRAVEL_TIME)
496 {
497 continue; // if it was already done, there is no more movement
498 }
499 else
500 {
501 // interpolate the previous position
502 const signed int traveling_for = last_dt - delay;
503 const signed int dx = x1 - DEAL_ANIM_DECK_X;
504 const signed char dy = y1 - DEAL_ANIM_DECK_Y;
505 prev_x = DEAL_ANIM_DECK_X + traveling_for * dx / DEAL_ANIM_TRAVEL_TIME;
506 prev_y = DEAL_ANIM_DECK_Y + traveling_for * dy / DEAL_ANIM_TRAVEL_TIME;
507 }
508
509 // is this card done travelling now?
510 unsigned int x;
511 unsigned char y;
512 unsigned char no_redraw;
513 if (frame_dt >= delay + DEAL_ANIM_TRAVEL_TIME)
514 {
515 x = x1;
516 y = y1;
517 no_redraw = 1;
518 }
519 else
520 {
521 // interpolate the current position
522 const signed int traveling_for = frame_dt - delay;
523 const signed int dx = x1 - DEAL_ANIM_DECK_X;
524 const signed char dy = y1 - DEAL_ANIM_DECK_Y;
525 x = DEAL_ANIM_DECK_X + traveling_for * dx / DEAL_ANIM_TRAVEL_TIME;
526 y = DEAL_ANIM_DECK_Y + traveling_for * dy / DEAL_ANIM_TRAVEL_TIME;
527 no_redraw = 0;
528 }
529
530 move_card(prev_x, prev_y, x, y, card, no_redraw);
531 }
532
533 // TODO draw deck
534
535 last_dt = frame_dt;
536 }
537}