AnnaConnect/connect4.c
2025-06-06 21:31:59 +02:00

372 lines
10 KiB
C

/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AnnaConnect is a silly little text-based connect 4 game c:
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#define BOARD_WIDTH 7
#define BOARD_HEIGTH 6
#define FIRST_NUMBER 1
#define XSTR(s) STR(s)
#define STR(s) #s
typedef enum {
PUT,
POP
} move_t;
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 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 ) );
}
#define BOARD_WIDTH_CHARS ( 3 * BOARD_WIDTH + 6 )
#define SCOREBOARD_OFFSET 5
#define SCOREBOARD_WIDTH 27
#define SCOREBOARD_HEIGTH 9
static void initBoard( void ){
printf( "\n\n " );
for( int column = 0; column < BOARD_WIDTH; column++ )
printf( "%2d ", column + FIRST_NUMBER );
putchar( '\n' );
for( int row = BOARD_HEIGTH - 1; row > -1; row-- ){
// begin row
printf( "%2d " , row + FIRST_NUMBER );
for( int column = 0; column < BOARD_WIDTH; column++ )
printf( "[ ]" );
// end of row
printf( "%2d" , row + FIRST_NUMBER );
switch( row ){
}
putchar( '\n' );
}
// end of board
printf( " " );
for( int column = 0; column < BOARD_WIDTH; column++ )
printf( "%2d ", column + FIRST_NUMBER );
#define SCOREBOARD_LINE_END "\033[B\033["XSTR(SCOREBOARD_WIDTH)"D"
printf("\033["XSTR(SCOREBOARD_OFFSET)"C\033["XSTR(SCOREBOARD_HEIGTH)"A"
"┌───────────────┬────┬────┐"SCOREBOARD_LINE_END
"│ player │ 0 │ 1 │"SCOREBOARD_LINE_END
"├───────────────┼────┼────┤"SCOREBOARD_LINE_END
"│ vertical │ │ │"SCOREBOARD_LINE_END
"│ horizontal │ │ │"SCOREBOARD_LINE_END
"│ diagonal up │ │ │"SCOREBOARD_LINE_END
"│ diagonal down │ │ │"SCOREBOARD_LINE_END
"├───────────────┼────┼────┤"SCOREBOARD_LINE_END
"│ total │ │ │"SCOREBOARD_LINE_END
"└───────────────┴────┴────┘"SCOREBOARD_LINE_END
);
#undef SCOREBOARD_LINE_END
putchar( '\n' );
}
static void updateBoard(
const wins_t wins,
const board_t board,
const move_t move,
const int column
){
int heigth = board.heigth [ column ];
switch( move ){
case PUT: // Only print the put one
printf(
"\033[%dA\033[%dC%c\033[%dB",
heigth + 3,
column * 3 + 4,
'0' + !!( board.player ),
heigth + 1
);
break;
default: // Print all the pieces in the column and the air above
printf( "\033[%dA", heigth + 4 );
if( heigth < BOARD_HEIGTH ) printf( "\033[%dC ", column * 3 + 4 );
else printf( "\033[%dC", column * 3 + 5 );
for( int row = heigth; row --> 0; )
printf(
"\033[B\033[D%c",
'0' + !!( board.column [ column ] & ( 1 << row ) )
);
printf( "\033[2B" );
}
printf(
"\r\033[7A\033[%dC"
"%3d │%3d\033[B\033[8D"
"%3d │%3d\033[B\033[8D"
"%3d │%3d\033[B\033[8D"
"%3d │%3d\033[2B\033[8D"
"%3d │%3d\033[2B"
,BOARD_WIDTH_CHARS + SCOREBOARD_WIDTH - 8
,wins.count0.vertical, wins.count1.vertical
,wins.count0.horizontal, wins.count1.horizontal
,wins.count0.diagonalUp, wins.count1.diagonalUp
,wins.count0.diagonalDown, wins.count1.diagonalDown
,wins.count0.total, wins.count1.total
);
printf( "\033[2K\n" );
}
#undef BOARD_WIDTH_CHARS
#undef SCOREBOARD_OFFSET
#undef SCOREBOARD_WIDTH
#undef SCOREBOARD_HEIGTH
static void playMove(
board_t *boardptr,
const move_t move,
const int column
){
switch( move ){
case PUT: // Put a new piece in the board
boardptr->column [ column ] |=
boardptr->player << boardptr->heigth [ column ];
boardptr->heigth [ column ]++;
break;
case POP: // Pop out the lowest and make all above fall down
boardptr->column [ column ] >>= 1;
boardptr->heigth [ column ]--;
}
}
static int askColumn(
const board_t board,
const move_t move
){
int column;
for(;;){
switch( move ){
case PUT: printf( "Wher player %d put? ", board.player );
break;
case POP: printf( "Wher player %d pop? ", board.player );
}
printf( "\033[K" ); // clear everything after cursor
scanf( "%d", &column );
column -= FIRST_NUMBER;
if( column >= 0 && column < BOARD_WIDTH )
switch( move ){
case PUT:
if( board.heigth [ column ] < BOARD_HEIGTH )
return column;
else printf( "\033[2Apls enter a column that ain't full" );
break;
case POP:
if( board.heigth [ column ] != 0 )
return column;
else printf( "\033[2Apls enter a column that ain't empty" );
}
else
printf( "\033[2Apls enter valid column" );
putchar( '\n' );
}
}
static move_t askMove( void ){
move_t move = PUT;
// TODO: actually ask what move
return move;
}
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 2024, 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?
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},
};
move_t move = PUT;
initBoard();
for( int column = 0;; playboard.player = !playboard.player ){
move = askMove();
column = askColumn( playboard, move );
playMove( &playboard, move, column );
calcWins( &wins, playboard );
updateBoard( wins, playboard, move, column );
}
}