cc65-Chess/src/apple2/platA2.c

400 lines
11 KiB
C
Raw Normal View History

Ported the program to the Apple II. Implementation notes: - General display uses the Apple II hires mode accessed via custom asm functions. - Menu display uses the 4 line bottom text option of the Apple II hires mode via cc65 CONIO functions. - All hires access is byte aligned, therefore the horizontal resolution is 40 (bytes). - Hires access is done via simple (binary) ROPs (raster operations) by using actual 6502 (immediate) opcodes. The C64 and Curses implementation both make heavy use of colors while Apple II implementation mustn't depend on (but may benefit from) colors. Therefore the user-operated cursor inverts the border of the current field. It's hard to find a compromise between making the cursor visible well and showing the piece "under" the cursor well. Additionally it is desirable to show different cursor states (empty, invalid, valid). The approach chosen is to have different thicknesses of the inverted border: - Valid: Thin - Invalid: Medium - Empty: Thick When it comes to showing attackers/defenders (via the keys 'a'/'d') there's no alternative to resorting to colors: - Attackers: Red - Defenders: Green So the only field display variant left is the piece selected for moving. Instead of introducing a third type of highlighting (beside border inversion and coloring) it is simply colored Magenta. The reasoning: - On a monochrome display the user won't have much fun showing attackers/defenders anyway. And without showing those the selected piece is the only colored (aka striped) piece making it clearly visible. - On a color display a third color (beside attackers/defenders) works just fine. As the Apple II doesn't have cursor-up and cursor-down keys the keys 'o' and 'l' work as alternatives to the those cursor keys.
2020-01-19 13:18:58 +00:00
/*
* platA2.c
* cc65 Chess
*
* Created by Oliver Schmidt, January 2020.
*
*/
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include "../types.h"
#include "../globals.h"
#include "../undo.h"
#include "../frontend.h"
#include "../plat.h"
/*-----------------------------------------------------------------------*/
#define BOARD_PIECE_WIDTH 3
#define BOARD_PIECE_HEIGHT 22
#define CHAR_HEIGHT 8
#define LOG_WINDOW_HEIGHT 23
#define SCREEN_WIDTH 40
#define SCREEN_HEIGHT 192
#define ROP_CONST(val) 0xA980|(val)
#define ROP_BLACK 0xA980
#define ROP_WHITE 0xA9FF
#define ROP_XOR(val) 0x4900|(val)
#define ROP_CPY 0x4980
#define ROP_INV 0x49FF
#define ROP_AND(val) 0x2900|(val)
char rop_Line[2][7] = {{0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F},
{0x00, 0x40, 0x60, 0x70, 0x78, 0x7C, 0x7E}};
char rop_Color[2][2] = {{0x55, 0x2A}, {0xD5, 0xAA}};
extern char hires_CharSet[96][CHAR_HEIGHT];
extern char hires_Pieces[6][2][BOARD_PIECE_WIDTH*BOARD_PIECE_HEIGHT];
void hires_Init(void);
void hires_Done(void);
void hires_Text(char flag);
void hires_Draw(char xpos, char ypos,
char xsize, char ysize,
unsigned rop, char *src);
void hires_Mask(char xpos, char ypos,
char xsize, char ysize,
unsigned rop);
/*-----------------------------------------------------------------------*/
void plat_Drawchar(char x, char y, unsigned rop, char c)
{
hires_Draw(x,y,1,CHAR_HEIGHT,rop,hires_CharSet[c-' ']);
}
/*-----------------------------------------------------------------------*/
void plat_Drawstring(char x, char y, unsigned rop, char *s)
{
while(*s)
plat_Drawchar(x++,y,rop,*s++);
}
/*-----------------------------------------------------------------------*/
void plat_Init()
{
char i;
// Clear the hires screen
hires_Mask(0,0,SCREEN_WIDTH,SCREEN_HEIGHT,ROP_BLACK);
// Show the hires screen
hires_Init();
// Show credits and wait for key press
plat_Drawstring(2,SCREEN_HEIGHT/2-10-CHAR_HEIGHT/2,
ROP_CPY,gszAbout);
plat_Drawstring(2,SCREEN_HEIGHT/2+10-CHAR_HEIGHT/2,
ROP_CPY,"Apple][ version by O. Schmidt, 2020.");
hires_Draw(SCREEN_WIDTH/2-2,SCREEN_HEIGHT/2-50-BOARD_PIECE_HEIGHT/2,
BOARD_PIECE_WIDTH,BOARD_PIECE_HEIGHT,ROP_CPY,
hires_Pieces[KING-1][SIDE_BLACK]);
hires_Draw(SCREEN_WIDTH/2-2,SCREEN_HEIGHT/2+50-BOARD_PIECE_HEIGHT/2,
BOARD_PIECE_WIDTH,BOARD_PIECE_HEIGHT,ROP_CPY,
hires_Pieces[KING-1][SIDE_WHITE]);
plat_ReadKeys(1);
// Draw the board border
hires_Mask( 1, 12,1,8*BOARD_PIECE_HEIGHT+2*2,ROP_CONST(rop_Line[1][2]));
hires_Mask(26, 12,1,8*BOARD_PIECE_HEIGHT+2*2,ROP_CONST(rop_Line[0][2]));
hires_Mask( 2, 12,8*BOARD_PIECE_WIDTH,2, ROP_WHITE);
hires_Mask( 2,190,8*BOARD_PIECE_WIDTH,2, ROP_WHITE);
// Add the A..H and 1..8 tile-keys
for(i=0; i<8; ++i)
{
plat_Drawchar(3+i*BOARD_PIECE_WIDTH,0, ROP_CPY,i+'A');
plat_Drawchar(0,21+i*BOARD_PIECE_HEIGHT,ROP_CPY,i+'1');
}
// Setting this to 0 will not show the "Quit" option in the main menu
gReturnToOS = 1;
}
/*-----------------------------------------------------------------------*/
void plat_UpdateScreen()
{
}
/*-----------------------------------------------------------------------*/
char plat_Menu(char **menuItems, char height, char *scroller)
{
int keyMask;
char i, j;
clrscr();
hires_Text(1);
// Show the title or the scroller if that is more relevant.
cputs(scroller == gszAbout ? menuItems[0] : scroller);
// Select the first item
i = 1;
do
{
// Show all the menu items
for(j=1; j<height; ++j)
{
if(!menuItems[j])
break;
gotoxy(((j-1)%2)*SCREEN_WIDTH/2,((j-1)/2)+1);
// Highlight the selected item
if(j==i)
revers(1);
cprintf(" %s ",menuItems[j]);
if(j==i)
revers(0);
}
// Look for user input
keyMask = plat_ReadKeys(1);
if(keyMask & INPUT_MOTION)
{
switch(keyMask & INPUT_MOTION)
{
case INPUT_LEFT:
if(!--i)
i = j-1;
break;
case INPUT_RIGHT:
if(j == ++i)
i = 1;
break;
case INPUT_UP:
if(i > 2)
i -= 2;
else
i = j-1-(i-1)%2;
break;
case INPUT_DOWN:
if(i < j-2)
i += 2;
else
i = 1+(i-1)%2;
break;
}
}
keyMask &= (INPUT_SELECT | INPUT_BACKUP);
} while(keyMask != INPUT_SELECT && keyMask != INPUT_BACKUP);
hires_Text(0);
// If backing out of the menu, return 0
if(keyMask & INPUT_BACKUP)
return 0;
// Return the selection
return i;
}
/*-----------------------------------------------------------------------*/
// Draw the chess board and possibly clear the log section
void plat_DrawBoard(char clearLog)
{
char i;
if(clearLog)
hires_Mask(27,0,SCREEN_WIDTH-27,SCREEN_HEIGHT,ROP_BLACK);
// Redraw all tiles
for(i=0; i<64; ++i)
plat_DrawSquare(i);
}
/*-----------------------------------------------------------------------*/
// Draw a tile with background and piece on it for positions 0..63
void plat_DrawSquare(char position)
{
unsigned rop;
char inv;
char y = position / 8, x = position & 7;
char piece = gChessBoard[y][x];
char blackWhite = !((x & 1) ^ (y & 1));
if(piece)
{
rop = blackWhite ? ROP_INV : ROP_CPY;
inv = blackWhite ^ (piece & PIECE_WHITE) != 0;
}
else
rop = blackWhite ? ROP_WHITE : ROP_BLACK;
hires_Draw(2+x*BOARD_PIECE_WIDTH,14+y*BOARD_PIECE_HEIGHT,
BOARD_PIECE_WIDTH,BOARD_PIECE_HEIGHT,rop,
hires_Pieces[(piece&PIECE_DATA)-1][inv]);
// Show the attack numbers
if(gShowAttackBoard)
{
char val[4];
rop = blackWhite ? ROP_INV : ROP_CPY;
sprintf(val,"%02X%d",gChessBoard[y][x],(gChessBoard[y][x]&PIECE_WHITE)>>7);
plat_Drawchar(2+x*BOARD_PIECE_WIDTH,14+(y+1)*BOARD_PIECE_HEIGHT-CHAR_HEIGHT,
rop,(gpAttackBoard[giAttackBoardOffset[position][0]])+'0');
plat_Drawchar(2+(x+1)*BOARD_PIECE_WIDTH-1,14+(y+1)*BOARD_PIECE_HEIGHT-CHAR_HEIGHT,
rop,(gpAttackBoard[giAttackBoardOffset[position][1]])+'0');
plat_Drawstring(2+x*BOARD_PIECE_WIDTH,14+y*BOARD_PIECE_HEIGHT,rop,val);
}
}
/*-----------------------------------------------------------------------*/
void plat_ShowSideToGoLabel(char side)
{
plat_Drawstring(28,SCREEN_HEIGHT-CHAR_HEIGHT,
side?ROP_CPY:ROP_INV,gszSideLabel[side]);
}
/*-----------------------------------------------------------------------*/
void plat_Highlight(char position, char color, char cursor)
{
char y = position / 8, x = position & 7;
if (cursor && color != HCOLOR_SELECTED)
{
char size = color == HCOLOR_EMPTY ? 6 : color == HCOLOR_INVALID ? 4 : 2;
hires_Mask(2+x*BOARD_PIECE_WIDTH,14+y*BOARD_PIECE_HEIGHT+size,
1,BOARD_PIECE_HEIGHT-2*size,ROP_XOR(rop_Line[0][size]));
hires_Mask(2+x*BOARD_PIECE_WIDTH+BOARD_PIECE_WIDTH-1,14+y*BOARD_PIECE_HEIGHT+size,
1,BOARD_PIECE_HEIGHT-2*size,ROP_XOR(rop_Line[1][size]));
hires_Mask(2+x*BOARD_PIECE_WIDTH,14+y*BOARD_PIECE_HEIGHT,
BOARD_PIECE_WIDTH,size,ROP_XOR(0x7F));
hires_Mask(2+x*BOARD_PIECE_WIDTH,14+y*BOARD_PIECE_HEIGHT+BOARD_PIECE_HEIGHT-size,
BOARD_PIECE_WIDTH,size,ROP_XOR(0x7F));
}
else
{
char set = color == HCOLOR_ATTACK;
char val = cursor ^ x * BOARD_PIECE_WIDTH & 1;
hires_Mask(2+x*BOARD_PIECE_WIDTH,14+y*BOARD_PIECE_HEIGHT,
1,BOARD_PIECE_HEIGHT,ROP_AND(rop_Color[set][!val]));
hires_Mask(2+x*BOARD_PIECE_WIDTH+1,14+y*BOARD_PIECE_HEIGHT,
1,BOARD_PIECE_HEIGHT,ROP_AND(rop_Color[set][val]));
hires_Mask(2+x*BOARD_PIECE_WIDTH+2,14+y*BOARD_PIECE_HEIGHT,
1,BOARD_PIECE_HEIGHT,ROP_AND(rop_Color[set][!val]));
}
}
/*-----------------------------------------------------------------------*/
void plat_ShowMessage(char *str, char)
{
plat_Drawstring(26,0,ROP_CPY,str);
}
/*-----------------------------------------------------------------------*/
void plat_ClearMessage()
{
hires_Mask(26,0,7,CHAR_HEIGHT,ROP_BLACK);
}
/*-----------------------------------------------------------------------*/
void plat_AddToLogWin()
{
char y;
for(y=0; y<=LOG_WINDOW_HEIGHT; ++y)
{
hires_Mask(SCREEN_WIDTH-6,y*CHAR_HEIGHT,
6,CHAR_HEIGHT,ROP_BLACK);
if(undo_FindUndoLine(LOG_WINDOW_HEIGHT-y))
{
frontend_FormatLogString();
plat_Drawstring(SCREEN_WIDTH-6,y*CHAR_HEIGHT,
gColor[0]?ROP_CPY:ROP_INV,gLogStrBuffer);
}
}
}
/*-----------------------------------------------------------------------*/
void plat_AddToLogWinTop()
{
plat_AddToLogWin();
}
/*-----------------------------------------------------------------------*/
int plat_ReadKeys(char blocking)
{
char key = 0;
int keyMask = 0;
if(blocking || kbhit())
key = cgetc();
else
return 0;
switch(key)
{
case CH_CURS_LEFT:
keyMask |= INPUT_LEFT;
break;
case CH_CURS_RIGHT:
keyMask |= INPUT_RIGHT;
break;
case 'O':
case 'o': // Reasonably located on the ][ keyboard
case 0x0B: // No CH_CURS_UP in apple2.h, only in apple2enh.h
keyMask |= INPUT_UP;
break;
case 'L':
case 'l': // Reasonably located on the ][ keyboard
case 0x0A: // No CH_CURS_DOWN in apple2.h, only in apple2enh.h
keyMask |= INPUT_DOWN;
break;
case CH_ESC:
keyMask |= INPUT_BACKUP;
break;
case CH_ENTER:
keyMask |= INPUT_SELECT;
break;
case 'A':
case 'a': // Show Attackers
keyMask |= INPUT_TOGGLE_A;
break;
case 'B':
case 'b': // Board attacks - Show all attacks
keyMask |= INPUT_TOGGLE_B;
break;
case 'D':
case 'd': // Show Defenders
keyMask |= INPUT_TOGGLE_D;
break;
case 'M':
case 'm':
keyMask |= INPUT_MENU;
break;
case 'R':
case 'r':
keyMask |= INPUT_REDO;
break;
case 'U':
case 'u':
keyMask |= INPUT_UNDO;
break;
}
return keyMask;
}
/*-----------------------------------------------------------------------*/
// Only ever gets called if gReturnToOS is true
void plat_Shutdown()
{
hires_Done();
}