Jump to content
rchennau

Atari Programming in CC65

Recommended Posts

I am stil hacking away... I've searched far and wide for CC65 examples that were very basic. I found some but I decided to write one as I go. Here is the result.

 

 

#include <atari.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <conio.h>  // standard cc65 for 'console' based applications


/***************************************************************
* #define SAVMSC is a cc65 MACRO for the Atari 8-BIT platform. *
* SAVMSC points to hardware register at memory location 88.    *
* refer to [url="http://www.atariarchives.org/mapping/memorymap.php"]http://www.atariarchives.org/mapping/memorymap.php[/url]  *
*                                                              *
***************************************************************/
The following define for SAVMSC is to assign a pointer
#define SAVMSC *(unsigned int *) 88 // pointer to atari hardware register that holds the screen position address

/***************************************************************
* __fastcall__ is a cc65 preprocessor command to place the     *
* the variable referenced at the top of the memory heap thus   *
* enabling faster access by the cpu                            *
***************************************************************/
void __fastcall__ statusLine(int x, int y, int capture);  // debug line to display function variables.  Should convert to a constructor


int __fastcall__ getCalc(int oper, int x, int y, int z);  // calculate the operation requested


/*************************************************************
* This program is built upon exercise  2.19 found in How to  *
* program C by Dietel / Dietel.         It has been addapted *
* to work on the Atari 8-Bit platform.                       *
*                                                            *
* Things to do:                                              *
   1. Call to statusLine should be done during a VLI        *
   2. Convert statusLine to a structure to handle more cases*
   3. Use TGI (Having probelms loading driver)              *
*************************************************************/
void main(void)
{
   int sum, average, product, smallest, largest;   // create five operators for math calculations
   int i1, i2, i3;                                 // create three integers for to be used by the five operators.
   char c;                                         // character c is need to check for EOF / user exit

   cursor(1);                                      // turn the atari cursor off

   gotox(0);                                       // position the cursor on X line 0
   printf("Enter three numbers: ");
   cscanf("%d", &i1);                              // need to add test for out of range entries
   statusLine(wherex(),wherey(),i1);               // debug function to determine where cursor is and the value of i1

   gotoxy(27,0);                                   // Put cursor on Line X 0 and Y 27 as it was moved by statusLine
   cscanf("%d", &i2);
   statusLine(wherex(),wherey(),i2);

   gotoxy(30,0);
   cscanf("%d", &i3);
   statusLine(wherex(),wherey(),i3);

   gotoxy(10,6);                                   // Move cursor to center of screen and display results
   sum = getCalc(1,i1,i2,i3);
   average = getCalc(2,i1,i2,i3);
   product = getCalc(3,i1,i2,i3);
   smallest = getCalc(4,i1,i2,i3);
   largest = getCalc(5,i1,i2,i3);

   printf("The sum = %d\n", sum);
   gotox(10);
   printf("The average = %d\n", average);
   gotox(10);
   printf("The product = %d\n", product);
   gotox(10);
   printf("The smallest = %d\n", smallest);
   gotox(10);
   printf("The largest = %d\n", largest);

   cursor(0);
   gotox(10);
   printf("Press Enter to exit");


   while (1)
   {
       gotoxy(20,11);
       statusLine(wherex(),wherey(),c=cgetc());
       if (c=='\n' || c==EOF)
       {

           break;
       }
   }

   return;
}

int __fastcall__ getCalc(int operand, int x, int y, int z)
{

   switch (operand)
   {
       case 1: // calculate the sum
           operand = x + y + z;
           return operand;
       case 2: // calculate the average
           operand = (x + y + z) / 3;
           return operand;

       case 3: // calculate the product
           operand = x*y*z;
           return operand;

       case 4: // calculate the smallest
           if (x <= y && y <= z)
               return x;
           else if (x >= y && y <= z)
               return y;
           else (x >= y && x >= z);
               return z;

       case 5: // calculate the largest
           if (x > y && x > z)
               return x;
           else if (y > x && y > z)
               return y;
           else (z > x && z > y);
               return z;

       default: // no calculation
           return EOF;
   }
}

