mirror of
https://github.com/StewBC/cc65-Chess.git
synced 2025-02-22 00:29:02 +00:00
cc65 Chess V1.0 Initial commit
This commit is contained in:
commit
82c002891f
345
Makefile
Normal file
345
Makefile
Normal 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
189
readme.txt
Normal 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
765
src/board.c
Normal 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
21
src/board.h
Normal 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
39
src/c64/chess.cfg
Normal 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
326
src/c64/data.c
Normal 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
15
src/c64/data.h
Normal 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
567
src/c64/plat64.c
Normal 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
713
src/cpu.c
Normal 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
14
src/cpu.h
Normal 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
174
src/frontend.c
Normal 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
17
src/frontend.h
Normal 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
76
src/globals.c
Normal 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
58
src/globals.h
Normal 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
343
src/human.c
Normal 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
14
src/human.h
Normal 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
115
src/main.c
Normal 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
26
src/plat.h
Normal 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
401
src/term/platTerm.c
Normal 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
91
src/types.h
Normal 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
222
src/undo.c
Normal 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
20
src/undo.h
Normal 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_
|
Loading…
x
Reference in New Issue
Block a user