"Javascriptifying" systems

As someone who started on JavaScript but switched over to Lua for Roblox development, I find it very fun to “Javascriptify” scripts when possible just for the sake of it. Recently, I’ve ported over a few system from JavaScript to Lua (currently done 2 but plan to do more in the future) while trying as much as possible to maintain it’s JavaScript "feel". This includes making use of modified tables so I can have my arrays start at 0 instead of 1, making use of "this" instead of self, and a few more.


Note: My sudden motive to do this was heavily by “The Coding Train” on YouTube which I highly recommend checking out if you’re into JavaScript based coding challenges https://www.youtube.com/c/TheCodingTrain




  1. Maze Generator
Hierarchy

“CellController” *(Script)*>“Cell” *(Module Script)>“Wall” *(Module Script)*

Demonstration

Shortened Version:

Full Version: (Probably not gonna be clicked cause of “Don’t click random links” but it’s the full mp4 version if you want to see it!)
https://mail.google.com/mail/u/0?ui=2&ik=99c9e41bab&attid=0.1&permmsgid=msg-a:r-1993163550845465856&th=183c2ef438c7f340&view=att&disp=safe&realattid=f_l931q76f0

Source!
  • CellController:
local Cell = require(script:WaitForChild("Cell"))

task.wait(10)
Cell:Setup()
task.wait(5)
warn("Initializing")
task.wait(1)
warn("Simulation beginning!")
Cell:Draw()

  • Cell:
--Type
type Type_Cell = {i:number,j:number,walls:{boolean},visited:boolean,Base:Part, WallObjs:{}}
type Type_Grid = {Type_Cell} 


--Math funcs
local floor,ceil,random = math.floor,math.ceil,math.random


--Module init
local Cell = {}
Cell.__index = Cell


--Setup/settings
local CanvasSize = 100

local cols,rows
local w = CanvasSize/30
local originOffset:Vector3
local grid:Type_Grid = {}

local partHeight = 1
local partNamePrefix = "Cell "

local order = {"Top", "Right", "Bottom", "Left"}

local Colors = {
	White = Color3.new(1,1,1),
	Black = Color3.new(0,0,0),
	Red = Color3.new(1,0,0),
	Green = Color3.new(0,1,0),
	Blue = Color3.new(0,0,1),
	Cyan = Color3.new(0,1,1),
	
}


--Status vars
local current:Type_Cell
local stack = {}


--Resources
local Wall = require(script:WaitForChild("Wall"))


--Indexing important local funcs
local Connect,Visualize = Wall.Connect, Wall.Visualize


--Local fundamental funcs
local function GetLength(tab:{})
	local zero = tab[0]
	return zero and #tab+1 or 0
end

local function Insert(tab:{}, ...:any)
	local packed = table.pack(...)
	for _,obj in ipairs(packed) do
		tab[GetLength(tab)] = obj
	end
	return tab
end

local function CreateTab(tab:{})
	return Insert({}, table.unpack(tab))
end

local function Iterate(tab:{}, callback:(i:number, v:any)->())
	local tabAmount = #tab
	for i = 0, tabAmount do
		callback(i, tab[i])
	end
end

local function Index(i:number,j:number)
	if i < 0 or j < 0 or i > cols- 1 or j > rows-1 then return -1 end
	
	return i + j*cols
end


