Magentix
if (OP.statement == false) postCount++;
- Reaction score
- 107
0. Index
1. Foreword
2. General Agreements
3. The Interface
4. The Method Interface
5. Type IDs
6. Advanced Cases
There, I hope this was interesting to read and inspired some people to use interfaces for their next projects
- Foreword
- General Agreements
- The Interface
- Definition
- Example of use: Making a marble roll
- Set up
- The Method Interface
- Definition
- Example of use: Expanding the marble
- Set up
- Type IDs
- Definition
- Example of use: A bag of marbles
- Set up
- Advanced Cases
1. Foreword
This tutorial is completely in JASS, meaning any GUI user can stop reading here.
Secondly, this tutorial requires you to have a basic knowledge of what structs and methods are.
If you don't know what they are, the advantage of using interfaces probably won't be clear to you anyway.
Secondly, this tutorial requires you to have a basic knowledge of what structs and methods are.
If you don't know what they are, the advantage of using interfaces probably won't be clear to you anyway.
2. General Agreements
Throughout this tutorial, we will continuously use the same example and expand it to suit our needs.
To keep everything clean and easy to look at, we will put everything in a library.
To keep everything clean and easy to look at, we will put everything in a library.
3. The Interface
a. Definition
A Basic Interface in vJASS is a structure that allows you to refer to multiple related objects as one object-type.
Difficult to understand at first, huh?
No problem, let's just move on to our example.
b. Example of use: Making a marble roll
Suppose we have a nice, round marble.
To describe the marble even more, we create a struct for it that describes the marble's mass, volume, color, etc...
Now, to let that marble roll, we have a function RollMarble that does all the rolling dynamics.
However, since every marble struct will have a different name, we can't write one function that will roll just any marble.
So as you probably already know, we'll have to create a "roll" method in every marble struct.
(The foreword did mention you need a basic knowledge of methods)
Example:
Now that we have our methods in every marble struct, we still have one problem:
How do we make different marbles "interact" in a function?
Suppose we want to take two marbles and only roll the heaviest?
This would be impossible since the function RollHeaviest wouldn't know what to require as arguments.
(All marble structs have different names, remember?)
This is where interfaces come in.
c. Set up
As I mentioned above, we need a function to take any two types of marble, and roll only the heaviest.
We can achieve this by giving ALL marbles the same reference name through an interface:
Notice how I used extends Marble on all of our marble structs?
This tells your vJASS compiler (JassHelper) that they can be referred to as "Marble".
However, every method that you use of a Marble inside an external function, must be set in the interface declaration.
In this case you notice that the function RollHeaviest uses the .roll() method and the .weight struct member, but it does not use .color in any way.
Well then, we only have to declare the method "roll" and the real "weight" in the interface, but we're not obliged to declare "color",
since no method or function outside of a specific Marble uses it.
One more thing:
Once you declare a method inside an interface, anything that extends that interface must have it as well!
So not having the method "roll", would make a struct extending Marble cause a syntax error.
Variables, however, can be defaulted inside the interface and any struct that then extends that interface,
will automaticly have that variable declared and defaulted, but may still overwrite it with its own default.
Example:
So our previous example would still be valid written like this:
A Basic Interface in vJASS is a structure that allows you to refer to multiple related objects as one object-type.
Difficult to understand at first, huh?
No problem, let's just move on to our example.
b. Example of use: Making a marble roll
Suppose we have a nice, round marble.
To describe the marble even more, we create a struct for it that describes the marble's mass, volume, color, etc...
Now, to let that marble roll, we have a function RollMarble that does all the rolling dynamics.
However, since every marble struct will have a different name, we can't write one function that will roll just any marble.
So as you probably already know, we'll have to create a "roll" method in every marble struct.
(The foreword did mention you need a basic knowledge of methods)
Example:
Now that we have our methods in every marble struct, we still have one problem:
How do we make different marbles "interact" in a function?
Suppose we want to take two marbles and only roll the heaviest?
This would be impossible since the function RollHeaviest wouldn't know what to require as arguments.
(All marble structs have different names, remember?)
This is where interfaces come in.
c. Set up
As I mentioned above, we need a function to take any two types of marble, and roll only the heaviest.
We can achieve this by giving ALL marbles the same reference name through an interface:
JASS:
library Marbles
interface Marble
// This is how you declare an obliged struct member
// for all structs extending Marble
real weight
// This is how you declare an obliged struct method
// for all structs extending Marble
method roll takes nothing returns nothing
endinterface
private struct RedMarble extends Marble
string color = "FF0000"
real weight = 0.
method roll takes nothing returns nothing
endmethod
endstruct
private struct GreenMarble extends Marble
string color = "00FF00"
real weight = 0.
method roll takes nothing returns nothing
endmethod
endstruct
function RollHeaviest takes Marble A, Marble B returns nothing
if A.weight > B.weight then
call A.roll()
else
call B.roll()
endif
endfunction
endlibrary
Notice how I used extends Marble on all of our marble structs?
This tells your vJASS compiler (JassHelper) that they can be referred to as "Marble".
However, every method that you use of a Marble inside an external function, must be set in the interface declaration.
In this case you notice that the function RollHeaviest uses the .roll() method and the .weight struct member, but it does not use .color in any way.
Well then, we only have to declare the method "roll" and the real "weight" in the interface, but we're not obliged to declare "color",
since no method or function outside of a specific Marble uses it.
One more thing:
Once you declare a method inside an interface, anything that extends that interface must have it as well!
So not having the method "roll", would make a struct extending Marble cause a syntax error.
Variables, however, can be defaulted inside the interface and any struct that then extends that interface,
will automaticly have that variable declared and defaulted, but may still overwrite it with its own default.
Example:
JASS:
library Marbles
interface Marble
// Defaults weight to 10.
// Any struct that extends Marble will automaticly
// have "weight" declared and defaulted to 10.
real weight = 10.
method roll takes nothing returns nothing
endinterface
// This will NOT cause a syntax error, since BlueMarble
// does have all the required methods, and the interface
// Marble defaults the "weight" variable for BlueMarble.
private struct BlueMarble extends Marble
string color = "0000FF"
method roll takes nothing returns nothing
endmethod
endstruct
// This will cause a syntax error, since Redmarble
// does not have the method roll, while the interface
// it extends (Marble) does.
private struct RedMarble extends Marble
string color = "FF0000"
endstruct
// Redeclaring weight is allowed and causes all
// instances of GreenMarble to have 20. as their
// weight default, instead of the interface defaulted 10.
private struct GreenMarble extends Marble
string color = "00FF00"
real weight = 20.
method roll takes nothing returns nothing
endmethod
endstruct
endlibrary
So our previous example would still be valid written like this:
JASS:
library Marbles
interface Marble
// Declared and defaulted to 0. for all extending structs
real weight = 0.
method roll takes nothing returns nothing
endinterface
private struct RedMarble extends Marble
string color = "FF0000"
method roll takes nothing returns nothing
endmethod
endstruct
private struct GreenMarble extends Marble
string color = "00FF00"
method roll takes nothing returns nothing
endmethod
endstruct
function RollHeaviest takes Marble A, Marble B returns nothing
if A.weight > B.weight then
call A.roll()
else
call B.roll()
endif
endfunction
endlibrary
4. The Method Interface
a. Definition
A Method Interface is how I like to call a combination of a Basic Interface with a "parent" struct.
b. Example of use: Expanding the marble
While explaining how and why we should use an interface, I used 2 marbles to show what I meant.
Now suppose we have a few hundred marbles.
All round marbles may have the exact way of rolling (in a straight line for example), but still be different structs because they have a different color, mass, etc.
Having to copy-paste the same "roll" method in every marble of that type, along with all the details would suck, wouldn't it?
This is where Method Interfaces come in handy.
c. Set up
Wow, that's a lot of information!
Let's break it down:
As you can see here, RedMarble no longer extends Marble directly, but uses a small detour over MarbleMethods.
This way, you can default some methods that hardly ever change inside MarbleMethods and still overwrite them if necessary inside a marble struct.
(Just like you can set/overwrite variables in the interface)
Note:
Unlike a struct that is a direct extension of an interface, you may not redeclare variables in a child struct of a Method Interface.
You may still use it and set its value to something else in a constructor, but redeclaring it will cause a syntax error.
Example:
To avoid any incidents like this, it is best to always set your child struct's default variables in a constructor method.
A Method Interface is how I like to call a combination of a Basic Interface with a "parent" struct.
b. Example of use: Expanding the marble
While explaining how and why we should use an interface, I used 2 marbles to show what I meant.
Now suppose we have a few hundred marbles.
All round marbles may have the exact way of rolling (in a straight line for example), but still be different structs because they have a different color, mass, etc.
Having to copy-paste the same "roll" method in every marble of that type, along with all the details would suck, wouldn't it?
This is where Method Interfaces come in handy.
c. Set up
JASS:
library Marbles
interface Marble
real weight = 10.
string color = "000000"
method getColor takes nothing returns nothing
method roll takes nothing returns nothing
endinterface
private struct MarbleMethods extends Marble
// Notice you may default both variables
// and methods inside a method interface.
// Here, color overwrites the color defaulted
// by the interface
string color = "FFFFFF"
// You may call this function on anything that
// extends MarbleMethods and it will still call
// the correct color, or "FFFFFF" if the extending
// struct didn't specify any color.
method getColor takes nothing returns nothing
call BJDebugMsg("Hi, my color is: " + .color)
endmethod
method roll takes nothing returns nothing
// Some normal rolling stuff
endmethod
endstruct
private struct RedMarble extends MarbleMethods
real weight = 20.
// This constructor will set the default color
// for any RedMarble. If you don't understand this:
// Read the note at the end of this section!
static method create takes nothing returns RedMarble
local RedMarble rm = RedMarble.allocate()
set rm.color = "FF0000"
return rm
endmethod
// Notice I no longer need to set "roll" here,
// since MarbleMethods created a default for RedMarble.
// You may, however, overwrite "roll" like
// SquareWhiteMarble does.
endstruct
private struct SquareWhiteMarble extends MarbleMethods
// Done't need to set color nor weight,
// since I take the default color of MarbleMethods
// and the default weight of Marble.
method roll takes nothing returns nothing
// Some square rolling stuff
endmethod
endstruct
endlibrary
Wow, that's a lot of information!
Let's break it down:
As you can see here, RedMarble no longer extends Marble directly, but uses a small detour over MarbleMethods.
This way, you can default some methods that hardly ever change inside MarbleMethods and still overwrite them if necessary inside a marble struct.
(Just like you can set/overwrite variables in the interface)
Note:
Unlike a struct that is a direct extension of an interface, you may not redeclare variables in a child struct of a Method Interface.
You may still use it and set its value to something else in a constructor, but redeclaring it will cause a syntax error.
Example:
JASS:
library Marbles
interface Marble
real weight
method roll takes nothing returns nothing
endinterface
struct MarbleMethods extends Marble
// weight got declared in MarbleMethods!
// This means Redmarble and GreenMarble can still
// "set" their value, but they may not redeclare it.
real weight = 0.
method roll takes nothing returns nothing
endmethod
endstruct
// This is a legit way, since it doesn't re-declare any
// variables from MarbleMethods.
private struct RedMarble extends MarbleMethods
string color = "FF0000"
endstruct
// This will cause syntax errors, since "weight"
// already got declared in MarbleMethods.
private struct GreenMarble extends MarbleMethods
real weight = 10.
string color = "00FF00"
endstruct
// This will NOT cause syntax errors, since
// we don't redeclare weight, we just change its
// value upon construction
private struct BlueMarble extends MarbleMethods
string color = "0000FF"
// This constructor will set the default weight for any BlueMarble
static method create takes nothing returns BlueMarble
local BlueMarble bm = BlueMarble.allocate()
set bm.weight = 100.
return bm
endmethod
endstruct
endlibrary
To avoid any incidents like this, it is best to always set your child struct's default variables in a constructor method.
5. Type IDs
a. Definition
TypeIDs are the pointers that tell vJASS where to store and find its data for a specific struct type.
b. Example of use: A bag of marbles
So far we learned how to create a standard for objects that allows us to both declare and default variables and methods for all the structs that extend it.
Now we will learn how to add a last, special element: randomness.
Suppose we have a collection of marbles. Let's say a bag of marbles.
Now, having the same marbles over and over again in a specific bag would be boring, wouldn't it?
Well, we are allowed to fill a bag with random marbles now, thanks to TypeIDs!
c. Set up
Wow, another bunch of code!
As you can see, we could create a boring bag of marbles, containing one green, blue and red marble.
Or we could go wild and grab 3 random marbles!
How we achieve this is simple:
We assign an extending struct's typeIDs to a variable by using:
set SomeInteger = STRUCTname.typeid
When we want to use this typeid to create a Marble, we just use:
set SomeStruct = INTERFACEname.create(STRUCTname.typeid)
or, in this case:
set SomeStruct = INTERFACEname.create(SomeInteger)
Well, in this case we have 3 marbles to random from.
So we create an integer array and assign all 3 Marble's typeID to the array.
Instead of just calling <Marble struct name>.create(), we can now call Marble.create(Random value from the TypeID array).
TypeIDs are the pointers that tell vJASS where to store and find its data for a specific struct type.
b. Example of use: A bag of marbles
So far we learned how to create a standard for objects that allows us to both declare and default variables and methods for all the structs that extend it.
Now we will learn how to add a last, special element: randomness.
Suppose we have a collection of marbles. Let's say a bag of marbles.
Now, having the same marbles over and over again in a specific bag would be boring, wouldn't it?
Well, we are allowed to fill a bag with random marbles now, thanks to TypeIDs!
c. Set up
JASS:
library Marbles initializer Init
globals
integer array marbleIDs
integer marbleMax = 0
endglobals
interface Marble
real weight = 0.
string color = "0000FF"
method roll takes nothing returns nothing
endinterface
struct MarbleMethods extends Marble
method roll takes nothing returns nothing
endmethod
endstruct
private struct RedMarble extends MarbleMethods
string color = "FF0000"
// Some other stuff here
endstruct
private struct GreenMarble extends MarbleMethods
string color = "00FF00"
// Some other stuff here
endstruct
private struct BlueMarble extends MarbleMethods
// Some other stuff here
endstruct
private struct BoringBagOfMarbles
Marble red
Marble blue
Marble green
static method create takes nothing returns BoringBagOfMarbles
local BoringBagOfMarbles bag = BoringBagOfMarbles.allocate()
set bag.red = RedMarble.create()
set bag.blue = BlueMarble.create()
set bag.green = GreenMarble.create()
return bag
endmethod
endstruct
private struct AmazingBagOfMarbles
Marble random1
Marble random2
Marble random3
static method create takes nothing returns AmazingBagOfMarbles
local AmazingBagOfMarbles bag = AmazingBagOfMarbles.allocate()
set bag.random1 = Marble.create(marbleIDs[GetRandomInt(0,marbleMax)])
set bag.random2 = Marble.create(marbleIDs[GetRandomInt(0,marbleMax)])
set bag.random3 = Marble.create(marbleIDs[GetRandomInt(0,marbleMax)])
return bag
endmethod
endstruct
// Initializer
private function Init takes nothing returns nothing
set marbleIDs[0] = RedMarble.typeid
set marbleIDs[1] = GreenMarble.typeid
set marbleIDs[2] = BlueMarble.typeid
set marbleMax = 2
endfunction
endlibrary
Wow, another bunch of code!
As you can see, we could create a boring bag of marbles, containing one green, blue and red marble.
Or we could go wild and grab 3 random marbles!
How we achieve this is simple:
We assign an extending struct's typeIDs to a variable by using:
set SomeInteger = STRUCTname.typeid
When we want to use this typeid to create a Marble, we just use:
set SomeStruct = INTERFACEname.create(STRUCTname.typeid)
or, in this case:
set SomeStruct = INTERFACEname.create(SomeInteger)
Well, in this case we have 3 marbles to random from.
So we create an integer array and assign all 3 Marble's typeID to the array.
Instead of just calling <Marble struct name>.create(), we can now call Marble.create(Random value from the TypeID array).
6. Advanced Cases
To finish off, I'd like to point out that you can let your imagination run wild with this "system".
For example, instead of having:
You could have:
Or, as a "real example":
The Interface Marble, extended by Method interfaces:
- RoundMarble (has the physics for working marbles)
- BrokenMarble (has the physics for broken marbles)
Which are in turn extended by their marble variants
For example, instead of having:
Code:
INTERFACE WITH DEFAULT VARS
|
|
V
STRUCT WITH DEFAULT METHODS
|
|
V
SEVERAL STRUCTS
You could have:
Code:
INTERFACE WITH DEFAULT VARS
| |
| |
V V
STRUCT A W/ DEF. MET. STRUCT B W/ DEF. MET.
| |
| |
V V
SEVERAL STRUCTS W/ MET. SET A SEVERAL STRUCTS W/ MET. SET B
Or, as a "real example":
The Interface Marble, extended by Method interfaces:
- RoundMarble (has the physics for working marbles)
- BrokenMarble (has the physics for broken marbles)
Which are in turn extended by their marble variants
There, I hope this was interesting to read and inspired some people to use interfaces for their next projects