Puzzle game touch

Hi guys,
I am working on a puzzle game but have been stuck on how to handle touch, ie how to move the parts on the board so I would like to ask for some advice or guidance. This is how its supposed to work. The board is built up of 36 (6x6) brushes. As a player you can press and hold a brush and then move it across the board to mix or extract colors from the brushes you move your selected brush over. You can only move the brush up, down, left or right. You can move the brush as far across the board as you want in one direction only to impact several brushes. So if you press a yellow brush and move it over a blue brush the blue brush becomes green. The yellow brush you selected stays yellow. When the player manages to align 3 brushes of the same color they should be removed and new colors should be added. There are some more rules but getting this sorted should help a lot.

Ive removed all my attempts to handle touch since none of them seems to make sense :slight_smile:

displayMode(FULLSCREEN)
supportedOrientations(PORTRAIT)

-- Main
function setup()
    -- colors
    colors = {
        color(255, 0, 0),    -- red
        color(255, 163, 0), --orange
        color(255, 255, 0),    --yellow
        color(0, 255, 0),    -- green
        color(0, 0, 255),    --blue
        color(192, 0, 255, 255)    --purple
    }
    
    -- create a palette to place brushes on
    palette = {}  
    paletteWidth = WIDTH
    paletteHeiht = WIDTH
    brushWidth = paletteWidth/6
    brushHeight = paletteWidth/6
    startX = brushWidth/2
    startY = brushHeight/2
    counter = 1
    for i=1, 6 do      
        palette[i] = {}
        for j=1, 6 do
            local brushColor = randomColor()  
            palette[i][j] = Brush(paletteWidth/6, vec2(startX,startY), brushColor, counter)
            startX = startX + brushWidth
            counter = counter + 1
        end
        startX = brushWidth/2
        startY = startY + brushHeight
    end
end

function draw()
    background(0)
    for i=1, 6 do
        for j=1, 6 do
            palette[i][j]:draw()
            fill(255,255,255)
            local x = palette[i][j].pos.x
            local y = palette[i][j].pos.y
            text(palette[i][j].ind, x, y)
        end
    end
end

function touched(touch)
    for i=1, 6 do
        for j=1, 6 do
            palette[i][j]:touched(touch)
        end
    end
end

function randomColor()
    local r = math.random(6)
    return colors[r]
end

-- Class Brush
Brush = class()

function Brush:init(s, p, c, index)
    self.pos = p
    self.size = s
    self.col = c
    self.ind = index
end

function Brush:draw()
    pushStyle()
    fill(self.col)
    rectMode(CENTER)
    rect(self.pos.x, self.pos.y, self.size, self.size)
    popStyle()
end

function Brush:hit(point)
    if point.x > (self.pos.x - self.size/2) and
       point.x < (self.pos.x + self.size/2) and
       point.y > (self.pos.y - self.size/2) and
       point.y < (self.pos.y + self.size/2) then
        return true
    end        
    return false
end

function Brush:touched(touch)
    if self:hit(vec2(touch.x, touch.y)) then
        selectedBrushY = self.pos.y
    end
    if touch.state == MOVING then
        if touch.y + brushHeight > selectedBrushY then
            -- do something :)
        elseif touch.y + brushHeight < selectedBrushY then
       -- do something else :)
        end
    end
end

I suggest that your touch function tests for state==BEGAN, which is the start of a touch, if so, then loop through to see which brush, if any, has been touched. If one has been touched, set a variable equal to that brush.