local function rect(x:number,y:number,width:number,height:number, index:string)
	local part = Instance.new("Part")
	
	part.Name = partNamePrefix..index
	part.Anchored = true
	
	local partCfr = part.CFrame
	local partLookVec = partCfr.LookVector
	
	part.Size = Vector3.new(width, partHeight, height)
	local partSize = part.Size
	
	local sizeX = partSize.X
	local sizeY = partSize.Y
	local sizeZ = partSize.Z
	
	part.Position = Vector3.new(x, sizeY/2, y) + originOffset
	partCfr = part.CFrame
	partLookVec = partCfr.LookVector
	
	
	local TopLeft = partCfr:PointToWorldSpace(Vector3.new(-sizeX/2,sizeY/2,-sizeZ/2))
	local TopRight = partCfr:PointToWorldSpace(Vector3.new(sizeX/2,sizeY/2,-sizeZ/2))
	
	local BottomRight = partCfr:PointToWorldSpace(Vector3.new(sizeX/2,sizeY/2,sizeZ/2))
	local BottomLeft = partCfr:PointToWorldSpace(Vector3.new(-sizeX/2,sizeY/2,sizeZ/2))
	
	
	local partProperties = {
		Color = Colors.White,
	}
	
	
	local Top = Wall:New(TopLeft, TopRight, part, nil, {Name = "Top"})
	local Right = Wall:New(TopRight, BottomRight, part, nil, {Name = "Right"})
	local Bottom = Wall:New(BottomRight, BottomLeft, part, nil, {Name = "Bottom"})
	local Left = Wall:New(BottomLeft, TopLeft, part, nil, {Name = "Left"})
	
	local WallObjs = CreateTab({Top, Right, Bottom, Left})
	
	
	part.Parent = workspace.Board
	for i,v in pairs(partProperties) do
		part[i] = v
	end
	
	return part,WallObjs
end

local function RemoveWalls(a:Type_Cell, b:Type_Cell)
	
	local x = a.i - b.i
	if x == 1 then
		a.walls[3] = false
		b.walls[1] = false
	elseif x == -1 then
		a.walls[1] = false
		b.walls[3] = false
	end
	
	local y = a.j - b.j
	if y == 1 then
		a.walls[0] = false
		b.walls[2] = false
	elseif y == -1 then
		a.walls[2] = false
		b.walls[0] = false
	end
	
end


--Fundamental module methods
function Cell:Setup()
	cols = floor(CanvasSize/w)
	rows = floor(CanvasSize/w)
	
	originOffset = Vector3.new(-(w*cols /2), 0, -(w*rows /2))
 
	for j = 0, rows-1 do
		for i = 0, cols-1 do
			local cell = Cell:New(i,j, i..","..j)
			Insert(grid, cell)
		end
		task.wait()
	end
	
	current = grid[0]
end

local loops = 0
local waitInterval = 20

function Cell:Draw()
	while true do
		loops = (loops+1)%waitInterval
		if loops == 0 then
			task.wait()
		end
		
		
		
		Iterate(grid, function(i,v)
			grid[i]:Show()
		end)
		
		
		current.visited = true
		current:Highlight()
		
		--STEP 1
		local nextCell:Type_Cell? = current:CheckNeighbors()
		if nextCell then
			nextCell.visited = true
			
			--STEP 2
			table.insert(stack, current)
			
			--STEP 3
			RemoveWalls(current, nextCell)
			
			--STEP 4
			current = nextCell
		elseif #stack > 0 then
			local stackAmount = #stack
			current = stack[stackAmount]
			table.remove(stack, stackAmount)
		else
			--Iterate(grid, function(i,v)
			--	grid[i].Base:Destroy()
			--end)
			--grid = {}
			--Cell:Setup()
			break
		end
	
	end
end


--Practical module funcs
function Cell:New(i,j, index:string?)
	local cell = {}::Type_Cell
	cell.i = i
	cell.j = j
	cell.walls = CreateTab({true,true,true,true})
	cell.visited = false
	
	if i == cols-1 and j == rows-1 then
		warn("END!")
		cell.walls[2] = false
	elseif i == 0 and j == 0 then
		warn("START!")
		cell.walls[0] = false	
	end
	
	local x = cell.i*w + (w/2)
	local y = cell.j*w + (w/2)
	
	cell.Base,cell.WallObjs = rect(x,y,w,w, index or "")
	
	return setmetatable(cell, Cell)
end


function Cell:Show()
	local self:Type_Cell = self
	
	local walls = self.walls
	local WallObjs = self.WallObjs
	local Base = self.Base
	
	self:UpdateWalls()
	
	if self.visited then
		Base.Color = Colors.Cyan
	else
		Base.Color = Colors.White
	end
	
