function cf(nr,nc)                 % user MAY specify number of rows and columns (at least 4 of each)

% sample solution of Connect Four project
% developed by Charles Riedesel
% February 2, 2012

if nargin < 2                      % default to 6 rows and 7 columns if user does not specify
   nr = 6;
   nc = 7;
end

if nr < 4                          % ensure number of rows and colums is at least 6 and 7
   nr = 6;
end
if nc < 4
   nc = 7;
end

names{1} = input('Enter first player''s name: ', 's');
names{2} = input('Enter second player''s name: ', 's');

tokens = 'XO';                     % X for player 1, O for player 2
again = true;                      % assume we want to play at least once!
startPlayer = 2;                   % we will alternate starting player with each game

while again                        % (another) game?  Of course yes on first time
   board = zeros(nr,nc);           % initialize game board to 0's (empty)
   startPlayer = 3-startPlayer;    % alternate starting player (note: 3-2=1 and 3-1=2)
   player = startPlayer;           % 'player' starts the game
   win = 0;                        % 0: no win, 1 or 2: player 1 or 2 wins, 3: tie game
   displayCF(board)                % display opening board
   fprintf('%s, your move with %c! (+col to add, -col to drop, 0 to reset): ', ...
        names{player}, tokens(player))
   play = input('');               % get desired column (negative for drop and zero for quit)
   while play ~= 0 && ~win         % continue while not resetting and no win/tie as yet
      if play < 0                  % if drop off bottom instead of add to top
          [board, statusGood] = dropOne(board, -play, player);
      else                         % else it's add to top
          [board, statusGood] = addOne(board, play, player);
      end
      if statusGood                % if requested move was valid
          displayCF(board)         %    display updated board
          player = 3-player;       %    alternate players (needed if not a win)
          win = checkWin(board);   %    check for a possible win
      else                         % else try again for same player
          fprintf('%s, invalid play.  Try again. \n', names{player})
      end
      if ~win                      % if no win yet
          fprintf('%s, your move with %c! (+col to add, -col to drop, 0 to reset): ', ...
                 names{player}, tokens(player))
          play = input('');        %    get desired column for next play
      else                         % else we can end game and declare win/tie
          if win < 3               %    remember - 1 or 2 for player 1 or 2 winning, 3 for tie
              fprintf('Congrats, %s wins!\n', names{win})
          else
              fprintf('Tie game!\n')
          end
      end
   end                             % end of this game?  ask for possible new game
   again = input('Another round? (no - 0, yes - 1) ');
end

end    % end of main function ----------------------------------------------------------------

function displayCF(board)          % display the current board
   [nr,nc] = size(board);
   board(board==0) = ' ';          % convert 0's (empties) to spaces for display
   board(board==1) = 'X';          % convert 1's (player 1 tokens) to X's for display
   board(board==2) = 'O';          % convert 2's (player 2 tokens) to O's for display
   fprintf('\n')
   fprintf(' %1d ', 1:nc)          % print the column numbers as labels on top each column
   fprintf('\n')
   for ii = nr:-1:1                % display each row (in descending order)
       fprintf('|%c|', board(ii,:))
       fprintf('\n')
   end
   fprintf('\n')
end    % end of display function -------------------------------------------------------------

function [board, status] = dropOne(board, column, marker)  % drop desired column if legal
   [nr,nc] = size(board);
   status = false;                 % assume invalid move until proven otherwise
   if column <= nc                 % does that column exist?
      if board(1, column) == marker   % does bottom token belong to current player?
         board(1:nr-1, column) = board(2:nr, column);   % yes: drop one level down
         board(nr, column) = 0;    % replace former top token with empty
         status = true;
      end
   end
end    % end of function to drop a token -----------------------------------------------------

function [board, status] = addOne(board, column, marker)   % add to desired column if legal
   [nr,nc] = size(board);
   status = false;                 % assume invalid move until proven otherwise
   if column <= nc                 % does that column exist?
      if  board(nr, column) == 0   % is the top position open?
         r = find(board(:, column) == 0, 1);   % yes: find current top of column
         board(r, column) = marker;  % and add new token
         status = true;
      end
   end 
end    % end of function to add a token ------------------------------------------------------

function win = checkWin(board)     % check if there is a win or tie
   [nr,nc] = size(board);
   win = 0;                        % assume no win/tie until proven otherwise
   vcheck = zeros(nr-3,nc);        % grid for counting matching tokens vertically
   hcheck = zeros(nr,nc-3);        % grid for counting matching tokens horizonally
   fcheck = zeros(nr-3,nc-3);      % grid for counting matching tokens diagonally forward
   bcheck = zeros(nr-3,nc-3);      % grid for counting matching tokens diagonally backward
   for ii = 2:4                    % try to match four overlays (3 possible matches)
      vcheck = vcheck + (board(1:nr-3, :) == board(ii:nr-4+ii, :));  % shift overlay up
      hcheck = hcheck + (board(:, 1:nc-3) == board(:, ii:nc-4+ii));  % shift overlay right
      fcheck = fcheck + (board(1:nr-3, 4:nc) == board(ii:nr-4+ii, 5-ii:nc+1-ii)); % ... diagonally
      bcheck = bcheck + (board(1:nr-3, 1:nc-3) == board(ii:nr-4+ii, ii:nc-4+ii)); % ... back diagonal
   end                             % grids should have total matches going each of 4 directions
   fours = logical(zeros(nr,nc));  % initialize grid that merges the four previous grids
   fours(1:nr-3, :) = vcheck == 3; %    watching only for cases of 3 matches (indicating 4 in a row)
   fours(:, 1:nc-3) = fours(:, 1:nc-3) | hcheck == 3;
   fours(1:nr-3, 4:nc) = fours(1:nr-3, 4:nc) | fcheck == 3;
   fours(1:nr-3, 1:nc-3) = fours(1:nr-3, 1:nc-3) | bcheck == 3;
   runs = board .* fours;          % convert logical trues (4 in a row) to player number (1 or 2)
   win = any(any(runs == 1)) + 2 * any(any(runs == 2));  % tricky! if both "win" get 1+2 = 3 for tie!
end    % end of function to check for win or tie ---------------------------------------------
 
