(coding) How to make user Interactive functions in a row?

Discuss any general programming issues here
Bugala
Posts: 759
Joined: Sun Feb 14, 2010 7:11 pm

(coding) How to make user Interactive functions in a row?

Post by Bugala » Fri Jul 07, 2017 2:44 pm

I am hopefully asking for coding tip from someone.

What I have many times encounted when programming, upon which i still dont have good programming solution, is following:

I might have

Code: Select all

function Myfunction()
func1()
func2()
func3()
endfunction
So far so good and simple, except, problem is that each of these functions reguire some interactivity from user, which means they cant be executed this simply, since by default they be executed in a row, but actually program needs to go to wait for users interaction (for example to push "yes" or "no" button) which makes the real execution much harder.


As close to real example, I am taking a board game which is based upon cards. Depending upon cards depends what happens next in game.

So lets suppose there are two different cards:

1. Card which makes player be able to do three different things: 1. put cube on board, 2. Draw card from other players hand, 3. Discard three cards from your hand.

2. Card which lets player do two thing: 1. Draw two new cards, 2. Discard one card from hand.


Cards like this would theoretically be extremely easy to add to the program, and even make more, for all I would need to do is following:

Code: Select all

function card1()
PutCubeOnBoard()
DrawCardFromAnotherPlayer()
DiscardCards(3)
endfunction

function card2()
DrawCard(1)
DiscardCards(1)
endfunction
And then in game I would just look which card is drawn, and tell it to either execute function "card1()" or "card2()"

However, in practice this doesnt work, since already in card1 case in first case I already need to "halt" the program to wait for player to choose where on board he wishes to put that cube. Then only after he have done that, it should return back to that "Card1()" function and continue to next line "DrawCardFromAnotherPlayer()" in which program would once again need to halt until player decides from which player he is going to steal that card from.



I do have one solution to this, which I personally dont think is very good one, but roughly it goes like this:

Code: Select all

Function card1()
If state = 1 
   PutCubeOnBoard()
elseif state = 2
   DrawCardFromAnotherPlayer()
elseif state = 3
   DiscardCards(3)
endif
endfunction

There are multiple problems with this one. First of all, i have to have some way for it to keep looping back to this "card1()" function when it is still not done. Then I also need to take care to change the state of this at end of for example when placing the cube, which again can be a problem since several cards can be using the same function (in this example both card1() and card2() have DiscardCards() function), and on top of that, these same functions might be used even by some who dont use this state system at all.

Or, even if i do general fix of putting after everyaction simply "state = state + 1", then i still get the problem that what if one of the functions goes on to execute another function. As example, lets say when player puts his cube to board, maybe that placing of cube activates another DiscardCard() function. In this case "state = state + 1" would happen twice before getting back to card1() function, and "DrawCardFromAnotherPLayer()" might get skipped.


Hence. Does anyone have any better solution to this? I am myself thinking if maybe some sort of Observer Pattern would be the solution? (I have just recently read about Observer Pattern, but havent tried it ever)

User avatar
Allanon
Posts: 461
Joined: Sun Feb 14, 2010 7:53 pm
Location: Italy
Contact:

Re: (coding) How to make user Interactive functions in a row

Post by Allanon » Fri Jul 07, 2017 6:48 pm

I Bugala,
I think I've roughtly understood you problem, which is almost the same problem encountered coding a game which need to loop continuosly to perform actions (like to animate the background for example).

The way to go, from my point of view, is to use game states, something like this pseudo code:

Code: Select all

functions definition...

init_code...

init_game_state... (for example : game_state = "waiting for player 1 to move")

SetInterval(your-looping-function...)
InstallEventHandler(if you need to raise user input this way...)

Repeat
   WaitEvent()
Forever
<your-looping-function> is a function that should check user input (if you haven't used InstallEventHandler), update graphics and perform actions looking at the current game state.
Your user-input routines could change the game state according to your game machanics.

If you can explain better the game mechanics I can give you a better help :)
----------------------------
[Allanon] Fabio Falcucci
AMC - Creative Development // Docs Site // Support Forum
Support me on Patreon for Hollywood libraries!

