您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

466 行
14KB

  1. /* moonlander.c – Lunar Lander for Commodore 64
  2. *
  3. * Build:
  4. * cl65 -t c64 -O -o moonlander.prg moonlander.c
  5. *
  6. * Controls (joystick port 2):
  7. * FIRE = upward thrust
  8. * LEFT/RIGHT = horizontal thrust
  9. *
  10. * Land gently on the green pad to score points.
  11. * Hard landing or missing the pad = CRASH.
  12. */
  13. #include <c64.h>
  14. #include <conio.h>
  15. #include <string.h>
  16. #include <stdio.h>
  17. /* ----------------------------------------------------------------
  18. Hardware register macros
  19. ---------------------------------------------------------------- */
  20. #define SP0X (*(unsigned char*)0xD000)
  21. #define SP0Y (*(unsigned char*)0xD001)
  22. #define MSIGX (*(unsigned char*)0xD010) /* MSB of sprite X coords */
  23. #define SPENA (*(unsigned char*)0xD015) /* sprite enable bits */
  24. #define SP0COL (*(unsigned char*)0xD027) /* sprite 0 color */
  25. #define BGCOL (*(unsigned char*)0xD021) /* background color */
  26. #define BORDER (*(unsigned char*)0xD020) /* border color */
  27. #define RASTER (*(unsigned char*)0xD012) /* current raster line */
  28. #define SCREEN ((unsigned char*)0x0400) /* screen RAM */
  29. #define COLRAM ((unsigned char*)0xD800) /* color RAM */
  30. #define SPR0PTR (*(unsigned char*)0x07F8) /* sprite 0 data pointer */
  31. /* CIA #1 $DC00 – joystick port 2 (active LOW) */
  32. #define JOY2 (*(unsigned char*)0xDC00)
  33. #define JOY_PORT2_UP 0x01
  34. #define JOY_PORT2_DOWN 0x02
  35. #define JOY_PORT2_LEFT 0x04
  36. #define JOY_PORT2_RIGHT 0x08
  37. #define JOY_PORT2_FIRE 0x10
  38. /* SID voice 1 */
  39. #define SID_FLO (*(unsigned char*)0xD400)
  40. #define SID_FHI (*(unsigned char*)0xD401)
  41. #define SID_CT (*(unsigned char*)0xD404)
  42. #define SID_AD (*(unsigned char*)0xD405)
  43. #define SID_SR (*(unsigned char*)0xD406)
  44. #define SID_VOL (*(unsigned char*)0xD418)
  45. /* ----------------------------------------------------------------
  46. Layout constants
  47. ---------------------------------------------------------------- */
  48. #define COLS 40
  49. #define ROWS 25
  50. #define TERRAIN_ROW 22 /* text row: top of terrain surface */
  51. #define PAD_COL 18 /* landing pad left column */
  52. #define PAD_W 4 /* landing pad width in characters */
  53. /* Sprite block 13 sits at $0340 (tape buffer area, safe to reuse) */
  54. #define SPR_BLOCK 13
  55. #define SPR_MEM ((unsigned char*)0x0340)
  56. /* Sprite Y where terrain surface begins (NTSC top ≈ 50, row*8 px) */
  57. #define GROUND_Y (50 + TERRAIN_ROW * 8) /* = 226 */
  58. /* Landing pad pixel X bounds (left border ≈ 24px for NTSC) */
  59. #define PAD_PIX_L (24 + PAD_COL * 8) /* 168 */
  60. #define PAD_PIX_R (24 + (PAD_COL + PAD_W) * 8) /* 200 */
  61. /* Fixed-point scale: multiply all positions/velocities by SCALE */
  62. #define SCALE 4
  63. /* ----------------------------------------------------------------
  64. Lander sprite: 24×21 hi-res, 63 bytes + 1 padding
  65. ----------------------------------------------------------------
  66. Looks like a rocket/lander body with two splayed landing legs.
  67. ---------------------------------------------------------------- */
  68. static const unsigned char spr_lander[64] = {
  69. 0x00, 0x18, 0x00, /* row 0 top nozzle */
  70. 0x00, 0x3C, 0x00, /* row 1 */
  71. 0x00, 0x7E, 0x00, /* row 2 */
  72. 0x01, 0xFF, 0x80, /* row 3 */
  73. 0x03, 0xFF, 0xC0, /* row 4 */
  74. 0x07, 0xFF, 0xE0, /* row 5 wide body */
  75. 0x03, 0xFF, 0xC0, /* row 6 */
  76. 0x01, 0xFF, 0x80, /* row 7 */
  77. 0x00, 0xFF, 0x00, /* row 8 */
  78. 0x00, 0x7E, 0x00, /* row 9 */
  79. 0x00, 0x3C, 0x00, /* row 10 */
  80. 0x00, 0x3C, 0x00, /* row 11 */
  81. 0x00, 0x3C, 0x00, /* row 12 */
  82. 0x01, 0xFF, 0x80, /* row 13 lower body */
  83. 0x03, 0xFF, 0xC0, /* row 14 */
  84. 0x07, 0xFF, 0xE0, /* row 15 */
  85. 0x06, 0x00, 0x60, /* row 16 leg struts */
  86. 0x0C, 0x00, 0x30, /* row 17 */
  87. 0x18, 0x00, 0x18, /* row 18 */
  88. 0x30, 0x00, 0x0C, /* row 19 */
  89. 0x60, 0x00, 0x06, /* row 20 footpads */
  90. 0x00 /* padding byte */
  91. };
  92. /* ----------------------------------------------------------------
  93. Thrust exhaust sprite: 24×21, placed below lander when firing
  94. ---------------------------------------------------------------- */
  95. static const unsigned char spr_exhaust[64] = {
  96. 0x00, 0x18, 0x00,
  97. 0x00, 0x3C, 0x00,
  98. 0x00, 0x7E, 0x00,
  99. 0x01, 0xFF, 0x80,
  100. 0x01, 0xE7, 0x80,
  101. 0x00, 0xC3, 0x00,
  102. 0x00, 0x42, 0x00,
  103. 0x00, 0x3C, 0x00,
  104. 0x00, 0x18, 0x00,
  105. 0x00, 0x00, 0x00,
  106. 0x00, 0x00, 0x00,
  107. 0x00, 0x00, 0x00,
  108. 0x00, 0x00, 0x00,
  109. 0x00, 0x00, 0x00,
  110. 0x00, 0x00, 0x00,
  111. 0x00, 0x00, 0x00,
  112. 0x00, 0x00, 0x00,
  113. 0x00, 0x00, 0x00,
  114. 0x00, 0x00, 0x00,
  115. 0x00, 0x00, 0x00,
  116. 0x00, 0x00, 0x00,
  117. 0x00
  118. };
  119. /* ----------------------------------------------------------------
  120. Game state
  121. ---------------------------------------------------------------- */
  122. static int px, py; /* lander sprite position × SCALE */
  123. static int vx, vy; /* velocity × SCALE */
  124. static int fuel; /* remaining fuel × SCALE */
  125. static int score;
  126. static unsigned char crashed;
  127. static unsigned char landed;
  128. /* ----------------------------------------------------------------
  129. Helpers
  130. ---------------------------------------------------------------- */
  131. /* Sync to raster line 250 – gives one tick per TV frame (≈50/60 Hz) */
  132. static void wait_frame(void)
  133. {
  134. while (RASTER != 250) {}
  135. while (RASTER == 250) {}
  136. }
  137. static void sid_thrust_on(void)
  138. {
  139. SID_VOL = 0x0F;
  140. SID_FLO = 0x60;
  141. SID_FHI = 0x00;
  142. SID_CT = 0x81; /* noise waveform + gate */
  143. SID_AD = 0x00;
  144. SID_SR = 0xF0;
  145. }
  146. static void sid_off(void)
  147. {
  148. SID_CT = 0x00; /* release gate */
  149. }
  150. static void sid_silence_all(void)
  151. {
  152. (*(unsigned char*)0xD404) = 0x00;
  153. (*(unsigned char*)0xD40B) = 0x00;
  154. (*(unsigned char*)0xD412) = 0x00;
  155. SID_VOL = 0x0F;
  156. }
  157. /* Draw two rows of terrain: solid blocks with a flat pad gap */
  158. static void draw_terrain(void)
  159. {
  160. unsigned char c;
  161. for (c = 0; c < COLS; c++) {
  162. unsigned int r1 = (unsigned int)TERRAIN_ROW * COLS + c;
  163. unsigned int r2 = (unsigned int)(TERRAIN_ROW + 1) * COLS + c;
  164. if (c >= PAD_COL && c < PAD_COL + PAD_W) {
  165. SCREEN[r1] = 0x2D; /* '-' in screen code = pad marker */
  166. COLRAM[r1] = 5; /* green */
  167. } else {
  168. SCREEN[r1] = 0xA0; /* solid block */
  169. COLRAM[r1] = 11; /* dark grey */
  170. }
  171. SCREEN[r2] = 0xA0;
  172. COLRAM[r2] = 11;
  173. }
  174. }
  175. static void draw_stars(void)
  176. {
  177. static const unsigned char sr[] = { 2, 4, 7, 10, 13, 3, 8, 15, 19, 6 };
  178. static const unsigned char sc[] = { 5, 15, 30, 8, 35, 22, 3, 18, 10, 37 };
  179. unsigned char i;
  180. for (i = 0; i < 10; i++) {
  181. unsigned int p = (unsigned int)sr[i] * COLS + sc[i];
  182. SCREEN[p] = 0x2E; /* '.' */
  183. COLRAM[p] = 1; /* white */
  184. }
  185. }
  186. /* Write sprite 0 position registers from internal fixed-point state */
  187. static void update_sprite(void)
  188. {
  189. unsigned int sx = (unsigned int)(px / SCALE);
  190. unsigned int sy = (unsigned int)(py / SCALE);
  191. if (sx > 255) {
  192. SP0X = (unsigned char)(sx - 256);
  193. MSIGX = MSIGX | 0x01;
  194. } else {
  195. SP0X = (unsigned char)sx;
  196. MSIGX = MSIGX & 0xFE;
  197. }
  198. SP0Y = (unsigned char)(sy > 247 ? 247 : sy);
  199. }
  200. /* Update exhaust sprite 1 (shown only while thrusting) */
  201. static void update_exhaust(unsigned char thrusting)
  202. {
  203. if (thrusting) {
  204. unsigned int sx = (unsigned int)(px / SCALE);
  205. unsigned int sy = (unsigned int)(py / SCALE) + 18;
  206. if (sx > 255) {
  207. (*(unsigned char*)0xD002) = (unsigned char)(sx - 256);
  208. MSIGX = MSIGX | 0x02;
  209. } else {
  210. (*(unsigned char*)0xD002) = (unsigned char)sx;
  211. MSIGX = MSIGX & 0xFD;
  212. }
  213. (*(unsigned char*)0xD003) = (unsigned char)(sy > 247 ? 247 : sy);
  214. SPENA = SPENA | 0x02;
  215. } else {
  216. SPENA = SPENA & 0xFD; /* hide exhaust sprite */
  217. }
  218. }
  219. static void draw_hud(void)
  220. {
  221. gotoxy(0, 0);
  222. cprintf("FUEL:%4d VY:%3d VX:%3d SC:%5d",
  223. fuel / SCALE, vy / SCALE, vx / SCALE, score);
  224. }
  225. static void center_msg(const char *msg, unsigned char row)
  226. {
  227. unsigned char len = (unsigned char)strlen(msg);
  228. unsigned char col = (COLS - len) / 2;
  229. gotoxy(col, row);
  230. textcolor(COLOR_WHITE);
  231. cputs(msg);
  232. }
  233. /* ----------------------------------------------------------------
  234. main
  235. ---------------------------------------------------------------- */
  236. int main(void)
  237. {
  238. unsigned char joy;
  239. unsigned char thrusting;
  240. unsigned char gravity_tick;
  241. unsigned char play_again;
  242. int lsx, lsy; /* lander sprite X/Y (not scaled) for collision */
  243. /* ---- Title screen ------------------------------------------ */
  244. clrscr();
  245. bgcolor(COLOR_BLACK);
  246. bordercolor(COLOR_BLACK);
  247. textcolor(COLOR_CYAN);
  248. center_msg("* MOONLANDER *", 4);
  249. textcolor(COLOR_YELLOW);
  250. center_msg("GUIDE YOUR SHIP TO THE GREEN PAD", 7);
  251. textcolor(COLOR_WHITE);
  252. center_msg("FIRE = UPWARD THRUST", 9);
  253. center_msg("LEFT/RIGHT = HORIZONTAL THRUST", 10);
  254. center_msg("LAND GENTLY TO SCORE POINTS", 11);
  255. center_msg("HARD LANDING OR MISSING PAD", 12);
  256. center_msg("MEANS CERTAIN DOOM!", 13);
  257. textcolor(COLOR_GREEN);
  258. center_msg("PRESS FIRE TO BEGIN", 17);
  259. /* Park lander off-screen during title */
  260. memcpy(SPR_MEM, spr_lander, 64);
  261. memcpy((unsigned char*)0x0380, spr_exhaust, 64);
  262. SPR0PTR = SPR_BLOCK; /* block 13 = lander */
  263. (*(unsigned char*)0x07F9) = 14; /* block 14 = exhaust */
  264. SP0COL = 7; /* yellow lander */
  265. (*(unsigned char*)0xD028) = 8; /* orange exhaust */
  266. SP0X = 0; SP0Y = 251;
  267. SPENA = 0x01;
  268. sid_silence_all();
  269. while (JOY2 & JOY_PORT2_FIRE) {} /* wait for fire press */
  270. while (!(JOY2 & JOY_PORT2_FIRE)) {} /* wait for release */
  271. play_again = 1;
  272. do {
  273. /* ---- Game setup -------------------------------------------- */
  274. clrscr();
  275. bgcolor(COLOR_BLACK);
  276. bordercolor(COLOR_BLACK);
  277. textcolor(COLOR_WHITE);
  278. draw_terrain();
  279. draw_stars();
  280. px = 160 * SCALE;
  281. py = 55 * SCALE;
  282. vx = 0;
  283. vy = 1; /* start with gentle downward drift */
  284. fuel = 200 * SCALE;
  285. score = 0;
  286. crashed = 0;
  287. landed = 0;
  288. gravity_tick = 0;
  289. SPENA = 0x01;
  290. update_sprite();
  291. update_exhaust(0);
  292. /* ---- Main game loop ---------------------------------------- */
  293. while (!crashed && !landed) {
  294. wait_frame();
  295. joy = JOY2;
  296. thrusting = 0;
  297. /* Upward thrust from fire button */
  298. if (!(joy & JOY_PORT2_FIRE) && fuel > 0) {
  299. vy -= 5;
  300. fuel -= SCALE;
  301. thrusting = 1;
  302. sid_thrust_on();
  303. } else {
  304. sid_off();
  305. }
  306. /* Horizontal thrust */
  307. if (!(joy & JOY_PORT2_LEFT) && fuel > 0) {
  308. vx -= 2;
  309. fuel -= 1;
  310. }
  311. if (!(joy & JOY_PORT2_RIGHT) && fuel > 0) {
  312. vx += 2;
  313. fuel -= 1;
  314. }
  315. if (fuel < 0) fuel = 0;
  316. /* Very gentle gravity: +1 sub-pixel every fourth frame */
  317. gravity_tick++;
  318. if (gravity_tick >= 4) {
  319. gravity_tick = 0;
  320. vy += 1;
  321. }
  322. /* Light horizontal damping (simulate RCS / air drag) */
  323. if (vx > 0) vx--;
  324. else if (vx < 0) vx++;
  325. /* Clamp velocities */
  326. if (vy > 32) vy = 32;
  327. if (vy < -24) vy = -24;
  328. if (vx > 16) vx = 16;
  329. if (vx < -16) vx = -16;
  330. /* Integrate position */
  331. px += vx;
  332. py += vy;
  333. /* Clamp to visible playfield (X: ~24..295 sprite pixels) */
  334. if (px < 24 * SCALE) { px = 24 * SCALE; vx = -vx / 2; }
  335. if (px > 295 * SCALE) { px = 295 * SCALE; vx = -vx / 2; }
  336. /* ---- Ground collision ---------------------------------- */
  337. lsx = (int)(px / SCALE);
  338. lsy = (int)(py / SCALE);
  339. /* Sprite is 21 px tall; check if bottom row reaches terrain */
  340. if (lsy + 21 >= GROUND_Y) {
  341. py = (GROUND_Y - 21) * SCALE;
  342. lsy = GROUND_Y - 21;
  343. /* Is lander center (X+12) over the pad? */
  344. if ((lsx + 12) >= PAD_PIX_L && (lsx + 12) <= PAD_PIX_R) {
  345. /* Safe if slow enough: vy <= 8, |vx| <= 6 */
  346. if (vy <= 8 && vx >= -6 && vx <= 6) {
  347. score = 1000 - (vy / SCALE) * 40 + (fuel / SCALE);
  348. if (score < 0) score = 0;
  349. landed = 1;
  350. } else {
  351. crashed = 1;
  352. }
  353. } else {
  354. crashed = 1; /* missed the pad */
  355. }
  356. }
  357. /* Don't let lander go above row 1 (HUD row) */
  358. if (py < 58 * SCALE) { py = 58 * SCALE; vy = 1; }
  359. update_sprite();
  360. update_exhaust(thrusting);
  361. draw_hud();
  362. }
  363. /* ---- Game over --------------------------------------------- */
  364. sid_off();
  365. if (landed) {
  366. SPENA = 0x01; /* keep lander visible */
  367. textcolor(COLOR_GREEN);
  368. center_msg("** SAFE LANDING! **", 9);
  369. {
  370. char buf[24];
  371. sprintf(buf, "YOUR SCORE: %d", score);
  372. textcolor(COLOR_YELLOW);
  373. center_msg(buf, 11);
  374. }
  375. textcolor(COLOR_WHITE);
  376. center_msg("NICE FLYING, PILOT!", 13);
  377. } else {
  378. SPENA = 0x00; /* hide sprite – it exploded */
  379. /* Draw explosion at impact site */
  380. {
  381. unsigned char ex_col = (unsigned char)((px / SCALE - 24) / 8);
  382. unsigned int ep;
  383. if (ex_col >= COLS) ex_col = COLS - 1;
  384. ep = (unsigned int)(TERRAIN_ROW - 1) * COLS + ex_col;
  385. SCREEN[ep] = 0x51; /* '*' in PETSCII screen code */
  386. COLRAM[ep] = 2; /* red */
  387. }
  388. textcolor(COLOR_RED);
  389. center_msg("** CRASH! **", 9);
  390. textcolor(COLOR_WHITE);
  391. center_msg("NO SCORE FOR YOU.", 11);
  392. }
  393. textcolor(COLOR_GREEN);
  394. center_msg("FIRE = PLAY AGAIN", 16);
  395. textcolor(COLOR_WHITE);
  396. center_msg("LEFT = QUIT", 17);
  397. while (!(JOY2 & JOY_PORT2_FIRE) || !(JOY2 & JOY_PORT2_LEFT)) {}
  398. do {
  399. joy = JOY2;
  400. } while ((joy & JOY_PORT2_FIRE) && (joy & JOY_PORT2_LEFT));
  401. play_again = !(joy & JOY_PORT2_FIRE);
  402. while (!(JOY2 & JOY_PORT2_FIRE) || !(JOY2 & JOY_PORT2_LEFT)) {}
  403. } while (play_again);
  404. /* ---- Return to BASIC --------------------------------------- */
  405. SPENA = 0x00;
  406. sid_silence_all();
  407. clrscr();
  408. bgcolor(6); /* restore C64 default blue */
  409. bordercolor(14);
  410. return 0;
  411. }

Powered by TurnKey Linux.