Then, again in the touched function, test if state==MOVING, in which case (assuming the brush variable has been set) you test which square the touch is in (eg subtract bottom left corner position of grid from the touch position and divide by square size to quickly calculate which square you are in. Then you can modify your colours.

Finally, again in touched, if state==ENDED, then set the brush variable to nil.

With something like this, I would add one little thing at a time and test. Events like touch can be tricky things.

@fikabord i liked your board and idea and colors, and you code is clean (but you should use more locals) so here is my little push. This code manages the touch, but you still have to work around:

  • the formula color1 and color2 => color3. I have put a simple addition there. This is not what you want to do.
  • intoduce limitations: in this version you can move wherever you want, not only horizontal and vertical.
    But this is your job, i just wrote a possible way to handle touch, so you have an example to start with
--displayMode(FULLSCREEN) -- ficabord
--supportedOrientations(PORTRAIT)

-- Main
function setup()
    -- colors
    colors = {
        color(255, 0, 0),    -- red
        color(255, 163, 0), --orange
        color(255, 255, 0),    --yellow
        color(0, 255, 0),    -- green
        color(0, 0, 255),    --blue
        color(192, 0, 255, 255)    --purple
    }

    -- create a palette to place brushes on
    palette = {}  
    paletteWidth = math.min(WIDTH,HEIGHT) -- <<<<
    paletteHeiht = paletteWidth -- <<<<
    brushWidth = paletteWidth/6
    brushHeight = paletteWidth/6
    startX = brushWidth/2
    startY = brushHeight/2
    counter = 1
    for i=1, 6 do      
        palette[i] = {}
        for j=1, 6 do
            local brushColor = randomColor()  
            palette[i][j] = Brush(paletteWidth/6, vec2(startX,startY), brushColor, counter)
            startX = startX + brushWidth
            counter = counter + 1
        end
        startX = brushWidth/2
        startY = startY + brushHeight
    end
end

function draw()
    background(0)
    for i=1, 6 do
        for j=1, 6 do
            palette[i][j]:draw()
            fill(255,255,255)
            local x = palette[i][j].pos.x
            local y = palette[i][j].pos.y
            text(palette[i][j].ind, x, y)
        end
    end
end

function touched(touch)
    for i=1, 6 do
        for j=1, 6 do
            palette[i][j]:touched(touch)
        end
    end
end

function randomColor()
    local r = math.random(6)
    return colors[r]
end

-- Class Brush
Brush = class()

function Brush:init(s, p, c, index)
    self.pos = p
    self.size = s
    self.col = c
    self.ind = index
end

function Brush:draw()
    pushStyle()
    fill(self.col)
    rectMode(CENTER)
    rect(self.pos.x, self.pos.y, self.size, self.size)
    popStyle()
end

function Brush:hit(point)
    if point.x > (self.pos.x - self.size/2) and
       point.x < (self.pos.x + self.size/2) and
       point.y > (self.pos.y - self.size/2) and
       point.y < (self.pos.y + self.size/2) then
        return true
    end        
    return false
end

local touchedBrushes = {}
function Brush:touched(touch)
    if not self:hit(vec2(touch.x, touch.y)) then return end
    if touch.state == BEGAN then
        refBrush = self
        print(self.col)
    elseif touch.state == MOVING 
    and self~=refBrush 
    and not touchedBrushes[self] then
        touchedBrushes[self] = true -- remember
        local a = self.col
        local b = refBrush.col
        -- you have to put here the correct formula you want
        local c = color(a.r+b.r, a.g+b.g, a.b+b.b) -- <<<<<<<<<<<<<<
        self.col = c
        print(self.col)
    elseif touch.state == ENDED then
         touchedBrushes = {} -- reset
    end
end

@fickabord have you tried it?

@jmv38 take advantage of the color.blend / color.mix functions instead off addition? You can end up with too much white

@Luatee you are right. But i didnt want to do it all for @fickabord, since he was requesting for help about ‘touch’ only. He can follow your advice. But he doesnt seem to check the forum very often. He is not used to our incredible reactivity, lol!

Ahh okay, well maybe he’ll become one of us :))

@jvm38 sorry been busy preparing for the holiday and finishing stuff at work. Thanks for all the good feedback and ill post something once ive given it a try

hi guys
I recently pricken up my codea project after a few months of too much work. Picking up were I left things I’ve spent days now trying to understand what’s wrong without any success. most attempts are now random :confused: I would really really appriciate with a cherry on top if I would get some help solving the current problem.

this is how its supposed to work.
get 3 in a row by mixing colors. colors can be mixed by either adding or “substracting”.
adding = add blue to yellow to create green.
substracting = substract green from blue to create yellow ( this is not really a natural thinking process so this will change but first another problem. must be fixed)

