diff --git a/connect4.c b/connect4.c new file mode 100644 index 0000000..9eb41c6 --- /dev/null +++ b/connect4.c @@ -0,0 +1,372 @@ +/* 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 . + */ + +#include + +#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 ); + } +}