Log In  


Hello,

I'm a beginner programmer, trying to figure out good practice. To be honest, I get a lot of joy out of refactoring code even though I know it's more important to create something of value. Seems to give my brain comfort.

I've been creating a card game and realised that one of my challenges is that I have a card object that has some game logic associated with it but I'm also mixing up a bunch of presentation information on that card. It's getting messy.

Which got me thinking about trying to separate concerns. Wondering what the community's view is on an approach like below?

The general idea here is that a card needs to know nothing about its presentation (but is linked to its presentation). It's a bidirectional link, a card_view is related to its card.

Obviously, in a game where there are not a lot of objects, this might be overkill.

function _init()
	renderer = Renderer:new()
	animator = Animator:new()
	deck = {}
	x_offset, y_offset, i = 0, 0, 0
	for i=1, 10 do
		local card = Card:new(i, "Hearts")
		local card_view = CardView:new{
			card=card, 
			x=15, 
			y=200, 
			target_x=15+x_offset, 
			target_y=106}
		card.view = card_view
		renderer:add_drawable(card_view)
		animator:add_moveable(card_view)
		add(deck, card)
		x_offset+=38
	end
end

function _update()
	if btnp(5) and i<#deck then
		deck[#deck-i].view.moving = true
		i+=1
	end
	animator:animate_entities()
end

function _draw()
	cls(27)
	renderer:draw_entities()
end

Card = {}
Card.__index = Card
function Card:new(rank, suit)
	local self = setmetatable({}, CardView)
	self.name = "Card"
	self.rank = rank
	self.suit = suit
	self.view = nil
	return self
end

function Card:update()
	--lots of game logic to go here
end

CardView = {}
CardView.__index = CardView
function CardView:new(options)
	local self = setmetatable({}, CardView)
	self.name = "Card View"
	self.card = options.card
	self.x = options.x
	self.y = options.y
	self.start_x = options.x
	self.start_y = options.y
	self.target_x = options.target_x
	self.target_y = options.target_y
	self.sp = 1
	self.moving = false
	return self
end

function CardView:_dist(x1, y1, x2, y2)
    return sqrt((x2 - x1)^2 + (y2 - y1)^2)
end

function CardView:update()
    if self.moving then
        local dx = self.target_x - self.x
        local dy = self.target_y - self.y
        local remaining_distance = sqrt(dx*dx + dy*dy)
        local total_distance = self:_dist(self.start_x, self.start_y, self.target_x, self.target_y)
        local traveled_distance = total_distance - remaining_distance

        if remaining_distance > 1 then
            local progress = traveled_distance / total_distance

            -- Move the self
            self.x += dx * 0.2
            self.y += dy * 0.2

            -- Check if 85% of the journey is complete
            if progress >= 0.85 and not self.revealed then
                self.sp = 2
                self.revealed = true
            end
        else
            self.x = self.target_x
            self.y = self.target_y
            self.moving = false
        end
    end
end

function CardView:draw()
	spr(self.sp, self.x, self.y)
	if self.revealed then
		print(self.card.suit, self.x+2, self.y+4, 8)
		print(self.card.rank, self.x+2, self.y+12, 8)
	end
end

Renderer = {}
Renderer.__index = Renderer

function Renderer:new()
    local self = setmetatable({}, Renderer)
    self.drawables = {}
    return self
end

function Renderer:add_drawable(drawable, zIndex)
    if type(drawable.draw) ~= "function" then
        error("No draw function for "..drawable.name)
    end
    drawable.zIndex = zIndex or 0 -- Default to 0 if no zIndex is provided
    local index = #self.drawables + 1

    -- Find the correct position to insert based on zIndex
    for i=1,#self.drawables do
        if self.drawables[i].zIndex > drawable.zIndex then
            index = i
            break
        end
    end

    -- Insert drawable at the found position
    for i = #self.drawables + 1, index + 1, -1 do
        self.drawables[i] = self.drawables[i-1]
    end
    self.drawables[index] = drawable
end

function Renderer:draw_entities()
    for _, drawable in ipairs(self.drawables) do
        if type(drawable.draw) ~= "function" then
            error("No draw function for "..drawable.name)
        end
        drawable:draw()
    end
end

Animator = {}
Animator.__index = Animator

function Animator:new()
    local self = setmetatable({}, Animator)
    self.moveables = {}
    return self
end

function Animator:add_moveable(moveable)
    add(self.moveables, moveable)
end

function Animator:animate_entities()
    for _, moveable in ipairs(self.moveables) do
        moveable:update()
    end
end



[Please log in to post a comment]