So I've been trying to code a Tetris on Roblox for a couple of days, and I'm stuck on this one bug that I can't seem to fix. In Tetris, you collide with other tetrominos (the Tetris blocks) and borders. I coded it so it will have collisions, and it somewhat works. It does work, but sometimes it doesn't detect some collisions, so you can sometimes cheat with this strange glitch. It usually starts happening when you rotate the tetrominos, and I've tried several solutions to try and fix it. This script is a local script in StarterPlayerScripts.

local player = game.Players.LocalPlayer
local tetrominos = game.ReplicatedStorage.Tetrominos
local tetrominoStorage = game.Workspace.Tetrominos
local borders = game.Workspace.Borders
local camera = game.Workspace.CurrentCamera
local UIS = game:GetService("UserInputService")
local debounce = false
local fallSpeed = 1
local previous

repeat wait() until player.Character
camera.CameraType = "Scriptable"
camera.CFrame = CFrame.new(0, 22, 35)


function IsColliding(tetromino)
	local minos = tetromino:GetChildren()
	local obstacles = borders:GetChildren()
	for i, v in pairs(tetrominoStorage:GetChildren()) do
		for _, mino in pairs(v:GetChildren()) do
			if mino.Name == "Mino" then
				table.insert(obstacles, mino)
			end
		end
	end
	for _, mino in pairs(minos) do
		for _, obstacle in pairs(obstacles) do
			if mino.Position == obstacle.Position then
				return true
			end
		end
	end
	return false
end