void __fastcall__ statusLine(int x,int y, int capture)
{
   gotoxy(0,20);
   printf("You entered: %d\n", capture);
   printf("Position x = %d\n" ,x);
   printf("Position y = %d ",y);

   return;
}

 

 

  • Like 1

Share this post


Link to post
Share on other sites

Another good CC65 example may be found here: Google Translator to AtariOnline.pl

 

 

1. Araknoid (brick breaker) : Google Translator to AtariOnline.pl

 

[b][font="Courier New"]#include < atari.h > 
#include < stdio.h > 
#include < peekpoke.h > 
#include < joystick.h > 

#define SCREEN_SIZE 40 
#define PADDLE_SIZE 6 
#define PADDLE_Y 20 

#define COLOR1 0x2C5 
#define COLOR2 0x2C6 
#define CDTMV3 0x21C 
#define RANDOM PEEK(0xD20A) 
// 29 is '=' character in ANTIC character set 
#define BRICK 29 
#define PADDLE 213 
#define BALL 84 

// Global variables 

unsigned char *video_ptr; 
unsigned char total_bricks; 
unsigned char paddle_pos=0; 
unsigned char ball_x,ball_y; 
signed char ball_dx,ball_dy; 
unsigned char lives=6; 

// Joystick 

extern char joy_driver; 
unsigned char joy; 

// Helper macros 
#define set_char(x,y,a) video_ptr[(x)+(y)*SCREEN_SIZE]=(a); 
#define get_char(x,y) video_ptr[(x)+(y)*SCREEN_SIZE] 

void set_colors() 
{ 
POKE(COLOR1,0xFF); // font color 
POKE(COLOR2,0); // background color 
} 

void draw_bricks() 
{ 
for (total_bricks=0;total_bricks < 4*SCREEN_SIZE;total_bricks++) 
video_ptr[total_bricks]=BRICK; 
} 

void draw_paddle() 
{ 
register unsigned char s; 
for (s=0;s < PADDLE_SIZE;++s) 
set_char(s+paddle_pos,PADDLE_Y,PADDLE); 
set_char(paddle_pos-1,PADDLE_Y,0); 
set_char(paddle_pos+PADDLE_SIZE,PADDLE_Y,0); 
} 

