New to OOP in Lua, need some help

Hi. I’m making a chess game in roblox using OOP, and I’m very new to it, but basically, whenever I create a new chess piece object and the module edits the properties of the object inside the new piece function, all of the chess piece’s properties get edited. I know this is how inheritance works, but I just can’t find out a way to have each chess piece object have their own set properties on creation. I did try to use an init function inside of the new piece function, but I know I also can’t call the init function inside of the neew piece function, and I would have to instead call init in the main script every time I create a new piece, which I obviously don’t want to do.

Here’s the code so far, (and other suggestions with my oop code would be appreciated)

(ModuleScript)

local gui = script.Parent
local pieces = gui:WaitForChild("Pieces")
local board = gui:WaitForChild("Board"):WaitForChild("Squares")

local Chess = {}
Chess.__index = Chess

local function getLocation(position)
	local location = board:FindFirstChild(position)
	assert(location, "Invalid position")
	
	return location
end

local function getAvailableSquares(piece)
	return {}
end

function Chess.new()
	local ChessGame = {}
	
	function ChessGame:NewPiece(pieceType, position)
		local piece = {}
		piece.__index = piece
		
		function piece:_init(pieceType, position)
			local folder = pieces:FindFirstChild(pieceType:sub(1, 5))
			assert(folder, "Invalid color name")

			local pieceInstance = folder:FindFirstChild(pieceType:sub(6))
			assert(pieceInstance, "Invalid piece name")

			local location = getLocation(position)
			
			self.Name = pieceType:sub(6)
			self.Color = pieceType:sub(1, 5)
			self.Position = position
			self._instance = pieceInstance:Clone()
			self.Data = {AvailableSquares = {}}
			
			self._instance:SetAttribute("Captured", false)

			self._instance.AttributeChanged:Connect(function(attribute)
				if attribute == "Captured" and self._instance:GetAttribute("Captured") then
					self._instance:Destroy()
				end
			end)

			self._instance.Destroying:Connect(function()
				setmetatable(self, nil)
			end)

			self._instance.Visible = true
			self._instance.Parent = location
		end
		
		function piece:Move(position)
			local location = getLocation(position)
			local capturedPiece = location:FindFirstChildOfClass("Frame")
			
			if capturedPiece then
				capturedPiece:SetAttribute("Captured", true)
			end
			
			self._instance.Parent = location
			self.Position = position
		end
		
		function piece:CheckSquare(position)
			if getAvailableSquares(self)[position] then
				return true
			end
		end
		
		piece:_init(pieceType, position)
		return setmetatable(self, piece)
	end
	
	return setmetatable(ChessGame, Chess)
end

return Chess

(LocalScript)

local gui = script.Parent
local ChessGame = require(gui:WaitForChild("ChessModule")).new()

local king = ChessGame:NewPiece("WhiteKing", "F2")
local blackPawn = ChessGame:NewPiece("BlackPawn", "G2")

task.wait(3)
king:Move("G2")

One last thing to note here, each square on the board is a frame in the gui, represented by the column/file (letter), and rank/row (number).

1 Like

Normally the methods are placed outside of the constructor, so they don’t get duplicated in memory like so:

local Chess = {}
Chess.__index = Chess

function Chess.new()
	return setmetatable({}, Chess)
end

function Chess:AddPiece(PieceType, Position)
end

return Chess

I would consider making a separate class ChessPiece and then constructing from it when doing :NewPiece. That way you don’t need to seemingly create a new interface for the class like you’re doing now.

You can also just append the _init function inside the constructor since it really doesn’t matter.

2 Likes

How would I go about creating the seperate class? Do I need to use multiple ModuleScripts? Also, this didn’t answer my initial question of how to create specific properties based on the parameters of the newly created chess piece on creation. As of now, every time I create a new chess piece, the properties of all chess pieces are set to the newly created one.

I forgot to mention something else I had in mind, how would having the addpiece function attached to the base module metatable work? Wouldn’t you just be able to directly call require(path):AddPiece() without having to construct the actual class? Would this lead to errors, or is there something I am missing?

Really? I was under the impression that functions are only stored once in memory.

Not necessarily, but I would recommend it.

You mean editing the properties of the Instance? You would just do:

self._instance[Prop] = Value

In this case, yes. Infact, you don’t really need to make Chess a class because all it really has is a single method. You might as well do:

local Chess = {}

function Chess:AddPiece()
end

Functions themselves can be cached, but the keys inside the table won’t (Because technically they are still dynamic), so we have to use __index to shortcut them in.

1 Like

Thank you for the responses, but for the question I was asking about setting specific properties for each piece, I meant the actual object, not the instance. When a new chess piece gets created via the :NewPiece method, all of the properties of the previously created chess piece objects are changed to the newly created one’s. For example, the name, the color, and it’s position. And finally, for the base chess class, I am planning to add methods and/or properties to it as the entire system gets more advanced, but I just wanted to know what effect would putting the piece class outside of the chess constructor have on the readability and functionality of the code. I can’t seem to wrap my mind around the chess:AddPiece method working on the base chess module, it’s a little confusing to me.

