1 00:00:01,070 --> 00:00:03,880 Let's start building our Battleship game engine. 2 00:00:03,880 --> 00:00:07,070 In Battleship, there are two grids, one for each player. 3 00:00:07,070 --> 00:00:10,904 Each player secretly places their set of five ships on the grid and 4 00:00:10,904 --> 00:00:14,610 then players take turns guessing where the ships are located. 5 00:00:14,610 --> 00:00:19,457 If you correctly guess that a ship is located at a position on your opponent's 6 00:00:19,457 --> 00:00:22,380 grid, your opponent marks that spot as a hit. 7 00:00:22,380 --> 00:00:27,050 If a ship has been hit at every unit of its length the ship is sunk. 8 00:00:27,050 --> 00:00:30,935 The first player to successfully sink all of their opponent ships is the winner. 9 00:00:30,935 --> 00:00:33,740 [APPLAUSE] >> So 10 00:00:33,740 --> 00:00:37,130 we'll need to write code that handles the current player and winner, 11 00:00:37,130 --> 00:00:42,160 the number of ships, their position, and their status as active or sunk. 12 00:00:42,160 --> 00:00:43,800 I know that sounds like a lot, but 13 00:00:43,800 --> 00:00:46,870 just like our title case function from the end of the previous stage, 14 00:00:46,870 --> 00:00:51,210 we'll use BDD to break all this down into small chunks by writing unit tests. 15 00:00:52,220 --> 00:00:55,760 So let's start with the smallest part of the game, the ships. 16 00:00:55,760 --> 00:01:01,290 Inside the test directory, I'll create a new test file to contain all of the tests 17 00:01:01,290 --> 00:01:06,810 about ship features, I'll call the file ship_test.js. 18 00:01:06,810 --> 00:01:10,200 I like adding the _test into my test file names so 19 00:01:10,200 --> 00:01:14,831 that I can find them more easily later, and it's a bit more obvious 20 00:01:14,831 --> 00:01:19,150 what my error reports are trying to tell me in the test output. 21 00:01:19,150 --> 00:01:22,529 Okay, so let's import expect from Chai first. 22 00:01:29,000 --> 00:01:33,145 So at this point, I can already guess at a few functions we might need. 23 00:01:33,145 --> 00:01:36,850 We'll probably need some kind of function to tell us whether a ship is located 24 00:01:36,850 --> 00:01:38,250 at certain coordinates. 25 00:01:38,250 --> 00:01:42,841 So for example, when a player puts a ship on the board, or when a player tries to 26 00:01:42,841 --> 00:01:48,010 attack, we need to check whether a ship is positioned at the location they're naming. 27 00:01:48,010 --> 00:01:53,204 So let's set up a new suite for this function using describe, and 28 00:01:53,204 --> 00:01:59,740 since the function is just going to check for a ship, I'll name it checkForShip. 29 00:02:08,480 --> 00:02:11,589 Remember, Mocha's representation of a test suite, 30 00:02:11,589 --> 00:02:14,300 the describe function, takes two arguments. 31 00:02:14,300 --> 00:02:19,650 A string describing all the tests inside, and a function to wrap them all together. 32 00:02:21,830 --> 00:02:24,520 So now, let's import the checkForShip function 33 00:02:24,520 --> 00:02:26,620 here at the top of our describe function. 34 00:02:26,620 --> 00:02:29,690 Because all the tests in this suite will need access to it. 35 00:02:42,377 --> 00:02:45,108 Remember this suite will just break at first, 36 00:02:45,108 --> 00:02:49,570 because the function we're referring to here doesn't exist at all. 37 00:02:49,570 --> 00:02:51,550 This is just an outline for now. 38 00:02:51,550 --> 00:02:55,500 So I'm making an educated guess that we'll want to special directory named 39 00:02:55,500 --> 00:02:59,890 game logic, to contain all of our game logic functions, and 40 00:02:59,890 --> 00:03:03,570 that will keep all the ship centered functions in a file of their own. 41 00:03:03,570 --> 00:03:06,370 So what should check for ship actually do? 42 00:03:06,370 --> 00:03:08,642 Well, we'll probably give it a coordinate, 43 00:03:08,642 --> 00:03:12,150 if there's no ship there I think it should probably just return false. 44 00:03:12,150 --> 00:03:16,320 We might change that later if our functions work differently than we expect. 45 00:03:16,320 --> 00:03:19,920 Maybe in the end we need it to return more information than just false. 46 00:03:19,920 --> 00:03:22,870 But for now, that makes sense, so we'll start there. 47 00:03:22,870 --> 00:03:27,210 In Mocha spec looks very similar to a suite. 48 00:03:27,210 --> 00:03:32,600 It takes two arguments, a string describing our desired behavior, and 49 00:03:32,600 --> 00:03:37,930 a function that wraps all of the specs, expectations, and logic up together. 50 00:03:40,740 --> 00:03:45,680 Each spec should be responsible for just one aspect of the function's behavior. 51 00:03:45,680 --> 00:03:50,851 So in order to describe that particular behavior 52 00:03:50,851 --> 00:03:55,768 of checkForShip, I'll name this test spec, 53 00:03:55,768 --> 00:04:01,710 should correctly report no ship at a given coordinate. 54 00:04:04,600 --> 00:04:09,540 That's really informative, it communicates what the spec is designed to prove. 55 00:04:09,540 --> 00:04:12,860 And also, some useful information about the nature of the function. 56 00:04:12,860 --> 00:04:15,260 The function takes a coordinate of some kind. 57 00:04:15,260 --> 00:04:18,880 But I just realize that this isn't enough information. 58 00:04:18,880 --> 00:04:22,690 Because there are two boards in Battleship, yours and your opponents. 59 00:04:22,690 --> 00:04:26,040 So check for ship will also need to accept a player so 60 00:04:26,040 --> 00:04:29,860 that it knows which board is checking for the given coordinates. 61 00:04:29,860 --> 00:04:34,340 So I'll change this to should correctly report no ship at a given 62 00:04:34,340 --> 00:04:35,560 player's coordinate. 63 00:04:38,980 --> 00:04:43,655 It's best to keep a tight feedback loop in BDD, since I've written a lot of 64 00:04:43,655 --> 00:04:47,900 test code already, let's run our test and see what happens. 65 00:04:47,900 --> 00:04:51,632 So in the console, I'll run npm tests. 66 00:04:54,189 --> 00:04:56,489 And I see that I get an error. 67 00:04:56,489 --> 00:05:00,189 It says error cannot find module. 68 00:05:00,189 --> 00:05:02,229 Game logic ship methods. 69 00:05:02,229 --> 00:05:05,760 Well, remember the ship methods file isn't even really it. 70 00:05:05,760 --> 00:05:06,840 So let's go fix that. 71 00:05:13,450 --> 00:05:20,360 So I'll create a new folder named game_logic. 72 00:05:20,360 --> 00:05:26,267 Then, inside this new folder, I'll create the ship_methods.js file. 73 00:05:35,831 --> 00:05:40,050 I'll go ahead and keep my ship test file open to compare against, and 74 00:05:40,050 --> 00:05:43,290 just copy over any code I'll need later. 75 00:05:43,290 --> 00:05:49,325 So I'll start by simply defining the checkForShip 76 00:05:49,325 --> 00:05:54,950 function inside the ship_methods.js file. 77 00:05:58,190 --> 00:06:05,765 Then I'll export it by typing module.exports.checkForShip, 78 00:06:06,912 --> 00:06:09,872 Equals checkforShip. 79 00:06:12,011 --> 00:06:15,511 All right, so that should be enough to get over the import error. 80 00:06:15,511 --> 00:06:20,210 So now if I go over to the console and npm test, 81 00:06:20,210 --> 00:06:25,890 cool, it shows that we're passing our new test. 82 00:06:25,890 --> 00:06:27,346 But we know that's a trick, 83 00:06:27,346 --> 00:06:30,880 because that test doesn't actually contain any expectations yet. 84 00:06:30,880 --> 00:06:33,528 But at least we know where files are connected properly and 85 00:06:33,528 --> 00:06:35,930 that we set up our suite and spec correctly. 86 00:06:35,930 --> 00:06:37,140 Those sanity checks are helpful. 87 00:06:41,350 --> 00:06:47,200 So let's write an expectation for this spec now in the ship test js file. 88 00:06:47,200 --> 00:06:50,570 For this spec to be meaningful, it has to call the function we're testing. 89 00:06:50,570 --> 00:06:54,660 So it's time to make some guesses about how this function actually works. 90 00:06:54,660 --> 00:06:58,366 We need to guess what kind of parameters check for ship will accept, 91 00:06:58,366 --> 00:07:00,001 in order to use it in our spec. 92 00:07:00,001 --> 00:07:04,492 Now we could represent our coordinates in lots of ways, a string of two numbers, 93 00:07:04,492 --> 00:07:06,550 an array, an object and so on. 94 00:07:06,550 --> 00:07:09,760 So I like the idea of arrays since they come with lots of 95 00:07:09,760 --> 00:07:12,990 useful methods built in that we may want to use. 96 00:07:12,990 --> 00:07:16,240 Again, I'm just making educated guesses right now. 97 00:07:16,240 --> 00:07:17,690 When I start writing the game, 98 00:07:17,690 --> 00:07:21,900 the implementation details might challenge our starting assumptions. 99 00:07:21,900 --> 00:07:23,970 So it might be easier to come back and 100 00:07:23,970 --> 00:07:28,930 adapt our tests than it would be to make our functions match our expectations. 101 00:07:28,930 --> 00:07:32,490 So the unit test we're writing just gives me a starting point that 102 00:07:32,490 --> 00:07:33,180 makes sense to me. 103 00:07:34,700 --> 00:07:42,225 Inside the spec, I'll write our expectation, to call checkForShip. 104 00:07:44,401 --> 00:07:45,807 With a player and 105 00:07:45,807 --> 00:07:52,610 an arbitrary location where none of that player's ships are located. 106 00:07:52,610 --> 00:07:54,500 So let's say 9,9. 107 00:07:58,521 --> 00:08:02,830 And since there are no ships there check for ship should return false. 108 00:08:02,830 --> 00:08:06,909 So let's add to.be.false. 109 00:08:06,909 --> 00:08:12,230 Okay, so now if we run npm test in the console, 110 00:08:13,670 --> 00:08:17,080 our test shows that our spec isn't quite ready. 111 00:08:17,080 --> 00:08:20,720 We're trying to pass checkForShip arguments that we haven't defined yet. 112 00:08:22,370 --> 00:08:26,665 The hardest part about BDD is deciding how the function we haven't written yet 113 00:08:26,665 --> 00:08:27,909 might actually work. 114 00:08:27,909 --> 00:08:32,666 We have to make an example of the kind of argument our function might expect, so 115 00:08:32,666 --> 00:08:35,131 that we can use it in our spec correctly. 116 00:08:35,131 --> 00:08:39,483 This is a lot like how we made up an imaginary array of people objects 117 00:08:39,483 --> 00:08:43,230 to test our gather names of function, back in stage one. 118 00:08:43,230 --> 00:08:46,930 The difference is that we don't have the real function in hand yet. 119 00:08:46,930 --> 00:08:50,589 And we're not adding that cruft to our real code files. 120 00:08:50,589 --> 00:08:55,179 These guess work function calls and data structures are just going to live 121 00:08:55,179 --> 00:08:59,120 here in our test spec isolated from all of our production code. 122 00:08:59,120 --> 00:09:03,179 The important thing is to focus on the general behavior of the function we're 123 00:09:03,179 --> 00:09:06,750 testing and not get bogged down in the implementation details yet. 124 00:09:06,750 --> 00:09:10,699 For now, I'll just invent something that will get the test up and running and 125 00:09:10,699 --> 00:09:11,750 kind of make sense. 126 00:09:11,750 --> 00:09:16,009 And if I need to revise it later, then that's okay. 127 00:09:16,009 --> 00:09:19,715 At this point, I'm guessing it makes sense for players to be represented as objects, 128 00:09:19,715 --> 00:09:22,840 since they keep track of different kinds of information. 129 00:09:22,840 --> 00:09:28,180 So right above our expectation, I'll create a player object. 130 00:09:30,530 --> 00:09:33,350 Each player will have a set of ships, so 131 00:09:33,350 --> 00:09:37,290 I'll set an array of ships as a property on the object. 132 00:09:38,595 --> 00:09:42,970 Again,there are lots of ways to represent sets, but 133 00:09:42,970 --> 00:09:45,560 I'll start with an array for convenience. 134 00:09:45,560 --> 00:09:50,200 Since this test only needs us to check one ship, that's all I'll add. 135 00:09:50,200 --> 00:09:53,769 We'll store each ship's location as a property. 136 00:09:53,769 --> 00:10:00,006 So I'll add a locations array to the ship and set it up with just one coordinate. 137 00:10:00,006 --> 00:10:03,186 Let's say 0,0. 138 00:10:04,346 --> 00:10:08,500 At this point we're assuming a lot about the structure of players, ships, and 139 00:10:08,500 --> 00:10:10,800 how we keep track of their location. 140 00:10:10,800 --> 00:10:12,990 It's a lot of guesswork up front. 141 00:10:12,990 --> 00:10:15,220 But even without writing tests, 142 00:10:15,220 --> 00:10:18,230 programmers still do a lot of guesswork as a program. 143 00:10:18,230 --> 00:10:21,594 It's rare that you know exactly how every part of a program works when 144 00:10:21,594 --> 00:10:23,401 you first sit down at your computer. 145 00:10:23,401 --> 00:10:27,934 So, it might feel a little strange to write all of these tests before you start 146 00:10:27,934 --> 00:10:30,140 writing your program. 147 00:10:30,140 --> 00:10:33,646 Just keep in mind that most people also feel awkward when they 148 00:10:33,646 --> 00:10:37,161 first start using outlines for their essays and blog posts. 149 00:10:37,161 --> 00:10:39,319 If you feel overwhelmed by the set up, 150 00:10:39,319 --> 00:10:43,650 just remember that you can always work in a tighter feedback loop. 151 00:10:43,650 --> 00:10:48,363 For example, you could have started this set up work by only writing an object 152 00:10:48,363 --> 00:10:52,510 name player to make that reference error go away in our test output. 153 00:10:52,510 --> 00:10:54,990 Then you would start with another small step and 154 00:10:54,990 --> 00:10:58,220 another until you had something that looked similar to this. 155 00:10:58,220 --> 00:11:04,260 Okay, so now I have a player and it has one ship with one location. 156 00:11:04,260 --> 00:11:09,333 And if I check for a ship at 9,9 checkForShip would return false, 157 00:11:09,333 --> 00:11:13,427 because the ship that player has is located at 0,0. 158 00:11:13,427 --> 00:11:16,806 They don't have any ships yet located at 9,9. 159 00:11:23,270 --> 00:11:28,308 Running the test in the console gives us a new assertion error, 160 00:11:28,308 --> 00:11:32,530 it says expected, undefined, to be false. 161 00:11:32,530 --> 00:11:35,390 Great, we get undefined because our real check for 162 00:11:35,390 --> 00:11:37,630 ship function doesn't return anything yet. 163 00:11:37,630 --> 00:11:39,055 So it's time to start writing our function. 164 00:11:47,320 --> 00:11:51,381 So back inside shipmethods.js, the check for 165 00:11:51,381 --> 00:11:57,180 ship function will accept the parameters player and coordinates. 166 00:11:59,710 --> 00:12:06,341 First, I'll declare two variables inside the function, shipPresent and ship. 167 00:12:09,083 --> 00:12:13,774 Now I know I'm gonna have some kind of conditional block, because I only want to 168 00:12:13,774 --> 00:12:17,640 return false if the player has no ships at the given coordinates. 169 00:12:17,640 --> 00:12:21,096 I know I'll need to loop through all the ships in the ships array and 170 00:12:21,096 --> 00:12:25,121 check each of their coordinates against the one we're giving the function. 171 00:12:25,121 --> 00:12:29,860 So I'll just put this condition inside a loop over all the player ships. 172 00:12:43,361 --> 00:12:47,684 Next I'll save the current ship within the loop to make things easier to read. 173 00:12:54,789 --> 00:12:59,108 Finally, I'll filter the current ship's location array down by comparing each 174 00:12:59,108 --> 00:13:00,870 value to the given coordinates. 175 00:13:18,479 --> 00:13:23,266 So again, this will filter the current ship's location array for 176 00:13:23,266 --> 00:13:29,300 matches against the given coordinate, both the x and y numbers should match. 177 00:13:29,300 --> 00:13:32,320 So this is going to return on an array containing 178 00:13:32,320 --> 00:13:34,300 only the coordinates that are a match. 179 00:13:34,300 --> 00:13:36,990 And if nothing is a match, the array will be empty. 180 00:13:59,690 --> 00:14:02,910 So it will be a coordinate only if there was a match. 181 00:14:02,910 --> 00:14:05,960 The first member of an empty array is undefined, 182 00:14:05,960 --> 00:14:08,250 which will fail conditional and it’s perfect. 183 00:14:08,250 --> 00:14:12,111 It means we only need to save the first element of the filtered array and 184 00:14:12,111 --> 00:14:13,950 use that as a conditional check. 185 00:14:13,950 --> 00:14:17,778 So if the array is empty that means there weren't any matches to our given 186 00:14:17,778 --> 00:14:19,740 coordinates and no ship is present. 187 00:14:21,000 --> 00:14:26,059 Right below, we'll check whether a ship is present at a given 188 00:14:26,059 --> 00:14:31,130 coordinate by saying, if no ship is present, return false. 189 00:14:37,850 --> 00:14:41,121 All right, so now we go over to the console and 190 00:14:41,121 --> 00:14:43,370 run npm test to see if it works. 191 00:14:46,020 --> 00:14:48,790 Great we get all green checks for our tests.