void win() 
{ 
for(; 
puts("Win!"); 
} 

void next_life() 
{ 
unsigned char i; 
--lives; 
if (lives==0) 
{ 
for(; 
puts("Game over"); 
} 
// show available balls 
for (i=0;i < lives;++i) 
set_char(i,PADDLE_Y+2,BALL); 
set_char(i,PADDLE_Y+2,0); 

// set_ball_position 

ball_x=paddle_pos+3; 
ball_y=18; 
ball_dy=-1; 
ball_dx=RANDOM%2?1:-1; 
} 


void move_ball() 
{ 
// remove ball 
set_char(ball_x,ball_y,0); 

// bounce ball on the top of the screen 
if (ball_y==0) 
ball_dy=1; 
else if (ball_y==PADDLE_Y-1) // ball on the line of the paddle 
{ 
// hit the paddle? 
if (ball_x > =paddle_pos && ball_x < paddle_pos+PADDLE_SIZE) 
ball_dy=-1; 
else // ball missed the paddle 
{ 
next_life(); 
return; 
} 
} 

// bounce the ball on the borders 
if (ball_x==0) 
ball_dx=1; 
else if (ball_x==SCREEN_SIZE-1) 
ball_dx=-1; 

// change the ball position 
ball_x+=ball_dx; 
ball_y+=ball_dy; 

// ball hit the brick 
if (get_char(ball_x,ball_y)==BRICK) 
{ 
if (--total_bricks==0) 
win(); 
ball_dy*=-1; 
} 

// draw ball 
set_char(ball_x,ball_y,BALL); 
} 

int main(void) 
{ 
joy_install(&joy_driver); 
_graphics(0); 
set_colors(); 

// get screen memory pointer 
video_ptr=(unsigned char*)(PEEKW( PEEKW(560)+4 )); 
draw_bricks(); 

paddle_pos=(SCREEN_SIZE-PADDLE_SIZE)/2; 
next_life(); 

for(; 
{ 
// wait a moment and read the joystick state after that 
POKE(CDTMV3,3); 
while(PEEK(CDTMV3)); 
POKE(0x4D,0); // Disable Attract mode 

joy=joy_read(JOY_1); 
if (JOY_BTN_LEFT(joy)) 
{ 
if (paddle_pos > 0) 
--paddle_pos; 
} 
if (JOY_BTN_RIGHT(joy)) 
{ 
if (paddle_pos < SCREEN_SIZE-PADDLE_SIZE) 
++paddle_pos; 
} 
draw_paddle(); 
move_ball(); 
} 
return 0; 
}[/font][/b] 

Share this post


Link to post
Share on other sites

There are no built in functions in cc65 for doing PMG, but you can get to the hardware easily with C.

 

BTW the explanation of fastcall in the code posted above is wrong. fastcall does something different in C code than in assembly code in the cc65 toolchain. Using fastcall in C will save some bytes but not really speed up execution. What it does, used in C code like shown in the code posted above, is move the "jsr pushax" into the function being called, rather than being done in the caller (saving bytes in your program, multiplied by how many times you are calling it.) Still a good thing to do though.

 

Better is to rewrite the code in assembly, and use fastcall there, which will expect the last argument to the function to be passed via the A and X register. That does speed up your programs...

Edited by Shawn Jefferson

Share this post


Link to post
Share on other sites

sorry, I meant grafik7 and pm or pm and grafik7 and text.

have here with this xforth.

 

goes the same with the CC65?

post-31221-0-40981900-1327086910_thumb.jpg

post-31221-0-10262700-1327086921_thumb.jpg

Edited by funkheld

Share this post


Link to post
Share on other sites

There is a command to change graphics modes, like the BASIC command GRAPHICS, but no drawing primitives. There is a cross-platform library callged TGI (Tiny Graphics Interface) that has some limited graphics mode support and graphics primitives like plot, line, etc...

 

You could use other people's work and re-use it. There are some examples of graphics and PM manipulation with CC65 around the forum.

Share this post


Link to post
Share on other sites

I picked up the proverbial coding pen again and am working on a Dungeon crawler. Not much progress but I've finally found something I can sink my teeth in. I've always enjoyed D&D. I've uploaded the code to github and made it public for any one who is starting out with CC65 or C for that matter. I try to heavily comment my code. Mostly so I can remember what it was I was trying to do...

 

code link: https://github.com/rchennau/CharacterGen/tree/master/Dungeon (I know I've reversed the naming logic).

 

Next up is to crate a linked list to store the die rolls and allow the users to scroll back and forwards. I should probably cap it at ten and make it a circular linked list.

 

I've looked at umoria, imoria and angband source for character generation and honestly it is either poorly documented or overly complicated. But maybe that is how things end up after simple beginnings.

Share this post


Link to post
Share on other sites

Some comments on your code:

- NEVER use structs - it requires a huge 6502 code to handle them properly in C.

- NEVER use dynamic memory allocation (malloc etc.) - Atari memory is too small for that. You will also encounter many problems with aligned data (required for most of the GFX related stuff).

- remember to use "signed" type or set a proper compiler option. By default types without it are unsigned in CC65.

- use unsigned char wherever possible instead of int. int is 16 bit - handling it on 6502 requires a lot of code and CPU time.

- it's the best to: typedef unsigned char byte;

Share this post


Link to post
Share on other sites

Being a 'C' programmer from way back, I was very excited to run into the CC65 compiler. After extensive use, though, I agree with what ilmenit said and would add the following ( all my opinion only, of course, your mileage may vary ) :

 

( Note - this applies only to large programs, especially games - you can knock out quick utils and small programs without worrying about all these restrictions )

 

1. Don't code as you would for normal C programs. You will run out of memory.

2. Call the OS rather than using stdio or stdlib. That may sound extreme, but you can usually code your own versions of exactly what you need without pulling in the relatively large runtime library. For example, use CIO calls instead of fopen(), etc. Create your own print routines, again with CIO.

3. Don't be afraid to drop into assembler for small functions, its very well handled by the compilation system.

4. Watch your parameter stack usage..basically you don't want to pass/return anything more than a byte or word or two to/from functions. This will allow you to cut down the huge 2k default parm stack size. Use globals and externs to reduce the need to pass stuff around.

5. Place your own memory blocks, using address specification and/or a custom linker config..

6. Structs are ok to format data conceptually, as long as you dont pass them around and don't use the -> or . operators to fetch things a lot. You can use unions or address offset #defines to place named items for easy access.

 

I found it best to think of CC65 as a very sophisticated macro assembler rather than as the full fledged C compiler that it is. This is strictly due to limitations of the machine, and not of CC65 itself, which is a very good C compiler.

Edited by danwinslow
  • Like 2

Share this post


Link to post
Share on other sites
ilmenit and danwinslow,

Thank you for the feedback. I tried some of the things suggested and indeed was able to drop the size of the xex by a full 1K. Nice for such a simple program at this point. I'll have to research on exactly how to call the OS routines direct vs. using stdlib and stdio. This is actually fun and a challenge.

 

Changes posted to github

Share this post


Link to post
Share on other sites

You are still using structs.

 

Instead of

 

typedef struct characterStats{
  int wisdom;
  int intelligence;
...
use

char wisdom[CHARACTERS_MAX];
char intelligence[CHARACTERS_MAX];
then you can replace pointers by indexes:

void buildCharacter (struct characterStats *thisCharacter);
to

void buildCharacter (byte index) {
   wisdom[index] = rollDice(index, 1) + wisdom[index];
   ...
}
6502 CPU can easily address arrays like that while is terrible at addressing by pointers. You will save at least a few kilobytes in the full program.

 

Also in:

void addRaceModifiers (unsigned char *race, struct characterStats *thisCharacter);
do not use pointer: unsigned char *race;

use simply byte race_index;

 

Do not use magic values like 5, use enums or #DEFINEs :

thisCharacter->floor = human[5];
The last mentioned function is especially bad. Place race modifiers in two-dimensional table. You can remove the switch-cases then.

 

char race_modifiers[RACES_MAX][STATS_MAX] = {
 {2,1,2,0,2,5,14}, // elf
 {1,-1,1,2,2,6,14}, // dwarf
 {0,0,0,0,0,6,14}, // human
 {2,2,0,-2,-2,3,14} // planes
};

void addRaceModifiers (byte race_index, byte char_index) {
  wisdom[char_index] = race_modifiers[race_index][STATS_WISDOM];
  floor[char_index] = race_modifiers[race_index][STATS_FLOOR];
  max[char_index] = race_modifiers[race_index][STATS_MAX];
};
Edited by ilmenit

Share this post


Link to post
Share on other sites

I'd stick with the conio screen output... you won't get screen scrolling, since it prints direct to screen memory. Avoid printf (if you can)

 

I still think writing programs in C is fine... optimize where and when you need to. Time's more important than saving 2k, unless you absolutely need that 2k (then you can optimize).

 

There's lots of tricks you can use to optimize without going to asm as well (a lot mentioned in this thread). Use zeropage variables where you can, globals (puts the variable in BSS or DATA segments and simplifies the generated code to access it), optimize your loops, optimize screen draws, etc...

Edited by Shawn Jefferson

Share this post


Link to post
Share on other sites

I'd agree, its fine to get things going in C first then later optimise some code into asm source instead, even drawing upon what the orginal C->asm looked like.

Share this post


Link to post
Share on other sites

Version with suggested changes. I still need to research how to avoid printf in favor of 'call the OS'. My next step is to record three sets of rolls and let the user scroll through them and accept the one they like. Endless rolling seems like a cheat to me. ;)

 

https://github.com/rchennau/CharacterGen/tree/master/Dungeon

 

Take a look at the sources of my simple game Viper here . It shows how to print a text with a simple conversion to ATASCII. However what I recommend when you use your own font set is to place letters in the set according to ASCII standard. Then you can ASCII strings easier.

  • Like 1

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...