diff --git a/config.h b/config.h
new file mode 100644
index 0000000..96adca5
--- /dev/null
+++ b/config.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#pragma once
+
+#define BOARD_WIDTH 7
+#define BOARD_HEIGTH 6
+
+#define FIRST_NUMBER 1
+
+#define ONLYPUT
+#define ARROWS
+
+#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
diff --git a/connect4.c b/connect4.c
index 8e8f4ce..1c8cdc3 100644
--- a/connect4.c
+++ b/connect4.c
@@ -17,460 +17,16 @@
* along with this program. If not, see .
*/
#include
-#include
-#include
-#include
+#include "logic.h"
+#include "config.h"
+#include "types.h"
+#include "ui.h"
#define VERSION 0.0.1
-#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
-
-#define ARROWS
-
-#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;
-#ifdef ARROWS
- move( BOARD_Y, BOARD_X );
- if( board.player ) addstr( "p1" );
- else addstr( "p0" );
- refresh();
- for(; board.heigth[ column ] >= BOARD_HEIGTH; )
- column += ( column < BOARD_WIDTH - 1 );
- for(;;){
- int ch = mvgetch(
- BOARD_Y + BOARD_DY * ( BOARD_HEIGTH - board.heigth[ column ] ),
- BOARD_X + 1 + BOARD_DX * ( column + 1 )
- );
- switch( ch ){
- case KEY_RIGHT:
- column += ( column < BOARD_WIDTH - 1 );
- break;
- case KEY_LEFT:
- column -= ( column > 0 );
- break;
- case KEY_DOWN:
- return column;
- }
- for(; board.heigth[ column ] >= BOARD_HEIGTH; ) switch( ch ){
- case KEY_RIGHT:
- if( column >= BOARD_WIDTH ) ch = KEY_LEFT;
- else column++;
- break;
- case KEY_LEFT:
- if( column == 0 ) ch = KEY_RIGHT;
- else column--;
- break;
- }
- }
-#else /* !ARROWS */
- 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
- 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 )
-#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" );
- }
-#endif /* ! ARROWS */
-}
-
-#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(
diff --git a/logic.c b/logic.c
new file mode 100644
index 0000000..5b74d35
--- /dev/null
+++ b/logic.c
@@ -0,0 +1,165 @@
+/* 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 "logic.h"
+
+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 ) );
+}
+
+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 */
+}
+
+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;
+}
diff --git a/logic.h b/logic.h
new file mode 100644
index 0000000..712ed87
--- /dev/null
+++ b/logic.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#pragma once
+
+#include "config.h"
+#include "types.h"
+
+extern void playMove(
+ board_t *boardptr,
+#ifndef ONLYPUT
+ const move_t move,
+#endif /* ! ONLYPUT */
+ const int column
+);
+
+extern void calcWins(
+ wins_t *wins,
+ const board_t board
+);
diff --git a/makefile b/makefile
index 290e1a6..911332f 100644
--- a/makefile
+++ b/makefile
@@ -2,7 +2,7 @@
FLAGS = -O3 -pedantic -Wall -Wextra -Werror -lncursesw
AnnaConnect: connect4.c
- cc $(FLAGS) -o connect4 connect4.c
+ cc $(FLAGS) -o connect4 ui_ncurses.c logic.c connect4.c
run: AnnaConnect
./connect4
diff --git a/types.h b/types.h
new file mode 100644
index 0000000..395c3f0
--- /dev/null
+++ b/types.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#pragma once
+#include "config.h"
+
+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;
diff --git a/ui.h b/ui.h
new file mode 100644
index 0000000..4423255
--- /dev/null
+++ b/ui.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#pragma once
+
+#include "types.h"
+#include "config.h"
+
+extern void initBoard( void );
+
+extern void updateBoard(
+ const wins_t wins,
+ const board_t board,
+ const int column
+);
+
+extern int askColumn(
+ const board_t board
+#ifndef ONLYPUT
+ ,const move_t move
+#endif /* ! ONLYPUT */
+);
+
+#ifndef ONLYPUT
+extern move_t askMove( void );
+#endif /* ! ONLYPUT */
diff --git a/ui_ncurses.c b/ui_ncurses.c
new file mode 100644
index 0000000..dcdf661
--- /dev/null
+++ b/ui_ncurses.c
@@ -0,0 +1,257 @@
+/* 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 "ui.h"
+#include
+#include
+
+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
+ );
+ }
+}
+
+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
+ );
+}
+
+int askColumn(
+ const board_t board
+#ifndef ONLYPUT
+ ,const move_t move
+#endif /* ONLYPUT */
+){
+ int column = 0;
+#ifdef ARROWS
+ move( BOARD_Y, BOARD_X );
+ if( board.player ) addstr( "p1" );
+ else addstr( "p0" );
+ refresh();
+ for(; board.heigth[ column ] >= BOARD_HEIGTH; )
+ column += ( column < BOARD_WIDTH - 1 );
+ for(;;){
+ int ch = mvgetch(
+ BOARD_Y + BOARD_DY * ( BOARD_HEIGTH - board.heigth[ column ] ),
+ BOARD_X + 1 + BOARD_DX * ( column + 1 )
+ );
+ switch( ch ){
+ case KEY_RIGHT:
+ column += ( column < BOARD_WIDTH - 1 );
+ break;
+ case KEY_LEFT:
+ column -= ( column > 0 );
+ break;
+ case KEY_DOWN:
+ return column;
+ }
+ for(; board.heigth[ column ] >= BOARD_HEIGTH; ) switch( ch ){
+ case KEY_RIGHT:
+ if( column >= BOARD_WIDTH ) ch = KEY_LEFT;
+ else column++;
+ break;
+ case KEY_LEFT:
+ if( column == 0 ) ch = KEY_RIGHT;
+ else column--;
+ break;
+ }
+ }
+#else /* !ARROWS */
+ 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
+ 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 )
+#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" );
+ }
+#endif /* ! ARROWS */
+}
+
+#ifndef ONLYPUT
+static move_t askMove( void ){
+ move_t move = PUT;
+ // TODO: actually ask what move
+ return move;
+}
+#endif /* ! ONLYPUT */