the problem
when you match 3 or more in a row those brushes are removed, you score some points and the abort brushes are supposed to fall down. problem is brushes further up are also falling down creating a hole in the palette.

the design for handling falling brushes is this. step through each column and look for brushes which are supposed to be removed, ie 3 in a row. when a scoring brush is found find the next brush which is not supposed to be removed. the brush which is supposed to be removed copies the color and position of the non-scoring brush. then animate by tweening this brush falling down to its original position. after the animations randomize new colors for brushes with index 37-72. These brushes should nerver be visible. and are just drawn for debug purposes.

when the animation of falling brushes are done all brushes should be back at their original positions.

the code can be found here
https://gist.github.com/fikabord/3c343bc011525e45cd2b

-- Main
function setup()
    -- colors
    colors = {
        color(255, 0, 0),    -- red
        color(255, 163, 0), --orange
        color(255, 255, 0),    --yellow
        color(0, 255, 0),    -- green
        color(0, 0, 255),    --blue
        color(192, 0, 255),    --purple
        color(180, 180, 180)    --gray
    }

    -- create a palette to place brushes on
    palette = {}  
    paletteWidth = math.min(WIDTH,HEIGHT) -- <<<<
    paletteHeiht = paletteWidth -- <<<<
    brushWidth = paletteWidth/6
    brushHeight = paletteWidth/6
    startX = brushWidth/2
    startY = brushHeight/2
    counter = 1
    for i=1, 6 do      
        palette[i] = {}
        for j=1, 6 do
            local brushColor = randomColor()  
            palette[i][j] = Brush(paletteWidth/6, vec2(startX,startY), brushColor, counter)
            startX = startX + brushWidth
            counter = counter + 1
        end
        startX = brushWidth/2
        startY = startY + brushHeight
    end

    playerScore = 0
end

function draw()
    background(0)
    for i=1, 6 do
        for j=1, 6 do
            palette[i][j]:draw()
            fill(255,255,255)
            local x = palette[i][j].pos.x
            local y = palette[i][j].pos.y
            text(palette[i][j].ind, x, y)
        end
    end
    text("score = "..playerScore, WIDTH/2, HEIGHT*0.9)
end

function touched(touch)
    for i=1, 6 do
        for j=1, 6 do
            palette[i][j]:touched(touch)
        end
    end
end

function randomColor()
    local r = math.random(6)
    return colors[r]
end

-- check for matched color combos
function checkMixedColors()
    -- start check which rows have 3 or more colors in a row
    for i=1, 6 do
        for j=1, 6 do
            local c = palette[i][j].col -- color to match with
            -- check for 5 in a row
            if palette[i][j+1] ~= nil and palette[i][j+2] ~=nil and palette[i][j+3] ~=nil and palette[i][j+4] ~=nil then
                if c == palette[i][j+1].col and c == palette[i][j+2].col and c == palette[i][j+3] and c == palette[i][j+4] then
                    local colorCombo = {}
                    colorCombo = {palette[i][j].ind, palette[i][j+1].ind, palette[i][j+2].ind, palette[i][j+3].ind, palette[i][j+4].ind}
                    resolveCombo(colorCombo)
                end
            end
            -- check for 4 in a row
            if palette[i][j+1] ~= nil and palette[i][j+2] and palette[i][j+3] ~=nil then
                if c == palette[i][j+1].col and c == palette[i][j+2].col and c == palette[i][j+3] then
                    local colorCombo = {}
                    colorCombo = {palette[i][j].ind, palette[i][j+1].ind, palette[i][j+2].ind, palette[i][j+3].ind}
                    resolveCombo(colorCombo)
                end
            end
            -- check for 3 in a row
            if palette[i][j+1] ~= nil and palette[i][j+2] ~=nil then
                if c == palette[i][j+1].col and c == palette[i][j+2].col then
                    local colorCombo = {}
                    colorCombo = {palette[i][j].ind, palette[i][j+1].ind, palette[i][j+2].ind}
                    resolveCombo(colorCombo)
                end
            end
        end
    end
    -- implement colums check
    for i=1, 6 do
        for j=1, 6 do
            local c = palette[i][j].col -- color to match with
             if palette[i+1][j] ~= nil and palette[i+2][j] ~=nil then
                if c == palette[i+1][j].col and c == palette[i+2][j].col then
                    local colorCombo = {}
                    colorCombo = {palette[i][j].ind, palette[i+1][j].ind, palette[i+2][j].ind}
                    resolveCombo(colorCombo)
                end
            end
        end
    end  
    -- score combos
    score()
