money!(player, money(player) + 100)

question

#1

Hi all,

does somebody know a good “design pattern” to simplify this kind of code?

money!(player, money(player) + 100)

In other languages it would be something like this, via getters/setters:

player.money += 100

But I have no “money” attribute, only the methods to read/write the money. I thought about introducing a new type like “PlayerMoney”, which implements += etc.

money(player) += 100

Any ideas for a nice/short julian way?


#2

How did you define the money! function if player has no money field?


#3

I am using lots of ccalls for a game server originally written in C, which has a fixed set of maximal 64 players. So I simply define a player via id:

type Player <: AbstractEntity
	id::Int32
end

So when a new game type needs “player money” I simply “compose” the money to this custom game type, like:

if !isdefined(:playersMoney)
	playersMoney = zeros(Float32, 64)
end
money( this::Player)        = playersMoney[this.id]
money!(this::Player, money) = playersMoney[this.id] = Float32(money)

#4

And why are you against using a money field in the Player struct? This seems like the most natural way of doing things. If Player is only a thin immutable wrapper of an integer, then there is no point defining it, I think. But it seems more natural to throw in all the information of a player in that struct and then pass it around as a whole so you don’t have to worry about many vectors, just a single vector of players.


#5

I don’t like a fixed set of attributes, since depending on the gametype or depending of the entities used inside a map, there might be money or not.

E.g. some maps and gametypes got no shop, so I dont wanna have a “money attribute” in a map without shops.

So if I would put all possible attributes into the player, I would loose this epic composability that Julia offers. I also like that I can quickly define a player by just using Player(23) e.g. This is perfect for C callbacks to go from a Int32 to a real player instance.

Yea, at the beginning I somehow worried about those extra playersMoney vectors, but I started to like this concept, since I can mix up everything dynamically just as I need it.


#6

Hmm, the way I see it you have a couple of options:

  1. Define a vector players and put the list of all possible info fields of the players, optional ones can be made Nullable or Union{T,Missing} using Missings.jl.
using Missings
mutable struct Player
     id::Int
     money::Union{Float64, Missing}
end
  1. Define a single struct for all players’ info and put your field vectors in there. Then you can define getters and setters on the PlayersInfo struct directly using the player id.
struct PlayerID
     id::Int
end
struct PlayersInfo
     money::Vector{Union{Float64,Missing}}
end
getmoney(ps::PlayersInfo, pid::PlayerID) = ps.money[pid.id]
setmoney(ps::PlayersInfo, money, pid::PlayerID) = ps.money[pid.id] = money

Also consider using a Dict field in the player struct.


#7

In other languages it would be something like this, via getters/setters:

On Julia v0.7 and beyond:

So I think this will let you get the syntax you want with ccall.


#8

Hi, thanks you two for your inputs, but I am not quite happy with it yet. The custom properties would probably turn out nice, but currently I got dependencies, which only work on 0.6.2

I realised I need some kind of reference into the playersMoney array, so I came up with this:

importall Base

type ArrayElement{T}
	array::Vector{T}
	index::Int32
end

function (+)(this::ArrayElement{T}, other)::ArrayElement{T} where T
	this.array[this.index] += T(other)
	return this
end

So I can add a new money! method, but with only one argument:

money!(this::Player) = ArrayElement{Float32}(playersMoney, this.id)

So now I can write something like:

money!(player1) + 100

I think it would be nicer to write this:

money(player) += 100

But I couldn’t figure out any way to overload += yet, that only seems to be supported for [] indexing stuff via getindex/setindex!.


#9

I must say that this reads very strangely. You don’t money people, you pay them. I suggest something along these lines:

pay!(p::Player, sum::Real) = (money[p.id] += sum)

where money is a dict or something.


#10

That doesn’t make it any nicer/shorter and is just unnecessary term mixing. I don’t money players, I operate on the money of players, hence overloading the + operator (or -, * or / etc.). Shall I also make up terms for decreasing money, or shall I pay them negative values? That’s leading nowhere.


#11

money! is a ridiculous name for that operation. And the operator overloading is odd, and makes the code unintuitive and hard to read.

pay! is indeed much nicer. But if you need to arbitrarily set their assets (which you did not make very clear), then something like setmoney!, setassets! or whatever.


#12

If syntax is the issue then you need Julia’s syntactic macros, for example (just as proof of concept):

julia> macro set(object::Symbol, update_expr::Expr)
           str_op = string(update_expr.head)
           if !endswith(str_op, "=") && length(str_op) !== 2
               error("syntax: expected update operator, got: `$str_op`.")
           end
           op = Symbol(str_op[1:end-1])
           attr = update_expr.args[1]
           func! = Symbol(attr, :!)
           ammount = update_expr.args[2]
           :($func!($object, $op($attr($object), $ammount))) |> esc
       end
@set (macro with 1 method)

julia> @macroexpand @set player money += 100
:(money!(player, money(player) + 100))

julia> @macroexpand @set spaceship defence -= 42
:(defence!(spaceship, defence(spaceship) - 42))

julia> @macroexpand @set monster speed *= 7
:(speed!(monster, speed(monster) * 7))

julia> @macroexpand @set boss health /= 2
:(health!(boss, health(boss) / 2))
  • Now you could just say: @set player money += 100, etc.
  • @macroexpand just returns the resulting expression, useful for debugging macros.
  • Notice that using esc in a whole expression is bad practice, but this is just a simple example, so it doesn’t do much error checking either about the shape of update_expr or str_op. :stuck_out_tongue:

Agree, verbs are better for this IMHO.