[Tutorial] Using strcmp without strtok, but more effective
#1

Introduction


Hello dear readers! In this tutorial I will try to show you my way of making commands.
I saw a lot of people complaining about strcmp and strtok speed. But in this scripting language it’s not hard to avoid strtok(what I think used the most time). In my opinion speed of this will be very fast. As I already said, I think, it’s fast and I hope experts in this field will comment the speed of it and and tell us if it is better or worse than other command systems as ZCMD,YCMD, or any else.

For me, the best way of understanding things about programming is trough examples.
That’s the reason I will try to teach you by making three commands. One “easy” one a “medium” and one “hard”,


Easy command (/suicide)
pawn Код:
public OnPlayerCommandText(playerid, cmdtext[])
{
    if(!strcmp(cmdtext,"/suicide",true,8) && !cmdtext[8])
    {
        SendClientMessage(playerid,COLOR_ORANGE,"Server:{FFFFFF} It's ilegal to use this function to avoid being arrested, killed, etc.");
        SetPlayerHealth(playerid,0);
        return 1;
    }
    return 0;
}
now explanation :
1)
pawn Код:
!strcmp(cmdtext,"/suicide",true,8)
strcmp checks if the two strings are equal( in this case ). If they are equal strcmp returns 0(lie, non-truth). With '!' we are telling program to make result opposite( if strings are equal strcmp will return
truth ( anything expect 0)) .
2)
pawn Код:
&& !cmdtext[8]
!cmdtext[8] is checking if on the 9th place in string is "End of string( aka EOS)", which value is 0(lie)- again in need to make the result opposite. I used && so both conditions need to be truth.
3)
pawn Код:
SendClientMessage(playerid,COLOR_ORANGE,"Server:{FFFFFF} It's ilegal to use this function to avoid being arrested, killed, etc.");//sending message to command user.
SetPlayerHealth(playerid,0);//Killing player
return 1;// command is done well.
Medium command (/report)
pawn Код:
public OnPlayerCommandText(playerid, cmdtext[])
{
    if(!strcmp("/report", cmdtext, true, 7))//first 7 char are /,r,e,p,o,r,t. Note that here we don't expect that on 8th place is EOS.
    {
        if(!cmdtext[7])return SendClientMessage(playerid,0xFFFFFF,"Ussage: /report [Text]");//if on the 8th place is EOS send usage message.
        if(cmdtext[7]==' ')// in case 8th char is space execute the command
        {
            new i;//defining a variable
            for(i=8;cmdtext[i]==' ';i++){}//checking if there are more spaces after 8th char. If so, we will cut that from message that is sent to admins.
            if(!cmdtext[i])return SendClientMessage(playerid,0xFFFFFF,"Ussage: /report [Text]");//if first char after space(s) is EOS stop the execution and send the usage message.
            new String[128+MAX_PLAYER_NAME];//declaring new variable
            GetPlayerName(playerid,String,MAX_PLAYER_NAME);//getting name of the reporter and storing it in String.
            format(String,128+MAX_PLAYER_NAME,"%s[%d] reporting: %s",String,playerid,cmdtext[i]);//Constructing the report message. cmdtext[i] is usually one char. But his time it is string( part of cmdtext with start from i-pos and end with EOS)
            for(i=0;i<MAX_PLAYERS;i++)if(IsPlayerConnected(i)&& !IsPlayerNPC(i))//looping all players that could be admins
            {
                if(IsPlayerAdmin(i) /* AdminLevel[i]>0*/)//checking if they are admins
                {
                    SendClientMessage(i,0xFF0000,String);//sending the message to admins
                }
            }
            SendClientMessage(playerid,0x00FFFF,"Report sent.");
            return 1;
        }
    }
    return 0;
}
Hard command (/pay)
pawn Код:
public OnPlayerCommandText(playerid, cmdtext[])
{
    if(!strcmp(cmdtext,"/pay",true,4))//first 4 chars: /, p, a, y
    {
        if(!cmdtext[4])return SendClientMessage(playerid,-1,"{FF0000}Usage: {FFFFFF}/pay {009BFF}[amount]{FFFFFF}.");//if there is nothing after /pay
        if(cmdtext[4]==' ')//if there is space after /pay
        {
            new i,j;//defining variables
            for(i=5;cmdtext[i]==' ';i++){}//cutting not-needed spaces also finding first char after spaces( first char of the word that comes after spaces )
            if(!cmdtext[i])return SendClientMessage(playerid,-1,"{FF0000}Usage: {FFFFFF}/pay {009BFF}[amount]{FFFFFF}.");// if after spaces comes EOS - stop the command
            for(j=i+1;cmdtext[j]!=' ';j++)if(!cmdtext[j])return SendClientMessage(playerid,-1,"{FF0000}Usage: {FFFFFF}/pay {009BFF}[amount]{FFFFFF}.");//finding the last char in the word. If loop finds EOS before space that means third parameter of the command isn't inputted - stop command.
            new Str[MAX_PLAYER_NAME];//defining a string
            strmid(Str,cmdtext,i,j);//inserting word into Str
            new id=GetIdOfPartOfNameOrId(Str);//as Function says. The function is below
            if(id==-1)return SendClientMessage(playerid,COLOR_RED,"Server: There are more players with that name.");
            if(id==-2)return SendClientMessage(playerid,COLOR_RED,"Server: That player doesn't exist.");
            if(id==playerid)return SendClientMessage(playerid,COLOR_RED,"Server: You cannot pay yourself!");
            for(i=j+1;cmdtext[i]==' ';i++){}//after finding id of player we want to cut space(s) after the Id or PlayerName
            if(!cmdtext[i])return SendClientMessage(playerid,-1,"{FF0000}Usage: {FFFFFF}/pay {009BFF}[amount]{FFFFFF}.");//if after spaces comes EOS - stop the command
            if(IsNumber(cmdtext[i]))// checking if string (part of cmdtext with beggining at i-pos) is number, positive one
            {
                new amount=strval(cmdtext[i]);//converting string into integer
                if(amount>gPlayerCash[playerid])SendClientMessage(playerid,COLOR_RED,"Server: You don't have that much money!");// does player have that amount of money?
                else
                {
                    gPlayerCash[playerid]-=amount;// decreasing player's cash by the amount
                    gPlayerCash[id]+=amount;//increasing player's cash by the amount
                    ShowPlayerMoney(id);//showing player his money
                    new String[MAX_PLAYER_NAME+60];//defining a string
                    format(String,sizeof(String),"takes out %s wallet and gives some ammount of cash to %s.",(gPlayerSex[playerid][0]=='M')?("his"):("her"),RealisticName(gPlayerCharName[id]));
                    //^^making message that will be sent to players around. Same as /me
                    SendTextToNearlyPlayers(playerid, String, ME);//sending message
                    PlayAnimEx(playerid, "DEALER", "shop_pay", 4.0, 1, 0, 0, 0, 0, 1);//animation
                }
                ShowPlayerMoney(playerid);
            }
            else SendClientMessage(playerid,COLOR_RED,"Server: The paying amount must be numeric!");
            return 1;
        }
    }
    return 0;
}
One more thing

