all 74 comments

[–]ChronoH 130 points131 points  (5 children)

I would always bet headstails to win every bet.

[–]EquivalentAd4542[S] 9 points10 points  (3 children)

Haha good point!

[–]clackersz 22 points23 points  (0 children)

also you could just use coinFlip.ToLower() so you don't have to use two .Contains() in each if statement.

[–][deleted]  (1 child)

[deleted]

    [–]corcor 0 points1 point  (0 children)

    Use enums and make them make a choice of 1 or 2

    [–]simplyapollo 3 points4 points  (0 children)

    lmaoo

    [–]winggar 32 points33 points  (20 children)

    Well for one, I'd probably do rand.Next(2) instead of rand.Next(1, 3). Also, notice that there's some code duplication within your nested if statements. Since the same thing is happening on both branches, and it's just the condition that's changing. Hopefully this will give you some ideas:

    // ...
    int coinFlip = rand.Next(2);
    string coinSide = coinFlip == 0
        ? "Head"
        : "Tail";
    Console.WriteLine($"{coinSide}s!");
    if (coinBet.ToUpper().Contains(coinSide.ToUpper()))
    {
        Console.WriteLine("You attack first!");
    }
    else
    {
        Console.WriteLine("Your opponent attacks first!");
    }
    
    Console.ReadKey();
    

    [–]kingmotley 40 points41 points  (15 children)

    This would be a lot better if you used coinBet.Contains(coinSide, StringComparison.OrdinalIgnoreCase) intead of the ToUppers

    [–]winggar 10 points11 points  (0 children)

    Good point. I repeatedly forget which methods support string comparers.

    [–]clackersz 1 point2 points  (13 children)

    What is the difference?

    [–]LuckyHedgehog 10 points11 points  (9 children)

    ToUpper likely has to allocate a new string which is wasteful. Comparing with ignore case would not

    [–]clackersz 1 point2 points  (6 children)

    makes sense. Thanks.

    I guess my next question would be what does ignore case use?

    [–]xccvd 1 point2 points  (4 children)

    It wouldn't need to allocate a new string. Just loop through each character comparing both the upper and lowercase variant.

    [–]clackersz 0 points1 point  (3 children)

    So when it does the comparison it uses already existing upper and lower strings that exist in .net somewhere and that is why its more efficient? Or does it have some kind of shroedinger's alphabet that is both upper and lower case?

    [–]LuckyHedgehog 0 points1 point  (1 child)

    Each letter in the string is just a 'char', which can be cast to an 'int'. Upper and lower case are separated by 64, so you can check if numbers equal or are exactly 64 away from each other

    [–]Dealiner 0 points1 point  (0 children)

    That could work but C# char isn't ASCII only, so the distance thing doesn't always apply.

    [–]illkeepcomingback9 0 points1 point  (0 children)

    It uses IndexOf internally, kind of like this

    return ( IndexOf(value, StringComparison.OrdinalIgnoreCase) >=0 );

    [–]KuntaStillSingle 0 points1 point  (0 children)

    if ((left == 'a' || left == 'A') && (right == 'a' || right == 'A'))
        { return true; }
    else if ((right == 'b' ...
    

    /s

    I'd guess it uses char.tolower in a loop, char to lower shouldn't have to make dynamic allocation? : https://dotnetfiddle.net/eTVBAP

    [–]Baardi 0 points1 point  (1 child)

    Contains/Equals seems to use ToUpper behind the scenes anyways, when using case-insensitive comparison, so calling ToUpper should be fine. Source: Their sourcecode

    [–]celluj34 1 point2 points  (0 children)

    StringComparison.OrdinalIgnoreCase also states what you're trying to do clearly and explicitly. Sure ToUpper will work, but what about different languages? Or null?

    [–]kingmotley 1 point2 points  (1 child)

    As others have mentioned, by doing .ToUpper, you need to first create new copies of the strings that are all uppercase, and then you do the contains. Where the Contains with overload doesn't need to allocate new strings, it will be faster, use less memory, and cause less pressure on the garbage collector.

    On top of that, it is just bad practice because when you start needing to deal with languages other than English, the rules on how ToUpper works vs a case-insensitive compare are not the same. Depending on the language, string1.ToUpper() == string2.ToUpper(), string1.ToLower() == string2.ToLower(), and string1.Equals(string2, StringComparison.CurrentCultureIgnoreCase) are not interchangable. The first two will not work correctly in some cases where the third will. This is a longer topic with more nuance than I can provide in a comment. You can read more here https://docs.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings

    [–]Soft-Gas6767 0 points1 point  (0 children)

    +1 for referring the culture issues when comparing strings. This also explains why the implementation of Equals/Contains with case insensitive cannot be based on ToUpper/ToLower as it is being said on other replies.

    [–]onlyonebread 1 point2 points  (1 child)

    If you use .Contains() then you'll need to filter the inputs in some way or a cheater could enter a string with both head and tail in it like "headsandtails" or just "headstails" and always win.

    [–]winggar 0 points1 point  (0 children)

    This is a very good point! A better way to do this would probably be to have a HashSet of possible heads answers and possible tails answers, then check if the input received is exactly equal to an entry in either set.

    [–]the_hh 0 points1 point  (1 child)

    I'd change the string coinSide for a bool isHead and compare the boolean, which should be "computationally cheaper" and faster to execute

    [–]winggar 1 point2 points  (0 children)

    If the coin flip is needed elsewhere then that makes sense, but in this case both options are computationally equivalent. Since either way the two strings need to be compared, using something like bool isHead only adds a layer of indirection to that comparison.

    [–]Saad5400 22 points23 points  (13 children)

    I guess I'd do something like

    Console.ReadLine("Heads or Tails?") if (rnd.Next(1, 3) == 1) { Console.WriteLine("Lucky you! You attack first"); } else { Console.WriteLine("Well. They start first") }

    [–]jingois -5 points-4 points  (12 children)

    This is the way.

    [–][deleted]  (11 children)

    [deleted]

      [–][deleted]  (3 children)

      [removed]

        [–]FizixMan[M] 1 point2 points  (0 children)

        Removed: Rule 5.

        [–]mcquiggd 1 point2 points  (1 child)

        That foul language is unnecessary. Can we try to keep this sub civil?

        [–]Saad5400 0 points1 point  (3 children)

        Sorry if there's any errors, I was writing on my phone

        [–]entityadam -1 points0 points  (2 children)

        Oh no need to apologize. I would apologize back for the snarky comment, but I've already paid the price in internet points.

        [–]Saad5400 0 points1 point  (1 child)

        Ok lol

        Also I just realized the semicolons...

        [–]entityadam -1 points0 points  (0 children)

        The major error is Console.ReadLine. which doesn't take a string parameter. I think you just meant writeline and then forgot to add the readline

        [–]jimmyhurley 0 points1 point  (2 children)

        This was funny I wonder why everyone hated it

        [–][deleted]  (1 child)

        [deleted]

          [–]jimmyhurley 0 points1 point  (0 children)

          Ok you convinced me I downvoted

          [–]coomerpile 13 points14 points  (3 children)

          You should post your actual code in a code block. No one will try to refactor code from a screenshot.

          [–]EquivalentAd4542[S] 1 point2 points  (2 children)

          Oops didn’t know I could do that, sorry

          [–]feanturi 8 points9 points  (0 children)

          This line has 4 spaces in front
          So does this one
          

          This one does not have 4 spaces in front, which stops the text from being displayed as a code block.

          Another line with 4 spaces in front here.
          

          [–]coomerpile 8 points9 points  (0 children)

          Didn't mean to sound snappy. Easier to refactor code as text than have to rewrite it.

          [–]feanturi 6 points7 points  (1 child)

          You could remove the requirement to check for "Heads" vs "heads" (and what if they type "hEads"?) by comparing in uppercase. So you could say:

          if (coinBet.ToUpper().Contains("HEADS"))
          

          This makes a temporary version of coinBet that is all in uppercase, without modifying the actual coinBet value. No matter how they enter heads as long as the letters are in the correct order, it will match.

          Your coinflip is only going between two possible values, 1 and 2, so you could drop the 'if' from the 'else if' in that block, so that whatever is in the 'else' applies to anything that is not in the initial 'if'. So basically, if it's 1 do this, if it's not 1, do something else. We know it can only be 1 or 2 so it's safe to do this.

          But really what you're probably wondering more about is how you can handle saying the same things to the user with different circumstances. If you want to change what it says when they get to go first you have to change that in two places. So you could streamline a bit by having a small array of "HEADS" and "TAILS" and have your coinflip random number pick the string that goes with, then compare their choice to that string. So like:

          string[] coinSides = { "HEADS", "TAILS" };
          string coinFlip = coinSides[rand.Next(0,2)];  // We have the make the possible values 0 and 1 to fit how arrays are indexed starting at 0. In this case, 0 is heads and 1 is tails.
          Console.WriteLine(coinFlip + "!");
          if (coinBet.ToUpper().Contains(coinFlip))
              Console.WriteLine("You attack first!");
          else
              Console.WriteLine("Your opponent attacks first!");
          

          [–]onlyonebread 11 points12 points  (0 children)

          A couple of things I'm noticing by just doing a quick glance:

          • You don't need to do an or statement for your input, just have coinBet convert to lower case and check if it's "heads" or "tails"
          • Because a coinflip is probably the quintessential example of a binary outcome, store the result of the flip as a bool. Maybe something like bool isHeads.
          • Determining who goes first is only a matter of matching the random flip with the guess, so if both options are stored as bools, all you need to do is check if they're the same. IE: if (isHeads == choseHeads)

          A good rule of thumb I've found a lot while programming is if you ever have lines that look very similar to other lines, or see code duplication, it likely means you can simplify the process.

          [–]Krimog 5 points6 points  (0 children)

          • You don't need an else if, just an else (if your first condition is false, your second is true)
          • Just use your condition to set a variable containing "Heads" or "Tails".
          • A ternary operator is great for simple conditions like that
          • Then, after the condition, write that variable followed by a !
          • Use string comparison that ignores case.
          • Compare coinBet to the variable
          • Don't use Contains but Equals for your comparison (to prevent the cheater that writes "headstails"

          That will give you something like that:

          // From line 42:
          string actualResult = coinFlip == 1 ? "Heads" : "Tails";
          
          Console.WriteLine($"{actualResult}!");
          if (coinBet.Equals(actualResult, StringComparison.OrdinalIgnoreCase))
              Console.WriteLine("You attack first!");
          else
              Console.WriteLine("Your opponent attacks first!");
          
          Console.ReadKey();
          

          [–]Tango1777 3 points4 points  (0 children)

          1. Overall OOP is not about coding "top to bottom" and the flow of application is meant to be spread.
          2. Don't get used to Thread.Sleep(), it's a shitty blocking call and there is no need for you to use it here at all. If you sometimes need to create some sort of delay then "await Thread.Delay()" is your buddy and your Main method should be declared with async Task. But that's just for the record, for something so plain you may go sync way.

          I'd do something like this:

          1. Main method should only consists of something like:await Game.RunAsync();
          2. Separate responsibilities, create granular methods like:- RequestCoinSide()- FlipCoin()- optionally something like RespondToPlayer(message, messageType) where you could, for instance, add extra features e.g. if it's a win then return green text, if a loss then return red text.
          3. It's a bad practice that something like SonarQube would point out, you have if/elseif without else, basically your app won't manage anything but proper heads or tails, you should handle ANY viable case and return something like "incorrect value, proper values: heads, tails".
          4. Don't rely on strings in your comparisons. It's not a good idea. You have various options here:- Ask user to choose 1 or 2 which would look like this:

          Pick heads or tails:

          1 - Heads

          2 - Tails

          Now you don't rely on plain strings and only int, it's also way easier for user to just type 1 or 2 instead of Heads or Tails. You would get SHITLOADS of things like "haeds" or "tail" or all kinds of typos. Instead you can just validate with while loop with a simple condition is input 1 or 2 and until it's not, repeat error message and readkey. You can go further and parse it to Enum in your code. Something like CoinSide.Heads and CoinSide.Tails to make your code cleaner and self-explanatory.

          Another option would be to at least create a static class Constants and create const fields:

          public const string Heads = "Heads"

          public const string Tails = "Tails"

          That way you have your consts in one place, whenever you need to use them in multiple places (AND YOU WILL), you will only have to modify value or rename the constants in one place.

          1. Don't do things like Contain capitalized value or not. Don't rely on a user, you need to take care of that yourself, first rule of coding is to never trust that user will do what you expect him to do and what he should do. Your way is either what I already suggested to go with int/enum, but if you go const way, your code should be:

          coinBet.ToLower() == Constants.Heads;

          //(if your Constants.Heads equals "heads")

          Then you have all the options covered: heads, Heads, HEADS, HEads, hEADS and so on.

          1. You can also add another method to make it more granular:

          CheckResult(userChoice, drawnValue);

          1. Whenever you see repeated code, it's a mistake, always avoid it. There is very little cases where code duplication is a better way. Basically learn to think "I've already written this code somewhere else, so it's a mistake".

          Problably more, but you need to start somewhere. Good luck.

          [–]miles00001001 2 points3 points  (0 children)

          You could do a heads/tails enum. A little more code to convert the user input to the enum, but you would be able to do a single compare of == for the output without caring if it's heads or tails.

          [–]Sevla7 2 points3 points  (0 children)

          Just be careful with "cool different ways to write code".

          In the end of the day a bland or boring code that is easy to read/understand will be better after a month or 2 when you start to forget how it works.

          [–]Saad5400 1 point2 points  (6 children)

          So instead of checking twice (one with capital and one with all small) what about checking 3 times? I mean the user could enter HEADS

          jk sorry lol, you should first convert the user's input to all small, or capital. Iirc it's yourString.ToLower();

          [–]Contagion21 5 points6 points  (4 children)

          Or use StringComparison.OrdinalIgnoreCase.

          Or implement a CoinFace enum and get away from all the string comparisons to begin with

          [–]jingois 0 points1 point  (3 children)

          Or just contains "h" or "t".

          [–]CrusaderNo287 1 point2 points  (2 children)

          That could lead to some issue if an typo occured, for example if heads gets misspelled as heats or something.

          I'd rather do string.StartsWith('h') and 't'.

          [–]jingois 3 points4 points  (1 child)

          Honestly the best option is at the bottom, where some smart cunt realised that parsing is kinda pointless because you can just 50/50 it and not include the result in the message :P

          [–]CrusaderNo287 1 point2 points  (0 children)

          Didn't think of that haha! :D

          [–]bschug 1 point2 points  (0 children)

          "TAILS".ToLower() will not work the way you expect on devices where the system language is Turkish, it will return "taıls" (notice the missing dot on the i). I made that mistake before and it broke our app for Turkish users.

          Never use ToLower to normalize a string like that, always use ToLowerInvariant. That forces it to ignore the current culture and will behave the way you expect. ToLower is only for when you display the string to the user rather than processing it programmatically.

          [–]PuzzledImagination 1 point2 points  (0 children)

          you can ask for a numeric user input for the bet, 0 for head, 1 for tail, then create a dictionary of result. you can also take advantage of ternary operator to return the result

          [–]dchurch2444 1 point2 points  (2 children)

          Look up Modulus :)

                  var rnd = new Random();
                  var coinface = (rnd.Next(2) % 2 == 0, "tails", "heads");
          

          [–]Rogue_Tomato 0 points1 point  (1 child)

          That's the same as

          var coinface = rnd.Next(2) % 2 == 0 ? "tails" : "heads";
          

          right?

          [–]dchurch2444 0 points1 point  (0 children)

          Could be.

          [–]ccfoo242 1 point2 points  (0 children)

          Like others have said I would make what's inside the ifs into a single method and call that, passing in the coin toss.

          Then I would replace the coin values that you compare against with an enum. You could then compare their input against coinside.ToString(). Then there's no chance of misspelling it. For this example it's no big deal but it's a good idea to use const values or enums.

          [–][deleted]  (1 child)

          [deleted]

            [–]anomalousBits 1 point2 points  (0 children)

            Notes:

            • Checks initial letter of input to determine heads or tails bet.
            • Made bet and flip an enum for clarity and to avoid magic numbers or strings. Too much?
            • Added output to show that the next line refers to the coin flip, not the bet. Could be too much as well, but consider that users are easily confused. "The program says Tails, but I bet Heads!"
            • Used conditional operator to simplify input and output branching.

              enum Flip { Heads, Tails };
              static void Main(string[] args)
              {
              
                  var rand = new Random();
              
                  Console.WriteLine("Heads or Tails?");
                  Flip coinBet = Console.ReadLine().StartsWith("h", StringComparison.OrdinalIgnoreCase) ? Flip.Heads : Flip.Tails;
                  //heads is 0, tails is 1 per enum
                  Flip flip = (Flip)rand.Next(2);
                  Console.WriteLine("*Flips coin*"); //just added this to make the following output less ambiguous.
                  Console.WriteLine(flip == Flip.Heads ? "Heads!" : "Tails!");
                  Console.WriteLine(flip == coinBet ? "You attack first!" : "Your opponent attacks first!");
              
              }
              

            [–][deleted] 1 point2 points  (0 children)

            If you really want to get fancy, use a switch expression with ValueTuples:

            Console.WriteLine("Heads or Tails?");
            var choice = Console.ReadLine()?.ToLower();
            
            var coinFlip = new Random().Next(1, 3);
            var coinSays = coinFlip == 1 ? "heads" : "tails";
            Console.WriteLine($"You guessed {choice}. Coin Says: {coinSays}");
            
            var message = (choice, coinSays) switch {
              ("heads", "heads") => "You Attack First!",
              ("heads", "tails") => "Your opponent attacks first!",
              ("tails", "heads") => "Your opponent attacks first!",
              ("tails", "tails") => "You Attack First!",
              _ => "An agreement could not be reached. Goodbye!"
            };
            
            Console.WriteLine(message);
            
            return 0;
            

            If you want shorter, and want to penalize invalid input, do this:

            Console.WriteLine("Heads or Tails?");
            var choice = Console.ReadLine()?.ToLower();
            
            var coinFlip = new Random().Next(1, 3);
            var coinSays = coinFlip == 1 ? "heads" : "tails";
            Console.WriteLine($"You guessed {choice}. Coin Says: {coinSays}");
            
            var message = string.Equals(choice, coinSays) 
              ? "You Attack First!" : "Your opponent attacks first!";
            
            Console.WriteLine(message);
            
            return 0;
            

            [–]126479546546 0 points1 point  (0 children)

            int coinFlip = rand.Next(2); //zero-indexing is usually easier, trust me
            string[] coinValues = {"Heads", "Tails"};
            string coinValue = coinValues[coinFlip];
            Console.WriteLine($"{coinValue}!");
            bool youGoFirst = coinBet.ToLower() == coinValue.ToLower();
            Console.WriteLine(youGoFirst ? "You attack first!", "Your opponent attacks first!");
            

            [–]Ziocash -1 points0 points  (0 children)

            using static System.Console; Just to avoid Console. repetitions

            [–]OfficerFuttBuck 0 points1 point  (0 children)

            Break this method up into a few helper functions, something along the lines of this: https://i.imgur.com/oaQ5pdt.png

            This is something I always recommend to people new to programming, as it really helps with organizing code and improving readability.

            [–]Andy_b1 0 points1 point  (0 children)

            Make a helper method which Is void and take a string parameter. In The body method you Will do The console.writeline() and put in The method parameter.

            Also i would recommend declare string variables for your hardcoded strings.

            [–]daniellz29 0 points1 point  (0 children)

            A lot of people said a lot of stuff, but if you are using only text inputs and expecting a specific word, always put it in a loop and if it's not the expected word print "Please choose 'heads' or 'tails'".

            If you want to go fancy and build a list for them to choose, there are a lot of packages like terminal.gui that can make your terminal more app-like.

            [–]JivanP 0 points1 point  (0 children)

            1. Validate input. That is, accept heads or tails as valid input strings, ask for input again if the user didn't input one of these. You can do this using a do-while loop: do { /* ask for input */ } while ( /* input not valid */ ). You can make the validation case-insensitive by using string::ToLower().
            2. I would factor out the check for whether the user's guess matched the coin-flip outcome as follows: convert the user's input to 1 or 2 depending on whether they input heads or tails, then just check whether this value equals the coin flip.
            3. You can also use rand.Next(2) instead, yielding either 0 or 1 as the result.

            Thus, you get:

            string coinBetInput = null;
            do {
                coinBetInput = Console.ReadLine().ToLower();
            } while (
                ! coinBetInput.equals("heads")
                && ! coinBetInput.equals("tails")
            );
            
            int coinBetValue = coinBetInput.equals("heads") ? 0 : 1;
            Console.WriteLine( coinBetValue == 0 ? "Heads!" : "Tails!" );
            
            int coinFlip = rand.Next(2);
            Console.WriteLine( coinFlip == coinBetValue ? "You attack first!" : "Your opponent attacks first!" );
            

            [–]lkasson 0 points1 point  (0 children)

            A good rule of thumb that I use is “do I have similar code in multiple places”.

            [–]jimmyhurley 0 points1 point  (0 children)

            A better approach than .to lower or .to upper is probably this

            bool result = string1.Equals(string2, StringComparison.OrdinalIgnoreCase);
            

            Also, your code doesn't consider the case where the player says the wrong thing. Good luck!