Bugala
Posts: 759
Joined: Sun Feb 14, 2010 7:11 pm

Re: (coding) How to make user Interactive functions in a row

Post by Bugala » Fri Jul 07, 2017 8:45 pm

Yes, you understood the idea right. I forgot to explain the very important part that the whole point is that the show must go on at same time as players input is needed.

So as example, like you gave, while card1() function is being executed with idea of being executed step by step, at same time the background is supposed to keep moving.

Hence the simple problem becomes a complex problem. That instead of being able to execute each function one by one and stepping to next one after each, in practice you first go start executing the first function, then jump back to main loop to update the background movement and do so until user does something.

When user does something, then you are supposed to jump back to that card1() function and skip the first function, and instead execute the next function.

And then in second function once again you jump back to mainloop until user interacts again, at which point you should once again jump back to card1() loop, and now skip first two loops and continue from the last function.


Only way I have been able to do that so far have been using "states" like you were suggesting too, but i have found it to be somewhat messy however.

Hence I am hoping for some better solution.

User avatar
Allanon
Posts: 461
Joined: Sun Feb 14, 2010 7:53 pm
Location: Italy
Contact:

Re: (coding) How to make user Interactive functions in a row

Post by Allanon » Sat Jul 08, 2017 8:38 am

Managing game states is not too complex when you are used to them, here is a more detailed example (pseudo code, and pseudo game mechanics since I've not understood how your game should work :) ):

So suppose that each player must accomplish the following actions:
- player 1 : play a card
- player 2 : play a card
- game : select which card/player win the turn
- game : check if a player win the game
- game : give two cards to each player
- player 1 : discard one card
- player 2 : discard one card
--- go to the top

Code: Select all

Const #STATE_PL1_PLAY = 0
Const #STATE_PL2_PLAY =1
Const #STATE_PL1_DISCARD = 2
Const #STATE_PL2_DISCARD = 3
Const #STATE_GAME_CHECK_CARDS = 4
Const #STATE_GAME_CHECK_WINNER = 5
Const #STATE_GAME_DRAW_CARDS = 6

Function CheckPlayer1_Play()
  ; Check if player 1 click on a card to put in play
  ...
  ; When the input is complete you have to set
  ; gameState = #STATE_PL2_PLAY
EndFunction
Function CheckPlayer2_Play()
  ; Check if player 2 click on a card to put in play
  ...
  ; When the input is complete you have to set
  ; gameState = #STATE_GAME_CHECK_CARDS
EndFunction
Function GamePlay_CheckCardsOnTable()
  ; Check which player has win the turn
  ...
  ; When the function ends you have to set
  ; gameState = #STATE_GAME_CHECK_WINNER
EndFunction
Function GamePlay_CheckWinner()
  ; Check if there is a winner
  ...
  ; When the function ends you have to set
  ; gameState = #STATE_GAME_DRAW_CARDS
EndFunction
Function GamePlay_DrawCards()
  ; Give two random cards to each player
  ...
  ; When the function ends you have to set
  ; gameState = #STATE_PL1_DISCARD
EndFunction
Function CheckPlayer1_DIscard()
  ; Check if player 1 click on a card to discard
  ...
  ; When the input is complete you have to set
  ; gameState = #STATE_PL2_DISCARD
EndFunction
Function CheckPlayer2_Discard()
  ; Check if player 2 click on a card to discard
  ...
  ; Returning to the first state
  ; gameState = #STATE_GAME_PL1_PLAY
EndFunction
Function UpdateGraphics()
  ; This function is called to update the graphics by an interval function
  ...
EndFunction

; Put all these functions into a table
Global state_funcs =
  { [#STATE_PL1_PLAY] = CheckPlayer1_Play,
    [#STATE_PL2_PLAY] = CheckPlayer2_Play,
    [#STATE_PL1_DISCARD] = CheckPlayer1_Discard,
    [#STATE_PL2_DISCARD] = CheckPlayer2_Discard,
    [#STATE_GAME_CHECK_CARDS] = GamePlay_CheckCardsOnTable,
    [#STATE_GAME_CHECK_WINNER] = GamePlay_CheckWinner,
    [#STATE_GAME_DRAW_CARDS] = GamePlay_DrawCards }

; Now set the starting state
Global gameState = #STATE_PL1_PLAY

Function RunGame()
  ; This function is called to execute a function according to the current game state
  
  state_funcs[gameState]()
EndFunction

; Setup an interval function to update your background each 15ms
SetInterval(1, UpdateGraphics, 15)

; Setup an interval function to call a function according to the current game state (every 10ms)
SetInterval(2, RunGame, 10)

Repeat
   WaitEvent()
Forever
The above code supposes that you check the input directly into the PL1 and PL2 functions, for example you can check if a player click on a layer representing each card or button. Ofcourse you shouldn't block the functions flow waiting for user input, you should check input using IsLeftDown(), IsKeyDown(), and so on...
You could also detect user input using EventHandlers, in this case the implementation is slightly different because you should check the game state in the function that have raised the event (for example when a mouse click has been detected).

Let me know if you need more details :)
----------------------------
[Allanon] Fabio Falcucci
AMC - Creative Development // Docs Site // Support Forum
Support me on Patreon for Hollywood libraries!

Bugala
Posts: 759
Joined: Sun Feb 14, 2010 7:11 pm

Re: (coding) How to make user Interactive functions in a row

Post by Bugala » Sat Jul 08, 2017 10:30 am

What I mean by thinking it not being very good comes out already on your example.

For what I like to do is to have simply one "CheckPlayer_Play( n )" function, but when using stgate system like that, you are now either having to write one for each player so the state changes to right thing, or you have to use:

Code: Select all

if n = 1 then gamestate = #STATE_PL2_PLAY
elseif n=2 then gamestate = #STATE_PL3_PLAY
...
elseif n=100 then gamestate = #STATE_GAME_CHECK_CARDS
Sure you could use simply the number change of the state. like "gamestate = 3 + n", but then the readability of code suffers.

Another thing is the reusability of code.

This example of yours works as long as the flow of program is always same.

But what I encounter all the time is by using the card game example again, that there are for example total of 9 different things to do in game (put cube on board, remove cube from board, get new cubes, steal a card, play card from hand...).

Now then the cards play in such way, that they always have 3 of those things in one card. Card could be for example "1. put cube on board, 2. steal a card, 3. play card from hand".

However, the idea is that each card has 3 of those nine things, and they can be any of those 9 things. Hence next card could be "1. remove cube from board, 2. put cube on board, 3. steal a card"

So as you can see, those cards have two functions that both are using ("put cube on board" and "steal a card"). Hence when you keep using that gamestate thing to change it, you have to take into calculation that you have to have some way to know which card it is using currently, as the next function to be used would be different, and hence the gamestates should be too.

This is the problem I see with gamestates that it easily becomes very easy to get bugs as you either do a whole load of code, like own function of "put cube on board" for each card (like you had own function for each "checkplayer_play"), or then there are several gamestates overlapping and it makes easy to get a bug that way.

As example, you can just follow the game state based upon what function is going on and when it ends, but you need another state to tell which card is being played etc.

It is even worse since "put cube on board" could also be normal option for a player. That say that player can either do one of regular options, like put cube on board, or he could be playing a card from hes hand that also lets him put cube on board, and then there could be even events, like when you put enough cubes on some certain location, maybe you are granted a chance to "put cube on board" again as a bonus.

User avatar
Allanon
Posts: 461
Joined: Sun Feb 14, 2010 7:53 pm
Location: Italy
Contact:

Re: (coding) How to make user Interactive functions in a row

Post by Allanon » Sat Jul 08, 2017 11:28 am

Well, there several ways to implement such mechanics, and which is the best depends from the game type and how you are used to code.
What I've showed is a possible schema subject to any change you may need to apply and not a definitive implementation.
For example, when you are saying:
Bugala wrote: For what I like to do is to have simply one "CheckPlayer_Play( n )" function, but when using stgate system like that, you are now either having to write one for each player so the state changes to right thing, or you have to use:
You could replace the two specific CHECK_PLAYER function with one, more generic CheckPlay func.
This will need of course two more variables that indicates for which player you are waiting (something like "ActivePlayer=1", for example) and a table where you have to store each player data (cards owned, points, etc...) so that you can index the players table with the "ActivePlayer" variable.
When the last player has played you can switch the game state so that you are able to avoid this:
Sure you could use simply the number change of the state. like "gamestate = 3 + n", but then the readability of code suffers.
because as you said readibility will suffer.
Another thing is the reusability of code.
This example of yours works as long as the flow of program is always same.

But what I encounter all the time is by using the card game example again, that there are for example total of 9 different things to do in game (put cube on board, remove cube from board, get new cubes, steal a card, play card from hand...).
Without going too deep into details you can easily use conditional code to decide what is the next allowed game state to set.
If your game machanics are complex I suggest you to write down a flow diagram to clearly states how the game should flow, you have to take into account every possible action players can take and how the game flow should change.
When you have a clear diagram flow you can easily transform it into coding blocks, at least is what I do when things get hard to track down (for ecample HGui event handling was a big challenge), I was able to build non blocking requester using something similar to what I've explained.
This is the problem I see with gamestates that it easily becomes very easy to get bugs as you either do a whole load of code, like own function of "put cube on board" for each card (like you had own function for each "checkplayer_play"), or then there are several gamestates overlapping and it makes easy to get a bug that way.
Yes, that's easy to be trapped by a subdle bug using game states, but if you want to use non-blocking code I think you have few options, and that's why I've developed a debug library to track down code flow. This helped me a lot during the development of complex apps.
It's a libary that can output messages to the console or to a file, you can define channels to higlight certain messages, and you can also have an idented output so you can easily follow the code flow.
Also it supports ANSI colors if your terminal is ANSI enabled.

This also helped me a lot to track TimeOut functions and Interval functions, especially the first one is very hard to track in a clear way when a callback function ends its job, especially if there are many of them running on their own.

Here is a screenshot of the output you can get:
Image

If you are interested I can send you the latest available version.
----------------------------
[Allanon] Fabio Falcucci
AMC - Creative Development // Docs Site // Support Forum
Support me on Patreon for Hollywood libraries!

User avatar
Allanon
Posts: 461
Joined: Sun Feb 14, 2010 7:53 pm
Location: Italy
Contact:

Re: (coding) How to make user Interactive functions in a row

Post by Allanon » Sat Jul 08, 2017 11:29 am

I forgot to say that if you will use the Debug library it will need (ofcourse) extra code to display those messages :)
----------------------------
[Allanon] Fabio Falcucci
AMC - Creative Development // Docs Site // Support Forum
Support me on Patreon for Hollywood libraries!

Bugala
Posts: 759
Joined: Sun Feb 14, 2010 7:11 pm

Re: (coding) How to make user Interactive functions in a row

Post by Bugala » Sat Jul 08, 2017 11:58 am

So i gave a try for that observer pattern.

I am not sure this really is observer pattern actually, but at least the idea is the same. This a working code example:

Code: Select all

observer = { table = {} }

Function observer:add(newobservable)
   newobservable.position = 1
   InsertItem(self.table, newobservable)
   Local n = TableItems(self.table)
   observer:execute(self.table[n-1], 1)
EndFunction



Function observer:execute(subtable, currentposition)
   subtable[currentposition].func()
EndFunction



Function observer:inform(message)

   Local currentposition
   Local n_items = TableItems(self.table)

   For n=0 To n_items-1
	item = self.table[n]
	currentposition = item.position
	If item[currentposition].message = message
		item.position = item.position + 1
		currentposition = item.position
		If HaveItem(item, currentposition) = True
			observer:execute(item, currentposition)
		Else
		        RemoveItem(self.table, n)
		EndIf
	EndIf
   Next

EndFunction


Function card1()
   temptable = {
	[1] = { func=init_up, message="up is done" },
	[2] = { func=init_down, message="down is done" },
	[3] = { func=init_up, message="up is done" }
	    }
   observer:add(temptable)
EndFunction


Function card2()
   temptable = {
	[1] = { func=init_down, message="down is done" },
	[2] = { func=init_up, message="up is done" }
	    }
   observer:add(temptable)
EndFunction


Function init_up()
   TextOut(300, 300, "press up", {name="up"})
   SetInterval(2, up, 15)
EndFunction


Function up()

   Local pressed = False

   If IsKeyDown("UP") = True Then pressed = True

   If pressed = True
      RemoveLayer("up")
      ClearInterval(2)
      Observer:inform("up is done")
   EndIf

EndFunction


Function init_down()
   TextOut(300, 400, "press down", {name="down"})
   SetInterval(3, down, 15)
EndFunction	


Function Down()
	
   Local pressed = False

   If IsKeyDown("DOWN") = True Then pressed = True
   If pressed = True
      RemoveLayer("down")
      ClearInterval(3)
      Observer:inform("down is done")
   EndIf

EndFunction




Function updategraphics()
   If dir = 0 Then x=x-3
   If dir = 1 Then x=x+3
   If x <100 Then dir = 1
   If x >500 Then dir = 0
   SetLayerStyle("text1", {x=x})
EndFunction



EnableLayers

TextOut(100, 100, "moving text", {name="text1"})

SetInterval(1, UpdateGraphics, 15)
card1()
/*card2()*/

Repeat
   WaitEvent()
Forever


This code still needs some modifications, but i left it like this to be easier to read.

Two things to notice.

1. When observer:Inform notices that last function of a card is already executed, it simply removes that item from the list. This is naturally not the way to do it actually, since suppose you have 10 things being observed, then you are at n=3 and remove number 3. What happens is that number 4 becomes new number 3, but you are already jumping to check state of number 4, which means that old number 4 is being completely skipped, but i left it this way to make it easier to understand.

2. In this example you can put card1 and card2 being observed at same time, however, since they are at some point both trying to removelayer same named layer, this will naturally crash the program. But otherwise even observing them at same time and both waiting for same thing to happen is not a problem.


Bit of explanation about the observer.

As name suggests, observer is someone who keeps observing what is happening with program. This happens through functions themselves informing observer that something has happened, and then observer makes sure that all those functions that are interested in knowing that, keep informed of the new happening.

In this case for example when "up" function is done, it sends message to observer that "up is done". Now observer looks through its list of observables to see if one of them is interested in knowing this.
In this example thing is somewhat simplified. I am sending tables that contain a list which tells which function is being executed first, and which message is to be waited, until next function in that list is being executed.

so for example:

Code: Select all

table = {
       [1] = { func = myfunction, message ="myfunction have been executed},
       [2] = { func = mysecondfunction, message "mysecondfunction is done"}
           }
means that function to be executed first is block [1] "myfunction". Then observer will be waiting for "myfunction have been executed" message, and when he receives that message, it will move to block [2] and start "mysecondfunction".

when all the [n]blocks are gone through, this observable will destroy itself away from the list.


There are several benefits in this compared to gamestate system.

As example it is very easy to have several observables wait for same thing to happen at same time.

For example, suppose I am waiting for cube to be placed on board, but then there is at same time another observable waiting for the same thing to happen, for there could be observable that is waiting for 10th cube to be placed on board so that player can be given "10 cubes placed on board" trophy. These can both happen at same "cube is being placed" message.

Also, one observable can be waiting for cube to be placed on board, while another is at same time waiting for settings menu choice to be picked.

With gamestates a system like this would be very messy, needing several states and lots of ifs at same time.

This observable still needs some improvising in way of giving more power to the functions to decide what they want to do.
As example that before mentioned waiting for 10 cubes to be places. It would be better if each time this waiting opbservable function receives the correct message, instead of automatically moving to next line, it can decide what it wishes to do. In this case everytime it would receive a message of "cube is placed on board" it could be simply doing "cubesplaced = cubesplaced + 1" and then "if cubesplaced > 10 then continue"

But this is a start anyway which seemed to work quite nice although for only one kind of purpose currently.


The usage in short:
You first need to make a table:

Code: Select all

temptable = {
        [1] = { func=function to be executed, message="message to be waited until moved on" }
so func is the function that is going to be executed and put as interval (need to work on that one too, since now they are hard numbered in this example)
message is the message that some function needs to send when that thing what you are waiting for have happened. After observer receives that message, it will move on to next block [n].

then you need to Add this temptable to observers list using following:

Code: Select all

Observer:Add(temptable)
After this the function is executed and it is waiting for the message

when that thing that you are waiting for have happened, you have to send a message to observer about it:

Code: Select all

Observer:Inform(message)
Where message is the exact string that the observable is waiting for.


While Observer can wait for anything, like time to run to 0, naturally its greatest use is in when waiting for user to do some action, for this you need to set up an interval. I was using system of instead of starting the actual function to first start the init_function which in turns sets the interval and actual function.


ohter than that, rest are just demonstration on using it and not core parts of the observable.

Bugala
Posts: 759
Joined: Sun Feb 14, 2010 7:11 pm

Re: (coding) How to make user Interactive functions in a row

Post by Bugala » Sun Jul 09, 2017 11:08 am

Heres a new, in my opinion better version of the observer. notice that there are couple of big changes in comparison to previous version. In previous version I had put it as observers responsibility to activate the next function in queue, now observer has only one job to do, it is waiting for certain message to appear, and then starts one function. In this version, observer cant have several functions in queue, but only one. Hence it is now the card1 functions responsibility to handle the queue itself.

Also, Observer doesnt take care of removing functions from its table anymore, but once again, functions themselves have to take care of it themself, this gives possibilties like having a counter like the one in example code.

Good thing however is, that observer is now both much easier to read and understand as well as more flexible.

Code: Select all

observer = { table = {} }

Function observer:add(name, message, func, ...) /* ... part are possible arguments */
	self.table[name] = { message=message, func=func, arg=arg }
EndFunction


Function observer:remove(name)
	self.table[name] = Nil
EndFunction

Function observer:execute(name)
	subtable = self.table[name]
	arg = subtable.arg
	subtable.func(Unpack(arg))   
EndFunction



Function observer:inform(message)
	For name, itemtable In Pairs(self.table)
		If itemtable.message = message Then observer:execute(name)
	Next
EndFunction





Function card1(n)

Local temptable = {
		[1] = { funcinadvance = init_up, name="card1", message="up is done", func=card1, arg=2,  },
		[2] = { funcinadvance = Init_down, name="card1", message="down is done", func=card1, arg=3,   },
		[3] = { funcinadvance = init_up, name="card1", message="up is done", func=card1, arg=4,  },
		[4] = { funcinadvance = Init_down, name="card1", message="down is done", func=card1, arg=5,   },
		[5] = { funcinadvance = init_up, name="card1", message="up is done", func=card1, arg=6,  },
		[6] = { funcinadvance = Init_down, name="card1", message="down is done", func=card1, arg=7,   },
		[7] = { funcinadvance = init_up, name="card1", message="up is done", func=card1, arg=8,  },
		[8] = { funcinadvance = Init_down, name="card1", message="down is done", func=card1, arg=9,   },
		[9] = { funcinadvance = init_up, name="card1", message="up is done", func=card1, arg=10,  }
	    	     }

If n = 0 then n=1
observer:remove("card1")

If n < 10
	temptable[n].funcinadvance()
	
	name="card1"
	message = temptable[n].message
	func = temptable[n].func
	arg = temptable[n].arg
	
	observer:add(name, message, func, arg)	
EndIf

EndFunction


Function init_upcounter()
   var_upcounter = 0
   TextOut(100, 300, "upcounter: "..var_upcounter, {name="upcounterlayer"} )
   observer:add("upcounter", "up is done", upcounter)
EndFunction


Function upcounter()
   var_upcounter=var_upcounter + 1
   SetLayerStyle("upcounterlayer", {text="upcounter: "..var_upcounter} )
   If var_upcounter = 3 
	observer:remove("upcounter")
   EndIf
EndFunction


Function init_up()
   TextOut(300, 300, "press up", {name="up"})
   SetInterval(2, up, 15)
EndFunction


Function up()

   Local pressed = False

   If IsKeyDown("UP") = True Then pressed = True

   If pressed = True
      RemoveLayer("up")
      ClearInterval(2)
      Observer:inform("up is done")
   EndIf

EndFunction


Function init_down()
   TextOut(300, 400, "press down", {name="down"})
   SetInterval(3, down, 15)
EndFunction	


Function Down()
	
   Local pressed = False

   If IsKeyDown("DOWN") = True Then pressed = True
   If pressed = True
      RemoveLayer("down")
      ClearInterval(3)
      Observer:inform("down is done")
   EndIf

EndFunction




Function updategraphics()
   If dir = 0 Then x=x-3
   If dir = 1 Then x=x+3
   If x <100 Then dir = 1
   If x >500 Then dir = 0
   SetLayerStyle("text1", {x=x})
EndFunction



EnableLayers

TextOut(100, 100, "moving text", {name="text1"})

SetInterval(1, UpdateGraphics, 15)
card1()
init_upcounter()

Repeat
   WaitEvent()
Forever
I am explaining things now.

First the Observer itself.

Observer has 4 commands:

Observer:Add(name, message, func, arg) - Args are optional.
Observer:Remove(name)
Observer:Execute(name)
Observer:Inform(message)

edit: I forgot the Observer:Inform from original message. Observer:Inform is the command that functions use to tell that something have happened, like "Observer:Inform("player have died")" And it is also the part that checks through the list of observers if any of the observers are waiting for that message. If one or more of the observers are waiting for that message, then it executes the function that was put with Observer:Add to be executed if that message is received.
- end edit -

Remove and Execute are simple to use. Simply give the name and Observer handles the rest, like adding the possible arguments to the execution.

Observer has a table inside it (observer.table) which contains each observer. Each observer has a unique name that you pick at point you use Observer:Add. This is so that functions outside of Observer would be able to Remove functions from observer list.

message is the message that the observer will be waiting for. When it receives that message, then function in func is being executed using observer:execute(name)

in observer:execute(name) it is simply starting that function with the args: "observer[name].func(arg)" There dont have to be any args, it is simply optional. For example the example "upcounter" function doesnt use any args, while "card1" function specifically uses one arg to make it possible to execute each function in a row (that card1 takes care of, observer is only handling one of them at a time).


the example functions. Starting from easier one, the upcounter function.

I start it from init_upcounter, where it sets "var_upcounter" as 0 and sets a text layer called "upcounterlayer".
After this it uses Observer:Add to add the upcounter() function to the observer list. Now each time a message "up is done" is received by the observer, "upcounter()" function is being executed. Notice that this "upcounter()" function is not being removed from observer but stays there, so that even "up is down" message is received several times, it is still being executed.

"upcounter" functions task is simple. "var_upcounter = var_upcounter + 1" and then it changes the text to show in practice how many times "up" key have been pressed (or "up is done" have happened).

However, after var_upcounter becomes 3, it will remove "upcounter" function from the observers list by using " observer:Remove("upcounter") ". After this, even if "up is done" happens, it doesnt execute "upcounter" anymore, as it is not listening to messages anymore in observer list, as it have been removed from it.



The harder function is the "card1" function.

In this function the idea is that there are several functions i want to be executed in a row which all require some user interaction (in this case either to press "up" arrow or "down" arrow)

At beginning of that function I am making local temptable. You could basically create a permanent table, but i came to conclusion that it is best to create local table each time, since you need that table only on that function and after that it becomes useless, until you return to that function. By using it as local, it doesnt stick to computer memory but gets cleaned out each time until being created again when "card1" function is being executed.

In "card1" function I am using optional arg called "n", this is to help it execute orderd (functions) in queue.

So hence when I am sending "observer:Add" message, I am also sending Arg which will be telling the number of the next function, so that next time it comes back to "card1" it will be executing that numbered function instead of the same one.

While that temptqable is otherwise having the usual "Observer:Add" stuff, it also includes this "funcinadvance".

Idea is that I will first execute inside "card1" function that funcinadvance function, which in this case is always either "init_down" or "init_up". After this I am telling it to "Observer:add" the stuff so it knows that when that "up" or "down" functions send their message of "up is done" or "down is done" it knows to start the "card1" function again with the arg number to tell which numbered function to start.

Notice that each time "card1" function is being executed, also the Observer:Remove("card1") is being executed. This means also the first time when there is no such observer on the list. However, this is not a problem as Observer:Remove is simply making some certain place "Nil", hence this can be done this way, although it is unnecessary the first time round.

Notice also that I am using "if n=0 then n=1"

Basically this line could be removed and at execution simply use "card1(1)" instead, but by using this "if n=0 then n=1" it is easier for the programmer to use this, as he dont need to know the innerworks of "card1", all he needs to do is to use function "card1()" as it is, instead of having to know that it needs to be used as "card(1)" for example.


I believe this observer is pretty good and flexible now as can be seen from the example. Both upcounter and card1() are waiting for same interaction at some points "up is done"-message. And it works very nicely. Also, if i wanted to add new observer, like to calculate down presses, all i would need to do is to make another function and add it to observer list to wait for "down is done" message, without any need to touch the actual "down" function.

When compared to using gamestates, in case like this i would also need to change the "down" function to either have some state to let the counter know it is supposed to do something, or (simpler) to add one more line of code to "down" function telling to execute "downcounter()".

And then if i decided that i dont need that downcounter anymore, in addition to removing the function, i would also need to remember to remove it from "downcounter", while now i only need to simply remove it from ever entering to observer.

This is even more true in case i would be having a function that would be reacting in several places, then i would either add this same function of "counter()" to each place and try to remember to remove it from all 10 places, versus to simply removing the function from observer list.

I believe this is somewhat good solution to most cases where code waits for some interaction from user while it cant stop the whole program to wait for the interaction to happen.

Bugala
Posts: 759
Joined: Sun Feb 14, 2010 7:11 pm

Re: (coding) How to make user Interactive functions in a row

Post by Bugala » Sun Jul 09, 2017 11:28 am

Actually realised something even more about the flexibility of this observer system. I could even have game turns done on top of all this at same time.

Now suppose there would be several different cards like "card1()".

Lets suppose each turn consists of drawing 3 card from deck and then doing as the cards say.

Now I would be drawing one card at a time, at which point something similar to "card1()" would be happening each time.

I would make one alteration to "card1()" kind of functions. At point it reaches that number 10, it would also send "Observer:Inform("card is done"). And this same message would be put in similar way to each card.

Now I can add an observer similar to the "counter()" function that is counting how many times "card is done" message have been sent. At point "card is done" function have came three times, it would destroy that observer from list and also send a message again "Observer:inform("player turn is done")"

This message would activate next observer function which would change the player to next one and then repeat the same "draw three cards" process. And, on top of that, after this function had, for example went through 4 different players (in 4 player game), then it would once again remove this from observers list and send message of "Turn is done" and this would invoke function that both calculates how many turns have been done as well as starts the next turn.

And this once again, when reaches for example turn 15, would send message of "turn 15 reached" and then the "game end scoring" function would start.


This way simply using these observers, i would have just got a whole game play sequence done in very simple way. Have to say, didnt even realise how effective this observer pattern thing is until now gave it a try for a first time.

Post Reply