Spherical forums

Sphere Development => Sphere Support => Script Support => Topic started by: Xenso on August 04, 2013, 04:17:33 pm

Title: My Very Simple Menu System
Post by: Xenso on August 04, 2013, 04:17:33 pm
I am making a very simple menu system using the MsgBox code I made.
Its just two customized windows with choices for selection and text to display information
So far the interaction between the windows works great.

Code: (javascript) [Select]

//Menu Script
function menu_cmd(menu_key)
{
this.menu_select  = ["01.Exit","02.Party","03.Item","04.Magic","05.Weapon","06.Armor","07.Misc","08.Status"]
this.menu_stat    = function(stat_txt){cmd_2.drawtext(stat_txt);}
this.menu_party   = function(member_array){cmd_1.drawchoice(member_array);}
//general method selection
this.menu_item    = function(item_array){cmd_2.drawchoice(item_array);}
this.menu_magic   = function(magic_array){cmd_2.drawchoice(magic_array);}
this.menu_weapon  = function(weapon_array){cmd_2.drawchoice(weapon_array);}
this.menu_armor   = function(armor_array){cmd_2.drawchoice(armor_array);}
this.menu_misc    = function(misc_array) {cmd_2.drawchoice(misc_array);}

//msgbox(x,y,w1,w2,h,xo,yo)
  cmd_1 = new MsgBox(4,sh/2-24,sw-8,sw-56,48,54,0); cmd_1.msg_face = face[2];
  cmd_2 = new MsgBox(32,32,sw-64,sw-64,sh-64,32,0);
 
  if (IsKeyPressed(menu_key)){
  var menu_done = false;
  while(!menu_done){
  switch(cmd_1.drawchoice(this.menu_select))
      {
      case 7:this.menu_stat("My Stats");break;
      case 1:this.menu_party(["01.Yunema","02.Seishin","03.Bee-Z-Bulb","04.Itzel"]);break;
      case 2:this.menu_item(["01.Life","02.Mana","03.Mana","04.Elixer"]);break;
      case 3:this.menu_magic(["01.Fire","02.Water","03.Earth","04.Air"]);break;
      case 4:this.menu_weapon(["01.Sword","02.Mace","03.Axe"]);break;
      case 5:this.menu_armor(["01.Mithril"]);break;
      case 6:this.menu_misc(["01.Elf Clothes"]);break;
      case 0:menu_done = true;break;
      }
    }
  }
}


But then how can I continue without using switch within switch within switch statements. I thought my method was fine but clearly it looks like it could get over complicated and messy.

Is there a better way I can make a menu system using simple choices and text without having to create an entirely new object that's pretty much just the same as MsgBox? Instantiation and Polymorphism and Prototype ring a bell but I'm not too sure how to use them effectively yet or if they are even necessary for this. I think I could use functions to be called in the case for each main option but I might need to read a little first on high order functions before it makes any sense.

What's the right path? (^_^)

