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 this lecture, I want to guide you through the channel files here. And I've just removed everything that could be in our way. So, when you have a look here on the left side in the browser, and I really just have these three files left and you can create those with the + button. And basically, just start with fresh files. I see, we also have not discussed the import statements yet, which I'm going to discuss in a second with you. They are not really hard. So, just bear with me here. And when I click through these files, then I have almost empty files. Basically, my GameManager and my ThreeInARow solidity files are empty. And in my Highscoremanager there is a little bit and I'm going to guide you through these files now. And, for reference, you can also find these files in our course repository, in the folder S02, the game in solidity, and then, C01, basic interfaces. And they are the same three files. If you want to start with me with these files or you can write them manually, it will not be too hard if you just keep following this video. If we go with the 'GameManager', then our GameManager contract is basically empty and it's importing two other files. And the import statements work as follows. I have the 'import' keyword and then, in my case, I have- I am not importing everything from this file, I'm importing a specific smart contract from this file. Just in case there are more smart contracts in one file, I can tell solidity to import one specific smart contract and that works. If you come from a Node.JS where challenge would very similar. So, you have import and then you have curly brackets and inside the curly brackets you say which contract you want to import from which file, and that one is the ThreeInARow solidity file. And, if you go into the ThreeInARow solidity file, then we see I have imported back the GameManager because I need it later and I have one empty contract, ThreeInARow, and that one will be imported here in the GameManager into this ThreeInARow variable. Same with the Highscoremanager, I'll importing it from the file Highscoremanager solidity. And inside my Highscoremanager, there is no other imports. Just inside the Highscoremanager, I have to reference back to the GameManager because I don't want external people to add to the high score. I want just the GameManager to be able to add to the high score. So, in our case, when the GameManager will later create a Highscoremanager, then it will give it the constructor the address of the GameManager. And we had this with the owned, or the admin variable before, where we save a specific address during deployment to the storage variable. And then, we have a modifier that only lets a specific person or a specific address interact with our smart contract then we are going to use this modifier later in order to add to the high score list or to the high score mapping. We have a couple of functions that our smart contract has to do and this is something that I want to add now into our ThreeInARow game. And then, in the next lectures, I want to add that logic to these functions. So, let me lay out the interface now before we add the logic to these functions. First of all, in our ThreeInARow game, we have to know the GameManager, the address of the GameManager, where we can add later on where we can give back the winner address. So, we are going to have a 'gameManager' and we are going to have a 'constructor', obviously. And, in the 'constructor(address_addrGameManager)', which when our game is started, the GameManager will give this constructor its own address. And we have an 'address payable _player1' who want to start this game. And, in general, our constructor will be payable. And here, we can already say, the 'gameManager' is, in capital letters, the 'GameManager(_addrGameManager)'. So, in this case, we are getting the address of the GameManager and we can tell solidity, this is going to be the interface of the smart contract that is running under this address. And from there on, we can just simply access the functions of the GameManager without needing to encode them ourselves manually. Solidity will take care of that, more on that later, so just bear with me for now. We're having a lot '//more functionality here - later!'. Then, we obviously have another function called 'joinGame'. And this is also a 'public payable' function because our player2 has to join the game and also has to pay 0.1 Ether or a specific amount of Ether. Then, we have one function to get the board. And this is just a view function because we're going to give back the board and as you've seen before, it's going to have a three times three array. In our case, '//here we return the board'. '//here player2 joins the game'. Then, we have one 'function setStone', where the player who's turn it is can set the stone. And in our case, this can be a 'uint8' because we just have a three times three board so it can maximum be from zero to two. So, a uint8, which ranges from zero to two and a 55, is more than enough and we have two coordinates: x and y. This is a 'public' function '//we set the stone here'. Then, we have one function for setting the winner and that is going to be a private function because we could do this in set the stone. So, we can just set the stone and inside the setStone function, when the stone is set, we can see if the current player's turn if there is either the horizontals are set, the verticals are set, or the diagonal's are set. But, I usually do this in an outside function to keep the function smaller, the function footprint smaller. So, I have another 'function setWinner(address payable _player)'. And it's going to be a private function because it should not be accessible from outside. And, in case we have no winner and the whole board is already set with stones, then we have a 'function setDraw'. We don't have a winner so we don't have private. So, we don't have a player here who gets the money. And, in case we have a winner and there is some failure during the transfer of the Ether, then we also have a 'withdrawWin' function. And we need more and more and that is more of an extra that you will figure out down the road when you have multiple parties accessing your smart contracts. Then, what happens if one of the people, one of the two people is not reacting anymore? So, instead of having Ether getting stuck in the smart contract, you really want to have something like an emergencyCashout functionality and you give the one player the ability to set a stone within five or 10 minutes. And if he's not setting the stone, then the game ended and both parties can withdraw their money again if they want to. So, just that the money is not stuck there. So, we're going to have one 'function emergencyCashout'. That's going to be the last thing that we're going to implement. Once our GameManager is called with creating a new game, then it will create a new instance of a ThreeInARow game and give this ThreeInARow game its own address. But, our GameManager doesn't have any functions yet. So, we have to give our GameManager the right functions. First of all, our GameManager should create a new Highscoremanager when the GameManager is deployed. And we can... So, we have one Highscoremanager here inside the GameManager and that is one specific address once the GameManager is deployed. So, it's not going to be every game has its own Highscoremanager. It's going to be, for all players, the entry point will be the GameManager and the GameManager itself holds an instance of the Highscoremanager and only the GameManager can access this Highscoremanager. And, I'm going to call this 'highscoremanager', in smaller letters. And here, we have a 'constructor() public'. And the 'highscoremanager = new Highscoremanager'. And it's giving the Highscoremanager its own address. So, if you look into the Highscoremanager, there's a constructor and it gets the address of the GameManager. And then, it adds the GameManager's address to its own storage variable. So, we have one highscoremanager that's fixed there. Now, the constructor will be called once and once only during deployment of the smart contracts. And then, we just have a few more functions that is a 'function enterWinner' in case the smart contract, the ThreeInARow smart contract, has a winner decided. And that can only be done if the smart contract was deployed by the GameManager, so we're going to add a modifier later on. We have a 'function getTop10'. That's a 'public view' function and 'returns' something that is yet to be determined so let's keep this open now. And then, we obviously we 'function startNewGame'. And obviously, if you want to start a new game, you have to pay the 0.1 Ether. So, it must be a payable function. I know this sounds a lot for now and there is one thing missing in the highscoremanager which, if you think about it, then this top 10, getting the top 10 players should actually be delivered by the Highscoremanager. And so here, in the Highscoremanager, there is at least one function missing. Actually, there are two functions missing. So, let me add one function and it's up to you then in a little challenge how to figure out which function is missing. It's not going to be too hard if you follow along. Because there are so many functions, it might take a little bit of brain power, but it should be fairly easy to figure out. First of all, we have one 'function addWin' for a specific address. And this one will get a modifier from only the GameManager can add this win. So, the idea is when the ThreeInARow game, the setWinner is called, the setWinner will call back to the GameManager and tell the GameManager enter a winner, and the GameManager enterWinner will determine if the address that calls enterWinner is from a ThreeInARow game which was started by a player. And this one in turn will call the highscoremanager and addWin functionality, which it can, because the GameManager is allowed to do so. And the other function will be used from the GameManager to get the top 10 players. So, it's going to be an interface to get the top 10 players. And I'll give you in our game, or in our programming challenge, to add this last function, which is missing in the highscoremanager. And if you want to do the challenge, then pause the video now. If you don't want to do the challenge, it's also okay. I'm going to show you in a second how it's done. Alright. If we call the high score of the GameManager top 10, then the GameManager should actually just cascade down to the Highscoremanager and called there a 'function getTop10' players. And, in our case, I'm going to call this 'function getTop10() public view' function and it will return something, which is yet to be determined. And GameManager will then call 'return highscoremanager.getTop10' but we don't have decided yet, but it will return. So, I'm going to remove this and that's it for this lecture. In the next lecture, and I know this was a lot now, but bear with me. We're going to fill in the logic step-by-step. So, I guess it will make a lot of sense once you've seen the first few bits and pieces of the logic that is coming in and it will all come together at the end of this section. Alright, 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.