Compare commits

...

10 commits

9 changed files with 76 additions and 65 deletions

View file

@ -2,4 +2,7 @@
AnnaConnect is a silly little text-based connect 4 game c: AnnaConnect is a silly little text-based connect 4 game c:
Supports vt100 escape codes and ncurses for drawing the UI Supports vt100 escape codes and ncurses for drawing the UI
Stuff still be changing so fast that the code is the documentation,
but at least it's got good enough comments methinks

View file

@ -5,5 +5,3 @@
#define BOARD_HEIGHT 6 #define BOARD_HEIGHT 6
#define FIRST_NUMBER 1 #define FIRST_NUMBER 1
#define ARROWS

27
logic.c
View file

@ -17,40 +17,33 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "logic.h" #include "logic.h"
#include "macros.h"
static inline int top( const int a, const int b ){ static column_t heightMask( const rowsint_t a ){
return a < b ? b : a;
}
static inline int bottom( const int a, const int b ){
return a < b ? a : b;
}
static inline int heightMask( const int a ){
return ( 1 << a ) - 1; return ( 1 << a ) - 1;
} }
static inline int safeHeightMask( const int a ){ static column_t safeHeightMask( const rowsint_t a ){
return a > 0 ? heightMask( a ) : 0; return a > 0 ? heightMask( a ) : 0;
} }
static inline int bottomHeightMask( const int a, const int b ){ static column_t bottomHeightMask( const rowsint_t a, const rowsint_t b ){
return safeHeightMask( bottom( a, b ) ); return safeHeightMask( bottom( a, b ) );
} }
inline void playMove( void playMove(
board_t *boardptr, board_t *boardptr,
const int column const columnsint_t column
){ ){
boardptr->column [ column ] |= boardptr->column [ column ] |=
boardptr->player << boardptr->height [ column ]; boardptr->player << boardptr->height [ column ];
boardptr->height [ column ]++; boardptr->height [ column ]++;
} }
inline void calcWins( void calcWins(
wins_t *wins, wins_t *wins,
const board_t board, const board_t board,
const int column const columnsint_t column
){ ){
// First the simplest win, the humble tower // First the simplest win, the humble tower
// Check for lil towers // Check for lil towers
@ -79,7 +72,7 @@ inline void calcWins(
// Now the rest of the wins // Now the rest of the wins
// First connect 2 // First connect 2
for( for(
int i = top( column - 1, 0 ); columnsint_t i = clipped_subtract( column, 1 );
i < bottom( column + 1, board.columns - 1 ); i < bottom( column + 1, board.columns - 1 );
i++ i++
){ ){
@ -108,7 +101,7 @@ inline void calcWins(
} }
// Then stitch the twos together and count // Then stitch the twos together and count
for( for(
int i = top( column - 3, 0 ); columnsint_t i = clipped_subtract( column, 3 );
i < bottom( column + 1, board.columns - 3 ); i < bottom( column + 1, board.columns - 3 );
i++ i++
){ ){

View file

@ -6,11 +6,11 @@
extern void playMove( extern void playMove(
board_t *boardptr, board_t *boardptr,
const int column const columnsint_t column
); );
extern void calcWins( extern void calcWins(
wins_t *wins, wins_t *wins,
const board_t board, const board_t board,
const int column const columnsint_t column
); );

View file

@ -1,5 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0-only */ /* SPDX-License-Identifier: GPL-2.0-only */
#pragma once #pragma once
#define top( a, b ) ( a < b ? b : a )
#define bottom( a, b ) ( a < b ? a : b )
#define clipped_subtract( a, b ) ( a < b ? 0 : a - b )
#define XSTR(s) STR(s) #define XSTR(s) STR(s)
#define STR(s) #s #define STR(s) #s

View file

@ -1,27 +1,46 @@
#!/usr/bin/make -f #!/usr/bin/make -f
# Target architecture for compilation
ARCH = native
# Optimization level
O = 3
# Flags for the compiler
FLAGS = -std=c23 FLAGS = -std=c23
FLAGS += -O3 FLAGS += -O$(O)
FLAGS += -march=native FLAGS += -march=$(ARCH)
# Only override gcc default tuning if specified in make command line
ifneq (, $(TUNE))
FLAGS += -mtune=$(TUNE)
endif
FLAGS += -pedantic FLAGS += -pedantic
FLAGS += -Wall FLAGS += -Wall
FLAGS += -Wextra FLAGS += -Wextra
FLAGS += -Werror FLAGS += -Werror
FLAGS += -Wformat-truncation=0 # This flag is because of the compiler otherwise giving too much warning
#FLAGS += -Wformat-truncation=0
# Compile flag for defining GITHASH to put at the end of the version string
GITFLAG = -D'GITHASH=$(shell git rev-parse --short=1 HEAD)' GITFLAG = -D'GITHASH=$(shell git rev-parse --short=1 HEAD)'
# Directories where .o files and excecutables will be placed
OBJDIR = obj OBJDIR = obj
OUTDIR = out OUTDIR = out
# The UI to compile for by default using make run
#UI_TARGET = vt100 #UI_TARGET = vt100
UI_TARGET = ncurses UI_TARGET = ncurses
# The URL of the plaintext version of the lisence, for the --license option
LICENSEURL = https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt LICENSEURL = https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
# C compiler
CC = cc CC = cc
.NOTINTERMEDIATE: .NOTINTERMEDIATE:
# Messages to print during different steps in making
MSG_FETCHING = FETCHING: MSG_FETCHING = FETCHING:
MSG_LINKING = Linking: MSG_LINKING = Linking:
MSG_COMPILING = Compiling C: MSG_COMPILING = Compiling C:
@ -29,44 +48,57 @@ MSG_CLEANING = Cleaning:
MSG_CLEANING_OBJ = Cleaning $(OBJDIR): MSG_CLEANING_OBJ = Cleaning $(OBJDIR):
MSG_CLEANING_OUT = Cleaning $(OUTDIR): MSG_CLEANING_OUT = Cleaning $(OUTDIR):
# Compile for all UI targets
all: $(addprefix $(OUTDIR)/connect4_,ncurses.elf vt100.elf) all: $(addprefix $(OUTDIR)/connect4_,ncurses.elf vt100.elf)
# Compile and run the default UI target
run: $(OUTDIR)/connect4_$(UI_TARGET).elf run: $(OUTDIR)/connect4_$(UI_TARGET).elf
./$< ./$<
# Compile and run with a specific UI target
run_%: $(OUTDIR)/connect4_%.elf run_%: $(OUTDIR)/connect4_%.elf
./$< ./$<
# Compile specifically the ncurses version
# This one's special because it needs -lncursesw
$(OUTDIR)/connect4_ncurses.elf: $(addprefix $(OBJDIR)/,ui_ncurses.o logic.o connect4.o LICENSE.o) $(OUTDIR)/connect4_ncurses.elf: $(addprefix $(OBJDIR)/,ui_ncurses.o logic.o connect4.o LICENSE.o)
@echo $(MSG_LINKING) $@ @echo $(MSG_LINKING) $@
@mkdir -p $(@D) @mkdir -p $(@D)
$(CC) $(FLAGS) -lncursesw -o $@ $^ $(CC) $(FLAGS) -lncursesw -o $@ $^
# Compile the final excecutable for a given UI target
$(OUTDIR)/connect4_%.elf: $(addprefix $(OBJDIR)/,ui_%.o logic.o connect4.o LICENSE.o) $(OUTDIR)/connect4_%.elf: $(addprefix $(OBJDIR)/,ui_%.o logic.o connect4.o LICENSE.o)
@echo $(MSG_LINKING) $@ @echo $(MSG_LINKING) $@
@mkdir -p $(@D) @mkdir -p $(@D)
$(CC) $(FLAGS) -o $@ $^ $(CC) $(FLAGS) -o $@ $^
# Make an object file for the license so it does not all need to be in the code
$(OBJDIR)/LICENSE.o: $(OBJDIR)/LICENSE $(OBJDIR)/LICENSE.o: $(OBJDIR)/LICENSE
@echo $(MSG_LINKING) $@ @echo $(MSG_LINKING) $@
@mkdir -p $(@D) @mkdir -p $(@D)
cd $(@D); ld -r -b binary -o $(@F) $(^F) cd $(@D); ld -r -b binary -o $(@F) $(^F)
# Compile the object file of the main code.
# This one's special because it includes the git hash
$(OBJDIR)/connect4.o: connect4.c $(OBJDIR)/connect4.o: connect4.c
@echo $(MSG_COMPILING) $^ @echo $(MSG_COMPILING) $^
@mkdir -p $(@D) @mkdir -p $(@D)
$(CC) -c $(FLAGS) $(GITFLAG) -o $@ $^ $(CC) -c $(FLAGS) $(GITFLAG) -o $@ $^
# Generic object file compile
$(OBJDIR)/%.o: %.c $(OBJDIR)/%.o: %.c
@echo $(MSG_COMPILING) $^ @echo $(MSG_COMPILING) $^
@mkdir -p $(@D) @mkdir -p $(@D)
$(CC) -c $(FLAGS) -o $@ $^ $(CC) -c $(FLAGS) -o $@ $^
# Download the license file in non-markdown form.
# This is not cleaned as that would be too silly
$(OBJDIR)/LICENSE: $(OBJDIR)/LICENSE:
@echo $(MSG_FETCHING) $@ @echo $(MSG_FETCHING) $@
curl $(LICENSEURL) \ curl $(LICENSEURL) \
| sed -n '/END OF TERMS AND CONDITIONS/q;p' > $(OBJDIR)/LICENSE | sed -n '/END OF TERMS AND CONDITIONS/q;p' > $(OBJDIR)/LICENSE
# Remove all compilation output and intermediate files
clean: clean:
@echo $(MSG_CLEANING) @echo $(MSG_CLEANING)
@echo $(MSG_CLEANING_OBJ) @echo $(MSG_CLEANING_OBJ)

View file

@ -5,7 +5,7 @@
#include <limits.h> #include <limits.h>
#include "config.h" #include "config.h"
// Only use big ints on architectures where it doesn't impact speed // Use fastest available ints unless it's 64 bits because that uses more memory
#if INT_FAST16_MAX == INT_FAST64_MAX #if INT_FAST16_MAX == INT_FAST64_MAX
typedef int rowsint_t; typedef int rowsint_t;
typedef unsigned columnsint_t; typedef unsigned columnsint_t;

View file

@ -71,6 +71,7 @@ void initBoard( const board_t board ){
keypad(stdscr, TRUE); keypad(stdscr, TRUE);
nonl(); nonl();
echo(); echo();
__attribute__((assume(board.columns < 999)));
for( columnsint_t column = 0; column < board.columns; column++ ){ for( columnsint_t column = 0; column < board.columns; column++ ){
char colnum[4]; char colnum[4];
snprintf( snprintf(
@ -96,8 +97,10 @@ void initBoard( const board_t board ){
} }
for( rowsint_t row = 0; row < board.rows; row++ ){ for( rowsint_t row = 0; row < board.rows; row++ ){
char rownum[4]; char rownum[4];
const int intToPrint = board.rows - row + FIRST_NUMBER - 1;
__attribute__((assume(intToPrint <= 256)));
snprintf( snprintf(
rownum, sizeof(rownum), "%2d", board.rows - row + FIRST_NUMBER - 1 rownum, sizeof(rownum), "%2d", intToPrint
); );
mvaddstr( mvaddstr(
BOARD_Y + BOARD_DY * ( row + 1 ), BOARD_Y + BOARD_DY * ( row + 1 ),
@ -211,13 +214,17 @@ columnsint_t askColumn(
const board_t board const board_t board
){ ){
columnsint_t column = 0; columnsint_t column = 0;
#ifdef ARROWS
move( BOARD_Y, BOARD_X ); move( BOARD_Y, BOARD_X );
if( board.player ) addstr( "p1" ); if( board.player ) addstr( "p1" );
else addstr( "p0" ); else addstr( "p0" );
refresh(); refresh();
for(; board.height[ column ] >= board.rows; ) for(; board.height[ column ] >= board.rows; ){
column += ( column < board.columns - 1 ); column++;
if( column == board.columns ){
getch();
return QUITCOLUMN;
}
}
for(;;){ for(;;){
int ch = mvgetch( int ch = mvgetch(
BOARD_Y + BOARD_DY * ( board.rows - board.height[ column ] ), BOARD_Y + BOARD_DY * ( board.rows - board.height[ column ] ),
@ -245,32 +252,6 @@ columnsint_t askColumn(
break; break;
} }
} }
#else /* !ARROWS */
for(;;){
mvaddstr(
INPUT_Y,
INPUT_X,
"Where does player "
);
if( board.player ) addstr( "1" );
else addstr( "0" );
addstr( " put the piece? " );
clrtoeol();
refresh();
scanw( " %d", &column );
column -= FIRST_NUMBER;
move(
INPUT_Y + 1,
INPUT_X
);
clrtoeol();
if( column >= 0 && column < board.columns )
if( board.height [ column ] < board.columns )
return column;
else addstr( "Pls enter a column that ain't full" );
else addstr( "Pls enter a column that exists" );
}
#endif /* ! ARROWS */
} }
void exit_ui(void){ void exit_ui(void){

View file

@ -65,14 +65,13 @@ void initBoard( const board_t board ){
} }
void updateBoard( void updateBoard(
const wins_t wins,
const board_t board, const board_t board,
const columnsint_t column const columnsint_t column
){ ){
rowsint_t height = board.height [ column ]; rowsint_t height = board.height [ column ];
printf( printf(
"\033[%dA\033[%dC%c\033[%dB", "\033[%dA\033[%dC%c\033[%dB",
height + 3, height + 2,
column * 3 + 4, column * 3 + 4,
'0' + !!( board.player ), '0' + !!( board.player ),
height + 1 height + 1
@ -85,11 +84,11 @@ void updateBoard(
"" WININT_FORMAT "" WININT_FORMAT "\033[2B\033[8D" "" WININT_FORMAT "" WININT_FORMAT "\033[2B\033[8D"
"" WININT_FORMAT "" WININT_FORMAT "\033[2B" "" WININT_FORMAT "" WININT_FORMAT "\033[2B"
,( 3 * board.columns + 6 ) + SCOREBOARD_WIDTH - 8 ,( 3 * board.columns + 6 ) + SCOREBOARD_WIDTH - 8
,wins.count0.vertical, wins.count1.vertical ,board.count0.vertical, board.count1.vertical
,wins.count0.horizontal, wins.count1.horizontal ,board.count0.horizontal, board.count1.horizontal
,wins.count0.diagonalUp, wins.count1.diagonalUp ,board.count0.diagonalUp, board.count1.diagonalUp
,wins.count0.diagonalDown, wins.count1.diagonalDown ,board.count0.diagonalDown, board.count1.diagonalDown
,wins.count0.total, wins.count1.total ,board.count0.total, board.count1.total
); );
printf( "\033[2K\n" ); printf( "\033[2K\n" );
} }
@ -112,9 +111,10 @@ columnsint_t askColumn(
if( column < FIRST_NUMBER ) return QUITCOLUMN; if( column < FIRST_NUMBER ) return QUITCOLUMN;
column -= FIRST_NUMBER; column -= FIRST_NUMBER;
if( column < board.columns ) if( column < board.columns )
if( board.height [ column ] < board.rows ) if( board.height [ column ] < board.rows ){
printf( "\033[A" );
return column; return column;
else printf( "\033[2Apls enter a column that ain't full" ); } else printf( "\033[2Apls enter a column that ain't full" );
else else
printf( "\033[2Apls enter valid column" ); printf( "\033[2Apls enter valid column" );
printf( "\033[K\n" ); printf( "\033[K\n" );