local Object = require 'libs.classic' local Piece = require 'game.piece' local Rotation = Object:extend() Rotation.spawn_positions = { I = { x=3, y= -1 }, J = { x=3, y= -1 }, L = { x=3, y= -1 }, O = { x=3, y= -1 }, S = { x=3, y= -1 }, T = { x=3, y= -1 }, Z = { x=3, y= -1 }, } Rotation.colourscheme = { I = "R", L = "O", J = "B", S = "M", Z = "G", O = "Y", T = "C", } Rotation.block_offsets = { I={ { {x=0, y=1}, {x=1, y=1}, {x=2, y=1}, {x=3, y=1} }, { {x=2, y=0}, {x=2, y=1}, {x=2, y=2}, {x=2, y=3} }, { {x=0, y=1}, {x=1, y=1}, {x=2, y=1}, {x=3, y=1} }, { {x=2, y=0}, {x=2, y=1}, {x=2, y=2}, {x=2, y=3} }, }, J={ { {x=0, y=1}, {x=1, y=1}, {x=2, y=1}, {x=2, y=2} }, { {x=1, y=0}, {x=1, y=1}, {x=1, y=2}, {x=0, y=2} }, { {x=0, y=1}, {x=0, y=2}, {x=1, y=2}, {x=2, y=2} }, { {x=1, y=0}, {x=2, y=0}, {x=1, y=1}, {x=1, y=2} }, }, L={ { {x=0, y=1}, {x=0, y=2}, {x=1, y=1}, {x=2, y=1} }, { {x=0, y=0}, {x=1, y=0}, {x=1, y=1}, {x=1, y=2} }, { {x=0, y=2}, {x=1, y=2}, {x=2, y=1}, {x=2, y=2} }, { {x=1, y=0}, {x=1, y=1}, {x=1, y=2}, {x=2, y=2} }, }, O={ { {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=2, y=2} }, { {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=2, y=2} }, { {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=2, y=2} }, { {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=2, y=2} }, }, S={ { {x=1, y=1}, {x=2, y=1}, {x=0, y=2}, {x=1, y=2} }, { {x=0, y=0}, {x=0, y=1}, {x=1, y=1}, {x=1, y=2} }, { {x=1, y=1}, {x=2, y=1}, {x=0, y=2}, {x=1, y=2} }, { {x=0, y=0}, {x=0, y=1}, {x=1, y=1}, {x=1, y=2} }, }, T={ { {x=0, y=1}, {x=1, y=1}, {x=1, y=2}, {x=2, y=1} }, { {x=0, y=1}, {x=1, y=0}, {x=1, y=1}, {x=1, y=2} }, { {x=0, y=2}, {x=1, y=1}, {x=1, y=2}, {x=2, y=2} }, { {x=1, y=0}, {x=1, y=1}, {x=2, y=1}, {x=1, y=2} }, }, Z={ { {x=0, y=1}, {x=1, y=1}, {x=1, y=2}, {x=2, y=2} }, { {x=2, y=0}, {x=1, y=1}, {x=2, y=1}, {x=1, y=2} }, { {x=0, y=1}, {x=1, y=1}, {x=1, y=2}, {x=2, y=2} }, { {x=2, y=0}, {x=1, y=1}, {x=2, y=1}, {x=1, y=2} }, } } Rotation.pieces = 7 -- Component functions. function Rotation:new(game_mode) self.game = require 'game.gamemode' end function Rotation:rotatePiece(inputs, piece, grid, prev_inputs, initial, lastdir) local new_inputs = {} for input, value in pairs(inputs) do if value and not prev_inputs[input] then new_inputs[input] = true end end local was_drop_blocked = piece:isDropBlocked(grid) if self:canPieceRotate(piece, grid) then -- if not self.held_rotate then -- self:attemptRotate(inputs, piece, grid, initial) -- self.held_rotate = true -- else self:attemptRotate(new_inputs, piece, grid, initial, lastdir) -- end end if not initial and not was_drop_blocked and piece:isDropBlocked(grid) then PlaySE("bottom") end -- prev_inputs becomes the previous inputs for input, value in pairs(inputs) do prev_inputs[input] = inputs[input] end end function Rotation:attemptRotate(new_inputs, piece, grid, initial, lastdir) local rot_dir = 0 if (new_inputs["rotate_left"] or new_inputs["rotate_left2"]) then rot_dir = 3 if lastdir == 0 then lastdir = -1 end elseif (new_inputs["rotate_right"] or new_inputs["rotate_right2"]) then rot_dir = 1 if lastdir == 0 then lastdir = 1 end end if rot_dir == 0 then return end local new_piece = piece:withRelativeRotation(rot_dir) self:attemptWallkicks(piece, new_piece, rot_dir, grid, lastdir) end function Rotation:attemptWallkicks(piece, new_piece, rot_dir, grid, lastdir) -- wallkick routine designed to maximize flexibility while minimizing teleports -- O doesn't kick if (piece.shape == "O") then return end -- assess precisely what rows/columns would be blocked given the desired rotation local sides = {top=false,uright=false,lright=false,uleft=false,lleft=false,center=false,bottom=false} local left_exists = false local right_exists = false local kick = {x=0,y=0} for _,offset in pairs(new_piece:getBlockOffsets()) do local x = piece.position.x + offset.x local y = piece.position.y + offset.y if offset.x == 0 then left_exists = true end if offset.x == 2 or offset.x == 3 then right_exists = true end if grid:isOccupied(x,y) then if offset.y == 0 then sides.top = true end if offset.y == 3 then sides.bottom = true end if offset.y == 1 and offset.x == 0 then sides.uleft = true end if offset.y == 2 and offset.x == 0 then sides.lleft = true end if offset.y == 1 and (offset.x == 2 or offset.x == 3) then sides.uright = true end if offset.y == 2 and (offset.x == 2 or offset.x == 3) then sides.lright = true end if offset.x == 1 then sides.center = true end end end if sides.top then kick = {x=0,y=1} elseif (sides.uleft and sides.lright) or (sides.uright and sides.lleft) or (sides.uright and sides.uleft) then kick = {x=0,y=1} elseif (sides.lleft and sides.lright) then kick = {x=0,y=-1} elseif (sides.lleft or sides.uleft) then kick = {x=1,y=0} elseif (sides.lright or sides.uright) then kick = {x=-1,y=0} elseif sides.center and left_exists then kick = {x=1,y=0} elseif sides.center and right_exists then kick = {x=-1,y=0} elseif sides.bottom then kick = {x=0,y=-1} end if grid:canPlacePiece(new_piece:withOffset({x=0,y=0})) then self:onPieceRotate(piece, grid) piece:setRelativeRotation(rot_dir):setOffset({x=0,y=0}) elseif grid:canPlacePiece(new_piece:withOffset(kick)) then self:onPieceRotate(piece, grid) piece:setRelativeRotation(rot_dir):setOffset(kick) end end function Rotation:movePiece(piece, grid, move, instant) local was_drop_blocked = piece:isDropBlocked(grid) local offset = ({x=0, y=0}) local moves = 0 local y = piece.position.y if move == "left" then offset.x = -1 moves = 1 elseif move == "right" then offset.x = 1 moves = 1 elseif move == "speedleft" then offset.x = -1 moves = grid.width elseif move == "speedright" then offset.x = 1 moves = grid.width end if not self:canPieceMove(piece, grid) then return end for i = 1, moves do local x = piece.position.x if moves ~= 1 then piece:moveInGrid(offset, 1, grid, instant) else piece:moveInGrid(offset, 1, grid, false) end if piece.position.x ~= x then self:onPieceMove(piece, grid) if piece.locked then break end end end if not was_drop_blocked and piece:isDropBlocked(grid) then PlaySE("bottom") end end function Rotation:dropPiece( inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked, hard_drop_enabled, additive_gravity, classic_lock ) local y = piece.position.y if inputs["down"] == true and drop_locked == false then piece:addGravity(math.max(gravity, drop_speed), grid, classic_lock) else piece:addGravity(gravity, grid, classic_lock) end if piece.position.y ~= y then self:onPieceDrop(piece, grid) end end function Rotation:lockPiece(piece, grid, lock_delay, classic_lock) if piece:isDropBlocked(grid) and piece.lock_delay >= lock_delay then piece.locked = true end end function Rotation:get180RotationValue() return 3 end function Rotation:getDefaultOrientation() return 1 end function Rotation:getDrawOffset(shape, orientation) return { x=0, y=0 } end function Rotation:getAboveFieldOffset(shape, orientation) if shape == "I" then return 1 else return 2 end end function Rotation:initializePiece( inputs, data, grid, gravity, prev_inputs, move, lock_delay, drop_speed, drop_locked, hard_drop_locked, big, irs, lastdir ) local spawn_positions spawn_positions = self.spawn_positions local colours colours = self.colourscheme self.last_dir = 0 self.held_rotate = false if inputs['left'] then self.last_dir = -1 elseif inputs['right'] then self.last_dir = 1 end local spawn_x = math.floor(spawn_positions[data.shape].x / 10 * grid.width) local spawn_dy spawn_dy = 0 local piece = Piece(data.shape, data.orientation - 1, { x = spawn_x or spawn_positions[data.shape].x, y = spawn_positions[data.shape].y - spawn_dy }, self.block_offsets, 0, 0, data.skin, colours[data.shape], big) self:onPieceCreate(piece) if irs then self:rotatePiece(inputs, piece, grid, {}, true, lastdir) end return piece end function Rotation:onPieceCreate(piece) end function Rotation:processPiece( inputs, piece, grid, gravity, prev_inputs, move, lock_delay, drop_speed, drop_locked, hard_drop_locked, hard_drop_enabled, additive_gravity, classic_lock, lastdir ) self:rotatePiece(inputs, piece, grid, prev_inputs, false, lastdir) self:movePiece(piece, grid, move, gravity >= grid.height) self:dropPiece( inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked, hard_drop_enabled, additive_gravity, classic_lock ) self:lockPiece(piece, grid, lock_delay, classic_lock) end function Rotation:canPieceMove(piece, grid) return true end function Rotation:canPieceRotate(piece, grid) return true end function Rotation:onPieceMove(piece) end function Rotation:onPieceRotate(piece) end function Rotation:onPieceDrop(piece) if piece.position.y > piece.lowest_point then piece.lock_delay = 0 piece.lowest_point = piece.position.y end end return Rotation