end

-- set which brushes generate a score
function resolveCombo(combo)
    for counter=1, #combo do
        for i=1, 6 do
            for j=1, 6 do
                if palette[i][j].ind == combo[counter] then
                    palette[i][j].score = true
                end
            end
        end
    end
end

function score()
    for i=1, 6 do
        for j=1, 6 do
            if palette[i][j].score == true then
                palette[i][j].col = randomColor()
                playerScore = playerScore + 1
            end
        end
    end
    --set all score flags to false
    for i=1, 6 do
        for j=1, 6 do
            palette[i][j].score = false
        end
    end
end
-- Class Brush
Brush = class()

function Brush:init(s, p, c, index)
    self.pos = p
    self.size = s
    self.col = c
    self.ind = index
    self.score = false
end

function Brush:draw()
    pushStyle()
    fill(self.col)
    rectMode(CENTER)
    rect(self.pos.x, self.pos.y, self.size, self.size)
    popStyle()
end

function Brush:hit(point)
    if point.x > (self.pos.x - self.size/2) and
       point.x < (self.pos.x + self.size/2) and
       point.y > (self.pos.y - self.size/2) and
       point.y < (self.pos.y + self.size/2) then
        return true
    end        
    return false
end

function Brush:setMixColor(startColor, mixColor)
    if startColor == colors[1] then -- color = red
        if mixColor == colors[1] then -- color = red
            return colors[1] -- color = red
        elseif mixColor == colors[2] then -- color = orange
            return colors[3] -- color = yellow
        elseif mixColor == colors[3] then -- color = yellow
            return colors[2] -- color = orange
        elseif mixColor == colors[4] then -- color = green
            return colors[7] -- color = gray
        elseif mixColor == colors[5] then -- color = blue
            return colors[6] -- color = purple
        elseif mixColor == colors[6] then -- color = purple
            return colors[7] -- color = gray
        elseif mixColor == colors[7] then -- color = gray
            return colors[7] -- color = gray
        end
    elseif startColor == colors[2] then -- color = orange
        if mixColor == colors[1] then -- color = red
            return colors[3] -- color = yellow
        elseif mixColor == colors[2] then -- color = orange
            return colors[2] -- color = orange
        elseif mixColor == colors[3] then -- color = yellow
            return colors[1] -- color = red
        elseif mixColor == colors[4] then -- color = green
            return colors[7] -- color = gray
        elseif mixColor == colors[5] then -- color = blue
            return colors[7] -- color = gray
        elseif mixColor == colors[6] then -- color = purple
            return colors[7] -- color = gray
        elseif mixColor == colors[7] then -- color = gray
            return colors[7] -- color = gray
        end
    elseif startColor == colors[3] then -- color = yellow
        if mixColor == colors[1] then -- color = red
            return colors[2] -- color = orange
        elseif mixColor == colors[2] then -- color = orange
            return colors[2] -- color = orange
        elseif mixColor == colors[3] then -- color = yellow
            return colors[1] -- color = red
        elseif mixColor == colors[4] then -- color = green
            return colors[5] -- color = blue
        elseif mixColor == colors[5] then -- color = blue
            return colors[4] -- color = gren
        elseif mixColor == colors[6] then -- color = purple
            return colors[7] -- color = gray
        elseif mixColor == colors[7] then -- color = gray
            return colors[7] -- color = gray
        end
    elseif startColor == colors[4] then -- color = green
        if mixColor == colors[1] then -- color = red
            return colors[7] -- color = gray
        elseif mixColor == colors[2] then -- color = orange
            return colors[7] -- color = gray
        elseif mixColor == colors[3] then -- color = yellow
            return colors[5] -- color = blue
        elseif mixColor == colors[4] then -- color = green
            return colors[4] -- color = green
        elseif mixColor == colors[5] then -- color = blue
            return colors[3] -- color = yellow
        elseif mixColor == colors[6] then -- color = purple
            return colors[7] -- color = gray
        elseif mixColor == colors[7] then -- color = gray
            return colors[7] -- color = gray
        end
    elseif startColor == colors[5] then -- color = blue
        if mixColor == colors[1] then -- color = red
            return colors[6] -- color = purple
        elseif mixColor == colors[2] then -- color = orange
            return colors[7] -- color = gray
        elseif mixColor == colors[3] then -- color = yellow
            return colors[4] -- color = green
        elseif mixColor == colors[4] then -- color = green
            return colors[3] -- color = yellow
        elseif mixColor == colors[5] then -- color = blue
            return colors[5] -- color = blue
        elseif mixColor == colors[6] then -- color = purple
            return colors[1] -- color = red
        elseif mixColor == colors[7] then -- color = gray
            return colors[7] -- color = gray
        end
    elseif startColor == colors[6] then -- color = purple
        if mixColor == colors[1] then -- color = red
            return colors[5] -- color = blue
        elseif mixColor == colors[2] then -- color = orange
            return colors[7] -- color = gray
        elseif mixColor == colors[3] then -- color = yellow
            return colors[7] -- color = gray
        elseif mixColor == colors[4] then -- color = green
            return colors[7] -- color = gray
        elseif mixColor == colors[5] then -- color = blue
            return colors[1] -- color = red
        elseif mixColor == colors[6] then -- color = purple
            return colors[6] -- color = purple
        elseif mixColor == colors[7] then -- color = gray
            return colors[7] -- color = gray
        end
    elseif startColor == colors[7] then -- color = gray
        return colors[7] -- color = gray
    end
