Need proper script syntax for while loop

Nilom

Veteran
Veteran
Joined
Sep 9, 2013
Messages
178
Reaction score
40
First Language
German
Primarily Uses
RMMV
Good evening!

For my project I'm designing a class which has some "card draw" (skill draw) mechanics. It will learn a given amount of its classes skills every turn and unlearn them on the end of turn.
I guess I got the basic principle of this right:

for (var i = 4; i >= 1; i--) {

if ($gameActors.actor(i).currentClass().id == 5) { //check if the actor is this class that has deck mechanics

var drawn1 = Math.randomInt(13)+67

var drawn2 = Math.randomInt(13)+67

var drawn3 = Math.randomInt(13)+67

$gameActors.actor(i).learnSkill(drawn1)

$gameActors.actor(i).learnSkill(drawn2)

$gameActors.actor(i).learnSkill(drawn3)

}}

The issue with it is that unlike a card game, skills that are already learned cannot be "drawn" (learned) again. So occassionally instead of drawing 3 skills it will happen that only 2 are drawn. And extremely rarely only 1. This happens if the skills were already learned. So in translation: If the random number (var drawn) is the same number multiple times.

I suppose I need a while loop that will break when X ( = draw number) different skills are learned. I just don't know the syntax for this.
Could someone please check if I did it right and translate this in proper RPG Maker MV script syntax? :biggrin:

