Perlin worms work by using noise to adjust their rotation and marching forward. Your code is only considering positions.
Also, your noise values are 0 because you are rounding them (math.noise always returns a decimal between -1 and 1), another odd thing is if you give math.noise only integers it ALWAYS returns 0 math.noise(45217, 247, 21) == 0

local Workspace = game:GetService("Workspace")
local blockSize = 4
local seed = math.random(1, 99999999)
local function newBlock(position)
local block = Instance.new("Part")
block.Position = position
block.Size = Vector3.new(blockSize, blockSize, blockSize)
block.Anchored = true
block.Parent = Workspace
return block
end
local lastBlock = newBlock(Vector3.new(0, 0, 0))
local cf = CFrame.new(0, 0, 0) -- The starting position & rotation of the worm.
local marchDistance = 4 -- How far worm traverses forward per step
local noiseFrequency = 0.07 -- Larger makes the worm straighter, smaller makes it more curly
local rotationStrength = 0.8 -- How sharp turns can be
for index = 1, 100, 1 do
local newBlock = newBlock(cf.Position)
-- Generate noise values.
-- Arbitrary decimals help avoid getting all integers + extra randomness
local xNoise = math.noise((cf.Y + 2313.682) * noiseFrequency, (cf.Z + 6721.123) * noiseFrequency, seed)
local yNoise = math.noise((cf.X + 4432.572) * noiseFrequency, (cf.Z - 8742.941) * noiseFrequency, seed)
local zNoise = math.noise((cf.X - 5472.377) * noiseFrequency, (cf.Y - 1049.189) * noiseFrequency, seed)
-- Rotate worms head based on noise, and march forward.
cf *= CFrame.Angles(
xNoise * rotationStrength,
yNoise * rotationStrength,
zNoise * rotationStrength
)
cf += cf.LookVector * marchDistance
lastBlock = newBlock
end