I have attached my game incase you want to take a look at the code in more detail and see how the menu works. 1 opens up the menu and Z is used as an action button.
Title: Re: My Very Simple Menu System
Post by: Radnen on August 04, 2013, 04:39:50 pm
While your code is great for simple menus (like pokémon's) it can get confusing on nested menus. Now I'm going to show you the idea of a callback or delegate.

To make a better menu you can use functions as if they were variables and call them from anywhere! To demonstrate:
Code: (javascript) [Select]

function OpenItemMenu()
{
    // show item menu in here.
}

my_menu.addItem("Items", OpenItemMenu);


Notice how we add a function as a parameter in the list? The trick is not calling the function, but passing it in for use later. Now, what does menu.addItem look like?

Code: (javascript) [Select]

function Menu()
{
    this.items = []; // [] is shorthand for an array.
}

Menu.prototype.addItem = function(name, callback)
{
    // here we create a JSON object and put that into an array.
    this.items.push( { name: name, call: callback } );
}

Menu.prototype.execute = function(x, y)
{
    var done = false;
    var choice = 0;

    while (!done) {
        if (IsMapEngineRunning()) RenderMap();

        for (var i = 0; i < this.items.length; ++i) {
            if (i == choice) {
                arrow.blit(x, y);
                font.setColorMask(cyan);
            }
            font.drawText(x + 16, y, this.items[i].name);
            font.setColorMask(white);
        }

        FlipScreen();

        while (AreKeysLeft()) {
            switch (GetKey) {
                 case KEY_ENTER:
                     this.items[choice].call(); // here is how a stored function is called :)
                 break;
                 //case KEY... // other keys
            }
        }
    }
}


The above is the gist of what's going on. A menu item doesn't have to be a string. In fact if you make a menu like so, and populate it you can keep them around and just call execute whenever you need to show it:
Code: (javascript) [Select]

var game_menu = new Menu();
game_menu.addItem("New Game", NewGame);
game_menu.addItem("Load Game", LoadGame);
game_menu.addItem("Options", Options);
game_menu.addItem("Quit", Exit);

function game()
{
    game_menu.execute(0, 0);
}


Title: Re: My Very Simple Menu System
Post by: Xenso on August 05, 2013, 06:30:33 am
Whats the difference between using

Code: (javascript) [Select]
function Menu()
{
    this.items = [];
}

Menu.prototype.addItem = function(name, callback)
{
    this.items.push( { name: name, call: callback } );
}


and this which seems cleaner

Code: (javascript) [Select]

function Menu()
{
    this.items = [];
    this.addItem =  function(name, callback) { this.items.push( { name: name, call: callback } ); }
}


I don't really get why prototype is used at all (what is it for?), cant we just use methods if the following code would still work all the same?

Code: (javascript) [Select]

var game_menu = new Menu();
game_menu.addItem("New Game", NewGame);


and if the only difference to the .drawchoice method is
Code: (javascript) [Select]
this.items[choice].call();


Is there a way we can instantiate that code and just add that in or do we have to re-write the entire .drawchoice method all over again just for the menu.

Apart from that I see how callback is very essential for a more dynamic menu system.
Title: Re: My Very Simple Menu System
Post by: Radnen on August 05, 2013, 06:49:08 am
Syntatically there is no difference in using prototype or setting it later. In performance there is - prototype is faster since it can be statically determined and compiled whereas setting a function using "this." happens later during runtime - it's slower to setup too and uses a bit more memory. But not much.

One advantage to the "this." version is you have access to 'private variables':
Code: (javascript) [Select]

function Obj()
{
    var private_x = 567;

    this.getX = function() {
        return private_x;
    }
}


Notice you cannot set the value - you can only get it which is great for making sure some things aren't accidentally changed.

You do not have to rewrite the entire choice menu. You just need to modify two lines of code by adding:
Code: (javascript) [Select]
this.items[choice].call();

and changing the drawText to this:
Code: (javascript) [Select]
font.drawText(x + 16, y, this.items[i].name);
Title: Re: My Very Simple Menu System
Post by: Xenso on August 05, 2013, 04:59:41 pm
I would need callback functions for common routine things like adding an item, removing an item, equipping something, getting a party members info for stats. This will definitely complete my menu nicely.

I read another interesting article on callback functions that helped clear out some other fuzz I had
http://javascriptissexy.com/understand-javascript-callback-functions-and-use-them/

Thanks for the tip, I'm going to try implement this starting tomorrow. its always better to start something on a new moon.
Title: Re: My Very Simple Menu System
Post by: Xenso on August 07, 2013, 01:19:32 pm
I have a stat object with two properties.
One for its name and one for its value.

How would I store all this information in text form so I can get an output like this
"stat name: stat value"
for 14 stats

I understand I can use a for loop and I am but how can I set the this.stat.value to a string and concatenate it with this.stat.name?

Code: (javascript) [Select]

this.stat = [];
this.stat.length = 14;
this.stat[0] = {name:"Level",value:1};
this.stat[1] = {name:"Exp",value:0};
this.stat[2] = {name:"MP",value:100};
this.stat[3] = {name:"HP",value:100};
this.stat[4] = {name:"Atk",value:10};
this.stat[5] = {name:"Def",value:10};
this.stat[6] = {name:"Strength",value:5};
this.stat[7] = {name:"Valor",value:5};
this.stat[8] = {name:"Wisdom",value:5};
this.stat[9] = {name:"Dexterity",value:5};
this.stat[10] ={name:"Intelligence",value:5};
this.stat[11] ={name:"Evasion",value:5};
this.stat[12] ={name:"Lucidity",value:5};
this.stat[13] ={name:"Recovery",value:5};
this.stat[14] ={name:"Charm",value:5};


How would I alter the stats once during start up depending on the class for each party member.
I hope I am using exponent in the right way. I want to change only a specific few values per character class
and give them a random value that averages to 10. This will later be used again for leveling up.

Code: (javascript) [Select]

this.set_stat = function()
   {
   //random number that is avarage of target
   function Exponential(target){return Math.floor(-target * Math.log(1 - Math.random()));}
   //Set Classes
      if(this.char_class == "Dream_Guardian")this.stat[7,9,11,12,15].value = Exponential(10);
else if(this.char_class == "Cyborg_Fighter")this.stat[6,7,8,10,13 ].value = Exponential(10);
else if(this.char_class == "Psycho_Mancer" )this.stat[10,12,11,9,8].value = Exponential(10);
else if(this.char_class == "Star_Serpant"  )this.stat[6,9,10,12,14].value = Exponential(10);
   }


I think I'm getting the hand of variable methods though. I learned of array.join() today but it did not really help/work maybe I'm using it all wrong. Any hints?

EDIT: Oh and realized yesterday that prototype is used for setting properties and methods when OUTSIDE the object...right?
Sorry for bugging you man.
Title: Re: My Very Simple Menu System
Post by: N E O on August 07, 2013, 01:55:41 pm
Two things: 1. You can't set this.stat.length, it's read-only based on what elements you set in the array so get rid of that line; 2. You can't index an array with comma-separated values and expect the results you want in JS, so rewrite set_stat to go through them item by item instead.
Title: Re: My Very Simple Menu System
Post by: Radnen on August 07, 2013, 02:27:51 pm
Even though this may work in JS it doesn't do what you wrote here:
Code: (javascript) [Select]

this.stat[7,9,11,12,15].value = Exponential(10);


In fact it'll set the value of the property last in that list, so the value at position 15 would have it's value set. Not all of them in the list. And actually I don't see that you have an element at the 15th spot, since your array only goes to 14. It might be 15 items, but that accounts for the starting 0.

To do something like that you might need to learn a bit of functional programming.

Code: (javascript) [Select]

MapOn(this.stat, [7, 9, 11, 12, 14], function() { return Exponential(10); });


Code: (javascript) [Select]

function MapOn(array, filter, func) {
    for (var i = 0; i < array.length; ++i) {
        if (Contains(filter, i)) array[i] = func();
    }
}

function ForEach(array, func, base) {
    for (var i = 0; i < array.length; ++i) {
        func.call(base, array[i], i, array);
    }
    return array;
}

function Contains(array, value) {
    for (var i = 0; i < array.length; ++i) {
        if (array[i] == value) return true;
    }
    return false;
}


So the above works by using functions to do work. MapOn takes 3 parameters an array, an array of where you want changes to go, and a function that does the change where you wanted the change to be. MapOn goes through each index of the array and sees if it's inside the other list of indices. If it is, it takes the i'th value and performs work on it via an anonymous function (what's also known as a lambda expression). In our case the work is a function that returns an exponential number centered around 10.

You don't want the value to be a string, because then it becomes useless for combat when you need to use the value in a damage formula of some kind. If you want to display it instead, just use string concatenation.

Code: (javascript) [Select]

// to display all stats:
for (var i = 0; i < array.length; ++i) {
    font.drawText(x, y + i * 16, array[i].name + ": " + array[i].value);
}

// or the functional approach:
ForEach(this.stat, function(obj, i) {
    font.drawText(x, y + i * 16, obj.name + ": " + obj.value);
});
Title: Re: My Very Simple Menu System
Post by: N E O on August 07, 2013, 02:35:19 pm
@Radnen - your first MapOn block is almost right; it instead replaces the whole item at each array index instead of setting only the value property of that item.

@Xenso - modify Radnen's approach so that you only set value of the array item instead of the whole array item and you should be good. 'Twill be a good programming exercise. :)
Title: Re: My Very Simple Menu System
Post by: Radnen on August 07, 2013, 03:37:08 pm
Oops, yeah I was looking at the wrong array for that and forgot that stat was an object with a value and name.

Thee are two ways to fix that code: one is easy but changes the meaning of MapOn, and the other preserves the meaning of MapOn while fixing the problem. I'll see which one you do and talk about the other. ;)
Title: Re: My Very Simple Menu System
Post by: Xenso on August 08, 2013, 06:02:57 am
Code: (javascript) [Select]

{
function MapOn(array, filter, func) {
    for (var i = 0; i < array.length; ++i) {
        if (Contains(filter, i)) array[i].value = func();
    }
}


I did the easy way and just hard coded array.value in.
I tried using an argument before but it gave me an error saying the argument was undefined at that moment which is true.
Title: Re: My Very Simple Menu System
Post by: Xenso on August 08, 2013, 02:36:56 pm
Code: (javascript) [Select]

function Exponential(target)
{
    return Math.floor(-target * Math.log(1 - Math.random()));
}

How can I set a random value once?

for example
Code: (javascript) [Select]
this.stat[6] = {name:"Strength",value:Exponential(10)};


My menu is running in an update script and each time I run the menu it gets a new exponential value for this.stat[6].value

Is there a way I can stop the values from updating themselves? Like how would I run a Menu NOT in an update script?
Title: Re: My Very Simple Menu System
Post by: Radnen on August 08, 2013, 02:50:58 pm

How can I set a random value once?


By setting it once of course. :P

Code: (javascript) [Select]

var exp = Exponential(10);

this.stat[6] = {name:"Strength",value: exp};
// ...



Is there a way I can stop the values from updating themselves? Like how would I run a Menu NOT in an update script?


Well you can still run a menu in an update script. The trick is to create the stats only once somewhere outside of a loop, preferably when you start a new game. Displaying the stats shouldn't cause them to always update.
Title: Re: My Very Simple Menu System
Post by: Xenso on August 08, 2013, 03:42:11 pm
I actually already tried that.

I have a function that sets the values for the players stats
Code: (javascript) [Select]

function set_stat(member)
{
var equ = Equilikely(10,15);
var exp = Exponential(15);
      for(i=6;i<member.stat.length;++i) { member.stat[i].value = equ}
      if(member.character_class === "Dream Guardian"  )MapOn(member.stat, [7, 9, 11, 12, 14], function() { return exp;});
else if(member.character_class === "Zio-Knite Cyborg")MapOn(member.stat, [6, 7,  8, 10, 13], function() { return exp;});
else if(member.character_class === "Psychomancer"    )MapOn(member.stat, [8, 10, 9, 11, 12], function() { return exp;});
else if(member.character_class === "Star Serpant"    )MapOn(member.stat, [6, 7,  9, 10, 12], function() { return exp;});
}


Code: (javascript) [Select]

this.member = [];

this.member[0] = new character("Yunema",face[2],"Dream Elf","Dream Guardian")
set_stat(this.member[0])

this.member[1] = new character("Seishin",face[1],"Human","Psychomancer")
set_stat(this.member[1]);

this.member[2] = new character("Bee-Z-Bulb",face[3],"Robot","Zio-Knite Cyborg")
set_stat(this.member[2]);

this.member[3] = new character("ItzelCoatl",face[4],"Yokai","Star Serpant")
set_stat(this.member[3]);



but I get the same problem

Apart from that the menu is coming along nicely and I got my NPC's to move randomly. I'm keeping a smile on my face just for that (^_^)
Title: Re: My Very Simple Menu System
Post by: Radnen on August 08, 2013, 05:04:04 pm
I don't know exactly what you mean by stopping them from updating themselves. It looks like everything is correct.
Title: Re: My Very Simple Menu System
Post by: Fat Cerberus on August 09, 2013, 01:30:46 am
He's saying the stats of his characters change every time he closes and reopens the menu.  This means that, for whatever reason, his stat setting code (or worse, the characters are being re-generated wholesale) is getting called every time the menu is shown.
Title: Re: My Very Simple Menu System
Post by: Xenso on August 09, 2013, 03:07:24 am
I think Radnen said something about running something out of a loop. My entire menu is inside a loop, I thought this was the right way to do it. I'll examine my code and see whats really going on. It could be a minor logic-error with where I'm setting the characters stats.
I've attached the game so you guys can have a look through and we can solve this problem together ::).

After downloading the game. Extract it and run it in Sphere. I reccomend Scale 2x since my graphics are small 16x16

KEY_1 Opens up the Menu
Go to Party ( Navigate using the arrow keys )
Hit KEY_Z to select it
Choose any Member
Select Status

Notice how after reading the stats if you read them again while in the menu they are the same

Notice how after reading the stats if you close the menu and open the menu again then go back to read them, the stats change

Notice how its the same for all characters
Title: Re: My Very Simple Menu System
Post by: Radnen on August 09, 2013, 04:38:28 am
Oh wow that was a pretty terrible menu design. See, what you were doing was creaing a bunch of objects in Menu() at the begenning and no sooner than using them you throw them away. In JS, just like in most languages objects you create within the scope of a function are deleted right after the function ends.

Code: (javascript) [Select]

// the gist of what you are doing:
function Show()
{
    function do_stuff() {
        //...
    }

    var party = [];
    CreateParty(party);

    do_stuff(party);
}

SetUpdateScript("Show();");


The problem with the above is that each time you call Show, a party is created, displayed and then at that last '{' bracket, the party is destroyed. The next time you see the party you have different stats, not only that you can't level up or add or remove stats.

You were also creating local functions in a continuously updating loop. I figured that would have a huge impact on the FPS and it did, your maximum FPS is 900 on my machine,  with my code fix it's at over 1800. I'll attach my fixes, they aren't pretty but it solves the problem. Basically I just created a single initializer, it runs once to add the info and then that's it. Ideally you want to have a global manager do this stuff for you. What happens if you need to add 3 hp to the 4th party member? It's not that easy in your design. In fact your menu shouldn't contain the actual items/party members/etc. Only represent them. Code design can be harder than the actual programming. You have to identify the code flow and know what values must persist (and this stay in the global scope) and what values can be thrown away.

I'd create a global static variable that holds all of the party info and then use that variable in my menu code to display party members.

Example, based on your code ideas:
Code: (javascript) [Select]

var PartyManager = (function() {

    var members = [];
    var exp = Exponential(10);
    members[0] = new Player("name_here", 85, 6, 45, 4);
    MapOn(members[0].stats, [2, 3, 5, 6, 8], function() { return exp; });
    var exp = Exponential(10);
    members[1] = new Player("name_here", 85, 6, 45, 4);
    MapOn(members[1].stats, [5, 6, 7, 9, 10], function() { return exp; });

    return {
        players: members,
        money: 0,
    }

}());

Abort(PartyManager.players[0].name);



If you ever have to do something like this:
Menu.party[4].hp += 3;

Then that's bad design, it should be something like:
PartyManager.player[4].hp += 3;

And the menu would have the changes automatically. In fact you should be able to create and throw away menus without affecting the party/items/etc in any way.
Title: Re: My Very Simple Menu System
Post by: Xenso on August 09, 2013, 04:48:11 am
 :( I'm going to scrap everything and stat again properly. You're menu has the same problem but its my fault. I should have designed the menu better. The skeleton is the most important part and I messed that up.
Title: Re: My Very Simple Menu System
Post by: Radnen on August 09, 2013, 04:57:26 am

:( I'm going to scrap everything and stat again properly. You're menu has the same problem but its my fault. I should have designed the menu better. The skeleton is the most important part and I messed that up.


Well I didn't know what values to expect. All I could do was a single initialization step if there was more to it, it was actually too hard for me to figure out. ;) Sometimes code isn't difficult but when you are learning you kinda make it more difficult, I ran into this problem long ago on my first games where it got so convoluted I had no idea where exactly to debug my code!

A good design from the beginning is always miles better. I'd say you had a good start with the menu - you just now need to focus on design. ;)
Title: Re: My Very Simple Menu System
Post by: N E O on August 09, 2013, 04:47:34 pm
Given that this is related to menu design, are there suggestions on how I can improve my menu tutorial?
Title: Re: My Very Simple Menu System
Post by: Radnen on August 09, 2013, 05:48:18 pm
I would add that a menu should only be used to display info, not used to carry your actual data throughout gameplay. I think that's an important distinction for newer users.
Title: Re: My Very Simple Menu System
Post by: N E O on August 09, 2013, 06:03:29 pm
Good idea!

edit: aaaaand done :)
Title: Re: My Very Simple Menu System
Post by: Xenso on August 10, 2013, 01:52:49 am
I'm going re-read that tutorial, I sort of got exited and rushed into coding thinking oh I'll figure it out as I go along.
Thanks for the pointers, I've decided to simultaneously take a crash course in JavaScript too, there are a lot of simple things like using variables to store values and how to properly use and create objects that I rushed through learning or learned from seeing it in other code but never really understood the theory/method to it. These small things might have not seemed important to understand but they creep up on you. Just like the menu I need to build a foundation in Javascript so as I really know what I'm doing.

Back to the drawing board! (Always wanted to say that ) (^_^)