for (var i = 4; i >= 1; i--) {

if ($gameActors.actor(i).currentClass().id == 5) { //check if the actor is this class that has deck mechanics

var learned = 0

var draw_amount = 3

while (learned<draw_amount) {

var drawn = Math.randomInt(13)+67

if !($gameActors.actor(i).skills().contains($dataSkills[drawn]) {

$gameActors.actor(i).learnSkill(drawn)

learned +=1

}

}}}

Thank you in advance! :smile:
 

Aesica

undefined
Veteran
Joined
May 12, 2018
Messages
1,609
Reaction score
1,515
First Language
English
Primarily Uses
RMMV
The easiest way to simulate drawing something from a deck (can only draw X once) is build an array of things, then do something like this:

Code:
var skillsDeck = [10, 11, 12, 13, 14, 15];
for (var i = 0; i < 3; i++)
{
  let drawId = Math.floor(Math.random() * skillsDeck.length);
  someActor.learnSkill(skillsDeck.splice(drawId, 1)[0]);
}

Specifically, splice is an array function that removes entries from the array and return them in a new array (hence [0]), so once a skill is "drawn" it can't be "drawn" again. Unless you have multiple same entries in the array, of course.
 

Nilom

Veteran
Veteran
Joined
Sep 9, 2013
Messages
178
Reaction score
40
First Language
German
Primarily Uses
RMMV
The easiest way to simulate drawing something from a deck (can only draw X once) is build an array of things, then do something like this:

Ahhhh! Why didn't I think of arrays? I used them a lot in Game Maker. Thank you for that idea! That would safe some performance.

Specifically, splice is an array function that removes entries from the array and return them in a new array (hence [0]), so once a skill is "drawn" it can't be "drawn" again. Unless you have multiple same entries in the array, of course.

So that means "splice" can move the array entries in other arrays like "hand" or "discard pile"? Sorry to be dumb but how do I splice an array entry into another array? :smile:
 

Shaz

Global Moderators
Global Mod
Joined
Mar 2, 2012
Messages
40,838
Reaction score
14,031
First Language
English
Primarily Uses
RMMV
The beauty of using arrays, especially if you prepopulate them with ONLY the valid options, then remove the options as they're chosen, is that you're not going to ever get the freakish situation where you draw invalid option after invalid option and end up in a very long loop while it's trying to choose a valid option (you could get this is only one option out of 20 were valid, and you were randomly choosing from the same full list each time)
 

Engr. Adiktuzmiko

Chemical Engineer, Game Developer, Using BlinkBoy'
Veteran
Joined
May 15, 2012
Messages
14,682
Reaction score
3,004
First Language
Tagalog
Primarily Uses
RMVXA
So that means "splice" can move the array entries in other arrays like "hand" or "discard pile"? Sorry to be dumb but how do I splice an array entry into another array?

var newArray = oldArray.splice(start,length);
 

Aesica

undefined
Veteran
Joined
May 12, 2018
Messages
1,609
Reaction score
1,515
First Language
English
Primarily Uses
RMMV
Ahhhh! Why didn't I think of arrays? I used them a lot in Game Maker. Thank you for that idea! That would safe some performance.



So that means "splice" can move the array entries in other arrays like "hand" or "discard pile"? Sorry to be dumb but how do I splice an array entry into another array? :smile:
Splice creates a new array from the item(s) removed from the array it's called on. If you want to create a discard pile/array, it'd probably be better to push the spliced value into your discard array, then reference the discard array's last entry (what you just pushed) to add it to the player.

Also, because that was a quickie, I forgot to mention that it's worth checking to make sure a valid skill id is retrieved. If the array is empty, nothing should be added to the player obviously because there's nothing left.
 

Nilom

Veteran
Veteran
Joined
Sep 9, 2013
Messages
178
Reaction score
40
First Language
German
Primarily Uses
RMMV
The beauty of using arrays, especially if you prepopulate them with ONLY the valid options, then remove the options as they're chosen, is that you're not going to ever get the freakish situation where you draw invalid option after invalid option and end up in a very long loop while it's trying to choose a valid option (you could get this is only one option out of 20 were valid, and you were randomly choosing from the same full list each time)

Yeah that's a point. I'm trying to do so.


Splice creates a new array from the item(s) removed from the array it's called on. If you want to create a discard pile/array, it'd probably be better to push the spliced value into your discard array, then reference the discard array's last entry (what you just pushed) to add it to the player.

Wow that's hard for a noob like me to imagine. In my project I currently will not need a discard pile but for the sake of learning I would like to understand this.
So I have one array for the valid "Deck mechanic skills", skillsDeck. I could have one array for the discard pile, discardDeck. And let's say an array for hand cards, handCards. Would that be correct?

Code:
for (var i = 4; i >= 1; i--) {                 //i = number of actors

if ($gameActors.actor(i).currentClass().id == 5) {            //check if it's the deck mechanic class

var skillsDeck = [10, 11, 12, 13, 14, 15];
var handCards = [];

for (var i = 0; i < 3; i++) {

let drawID = Math.floor(Math.random() * skillsDeck.length);

$gameActors.actor(i).learnSkill(drawID);

handCards.push() = skillsDeck.splice(drawID, 1);

}}}

I'm pretty sure this is wrong. It's hard for me to understand code descriptions if I do not see the actual code. Because I'm still a rookie. :)


Also, because that was a quickie, I forgot to mention that it's worth checking to make sure a valid skill id is retrieved. If the array is empty, nothing should be added to the player obviously because there's nothing left.

Ah yeah. The array will never be empty in my case. The drawn/learned skills will be unlearned at the end of turn and the array will be reset to contain its former values.
But if I would need to check this that would be:

if (skillsDeck.length>0) { .. }

Right?


I have another question regarding this. What is the difference between Math.floor(Math.random() * x) and Math.randomInt(x) + y? Is there a benefit (technical or performance wise) over using one over the other?

Thank you for the patience! :D




Edit:


I modified the code above as there were some things that I didn't consider before.
The actual code looks like this:

Code:
for (var i = $player_count; i >= 1; i--) {         //$player_count is 1 to 4

    if ($gameActors.actor(i).currentClass().id == 5) {        //check if class with drawing mechanism

        for (var b = 67; b < 119; b++) {        //run through all of classes skills

            if $gameActors.actor(i).skills().contains($dataSkills[b]) {         //if actor/player learned the skill

                $skillsDeck[i].push(b)          //insert the skill into the deck array

                $skillsLearn[i].push(b)         //store unlearned skills in an array to learn them back at the end of combat

                $gameActors.actor(i).forgetSkill(b)         //unlearn skill (player needs to draw it instead, for learning it again)

            }
        }
        for (var c = 0; c < 3; c++) {         //drawing mechanism, 3=number of cards to draw

            let drawID = Math.floor(Math.random() * $skillsDeck[i].length);

            $gameActors.actor(i).learnSkill(drawID);

            $handCards[i].push($skillsDeck[i].splice(drawID,1)) ;         //store hand cards in array and remove deck cards from skillsDeck array of actor i
        }
    }
}

It will be run at the start of combat and the drawing part below each round.

Then at the start of each round this will be run:

Code:
for (var i = $player_count; i >= 1; i--) {

    if ($gameActors.actor(i).currentClass().id == 5) {

        $skillsDeck[i] = $skillsLearn[i] //reset the deck

        for (var b = $handCards[i].length; b > = 0 ;  b--) { //unlearn previously drawn skills and delete handCards array entry for them

            $gameActors.actor(i).forgetSkill(b)

            $handCards[i].splice(b,1)

        }

        for (var c = 0; c < 3; c++) {

            let drawID = Math.floor(Math.random() * $skillsDeck[i].length);

            $gameActors.actor(i).learnSkill(drawID);

            $handCards[i].push($skillsDeck[i].splice(drawID,1));
        }
    }
}

The previously learned/drawn skills will be unlearned and the hand array will be reset. Then new skills will be drawn.

At the end of battle all skills stored in $skillsLearn[x] will be relearned and the arrays will be emptied:

Code:
for (var i = $player_count; i >= 1; i--) {

    if ($gameActors.actor(i).currentClass().id == 5) {

        for (d = skillsLearn[i].lenght; d = 0; d--) {

        $gameActors.actor(i).learnSkill(d);

        }

    $skillsDeck[i] = []

    $handCards[i] = []

    }

}


I know it is much to read and instead of asking I would just test this out. But at the moment I can not test this in my project yet.

Is the code and thought behind it correct the way I did it? :smile:
 
Last edited:

Aesica

undefined
Veteran
Joined
May 12, 2018
Messages
1,609
Reaction score
1,515
First Language
English
Primarily Uses
RMMV
I have another question regarding this. What is the difference between Math.floor(Math.random() * x) and Math.randomInt(x) + y? Is there a benefit (technical or performance wise) over using one over the other?
Math.randomInt() isn't a native function of the Math class, and was added in rpg_core.js - the entire contents of the function is literally the former approach. While technically, Math.floor(Math.random() * x) is one less function call, that tiny bit of performance optimization isn't at all necessary in just about any case we'll be working with in RM.

Code:
for (var i = $player_count; i >= 1; i--) {         //$player_count is 1 to 4

    if ($gameActors.actor(i).currentClass().id == 5) {        //check if class with drawing mechanism

        for (var b = 67; b < 119; b++) {        //run through all of classes skills

            if $gameActors.actor(i).skills().contains($dataSkills[b]) {         //if actor/player learned the skill

                $skillsDeck[i].push(b)          //insert the skill into the deck array

                $skillsLearn[i].push(b)         //store unlearned skills in an array to learn them back at the end of combat

                $gameActors.actor(i).forgetSkill(b)         //unlearn skill (player needs to draw it instead, for learning it again)

            }
        }
        for (var c = 0; c < 3; c++) {         //drawing mechanism, 3=number of cards to draw

            let drawID = Math.floor(Math.random() * $skillsDeck[i].length);

            $gameActors.actor(i).learnSkill(drawID);

            $handCards[i].push($skillsDeck[i].splice(drawID,1)) ;         //store hand cards in array and remove deck cards from skillsDeck array of actor i
        }
    }
}
At a glance:
$gameActors.actor(i).learnSkill(drawID);
...should be...
$gameActors.actor(i).learnSkill($skillsDeck[drawID]);
...since drawID is an array index in $skillsDeck that points to a skill id. It is NOT a skill id itself. $skillsDeck[drawID] references the proper skill id. Same for the $handCards and forget skills section that references drawID.

You know, a MUCH simpler approach is to just use states to teach these skills temporarily. These states can be set to expire in a single round and at the end of combat, so all you'd really have to do is refresh the array of things for them to learn the following round and add the new hand. The engine's state management code would take care of all the cleanup for you. Something like:

Code:
var skillStates, actors = $gameParty.battleMembers();
for (i in actors)
{
  skillStates = [24, 25, 26, 27, 28, 29]; // whatever states you have the skills attached to
  for (var j = 0; j < 3; j++)
  {
    let skillId = Math.randomInt(skillStates.length);
    let state = skillStates.splice(skillId, 1)[0];
    actors.addState(state);
  }
}

For the states, each one should be set add the appropriate skill to the user, expire at the end of combat, and expire at the end of either 1 turn or 1 action (try and see which works best for you).
 

Nilom

Veteran
Veteran
Joined
Sep 9, 2013
Messages
178
Reaction score
40
First Language
German
Primarily Uses
RMMV
Math.randomInt() isn't a native function of the Math class, and was added in rpg_core.js - the entire contents of the function is literally the former approach. While technically, Math.floor(Math.random() * x) is one less function call, that tiny bit of performance optimization isn't at all necessary in just about any case we'll be working with in RM.

Thanks for clarification. Then I will use Math.randomInt() for better readability. :smile:


At a glance:
$gameActors.actor(i).learnSkill(drawID);
...should be...
$gameActors.actor(i).learnSkill($skillsDeck[drawID]);
...since drawID is an array index in $skillsDeck that points to a skill id. It is NOT a skill id itself. $skillsDeck[drawID] references the proper skill id. Same for the $handCards and forget skills section that references drawID.

Yeah that part of the code was heavily bugged before. :biggrin:
I forgot to update the code in this thread. I had changed it to:

Code:
$skillsDeck = []
$skillsLearn = []
$handCards = []
for (var i = 4; i > 0; i--) {
$skillsDeck[i] = []
$skillsLearn[i] = []
$handCards[i] = []
}


for (var i = 1; i < 5; i++) { console.log("================================================================================"); console.log(" PLAYER "+i); console.log("================================================================================");
    if ($gameActors.actor(i).currentClass().id == 5) {
        for (var b = 67; b < 119; b++) {
            if ($gameActors.actor(i).skills().contains($dataSkills[b])) {
                $skillsDeck[i].push(b); console.log("skillsDeck[" + i + "] = " + b);
                $skillsLearn[i].push(b);
                $gameActors.actor(i).forgetSkill(b); }}
        for (var c = 0; c < 3; c++) {
            let drawID = Math.randomInt($skillsDeck[i].length); console.log("draw iteration #: " + c); console.log("$skillsDeck[" + i + "].length (Number of cards in the deck) = " + $skillsDeck[i].length);
            $gameActors.actor(i).learnSkill($skillsDeck[i][drawID]); console.log("randomly rolled Deck["+i+"] array position #= "+drawID); console.log("attempting to learn skill ID: "+$skillsDeck[i][drawID]);
            $handCards[i].push($skillsDeck[i].splice(drawID,1)); console.log("Hand Size = "+$handCards[i].length)
        }
    }
}

With that I got it to finally work. I had to use $gameActors.actor(i).learnSkill($skillsDeck[drawID]) to make it learn the right skills. Do not wonder about the squished console logs. In the editor the script command space is very limited. :biggrin:


You know, a MUCH simpler approach is to just use states to teach these skills temporarily. These states can be set to expire in a single round and at the end of combat, so all you'd really have to do is refresh the array of things for them to learn the following round and add the new hand. The engine's state management code would take care of all the cleanup for you. Something like:

You are right that would simplify the code. But I want to take into account what skills the actors have learned. That way it is a lot more flexible and they can learn new cards/skills through level ups or items or rewards. And I do not want to have too many states on the chars, as I will heavily utilize states for actual skills and passives. But thank you for the suggested code. :smile:

Now there is only a problem with discarding the "hand cards" at the start of the new turn. I guess I use the .splice() method wrongly.



Edit:


Whatever I try to do, I can not get the unlearn skill part to work. In the log file I can not find any errors. That what the code does with the variables looks just fine to me. But the game just doesn't let the actor(s) unlearn the skills.




What did I do wrong? Is it a problem with the 2D array? But for the logfile I used exactly the same variable
Code:
$handCards[i][0]
and it shows the exact right skill ID.

Please help! :frown::biggrin:



Edit 2, sorry for better log readability:

BATTLE START:


ROUND 2:





And the full code:


Code:
for (var i = 1; i < 5; i++) { //console.log("================================================================================"); console.log(" PLAYER "+i); console.log("================================================================================");
    if ($gameActors.actor(i).currentClass().id == 5) {
        $skillsDeck[i] = $skillsLearn[i]; console.log("Resetting Skills Deck to: "+$skillsLearn[i]);
        for (var b = $handCards[i].length; b > 0;  b--) { 
            $gameActors.actor(i).forgetSkill($handCards[i][0]); console.log("Hand Size = "+$handCards[i].length); console.log("Hand Cards: "+$handCards[i]); console.log("Hand Card 1 ID: "+$handCards[i][0]); console.log("Hand Card 2 ID: "+$handCards[i][1]); console.log("Hand Card 3 ID: "+$handCards[i][2]); console.log("----- Attempting to forget skill ID: "+$handCards[i][0]+" -----");
            $handCards[i].splice(0,1); }
        for (var c = 0; c < 3; c++) { console.log("Hand Cards: "+$handCards[i]);
            let drawID = Math.randomInt($skillsDeck[i].length); console.log("Draw iteration #: " + c); console.log("$skillsDeck[" + i + "].length (Number of cards in the deck) = " + $skillsDeck[i].length);
            $gameActors.actor(i).learnSkill($skillsDeck[i][drawID]); console.log("randomly rolled Deck["+i+"] array position #= "+drawID); console.log("attempting to learn skill ID: "+$skillsDeck[i][drawID]);
            $handCards[i].push($skillsDeck[i].splice(drawID,1)); console.log("Hand Size = "+$handCards[i].length); console.log("Hand Cards: "+$handCards[i]); console.log("Hand Card 1 ID: "+$handCards[i][0]); console.log("Hand Card 2 ID: "+$handCards[i][1]); console.log("Hand Card 3 ID: "+$handCards[i][2]); 
        }
    }
}
 
Last edited:

Poryg

Dark Lord of the Castle of Javascreeps
Veteran
Joined
Mar 23, 2017
Messages
4,125
Reaction score
10,644
First Language
Czech
Primarily Uses
RMMV
What kind of edits did you make to the forgetSkill?
 

Nilom

Veteran
Veteran
Joined
Sep 9, 2013
Messages
178
Reaction score
40
First Language
German
Primarily Uses
RMMV
What kind of edits did you make to the forgetSkill?

Do you mean plugin wise? I use a couple of Yanfly's plugins. Could that be a cause of issue?

(Btw. I updated my post for better logfiles and for the code)
 

Poryg

Dark Lord of the Castle of Javascreeps
Veteran
Joined
Mar 23, 2017
Messages
4,125
Reaction score
10,644
First Language
Czech
Primarily Uses
RMMV
No, I meant your edits. The code looks alright at first glance and if you didn't do any edits to the forgetSkill method, I'm not sure why would it not work. If you type it in console manually, would it work?
 

Nilom

Veteran
Veteran
Joined
Sep 9, 2013
Messages
178
Reaction score
40
First Language
German
Primarily Uses
RMMV
No, I meant your edits. The code looks alright at first glance and if you didn't do any edits to the forgetSkill method, I'm not sure why would it not work. If you type it in console manually, would it work?

I didn't even know you can put it manually o_o. I will try that out! :D

Oh and I did no edits to the forgetSkill method.

When I put that manually $gameActors.actor(1).forgetSkill($handCards[1][0]) it tells me undefined. But if I type $handCards[1][0] it told me a skill ID (96).



:LZYhuh:



Edit:

That seems strange. I mean, I never tried that before but shouldn't it be unlearned 100% if I directly put the skill IDs? And if I check if the char has these skills it says no ._.




Edit 2:

Oh wait! Returning to the main commands in battle and going back in to the skills made the skills disappear! But why doesn't this work with the script command? oO
 
Last edited:

Aesica

undefined
Veteran
Joined
May 12, 2018
Messages
1,609
Reaction score
1,515
First Language
English
Primarily Uses
RMMV
Your forget loop is a bit weird. When cycling through array elements in reverse order (i--) you have to do it like this if you want to touch every element in the array:

for (var i = someArray.length - 1; i >= 0; i--)
{
...do stuff
}

That's because an array with a length of 5 has values assigned to index 0, 1, 2, 3, and 4. Your loop appears to be looking at element 5 (undefined) and skipping element 0.
 

Nilom

Veteran
Veteran
Joined
Sep 9, 2013
Messages
178
Reaction score
40
First Language
German
Primarily Uses
RMMV
Oh really? I thought the .length will return a value between 1 and x. I read it there.
I changed the for function but unfortunately the issue still persists.



Edit:

Lol I did some more testing. Everything with a red X was not working and with a red tick was working.



So what? Inside ".forgetSkill()" arrays can not be referenced/read?? Is that intended by the developers?
I tested it again with all my plugins turned off and the results were the same.


Okay this did not work either:



$handCards[1][0] returns 118 but when I want to save that ID (118) in a var variablename then it doesn't work either!

Let's conclude:

In .forgetSkill() ...
..numbers do work
..variables do work

..arrays do not work
..variables that saved a simple number from an array do not work either

Ok how am I supposed to get this working? How can I "extract" the ID from the array inside the .forgetSkill() method? :barf::troll:
 
Last edited:

Poryg

Dark Lord of the Castle of Javascreeps
Veteran
Joined
Mar 23, 2017
Messages
4,125
Reaction score
10,644
First Language
Czech
Primarily Uses
RMMV
Oooh, I see!

$handCards[1][0] is not a number, but an array! So the number is $handCards[1][0][0]!
 

Aesica

undefined
Veteran
Joined
May 12, 2018
Messages
1,609
Reaction score
1,515
First Language
English
Primarily Uses
RMMV
^ Ha! Somehow I missed that too.

Yeah, it's important to realize that splice returns an ARRAY, even if you only cut out one thing.

So given the following:

var myArray = [3, 5, 7, 9];
var value = myArray.splice(0, 1);

value is [3], which is an array of only one element. If you write your splice like so:

var value = myArray.splice(0, 1)[0];

value is now 3, which is what we want it to be since, after splicing, you're requesting the first element [0] in the array.

Also since length returns the number of elements in an array, it will return 0 if the array is empty. The last element in a non-empty array is always going to be length - 1
 

Nilom

Veteran
Veteran
Joined
Sep 9, 2013
Messages
178
Reaction score
40
First Language
German
Primarily Uses
RMMV
$handCards[1][0] is not a number, but an array! So the number is $handCards[1][0][0]!
Yeah, it's important to realize that splice returns an ARRAY, even if you only cut out one thing.

*Mindblow*
Yay! It works perfectly fine now! At the start of battle all character card skills will be unlearned (all other will be kept). Then at the start of each turn 3 of the cards will be drawn randomly. :smile:

Thank you both so much for helping me solve this! :kaoluv:
 

Latest Threads

Latest Posts

Latest Profile Posts

Looking back at some sketches, and game design documents on my PC dated summer of 2015. I started development with the release of MZ, but in 2015, I felt a strong desire to make a game out of the blue. I remember feeling sad for no apparent reason, and all these ideas rushed into my head. Now that I think about it, since that day, everything has become easier to do on my PC . . . it’s very creepy.
Everything's going to be alright! We're all in this together. <3
Aaaaannd published my game's tech demo. :D

Feel free to download and play it. And give feedback!
Hey everyone, we know that the edit bar is missing. We're working on it. You can talk about it in the announcement here: https://forums.rpgmakerweb.com/index.php?threads/forum-errors-missing-edit-bar-etc.132715/
So, explain why we can no longer use BBC code or smilies in our posts? This sparks much sadness...

Forum statistics

Threads
107,794
Messages
1,032,254
Members
139,941
Latest member
Finley
Top