225 lines
6.9 KiB
Matlab
225 lines
6.9 KiB
Matlab
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
|