function TicTacToe % Set up the board (one for each player) boards = false(3, 3, 2); % Players' pieces rep = [' 1 | 4 | 7' ; ' 2 | 5 | 8' ; ' 3 | 6 | 9']; % Prompt user with options fprintf('Welcome to Tic-Tac-Toe!\n') nHumans = str2double(input('Enter the number of human players: ', 's')); if isnan(nHumans) || ceil(nHumans) ~= nHumans || nHumans < 1 || nHumans > 2 nHumans = 0; pHuman = false(2, 1); elseif nHumans == 1 humanFirst = input('Would the human like to go first (Y/N)? ', 's'); if length(humanFirst) == 1 && lower(humanFirst) == 'n' pHuman = [false ; true]; else pHuman = [true ; false]; end else pHuman = true(2, 1); end if any('o' == input('Should Player 1 use X or O? ', 's')) marks = 'OX'; else marks = 'XO'; end fprintf('So Player 1 is %shuman and %cs and Player 2 is %shuman and %cs.\n', ... char('not '.*~pHuman(1)), marks(1), char('not '.*~pHuman(2)), marks(2)) if nHumans > 0 fprintf('Select the space to mark by entering the space number.\n') fprintf('No entry will quit the game.\n') end % Play game gameOver = false; turn = 1; while ~gameOver fprintf('\n') disp(rep) fprintf('\n') if pHuman(turn) [move, isValid, isQuit] = GetMoveFromPlayer(turn, boards); gameOver = isQuit; else move = GetMoveFromComputer(turn, boards); fprintf('Player %d chooses %d\n', turn, move) isValid = true; isQuit = false; end if isValid && ~isQuit [r, c] = ind2sub([3 3], move); boards(r, c, turn) = true; rep(r, 4*c) = marks(turn); if CheckWin(boards(:, :, turn)) gameOver = true; fprintf('\n') disp(rep) fprintf('\nPlayer %d wins!\n', turn) elseif CheckDraw(boards) gameOver = true; fprintf('\n') disp(rep) fprintf('\nCat''s game!\n') end turn = ~(turn-1)+1; end end end function [move, isValid, isQuit] = GetMoveFromPlayer(pNum, boards) % move - 1-9 indicating move position, 0 if invalid move % isValid - logical indicating if move was valid, true if quitting % isQuit - logical indicating if player wishes to quit game p1 = boards(:, :, 1); p2 = boards(:, :, 2); moveStr = input(sprintf('Player %d: ', pNum), 's'); if isempty(moveStr) fprintf('Play again soon!\n') move = 0; isValid = true; isQuit = true; else move = str2double(moveStr); isQuit = false; if isnan(move) || move < 1 || move > 9 || p1(move) || p2(move) fprintf('%s is an invalid move.\n', moveStr) isQuit = 0; isValid = false; else isValid = true; end end end function move = GetMoveFromComputer(pNum, boards) % pNum - 1-2 player number % boards - 3x3x2 logical array where pBoards(:,:,1) is player 1's marks % Assumes that it is possible to make a move if ~any(boards(:)) % Play in the corner for first move move = 1; else % Use Newell and Simon's "rules to win" pMe = boards(:, :, pNum); pThem = boards(:, :, ~(pNum-1)+1); possMoves = find(~(pMe | pThem)).'; % Look for a winning move move = FindWin(pMe, possMoves); if move return end % Look to block opponent from winning move = FindWin(pThem, possMoves); if move return end % Look to create a fork (two non-blocked lines of two) for m = possMoves newPMe = pMe; newPMe(m) = true; if CheckFork(newPMe, pThem) move = m; return end end % Look to make two in a row so long as it doesn't force opponent to fork notGoodMoves = false(size(possMoves)); for m = possMoves newPMe = pMe; newPMe(m) = true; if CheckPair(newPMe, pThem) nextPossMoves = possMoves; nextPossMoves(nextPossMoves == m) = []; theirMove = FindWin(newPMe, nextPossMoves); newPThem = pThem; newPThem(theirMove) = true; if ~CheckFork(newPThem, newPMe) move = m; return else notGoodMoves(possMoves == m) = true; end end end possMoves(notGoodMoves) = []; % Play the center if available if any(possMoves == 5) move = 5; return end % Play the opposite corner of the opponent's piece if available corners = [1 3 7 9]; move = intersect(possMoves, ... corners(~(pMe(corners) | pThem(corners)) & pThem(fliplr(corners)))); if ~isempty(move) move = move(1); return end % Play an empty corner if available move = intersect(possMoves, corners); if move move = move(1); return end % Play an empty side if available sides = [2 4 6 8]; move = intersect(possMoves, sides); if move move = move(1); return end % No good moves, so move randomly possMoves = find(~(pMe | pThem)); move = possMoves(randi(length(possMoves))); end end function move = FindWin(board, possMoves) % board - 3x3 logical representing one player's pieces % move - integer indicating position to move to win, or 0 if no winning move for m = possMoves newPMe = board; newPMe(m) = true; if CheckWin(newPMe) move = m; return end end move = 0; end function win = CheckWin(board) % board - 3x3 logical representing one player's pieces % win - logical indicating if that player has a winning board win = any(all(board)) || any(all(board, 2)) || ... all(diag(board)) || all(diag(fliplr(board))); end function fork = CheckFork(p1, p2) % fork - logical indicating if player 1 has created a fork unblocked by player 2 fork = sum([sum(p1)-sum(p2) (sum(p1, 2)-sum(p2, 2)).' ... sum(diag(p1))-sum(diag(p2)) ... sum(diag(fliplr(p1)))-sum(diag(fliplr(p2)))] == 2) > 1; end function pair = CheckPair(p1, p2) % pair - logical indicating if player 1 has two in a line unblocked by player 2 pair = any([sum(p1)-sum(p2) (sum(p1, 2)-sum(p2, 2)).' ... sum(diag(p1))-sum(diag(p2)) ... sum(diag(fliplr(p1)))-sum(diag(fliplr(p2)))] == 2); end function draw = CheckDraw(boards) % boards - 3x3x2 logical representation of all players' pieces draw = all(all(boards(:, :, 1) | boards(:, :, 2))); end