end

function Cell:UpdateWalls()
	local self:Type_Cell = self

	local walls = self.walls
	local WallObjs = self.WallObjs
	
	Iterate(walls, function(i,v)
		WallObjs[i]:SetVisibility(v)
	end)
end

function Cell:CheckNeighbors()
	local self:Type_Cell = self
	
	local Neighbors = {}
	
	
	local i,j = self.i,self.j
	
	local Candidates:{[string]: Type_Cell} = {
		Top = grid[Index(i, j-1)],
		Right = grid[Index(i+1, j)],
		Bottom = grid[Index(i, j+1)],
		Left = grid[Index(i-1, j)]
	}
	
	
	
	for _,v in ipairs(order) do
		local Candidate = Candidates[v]
		if Candidate and not Candidate.visited then
			table.insert(Neighbors, Candidate)
		end
	end
	
	
	local length = #Neighbors
	
	if length > 0 then
		local r = random(1, length)
		return Neighbors[r]
	else
		return
	end
	
end

function Cell:Highlight()
	local self:Type_Cell = self
	
	self.Base.Color = Colors.Red
end


return Cell


  • Wall:
--Types
type Type_WallObj = {Obj:Part, Visible:boolean} 


--Module init
local Wall = {}
Wall.__index = Wall


--Visibility funcs



--Local funcs
local function Connect(Point1:Vector3,Point2:Vector3, Properties:{[string]:any}):Part
	local p = Instance.new("Part")
	p.Anchored = true

	local size = 1
	local Dist = (Point1-Point2).Magnitude

	p.Size = Vector3.new(size,size, Dist)
	p.CFrame = CFrame.new(Point1, Point2) * CFrame.new(0, 0, -Dist / 2)

	if Properties then
		for i,v in pairs(Properties) do
			p[i] = v
		end
	end
	return p
end

local function Visualize(pos:Vector3, Properties:{[string]:any})
	local p = Instance.new("Part")
	p.Shape = Enum.PartType.Ball
	p.Anchored = true

	local size = 1
	p.Size = Vector3.one * size
	p.Position = pos

	p.Parent = workspace.Visuals
	if Properties then
		for i,v in pairs(Properties) do
			p[i] = v
		end
	end

	return p
end


--Connection local funcs to the module
Wall.Connect = Connect
Wall.Visualize = Visualize


--Main module funcs
function Wall:New(Point1:Vector3,Point2:Vector3, part:Part, Show:boolean?, ExtraProperties:{[string]:any})
	local WallProperties = {
		Parent = part,
		Color = Color3.fromRGB(255, 255, 0),
	}
	
	local wall:Type_WallObj = setmetatable({}, Wall)
	
	local Obj = Connect(Point1, Point2, WallProperties)
	wall.Obj = Obj
	
	if ExtraProperties then
		for i,v in pairs(ExtraProperties) do
			Obj[i] = v
		end
	end
	
	wall.Visible = true
	
	if not Show then
		wall:Hide()
	end
	
	return wall
end


function Wall:Show()
	local self:Type_WallObj = self
	
	self.Visible = true
	self.Obj.Transparency = 0.7
end

function Wall:Hide()
	local self:Type_WallObj = self

	self.Visible = false
	self.Obj.Transparency = 1
end

function Wall:SetVisibility(status:boolean)
	if status then
		self:Show()
	else
		self:Hide()
	end
end


return Wall
  1. Firework System
Hierarchy

ReplicatedStorage> “Particle” *(Module Script)*
ServerScriptService> “Handler” *(Script)*

Demonstration

Source!
  • Handler:
--Math funcs
local floor,ceil,random,cos,sin,round,rad,deg = math.floor,math.ceil,math.random,math.cos,math.sin,math.round,math.rad,math.deg

--Services
local rep = game:GetService("ReplicatedStorage")


--
local Particle = require(rep:WaitForChild("Particle"))


