/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AnnaConnect is a silly little text-based connect 4 game c:
* Copyright (C) 2025 Anna Snoeijs. anna@snoeijs.tech
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#define VERSION 25.01.sid
#define BOARD_WIDTH 7
#define BOARD_HEIGTH 6
#define FIRST_NUMBER 1
#define INPUT_X 2
#define INPUT_Y 1
#define SCOREBOARD_X INPUT_X
#define SCOREBOARD_Y INPUT_Y + 2
#define BOARD_X SCOREBOARD_X + 28
#define BOARD_Y SCOREBOARD_Y + 1
#define BOARD_DX 3
#define BOARD_DY 1
#define SCOREBOARD_HEIGTH 10
#define XSTR(s) STR(s)
#define STR(s) #s
#define ONLYPUT
#ifndef ONLYPUT
typedef enum {
PUT,
POP
} move_t;
#endif /* ! ONLYPUT */
typedef struct {
int player;
int heigth [ BOARD_WIDTH ];
int column [ BOARD_WIDTH ];
} board_t;
typedef struct {
int vertical2 [ BOARD_WIDTH ];
int horizontal2 [ BOARD_WIDTH - 1 ];
int diagonalUp2 [ BOARD_WIDTH - 1 ];
int diagonalDown2 [ BOARD_WIDTH - 1 ];
int vertical4 [ BOARD_WIDTH ];
int horizontal4 [ BOARD_WIDTH - 3 ];
int diagonalUp4 [ BOARD_WIDTH - 3 ];
int diagonalDown4 [ BOARD_WIDTH - 3 ];
} directions_t;
typedef struct {
int total;
int horizontal;
int vertical;
int diagonalUp;
int diagonalDown;
} wincount_t;
typedef struct {
wincount_t count0;
wincount_t count1;
int win0 [ BOARD_WIDTH ];
int win1 [ BOARD_WIDTH ];
directions_t same;
} wins_t;
static inline int top( const int a, const int b ){
return a < b ? b : a;
}
static inline int bottom( const int a, const int b ){
return a < b ? a : b;
}
static inline int heigthMask( const int a ){
return ( 1 << a ) - 1;
}
static inline int safeHeigthMask( const int a ){
return a > 0 ? heigthMask( a ) : 0;
}
static inline int bottomHeigthMask( const int a, const int b ){
return safeHeigthMask( bottom( a, b ) );
}
static void initBoard( void ){
setlocale(LC_ALL, "");
initscr();
keypad(stdscr, TRUE);
nonl();
echo();
for( int column = 0; column < BOARD_WIDTH; column++ ){
char colnum[4];
sprintf( colnum, "%2d", column + FIRST_NUMBER );
mvaddstr(
BOARD_Y,
BOARD_X + BOARD_DX * ( column + 1 ),
colnum
);
for(int row = 0; row < BOARD_HEIGTH; row++){
mvaddstr(
BOARD_Y + BOARD_DY * ( row + 1 ),
BOARD_X + BOARD_DX * ( column + 1 ),
"[ ]"
);
}
mvaddstr(
BOARD_Y + BOARD_DY * ( BOARD_HEIGTH + 1 ),
BOARD_X + BOARD_DX * ( column + 1 ),
colnum
);
}
for(int row = 0; row < BOARD_HEIGTH; row++ ){
char rownum[4];
sprintf( rownum, "%2d", row + FIRST_NUMBER );
mvaddstr(
BOARD_Y + BOARD_DY * ( row + 1 ),
BOARD_X,
rownum
);
mvaddstr(
BOARD_Y + BOARD_DY * ( row + 1 ),
BOARD_X + BOARD_DX * ( BOARD_WIDTH + 1 ),
rownum
);
}
for( int y = 0; y < SCOREBOARD_HEIGTH; y++ ){
char *str;
switch(y){
case 0: str = "┌───────────────┬────┬────┐"; break;
case 1: str = "│ player │ 0 │ 1 │"; break;
case 2: str = "├───────────────┼────┼────┤"; break;
case 3: str = "│ vertical │ │ │"; break;
case 4: str = "│ horizontal │ │ │"; break;
case 5: str = "│ diagonal up │ │ │"; break;
case 6: str = "│ diagonal down │ │ │"; break;
case 7: str = "├───────────────┼────┼────┤"; break;
case 8: str = "│ total │ │ │"; break;
case 9: str = "└───────────────┴────┴────┘"; break;
default: str = "The code be brokeys at initboard for the score";
}
mvaddstr(
SCOREBOARD_Y + y,
SCOREBOARD_X,
str
);
}
}
static void updateBoard(
const wins_t wins,
const board_t board,
const int column
){
for(int row = 0; row < board.heigth[ column ]; row++){
mvaddstr(
BOARD_Y + BOARD_DY * ( BOARD_HEIGTH - row ),
BOARD_X + 1 + BOARD_DX * ( column + 1 ),
board.column[ column ] & 1 << row ? "1" : "0"
);
}
char num[4];
sprintf( num, "%3d", wins.count0.vertical );
mvaddstr(
SCOREBOARD_Y + 3,
SCOREBOARD_X + 17,
num
);
sprintf( num, "%3d", wins.count1.vertical );
mvaddstr(
SCOREBOARD_Y + 3,
SCOREBOARD_X + 22,
num
);
sprintf( num, "%3d", wins.count0.horizontal );
mvaddstr(
SCOREBOARD_Y + 4,
SCOREBOARD_X + 17,
num
);
sprintf( num, "%3d", wins.count1.horizontal );
mvaddstr(
SCOREBOARD_Y + 4,
SCOREBOARD_X + 22,
num
);
sprintf( num, "%3d", wins.count0.diagonalUp );
mvaddstr(
SCOREBOARD_Y + 5,
SCOREBOARD_X + 17,
num
);
sprintf( num, "%3d", wins.count1.diagonalUp );
mvaddstr(
SCOREBOARD_Y + 5,
SCOREBOARD_X + 22,
num
);
sprintf( num, "%3d", wins.count0.diagonalDown );
mvaddstr(
SCOREBOARD_Y + 6,
SCOREBOARD_X + 17,
num
);
sprintf( num, "%3d", wins.count1.diagonalDown );
mvaddstr(
SCOREBOARD_Y + 6,
SCOREBOARD_X + 22,
num
);
sprintf( num, "%3d", wins.count0.total );
mvaddstr(
SCOREBOARD_Y + 8,
SCOREBOARD_X + 17,
num
);
sprintf( num, "%3d", wins.count1.total );
mvaddstr(
SCOREBOARD_Y + 8,
SCOREBOARD_X + 22,
num
);
}
static void playMove(
board_t *boardptr,
#ifndef ONLYPUT
const move_t move,
#endif /* ! ONLYPUT */
const int column
){
#ifndef ONLYPUT
switch( move ){
case PUT: // Put a new piece in the board
#endif /* ! ONLYPUT */
boardptr->column [ column ] |=
boardptr->player << boardptr->heigth [ column ];
boardptr->heigth [ column ]++;
#ifndef ONLYPUT
break;
case POP: // Pop out the lowest and make all above fall down
boardptr->column [ column ] >>= 1;
boardptr->heigth [ column ]--;
}
#endif /* ! ONLYPUT */
}
static int askColumn(
const board_t board
#ifndef ONLYPUT
,const move_t move
#endif /* ONLYPUT */
){
int column = 0;
for(;;){
mvaddstr(
INPUT_Y,
INPUT_X,
"Where does player "
);
if( board.player ) addstr( "1" );
else addstr( "0" );
#ifndef ONLYPUT
switch( move ){
case PUT:
#endif /* ONLYPUT */
addstr( " put the piece? " );
#ifndef ONLYPUT
break;
case POP:
addstr( " pop a piece? " );
}
#endif /* ONLYPUT */
clrtoeol();
refresh();
scanw( " %d", &column );
column -= FIRST_NUMBER;
move(
INPUT_Y + 1,
INPUT_X
);
clrtoeol();
if( column >= 0 && column < BOARD_WIDTH ) // The collumn--; is a really
// temporary placeholder to make the unfinished shit compile at least
#ifndef ONLYPUT
switch( move ){
case PUT:
#endif /* ! ONLYPUT */
if( board.heigth [ column ] < BOARD_HEIGTH )
return column;
else addstr( "Pls enter a column that ain't full" );
#ifndef ONLYPUT
break;
case POP:
if( board.heigth [ column ] != 0 )
return column;
else addstr( "Pls enter a column that ain't empty" );
}
#endif /* ! ONLYPUT */
else addstr( "Pls enter a column that exists" );
}
}
#ifndef ONLYPUT
static move_t askMove( void ){
move_t move = PUT;
// TODO: actually ask what move
return move;
}
#endif /* ! ONLYPUT */
static void calcWins(
wins_t *wins,
const board_t board
){
// First the simplest win, the humble tower
wins->count0.vertical = 0;
wins->count1.vertical = 0;
for( int i = 0; i < BOARD_WIDTH; i++ ){
// Check for lil towers
wins->same.vertical2 [ i ] =
~( board.column [ i ]
^ board.column [ i ] >> 1 )
& safeHeigthMask( board.heigth [ i ] - 1 );
// Actually check for win
wins->same.vertical4 [ i ] =
wins->same.vertical2 [ i ]
& wins->same.vertical2 [ i ] >> 1
& wins->same.vertical2 [ i ] >> 2;
// Count 'em
wins->count0.vertical += __builtin_popcount(
wins->same.vertical4 [ i ] & ~board.column [ i ]
);
wins->count1.vertical += __builtin_popcount(
wins->same.vertical4 [ i ] & board.column [ i ]
);
}
// Now the rest of the wins
wins->count0.horizontal = 0;
wins->count1.horizontal = 0;
wins->count0.diagonalUp = 0;
wins->count1.diagonalUp = 0;
wins->count0.diagonalDown = 0;
wins->count1.diagonalDown = 0;
// First connect 2
for( int i = 0; i < BOARD_WIDTH - 1; i++ ){
wins->same.horizontal2 [ i ] =
~( board.column [ i ]
^ board.column [ i + 1 ] )
& bottomHeigthMask(
board.heigth [ i ],
board.heigth [ i + 1 ]
);
wins->same.diagonalUp2 [ i ] =
~( board.column [ i ]
^ board.column [ i + 1 ] >> 1 )
& bottomHeigthMask(
board.heigth [ i ],
board.heigth [ i + 1 ] - 1
);
wins->same.diagonalDown2 [ i ] =
~( board.column [ i ]
^ board.column [ i + 1 ] << 1 )
& bottomHeigthMask(
board.heigth [ i ],
board.heigth [ i + 1 ] + 1
)
& ~1; // A diagonal line down ain't starts at the floor innit?
}
// Then stitch the twos together and count
for( int i = 0; i < BOARD_WIDTH - 3; i++ ){
wins->same.horizontal4 [ i ] =
wins->same.horizontal2 [ i ]
& wins->same.horizontal2 [ i + 1 ]
& wins->same.horizontal2 [ i + 2 ];
wins->same.diagonalUp4 [ i ] =
wins->same.diagonalUp2 [ i ]
& wins->same.diagonalUp2 [ i + 1 ] >> 1
& wins->same.diagonalUp2 [ i + 2 ] >> 2;
wins->same.diagonalDown4 [ i ] =
wins->same.diagonalDown2 [ i ]
& wins->same.diagonalDown2 [ i + 1 ] << 1
& wins->same.diagonalDown2 [ i + 2 ] << 2;
wins->count0.horizontal += __builtin_popcount(
wins->same.horizontal4 [ i ] & ~board.column [ i ]
);
wins->count1.horizontal += __builtin_popcount(
wins->same.horizontal4 [ i ] & board.column [ i ]
);
wins->count0.diagonalUp += __builtin_popcount(
wins->same.diagonalUp4 [ i ] & ~board.column [ i ]
);
wins->count1.diagonalUp += __builtin_popcount(
wins->same.diagonalUp4 [ i ] & board.column [ i ]
);
wins->count0.diagonalDown += __builtin_popcount(
wins->same.diagonalDown4 [ i ] & ~board.column [ i ]
);
wins->count1.diagonalDown += __builtin_popcount(
wins->same.diagonalDown4 [ i ] & board.column [ i ]
);
}
wins->count0.total =
wins->count0.vertical
+ wins->count0.horizontal
+ wins->count0.diagonalUp
+ wins->count0.diagonalDown;
wins->count1.total =
wins->count1.vertical
+ wins->count1.horizontal
+ wins->count1.diagonalUp
+ wins->count1.diagonalDown;
}
int main(){
// First the boilerplate stuffs
printf(
"AnnaConnect version "XSTR(VERSION)", Copyright (C) Anna Snoeijs\n"
"AnnaConnect comes with ABSOLUTELY NO WARRANTY\n"
"This is free software, and you are welcome to redistribute it\n"
"under certain condtions\n"
);
// board, innit?
initBoard();
board_t playboard = {
.player = 0,
.column = {0},
.heigth = {0},
};
wins_t wins = {
.count0 = {
.total = 0,
.vertical = 0,
.horizontal = 0,
.diagonalUp = 0,
.diagonalDown = 0,
},
.count1 = {
.total = 0,
.vertical = 0,
.horizontal = 0,
.diagonalUp = 0,
.diagonalDown = 0,
},
.win0 = {0},
.win1 = {0},
};
for(;; playboard.player = !playboard.player ){
#ifndef ONLYPUT
move_t move = askMove();
int column = askColumn( playboard, move );
playMove( &playboard, move, column );
#else /* ONLYPUT */
int column = askColumn( playboard );
playMove( &playboard, column );
#endif /* ONLYPUT */
calcWins( &wins, playboard );
updateBoard( wins, playboard, column );
}
}