[SOLVED] How to animate cups simultaneously like in the shell game or cups and ball game?

Maybe I’m misunderstanding coroutines but I’m trying to have it so several different objects can be animated simultaneously and saw the post about coroutines, but the question is how exactly do you activate one in a funciton?

local BOOKSHELF_POSITIONS_X = {480, 960,1440}

local function bookshelf_animate(url, property, playback, to, easing, duration, delay, playback)
	local co = coroutine.running()
	assert(co, "You must call this function from within a coroutine")
	go.animate(url, property, playback, to, easing, duration, delay, function()
		coroutine.resume(co)
	end,playback)
	coroutine.yield()
end

coroutine.wrap(function()
	bookshelf_animate("/bookshelf_1", "position.x", go.PLAYBACK_ONCE_FORWARD, BOOKSHELF_POSITIONS_X[1], go.EASING_INQUART, 1)
	bookshelf_animate("/bookshelf_2", "position.x", go.PLAYBACK_ONCE_FORWARD, BOOKSHELF_POSITIONS_X[2], go.EASING_INQUART, 1)
	bookshelf_animate("/bookshelf_3", "position.x", go.PLAYBACK_ONCE_FORWARD, BOOKSHELF_POSITIONS_X[3], go.EASING_INQUART, 1)
end)()

local function set_book_behind_bookshelf()
	BOOKSHELF_POSITIONS_X = minigames.shuffle(BOOKSHELF_POSITIONS_X)

	-- how to call the coroutine?
end

Thanks to anyone in advance who replies.

In your code the animations will be run one after the other. If you don’t want this then why bother with a coroutine to synchronize things? You might as well call go.animate() three times.

I think the problem here is that you saw the post about coroutines, but you don’t need that here.

Look at this example to see how to chain animation.

local function mournloop(self) --this animation happens when it is triggered
go.animate("face", "position.x", go.PLAYBACK_LOOP_PINGPONG, 5, go.EASING_INOUTQUAD, 1.2)
end

local function mourn(self) --these animations happen first, and they happen at the same time
go.animate("body", "euler.z", go.PLAYBACK_ONCE_FORWARD, -5, go.EASING_OUTSINE, 1)
go.animate("face", "position.x", go.PLAYBACK_ONCE_FORWARD, 11, go.EASING_OUTSINE, 1,0, mournloop) --this animation triggers the mournloop function when it has finished.
end

Ah I see, thanks. The reason why I thought I might have needed coroutines was because I was trying to program the shell game or cups and ball game, so I thought I would’ve needed to find a way to play the ‘cups’ animating simultaneously (i.e. swapping positions).

There’s something I’m slightly confused about, why does it only swap the positions of the bookshelves once? From what I’m debugging it’s running what’s inside of the timer.delay function first, then checking whether the timer’s still active, so am I supposed to reverse the order of how they’re called? But then how would you create the timer?

local BOOKSHELF_POSITIONS_X = {480, 960,1440}

local function set_book_behind_bookshelf()
	BOOKSHELF_POSITIONS_X = minigames.shuffle(BOOKSHELF_POSITIONS_X)

	go.animate("/bookshelf_1", "position.x", go.PLAYBACK_ONCE_FORWARD, BOOKSHELF_POSITIONS_X[1], go.EASING_INQUART, 1)
	go.animate("/bookshelf_2", "position.x", go.PLAYBACK_ONCE_FORWARD, BOOKSHELF_POSITIONS_X[2], go.EASING_INQUART, 1)
	go.animate("/bookshelf_3", "position.x", go.PLAYBACK_ONCE_FORWARD, BOOKSHELF_POSITIONS_X[3], go.EASING_INQUART, 1)
end

local function bookshelf_cancel_swap()
	go.cancel_animations("/bookshelf_1")
	go.cancel_animations("/bookshelf_2")
	go.cancel_animations("/bookshelf_3")
end

function init(self)
	book_index = math.random(1,3)

	bookshelf_swap_pos = timer.delay(5, false, function()
		timer.cancel(bookshelf_swap_pos)
		bookshelf_cancel_swap()
	end)

	while timer.trigger(bookshelf_swap_pos) do
		set_book_behind_bookshelf()
	end
	
	minigames.set_countdown_timer()
end

I’m not entirely sure what you are trying to do. That while loop doesn’t look ok for instance. You really shouldn’t have any blocking loops like that anywhere in your code.

Perhaps if you try to explain what the goal is?

