|
Post by nexus6 on Nov 5, 2022 13:50:15 GMT -5
I'm interested in programming a Vectrex game, but I have only little programming experience. Therefore, C would certainly be more suitable for me than assembler. I found a nice tutorial for a Pong game on the Vectrex on Youtube: youtu.be/I3f8qO8AU_k The problem is, that the tutorial was made for Roger Boesch's "TheClassicCoder" app, which unfortunately is only available for macOS. For Windows there is "Vide" from Malban, where you can also program in C. Unfortunately, the two programs are not compatible. When I copy the tutorial's code ( github.com/rogerboesch/vectrex-dev/commit/9d0000174783e1d40e1dfd665663955b348c3dcc ) into Vide (The default header is obviously different, so I left it out) I get errors. Can the code changed Vide-conform or are the differences too big?
|
|
|
Post by Malban on Nov 7, 2022 5:08:45 GMT -5
Hi, I am probably biased, since I wrote Vide. My "thing" about Rogers classic coder: If it were completely standalone in the Vectrex world - his IDE would be fantastic, it is great that exists and that more people than just me care about others coding Vectrex. I have been in contact with Roger before and he is a great guy. BUT! I have to say personaly I don't like the way he implemented things in his environment. a) Usage of CMOC While it works and compiles ok - gcc6809 is for "our" purpose (at this time at least) better. It optimizes better and you have more possibilities to tweak the code. For example CMOC does not support register calling convention. This means each and every subfunction creates its own stack environment. This can be terrible performance wise. CMOC supports not the complete "C" language - only parts of it... to name a few handycaps - there are more... b) function names There are basically two widely used function names when it comes to vectrex. BIOS functions that is. 1) Bruce Tomlin names These are the onew I grew up with and which are supported by Vide. These function names were given by Bruce Tomlin, when he disassembled (based on Fred Tafts disassembly) the original BIOS. At that time there was no copy of the original BIOS source code available. I would say about 80% of all homebrew scene use these BIOS names. 2) Original Bios names At some stage (10-15 years ago?) copies of the original BIOS source became available alongside with PDF files with instructions to the functions. These names are a bit more "cryptic" since - well the eighties and 8 letters per name or so. But there are quite a few programmers out there who use these names. -> Rogers implementation uses yet another set of function names. They are similar to Bruce Tomlins names - but differ enough to not be compatible. Also - his naming convention is inconsistent: example of his first tutorial: // Initialise void vectrex_init() { set_beam_intensity(DEFAULT_INTENSITY); set_scale(DEFAULT_SCALE);
stop_music(); stop_sound();
controller_enable_1_x(); controller_enable_1_y(); controller_enable_2_x(); controller_enable_2_y(); } THREE different function "sets": 1) "setters" 2) "start" / "stop" 3) enablers without parameters Something consistent would be: set_beam_intensity(127); set_music(FALSE); set_controller(1, HORIZONTAL_AXIS, TRUE); It is confusing... especially to "noobs" (IMHO). --- There are BASIC Vectrex truths every (even noobs as you call it) should know. Or at least start with. Especially if you start with a programming environment that (again IMHO) can not carry you to build performant games. If you just want to dabble a week.... it might be Ok. One of the BASIC truths with Vectrex is that Y position ALLWAYS goes before X position. Personally I find it terrible to see in a Vectrex tutorial something like: // Game structure used to hold data for each game object struct game_object { int8_t x; int8_t y; uint8_t speed; int8_t scale;
int8_t x1, y1; // top-left int8_t x2, y2; // bottom-right };
I know because of school, university or textbooks you learn the convention x,y. BUT If you dabble deeper (not really to deep) you will notice, that data structur order y,x is (regarding vectrex programming) ALWAYS preferable to the other way around (no, I am not even writing that down). This is not negotiable - since it is actually a hardware fact! (Yes it runs. You can do it if you must. But when I try to teach something, I always try to teach the right way... and not something that if you want to dig deeper later - you have to throw overboard and learn again). However... There are also some draw backs to 6809 gcc setup. In general you do not have any standard C-libraries - or functions. e.g. the sprintf() function Roger uses in his debug outputs is not available. Also I have not been able to get elipsis to work. If you want something like that - you have to program your own routines. (or use routines already programmed..., by now there are quite a few complete "C" sources for the Vectrex available to learn from or rip appart) BUGS in Rogers source: He calls the object draw with a pointer of a pointer: game_object_draw(&ball, &ball_vertices); correct this should be: game_object_draw(&ball, ball_vertices); The intensity must be set each game round AFTER the input - questioning Joyport 2 destroys intensity settings! ------------------- Nonetheless If you create in Vide a C project, than following changes will make Pong run: (Exception scoring, since I didn't implement a working sprintf function) Basically mapping functions / names: // ----------------------------------
#include "controller.h" #define uint8_t unsigned int #define int8_t signed int #define BOOL int #define TRUE 1 #define FALSE 0 #define abs(a) (a)>0?(a):-(a)
#define zero_beam Reset0Ref #define print_str_c(y,x,s) Print_Str_d(((const int)(y)), ((const int)(x)), s) #define moveto_d Moveto_d #define draw_vlc(l) Draw_VLc((void* const)(l)) #define set_beam_intensity Intensity_a #define set_scale(s) VIA_t1_cnt_lo=((unsigned int)(s)) #define stop_music Init_Music_Buf #define stop_sound Clear_Sound #define wait_recal Wait_Recal
#define controller_check_joysticks Joy_Digital #define controller_joystick_1_down joystick_1_down #define controller_joystick_1_up joystick_1_up #define controller_joystick_2_down joystick_2_down #define controller_joystick_2_up joystick_2_up
#define controller_enable_1_x enable_controller_1_x #define controller_enable_1_y enable_controller_1_y #define controller_enable_2_x enable_controller_2_x #define controller_enable_2_y enable_controller_2_y
#define JOYSTICK_1 0 #define JOYSTICK_2 1
// dummy! int sprintf(char * dest, const char *src, ...) { (void) src; dest[0] = '0'; dest[1] = '0'; dest[2] = '0'; dest[3] = 0x80; return 0; }
// --------------------------------- The complete "main" sources (with above mentioned bug fixes): // *************************************************************************** // main // *************************************************************************** // This file was developed by Prof. Dr. Peer Johannsen as part of the // "Retro-Programming" and "Advanced C Programming" class at // Pforzheim University, Germany. // // It can freely be used, but at one's own risk and for non-commercial // purposes only. Please respect the copyright and credit the origin of // this file. // // Feedback, suggestions and bug-reports are welcome and can be sent to: // peer.johannsen@pforzheim-university.de // ---------------------------------------------------------------------------
#include <vectrex.h> #include <assert.h> // As default assertions are enabled. // to disable do a // "#define NDEBUG" // or set the gcc option "-D NDEBUG" (Vide project file)
// --------------------------------------------------------------------------- // cold reset: the vectrex logo is shown, all ram data is cleared // warm reset: skip vectrex logo and keep ram data // --------------------------------------------------------------------------- // at system startup, when powering up the vectrex, a cold reset is performed // if the reset button is pressed, then a warm reset is performed // --------------------------------------------------------------------------- // after each reset, the cartridge title is shown and then main() is called // ---------------------------------------------------------------------------
/* #pragma vx_copyright "2020" #pragma vx_title_pos 0,-80 #pragma vx_title_size -8, 80 #pragma vx_title "g PONG" #pragma vx_music vx_music_1 */
#include "controller.h" #define uint8_t unsigned int #define int8_t signed int #define BOOL int #define TRUE 1 #define FALSE 0 #define abs(a) (a)>0?(a):-(a)
#define zero_beam Reset0Ref #define print_str_c(y,x,s) Print_Str_d(((const int)(y)), ((const int)(x)), s) #define moveto_d Moveto_d #define draw_vlc(l) Draw_VLc((void* const)(l)) #define set_beam_intensity Intensity_a #define set_scale(s) VIA_t1_cnt_lo=((unsigned int)(s)) #define stop_music Init_Music_Buf #define stop_sound Clear_Sound #define wait_recal Wait_Recal
#define controller_check_joysticks Joy_Digital #define controller_joystick_1_down joystick_1_down #define controller_joystick_1_up joystick_1_up #define controller_joystick_2_down joystick_2_down #define controller_joystick_2_up joystick_2_up
#define controller_enable_1_x enable_controller_1_x #define controller_enable_1_y enable_controller_1_y #define controller_enable_2_x enable_controller_2_x #define controller_enable_2_y enable_controller_2_y
#define JOYSTICK_1 0 #define JOYSTICK_2 1
// dummy! int sprintf(char * dest, const char *src, ...) { (void) src; dest[0] = '0'; dest[1] = '0'; dest[2] = '0'; dest[3] = 0x80; return 0; }
// Screen settings const uint8_t screen_width = 255; const uint8_t screen_height = 255; const int8_t screen_max_x = 127; const int8_t screen_min_x = -128; const int8_t screen_max_y = 127; const int8_t screen_min_y = -128;
#define DEFAULT_SCALE 0x7F #define DEFAULT_INTENSITY 0x7F #define PLAYER_1 JOYSTICK_1 #define PLAYER_2 JOYSTICK_2
// Game settings int8_t paddle_speed = 4; int8_t ball_speed_x = 2; int8_t ball_speed_y = 1; int8_t score_player1 = 0; int8_t score_player2 = 0;
// Game structure used to hold data for each game object struct game_object { int8_t x; int8_t y; uint8_t speed; int8_t scale;
int8_t x1, y1; // top-left int8_t x2, y2; // bottom-right };
// Game variables static struct game_object paddle[2]; static struct game_object ball; static struct game_object border;
// Vertices used for later drawing static const int8_t paddle_vertices[] = { 3, // Size 0, 5, // Top left is local (0,0) -20, 0, 0, -5, 20, 0 };
static const int8_t ball_vertices[] = { 4, 4 / 2, 4 / 2, -4, -4, 4 / 2, 4 / 2, -4 / 2, 4 / 2, 4, -4, };
static const int8_t border_vertices[] = { 7, 127, 0, 127, 0, 0, 127, 0, 127, -128, 0, -128, 0, 0, -128, 0, -128, };
// Debug helpers void debug_str(uint8_t line, char* str) { zero_beam();
print_str_c((line*20)-120, -120, str); }
void game_object_dump(uint8_t line, struct game_object *object) { zero_beam();
char temp[100]; sprintf(temp, "X:%d,Y:%d", object->x, object->y); debug_str(line, temp);
sprintf(temp, "X1:%d,Y1:%d,X2:%d,Y2:%d", object->x1, object->y1, object->x2, object->y2); debug_str(line+1, temp); }
// Game object helpers BOOL game_object_is_colliding(struct game_object *object1, struct game_object *object2) { return ((object1->x + 128 + object1->x1) < (object2->x + 128 + object2->x2) && // Left vs. Right (object1->x + 128 + object1->x2) > (object2->x + 128 + object2->x1) && // Right vs. Left (object1->y + 128 - object1->y2) < (object2->y + 128 + object2->y1) && // Bottom vs. Top (object1->y + 128 + object1->y1) > (object2->y + 128 - object2->y2)); // Top vs. Bottom }
void game_object_draw(struct game_object *object, const int8_t vertices[]) { zero_beam(); set_scale(object->scale); moveto_d(object->y, object->x); draw_vlc(vertices); }
// Game specific helpers BOOL paddle_is_on_top(uint8_t number) { int8_t top = paddle[number].y + paddle[number].y1; if (top <= (screen_max_x - paddle_speed)) return FALSE; return TRUE; }
BOOL paddle_is_on_bottom(uint8_t number) { int8_t bottom = paddle[number].y - paddle[number].y2;
if (bottom >= (screen_min_y + paddle_speed)) return FALSE;
return TRUE; }
void collission_detection() { if (game_object_is_colliding(&ball, &paddle[PLAYER_1])) { ball_speed_x = -ball_speed_x;
while (game_object_is_colliding(&ball, &paddle[PLAYER_1])) { ball.x += ball_speed_x; ball.y += ball_speed_y; } }
if (game_object_is_colliding(&ball, &paddle[PLAYER_2])) { ball_speed_x = -ball_speed_x;
while (game_object_is_colliding(&ball, &paddle[PLAYER_2])) { ball.x += ball_speed_x; ball.y += ball_speed_y; } } }
void check_ball_position() { int ballSpeedX = abs(ball_speed_x); int ballSpeedY = abs(ball_speed_y);
// Bounce ball if ((ball.y + ball.y1 >= (screen_max_y - ballSpeedY)) || ball.y + ball.y2 <= (screen_min_y + ballSpeedY)) {
ball_speed_y = -ball_speed_y; ball.x += ball_speed_x; ball.y += ball_speed_y; }
// Ball touches border: Point for player1 if left border, otherwise for player 2 BOOL ball_is_on_right_side = ball.x + ball.x2 >= (screen_max_x - ballSpeedX); if (ball_is_on_right_side || ball.x + ball.x1 <= (screen_min_x + ballSpeedX)) { ball_speed_x = -ball_speed_x;
ball.x += ball_speed_x; ball.y += ball_speed_y;
if (ball_is_on_right_side) { ++score_player1; } else { ++score_player2; } } }
// Initialize void vectrex_init() { set_beam_intensity(DEFAULT_INTENSITY); set_scale(DEFAULT_SCALE);
stop_music(); stop_sound();
controller_enable_1_x(); controller_enable_1_y(); controller_enable_2_x(); controller_enable_2_y(); }
// Game logic void game_init() { paddle[PLAYER_1].x = -100; paddle[PLAYER_1].y = 0; paddle[PLAYER_1].x1 = 0; paddle[PLAYER_1].y1 = 0; paddle[PLAYER_1].x2 = 5; paddle[PLAYER_1].y2 = 20; paddle[PLAYER_1].scale = DEFAULT_SCALE;
paddle[PLAYER_2].x = 100; paddle[PLAYER_2].y = 0; paddle[PLAYER_2].x1 = 0; paddle[PLAYER_2].y1 = 0; paddle[PLAYER_2].x2 = 5; paddle[PLAYER_2].y2 = 20; paddle[PLAYER_2].scale = DEFAULT_SCALE;
ball.x = 0; ball.y = 50; ball.x1 = 0; ball.y1 = 0; ball.x2 = 8; ball.y2 = 8; ball.scale = DEFAULT_SCALE;
border.x = -127; border.y = -127; border.scale = DEFAULT_SCALE; }
void game_input() { controller_check_joysticks();
controller_enable_1_x(); controller_enable_1_y(); controller_enable_2_x(); controller_enable_2_y();
if (controller_joystick_1_up() && !paddle_is_on_top(PLAYER_1)) { paddle[PLAYER_1].y += paddle_speed; } else if (controller_joystick_1_down() && !paddle_is_on_bottom(PLAYER_1)) { paddle[PLAYER_1].y -= paddle_speed; }
if (controller_joystick_2_up() && !paddle_is_on_top(PLAYER_2)) { paddle[PLAYER_2].y += paddle_speed; } else if (controller_joystick_2_down() && !paddle_is_on_bottom(PLAYER_2)) { paddle[PLAYER_2].y -= paddle_speed; } }
void game_update() { ball.x += ball_speed_x; ball.y += ball_speed_y;
check_ball_position(); collission_detection(); }
void game_draw() { game_object_draw(&border, border_vertices); game_object_draw(&paddle[PLAYER_1], paddle_vertices); game_object_draw(&paddle[PLAYER_2], paddle_vertices); game_object_draw(&ball, ball_vertices);
char temp[40];
zero_beam(); sprintf(temp, "%d ", score_player1); print_str_c(120, -125, temp);
zero_beam(); sprintf(temp, " %d", score_player2); print_str_c(120, 80, temp); }
// Game loop int main() { vectrex_init();
game_init();
while (TRUE) { wait_recal();
game_input(); game_update(); set_beam_intensity(DEFAULT_INTENSITY); game_draw(); }
return 0; } // *************************************************************************** // end of file // ***
************************************************************************
Malban
|
|
|
Post by nexus6 on Nov 7, 2022 8:21:53 GMT -5
Thank you so much for the very detailed answer and for changing the code. That will definitely help me. Interesting that the original BIOS was available only 10-15 years ago.
|
|
|
Post by Peer on Nov 7, 2022 11:53:44 GMT -5
There are also some draw backs to 6809 gcc setup. In general you do not have any standard C-libraries - or functions. e.g. the sprintf() function Roger uses in his debug outputs is not available. Personally, when programming C for the Vectrex, I never ever needed any part(s) of the standard C-libraries.
Self-written print functions and debugging macros (e.g. assert) are available as gcc6809 link libraries. Those are included in Vide, or at least I thought they are. I have to check...
|
|
|
Post by D-Type on Nov 7, 2022 16:49:44 GMT -5
"...naming convention is inconsistent..."
Don't look at the Forth community, you'll run away screaming.
40 years on and people are still arguing every single day about whether the standard language should include local variables over factoring your code, using tail recursion over loops, using 1KiB source code blocks over plain text files, using scaled integer over floating point, should there be a case/switch statement...the list is endless.
The old adage "When you've seen one Forth, you've see one Forth" is absolutely true; e.g. there are 20+ different Object Oriented Forth extensions.
But these aspects are what keep the language alive and make it exciting to use, live coding with no limitations or safety belt!
|
|