From b61ebdac93e6206fc41c05e17d07aa0a6c9618a5 Mon Sep 17 00:00:00 2001 From: Septh Date: Tue, 27 Sep 2016 02:28:29 +0200 Subject: [PATCH] Release 1.0.0 --- 2048.toc | 3 +- 2048.xml | 44 ------- Readme.md | 27 ++-- game.lua | 276 ++++++++++++++++++++--------------------- locales.lua | 17 +++ main.lua | 351 ++++++++++++++++++++++++++++------------------------ 6 files changed, 360 insertions(+), 358 deletions(-) delete mode 100644 2048.xml 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