Preventing traps in crossy road type map generation

I am working on a game similar to crossy road and have the map generation side of it complete. It works fine and I’m wondering how I could add the map elements (trees, bushes, rocks, etc) spawn randomly while also not trapping the player and making sure they are able to move forward.

One idea i had is with each floor piece could have an invisible part in the middle of each grid of the floor that would be able to spawn a random map piece depending on the floor type. But the issue would still remain of making sure this doesn’t block the player from moving.

Script in ServerScriptService that generates a random map seed:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local startingRandom = 25267357

ReplicatedStorage.seed.Value = math.random() * startingRandom --numberValue being given a random number

Players.PlayerAdded:Connect(function(player)
	player:WaitForChild("PlayerGui"):WaitForChild("respawn").Frame.TextButton.MouseButton1Click:Connect(function()
		ReplicatedStorage.seed.Value = math.random() * startingRandom
	end)
end)

LocalScript in ReplicatedFirst that generates each floor piece randomly:

local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

if not game:IsLoaded() then game.Loaded:Wait() end

local player = Players.LocalPlayer
local part = Workspace.startingEnvironment:WaitForChild("start") --the connecting piece from the starting floor that isnt random, where the next generated piece will connect
local random = Random.new(ReplicatedStorage.seed.Value)

local mapReset = part:Clone()
mapReset.Parent = ReplicatedStorage.mapReset

local models = ReplicatedStorage.models:GetChildren()
table.sort(models, function(a, b) return a.Name < b.Name end)

local debounce = true

RunService.Heartbeat:Connect(function(deltaTime)
	for _,v in pairs(workspace:GetDescendants()) do
		if v:IsA("Texture") then
			v:Destroy()
		end
	end

	if not player.Character then return end
	if (part.Position - player.Character:GetPivot().Position).Magnitude > 200 then return end

	local model = models[random:NextInteger(1, #models)]:Clone()
	model:PivotTo(part.CFrame)
	model.start:Destroy()
	model.End.Transparency = 1
	model.Parent = workspace.environment.ground

	part:Destroy()
	part = model.End

	ReplicatedStorage.seed.Changed:Connect(function()
		if debounce then
			debounce = false
			print("respawning")
			task.wait(1.2)

			for _,x in pairs(Workspace.environment.ground:GetChildren())do
				x:Destroy()
			end

			part = ReplicatedStorage.mapReset["start"]:Clone()
			part.Parent = Workspace.startingEnvironment

			local modelReload = models[random:NextInteger(1, #models)]:Clone()
			modelReload:PivotTo(part.CFrame)
			modelReload.start:Destroy()
			modelReload.End.Transparency = 1
			modelReload.Parent = workspace.environment.ground

			part:Destroy()
			part = modelReload.End
			
			debounce = true
		end
	end)
end)