From cc32a31c1b10251982515993fcc3165240ab5d14 Mon Sep 17 00:00:00 2001 From: Inanc Gumus Date: Thu, 14 Mar 2019 21:16:15 +0300 Subject: [PATCH] add: bouncing ball exercise #2 --- 18-bouncing-ball-project/README.md | 40 ++---- .../exercises/01-width-and-height/main.go | 5 +- .../exercises/02-previous-positions/main.go | 126 ++++++++++++++++++ .../02-previous-positions/solution/main.go | 101 ++++++++++++++ 18-bouncing-ball-project/exercises/README.md | 4 + 5 files changed, 249 insertions(+), 27 deletions(-) create mode 100644 18-bouncing-ball-project/exercises/02-previous-positions/main.go create mode 100644 18-bouncing-ball-project/exercises/02-previous-positions/solution/main.go diff --git a/18-bouncing-ball-project/README.md b/18-bouncing-ball-project/README.md index 83f0ebd..c34e12a 100644 --- a/18-bouncing-ball-project/README.md +++ b/18-bouncing-ball-project/README.md @@ -2,13 +2,11 @@ Use the following tips only when you get stuck. This document isn't in a particular order, please do not follow it like so. -➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ - -## Ball position and velocity +## CALCULATING THE VELOCITY 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. -On each loop step: Add velocities to ball's position. This will make the ball move. +* On each loop step: Add velocities to ball's position. This will make the ball move. * **Velocity means: Speed and Direction** @@ -18,36 +16,26 @@ On each loop step: Add velocities to ball's position. This will make the ball mo * Y velocity = -1 -> _ball moves up_ * **For more information on graphics and velocity:** - * Youtube: Crash Course: 2D Graphics - -> https://www.youtube.com/watch?v=7Jr0SFMQ4Rs&t=529 - * Youtube: Crash Course: Velocity - -> https://www.youtube.com/watch?v=ZM8ECpBuQYE + * [Youtube: Crash Course: 2D Graphics](https://www.youtube.com/watch?v=7Jr0SFMQ4Rs&t=529) -➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ + * [Youtube: Crash Course: Velocity](https://www.youtube.com/watch?v=ZM8ECpBuQYE) -## CREATE THE BOARD +## CREATING THE BOARD 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. -➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ +## CLEARING THE SCREEN -## CLEAR THE SCREEN - -* Before the loop, clear the screen once by using my screen package. +* Before the loop, clear the screen once by using my [screen package](https://github.com/inancgumus/screen), click on the link. You can find its [documentation here](https://godoc.org/github.com/inancgumus/screen). * 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). -* You can find more information about it in the Retro Clock project section. +* You can find more information about the screen package and screen clearing in the [Retro Clock project section lectures](https://github.com/inancgumus/learngo/tree/master/15-project-retro-led-clock). -➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ +## 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 can draw the board and the ball once by printing the buffer. - -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. +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. I use a `[]rune` slice as a buffer because `rune` can store an emoji character. * Make a large enough rune slice named `buf` using the `make` function. @@ -65,10 +53,10 @@ var buffer []rune str := string(buffer) ``` -➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖ - -## SLOW DOWN THE SPEED +## SLOWING DOWN THE SPEED 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.Second / 20)` +```go +time.Sleep(time.Second / 20) +``` diff --git a/18-bouncing-ball-project/exercises/01-width-and-height/main.go b/18-bouncing-ball-project/exercises/01-width-and-height/main.go index 67a904c..af2f8d3 100644 --- a/18-bouncing-ball-project/exercises/01-width-and-height/main.go +++ b/18-bouncing-ball-project/exercises/01-width-and-height/main.go @@ -20,6 +20,9 @@ // // 1. Go here: https://godoc.org/golang.org/x/crypto/ssh/terminal // +// Download the package: +// go get -u golang.org/x/crypto/ssh/terminal +// // 2. Find the function that gives you the width and height // of the terminal. // @@ -47,7 +50,7 @@ // columns (or cells). Ordinary characters have a // single column. // -// 1. Get the width of the ball emoji using function +// 1. Get the width of the ball emoji using a function // from the following package: // // go get -u github.com/mattn/go-runewidth diff --git a/18-bouncing-ball-project/exercises/02-previous-positions/main.go b/18-bouncing-ball-project/exercises/02-previous-positions/main.go new file mode 100644 index 0000000..d912c40 --- /dev/null +++ b/18-bouncing-ball-project/exercises/02-previous-positions/main.go @@ -0,0 +1,126 @@ +// 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" + "github.com/mattn/go-runewidth" +) + +// --------------------------------------------------------- +// EXERCISE: Previous positions +// +// Let's optimize the program once more. This time you're +// going to optimize the clearing off the previous positions. +// +// 1. Find the code below marked as "remove the previous ball" +// +// 2. Instead of clearing every position on the board to false, +// only set the previous position to false. So, don't use +// a loop, remove it. +// +// 3. Change the velocity of the ball like so: +// +// vx, vy = 5, 2 +// +// 4. Run the program and solve the problem +// +// +// HINT +// +// Don't forget saving the previous position. +// +// --------------------------------------------------------- + +func main() { + const ( + 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) + ) + + // you can get the width and height using the screen package easily: + width, height := screen.Size() + + // get the rune width of the ball emoji + ballWidth := runewidth.RuneWidth(cellBall) + + // adjust the width and height + width /= ballWidth + height-- // there is a 1 pixel border in my terminal + + // 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/18-bouncing-ball-project/exercises/02-previous-positions/solution/main.go b/18-bouncing-ball-project/exercises/02-previous-positions/solution/main.go new file mode 100644 index 0000000..37d1da3 --- /dev/null +++ b/18-bouncing-ball-project/exercises/02-previous-positions/solution/main.go @@ -0,0 +1,101 @@ +// 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" + "github.com/mattn/go-runewidth" +) + +func main() { + const ( + cellEmpty = ' ' + cellBall = '⚾' + + maxFrames = 1200 + speed = time.Second / 20 + ) + + var ( + px, py int // ball position + ppx, ppy int // previous ball position + vx, vy = 5, 2 // velocities + + cell rune // current cell (for caching) + ) + + // you can get the width and height using the screen package easily: + width, height := screen.Size() + + // get the rune width of the ball emoji + ballWidth := runewidth.RuneWidth(cellBall) + + // adjust the width and height + width /= ballWidth + height-- // there is a 1 pixel border in my terminal + + // 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/18-bouncing-ball-project/exercises/README.md b/18-bouncing-ball-project/exercises/README.md index 4277e08..edea2eb 100644 --- a/18-bouncing-ball-project/exercises/README.md +++ b/18-bouncing-ball-project/exercises/README.md @@ -4,3 +4,7 @@ In this exercise, your goal is getting the width and height of the terminal screen from your operating system (instead of setting the width and height manually). +2. **[Previous positions](https://github.com/inancgumus/learngo/tree/master/18-bouncing-ball-project/exercises/02-previous-positions)** + + Let's optimize the program once more. This time you're going to optimize the clearing off the previous positions. +