diff --git a/x-tba/slices/21-bouncing-ball-project/01-draw-the-board/main.go b/x-tba/slices/21-bouncing-ball-project/01-draw-the-board/main.go new file mode 100644 index 0000000..c0980c8 --- /dev/null +++ b/x-tba/slices/21-bouncing-ball-project/01-draw-the-board/main.go @@ -0,0 +1,52 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +import ( + "fmt" +) + +func main() { + const ( + width = 50 + height = 10 + + cellEmpty = ' ' + cellBall = '⚾' + ) + + var cell rune // current cell (for caching) + + // create the board + board := make([][]bool, width) + for row := range board { + board[row] = make([]bool, height) + } + + // draw a smiley + board[12][2] = true + board[16][2] = true + board[14][4] = true + board[10][6] = true + board[18][6] = true + board[12][7] = true + board[14][7] = true + board[16][7] = true + + // print the board directly to the console + for y := range board[0] { + for x := range board { + cell = cellEmpty + if board[x][y] { + cell = cellBall + } + fmt.Print(string(cell), " ") + } + fmt.Println() + } +} diff --git a/x-tba/slices/21-bouncing-ball-project/01-solution-draw-the-board/main.go b/x-tba/slices/21-bouncing-ball-project/01-solution-draw-the-board/main.go deleted file mode 100644 index 8e2897b..0000000 --- a/x-tba/slices/21-bouncing-ball-project/01-solution-draw-the-board/main.go +++ /dev/null @@ -1,47 +0,0 @@ -// For more tutorials: https://blog.learngoprogramming.com -// -// Copyright © 2018 Inanc Gumus -// Learn Go Programming Course -// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ -// - -package main - -import ( - "fmt" -) - -const ( - width = 25 - height = 8 -) - -func main() { - // ------------------------------------------------- - // CREATE THE BOARD - // ------------------------------------------------- - board := make([][]bool, width) - for row := range board { - board[row] = make([]bool, height) - } - - // ------------------------------------------------- - // DRAW THE BOARD - // ------------------------------------------------- - var ( - buf = make([]rune, 0, width*height) - ball rune - ) - - for y := range board[0] { - for x := range board { - ball = '🎱' - if board[x][y] { - ball = '🎾' - } - buf = append(buf, ball, ' ') - } - buf = append(buf, '\n') - } - fmt.Print(string(buf)) -} diff --git a/x-tba/slices/21-bouncing-ball-project/02-add-a-buffer/main.go b/x-tba/slices/21-bouncing-ball-project/02-add-a-buffer/main.go new file mode 100644 index 0000000..2da18e7 --- /dev/null +++ b/x-tba/slices/21-bouncing-ball-project/02-add-a-buffer/main.go @@ -0,0 +1,66 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +import ( + "fmt" +) + +func main() { + const ( + width = 50 + height = 10 + + cellEmpty = ' ' + cellBall = '⚾' + ) + + var cell rune // current cell (for caching) + + // create the board + board := make([][]bool, width) + for row := range board { + board[row] = make([]bool, height) + } + + // create a drawing buffer + buf := make([]rune, 0, width*height) + + // draw a smiley + board[12][2] = true + board[16][2] = true + board[14][4] = true + board[10][6] = true + board[18][6] = true + board[12][7] = true + board[14][7] = true + board[16][7] = true + + // use the loop for measuring the performance difference + for i := 0; i < 1000; i++ { + // rewind the buffer so that the program reuses it + buf = buf[:0] + + // draw the board into the buffer + for y := range board[0] { + for x := range board { + cell = cellEmpty + if board[x][y] { + cell = cellBall + } + // fmt.Print(string(cell), " ") + buf = append(buf, cell, ' ') + } + // fmt.Println() + buf = append(buf, '\n') + } + + // print the buffer + fmt.Print(string(buf)) + } +} diff --git a/x-tba/slices/21-bouncing-ball-project/02-solution-drawing-loop/main.go b/x-tba/slices/21-bouncing-ball-project/02-solution-drawing-loop/main.go deleted file mode 100644 index 1eca517..0000000 --- a/x-tba/slices/21-bouncing-ball-project/02-solution-drawing-loop/main.go +++ /dev/null @@ -1,68 +0,0 @@ -// For more tutorials: https://blog.learngoprogramming.com -// -// Copyright © 2018 Inanc Gumus -// Learn Go Programming Course -// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ -// - -package main - -import ( - "fmt" - "time" - - "github.com/inancgumus/screen" -) - -const ( - width = 25 - height = 8 - - maxFrames = 1200 -) - -func main() { - // ------------------------------------------------- - // CREATE THE BOARD - // ------------------------------------------------- - board := make([][]bool, width) - for row := range board { - board[row] = make([]bool, height) - } - - var X, Y int // ball positions - - screen.Clear() - - for i := 0; i < maxFrames; i++ { - // ------------------------------------------------- - // PUT THE BALL - // ------------------------------------------------- - board[X][Y] = true - - // ------------------------------------------------- - // DRAW THE BOARD - // ------------------------------------------------- - var ( - buf = make([]rune, 0, width*height) - ball rune - ) - - for y := range board[0] { - for x := range board { - ball = '🎱' - if board[x][y] { - ball = '🎾' - } - buf = append(buf, ball, ' ') - } - buf = append(buf, '\n') - } - - screen.MoveTopLeft() - fmt.Print(string(buf)) - - // draw after a while: slows down the animation - time.Sleep(time.Second / 20) - } -} diff --git a/x-tba/slices/21-bouncing-ball-project/03-create-the-drawing-loop/main.go b/x-tba/slices/21-bouncing-ball-project/03-create-the-drawing-loop/main.go new file mode 100644 index 0000000..eabfd32 --- /dev/null +++ b/x-tba/slices/21-bouncing-ball-project/03-create-the-drawing-loop/main.go @@ -0,0 +1,93 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +import ( + "fmt" + "time" + + "github.com/inancgumus/screen" +) + +func main() { + const ( + width = 50 + height = 10 + + cellEmpty = ' ' + cellBall = '⚾' + + maxFrames = 1200 + speed = time.Second / 20 + ) + + var ( + px, py int // ball position + vx, vy = 1, 1 // velocities + + cell rune // current cell (for caching) + ) + + // create the board + board := make([][]bool, width) + for row := range board { + board[row] = make([]bool, height) + } + + // create a drawing buffer + buf := make([]rune, 0, width*height) + + // clear the screen once + screen.Clear() + + for i := 0; i < maxFrames; i++ { + // calculate the next ball position + px += vx + py += vy + + // when the ball hits a border reverse its direction + if px <= 0 || px >= width-1 { + vx *= -1 + } + if py <= 0 || py >= height-1 { + vy *= -1 + } + + // remove the previous ball + for y := range board[0] { + for x := range board { + board[x][y] = false + } + } + + // put the new ball + board[px][py] = true + + // rewind the buffer (allow appending from the beginning) + buf = buf[:0] + + // draw the board into the buffer + for y := range board[0] { + for x := range board { + cell = cellEmpty + if board[x][y] { + cell = cellBall + } + buf = append(buf, cell, ' ') + } + buf = append(buf, '\n') + } + + // print the buffer + screen.MoveTopLeft() + fmt.Print(string(buf)) + + // slow down the animation + time.Sleep(speed) + } +} diff --git a/x-tba/slices/21-bouncing-ball-project/03-solution-final/main.go b/x-tba/slices/21-bouncing-ball-project/03-solution-final/main.go deleted file mode 100644 index 9b5197e..0000000 --- a/x-tba/slices/21-bouncing-ball-project/03-solution-final/main.go +++ /dev/null @@ -1,94 +0,0 @@ -// For more tutorials: https://blog.learngoprogramming.com -// -// Copyright © 2018 Inanc Gumus -// Learn Go Programming Course -// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ -// - -package main - -import ( - "fmt" - "time" - - "github.com/inancgumus/screen" -) - -const ( - width = 25 - height = 8 - - maxFrames = 1200 -) - -func main() { - // ------------------------------------------------- - // CREATE THE BOARD - // ------------------------------------------------- - board := make([][]bool, width) - for row := range board { - board[row] = make([]bool, height) - } - - var ( - X, Y int // ball positions - xVel, yVel = 1, 1 // velocities - ) - - screen.Clear() - - for i := 0; i < maxFrames; i++ { - // ------------------------------------------------- - // CALCULATE THE NEXT BALL POSITION - // ------------------------------------------------- - X += xVel - Y += yVel - - // when the ball hits the borders change its direction - // by changing its velocity - if X <= 0 || X >= width-1 { - xVel *= -1 - } - if Y <= 0 || Y >= height-1 { - yVel *= -1 - } - - // ------------------------------------------------- - // CLEAR THE BOARD AND PUT THE BALL - // ------------------------------------------------- - for y := range board[0] { - for x := range board { - board[x][y] = false - } - } - board[X][Y] = true - - // ------------------------------------------------- - // DRAW THE BOARD - // ------------------------------------------------- - var ( - buf = make([]rune, 0, width*height) - ball rune - ) - - for y := range board[0] { - for x := range board { - ball = '🎱' - if board[x][y] { - ball = '🏈' - } - buf = append(buf, ball, ' ') - } - buf = append(buf, '\n') - } - - // clear the screen and draw the board - screen.MoveTopLeft() - fmt.Print(string(buf)) - - // TODO: in the notes this is at the beginning of the loop - - // draw after a while: slows down the animation - time.Sleep(time.Second / 20) - } -} diff --git a/x-tba/slices/21-bouncing-ball-project/04-finalize/main.go b/x-tba/slices/21-bouncing-ball-project/04-finalize/main.go new file mode 100644 index 0000000..06f9b56 --- /dev/null +++ b/x-tba/slices/21-bouncing-ball-project/04-finalize/main.go @@ -0,0 +1,100 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +import ( + "fmt" + "os" + "time" + + "github.com/inancgumus/screen" + "golang.org/x/crypto/ssh/terminal" +) + +func main() { + const ( + cellEmpty = ' ' + cellBall = '⚾' + + maxFrames = 1200 + speed = time.Second / 20 + ) + + // get the width and height of the terminal dynamically *** + width, height, err := terminal.GetSize(int(os.Stdout.Fd())) + if err != nil { + fmt.Println("cannot get width and height", err) + return + } + width /= 2 // our emoji is 2 chars wide + + var ( + px, py int // ball position + ppx, ppy int // previous ball position *** + vx, vy = 1, 1 // velocities + + cell rune // current cell (for caching) + ) + + // create the board + board := make([][]bool, width) + for row := range board { + board[row] = make([]bool, height) + } + + // create a drawing buffer + buf := make([]rune, 0, width*height) + + // clear the screen once + screen.Clear() + + for i := 0; i < maxFrames; i++ { + // calculate the next ball position + px += vx + py += vy + + // when the ball hits a border reverse its direction + if px <= 0 || px >= width-1 { + vx *= -1 + } + if py <= 0 || py >= height-1 { + vy *= -1 + } + + // check whether the ball goes beyond the borders + if !(px >= width || py >= height) { + // remove the previous ball and put the new ball + board[px][py], board[ppx][ppy] = true, false + + // save the previous positions + ppx, ppy = px, py + } + + // rewind the buffer (allow appending from the beginning) + buf = buf[:0] + + // draw the board into the buffer + for y := range board[0] { + for x := range board { + cell = cellEmpty + if board[x][y] { + cell = cellBall + } + buf = append(buf, cell, ' ') + } + buf = append(buf, '\n') + } + + // print the buffer + screen.MoveTopLeft() + fmt.Print(string(buf)) + + // slow down the animation + time.Sleep(speed) + } +} diff --git a/x-tba/slices/21-bouncing-ball-project/04-solution-final-optimize/main.go b/x-tba/slices/21-bouncing-ball-project/04-solution-final-optimize/main.go deleted file mode 100644 index 5d1c854..0000000 --- a/x-tba/slices/21-bouncing-ball-project/04-solution-final-optimize/main.go +++ /dev/null @@ -1,92 +0,0 @@ -// For more tutorials: https://blog.learngoprogramming.com -// -// Copyright © 2018 Inanc Gumus -// Learn Go Programming Course -// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ -// - -package main - -import ( - "fmt" - "time" - - "github.com/inancgumus/screen" -) - -const ( - width = 25 - height = 8 - - maxFrames = 1200 -) - -func main() { - // ------------------------------------------------- - // CREATE THE BOARD - // ------------------------------------------------- - board := make([][]bool, width) - for row := range board { - board[row] = make([]bool, height) - } - - var ( - X, Y int // ball positions - pX, pY int // previous ball positions (for clearing) - xVel, yVel = 1, 1 // velocities - - buf [width * height]rune // drawing buffer - ) - - screen.Clear() - - for i := 0; i < maxFrames; i++ { - // ------------------------------------------------- - // CALCULATE THE NEXT BALL POSITION - // ------------------------------------------------- - X += xVel - Y += yVel - - // when the ball hits the borders change its direction - // by changing its velocity - if X <= 0 || X >= width-1 { - xVel *= -1 - } - if Y <= 0 || Y >= height-1 { - yVel *= -1 - } - - // ------------------------------------------------- - // PUT THE BALL AND CLEAR THE BOARD - // ------------------------------------------------- - board[X][Y] = true - board[pX][pY] = false - pX, pY = X, Y - - // ------------------------------------------------- - // DRAW THE BOARD - // ------------------------------------------------- - var ball rune - - // rewind the buffer - bufs := buf[:] - - for y := range board[0] { - for x := range board { - ball = ' ' - if board[x][y] { - ball = '🎾' - } - bufs = append(bufs, ball, ' ') - } - bufs = append(bufs, '\n') - } - - // clear the screen and draw the board - screen.MoveTopLeft() - fmt.Print(string(bufs)) - - // draw after a while: slows down the animation - time.Sleep(time.Second / 20) - } -} diff --git a/x-tba/slices/21-bouncing-ball-project/README.md b/x-tba/slices/21-bouncing-ball-project/README.md index 90bdc42..83f0ebd 100644 --- a/x-tba/slices/21-bouncing-ball-project/README.md +++ b/x-tba/slices/21-bouncing-ball-project/README.md @@ -1,18 +1,14 @@ -# Bouncing Ball Challenge +# Bouncing Ball Challenge Tips -This document contains what you need to do to create the bouncing ball animation.You're going to learn a great deal of knowledge about slices and you'll gain a good experience while doing this. +Use the following tips only when you get stuck. This document isn't in a particular order, please do not follow it like so. -➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ +➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ -## Declare the constants -* The width and the height of the board. +## Ball position and velocity -* It can be anything, just experiment. +You can use velocity to change the ball's speed and position. In my example, the speed is constant, so I always use unit value: 1. -➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ - -## Declare the ball positions and velocity: -The ball should be in a known position on your board, and it should have a velocity. +On each loop step: Add velocities to ball's position. This will make the ball move. * **Velocity means: Speed and Direction** @@ -21,146 +17,58 @@ The ball should be in a known position on your board, and it should have a veloc * Y velocity = 1 -> _ball moves down_ * Y velocity = -1 -> _ball moves up_ -* Declare variables for the ball's positions: `X` and `Y` +* **For more information on graphics and velocity:** + * Youtube: Crash Course: 2D Graphics + -> https://www.youtube.com/watch?v=7Jr0SFMQ4Rs&t=529 -* Declare variables for the ball's velocity: `xVelocity` and `yVelocity` - -* On each step: Add velocities to ball's position. This will make the ball move. + * Youtube: Crash Course: Velocity + -> https://www.youtube.com/watch?v=ZM8ECpBuQYE ➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ -## 🎾 CREATE THE BOARD +## CREATE THE BOARD -You can use a multi-dimensional bool slice to create the board. Each element in the slice corresponds to whether the ball is in that element (or position) or not. - -* Use the `make` function to initialize your board. - - * Remember: Zero value for a slice is `nil`. - * So, you need to initialize each sub-slice of the board slice. - -* You can use `[][]bool` for your board. - - * It's because, when you set one of the elements to true, then you'd know that the ball is in that position. - - * *EXAMPLE:* - ``` - false false false false - false true -+ false false - false false | false false - v - the ball is here - board[1][1] is true - ``` +I use `[][]bool` for the board but you can use anything you like. For example, you can directly use `[][]rune` or `[]rune`. Experiment with them and decide which one is the best for you. ➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ -## 🎾 CLEAR THE SCREEN +## CLEAR THE SCREEN -* Before the loop, clear the screen once by using the screen package. +* Before the loop, clear the screen once by using my screen package. + +* After each loop step, move the cursor to the top-left position by using the screen package. So that you can draw the animation frame all over again in the same position. * It's [here](https://github.com/inancgumus/screen). * Its documentation is [here](https://godoc.org/github.com/inancgumus/screen). - -➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ - -## 🎾 DRAWING LOOP -This is the main loop that will draw the board and the ball to the screen continuously. - -* Create the loop - -* On each step of the loop, you're going to: - * Clear the board - * Calculate the next ball position - * Draw the board and the balls - -Explanations for these steps follow... +* You can find more information about it in the Retro Clock project section. ➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ -## 🎾 CLEAR THE BOARD -At the beginning your board should not contain the ball. +## DRAW THE BOARD +Instead of drawing the board and the ball to the screen everytime, you will fill a buffer, and when you complete, you can draw the board and the ball once by printing the buffer. -* So, set all the inner slices of the board slice to `false`. - -➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ - -## 🎾 CALCULATE THE NEXT BALL POSITION -You need to calculate the ball's next position on the board. - -* Add the velocities to the ball's current position: - - * `X += xVelocity` - * `Y += yVelocity` - -* When the ball hits the borders of the board change its direction. - - * **HINT:** You can multiply any velocity by `-1` to change its direction. - -* Set the ball's position on the board. - - * You will use this information when drawing the board. - -➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ - -## 🎾 DRAW THE BOARD -Instead of drawing the board and the ball to the screen everytime, you will fill a buffer, and when you complete, you will draw the whole board and the ball once by printing the buffer that you filled. +I use a `[]rune` buffer because `rune` can store an emoji character, and I use a slice of runes because I want to draw empty cells and a ball in the end. * Make a large enough rune slice named `buf` using the `make` function. -* **HINT:** `width * height` will give you a large enough buffer. + * **HINT:** `width * height` will give you a large enough buffer. -* **TIP:** You could also use `string` concatenation but it would be inefficient. + * **TIP:** You could also use `string` concatenation to draw into a `string` buffer but it would be inefficient. -➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ + * You will find more information about bytes and runes in the strings section. -## FILL YOUR BUFFER: +```go +// TIP for converting the buffer +var buffer []rune -* Filling your buffer: - - * Loop for the height of the board (_described below_). - - * Then in a nested loop (_described below_), loop for the width of the board. - -➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ - -## NESTEP LOOP: WIDTH LOOP -* On each step of the nested loop, do this: - - * Check whether the ball is in the x, y positions. - * You need to check for it using your board slice. - - * If so, `append` this tennis ball '🎾' to the buffer. - * If not, `append` this pool ball '🎱' to the buffer. - -➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ - -## NESTEP LOOP: HEIGHT LOOP - -* After the nested loop (but in the height loop): - -* `append` the newline character to the buffer: `'\n'` - * This will allow you to print the next row to the next line. - -➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ - -## 🎾 MOVE THE CURSOR - -* After the loop, move the cursor to the top-left position by using the screen package. - -➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ - -## PRINT THE BOARD (USING THE BUFFER): - -* Do not forget converting it to `string`. Because your buffer is `[]rune`, like so: - - `fmt.Print(string(buf))` - -* You'll learn the details about rune and strings later. +// For printing, you can convert a rune slice to a string like so: +str := string(buffer) +``` ➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ ## SLOW DOWN THE SPEED -And lastly, call the `time.Sleep` function to slow down the speed of the loop, so you can see the ball :) Like so: +Call the `time.Sleep` function to slow down the speed of the loop a little bit, so you can see the ball :) - `time.Sleep(time.Millisecond * 60)` + `time.Sleep(time.Second / 20)`