end

local touchedBrushes = {}
local brushAlreadyMixed = false
function Brush:touched(touch)
    --local brushAlreadyMixed = false
    if not self:hit(vec2(touch.x, touch.y)) then return end
    if touch.state == BEGAN then
        refBrush = self
    elseif touch.state == MOVING 
    and self~=refBrush
    and not touchedBrushes[self] then
        touchedBrushes[self] = true -- remember
        if brushAlreadyMixed == false then
            if touch.y > refBrush.pos.y + brushHeight/2 then
                self.col = Brush:setMixColor(refBrush.col, self.col)
                brushAlreadyMixed = true
            elseif touch.y < refBrush.pos.y - brushHeight/2 then
                self.col = Brush:setMixColor(refBrush.col, self.col)
                brushAlreadyMixed = true
            elseif touch.x < refBrush.pos.y - brushWidth/2 then
                self.col = Brush:setMixColor(refBrush.col, self.col)
                brushAlreadyMixed = true
            elseif touch.x > refBrush.pos.y + brushWidth/2 then
                self.col = Brush:setMixColor(refBrush.col, self.col)
                brushAlreadyMixed = true
            end
        end
  elseif touch.state == ENDED then
         touchedBrushes = {} -- reset
        brushAlreadyMixed = false
        checkMixedColors()
    end
end

@jvm38 @Luatee I finally had some time to sit down and do some coding.
Added:

  • now mix correct colors (will change depening on how complex the design turns out)
  • scoring (simple to get some feedback)
  • removes 3 or more rows of the same color. Does not work for colums. Ill get to that later.
  • limit to just mix colors above, below, left or right of selected brush.

Its getting close to a first playable version but some things are missing and id like to ask for guidance here.

  • scoring +3 brushes in a row works great but using the same code to check for colums does work at all. What am I missing here? Been rewriting the code a couple of times but always the same error.

  • id like to add an animation when scoring making the blocks fall down were you created a hole like most 3 in a row games. Any advice on were to start?

  • there is a bug which prevents brush with id 7 to mix with brush id 8. Really odd!

