The course is part of this learning path
In this course, we begin building a game in Solidity, and more specifically, we begin to look at defining the mechanics and the components of the game.
In the previous lecture, we were defining our board here and whenever you want to play some things at the stone, then you can do so right now. And in the next few lectures, we want to implement the actual winning losing, and the drawing functionality. And I want to start with the draw because in my opinion, this is the easiest one. For determining how to win a game, you have to go through the horizontal, the vertical, and the diagonal rules. But the drawing is quite easy because if you want to figure out that nobody won the game, then all you have to do is at the end, see all the stones have been set. So, what that means in our case is that, we are counting how many times the setStone function was already called successfully and when this is the same as the length, that the square length meaning nine, then there is no more possibility to set the stone, and that is a draw and the game is over. I would say we just give this a try and see how this works out . Now obviously, our winning functionality is missing. So, even if we win in between, we can play until the game is over at the moment but let's just roll with it. To determine how many times we have already played, I will introduce a new helper variable here and I will just call this uint8 and then gameRounds;. And that is initially 0 and we are going to increment this every time we are calling the setStone function. Actually, let me call this movesCounter; because it's actually a count of how many moves we've made. The name might be a little bit misleading when we call it gameRounds. So, the movesCounter is 0 and the movesCounter becomes 1. Once we call it for a setStone function and it becomes 9 when the last stone is set on the board and if the last stone is set on the board, that means it's going to be the board size, the squared board size which is 3*3, which is 9. So, all we have to do is here, check if it's a draw draw. If the movesCounter, is the same as the (boardSize**2). So in solidity, if you want to make something to the power of something, then you have to enter these two stars here and that is the board size is free because this is the length of the board. This is where it comes from and that is 9. And then we have this setDraw function which is called and should refund the players. Now the at this point, I want to do a little coding challenge because the draw is not as hard as you might think but it's also not as easy as you might think. And I think it's a good idea to talk at this point about the pull versus the push method, how to get money out of a smart contract. And I'm going to start a little challenge now which is just finish up the setDraw function. So, that the balance of this smart contract which should be 0.2 ethos is distributed evenly across the two players. You can use the send function. You can use the transfer function. We have talked about them before and if you want to do the challenge then pause the video now and I'm going to talk a little bit more about how this is to solve after this pause. The 1st one, the balance to pay out is actually relatively easy to figure out because it's the balance of this smart contract divided by 2 and because both of the players had to put in the same amount of ether. It is divisible by two without any remainder. So, I'm trying to have the balanceToPayOut with this address and the address always has a balance helper. Sorry. Balance helper. And we can divide this by 2 without a remainder for sure because the logic in our smart contract doesn't allow any different amount than having two times the same amount for the players. Now, in our case we could do something like, player1.transfer(balanceToPayOut);, player2.transfer(balanceToPayOut); and if you came this far, then you're actually already very good. So, that is normally not more to do. But this one is a so called Push Method. Why is it called the push method? If we are playing this game and we come to the point where we have a draw, when automatically after last move has been made, the both the players get the money transferred to their account. So, it's going to be pushed to their account, by actually having another functionality which is actually setting the stone. And this is not always the best idea. Let me explain why. If you are playing from what is called Externally Owned Account which means a typical account where there's a private key behind which we have here in our run environment, then everything is fine. You can always transfer money to an externally owned account. This is not a problem. But if for whatever reason somebody decided to have a smart contract interact with our free in a row smart contract so, he's using something like an unchained wallet or a smart contract that is sitting in between his account and our game, then he could force an exception once ether is coming into his address of his smart contract. That would mean the setDraw function would never work. And that would also mean that the game in our case, would never end. So, in general it's a very bad idea to push money out of a smart contract automatically when something happens. The better version is to withdraw money or let people withdraw money. So, credit them a certain balance and then let them withdraw the balance so that at some point they are responsible for their own faith so they can maybe choose to withdraw their balance to someone else and they have to pay for the gas not the other player. Now, let me implement this and comment this a little bit. First of all, the transfer function is a high level function which automatically cascades exceptions. So in our case, when we transfer something to player one and that there is an exception happening, then this whole setDraw, everything that happened within the last setStone, call will be avoided and the whole exception the whole transaction will never be baked onto the Blockchain as a success. There is another function which we can call on an address which is send. And send is a low level function and I'm going to implement this even though in a production environment, real production environment, you maybe want to avoid at all that somebody is sending money directly out. I'm going to talk about this a little bit later again. So, the send function will give you a boolean if the scent was successful or not. So, if the sent is successful, then it will give you a boolean true. If the sent was not as successful, if there is an exception happening behind this address which receives the ether, then this will be false. And this is where our withdrawal, but win comes into place. Now, if our send function is not true so if this is false, then we want to credit our balanceToPayOut to new variable that I'm going to introduce so that when the player one is going to withdraw his win, then he can always withdraw the amount that he has in his balanceToPayOut for player 1. I'm just going to finish up these functions. If this is false, then we are going to introduce a new variable here which is a uint balanceToWithdrawPlayer1; and the uint balanceToWithdrawPlayer2. balanceToWithdrawalPlayer1 is incremented by the balanceToPayOut for player1 and balanceToWithdrawPlayer2 is also incremented by the balanceToPayOut. And the withdrawalToWin function then looks slightly different. First of all, I want to give them the opportunity to withdraw the win to a different address. So, we are going to have an address payable_to here and then we say if (msg_sender which is the person who initiated this transaction, = player1), then we first of all, require the balance to be > 0 or else it doesn't make sense to call this function. And then we are going to say our uint. And I'm going to talk about this in a second. And then the two functional the two address is going to be the balanceToWithdraw. _t.transfer(balanceToWithdraw);. So, the same happens for player2, obviously and then we have one more thing which the compiler usually don't like. We're getting there. What happens here is we are going to try and avoid any writing to our state variables after an external call. Why is this important? There is something called a Re-Entrance Possibility that might come up down the road and when you do any external function calls, then there is a slight chance that somebody can call back to the same function again and call it again. So, let's say you are withdrawing or you're sending the balance from player1 but you're not setting it to 0. So, in our case, the balance to player1 will still have 0.1 ether and you're doing this balance 20 after the transfer. That means if I call an external smart contract and let's say there's enough gas. We are talking about gas later but say let's say there's enough gas to call back the withdrawal to being functionality again. Then, once he calls this again and the stack is still not through here, then it will again have the balance with 0.1 ether and it will again transfer it. So, there is away a possibility to get the 0.1 ether twice even though he will actually only be allowed to get it once. So in our case, we have to set the balance to withdraw to 0 before we are going to transfer the money out in order to avoid letting them get the same balance or calling the same function over and over again. And then let's give it a quick try. We are going to deploy our manager. We are going to start a new game. We're going to change our account and we're going to copy became address. Let's join this game. Good we have joined the game. Who is the next player? The next player is the one which is here selected. So, let's go and set the stone to 0,0 and select the other player. Set the stone to 0,1. Select the other player Set the stone to 0,2. Select the other players. Set the stone to 1,0. So, like the other player. Set the stone to 1,1. The other players, this time to 1,2, 2,0, 2,1. And now it becomes interesting. We have 99.89 ether in here. And if I set this to 2,2, then we have our 0.1 etherbec and that's it for this lecture. In the next one, we are going to talk about how we can determine if somebody wins based on you know our tic tac toe, three in a row rules. Right now, we can just check if it's a draw but obviously we want to also check if it's a win. So, that is entirely missing. I'll see you in the next lecture.
Tom is a CTO, senior back-end developer, and systems architect with over twenty years of hands-on development experience in a variety of languages and systems. He has a CS master's degree and has been working with Ethereum and blockchain technologies since 2016.