And, one thing more. To make it faster I and one friend of mine( Alex ) came up with this idea. On the beginning of the OnPlayerCommandText make a >>CommandTeleport<<.
To make it, you first need to sort all commands in alphabet order.
After that, add this on the beginning of the OnPlayerCommandText.
pawn Код:
public OnPlayerCommandText(playerid, cmdtext[])
{
    switch(cmdtext[1])
    {
        case 'a','A':goto CmdA;
        case 'b','B':goto CmdB;
        case 'c','C':goto CmdC;
        case 'd','D':goto CmdD;
        case 'e','E':goto CmdE;
        case 'f','F':goto CmdF;
        case 'g','G':goto CmdG;
        case 'h','H':goto CmdH;
        case 'i','I':goto CmdI;
        case 'j','J':goto CmdJ;
        case 'k','K':goto CmdK;
        case 'l','L':goto CmdL;
        case 'm','M':goto CmdM;
        case 'n','N':goto CmdN;
        case 'o','O':goto CmdO;
        case 'p','P':goto CmdP;
        case 'q','Q':goto CmdQ;
        case 'r','R':goto CmdR;
        case 's','S':goto CmdS;
        case 't','T':goto CmdT;
        case 'u','U':goto CmdU;
        case 'v','V':goto CmdV;
        case 'w','W':goto CmdW;
        case 'x','X':goto CmdX;
        case 'y','Y':goto CmdY;
        case 'z','Z':goto CmdZ;
        default: return 0;
    }
    CmdA:
    //cmds that start with 'a' .( example /Auction)
    CmdB
    // cmds that start with 'b'.(example /breakdoor)
    //etc
    return 0;
}
All I did in this tutorial should end up like this:
pawn Код:
public OnPlayerCommandText(playerid, cmdtext[])
{
    switch(cmdtext[1])
    {
        case 'a','A':goto CmdA;
        case 'b','B':goto CmdB;
        case 'c','C':goto CmdC;
        case 'd','D':goto CmdD;
        case 'e','E':goto CmdE;
        case 'f','F':goto CmdF;
        case 'g','G':goto CmdG;
        case 'h','H':goto CmdH;
        case 'i','I':goto CmdI;
        case 'j','J':goto CmdJ;
        case 'k','K':goto CmdK;
        case 'l','L':goto CmdL;
        case 'm','M':goto CmdM;
        case 'n','N':goto CmdN;
        case 'o','O':goto CmdO;
        case 'p','P':goto CmdP;
        case 'q','Q':goto CmdQ;
        case 'r','R':goto CmdR;
        case 's','S':goto CmdS;
        case 't','T':goto CmdT;
        case 'u','U':goto CmdU;
        case 'v','V':goto CmdV;
        case 'w','W':goto CmdW;
        case 'x','X':goto CmdX;
        case 'y','Y':goto CmdY;
        case 'z','Z':goto CmdZ;
        default: return 0;
    }
    CmdP:
    if(!strcmp(cmdtext,"/pay",true,4))//first 4 chars: /, p, a, y
    {
        if(!cmdtext[4])return SendClientMessage(playerid,-1,"{FF0000}Usage: {FFFFFF}/pay {009BFF}[amount]{FFFFFF}.");//if there is nothing after /pay
        if(cmdtext[4]==' ')//if there is space after /pay
        {
            new i,j;//defining variables
            for(i=5;cmdtext[i]==' ';i++){}//cutting not-needed spaces also finding first char after spaces( first char of the word that comes after spaces )
            if(!cmdtext[i])return SendClientMessage(playerid,-1,"{FF0000}Usage: {FFFFFF}/pay {009BFF}[amount]{FFFFFF}.");// if after spaces comes EOS - stop the command
            for(j=i+1;cmdtext[j]!=' ';j++)if(!cmdtext[j])return SendClientMessage(playerid,-1,"{FF0000}Usage: {FFFFFF}/pay {009BFF}[amount]{FFFFFF}.");//finding the last char in the word
            new Str[MAX_PLAYER_NAME];//defining a string
            strmid(Str,cmdtext,i,j);//inserting word into Str
            new id=GetIdOfPartOfNameOrId(Str);//as Function says. The function is below
            if(id==-1)return SendClientMessage(playerid,0xFF0000FF,"Server: There are more players with that name.");
            if(id==-2)return SendClientMessage(playerid,0xFF0000FF,"Server: That player doesn't exist.");
            if(id==playerid)return SendClientMessage(playerid,0xFF0000FF,"Server: You cannot pay yourself!");
            for(i=j+1;cmdtext[i]==' ';i++){}//after finding id of player we want to pay cutting spaces
            if(!cmdtext[i])return SendClientMessage(playerid,-1,"{FF0000}Usage: {FFFFFF}/pay {009BFF}[amount]{FFFFFF}.");//if after spaces comes EOS - stop the command
            if(IsNumber(cmdtext[i]))// checking if string is number, positive one
            {
                new amount=strval(cmdtext[i]);//converting string into integer
                if(amount>gPlayerCash[playerid])SendClientMessage(playerid,0xFF0000FF,"Server: You don't have that much money!");// does player have that amount of money?
                else
                {
                    gPlayerCash[playerid]-=amount;// decreasing player's cash by the amount
                    gPlayerCash[id]+=amount;//increasing player's cash by the amount
                    ShowPlayerMoney(id);//showing player his money
                    new String[MAX_PLAYER_NAME+60];//defining a string
                    format(String,sizeof(String),"takes out %s wallet and gives some ammount of cash to %s.",(gPlayerSex[playerid][0]=='M')?("his"):("her"),RealisticName(gPlayerCharName[id]));
                    //^^making message that will be sent to players around. Same as /me
                    SendTextToNearlyPlayers(playerid, String, ME);//sending message
                    PlayAnimEx(playerid, "DEALER", "shop_pay", 4.0, 1, 0, 0, 0, 0, 1);//animation
                }
                ShowPlayerMoney(playerid);
            }
            else SendClientMessage(playerid,0xFF0000FF,"Server: The paying amount must be numeric!");
            return 1;
        }
    }
    CmdR:
    if(!strcmp("/report", cmdtext, true, 7))//first 7 char are /,r,e,p,o,r,t. Note that here we don't expect that on 8th place is EOS.
    {
        if(!cmdtext[7])return SendClientMessage(playerid,0xFFFFFFFF,"Ussage: /report [Text]");//if on the 8th place is EOS send usage message.
        if(cmdtext[7]==' ')// in case 8th char is space execute the command
        {
            new i;//defining a variable
            for(i=8;cmdtext[i]==' ';i++){}//checking if there are more spaces after 8th char. If so we will cut that from message that is sent to admins
            if(!cmdtext[i])return SendClientMessage(playerid,0xFFFFFFFF,"Ussage: /report [Text]");//if first char after space(s) is EOS stop the execution and send the usage message.
            new String[128+MAX_PLAYER_NAME];//declaring new variable
            GetPlayerName(playerid,String,MAX_PLAYER_NAME);//getting name of the reporter and storing it in String.
            format(String,128+MAX_PLAYER_NAME,"%s[%d] reporting: %s",String,playerid,cmdtext[i]);//Constructing the report message
            for(i=0;i<MAX_PLAYERS;i++)if(IsPlayerConnected(i)&& !IsPlayerNPC(i))//looping all players that could be admins
            {
                if(IsPlayerAdmin(i) /* AdminLevel[i]>0*/)//checking if they are admins
                {
                    SendClientMessage(i,0xFF0000FF,String);//sending the message to admins
                }
            }
            SendClientMessage(playerid,0x00FFFFFF,"Report sent.");
            return 1;
        }
    }
    CmdS:
    if(!strcmp(cmdtext,"/suicide",true,8) && !cmdtext[8])
    {
        SendClientMessage(playerid,0xFF0000FF,"Server:{FFFFFF} It's ilegal to use this function to avoid being arrested, killed, etc.");
        SetPlayerHealth(playerid,0);
        return 1;
    }
    //strcmp checks if the two strings are equal( in this case ). If they are equal strcmp returns 0(lie, non-truth). With '!' we are telling program to make result opposite( if strings are equal strcmp will return true) .
    //!cmdtext[8] is checking if on the 9th place in string is "End of string( aka EOS)" what's value is 0(lie).
    //sending message to command user.
    //Killing player
    if(!strcmp(cmdtext,"/pay",true,4))//first 4 chars: /, p, a, y
    {
        if(!cmdtext[4])return SendClientMessage(playerid,-1,"{FF0000}Usage: {FFFFFF}/pay {009BFF}[amount]{FFFFFF}.");//if there is nothing after /pay
        if(cmdtext[4]==' ')//if there is space after /pay
        {
            new i,j;//defining variables
            for(i=5;cmdtext[i]==' ';i++){}//cutting not-needed spaces also finding first char after spaces( first char of the word that comes after spaces )
            if(!cmdtext[i])return SendClientMessage(playerid,-1,"{FF0000}Usage: {FFFFFF}/pay {009BFF}[amount]{FFFFFF}.");// if after spaces comes EOS - stop the command
            for(j=i+1;cmdtext[j]!=' ';j++)if(!cmdtext[j])return SendClientMessage(playerid,-1,"{FF0000}Usage: {FFFFFF}/pay {009BFF}[amount]{FFFFFF}.");//finding the last char in the word
            new Str[MAX_PLAYER_NAME];//defining a string
            strmid(Str,cmdtext,i,j);//inserting word into Str
            new id=GetIdOfPartOfNameOrId(Str);//as Function says. The function is below
            if(id==-1)return SendClientMessage(playerid,0xFF0000FF,"Server: There are more players with that name.");
            if(id==-2)return SendClientMessage(playerid,0xFF0000FF,"Server: That player doesn't exist.");
            if(id==playerid)return SendClientMessage(playerid,0xFF0000FF,"Server: You cannot pay yourself!");
            for(i=j+1;cmdtext[i]==' ';i++){}//after finding id of player we want to pay cutting spaces
            if(!cmdtext[i])return SendClientMessage(playerid,-1,"{FF0000}Usage: {FFFFFF}/pay {009BFF}[amount]{FFFFFF}.");//if after spaces comes EOS - stop the command
            if(IsNumber(cmdtext[i]))// checking if string is number, positive one
            {
                new amount=strval(cmdtext[i]);//converting string into integer
                if(amount>gPlayerCash[playerid])SendClientMessage(playerid,0xFF0000FF,"Server: You don't have that much money!");// does player have that amount of money?
                else
                {
                    gPlayerCash[playerid]-=amount;// decreasing player's cash by the amount
                    gPlayerCash[id]+=amount;//increasing player's cash by the amount
                    ShowPlayerMoney(id);//showing player his money
                    new String[MAX_PLAYER_NAME+60];//defining a string
                    format(String,sizeof(String),"takes out %s wallet and gives some ammount of cash to %s.",(gPlayerSex[playerid][0]=='M')?("his"):("her"),RealisticName(gPlayerCharName[id]));
                    //^^making message that will be sent to players around. Same as /me
                    SendTextToNearlyPlayers(playerid, String, ME);//sending message
                    PlayAnimEx(playerid, "DEALER", "shop_pay", 4.0, 1, 0, 0, 0, 0, 1);//animation
                }
                ShowPlayerMoney(playerid);
            }
            else SendClientMessage(playerid,0xFF0000FF,"Server: The paying amount must be numeric!");
            return 1;
        }
    }
    return 0;
GetIdOfPartOfNameOrId - fuction I used in tutorial
pawn Код:
GetIdOfPartOfNameOrId(str[])
{
    new id=0,i,bool:flag=true;
    for(i=0;str[i];i++)
    {
        if('9'<str[i] || str[i]<'0'){flag=false;break;}
        id=id*10+str[i]-'0';
    }
    if(flag)return (IsPlayerConnected(id))?id:-2;
    new Name[MAX_PLAYER_NAME],br=0,lenght;
    lenght=strlen(str);
    for(i=0;i<MAX_PLAYERS;i++)
    {
        if(!IsPlayerConnected(i))continue;
        GetPlayerName(i,Name,MAX_PLAYER_NAME);
        if(strcmp(Name,str,true,lenght)==0)
        {
            br++;
            if(br==2)return -1;
            id=i;
        }
    }
    if(br==1)return (IsPlayerConnected(id))?id:-2;
    else return -2;
}
Last words

Thank you for reading it. I hope you found it usefull.
Reply
#2

ZCMD is faster and easier to use. There's a reason why everyone recommends using ZCMD on YCMD.
Reply
#3

Quote:
Originally Posted by im
Посмотреть сообщение
ZCMD is faster and easier to use. There's a reason why everyone recommends using ZCMD on YCMD.
Yes ZCMD was faster than regular strcmp/strtok. And in my opinion strtok is too slow, which isn't needed. ZCMD uses CallRemoteFunction what is faster than if-else ( only if there are a lot of if-elses). In this case there won't be a lot of if-elses because of switch on beggining we jump/skip a 80 percent of them. So, this could be faster then ZCMD. About YCMD, I dunno how it works so I can't tell anything about it. And I want to underline that I am not expert in processing speed.
Reply
#4

Good work I wonder if that command teleport is as good as ZCMD,would help if someone does some tests.
Reply
#5

You should never use "goto". It's a lazy way of correcting bad program flow.
pawn Код:
public OnPlayerCommandText(playerid, cmdtext[])
{
    switch(cmdtext[1])
    {
        case 'a','A':
        {
            if(!strcmp(cmdtext, "/apple", true))
            {
               
            }
            // commands with A
        }
        // so on and so forth
        default: return 0;
    }

    return 0;
}
Will using characters in switch statements even compile? I was under the impression you could only use integers.
Reply
#6

Quote:
Originally Posted by ******
Посмотреть сообщение
Characters ARE integers. Anyway, I've not seen the switch trick in a long time (but I'm afraid I HAVE seen it before), so that's one good way of improving speeds. But I still have to ask why you are still using this method rather than any other? You mentioned that ZCMD is only faster than strcmp for multiple commands, and that's true, but have you actually done any timings that include the switch? The reason I ask is that the number of "multiple" commands is very low - you only need 5-10 commands for ZCMD to become faster, so yes that's more like 125-250 using your method, but that's ignoring the other overheads and the development simplicity.
I am using this method because I feel safely while doing it( I know how they work and what are they supposed to do ). Also, when I know how operators work it's easy to find the problem when it occurs. I tried to understand how ZCMD works but didn't succeed. Now, when I use word "work", I don't mean 'how to use it', but how is it scripted/programmed (what actually stands behind 'CMD:'). In additoin, with logical thinking (doesn't mean it's correct) switch is more, more faster then CallRemoteFunction. As well as that, in every command group ( we sorted them in groups: the ones that start with 'a' or 'A', etc.) we can put another switch if there are too many commands to make it faster. I know once it will be slower then ZCMD, but when? when it reaches 20'000 commands? Got my point?
Reply
#7

switch is implemented just as a if-elseif-else in current Pawn VM so you are doing pretty much the same slow stuff.
Reply
#8

Nice tutorial for newbies but would have been better with sscanf.Anyways keep it up.Good work.
Reply
#9

Quote:
Originally Posted by Roko_foko
Посмотреть сообщение
I am using this method because I feel safely while doing it( I know how they work and what are they supposed to do ). Also, when I know how operators work it's easy to find the problem when it occurs. I tried to understand how ZCMD works but didn't succeed. Now, when I use word "work", I don't mean 'how to use it', but how is it scripted/programmed (what actually stands behind 'CMD:'). [...]
Why not read the source code of ZCMD?

You are doing pretty much the same thing that strtok does: skip whitespace and extract a word from a long string. Except strtok is a lot cleaner, as you don't have to worry about string cutting and invent a new processing system for every command. I'm not promoting strtok here, but I'm promoting the usage of functions for similar tasks. If you put the string processing code into a function, you would get strtok.

I support doing things differently, showing that there are other ways, but I would rather use strtok than this. Just my opinion.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)