Any way to watch a variable?

Monkey Forums/Monkey Programming/Any way to watch a variable?

SLotman(Posted 2015) [#1]
I'm thinking about adding "missions" or "challenges" into the game I'm making - and the easiest way to do it would be somehow to be able to register what to look for, for example, a way to watch the player score, and the ammount of lives.

What I'm looking for is something like this:


So, everytime Player.score changes, scoreWatch would change too. I know in C I can just make scoreWatch a pointer to whatever... but how to do it in Monkey?

I'm thinking about reflection, but I really have no idea how to do it. Any tips?


ImmutableOctet(SKNG)(Posted 2015) [#2]
I'm really confused about what you want. If you want a reference/pointer to 'score', what's the point? Can't you just use the variable? If you want it to be "dynamic", and grab the state of the variable from somewhere else, you could use a reference to the object, then get its field. This basically applies to global variables too. If you want a "variable" somewhere that makes the way you access the variable abstract, then you need a property. Other than that, I don't understand what you're asking. Just pass your objects around.

Example of what I was talking about:


Really, if you're going to have a 'Player' class anyway, you might as well do it right, and make 'score' a field. Well regardless, the best you have is "accessors", the nicest of which are properties.

Building a proper 'Player' system where you use fields and objects instead of global variables is ideal, though. Objects are passed by reference automatically. Most notably, most of the C++ targets just use pointers anyway. Meaning it's the same as C in this regard.


itto(Posted 2015) [#3]
Reflection for a situation like this would be overkill and break encapsulation. In OOP you realize this via the Publish-Subscribe pattern (https://en.wikipedia.org/wiki/Publish–subscribe_pattern).

Basically, an object subscribes to the topics (or events, if you prefer) of a publisher object, so that when the publisher publishes new topics, all the subscribers to that particular topic get notified. Then, the subscriber may ask the publisher for the properties which should have changed, or the publisher may send the changed properties along with the event notification. This depends on how you decide to implement this model. I generally prefer the latter, because of the tell/don't ask principle, and to have less manual work to do.

Of course to be able to do this, you should setup your objects so that their attributes are set through methods or properties, not by assigning directly to their fields, otherwise they wouldn't know when some of their properties changed. This in fact could be a good place to start using properties for your objects.

It's really handy and versatile, I use it in various places in my engine and games. I suggest you to make the required code abstract enough that you can use it in any part of your projects. Here's the interface code that I'm using:

Publisher
' with boxing
interface IPublisher
    method Publish:void(event:string, data:Object)
    method Subscribe:void(subscriber:ISubscriber, event:string)
    method Unsubscribe:void(subscriber:ISubscriber, event:string)
end

' with generics
interface IPublisher<T>
    method Publish:void(event:string, data:T)
    method Subscribe:void(subscriber:ISubscriber, event:string)
    method Unsubscribe:void(subscriber:ISubscriber, event:string)
end


Subscriber
interface ISubscriber
    method OnEventPublished:void(event:string, data:Object)
end


To adapt your code:

Class Player extends Publisher ' <-- make a base Publisher class to extend which does the bookkeeping for you
    method Score:void(amount:int) property
        score = amount
        Publish("score-changed", BoxInt(score)) ' <-- all the subscribers to the "score-changed" event get notified
    end
    private field score:Int = 0
End Class

Function Main()
	New Test()
End

Class Test Extends App implements ISubscriber

        Method New()
             player = New Player
             player.Subscribe(self, "score-changed") ' <-- subscribe to topics, from now on you'll get notified of changes
        end

	Method OnUpdate()
	    if KeyHit(KEY_ESCAPE) then Error ""
	    Player.Score = Player.Score + 1
	    Print "Player.score:" + Player.score
	    Print "score watch:" + scoreWatch
	End Method

        Method OnEventPublished:void(event:string, data:Object)
            ' you react on the event notifications here
            Select event
                Case "score-changed"
                    scoreWatch = UnboxInt(data) ' or you receive data from the publisher,
                    ' scoreWatch = player.Score   ' or you ask for the data yourself
            End
        End

        Field scoreWatch:= 0
        Field player:Player

End Class


There are many different implementations of this pattern, this is a very general one and you can craft one which suits your needs better, but this should give you a basic idea of how it works. Another alternative is the Observer pattern.


muddy_shoes(Posted 2015) [#4]
While it's possible to simply hard-wire checks against specific entity attributes like the player object - score, this is going to get messy fairly quickly. It sounds like you're really on the way to needing something along the lines of an achievements system.

How I've done this in the past is to create a GameStats class, which is essentially a StringMap to Int at its core. This allows values to be registered by name and has utility functions like SetIfGreater/SetIfLesser. It also provides a subscriber/listener interface as Itto mentions above. The stats class can be saved/initialised via JSON/XML/whatever to allow or defaults or save states. So, with this in place I can create things like a "PlayerScore" stat, "Level1Beaten" stat, "EnemiesKilled" stat etc. and have a central place to update them and to check their values or listen for changes.

In addition to this I created an Achievements class. This listens for changes to the stats class and checks the values to see if certain conditions are met. Again, this provides a subscriber/listener interface so that systems can listen for these higher level events. The achievements themselves are defined in JSON, which allows them to be easily changed or added to:

[{"name":"viewedHelp", "requirements":[{"statname":"viewedHelp","checktype":">","value":0}]},
 {"name":"level0Complete", "requirements":[{"statname":"level0Complete","checktype":"=","value":1}]},
 {"name":"level0**", "requirements":[{"statname":"level0Time","checktype":"<","value":3600}]},
 {"name":"level0***", "requirements":[{"statname":"level0Time","checktype":"<","value":2500}]}
]


As the example JSON shows, the system isn't limited to tracking things that are strictly achievements. You can use it for housekeeping all sorts of player behaviour like whether they've seen the help or done the tutorial.

Another handy thing about this setup is that it easily interfaces with pretty much all of the standard achievement systems out there on places like Kongregate/Steam etc. The amount of extra wiring to be done is minimal.


Nobuyuki(Posted 2015) [#5]
Great advice, guys. I'm taking what muddy and itto said and starting to implement a GameStats class for my next project. For the version I'm trying, I want GameStat to be an interface (possibly generic) which requires implementing a Value property and a ToJSON:JsonValue property. However, I'm not sure if I'm going to be going about this the right way.

What I'm looking to do is to have the GameStats (manager class) be extendible so that convenience properties can be added to an individual game or set of stats. I'm thinking that this would this be the best way to handle reducing the amount of boilerplate necessary to access a stat var, but then it might potentially increase the amount of boilerplate needed to add new stats to the collection quite a bit. "Helper" classes (GameStatInt, GameStatString, GameStatBool, etc) could alleviate this, but the main idea here is to try to reduce the amount of writing needed to have a system like this. If it requires more boilerplate than my old way of doing things (Bussing a lot of variables to/from Savestate-compatible classes to JSON as needed), it might not be any better than what I'd be currently using. Being able to watch variables at runtime seems like a nice bonus for this, but I'm questioning both the overhead and the potential amount of extra typing that this may bring...


itto(Posted 2015) [#6]
@Nobuyuki It depends on the specific situation you are facing, of course, but if you are already using a basic data structure to store your data, you could start with something really simple by making that structure smart. For example in the case of a map, you'd make one which implements the Publisher interface, and sends notifications to subscribers every time an attribute has been added or modified.

Being able to "watch variables" this way can go a great way in helping you keeping your code modular. The downside is when you go too far with event-driven programming, that you're less and less able to understand what your code is doing because it's just about listening to events and firing others in response :) But the same could be said for OOP method delegations. And there's some overhead, of course.

I personally find it extremely useful for coordinating the major submodules of a project. For example my battle state is now divided into a battle simulation, graphics, sounds and GUI subsystems. When the battle simulation is forwarded and some event happens (i.e. an enemy has been hit), the event propagates to all the interested subsystems (only the ones who have subscribed to that event), and they act accordingly (play the related sound, show the animation, etc.). All subsystems are completely separated from each other. You can disable one or more of them, and the others will go on doing their business like nothing happened. They don't have to care about contacting other subsystems, they just do what they are supposed to do, and make noise when something happens. Now to switch from a 2d to a 3d battle representation I just have to switch one subsystem.

Bussing a lot of variables to/from Savestate-compatible classes to JSON as needed

Why should a class be SaveState-compatible? The way I'm using reflection, all my classes are serializable, without having to do anything in merit. I just pass to the serializer function the class I want to serialize, and it becomes a JSON (or whatever) string. Usually I just have a GameData class and serialize/deserialize it. I had to write my own JSON parser though. The one which comes with Monkey produces a lot of garbage and adds an intermediary step. By using reflection you don't need the JSONObjects at all, my parser immediately recreates the serialized classes at once.


itto(Posted 2015) [#7]
@Nobuyuki It depends on the specific situation you are facing, of course, but if you are already using a basic data structure to store your data, you could start with something really simple by making that structure smart. For example in the case of a map, you'd make one which implements the Publisher interface, and sends notifications to subscribers every time an attribute has been added or modified.

Being able to "watch variables" this way can go a great way in helping you keeping your code modular. The downside is when you go too far with event-driven programming, that you're less and less able to understand what your code is doing because it's just about listening to events and firing others in response :) But the same could be said for OOP method delegations. And there's some overhead, of course.

I personally find it extremely useful for coordinating the major submodules of a project. For example my battle state is now divided into a battle simulation, graphics, sounds and GUI subsystems. When the battle simulation is forwarded and some event happens (i.e. an enemy has been hit), the event propagates to all the interested subsystems (only the ones who have subscribed to that event), and they act accordingly (play the related sound, show the animation, etc.). All subsystems are completely separated from each other. You can disable one or more of them, and the others will go on doing their business like nothing happened. They don't have to care about contacting other subsystems, they just do what they are supposed to do, and make noise when something happens. Now to switch from a 2d to a 3d battle representation I just have to switch one subsystem.

Bussing a lot of variables to/from Savestate-compatible classes to JSON as needed

Why should a class be SaveState-compatible? The way I'm using reflection, all my classes are serializable, without having to do anything in merit. I just pass to the serializer function the class I want to serialize, and it becomes a JSON (or whatever) string. Usually I just have a GameData class and serialize/deserialize it. I had to write my own JSON parser though. The one which comes with Monkey produces a lot of garbage and adds an intermediary step. By using reflection you don't need the JSONObjects at all, my parser immediately recreates the serialized classes at once.


SLotman(Posted 2015) [#8]
Wow. I just returned now, after I finish the game, and see all those wonderful ideas ^_^

Even i didn't know about the "publisher" design, I implemented something just like that - I've made an "Observer" class, that receives notifications from other classes, check if something changed, and then pass along the messages to the achievement class :)

It is working well, you can see the game here: http://contest.gamedevfort.com/submission/410#.VcYu71dtrOC
(Click the DOWNLOAD NOW button, and it will open the game in your browser) - and [BEG MODE] please vote for it if you like it :) [/BEG MODE]