# Game 2048

--By Rocket on Wheelz  18349047

-- init APIs, I guess - Khởi tạo API
-- p/s: WTH is API? i have no idea
local Block  = class.Block.new()
--local Area   = class.Area.new()
local Chat   = class.Chat.new()
local Game   = class.Game.new()
local Player = class.Player.new()
local Actor  = class.Actor.new()
local eventHandler = {}
local game2048 = {}
Chat:sendSystemMsg([=[I am fine, thank you. And you?]=], 0)
 
-- define constants - định nghĩa các hằng số
local locations = {
  boardCorner = {x = 35, y = 14, z = 26},
  materials   = {x = 37, y = 7, z = 7},
  xSpot       = {x = 35, y = 10, z = 17},
  undoSpot    = {x = 35, y = 8, z = 17},
  notiSound   = {x = 35, y = 17, z = 26}
}
local items = {
  upButton    = 2003,
  downButton  = 2004,
  leftButton  = 2002,
  rightButton = 2001,
  startButton = 4097
}
local blocks = {
  x = 2009,
  undo = {[0] = 2008, [1] = 2007},
  lithium = 415
}
-- end constant def
 
-- Game data - Dữ liệu game
local game_board = {
  cur = {
    {0, 0, 0, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0}
  },
  undo = {
    {0, 0, 0, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0}
  },
  temp = {
    {0, 0, 0, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0}
  }
}
local data2048 = {
  isGameRunning    = 0,
  isMoveSuccessful = 0,
  undoLeft         = 0,
  score            = 0,
  scoreUndo        = 0,
  currentPlayerID  = 0,
  spamUndoCount    = 0
}
-- end data
 
-- miscellaneous functions - các lệnh bổ sung
local function boardCopy(board1,board2)
  for i = 1,4 do
    for j = 1,4 do
      board1[i][j] = board2[i][j]
    end
  end
end
 
local function locationOffset(loca, dx, dy, dz)
    return {loca.x + dx, loca.y + dy, loca.z + dz}
end
 
local function occupiedCellCount(board)
  local count = 0
    for i, v in ipairs(board) do
      for j, u in ipairs(v) do
        if u>0 then
        count = count + 1
      end
    end
  end
  return count
end
 
local function cellSpawn()
  local pos = 0
  math.randomseed(os.time())
  pos = math.random(1, 16 - occupiedCellCount(game_board.cur))
  --Chat:sendSystemMsg(pos, 0)
  --Game:msgBox(pos)
  for i, v in ipairs(game_board.cur) do
    if pos == 0 then break end
    for j, u in ipairs(v) do
      if u == 0 then
        pos = pos - 1
        if pos == 0 then game_board.cur[i][j] = math.floor(math.random(1,5)/5 + 1) end
      end
    end
  end
end
 
local function showMeTheBoard()
  --cellSpawn()
  local str = ""
    for i, v in ipairs(game_board.temp) do
    for j, u in ipairs(v) do
      str = str.." "..u
    end
    str = str.."\n"
  end
  Game:msgBox(str)
end
 