Just an update, I have followed your instructions on creating modules for separate classes that I still wish were all packed into one nice class, but at this point, it doesn’t matter too much anymore. I still have the same issue as I mentioned initially when creating this topic, and need help. Here’s the new code.

Main Script

local gui = script.Parent
local ChessGame = require(gui:WaitForChild("Game"))
local Piece = require(gui:WaitForChild("Piece"))

local activeGame = ChessGame.new()
local whiteKing = Piece.NewPiece("WhiteKing", "E1")
print(whiteKing.Position, whiteKing.Color, whiteKing.Name) -- Prints out "E1 White King" (correct)

local blackKing = Piece.NewPiece("BlackKing", "E8")

print(whiteKing.Position, whiteKing.Color, whiteKing.Name) -- Prints out "E8 Black King" (incorrect), same object, yet properties have changed to the newly created (black king) one.

ChessGame Module (Useless as of now, but it will have more use in the future.)

local gui = script.Parent
local pieces = gui:WaitForChild("Pieces")
local board = gui:WaitForChild("Board"):WaitForChild("Squares")
--local Piece = require(gui:WaitForChild("Piece"))

local ChessGame = {}
ChessGame.__index = ChessGame

function ChessGame.new()
	return setmetatable(ChessGame, {})
end



return ChessGame

Piece Module

local gui = script.Parent
local pieces = gui:WaitForChild("Pieces")
local board = gui:WaitForChild("Board"):WaitForChild("Squares")

local piece = {}
piece.__index = piece

local function getLocation(position)
	local location = board:FindFirstChild(position)
	assert(location, "Invalid position")

	return location
end

local function getAvailableSquares(piece)
	return {}
end

function piece.NewPiece(pieceType, position)
	local folder = pieces:FindFirstChild(pieceType:sub(1, 5))
	assert(folder, "Invalid color name")

	local pieceInstance = folder:FindFirstChild(pieceType:sub(6))
	assert(pieceInstance, "Invalid piece name")

	local location = getLocation(position)

	piece.Name = pieceType:sub(6)
	piece.Color = pieceType:sub(1, 5)
	piece.Position = position
	piece._instance = pieceInstance:Clone()
	piece.Data = {AvailableSquares = {}}

	piece._instance:SetAttribute("Captured", false)

	piece._instance.AttributeChanged:Connect(function(attribute)
		if attribute == "Captured" and piece._instance:GetAttribute("Captured") then
			piece._instance:Destroy()
		end
	end)

	piece._instance.Destroying:Connect(function()
		setmetatable(piece, nil)
	end)

	piece._instance.Visible = true
	piece._instance.Parent = location
	
	return setmetatable({}, piece)
end

function piece:Move(position)
	local location = getLocation(position)
	local capturedPiece = location:FindFirstChildOfClass("Frame")

	if capturedPiece then
		capturedPiece:SetAttribute("Captured", true)
	end

	self._instance.Parent = location
	self.Position = position
end

function piece:CheckSquare(position)
	if getAvailableSquares(self)[position] then
		return true
	end
end

return piece

Please let me know any other bad practices I am doing with my newly learned OOP abilities, as well as the solution to this very annoying problem I have. Thank you.

The problem is that you’re editing/modifying the original table which every piece inherits from. You’d want your piece code to look like this:

local gui = script.Parent
local pieces = gui:WaitForChild("Pieces")
local board = gui:WaitForChild("Board"):WaitForChild("Squares")

local piece = {}
piece.__index = piece

local function getLocation(position)
	local location = board:FindFirstChild(position)
	assert(location, "Invalid position")

	return location
end

local function getAvailableSquares(piece)
	return {}
end

function piece.NewPiece(pieceType, position)
    local newPiece = {} -- This will only apply for the piece that's currently being created

	local folder = pieces:FindFirstChild(pieceType:sub(1, 5))
	assert(folder, "Invalid color name")

	local pieceInstance = folder:FindFirstChild(pieceType:sub(6))
	assert(pieceInstance, "Invalid piece name")

	local location = getLocation(position)

	newPiece.Name = pieceType:sub(6)
	newPiece.Color = pieceType:sub(1, 5)
	newPiece.Position = position
	newPiece._instance = pieceInstance:Clone()
	newPiece.Data = {AvailableSquares = {}}

	newPiece._instance:SetAttribute("Captured", false)

	newPiece._instance.AttributeChanged:Connect(function(attribute)
		if attribute == "Captured" and newPiece._instance:GetAttribute("Captured") then
			newPiece._instance:Destroy()
		end
	end)

	newPiece._instance.Destroying:Connect(function()
		setmetatable(newPiece, nil)
	end)

	newPiece._instance.Visible = true
	newPiece._instance.Parent = location
	
	return setmetatable(newPiece, piece)
end

function piece:Move(position)
	local location = getLocation(position)
	local capturedPiece = location:FindFirstChildOfClass("Frame")

	if capturedPiece then
		capturedPiece:SetAttribute("Captured", true)
	end

	self._instance.Parent = location
	self.Position = position
end

function piece:CheckSquare(position)
	if getAvailableSquares(self)[position] then
		return true
	end
end

return piece
3 Likes

Thank you so much for the help. Really cleared out the confusion.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.