Welcome to Keen Software House Forums! Log in or Sign up to interact with the KSH community.
  1. You are currently browsing our forum as a guest. Create your own forum account to access all forum functionality.

[Guide] Programmable Block - C# 102 for Space Engineers: Loops, Strings, and Other Things

Discussion in 'Programming Guides and Tools' started by Textor, Jan 5, 2015.

Thread Status:
This last post in this thread was made more than 31 days old.
  1. Textor Junior Engineer

    Messages:
    775
    Welcome to C# 102 for Space Engineers: Loops, Strings, and Other Things!
    This guide assumes you have read C# 101 for Space Engineers. If you have not read this, some of this may not make sense.
    We will be discussing more advanced concepts in C#, but not anything overly advanced: Loops, Strings, Inheritance, escape keywords, and ref.
    Thank you to Shimonu who assisted in verifying the functionality of the where() loop!
    Loops
    for loop:
    The for loop is one of the more frequent loops you will encounter. The syntax is rather simple:
    for(variable declaration; logical comparison; variable iterator){}
    So what does this all mean?
    Variable declaration – you may have noticed most of the time people put “int i = 0” here. This isn’t required—you can have any variable declared, even ones that have already been created. For instance, if I have a variable named “counter” initialized earlier than the loop:
    Code:
    int counter = 1;
    for(counter; counter < 10; counter++){}
    
    This will work just the same. I can also assign a value during the declaration, as well—so if I wanted to set counter to 0, I can just use “counter = 0” during the declaration.
    Logical comparison
    This is usually something looked at to determine when the for loop is to end. Most of the time you’ll see something like, “i < blocks.Count” in the middle there. This is because we are going to make sure that “i” stays less than how many blocks are in the list of blocks. You can use anything you want, really, though to prevent an infinite loop (which the devs have explicitly stated won’t work) you should make sure the comparison is something that the loop can end. You CAN make an infinite loop (theoretically) if you terminate the loop using a keyword like “break” when you are checking for something inside the loop, though usually people will use the “while” loop that we will discuss later. A non-traditional for loop condition would be:
    Code:
    List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
    GridTerminalSystem.GetBlocksOfType<IMyBeacon>(blocks);
    for(int i = 0;blocks[blocks.Count-1].CustomName == “Block!”; i++)
    {
                    blocks[i].SetCustomName(“Block!”);
    }
    
    This loop will run until the last block in the list is named “Block!” Keep in mind that this example is only to demonstrate that a condition not involving the initialized variable is possible, not to advocate for doing it this way—In this example, it would have been a lot easier (and easier to understand) to simply use the traditional “i < blocks.Count” condition. Simply put: don’t use forced examples as a gold standard for how you should write code. They are here to demonstrate possibility, not best practices.
    Iteration – This tells the loop what you want to do with the initialized variable. Usually it is an increment or decrement. Most of the time, you’ll see i++ or ++i, but if you are looping through a list backwards (entirely possible) you may use i--or --i. If you aren’t doing anything with the declared variable, just put the variable itself with no modifiers in. If you want to skip around a bit, you can increment by whatever amount you want: i+2 is just as valid as i++. Just remember to use what is appropriate to the case.
    Code:
    for(int i = 0; i < blocks.Count; i)
    {
                    i++;
    }
    
    This will work just as well as putting i++ in the increment, though it seems unnecessary. Remember what I said about using forced examples as a standard for your code: don’t.
    Finally, you can declare an infinite for loop by using: for(; ; ). But you shouldn't do that, because your code won't execute.
    while loop:
    So far I haven’t seen many examples of code using the while loop. While is actually a very useful loop when you want something to happen until a condition is met. The syntax for while is:
    Code:
    while(condition is true)
    {
                    /*do stuff.*/
    }
    
    Simple, right? You may think of while as if() that loops and does not having else as a companion. Remember the ! operator from 101? That’s useful here, too. ! allows you to loop while a condition is false because the ! operator reverses the logic:
    Code:
    while(!(1 > 10))
    {
    }
    
    A bit contrived, but it works—this says, “while NOT 1 > 10” (1 > 10 returns false, ! = not, so “While not false”). This would also be an infinite loop, since while could never make 1 more than 10 (therefore making the check turn to true, which would reverse to false and end the loop. Confused you yet?) while(true) is an infinite loop. Don’t use that, SE will put your script down faster than old yeller.
    Honestly, I’m not even 100% sure about while as a viable loop for SE—since there is no clear iteration, your script can hit the instruction limit and return “Too complex.” You can have it poll a small amount of times by adding an incrementing variable:
    Code:
    int I = 0;
    While(condition)
    {
                    i++;
                    if(i >= 10)
                    {
                                    /*maybe call a timer block to restart the script here?*/
                                    break;
                    }
                    /*do other stuff*/
    }
    

    do – This is the brother of while. It does not work like else. It is basically… “do this one time, then loop if the conditions aren’t met.”
    Code:
    Int I = 0;
    do
    {
                    i+=2;
    }
    while(i < 10);
    
    So, in this case, it will add 2 to i, check to see if i is 10 or above, then do it over again until that condition is met. If I changed it to “i + 10” it would end immediately after the “do” without looping.
    foreach The foreach loop is not officially supported as of 1.063.008. I will explain the foreach loop here just in case the functionality is eventually supported, since it makes it a bit easier to work with certain things, like lists.
    Code:
    foreach(object variable in arrayvariable)
    {
    }
    
    This is the basic syntax for foreach. Let’s break it down so we can figure out what it is doing. Conveniently, foreach can be read as if it is an actual statement, making it quite easy to figure out what it is doing:
    Code:
    int[] intarray = {1, 2, 3, 4, 5};
    foreach(int integer in intarray)
    {
                    Console.Writeline(integer.ToString()); /*Yes, I know this isn’t in SE, but it makes it easier to write examples than renaming beacons constantly. Just imagine that Console.Writeline is actually a multiline thing defining a beacon object and setting the name. Happy?*/
    }
    
    Ok, so, in this example let’s read it as English:
    FOR EACH INT NAMED INTEGER IN THE ARRAY INTEGERARRAY, DO THE FOLLOWING.
    That’s pretty easy, right? Let’s break it down even further:
    Code:
    foreach(int integer
    
    Declare a variable named “integer” of type “int”
    Code:
    in intarray
    
    Look at the array “intarray” and assign the value for each of the members of the array to the variable. (For each? Get it? Foreach? Damned C# and its obviousness!)
    So, let’s see what the array actually does:
    Loop 1:
    Beacon is renamed to “1”
    Loop 2:
    Beacon is renamed to “2”
    Loop 3:
    “Beacon is renamed to “3”
    Loop 4:
    Beacon is renamed to “4”
    Loop 5:
    Becaon is renamed to “5”
    Loop 6:
    There is no loop 6. It stopped at loop 5. Were you paying attention to the code? No? Go back and read it. I can wait. while(reader != comprehend){ GuideWriter.ListenToMozart();}
    Ok, so how is this good for SE programming? Well, we use lists a lot. Think of a list as a really advanced form of array, that comes with a bunch of awesome bells and whistles automatically. Right now, we are using the “workaround” solution of accessing entire entries of lists by doing for(int i = 0;, i < list.Count;i++). Foreach allows us to make it a lot easier to read and work with:
    Code:
    Int i = 10;
    foreach(IMyBeacon beacon in blocks)
    {
                    Beacon.SetCustomName(“Beacon {0}”,i);
                    i++;
    }
    
    Ok, so, as you can see, we implicitly cast IMyBeacon on each member in block, adding it to a one-time use variable called beacon. Each beacon is set to a new name, of “Beacon x”. i is set to 10, so if we have 5 beacons, we will have named them all “Beacon 10”, “Beacon 11”, “Beacon 12”, “Beacon 13”, and “Beacon 14”. A lot easier than all that “for” business, hm? Just remember- this is not currently supported as of 1.063.008.
    Recursion
    Recursion is a type of loop, though it is a bit more advanced than simply using a for, while, do-while, or foreach loop. How does recursion work? Through methods.
    To recurse, you must call a method from itself. You should only recurse when you have a clear exit condition. If you do not, your code will not execute due to an infinite loop. If you aren’t sure why you should recurse in a particular situation, stick to normal loops. Basically, the best way to tell if you should use recursion is because you can’t figure out how to loop something iteratively. Recursion example (remember, this isn’t best practice—just a forced example demonstrating how it works.):
    Code:
    void Main()
    {
                    int i = 3;
    int j = 2;
    Console.Writeline(AddTwoNumbers(i,j).ToString());
    }
    int AddTwoNumbers(int i, int j)
    {
    int k = i+j;
                    If(k < 10)
                    {
                                    k = AddTwoNumbers(k, j);
                    }
    return k;
    }
    
    So, what happens here?
    AddTwoNumbers(3,2) – k = 5. 5 < 10, so
    AddTwoNumbers(5,2). – k = 7. 7 < 10 so
    AddTwoNumbers(7,2). – k = 9. 9 < 10 so
    AddTwoNumbers(9,2) – k = 11. 11 is not less than 10, so return 11.
    Beacon is renamed to “11”
    So, was that easier than for(int i = 3; i < 10; i) { i+=2;}?
    No. Definitely not. Usually recursion is used for much more advanced things than adding two numbers. Also, notice that we had an if() statement in the method. This is because without it, it would recurse forever and the script will fail due to complexity.
    Working with Strings
    Strings are very important. People use them every day, in every program. They also use them in Space Engineers all the time. So working with strings is kinda something all you SE scripter people want to know how to do.
    So, first thing’s first: How to make a new line.
    Code:
    string s = “This is going to appear\n\rOn two lines.”;
    
    So, that looks simple, except for those confusing bits in the middle of the line? WTF is with the \n\r thing? Well, basically, in computer-speak, \n\r means “NEW LINE, CARRIAGE RETURN.” Ever wonder why some keyboards say “return” or people say “hit return” instead of the “ENTER” key on your keyboard? Well, it is called “return” because it is short for “Carriage Return.” Think of it as a throwback to typewriters. In a typewriter, a CR command would return the carriage to the beginning of the line. Clever, hm? New line should be self-explanatory.
    We’ve already covered this briefly in 101, but here’s how you add a bunch of different things to a string:
    Code:
    int i = 0;
    string s = “This is an example string that has stuff like”;
    s += i.ToString() + “ added to the back of it.”;
    
    So that will make s be “This is an example string that has 0 added to the back of it.”
    Ok, what about case management? As I’ve mentioned before, strings are case-sensitive. That means comparing two strings that aren’t EXACTLY alike will return false: “Hi” == “hi” returns false. How do we fix something like this? Well, we can use ToUpper() and ToLower():
    Code:
    string s = “Hi”;
    if(s.ToUpper() == “HI”)
    {
    /*This code will execute*/
    }
    
    What does this do? Well, ToUpper() looks at your string and makes it ALL CAPITAL LETTERS. So if you type in a long string, like “THIS IS A LONG STRING THAT I WANT TO COMPARE.” And you have a variable that contains “This is a long string that I want to compare.”, ToUpper() will make the strings match. Alternatevely, you can use ToLower(), which does the opposite. Preference, really.
    Another useful method is String.Contains(). This compares the thing inside the parenthesis with the string, returning true if it found that substring:
    Code:
    string s = “this is an example string.”;
    if(s.Contains(“example”)
                    Console.Writeline(“True!”);
    else
                    Console.Writeline(“False!”);
    if(s.Contains(“Textor”)
                    Console.Writeline(“True!”);
    else
                    Console.Writeline(“False!”);
    
    This would name a beacon:
    True!
    then
    False!

    Ok, what about the backslash and quotation mark characters? How do I put those in the strings? With the escape (\) character:
    Code:
    string s = "I want a \" here and a \\ here."
    Console.WriteLine(s);
    
    This would name the beacon: "I want a " here and a \ here."
    Finally, I'd like to talk about String.Format(). This is an awesome string function- it lets you insert variables right into the middle of your string without all those pesky + in the middle!
    Code:
    int i = 0;
    string t = "BLAH!";
    string s = string.Format("This is an example, where I insert the values \"{0}\" and \"{1}\" into the string.", t, i.ToString());
    Console.WriteLine(s);
    
    The beacon would be named: "This is an example, where I insert the values "BLAH!" and "0" into the string."
    There are a LOT of string functions, more than I want to talk about here. If you want to know what else you can do with strings, check out Microsoft Developer Network (Your Source for official C# Documentation!). Please note that MSDN is for C# in general, NOT Space Engineers. Because of this Your Mileage May Vary when using this documentation for coding in Space Engineers.
    Strings: http://msdn.microsoft.com/en-us/library/system.string(v=vs.110).aspx
    Inheritance
    Wow, C# will give you money!? No. Sorry. Inheritance works a lot like that concept, only requires nothing to die and you only really inherit not having to write a lot of code again.
    Ok, so what is inheritance? Well, we spoke about making a class in 101. Sometimes you have a class that is related to something else. So, for instance, a block is related to a light, because a light IS a block. Well, let’s assume a block has an ID code and a damage value:
    Code:
    class block
    {
                    int damage;
                    int id;
                    public block(int i, int d)
                    {
                                    this.damage = d;
                                    this.id = i;
                    }
    }
    
    Well, lights have that, too, but let’s say they also have an “On” value:
    Code:
    class light
    {
                    bool on;
                    public light(bool b)
                    {
                                    this.on = b;
                    }
    }
    
    Well, I don’t want to have to also define ID and damage to the light, because I’m lazy. (Laziness is a good trait in programmers—you’ll work less hard to achieve the same result). So we’ll inherit:
    Code:
    class light : block
    {
                    bool on;
                    public block(int i, int d, bool b) : base (i, d)
                    {
                                    this.on = b;
                    }
    }
    
    So what did I do here? Well, : means “Inherit from” when you define your class. So this says “Light inherits from Block.” When we make the initializer, we say that light requires an id value, a damage value, and an on/off state value. Instead of having to define all of that, we just tell it to initialize it with the values i and d passed to the base class (aka, block).
    If you have a method in the base class, say, “SetID()” and you want to use it in the class, you would simply put:
    Code:
    base.SetID(123);
    
    Which tells the code “check the class I’m inheriting from for a method called “SetID” and then run it with the parameter “123”.
    You’ll notice that you can do a lot of the same stuff with the same block interfaces: this is because the blocks all inherit from other types of blocks, creating a hierarchy. Makes for a lot less coding.
    Escape Keywords
    There are two keywords I want to talk about. These are “escape keywords.” I call them that because they have the power to escape the current loop or method you are in.
    return – If you call this, it returns a value immediately and terminates the method you are in. If your method has a type other than void, you must return a type (return 0) with it. If it has void, you can simply say “return;” to have it terminate the method.
    break – Stop processing the loop. Do not pass go, do not collect $200.
    Code:
    for(int I = 0; i < 10; i++)
    {
                    Console.WriteLine(i.ToString());
    break;
    }
    
    This would name the beacon “0” and immediately stop.
    Why are these important? Well, sometimes you reach a condition and you want it to completely stop what it is doing when it reaches that condition. Return and break force what the script is doing to come to a complete halt right at that line. Return should only be at the end of a method or inside of an if() or switch() statement. If you do this:
    Code:
    void example()
    {
                    int i = 0;
                    int j = 1;
                    return;
                    int k = i+j;
    }
    
    The line “int k = i+j;” would never execute.
    Ref Keyword
    Ok, so there is one final keyword I want to talk about: ref. Ref is used to pass a variable by reference. See what they did there? So, let’s take a look at ref:
    Code:
    void Main()
    {
                    int i = 0;
                    Test(ref i);
                    Console.WriteLine(i.ToString());
    }
    void Test(ref int i)
    {
                    i = 10;
    }
    
    The beacon would be named 10.
    This allows you to pass a variable into a method without having to find ways to return it. Ref is mainly useful if you have a whole host of parameters you all need processed at once and need them all to retain their changes when done. In general, this should be done with multiple methods rather than ref, but sometimes ref is a necessary thing.
    out:
    This keyword works like ref, only doesn't require the variable you are passing in to be initialized ahead of time:
    Code:
    void Main()
    {
                    int i;
                    Test(out i);
                    Console.WriteLine(i.ToString());
    }
    void Test(out int i)
    {
                    i = 10;
    }
    
    This would set the beacon to 10, also.
    Thanks to pipakin for reminding me to put out in this guide.
    Glossary:
    Recursion – The best way to define recursion is to use recursion.
    Iteration – a process which will eventually lead to a result. If we add 1 to i until it becomes 10, each value of i is an iteration of i. Loops are iterative— their function is to repeat a process until you reach a specific result, then end the loop.
     
    Last edited by a moderator: Jan 5, 2015
    • Like Like x 1
  2. pipakin Apprentice Engineer

    Messages:
    182
    You might want to mention out, the cousin of ref. It acts much like ref except the parameter does not have to be initialized before calling the method and must be set before exiting the method.
     
    Last edited by a moderator: Jan 5, 2015
  3. Textor Junior Engineer

    Messages:
    775
    Ah, right. I almost never use it so I forgot it existed. I'll add it in there.
     
  4. pipakin Apprentice Engineer

    Messages:
    182
    Out is indeed a rare beast, and using it is usually a code smell. However, it is there for a reason. Just....not entirely sure what that reason is...

    I write C# code for a living btw, and I think there's an unspoken rule here that if you write code using ref or out you have to be able to justify it to everyone, since it's a fairly odd thing to do.
     
  5. Textor Junior Engineer

    Messages:
    775
    Yeah... I thought I'd put it in here in case anyone ever had the "hey, can I do this...!?" question, but you are completely right-- I don't think I've used ref or out since college, when we learned about ref and out.
     
  6. Textor Junior Engineer

    Messages:
    775
    Added two new things to the string section: Escape character and String.Format().
     
  7. Cuber Apprentice Engineer

    Messages:
    262
    No do...while? :(
    You might want to also add a couple of lines about using ref with reference types.
    Also, have you explained the difference between value and reference types already?
     
  8. Textor Junior Engineer

    Messages:
    775
    Yes and no... do...while works, but only if you limit how many executions it can do.

    I'll probably talk about this when I go over reference types more in-depth.
     
  9. Arcelf Trainee Engineer

    Messages:
    82
    Wow Textor, your guides are comming in really handy, thanks man :)
     
  10. Arcelf Trainee Engineer

    Messages:
    82
    Any help on the Async and Await keywords much appriciated :p
     
  11. Textor Junior Engineer

    Messages:
    775
    That sounds like threading. Without any testing and a completely educated (but ultimately wild) guess: These won't work.
     
  12. Darkthought Apprentice Engineer

    Messages:
    140
    Any chance you'll add a section on parsing strings, so we can pull useful stuff out of the DetailedInfo output?
     
  13. azr Trainee Engineer

    Messages:
    5
    Hi everyone ! my first post here :)
    My question might be quite complicated for non-programmers - but i'll try anyway (as I am also a c# programmer for a living)
    The main thing i can not find anywhere is information about variable retention. To be exact, i want to execute my program in some intervals (triggered by timer block) and compare parameters that i find interesting and then execute some actions based on that (on the change of the value to be exact).
    Example - a solar panel connected to a rotor: a rotor won't change its position instantly, so i need to store current output power of an attached solar panel, and in second cycle compare it, and then decide, to continue the rotation or to reverse it, or maybe stop. so i clearly need some kind of variables to store it that will retain the data between executions.
    the ship changes position relative to the sun, and the whole process starts again.
    Technically the structure of the code in programmable component looks strictly like a single execution program. All the (global) variables are set on execution begin, and are garbage collected on the end, so hence, to retain the data you would have to encode it to certain (known and 'findable' from the code) blocks names (just top-of-the-head idea)
    So my question is : is that assumption correct, or maybe, the execution is event-based and the actually Main method is sort of event handler, and the variables (global ones) are declared on 'Remember and Exit'.
    Hope i made myself at least a bit understandable, and haven't posted in the wrong topic :p
     
  14. SigmaStrain Trainee Engineer

    Messages:
    27
    From my experience that's actually correct. Main does work like an event-handler and variables are initially declared and set upon first run. Variables are not garbage collected once the Main method exits. Their state persists across calls. Hope my reply makes sense.
     
  15. JoeTheDestroyer Junior Engineer

    Messages:
    573
    But not across saves. The only variable that will persist across a save/load cycle is the Storage variable (a string).

    It's best to think of the code you write in the PB as wrapped in a class:
    Code:
    class Program
    {
       IMyGridTerminalSystem GridTerminalSystem;
       string Storage;
       /* Your code from PB. */
    }
    From what I gather, this is more or less how the game implements it.

    When you hit "Remember and Exit", the code is compiled and an instance of the class is created. The GridTerminalSystem member is initialized and the Storage member is initialized to "".

    In the case of loading a savegame, the above also happens. Except that the Storage member is initialized to the value in the save.

    On saving of the game, the value of the Storage string (and nothing else) is saved.

    When the Run action is applied to a PB, the Main() method (of the previously created instance) is executed. Nothing is created or released. The entire execution takes place within the span of a single "update" (i.e., physics update, which occurs at a rate of 60 per in-game second).

    So, members of the Program class (i.e., "global" variables) will persist across Runs, but only in the same game session. They will be reset when you either update the code or load a savegame. The Storage variable can be used for persistence across saves, but is reset if you update the code. If you need total persistence, you can use either the old method of changing a block's name or save the text into an lcd/text panel.
     
    Last edited by a moderator: Mar 7, 2015
    • Like Like x 1
  16. azr Trainee Engineer

    Messages:
    5
    Thank you all for replies
    I wondered about that - i've thought of the object name to store something, but the TextPanel idea is awsome, you can serialize / deserialize a class containing your whole ship/station state. :D and have a continuous state machine ;)although passing a signal to trigger a external action may prove to be quite another story.
    As for now only thing that as i can imagine to act as logical switch is a button panel triggering lights on/off which(lights) state is consequently read and acted upon.
     
  17. SunburstMoon Trainee Engineer

    Messages:
    39
    Thanks Textor for your tutorials. I already have plenty of experience with VB, Java and C++, but I needed to learn the particularities of K#.

    You should mention the continue keyword in the for loop or escape keyword sections, as this can be an elegant alternative to the lack of foreach support, as displayed in the code below:

    Code:
    int i = 0; 
    void Main() 
    { 
        List<IMyTerminalBlock> screens = new List<IMyTerminalBlock>(); 
        GridTerminalSystem.GetBlocksOfType<IMyTextPanel>(screens); 
        if (screens.Count > 0) 
        { 
            for (int j = 0; j < screens.Count; j++) 
            { 
                if (screens[j].CustomName != "Monitor") [B]continue[/B]; 
                if (i == 0 || i >= 17)  
                {  
                    ((IMyTextPanel)screens[j]).WritePublicText("");  
                    i = 0;  
                }  
                ((IMyTextPanel)screens[j]).WritePublicText("This is a code test.\n", true); 
                ((IMyTextPanel)screens[j]).ShowTextureOnScreen(); 
                ((IMyTextPanel)screens[j]).ShowPublicTextOnScreen(); 
            } 
            i++; 
        } 
    }
    
    For the beginners:
    Basically this code will find all Text Panels and LCD Screens on the grid, will ignore those with a name that is not "Monitor", and will add text on the ones with the name "Monitor" (in case you want to have multiple screens across your ship to display the same information). This script also clears the screens when it's full (which is 17 lines of text at the default 1.0 font size). I have absolutely no idea if this code is the most "efficient", but it works.
     
  18. simmaster666 Trainee Engineer

    Messages:
    3
    I know this is probably something simple but how do you make the WritePublicText work over multiple lines... like if I want 3 words on the screen like so:

    ROOM
    DECOMPRESSINGNOW!!

    If anyone knows and can help me out thanks.
     
  19. JoeTheDestroyer Junior Engineer

    Messages:
    573
    Put a "\n" (for newline) in the string. So:
    Code:
    lcd.WritePublicText("ROOM\nDECOMPRESSINGNOW!!");
     
  20. CubeGamer6 Trainee Engineer

    Messages:
    1
    I have tried to use this multiple times in one whole string, but that doesn't work.
    For example:
    Code:
    public void Main(string argument)
    {
    	 //The following string says "This is a test sentence" with a newline after each word.
    	  string example = "This\nis\na\ntest\nsentence";
    }
    I have found a way around it though, but it feels a bit too messy, so i was wondering if there is a better way for it.
    My way around it:
    Code:
    {
    	 //Here i seperated all words and combined them with a '+'
    	  string example = "This" + "\nis" + "\na" + "\ntest" + "\nsentence";
    }
     
  21. d4rky1989 Apprentice Engineer

    Messages:
    332
    Very curious if your examples have different behaviors. They should be perfectly equivalent.
     
  22. AngelinaGets Trainee Engineer

    Messages:
    10
    Thanks, its working
     
Thread Status:
This last post in this thread was made more than 31 days old.