-- game processor - bộ điều hành game
game2048 = {
 
  initGame = function(playerId)
    data2048.isGameRunning = 1
    data2048.score = 0
    data2048.undoLeft = 0
    data2048.undoSpamCount = 0
    -- give player control buttons
    Player:gainItems(playerId, items.upButton, 1, 1)
    Player:gainItems(playerId, items.downButton, 1, 1)
    Player:gainItems(playerId, items.leftButton, 1, 1)
    Player:gainItems(playerId, items.rightButton, 1, 1)
    -- display HUD
    game2048.displayScore(data2048.score)
    local temp = locations.xSpot
    Block:placeBlock(blocks.x, temp.x, temp.y, temp.z, 4)
    local t, v = locations.materials, locations.boardCorner
    for i = 0, 7 do
      for j = 0, 7 do
        Block:placeBlock(0, v.x, v.y - j, v.z - i, 1)
      end
    end
    for i = 0, 4 do
      for j = 0,1 do
        local err, b = Block:getBlockID(t.x, t.y + j, t.z - i)
        Block:placeBlock(b, v.x, v.y + j - 1, v.z - i - 9, 4)
      end
    end
    cellSpawn()
    game2048.updateBoard()
  end,
 
  displayScore = function(score)
    --Chat:sendSystemMsg('disp',0)
    local s, d = locations.materials, locations.xSpot
    local n = 0 -- number of digits
    if score > 0 then n = math.floor(math.log10(score)) end
    for i = 0,n do
      local v = math.floor((score % (10 ^ (i + 1))) / 10 ^ i)
      --Chat:sendSystemMsg(v,0)
      for j = 0, 1 do
        local err, b = Block:getBlockID(s.x + 1, s.y + j, s.z - v)
        Block:placeBlock(b, d.x, d.y + j + 1, d.z + i - n, 4)
      end
    end
  end,
 
  updateBoard = function()
    local t = locations.undoSpot
    Block:placeBlock(blocks.undo[data2048.undoLeft], t.x, t.y, t.z, 4)
    local d, s = locations.boardCorner, locations.materials
    for i = 1,4 do
      for j = 1,4 do
        local value = game_board.cur[i][j]
        for k = 0,1 do
          for l = 0,1 do
            local err, b = Block:getBlockID(s.x + 4 - l, s.y + 1 - k, s.z - value)
            Block:placeBlock(b, d.x, d.y - k - 2*i + 2, d.z - l -2*j + 2, 4)
          end
        end
       
      end
    end
  end,
 
  playNotiSound = function()
    local loca = locations.notiSound
    Block:placeBlock(blocks.lithium, loca.x, loca.y, loca.z, 1)
    Block:placeBlock(0, loca.x, loca.y, loca.z, 1)
  end,
 
  moveUp = function()
    if data2048.isGameRunning == 0 then return end
    local tempScore = data2048.score
    local b = game_board
    boardCopy(b.temp, b.cur)
    data2048.undoLeft = 1
    for j = 1,4 do
      local fuseLeft = 1
      for i = 1,3 do
        for k = i,1,-1 do
          if b.cur[k+1][j] == 0 then break end
          if b.cur[k][j] == 0 then
            b.cur[k][j] = b.cur[k+1][j]
            b.cur[k+1][j] = 0
          elseif b.cur[k][j] == b.cur[k+1][j] then
            if fuseLeft == 1 then
              b.cur[k][j] = b.cur[k][j]+1
              data2048.score = data2048.score + 2 ^ b.cur[k][j]
              b.cur[k+1][j] = 0
              fuseLeft = 0
              break
            else fuseLeft = 1
            end
          else
            fuseLeft = 1
            break
          end
        end
      end
    end
    if game2048.checkMoveSuccessful() ~= 0 then
      cellSpawn()
      game2048.updateBoard()
      data2048.undoScore = tempScore
      boardCopy(b.undo, b.temp)
      game2048.displayScore(data2048.score)
    else game2048.playNotiSound()
    end
  end,
 
  moveDown = function()
    if data2048.isGameRunning == 0 then return end
    local tempScore = data2048.score
    local b = game_board
    boardCopy(b.temp, b.cur)
    data2048.undoLeft = 1
    for j = 1,4 do
      local fuseLeft = 1
      for i = 4,2,-1 do
        for k = i,4 do
          if b.cur[k-1][j] == 0 then break end
          if b.cur[k][j] == 0 then
            b.cur[k][j] = b.cur[k-1][j]
            b.cur[k-1][j] = 0
          elseif b.cur[k][j] == b.cur[k-1][j] then
            if fuseLeft == 1 then
              b.cur[k][j] = b.cur[k][j]+1
              data2048.score = data2048.score + 2 ^ b.cur[k][j]
              b.cur[k-1][j] = 0
              fuseLeft = 0
              break
            else fuseLeft = 1
            end
          else
            fuseLeft = 1
            break
          end
        end
      end
    end
    if game2048.checkMoveSuccessful() ~= 0 then
      cellSpawn()
      game2048.updateBoard()
      data2048.undoScore = tempScore
      boardCopy(b.undo, b.temp)
      game2048.displayScore(data2048.score)
    else game2048.playNotiSound()
    end
  end,
 
  moveLeft = function()
    if data2048.isGameRunning == 0 then return end
    local tempScore = data2048.score
    local b = game_board
    boardCopy(b.temp, b.cur)
    data2048.undoLeft = 1
    for i = 1,4 do
      local fuseLeft = 1
      for j = 1,3 do
        for k = j,1,-1 do
          if b.cur[i][k+1] == 0 then break end
          if b.cur[i][k] == 0 then
            b.cur[i][k] = b.cur[i][k+1]
            b.cur[i][k+1] = 0
          elseif b.cur[i][k] == b.cur[i][k+1] then
            if fuseLeft == 1 then
              b.cur[i][k] = b.cur[i][k]+1
              data2048.score = data2048.score + 2 ^ b.cur[i][k]
              b.cur[i][k+1] = 0
              fuseLeft = 0
              break
            else fuseLeft = 1
            end
          else
            fuseLeft = 1
            break
          end
        end
      end
    end
    if game2048.checkMoveSuccessful() ~= 0 then
      cellSpawn()
      game2048.updateBoard()
      data2048.undoScore = tempScore
      boardCopy(b.undo, b.temp)
      game2048.displayScore(data2048.score)
    else game2048.playNotiSound()
    end
  end,
 
  moveRight = function()
    if data2048.isGameRunning == 0 then return end
    local tempScore = data2048.score
    local b = game_board
    boardCopy(b.temp, b.cur)
    data2048.undoLeft = 1
    for i = 1,4 do
      local fuseLeft = 1
      for j = 4,2,-1 do
        for k = j,4 do
          if b.cur[i][k-1] == 0 then break end
          if b.cur[i][k] == 0 then
            b.cur[i][k] = b.cur[i][k-1]
            b.cur[i][k-1] = 0
          elseif b.cur[i][k] == b.cur[i][k-1] then
            if fuseLeft == 1 then
              b.cur[i][k] = b.cur[i][k]+1
              data2048.score = data2048.score + 2 ^ b.cur[i][k]
              b.cur[i][k-1] = 0
              fuseLeft = 0
              break
            else fuseLeft = 1
            end
          else
            fuseLeft = 1
            break
          end
        end
      end
    end
    if game2048.checkMoveSuccessful() ~= 0 then
      cellSpawn()
      game2048.updateBoard()
      data2048.undoScore = tempScore
      boardCopy(b.undo, b.temp)
      game2048.displayScore(data2048.score)
    else game2048.playNotiSound()
    end
  end,
 
  checkMoveSuccessful = function()
    local answer = 0
    local b = game_board
    for i = 1,4 do
      for j = 1,4 do
        if b.cur[i][j] ~= b.temp[i][j] then
          data2048.undoSpamCount = 0
          answer = 1
          break
        end
      end
    end
    return answer
  end,
 
  cancelGame = function()
    if data2048.isGameRunning == 0 then return end
    data2048.isGameRunning = 0
    for i, v in pairs(game_board) do
      for j = 1,4 do
        for k = 1,4 do
          v[j][k] = 0
        end
      end
    end
    for i, v in pairs(items) do
      if i ~= 'startButton' then
        Player:removeBackpackItem(data2048.currentPlayerID, v, 1)
      end
    end
    local t = locations.boardCorner
    for i = 0, 7 do
      for j = 0, 14 do
        Block:placeBlock(0, t.x, t.y - i, t.z - j, 1)
      end
    end
    local v = locations.materials
    for i = 0, 3 do
      for j = 0, 1 do
        local err, b = Block:getBlockID(v.x - 1, v.y + j, v.z - i)
        Block:placeBlock(b, t.x, t.y + j - 4, t.z - i - 2, 4)
      end
    end
  end,
 
  undo = function()
    if data2048.undoLeft == 0 then return end
    data2048.score = data2048.scoreUndo
    local b = game_board
    game2048.displayScore(data2048.score)
    boardCopy(b.cur, b.undo)
    data2048.undoLeft = 0
    game2048.updateBoard()
  end
}
 
