diff --git a/2048.toc b/2048.toc
index 7617828..0da2351 100644
--- a/2048.toc
+++ b/2048.toc
@@ -2,7 +2,7 @@
## Title: 2048
## Notes: Play while you wait!
## Notes-frFR: Jouez au 2048 entre deux raids !
-## Version: 0.3.0
+## Version: 1.0.0
## Author: Septh
## DefaultState: disabled
## SavedVariables: DB2048
@@ -20,7 +20,6 @@ libs\AceDBOptions-3.0\AceDBOptions-3.0.xml
libs\AceLocale-3.0\AceLocale-3.0.xml
libs\AceTimer-3.0\AceTimer-3.0.xml
-2048.xml
locales.lua
main.lua
game.lua
diff --git a/2048.xml b/2048.xml
deleted file mode 100644
index ce489ad..0000000
--- a/2048.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
-
diff --git a/Readme.md b/Readme.md
index f461a97..0516db3 100644
--- a/Readme.md
+++ b/Readme.md
@@ -1,24 +1,31 @@
-## One more step towards the release!
-
----
# 2048 for World of Warcraft
-#### Play while you wait to play!####
+#### Play while you wait to play!
2048 is a port of [Gabriele Cirulli's 2048 game](http://gabrielecirulli.github.io/2048/). Based on 1024 by Veewo Studio and conceptually similar to Threes by Asher Vollmer.
The idea came while I was waiting for a LFR group to come up. I was playing 2048 on my tablet and suddently thought "hey, wouldn't it be great if I had a 2048 game inside WoW?". And so there it is.
+
## Instructions
Nothing fancy here, juste type `/2048` to show the game or click on the 2048 icon if you use a LDB display such as ChocolateBar or Bazooka.
-Use the arrow buttons to move the tiles. You may also use your keyboard if the option is checked in the control panel. Be aware though that the keyboard will only work when the mouse cursor is over the game's frame.
+Click the arrow buttons to move the tiles. You may also use your keyboard if the option is checked in the control panel. Be aware though that the keyboard will only work when the mouse cursor is over the game's frame.
+
+Keys: `Left`, `Right`, `Up` and `Down` arrows to move the tiles, `Esc` to close the game.
+
+
+## Help
+Comments are disabled on Curse.com and on Curseforge.com. Should you need any help, please open an issue on [GitHub](https://github.com/Septh/WoW-2048 "GitHub repository"), where the projet lives.
+
+
+## Changelog
+**Version 1.0.0** - Initial release. Settings and scores from previous, beta versions are incompatible and thus reset on first use. Sorry about that.
-**NB:** since 2048 is still in beta stage, it is disabled by default! You must specifically enable it in the AddOn Manager before you can play.
-~~**NB2:** I'am aware that 2048 suffers from some animation glitches since Legion. I'll work on it when I have time.~~
+## Licence
+2048 for WoW is released under the MIT licence.
+Enjoy!
-## TODO:
-* ~~**Better animation**. Blizzard API is really not intended for such things so I even don't know if this is possible at all.~~
-* **Testing**, testing and testing. Thank to anyone who can provide feedback either on [Curse.com](http://mods.curse.com/addons/wow/wow2048) or on [GitHub](https://github.com/Septh/WoW-2048)
+-- Septh
diff --git a/game.lua b/game.lua
index af7a4d1..3cf24eb 100644
--- a/game.lua
+++ b/game.lua
@@ -3,71 +3,76 @@ local addon = LibStub('AceAddon-3.0'):GetAddon('2048')
local game = addon:NewModule('game')
local grid_size = 4 -- actuel gris size
-local min_grid_size = 4 -- minimum grid size
-local max_grid_size = 8 -- maximum grid size
-local default_grid_size = 4 -- grid size if not specified
local start_tiles = 2 -- # of tiles on game startup
------------------------------------------------
--- Continue an old game or start a new one
+-- Initialize the grid
------------------------------------------------
-function game:new_game(state, size)
+function game:OnInitialize()
+
+ self.size = grid_size
+ self.grid = {}
+
+ -- Initialize out of bounds cells to -1
+ for row = 0, self.size + 1 do
+ self.grid[row] = {}
+ for col = 0, self.size + 1 do
+ self.grid[row][col] = {
+ row = row,
+ col = col,
+ val = (row < 1 or row > self.size or col < 1 or col > self.size) and -1 or 0
+ }
+ end
+ end
+end
- self.size = grid_size
- self.grid = wipe(self.grid or {})
+------------------------------------------------
+-- Restore a saved game -- used at addon startup
+------------------------------------------------
+function game:restore_game(state)
- if state then
+ if #state.grid > 0 then
+ -- We have some data to start with
+ self.best = state.best
self.score = state.score
+ self.moves = state.moves
self.over = state.over
self.won = state.won
self.cont = state.cont
- -- Recreate the grid matrix
- for row = 0, self.size + 1 do
- self.grid[row] = {}
- for col = 0, self.size + 1 do
- self.grid[row][col] = {
- row = row,
- col = col
- }
-
- -- Set value to -1 for 'out of bounds' cells
- if row < 1 or row > self.size or col < 1 or col > self.size then
- self.grid[row][col].value = -1
- else
- self.grid[row][col].value = state.grid[row][col]
- end
+ for row = 1, self.size do
+ for col = 1, self.size do
+ self.grid[row][col].val = state.grid[row][col]
end
end
else
- self.score = 0
- self.over = false
- self.won = false
- self.cont = false
-
- -- Recreate the grid matrix
- for row = 0, self.size + 1 do
- self.grid[row] = {}
- for col = 0, self.size + 1 do
- self.grid[row][col] = {
- row = row,
- col = col
- }
-
- -- Set value to -1 for 'out of bounds' cells
- if row < 1 or row > self.size or col < 1 or col > self.size then
- self.grid[row][col].value = -1
- else
- self.grid[row][col].value = 0
- end
- end
- end
+ -- Just start a new game
+ self:new_game()
+ end
+end
- -- Add a few cells to start with if this is a new game
- for i = 1, start_tiles do
- self:add_random_cell()
+------------------------------------------------
+-- Start a brand new game
+------------------------------------------------
+function game:new_game()
+
+ self.best = self.best or 0
+ self.score = 0
+ self.moves = 0
+ self.over = false
+ self.won = false
+ self.cont = false
+
+ for row = 1, self.size do
+ for col = 1, self.size do
+ self.grid[row][col].val = 0
end
end
+
+ -- Add a few cells to start with
+ for i = 1, start_tiles do
+ self:add_random_cell()
+ end
end
------------------------------------------------
@@ -75,110 +80,114 @@ end
------------------------------------------------
function game:save_state(state)
+ -- Copy state
+ state.best = self.best
state.score = self.score
+ state.moves = self.moves
state.over = self.over
state.won = self.won
state.cont = self.cont
- state.size = self.size
- state.grid = {}
+ state.grid = wipe(state.grid or {})
for row = 1, self.size do
state.grid[row] = {}
for col = 1, self.size do
- state.grid[row][col] = self.grid[row][col].value
+ state.grid[row][col] = self.grid[row][col].val
end
end
end
------------------------------------------------
--- Call function on every cell
-function game:for_each_cell(callback)
- for row = 1, self.size do
- for col = 1, self.size do
- callback(row, col, self.grid[row][col])
- end
- end
-end
-
+-- Get/set the cell at (row,col)
------------------------------------------------
--- Return the cell at (row,col)
function game:get_cell(row, col)
return self.grid[row][col]
end
------------------------------------------------
--- Return the value of the cell at (row,col)
-function game:get_cell_value(row, col)
- return self.grid[row][col]['value']
+function game:set_cell_value(row, col, val)
+ self.grid[row][col].val = val
end
-------------------------------------------------
--- Set the value of the cell at (row,col)
-function game:set_cell_value(row, col, value)
- self.grid[row][col].value = value
-end
-
-------------------------------------------------
--- Find all empty cells
-------------------------------------------------
-local _empty = {}
-function game:get_empty_cells()
+-----------------------------------------------
+-- Check if further moves are possible
+-----------------------------------------------
+function game:moves_available()
- wipe(_empty)
for row = 1, self.size do
for col = 1, self.size do
- if self.grid[row][col].value == 0 then
- table.insert(_empty, { row = row, col = col } )
+ -- Only check right and down as actual left and up were checked on previous iteration
+ local cell = self.grid[row][col].val
+ local right = self.grid[row][col + 1].val
+ local below = self.grid[row + 1][col].val
+
+ if right == 0 or right == cell or below == 0 or below == cell then
+ return true
end
end
end
- return _empty
+ return false
end
------------------------------------------------
-- Set a random cell to a random value
------------------------------------------------
+local _empty = {}
function game:add_random_cell()
-- Collect and count empty cells
- local empty = self:get_empty_cells()
+ wipe(_empty)
+ for row = 1, self.size do
+ for col = 1, self.size do
+ if self.grid[row][col].val == 0 then
+ table.insert(_empty, { row = row, col = col } )
+ end
+ end
+ end
- if #empty > 0 then
- local random_cell = math.ceil(math.random() * #empty)
+ if #_empty > 0 then
+ local random_cell = math.ceil(math.random() * #_empty)
local random_value = (math.random() < 0.7) and 2 or 4
- self:set_cell_value(empty[random_cell].row, empty[random_cell].col, random_value)
+ self:set_cell_value(_empty[random_cell].row, _empty[random_cell].col, random_value)
return true
end
return false
end
------------------------------------------------
--- Update the scores
+-- Get/set the score
+------------------------------------------------
function game:add_score(n)
self.score = self.score + n
+ if self.score > self.best then
+ self.best = self.score
+ end
end
------------------------------------------------
--- Return the scores
-function game:get_score()
- return self.score
+function game:add_move()
+ self.moves = self.moves + 1
end
------------------------------------------------
--- Check if the game is terminated
+function game:get_scores()
+ return self.moves, self.score, self.best
+end
+
+------------------------------------------------
+-- Check whether the game is terminated
+------------------------------------------------
function game:is_terminated()
- return self.over or (self.won and not self.cont)
+ return self:is_won() or self:is_over()
end
------------------------------------------------
--- Check if the game is won
function game:is_won()
return self.won and not self.cont
end
------------------------------------------------
--- Check if the game is over
function game:is_over()
return self.over
end
@@ -190,28 +199,7 @@ function game:keep_playing()
end
-----------------------------------------------
--- Check if further moves are possible
------------------------------------------------
-function game:moves_available()
-
- for row = 1, self.size do
- for col = 1, self.size do
- local cell = self:get_cell_value(row, col)
- local right = self:get_cell_value(row, col + 1)
- local below = self:get_cell_value(row + 1, col)
-
- -- Only check right and down as actual left and up were checked on previous iteration
- if right == 0 or right == cell or
- below == 0 or below == cell then
- return true
- end
- end
- end
- return false
-end
-
------------------------------------------------
--- Move tiles on the grid in the specified direction
+-- Move cells on the grid in the specified direction
-----------------------------------------------
local _moves = {}
function game:move_cells(direction)
@@ -220,64 +208,62 @@ function game:move_cells(direction)
local function find_dest(cell, row_delta, col_delta)
local dest = self.grid[cell.row + row_delta][cell.col + col_delta]
- while dest.value == 0 do
+ while dest.val == 0 do
-- Destination is empty, try further
dest = self.grid[dest.row + row_delta][dest.col + col_delta]
end
- if (dest.value == cell.value) and not (cell.merged or dest.merged) then
+ if (dest.val == cell.val) and not (cell.merged or dest.merged) then
-- We can merge with this cell but only if neither cell and dest were already merged
return dest
end
-- We either reached out of bounds or we are blocked by another cell
- -- In any case, return the previous cell
+ -- In any case, return the last valid cell we found
return self.grid[dest.row - row_delta][dest.col - col_delta]
end
-- Move a cell to its destination
local function move_cell(cell, dest)
if dest.row ~= cell.row or dest.col ~= cell.col then
- local merged = dest.value == cell.value
+ local merged = dest.val == cell.val
table.insert(_moves, {
p_row = cell.row,
p_col = cell.col,
- p_val = cell.value,
+ p_val = cell.val,
n_row = dest.row,
n_col = dest.col,
- n_val = dest.value + cell.value,
+ n_val = dest.val + cell.val,
merged = merged
})
- dest.value = dest.value + cell.value -- dest.value is either 0 or same as cell.value
+ dest.val = dest.val + cell.val -- dest.val is either 0 or same as cell.val
dest.merged = merged
- cell.value = 0
+ cell.val = 0
if merged then
- self:add_score(dest.value)
+ self:add_score(dest.val)
-- The mighty 2048 tile?
- self.won = (dest.value == 2048)
+ self.won = self.won or (dest.val == 2048)
end
end
end
- -- Reset the merged status of all cells
- self:for_each_cell(function(row, col, cell)
- cell.merged = false
- end)
-
+ -- Move thes cells
wipe(_moves)
if not self:is_terminated() then
+ self:add_move()
+
-- Traverse the grid in the right direction
local source
if direction == 'UP' then
for row = 2, self.size do -- skip row #1 since it can't move up
for col = 1, self.size do
source = self.grid[row][col]
- if source.value > 0 then
+ if source.val > 0 then
move_cell(source, find_dest(source, -1, 0))
end
end
@@ -286,7 +272,7 @@ function game:move_cells(direction)
for row = self.size-1, 1, -1 do -- skip last row since it can't move down
for col = 1, self.size do
source = self.grid[row][col]
- if source.value > 0 then
+ if source.val > 0 then
move_cell(source, find_dest(source, 1, 0))
end
end
@@ -295,7 +281,7 @@ function game:move_cells(direction)
for row = 1, self.size do
for col = 2, self.size do -- skip col #1 since it can't move left
source = self.grid[row][col]
- if source.value > 0 then
+ if source.val > 0 then
move_cell(source, find_dest(source, 0, -1))
end
end
@@ -304,7 +290,7 @@ function game:move_cells(direction)
for row = 1, self.size do
for col = self.size-1, 1, -1 do -- skip last col since it can't move right
source = self.grid[row][col]
- if source.value > 0 then
+ if source.val > 0 then
move_cell(source, find_dest(source, 0, 1))
end
end
@@ -315,19 +301,25 @@ function game:move_cells(direction)
return _moves
end
-
+-----------------------------------------------
function game:next_turn()
- -- Update the celles that moved
- for _, move in ipairs(_moves) do
- self.grid[move.n_row][move.n_col].value = move.n_val
+ -- Reset all cells states
+ for row = 1, self.size do
+ for col = 1, self.size do
+ local cell = self.grid[row][col]
+ cell.row = row
+ cell.col = col
+ cell.merged = false
+ end
end
- -- Add a new cell
- if #_moves > 0 then
- self:add_random_cell()
-
- -- Game over?
- self.over = not self:moves_available()
+ -- Update the values of those cells that actually moved
+ for _, move in ipairs(_moves) do
+ local cell = self.grid[move.n_row][move.n_col]
+ cell.val = move.n_val
end
+
+ -- Add a new cell, check for game over
+ self.over = not (self:add_random_cell() and self:moves_available())
end
diff --git a/locales.lua b/locales.lua
index fff44c5..a3ae28a 100644
--- a/locales.lua
+++ b/locales.lua
@@ -3,6 +3,14 @@
local L = LibStub('AceLocale-3.0'):NewLocale('2048', 'enUS', true)
if not L then return end
+L['Play while you wait to play!'] = true
+
+L['Old settings reset to defaults - sorry about that.'] = true
+
+L['MOVES'] = true
+L['SCORE'] = true
+L['BEST'] = true
+
L['You won!'] = true
L['Game over!'] = true
L['Restart?'] = true
@@ -19,6 +27,14 @@ L['Join the numbers and get to the |cFFFF00002048|r tile!'] = true
--frFR
L = LibStub('AceLocale-3.0'):NewLocale('2048', 'frFR')
if L then
+ L['Play while you wait to play!'] = 'Jouez en attendant de jouer !'
+
+ L['Old settings reset to defaults - sorry about that.'] = 'Réglages et scores réinitialisés - désolé.'
+
+ L['MOVES'] = 'COUPS'
+ L['SCORE'] = 'SCORE'
+ L['BEST'] = 'TOP'
+
L['You won!'] = 'Gagné !'
L['Game over!'] = 'Fini !'
L['Restart?'] = 'Recommencer ?'
@@ -27,6 +43,7 @@ if L then
L['Keep playing'] = 'Continuer'
L['Yes'] = 'Oui'
L['No'] = 'Non'
+
L['Enable keyboard use'] = 'Utiliser le clavier'
L['Window scale'] = 'Taille de la fenêtre'
L['Join the numbers and get to the |cFFFF00002048|r tile!'] ='Tentez d\'obtenir le nombre |cFFFF00002048|r !'
diff --git a/main.lua b/main.lua
index 7911b34..9a616dd 100644
--- a/main.lua
+++ b/main.lua
@@ -88,20 +88,24 @@ local colors = {
-- Ace3 DB
local db_defaults = {
global = {
- scale = 1.0,
- useKeyboard = true,
- pos = {
- p = 'CENTER',
- x = 0,
- y = 0,
+ version = 1,
+ frame = {
+ scale = 1.0,
+ useKeyboard = true,
+ pos = {
+ p = 'CENTER',
+ x = 0,
+ y = 0,
+ },
},
- best = 0,
- state = {
+ game = {
+ best = 0,
+ moves = 0,
score = 0,
over = false,
won = false,
cont = false,
- grid = {}
+ grid = {} -- Always have a grid, even empty
}
}
}
@@ -112,14 +116,39 @@ local config_table = {
handler = addon,
type = 'group',
args = {
+ version = {
+ order = 1,
+ type = 'description',
+ name = 'Version 1.0.0' -- GetAddOnMetadata('2048', 'Version') won't work :(
+ },
+
+ punchline = {
+ order = 2,
+ type = 'description',
+ name = L['Play while you wait to play!'] .. '\n',
+ fontSize = 'medium'
+ },
+
+ author = {
+ order = 3,
+ type = 'description',
+ name = 'Septh - https://github.com/Septh/WoW-2048'
+ },
+
+ sep = {
+ order = 4,
+ type = 'header',
+ name = ''
+ },
+
useKeyboard = {
order = 10,
type = 'toggle',
name = L['Enable keyboard use'],
width = 'full',
- get = function(info) return addon.db.global.useKeyboard end,
+ get = function(info) return addon.db.global.frame.useKeyboard end,
set = function(info, value)
- addon.db.global.useKeyboard = value
+ addon.db.global.frame.useKeyboard = value
addon.frame:EnableKeyboard(value)
end,
},
@@ -131,9 +160,9 @@ local config_table = {
max = 2.0,
step = 0.05,
width = 'full',
- get = function(info) return addon.db.global.scale end,
+ get = function(info) return addon.db.global.frame.scale end,
set = function(info, value)
- addon.db.global.scale = value
+ addon.db.global.frame.scale = value
addon.frame:SetScale(value)
end,
isPercent = true,
@@ -146,17 +175,20 @@ local config_table = {
------------------------------------------------
function addon:OnInitialize()
+ -- Load or create SavedVariables
+ self.db = LibStub('AceDB-3.0'):New('DB2048', db_defaults, true)
+ if (self.db.global.version or 0) ~= 1 then
+ self:Print(L['Old settings reset to defaults - sorry about that.'])
+ self.db:ResetDB('Default')
+ end
+
+ -- Prepare the fonts
local function make_font(name, style, size)
local f = CreateFont(name)
f:SetFont('Interface\\AddOns\\2048\\fonts\\ClearSans\\ClearSans-'..style..'.ttf', size)
f:SetTextColor(1, 1, 1, 1)
return f
end
-
- -- Load or create SavedVariables
- self.db = LibStub('AceDB-3.0'):New('DB2048', db_defaults, true)
-
- -- Prepare the fonts
self.ClearSans14 = make_font('ClearSans14', 'Regular', 14)
self.ClearSans20 = make_font('ClearSans20', 'Regular', 20)
self.ClearSansBold14 = make_font('ClearSansBold14', 'Bold', 14)
@@ -165,9 +197,9 @@ function addon:OnInitialize()
-- Create the main game frame
self.frame = CreateFrame('Frame', nil, UIParent)
- self.frame:SetPoint(self.db.global.pos.p, self.db.global.pos.x, self.db.global.pos.y)
+ self.frame:SetPoint(self.db.global.frame.pos.p, self.db.global.frame.pos.x, self.db.global.frame.pos.y)
self.frame:SetSize(inner_width + (border_size * 2), inner_height + (border_size * 2))
- self.frame:SetScale(self.db.global.scale)
+ self.frame:SetScale(self.db.global.frame.scale)
self.frame:SetBackdrop(plain_bg)
self.frame:SetBackdropColor(unpack(colors['bg']['frame']))
self.frame:EnableMouse(true)
@@ -196,22 +228,22 @@ function addon:OnInitialize()
frame:SetAlpha(0.5)
if frame:IsMouseOver() then
frame.fadein:Play()
- frame:EnableKeyboard(addon.db.global.useKeyboard)
+ frame:EnableKeyboard(addon.db.global.frame.useKeyboard)
else
frame:EnableKeyboard(false)
end
end)
self.frame:SetScript('OnEnter', function(frame)
+ if frame.skipNextEnter then frame.skipNextEnter = nil; return end
frame.fadeout:Stop()
frame.fadein:Play()
- frame:EnableKeyboard(addon.db.global.useKeyboard)
+ frame:EnableKeyboard(addon.db.global.frame.useKeyboard)
end)
self.frame:SetScript('OnLeave', function(frame)
- if not frame:IsMouseOver() then
- frame.fadein:Stop()
- frame.fadeout:Play()
- frame:EnableKeyboard(false)
- end
+ if frame:IsMouseOver() then frame.skipNextEnter = true; return end
+ frame.fadein:Stop()
+ frame.fadeout:Play()
+ frame:EnableKeyboard(false)
end)
self.frame:SetScript('OnDragStart', function(frame, button)
frame:StartMoving()
@@ -219,9 +251,9 @@ function addon:OnInitialize()
self.frame:SetScript('OnDragStop', function(frame, button)
frame:StopMovingOrSizing()
local p, rf, rp, x, y = frame:GetPoint()
- addon.db.global.pos.p = p
- addon.db.global.pos.x = x
- addon.db.global.pos.y = y
+ addon.db.global.frame.pos.p = p
+ addon.db.global.frame.pos.x = x
+ addon.db.global.frame.pos.y = y
end)
self.frame:SetScript('OnKeyDown', function(frame, key)
addon:handle_key(key)
@@ -237,56 +269,72 @@ function addon:OnInitialize()
self.title:SetJustifyH('LEFT')
-- scores
- self.score = {}
- self.score.bg = self.frame:CreateTexture(nil, 'ARTWORK')
- self.score.bg:SetPoint('TOPRIGHT', self.title)
- self.score.bg:SetSize(tile_size, score_height)
- self.score.bg:SetColorTexture(unpack(colors['bg']['score']))
- self.score.label = self.frame:CreateFontString(nil, 'ARTWORK')
- self.score.label:SetPoint('TOP', self.score.bg, 'TOP', 0, -10)
- self.score.label:SetFontObject(self.ClearSansBold14)
- self.score.label:SetTextColor(unpack(colors['fg']['score']))
- self.score.label:SetText('SCORE')
- self.score.text = self.frame:CreateFontString(nil, 'ARTWORK')
- self.score.text:SetPoint('BOTTOM', self.score.bg, 'BOTTOM', 0, 10)
- self.score.text:SetFontObject(self.ClearSansBold14)
- self.score.text:SetTextColor(unpack(colors['fg']['score']))
- self.score.text:SetText('0')
-
self.best = {}
self.best.bg = self.frame:CreateTexture(nil, 'ARTWORK')
- self.best.bg:SetPoint('TOPRIGHT', self.score.bg, 'TOPLEFT', -gutter_size, 0)
+ self.best.bg:SetPoint('TOPRIGHT', self.title)
self.best.bg:SetSize(tile_size, score_height)
self.best.bg:SetColorTexture(unpack(colors['bg']['score']))
self.best.label = self.frame:CreateFontString(nil, 'ARTWORK')
self.best.label:SetPoint('TOP', self.best.bg, 'TOP', 0, -10)
self.best.label:SetFontObject(self.ClearSansBold14)
self.best.label:SetTextColor(unpack(colors['fg']['score']))
- self.best.label:SetText('BEST')
+ self.best.label:SetText(L['BEST'])
self.best.text = self.frame:CreateFontString(nil, 'ARTWORK')
self.best.text:SetPoint('BOTTOM', self.best.bg, 'BOTTOM', 0, 10)
self.best.text:SetFontObject(self.ClearSansBold14)
self.best.text:SetTextColor(unpack(colors['fg']['score']))
self.best.text:SetText('0')
- -- base line
- self.baseline = self.frame:CreateFontString(nil, 'ARTWORK')
- self.baseline:SetPoint('TOPLEFT', self.title, 'BOTTOMLEFT')
- self.baseline:SetSize(inner_width, intro_height)
- self.baseline:SetFontObject(self.ClearSans14)
- self.baseline:SetTextColor(unpack(colors['fg']['info']))
- self.baseline:SetJustifyH('LEFT')
- self.baseline:SetText(L['Join the numbers and get to the |cFFFF00002048|r tile!'])
+ self.score = {}
+ self.score.bg = self.frame:CreateTexture(nil, 'ARTWORK')
+ self.score.bg:SetPoint('TOPRIGHT', self.best.bg, 'TOPLEFT', -gutter_size / 2, 0)
+ self.score.bg:SetSize(tile_size, score_height)
+ self.score.bg:SetColorTexture(unpack(colors['bg']['score']))
+ self.score.label = self.frame:CreateFontString(nil, 'ARTWORK')
+ self.score.label:SetPoint('TOP', self.score.bg, 'TOP', 0, -10)
+ self.score.label:SetFontObject(self.ClearSansBold14)
+ self.score.label:SetTextColor(unpack(colors['fg']['score']))
+ self.score.label:SetText(L['SCORE'])
+ self.score.text = self.frame:CreateFontString(nil, 'ARTWORK')
+ self.score.text:SetPoint('BOTTOM', self.score.bg, 'BOTTOM', 0, 10)
+ self.score.text:SetFontObject(self.ClearSansBold14)
+ self.score.text:SetTextColor(unpack(colors['fg']['score']))
+ self.score.text:SetText('0')
+
+ self.moves = {}
+ self.moves.bg = self.frame:CreateTexture(nil, 'ARTWORK')
+ self.moves.bg:SetPoint('TOPRIGHT', self.score.bg, 'TOPLEFT', -gutter_size / 2, 0)
+ self.moves.bg:SetSize(tile_size, score_height)
+ self.moves.bg:SetColorTexture(unpack(colors['bg']['score']))
+ self.moves.label = self.frame:CreateFontString(nil, 'ARTWORK')
+ self.moves.label:SetPoint('TOP', self.moves.bg, 'TOP', 0, -10)
+ self.moves.label:SetFontObject(self.ClearSansBold14)
+ self.moves.label:SetTextColor(unpack(colors['fg']['score']))
+ self.moves.label:SetText(L['MOVES'])
+ self.moves.text = self.frame:CreateFontString(nil, 'ARTWORK')
+ self.moves.text:SetPoint('BOTTOM', self.moves.bg, 'BOTTOM', 0, 10)
+ self.moves.text:SetFontObject(self.ClearSansBold14)
+ self.moves.text:SetTextColor(unpack(colors['fg']['score']))
+ self.moves.text:SetText('0')
+
+ -- punch line
+ self.punchline = self.frame:CreateFontString(nil, 'ARTWORK')
+ self.punchline:SetPoint('TOPLEFT', self.title, 'BOTTOMLEFT')
+ self.punchline:SetSize(inner_width, intro_height)
+ self.punchline:SetFontObject(self.ClearSans14)
+ self.punchline:SetTextColor(unpack(colors['fg']['info']))
+ self.punchline:SetJustifyH('LEFT')
+ self.punchline:SetText(L['Join the numbers and get to the |cFFFF00002048|r tile!'])
-- board
self.board = self.frame:CreateTexture(nil, 'BACKGROUND', nil, 2)
- self.board:SetPoint('TOP', self.baseline, 'BOTTOM', 0, -10)
+ self.board:SetPoint('TOP', self.punchline, 'BOTTOM', 0, -10)
self.board:SetSize(board_width, board_height)
self.board:SetColorTexture(unpack(colors['bg']['board']))
local x, y = gutter_size, -gutter_size
for row = 1, grid_size do
for col = 1, grid_size do
- local bg = self.frame:CreateTexture(nil, 'BAKCGROUND', nil, 3)
+ local bg = self.frame:CreateTexture(nil, 'BACKGROUND', nil, 3)
bg:SetPoint('TOPLEFT', self.board, x, y)
bg:SetSize(tile_size, tile_size)
bg:SetColorTexture(unpack(colors['bg']['tiles'][0]))
@@ -315,32 +363,6 @@ function addon:OnInitialize()
t.s:SetAllPoints(t)
t.s:SetText(" ")
- --[[
- local b = CreateFrame('Button', nil, t)
- b:SetPoint('TOPLEFT', t, 0, 0)
- b:SetSize(tile_size, tile_size)
- b:SetFrameLevel(b:GetFrameLevel() + 10)
-
- b.s = b:CreateFontString(nil, 'ARTWORK')
- b.s:SetFontObject(GameFontNormal)
- b.s:SetPoint('TOPLEFT', t)
- b.s:SetAlpha(0.3)
- b.s:SetFormattedText("#%d (%d,%d)", ((row-1) * grid_size) + col, row, col)
-
- b.col = col
- b.row = row
- b:RegisterForClicks('AnyUp')
- b:SetScript('OnClick', function(self, button)
- local cell = addon.game:get_cell(self.row, self.col)
- if button == 'RightButton' then
- cell.value = 0
- else
- cell.value = max(cell.value, 1) * 2
- end
- addon:update_tile(self.row, self.col)
- end)
- --]]
-
-- Each tile must have its own animation groups!
t.trans = t:CreateAnimationGroup()
t.trans.anim = t.trans:CreateAnimation('TRANSLATION')
@@ -409,8 +431,9 @@ function addon:OnInitialize()
self.msgbox.frame:SetAllPoints(self.board)
self.msgbox.frame:SetBackdrop(plain_bg)
self.msgbox.frame:SetBackdropColor(unpack(colors['bg']['msgbox']['msg']))
- self.msgbox.frame:SetFrameStrata('HIGH')
+ self.msgbox.frame:SetFrameLevel(self.frame:GetFrameLevel() + 10)
self.msgbox.frame:Hide()
+
self.msgbox.text = self.msgbox.frame:CreateFontString(nil, 'ARTWORK')
self.msgbox.text:SetAllPoints(self.board)
self.msgbox.text:SetPoint('TOPLEFT', self.board, 'TOPLEFT', 0, -tile_size)
@@ -473,14 +496,24 @@ function addon:OnEnable()
-- Initialize the game
self.game = self:GetModule('game')
- self.game:new_game(self.db.global.state, grid_size)
+ self.game:restore_game(self.db.global.game)
-- Draw the board
self:update()
+
+ -- Saved game was won?
+ if self.game:is_won() then
+ self:show_message_box('MSG_WON')
+
+ -- Or over?
+ elseif self.game:is_over() then
+ self:show_message_box('MSG_LOST')
+ end
end
------------------------------------------------
-- Toggle the frame
+------------------------------------------------
function addon:ToggleGameBoard()
if self.frame:IsShown() then
@@ -492,6 +525,7 @@ end
------------------------------------------------
-- Display a message and wait for an answer
+------------------------------------------------
function addon:show_message_box(msg)
-- Remember which question was asked
@@ -507,7 +541,7 @@ function addon:show_message_box(msg)
end
self.msgbox.frame:Show()
- UIFrameFadeIn(self.msgbox.frame, 0.3, 0, 1)
+ UIFrameFadeIn(self.msgbox.frame, 0.3, 0, 0.9)
end
------------------------------------------------
@@ -518,22 +552,23 @@ function addon:handle_message_button(button)
if self.msgbox.q == 'MSG_WON' then
if button == 1 then
- self.game:new_game(nil, grid_size)
+ self.game:new_game()
+ self:update()
else
self.game:keep_playing()
+ self:prepare_next_turn()
end
elseif self.msgbox.q == 'MSG_LOST' then
- self.game:new_game(nil, grid_size)
+ self.game:new_game()
+ self:update()
elseif self.msgbox.q == 'MSG_RESTART' then
if button == 1 then
- self.game:new_game(nil, grid_size)
+ self.game:new_game()
+ self:update()
end
end
-
- -- Redraw the board
- self:update()
end
------------------------------------------------
@@ -549,13 +584,15 @@ end
------------------------------------------------
function addon:update_scores()
- self.score.text:SetText(self.game:get_score())
- self.best.text:SetText(self.db.global.best)
+ local moves, score, best = self.game:get_scores()
+
+ self.moves.text:SetText(moves)
+ self.score.text:SetText(score)
+ self.best.text:SetText(best)
end
------------------------------------------------
function addon:update_board()
-
for row = 1, grid_size do
for col = 1, grid_size do
self:update_tile(row, col)
@@ -565,10 +602,9 @@ end
------------------------------------------------
function addon:update_tile(row, col)
-
- local tile = self.tiles[row..'x'..col]
local cell = self.game:get_cell(row, col)
- local val = cell.value
+ local val = cell.val
+ local tile = self.tiles[row..'x'..col]
-- Set the value
tile.s:SetText(val > 0 and val or ' ')
@@ -578,7 +614,7 @@ function addon:update_tile(row, col)
tile.s:SetTextColor(unpack(colors['fg']['tiles'][val]))
tile:SetBackdropColor(unpack(colors['bg']['tiles'][val]))
- -- Restore frame level after animation
+ -- Restore the tile's frame level after animation
tile:SetFrameLevel(self.frame:GetFrameLevel() + 1)
end
@@ -591,7 +627,10 @@ function addon:handle_key(key)
if self.msgbox.frame:IsShown() then return end
- if key == 'RESTART' then
+ if key == 'ESCAPE' then
+ self:ToggleGameBoard()
+
+ elseif key == 'RESTART' then
self:show_message_box('MSG_RESTART')
elseif _keys[key] then
@@ -599,82 +638,74 @@ function addon:handle_key(key)
local moves = self.game:move_cells(key)
-- Animate those tiles that were actually moved
- _anims_count = 0
- for xx, move in ipairs(moves) do
-
- local n_tile = self.tiles[move.n_row..'x'..move.n_col]
- local p_tile = self.tiles[move.p_row..'x'..move.p_col]
-
- -- Transition the tiles from their previous position to the current
- p_tile:SetFrameLevel(p_tile:GetFrameLevel() + 2) -- Make sure moving tiles are above the others
- p_tile.trans.anim:SetDuration(0.1)
- p_tile.trans.anim:SetOffset((move.n_col - move.p_col) * (tile_size + gutter_size), (move.n_row - move.p_row) * -(tile_size + gutter_size))
- p_tile.trans.anim:SetStartDelay(xx * 0.01) --- nicer
- p_tile.trans.anim:SetScript('OnFinished', function()
- _anims_count = _anims_count - 1
-
- -- Update the old position
- addon.game:set_cell_value(move.p_row, move.p_col, 0)
- addon:update_tile(move.p_row, move.p_col)
-
- -- then the new one
- addon.game:set_cell_value(move.n_row, move.n_col, move.n_val)
- addon:update_tile(move.n_row, move.n_col)
-
- -- Pulse?
- if move.merged then
- n_tile.scale.anim:SetDuration(0.1)
- n_tile.scale.anim:SetScale(1.2, 1.2)
- n_tile.scale.anim:SetOrigin('CENTER', 0, 0)
- n_tile.scale.anim:SetScript('OnFinished', function()
- _anims_count = _anims_count - 1
- end)
-
- _anims_count = _anims_count + 1
- n_tile.scale:Play()
- end
- end)
- _anims_count = _anims_count + 1
- p_tile.trans:Play()
- end
-
- -- Wait for all animations to end
- self:prepare_next_turn()
+ if #moves > 0 then
+ _anims_count = 0
+ for xx, move in ipairs(moves) do
+
+ local p_tile = self.tiles[move.p_row..'x'..move.p_col]
+ local n_tile = self.tiles[move.n_row..'x'..move.n_col]
+
+ -- Transition the tiles from their previous position to the new
+ p_tile:SetFrameLevel(p_tile:GetFrameLevel() + 2) -- Make sure moving tiles are above the others
+ p_tile.trans.anim:SetDuration(0.1)
+ p_tile.trans.anim:SetOffset((move.n_col - move.p_col) * (tile_size + gutter_size), (move.n_row - move.p_row) * (tile_size + gutter_size) * -1)
+ p_tile.trans.anim:SetStartDelay(xx * 0.01) --- nicer
+ p_tile.trans.anim:SetScript('OnFinished', function()
+ _anims_count = _anims_count - 1
+
+ -- Update the old position
+ addon.game:set_cell_value(move.p_row, move.p_col, 0)
+ addon:update_tile(move.p_row, move.p_col)
+
+ -- then the new one
+ addon.game:set_cell_value(move.n_row, move.n_col, move.n_val)
+ addon:update_tile(move.n_row, move.n_col)
+
+ -- Pulse?
+ if move.merged then
+ n_tile.scale.anim:SetDuration(0.1)
+ n_tile.scale.anim:SetScale(1.2, 1.2)
+ n_tile.scale.anim:SetOrigin('CENTER', 0, 0)
+ n_tile.scale.anim:SetScript('OnFinished', function()
+ _anims_count = _anims_count - 1
+ end)
+
+ _anims_count = _anims_count + 1
+ n_tile.scale:Play()
+ end
+ end)
+ _anims_count = _anims_count + 1
+ p_tile.trans:Play()
+ end
- elseif key == 'ESCAPE' then
- self:ToggleGameBoard()
+ -- Redraw the game and wait for user input
+ self:prepare_next_turn()
+ end
end
-
end
------------------------------------------------
-- Prepare for next turn
+------------------------------------------------
function addon:prepare_next_turn()
+ -- Wait for all anims to finish
if _anims_count > 0 then
self:ScheduleTimer('prepare_next_turn', 0.1)
return
end
- -- Get the score
- local score = self.game:get_score()
- if score > self.db.global.best then
- self.db.global.best = score
- end
-
- -- Redraw the board
+ -- Make everything up-to-date, save the game's current state and redraw the board
self.game:next_turn()
+ self.game:save_state(self.db.global.game)
self:update()
- -- Game over?
- if self.game:is_over() then
- self:show_message_box('MSG_LOST')
-
-- Game won?
- elseif self.game:is_won() then
+ if self.game:is_won() then
self:show_message_box('MSG_WON')
- end
- -- Save current state
- self.game:save_state(self.db.global.state)
+ -- Game over?
+ elseif self.game:is_over() then
+ self:show_message_box('MSG_LOST')
+ end
end