From 6d9c3d7fe363ced98a7bc55f15403ead6a9dadcc Mon Sep 17 00:00:00 2001 From: Inanc Gumus Date: Thu, 14 Mar 2019 22:12:12 +0300 Subject: [PATCH] add: bouncing ball exercise #3 --- .../exercises/03-single-dimensional/main.go | 121 ++++++++++++++++++ .../03-single-dimensional/solution/main.go | 104 +++++++++++++++ 18-bouncing-ball-project/exercises/README.md | 3 + 3 files changed, 228 insertions(+) create mode 100644 18-bouncing-ball-project/exercises/03-single-dimensional/main.go create mode 100644 18-bouncing-ball-project/exercises/03-single-dimensional/solution/main.go diff --git a/18-bouncing-ball-project/exercises/03-single-dimensional/main.go b/18-bouncing-ball-project/exercises/03-single-dimensional/main.go new file mode 100644 index 0000000..3dc7ab6 --- /dev/null +++ b/18-bouncing-ball-project/exercises/03-single-dimensional/main.go @@ -0,0 +1,121 @@ +// 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: Single Dimensional +// +// In this exercise you will understand why I use +// a multi-dimensional board slice instead of a +// single-dimensional one. +// +// 1. Remove this: +// board := make([][]bool, width) +// +// 2. Use this: +// board := make([]bool, width*height) +// +// 3. Adjust the rest of the operations in the code to work +// with this single-dimensional slice. +// +// You'll see how hard it becomes to work with it. +// +// --------------------------------------------------------- + +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/03-single-dimensional/solution/main.go b/18-bouncing-ball-project/exercises/03-single-dimensional/solution/main.go new file mode 100644 index 0000000..c347908 --- /dev/null +++ b/18-bouncing-ball-project/exercises/03-single-dimensional/solution/main.go @@ -0,0 +1,104 @@ +// 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 a single-dimensional board + board := make([]bool, width*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 { + // calculate the new and the previous ball positions + pos := py*width + px + ppos := ppy*width + ppx + + // remove the previous ball and put the new ball + board[pos], board[ppos] = 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 := 0; y < height; y++ { + for x := 0; x < width; x++ { + cell = cellEmpty + + if board[y*width+x] { + 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 edea2eb..98dd590 100644 --- a/18-bouncing-ball-project/exercises/README.md +++ b/18-bouncing-ball-project/exercises/README.md @@ -8,3 +8,6 @@ Let's optimize the program once more. This time you're going to optimize the clearing off the previous positions. +3. **[Use a single dimensional slice]()** + + For the board slice, instead of using a multi-dimensional slice, let's use a single-dimensional slice. In this exercise, you'll understand and deeply internalize why I've used a multi-dimensional board slice. \ No newline at end of file