:ruby
def evaluate(board)
# Check rows
(0..8).step(3) do |i|
if board[i] == board[i + 1] && board[i] == board[i + 2]
return -10 if board[i] == 'O'
return 10 if board[i] == 'X'
end
end
# Check columns
(0..2).each do |i|
if board[i] == board[i + 3] && board[i] == board[i + 6]
return -10 if board[i] == 'O'
return 10 if board[i] == 'X'
end
end
# Check diagonals
if board[0] == board[4] && board[0] == board[8]
return -10 if board[0] == 'O'
return 10 if board[0] == 'X'
end
if board[2] == board[4] && board[2] == board[6]
return -10 if board[2] == 'O'
return 10 if board[2] == 'X'
end
# No winner yet
0
end
def is_draw(board)
board.none? { |cell| cell == '-' }
end
def minimax(board, depth, is_maximizing_player, memo = {})
# Check for terminal states (win/loss/draw)
score = evaluate(board)
return score - depth if score == 10
return score + depth if score == -10
return 0 if is_draw(board)
# Check if result is memoized
memo_key = "#{board.join(',')}:#{depth}:#{is_maximizing_player}"
return memo[memo_key] if memo.key?(memo_key)
# Recursively evaluate possible moves
best_score = is_maximizing_player ? -Float::INFINITY : Float::INFINITY
player = is_maximizing_player ? 'X' : 'O'
board.each_with_index do |cell, i|
if cell == '-'
board[i] = player
current_score = minimax(board, depth + 1, !is_maximizing_player, memo)
best_score = [best_score, current_score].max if is_maximizing_player
best_score = [best_score, current_score].min unless is_maximizing_player
board[i] = '-'
end
end
# Memoize result and return
memo[memo_key] = best_score
best_score
end
def find_best_move(board)
best_score = -Float::INFINITY
best_move = -1
board.each_with_index do |cell, i|
if cell == '-'
board[i] = 'X'
current_score = minimax(board, 0, false)
if current_score > best_score
best_score = current_score
best_move = i
end
board[i] = '-'
end
end
best_move
end
def jump_link(s, player_move)
board_string = s[:board].join()
board_string[player_move] = 'X'
if !s[:board].all?('-')
board_string[s[:computer_move]] = 'O'
end
"#" + board_string
end
def is_endgame(s)
current = s[:board].dup
current[s[:computer_move]] = 'O'
return evaluate(current) != 0 || is_draw(current)
end
values = ['O', 'X', '-']
all = values.repeated_permutation(9)
all_solutions = all
.filter { |board| (board.count('O') - board.count('X')).abs <= 1 && evaluate(board) <= 0 }
.sort_by { |board| [board.count('X'), board.count('O')] }.reverse!
.map do |board| { :board => board, :computer_move => find_best_move(board) }
end
- all_solutions.each do |s,j|
%div.b{:id => s[:board].join() }
- s[:board].each_with_index do |c,i|
- if s[:computer_move] == i && !s[:board].all?('-')
%svg.o.s
%circle
- elsif c == '-' && !is_endgame(s)
%a.s{ :href => jump_link(s, i) }
%div
- elsif c == '-'
%div.s
- elsif c == 'O'
%svg.o.s
%circle.c
- else
%div.x
View Compiled
body {
background: #1f3a93;
}
.b {
position: absolute;
display: flex;
flex-wrap: wrap;
padding-left: 0;
div, svg {
display: inline-block;
flex: 0 0 33.333333%;
}
circle {
stroke: #f62459;
cx: 50px;
cy: 50px;
r: 30px;
stroke-dasharray: 200px;
stroke-dashoffset: 200px;
stroke-linecap: "round";
stroke-width: 10px;
fill: none;
}
}
.b, :target ~ #--------- {
.s {
display:none;
}
}
:target, #--------- {
width: 300px;
height: 300px;
left: calc(50vw - 150px);
top: calc(50vh - 150px);
background-image: url(https://i.imgur.com/JOnn3v1.gif);
background-repeat: no-repeat;
background-size: 100% auto;
.s {
z-index: 1;
display: inline-block;
}
.x {
z-index: 1;
display: inline-block;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='x'%3E%3Cstyle%3E%0A* %7B box-sizing: border-box;%0A%7D svg %7B height: 100px; width: 100px;%0A%7D svg path %7B stroke-dashoffset: 0; stroke: %2336dbd7;%0A%7D%0A%3C/style%3E%3Cpath class='cross' d='M 20 20 L 80 80' fill='none' stroke-width='10' stroke-linecap='round' stroke-dasharray='100' stroke-dashoffset='0' stoke='%2336dbd7'%3E%3C/path%3E%3Cpath class='cross' d='M 80 20 L 20 80' fill='none' stroke-width='10' stroke-linecap='round' stroke-dasharray='100' stroke-dashoffset='0' stoke='%2336dbd7'%3E%3C/path%3E%3C/svg%3E%0A");
height: 100px;
width: 100px;
}
circle {
animation-fill-mode: forwards;
animation-name: draw;
animation-duration: 1s;
&.c {
animation-play-state: paused;
animation-delay: -1s;
}
}
}
svg {
height: 100px;
width: 100px;
}
.o {
transform: rotateY(180deg) rotate(-35deg);
}
@keyframes draw {
to {
stroke-dashoffset: 0;
}
}
@keyframes draw {
to {
stroke-dashoffset: 0;
}
}
.s {
width: 100px;
height: 100px;
}
View Compiled
/* no script! */
This Pen doesn't use any external JavaScript resources.