A port of Zachtronics' match-4 game HACK*MATCH to the TI-84 Plus CE
1#include <sys/rtc.h>
2#include <ti/getcsc.h>
3#include <fileioc.h>
4#include <graphx.h>
5#include <keypadc.h>
6#include <stdlib.h>
7#include <stdio.h>
8#include <math.h>
9#include <time.h>
10
11#include "gfx/gfx.h"
12#include "variables.h"
13#include "drawing.h"
14
15#include <debug.h>
16
17unsigned char global_palette[32 + sizeof_grid_palette];
18unsigned char deathStage;
19bool deathFlashOn;
20unsigned long nextDeathFlash;
21
22struct gfx_sprite_t *fileSprites[14];
23struct gfx_sprite_t *fileMatchSprites[6];
24struct gfx_sprite_t *digitSprites[10];
25struct gfx_sprite_t *deathSprites[3];
26
27gfx_UninitedSprite(behindExa, exa_empty_width, exa_empty_height);
28gfx_UninitedSprite(behindGameOver, play_again_width, play_again_height);
29
30void drawExa()
31{
32 const unsigned char exaX = EXA_HOFFSET + GRID_SIZE * exaCol;
33 if (deathStage == 0 && isHoldingFile)
34 {
35 if (heldFile & 0x10)
36 {
37 gfx_Sprite_NoClip(file_locked, exaX + EXA_HELD_HOFFSET, EXA_VOFFSET + EXA_HELD_VOFFSET);
38 }
39 else
40 {
41 gfx_Sprite_NoClip(fileSprites[heldFile], exaX + EXA_HELD_HOFFSET, EXA_VOFFSET + EXA_HELD_VOFFSET);
42 }
43 }
44
45 if (deathStage > 0) gfx_TransparentSprite_NoClip(deathSprites[deathStage], exaX, EXA_VOFFSET);
46 else if (isHoldingFile)
47 {
48 if (heldFile & 0x08) gfx_TransparentSprite_NoClip(exa_star, exaX, EXA_VOFFSET);
49 else gfx_TransparentSprite_NoClip(exa_file, exaX, EXA_VOFFSET);
50 }
51 else gfx_TransparentSprite_NoClip(exa_empty, exaX, EXA_VOFFSET);
52}
53
54void drawCol(const unsigned char col)
55{
56 const unsigned int x = GRID_HOFFSET + col * GRID_SIZE;
57
58 unsigned char y = GRID_VOFFSET - gridMoveOffset;
59 for (unsigned char row = 0; row < MAX_ROWS; row++)
60 {
61 if (files[col][row] != FILE_EMPTY)
62 {
63 // draw a file
64 if (files[col][row] & 0x80)
65 {
66 if (files[col][row] & 0x08) gfx_Sprite(star_match, x, y);
67 else gfx_Sprite(fileMatchSprites[files[col][row] & 0x7f], x, y);
68 }
69 else if (files[col][row] & 0x10) gfx_Sprite(file_locked, x, y);
70 else gfx_Sprite(fileSprites[files[col][row]], x, y);
71 }
72 else
73 {
74 gfx_SetColor(COLOR_BLACK);
75 gfx_FillRectangle(x, y, GRID_SIZE, GRID_SIZE);
76
77 if (col == exaCol)
78 {
79 // draw the dashed line up from the exa
80 gfx_SetColor(COLOR_DASH);
81 const unsigned char xx = x + DASH_HOFFSET;
82 for (unsigned char yy = y + DASH_VOFFSET; yy < y + GRID_SIZE; yy += DASH_INTERVAL)
83 {
84 gfx_FillRectangle(xx, yy, DASH_WIDTH, DASH_HEIGHT);
85 }
86 }
87 }
88 y += GRID_SIZE;
89 }
90
91 // fill in below the grid for partially-lowered files
92 gfx_SetColor(COLOR_BLACK);
93 gfx_FillRectangle(x, y, GRID_SIZE, gridMoveOffset);
94
95 if (col == exaCol)
96 {
97 gfx_SetColor(COLOR_DASH);
98 const unsigned char xx = x + DASH_HOFFSET;
99 for (unsigned char yy = y + DASH_VOFFSET; yy < y + gridMoveOffset; yy += DASH_INTERVAL)
100 {
101 gfx_FillRectangle_NoClip(xx, yy, DASH_WIDTH, DASH_HEIGHT);
102 }
103
104 drawExa(); // else it will have been drawn over a bit
105 }
106}
107
108void clearifySprites(const unsigned char step)
109{
110 unsigned int y = GRID_VOFFSET - gridMoveOffset;
111 for (unsigned char row = 0; row < MAX_ROWS; row++)
112 {
113 unsigned char x = GRID_HOFFSET;
114 for (unsigned char col = 0; col < NUM_COLS; col++)
115 {
116 if (files[col][row] & 0x80)
117 {
118 if (files[col][row] & 0x08)
119 {
120 if (step == 0) gfx_Sprite(star_erase_1, x, y);
121 else gfx_Sprite(star_erase_2, x, y);
122 }
123 else
124 {
125 if (step == 0) gfx_Sprite(file_erase_1, x, y);
126 else gfx_Sprite(file_erase_2, x, y);
127 }
128 }
129
130 x += GRID_SIZE;
131 }
132
133 y += GRID_SIZE;
134 }
135}
136
137void animateClear() // relies on to-clears being flagged with bit 7
138{
139 clock_t refundTimer = clock();
140
141 // get everything in position for the animation
142 for (unsigned char col = 0; col < NUM_COLS; col++)
143 {
144 drawCol(col);
145 }
146
147 for (unsigned char step = 0; step < 2; step++)
148 {
149 clock_t animationTimer = clock();
150
151 clearifySprites(step);
152
153 while (clock() - animationTimer < CLEAR_ANIMATION_FRAME_TIME);
154
155 gfx_BlitBuffer();
156 }
157
158 nextMoveTime += clock() - refundTimer;
159}
160
161void drawNumber(const unsigned int x, const unsigned char y, const unsigned int toDraw)
162{
163 bool significant = false;
164 unsigned int remainder = toDraw;
165 unsigned int digitX = x;
166 for (unsigned int divisor = 100000; divisor >= 1; divisor /= 10)
167 {
168 const unsigned char thisDigit = remainder / divisor;
169 remainder -= divisor * thisDigit;
170 if (thisDigit > 0) significant = true;
171
172 if (!significant) gfx_Sprite_NoClip(digit_0_grey, digitX, y);
173 else gfx_Sprite_NoClip(digitSprites[thisDigit], digitX, y);
174
175 digitX += 8;
176 }
177}
178
179void drawFrame()
180{
181 // erase the old exa
182 gfx_Sprite_NoClip(behindExa, EXA_HOFFSET + GRID_SIZE * exaCol, EXA_VOFFSET);
183
184 // draw the new grid and exa
185 for (unsigned char col = 0; col < NUM_COLS; col++)
186 {
187 drawCol(col);
188 }
189
190 // draw the score
191 drawNumber(SCORE_HOFFSET, SCORE_VOFFSET, score);
192
193#ifndef NO_BUFFER
194 gfx_BlitBuffer();
195#endif
196}
197
198bool titleScreen()
199{
200 gfx_FillScreen(COLOR_BLACK);
201 gfx_RLETSprite_NoClip(title, TITLE_SPRITE_HOFFSET, TITLE_SPRITE_VOFFSET);
202
203 gfx_SetTextFGColor(COLOR_RED);
204 gfx_SetTextXY(TITLE_TEXT_HOFFSET, TITLE_TEXT_VOFFSET);
205 gfx_PrintString("Press any key...");
206
207#ifndef NO_BUFFER
208 gfx_BlitBuffer();
209#endif
210
211 // wait for no keys to be pressed
212 while (kb_AnyKey());
213
214 // wait for a key to be pressed
215 while (true)
216 {
217 kb_Scan();
218 if (kb_IsDown(kb_KeyClear)) return true;
219 else if (kb_AnyKey()) return false;
220 }
221}
222
223void sleep(unsigned long time)
224{
225 clock_t sleepTimer = clock();
226 while (clock() - sleepTimer < time);
227}
228
229void darken()
230{
231 for (unsigned char i = 16; i < 32; i++) gfx_palette[i] = gfx_Darken(gfx_palette[i], DEATH_DARKEN_LEVEL);
232}
233
234void animateDeath()
235{
236 // do the animation
237 for (deathStage = 0; deathStage < 3; deathStage++)
238 {
239 darken();
240 drawFrame();
241 sleep(DEATH_ANIMATION_FRAME_TIME / 8);
242
243 for (unsigned char i = 0; i < 7; i++)
244 {
245 darken();
246 sleep(DEATH_ANIMATION_FRAME_TIME / 8);
247 }
248 }
249
250 // say "GAME OVER"
251 behindGameOver->width = play_again_width;
252 behindGameOver->height = play_again_height;
253 gfx_GetSprite(behindGameOver, PLAY_AGAIN_HOFFSET, PLAY_AGAIN_VOFFSET);
254 gfx_RLETSprite_NoClip(play_again, PLAY_AGAIN_HOFFSET, PLAY_AGAIN_VOFFSET);
255 gfx_BlitBuffer();
256 deathFlashOn = true;
257 nextDeathFlash = clock() + GAME_OVER_FLASH_TIME;
258}
259
260void deathPeriodic()
261{
262 if (clock() < nextDeathFlash) return;
263
264 if (deathFlashOn) gfx_Sprite_NoClip(behindGameOver, PLAY_AGAIN_HOFFSET, PLAY_AGAIN_VOFFSET);
265 else gfx_RLETSprite_NoClip(play_again, PLAY_AGAIN_HOFFSET, PLAY_AGAIN_VOFFSET);
266
267 gfx_BlitBuffer();
268
269 deathFlashOn = !deathFlashOn;
270 nextDeathFlash = clock() + GAME_OVER_FLASH_TIME;
271}