#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;
}