cc65 Chess V1.0 Initial commit

This commit is contained in:
StewBC 2014-02-14 16:57:00 +01:00
commit 82c002891f
22 changed files with 4551 additions and 0 deletions

345
Makefile Normal file
View File

@ -0,0 +1,345 @@
###############################################################################
### Generic Makefile for cc65 projects - full version with abstract options ###
### V1.3.0(w) 2010 - 2013 Oliver Schmidt & Patryk "Silver Dream !" Łogiewa ###
###############################################################################
###############################################################################
### In order to override defaults - values can be assigned to the variables ###
###############################################################################
# Space or comma separated list of cc65 supported target platforms to build for.
# Default: c64 (lowercase!)
TARGETS :=
# Name of the final, single-file executable.
# Default: name of the current dir with target name appended
PROGRAM :=
# Path(s) to additional libraries required for linking the program
# Use only if you don't want to place copies of the libraries in SRCDIR
# Default: none
LIBS :=
# Custom linker configuration file
# Use only if you don't want to place it in SRCDIR
# Default: none
CONFIG :=
# Additional C compiler flags and options.
# Default: none
CFLAGS =
# Additional assembler flags and options.
# Default: none
ASFLAGS =
# Additional linker flags and options.
# Default: none
LDFLAGS =
# Path to the directory containing C and ASM sources.
# Default: src
SRCDIR :=
# Path to the directory where object files are to be stored (inside respective target subdirectories).
# Default: obj
OBJDIR :=
# Command used to run the emulator.
# Default: depending on target platform. For default (c64) target: x64 -kernal kernal -VICIIdsize -autoload
EMUCMD :=
# Optional commands used before starting the emulation process, and after finishing it.
# Default: none
#PREEMUCMD := osascript -e "tell application \"System Events\" to set isRunning to (name of processes) contains \"X11.bin\"" -e "if isRunning is true then tell application \"X11\" to activate"
#PREEMUCMD := osascript -e "tell application \"X11\" to activate"
#POSTEMUCMD := osascript -e "tell application \"System Events\" to tell process \"X11\" to set visible to false"
#POSTEMUCMD := osascript -e "tell application \"Terminal\" to activate"
PREEMUCMD :=
POSTEMUCMD :=
# On Windows machines VICE emulators may not be available in the PATH by default.
# In such case, please set the variable below to point to directory containing
# VICE emulators.
#VICE_HOME := "C:\Program Files\WinVICE-2.2-x86\"
VICE_HOME :=
# Options state file name. You should not need to change this, but for those
# rare cases when you feel you really need to name it differently - here you are
STATEFILE := Makefile.options
###################################################################################
#### DO NOT EDIT BELOW THIS LINE, UNLESS YOU REALLY KNOW WHAT YOU ARE DOING! ####
###################################################################################
###################################################################################
### Mapping abstract options to the actual compiler, assembler and linker flags ###
### Predefined compiler, assembler and linker flags, used with abstract options ###
### valid for 2.14.x. Consult the documentation of your cc65 version before use ###
###################################################################################
# Compiler flags used to tell the compiler to optimise for SPEED
define _optspeed_
CFLAGS += -Oris
endef
# Compiler flags used to tell the compiler to optimise for SIZE
define _optsize_
CFLAGS += -Or
endef
# Compiler and assembler flags for generating listings
define _listing_
CFLAGS += --listing $$(@:.o=.lst)
ASFLAGS += --listing $$(@:.o=.lst)
REMOVES += $(addsuffix .lst,$(basename $(OBJECTS)))
endef
# Linker flags for generating map file
define _mapfile_
LDFLAGS += --mapfile $$@.map
REMOVES += $(PROGRAM).map
endef
# Linker flags for generating VICE label file
define _labelfile_
LDFLAGS += -Ln $$@.lbl
REMOVES += $(PROGRAM).lbl
endef
# Linker flags for generating a debug file
define _debugfile_
LDFLAGS += -Wl --dbgfile,$$@.dbg
REMOVES += $(PROGRAM).dbg
endef
###############################################################################
### Defaults to be used if nothing defined in the editable sections above ###
###############################################################################
# Presume the C64 target like the cl65 compile & link utility does.
# Set TARGETS to override.
ifeq ($(TARGETS),)
TARGETS := c64
endif
# Presume we're in a project directory so name the program like the current
# directory. Set PROGRAM to override.
ifeq ($(PROGRAM),)
PROGRAM := $(notdir $(CURDIR))
endif
# Presume the C and asm source files to be located in the subdirectory 'src'.
# Set SRCDIR to override.
ifeq ($(SRCDIR),)
SRCDIR := src
endif
# Presume the object and dependency files to be located in the subdirectory
# 'obj' (which will be created). Set OBJDIR to override.
ifeq ($(OBJDIR),)
OBJDIR := obj
endif
TARGETOBJDIR := $(OBJDIR)/$(TARGETS)
# On Windows it is mandatory to have CC65_HOME set. So do not unnecessarily
# rely on cl65 being added to the PATH in this scenario.
ifdef CC65_HOME
CC := $(CC65_HOME)/bin/cl65
else
CC := cl65
endif
# Default emulator commands and options for particular targets.
# Set EMUCMD to override.
c64_EMUCMD := $(VICE_HOME)x64 -kernal kernal -VICIIdsize -autoload
c128_EMUCMD := $(VICE_HOME)x128 -kernal kernal -VICIIdsize -autoload
vic20_EMUCMD := $(VICE_HOME)xvic -kernal kernal -VICdsize -autoload
pet_EMUCMD := $(VICE_HOME)xpet -Crtcdsize -autoload
plus4_EMUCMD := $(VICE_HOME)xplus4 -TEDdsize -autoload
# So far there is no x16 emulator in VICE (why??) so we have to use xplus4 with -memsize option
c16_EMUCMD := $(VICE_HOME)xplus4 -ramsize 16 -TEDdsize -autoload
cbm510_EMUCMD := $(VICE_HOME)xcbm2 -model 510 -VICIIdsize -autoload
cbm610_EMUCMD := $(VICE_HOME)xcbm2 -model 610 -Crtcdsize -autoload
atari_EMUCMD := atari800 -windowed -xl -pal -nopatchall -run
ifeq ($(EMUCMD),)
EMUCMD = $($(CC65TARGET)_EMUCMD)
endif
###############################################################################
### The magic begins ###
###############################################################################
# The "Native Win32" GNU Make contains quite some workarounds to get along with
# cmd.exe as shell. However it does not provide means to determine that it does
# actually activate those workarounds. Especially does $(SHELL) NOT contain the
# value 'cmd.exe'. So the usual way to determine if cmd.exe is being used is to
# execute the command 'echo' without any parameters. Only cmd.exe will return a
# non-empy string - saying 'ECHO is on/off'.
#
# Many "Native Win32" programs accept '/' as directory delimiter just fine. How-
# ever the internal commands of cmd.exe generally require '\' to be used.
#
# cmd.exe has an internal command 'mkdir' that doesn't understand nor require a
# '-p' to create parent directories as needed.
#
# cmd.exe has an internal command 'del' that reports a syntax error if executed
# without any file so make sure to call it only if there's an actual argument.
ifeq ($(shell echo),)
MKDIR = mkdir -p $1
RMDIR = rmdir $1
RMFILES = $(RM) $1
else
MKDIR = mkdir $(subst /,\,$1)
RMDIR = rmdir $(subst /,\,$1)
RMFILES = $(if $1,del /f $(subst /,\,$1))
endif
COMMA := ,
SPACE := $(N/A) $(N/A)
define NEWLINE
endef
# Note: Do not remove any of the two empty lines above !
TARGETLIST := $(subst $(COMMA),$(SPACE),$(TARGETS))
ifeq ($(words $(TARGETLIST)),1)
# Set PROGRAM to something like 'myprog.c64'.
override PROGRAM := $(PROGRAM).$(TARGETLIST)
# Set SOURCES to something like 'src/foo.c src/bar.s'.
# Use of assembler files with names ending differently than .s is deprecated!
SOURCES := $(wildcard $(SRCDIR)/*.c)
SOURCES += $(wildcard $(SRCDIR)/*.s)
SOURCES += $(wildcard $(SRCDIR)/*.asm)
SOURCES += $(wildcard $(SRCDIR)/*.a65)
# Add to SOURCES something like 'src/c64/me.c src/c64/too.s'.
# Use of assembler files with names ending differently than .s is deprecated!
SOURCES += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.c)
SOURCES += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.s)
SOURCES += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.asm)
SOURCES += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.a65)
# Set OBJECTS to something like 'obj/c64/foo.o obj/c64/bar.o'.
OBJECTS := $(addsuffix .o,$(basename $(addprefix $(TARGETOBJDIR)/,$(notdir $(SOURCES)))))
# Set DEPENDS to something like 'obj/c64/foo.d obj/c64/bar.d'.
DEPENDS := $(OBJECTS:.o=.d)
# Add to LIBS something like 'src/foo.lib src/c64/bar.lib'.
LIBS += $(wildcard $(SRCDIR)/*.lib)
LIBS += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.lib)
# Add to CONFIG something like 'src/c64/bar.cfg src/foo.cfg'.
CONFIG += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.cfg)
CONFIG += $(wildcard $(SRCDIR)/*.cfg)
# Select CONFIG file to use. Target specific configs have higher priority.
ifneq ($(word 2,$(CONFIG)),)
CONFIG := $(firstword $(CONFIG))
$(info Using config file $(CONFIG) for linking)
endif
.SUFFIXES:
.PHONY: all test clean zap love
all: $(PROGRAM)
-include $(DEPENDS)
-include $(STATEFILE)
# If OPTIONS are given on the command line then save them to STATEFILE
# if (and only if) they have actually changed. But if OPTIONS are not
# given on the command line then load them from STATEFILE. Have object
# files depend on STATEFILE only if it actually exists.
ifeq ($(origin OPTIONS),command line)
ifneq ($(OPTIONS),$(_OPTIONS_))
ifeq ($(OPTIONS),)
$(info Removing OPTIONS)
$(shell $(RM) $(STATEFILE))
$(eval $(STATEFILE):)
else
$(info Saving OPTIONS=$(OPTIONS))
$(shell echo _OPTIONS_=$(OPTIONS) > $(STATEFILE))
endif
$(eval $(OBJECTS): $(STATEFILE))
endif
else
ifeq ($(origin _OPTIONS_),file)
$(info Using saved OPTIONS=$(_OPTIONS_))
OPTIONS = $(_OPTIONS_)
$(eval $(OBJECTS): $(STATEFILE))
endif
endif
# Transform the abstract OPTIONS to the actual cc65 options.
$(foreach o,$(subst $(COMMA),$(SPACE),$(OPTIONS)),$(eval $(_$o_)))
# Strip potential variant suffix from the actual cc65 target.
CC65TARGET := $(firstword $(subst .,$(SPACE),$(TARGETLIST)))
# The remaining targets.
$(TARGETOBJDIR):
$(call MKDIR,$@)
vpath %.c $(SRCDIR)/$(TARGETLIST) $(SRCDIR)
$(TARGETOBJDIR)/%.o: %.c | $(TARGETOBJDIR)
$(CC) -t $(CC65TARGET) -c --create-dep $(@:.o=.d) $(CFLAGS) -o $@ $<
vpath %.s $(SRCDIR)/$(TARGETLIST) $(SRCDIR)
$(TARGETOBJDIR)/%.o: %.s | $(TARGETOBJDIR)
$(CC) -t $(CC65TARGET) -c --create-dep $(@:.o=.d) $(ASFLAGS) -o $@ $<
vpath %.asm $(SRCDIR)/$(TARGETLIST) $(SRCDIR)
$(TARGETOBJDIR)/%.o: %.asm | $(TARGETOBJDIR)
$(CC) -t $(CC65TARGET) -c --create-dep $(@:.o=.d) $(ASFLAGS) -o $@ $<
vpath %.a65 $(SRCDIR)/$(TARGETLIST) $(SRCDIR)
$(TARGETOBJDIR)/%.o: %.a65 | $(TARGETOBJDIR)
$(CC) -t $(CC65TARGET) -c --create-dep $(@:.o=.d) $(ASFLAGS) -o $@ $<
$(PROGRAM): $(CONFIG) $(OBJECTS) $(LIBS)
$(CC) -t $(CC65TARGET) $(LDFLAGS) -o $@ $(patsubst %.cfg,-C %.cfg,$^)
test: $(PROGRAM)
$(PREEMUCMD)
$(EMUCMD) $<
$(POSTEMUCMD)
clean:
$(call RMFILES,$(OBJECTS))
$(call RMFILES,$(DEPENDS))
$(call RMFILES,$(REMOVES))
$(call RMFILES,$(PROGRAM))
else # $(words $(TARGETLIST)),1
all test clean:
$(foreach t,$(TARGETLIST),$(MAKE) TARGETS=$t $@$(NEWLINE))
endif # $(words $(TARGETLIST)),1
OBJDIRLIST := $(wildcard $(OBJDIR)/*)
zap:
$(foreach o,$(OBJDIRLIST),-$(call RMFILES,$o/*.o $o/*.d $o/*.lst)$(NEWLINE))
$(foreach o,$(OBJDIRLIST),-$(call RMDIR,$o)$(NEWLINE))
-$(call RMDIR,$(OBJDIR))
-$(call RMFILES,$(basename $(PROGRAM)).* $(STATEFILE))
love:
@echo "Not war, eh?"
###################################################################
### Place your additional targets in the additional Makefiles ###
### in the same directory - their names have to end with ".mk"! ###
###################################################################
-include *.mk

189
readme.txt Normal file
View File

@ -0,0 +1,189 @@
I. Introduction
I started playing chess about 3 months ago and this got me wondering how
difficult it would be to make a computer chess game. I decided to try and
since it's just a "for the fun of it" project, I decided to make it for the
Commodore 64, still my all-time favorite computer. Using the excellent cc65
tools I could do it all in C and thus make it portable to other systems also.
I learnt that making the game isn't hard, but getting the computer to play
something that resembles a reasonable game is hard. I don't know enough about
chess to get it right, but in this version of today, 14 Feb 2014, the AI is
not very good.
The game was developed on OS X using cc65 and the VICE emulator.
There is a video of the game here: http://youtu.be/bkA4vtwxaJg
II. Use and keys
The colors here refer to the C64 version. The terminal version has a minimal
working display but does try to somewhat match the colors of the C64.
The user controls an on-screen cursor. The cursor changes color to indicate
a state. The colors for selection are:
Green - the piece can be selected
Red - The piece cannot be selected as it doesn't have valid moves
Purple - Empty tile or piece on the other side
Blue - The currently selected piece
Cyan - A valid destination for the currently selected piece
To move the cursor, use the cursor keys. To select a piece, press the RETURN
key while the piece is selected. To deselect the piece, press RETURN on the
same piece again, or press RUN/STOP.
To bring up the menu, press the M key, or the RUN/STOP key when no piece is
selected. Pressing RUN/STOP in a menu backs out of the menu, to the previous
menu or back to the game. Press RETURN to select a menu item and use the up
and down cursor keys to change the selection.
While a side is under human control, there are a few more options. Press B to
toggle on/off a state showing on every tile how many of both black and white's
pieces can attack that tile. Pressing A will toggle a highlight of all of the
pieces on the opposing side that attack the selected tile. Pressing D will
toggle a highlight of all the pieces on the side currently playing's side that
can defend the selected tile. All three of these options basically give a
visual representation of the Attack DB. The colors are: For attackers Cyan
and for defenders Red.
III. Distribution
This version has code for a C64, using multi-colored text mode, and also code
for a terminal version using the curses library. The terminal version was
only tested on OS X but I suspect it will run under Linux and Windows.
IV. Building from source
a) For the C64 (and other cc65 supported platforms):
Using a properly installed cc65 distribution, the C64 version should build
using make in the folder with the Makefile. I suggest compiling for speed
but optimizing for size does save a bit of memory (1K at present):
make OPTIONS=optspeed
b) For a terminal version:
I built it on OS X using the following command line from the src folder:
cc -I. -lcurses -funsigned-char globals.c undo.c board.c cpu.c human.c \
frontend.c main.c term/platTerm.c -o chess
V. Porting
The code in the src folder should compile on any system (cc65 has type char as
unsigned by default - the char type is almost the only type really used in the
code). A new system will need platform specific implementations of the
functions in plat.h. When I created the terminal port, it literally worked in
under an hour as it was mostly replacing cursor positioning and printing
function names, along with initialization and color management. I had to redo
the log update and timer completely but still, the only file that needed
changes was the platTerm.c copy I made from plat64.c.
In the C64 specific folder under src is a data.c file which contains the
graphics characters to draw the pieces. The layout and chosen bit-pattern of
that data is also explained in that file.
VI. The AI & other thoughts on the code
The game is a fine 2-player chess game, but the computer is not a great chess
player. My approach for the AI is this:
For each piece, calculate a score (see later) for the tile the piece currently
occupies. Then look at all available moves for the piece, and score each
destination tile separately. If the computer is fast, I would now "effect"
each move and run the same algorithm on the opponent, getting a "retaliation
score". Effect the opponent move and run the algorithm again, getting a
subsequent score. The accumulated "score - retaliation score + this side
next score - other side next retaliation score" sum, up to as many levels
deep as desired, would be the final score for that piece and destination.
Since the C64 isn't fast enough for all that, I have it calculate the score
for the piece where it stands and for all destinations. The highest scoring
move, if valid, becomes the move for the piece. The scores for all pieces
are then stack-ranked. Some number of these are then chosen to pursue. I set
it to 16 (gWidth), making it pursues all pieces.
Pursuit of the best moves means doing the depth search for opponent moves and
back to own moves. This is set to go to a level controlled by a variable
named gMaxLevel.
There is another variable, gDeepThoughts, that affects difficulty and speed.
This variable, when set to 1, ensure the moves chosen when evaluating best
moves, are valid. It also, when set, causes the AttackDB to be updated.
Both of these are slow operations. Not doing the work makes things a lot
faster, but obviously less accurate. Especially the further away the
thinking gets from the current, accurate, state.
All three these variables are set from the difficulty selection if there's
an AI player.
Scoring a piece means this: Positive points encourage the piece to move,
negative points discourage making a move.
A) For where the piece stands the score is calculated by looking at:
If this piece is under attack increase chance to move, else decrease
If this piece is being defended, decrease chance to move, else increase
Providing support to a piece on own team, decrease
if supported piece is under attack
if only defender, decrease else increase
if supported piece is more valuable, decrease
not supporting a piece, increase
B) For every destination the piece can move to, score like this:
If this piece will be under attack there, decrease
If it will be defended there, increase else decrease
If a piece is taken at dest, increase
If providing support to a piece on same side from there, increase
if that piece is under attack, increase
if this will be the only defender of that piece, increase
if the supported piece is more valuable than this piece, increase
If attacking a piece on the other side from there, increase
if the attacked piece is more valuable, increase
if the attacked piece has no defenders, increase
The values for increase and decrease aren't always 1. Some situations I
deemed more important so the value may be 2, or the value of the piece itself
for which I use: 1 PAWN, 3 KNIGHT, 3 BISHOP, 5 ROOK, 9 QUEEN, 10 KING, but
modified to 2+(3*value). The +2 compensates for the +/-1's that encourage or
discourage a move, and the 3*value makes the value really meaningful.
There is another scoring opportunity that happens before any other. It is
meant to take a holistic view of the board. Currently, all it does is see
if the king has no moves then all of its neighboring pieces on the same side
are encouraged to move and; it encourages pawns to move so they can get to
promote. The closer they get to the opposite side, the stronger the
encouragement.
I have no real plans to keep working on this project. As stated, I wanted
to see how hard it would be, and now I know. I rushed this V1.0 release so,
sadly, I am sure there will be bugs. There's also lots of room to experiment
with scores, values and relative importance of things like being under attack
vs. supporting another piece.
The code is reasonably clean but I really didn't design this as a game. It
all evolved from the writing of the functions to build an array of valid moves
into a game. The en passant and castling is somewhat hacked in, for example
and may be hard to make sense of.
VII. Credits
The Makefile has the following notice:
###############################################################################
### Generic Makefile for cc65 projects - full version with abstract options ###
### V1.3.0(w) 2010 - 2013 Oliver Schmidt & Patryk "Silver Dream !" Łogiewa ###
###############################################################################
cl65 --version prints the following in my installation:
cl65 V2.13.9 - (C) Copyright 1998-2011 Ullrich von Bassewitz
VIII. Contact
Feel free to send me an email if you have any comments. If you do make a port
or something else, I would love to hear about it!
swessels@email.com
Thank you!

765
src/board.c Normal file
View File

@ -0,0 +1,765 @@
/*
* board.c
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#include <stdlib.h>
#include <string.h>
#include "types.h"
#include "globals.h"
#include "board.h"
#include "frontend.h"
#include "plat.h"
/*-----------------------------------------------------------------------*/
void board_LoadMoves(char x, char y, char dx, char dy, char n, char addDefenceMove);
void board_GenPawnMoves(char position, char color, char addDefenceMove);
void board_GenRookMoves(char position, char addDefenceMove);
void board_GenKnightMoves(char position, char addDefenceMove);
void board_GenBishopMoves(char position, char addDefenceMove);
void board_GenKingMoves(char position, char addDefenceMove);
void board_GenQueenMoves(char position, char addDefenceMove);
char board_CheckLineAttack(char t1, char t2, char side);
void board_UpdateAttackGrid(int offset, char side);
/*-----------------------------------------------------------------------*/
// board_UpdateAttackGrid may add entries (behind the king in a line
// attack, for example) and these variables track those changes so they
// can easily be undone
static int si_fixupTable[(8*2)+7+6];
static char sc_numFixes;
/*-----------------------------------------------------------------------*/
void board_Init()
{
char i;
memset(gpChessBoard,NONE,8*8*sizeof(char));
gChessBoard[0][0] = gChessBoard[7][0] = ROOK;
gChessBoard[0][1] = gChessBoard[7][1] = KNIGHT;
gChessBoard[0][2] = gChessBoard[7][2] = BISHOP;
gChessBoard[0][3] = gChessBoard[7][3] = QUEEN;
gChessBoard[0][4] = gChessBoard[7][4] = KING;
gChessBoard[0][5] = gChessBoard[7][5] = BISHOP;
gChessBoard[0][6] = gChessBoard[7][6] = KNIGHT;
gChessBoard[0][7] = gChessBoard[7][7] = ROOK;
for(i=0; i < 8; ++i)
{
gChessBoard[1][i] = PAWN;
gChessBoard[6][i] = PAWN | PIECE_WHITE;
gChessBoard[7][i] |= PIECE_WHITE;
}
gKingData[0] = MK_POS(0,4);
gKingData[1] = MK_POS(7,4);
// Not part of the board but does need resetting for every game
gCursorPos[SIDE_BLACK][1] = gCursorPos[SIDE_WHITE][1] = 4;
gCursorPos[SIDE_BLACK][0] = 0;
gCursorPos[SIDE_WHITE][0] = 7;
board_PlacePieceAttacks();
}
/*-----------------------------------------------------------------------*/
// For every piece on the board, make entries in the attack db of what it
// is attacking or defending
void board_PlacePieceAttacks()
{
char i, j, piece;
memset(gpAttackBoard,0,8*8*ATTACK_WIDTH);
for(i=0; i<64; ++i)
{
piece = gpChessBoard[i];
if(piece != NONE)
{
char color = (piece & PIECE_WHITE) >> 7;
board_GeneratePossibleMoves(i, 1);
if(gNumMoves)
{
for(j=0; j<gNumMoves; ++j)
{
int offset = giAttackBoardOffset[gPossibleMoves[j]][color];
char attackers = gpAttackBoard[offset];
gpAttackBoard[offset+1+attackers] = i;
++gpAttackBoard[offset];
}
}
}
}
}
/*-----------------------------------------------------------------------*/
// For straight-moving pieces, add open tiles or tiles that meet the
// inclusion criteria to the gPossibleMoves buffer
void board_LoadMoves(char x, char y, char dx, char dy, char n, char addDefenceMove)
{
char piece, /*color,*/ my_color = gChessBoard[y][x] & PIECE_WHITE;
do
{
x += dx;
y += dy;
--n;
// Don't go over the edge of the board.
// Unsigned char will wrap at -1 to 255
if(x > 7 || y > 7)
break;
piece = gChessBoard[y][x];
// color = piece & PIECE_WHITE;
// piece &= PIECE_DATA;
if(NONE == piece || addDefenceMove || my_color != (piece & PIECE_WHITE))
gPossibleMoves[gNumMoves++] = MK_POS(y,x);
// any non-emty tiles ends the scan
if(NONE != piece)
break;
} while(n);
}
/*-----------------------------------------------------------------------*/
// position is a tile number 0..63. If there's a piece on the tile,
// it's available moves will be placed in gPossibleMoves. if
// addDefenceMove is non-zero, moves onto friendly pieces and moves
// by PAWNS to capture are allso added (defence and pawn-take)
void board_GeneratePossibleMoves(char position, char addDefenceMove)
{
char piece, color;
// Reset the global counter - used as an index into the
// global buffer for tracking moves for a piece
gNumMoves = 0;
piece = gpChessBoard[position];
color = (piece & PIECE_WHITE) >> 7;
piece &= PIECE_DATA;
// Call the piece-specific function to generate the list
// for the piece at tile "position"
switch(piece)
{
case PAWN:
board_GenPawnMoves(position, color, addDefenceMove);
break;
case ROOK:
board_GenRookMoves(position, addDefenceMove);
break;
case KNIGHT:
board_GenKnightMoves(position, addDefenceMove);
break;
case BISHOP:
board_GenBishopMoves(position, addDefenceMove);
break;
case KING:
board_GenKingMoves(position, addDefenceMove);
break;
case QUEEN:
board_GenQueenMoves(position, addDefenceMove);
break;
}
}
/*-----------------------------------------------------------------------*/
void board_GenPawnMoves(char position, char sideColor, char addDefenceMove)
{
const signed char cap_x[2] = {-1, 1};
char i, nx, piece, color, y = position / 8, x = position & 7;
signed char d = sideColor ? -1 : 1;
if(y == 0 || y == 7)
return;
// if addDefenceMove is true, the move forward of a pawn isn't added
// becase a pawn doesn't "defend" the next square in that it can't
// take the piece there
if(!addDefenceMove && NONE == gChessBoard[y+d][x])
{
gPossibleMoves[gNumMoves++] = MK_POS(y+d,x);
if(((sideColor && y == 6) || (!sideColor && y == 1)) && NONE == gChessBoard[y+(2*d)][x])
gPossibleMoves[gNumMoves++] = MK_POS(y+(2*d),x);
}
for(i=0; i<2; ++i)
{
nx = x + cap_x[i];
if(nx > 7)
continue;
position = MK_POS(y+d,nx);
if(gEPPawn != position)
{
// Read the piece from the chessboard
piece = gChessBoard[y+d][nx];
color = (piece & PIECE_WHITE) >> 7;
piece &= PIECE_DATA;
}
else
{
// Create a fake piece to represent the en passant pawn
piece = PAWN;
color = 1 - sideColor;
}
if(addDefenceMove || (NONE != piece && color != sideColor))
gPossibleMoves[gNumMoves++] = position;
}
}
/*-----------------------------------------------------------------------*/
void board_GenRookMoves(char position, char addDefenceMove)
{
char y = position / 8, x = position & 7;
board_LoadMoves(x, y, -1, 0, 8, addDefenceMove);
board_LoadMoves(x, y, 0, -1, 8, addDefenceMove);
board_LoadMoves(x, y, 1, 0, 8, addDefenceMove);
board_LoadMoves(x, y, 0, 1, 8, addDefenceMove);
}
/*-----------------------------------------------------------------------*/
void board_GenKnightMoves(char position, char addDefenceMove)
{
char y = position / 8, x = position & 7;
board_LoadMoves(x, y, -2, -1, 1, addDefenceMove);
board_LoadMoves(x, y, -1, -2, 1, addDefenceMove);
board_LoadMoves(x, y, 1, -2, 1, addDefenceMove);
board_LoadMoves(x, y, 2, -1, 1, addDefenceMove);
board_LoadMoves(x, y, 2, 1, 1, addDefenceMove);
board_LoadMoves(x, y, 1, 2, 1, addDefenceMove);
board_LoadMoves(x, y, -1, 2, 1, addDefenceMove);
board_LoadMoves(x, y, -2, 1, 1, addDefenceMove);
}
/*-----------------------------------------------------------------------*/
void board_GenBishopMoves(char position, char addDefenceMove)
{
char y = position / 8, x = position & 7;
board_LoadMoves(x, y, -1, -1, 8, addDefenceMove);
board_LoadMoves(x, y, 1, -1, 8, addDefenceMove);
board_LoadMoves(x, y, 1, 1, 8, addDefenceMove);
board_LoadMoves(x, y, -1, 1, 8, addDefenceMove);
}
/*-----------------------------------------------------------------------*/
void board_GenKingMoves(char position, char addDefenceMove)
{
char okayToCastle[2] = {0,0}, y = position / 8, x = position & 7;
// If the king hasn't moved (| PIECE_MOVED == 0)
if(KING == (gpChessBoard[position] & (PIECE_DATA | PIECE_MOVED)))
{
// and the rook hasn't moved
if(ROOK == (gpChessBoard[position-4] & (PIECE_DATA | PIECE_MOVED)))
{
// and there are 3 open spaces between them, then
// casteling is possible
board_LoadMoves(x, y, -1, 0, 3, addDefenceMove);
if(3 == gNumMoves)
okayToCastle[0] = 1;
gNumMoves = 0;
}
if(ROOK == (gpChessBoard[position+3] & (PIECE_DATA | PIECE_MOVED)))
{
// on this side, 2 open spaces is all that's needed
board_LoadMoves(x, y, 1, 0, 2, addDefenceMove);
if(2 == gNumMoves)
okayToCastle[1] = 1;
gNumMoves = 0;
}
}
if(okayToCastle[0])
board_LoadMoves(x, y, -2, 0, 1, addDefenceMove);
if(okayToCastle[1])
board_LoadMoves(x, y, 2, 0, 1, addDefenceMove);
board_LoadMoves(x, y, -1, 0, 1, addDefenceMove);
board_LoadMoves(x, y, -1, -1, 1, addDefenceMove);
board_LoadMoves(x, y, 0, -1, 1, addDefenceMove);
board_LoadMoves(x, y, 1, -1, 1, addDefenceMove);
board_LoadMoves(x, y, 1, 0, 1, addDefenceMove);
board_LoadMoves(x, y, 1, 1, 1, addDefenceMove);
board_LoadMoves(x, y, 0, 1, 1, addDefenceMove);
board_LoadMoves(x, y, -1, 1, 1, addDefenceMove);
}
/*-----------------------------------------------------------------------*/
void board_GenQueenMoves(char position, char addDefenceMove)
{
char y = position / 8, x = position & 7;
board_LoadMoves(x, y, -1, 0, 8, addDefenceMove);
board_LoadMoves(x, y, -1, -1, 8, addDefenceMove);
board_LoadMoves(x, y, 0, -1, 8, addDefenceMove);
board_LoadMoves(x, y, 1, -1, 8, addDefenceMove);
board_LoadMoves(x, y, 1, 0, 8, addDefenceMove);
board_LoadMoves(x, y, 1, 1, 8, addDefenceMove);
board_LoadMoves(x, y, 0, 1, 8, addDefenceMove);
board_LoadMoves(x, y, -1, 1, 8, addDefenceMove);
}
/*-----------------------------------------------------------------------*/
// This is the function that will move pieces from gTile[0] to gTile[1]
// if the move is valid. It will make sure pieces dont' do illegal moves
char board_ProcessAction(void)
{
// Get the king's tile and an offset to the king's attackers
char kingTile = gKingData[gColor[0]];
// If the king is moving onto a tile under attack the move is invalid
// Can't move into check
if(KING == gPiece[0] && gpAttackBoard[giAttackBoardOffset[gTile[1]][1-gColor[0]]])
return OUTCOME_INVALID;
if(KING == gPiece[0])
{
kingTile = gTile[1];
// See if the King is trying to castle
board_ProcessCastling(3, 2);
}
else if(PAWN == gPiece[0])
{
if(gEPPawn == gTile[1])
board_ProcessEnPassant(ENPASSANT_TAKE);
else if(gTile[1] < 8 || gTile[1] > 55)
{
if(!gAI)
gpChessBoard[gTile[0]] = frontend_GetPromotion() | (gpChessBoard[gTile[0]] & PIECE_EXTRA_DATA);
else
gpChessBoard[gTile[0]] = QUEEN | (gpChessBoard[gTile[0]] & PIECE_EXTRA_DATA);
}
}
// Make the move effective
gpChessBoard[gTile[1]] = gpChessBoard[gTile[0]];
gpChessBoard[gTile[0]] = NONE;
// Recalculate the attack DB for the post-move board
board_PlacePieceAttacks();
// If the king is (still) under attack, the move isn't valid. Can't move/leave king in check
if(gpAttackBoard[giAttackBoardOffset[kingTile][1-gColor[0]]])
{
// restore the board, incl. reversing a casteling move or unpromoting a pawn, etc.
gpChessBoard[gTile[0]] = gpChessBoard[gTile[1]];
gpChessBoard[gTile[1]] = gPiece[1] | (gColor[1] << 7) | gMove[1];
if(PAWN == gPiece[0])
{
if(gEPPawn == gTile[1])
{
gpChessBoard[gTile[2]] = PAWN | ((1-gColor[0]) ? PIECE_WHITE : 0);
gTile[2] = NULL_TILE;
}
else if(gTile[1] < 8 || gTile[1] > 55)
gpChessBoard[gTile[0]] = PAWN | (gpChessBoard[gTile[0]] & PIECE_EXTRA_DATA);
}
else if(KING == gPiece[0])
board_ProcessCastling(2, 3);
// Set the attack DB back to how it was before the move was put into effect
board_PlacePieceAttacks();
// Regenerate the possible moves for the selected piece as it was destroyed by PlacePieceAttacks
board_GeneratePossibleMoves(gTile[0], 0);
return OUTCOME_INVALID;
}
// Reset the en passant decoy at every move
gEPPawn = NULL_TILE;
// Update the special king-tile-tracker
if(PAWN == gPiece[0])
{
if(!gMove[0])
board_ProcessEnPassant(ENPASSANT_MAYBE);
}
else if(KING == gPiece[0])
gKingData[gColor[0]] = gTile[1];
// The move succeeded so mark the piece as having moved
gpChessBoard[gTile[1]] |= PIECE_MOVED;
// If the opposing king is in check
if(gpAttackBoard[giAttackBoardOffset[gKingData[1-gColor[0]]][gColor[0]]])
{
sc_numFixes = 0;
// Check if that king is in check-mate
return board_CheckForMate(1-gColor[0]);
}
return OUTCOME_OK;
}
/*-----------------------------------------------------------------------*/
// Use Bresenham's algorithm to see what tiles lie
// between the attacker and the king
// not for use with a knight attacker
char board_CheckLineAttack(char t1, char t2, char side)
{
int offset;
signed char y1, x1, y2, x2, dx, dy, sx, sy, err, e2;
char piece, defenders;
y1 = t1 / 8;
x1 = t1 & 7;
y2 = t2 / 8;
x2 = t2 & 7;
dx = abs(x2-x1);
dy = abs(y2-y1);
if(x1 < x2)
sx = 1;
else
sx = -1;
if(y1 < y2)
sy = 1;
else
sy = -1;
err = dx - dy;
// for every block from the attacker to the king, consider
while(1)
{
if(x1 == x2 && y1 == y2)
break;
offset = giAttackBoardOffset[t1][side];
// How many of the king's pieces attack this block, and what is on the block
defenders = gpAttackBoard[offset];
piece = gpChessBoard[t1] & PIECE_DATA;
if(defenders)
{
char i;
// For every defender of the king, see if they can break the check-mate
for(i=1; i<=defenders; ++i)
{
// Who is defending and what tile is the defender on
char defTile = gpAttackBoard[offset+i];
char defPiece = gpChessBoard[defTile] & PIECE_DATA;
if(PAWN == defPiece)
{
// see if the pawn is in a move or attack position relative to the
// path of the attacker
char d1 = (defTile+8), d2 = (defTile-8);
// If it's the path, not the attacker self
if(NONE == piece)
{
// if the pawn can move onto the path then it's not check-mate
if(d1 == t1 || d2 == t1)
return OUTCOME_CHECK;
}
else
{
// if it's the attacker itself and the pawn can take it, then
// it's not check-mate
if(d1 != t1 && d2 != t1)
return OUTCOME_CHECK;
}
}
// if the defender is the king and the code is here, it means
// the attacker has backup (or the code won't get here) and the
// king can't take the attacker (because he would still be under attack).
// For any other defender, the defender can take the attacker and
// it's not check-mate
else if(KING != defPiece)
return OUTCOME_CHECK;
}
}
e2 = err << 1;
if(e2 > -dy)
{
err = err - dy;
x1 = x1 + sx;
}
if(e2 < dx)
{
err = err + dx;
y1 = y1 + sy;
}
t1 = MK_POS(y1, x1);
}
return OUTCOME_CHECKMATE;
}
/*-----------------------------------------------------------------------*/
// Make sure gAttackBoard has all the moves in it that's needed to determine a check-mate
// Offset is in the attack DB for the attackers of the king on "side"
void board_UpdateAttackGrid(int offset, char side)
{
char i, j, piece, color, tile, king, attackers, numAttackers, *attackPieces;
// Get a handle to the pieces that are attacking the king, causing check
numAttackers = gpAttackBoard[offset];
attackPieces = &gpAttackBoard[offset+1];
// Backup the king and remove him from the board
tile = gKingData[side];
king = gpChessBoard[tile];
gpChessBoard[tile] = NONE;
for(i=0; i<64; ++i)
{
piece = gpChessBoard[i];
if(piece != NONE)
{
color = (piece & PIECE_WHITE) >> 7;
piece &= PIECE_DATA;
// if the piece is one of the kings' pawns, now a move forward
// is also an "attack" (or a defensive move) that can cut the
// path from the attacker to the king, so add these moves
if(PAWN == piece && color == side)
{
signed char di, d = side ? -8 : 8;
di = i+d;
if(di < 0 || di > 63)
continue;
// Check the square in front of the pawn
if(NONE == gpChessBoard[di])
{
offset = giAttackBoardOffset[di][color];
attackers = gpAttackBoard[offset] + 1;
gpAttackBoard[offset+attackers] = i;
// Keep a log of all modifications so this can be "unwound" at the end
// to return gAttackBoard to the in-game state without re-generating from
// scratch
++gpAttackBoard[offset];
si_fixupTable[sc_numFixes++] = offset;
// and also 2 in front of, if the pawn hasn't moved yet
// if(((side && (i >= 48 && i <= 55)) || (!side && (i >= 8 && i <= 15))) && NONE == gpChessBoard[i+(2*d)])
if(!(gpChessBoard[i] & PIECE_MOVED) && NONE == gpChessBoard[i+(2*d)])
{
offset += (d*ATTACK_WIDTH);
attackers = gpAttackBoard[offset] + 1;
gpAttackBoard[offset+attackers] = i;
++gpAttackBoard[offset];
si_fixupTable[sc_numFixes++] = offset;
}
}
}
// If the piece is an enemy piece, and not a knight
else if(color != side && piece != KNIGHT)
{
// Is the piece directly attacking the king
if(board_findInList(attackPieces, numAttackers, i))
{
// Get a list of all blocks this piece is attacking -
// this list will include blocks "behind" the king, since
// the king has been removed
board_GeneratePossibleMoves(i, 1);
j = 0;
while(j < gNumMoves)
{
offset = giAttackBoardOffset[gPossibleMoves[j++]][color];
attackers = gpAttackBoard[offset];
// See if this tile has already been marked as being attacked by this piece
if(!board_findInList(&gpAttackBoard[offset+1], attackers, i))
{
// If not, mark it as being under attack from this piece -
// these are the tiles "behind" the king
si_fixupTable[sc_numFixes++] = offset;
++gpAttackBoard[offset++];
gpAttackBoard[offset+attackers] = i;
}
}
}
}
}
}
// Put the king back on the board
gpChessBoard[tile] = king;
}
/*-----------------------------------------------------------------------*/
void board_ProcessEnPassant(char state)
{
switch(state)
{
case ENPASSANT_TAKE: // Take the pawn
if((gTile[0] & 7) < (gTile[1] & 7))
gTile[2] = gTile[0] + 1;
else
gTile[2] = gTile[0] - 1;
gpChessBoard[gTile[2]] = NONE;
break;
case ENPASSANT_UNTAKE: // Reverse 1; Restore the pawn
if((gTile[0] & 7) < (gTile[1] & 7))
gTile[2] = gTile[0] + 1;
else
gTile[2] = gTile[0] - 1;
gpChessBoard[gTile[2]] = PAWN | ((1-gColor[0]) ? PIECE_WHITE : 0);
gEPPawn = gTile[1];
break;
case ENPASSANT_MAYBE: // See if the move forwards creates an en passant opportunity
{
char x0 = (gTile[0] / 8), x1 = (gTile[1] / 8);
if(x0 < x1)
{
if(x0 == x1 - 2)
gEPPawn = gTile[0] + 8;
}
else
{
if(x0 == x1 + 2)
gEPPawn = gTile[0] - 8;
}
}
break;
}
}
/*-----------------------------------------------------------------------*/
// This moves the rook and marks its move bit if the king is moving
// to cause a "casteling" move.
// Can be called with the parameters reversed to undo the casteling,
// whcih also clears the move bit on the rook.
void board_ProcessCastling(char a, char b)
{
gTile[2] = gTile[3] = NULL_TILE;
// If the king hasn't moved and is now moving
// 2 spaces, it's already known that the rook hasn't moved
// because that's the only way this shows up as
// a valid move, so no need to check the rook. The reason
// to check the king is to make sure this is the
// first move for the king, otherwise this code should
// not do anything.
if(gTile[1] == gKingMovingTo[gColor[0]][0] && !gMove[0])
{
gTile[2] = gTile[1] - 2;
gTile[3] = gTile[1] + 1;
}
else if(gTile[1] == gKingMovingTo[gColor[0]][1] && !gMove[0])
{
gTile[2] = gTile[1] + 1;
gTile[3] = gTile[1] - 1;
}
if(NULL_TILE != gTile[2])
{
gpChessBoard[gTile[a]] = gpChessBoard[gTile[b]];
if(a > b)
gpChessBoard[gTile[a]] |= PIECE_MOVED;
else
gpChessBoard[gTile[a]] &= ~PIECE_MOVED;
gpChessBoard[gTile[b]] = NONE;
}
}
/*-----------------------------------------------------------------------*/
// This is called to see if the king on "side" is in check-mate
char board_CheckForMate(char side)
{
int offset;
char i, tile, other = 1-side;
// Look at the king's attackers
tile = gKingData[side];
offset = giAttackBoardOffset[tile][other];
// Make gAttackBoard contain all needed attacks and defences
// to determine a check mate state
board_UpdateAttackGrid(offset, side);
// See where the king might hide
board_GeneratePossibleMoves(tile, 0);
// This also triggers if the attacker has no backup and the
// king can take it
for(i=0; i<gNumMoves; ++i)
{
offset = giAttackBoardOffset[gPossibleMoves[i]][other];
if(!gpAttackBoard[offset])
return OUTCOME_CHECK;
}
// Go back to the attackers of the king
offset = giAttackBoardOffset[tile][other];
// If there's more than one attacker then it's mate
if(gpAttackBoard[offset] > 1)
return OUTCOME_CHECKMATE;
// There's only 1 attacker, so work with it
tile = gpAttackBoard[offset+1];
// Deal with a knight attacker here as there is no "interruptable path"
// between a horse attacker and the king
if(KNIGHT == (gpChessBoard[tile] & PIECE_DATA))
{
// Get an offset to the defenders ("attackers" on same side as king)
offset = giAttackBoardOffset[tile][side];
// See if the Horse can be taken by any defenders
if(gpAttackBoard[offset])
return OUTCOME_CHECK;
// If not then it's check-mate
return OUTCOME_CHECKMATE;
}
// See if the attacker can be eliminated or the path between the attacker
// and the king can be blocked
i = board_CheckLineAttack(tile, gKingData[side], side);
// Clean up changes it made to the attack DB in board_UpdateAttackGrid
while(sc_numFixes)
{
--sc_numFixes;
--gpAttackBoard[si_fixupTable[sc_numFixes]];
}
return i;
}
/*-----------------------------------------------------------------------*/
// Utility function to find a number in a series of numbers
char board_findInList(char *list, char numElements, char number)
{
while(numElements--)
{
if(number == list[numElements])
return 1;
}
return 0;
}

21
src/board.h Normal file
View File

@ -0,0 +1,21 @@
/*
* board.h
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#ifndef _BOARD_H_
#define _BOARD_H_
void board_Init();
void board_PlacePieceAttacks();
void board_GeneratePossibleMoves(char position, char addDefenceMove);
char board_ProcessAction(void);
void board_ProcessEnPassant(char state);
void board_ProcessCastling(char a, char b);
char board_CheckForMate(char side);
char board_findInList(char *list, char numElements, char number);
#endif //_BOARD_H_

39
src/c64/chess.cfg Normal file
View File

@ -0,0 +1,39 @@
SYMBOLS {
__LOADADDR__: type = import;
__EXEHDR__: type = import;
__STACKSIZE__: type = weak, value = $0800; # 2k stack
}
MEMORY {
ZP: file = "", define = yes, start = $0002, size = $001A;
LOADADDR: file = %O, start = $07FF, size = $0002;
HEADER: file = %O, start = $0801, size = $000C;
RAM1: file = %O, define = yes, start = $080D, size = $7BF3;
RAM: file = %O, define = yes, start = $9000, size = $3C00 - __STACKSIZE__;
}
SEGMENTS {
LOADADDR: load = LOADADDR, type = ro;
EXEHDR: load = HEADER, type = ro;
STARTUP: load = RAM1, type = ro;
LOWCODE: load = RAM1, type = ro, optional = yes;
INIT: load = RAM1, type = ro, define = yes, optional = yes;
CODE: load = RAM1, type = ro;
RODATA: load = RAM1, type = ro;
DATA: load = RAM1, type = rw;
ZPSAVE: load = RAM, type = bss, define = yes;
BSS: load = RAM, type = bss, define = yes;
ZEROPAGE: load = ZP, type = zp;
}
FEATURES {
CONDES: segment = INIT,
type = constructor,
label = __CONSTRUCTOR_TABLE__,
count = __CONSTRUCTOR_COUNT__;
CONDES: segment = RODATA,
type = destructor,
label = __DESTRUCTOR_TABLE__,
count = __DESTRUCTOR_COUNT__;
CONDES: segment = RODATA,
type = interruptor,
label = __INTERRUPTOR_TABLE__,
count = __INTERRUPTOR_COUNT__;
}

326
src/c64/data.c Normal file
View File

@ -0,0 +1,326 @@
/*
* data.c
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#include "../types.h"
#include "data.h"
/*-----------------------------------------------------------------------*/
// C64 specific graphics for the chess pieces
// The comment shows an icon 16x24
// The data is 24 chars & 24 chars (2 * 8x24). The second set of 24
// is the right-hand side 8-bits of the icon
// The pieces use bit pattern 11 as the transparent color, which uses
// the color from the color memory. Bit pattern 01 is the piece color
// and uses bgcolor1 for black pieces. In code, 01 is switched to 10
// for white pieces. 10 uses bgcolor2 for its color
const char gfxTiles[PAWN][2*24] =
{
{
0xff, /* XXXXXXXXXXXXXXXX */ // 0
0xff, /* XXXXXXXXXXXXXXXX */
0xff, /* XXXXXXXXXXXXXXXX */
0xff, /* XXXXXXXXXXXXXXXX */
0xff, /* XXXXXXXXXXXXXXXX */
0xff, /* XXXXXXXXXXXXXXXX */
0xff, /* XXXXXXXXXXXXXXXX */
0xff, /* XXXXXXXXXXXXXXXX */
0xdd, /* XX-XXX-X-XXX-XXX */
0xdd, /* XX-XXX-X-XXX-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */ /* 48 */
0xff,
0xff,
0xff,
0xff,
0xff,
0xff,
0xff,
0xff,
0x77,
0x77,
0x57,
0x57,
0x57,
0x5f,
0x5f,
0x5f,
0x5f,
0x5f,
0x5f,
0x5f,
0x5f,
0x5f,
0x57,
0x57,
},
{
0xff, /* XXXXXXXXXXXXXXXX */ // 1
0xff, /* XXXXXXXXXXXXXXXX */
0xff, /* XXXXXXXXXXXXXXXX */
0xff, /* XXXXXXXXXXXXXXXX */
0xff, /* XXXXXXXXXX-XXXXX */
0xff, /* XXXXXXXX-X-XXXXX */
0xfd, /* XXXXXX-X-X-X-XXX */
0xf5, /* XXXX-X-X-X-X-XXX */
0xf7, /* XXXX-XXX-X-X-XXX */
0xd7, /* XX-X-XXX-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xdd, /* XX-XXX-X-X-XXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */ /* 96 */
0xff,
0xff,
0xff,
0xff,
0xdf,
0x5f,
0x57,
0x57,
0x57,
0x57,
0x57,
0x57,
0x57,
0x57,
0x5f,
0x7f,
0x7f,
0x7f,
0x7f,
0x5f,
0x5f,
0x5f,
0x57,
0x57,
},
{
0xff, /* XXXXXXXXXXXXXXXX */ // 2
0xff, /* XXXXXXXXXXXXXXXX */
0xff, /* XXXXXXXXXXXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xf5, /* XXXX-X-XXXXXXXXX */
0xf7, /* XXXX-XXXXX-XXXXX */
0xf7, /* XXXX-XXX-X-XXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */ /* 144 */
0xff,
0xff,
0xff,
0x7f,
0xff,
0xdf,
0x5f,
0x7f,
0x7f,
0x7f,
0x5f,
0x5f,
0x7f,
0x7f,
0x7f,
0x7f,
0x7f,
0x7f,
0x7f,
0x5f,
0x5f,
0x57,
0x57,
0x57,
},
{
0xff, /* XXXXXXXXXXXXXXXX */ // 3
0xff, /* XXXXXXXXXXXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */ /* 192 */
0xff,
0xff,
0x7f,
0x5f,
0x57,
0x57,
0x57,
0x7f,
0x5f,
0x57,
0x7f,
0x7f,
0x7f,
0x7f,
0x7f,
0x7f,
0x7f,
0x7f,
0x7f,
0x7f,
0x5f,
0x57,
0x57,
0x57,
},
{
0xff, /* XXXXXXXXXXXXXXXX */ // 4
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */ /* 240 */
0xff,
0x7f,
0x7f,
0x7f,
0x57,
0x57,
0x7f,
0x7f,
0x7f,
0x5f,
0x5f,
0x5f,
0x5f,
0x7f,
0x7f,
0x7f,
0x7f,
0x7f,
0x7f,
0x5f,
0x5f,
0x57,
0x57,
0x57,
},
{
0xff, /* XXXXXXXXXXXXXXXX */ // 5
0xff, /* XXXXXXXXXXXXXXXX */
0xff, /* XXXXXXXXXXXXXXXX */
0xff, /* XXXXXXXXXXXXXXXX */
0xff, /* XXXXXXXXXXXXXXXX */
0xff, /* XXXXXXXXXXXXXXXX */
0xff, /* XXXXXXXXXXXXXXXX */
0xff, /* XXXXXXXXXXXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xfd, /* XXXXXX-X-XXXXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xf5, /* XXXX-X-X-X-XXXXX */
0xd5, /* XX-X-X-X-X-X-XXX */
0xd5, /* XX-X-X-X-X-X-XXX */ /* 288 */
0xff,
0xff,
0xff,
0xff,
0xff,
0xff,
0xff,
0xff,
0x7f,
0x5f,
0x5f,
0x5f,
0x7f,
0x7f,
0x5f,
0x5f,
0x7f,
0x7f,
0x7f,
0x7f,
0x5f,
0x5f,
0x57,
0x57,
}
};
const int gfxTileSetSize = sizeof(gfxTiles) / sizeof(gfxTiles[0]);

15
src/c64/data.h Normal file
View File

@ -0,0 +1,15 @@
/*
* data.h
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#ifndef _DATA_H_
#define _DATA_H_
extern const char gfxTiles[PAWN][2*24];
extern const int gfxTileSetSize;
#endif //_DATA_H_

567
src/c64/plat64.c Normal file
View File

@ -0,0 +1,567 @@
/*
* plat64.c
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#include <c64.h>
#include <conio.h>
#include <string.h>
#include "../types.h"
#include "../globals.h"
#include "../undo.h"
#include "../frontend.h"
#include "../plat.h"
#include "data.h"
/*-----------------------------------------------------------------------*/
// System locations
#define VIC_BASE_RAM (0x8000)
#define SCREEN_RAM ((char*)VIC_BASE_RAM+0x0400)
#define CHARMAP_RAM ((char*)VIC_BASE_RAM+0x0800)
#define COLOUR_RAM ((char*)0xd800)
#define MEM_KRNL_PRNT ((char*)0x288)
/*-----------------------------------------------------------------------*/
#define BOARD_PIECE_WIDTH 4
#define BOARD_PIECE_HEIGHT 3
#define SCREEN_WIDTH 40
#define SCREEN_HEIGHT 25
#define COLOR_OFFSET (int)(COLOUR_RAM - SCREEN_RAM)
#define FONT_INVERSE 0x80
#define FONT_SPACE 0x20
#define BOARD_BLOCK (FONT_INVERSE + FONT_SPACE)
#define SCROLL_SPEED 0xf5
#define LOG_WINDOW_HEIGHT SCREEN_HEIGHT-2
#define CHARMAP_DEST (BOARD_BLOCK + 1)
/*-----------------------------------------------------------------------*/
// Internal function Prototype
char plat_TimeExpired(unsigned int aTime, char *timerInit);
/*-----------------------------------------------------------------------*/
// The charactres to make the [ and ] around the highlighted pieces
static char brackets[4] = {240,237,238,253};
/*-----------------------------------------------------------------------*/
// These variables hold copies of system info that needs to be restored
// before returning back to DOS
static char sc_ddra, sc_pra, sc_vad, sc_ct2, sc_vbc, sc_vbg0, sc_vbg1, sc_vbg2, sc_mcp, sc_txt, sc_x, sc_y;
static char gColor_ram[SCREEN_HEIGHT*SCREEN_WIDTH];
/*-----------------------------------------------------------------------*/
// Called one-time to set up the platform (or computer or whatever)
void plat_Init()
{
int i;
char c;
// Setting this to 0 will not show the "Quit" option in the main menu
gReturnToOS = 1;
if(gReturnToOS)
{
// Save these values so they can be restored
memcpy(gColor_ram, COLOUR_RAM, SCREEN_HEIGHT*SCREEN_WIDTH);
sc_ddra = CIA2.ddra;
sc_pra = CIA2.pra;
sc_vad = VIC.addr;
sc_ct2 = VIC.ctrl2;
sc_vbc = VIC.bordercolor;
sc_vbg0 = VIC.bgcolor0;
sc_vbg1 = VIC.bgcolor1;
sc_vbg2 = VIC.bgcolor2;
sc_mcp = *MEM_KRNL_PRNT;
sc_x = wherex();
sc_y = wherey();
}
// Set up a user defined font, and move the screen to the appropriate position
CIA2.ddra |= 0x03;
CIA2.pra = (CIA2.pra & 0xfc) | (3-(VIC_BASE_RAM / 0x4000));
VIC.addr = ((((int)(SCREEN_RAM - VIC_BASE_RAM) / 0x0400) << 4) + (((int)(CHARMAP_RAM - VIC_BASE_RAM) / 0x0800) << 1));
VIC.ctrl2 |= 16;
VIC.bordercolor = VIC.bgcolor0 = COLOR_LIGHTBLUE;
VIC.bgcolor1 = COLOR_RED;
VIC.bgcolor2 = COLOR_GRAY3;
*MEM_KRNL_PRNT = (int)SCREEN_RAM / 256;
// Save and set the text color
sc_txt = textcolor(COLOR_BLUE);
clrscr();
// Copy the standard font to where the redefined char font will live
CIA1.cra = (CIA1.cra & 0xfe);
*(char*)0x01 = *(char*)0x01 & 0xfb;
memcpy(CHARMAP_RAM,COLOUR_RAM,256*8);
*(char*)0x01 = *(char*)0x01 | 0x04;
CIA1.cra = (CIA1.cra | 0x01);
// copy the charactes to make the chess pieces font.
memcpy(&CHARMAP_RAM[8*CHARMAP_DEST], &gfxTiles[0][0], sizeof(gfxTiles));
// For the second (white) set, set all 01 bits to 10 so it uses bgcolor2
for(i=0; i<sizeof(gfxTiles); ++i)
{
c = ((char*)&gfxTiles)[i];
if(0x01 == (c & 0x03))
{
c &= ~0x03;
c |= 0x02;
}
if(0x04 == (c & 0x0c))
{
c &= ~0x0c;
c |= 0x08;
}
if(0x10 == (c & 0x30))
{
c &= ~0x30;
c |= 0x20;
}
if(0x40 == (c & 0xc0))
{
c &= ~0xc0;
c |= 0x80;
}
CHARMAP_RAM[(8*CHARMAP_DEST)+sizeof(gfxTiles)+i] = c;
}
}
/*-----------------------------------------------------------------------*/
// This is not needed on the C64
void plat_UpdateScreen()
{
}
/*-----------------------------------------------------------------------*/
// Very simple menu with a heading and a scrolling banner as a footer
char plat_Menu(char **menuItems, char height, char *scroller)
{
static char *prevScroller, *pScroller, *pEnd;
int keyMask;
char i, j, sx, sy, numMenuItems, timerInit = 0, maxLen = 0;
// If the scroller message chages, cache the new one
if(prevScroller != scroller)
{
prevScroller = scroller;
pScroller = scroller;
pEnd = scroller + strlen(scroller);
}
// Find the longest entry
for(numMenuItems=0; menuItems[numMenuItems]; ++numMenuItems)
{
char len = strlen(menuItems[numMenuItems]);
if(len > maxLen)
maxLen = len;
}
// Centre on the screen
sy = MAX_SIZE(0, (SCREEN_HEIGHT / 2) - (height / 2) - 1);
sx = MAX_SIZE(0, (SCREEN_WIDTH / 2) - (maxLen / 2) - 1);
maxLen = MIN_SIZE(SCREEN_WIDTH-2, maxLen);
// Show the title
textcolor(COLOR_GREEN);
gotoxy(sx, sy);
cprintf(" %.*s ",maxLen, menuItems[0]);
// Leave a blank line
textcolor(COLOR_BLACK);
gotoxy(sx, ++sy);
for(j=0; j<maxLen+2; ++j)
cputc(' ');
// Show all the menu items
for(i=1; i<numMenuItems; ++i)
{
gotoxy(sx, sy+i);
cprintf(" %.*s ",maxLen, menuItems[i]);
}
// Pad with blank lines to menu height
for(;i<height;++i)
{
gotoxy(sx, sy+i);
for(j=0; j<maxLen+2; ++j)
cputc(' ');
}
// Select the first item
i = 1;
do
{
// Highlight the selected item
gotoxy(sx, sy+i);
textcolor(COLOR_WHITE);
cprintf(">%.*s<",maxLen, menuItems[i]);
textcolor(COLOR_BLACK);
// Look for user input
keyMask = plat_ReadKeys(0);
if(keyMask & INPUT_MOTION)
{
// selection changes so de-highlight the selected item
gotoxy(sx, sy+i);
cprintf(" %.*s ",maxLen, menuItems[i]);
// see if the selection goes up or down
switch(keyMask & INPUT_MOTION)
{
case INPUT_UP:
if(!--i)
i = numMenuItems-1;
break;
case INPUT_DOWN:
if(numMenuItems == ++i)
i = 1;
break;
}
}
keyMask &= (INPUT_SELECT | INPUT_BACKUP);
// Show the scroller
gotoxy(sx,sy+height);
textcolor(COLOR_CYAN);
cprintf(" %.*s ",maxLen, pScroller);
// Wrap the message if needed
if((pEnd - pScroller) < maxLen-1)
{
gotoxy(sx+(pEnd-pScroller)+1,sy+height);
cprintf(" %.*s ",maxLen-(pEnd - pScroller)-1, scroller);
}
// Only update the scrolling when needed
if(plat_TimeExpired(SCROLL_SPEED, &timerInit))
{
++pScroller;
if(!*pScroller)
pScroller = scroller;
}
} while(keyMask != INPUT_SELECT && keyMask != INPUT_BACKUP);
// 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)
{
for(i=0; i<25; ++i)
memset(SCREEN_RAM+1+(i*SCREEN_WIDTH)+(8*BOARD_PIECE_WIDTH), FONT_SPACE, SCREEN_WIDTH-1-(8*BOARD_PIECE_WIDTH));
}
// redraw all tiles
for(i=0; i<64; ++i)
plat_DrawSquare(i);
// Add the A..H and 1..8 tile-keys
textcolor(COLOR_GREEN);
for(i=0; i<8; ++i)
{
gotoxy(3+i*BOARD_PIECE_WIDTH,0);
cprintf("%c",'A'+i);
gotoxy(0,2+i*BOARD_PIECE_HEIGHT);
cprintf("%d",8-i);
}
}
/*-----------------------------------------------------------------------*/
// Draw a tile with background and piece on it for positions 0..63
void plat_DrawSquare(char position)
{
char piece, color, dx, dy;
char y = position / 8, x = position & 7;
char* drawDest = (char*)SCREEN_RAM+SCREEN_WIDTH+(y*SCREEN_WIDTH*BOARD_PIECE_HEIGHT)+(x*BOARD_PIECE_WIDTH)+1;
char blackWhite = !((x & 1) ^ (y & 1));
if(blackWhite)
color = COLOR_WHITE;
else
color = COLOR_BLACK;
// Unsofisticated draw
for(dy=0; dy<BOARD_PIECE_HEIGHT; ++dy)
{
for(dx=0; dx<BOARD_PIECE_WIDTH; ++dx)
{
*drawDest = BOARD_BLOCK;
*(drawDest+COLOR_OFFSET) = 8+color; // Color's > 8 are multi-color mode colors
drawDest += 1;
}
drawDest += (SCREEN_WIDTH - BOARD_PIECE_WIDTH);
}
// Show the attack numbers
if(gShowAttackBoard)
{
textcolor(COLOR_RED);
gotoxy(1+x*BOARD_PIECE_WIDTH,(y+1)*BOARD_PIECE_HEIGHT);
cprintf("%d",(gpAttackBoard[giAttackBoardOffset[position][0]]));
textcolor(COLOR_YELLOW);
gotoxy(1+x*BOARD_PIECE_WIDTH+3,(y+1)*BOARD_PIECE_HEIGHT);
cprintf("%d",(gpAttackBoard[giAttackBoardOffset[position][1]]));
gotoxy(1+x*BOARD_PIECE_WIDTH,1+y*BOARD_PIECE_HEIGHT);
cprintf("%02X",gChessBoard[y][x]);
gotoxy(1+x*BOARD_PIECE_WIDTH+3,1+y*BOARD_PIECE_HEIGHT);
cprintf("%d",(gChessBoard[y][x]&PIECE_WHITE)>>7);
}
drawDest -= ((BOARD_PIECE_HEIGHT * SCREEN_WIDTH)-1);
// Get the piece data to draw the piece over the tile
piece = gChessBoard[y][x];
color = piece & PIECE_WHITE;
piece &= PIECE_DATA;
if(piece)
{
// The white pieces are 36 characters after the start of the black pieces
// (6 pieces at 6 characters each to draw a piece)
if(color)
color = 36;
color += CHARMAP_DEST+(piece-1)*6;
drawDest[0] = color;
drawDest[1] = color+3;
drawDest[SCREEN_WIDTH] = color+1;
drawDest[SCREEN_WIDTH+1] = color+4;
drawDest[2*SCREEN_WIDTH] = color+2;
drawDest[2*SCREEN_WIDTH+1] = color+5;
}
}
/*-----------------------------------------------------------------------*/
void plat_ShowSideToGoLabel(char side)
{
gotoxy(2+8*BOARD_PIECE_WIDTH,0);
textcolor(side);
cprintf("%s",gszSideLabel[side]);
}
/*-----------------------------------------------------------------------*/
void plat_Highlight(char position, char color)
{
char y = position / 8, x = position & 7;
char *drawDest = (char*)SCREEN_RAM+SCREEN_WIDTH+(y*SCREEN_WIDTH*BOARD_PIECE_HEIGHT)+(x*BOARD_PIECE_WIDTH)+1;
drawDest[COLOR_OFFSET] = color;
drawDest[COLOR_OFFSET+SCREEN_WIDTH] = color;
drawDest[COLOR_OFFSET+2*SCREEN_WIDTH] = color;
drawDest[COLOR_OFFSET+BOARD_PIECE_WIDTH-1] = color;
drawDest[COLOR_OFFSET+SCREEN_WIDTH+BOARD_PIECE_WIDTH-1] = color;
drawDest[COLOR_OFFSET+2*SCREEN_WIDTH+BOARD_PIECE_WIDTH-1] = color;
drawDest[0] = brackets[0];
drawDest[2*SCREEN_WIDTH] = brackets[1];
drawDest[BOARD_PIECE_WIDTH-1] = brackets[2];
drawDest[2*SCREEN_WIDTH+BOARD_PIECE_WIDTH-1] = brackets[3];
}
/*-----------------------------------------------------------------------*/
void plat_ShowMessage(char *str, char color)
{
gotoxy(1+(8*BOARD_PIECE_WIDTH),SCREEN_HEIGHT-1);
textcolor(color);
cprintf("%.*s",SCREEN_WIDTH-1-(8*BOARD_PIECE_WIDTH),str);
}
/*-----------------------------------------------------------------------*/
void plat_ClearMessage()
{
memset(SCREEN_RAM+1+(SCREEN_HEIGHT-1)*SCREEN_WIDTH+8*BOARD_PIECE_WIDTH,FONT_SPACE,SCREEN_WIDTH-1-8*BOARD_PIECE_WIDTH);
}
/*-----------------------------------------------------------------------*/
void plat_AddToLogWin()
{
char i;
// Scroll the log up
for(i=2; i<SCREEN_HEIGHT-1; ++i)
{
memcpy(SCREEN_RAM+1+((i-1)*SCREEN_WIDTH)+(8*BOARD_PIECE_WIDTH), SCREEN_RAM+1+(i*SCREEN_WIDTH)+(8*BOARD_PIECE_WIDTH), SCREEN_WIDTH-1-(8*BOARD_PIECE_WIDTH));
memcpy(COLOR_OFFSET+SCREEN_RAM+1+((i-1)*SCREEN_WIDTH)+(8*BOARD_PIECE_WIDTH), COLOR_OFFSET+SCREEN_RAM+1+(i*SCREEN_WIDTH)+(8*BOARD_PIECE_WIDTH), SCREEN_WIDTH-1-(8*BOARD_PIECE_WIDTH));
}
memset(SCREEN_RAM+1+(SCREEN_HEIGHT-2)*SCREEN_WIDTH+8*BOARD_PIECE_WIDTH,FONT_SPACE,SCREEN_WIDTH-1-8*BOARD_PIECE_WIDTH);
// format and draw the information to the bottom of the log area
frontend_FormatLogString();
gotoxy(2+(8*BOARD_PIECE_WIDTH),SCREEN_HEIGHT-2);
textcolor(gColor[0] ? HCOLOR_WHITE : HCOLOR_BLACK);
cprintf("%.*s",SCREEN_WIDTH-2-(8*BOARD_PIECE_WIDTH),gLogStrBuffer);
}
/*-----------------------------------------------------------------------*/
// Important note about this function is that it alters the gTile...
// global data trackers so beware when calling it
void plat_AddToLogWinTop()
{
char i;
// Scroll the log down
for(i=SCREEN_HEIGHT-2; i>1; --i)
{
memcpy(SCREEN_RAM+1+(i*SCREEN_WIDTH)+(8*BOARD_PIECE_WIDTH), SCREEN_RAM+1+((i-1)*SCREEN_WIDTH)+(8*BOARD_PIECE_WIDTH), SCREEN_WIDTH-1-(8*BOARD_PIECE_WIDTH));
memcpy(COLOR_OFFSET+SCREEN_RAM+1+(i*SCREEN_WIDTH)+(8*BOARD_PIECE_WIDTH), COLOR_OFFSET+SCREEN_RAM+1+((i-1)*SCREEN_WIDTH)+(8*BOARD_PIECE_WIDTH), SCREEN_WIDTH-1-(8*BOARD_PIECE_WIDTH));
}
memset(SCREEN_RAM+1+SCREEN_WIDTH+8*BOARD_PIECE_WIDTH,FONT_SPACE,SCREEN_WIDTH-1-8*BOARD_PIECE_WIDTH);
// If an older entry is there to become visible, add it at the top of the log
if(undo_FindUndoLine(SCREEN_HEIGHT-3))
{
frontend_FormatLogString();
gotoxy(2+(8*BOARD_PIECE_WIDTH),1);
textcolor(gColor[0] ? HCOLOR_WHITE : HCOLOR_BLACK);
cprintf("%.*s",SCREEN_WIDTH-2-(8*BOARD_PIECE_WIDTH),gLogStrBuffer);
}
}
/*-----------------------------------------------------------------------*/
// Use timer B to time a duration
char plat_TimeExpired(unsigned int aTime, char *timerInit)
{
if(!*timerInit || (CIA1.tb_lo < aTime))
{
*timerInit = 1;
CIA1.crb &= 0xfe;
CIA1.tb_lo = 0xff;
CIA1.tb_hi = 0xff;
CIA1.crb |= 0x41;
return 1;
}
return 0;
}
/*-----------------------------------------------------------------------*/
int plat_ReadKeys(char blocking)
{
char key = 0;
int keyMask = 0;
if(blocking || kbhit())
key = cgetc();
else
return 0;
switch(key)
{
case 145: // Up
keyMask |= INPUT_UP;
break;
case 29: // Right
keyMask |= INPUT_RIGHT;
break;
case 17: // Down
keyMask |= INPUT_DOWN;
break;
case 157: // Left
keyMask |= INPUT_LEFT;
break;
case 3: // Esc
keyMask |= INPUT_BACKUP;
break;
case 65: // 'a' - Show Attackers
keyMask |= INPUT_TOGGLE_A;
break;
case 66: // 'b' - Board attacks - Show all attacks
keyMask |= INPUT_TOGGLE_B;
break;
case 68: // 'd' - Show Defenders
keyMask |= INPUT_TOGGLE_D;
break;
case 77: // 'm' - Menu
keyMask |= INPUT_MENU;
break;
case 13: // Enter
keyMask |= INPUT_SELECT;
break;
case 82:
keyMask |= INPUT_REDO;
break;
case 85:
keyMask |= INPUT_UNDO;
break;
// default: // Debug - show key code
// {
// char s[] = "Key:000";
// s[4] = (key/100)+'0';
// key -= (s[4] - '0') * 100;
// s[5] = (key/10)+'0';
// s[6] = (key%10)+'0';
// plat_ShowMessage(s,COLOR_RED);
// }
break;
}
return keyMask;
}
/*-----------------------------------------------------------------------*/
// Restore the C64 to the state it was in before RUN was typed. Only
// ever gets called if gReturnToOS is true
void plat_Shutdown()
{
CIA2.ddra = sc_ddra;
CIA2.pra = sc_pra;
VIC.addr = sc_vad;
VIC.ctrl2 = sc_ct2;
VIC.bordercolor = sc_vbc;
VIC.bgcolor0 = sc_vbg0;
VIC.bgcolor1 = sc_vbg1;
VIC.bgcolor2 = sc_vbg2;
*MEM_KRNL_PRNT = sc_mcp;
CIA1.crb &= 0xfe;
CIA1.tb_lo = 0xff;
CIA1.tb_hi = 0xff;
CIA1.crb = 0x8;
textcolor(sc_txt);
gotoxy(sc_x, sc_y);
memcpy(COLOUR_RAM, gColor_ram, SCREEN_HEIGHT*SCREEN_WIDTH);
// didn't want to make a new crt0.s just for this but if the C64
// was aleady in lowercase before run, then this is wrong ;)
__asm__("lda #142");
__asm__("jsr $ffd2");
}

713
src/cpu.c Normal file
View File

@ -0,0 +1,713 @@
/*
* cpu.c
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#include <limits.h>
#include <string.h>
#include "types.h"
#include "globals.h"
#include "undo.h"
#include "board.h"
#include "cpu.h"
#include "frontend.h"
#include "plat.h"
/*-----------------------------------------------------------------------*/
// Used to keep a sorted array of scores with source-destination tiles
typedef struct tagScore
{
char m_piece;
char m_source;
char m_dest;
int m_score;
} t_score;
/*-----------------------------------------------------------------------*/
// Internal function Prototype
void cpu_AddScoreSorted(t_score array[], char width, char source, char dest, int score);
void cpu_InitPieceData(char side);
void cpu_HolisticScore(char side);
int cpu_SourceScore(char position);
int cpu_DestScore(char position, char destination);
void cpu_ScorePieceMoves();
int cpu_FindBestOpponentMove(char side, char *from, char *to);
int cpu_ScorePieceSubTree(char level, char side, signed char sign, char src, char dst);
/*-----------------------------------------------------------------------*/
static const signed char scsc_neighbours[] = {-9,-8,-7,-1,1,7,8,9};
static t_score sts_pieceScores[NUM_PIECES_SIDE], sts_moveScore[MAX_PIECE_MOVES];
static char sc_index[64], sc_myMoves[MAX_PIECE_MOVES], sc_numMyMoves;
/*-----------------------------------------------------------------------*/
// Insert scores into an array making sure valid destinations end up
// at the head (index[0]), sorted by score
void cpu_AddScoreSorted(t_score array[], char width, char source, char dest, int score)
{
char i;
if(NULL_TILE == dest)
return;
for(i=0; i<width; ++i)
{
if(array[i].m_dest == NULL_TILE || score >= array[i].m_score)
{
memmove(&array[i+1], &array[i], ((width-1)-i)*sizeof(t_score));
array[i].m_source = source;
array[i].m_dest = dest;
array[i].m_score = score;
break;
}
}
}
/*-----------------------------------------------------------------------*/
// Init two helper data structures
void cpu_InitPieceData(char side)
{
char i, piece, iPiece = 0;
// Init the twho helper arrays. The sc_index is easily removed
// but having it takes less space than the code to do the
// lookups on the C64 so it saves space in RAM1
memset(sc_index, NULL_TILE, 64);
memset(sts_pieceScores, NULL_TILE, sizeof(sts_pieceScores));
for(i=0;i<64;++i)
{
piece = gpChessBoard[i];
if(NONE != piece)
{
if((piece & PIECE_WHITE) >> 7 == side)
{
// if the piece is on the side the AI is running for, set
// index to point back at the iPiece'th number of the piece (iPiece'th
// being where it was encountered in the scan of the board from tile 0 to 63)
// and init the sts_pieceScores for that iPiece number
sc_index[i] = iPiece;
sts_pieceScores[iPiece].m_piece = piece & PIECE_DATA;
sts_pieceScores[iPiece].m_source = i;
sts_pieceScores[iPiece].m_score = 0;
++iPiece;
}
}
}
}
/*-----------------------------------------------------------------------*/
// This routine is where any code that looks at the global board should
// go. What's here now does only this: a) If the king doesn't have a valid
// move (like the start of the game), encourage any pieces next to the
// king, on his side, to move so the king has an escape; b) encourage
// pawns to keep moving forward, more so the closer they get to
// promotion.
// This routine probably needs a lot more to make the chess any good,
// for example looking at freeing up a pawn stuck behind a pawn of its
// own color, or getting a sense for where the pressure will be and
// putting a defence in place
void cpu_HolisticScore(char side)
{
int offset;
char i, nTile, nPiece, scratch, src;
// Get the king
src = gKingData[side];
board_GeneratePossibleMoves(src, 0);
// If the king has nowhere to go
if(!gNumMoves)
{
// encourage a neighbour to make space for the king
for(i=0; i<8; ++i)
{
// For each neighbour
nTile = src+scsc_neighbours[i];
if(nTile<64)
{
// See if it's a piece and if the neighbour piece is on the same side as the king
nPiece = sc_index[nTile];
if(NULL_TILE != nPiece)
{
// See who's attacking it
offset = giAttackBoardOffset[nTile][1-side];
scratch = gpAttackBoard[offset];
// if it has no attackers, encourage it to move
if(!scratch)
sts_pieceScores[nPiece].m_score = 4;
// if the neighbour is a pawn and the pawn has one attacker
else if(1 == scratch && PAWN == sts_pieceScores[nPiece].m_piece)
{
// Get the tile of the attacker
scratch = gpAttackBoard[offset+1];
// if it is a vertical (head-on) attack, encourage the pawn to step forward
if((nTile & 7) == (scratch & 7))
sts_pieceScores[nPiece].m_score = 4;
}
}
}
}
}
// Take a look at the pawns
for(i=0;i<NUM_PIECES_SIDE;++i)
{
if(PAWN == sts_pieceScores[i].m_piece)
{
// See if the pawn can move up
nTile = sts_pieceScores[i].m_source + (side ? -8 : 8);
if(nTile < 64)
{
nPiece = sc_index[nTile];
if(NULL_TILE == nPiece)
{
// See if the next block is under attack
offset = giAttackBoardOffset[nTile][1-side];
scratch = gpAttackBoard[offset];
// if the pawn can move because the square isn't under attack or is well defended
if(!scratch || scratch < giAttackBoardOffset[nTile][side])
{
scratch = (nTile / 8);
if(side)
scratch = 7 - scratch;
// encourage the PAWN to move
sts_pieceScores[i].m_score += scratch;
}
}
}
}
}
}
/*-----------------------------------------------------------------------*/
// This routine scores a piece where it stands. Positive "score" encourages
// it to move, negative score discourages a move to a new spot
int cpu_SourceScore(char position)
{
char i, color, piece, value, tile, targetPiece, other, found;
int score = 0;
// Extract the piece data
piece = gpChessBoard[position];
color = (piece & PIECE_WHITE) >> 7;
piece &= PIECE_DATA;
value = gPieceValues[piece];
other = 1-color;
// If this piece is under attack increase chance to move, else decrease
if(gpAttackBoard[giAttackBoardOffset[position][other]])
score += value;
else
score -= 1;
// If this piece is being defended, decrease chance to move, else increase
if(gpAttackBoard[giAttackBoardOffset[position][color]])
score -= 1;
else
score += 1;
// Generate moves, including defend/attack moves
board_GeneratePossibleMoves(position, 1);
found = 0;
for(i=0;i<gNumMoves;++i)
{
tile = gPossibleMoves[i];
targetPiece = gpChessBoard[gPossibleMoves[i]];
if(NONE != targetPiece)
{
// Providing support to a piece on own team
if((targetPiece & PIECE_WHITE)>> 7 == color)
{
found = 1;
// discourage move
score -= 1;
// Is the piece being supported under attack
if(gpAttackBoard[giAttackBoardOffset[tile][other]])
{
// Is position the only defender of the piece under attack, then discourace moving
if(1 == gpAttackBoard[giAttackBoardOffset[tile][color]])
score -= 2;
else
score += 1;
// If the supported piece is more valuable than the position piece then discourage move
if(gPieceValues[targetPiece & PIECE_DATA] > value)
score -= 2;
else
score += 1;
}
}
}
}
// If not supporting a piece, encourage a move
if(!found)
score += 1;
return score;
}
/*-----------------------------------------------------------------------*/
// This routine scores a piece should it move from position to destination
int cpu_DestScore(char position, char destination)
{
char i, color, piece, value, tile, targetPiece, other;
int score = 0;
// extract the piece at position
piece = gpChessBoard[position];
color = (piece & PIECE_WHITE) >> 7;
piece &= PIECE_DATA;
value = gPieceValues[piece];
other = 1-color;
// If this piece will be under attack decrease chance to move
if(gpAttackBoard[giAttackBoardOffset[destination][other]])
score -= value;
// Will be defended
if(gpAttackBoard[giAttackBoardOffset[destination][color]])
score += 1;
else
score -= 1;
// If a piece is taken at destination, the score is the value of the piece
targetPiece = gpChessBoard[destination];
if(NONE != targetPiece && color != ((targetPiece & PIECE_WHITE) >> 7))
score += gPieceValues[targetPiece & PIECE_DATA];
// "Move" the piece to destination
gpChessBoard[destination] = gpChessBoard[position];
// Generate moves, including defend/attack moves
board_GeneratePossibleMoves(destination, 1);
// Restore the destination piece
gpChessBoard[destination] = targetPiece;
for(i=0;i<gNumMoves;++i)
{
tile = gPossibleMoves[i];
targetPiece = gpChessBoard[gPossibleMoves[i]];
if(NONE != targetPiece)
{
// Providing support to a piece on my team
if((targetPiece & PIECE_WHITE)>> 7 == color)
{
// encourage move
score += 1;
// Is the piece being supported under attack
if(gpAttackBoard[giAttackBoardOffset[tile][other]])
{
// Encourage move
score += 1;
// Well dest be the only defender of the piece under attack, then encourage moving
if(1 == gpAttackBoard[giAttackBoardOffset[tile][color]])
score += 2;
// If the supported piece is more valuable than the position piece then encourage move
if(gPieceValues[targetPiece & PIECE_DATA] > value)
score += 1;
}
}
else
{
// Attacking a piece on the other team from dest is encouraged
score += 1;
// If the piece attacked is of greater value than self
targetPiece = gPieceValues[targetPiece & PIECE_DATA];
if(targetPiece > value)
score += targetPiece - value;
// The piece that will be attacked has no support
if(!gpAttackBoard[giAttackBoardOffset[tile][other]])
score += 2;
}
}
}
return score;
}
/*-----------------------------------------------------------------------*/
void cpu_ScorePieceMoves()
{
char i, j, src, dest;
int sourceScore;
// Look at all the pieces on this side, using the support data structure
for(i=0;i<NUM_PIECES_SIDE;++i)
{
// Only check pieces that are still on the board
src = sts_pieceScores[i].m_source;
if(NULL_TILE == src)
break;
// For every piece, make sure the array for scores to every dest is reset
memset(sts_moveScore, NULL_TILE, sizeof(sts_moveScore));
// Generate all the moves for the piece
board_GeneratePossibleMoves(src, 0);
if(!gNumMoves)
continue;
// Back up the moves since the global buffer is reued
memcpy(sc_myMoves, gPossibleMoves, gNumMoves);
sc_numMyMoves = gNumMoves;
// Calc a score at source
sourceScore = cpu_SourceScore(src);
for(j = 0; j<sc_numMyMoves; ++j)
{
dest = sc_myMoves[j];
// Sort the scores at each dest + source into the sts_moveScore array
cpu_AddScoreSorted(sts_moveScore, MAX_PIECE_MOVES, src, dest, sourceScore + cpu_DestScore(src, dest));
}
// Start with the top scoring move, moving through the moves, see if
// there's a valid move
j = 0;
gOutcome = OUTCOME_INVALID;
while(j < MAX_PIECE_MOVES && NULL_TILE != sts_moveScore[j].m_dest && OUTCOME_INVALID == gOutcome)
{
// Prepare to call board_ProcessAction
gTile[0] = sts_moveScore[j].m_source;
gTile[1] = sts_moveScore[j].m_dest;
gPiece[0] = gpChessBoard[gTile[0]];
gPiece[1] = gpChessBoard[gTile[1]];
gColor[0] = (gPiece[0] & PIECE_WHITE) >> 7;
gColor[1] = (gPiece[1] & PIECE_WHITE) >> 7;
gMove[0] = gPiece[0] & PIECE_MOVED;
gMove[1] = gPiece[1] & PIECE_MOVED;
gPiece[0] &= PIECE_DATA;
gPiece[1] &= PIECE_DATA;
// See if the move is valid
if(OUTCOME_INVALID != (gOutcome = board_ProcessAction()))
{
// if it was a valid move, add it to undo, and immediately undo the move
undo_AddMove();
undo_Undo();
}
else
++j;
}
// If a valid move was found, add it as the move for this piece
if(OUTCOME_INVALID != gOutcome)
{
// Add the dest and score as the best (highest scoring)
// move for this piece on this side.
sts_pieceScores[i].m_dest = sts_moveScore[j].m_dest;
sts_pieceScores[i].m_score += sts_moveScore[j].m_score;
}
}
}
/*-----------------------------------------------------------------------*/
// This function looks at all the pieces on a side and finds the top
// scoring move, tracking the top MAX_PIECE_MOVES moves, one of which
// is hopefully actually valid
int cpu_FindBestOpponentMove(char side, char *from, char *to)
{
char i, j, piece, color, move;
int score;
memset(sts_moveScore, NULL_TILE, sizeof(sts_moveScore));
for(i=0;i<64;++i)
{
piece = gpChessBoard[i];
if(NONE != piece)
{
color = (piece & PIECE_WHITE) >> 7;
if(side == color)
{
// For every piece on "side" generate moves
board_GeneratePossibleMoves(i, 0);
if(!gNumMoves)
continue;
memcpy(sc_myMoves, gPossibleMoves, gNumMoves);
sc_numMyMoves = gNumMoves;
// Score the source
score = cpu_SourceScore(i);
for(j = 0; j<sc_numMyMoves; ++j)
{
move = sc_myMoves[j];
// Sort the scores at each dest + source into the sts_moveScore array
cpu_AddScoreSorted(sts_moveScore, MAX_PIECE_MOVES, i, move, score + cpu_DestScore(i, move));
}
}
}
}
j = 0;
// If deepThinking then validate the moves
if(gDeepThoughts)
{
gOutcome = OUTCOME_INVALID;
while(j < MAX_PIECE_MOVES && NULL_TILE != sts_moveScore[j].m_dest && OUTCOME_INVALID == gOutcome)
{
// Prepare to call board_ProcessAction
gTile[0] = sts_moveScore[j].m_source;
gTile[1] = sts_moveScore[j].m_dest;
gPiece[0] = gpChessBoard[gTile[0]];
gPiece[1] = gpChessBoard[gTile[1]];
gColor[0] = (gPiece[0] & PIECE_WHITE) >> 7;
gColor[1] = (gPiece[1] & PIECE_WHITE) >> 7;
gMove[0] = gPiece[0] & PIECE_MOVED;
gMove[1] = gPiece[1] & PIECE_MOVED;
gPiece[0] &= PIECE_DATA;
gPiece[1] &= PIECE_DATA;
// Do the move
if(OUTCOME_INVALID != (gOutcome = board_ProcessAction()))
{
// if it was a valid move, add it to undo, and immediately undo the move
undo_AddMove();
undo_Undo();
}
else
++j;
}
// If no valid moves are found, return a score of INT_MIN/2
if(MAX_PIECE_MOVES == j)
return INT_MIN/2;
}
*from = sts_moveScore[j].m_source;
*to = sts_moveScore[j].m_dest;
return sts_moveScore[j].m_score;
}
/*-----------------------------------------------------------------------*/
// This function effects a move and then looks for the best move on the
// opposing team. It will recursively call itself with the outcome
// up to gMaxLevel's. The sign flips between 1 and -1. The score is
// multiplied by the sign so you (1), opponent (-1), you (1), etc. will
// calc a score for your team subtracting victories for the opponent
// in the deeper levels but adding in your own victories in those deeper
// levels, generating a single comprhensive score for the impact of this move
int cpu_ScorePieceSubTree(char level, char side, signed char sign, char src, char dst)
{
int score;
char pieces[2], from, to, col;
// This move is known (maybe only assumed -based on deepThinking)
// to be valid. Back up the pieces and make the move
pieces[0] = gpChessBoard[src];
pieces[1] = gpChessBoard[dst];
// If the king is taken, stop looking
if(KING == (pieces[1] & PIECE_DATA))
return gPieceValues[KING];
// Make the move, set the move bit and update the king tracker if needed
gpChessBoard[src] = NONE;
gpChessBoard[dst] = pieces[0] | PIECE_MOVED;
if(KING == (pieces[0] & PIECE_DATA))
gKingData[(col = (pieces[0] & PIECE_WHITE)>>7)] = dst;
// See if this move creates an en passant opportunity for the opposing team
gEPPawn = NULL_TILE;
if(!(pieces[0] & PIECE_MOVED) && PAWN == (pieces[0] & PIECE_DATA))
{
gTile[0] = src;
gTile[1] = dst;
board_ProcessEnPassant(ENPASSANT_MAYBE);
}
// If deep thinking, update the attack db to be accurate
if(gDeepThoughts)
board_PlacePieceAttacks();
// Find what the opponent will likely do
score = cpu_FindBestOpponentMove(1-side, &from, &to);
if(score > INT_MIN/2)
{
// Invert for opposing side
score = (score * sign);
// Then follow that move for gMaxLevel's deep
if(level < gMaxLevel)
score += cpu_ScorePieceSubTree(level+1, 1-side, sign ^ 0xfe, from, to);
}
else
{
// INT_MIN/2 is artificial for a dead-end sub-tree.
// I haven't thought this through
score = (INT_MIN/2) * sign;
}
// restore the backed up pieces, including possibly the king-tracker
gpChessBoard[src] = pieces[0];
gpChessBoard[dst] = pieces[1];
if(KING == (pieces[0] & PIECE_DATA))
gKingData[col] = src;
return score;
}
/*-----------------------------------------------------------------------*/
// main entry point for the AI play
char cpu_Play(char side)
{
t_score ts_pieceScore[NUM_PIECES_SIDE];
char i, best;
int iBest = INT_MIN;
// This is realy just to avoid the pop-up for pawn promotion
gAI = 1;
// Set up some helper structures
cpu_InitPieceData(side);
// Take a holistic view of the board
cpu_HolisticScore(side);
// Get a score for all pieces on the AI side
cpu_ScorePieceMoves();
memset(ts_pieceScore, NULL_TILE, sizeof(ts_pieceScore));
// Sort the AI piece's scores by score and validity
for(i=0;i<NUM_PIECES_SIDE;++i)
cpu_AddScoreSorted(ts_pieceScore, NUM_PIECES_SIDE, sts_pieceScores[i].m_source, sts_pieceScores[i].m_dest, sts_pieceScores[i].m_score);
i = 0;
best = 0;
// For the best scores up to gWidth, calculate a sub-tree score. Sub tree is what move will the oponent likely make,
// then the AI then the oponent, etc., up to gMaxLevel's deep
for(i=0; i<gWidth; ++i)
{
// Only consider valid moves
if(NULL_TILE == ts_pieceScore[i].m_dest)
continue;
// Score the move and its sub-tree
ts_pieceScore[i].m_score += cpu_ScorePieceSubTree(0, side, -1, ts_pieceScore[i].m_source, ts_pieceScore[i].m_dest);
// If it's better than the previous best score, consider this the best score
if(ts_pieceScore[i].m_score >= iBest)
{
best = i;
iBest = ts_pieceScore[i].m_score;
}
}
// If deep thinking, update the attack db to be accurate
if(gDeepThoughts)
board_PlacePieceAttacks();
// If the destination move is not valid, there's no valid move so stalemate
if(NULL_TILE == ts_pieceScore[best].m_dest)
{
char tile[2], piece[2], color[2], move[2], outcome;
// See what the last move was
undo_FindUndoLine(0);
// if the outcome was already something other than OK (like check-mate)
// then nothing more needs happen here
if(OUTCOME_OK == gOutcome)
{
// a hack of sorts to make the outcome in the undo-stack be
// stalemate so the correct outcome is displayed along the
// moved that caused it, which was actually the last move
// the opponent made, but it has only been detected now.
// save the undo state of the last move
tile[0] = gTile[0];
tile[1] = gTile[1];
piece[0] = gPiece[0];
piece[1] = gPiece[1];
color[0] = gColor[0];
color[1] = gColor[1];
move[0] = gMove[0];
move[1] = gMove[1];
outcome = gOutcome;
// undo the move that caused stalemate
undo_Undo();
frontend_LogMove(1);
// restore that state, but set the outcome to
// OUTCOME_STALEMATE
gTile[0] = tile[0];
gTile[1] = tile[1];
gPiece[0] = piece[0];
gPiece[1] = piece[1];
gColor[0] = color[0];
gColor[1] = color[1];
gMove[0] = move[0];
gMove[1] = move[1];
board_ProcessAction();
gOutcome = OUTCOME_STALEMATE;
// Add this as the move
undo_AddMove();
// Log the move to update the display
frontend_LogMove(0);
}
// will exit so turn off the flag
gAI = 0;
// returning stalemate, even if it's already check-mate is
// fine as it only means the side-to-go status isn't changed
return OUTCOME_STALEMATE;
}
// Set up to do board_ProcessAction
gTile[0] = ts_pieceScore[best].m_source;
gTile[1] = ts_pieceScore[best].m_dest;
gPiece[0] = gpChessBoard[gTile[0]];
gPiece[1] = gpChessBoard[gTile[1]];
gColor[0] = (gPiece[0] & PIECE_WHITE) >> 7;
gColor[1] = (gPiece[1] & PIECE_WHITE) >> 7;
gMove[0] = (gPiece[0] & PIECE_MOVED);
gMove[1] = (gPiece[1] & PIECE_MOVED);
gPiece[0] &= PIECE_DATA;
gPiece[1] &= PIECE_DATA;
// The move will have been previously tested so will be valid
if(OUTCOME_INVALID != (gOutcome = board_ProcessAction()))
{
// If no piece is taken, up the counter or reset it to zero
if(NONE != (gPiece[1] & PIECE_DATA))
gMoveCounter = 0;
else if(NUM_MOVES_TO_DRAW == ++gMoveCounter)
gOutcome = OUTCOME_DRAW;
// Add the AI move to the undo stack
undo_AddMove();
// Update the display
plat_DrawSquare(gTile[0]);
plat_DrawSquare(gTile[1]);
if(NULL_TILE != gTile[2])
{
plat_DrawSquare(gTile[2]);
gTile[2] = NULL_TILE;
}
if(NULL_TILE != gTile[3])
{
plat_DrawSquare(gTile[3]);
gTile[3] = NULL_TILE;
}
// Log the move
frontend_LogMove(0);
}
gAI = 0;
return gOutcome;
}

14
src/cpu.h Normal file
View File

@ -0,0 +1,14 @@
/*
* cpu.h
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#ifndef _CPU_H_
#define _CPU_H_
char cpu_Play(char side);
#endif //_CPU_H_

174
src/frontend.c Normal file
View File

@ -0,0 +1,174 @@
/*
* frontend.c
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#include "types.h"
#include "globals.h"
#include "plat.h"
#include "frontend.h"
/*-----------------------------------------------------------------------*/
// All menu's in the games are the same height
#define MENU_HEIGHT 6
/*-----------------------------------------------------------------------*/
// Show the main menu and deal with the selection. If a confirmation or
// side-selection is needed, show that and deal with that oucome as well.
char frontend_Menu(char activeGame)
{
char outcome = OUTCOME_MENU;
// If there's a game in progress, show the "Resume" option
if(activeGame)
{
gMainMenu[4] = gszQuit;
gMainMenu[5] = gszResume;
}
else
{
// if no game in progress, only show the quit (to dos) option
// if okay to do so
gMainMenu[4+gReturnToOS] = 0;
}
while(OUTCOME_MENU == outcome)
{
// Show the main menu
switch(plat_Menu(gMainMenu, MENU_HEIGHT, gszAbout))
{
// back-up
case 0:
if(activeGame)
outcome = OUTCOME_OK;
else
outcome = OUTCOME_QUIT;
break;
// 1 player
case 1:
// Show the menu allowing the user to choose a side
switch(plat_Menu(gColorMenu, MENU_HEIGHT, gszAbout))
{
case 1:
gUserMode = USER_WHITE;
outcome = OUTCOME_OK;
break;
case 2:
gUserMode = USER_BLACK;
outcome = OUTCOME_OK;
break;
}
break;
// 2 human players
case 2:
gUserMode = USER_BLACK | USER_WHITE;
outcome = OUTCOME_OK;
break;
// Both players AI
case 3:
gUserMode = 0;
outcome = OUTCOME_OK;
break;
// Quit
case 4:
outcome = OUTCOME_QUIT;
break;
// Resume
case 5:
outcome = OUTCOME_OK;
break;
}
if(OUTCOME_QUIT == outcome)
{
// Get confirmation of quit
if(1 != plat_Menu(gAreYouSureMenu, MENU_HEIGHT, gszAbout))
outcome = OUTCOME_MENU;
else if(activeGame)
outcome = OUTCOME_ABANDON;
}
else
{
if(gUserMode != 3)
{
char skill = plat_Menu(gSkillMenu, MENU_HEIGHT, gszAbout);
if(skill)
{
skill -= 1;
skill *= 3;
gWidth = gSkill[skill];
gMaxLevel = gSkill[skill+1];
gDeepThoughts = gSkill[skill+2];
}
else
outcome = OUTCOME_MENU;
}
}
}
// Redraw the whole board, erasing the menu
plat_DrawBoard(0);
return outcome;
}
/*-----------------------------------------------------------------------*/
// Show a menu seeing what the user wants to do with the pawn that reached
// the other side
char frontend_GetPromotion()
{
char promotion;
switch(plat_Menu(gPromoteMenu, MENU_HEIGHT, gszpromote))
{
case 2:
promotion = ROOK;
break;
case 3:
promotion = BISHOP;
break;
case 4:
promotion = KNIGHT;
break;
default:
promotion = QUEEN;
break;
}
plat_DrawBoard(0);
return promotion;
}
/*-----------------------------------------------------------------------*/
// This formats the log entry. It's here because it's display related
void frontend_FormatLogString()
{
gLogStrBuffer[0] = 'A' + (gTile[0] & 7);
gLogStrBuffer[1] = '8' - (gTile[0] / 8);
gLogStrBuffer[2] = (gPiece[1] & PIECE_DATA) != NONE ? 'x' : '-';
gLogStrBuffer[3] = 'A' + (gTile[1] & 7);
gLogStrBuffer[4] = '8' - (gTile[1] / 8);
gLogStrBuffer[5] = gMoveSymbol[gOutcome-1];
}
/*-----------------------------------------------------------------------*/
// Call the platform functions to draw the log update - at the bottom
// or top depending on undo (top) or new/redo (bottom)
// The plat_* functions probably call undo_FindUndoLine which changes
// the global gTile, etc. variables
void frontend_LogMove(char atTop)
{
if(atTop)
plat_AddToLogWinTop();
else
plat_AddToLogWin();
}

17
src/frontend.h Normal file
View File

@ -0,0 +1,17 @@
/*
* frontend.h
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#ifndef _FRONTEND_H_
#define _FRONTEND_H_
char frontend_Menu(char activeGame);
char frontend_GetPromotion();
void frontend_FormatLogString();
void frontend_LogMove(char atTop);
#endif //_FRONTEND_H_

76
src/globals.c Normal file
View File

@ -0,0 +1,76 @@
/*
* globals.c
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#include "types.h"
#include "globals.h"
/*-----------------------------------------------------------------------*/
char gChessBoard[8][8]; // The chess board itself
char* gpChessBoard = &gChessBoard[0][0]; // With a linear address alias
// This is an 8x8 grid with a 2-part array for each cell. At [8][8][0] is the
// number of entries to follow between [y][x]][0] and [y][x][17]. At [y][x][17]
// is the number of entries between [y][x][17] and [y][x+1][0]. These between
// entries are tiles of black [0] or white [17] that can land on this tile to
// take (or defend) whatever piece is on the tile. I refer to this as the
// Attack DB on the code
char gAttackBoard[8][8][ATTACK_WIDTH];
char* gpAttackBoard = &gAttackBoard[0][0][0]; // Linear version
int giAttackBoardOffset[64][2]; // Offsets to the [0]'s in the AttackDB
char gUserMode; // =0, all AI; =1, Black is user; =2, White; =3, both
char gAI; // Prevents AI code from asking for pawn promotion
char gMoveCounter; // Moves without a piece taken for 50 move rule
char gTile[4]; // [0] = src tile, 1 = to dst
char gPiece[2]; // [0] = piece at src, [1] = piece at dst
char gMove[2]; // [0] = piece0 moved or no, ditto [1] on piece1
char gColor[2]; // [0] =1 white piece0, =0 black; [1] ditto on piece1
char gOutcome; // Result of board_ProcessAction for log. Bad design
char gEPPawn; // Tile where a pawn can be taken en passant or NULL_TILE
char gMaxLevel; // How many levels down the AI thinks ahead. 0 = self and opponent
char gWidth; // How many of the top ranked moves for self tried on think ahead
char gDeepThoughts; // =1 - moves are check and AttackDB updated; =0 - inaccurate but faster
char gReturnToOS; // =1 can quit game; =0 cannot quit game
char gPossibleMoves[MAX_PIECE_MOVES]; // All the tiles a piece can get to from current pos
char gNumMoves; // Entries in gPossibleMoves
char gCursorPos[2][2]; // Remember last cursor pos for human players
char gKingData[2]; // Tracks the king's tile on the board
char gShowAttackBoard; // Visibility toggle
char gShowAttacks[2]; // Visibility toggle per side
char gLogStrBuffer[7]; // String placeholder for the movve log
char gKingMovingTo[2][2] = {{2,6},{58,62}}; // Tiles where a king lands when casteling
char gSkill[4*3] = {1,0,0,16,1,0,16,2,1,16,3,1}; // Values for gWidth, gMaxLevel & gDeepThoughts over 4 skills
// Values AI sees when looking at pieces; +2 compensates for other move hints and 3 * makes the
// piece value much more meaningful
char gPieceValues[PAWN+1] = {
(0), // NONE
2+(3*5), // ROOK,
2+(3*3), // KNIGHT,
2+(3*3), // BISHOP,
2+(3*10), // KING,
2+(3*9), // QUEEN,
2+(3*1), // PAWN,
};
/*-----------------------------------------------------------------------*/
// All Display Strings
char gMoveSymbol[OUTCOME_STALEMATE] = {'\0', '+', '#', '/', '!'};
char gszNoUndo[] = "No Undo";
char gszNoRedo[] = "No Redo";
char gszInvalid[] = "Invalid";
char gszAbout[] = "cc65 Chess V1.0 by S. Wessels, 2014. ";
char gszResume[] = "Resume Game ";
char gszQuit[] = "Quit Game ";
char gszSelect[] = " Select ";
char gszpromote[] = "Select a rank to promote the pawn to. ";
char* gMainMenu[] = {gszSelect, "1 Human player ","2 Human players","Both players AI",gszQuit, 0, 0};
char* gSkillMenu[] = {gszSelect, " Very Easy "," Easy "," Harder "," Very Hard ", 0};
char* gColorMenu[] = {gszSelect," Play White "," Play Black ", 0};
char* gAreYouSureMenu[] = {" Are you sure? "," Absolutely! "," Not so much ",0};
char* gPromoteMenu[] = {"Promotion", " Queen ", " Rook ", " Bishop ", " Knight ", 0};
char* gszSideLabel[2] = {"Black", "White"};

58
src/globals.h Normal file
View File

@ -0,0 +1,58 @@
/*
* globals.h
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#ifndef _GLOBALS_H_
#define _GLOBALS_H_
extern char gChessBoard[8][8];
extern char* gpChessBoard;
extern char gAttackBoard[8][8][ATTACK_WIDTH];
extern char* gpAttackBoard;
extern int giAttackBoardOffset[64][2];
extern char gUserMode;
extern char gAI;
extern char gMoveCounter;
extern char gTile[4];
extern char gPiece[2];
extern char gMove[2];
extern char gColor[2];
extern char gOutcome;
extern char gEPPawn;
extern char gMaxLevel;
extern char gWidth;
extern char gDeepThoughts;
extern char gReturnToOS;
extern char gPossibleMoves[MAX_PIECE_MOVES];
extern char gNumMoves;
extern char gCursorPos[2][2];
extern char gKingData[2];
extern char gShowAttackBoard;
extern char gShowAttacks[2];
extern char gLogStrBuffer[7];
extern char gKingMovingTo[2][2];
extern char gSkill[4*3];
extern char gPieceValues[PAWN+1];
/*-----------------------------------------------------------------------*/
// Display labels
extern char gMoveSymbol[OUTCOME_STALEMATE];
extern char gszNoUndo[];
extern char gszNoRedo[];
extern char gszInvalid[];
extern char gszAbout[];
extern char gszResume[];
extern char gszQuit[];
extern char gszpromote[];
extern char* gMainMenu[];
extern char* gSkillMenu[];
extern char* gColorMenu[];
extern char* gAreYouSureMenu[];
extern char* gPromoteMenu[];
extern char* gszSideLabel[2];
#endif //_GLOBALS_H_

343
src/human.c Normal file
View File

@ -0,0 +1,343 @@
/*
* human.c
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#include "types.h"
#include "globals.h"
#include "undo.h"
#include "board.h"
#include "human.h"
#include "frontend.h"
#include "plat.h"
/*-----------------------------------------------------------------------*/
// Internal function Prototype
void human_ProcessInput(int keyMask);
void human_ProcessToggle(int keyMask, char side, char tile);
/*-----------------------------------------------------------------------*/
// Track the user controlled curson on the board
static char sc_cursorX, sc_cursorY;
/*-----------------------------------------------------------------------*/
// Handle the cursor movement
void human_ProcessInput(int keyMask)
{
switch(keyMask)
{
case INPUT_UP:
if(!sc_cursorY)
sc_cursorY = 7;
else
--sc_cursorY;
break;
case INPUT_RIGHT:
if(7==sc_cursorX)
sc_cursorX = 0;
else
++sc_cursorX;
break;
case INPUT_DOWN:
if(7==sc_cursorY)
sc_cursorY = 0;
else
++sc_cursorY;
break;
case INPUT_LEFT:
if(!sc_cursorX)
sc_cursorX = 7;
else
--sc_cursorX;
break;
}
}
/*-----------------------------------------------------------------------*/
// Deal with the toggling on/off of the attack and defend states
// as well as the 'b'oard state which shows all attcks/defences
void human_ProcessToggle(int keyMask, char side, char tile)
{
char attack = 0;
switch(keyMask)
{
case INPUT_TOGGLE_B:
gShowAttackBoard = 1 - gShowAttackBoard;
plat_DrawBoard(0);
break;
case INPUT_TOGGLE_A:
attack = 1;
// Intentional fall-through since the code
// below is the same for showing attack or defence
case INPUT_TOGGLE_D:
{
int offset;
char attackers, attacker, i;
gShowAttacks[side] ^= SET_BIT(attack);
offset = giAttackBoardOffset[tile][0];
if((side & !attack) || (!side && attack))
offset += ATTACK_WHITE_OFFSET;
attackers = gpAttackBoard[offset];
++offset;
for(i=0;i<attackers;++i)
{
attacker = gpAttackBoard[offset+i];
if(gShowAttacks[side] & SET_BIT(attack))
plat_Highlight(attacker,2+attack);
else
plat_DrawSquare(attacker);
}
}
break;
}
}
/*-----------------------------------------------------------------------*/
// Main routine when it's a human player's turn.
// The routine is sort-of in 2 parts. The bit before the user key is read
// and the bit after the key is read.
char human_Play(char side)
{
char validMove = 0, selector = 0;
int keyMask = INPUT_MOTION;
// get this sides' cursor
sc_cursorY = gCursorPos[side][0];
sc_cursorX = gCursorPos[side][1];
do
{
// Load the global variables with the current piece under the cursor
gTile[selector] = MK_POS(sc_cursorY,sc_cursorX);
gPiece[selector] = gpChessBoard[gTile[selector]];
gColor[selector] = (gPiece[selector] & PIECE_WHITE) >> 7;
gMove[selector] = gPiece[selector] & PIECE_MOVED;
gPiece[selector] &= PIECE_DATA;
if(keyMask & INPUT_MOTION)
{
// If no piece selected and the cursor moved, update the possible moves for this tile
if(!selector)
board_GeneratePossibleMoves(gTile[0], 0);
else
{
// If a piece is selected, see if the second tile, under the cursor, is
// a valid move-to tile
validMove = board_findInList(gPossibleMoves, gNumMoves, gTile[1]);
plat_Highlight(gTile[1], validMove ? HCOLOR_ATTACK : HCOLOR_INVALID);
}
// Show the cursor
plat_Highlight(gTile[0], selector ? HCOLOR_SELECTED : NONE == gPiece[0] || gColor[0] != side ? HCOLOR_EMPTY : gNumMoves ? HCOLOR_VALID : HCOLOR_INVALID);
}
// If the cursor moved and the toggle-show-attackers/defenders states were on for this side,
// Handle showing them for the now selected tile. Bit 2/3 says it was on, so toggle it on.
// 2/3 is set whem the selection changes, lower in this same routine
if(gShowAttacks[side] & SET_BIT(2))
{
gShowAttacks[side] &= ~SET_BIT(2);
human_ProcessToggle(INPUT_TOGGLE_D, side, gTile[selector]);
}
if(gShowAttacks[side] & SET_BIT(3))
{
gShowAttacks[side] &= ~SET_BIT(3);
human_ProcessToggle(INPUT_TOGGLE_A, side, gTile[selector]);
}
// Get input
keyMask = plat_ReadKeys(1);
// Always clear the message area once a key is pressed
plat_ClearMessage();
// If the selected tile changes, make sure the toggle-show updates will happen (Set 2/3 bit)
if(keyMask & (INPUT_MOTION | INPUT_UNDOREDO) || ((keyMask & INPUT_SELECT) && selector && validMove))
{
if(gShowAttacks[side] & SET_BIT(0))
{
gShowAttacks[side] |= SET_BIT(2);
human_ProcessToggle(INPUT_TOGGLE_D, side, gTile[selector]);
}
if(gShowAttacks[side] & SET_BIT(1))
{
gShowAttacks[side] |= SET_BIT(3);
human_ProcessToggle(INPUT_TOGGLE_A, side, gTile[selector]);
}
}
if(keyMask & INPUT_MOTION)
{
// Erase the cursor and move it
plat_DrawSquare(gTile[selector]);
human_ProcessInput(keyMask & INPUT_MOTION);
}
else if(keyMask & INPUT_MENU)
{
// Drop out so the menu can be displayed
return OUTCOME_MENU;
}
else if(keyMask & INPUT_TOGGLE)
{
// Handle the toggle-show-attackers-defenders-board state chages
human_ProcessToggle(keyMask & INPUT_TOGGLE, side, gTile[selector]);
}
else if(keyMask & INPUT_BACKUP)
{
// If a piece was selected, deselct the piece
if(selector)
{
plat_DrawSquare(gTile[selector]);
selector = 0;
sc_cursorY = gTile[0] / 8;
sc_cursorX = gTile[0] & 7;
keyMask = INPUT_MOTION;
}
// Otherwise bring up the menu
else
return OUTCOME_MENU;
}
else if(keyMask & INPUT_UNDOREDO)
{
// if there's data in the undo/redo buffers to undo or redo, then do the undo/redo
if(((keyMask & INPUT_UNDO) && undo_CanUndo()) || ((keyMask & INPUT_REDO) && undo_CanRedo()))
{
char numUndo = 1;
// If there's AI, undo 2 moves, to get back to the humans' previous move
if(gUserMode != (USER_BLACK | USER_WHITE))
numUndo = 2;
do
{
plat_DrawSquare(gTile[0]);
if(selector)
plat_DrawSquare(gTile[1]);
if(keyMask & INPUT_UNDO)
undo_Undo();
else
undo_Redo();
plat_DrawSquare(gTile[0]);
plat_DrawSquare(gTile[1]);
if(NULL_TILE != gTile[2])
{
plat_DrawSquare(gTile[2]);
gTile[2] = NULL_TILE;
}
if(NULL_TILE != gTile[3])
{
plat_DrawSquare(gTile[3]);
gTile[3] = NULL_TILE;
}
gCursorPos[1-side][0] = gTile[0] / 8;
gCursorPos[1-side][1] = gTile[0] & 7;
// Undo scrolls down so updates at the top of the log, redo scrolls up
if(keyMask & INPUT_UNDO)
frontend_LogMove(1);
else
frontend_LogMove(0);
} while(--numUndo);
// Fix the Attack DB
board_PlacePieceAttacks();
// If 2 humans are playing, return so sides can switch
if(gUserMode == (USER_BLACK | USER_WHITE))
return OUTCOME_OK;
keyMask = INPUT_MOTION;
}
else
{
// if there's nothing in the undo/redo buffer, show a message to say so
// This could be if all moves have been undone or redone also
plat_ShowMessage((keyMask & INPUT_UNDO) ? gszNoUndo : gszNoRedo, HCOLOR_INVALID);
}
}
else if(keyMask & INPUT_SELECT)
{
if(!selector && gColor[0] == side && gNumMoves)
{
// If the cursor is on a piece of this turn, that has moved and no other piece
// has yet been selected, then select this piece
++selector;
keyMask = INPUT_MOTION;
}
else if(gTile[0] == gTile[1] && gColor[0] == side)
{
// If the selected tile is re-selected, deselct it
--selector;
keyMask = INPUT_MOTION;
}
else if(selector && validMove)
{
// If a dest is selected for the selected tile, try to do the move
// gOutcome of 0 is OUTCOME_INVALID, example moving into check
if((gOutcome = board_ProcessAction()))
{
// If no piece was taken, update the move without taking counter
// else reset it to zero
if(NONE != (gPiece[1] & PIECE_DATA))
gMoveCounter = 0;
else if(NUM_MOVES_TO_DRAW == ++gMoveCounter)
gOutcome = OUTCOME_DRAW;
// Add the move to the undo stack and update the display
undo_AddMove();
plat_DrawSquare(gTile[0]);
plat_DrawSquare(gTile[1]);
if(NULL_TILE != gTile[2])
{
plat_DrawSquare(gTile[2]);
gTile[2] = NULL_TILE;
}
if(NULL_TILE != gTile[3])
{
plat_DrawSquare(gTile[3]);
gTile[3] = NULL_TILE;
}
// Log the move
frontend_LogMove(0);
// This will exit the while loop
++selector;
}
else
{
// Show a message to indicate that the move was invalid
plat_ShowMessage(gszInvalid,HCOLOR_INVALID);
keyMask = INPUT_MOTION;
}
}
}
// This does nothing on the C64 but some other platforms may need a
// screen refresh - since this function doesn't always fall back to main
plat_UpdateScreen();
} while(selector < 2);
// Save the cursor positions for next time
gCursorPos[side][0] = sc_cursorY;
gCursorPos[side][1] = sc_cursorX;
return OUTCOME_OK;
}

14
src/human.h Normal file
View File

@ -0,0 +1,14 @@
/*
* human.h
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#ifndef _HUMAN_H_
#define _HUMAN_H_
char human_Play(char side);
#endif //_HUMAN_H_

115
src/main.c Normal file
View File

@ -0,0 +1,115 @@
/*
* main.c
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#include "types.h"
#include "globals.h"
#include "undo.h"
#include "board.h"
#include "cpu.h"
#include "human.h"
#include "frontend.h"
#include "plat.h"
/*-----------------------------------------------------------------------*/
// Internal function Prototype
void mainLoop();
void init();
/*-----------------------------------------------------------------------*/
int main()
{
init();
mainLoop();
plat_Shutdown();
return 0;
}
/*-----------------------------------------------------------------------*/
void init()
{
char i;
int offset;
// Init the global variables that aren't initialized anywhere else
// (mostly other *_Init() functions, or in mainLoop)
gEPPawn = gTile[0] = gTile[1] = gTile[2] = gTile[3] = NULL_TILE;
gLogStrBuffer[6] = gShowAttacks[0] = gShowAttacks[1] = gShowAttackBoard = gNumMoves = gPiece[0] = gPiece[1] = gOutcome = gMove[0] = gMove[1] = gColor[0] = gColor[1] = gAI = 0;
// Init the lookup table for ecery piece on the board to
// look up directly in gPAttackBoard how many attackers/defender
// there are for the tile, and what tiles they may be
offset = 0;
for(i=0; i<64; ++i)
{
giAttackBoardOffset[i][0] = offset;
giAttackBoardOffset[i][1] = offset+ATTACK_WHITE_OFFSET;
offset += ATTACK_WIDTH;
}
plat_Init();
}
/*-----------------------------------------------------------------------*/
void mainLoop()
{
char activeGame, sideToGo, outcome;
do
{
// Execute once for every game
board_Init();
undo_Init();
plat_DrawBoard(1);
gMoveCounter = 0;
gUserMode = 0;
activeGame = 0;
sideToGo = SIDE_WHITE;
outcome = OUTCOME_MENU;
while(outcome <= OUTCOME_MENU)
{
// Allows interruption of AI vs AI
if(INPUT_MENU & plat_ReadKeys(0))
outcome = OUTCOME_MENU;
if(OUTCOME_MENU == outcome)
{
outcome = frontend_Menu(activeGame);
if(outcome < OUTCOME_ABANDON)
activeGame = 1;
}
if(outcome <= OUTCOME_STALEMATE)
{
plat_ShowSideToGoLabel(sideToGo);
if((sideToGo+1) & gUserMode)
outcome = human_Play(sideToGo);
else
outcome = cpu_Play(sideToGo);
if(gShowAttackBoard)
plat_DrawBoard(0);
// Only switch sides if not coming from a menu and it's not STALEMATE
if(outcome != OUTCOME_MENU && outcome != OUTCOME_STALEMATE)
sideToGo = 1 - sideToGo;
// if it's game-over then make it a USER vs USER state so control
// returns no matter which side should have gone next
if(outcome >= OUTCOME_CHECKMATE)
gUserMode = USER_BLACK | USER_WHITE;
// Any platforms that need to redraw should do so now
plat_UpdateScreen();
}
}
} while(OUTCOME_QUIT != outcome);
}

26
src/plat.h Normal file
View File

@ -0,0 +1,26 @@
/*
* plat.h
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#ifndef _PLAT_H_
#define _PLAT_H_
void plat_Init();
void plat_UpdateScreen();
char plat_Menu(char **menuItems, char height, char *scroller);
void plat_DrawBoard(char clearLog);
void plat_DrawSquare(char position);
void plat_ShowSideToGoLabel(char side);
void plat_Highlight(char position, char color);
void plat_ShowMessage(char *str, char color);
void plat_ClearMessage();
void plat_AddToLogWin();
void plat_AddToLogWinTop();
int plat_ReadKeys(char blocking);
void plat_Shutdown();
#endif //_PLAT_H_

401
src/term/platTerm.c Normal file
View File

@ -0,0 +1,401 @@
/*
* platTerm.c
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#include <curses.h>
#include <string.h>
#include <sys/time.h>
#include "types.h"
#include "globals.h"
#include "undo.h"
#include "frontend.h"
#include "plat.h"
/*-----------------------------------------------------------------------*/
// Internal function Prototype
char plat_TimeExpired(unsigned int aTime);
/*-----------------------------------------------------------------------*/
// These are read from the curses library but I assume at least 24 rows
// and 55 cols
int SCREEN_HEIGHT, SCREEN_WIDTH;
int LOG_WINDOW_HEIGHT;
/*-----------------------------------------------------------------------*/
#define SCROLL_SPEED 150000
#define BOARD_PIECE_WIDTH 6
#define BOARD_PIECE_HEIGHT 3
/*-----------------------------------------------------------------------*/
// Names of the pieces NONE, Rook, knight, Bishop, Queen, King, pawn
static const char sc_pieces[] = {'\0','R','k','B','Q','K','p'};
/*-----------------------------------------------------------------------*/
void plat_Init()
{
// Curses init
initscr();
intrflush(stdscr, FALSE);
keypad(stdscr, TRUE);
nonl();
cbreak();
noecho();
timeout(0);
curs_set(0);
getmaxyx(stdscr, SCREEN_HEIGHT, SCREEN_WIDTH) ;
LOG_WINDOW_HEIGHT = SCREEN_HEIGHT - 2;
if(has_colors() != FALSE)
{
start_color();
assume_default_colors(COLOR_WHITE, COLOR_BLACK);
init_pair(1, COLOR_BLACK, COLOR_YELLOW);
init_pair(2, COLOR_WHITE, COLOR_BLACK);
init_pair(3, COLOR_GREEN, COLOR_BLACK);
init_pair(4, COLOR_RED, COLOR_BLACK);
init_pair(5, COLOR_CYAN, COLOR_BLACK);
init_pair(6, COLOR_BLUE, COLOR_BLACK);
init_pair(7, COLOR_WHITE, COLOR_WHITE);
init_pair(8, COLOR_BLACK, COLOR_BLACK);
}
// Setting this to 0 will not show the "Quit" option in the main menu
gReturnToOS = 1;
}
/*-----------------------------------------------------------------------*/
void plat_UpdateScreen()
{
refresh();
}
/*-----------------------------------------------------------------------*/
// Very simple menu with a heading and a scrolling banner as a footer
char plat_Menu(char **menuItems, char height, char *scroller)
{
static char *prevScroller, *pScroller;
char *pEnd;
int keyMask;
char i, j, sx, sy, numMenuItems, maxLen = 0;
if(prevScroller != scroller)
{
prevScroller = scroller;
pScroller = scroller;
}
pEnd = scroller + strlen(scroller);
for(numMenuItems=0; menuItems[numMenuItems]; ++numMenuItems)
{
char len = strlen(menuItems[numMenuItems]);
if(len > maxLen)
maxLen = len;
}
sy = MAX_SIZE(0, ((8*BOARD_PIECE_HEIGHT) / 2) - (height / 2) - 1);
sx = MAX_SIZE(0, ((8*BOARD_PIECE_WIDTH) / 2) - (maxLen / 2) - 1);
maxLen = MIN_SIZE((8*BOARD_PIECE_WIDTH)-2, maxLen);
color_set(3,0);
move(sy,sx);
printw(" %.*s ",maxLen, menuItems[0]);
color_set(1,0);
move(++sy, sx);
for(j=0; j<maxLen+2; ++j)
printw(" ");
for(i=1; i<numMenuItems; ++i)
{
move(sy+i, sx);
printw(" %.*s ",maxLen, menuItems[i]);
}
for(;i<height;++i)
{
move(sy+i, sx);
for(j=0; j<maxLen+2; ++j)
printw(" ");
}
i = 1;
do
{
move(sy+i,sx);
attron(WA_REVERSE);
color_set(2,0);
printw(">%.*s<",maxLen, menuItems[i]);
attroff(WA_REVERSE);
color_set(1,0);
keyMask = plat_ReadKeys(0);
if(keyMask & INPUT_MOTION)
{
move(sy+i,sx);
printw(" %.*s ",maxLen, menuItems[i]);
switch(keyMask & INPUT_MOTION)
{
case INPUT_UP:
if(!--i)
i = numMenuItems-1;
break;
case INPUT_DOWN:
if(numMenuItems == ++i)
i = 1;
break;
}
}
keyMask &= (INPUT_SELECT | INPUT_BACKUP);
move(sy+height,sx);
color_set(5,0);
printw(" %.*s ",maxLen, pScroller);
if((pEnd - pScroller) < maxLen-1)
{
move(sy+height,sx+(pEnd-pScroller)+1);
printw(" %.*s ",maxLen-(pEnd - pScroller)-1, scroller);
}
if(plat_TimeExpired(SCROLL_SPEED))
{
++pScroller;
if(!*pScroller)
pScroller = scroller;
}
} while(keyMask != INPUT_SELECT && keyMask != INPUT_BACKUP);
if(keyMask & INPUT_BACKUP)
return 0;
return i;
}
/*-----------------------------------------------------------------------*/
void plat_DrawBoard(char clearLog)
{
char i;
if(clearLog)
erase();
for(i=0; i<64; ++i)
plat_DrawSquare(i);
}
/*-----------------------------------------------------------------------*/
void plat_DrawSquare(char position)
{
char piece, color, dx, dy;
char y = position / 8, x = position & 7;
char blackWhite = !((x & 1) ^ (y & 1));
if(blackWhite)
color = 7;
else
color = 8;
for(dy=0; dy<BOARD_PIECE_HEIGHT; ++dy)
{
for(dx=0; dx<BOARD_PIECE_WIDTH; ++dx)
{
move(dy+y*BOARD_PIECE_HEIGHT,dx+x*BOARD_PIECE_WIDTH);
color_set(color,0);
printw(" ");
}
}
// Show the attack numbers
if(gShowAttackBoard)
{
color_set(1, 0);
move(y*BOARD_PIECE_HEIGHT+2, x*BOARD_PIECE_WIDTH);
printw("%d",(gpAttackBoard[giAttackBoardOffset[position][0]]));
color_set(2,0);
move(y*BOARD_PIECE_HEIGHT+2, x*BOARD_PIECE_WIDTH+5);
printw("%d",(gpAttackBoard[giAttackBoardOffset[position][1]]));
move(y*BOARD_PIECE_HEIGHT, x*BOARD_PIECE_WIDTH);
printw("%02X",gChessBoard[y][x]);
move(y*BOARD_PIECE_HEIGHT, x*BOARD_PIECE_WIDTH+5);
printw("%d",(gChessBoard[y][x]&PIECE_WHITE)>>7);
}
piece = gChessBoard[y][x];
color = piece & PIECE_WHITE;
piece &= PIECE_DATA;
if(piece)
{
move(y*BOARD_PIECE_HEIGHT+(BOARD_PIECE_HEIGHT/2),x*BOARD_PIECE_WIDTH+(BOARD_PIECE_WIDTH/2));
color_set(color?2:1,0);
printw("%c",sc_pieces[piece]);
}
}
/*-----------------------------------------------------------------------*/
void plat_ShowSideToGoLabel(char side)
{
move(0, 2+8*BOARD_PIECE_WIDTH);
color_set(side?2:1, 0);
printw("%s",gszSideLabel[side]);
}
/*-----------------------------------------------------------------------*/
void plat_Highlight(char position, char color)
{
char y = (BOARD_PIECE_HEIGHT/2)+1+BOARD_PIECE_HEIGHT*((position / 8)), x = (BOARD_PIECE_WIDTH/2)+BOARD_PIECE_WIDTH*((position & 7));
move(y,x);
color_set(color,0);
printw("*");
}
/*-----------------------------------------------------------------------*/
void plat_ShowMessage(char *str, char color)
{
move((8*BOARD_PIECE_HEIGHT)-1, 1+(8*BOARD_PIECE_WIDTH));
color_set(color,0);
printw("%.*s",SCREEN_WIDTH-1-(8*BOARD_PIECE_WIDTH),str);
clrtoeol();
}
/*-----------------------------------------------------------------------*/
void plat_ClearMessage()
{
move((8*BOARD_PIECE_HEIGHT)-1, 1+(8*BOARD_PIECE_WIDTH));
clrtoeol();
}
/*-----------------------------------------------------------------------*/
// This function can/will gange the gTile and related global variables so
// caution is needed
void plat_AddToLogWin()
{
char bot = (8*BOARD_PIECE_HEIGHT)-2, y = 1, x = 1+(8*BOARD_PIECE_WIDTH);
for(; y<=bot; ++y)
{
move(y, x);
if(undo_FindUndoLine(bot-y))
{
frontend_FormatLogString();
color_set(gColor[0]+1,0);
printw("%.*s",SCREEN_WIDTH-1-x,gLogStrBuffer);
}
clrtoeol();
}
}
/*-----------------------------------------------------------------------*/
void plat_AddToLogWinTop()
{
plat_AddToLogWin();
}
/*-----------------------------------------------------------------------*/
char plat_TimeExpired(unsigned int aTime)
{
static struct timeval sst_store;
static int si_init = 0;
struct timeval now;
gettimeofday(&now, NULL);
if(!si_init || (MAX_SIZE(now.tv_usec,sst_store.tv_usec) - MIN_SIZE(now.tv_usec,sst_store.tv_usec) > SCROLL_SPEED))
{
si_init = 1;
sst_store = now;
return 1;
}
return 0;
}
/*-----------------------------------------------------------------------*/
int plat_ReadKeys(char blocking)
{
char key = 0;
int keyMask = 0;
if(blocking)
{
timeout(-1) ;
key = getch();
timeout(0);
}
else
{
key = getch();
}
switch(key)
{
case 3: // Up
keyMask |= INPUT_UP;
break;
case 5: // Right
keyMask |= INPUT_RIGHT;
break;
case 2: // Down
keyMask |= INPUT_DOWN;
break;
case 4: // Left
keyMask |= INPUT_LEFT;
break;
case 27: // Esc
keyMask |= INPUT_BACKUP;
break;
case 'a': // 'a' - Show Attackers
keyMask |= INPUT_TOGGLE_A;
break;
case 'b': // 'b' - Board attacks - Show all attacks
keyMask |= INPUT_TOGGLE_B;
break;
case 'd': // 'd' - Show Defenders
keyMask |= INPUT_TOGGLE_D;
break;
case 'm': // 'm' - Menu
keyMask |= INPUT_MENU;
break;
case 13: // Enter
keyMask |= INPUT_SELECT;
break;
case 'r':
keyMask |= INPUT_REDO;
break;
case 'u':
keyMask |= INPUT_UNDO;
break;
// default: // Debug - show key code
// {
// char s[] = "Key:000";
// if(key != 255)
// {
// s[4] = (key/100)+'0';
// key -= (s[4] - '0') * 100;
// s[5] = (key/10)+'0';
// s[6] = (key%10)+'0';
// plat_ShowMessage(s,COLOR_RED);
// }
// refresh();
// }
// break;
}
return keyMask;
}
/*-----------------------------------------------------------------------*/
void plat_Shutdown()
{
endwin();
}

91
src/types.h Normal file
View File

@ -0,0 +1,91 @@
/*
* types.h
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#ifndef _TYPES_H_
#define _TYPES_H_
#define SET_BIT(x) (1<<(x))
#define MAX_SIZE(x,y) ((x)>=(y) ? (x) : (y))
#define MIN_SIZE(x,y) ((x)<=(y) ? (x) : (y))
#define MK_POS(y,x) ((y)*8+x)
#define SIDE_BLACK 0
#define SIDE_WHITE 1
#define USER_BLACK 1
#define USER_WHITE 2
#define NUM_PIECES_SIDE 16
#define MAX_PIECE_MOVES 28
#define NULL_TILE 128
#define NUM_MOVES_TO_DRAW 50
#define HCOLOR_WHITE 1
#define HCOLOR_BLACK 2
#define HCOLOR_EMPTY 4
#define HCOLOR_VALID 5
#define HCOLOR_INVALID 2
#define HCOLOR_SELECTED 6
#define HCOLOR_ATTACK 3
#define ATTACK_WIDTH (2+2*NUM_PIECES_SIDE)
#define ATTACK_WHITE_OFFSET 17
#define PIECE_WHITE SET_BIT(7)
#define PIECE_MOVED SET_BIT(6)
#define PIECE_EXTRA_DATA (PIECE_WHITE | PIECE_MOVED)
#define PIECE_DATA (SET_BIT(0) | SET_BIT(1) | SET_BIT(2))
#define INPUT_UP SET_BIT(0)
#define INPUT_RIGHT SET_BIT(1)
#define INPUT_DOWN SET_BIT(2)
#define INPUT_LEFT SET_BIT(3)
#define INPUT_BACKUP SET_BIT(4)
#define INPUT_TOGGLE_A SET_BIT(5)
#define INPUT_TOGGLE_B SET_BIT(6)
#define INPUT_TOGGLE_D SET_BIT(7)
#define INPUT_SELECT SET_BIT(8)
#define INPUT_MENU SET_BIT(9)
#define INPUT_UNDO SET_BIT(10)
#define INPUT_REDO SET_BIT(11)
#define INPUT_UNDOREDO (INPUT_UNDO | INPUT_REDO)
#define INPUT_MOTION (INPUT_UP | INPUT_RIGHT | INPUT_DOWN | INPUT_LEFT)
#define INPUT_TOGGLE (INPUT_TOGGLE_A | INPUT_TOGGLE_B | INPUT_TOGGLE_D)
#define PAWN_PROMOTE SET_BIT(1)
#define PAWN_ENPASSANT SET_BIT(4)
#define ENPASSANT_TAKE 0
#define ENPASSANT_UNTAKE 1
#define ENPASSANT_MAYBE 2
#define OUTCOME_MASK 0x07
enum
{
NONE,
ROOK,
KNIGHT,
BISHOP,
QUEEN,
KING,
PAWN,
};
enum
{
OUTCOME_INVALID,
OUTCOME_OK,
OUTCOME_CHECK,
OUTCOME_CHECKMATE,
OUTCOME_DRAW,
OUTCOME_STALEMATE,
OUTCOME_MENU,
OUTCOME_ABANDON,
OUTCOME_QUIT,
};
#endif //_TYPES_H_

222
src/undo.c Normal file
View File

@ -0,0 +1,222 @@
/*
* undo.c
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#include "types.h"
#include "globals.h"
#include "undo.h"
#include "board.h"
#include "frontend.h"
#include "plat.h"
/*-----------------------------------------------------------------------*/
// The size of the undo buffer. This buffer is a circular buffer and
// access is available to UNDO_STACK_SIZE-1 moves in the past
// This implementation does math with char's so 255 is the max
#define UNDO_STACK_SIZE 255
/*-----------------------------------------------------------------------*/
// m_piece1 is in 2 parts. Bits 0-3 is the piece that was at gPiece[1]
// whereas bits 4-7 is the piece that's there now - which isn't gPiece[0] if
// there was a pawn promotion.
// m_code is a bit structure: Bits are:
// 0 - gColor[0], 1 - gColor[1], 2 - gMove[0], 3 - gMove[1],
// 4 - en passant, 5-7 - gOutcome
typedef struct tag_UndoEntry
{
char m_tile0;
char m_tile1;
char m_piece0;
char m_piece1;
char m_code;
} t_UndoEntry;
/*-----------------------------------------------------------------------*/
// the undo stack and 3 pointers to use it
t_UndoEntry stu_undoStack[UNDO_STACK_SIZE];
static char sc_undoTop, sc_undoBottom, sc_undoPtr;
/*-----------------------------------------------------------------------*/
void undo_Init()
{
sc_undoTop = sc_undoBottom = sc_undoPtr = 0;
}
/*-----------------------------------------------------------------------*/
// Store the globals for tile, piece, color, move and outcome on the undo
// stack
void undo_AddMove()
{
stu_undoStack[sc_undoPtr].m_tile0 = gTile[0];
stu_undoStack[sc_undoPtr].m_tile1 = gTile[1];
stu_undoStack[sc_undoPtr].m_piece0 = gPiece[0];
stu_undoStack[sc_undoPtr].m_piece1 = gPiece[1];
// Back up the piece found on the board at position gTile1. If a pawn was promoted
// it's not the same piece that left gTile[0]
stu_undoStack[sc_undoPtr].m_piece1 |= (gpChessBoard[gTile[1]] & PIECE_DATA) << 4;
// Pack the status items into a single char
stu_undoStack[sc_undoPtr].m_code = gColor[0] | (gColor[1] << 1) | (gMove[0] >> 4) | (gMove[1] >> 3) | (gOutcome << 5);
// if there was an en passant capture, record it
if(gTile[2] != NULL_TILE && gTile[3] == NULL_TILE)
stu_undoStack[sc_undoPtr].m_code |= PAWN_ENPASSANT;
if(++sc_undoPtr == UNDO_STACK_SIZE)
sc_undoPtr = 0;
if(sc_undoPtr == sc_undoBottom)
{
if(++sc_undoBottom == UNDO_STACK_SIZE)
sc_undoBottom = 0;
}
sc_undoTop = sc_undoPtr;
}
/*-----------------------------------------------------------------------*/
// This routine doesn't check if an undo is available. canUndo must be
// checked before calling this
void undo_Undo()
{
if(sc_undoPtr == 0)
sc_undoPtr = UNDO_STACK_SIZE;
--sc_undoPtr;
// In the case of an undo the move might have turned off the en passant
// opportunuti created by the previous move, so go look at that move
// to see if it created an en passant opportunity and if it did, reset
// gEPPawn for the opportunuty
gEPPawn = NULL_TILE;
if(undo_FindUndoLine(0))
{
if(!gMove[0] && PAWN == gPiece[0])
board_ProcessEnPassant(ENPASSANT_MAYBE);
}
gTile[0] = stu_undoStack[sc_undoPtr].m_tile0;
gTile[1] = stu_undoStack[sc_undoPtr].m_tile1;
gPiece[0] = stu_undoStack[sc_undoPtr].m_piece0;
// For undo, restore the piece that was at gTile1 before the move
gPiece[1] = stu_undoStack[sc_undoPtr].m_piece1 & PIECE_DATA;
gColor[0] = stu_undoStack[sc_undoPtr].m_code & 1;
gColor[1] = (stu_undoStack[sc_undoPtr].m_code & 2) >> 1;
gMove[0] = (stu_undoStack[sc_undoPtr].m_code & 4) << 4;
gMove[1] = (stu_undoStack[sc_undoPtr].m_code & 8) << 3;
gOutcome = (stu_undoStack[sc_undoPtr].m_code >> 5) & OUTCOME_MASK;
gpChessBoard[gTile[0]] = gPiece[0] | (gColor[0] << 7) | gMove[0];
gpChessBoard[gTile[1]] = gPiece[1] | (gColor[1] << 7) | gMove[1];
// if en passant set, also restore the en passant taken pawn
if(stu_undoStack[sc_undoPtr].m_code & PAWN_ENPASSANT)
{
board_ProcessEnPassant(ENPASSANT_UNTAKE);
}
else if(KING == gPiece[0])
{
gKingData[gColor[0]] = gTile[0];
board_ProcessCastling(2, 3);
}
}
/*-----------------------------------------------------------------------*/
void undo_Redo()
{
gTile[0] = stu_undoStack[sc_undoPtr].m_tile0;
gTile[1] = stu_undoStack[sc_undoPtr].m_tile1;
// Put the piece on gTile1 that was there after the move
gPiece[0] = (stu_undoStack[sc_undoPtr].m_piece1 >> 4) & PIECE_DATA;
gPiece[1] = stu_undoStack[sc_undoPtr].m_piece1 & 0x0f;
gColor[0] = stu_undoStack[sc_undoPtr].m_code & 1;
gColor[1] = (stu_undoStack[sc_undoPtr].m_code & 2) >> 1;
gMove[0] = (stu_undoStack[sc_undoPtr].m_code & 4) << 4;
gMove[1] = (stu_undoStack[sc_undoPtr].m_code & 8) << 3;
gOutcome = (stu_undoStack[sc_undoPtr].m_code >> 5) & OUTCOME_MASK;
if(stu_undoStack[sc_undoPtr].m_code & PAWN_ENPASSANT)
{
board_ProcessEnPassant(ENPASSANT_TAKE);
}
else if(KING == gPiece[0])
{
gKingData[gColor[0]] = gTile[1];
board_ProcessCastling(3, 2);
}
else if(PAWN == gPiece[0])
{
board_ProcessEnPassant(ENPASSANT_MAYBE);
}
gpChessBoard[gTile[0]] = NONE;
// Flag the piece now on gTile1 as having moved, not with the
// move status it had before the move or the status the piece
// on gTile1 had previously
gpChessBoard[gTile[1]] = gPiece[0] | (gColor[0] << 7) | PIECE_MOVED;
if(++sc_undoPtr == UNDO_STACK_SIZE)
sc_undoPtr = 0;
}
/*-----------------------------------------------------------------------*/
// Look backwards in the undo stack to see what the move was, linesBack
// ago. Beware: This function also changes the global gTile, etc.
// variables even though it's just looking back.
char undo_FindUndoLine(char linesBack)
{
char line;
// Since the undoPtr is always pointing 1 ahead of the last move,
// asking for linesBack 0 is asing for 1 before the current undoPtr
// Can't ask further back than the # of entries being kept
if(++linesBack >= UNDO_STACK_SIZE)
return 0;
// check for a wrap in the circular buffer
if(sc_undoPtr < linesBack)
{
// Can't give back entries further back than have been added
if(sc_undoPtr >= sc_undoBottom)
return 0;
line = UNDO_STACK_SIZE - (linesBack - sc_undoPtr);
}
else
{
line = sc_undoPtr - linesBack;
// Catches the case where the bottom > 0 due to wrap
// and the undoPtr is > bottom due to previous undo's
// and linesBack is durther back than what's available
if(sc_undoBottom <= sc_undoPtr && line < sc_undoBottom)
return 0;
}
gTile[0] = stu_undoStack[line].m_tile0;
gTile[1] = stu_undoStack[line].m_tile1;
gPiece[0] = stu_undoStack[line].m_piece0;
gPiece[1] = stu_undoStack[line].m_piece1 & 0x0f;
gColor[0] = stu_undoStack[line].m_code & 1;
gColor[1] = (stu_undoStack[line].m_code & 2) >> 1;
gMove[0] = (stu_undoStack[line].m_code & 4) << 4;
gMove[1] = (stu_undoStack[line].m_code & 8) << 3;
gOutcome = (stu_undoStack[line].m_code >> 5) & OUTCOME_MASK;
return 1;
}
/*-----------------------------------------------------------------------*/
char undo_CanUndo()
{
return sc_undoPtr != sc_undoBottom;
}
/*-----------------------------------------------------------------------*/
char undo_CanRedo()
{
return sc_undoPtr != sc_undoTop;
}

20
src/undo.h Normal file
View File

@ -0,0 +1,20 @@
/*
* undo.h
* cc65 Chess
*
* Created by Stefan Wessels, February 2014.
*
*/
#ifndef _UNDO_H_
#define _UNDO_H_
void undo_Init();
void undo_AddMove();
void undo_Undo();
void undo_Redo();
char undo_FindUndoLine(char linesBack);
char undo_CanUndo();
char undo_CanRedo();
#endif //_UNDO_H_