function FixTetrominoPosition(tetromino)
	for _, mino in pairs(tetromino:GetChildren()) do
		for _, block in pairs(tetrominoStorage:GetDescendants()) do
			if block.Name == "Mino" then
				if mino.Position == block.Position then
					repeat
						tetromino:SetPrimaryPartCFrame(CFrame.new(0, 2, 0) * tetromino.PrimaryPart.CFrame)
					until not IsColliding(tetromino)
				end
			end
		end
		for _, border in pairs(borders:GetChildren()) do
			if mino.Position == border.Position then
				local borderType = string.sub(border.Name, 7, #border.Name)
				if borderType == "Left" then
					repeat
						tetromino:SetPrimaryPartCFrame(CFrame.new(2, 0, 0) * tetromino.PrimaryPart.CFrame)
					until not IsColliding(tetromino)
				elseif borderType == "Right" then
					repeat
						tetromino:SetPrimaryPartCFrame(CFrame.new(-2, 0, 0) * tetromino.PrimaryPart.CFrame)
					until not IsColliding(tetromino)
				elseif borderType == "Bottom" then
					repeat
						tetromino:SetPrimaryPartCFrame(CFrame.new(0, 2, 0) * tetromino.PrimaryPart.CFrame)
					until not IsColliding(tetromino)
				end
			end
		end
	end
end

function ClearFullRows()
	local rows = {}
	local hasFullRows = false
	for i = 1, 20 do
		local minos = {}
		for _, mino in pairs(tetrominoStorage:GetDescendants()) do
			if mino.Name == "Mino" then
				if mino.Position.Y == i * 2 + 1 then
					table.insert(minos, mino)
				end
			end
		end
		table.insert(rows, minos)
	end
	for i, v in pairs(rows) do
		if #v == 10 then
			hasFullRows = true
			for int, mino in pairs(v) do
				mino:Destroy()
			end
			local row = rows[i + 1]
			if row then
				for int, rowsAbove in pairs(rows) do
					if int >= i + 1 then
						for _, mino in pairs(rowsAbove) do
							mino.CFrame = CFrame.new(0, -2, 0) * mino.CFrame
						end
					end
				end
			end
		end
	end
end

function UpdatePreview(tetromino)
	if previous then
		previous:Destroy()
	end
	local preview = tetromino:Clone()
	preview.Parent = game.Workspace
	preview.Name = "Preview"
	for i, v in pairs(preview:GetChildren()) do
		if v.Name == "Mino" then
			v.SelectionBox.Color3 = Color3.new(1, 1, 1)
			v.Material = "ForceField"
			v.Transparency = 0.5
		end
	end
	previous = preview
	repeat
		preview:SetPrimaryPartCFrame(CFrame.new(0, -2, 0) * preview.PrimaryPart.CFrame)
	until IsColliding(preview)
	preview:SetPrimaryPartCFrame(CFrame.new(0, 2, 0) * preview.PrimaryPart.CFrame)
end

function CreateTetromino()
	local tetromino = tetrominos:GetChildren()[math.random(1, #tetrominos:GetChildren())]:Clone()
	tetromino.Parent = game.Workspace
	return tetromino
end

local tetromino = CreateTetromino()
UpdatePreview(tetromino)

UIS.InputBegan:Connect(function(input)
	if input.KeyCode == Enum.KeyCode.A or input.KeyCode == Enum.KeyCode.Left then
		tetromino:SetPrimaryPartCFrame(CFrame.new(-2, 0, 0) * tetromino.PrimaryPart.CFrame)
		if IsColliding(tetromino) then
			FixTetrominoPosition(tetromino)
		end
	elseif input.KeyCode == Enum.KeyCode.D or input.KeyCode == Enum.KeyCode.Right then
		tetromino:SetPrimaryPartCFrame(CFrame.new(2, 0, 0) * tetromino.PrimaryPart.CFrame)
		if IsColliding(tetromino) then
			FixTetrominoPosition(tetromino)
		end
	end
	if input.KeyCode == Enum.KeyCode.W or input.KeyCode == Enum.KeyCode.Up then
		tetromino:SetPrimaryPartCFrame(tetromino.PrimaryPart.CFrame * CFrame.Angles(0, 0, math.rad(-90)))
		if IsColliding(tetromino) then
			for _, mino in pairs(tetromino:GetChildren()) do
				for _, block in pairs(tetrominoStorage:GetDescendants()) do
					if block.Name == "Mino" then
						if mino.Position == block.Position then
							tetromino:SetPrimaryPartCFrame(tetromino.PrimaryPart.CFrame * CFrame.Angles(0, 0, math.rad(90)))
						end
					end
				end
			end
			FixTetrominoPosition(tetromino)
		end
	end
	if input.KeyCode == Enum.KeyCode.S or input.KeyCode == Enum.KeyCode.Down then
		tetromino:SetPrimaryPartCFrame(CFrame.new(0, -2, 0) * tetromino.PrimaryPart.CFrame)
		if IsColliding(tetromino) then
			FixTetrominoPosition(tetromino)
			tetromino.Parent = tetrominoStorage
			tetromino = CreateTetromino()
			ClearFullRows()
		end
	end
	if input.KeyCode == Enum.KeyCode.Space or input.KeyCode == Enum.KeyCode.KeypadEnter then
		repeat
			tetromino:SetPrimaryPartCFrame(CFrame.new(0, -2, 0) * tetromino.PrimaryPart.CFrame)
		until IsColliding(tetromino)
		tetromino:SetPrimaryPartCFrame(CFrame.new(0, 2, 0) * tetromino.PrimaryPart.CFrame)
		tetromino.Parent = tetrominoStorage
		tetromino = CreateTetromino()
		ClearFullRows()
	end
	UpdatePreview(tetromino)
	debounce = false
	wait(0.5)
	if not debounce then
		debounce = true
	end
end)

spawn(function()
	while wait(1 / fallSpeed) do
		local canFall = true
		for _, input in pairs(UIS:GetKeysPressed()) do
			if input.KeyCode == Enum.KeyCode.S or input.KeyCode == Enum.KeyCode.Down then
				canFall = false
			end
		end
		if canFall then
			tetromino:SetPrimaryPartCFrame(CFrame.new(0, -2, 0) * tetromino.PrimaryPart.CFrame)
		end
		fallSpeed = fallSpeed + (1 / 120)
		if IsColliding(tetromino) then
			FixTetrominoPosition(tetromino)
			tetromino.Parent = tetrominoStorage
			tetromino = CreateTetromino()
			ClearFullRows()
		end
		UpdatePreview(tetromino)
	end
end)