#define VERSION "V1.4" /* * Ersetz * * Game Design by Der Luchs * Code by Graham Toal * Music by Brett Walach * * Changelog * --------- * v0.9 - Initial Release without sound * v1.0 - Added music and death sound * v1.1 - Fixes bug on game over screen, could not restart after waiting too long * v1.2 - Fixes music loop to be more on beat * v1.3 - Merged with GT original * v1.4 - bug fix for timer wraparound */ #include <vectrex.h> #include <assert.h> #include <ymPlayerOptimSpeed.h> #ifndef FALSE #define FALSE (0!=0) #define TRUE (0==0) #endif #include "controller.h" #include "spritelib.h" #include "music.h" #define SC 4 const int quad[] = { /* * (0,8) ^ 4 v *<-- 4 --> + <-- 4 -->* (8,0) (-8,0) ^ | | 10 | | | v * (0,-20) */ 0,10*SC,0, -1,-10*SC,-8*SC, -1,-22*SC,8*SC, -1,22*SC,8*SC, -1,10*SC,-8*SC, 1 }; const int sm_tri[] = { /* * (0,2) (-3,0) * + * (3,0) */ 0, 2*SC,0, -1,-2*SC,-4*SC, 0,0,8*SC, -1,2*SC,-4*SC, 1 }; const int lg_tri[] = { /* * (0,8) (-3,0) * + * (3,0) */ 0, 8*SC,0, -1, -8*SC,-3*SC, 0, 0,6*SC, -1, 8*SC,-3*SC, 1 }; const MovableVLp smLeftVLp = { .points = ((sizeof sm_tri)-1)/3, .packet_data = sm_tri }; const MovableVLp lgLeftVLp = { .points = ((sizeof lg_tri)-1)/3, .packet_data = lg_tri }; const MovableVLp smRightVLp = { .points = ((sizeof sm_tri)-1)/3, .packet_data = sm_tri }; const MovableVLp lgRightVLp = { .points = ((sizeof lg_tri)-1)/3, .packet_data = lg_tri }; const MovableVLp QuadVLp = { .points = ((sizeof quad)-1)/3, .packet_data = quad }; static Rotatable_VLp_Object Quad = { RotatableVL, // PRIMITIVE type; 0, 0, // int y, x; 63+128, // unsigned int angle; 0, // int deltaangle; 0x6f, // unsigned int Intensity; 127, 127/5, // unsigned int move_scale, draw_scale; (MovableVLp *)&QuadVLp // object data }; static const int wallVLp[] = { //127, // scale 0, -63,-51, // moveto -1,127,0, // lineto 0,-127,0, 0, 0,102, // moveto -1,127,0, // lineto 1 // end }; static FixedVLp walls = { (int *)wallVLp }; static Fixed_VLp_Object Walls = { FixedVL, 127, &walls }; static Rotatable_VLp_Object Tri[4] = { { RotatableVL, // PRIMITIVE type; 0, -100, // int y, x; 63, // unsigned int angle; 0, // int deltaangle; 0x6f, // unsigned int Intensity; 127, 127/5, // unsigned int move_scale, draw_scale; (MovableVLp *)&smLeftVLp // object data }, { RotatableVL, // PRIMITIVE type; 127, -100, // int y, x; 64, // unsigned int angle; 0, // int deltaangle; 0x6f, // unsigned int Intensity; 127, 127/5, // unsigned int move_scale, draw_scale; (MovableVLp *)&smLeftVLp // object data }, { RotatableVL, // PRIMITIVE type; -60, 100, // int y, x; 64+128, // unsigned int angle; 0, // int deltaangle; 0x6f, // unsigned int Intensity; 127, 127/5, // unsigned int move_scale, draw_scale; (MovableVLp *)&smRightVLp // object data }, { RotatableVL, // PRIMITIVE type; 60, 100, // int y, x; 64+128, // unsigned int angle; 0, // int deltaangle; 0x6f, // unsigned int Intensity; 127, 127/5, // unsigned int move_scale, draw_scale; (MovableVLp *)&smRightVLp // object data } }; /* letter grid: * * * 4 * * * 3 * * * 2 * * * 1 * * * 0 0 1 2 */ #define LX 30 #define LY 30 static const int E_VLp[] = { 0, 1*LY,2*LX, -1, -1*LY,-2*LX, -1, 4*LY,0*LX, -1, -1*LY,2*LX, 0, -1*LY,-2*LX, -1, 0*LY,2*LX, 1 }; static const int R_VLp[] = { -1, 4*LY,0*LX, -1, -1*LY,2*LX, -1, -1*LY,-2*LX, -1, -2*LY,2*LX, 1 }; static const int S_VLp[] = { -1, 2*LY,2*LX, -1, 0*LY,-2*LX, -1, 2*LY,2*LX, 1 }; static const int T_VLp[] = { 0, 0*LY,1*LX, -1, 4*LY,0*LX, -1, -1*LY,1*LX, 0, 0*LY,-2*LX, -1, 1*LY,1*LX, 1 }; static const int Z_VLp[] = { 0, 0*LY,2*LX, -1, 0*LY,-2*LX, -1, 4*LY, 2*LX, -1, 0*LY, -2*LX, 1 }; const int *ERSETZT[] = { E_VLp, R_VLp, S_VLp, E_VLp, T_VLp, Z_VLp, T_VLp }; /* PRIMITIVE type; // == RotatableVL int y, x; unsigned int angle; int deltaangle; unsigned int Intensity; unsigned int move_scale, draw_scale; MovableVLp *data; int points; // count of points in the VLp list - required. const int *packet_data; */ void bump_score(char *n) { // Add a fixed 11 points n[3] += 1; if (n[3] > '9') { n[3] -= 10; n[2] += 1; } n[2] += 1; if (n[2] > '9') { n[2] -= 10; n[1] += 1;} if (n[1] > '9') { n[1] -= 10; n[0] += 1;} if (n[0] > '9') { n[3] = n[2] = n[1] = n[0] = '0'; } } int Pointing; #define LEFT 0 #define RIGHT 1 int HIT(void) { int i, low, high; if (Quad.deltaangle == 0) { // horizontal if (Pointing == LEFT) { low = 0; high = 1; } else { low = 2; high = 3; } for (i = low; i <= high; i++) { if ((abs(Tri[i].y) < 10) && (Tri[i].Intensity)) { return TRUE; } } } else if (Quad.deltaangle == 4) { if (Pointing == RIGHT) { // currently moving from right to left low = 0; high = 1; } else { low = 2; high = 3; } for (i = low; i <= high; i++) { if ((Tri[i].Intensity) && (Tri[i].y == 0 || Tri[i].y == -1 || Tri[i].y == -2) && ((Quad.angle == 63-4) || (Quad.angle == 63+128+4))) { // one tick below horizontal return TRUE; } } } return FALSE; } static void FakeWaitRecal(void) { DP_to_C8(); Explosion_Snd(current_explosion); Init_Music_chk(current_music); UpdateDisplay(); if (pause_ym_sound == 0) { ym_sound(); } else { // unpause our main tune if expl & music are not playing if (Vec_Music_Flag == 0 && Vec_Expl_Timer == 0) { // pause_ym_sound = 0; return;// 1; } } if (ym_cdata == 0) // song ended { ym_init((void*)song.data); /* 'C' callable init, sound data */ } Do_Sound(); } void offer_credits(void) { if (buttons_pressed()&1) { unsigned int text_timer = 255; int old_text_height = Vec_Text_Height; int old_text_width = Vec_Text_Width; do { Wait_Recal(); // raw Reset0Ref(); InternalSetScale(127); Intensity_a(0x70); Vec_Text_Height = -8; Print_Str_d(55, -67, "ERSETZT " VERSION "\x80"); Vec_Text_Width = 80; Vec_Text_Height = -3; Print_Str_d(30, -55, "GAME BY\x80"); Print_Str_d(20, -65, "DER LUCHS\x80"); Print_Str_d(0, -55, "CODE BY\x80"); Print_Str_d(-10, -65, "GRAHAM TOAL\x80"); Print_Str_d(-30, -55, "MUSIC BY\x80"); Print_Str_d(-40, -65, "BRETT WALACH\x80"); } while (--text_timer != 0); Vec_Text_Height = old_text_height; Vec_Text_Width = old_text_width; } } int main(void) { static int init = 0; /* Still to do: Music player option TITLETRACK then TRACK01 .. TRACK 10 GAME OVER |< >| */ char Score[6] = { '0', '0', '0', '0', 0x80, 0 }; long timer, trigger[2] = {-1, -1}; // RIGHT, LEFT int side, i; int TRI[6], WALLS=0, QUAD=0; RESTART: InitUniverse(); // Positioning scale for objects is currently hard-coded as 127 - to be improved? // Other scales are per-object // Create and enable your sprites: WALLS = Post((Object *)&Walls, 0, 0, 127); QUAD = Post((Object *)&Quad, 0, 0, 127); for (i = 0; i < 4; i++) { TRI[i] = Post((Object *)&Tri[i], Tri[i].y, Tri[i].x, 127); } trigger[1] = trigger[0] = -1; //left = 1; right = 3; // index of next triangle to drop off... Score[3] = Score[2] = Score[1] = Score[0] = '0'; Brightness(TRI[0], 0);Brightness(TRI[1], 0); // easy start with nothing on LHS Brightness(TRI[2], 0);Brightness(TRI[3], 0); // easy start with nothing on RHS // Kick off the sound! song.data = &(mad_max_enchanted_lands7_data); ym_init((void*)song.data); // only init music once sound_init(); pause_ym_sound = 0; explosion_done = 0; // Big title screen. Choose game or music. Only game implemented so far. for (;;) { static int choice = 1; int x = -127; Wait_Recal(); // real one, so no sprite support here, no sound. check_buttons(); Intensity_a(0x70); for (i = 0; i < 7; i++) { Reset0Ref(); InternalSetScale(0x7f); Moveto_d(8, x); InternalSetScale(0x42); Draw_VLp((int *)ERSETZT[i]); x += 39; } InternalSetScale(0x7f); offer_credits(); // B1 during "ERSETZT" message gives credits if (buttons_pressed()&(2|4)) { choice = -choice; } Reset0Ref(); Intensity_a((choice>0?0x7fU:0x50U)); Print_Str_d(-8, -26, "START\x80"); Reset0Ref(); Intensity_a((choice>0?0x50U:0x7fU)); Print_Str_d(-30, -60, "MUSIC PLAYER\x80"); if (buttons_pressed()&8) { if (choice > 0) break; unsigned int text_timer = 255; int old_text_height = Vec_Text_Height; int old_text_width = Vec_Text_Width; do { Wait_Recal(); // raw Reset0Ref(); InternalSetScale(127); Intensity_a(0x70); Vec_Text_Height = -8; Print_Str_d(55, -65, "MUSIC PLAYER?\x80"); Vec_Text_Width = 80; Vec_Text_Height = -3; Print_Str_d(30, -100, "WHAT MUSIC PLAYER?!\x80"); Print_Str_d(-10, -100, "WE HAVEN'T WRITTEN \x80"); Print_Str_d(-22, -70, "THAT BIT YET\x80"); } while (--text_timer != 0); Vec_Text_Height = old_text_height; Vec_Text_Width = old_text_width; } } // end of "ERSETZ" startup. Play game. Pointing = LEFT; timer = 0L; for (;;) { FakeWaitRecal(); Reset0Ref(); Intensity_a(0x50); Print_Str_d(127, -30, Score); // RasterString not yet in library if ((timer > 25L) && HIT()) { pause_ym_sound = 1; play_explosion(&bang); for (i = 0; i < 4; i++) Unpost(TRI[i]); Unpost(WALLS); Unpost(QUAD); // Would unpost score if strings were supported timer = 0; for (;;) { int old_text_height = Vec_Text_Height; int old_text_width = Vec_Text_Width; if (timer < 100) timer += 1L; // stops it going negative after 65536 ticks FakeWaitRecal(); Reset0Ref(); Intensity_a(0x50); Print_Str_d(127, -30, Score); // PrintRaster string not yet in library Intensity_a(0x70); Print_Str_d(8, -60, "GAME OVER\x80"); Vec_Text_Width = 60; Vec_Text_Height = -3; Print_Str_d(-30, -122, "(PRESS ANY BUTTON TO RESTART)\x80"); Vec_Text_Height = old_text_height; Vec_Text_Width = old_text_width; init = 0; // just a short lockout to ensure they see the "GAME OVER" message. if ((timer > 30L) && (buttons_pressed()&15)) goto RESTART; } } if ((Quad.angle == 63) && (Pointing == RIGHT)) { Quad.deltaangle = 0; Pointing = LEFT; bump_score(Score); } else if ((Quad.angle == 63+128) && (Pointing == LEFT)) { Quad.deltaangle = 0; Pointing = RIGHT; bump_score(Score); } if ((buttons_pressed()&15) && (Quad.deltaangle == 0)) { if (Pointing == LEFT) Quad.deltaangle = -4; if (Pointing == RIGHT) Quad.deltaangle = 4; } for (i = 0; i < 4; i++) { Tri[i].y = (Tri[i].y - 3); if (Tri[i].y > 32) Tri[i].data = (MovableVLp *)(i < 2 ? &smLeftVLp : &smRightVLp); else if (Tri[i].y < -125) { Brightness(TRI[i], 0x00); // I'm using brightness as a flag for whether the triangle // is active or not, it's cheap to draw with 0 intensity // and less complicated than Unpost-ing/Post-ing. I guess // this is telling me I need to add a 'hide/unhide' option... } else if (Tri[i].y < -12) Tri[i].data = (MovableVLp *)(i < 2 ? &smLeftVLp : &smRightVLp); else if (Tri[i].y < 32) Tri[i].data = (MovableVLp *)(i < 2 ? &lgLeftVLp : &lgRightVLp); } // last action: replenish the missing triangles // Both triangles missing: for (side = 0; side < 3; side += 2) { if (!Tri[side].Intensity && !Tri[side+1].Intensity) { // kick one off right away trigger[side>>1] = timer + (Random()&31); // DANGER WILL ROBINSON! If playing a long time, the timer can go negative. // at 50Hz frame rate that would be 32767/50 = 650 seconds or 10 minutes. // Is anyone likely to want to play a single round that long? // (timer is reset on each round) // Maybe the game should be modified slightly to put a time limit on a round // and see how high you can score within that time. // Also to do: speed the movement up after a certain time. // Brett has a faster tune we can slip in when that happens! (in his ersetz1.3 zip) } int this_side, that_side, other; if (!Tri[side].Intensity) this_side = side; else this_side = side+1; // right is the one to add. other = this_side^1; that_side = (this_side&2)^2; if ((trigger[side>>1] == -1) && (!Tri[side].Intensity || !Tri[side+1].Intensity)) { if ( (Tri[other].y < 50) && (!Tri[that_side].Intensity || Tri[that_side].y < 50) && (!Tri[that_side^1].Intensity || Tri[that_side^1].y < 50) ) { // first determine minimum spacing trigger[side>>1] = timer + (Random()&31); } } if ((timer >= trigger[side>>1]) && (!Tri[other].Intensity || Tri[other].y < 50) && (!Tri[that_side].Intensity || Tri[that_side].y < 50) && (!Tri[that_side^1].Intensity || Tri[that_side^1].y < 50) ) { if (!Tri[side+1].Intensity) { Tri[side+1].y = 120; Brightness(TRI[side+1], 0x7F); } else if (!Tri[side].Intensity) { Tri[side].y = 120; Brightness(TRI[side], 0x7F); } trigger[side>>1] = -1; } } timer += 1L; } return 0; }