minigames.h 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958
  1. /**
  2. Simple minigames for SAF.
  3. by drummyfish, released under CC0 1.0, public domain
  4. */
  5. //#define SAF_SETTING_FORCE_1BIT 1
  6. #define SAF_SETTING_BACKGROUND_COLOR 0xe0
  7. #define SAF_PROGRAM_NAME "Minigames"
  8. #define SAF_SETTING_FASTER_1BIT 2
  9. #define SAF_SETTING_ENABLE_SOUND 1
  10. #define SAF_SETTING_ENABLE_SAVES 1
  11. #define SAF_SETTING_BACKGROUND_COLOR 0
  12. //#define SAF_SETTING_FORCE_1BIT 1
  13. #include "../saf.h"
  14. #define GAMES 5 ///< number of minigames
  15. static const char *gameNames[GAMES] =
  16. {
  17. "SNEK",
  18. "MINE",
  19. "BLOQ",
  20. "2048",
  21. "RUNR"
  22. };
  23. #define MEMORY_SIZE 512
  24. #define MEMORY_VARIABLE_AREA ((32 * 29) / 2) // = 464
  25. #define BUTTON_HOLD_PERIOD 18
  26. void (*stepFunction) (void);
  27. void menuStep(void);
  28. void quitGame(void)
  29. {
  30. stepFunction = &menuStep;
  31. SAF_playSound(SAF_SOUND_CLICK);
  32. }
  33. /** Memory that's used by the games: the memory is shared between games and at
  34. any time at most one game is using the memory. This helps save RAM on
  35. platforms that don't have much of it. The memory has two parts:
  36. - array area (first MEMORY_VARIABLE_AREA bytes): used for 2D array data, the
  37. size e.g. allows to store 32 * 29 half-byte values.
  38. - variable area: game's global variables should be mapped into this area */
  39. uint8_t memory[MEMORY_SIZE];
  40. #define VAR(type, index) (*((type *) (memory + MEMORY_VARIABLE_AREA + index)))
  41. void clearMemory()
  42. {
  43. uint8_t *m = memory;
  44. for (uint16_t i = 0; i < MEMORY_SIZE; ++i, ++m)
  45. *m = 0;
  46. }
  47. void setMemoryHalfByte(uint16_t index, uint8_t data)
  48. {
  49. uint8_t *m = memory + index / 2;
  50. *m = (index % 2) ? ((*m & 0xf0) | data) : ((*m & 0x0f) | (data << 4));
  51. }
  52. uint8_t getMemoryHalfByte(uint16_t index)
  53. {
  54. return (index % 2) ? (memory[index / 2] & 0x0f) : (memory[index / 2] >> 4);
  55. }
  56. uint8_t buttonPressedOrHeld(uint8_t key)
  57. {
  58. uint8_t b = SAF_buttonPressed(key);
  59. return (b == 1) || (b >= BUTTON_HOLD_PERIOD);
  60. }
  61. void drawTextRightAlign(int8_t x, int8_t y, const char *text, uint8_t color,
  62. uint8_t size)
  63. {
  64. uint8_t l = 0;
  65. while (text[l] != 0)
  66. l++;
  67. x = x - l * 5 * size + 1;
  68. SAF_drawText(text,x,y,color,size);
  69. }
  70. void blinkHighScore()
  71. {
  72. if (SAF_frame() & 0x10)
  73. {
  74. SAF_drawRect(5,20,54,16,SAF_COLOR_GRAY_DARK,1);
  75. SAF_drawText("HISCORE!",11,25,SAF_COLOR_GREEN,1);
  76. }
  77. }
  78. void saveHiScore(uint8_t index, uint16_t score)
  79. {
  80. SAF_save(index * 2,score & 0x00ff);
  81. SAF_save(index * 2 + 1,score / 256);
  82. }
  83. uint16_t getHiScore(uint8_t index)
  84. {
  85. return SAF_load(index * 2) + (((uint16_t) SAF_load(index * 2 + 1)) * 256);
  86. }
  87. // SNAKE -----------------------------------------------------------------------
  88. #define SNAKE_BOARD_W 32
  89. #define SNAKE_BOARD_H 29
  90. #define SNAKE_START_LEN 6
  91. #define SNAKE_MOVE_SPEED 6
  92. #define SNAKE_COLOR1 SAF_COLOR_BLACK
  93. #define SNAKE_COLOR2 SAF_COLOR_RED
  94. #define SNAKE_SQUARE_EMPTY 0
  95. #define SNAKE_SQUARE_SU 1 // snake segment pointing upwards to next segment
  96. #define SNAKE_SQUARE_SR 2
  97. #define SNAKE_SQUARE_SD 3
  98. #define SNAKE_SQUARE_SL 4
  99. #define SNAKE_SAVE_SLOT 0
  100. // variables:
  101. #define SNAKE_TAIL_SQUARE VAR(int16_t,0)
  102. #define SNAKE_HEAD_SQUARE VAR(int16_t,2)
  103. #define SNAKE_FOOD_SQUARE VAR(int16_t,4)
  104. #define SNAKE_MOVE_COUNT VAR(uint8_t,6) // countdown to next snake move
  105. #define SNAKE_DIRECTION VAR(uint8_t,7)
  106. #define SNAKE_FOOD_STEPS VAR(uint8_t,8)
  107. #define SNAKE_SCORE VAR(uint16_t,9)
  108. #define SNAKE_GAME_STATE VAR(uint8_t,11) // 0: playing, 1: end, 2: end, hiscore
  109. void snakeSpawnFood()
  110. {
  111. int16_t square = ((((uint16_t) SAF_random()) << 8) | SAF_random()) %
  112. (SNAKE_BOARD_W * SNAKE_BOARD_H);
  113. while (getMemoryHalfByte(square) != SNAKE_SQUARE_EMPTY)
  114. square = ((square != 0) ? square : (SNAKE_BOARD_W * SNAKE_BOARD_H)) - 1;
  115. SNAKE_FOOD_SQUARE = square;
  116. }
  117. void snakeInit()
  118. {
  119. clearMemory();
  120. // TODO: init snake to random colors?
  121. SNAKE_TAIL_SQUARE = (SNAKE_BOARD_H / 2) * SNAKE_BOARD_W + SNAKE_BOARD_W / 2
  122. - SNAKE_START_LEN / 2;
  123. SNAKE_GAME_STATE = 0;
  124. SNAKE_HEAD_SQUARE = SNAKE_TAIL_SQUARE + SNAKE_START_LEN - 1;
  125. SNAKE_DIRECTION = SNAKE_SQUARE_SR;
  126. SNAKE_MOVE_COUNT = SNAKE_MOVE_SPEED;
  127. SNAKE_FOOD_STEPS = 0;
  128. SNAKE_SCORE = 0;
  129. for (uint8_t i = 0; i < SNAKE_START_LEN; ++i)
  130. setMemoryHalfByte(SNAKE_TAIL_SQUARE + i,SNAKE_SQUARE_SR);
  131. snakeSpawnFood();
  132. }
  133. int16_t snakeNextSquare(int16_t square)
  134. {
  135. switch (getMemoryHalfByte(square))
  136. {
  137. case SNAKE_SQUARE_SU: return square - 32; break;
  138. case SNAKE_SQUARE_SR: return square + 1; break;
  139. case SNAKE_SQUARE_SD: return square + 32; break;
  140. case SNAKE_SQUARE_SL: return square - 1; break;
  141. default: return square; break;
  142. }
  143. }
  144. void snakeDraw()
  145. {
  146. SAF_clearScreen(SAF_COLOR_WHITE);
  147. SAF_drawRect(0,SAF_SCREEN_HEIGHT,SAF_SCREEN_WIDTH,-6,SAF_COLOR_GRAY_DARK,1);
  148. char scoreText[6];
  149. SAF_drawText(SAF_intToStr(SNAKE_SCORE,scoreText),2,SAF_SCREEN_HEIGHT - 5,
  150. SAF_COLOR_WHITE,1);
  151. int16_t square = SNAKE_TAIL_SQUARE;
  152. uint8_t val = getMemoryHalfByte(square);
  153. #if SAF_PLATFORM_COLOR_COUNT > 2
  154. uint8_t pattern = 1; // pattern of next and prev square, for coloring
  155. #endif
  156. while (1) // draw from tail to head
  157. {
  158. uint8_t combinedPattern = 0;
  159. #if SAF_PLATFORM_COLOR_COUNT > 2
  160. switch (val)
  161. {
  162. case SNAKE_SQUARE_SU: combinedPattern = 1 | pattern; pattern = 4; break;
  163. case SNAKE_SQUARE_SR: combinedPattern = 2 | pattern; pattern = 8; break;
  164. case SNAKE_SQUARE_SD: combinedPattern = 4 | pattern; pattern = 1; break;
  165. case SNAKE_SQUARE_SL: combinedPattern = 8 | pattern; pattern = 2; break;
  166. default: break;
  167. }
  168. #endif
  169. uint8_t x = (square % 32) * 2;
  170. uint8_t y = (square / 32) * 2;
  171. #if SAF_PLATFORM_COLOR_COUNT > 2
  172. SAF_drawPixel(x,y,((combinedPattern & (1 | 8) ) || (combinedPattern == (2 | 4))) ? SNAKE_COLOR2 : SNAKE_COLOR1);
  173. SAF_drawPixel(x + 1,y,(combinedPattern & 2) ? SNAKE_COLOR2 : SNAKE_COLOR1);
  174. SAF_drawPixel(x,y + 1,(combinedPattern & 4) ? SNAKE_COLOR2 : SNAKE_COLOR1);
  175. SAF_drawPixel(x + 1,y + 1,SNAKE_COLOR1);
  176. #else
  177. SAF_drawPixel(x,y,SAF_COLOR_BLACK);
  178. SAF_drawPixel(x + 1,y,SAF_COLOR_BLACK);
  179. SAF_drawPixel(x,y + 1,SAF_COLOR_BLACK);
  180. SAF_drawPixel(x + 1,y + 1,SAF_COLOR_BLACK);
  181. #endif
  182. if (square == SNAKE_HEAD_SQUARE)
  183. break;
  184. square = snakeNextSquare(square);
  185. #if SAF_PLATFORM_COLOR_COUNT > 2
  186. val = getMemoryHalfByte(square);
  187. #endif
  188. }
  189. // draw food:
  190. uint8_t x = (SNAKE_FOOD_SQUARE % 32) * 2;
  191. uint8_t y = (SNAKE_FOOD_SQUARE / 32) * 2;
  192. SAF_drawPixel(x,y,SAF_COLOR_BLUE);
  193. SAF_drawPixel(x + 1,y,SAF_COLOR_BLUE);
  194. SAF_drawPixel(x,y + 1,SAF_COLOR_BLUE);
  195. SAF_drawPixel(x + 1,y + 1,SAF_COLOR_BLUE);
  196. #if SAF_PLATFORM_COLOR_COUNT <= 2
  197. SAF_drawRect(x - 1,y - 1,4,4,SAF_COLOR_WHITE,0);
  198. #endif
  199. if (SNAKE_GAME_STATE == 2)
  200. blinkHighScore();
  201. }
  202. void snakeStep()
  203. {
  204. if (SAF_buttonPressed(SAF_BUTTON_B) >= BUTTON_HOLD_PERIOD)
  205. quitGame();
  206. if (SNAKE_GAME_STATE != 0)
  207. {
  208. if (SAF_buttonJustPressed(SAF_BUTTON_A))
  209. snakeInit();
  210. snakeDraw();
  211. return;
  212. }
  213. SNAKE_MOVE_COUNT--;
  214. uint8_t timePressed = 0;
  215. // we register the button pressed for shortest time (allows best control):
  216. #define checkButton(b,d)\
  217. if (SAF_buttonPressed(b) && \
  218. (timePressed == 0 || SAF_buttonPressed(b) < timePressed)) {\
  219. timePressed = SAF_buttonPressed(b);\
  220. SNAKE_DIRECTION = d;}
  221. checkButton(SAF_BUTTON_UP,SNAKE_SQUARE_SU)
  222. checkButton(SAF_BUTTON_RIGHT,SNAKE_SQUARE_SR)
  223. checkButton(SAF_BUTTON_DOWN,SNAKE_SQUARE_SD)
  224. checkButton(SAF_BUTTON_LEFT,SNAKE_SQUARE_SL)
  225. if (SNAKE_MOVE_COUNT <=
  226. (SAF_buttonPressed(SAF_BUTTON_A) ? 0 : (SNAKE_MOVE_SPEED * 3) / 4))
  227. {
  228. if (SNAKE_FOOD_STEPS < 255)
  229. SNAKE_FOOD_STEPS++;
  230. uint8_t headVal = getMemoryHalfByte(SNAKE_HEAD_SQUARE);
  231. if (SNAKE_DIRECTION != headVal)
  232. {
  233. uint8_t opposite = 0;
  234. switch (headVal)
  235. {
  236. case SNAKE_SQUARE_SU: opposite = SNAKE_SQUARE_SD; break;
  237. case SNAKE_SQUARE_SR: opposite = SNAKE_SQUARE_SL; break;
  238. case SNAKE_SQUARE_SD: opposite = SNAKE_SQUARE_SU; break;
  239. case SNAKE_SQUARE_SL: opposite = SNAKE_SQUARE_SR; break;
  240. default: break;
  241. }
  242. // disallow turning 180 degrees (instadeath):
  243. if (SNAKE_DIRECTION != opposite)
  244. {
  245. headVal = SNAKE_DIRECTION;
  246. setMemoryHalfByte(SNAKE_HEAD_SQUARE,headVal);
  247. }
  248. }
  249. int16_t nextSquare = snakeNextSquare(SNAKE_HEAD_SQUARE);
  250. // check collision:
  251. uint8_t collides = 0;
  252. int8_t diff = (nextSquare % 32) - (SNAKE_HEAD_SQUARE % 32);
  253. if ( // collision with map borders
  254. (diff != 0 && diff != 1 && diff != -1) ||
  255. ((nextSquare < 0) || (nextSquare >= (SNAKE_BOARD_W * SNAKE_BOARD_H)))
  256. )
  257. collides = 1;
  258. if (!collides)
  259. {
  260. uint8_t squareVal = getMemoryHalfByte(nextSquare);
  261. if (squareVal != SNAKE_SQUARE_EMPTY)
  262. collides = 1; // collision with self
  263. }
  264. if (collides)
  265. {
  266. SAF_playSound(SAF_SOUND_BOOM);
  267. SNAKE_GAME_STATE = 1;
  268. if (SNAKE_SCORE >= getHiScore(SNAKE_SAVE_SLOT))
  269. {
  270. saveHiScore(SNAKE_SAVE_SLOT,SNAKE_SCORE);
  271. SNAKE_GAME_STATE = 2;
  272. }
  273. return;
  274. }
  275. setMemoryHalfByte(nextSquare,headVal);
  276. SNAKE_HEAD_SQUARE = nextSquare;
  277. if (nextSquare == SNAKE_FOOD_SQUARE) // takes food?
  278. {
  279. SNAKE_SCORE += 20 - SNAKE_FOOD_STEPS / 25;
  280. SNAKE_FOOD_STEPS = 0;
  281. SAF_playSound(SAF_SOUND_CLICK);
  282. snakeSpawnFood();
  283. }
  284. else
  285. {
  286. nextSquare = snakeNextSquare(SNAKE_TAIL_SQUARE);
  287. setMemoryHalfByte(SNAKE_TAIL_SQUARE,SNAKE_SQUARE_EMPTY);
  288. SNAKE_TAIL_SQUARE = nextSquare;
  289. }
  290. SNAKE_MOVE_COUNT = SNAKE_MOVE_SPEED;
  291. }
  292. snakeDraw();
  293. }
  294. // MINESWEEPER -----------------------------------------------------------------
  295. #define MINES_BOARD_W 12
  296. #define MINES_BOARD_H 11
  297. #define MINES_SQUARE_SIZE 5
  298. #define MINES_MINE_COUNT 22
  299. #define MINES_OFFSET_X 2
  300. #define MINES_OFFSET_Y 8
  301. #define MINES_SAVE_SLOT 1
  302. #define MINES_SQUARE_COVERED_MARKED 0x0f
  303. #define MINES_SQUARE_COVERED 0x0e
  304. #define MINES_SQUARE_MINE_MARKED 0x0d
  305. #define MINES_SQUARE_MINE 0x0c
  306. #define MINES_SELECTED_SQUARE VAR(uint16_t,0)
  307. #define MINES_GAME_STATE VAR(uint8_t,2) /* 0: start, 1: play, 2: lost, 3: won,
  308. 4: won, hiscore */
  309. #define MINES_SQUARES_LEFT VAR(uint8_t,3)
  310. #define MINES_MS_ELAPSED VAR(uint32_t,4)
  311. void minesInit()
  312. {
  313. MINES_GAME_STATE = 0;
  314. MINES_SELECTED_SQUARE = (MINES_BOARD_W * MINES_BOARD_H) / 2;
  315. for (uint16_t i = 0; i < MINES_BOARD_W * MINES_BOARD_H; ++i)
  316. setMemoryHalfByte(i,MINES_SQUARE_COVERED);
  317. MINES_SQUARES_LEFT = MINES_BOARD_W * MINES_BOARD_H - MINES_MINE_COUNT;
  318. MINES_MS_ELAPSED = 0;
  319. }
  320. void minesPlaceMines(uint16_t clickSquare)
  321. {
  322. for (uint8_t i = 0; i < MINES_MINE_COUNT; ++i)
  323. {
  324. uint16_t square = SAF_random() % (MINES_BOARD_W * MINES_BOARD_H);
  325. while (getMemoryHalfByte(square) == MINES_SQUARE_MINE ||
  326. square == clickSquare)
  327. square = ((square != 0) ? square : (MINES_BOARD_W * MINES_BOARD_H)) - 1;
  328. setMemoryHalfByte(square,MINES_SQUARE_MINE);
  329. }
  330. }
  331. void minesDraw()
  332. {
  333. uint8_t state = MINES_GAME_STATE;
  334. uint8_t color = SAF_COLOR_WHITE;
  335. #if SAF_PLATFORM_COLOR_COUNT > 2
  336. if (state == 2)
  337. color = SAF_COLOR_RED;
  338. else if (state == 3 || state == 4)
  339. color = SAF_COLOR_GREEN;
  340. #endif
  341. SAF_clearScreen(color);
  342. uint16_t square = 0;
  343. // bar:
  344. SAF_drawRect(0,0,SAF_SCREEN_WIDTH,MINES_OFFSET_Y - 2,SAF_COLOR_GRAY_DARK,1);
  345. char text[5];
  346. SAF_drawText(SAF_intToStr(MINES_MS_ELAPSED / 1000,text),26,1,
  347. SAF_COLOR_WHITE,1);
  348. SAF_drawText(SAF_intToStr(MINES_SQUARES_LEFT,text),3,1,SAF_COLOR_WHITE,1);
  349. SAF_drawText("E",58,1,SAF_COLOR_WHITE,1);
  350. // background rect:
  351. SAF_drawRect(MINES_OFFSET_X,MINES_OFFSET_Y,
  352. MINES_BOARD_W * MINES_SQUARE_SIZE,
  353. MINES_BOARD_H * MINES_SQUARE_SIZE,
  354. #if SAF_PLATFORM_COLOR_COUNT <= 2
  355. SAF_COLOR_BLACK,
  356. #else
  357. SAF_COLOR_BROWN,
  358. #endif
  359. 1);
  360. // squares:
  361. for (uint8_t y = MINES_OFFSET_Y;
  362. y < (MINES_BOARD_H * MINES_SQUARE_SIZE) + MINES_OFFSET_Y;
  363. y += MINES_SQUARE_SIZE)
  364. for (uint8_t x = MINES_OFFSET_X;
  365. x < (MINES_BOARD_W * MINES_SQUARE_SIZE) + MINES_OFFSET_X;
  366. x += MINES_SQUARE_SIZE)
  367. {
  368. uint8_t squareVal = getMemoryHalfByte(square);
  369. uint8_t marked = squareVal == MINES_SQUARE_COVERED_MARKED ||
  370. squareVal == MINES_SQUARE_MINE_MARKED;
  371. #if SAF_PLATFORM_COLOR_COUNT > 2
  372. color = marked ? SAF_COLOR_RED :
  373. ((squareVal < MINES_SQUARE_MINE) ? SAF_COLOR_WHITE : SAF_COLOR_GRAY);
  374. #else
  375. color = (squareVal >= MINES_SQUARE_MINE) ?
  376. SAF_COLOR_WHITE : SAF_COLOR_BLACK;
  377. #endif
  378. uint8_t s = squareVal < MINES_SQUARE_MINE ? MINES_SQUARE_SIZE : (MINES_SQUARE_SIZE - 1);
  379. SAF_drawRect(x,y,s,s,color,1);
  380. #if SAF_PLATFORM_COLOR_COUNT <= 2
  381. if (marked)
  382. SAF_drawText("*",x,y,SAF_COLOR_BLACK,1);
  383. #endif
  384. if (squareVal < MINES_SQUARE_MINE && squareVal > 0)
  385. {
  386. #if SAF_PLATFORM_COLOR_COUNT > 2
  387. switch (squareVal)
  388. {
  389. case 1: color = SAF_COLOR_BLUE; break;
  390. case 2: color = SAF_COLOR_GREEN; break;
  391. case 3: color = SAF_COLOR_RED; break;
  392. case 4: color = SAF_COLOR_BLUE_DARK; break;
  393. case 5: color = SAF_COLOR_GRAY_DARK; break;
  394. case 6: color = SAF_COLOR_RED_DARK; break;
  395. case 7: color = SAF_COLOR_YELLOW; break;
  396. case 8: color = SAF_COLOR_BROWN; break;
  397. default: break;
  398. }
  399. #else
  400. color = SAF_COLOR_WHITE;
  401. #endif
  402. char numText[2] = "0";
  403. numText[0] += squareVal;
  404. SAF_drawText(numText,x,y,color,1);
  405. }
  406. else if (state == 2 && squareVal == MINES_SQUARE_MINE)
  407. {
  408. char t[2] = "0";
  409. SAF_drawText(t,x,y,
  410. #if SAF_PLATFORM_COLOR_COUNT > 2
  411. SAF_COLOR_RED,
  412. #else
  413. SAF_COLOR_BLACK,
  414. #endif
  415. 1);
  416. }
  417. square++;
  418. }
  419. uint16_t selectedSquare = MINES_SELECTED_SQUARE;
  420. uint8_t x = MINES_OFFSET_X + (selectedSquare % MINES_BOARD_W) * MINES_SQUARE_SIZE;
  421. uint8_t y = MINES_OFFSET_Y + (selectedSquare / MINES_BOARD_W) * MINES_SQUARE_SIZE;
  422. #if SAF_PLATFORM_COLOR_COUNT <= 2
  423. SAF_drawRect(x - 1,y - 1,MINES_SQUARE_SIZE + 1,MINES_SQUARE_SIZE + 1,SAF_COLOR_WHITE,0);
  424. SAF_drawRect(x - 2,y - 2,MINES_SQUARE_SIZE + 3,MINES_SQUARE_SIZE + 3,SAF_COLOR_BLACK,0);
  425. #else
  426. SAF_drawRect(x - 2,y - 2,MINES_SQUARE_SIZE + 3,MINES_SQUARE_SIZE + 3,SAF_COLOR_BLACK,0);
  427. #endif
  428. if (state == 4)
  429. blinkHighScore();
  430. }
  431. void minesReveal(uint16_t square)
  432. {
  433. uint8_t squareVal = getMemoryHalfByte(square);
  434. if (squareVal != MINES_SQUARE_COVERED &&
  435. squareVal != MINES_SQUARE_COVERED_MARKED)
  436. return;
  437. MINES_SQUARES_LEFT--;
  438. uint8_t mineCount = 0;
  439. int8_t offsets[8] = {
  440. -1 * MINES_BOARD_W - 1,
  441. -1 * MINES_BOARD_W,
  442. -1 * MINES_BOARD_W + 1,
  443. -1, 1,
  444. MINES_BOARD_W - 1,
  445. MINES_BOARD_W,
  446. MINES_BOARD_W + 1};
  447. uint8_t checkSquares = 0xff;
  448. uint8_t coordinate = square % MINES_BOARD_W; // x
  449. if (coordinate == 0)
  450. checkSquares &= 0x6b;
  451. else if (coordinate == MINES_BOARD_W - 1)
  452. checkSquares &= 0xd6;
  453. coordinate = square / MINES_BOARD_W; // y
  454. if (coordinate == 0)
  455. checkSquares &= 0x1f;
  456. else if (coordinate == MINES_BOARD_H - 1)
  457. checkSquares &= 0xf8;
  458. uint8_t checkSquaresbackup = checkSquares;
  459. for (uint8_t i = 0; i < 8; ++i)
  460. {
  461. if (checkSquares & 0x80)
  462. {
  463. squareVal = getMemoryHalfByte(square + offsets[i]);
  464. if (squareVal == MINES_SQUARE_MINE || squareVal == MINES_SQUARE_MINE_MARKED)
  465. mineCount++;
  466. }
  467. checkSquares <<= 1;
  468. }
  469. setMemoryHalfByte(square,mineCount);
  470. if (mineCount == 0)
  471. for (uint8_t i = 0; i < 8; ++i)
  472. {
  473. if (checkSquaresbackup & 0x80)
  474. minesReveal(square + offsets[i]);
  475. checkSquaresbackup <<= 1;
  476. }
  477. #undef checkSquare
  478. }
  479. void minesStep(void)
  480. {
  481. if (SAF_buttonPressed(SAF_BUTTON_B) >= BUTTON_HOLD_PERIOD)
  482. quitGame();
  483. if (MINES_GAME_STATE == 0 || MINES_GAME_STATE == 1)
  484. {
  485. if (MINES_SQUARES_LEFT == 0) // win
  486. {
  487. SAF_playSound(SAF_SOUND_BEEP);
  488. MINES_GAME_STATE = 3;
  489. uint16_t score = getHiScore(MINES_SAVE_SLOT);
  490. if (score == 0 || MINES_MS_ELAPSED / 1000 < score)
  491. {
  492. saveHiScore(MINES_SAVE_SLOT,MINES_MS_ELAPSED / 1000);
  493. MINES_GAME_STATE = 4;
  494. }
  495. return;
  496. }
  497. MINES_MS_ELAPSED += SAF_MS_PER_FRAME;
  498. uint8_t x = MINES_SELECTED_SQUARE % MINES_BOARD_W;
  499. uint8_t y = MINES_SELECTED_SQUARE / MINES_BOARD_W;
  500. if (buttonPressedOrHeld(SAF_BUTTON_RIGHT) && x < MINES_BOARD_W - 1)
  501. MINES_SELECTED_SQUARE++;
  502. else if (buttonPressedOrHeld(SAF_BUTTON_LEFT) && x > 0)
  503. MINES_SELECTED_SQUARE--;
  504. if (buttonPressedOrHeld(SAF_BUTTON_UP) && y > 0)
  505. MINES_SELECTED_SQUARE -= MINES_BOARD_W;
  506. else if (buttonPressedOrHeld(SAF_BUTTON_DOWN) && y < MINES_BOARD_H - 1)
  507. MINES_SELECTED_SQUARE += MINES_BOARD_W;
  508. uint8_t squareVal = getMemoryHalfByte(MINES_SELECTED_SQUARE);
  509. // mark/unmark:
  510. if (SAF_buttonJustPressed(SAF_BUTTON_B) && squareVal >= MINES_SQUARE_MINE)
  511. setMemoryHalfByte(MINES_SELECTED_SQUARE,squareVal ^ 0x01);
  512. // reveal:
  513. if (SAF_buttonJustPressed(SAF_BUTTON_A))
  514. {
  515. if (squareVal == MINES_SQUARE_COVERED)
  516. {
  517. if (MINES_GAME_STATE == 0)
  518. {
  519. minesPlaceMines(MINES_SELECTED_SQUARE);
  520. MINES_GAME_STATE = 1;
  521. }
  522. minesReveal(MINES_SELECTED_SQUARE);
  523. }
  524. else if (squareVal == MINES_SQUARE_MINE) // loss
  525. {
  526. MINES_GAME_STATE = 2;
  527. SAF_playSound(SAF_SOUND_BOOM);
  528. }
  529. }
  530. }
  531. else // won or lost, waiting for keypress
  532. if (SAF_buttonJustPressed(SAF_BUTTON_A) ||
  533. SAF_buttonJustPressed(SAF_BUTTON_B))
  534. minesInit();
  535. minesDraw();
  536. }
  537. // BLOCKS ----------------------------------------------------------------------
  538. /* square format is following:
  539. MSB 76543210 LSB
  540. 012: block type/color, 0 = empty square
  541. 3456: for rotation, says the position of the block within the tetromino
  542. 7: 1 for an active (falling) block, 0 otherwise
  543. the square with value 0xff is a flashing square to be removed */
  544. #define BLOCKS_BOARD_W 10
  545. #define BLOCKS_BOARD_H 15
  546. #define BLOCKS_BOARD_SQUARES (BLOCKS_BOARD_W * BLOCKS_BOARD_H)
  547. #define BLOCKS_SQUARE_SIZE 4
  548. #define BLOCKS_BLOCK_TYPES 8
  549. #define BLOCKS_LINE_SCORE 10
  550. #define BLOCKS_LAND_SCORE 1
  551. #define BLOCKS_LEVEL_DURATION (SAF_FPS * 60)
  552. #define BLOCKS_SPEED_INCREASE 3
  553. #define BLOCKS_START_SPEED 25
  554. #define BLOCKS_SAVE_SLOT 2
  555. #define BLOCKS_OFFSET_X \
  556. (SAF_SCREEN_WIDTH - BLOCKS_BOARD_W * BLOCKS_SQUARE_SIZE)
  557. #define BLOCKS_OFFSET_Y 2
  558. #define BLOCKS_STATE VAR(uint8_t,0)
  559. #define BLOCKS_SPEED VAR(uint8_t,1)
  560. #define BLOCKS_NEXT_MOVE VAR(uint8_t,2)
  561. #define BLOCKS_WAIT_TIMER VAR(uint8_t,3)
  562. #define BLOCKS_LEVEL VAR(uint8_t,4)
  563. #define BLOCKS_SCORE VAR(uint16_t,5)
  564. #define BLOCKS_NEXT_LEVEL_IN VAR(uint32_t,7)
  565. uint8_t blocksSquareIsSolid(uint8_t square)
  566. {
  567. uint8_t val = memory[square];
  568. return (val != 0) && (val & 0x80) == 0;
  569. }
  570. uint8_t blocksSpawnBlock(uint8_t type)
  571. {
  572. if (type == 0)
  573. type = 7;
  574. uint8_t s[4] = {4,5,14,15}; // start with square tetromino
  575. uint8_t v[4] = {6,6,6,6}; // all center squares, unrotatable
  576. /* the v array holds the position of the tetromino like this:
  577. 0
  578. 123
  579. 45678
  580. 9ab
  581. c */
  582. switch (type) // modify to a specific shape
  583. {
  584. case 1: // reverse L
  585. s[0] = 3; s[1] = 13;
  586. v[0] = 1; v[1] = 5; v[3] = 7;
  587. break;
  588. case 2: // L
  589. s[0] = 13;
  590. v[0] = 5; v[1] = 3; v[3] = 7;
  591. break;
  592. case 3: // S
  593. s[0] = 6;
  594. v[0] = 3; v[1] = 2; v[2] = 5; v[3] = 6;
  595. break;
  596. case 4: // Z
  597. s[2] = 16;
  598. v[0] = 1; v[1] = 2; v[2] = 7; v[3] = 6;
  599. break;
  600. case 5: // upside-down T
  601. s[0] = 16;
  602. v[0] = 7; v[1] = 2; v[2] = 5; v[3] = 6;
  603. break;
  604. case 6: // I
  605. s[2] = 3; s[3] = 6;
  606. v[1] = 7; v[2] = 5; v[3] = 8;
  607. break;
  608. default: break;
  609. }
  610. type |= 0x80;
  611. uint8_t result = 1;
  612. for (uint8_t i = 0; i < 4; ++i)
  613. {
  614. uint8_t square = s[i];
  615. if (memory[square] != 0)
  616. result = 0;
  617. memory[square] = type | ((v[i] << 3));
  618. }
  619. return result;
  620. }
  621. void blocksInit(void)
  622. {
  623. clearMemory();
  624. BLOCKS_SPEED = BLOCKS_START_SPEED;
  625. BLOCKS_NEXT_MOVE = BLOCKS_START_SPEED;
  626. BLOCKS_WAIT_TIMER = 0;
  627. BLOCKS_LEVEL = 0;
  628. BLOCKS_SCORE = 0;
  629. BLOCKS_NEXT_LEVEL_IN = 0;
  630. BLOCKS_STATE = 0;
  631. blocksSpawnBlock(SAF_random() % BLOCKS_BLOCK_TYPES);
  632. }
  633. void blocksRotate(uint8_t left)
  634. {
  635. const int8_t rotationMap[13 * 2] =
  636. /* old new square offset */
  637. {/* 0 */ 8, 2 * BLOCKS_BOARD_W + 2,
  638. /* 1 */ 3, 2,
  639. /* 2 */ 7, BLOCKS_BOARD_W + 1,
  640. /* 3 */ 11, 2 * BLOCKS_BOARD_W,
  641. /* 4 */ 0, -2 * BLOCKS_BOARD_W + 2,
  642. /* 5 */ 2, -1 * BLOCKS_BOARD_W + 1,
  643. /* 6 */ 6, 0,
  644. /* 7 */ 10, BLOCKS_BOARD_W - 1,
  645. /* 8 */ 12, 2 * BLOCKS_BOARD_W - 2,
  646. /* 9 */ 1, -2 * BLOCKS_BOARD_W,
  647. /* 10*/ 5, -1 * BLOCKS_BOARD_W - 1,
  648. /* 11*/ 9, -2,
  649. /* 12*/ 4, -2 * BLOCKS_BOARD_W - 2};
  650. uint8_t blocksProcessed = 0;
  651. uint8_t newPositions[4];
  652. uint8_t newValues[4];
  653. for (uint8_t i = 0; i < BLOCKS_BOARD_SQUARES; ++i)
  654. {
  655. uint8_t square = memory[i];
  656. if (square & 0x80)
  657. {
  658. uint8_t index = 2 * ((square >> 3) & 0x0f);
  659. uint8_t newPos = i + rotationMap[index + 1];
  660. if (left)
  661. {
  662. // rotate two more times
  663. index = rotationMap[index] * 2;
  664. newPos += rotationMap[index + 1];
  665. index = rotationMap[index] * 2;
  666. newPos += rotationMap[index + 1];
  667. }
  668. int xDiff = (newPos % BLOCKS_BOARD_W) - (i % BLOCKS_BOARD_W);
  669. xDiff = xDiff >= 0 ? xDiff : (-1 * xDiff);
  670. if ((xDiff > 2) || // left/right outside?
  671. (newPos >= BLOCKS_BOARD_SQUARES) || // top/bottom outside?
  672. blocksSquareIsSolid(newPos))
  673. return; // can't rotate
  674. newValues[blocksProcessed] = (square & 0x87) | (rotationMap[index] << 3);
  675. newPositions[blocksProcessed] = newPos;
  676. blocksProcessed++;
  677. if (blocksProcessed >= 4)
  678. break;
  679. }
  680. }
  681. for (uint8_t i = 0; i < BLOCKS_BOARD_SQUARES; ++i)
  682. if (memory[i] & 0x80)
  683. memory[i] = 0;
  684. for (uint8_t i = 0; i < 4; ++i)
  685. memory[newPositions[i]] = newValues[i];
  686. }
  687. void blocksDraw(void)
  688. {
  689. SAF_clearScreen(BLOCKS_STATE ? SAF_COLOR_RED_DARK : SAF_COLOR_GRAY_DARK);
  690. SAF_drawRect(
  691. #if SAF_PLATFORM_COLOR_COUNT > 2
  692. BLOCKS_OFFSET_X,BLOCKS_OFFSET_Y,
  693. BLOCKS_BOARD_W * BLOCKS_SQUARE_SIZE, BLOCKS_BOARD_H * BLOCKS_SQUARE_SIZE,
  694. #else
  695. BLOCKS_OFFSET_X - 1,BLOCKS_OFFSET_Y- 1,BLOCKS_BOARD_W * BLOCKS_SQUARE_SIZE
  696. + 2, BLOCKS_BOARD_H * BLOCKS_SQUARE_SIZE + 2,
  697. #endif
  698. SAF_COLOR_WHITE,1);
  699. char text[16] = "L ";
  700. SAF_intToStr(BLOCKS_LEVEL,text + 1);
  701. SAF_drawText(text,2,3,SAF_COLOR_WHITE,1);
  702. SAF_intToStr(BLOCKS_SCORE,text);
  703. SAF_drawText(text,2,10,SAF_COLOR_WHITE,1);
  704. uint8_t x = 0, y = 0;
  705. for (uint8_t i = 0; i < BLOCKS_BOARD_SQUARES; ++i)
  706. {
  707. uint8_t square = memory[i];
  708. if (square)
  709. {
  710. if (square != 0xff)
  711. square &= 0x07;
  712. uint8_t color = 0;
  713. #if SAF_PLATFORM_COLOR_COUNT > 2
  714. switch (square)
  715. {
  716. case 1: color = SAF_COLOR_RED; break;
  717. case 2: color = SAF_COLOR_GREEN; break;
  718. case 3: color = SAF_COLOR_BROWN; break;
  719. case 4: color = SAF_COLOR_YELLOW; break;
  720. case 5: color = SAF_COLOR_ORANGE; break;
  721. case 6: color = SAF_COLOR_BLUE; break;
  722. case 7: color = SAF_COLOR_GREEN_DARK; break;
  723. case 0xff: color = ((SAF_frame() >> 2) & 0x01) ? SAF_COLOR_BLACK : SAF_COLOR_WHITE; break;
  724. default: break;
  725. }
  726. #else
  727. color = (square != 0xff) ? SAF_COLOR_BLACK :
  728. ((SAF_frame() >> 2) & 0x01) ? SAF_COLOR_BLACK : SAF_COLOR_WHITE;
  729. #endif
  730. uint8_t
  731. drawX = BLOCKS_OFFSET_X + x * BLOCKS_SQUARE_SIZE,
  732. drawY = BLOCKS_OFFSET_Y + y * BLOCKS_SQUARE_SIZE;
  733. SAF_drawRect(drawX,drawY,BLOCKS_SQUARE_SIZE,BLOCKS_SQUARE_SIZE,color,1);
  734. }
  735. x++;
  736. if (x >= 10)
  737. {
  738. x = 0;
  739. y++;
  740. }
  741. }
  742. if (BLOCKS_STATE && BLOCKS_SCORE == getHiScore(BLOCKS_SAVE_SLOT))
  743. blinkHighScore();
  744. }
  745. uint8_t blocksFallStep(void)
  746. {
  747. uint8_t canFall = 1;
  748. for (uint8_t i = 0; i < BLOCKS_BOARD_SQUARES; ++i)
  749. if (memory[i] & 0x80)
  750. {
  751. if (i / BLOCKS_BOARD_W == BLOCKS_BOARD_H - 1)
  752. {
  753. canFall = 0;
  754. break;
  755. }
  756. if (blocksSquareIsSolid(i + BLOCKS_BOARD_W))
  757. {
  758. canFall = 0;
  759. break;
  760. }
  761. }
  762. if (canFall)
  763. {
  764. for (uint8_t i = BLOCKS_BOARD_W * (BLOCKS_BOARD_H - 1) - 1; i != 255; --i)
  765. if (memory[i] & 0x80)
  766. {
  767. memory[i + BLOCKS_BOARD_W] = memory[i];
  768. memory[i] = 0;
  769. }
  770. }
  771. return canFall;
  772. }
  773. void blocksMoveHorizontally(uint8_t left)
  774. {
  775. uint8_t limitCol = left ? 0 : (BLOCKS_BOARD_W - 1);
  776. int8_t increment = left ? -1 : 1;
  777. for (uint8_t i = 0; i < BLOCKS_BOARD_SQUARES; ++i)
  778. if ((memory[i] & 0x80) &&
  779. (
  780. (i % BLOCKS_BOARD_W == limitCol) ||
  781. blocksSquareIsSolid(i + increment)
  782. ))
  783. return;
  784. uint8_t i0 = 0, i1 = BLOCKS_BOARD_SQUARES;
  785. if (!left)
  786. {
  787. i0 = BLOCKS_BOARD_SQUARES - 1;
  788. i1 = 255;
  789. }
  790. for (uint8_t i = i0; i != i1; i -= increment)
  791. if (memory[i] & 0x80)
  792. {
  793. memory[i + increment] = memory[i];
  794. memory[i] = 0;
  795. }
  796. }
  797. void blocksRemoveLines(void)
  798. {
  799. for (uint8_t i = 0; i < BLOCKS_BOARD_SQUARES; ++i)
  800. if (memory[i] == 0xff)
  801. {
  802. BLOCKS_SCORE += BLOCKS_LINE_SCORE;
  803. for (uint8_t j = i + BLOCKS_BOARD_W - 1; j >= BLOCKS_BOARD_W; --j)
  804. memory[j] = memory[j - BLOCKS_BOARD_W];
  805. for (uint8_t j = 0; j < BLOCKS_BOARD_W; ++j)
  806. memory[j] = 0;
  807. }
  808. }
  809. void blocksStep(void)
  810. {
  811. if (SAF_buttonPressed(SAF_BUTTON_B) >= BUTTON_HOLD_PERIOD)
  812. quitGame();
  813. blocksDraw();
  814. if (BLOCKS_STATE == 1)
  815. {
  816. if (SAF_buttonJustPressed(SAF_BUTTON_A) ||
  817. SAF_buttonJustPressed(SAF_BUTTON_B))
  818. {
  819. blocksInit();
  820. BLOCKS_STATE = 0;
  821. }
  822. return;
  823. }
  824. if (BLOCKS_NEXT_LEVEL_IN == 0)
  825. {
  826. BLOCKS_LEVEL += 1;
  827. BLOCKS_NEXT_LEVEL_IN = BLOCKS_LEVEL_DURATION;
  828. if (BLOCKS_SPEED > BLOCKS_SPEED_INCREASE)
  829. BLOCKS_SPEED -= BLOCKS_SPEED_INCREASE;
  830. }
  831. BLOCKS_NEXT_LEVEL_IN -= 1;
  832. if (BLOCKS_WAIT_TIMER > 0)
  833. {
  834. BLOCKS_WAIT_TIMER -= 1;
  835. if (BLOCKS_WAIT_TIMER == 1)
  836. blocksRemoveLines();
  837. return;
  838. }
  839. BLOCKS_NEXT_MOVE = BLOCKS_NEXT_MOVE - 1;
  840. if (SAF_buttonJustPressed(SAF_BUTTON_A))
  841. {
  842. // drop the block:
  843. while (blocksFallStep());
  844. SAF_playSound(SAF_SOUND_BUMP);
  845. BLOCKS_NEXT_MOVE = 0;
  846. }
  847. if (buttonPressedOrHeld(SAF_BUTTON_LEFT))
  848. blocksMoveHorizontally(1);
  849. else if (buttonPressedOrHeld(SAF_BUTTON_RIGHT))
  850. blocksMoveHorizontally(0);
  851. if (SAF_buttonJustPressed(SAF_BUTTON_UP))
  852. blocksRotate(0);
  853. else if (SAF_buttonJustPressed(SAF_BUTTON_DOWN))
  854. blocksRotate(1);
  855. if (BLOCKS_NEXT_MOVE == 0)
  856. {
  857. if (!blocksFallStep())
  858. {
  859. for (uint8_t i = 0; i < BLOCKS_BOARD_SQUARES; ++i)
  860. memory[i] &= 0x07;
  861. // scan for completed lines:
  862. uint8_t col = 0;
  863. uint8_t count = 0;
  864. uint8_t lineCompleted = 0;
  865. for (uint8_t i = 0; i < BLOCKS_BOARD_SQUARES; ++i)
  866. {
  867. if (memory[i] != 0)
  868. count++;
  869. col++;
  870. if (col >= BLOCKS_BOARD_W)
  871. {
  872. if (count >= BLOCKS_BOARD_W)
  873. {
  874. lineCompleted = 1;
  875. for (uint8_t j = i - BLOCKS_BOARD_W + 1; j <= i; ++j)
  876. memory[j] = 0xff;
  877. }
  878. col = 0;
  879. count = 0;
  880. }
  881. }
  882. if (lineCompleted)
  883. {
  884. BLOCKS_WAIT_TIMER = 20;
  885. SAF_playSound(SAF_SOUND_BEEP);
  886. }
  887. BLOCKS_SCORE += 1;
  888. if (!blocksSpawnBlock(SAF_random() % BLOCKS_BLOCK_TYPES))
  889. {
  890. BLOCKS_STATE = 1; // game over
  891. SAF_playSound(SAF_SOUND_BOOM);
  892. if (BLOCKS_SCORE >= getHiScore(BLOCKS_SAVE_SLOT))
  893. saveHiScore(BLOCKS_SAVE_SLOT,BLOCKS_SCORE);
  894. }
  895. }
  896. BLOCKS_NEXT_MOVE = BLOCKS_SPEED;
  897. }
  898. }
  899. // 2048 ------------------------------------------------------------------------
  900. /* The first 16 bytes of the board hold the power values of the squares, and
  901. another 16 byte block follows which contains info for animation: each square
  902. has the previous square value in the lower 4 bits and the travel distance in
  903. the upper 4 bits. */
  904. #define G2048_SQUARE_SIZE 11
  905. #define G2048_BOARD_SIZE ((G2048_SQUARE_SIZE + 1) * 4 + 1)
  906. #define G2048_SQUARES 16
  907. #define G2048_OFFSET_X ((SAF_SCREEN_WIDTH - G2048_BOARD_SIZE) / 2)
  908. #define G2048_OFFSET_Y 2
  909. #define G2048_SAVE_SLOT 3
  910. #define G2048_ANIMATION_FRAMES 8
  911. #define G2048_ANIMATION_COUNTDOWN VAR(uint8_t,0)
  912. #define G2048_ANIMATION_DIRECTION VAR(uint8_t,1)
  913. #define G2048_WON VAR(uint8_t,2)
  914. #define G2048_MS_ELAPSED VAR(uint32_t,3)
  915. #define G2048_HIGHSCORE_COUNTDOWN VAR(uint8_t,7)
  916. void g2048Spawn(void)
  917. {
  918. uint8_t square = SAF_random() % G2048_SQUARES;
  919. for (uint8_t i = 0; i < G2048_SQUARES; ++i)
  920. {
  921. uint8_t s = (square + i) % G2048_SQUARES;
  922. if (memory[s] == 0)
  923. {
  924. memory[s] = (SAF_random() < 200) ? 1 : 2;
  925. break;
  926. }
  927. }
  928. }
  929. void g2048Init(void)
  930. {
  931. G2048_ANIMATION_COUNTDOWN = 0;
  932. G2048_ANIMATION_DIRECTION = 0;
  933. G2048_WON = 0;
  934. G2048_MS_ELAPSED = 0;
  935. G2048_HIGHSCORE_COUNTDOWN = 0;
  936. for (uint8_t i = 0; i < G2048_SQUARES; ++i)
  937. memory[i] = 0;
  938. g2048Spawn();
  939. }
  940. void g2048Draw(void)
  941. {
  942. #if SAF_PLATFORM_COLOR_COUNT > 2
  943. SAF_clearScreen(SAF_COLOR_GRAY);
  944. #else
  945. SAF_clearScreen(SAF_COLOR_BLACK);
  946. #endif
  947. SAF_drawRect(G2048_OFFSET_X,G2048_OFFSET_Y,
  948. G2048_BOARD_SIZE,G2048_BOARD_SIZE,SAF_COLOR_WHITE,1);
  949. uint8_t square = 0;
  950. uint16_t animationNominator = G2048_SQUARE_SIZE *
  951. (G2048_ANIMATION_FRAMES - G2048_ANIMATION_COUNTDOWN + 1);
  952. for (uint8_t y = 0; y < 4; ++y)
  953. for (uint8_t x = 0; x < 4; ++x)
  954. {
  955. uint8_t v = memory[
  956. G2048_ANIMATION_COUNTDOWN == 0 ? square : (G2048_SQUARES + square)];
  957. if (v == 0)
  958. {
  959. square++;
  960. continue;
  961. }
  962. int8_t animX = 0, animY = 0;
  963. if (G2048_ANIMATION_COUNTDOWN != 0)
  964. {
  965. uint16_t offset = v >> 4;
  966. offset = (offset * animationNominator) / G2048_ANIMATION_FRAMES;
  967. switch (G2048_ANIMATION_DIRECTION)
  968. {
  969. case 0: animY = -1 * offset; break;
  970. case 1: animX = offset; break;
  971. case 2: animY = offset; break;
  972. case 3: animX = -1 * offset; break;
  973. default: break;
  974. }
  975. v &= 0x0f;
  976. }
  977. uint8_t drawX = G2048_OFFSET_X + 1 + x * (G2048_SQUARE_SIZE + 1) + animX,
  978. drawY = G2048_OFFSET_Y + 1 + y * (G2048_SQUARE_SIZE + 1) + animY;
  979. uint8_t color = SAF_COLOR_BLACK,
  980. color2 = SAF_COLOR_GRAY_DARK;
  981. #if SAF_PLATFORM_COLOR_COUNT == 2
  982. color2 = SAF_COLOR_WHITE;
  983. #else
  984. switch (v)
  985. {
  986. case 1: color = SAF_COLOR_RGB(31,222,79); break;
  987. case 2: color = SAF_COLOR_RGB(122,120,235); break;
  988. case 3: color = SAF_COLOR_RGB(235,123,89); break;
  989. case 4: color = SAF_COLOR_RGB(179,107,77); break;
  990. case 5: color = SAF_COLOR_RGB(237,162,36); break;
  991. case 6: color = SAF_COLOR_RGB(209,17,42); break;
  992. case 7: color = SAF_COLOR_RGB(13,189,148); break;
  993. case 8: color = SAF_COLOR_RGB(207,112,190); break;
  994. case 9: color = SAF_COLOR_RGB(156,62,115);
  995. color2 = SAF_COLOR_WHITE; break;
  996. case 10: color = SAF_COLOR_RGB(80,156,36);
  997. color2 = SAF_COLOR_WHITE; break;
  998. case 11: color = SAF_COLOR_RGB(224,126,20); break;
  999. default: color = SAF_COLOR_RGB(122,15,5);
  1000. color2 = SAF_COLOR_GRAY; break;
  1001. }
  1002. #endif
  1003. SAF_drawRect(drawX,drawY,
  1004. G2048_SQUARE_SIZE,G2048_SQUARE_SIZE,color,1);
  1005. char t[3] = " ";
  1006. uint8_t plusX = 4;
  1007. if (v < 10)
  1008. t[0] = '0' + v;
  1009. else
  1010. {
  1011. t[0] = '1';
  1012. t[1] = '0' + v - 10;
  1013. plusX = 0;
  1014. }
  1015. SAF_drawText(t,drawX + plusX,drawY + (G2048_SQUARE_SIZE - 4) / 2,
  1016. color2,1);
  1017. square++;
  1018. }
  1019. SAF_drawRect(
  1020. 0,SAF_SCREEN_HEIGHT - 6,SAF_SCREEN_WIDTH,6,SAF_COLOR_GRAY_DARK,1);
  1021. char timeText[8];
  1022. SAF_drawText(SAF_intToStr(G2048_MS_ELAPSED / 1000,timeText),2,
  1023. SAF_SCREEN_HEIGHT - 5,SAF_COLOR_WHITE,1);
  1024. if (G2048_WON)
  1025. SAF_drawText("WIN",SAF_SCREEN_WIDTH - 18,SAF_SCREEN_HEIGHT - 5,
  1026. SAF_COLOR_YELLOW,1);
  1027. if (G2048_HIGHSCORE_COUNTDOWN)
  1028. blinkHighScore();
  1029. }
  1030. int8_t g2048NextSquare(int8_t square, uint8_t direction)
  1031. {
  1032. switch (direction)
  1033. {
  1034. case 0: return (square >= 4) ? square - 4 : -1; break; // up
  1035. case 1: return (square % 4 != 3) ? square + 1 : -1; break; // right
  1036. case 2: return (square < 12) ? square + 4 : -1; break; // down
  1037. case 3: return (square % 4 != 0) ? square - 1 : -1; break; // left
  1038. default: return -1;
  1039. }
  1040. }
  1041. uint8_t g2048Shift(uint8_t direction)
  1042. {
  1043. uint8_t shifted = 0;
  1044. int8_t square = (direction == 1 || direction == 2) ? G2048_SQUARES - 1 : 0;
  1045. int8_t nextLineOffset = 0;
  1046. uint8_t oppositeDirection = (direction + 2) % 4;
  1047. // make a copy of the board that will be used for animation:
  1048. for (uint8_t i = 0; i < G2048_SQUARES; ++i)
  1049. memory[G2048_SQUARES + i] = memory[i];
  1050. switch (direction)
  1051. {
  1052. case 0: nextLineOffset = -4 * 3 + 1; break;
  1053. case 1: nextLineOffset = -1; break;
  1054. case 2: nextLineOffset = 4 * 3 - 1; break;
  1055. case 3: nextLineOffset = 1; break;
  1056. default: break;
  1057. }
  1058. for (uint8_t i = 0; i < 4; ++i) // for each "line"
  1059. {
  1060. while (1) // for each square in the "line"
  1061. {
  1062. int8_t nextSquare = square;
  1063. uint8_t *animSquare = memory + G2048_SQUARES + square;
  1064. uint8_t distance = 0;
  1065. uint8_t squareVal = memory[square];
  1066. if (squareVal != 0)
  1067. {
  1068. while (1) // slide the square
  1069. {
  1070. int8_t previouSquare = nextSquare;
  1071. nextSquare = g2048NextSquare(nextSquare,direction);
  1072. if (nextSquare < 0)
  1073. break; // end of board
  1074. uint8_t squareVal2 = memory[nextSquare];
  1075. if (squareVal2 == squareVal)
  1076. {
  1077. memory[nextSquare] = squareVal + 1;
  1078. if (squareVal == 10)
  1079. {
  1080. G2048_WON = 1;
  1081. uint16_t score = getHiScore(G2048_SAVE_SLOT);
  1082. if (score == 0 || G2048_MS_ELAPSED / 1000 < score)
  1083. {
  1084. saveHiScore(G2048_SAVE_SLOT,G2048_MS_ELAPSED / 1000);
  1085. G2048_HIGHSCORE_COUNTDOWN = 75;
  1086. }
  1087. }
  1088. memory[previouSquare] = 0;
  1089. shifted = 1;
  1090. distance++;
  1091. break;
  1092. }
  1093. if (squareVal2 == 0)
  1094. {
  1095. memory[nextSquare] = squareVal;
  1096. memory[previouSquare] = 0;
  1097. shifted = 1;
  1098. distance++;
  1099. }
  1100. else
  1101. break;
  1102. }
  1103. *animSquare |= (distance << 4);
  1104. }
  1105. nextSquare = g2048NextSquare(square,oppositeDirection);
  1106. if (nextSquare < 0)
  1107. {
  1108. square += nextLineOffset;
  1109. break;
  1110. }
  1111. square = nextSquare;
  1112. }
  1113. }
  1114. if (shifted)
  1115. {
  1116. G2048_ANIMATION_COUNTDOWN = G2048_ANIMATION_FRAMES;
  1117. G2048_ANIMATION_DIRECTION = direction;
  1118. }
  1119. return shifted;
  1120. }
  1121. void g2048Step(void)
  1122. {
  1123. if (SAF_buttonPressed(SAF_BUTTON_B) >= BUTTON_HOLD_PERIOD)
  1124. quitGame();
  1125. G2048_MS_ELAPSED += SAF_MS_PER_FRAME;
  1126. if (G2048_HIGHSCORE_COUNTDOWN > 0)
  1127. G2048_HIGHSCORE_COUNTDOWN -= 1;
  1128. if (G2048_ANIMATION_COUNTDOWN == 0)
  1129. {
  1130. int8_t direction = -1;
  1131. if (SAF_buttonJustPressed(SAF_BUTTON_UP))
  1132. direction = 0;
  1133. else if (SAF_buttonJustPressed(SAF_BUTTON_RIGHT))
  1134. direction = 1;
  1135. else if (SAF_buttonJustPressed(SAF_BUTTON_DOWN))
  1136. direction = 2;
  1137. else if (SAF_buttonJustPressed(SAF_BUTTON_LEFT))
  1138. direction = 3;
  1139. if (direction >= 0)
  1140. if (g2048Shift(direction))
  1141. g2048Spawn();
  1142. }
  1143. else
  1144. {
  1145. G2048_ANIMATION_COUNTDOWN -= 1;
  1146. }
  1147. g2048Draw();
  1148. }
  1149. // RUNNER ----------------------------------------------------------------------
  1150. /* Memory is a list of obstacles, terminated by 0. Each obstacle takes two
  1151. bytes: 1st is type, 2nd is x position. */
  1152. #define RUNNER_OBSTACLE_NONE 0
  1153. #define RUNNER_OBSTACLE_WALL 1
  1154. #define RUNNER_OBSTACLE_CEILING 2
  1155. #define RUNNER_OBSTACLE_WALL_TALL 3
  1156. #define RUNNER_OBSTACLE_WALL_LONG 4
  1157. #define RUNNER_OBSTACLE_MOVING 5
  1158. #define RUNNER_REAL_OBSTACLES 5
  1159. #define RUNNER_OBSTACLE_BONUS 6 // item: extra points
  1160. #define RUNNER_OBSTACLE_SHIELD 7 // item: protection
  1161. #define RUNNER_SCORE_OBSTACLE 2
  1162. #define RUNNER_SCORE_BONUS 5
  1163. #define RUNNER_PLAYER_WIDTH 5
  1164. #define RUNNER_PLAYER_HEIGHT 8
  1165. #define RUNNER_PLAYER_X_POSITION 16
  1166. #define RUNNER_GROUND_POS 40
  1167. #define RUNNER_RENDER_X_OFFSET 8 // so that obstacles go whole behind L border
  1168. #define RUNNER_PLAYER_PHASE_INCREASE 7
  1169. #define RUNNER_SAVE_SLOT 4
  1170. #define RUNNER_POSITION VAR(uint16_t,0)
  1171. #define RUNNER_PLAYER_PHASE VAR(uint8_t,2)
  1172. #define RUNNER_NEXT_OBSTACLE_IN VAR(uint8_t,3)
  1173. #define RUNNER_SHIELD_COUNTDOWN VAR(uint8_t,4)
  1174. #define RUNNER_STATE VAR(uint8_t,5) // 0: play, 1: lost, 2: lost, high score
  1175. #define RUNNER_SCORE VAR(uint16_t,6)
  1176. uint8_t runnerLevel(void)
  1177. {
  1178. return (RUNNER_POSITION >> 8) & 0xff;
  1179. }
  1180. void runnerAddObstacle(uint8_t type)
  1181. {
  1182. uint8_t i = 0;
  1183. while (memory[i] != RUNNER_OBSTACLE_NONE)
  1184. i += 2;
  1185. memory[i] = type;
  1186. i++;
  1187. memory[i] = SAF_SCREEN_WIDTH + RUNNER_RENDER_X_OFFSET;
  1188. i++;
  1189. memory[i] = RUNNER_OBSTACLE_NONE; // terminate
  1190. uint8_t level = runnerLevel();
  1191. if (level > 10)
  1192. level = 10;
  1193. int8_t minDistance = 25 - level;
  1194. uint8_t additionalDistance = SAF_random() % (18 - level);
  1195. RUNNER_NEXT_OBSTACLE_IN = minDistance + additionalDistance;
  1196. }
  1197. uint8_t runnerObstacleIsTakeable(uint8_t type)
  1198. {
  1199. return type == RUNNER_OBSTACLE_BONUS || type == RUNNER_OBSTACLE_SHIELD;
  1200. }
  1201. void runnerGetObstacleSize(uint8_t type, uint8_t position,
  1202. uint8_t *width, uint8_t *height, uint8_t *elevation)
  1203. {
  1204. *width = RUNNER_PLAYER_WIDTH;
  1205. *height = (RUNNER_PLAYER_HEIGHT * 3) / 4;
  1206. *elevation = 0;
  1207. switch (type)
  1208. {
  1209. case RUNNER_OBSTACLE_WALL_TALL:
  1210. *width -= 1; *height = RUNNER_PLAYER_HEIGHT;
  1211. break;
  1212. case RUNNER_OBSTACLE_WALL_LONG:
  1213. *width = (*width * 7) / 4; *height /= 2;
  1214. break;
  1215. case RUNNER_OBSTACLE_CEILING:
  1216. *elevation = RUNNER_PLAYER_HEIGHT - 2;
  1217. break;
  1218. case RUNNER_OBSTACLE_MOVING:
  1219. *width -= 2;
  1220. *elevation =
  1221. (SAF_cos((RUNNER_POSITION * 4 - position * 2) & 0xff) + 128) / 32;
  1222. break;
  1223. case RUNNER_OBSTACLE_BONUS:
  1224. case RUNNER_OBSTACLE_SHIELD:
  1225. *width = 4; *height = 4; *elevation = (RUNNER_PLAYER_HEIGHT * 10) / 4;
  1226. break;
  1227. default: break;
  1228. }
  1229. }
  1230. void runnerInit(void)
  1231. {
  1232. RUNNER_POSITION = 0;
  1233. RUNNER_PLAYER_PHASE = 0;
  1234. RUNNER_NEXT_OBSTACLE_IN = 0;
  1235. RUNNER_SHIELD_COUNTDOWN = 0;
  1236. RUNNER_STATE = 0;
  1237. RUNNER_SCORE = 0;
  1238. memory[0] = RUNNER_OBSTACLE_NONE;
  1239. }
  1240. uint8_t runnerPlayerHeight(void)
  1241. {
  1242. return SAF_sin(RUNNER_PLAYER_PHASE) / 8;
  1243. }
  1244. uint8_t runnerPlayerTallness(void)
  1245. {
  1246. return RUNNER_PLAYER_PHASE != 255 ? RUNNER_PLAYER_HEIGHT :
  1247. (RUNNER_PLAYER_HEIGHT / 2);
  1248. }
  1249. void runnerRemoveObstacle(uint8_t index)
  1250. {
  1251. while (memory[index] != RUNNER_OBSTACLE_NONE)
  1252. {
  1253. memory[index] = memory[index + 2];
  1254. memory[index + 1] = memory[index + 3];
  1255. index += 2;
  1256. }
  1257. }
  1258. void runnerResolveCollisions(void)
  1259. {
  1260. uint8_t i = 0;
  1261. uint8_t playerFrom = runnerPlayerHeight(),
  1262. playerTo = playerFrom + runnerPlayerTallness();
  1263. while (memory[i] != RUNNER_OBSTACLE_NONE)
  1264. {
  1265. uint8_t w, h, e;
  1266. uint8_t pos = memory[i + 1];
  1267. uint8_t obstacle = memory[i];
  1268. runnerGetObstacleSize(obstacle,pos,&w,&h,&e);
  1269. if (pos >= RUNNER_PLAYER_X_POSITION + RUNNER_PLAYER_WIDTH)
  1270. break;
  1271. uint8_t obstacleTo = e + h;
  1272. if (pos + w > RUNNER_PLAYER_X_POSITION &&
  1273. ((playerFrom >= e && playerFrom < obstacleTo) ||
  1274. (playerTo >= e && playerTo < obstacleTo)))
  1275. {
  1276. if (runnerObstacleIsTakeable(memory[i]))
  1277. runnerRemoveObstacle(i);
  1278. if (obstacle == RUNNER_OBSTACLE_SHIELD)
  1279. {
  1280. RUNNER_SHIELD_COUNTDOWN = 255;
  1281. SAF_playSound(SAF_SOUND_CLICK);
  1282. }
  1283. else if (obstacle == RUNNER_OBSTACLE_BONUS)
  1284. {
  1285. RUNNER_SCORE += RUNNER_SCORE_BONUS;
  1286. SAF_playSound(SAF_SOUND_CLICK);
  1287. }
  1288. else
  1289. {
  1290. if (RUNNER_SHIELD_COUNTDOWN == 0)
  1291. {
  1292. RUNNER_STATE = 1;
  1293. SAF_playSound(SAF_SOUND_BOOM);
  1294. uint16_t score = getHiScore(RUNNER_SAVE_SLOT);
  1295. if (RUNNER_SCORE > score)
  1296. {
  1297. saveHiScore(RUNNER_SAVE_SLOT,RUNNER_SCORE);
  1298. RUNNER_STATE = 2;
  1299. }
  1300. }
  1301. else
  1302. {
  1303. runnerRemoveObstacle(i);
  1304. RUNNER_SHIELD_COUNTDOWN = 0;
  1305. }
  1306. }
  1307. return;
  1308. }
  1309. i += 2;
  1310. }
  1311. }
  1312. void runnerDraw(void)
  1313. {
  1314. SAF_drawRect(0,0,SAF_SCREEN_WIDTH,RUNNER_GROUND_POS,SAF_COLOR_WHITE,1);
  1315. uint8_t groundPos = RUNNER_POSITION % 32;
  1316. for (uint8_t i = 0; i < SAF_SCREEN_WIDTH; ++i) // draw ground
  1317. {
  1318. for (uint8_t j = RUNNER_GROUND_POS; j < SAF_SCREEN_HEIGHT; ++j)
  1319. SAF_drawPixel(i,j,
  1320. #if SAF_PLATFORM_COLOR_COUNT > 2
  1321. groundPos & 0x10 ? SAF_COLOR_GRAY : SAF_COLOR_GRAY_DARK
  1322. #else
  1323. (i % 2 == j % 2) ? SAF_COLOR_BLACK : SAF_COLOR_WHITE
  1324. #endif
  1325. );
  1326. groundPos++;
  1327. }
  1328. uint8_t height = runnerPlayerTallness(),
  1329. color = (RUNNER_STATE == 0) ? SAF_COLOR_BLACK :
  1330. (SAF_frame() & 0x2 ? SAF_COLOR_BLACK : SAF_COLOR_WHITE),
  1331. dx = RUNNER_PLAYER_X_POSITION - RUNNER_RENDER_X_OFFSET,
  1332. dy = RUNNER_GROUND_POS - height - runnerPlayerHeight();
  1333. // draw the player:
  1334. SAF_drawRect(dx,dy,RUNNER_PLAYER_WIDTH,height,color,1);
  1335. if (RUNNER_SHIELD_COUNTDOWN != 0)
  1336. SAF_drawCircle(dx + RUNNER_PLAYER_WIDTH / 2,dy + RUNNER_PLAYER_HEIGHT / 2,7,
  1337. #if SAF_PLATFORM_COLOR_COUNT > 2
  1338. SAF_COLOR_BLUE,
  1339. #else
  1340. SAF_COLOR_BLACK,
  1341. #endif
  1342. 0);
  1343. uint8_t i = 0;
  1344. while (1) // draw obstacles
  1345. {
  1346. uint8_t obstacle = memory[i];
  1347. if (obstacle == RUNNER_OBSTACLE_NONE)
  1348. break;
  1349. uint8_t w, h, e;
  1350. runnerGetObstacleSize(obstacle,memory[i + 1],&w,&h,&e);
  1351. dx = memory[i + 1] - RUNNER_RENDER_X_OFFSET;
  1352. dy = RUNNER_GROUND_POS - h - e;
  1353. if (runnerObstacleIsTakeable(obstacle))
  1354. SAF_drawCircle(dx,dy,w / 2,
  1355. #if SAF_PLATFORM_COLOR_COUNT > 2
  1356. obstacle == RUNNER_OBSTACLE_BONUS ?
  1357. SAF_COLOR_YELLOW : SAF_COLOR_BLUE,1
  1358. #else
  1359. SAF_COLOR_BLACK, obstacle == RUNNER_OBSTACLE_BONUS
  1360. #endif
  1361. );
  1362. else
  1363. SAF_drawRect(dx,dy,w,h,
  1364. #if SAF_PLATFORM_COLOR_COUNT > 2
  1365. SAF_COLOR_RED_DARK,
  1366. #else
  1367. SAF_COLOR_BLACK,
  1368. #endif
  1369. 1);
  1370. i += 2;
  1371. }
  1372. char text[12];
  1373. SAF_intToStr(RUNNER_POSITION / 8,text);
  1374. uint8_t p = 0;
  1375. while (text[p] != 0)
  1376. p++;
  1377. text[p] = 'M';
  1378. text[p + 1] = 0;
  1379. SAF_drawText(text,2,2,
  1380. #if SAF_PLATFORM_COLOR_COUNT > 2
  1381. SAF_COLOR_GRAY
  1382. #else
  1383. SAF_COLOR_BLACK
  1384. #endif
  1385. ,1);
  1386. drawTextRightAlign(SAF_SCREEN_WIDTH - 2,2,SAF_intToStr(RUNNER_SCORE,text),
  1387. SAF_COLOR_BLACK,1);
  1388. if (RUNNER_STATE == 2)
  1389. blinkHighScore();
  1390. }
  1391. void runnerScroll(void)
  1392. {
  1393. RUNNER_POSITION += 1;
  1394. uint8_t i = 0;
  1395. while (memory[i] != RUNNER_OBSTACLE_NONE)
  1396. {
  1397. memory[i + 1] -= 1;
  1398. i += 2;
  1399. }
  1400. uint8_t addScore = 0;
  1401. if (memory[0] != RUNNER_OBSTACLE_NONE && memory[1] == 0)
  1402. {
  1403. addScore = memory[0] != RUNNER_OBSTACLE_SHIELD &&
  1404. memory[0] != RUNNER_OBSTACLE_BONUS;
  1405. // remove the obstacle once it's at position 0
  1406. runnerRemoveObstacle(0);
  1407. }
  1408. if (addScore)
  1409. RUNNER_SCORE += RUNNER_SCORE_OBSTACLE;
  1410. }
  1411. void runnerStep(void)
  1412. {
  1413. runnerDraw();
  1414. if (SAF_buttonPressed(SAF_BUTTON_B) >= BUTTON_HOLD_PERIOD)
  1415. quitGame();
  1416. if (RUNNER_STATE != 0)
  1417. {
  1418. if (SAF_buttonJustPressed(SAF_BUTTON_A) ||
  1419. SAF_buttonJustPressed(SAF_BUTTON_B))
  1420. runnerInit();
  1421. return;
  1422. }
  1423. uint8_t duck = SAF_buttonPressed(SAF_BUTTON_DOWN);
  1424. if (RUNNER_PLAYER_PHASE == 0)
  1425. {
  1426. if (SAF_buttonJustPressed(SAF_BUTTON_A) || SAF_buttonPressed(SAF_BUTTON_UP))
  1427. {
  1428. // jump:
  1429. RUNNER_PLAYER_PHASE = RUNNER_PLAYER_PHASE_INCREASE;
  1430. SAF_playSound(SAF_SOUND_BUMP);
  1431. }
  1432. else if (duck)
  1433. RUNNER_PLAYER_PHASE = 255;
  1434. }
  1435. else if (RUNNER_PLAYER_PHASE != 255)
  1436. {
  1437. RUNNER_PLAYER_PHASE += RUNNER_PLAYER_PHASE_INCREASE;
  1438. if (RUNNER_PLAYER_PHASE > 128)
  1439. RUNNER_PLAYER_PHASE = 0;
  1440. }
  1441. else
  1442. RUNNER_PLAYER_PHASE = duck ? 255 : 0;
  1443. if (RUNNER_SHIELD_COUNTDOWN != 0)
  1444. RUNNER_SHIELD_COUNTDOWN -= 1;
  1445. runnerScroll();
  1446. runnerResolveCollisions();
  1447. if (RUNNER_NEXT_OBSTACLE_IN == 0)
  1448. {
  1449. uint8_t level = runnerLevel() + 1;
  1450. if (level > RUNNER_REAL_OBSTACLES)
  1451. level = RUNNER_REAL_OBSTACLES;
  1452. uint8_t r = SAF_random();
  1453. if (r > 240)
  1454. {
  1455. runnerAddObstacle(r > 245 ? RUNNER_OBSTACLE_BONUS
  1456. : RUNNER_OBSTACLE_SHIELD);
  1457. }
  1458. else
  1459. runnerAddObstacle(SAF_random() % level + 1);
  1460. }
  1461. else
  1462. RUNNER_NEXT_OBSTACLE_IN -= 1;
  1463. }
  1464. // -----------------------------------------------------------------------------
  1465. uint8_t menuItem = 0;
  1466. uint8_t firstClick = 0;
  1467. void menuStep(void)
  1468. {
  1469. SAF_clearScreen(SAF_COLOR_WHITE);
  1470. SAF_drawCircle(13,47,17,SAF_COLOR_GRAY_DARK,1);
  1471. SAF_drawRect(0,29,SAF_SCREEN_WIDTH,6,SAF_COLOR_GREEN,1);
  1472. #if SAF_PLATFORM_COLOR_COUNT > 2
  1473. SAF_drawText(gameNames[menuItem],12,25,SAF_COLOR_GRAY,2);
  1474. #endif
  1475. SAF_drawText(gameNames[menuItem],11,24,SAF_COLOR_BLACK,2);
  1476. uint16_t score = getHiScore(menuItem);
  1477. char scoreText[6] = "XXX";
  1478. if (score != 0)
  1479. SAF_intToStr(score,scoreText);
  1480. SAF_drawText(scoreText,score < 10000 ? 6 : 1,45,SAF_COLOR_WHITE,1);
  1481. if (buttonPressedOrHeld(SAF_BUTTON_RIGHT))
  1482. {
  1483. menuItem = (menuItem + 1) % GAMES;
  1484. SAF_playSound(SAF_SOUND_CLICK);
  1485. }
  1486. else if (buttonPressedOrHeld(SAF_BUTTON_LEFT))
  1487. {
  1488. menuItem = (menuItem > 0) ? menuItem - 1 : (GAMES - 1);
  1489. SAF_playSound(SAF_SOUND_CLICK);
  1490. }
  1491. if (SAF_buttonPressed(SAF_BUTTON_A))
  1492. {
  1493. if (!firstClick)
  1494. {
  1495. SAF_randomSeed(SAF_frame()); // create a somewhat random initial seed
  1496. firstClick = 1;
  1497. }
  1498. switch (menuItem)
  1499. {
  1500. case 0: snakeInit(); stepFunction = &snakeStep; break;
  1501. case 1: minesInit(); stepFunction = &minesStep; break;
  1502. case 2: blocksInit(); stepFunction = &blocksStep; break;
  1503. case 3: g2048Init(); stepFunction = &g2048Step; break;
  1504. case 4: runnerInit(); stepFunction = &runnerStep; break;
  1505. default: break;
  1506. }
  1507. SAF_playSound(SAF_SOUND_CLICK);
  1508. }
  1509. }
  1510. void SAF_init(void)
  1511. {
  1512. stepFunction = &menuStep;
  1513. }
  1514. uint8_t SAF_loop(void)
  1515. {
  1516. stepFunction();
  1517. return 1;
  1518. }