Skip to main content

News

Topic: My Very Simple Menu System (Read 6022 times) previous topic - next topic

My Very Simple Menu System
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.
  • Last Edit: August 04, 2013, 04:29:03 pm by Xenso

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: My Very Simple Menu System
Reply #1
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);
}


If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

Re: My Very Simple Menu System
Reply #2
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.
  • Last Edit: August 05, 2013, 06:36:43 am by Xenso

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: My Very Simple Menu System
Reply #3
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);
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

Re: My Very Simple Menu System
Reply #4
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.

Re: My Very Simple Menu System
Reply #5
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.
  • Last Edit: August 07, 2013, 01:29:23 pm by Xenso

  • N E O
  • [*][*][*][*][*]
  • Administrator
  • Senior Administrator
Re: My Very Simple Menu System
Reply #6
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.

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: My Very Simple Menu System
Reply #7
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);
});
  • Last Edit: August 07, 2013, 03:35:04 pm by Radnen
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

  • N E O
  • [*][*][*][*][*]
  • Administrator
  • Senior Administrator
Re: My Very Simple Menu System
Reply #8
@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. :)

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: My Very Simple Menu System
Reply #9
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. ;)
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

Re: My Very Simple Menu System
Reply #10
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.
  • Last Edit: August 08, 2013, 02:36:40 pm by Xenso

Re: My Very Simple Menu System
Reply #11
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?

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: My Very Simple Menu System
Reply #12

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.
  • Last Edit: August 08, 2013, 05:02:28 pm by Radnen
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

Re: My Very Simple Menu System
Reply #13
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 (^_^)
  • Last Edit: August 08, 2013, 04:14:16 pm by Xenso

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: My Very Simple Menu System
Reply #14
I don't know exactly what you mean by stopping them from updating themselves. It looks like everything is correct.
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here