···22 - game end screen now flashes "GAME OVER" instead of just displaying it
33 - displayed high score now updates when the game is restarted
44 - added clear to exit to controls list in readme
55- - changed file removal behavior
55+ - overhauled file match, scoring, and removal behavior
66+ - made files fall gradually
+6-2
README.md
···6677Support the official release! Check out *EXAPUNKS*, an incredible open-ended programming puzzle game and good introduction to Zachtronics's catalog; *Last Call BBS*, a collection best described as several outstanding games rolled into one; or check out the NES port this is most closely based on on itch.io.
8899+## Installation
1010+1111+Using any linking program (TILP, TI Connect CE, etc), import HACKMTCH.8xg onto your calculator. This program also relies on the standard C library available [here](https://github.com/CE-Programming/libraries/releases). If launching the program displays the error "need libload", try reinstalling that library.
1212+913## How to Play
10141115*HACK\*MATCH* is a match-4 game in the vein of Candy Crush, but (hopefully!) better. Move matching files into groups of 4 to remove them and score points. Files will continue to be added from the top; if they reach the bottom, it's game over!
···2630Program Type: ASM
27312832Size and Variable Usage:
2929-- RAM: 7360 B
3030- - HACKMTCH: 7340 B
3333+- RAM: 7705 B
3434+ - HACKMTCH: 7685 B
3135 - HKMCHDAT: 20 B
3236- ARC: 54117 B
3337 - HKMCHGFX: 54117 B
+33-13
src/drawing.c
···1212#include "variables.h"
1313#include "drawing.h"
14141515+#include <debug.h>
1616+1517unsigned char global_palette[32 + sizeof_grid_palette];
1618unsigned char deathStage;
1719bool deathFlashOn;
···5355{
5456 const unsigned int x = GRID_HOFFSET + col * GRID_SIZE;
55575656- unsigned char y = GRID_VOFFSET;
5858+ unsigned char y = GRID_VOFFSET - gridMoveOffset;
5759 for (unsigned char row = 0; row < MAX_ROWS; row++)
5860 {
5961 if (files[col][row] != FILE_EMPTY)
6062 {
6163 // draw a file
6262- if (files[col][row] & 0x80) gfx_Sprite_NoClip(fileMatchSprites[files[col][row] & 0x7f], x, y);
6363- else if (files[col][row] & 0x10) gfx_Sprite_NoClip(file_locked, x, y);
6464- else gfx_Sprite_NoClip(fileSprites[files[col][row]], x, y);
6464+ if (files[col][row] & 0x80)
6565+ {
6666+ if (files[col][row] & 0x08) gfx_Sprite(star_match, x, y);
6767+ else gfx_Sprite(fileMatchSprites[files[col][row] & 0x7f], x, y);
6868+ }
6969+ else if (files[col][row] & 0x10) gfx_Sprite(file_locked, x, y);
7070+ else gfx_Sprite(fileSprites[files[col][row]], x, y);
6571 }
6672 else
6773 {
6874 gfx_SetColor(COLOR_BLACK);
6969- gfx_FillRectangle_NoClip(x, y, GRID_SIZE, GRID_SIZE);
7575+ gfx_FillRectangle(x, y, GRID_SIZE, GRID_SIZE);
70767177 if (col == exaCol)
7278 {
···7581 const unsigned char xx = x + DASH_HOFFSET;
7682 for (unsigned char yy = y + DASH_VOFFSET; yy < y + GRID_SIZE; yy += DASH_INTERVAL)
7783 {
7878- gfx_SetPixel(xx, yy);
8484+ gfx_FillRectangle(xx, yy, DASH_WIDTH, DASH_HEIGHT);
7985 }
8086 }
8187 }
8288 y += GRID_SIZE;
8389 }
84908585- if (col == exaCol) drawExa(); // else it will have been drawn over a bit
9191+ // fill in below the grid for partially-lowered files
9292+ gfx_SetColor(COLOR_BLACK);
9393+ gfx_FillRectangle(x, y, GRID_SIZE, gridMoveOffset);
9494+9595+ if (col == exaCol)
9696+ {
9797+ gfx_SetColor(COLOR_DASH);
9898+ const unsigned char xx = x + DASH_HOFFSET;
9999+ for (unsigned char yy = y + DASH_VOFFSET; yy < y + gridMoveOffset; yy += DASH_INTERVAL)
100100+ {
101101+ gfx_FillRectangle_NoClip(xx, yy, DASH_WIDTH, DASH_HEIGHT);
102102+ }
103103+104104+ drawExa(); // else it will have been drawn over a bit
105105+ }
86106}
8710788108void clearifySprites(const unsigned char step)
89109{
9090- unsigned int y = GRID_VOFFSET;
110110+ unsigned int y = GRID_VOFFSET - gridMoveOffset;
91111 for (unsigned char row = 0; row < MAX_ROWS; row++)
92112 {
93113 unsigned char x = GRID_HOFFSET;
···97117 {
98118 if (files[col][row] & 0x08)
99119 {
100100- if (step == 0) gfx_Sprite_NoClip(star_erase_1, x, y);
101101- else gfx_Sprite_NoClip(star_erase_2, x, y);
120120+ if (step == 0) gfx_Sprite(star_erase_1, x, y);
121121+ else gfx_Sprite(star_erase_2, x, y);
102122 }
103123 else
104124 {
105105- if (step == 0) gfx_Sprite_NoClip(file_erase_1, x, y);
106106- else gfx_Sprite_NoClip(file_erase_2, x, y);
125125+ if (step == 0) gfx_Sprite(file_erase_1, x, y);
126126+ else gfx_Sprite(file_erase_2, x, y);
107127 }
108128 }
109129···135155 gfx_BlitBuffer();
136156 }
137157138138- nextLineTime += clock() - refundTimer;
158158+ nextMoveTime += clock() - refundTimer;
139159}
140160141161void drawNumber(const unsigned int x, const unsigned char y, const unsigned int toDraw)
+59-27
src/main.c
···2424bool isHoldingFile;
2525unsigned char heldFile;
26262727-clock_t nextLineTime;
2727+unsigned char gridMoveOffset;
2828+clock_t nextMoveTime;
28292930unsigned char prevRight, prevLeft, prev2nd, prevAlpha;
30313132bool matched;
3233unsigned char starMatches;
3334clock_t clearTime;
3434-unsigned char nextValue;
3535+unsigned int nextValue;
35363637bool getTargetedFile(unsigned char *output) // each of these returns false on failure
3738{
···9192 gfx_SetDrawBuffer();
9293#endif
93949494- nextLineTime += clock() - refundTimer;
9595+ nextMoveTime += clock() - refundTimer;
95969697 return true;
9798}
···127128 gfx_SetDrawBuffer();
128129#endif
129130130130- nextLineTime += clock() - refundTimer;
131131+ nextMoveTime += clock() - refundTimer;
131132132133 return true;
133134}
···224225 }
225226}
226227227227-void getNextLineTime()
228228+void getNextMoveTime()
228229{
229230 if (score < ROW_INTERVAL_SCALE_END)
230231 {
231231- nextLineTime = clock() + MAX_NEW_ROW_INTERVAL;
232232+ nextMoveTime = clock() + MAX_NEW_ROW_INTERVAL / 4;
232233233234 if (score > ROW_INTERVAL_SCALE_START)
234235 {
235235- nextLineTime -= (score - ROW_INTERVAL_SCALE_START) * ROW_INTERVAL_SCALE_FACTOR;
236236+ nextMoveTime -= (score - ROW_INTERVAL_SCALE_START) * ROW_INTERVAL_SCALE_FACTOR / 4;
236237 }
237238 }
238239 else
239240 {
240240- nextLineTime = clock() + MIN_NEW_ROW_INTERVAL;
241241+ nextMoveTime = clock() + MIN_NEW_ROW_INTERVAL / 4;
241242 }
242243}
243244···291292 break;
292293 }
293294 }
295295+296296+ // move all the blocks back up so it's not too jarring
297297+ gridMoveOffset += GRID_SIZE;
294298}
295299296300void collapseGrid()
···326330 clearTime = clock() + MATCH_TIME;
327331}
328332333333+void clearMarks(const bool toMark)
334334+{
335335+ for (unsigned char checkRow = 0; checkRow < MAX_ROWS; checkRow++)
336336+ {
337337+ for (unsigned char checkCol = 0; checkCol < NUM_COLS; checkCol++)
338338+ {
339339+ if (toMark) files[checkCol][checkRow] &= 0xbf;
340340+ else if (files[checkCol][checkRow] & 0x40) files[checkCol][checkRow] &= 0x3f;
341341+ }
342342+ }
343343+}
344344+329345void checkForMatch()
330346{
331347 // check for sets to pop and score
···344360 if (numMatchingStars < 2)
345361 {
346362 // if not, we don't care about it, bc it can't form a set
347347- files[col][row] &= 0x7f;
363363+ clearMarks(false);
348364 }
349365 else
350366 {
367367+ starMatches += numMatchingStars;
368368+351369 // if we get here, it does have a match!
352370 const unsigned char target = files[col][row] & 0x07;
353371 for (unsigned char starRow = 0; starRow < MAX_ROWS; starRow++)
···370388 // find all contiguous matching blocks and their count
371389 unsigned char numMatchingBlocks = 0;
372390 findMatchRegion(row, col, files[col][row], &numMatchingBlocks);
373373- const bool toMark = numMatchingBlocks >= AMOUNT_FILES_TO_MATCH;
374391375392 // unmark or cement the matching region
376376- for (unsigned char checkRow = 0; checkRow < MAX_ROWS; checkRow++)
377377- {
378378- for (unsigned char checkCol = 0; checkCol < NUM_COLS; checkCol++)
379379- {
380380- if (toMark) files[checkCol][checkRow] &= 0xbf;
381381- else if (files[checkCol][checkRow] & 0x40) files[checkCol][checkRow] &= 0x3f;
382382- }
383383- }
384384-393393+ const bool toMark = numMatchingBlocks >= AMOUNT_FILES_TO_MATCH;
394394+ clearMarks(toMark);
385395 if (toMark) setClearTime();
386396 }
387397 }
···407417 }
408418409419 // calculate the score for the matching region
410410- unsigned char atBase = nextValue == BASE_BLOCK_VALUE ? AMOUNT_FILES_TO_MATCH : 0;
420420+ unsigned char atBase = 0;
421421+ if (nextValue == BASE_BLOCK_VALUE) atBase = AMOUNT_FILES_TO_MATCH;
411422 numMatchingBlocks -= starMatches;
412423 while (numMatchingBlocks > 0)
413424 {
···416427417428 score += nextValue;
418429 numMatchingBlocks--;
430430+431431+ dbg_printf("added block with value %u\n", nextValue);
419432 }
420433434434+ dbg_printf("total score: %u\n", score);
435435+421436 // tidy up
422437 matched = false;
423438 starMatches = 0;
···426441427442void updateGrid()
428443{
444444+ clock_t refundTimer = clock();
445445+429446 // remove the matched blocks and collapse the grid if it's time
430447 if (matched && clock() > clearTime)
431448 {
···436453 checkForMatch();
437454438455 // reset the score if not
439439- if (!matched) nextValue = BASE_BLOCK_VALUE;
456456+ if (!matched)
457457+ {
458458+ nextValue = BASE_BLOCK_VALUE;
459459+460460+ // ...and move the grid down if it's time
461461+ if (clock() > nextMoveTime)
462462+ {
463463+ gridMoveOffset -= GRID_SIZE / GRID_MOVE_STEPS;
464464+ if (gridMoveOffset == 0) addNewRow();
465465+ getNextMoveTime();
466466+ }
467467+ }
440468441441- // move the grid down if it's time
442442- if (clock() > nextLineTime)
469469+ // if there is a match, don't move the grid down
470470+ else
443471 {
444444- addNewRow();
445445- getNextLineTime();
472472+ nextMoveTime += clock() - refundTimer;
446473 }
447474}
448475···481508 }
482509483510 // populate the initial grid
484484- for (unsigned char i = 0; i < NUM_INITIAL_ROWS; i++)
511511+ for (unsigned char i = 0; i <= NUM_INITIAL_ROWS; i++)
485512 {
486513 addNewRow();
487514 }
488515516516+ gridMoveOffset = GRID_SIZE;
517517+489518 // set and draw initial exa position
490519 exaCol = EXA_START_COL;
491520 gfx_GetSprite(behindExa, EXA_HOFFSET + EXA_START_COL * GRID_SIZE, EXA_VOFFSET);
492521 gfx_TransparentSprite_NoClip(exa_empty, EXA_HOFFSET + EXA_START_COL * GRID_SIZE, EXA_VOFFSET);
493522494494- nextLineTime = clock() + MIN_NEW_ROW_INTERVAL;
523523+ nextMoveTime = clock() + MIN_NEW_ROW_INTERVAL;
495524}
496525497526bool endGame()
···574603#else
575604 gfx_SetDrawBuffer();
576605#endif
606606+607607+ // set the clip area so that the grid can be offset
608608+ gfx_SetClipRegion(0, CLIP_VOFFSET, GFX_LCD_WIDTH, GFX_LCD_HEIGHT);
577609578610 // prepare the sketchy combination palette
579611 for (unsigned char i = 0; i < sizeof_fixed_palette; i++) global_palette[i] = fixed_palette[i];