refactor: bouncing ball

This commit is contained in:
Inanc Gumus
2019-03-08 10:05:39 +03:00
parent d5b6077da4
commit 6576f83d16
9 changed files with 343 additions and 425 deletions

View File

@ -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()
}
}

View File

@ -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))
}

View File

@ -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))
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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 ## Ball position and velocity
* The width and the height of the board.
* 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.
On each loop step: Add velocities to ball's position. This will make the ball move.
## Declare the ball positions and velocity:
The ball should be in a known position on your board, and it should have a velocity.
* **Velocity means: Speed and Direction** * **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 down_
* Y velocity = -1 -> _ball moves up_ * 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` * Youtube: Crash Course: Velocity
-> https://www.youtube.com/watch?v=ZM8ECpBuQYE
* On each step: Add velocities to ball's position. This will make the ball move.
## 🎾 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. 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.
* 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
```
## 🎾 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). * It's [here](https://github.com/inancgumus/screen).
* Its documentation is [here](https://godoc.org/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.
## 🎾 DRAWING LOOP ## DRAW THE BOARD
This is the main loop that will draw the board and the ball to the screen continuously. 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.
* Create the loop 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.
* 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...
## 🎾 CLEAR THE BOARD
At the beginning your board should not contain the ball.
* 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.
* Make a large enough rune slice named `buf` using the `make` function. * 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: // For printing, you can convert a rune slice to a string like so:
str := string(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.
## SLOW DOWN THE SPEED ## 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)`