-- yes, it handles triggered events as its name suggests
-- bộ giao tiếp với các sự kiện được kích hoạt
eventHandler = {
 
  pressedButtonDetect = function(params)
    local id = params.itemid
    if id==items.startButton then
      data2048.currentPlayerID = params.eventobjid
      if data2048.isGameRunning==0 then game2048.initGame(params.eventobjid)
      else Chat:sendSystemMsg('Hey, the game is not finished! \nChơi hết ván trước đi đã!', 0)
      end
    elseif id==items.upButton then
      game2048.moveUp()
    elseif id==items.downButton then
      game2048.moveDown()
    elseif id==items.leftButton then
      game2048.moveLeft()
    elseif id==items.rightButton then
      game2048.moveRight()
    end
  end,
 
  pressedBlockDetect = function(params)
    local id = params.blockid
    local d = data2048
    if id == blocks.x then
      game2048.cancelGame()
    elseif id == blocks.undo[0] then
      game2048.playNotiSound()
      d.undoSpamCount = d.undoSpamCount + 1
      if d.undoSpamCount == 3 then
        Chat:sendSystemMsg([[Hey, don't spam hitting me! Này, đừng có ấn vào tôi nhiều thế chứ !]], 0)
     end
     if d.undoSpamCount == 6 then
       d.undoSpamCount = 0
       Chat:sendSystemMsg([[Hey, you want a punch, don't you?]], 0)
        Chat:sendSystemMsg([[Thích đánh nhau phải không? :((]], 0)
      end
    elseif id == blocks.undo[1] then
      d.undoSpamCount = 0
      game2048.undo()
    end
  end
}
 
--ScriptSupportEvent:registerEvent('Player.ClickBlock', showMeTheBoard)
ScriptSupportEvent:registerEvent('Player.SelectShortcut', eventHandler.pressedButtonDetect)
ScriptSupportEvent:registerEvent('Player.ClickBlock', eventHandler.pressedBlockDetect)
Last Update: 11/1/2019, 6:30:06 PM