Skip to content

Instantly share code, notes, and snippets.

@pablomayobre
Last active October 18, 2018 16:27
Show Gist options
  • Save pablomayobre/c7b7ad81eb02b3f79a349cb3ad2c74a4 to your computer and use it in GitHub Desktop.
Save pablomayobre/c7b7ad81eb02b3f79a349cb3ad2c74a4 to your computer and use it in GitHub Desktop.
1K - Tic Tac Toe in LÖVE (with AI)
local bit = require 'bit' --Bitops
local max_int = bit.bnot(0) --Max integer
--Players are represented as 9bit numbers
local players = {0,0}
--Winner (1 - player 1, 2 - player 2, 3 - tie, nil - still playing)
local winner = nil
local tile_size = 200 --Tile width and height
love.window.setMode(tile_size * 3, tile_size * 3)
local function is_empty_tile (tile)
return bit.bor(bit.bnot(tile), players[1], players[2]) ~= max_int
end
--Win conditions
local win_conditions = {7, 56, 73, 84, 146, 273, 292, 448}
for index, win in ipairs(win_conditions) do
win_conditions[index] = bit.bnot(win)
end
local function is_win_move (player_index, tile)
for _, win in ipairs(win_conditions) do
if bit.bor(win, players[player_index], tile) == max_int then
return true
end
end
return false
end
local function has_won (player_index)
if not winner and is_win_move(player_index, 0) then
winner = player_index
end
end
local full_board = 2^9 - 1
local function is_board_full ()
return bit.bor(players[1], players[2]) == full_board
end
--AI score for tiles, the order should be corners, center and then other places
local base_tile_score = {9,4,8,3,5,2,7,1,6}
local function ai_turn()
local tile_score, tile = 0, 0
for i=0, 8 do
local current_tile = 2^i
if is_empty_tile(current_tile) then
local current_score = base_tile_score[i+1]
--Prioritize win movement
current_score = current_score + (is_win_move(2, current_tile) and 11 or 0)
--Prioritize a move that stops the player from winning
current_score = current_score + (is_win_move(1, current_tile) and 10 or 0)
if current_score > tile_score then
tile_score, tile = current_score, current_tile
end
end
end
players[2] = bit.bor(tile, players[2])
has_won(2) --Check if AI won
end
local function get_tile_bit (x, y)
y = math.floor(y/tile_size)
x = math.floor(x/tile_size)
local tile = y * 3 + x
return 2 ^ tile --bit.lshift(1, tile)
end
function love.mousepressed (x, y)
--Someone won so after a click we reset the game
if winner then
players[1], players[2] = 0,0
winner = nil
return
end
--Turn mouse position into a bit representing the tile
local tile = get_tile_bit(x, y)
if is_empty_tile(tile) then
players[1] = bit.bor(players[1], tile) --Mark the tile for this player
has_won(1) --Check if player won
if not winner then
ai_turn() --AI plays
end
--Tie is when no one has won and board is full
winner = (not winner and is_board_full()) and 3 or winner
end
end
local timer, flash = 0, false
function love.update (dt)
timer = timer + dt
--We flash every .3 seconds
while timer > .3 do
flash = not flash
timer = timer - .3
end
end
local colors = {{255, 0, 0}, {0, 0, 255}, {255, 255, 255}}
local function get_color (player_index)
if not flash and (winner == player_index or winner == 3) then
return colors[3]
else
return colors[player_index]
end
end
local function draw_cross (cross_size)
love.graphics.line(-cross_size, -cross_size, cross_size, cross_size)
love.graphics.line(-cross_size, cross_size, cross_size, -cross_size)
end
function love.draw ()
local tile_center = tile_size / 2
local marks_size = tile_size * .4
for i=0, 8 do
love.graphics.setColor(colors[3]) --White
local x, y = (i%3) * tile_size, math.floor(i/3) * tile_size
local mask = bit.bnot(2^i)
--Draw board
love.graphics.rectangle('line', x, y, tile_size, tile_size) --9 rectangles
love.graphics.push('all')
--Move to the center of the tile
love.graphics.translate(x + tile_center,y + tile_center)
--Check if tile is marked by Player 1
if bit.bor(mask, players[1]) == max_int then
love.graphics.setColor(get_color(1))
draw_cross(marks_size)--Draw Player 1
--Check if tile is marked by Player 2 (AI)
elseif bit.bor(mask, players[2]) == max_int then
love.graphics.setColor(get_color(2))
love.graphics.circle('line', 0, 0, marks_size) --Draw Player 2
end
love.graphics.pop()
end
end
--Note that when I performed the minification
--I changed a lots of parts of the code
--In order to get into the 1024 characters
--Some of those reductions are not performed in this code
--This is only a guide to understand how the game works
Y = require 'bit' --Bitops
a,b,c = Y.bnot,Y.bor,Y.band --Operations
m = a(0) --Max integer
F = math.floor
w = {7, 56, 73, 84, 146, 273, 292, 448} --Win conditions
C = {255,255,255} --Colors
A = {9,4,8,3,5,2,7,1,6} --Score for tiles
p = {0,0} --Players
X = nil --Winner (1 - player 1, 2 - player 2, 3 - tie, false - still playing)
--Love
L = love
G = L.graphics
--Width and Height
D = 200
B, E, H = D*3, D/2, D*.4
L.window.setMode(B,B)
--Flashing
v = 0
M = 1
L.mousepressed = function(x, y)
if X then --Someone won so after a click we reset the game
p = {0,0}
X = nil
t = 1
return
end
T = 2^(F(y/D)*3 + F(x/D)) --Tile
if b(a(T), p[1], p[2]) ~= m then --Empty tile
p[1] = b(p[1], T) --Mark player
Z(1) --Check if player won
if not X then
O, P = 0, 0
for i=0, 8 do
Q, R = 2^i, 0
if b(a(Q), p[1], p[2]) ~= m then
R = A[i+1] --The order should be corners, center and then other places
for j=1, 8 do
g = a(w[j])
--Prioritize win movement, or a movement that prevent the player from winning
R = R + (b(g, p[1], Q)==m and 10 or 0) + (b(g, p[2], Q)==m and 11 or 0)
end
if R > O then
O, P = R, Q
end
end
end
p[2]=b(P,p[2])
Z(2) --Check if AI won
end
--Tie is when no one has one and board is full
X = (not X and (b(p[1], p[2]) + 1 == 2^9)) and 3 or X
end
end
Z = function (N)
for i=1, 8 do
f = a(w[i]) --Win condition
--If the win condition is matched then player wins
X = (not X and b(f, p[N])==m) and N or X
end
end
L.update = function (dt)
v = v + dt --We just make M flash every .3 seconds
if v > .3 then
M = not M
v = 0
end
end
L.draw = function ()
for i=0, 8 do
G.setColor(255,255,255) --White
x, y, n = (i%3)*D, F(i/3)*D, a(2^i)
--Draw board
G.rectangle('line', x, y, D, D) --9 rectangles
G.push('all')
G.translate(x+E,y+E) --The center of the tile
if b(n, p[1]) == m then
--M is flash, so if player 1 wins or there is a tie we flash the pieces
G.setColor((not M and(X==1 or X==3))and C or{255,0,0})
--Draw player 1
G.line(-H, -H, H, H) --Cross
G.line(-H, H, H, -H)
elseif b(n, p[2]) == m then
--M is flash, so if player 2 wins or there is a tie we flash the pieces
G.setColor((not M and(X==2 or X==3))and C or{0,0,255})
--Draw player 2
G.circle('line', 0, 0, H) --Circle
end
G.pop()
end
end
Y,F,w,C,A,p,k,L,D,v,M=require'bit',math.floor,{7,56,73,84,146,273,292,448},{255,255,255},{9,4,8,3,5,2,7,1,6},0,0,love,200,0,1;q,m,b,G,B,Z,L.mousepressed,L.update,L.draw='line',Y.bnot(0),Y.bor,L.graphics,600,function(N)f=N==1 and p or k;for i=1,8 do X=not X and m==b(m-w[i],f)and N or X end end,function(x,y)if X then p,k,X=0,0;return end;T=2^(F(y/D)*3+F(x/D))if m~=b(m-T,p,k)then p=b(p,T)Z(1)if not X then O,P=0,0;for i=0,8 do Q=2^i;if m~=b(m-Q,p,k)then R=A[i+1]for j=1,8 do S=m-w[j]R=R+(m==b(S,p,Q)and 10 or 0)+(m==b(S,k,Q)and 11 or 0)end;if R>O then O,P=R,Q end end end;k=b(P,k)Z(2)end;X=not X and 2^9-1==b(p,k)and 3 or X end end,function(t)v=v+t;if v>.3 then v,M=0,not M end end,function()for i=0,8 do n,x,y=m-2^i,D*(i%3),D*F(i/3)o(C)G.rectangle(q,x,y,D,D)if m==b(n,p)then o((M and(X==1 or X==3))and C or{255,0,0})J,H,r,s=20+x,180+x,20+y,180+y;e(J,r,H,s)e(J,s,H,r)elseif m==b(n,k)then o((not M and(X==2 or X==3))and C or{0,0,255})G.circle(q,x+100,y+100,90)end end end;L.window.setMode(B,B)e,o=G.line,G.setColor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment