/* 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@hotmail.com * * 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 "logic.h" #include "macros.h" static column_t heightMask( const rowsint_t a ){ return (column_t)( (column_t)1 << (column_t)a ) - (column_t)1; } static column_t safeHeightMask( const rowsint_t a ){ return a > 0 ? heightMask( a ) : 0; } static column_t bottomHeightMask( const rowsint_t a, const rowsint_t b ){ return safeHeightMask( bottom( a, b ) ); } static void updateTotal( wincount_t *count ){ count->total = (winint_t)( count->horizontal + count->vertical + count->diagonalUp + count->diagonalDown); } // popcount for a column static winint_t popcnt_column( const column_t column ){ // winint_t i = (winint_t)__builtin_popcountg( (uint64_t)column ); winint_t i = 0; for( column_t c = column; c != 0; i++ ) c &= c - 1; return i; } // Reset a wincount static void resetcount( wincount_t *count ){ count->total = 0; count->horizontal = 0; count->vertical = 0; count->diagonalUp = 0; count->diagonalDown = 0; } // Returns 0 if counts are the same winint_t countcmp( const board_t a, const board_t b ){ winint_t i = 0; if( a.count0.total != b.count0.total ) i+=1; if( a.count1.total != b.count1.total ) i+=2; return i; } // return a random valid column columnsint_t randomColumn( const board_t board ){ columnsint_t i = 0; do{ if( i == board.columns ) return QUITCOLUMN; }while( board.height[ i++ ] == board.rows ); columnsint_t column; do{ column = (columnsint_t)rand() % board.columns; }while( board.height[ column ] == board.rows ); return column; } // Play a move void playMove( board_t *boardptr, const columnsint_t column ){ boardptr->column [ column ] |= boardptr->player << boardptr->height [ column ]; boardptr->height [ column ]++; } // Reference implementation of calcWins() // Not intended for use in the main program, only for testing purposes // Has silly behaviour for unknown reasons void calcWins_slow( board_t *boardptr ){ resetcount( &boardptr->count0 ); resetcount( &boardptr->count1 ); for( columnsint_t column = 0; column < boardptr->columns; column++ ){ boardptr->count0.vertical += popcnt_column( ~(boardptr->column[ column ] | boardptr->column[ column ] >> 1 | boardptr->column[ column ] >> 2 | boardptr->column[ column ] >> 3 )&safeHeightMask( boardptr->height[ column ] - 3 ) ); boardptr->count1.vertical += popcnt_column( boardptr->column[ column ] & boardptr->column[ column ] >> 1 & boardptr->column[ column ] >> 2 & boardptr->column[ column ] >> 3 ); }; for( columnsint_t column = 0; column < boardptr->columns - 3; column++ ){ boardptr->count0.horizontal += popcnt_column( ~(boardptr->column[ column ] | boardptr->column[ column + 1 ] | boardptr->column[ column + 2 ] | boardptr->column[ column + 3 ] ) & safeHeightMask( boardptr->height[ column ] ) & safeHeightMask( boardptr->height[ column + 1 ] ) & safeHeightMask( boardptr->height[ column + 2 ] ) & safeHeightMask( boardptr->height[ column + 3 ] ) & safeHeightMask( boardptr->rows ) ); boardptr->count1.horizontal += popcnt_column( boardptr->column[ column ] & boardptr->column[ column + 1 ] & boardptr->column[ column + 2 ] & boardptr->column[ column + 3 ] ); boardptr->count0.diagonalUp += popcnt_column( ~(boardptr->column[ column ] | boardptr->column[ column + 1 ] >> 1 | boardptr->column[ column + 2 ] >> 2 | boardptr->column[ column + 3 ] >> 3 ) & safeHeightMask( boardptr->height[ column ] ) & safeHeightMask( boardptr->height[ column + 1 ] - 1 ) & safeHeightMask( boardptr->height[ column + 2 ] - 2 ) & safeHeightMask( boardptr->height[ column + 3 ] - 3 ) & safeHeightMask( boardptr->rows ) ); boardptr->count1.diagonalUp += popcnt_column( boardptr->column[ column ] & boardptr->column[ column + 1 ] >> 1 & boardptr->column[ column + 2 ] >> 2 & boardptr->column[ column + 3 ] >> 3 ); boardptr->count0.diagonalDown += popcnt_column( ~(boardptr->column[ column ] | boardptr->column[ column + 1 ] << 1 | boardptr->column[ column + 2 ] << 2 | boardptr->column[ column + 3 ] << 3 | (column_t)( 0x07 ) // A line diagonal down don't start at one of the bottom 3 ) & bottomHeightMask( bottom( boardptr->height[ column ], boardptr->height[ column + 1 ] + 1 ), bottom( boardptr->height[ column + 2 ] + 2, boardptr->height[ column + 3 ] + 3 ) ) & safeHeightMask( boardptr->rows ) ); boardptr->count1.diagonalDown += popcnt_column( boardptr->column[ column ] & boardptr->column[ column + 1 ] << 1 & boardptr->column[ column + 2 ] << 2 & boardptr->column[ column + 3 ] << 3 ); }; updateTotal( &boardptr->count0 ); updateTotal( &boardptr->count1 ); } // Less slow implementation of calcWins(); // Only works if called after avery move void calcWins( wins_t *wins, board_t *boardptr, const columnsint_t column ){ // If no remembering between runs, run the reference implementation if( wins == NULL ) { calcWins_slow( boardptr ); return; } // First the simplest win, the humble tower // Check for lil towers wins->same.vertical2 [ column ] = ~( boardptr->column [ column ] ^ boardptr->column [ column ] >> 1 ) & safeHeightMask( boardptr->height [ column ] - 1 ); // Actually check for wins column_t newcolumn = wins->same.vertical2 [ column ] & wins->same.vertical2 [ column ] >> 1 & wins->same.vertical2 [ column ] >> 2; // Count 'em if there's a new one if( wins->same.vertical4 [ column ] != newcolumn ){ if( ( newcolumn ^ wins->same.vertical4 [ column ] ) & boardptr->column [ column ] ){ boardptr->count1.vertical ++; } else { boardptr->count0.vertical ++; } wins->same.vertical4 [ column ] = newcolumn; } // Now the rest of the wins // First connect 2 for( columnsint_t i = clipped_subtract( column, 1 ); i < bottom( column + 1, boardptr->columns - 1 ); i++ ){ wins->same.horizontal2 [ i ] = ~( boardptr->column [ i ] ^ boardptr->column [ i + 1 ] ) & bottomHeightMask( boardptr->height [ i ], boardptr->height [ i + 1 ] ); wins->same.diagonalUp2 [ i ] = ~( boardptr->column [ i ] ^ boardptr->column [ i + 1 ] >> 1 ) & bottomHeightMask( boardptr->height [ i ], boardptr->height [ i + 1 ] - 1 ); wins->same.diagonalDown2 [ i ] = ~( boardptr->column [ i ] ^ boardptr->column [ i + 1 ] << 1 ) & bottomHeightMask( boardptr->height [ i ], boardptr->height [ i + 1 ] + 1 ) & ~(column_t)1; // A diagonal line down ain't starts at the floor innit? } // Then stitch the twos together and count for( columnsint_t i = clipped_subtract( column, 3 ); i < bottom( column + 1, boardptr->columns - 3 ); i++ ){ newcolumn = wins->same.horizontal2 [ i ] & wins->same.horizontal2 [ i + 1 ] & wins->same.horizontal2 [ i + 2 ]; if( wins->same.horizontal4 [ i ] != newcolumn ){ if( ( newcolumn ^ wins->same.horizontal4 [ i ] ) & boardptr->column [ i ] ){ boardptr->count1.horizontal++; } else { boardptr->count0.horizontal++; } wins->same.horizontal4 [ i ] = newcolumn; } newcolumn = wins->same.diagonalUp2 [ i ] & wins->same.diagonalUp2 [ i + 1 ] >> 1 & wins->same.diagonalUp2 [ i + 2 ] >> 2; if( wins->same.diagonalUp4 [ i ] != newcolumn ){ if( ( newcolumn ^ wins->same.diagonalUp4 [ i ] ) & boardptr->column [ i ] ){ boardptr->count1.diagonalUp++; } else { boardptr->count0.diagonalUp++; } wins->same.diagonalUp4 [ i ] = newcolumn; } newcolumn = wins->same.diagonalDown2 [ i ] & wins->same.diagonalDown2 [ i + 1 ] << 1 & wins->same.diagonalDown2 [ i + 2 ] << 2; if( wins->same.diagonalDown4 [ i ] != newcolumn ){ if( ( newcolumn ^ wins->same.diagonalDown4 [ i ] ) & boardptr->column [ i ] ){ boardptr->count1.diagonalDown++; } else { boardptr->count0.diagonalDown++; } wins->same.diagonalDown4 [ i ] = newcolumn; } } updateTotal( &boardptr->count0 ); updateTotal( &boardptr->count1 ); }