I should’ve done that from the beginning, that was my bad. So I’m trying to replicate the shell game or the cups and ball game. Right now I’m trying to figure out how to handle the cups moving, or in my case bookshelves, my idea was to shuffle the positions then pass that into each bookshelf and then animate them simultaneously. This should start when the collection is initalialised and should only last for a couple of seconds, that’s the reason why I was thinking of using a while loop and a timer, the timer would cancel the animations so the swapping positions wouldn’t happen and while the timer was active the swapping positions would occur.

Am I going about the logic in the wrong or unoptimal way?

I think I’d try something like this:

local BOOKSHELF_POSITIONS_X = {480, 960,1440}

-- shuffle/swap the positions of the bookshelves once
-- send a message when animation has finished
local function shuffle_bookshelves()
	BOOKSHELF_POSITIONS_X = minigames.shuffle(BOOKSHELF_POSITIONS_X)

	go.animate("/bookshelf_1", "position.x", go.PLAYBACK_ONCE_FORWARD, BOOKSHELF_POSITIONS_X[1], go.EASING_INQUART, 1, 0)
	go.animate("/bookshelf_2", "position.x", go.PLAYBACK_ONCE_FORWARD, BOOKSHELF_POSITIONS_X[2], go.EASING_INQUART, 1, 0)
	go.animate("/bookshelf_3", "position.x", go.PLAYBACK_ONCE_FORWARD, BOOKSHELF_POSITIONS_X[3], go.EASING_INQUART, 1, 0, function()
		msg.post("#", "bookshelves_shuffled")
	end)
end

function init(self)
	self.shuffle_count = 3 			-- number of times to shuffle
	shuffle_bookshelves()
end

function on_message(self, message_id, message, sender)
	-- message sent when the bookshelves have been shuffled once
	if message_id == hash("bookshelves_shuffled") then
		-- decrease the shuffle count and check if we should shuffle more or if we are done
		self.shuffle_count = self.shuffle_count - 1
		if self.shuffle_count > 0 then
			shuffle_bookshelves()
		else
			print("Bookshelves are shuffled!")
		end
	end
end
1 Like

Thank you britzl, that’s what I wanted to achieve.

Is it possible to exclude certain values from shuffling? What I mean by that is that the positions could be shuffled but then have the same values as before, so {440, 960, 1440} shuffled could still be {440, 960, 1440}, at least from what I notice because although I’ve set my shuffle_count to be 10 sometimes it looks like it doesn’t actually shuffle at all. So I think the key would be n can’t be equal to k.

function M.shuffle(t) 
	for n=1,#t do
		local k = math.random(#t)
		t[n], t[k] = t[k], t[n]
	end
	return t
end

I believe you are talking about two different things here.

Yes you can do it by removing values from a table(temp table perhaps)

math.random might cause this. You must call it a few times to discard the same numbers:

or you can use my extension:

This is the key here. My advice would be that you shuffle the bookcases in a pattern (non random) but you do it a random number of times.

Oh I see, thank you for the idea, I also looked up another possible solution too after realising there probably is one out there: javascript - Random integer in a certain range excluding one number - Stack Overflow, so this would also probably work, just change the language. Ah alright, so I did a bit of modifications and got interesting results, which is being one of the bookshelves don’t move at all. Also clojure - Generate a random number, excluding a single number - Stack Overflow

function M.shuffle(t) 
	for n=1,#t do
		local k = n
		while k == n do 
			k =math.random(#t)
		end
		t[n], t[k] = t[k], t[n]
	end
	return t
end

Hm, seems to work better. Oh yea it absolutely works better.

Thank you, will install this.

Hm, now I’m curious as to why even though I’m clicking on the right bookshelf it’s still considered wrong. :thinking: I’m passing the right id and the right index, so I’m confused.

-- minigame script
function on_message(self, message_id, message, sender)
	if message_id == hash("check_book_index_location") then
		if message.bookshelf_id == "/bookshelf_1" and book_index == 1 or message.bookshelf_id == "/bookshelf_2" and book_index == 2  or message.bookshelf_id == "/bookshelf_3" and book_index == 3 then
			label.set_text("#label", "RIGHT")
			msg.post("#", "found_book", {correct_book_location = true})
		else
			label.set_text("#label", "WRONG")
			msg.post("#", "found_book", {correct_book_location = false})
		end
	end
end

-- bookshelf script
function on_message(self, message_id, message, sender)
	if message_id == hash("interact") then
		msg.post("/main#find_book_shell_game", "check_book_index_location", { bookshelf_id = go.get_id()})
	end
end



bookshelf_id is a hash, not a string!

You need to compare with the hashed value:

message.bookshelf_id == hash("/bookshelf_1")
1 Like

Oh I see, thank you britzl!