--script env settings
local framerate = 60
local gravity = Vector2.new(0, 0.8)

local particlesAmount = 25

--vars
type Type_Firework = {firework:Particle.Type_Particle,exploded:boolean,particles:{Particle.Type_Particle}, hue:number}
local FIREWORKS = {}
FIREWORKS.__index = FIREWORKS

local fireworks:{Type_Firework} = setmetatable({}, FIREWORKS)


do
	function FIREWORKS:New()
		local this:Type_Firework = setmetatable({}, FIREWORKS)
		
		local width = Particle.width
		local height = Particle.height
		
		this.hue = random(255)
		this.firework = Particle:New(random(0, width), height/2, this.hue, true)
		this.exploded = false
		this.particles = {}
		
		
	
		
		return this
	end
	
	function FIREWORKS:Update()
		local this:Type_Firework = self
		
		if not this.exploded then
			this.firework:ApplyForce(gravity)
			this.firework:Update()
			
			if this.firework.vel.Y >= 0 then
				this.exploded = true
				this:Explode()
			end
			
		end
		
		for i = #this.particles, 1, -1 do
			this.particles[i]:ApplyForce(gravity)
			this.particles[i]:Update()
			
			if this.particles[i]:Done() then
				this.particles[i]:Destroy()
				table.remove(this.particles, i)
			end
		end
		
	end
	
	function FIREWORKS:Done()
		local this:Type_Firework = self
		
		return this.exploded and #this.particles == 0
	end
	
	function FIREWORKS:Explode()
		local this:Type_Firework = self
		
		for i = 1, particlesAmount do
			local p = Particle:New(this.firework.pos.X, this.firework.pos.Y, this.hue)
			table.insert(this.particles, p)
		end
		
		
	end
	
	function FIREWORKS:Show()
		local this:Type_Firework = self 
		
		this.firework:SetVisibility(not this.exploded)
		this.firework:Show()
		
		for i = 1, #this.particles do
			this.particles[i]:Show()
		end
		
	end
	
	
end


--Fundamental funcs
local function Setup()
	Particle:Setup()
	
	local width = Particle.width
	local height = Particle.height
	
	
end

local function Draw()
	if random() < 0.3  then
		table.insert(fireworks, fireworks:New())
	end
	
	
	for i = #fireworks, 1, -1 do
		fireworks[i]:Update()
		fireworks[i]:Show()
		if fireworks[i]:Done() then
			fireworks[i].firework:Destroy()
			table.remove(fireworks, i)
		end
	end
	
end



--
Setup()
while true do
	task.wait(1/framerate)
	Draw()
end
  • Particle:
--Types
export type Type_Particle = {pos:Vector2, vel:Vector2, acc:Vector2, obj:Part, firework:boolean?, lifespan:number, hue:number}

--Math funcs
local floor,ceil,random,cos,sin,round,rad,deg = math.floor,math.ceil,math.random,math.cos,math.sin,math.round,math.rad,math.deg


--Module init
local Particle = {}
Particle.__index = Particle


--Settings
local Canvas:Part
local CanvasSize = Vector2.new(200,200)

local width:number
local height:number
local offset:Vector2
local offset3D:Vector3

local Parts = {}
local PartsPos = Vector3.new(0,-100,0)


--Obj settings
local fireworkSize = 4
local particleSize = 1.5



--Resources
local rep = game:GetService("ReplicatedStorage")


--local funcs
local function Random2D()
	local angle = random(1,360)
	return Vector2.new(cos(angle), sin(angle))
end

local function AddPart()
	local p = Instance.new("Part")
	p.CanCollide = false
	p.CanQuery = false
	p.CastShadow = false
	p.Anchored = true
	
	p.Material = Enum.Material.Neon
	p.Shape = Enum.PartType.Ball
	
	p.Position = PartsPos
	p.Parent = workspace.Visuals
	table.insert(Parts, p)
end