Btw, you need to remove the colums check part of the code to make it work. Starting at row 97. I kept it in there since i had some questions about it.

Also, thanks for helping me with my previous questions. With the help i would not have been able to take it this far.

Doing some more testing it turns out brush id 7, 14, 21, 28, 35 cannot change color to the brush to the right. This is a wierd pattern and i dont understand why this is the case. Thoughts?

@fikabord - something I noticed, the function Brush:setMixColor(startColor, mixColor) can be rewritten as …

function Brush:setMixColor(startColor, mixColor)
    return Mixed[startColor][mixColor]
end

…where Mixed is a 2D table containing the colour resulting from mixing any two colours. This gets rid of a mountain of code.

@fikabord - I would also suggest rewriting the code that checks for 3 or more touches. There is no need to check for 5, then 4, then 3 in a row, you can do it all at once, like so.

    for i=1, 6 do 
        local c,count,maxSoFar,maxColour=nil,0,0,0 
        for j=1, 6 do  
            if palette[i][j] then --only count filled cells
                if c==nil then --we're not counting yet, start with this colour
                    c=palette[i][j].col 
                    count=1  
                elseif palette[i][[j]==c then --keep counting while colour is the same
                    count=count+1
                else  --colour has changed, update max. in a row 
                    if count>maxSoFar then maxSoFar=count maxColour=c end
                    c=palette[i][j].col  --restart count with new colour
                    count=1  
                end
            else  --blank cell, check if we have been counting
                if count>0 then --update max. in a row, if we were counting
                    if count>maxSoFar then maxSoFar=count maxColour=c end
                    c=nil --reset c to blank
                    count=0  
                end
            end
        end
        --put code here to do something with the results
        --maxSoFar will tell you how many were in a row
        --maxColour will be the colour
    end

or something like it (not tested)…

You can then repeat the code with i and j reversed. My preference would also be to use r and c instead of i and j, so it is easier to figure which are coloumns and which are rows!

@ignatz i knew there was a bettet way of writing that code :slight_smile: guess experiance will help avoiding this. Ill have a look at the code and will report back later.

Thank you very much

wow after just making a program to detect touchs on a mesh. It was awesome to read someones elses touch code and understand the it the first read. :slight_smile:

@ignatz with the help of your code I rewrote the matching function to this. Works for both colums and rows. Thank you very much. Feel free to comment if the code is funky :slight_smile:

-- check for matched color combos
function checkMixedColors()
    for i=1, 6 do      
        local rowCount = 0
        local rowMaxCount = 1
        local rowCombo = {}
        local rowC = nil
        local columCount = 0
        local columMaxCount = 1
        local columCombo = {}
        local columColor = nil
        for j=1, 6 do
            -- check for +3 in a row in rows first
            if palette[i][j].col == rowColor then
                rowCount = rowCount + 1             
                if rowCount > rowMaxCount then
                    rowMaxCount = rowMaxCount + 1
                    rowCombo[#rowCombo+1] = palette[i][j].ind
                end
            else
                rowColor = palette[i][j].col
                rowCount = 1
                if rowMaxCount == 1 then
                    rowCombo[1] = palette[i][j].ind
                end
            end
            -- then check for +3 in a row in colums     
            if palette[j][i].col == columColor then
                columCount = columCount + 1             
                if columCount > columMaxCount then
                    columMaxCount = columMaxCount + 1
                    columCombo[#columCombo+1] = palette[j][i].ind
                end
            else
                columColor = palette[j][i].col
                columCount = 1
                if columMaxCount == 1 then
                    columCombo[1] = palette[j][i].ind
                end
            end          
        end
        -- if we find +3 in a row then mark witch brushes to score
        if rowMaxCount >= 3 then
            for i=1, #rowCombo do
                resolveCombo(rowCombo)
            end
        end
        if columMaxCount >= 3 then
            for i=1, #columCombo do
                resolveCombo(columCombo)
            end
        end
    end
    score()
end

@fikabord since your project is spread over multiple tabs, it woul be simpler for us to get vis a single gist or an entry to CC.

@jvm38 ok, ill take a look at that