Just another update

This commit is contained in:
Ingy döt Net 2015-02-20 00:35:01 -05:00
parent a25938f123
commit 00a190b0a6
6591 changed files with 94363 additions and 23227 deletions

View file

@ -1 +1,2 @@
Play a game of [[wp:Tic-tac-toe|tic-tac-toe]]. Ensure that legal moves are played and that a winning position is notified.
Play a game of [[wp:Tic-tac-toe|tic-tac-toe]].
Ensure that legal moves are played and that a winning position is notified.

View file

@ -0,0 +1,92 @@
(defun generate-board ()
(loop repeat 9 collect nil))
(defparameter *straights* '((1 2 3) (4 5 6) (7 8 9) (1 4 7) (2 5 8) (3 6 9) (1 5 9) (3 5 7)))
(defparameter *current-player* 'x)
(defun get-board-elt (n board)
(nth (1- n) board))
(defun legal-p (n board)
(null (get-board-elt n board)))
(defun set-board-elt (n board symbol)
(if (legal-p n board)
(setf (nth (1- n) board) symbol)
(progn (format t "Illegal move. Try again.~&")
(set-board-elt (read) board symbol))))
(defun list-legal-moves (board)
(loop for i from 1 to (length board)
when (legal-p i board)
collect i))
(defun get-random-element (lst)
(nth (random (length lst)) lst))
(defun multi-non-nil-eq (lst)
(and (notany #'null lst)
(notany #'null (mapcar #'(lambda (x) (eq (car lst) x)) lst))
(car lst)))
(defun elements-of-straights (board)
(loop for i in *straights*
collect (loop for j from 0 to 2
collect (get-board-elt (nth j i) board))))
(defun find-winner (board)
(car (remove-if #'null (mapcar #'multi-non-nil-eq (elements-of-straights board)))))
(defun set-player (mark)
(format t "Shall a computer play as ~a? (y/n)~&" mark)
(let ((response (read)))
(cond ((equalp response 'y) t)
((equalp response 'n) nil)
(t (format t "Come again?~&")
(set-player mark)))))
(defun player-move (board symbol)
(format t "~%Player ~a, please input your move.~&" symbol)
(set-board-elt (read) board symbol)
(format t "~%"))
(defun computer-move (board symbol)
(let ((move (get-random-element (list-legal-moves board))))
(set-board-elt move board symbol)
(format t "~%computer selects ~a~%~%" move)))
(defun computer-move-p (current-player autoplay-x-p autoplay-o-p)
(if (eq current-player 'x)
autoplay-x-p
autoplay-o-p))
(defun perform-turn (current-player board autoplay-x-p autoplay-o-p)
(if (computer-move-p current-player autoplay-x-p autoplay-o-p)
(computer-move board current-player)
(player-move board current-player)))
(defun switch-player ()
(if (eq *current-player* 'x)
(setf *current-player* 'o)
(setf *current-player* 'x)))
(defun display-board (board)
(loop for i downfrom 2 to 0
do (loop for j from 1 to 3
initially (format t "|")
do (format t "~a|" (or (get-board-elt (+ (* 3 i) j) board) (+ (* 3 i) j)))
finally (format t "~&"))))
(defun tic-tac-toe ()
(setf *current-player* 'x)
(let ((board (generate-board))
(autoplay-x-p (set-player 'x))
(autoplay-o-p (set-player 'o)))
(format t "~%")
(loop until (or (find-winner board) (null (list-legal-moves board)))
do (display-board board)
do (perform-turn *current-player* board autoplay-x-p autoplay-o-p)
do (switch-player)
finally (if (find-winner board)
(format t "The winner is ~a!" (find-winner board))
(format t "It's a tie.")))))

View file

@ -6,7 +6,7 @@ struct GameBoard {
enum : dchar { human = 'X', computer = 'O' }
enum Game { going, humanWins, computerWins, draw }
const pure nothrow invariant() {
const pure nothrow @safe @nogc invariant() {
int nHuman = 0, nComputer = 0;
foreach (immutable i, immutable c; board)
if (c.isDigit)
@ -22,15 +22,15 @@ struct GameBoard {
return format("%(%-(%s|%)\n-+-+-\n%)", board[].chunks(3));
}
bool isAvailable(in int i) const pure nothrow {
bool isAvailable(in int i) const pure nothrow @safe @nogc {
return i >= 0 && i < 9 && board[i].isDigit;
}
int[] availablePositions() const pure nothrow {
return 9.iota.filter!(i => isAvailable(i)).array;
auto availablePositions() const pure nothrow @safe /*@nogc*/ {
return 9.iota.filter!(i => isAvailable(i));
}
Game winner() const pure nothrow {
Game winner() const pure nothrow @safe /*@nogc*/ {
static immutable wins = [[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6]];
@ -49,7 +49,7 @@ struct GameBoard {
return availablePositions.empty ? Game.draw: Game.going;
}
bool isFinished() const pure nothrow {
bool isFinished() const pure nothrow @safe /*@nogc*/ {
return winner != Game.going;
}
@ -57,8 +57,8 @@ struct GameBoard {
out(res) {
assert(res >= 0 && res < 9 && isAvailable(res));
} body {
// return availablePositions.choice;
return availablePositions[uniform(0, $)];
// return availablePositions.array.choice;
return availablePositions.array[uniform(0, $)];
}
}

View file

@ -0,0 +1,100 @@
include std/console.e
include std/text.e
include std/search.e
include std/sequence.e
sequence board
sequence players = {"X","O"}
function DisplayBoard()
for i = 1 to 3 do
for j = 1 to 3 do
printf(1,"%s",board[i][j])
if j < 3 then
printf(1,"%s","|")
end if
end for
if i < 3 then
puts(1,"\n-----\n")
else
puts(1,"\n\n")
end if
end for
return 1
end function
function CheckWinner()
sequence temp = board
for a = 1 to 2 do
for i = 1 to 3 do
if equal({"X","X","X"},temp[i]) then
puts(1,"X wins\n\n")
return 1
elsif equal({"O","O","O"},temp[i]) then
puts(1,"O wins\n\n")
return 1
end if
end for
temp = columnize(board)
end for
if equal({"X","X","X"},{board[1][1],board[2][2],board[3][3]}) or
equal({"X","X","X"},{board[1][3],board[2][2],board[3][1]}) then
puts(1,"X wins\n")
return 1
elsif equal({"O","O","O"},{board[1][1],board[2][2],board[3][3]}) or
equal({"O","O","O"},{board[1][3],board[2][2],board[3][1]}) then
puts(1,"O wins\n")
return 1
end if
if moves = 9 then
puts(1,"Draw\n\n")
return 1
end if
return 0
end function
integer turn, row, column, moves
sequence replay
while 1 do
board = repeat(repeat(" ",3),3)
DisplayBoard()
turn = rand(2)
moves = 0
while 1 do
while 1 do
printf(1,"%s's turn\n",players[turn])
row = prompt_number("Enter row: ",{})
column = prompt_number("Enter column: ",{})
if match(board[row][column]," ") then
board[row][column] = players[turn]
moves += 1
exit
else
puts(1,"Space already taken - pick again\n")
end if
end while
DisplayBoard()
if CheckWinner() then
exit
end if
if turn = 1 then
turn = 2
else
turn = 1
end if
end while
replay = lower(prompt_string("Play again (y/n)?\n\n"))
if match(replay,"n") then
exit
end if
end while

View file

@ -147,7 +147,7 @@ changePlayer 'X' = 'O'
-- first, the computer looks for two pieces of his opponent in a row
-- and tries to block.
-- otherwise, it tries to guess the best position for the next movement.
-- as a least ressource, it places a piece randomly.
-- as a last resort, it places a piece randomly.
autoTurn :: Bool -> (Int, Char, String) -> IO (Int, Char, String)
autoTurn forceRandom (count, player, game) = do
-- try a random position 'cause everything else failed

View file

@ -0,0 +1,224 @@
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

View file

@ -2,7 +2,7 @@
oops =$ '***error!*** '; cell# ='cell number' /*a couple of literals*/
$=copies('',9) /*eyecatcher literal for messages*/
sing=''; jam=''; bar=''; junc=''; dbl=jam || bar || junc
sw=80-1 /*LINESIZE() bif would be better.*/
sw=linesize()-1 /*get the width of the terminal. */
parse arg N hm cm .,@.; if N=='' then N=3; oN=N /*specifying some args?*/
N=abs(N); NN=N*N; middle=NN%2+N%2 /*if N < 0, computer goes first.*/
if N<2 then do; say oops 'tic-tac-toe grid is too small: ' N; exit; end

View file

@ -25,7 +25,7 @@
;; optimal-move :: State -> Move
;; Choses the optimal move.
;; If several equipollent moves exist -- choses one randomly.
;; If several equivalent moves exist -- choses one randomly.
(define/public ((optimal-move look-ahead) S)
(! (argmax (λ (m) (! (minimax (game-tree S m look-ahead))))
(shuffle (possible-moves S)))))
@ -36,7 +36,7 @@
[i 1]
[s (my-move S m)])
(cond
[(my-win? s) (/ 1 i)] ; more close wins and looses
[(my-win? s) (/ 1 i)] ; more close wins and loses
[(my-loss? s) (/ -1 i)] ; have bigger weights
[(draw-game? s) 0]
[(>= i look-ahead) (/ 1 i)]

View file

@ -1,74 +1,97 @@
require 'set'
module TicTacToe
ROWS = [[1,2,3],[4,5,6],[7,8,9],[1,4,7],[2,5,8],[3,6,9],[1,5,9],[3,5,7]]
LINES = [[1,2,3],[4,5,6],[7,8,9],[1,4,7],[2,5,8],[3,6,9],[1,5,9],[3,5,7]]
class Game
def initialize(player1Class, player2Class)
@board = Array.new(10)
@free_positions = (1..9).to_a
@players = [player1Class.new(self), player2Class.new(self)]
@turn = rand(2)
puts "#{@players[@turn]} goes first."
@players[@turn].marker = "X"
@players[nextTurn].marker = "O"
def initialize(player_1_class, player_2_class)
@board = Array.new(10) # we ignore index 0 for convenience
@current_player_id = 0
@players = [player_1_class.new(self, "X"), player_2_class.new(self, "O")]
puts "#{current_player} goes first."
end
attr_reader :free_positions, :board, :turn
attr_reader :board, :current_player_id
def play
loop do
player = @players[@turn]
idx = player.select
puts "#{player} selects #{player.marker} position #{idx}"
@board[idx] = player.marker
@free_positions.delete(idx)
place_player_marker(current_player)
# check for a winner
ROWS.each do |row|
if row.all? {|idx| @board[idx] == player.marker}
puts "#{player} wins!"
print_board
return
end
end
# no winner, is board full?
if @free_positions.empty?
if player_has_won?(current_player)
puts "#{current_player} wins!"
print_board
return
elsif board_full?
puts "It's a draw."
print_board
return
end
nextTurn!
switch_players!
end
end
def nextTurn
1 - @turn
def free_positions
Set.new((1..9).select {|position| @board[position].nil?})
end
def nextTurn!
@turn = nextTurn
def place_player_marker(player)
position = player.select_position!
puts "#{player} selects #{player.marker} position #{position}"
@board[position] = player.marker
end
def player_has_won?(player)
LINES.any? do |line|
line.all? {|position| @board[position] == player.marker}
end
end
def board_full?
free_positions.empty?
end
def other_player_id
1 - @current_player_id
end
def switch_players!
@current_player_id = other_player_id
end
def current_player
@players[current_player_id]
end
def opponent
@players[nextTurn]
@players[other_player_id]
end
def turn_num
10 - free_positions.size
end
def print_board
display =lambda{|row| row.map {|i| @board[i] ? @board[i] : i}.join("|")}
puts display[[1,2,3]], "-+-+-", display[[4,5,6]], "-+-+-", display[[7,8,9]]
col_separator, row_separator = " | ", "--+---+--"
label_for_position = lambda{|position| @board[position] ? @board[position] : position}
row_for_display = lambda{|row| row.map(&label_for_position).join(col_separator)}
row_positions = [[1,2,3], [4,5,6], [7,8,9]]
rows_for_display = row_positions.map(&row_for_display)
puts rows_for_display.join("\n" + row_separator + "\n")
end
end
class Player
def initialize(game)
def initialize(game, marker)
@game = game
@marker = nil
@marker = marker
end
attr_accessor :marker
attr_reader :marker
end
class HumanPlayer < Player
def select
def select_position!
@game.print_board
loop do
print "Select your #{marker} position: "
@ -84,39 +107,75 @@ module TicTacToe
end
class ComputerPlayer < Player
def group_row(row)
markers = row.group_by {|idx| @game.board[idx]}
DEBUG = false # edit this line if necessary
def group_positions_by_markers(line)
markers = line.group_by {|position| @game.board[position]}
markers.default = []
markers
end
def select
def select_position!
opponent_marker = @game.opponent.marker
# look for winning rows
for row in ROWS
markers = group_row(row)
next if markers[nil].length != 1
if markers[self.marker].length == 2
return markers[nil].first
elsif markers[opponent_marker].length == 2
idx = markers[nil].first
end
winning_or_blocking_position = look_for_winning_or_blocking_position(opponent_marker)
return winning_or_blocking_position if winning_or_blocking_position
if corner_trap_defense_needed?
return corner_trap_defense_position(opponent_marker)
end
# look for opponent's winning rows to block
return idx if idx
# could make this smarter by sometimes doing corner trap offense
# need some logic here to get the computer to pick a smarter position
return random_prioritized_position
end
# simply pick a position in order of preference
def look_for_winning_or_blocking_position(opponent_marker)
for line in LINES
markers = group_positions_by_markers(line)
next if markers[nil].length != 1
if markers[self.marker].length == 2
log_debug "winning on line #{line.join}"
return markers[nil].first
elsif markers[opponent_marker].length == 2
log_debug "could block on line #{line.join}"
blocking_position = markers[nil].first
end
end
if blocking_position
log_debug "blocking at #{blocking_position}"
return blocking_position
end
end
def corner_trap_defense_needed?
corner_positions = [1, 3, 7, 9]
opponent_chose_a_corner = corner_positions.any?{|pos| @game.board[pos] != nil}
return @game.turn_num == 2 && opponent_chose_a_corner
end
def corner_trap_defense_position(opponent_marker)
# if you respond in the center or the opposite corner, the opponent can force you to lose
log_debug "defending against corner start by playing adjacent"
# playing in an adjacent corner could also be safe, but would require more logic later on
opponent_position = @game.board.find_index {|marker| marker == opponent_marker}
safe_responses = {1=>[2,4], 3=>[2,6], 7=>[4,8], 9=>[6,8]}
return safe_responses[opponent_position].sample
end
def random_prioritized_position
log_debug "picking random position, favoring center and then corners"
([5] + [1,3,7,9].shuffle + [2,4,6,8].shuffle).find do |pos|
@game.free_positions.include?(pos)
end
end
def log_debug(message)
puts "#{self}: #{message}" if DEBUG
end
def to_s
"Computer#{@game.turn}"
"Computer#{@game.current_player_id}"
end
end
end
@ -125,4 +184,5 @@ include TicTacToe
Game.new(ComputerPlayer, ComputerPlayer).play
puts
Game.new(HumanPlayer,ComputerPlayer).play
players_with_human = [HumanPlayer, ComputerPlayer].shuffle
Game.new(*players_with_human).play

View file

@ -77,7 +77,7 @@ for i = 1 to 8
b2 = val(mid$(b$,2,1))
b3 = val(mid$(b$,3,1))
if box$(b1) = "O" and box$(b2) = "O" and box$(b3) = "O" then
print "You Loose!"
print "You Lose!"
goto [playAgain]
end if
if box$(b1) = "X" and box$(b2) = "X" and box$(b3) = "X" then