 Continuing on from where we left off, this time we're going to create some actual real programs in a sense, some interactive programs. We're gonna have a number guessing game and a hangman game and a tic-tac-toe game. And these are all going to be on the console. You're gonna enter text on the console and see text spit out on the console. So these aren't graphical applications in their own window. We don't have that facility in Pigeon, but we will at least have a interactive program. Before getting into that though, there's a correction, small correction. I accidentally in the previous video, I put commas in between the elements of a list. And this is not proper Pigeon. You can't put commas to separate your operands of your operators. So you should write the list like so with no commas at all. Otherwise, you'll get a compile error. So first off, for our number guessing game, you're going to enter text at the console, a number at console, hit enter, and then that gets us back a string. But we want to take that string and interpret it as a number. So the user's gonna enter something like, let's say a string that's like 365. But the problem is this is a string, not a number. We need a number. So we need to be able to take this string and convert it into the number of 365. The string 365 and the number 365 are not the same thing, right? They're distinct pieces of data. So how do we do that? Well, there's no built-in operator for this in Pigeon, but we can write a function that does this for us. We'll call it parse integer. And it's gonna use this function numeralton number, which we'll define down here. And numeralton number given a string, which is supposed to be a single character string, a single digit, like say, zero, one, two, three, all of the single numeral strings, it then returns back the corresponding number. So if s is equal to zero, we get back zero. If s is equal to the string one, we get back to the number one, et cetera, et cetera. And then the event that what we pass to numeralton number is not a numeral string at all. Or if it's not equal to any of these, then we're gonna return nil, which effectively is gonna signal that the input was bad. And so our parse integer is gonna take a string, something like say, three, six, five, and we wanna get back this number, three, six, five. And parse integer is gonna do the same thing where it returns nil in the event that the input is invalid. So like if you try and parse a string that's f four two, that can see, you know, this is not a valid number character, it's not a numeral. So this will give us nil when we call parse integer and parse sn. So this only works on strings where all of the characters are numerals. So this is valid, but as soon as you put something which is not a numeral, now that's invalid and you'll get back nil. So what's going on here is the general trick here is that we wanna work through our string backwards. We're gonna start on the right and individually convert each one of these characters. So the three here becomes three, six becomes six using the numeral number function. But then you need to multiply them according to their digit place. So the rightmost digit you multiply by one because it's in the ones position. This is in the tens position, so you multiply by 10, this you multiply by 100. And if we had another digit like seven here, then this would be multiplied by a thousand, right? So what we're gonna do is we're going to work through our string backwards. We're gonna start on the right side and we're gonna have this factor one and each time through the loop when we get a character from the string, we're gonna first get five, then six, then three, then seven. And the factor, first time through the loop will be one, but then each time through the loop, we increase it, we multiply it by 10. So the second time through the loop, the factor will be 10. Third time through the loop, it will be 100. The fourth time through the loop will be a thousand. And so we get these individual numbers, seven, three, six, five, we multiply them each by the factor. So five will be times one is five, six times 10, six times 10 is 60, three times 100 is 300, and seven times a thousand is 7,000. So we have all those individual numbers and each time through the loop, we're also, we're accumulating them as the results. The result starts out zero. It's ultimately why we return and each time through the loop, we take the number here, which the, you know, the digit from the string, we multiply it by the factor, then we add it to the result. And so each time through the loop, we're accumulating the result. First five, then we add 60, then 300, then 7,000. Looking at the rest of the code here, we have this first check of, well, what if the anterior string that's empty? So we're going to return nil in that case because that's an error. That's, you can't parse an empty string. That doesn't make any sense. So we're going to want to return nil. And then to iterate through the string backwards, the four each loop doesn't work on strings number one, so you could use charlist to get the elements of the string. But we need to go backwards. So we need four deck. Four deck is the most convenient way to do that. So we have our counter variable i and we're going to start at one less than one of s. That's going to be the first value of i is one less than the length of s. And so effectively it'll be at the position here of the rightmost digit. And we're going to work down to and through zero. Index i here in the string is three, six is at index two, three is at index one and seven is at index zero. So it goes backwards through those indexes. And then each time the loop, we get a single character string at that corresponding index of the string. That's what get char does, if you recall. So first time the loop that gets us a string, which is just the digit five, second time three will get us just the digit six, et cetera, convert that to a number with our numerals to number functions thing down here. Assign that to n. And then we need to test if n is equal to nil because if there's some kind of error, because maybe the input is invalid and one of the characters is not like it could be, I don't know, something number sign or what, anything that's not a numeral. In that case, the numeral to number will return nil and we're going to check for that and return nil out of parse integer indicating that like the parsing is invalid. We can't parse this string. Otherwise, we proceed as normal. We take that n, multiply by our current factor, accumulate that in the result. That's what all this does. And then we increase the factor. So next time around factor will be an order of magnitude greater. Okay, and then once we're done with the loop, we have our accumulated result and we return it. So that's a function which parses an integer. Here's some example use cases. If we parse integer three, five, we get back 35. If we parse integer 999, we get back 999. If we parse integer with an empty string, we get back nil. And if we parse integer here with characters that aren't numerals, then we also get back nil. So our number guessing game is going to need to pick a number between one and 10 at random. And the problem is that well, computers don't do things randomly. They execute instructions one after the other deterministically, they're not random. But there are algorithms that given a chosen value, as chosen so-called seed value, from there it'll generate a series of numbers deterministically that has a high degree of entropy. So it's a series of numbers that seems to be, at least to a human observer, it appears to be random given the starting point, given that starting seed. But every time you use this algorithm with the same seed value, you get the same series of numbers. So we can get a random number effectively if every time a program runs, we use such an algorithm but use a different seed every time a program runs. So how do we get a different seed every time a program runs? Well, we can just use the system clock to get the current time and date. And because the current time and date always advances, always goes forward in time. As long as our system clock is set properly, like we don't accidentally set it back into the past, then every time we run a program, we're gonna get back a different series of random numbers. Our seed will be different and so the series of numbers we get from this algorithm will be different every time. Now in Pigeon, we don't have to get into any of the details of reading the system clock or creating such an algorithm because it's provided for us. We have the random operator, which does exactly what I described to return a random number that's between zero and one. And zero is inclusive whereas one is exclusive, meaning the random number returned, it might be zero, but it's not gonna be one. It might be 0.99999, but it's never gonna be one because one is excluded. Now in our program, we don't want a random fractional value, we want an integer between a specified range starting at one and up to and including 10. So how do we get a random integer of some range? Well, we've created this function random int where we specify start and we specify an end and the end is gonna be exclusive. So if given random int one and 11 here, it's not going to include 11. So the highest return value will be 10, not 11. It'll be one less than 11. So to randomly generate an integer in the range between some starting value and some end value where the end is excluded. So say if start is seven and end is 20, then we get a random number that's either seven, eight, nine, 10 up through 19. So it might be 19, but it's not gonna be 20. It's gonna be one less than end. And to do that, we're gonna have a local variable n which we'll use just as a convenience to store intermediate values just to make the code more readable. We don't really need it. We could just have one big return statement down here, but it makes it go easy to read. And first thing we do is we get a random number between zero and one using random. And then we need to find the distance between the end and our start, the magnitude of the distance between the end and start. So we subtract start from end. And in this case, assuming it started seven and end is 20, that'll give us 13. And if we multiply 13 times n, then effectively we have a random number that's between zero and 13 exclusive. So it might be 12.99999, but it's not gonna be 13. Might be zero, might be 0.1, might be 2.7, but it's not gonna be 13. So we store that in n. And we don't want a random number between zero and 13. We want a random number between seven and 20. So we're gonna add start to n. So start is seven. So you add seven to this random number between zero and 13. Effectively then you have a random number between seven and 20. And the last remaining problem is that this number is not necessarily an integer. It could be an integer. Might happen to be eight or 11 or 17 or something like that. But very likely it's not. It could be 14.2 or something. So we need to take this fractional value and get an integer from it. And for that we'll use floor. Floor is an operator that given the number rounds down. So given 13.9 you get 13, given 13.8, you get 13, given 13.01, you get 13. Effectively just truncates everything after the decimal point and you get that as an integer. So in this example, again if start is seven and end is 20 then after flooring we're gonna have an integer that might be seven, might be eight, might be nine, it might be anything up to 19. Because remember the random number we had here after this step that was something from 17 up to, but not including 20. So it'd be say 19.9999. If you floor 19.9999 you get 19. So the greatest possible value returned in this case would be 19. So that is our randint function. Given randint with the arguments one and 11 we'll get a random integer between one and 10. One less than this end value. So the max value is gonna be 10. And here we just print that out. Okay, so we have that in place. And now for our guessing game we're gonna have two local variables, guess and end. Guess is gonna store what the user enters. And end is going to be the random number that's picked. So first we're gonna generate a random number end using the randint function we just defined and we get back a random integer between one and 10 up to and including 10. And then we use the prompt operator to prompt the user to pick a number between one and 10. The prompt operator like print, prints out all of the operands. So you can have any number of things here and they'll all get printed out to console. But unlike print it also pauses their program and waits until the user enters text at the console and hits enter. So program here is gonna sit here and wait until the user hits enter. And once they do everything they've typed gets returned as a string not including the new line at the end of what they typed. It gets returned as a string. And so here what if user types is gonna be assigned to guess. But guess is a string and we want it as a number. And so we're gonna use parse integer to convert their guess into a number. And if what they entered is not a valid integer at all then guess will be nil. And so in that case given this condition we're gonna print your guess was not a number. Otherwise we need to check if their guess was between one and 10. If not it's not a valid guess at all. And we're gonna tell them your guess was not between one and 10. Otherwise we're gonna check if their guess is equal to the random number we generated in if so they win. Otherwise in the last case well they picked a number between one and 10 but they didn't guess correctly. And so they lose and we tell them what the number was. Okay let's finally see this program in action. I've copied all the functions into a file I call test.dp. Doesn't matter if her purpose is what I call it. Could have called it number guessing game I guess. It does matter though that it has the extension dp. The pigeon compiler expects your dynamic pigeon code to end in that extension. Otherwise it won't accept it as dynamic pigeon code. And I have the main function down at the bottom that's not strictly necessary but it is a very strong convention to put main at the bottom. Though it really doesn't matter what order we put the functions in. So to run this program at the command line I type pigeon that's the name of the pigeon compiler run. And then from where my current directory it's found under examples. Test something. There we go. Should be finding it. There we go. And I hit enter. It'll compile it and run it. Let's first test the failure cases. So let's see if I enter something that's not a number at all like that. And enter, your guess was not a number. That's good. Now if I put a number out of range that's also invalid. It's not between one and 10. Let's see, let's go for the edge cases. So 11, is that out of, yeah that's correctly out of bounds. Zero, also out of bounds, good. And now let's do valid input. I'm just gonna do one. Just keep guessing one until I win. Let's see how many times it takes. You lose. You lose two. There's five again. Three. Wonder how many times it's gonna take. Sometimes it takes a while. Could just be unlucky. I'm tempted to change the program. So it's a smaller range of numbers. There we go. That wasn't too many tries. Was that even 10? That was not so many attempts. That was about 10. Anyway, so you win. So the program seems to work at least. It could be possible there's some weird bug lurking where maybe for given input or a certain randomly chosen number the logic breaks down. But I'm gonna say looking at the code this is a very simple program. I'm pretty sure it's correct. Now as a minor enhancement maybe we want the code in the event of them entering something that's not a number or not between one and 10 something that's out of bounds. Maybe we want to keep prompting them until they enter something valid. That seems more sensible to me. We can arrange this quite easily by just simply adding an infinite loop that just has a condition of true. So it just will always keep iterating until we either break out a loop or return from the loop. And so it'll prompt them and pick a number between one and 10. And if they pick something that's not a number or not between one and 10, it won't return. But when they pick something that's valid and win or lose, then we return. Then we end the program. So I put the change into test.dp and go back and rerun the program. And if I put something in valid, it should prompt me again and say, keep asking until I pick something valid. Let's see, 11, 12. That's still not right. It keeps asking. So finally I enter 10. And it says I lose and loop ends in the program exits. So now let's look at a hangman game. And we're gonna make use of the functions we've defined before. Random, we just defined sublist contains any join. We've worked with all those before. Sublist, if you recall, given a list returns a new list, which is containing all the elements from start up to but not including end from the original list. Copies those elements over to the new list. Contains any tests if a string contains some set of characters. If it returns true, if it contains any of those characters, otherwise returns false. And join takes a list of strings and a separated string. And it concatenates all those strings together but puts a separator in between each of them. And before we get into the code of the hangman, the stuff new, we'll look at the actual program and I'll run it. And it's saying, hey, you have 11 remaining guesses. Pick a letter. We're guessing a randomly chosen word that's 11 characters long. That's why we have 11 guesses because it's a 11 character string. All the words I've picked are animal names. So I know it's an animal. But I'm gonna randomly start with some vowel E. Nope, nope, yeah, there was an E. And because I guessed correctly, I still have 11 remaining guesses. I only wanna guess incorrectly. Do we decrement the number of remaining guesses? J is there a J? No, there's no J. There's an I. Nope, there's no I. There's an O. Nope, yeah, yes, there is. P, T, I know what this word is. Ter, row, deck. Tool. And hey, okay, so I filled the word in and I win. Cause I guess it's pterodactyl. Kinda cheated because I know what the words are. Anyway, so that's the hangman game. And so looking at main here, we'll come back to these utility functions as we come across them. So we have words, which is gonna be just this list of words. This is the set we're choosing the word from. And found is gonna be a list that keeps track of all the characters we've currently found. When you print it out here, that's what you're seeing here is this is the found list of characters. And letter is we're going to prompt them to give us back a letter. That's what the get letter function does. We'll look at that in a second, but we'll get back the letter they entered. And guess this keeps track of how many guesses they ever made. So first off words, again, is this list of strings. Just see, I made them all animal names. And this comma, I don't think I've shown the syntax before. As a matter of convenience, sometimes you'll have what would otherwise be a really long line and you think it's ugly and you want to split it up into successive lines. And so you can do that with the comma. And you should indent, I forget if I made it mandatory, but you should indent underneath to make it clear that this is a continuation line, but that's what the comma does. It's so you can have successive lines that continue what otherwise would be one long ugly line. It's just an allowance that makes our code a little more readable. And so word, we want to randomly pick a word from this list here. And so we need to randomly pick a number that's index zero up through, and I think there's nine strings long. So the last index is eight. So we want a random integer that's from zero up to and including eight. So if we use a random int function with a start of zero and an end of the length of words, which should be nine, then we'll get back a random integer that's either zero, one, two, three, four, five, six, seven, or eight. So with that random index, we get the word at that index and assign it to word. So we have a randomly chosen word. And the number of guesses that the player gets is equal to the length of the word. And this found list, we started out empty and then for the length of the word, we push a underscore onto the list. So it starts out as if it's five characters long here, I'll just show the game again. So three, oh, three character strings. So found is three strings each a single character in underscore. That's what found starts out as. And then we enter the main loop of our game where as long as guesses is greater than zero and guesses is greater than zero, we keep going. But once it's equal to zero, once it gets decremented down to zero, then the game is over and we tell them you lose. The word was whatever it was. But in the main loop here, so we first tell them you have each time through, tell them how many guesses they have remaining. And then we need to get a letter. We need to prompt them to enter a letter. And so that's what the get letter function does. We need to pass in the found list because it gets printed out by get letter. So looking up here, get letter. Letter is gonna keep track of the letter. That's where we store the letter they enter. And alphabets, you'll see how that's used in a second. That's just a list of all the lowercase letters of the alphabet. Anyway, so we enter this infinite loop because we're gonna allow them to keep entering input until they enter something valid. Once they enter something valid, a single character lowercase letter, then we'll exit the function, then we return. But until then we keep looping. So we prompt them, hey, pick a letter, and we show them the current state of what they've currently guessed. We take the found list, join it together, and that's why you see this printed out here. This is the found list printed out right here. Okay, so we get a letter and we need to make sure it's valid. So first it has to be a length of one. That's what this is asking, is this a length of one? And also we wanna make sure that the letter is a lowercase letter. And so that's what this test does. We're taking, contains any is expecting a list of characters, so a list of strings. So we make a list out of our single letter and then test if alphabet contains the letter anywhere. And if true, this will return true. So upon valid input, this whole condition will be true and we return the letter. Otherwise we tell them, hey, the input wasn't valid. You have to enter a single lowercase letter and prompt them again and go through the process until they enter something valid. So this function keeps iterating until it returns a letter. And we get that back and assign it to this local variable letter. Be clear, letter here is a different local variable from the letter and main. Separate variables, same name, but separate functions. So they're separate variables. Okay, and then we're gonna test if the letter they entered, we wanna know if it's not in the word. That's what this test does. We test if the letter, again, we add it to a list and if the word, if contains any says the letter's not found in word, then we decrement and guess us. They guessing correctly. So we decrement and guess is by one. But then we also need to update the found list. So maybe they guessed correctly and so where they guessed correctly, that needs to get plugged in into the slots and found where that letter should be located. And so that's what update found does and we pass in the found list, the word and the letter. And so looking up here at update found, this function is gonna update the found list but also it returns true or false. It returns true if we found all the letters, if we filled out the found list, otherwise it returns false. So this complete variable here starts out true and as we go through all the characters, the word and look at found, if any of the characters in found are still an underscore then we mark complete false. So update found will say that the list is not complete. But anyway, inside the loop, we're going through all the characters of the word, the word the player's trying to guess and see if the letter they guessed is equal to any one of those letters. And if so, then we update the corresponding letter in found to be that letter. We replace what would be an underscore string with the letter string at that position. And then having done that at that index, that's when we test if the value in found is still an underscore string. And if so, that's when we know that we still haven't found the complete word. There are still letters missing. So that's what the update found function does. It's called here as the condition of an if. So it's doing two things. It's updating found, but it's also returning true or false. And if it returns true, if the word is now complete, then the user wins and we return exiting main and therefore exiting the whole program and we're done. So lastly, we're gonna do a tic-tac-toe game and I'll get in and show you the program first. So I'm gonna clear that. Run it again. It prints out the board. It represents the board as just these slots displayed as underscores. So this is an empty board with nine slots and it tells player X to select the top middle or bottom row. And so I'm gonna pick the middle and then we'll pick the middle column. So it puts an X there. Now it's player O's turn, they do the same. Top and left, let's say X is turned again. I'll just demonstrate that I can't pick a slot that's already occupied so I can't do top left right here. It says, nope, that slot is occupied and asks player X to pick again. So I'm just gonna do top right and then player O, they do bottom left and X is say top middle O's. And O's now can win. So they do middle, left and O's win. And of course, if you fill in the board and there's a draw it shows you, it says tie I believe. So looking at the code now, what we have is we're representing the board with these three variables. We just represent the rows separately as three separate lists. The three slots of the top row, the three slots of the middle row and the three slots of the bottom row and we're just representing them as strings. That's one way to do it. There are many different ways we could do it but this way works. I could have had a single global that is I could have said like board and then have a list with all of these lists inside. So I'll just have this three times and get rid of these. I could have done this. I just found it more convenient to have three separate variables. I just made the code look a bit cleaner when we worked with the otherwise I'd have to access the individual rows and then access the elements. So just would make things more verbose. I don't really need these to be global variables and generally good style is to avoid introducing global variables that you don't really need. Instead you would create them as local variables in one function and then just pass the value to the functions that need them. Whenever you can arrange that, that is preferable but for stylistic purposes, just simplicity it was easier to make them globals. And then we have this function player move which takes the current player and either a string that says X or O and asks them to move and gets their input and it keeps prompting them until they enter a valid input and returns what their move is as we'll discuss. And there's also the winter function which the logic looks ugly but it is verbose but it's just simply checking if there's a row of three X's or O's in any of the rows, columns or diagonals and if so there's a it returns X if X wins, O if O wins and returns underscore if no one wins and tie if there's a tie. So it just returns a string specifying if there's a winner or not. We'll get back to that and then there's the main function. So there's only three functions. We in fact we don't have any of those utility functions like we did in the Hangman program. So it's just these three functions. And in main we have W, this is what we're gonna use to store the return value of win and then test the various values that might return. And current player is going to store a marker of who's the current player and it starts out player X's term. And we enter this loop and we're gonna print out the board. That's what all this does. We're just concatenating the top row, middle row and bottom row together, putting backslash N in between. Oh, I have not explained this before. There are certain kinds of characters that you can't normally write in your strings when you know the things we type here. It's not legal to just put a new line in the middle of a string, that's bad code. You can't have that. So if we wanna represent a new line in our string, meaning the character that represents hey go down to the next line, then we have to write that in our strings as backslash N. This is called an escape sequence. And there are a few of them that's like backslash T for the horizontal tab character. There's another example. If you wanna have in your string, if you wanna have a double quote mark, well how could you do that? Cause double quote marks used to represent the other string and so how does the language know you don't mean double quote mark did mean and the string you just wanna have a double quote character in your string. Well, you have to escape it. You have to have backslash double quote mark and that's how you can have it. So what this means though is if you wanna have a backslash in your string, you have to write two backslashes. Backslash itself has to be escaped if you wanna have a backslash in your string. If you wanna have the backslash character in your string. Okay, so let's just print it out the board with new lines in between. That's why it's printed on the separate lines. I guess I could have just done three print lines. Yeah, that would have been equivalent of course. I could just, maybe they're even easier. I'll just demonstrate real quick. Just in case you were wondering, this would be totally equivalent. Don't know why I didn't do that. Anyway, we'll leave it like that, a little more compact. So, we print out the string and we test if there's a winner. Even though we just started the game in the first iteration through this loop here, we still test if there's a winner. And there's not gonna be a winner because all the slots are just underscores. And so what we'll get back from W is gonna be the, it said in the event there's no one winning, it returns an underscored string. And so this test will be false. W will not be X, it will not be zero. I mean, excuse me, O, it'll not be tie. It'll be something else. And so we come down here and we call player move, passing in who the current player is. It's currently X, it gets the input and also updates the board. And then after a player moves, we need to toggle who the current player is. So if the current player is X, then we change it to be O. Otherwise, we change it to be X. If it's not X, obviously it's O. So it's gonna, this is effectively gonna toggle it back and forth. So let's go look at player move. So player move, passing the current player, it's either gonna be string X or a string O. And we have three locals, row, column, slot, culture for column. We're going to have this infinite loop where we're gonna only return out of it once they've entered valid input and we've updated the board. Otherwise, we just keep prompting them. And first off, we need to get the row. That's what this all is. So we set row to nil. It needs to start out nil. And as long as row is nil, we're gonna prompt and get either, we wanna get T, M or B. So we store that into row. And if it is any one of these characters, if it's T, then we set row to top row. If it's M, then we set it to middle row. These are global variables, right? So these are these things over here. So we're signing row one of those three lists. Otherwise, if row, if what the user enter does not T or M or B lowercase, and we say invalid input, try again. And we set row back to nil. So this condition still tests false. Yeah, I guess, you know what? Simple way to do this. Much simpler way to do this. We can get rid of that. And we're gonna just break out. I think when I wrote this originally, I didn't have break in language. So that would explain why I didn't wrote it this way. So we just need an infinite loop again. There we go. Yeah, I think that's a little easier to look at. So once they enter something valid, then we break out of this loop. And again, be clear that break, the way it works is that this is inside this while loop. That's the one that's most directly contained inside. So it's only breaking out of this one, not also this one. Okay, and so down here, it's the same logic, but just for the column, exact same logic, and I'm gonna update it accordingly to do break, break, break, get rid of that, don't need that. Okay, yeah, so it's just getting his input, just sign it to call and then doing this test. If it's L, M or R, if it's L, then we're assigning the index, the respective index for that column within list. So like if it's left, then that's index zero within one of these rows. That's like this one, this one, or this one, right? That's what we want. Okay, so once we break out of both of these loops, we've gotten the row in a column, then we get the slot from the row at that index. We get the slot, which is a string. And if it's underscore, then it's not already occupied and we can set it to be whatever the current player is. Current player is either gonna be capital X or capital O. So we're gonna set in that row list, we're gonna set that index column, the current player string to that position. And then we're done, so we return. But if the slot is not an underscore, then the slot must all be occupied and then we tell them to, we tell them try again and our loop up here, the outer loop iterates once more. So that keeps happening until eventually we get valid input and we return here. So that's the player move function. Let's look now at the winner function. And in the winner function, there are eight possible cases of when we have a winner. We have all X's or O's in the top row, or the middle row, or the bottom row, or the left column, the middle column, the right column, or the two diagonals. So we have those eight separate cases and unfortunately there's no real elegant way to check this except just to have separate cases for each. And so first we're checking the top row. We're checking if all of the elements they're equal to each other, like if they're all X's or all O's. And if so, we also need to make sure that they're not, they could all be equal to each other, but they could all be underscores in which case we wouldn't have a winner. So we also need to make sure that any one of these three elements in the top row, index zero, one or two of the top row are not equal to underscore. Doesn't matter which one I pick here, the zero here could be one or two and it would be the same, it would logically be the same business. So assuming this is not true, and assuming this is true, assuming they're all equal, then we want to return either X or O depending upon whatever these values are, either they're all X's or they're all O's. So we return, it doesn't matter again which one we pick, I just picked the first one, so this could be a one or two here instead, same logic. And so it's gonna return either X or O in the event that the top rows are all X's or all O's. And then for the middle row, it's the exact same logic, it's just instead of top row, we have middle row here for the bottom row, same deal, it's just all bottom row instead. And for the left column, logic is very similar, except now the slots we're looking at are index zero of top row, index zero of middle row and index zero of bottom row. So instead of all just being one row, we're looking at the three different rows, but the same column, the same index here. For the middle column and the right column, same deal, it's index one instead of zero because we're looking in the second element in these lists, effectively that's the middle column, right column, same deal, except it's index two, the right most column. And then for the diagonals, okay, so we're getting top row zero, middle row one, and bottom row two, that's the top left to bottom right diagonal, and then for the bottom left to top right diagonal, the other diagonal, it's bottom row zero, middle row one, top row two. So these are the eight cases of when we have a winner, but now we need to make sure there's not a tie. We have to check if there's a tie. So what I've done is, here's a new operator we haven't seen before, there's L concat, L is in list. We're taking these three rows, concatenate them together, and what we get is the three elements of this followed by the three elements of this, followed by the three elements of this. So you just take any number of lists and concatenate them together into one big list. And we're gonna iterate through that big list where S is gonna be the value from the list and we check if any of the elements in top or middle row or bottom row are still a underscore, that means the board isn't full, and so we don't have a tie. So in that case, we return underscore. Otherwise, if we get to this whole loop and we don't return, then down here, this is the default case. It turns out, oh, there was a tie because the board is full, but there's no winner. So that's the winner function and that's our whole tic-tac-toe game. In fact, that means we're done with dynamic pigeon and we're gonna move on to static pigeon in the next video.