From 3271dd5e63ad3d36235f453289c937684548d2d5 Mon Sep 17 00:00:00 2001 From: Inanc Gumus Date: Thu, 14 Mar 2019 20:36:32 +0300 Subject: [PATCH] add: bouncing ball exercise #1 --- .../exercises/01-width-and-height/main.go | 173 ++++++++++++++++++ .../01-width-and-height/solution}/main.go | 46 +++-- 18-bouncing-ball-project/exercises/README.md | 6 + 3 files changed, 210 insertions(+), 15 deletions(-) create mode 100644 18-bouncing-ball-project/exercises/01-width-and-height/main.go rename 18-bouncing-ball-project/{04-finalize => exercises/01-width-and-height/solution}/main.go (64%) create mode 100644 18-bouncing-ball-project/exercises/README.md 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 new file mode 100644 index 0000000..67a904c --- /dev/null +++ b/18-bouncing-ball-project/exercises/01-width-and-height/main.go @@ -0,0 +1,173 @@ +// 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/ +// + +// --------------------------------------------------------- +// EXERCISE: Adjust the width and height automatically +// +// In this exercise, your goal is simple: +// +// Instead of setting the width and height manually, +// you need to get the width and height of the terminal +// screen from your operating system. +// +// Don't worry, it is easier than it sounds. You just +// need to read a few documentation and install a +// Go package. +// +// 1. Go here: https://godoc.org/golang.org/x/crypto/ssh/terminal +// +// 2. Find the function that gives you the width and height +// of the terminal. +// +// 3. Call that function from your program and get the +// width and height. +// +// 4. When an error occurs while retrieving the width +// and height, report it. +// +// 5. Set the width and height of the board. +// +// 6. After solving the above steps, update your program +// to use my screen package instead. It offers an +// easier way to get the width and height of a +// terminal. +// +// go get -u https://github.com/inancgumus/screen +// +// +// BONUS +// +// 1. When you set the width, you may see that the ball +// goes beyond the left and right borders. This happens +// because the ball emoji spans to multiple console +// columns (or cells). Ordinary characters have a +// single column. +// +// 1. Get the width of the ball emoji using function +// from the following package: +// +// go get -u github.com/mattn/go-runewidth +// +// 2. Divide the width using the rune width of the +// ball emoji. +// +// 2. Your terminal may have borders, so reduce the +// height by taking into account the height of +// your terminal borders. +// +// +// EXPECTED OUTPUT +// +// When you run the program, the ball should start +// animating across the total width and height of your +// terminal screen dynamically. +// +// Currently you set width and height manually, so it +// wasn't matter whether your terminal was bigger or +// smaller, but now it will be! +// +// +// HINT +// +// Please take a look at this if you get stuck. +// +// You need to pass the Standard Out file handler +// to the function that returns you the dimensions. +// +// Check out my screen package to find out how I'm +// passing the Standard Out file handler. +// +// https://github.com/inancgumus/screen/blob/master/dimensions.go +// +// --------------------------------------------------------- + +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/18-bouncing-ball-project/04-finalize/main.go b/18-bouncing-ball-project/exercises/01-width-and-height/solution/main.go similarity index 64% rename from 18-bouncing-ball-project/04-finalize/main.go rename to 18-bouncing-ball-project/exercises/01-width-and-height/solution/main.go index f7a4de4..246c9ba 100644 --- a/18-bouncing-ball-project/04-finalize/main.go +++ b/18-bouncing-ball-project/exercises/01-width-and-height/solution/main.go @@ -9,9 +9,13 @@ package main import ( "fmt" + "os" "time" + "github.com/mattn/go-runewidth" + "github.com/inancgumus/screen" + "golang.org/x/crypto/ssh/terminal" ) func main() { @@ -23,19 +27,30 @@ func main() { speed = time.Second / 20 ) - // get the width and height of the terminal dynamically *** - width, height := screen.Size() - width /= 2 // our emoji is 2 chars wide - height-- // for the border - var ( - px, py int // ball position - ppx, ppy int // previous ball position *** - vx, vy = 1, 1 // velocities + px, py int // ball position + vx, vy = 1, 1 // velocities cell rune // current cell (for caching) ) + // get the width and height + width, height, err := terminal.GetSize(int(os.Stdout.Fd())) + if err != nil { + fmt.Println(err) + return + } + + // 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 { @@ -61,15 +76,16 @@ func main() { 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 + // 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] diff --git a/18-bouncing-ball-project/exercises/README.md b/18-bouncing-ball-project/exercises/README.md new file mode 100644 index 0000000..4277e08 --- /dev/null +++ b/18-bouncing-ball-project/exercises/README.md @@ -0,0 +1,6 @@ +# Exercises + +1. **[Adjust the width and height automatically](https://github.com/inancgumus/learngo/tree/master/18-bouncing-ball-project/exercises/01-width-and-height)** + + 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). +