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 );
+ }
+}