local function GetPart()
	if #Parts == 0 then
		AddPart()
	end
	
	local p = Parts[#Parts]
	table.remove(Parts, #Parts)
	return p
end

local function ReturnPart(p:Part)
	p.Position = PartsPos
	table.insert(Parts, p)
end


--Fundamental methods
function Particle:Setup()
	width = CanvasSize.X
	height = CanvasSize.Y
	
	offset = Vector2.new(-width/2, -height/2)
	offset3D = Vector3.new(offset.x, 0, offset.Y)
	
	
	Particle.width = width
	Particle.height = height
	
	Particle.offset = offset
	Particle.offset3D = offset3D
	
	Canvas = Instance.new("Part")
	Canvas.Anchored = true
	Canvas.CanCollide = false
	Canvas.Name = "Canvas"
	Canvas.Size = Vector3.new(width, 1, height)
	Canvas.Position = Vector3.new(0, -Canvas.Size.Y/2 + 0.1, 0)
	Canvas.Color = Color3.new(0,0,0)
	Canvas.Parent = workspace
	
	for i = 1, 1000 do
		AddPart()
	end
end

function Particle:New(x:number,y:number, hue:number, firework:boolean?)
	local this:Type_Particle = setmetatable({}, Particle)
	
	this.pos = Vector2.new(x,y)
	this.firework = firework
	this.lifespan = 0
	this.hue = hue
	
	if this.firework then
		this.vel = Vector2.new(0,random(-16,-8))
	else
		this.vel = Random2D() * random(2, 10)
	end
	
	this.acc = Vector2.new(0,0)
	
	local obj = GetPart()
	if this.firework then
		obj.Size = Vector3.one * fireworkSize
	else
		obj.Size = Vector3.one * particleSize
	end
	
	obj.Color = Color3.fromHSV(this.hue/255, 1, 1)
	
	this.obj = obj
	
	
	
	this:Show()
	
	
	return this
end



--Practical methods
function Particle:ApplyForce(force:Vector2)
	local this:Type_Particle = self
	
	this.acc += force
end


function Particle:Update()
	local this:Type_Particle = self
	
	if not this.firework then
		this.vel *= 0.95
		this.lifespan += 0.025
	end
	
	this.vel += this.acc
	this.pos += this.vel / 2.5
	this.acc *= 0
end

function Particle:Show()
	local this:Type_Particle = self 
	
	local obj = this.obj
	local objSize = obj.Size
	
	local sizeY = objSize.Y
	
	
	if not this.firework then
		obj.Transparency = this.lifespan
	end
	
	obj.Position = Vector3.new(this.pos.X, sizeY/2, this.pos.Y) + offset3D
	
	
	
	
end

function Particle:Destroy()
	local this:Type_Particle = self
	
	ReturnPart(this.obj)
end

function Particle:Done()
	local this:Type_Particle = self
	return this.lifespan >= 1
end

function Particle:SetVisibility(status:boolean?)
	local this:Type_Particle = self
	
	local obj = this.obj
	
	if status then
		obj.Transparency = 0
	else
		obj.Transparency = 1
	end
end



return Particle



Source Files:






What is the point of this you might ask? Well I’m hoping someone can feel inspired to create systems similar to this and share it or simply take some of the code and make it more awesome!

Edit: It seems that there isn’t any JavaScript “feel” in the first example but I hope the second one does to you

1 Like

I realize this might be the wrong category so feel free to tell me where this should go.

There’s actually roblox-ts; it turns TypeScript code into Lua Code. I don’t really see the need to “Javascriptify” Lua as it’s its own language. I’d recommend looking into it if you want to use JavaScript instead. Additionally, Roblox-ts is used by a few popular games:

  • Islands
  • Bedwars
  • Deadline (that super realistic Roblox firearm fighting game)

I’d really recommend using it, it’s amazing.

1 Like

I think you got my intentions wrong, I’m very contented with programming in Lua, I just thought It’ll be a fun little thing to try out

ah ok yea that makes sense then

in that case i wish you the best of luck with this, it seems cool