├── examples ├── screenshot_1.png ├── screenshot_2.png └── julia.go ├── nonsysioctl.go ├── sysioctl.go ├── LICENSE ├── terminal.go ├── README.md └── asciibrot.go /examples/screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esimov/asciibrot/HEAD/examples/screenshot_1.png -------------------------------------------------------------------------------- /examples/screenshot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esimov/asciibrot/HEAD/examples/screenshot_2.png -------------------------------------------------------------------------------- /nonsysioctl.go: -------------------------------------------------------------------------------- 1 | // +build windows plan9 solaris 2 | 3 | package asciibrot 4 | 5 | func getWinsize() (*winsize, error) { 6 | ws := new(winsize) 7 | 8 | ws.Col = 80 9 | ws.Row = 24 10 | 11 | return ws, nil 12 | } 13 | -------------------------------------------------------------------------------- /sysioctl.go: -------------------------------------------------------------------------------- 1 | // +build !windows,!plan9,!solaris 2 | 3 | package asciibrot 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "runtime" 9 | "syscall" 10 | "unsafe" 11 | ) 12 | 13 | func getWinsize() (*winsize, error) { 14 | ws := new(winsize) 15 | 16 | var _TIOCGWINSZ int64 17 | 18 | switch runtime.GOOS { 19 | case "linux": 20 | _TIOCGWINSZ = 0x5413 21 | case "darwin": 22 | _TIOCGWINSZ = 1074295912 23 | } 24 | 25 | r1, _, errno := syscall.Syscall(syscall.SYS_IOCTL, 26 | uintptr(syscall.Stdin), 27 | uintptr(_TIOCGWINSZ), 28 | uintptr(unsafe.Pointer(ws)), 29 | ) 30 | 31 | if int(r1) == -1 { 32 | fmt.Println("Error:", os.NewSyscallError("GetWinsize", errno)) 33 | return nil, os.NewSyscallError("GetWinsize", errno) 34 | } 35 | return ws, nil 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Simo Endre 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /terminal.go: -------------------------------------------------------------------------------- 1 | package asciibrot 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/mattn/go-colorable" 10 | ) 11 | 12 | type winsize struct { 13 | Row uint16 14 | Col uint16 15 | } 16 | 17 | // Screen buffer 18 | // Do not write to buffer directly, use package Print, Printf, Println functions instead. 19 | var Screen *bytes.Buffer = new(bytes.Buffer) 20 | var output *bufio.Writer = bufio.NewWriter(colorable.NewColorableStdout()) 21 | 22 | func init() { 23 | // Clear console 24 | output.WriteString("\033[2J") 25 | // Remove blinking cursor 26 | output.WriteString("\033[?25l") 27 | } 28 | 29 | // Get console width 30 | func Width() int { 31 | ws, err := getWinsize() 32 | 33 | if err != nil { 34 | return -1 35 | } 36 | 37 | return int(ws.Row) 38 | } 39 | 40 | // Get console height 41 | func Height() int { 42 | ws, err := getWinsize() 43 | if err != nil { 44 | return -1 45 | } 46 | return int(ws.Col) 47 | } 48 | 49 | // Flush buffer and ensure that it will not overflow screen 50 | func Flush() { 51 | for idx, str := range strings.Split(Screen.String(), "\n") { 52 | if idx > Height() { 53 | return 54 | } 55 | 56 | output.WriteString(str + "\n") 57 | } 58 | 59 | output.Flush() 60 | Screen.Reset() 61 | } 62 | 63 | // Move cursor to given position 64 | func MoveCursor(x int, y int) { 65 | fmt.Fprintf(Screen, "\033[%d;%dH", x, y) 66 | } 67 | -------------------------------------------------------------------------------- /examples/julia.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/esimov/asciibrot" 6 | "math" 7 | "math/rand" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | ) 13 | 14 | const ( 15 | MAX_IT int = 1000 16 | ) 17 | 18 | var zoom float64 = 1.0 19 | var isColor bool = false 20 | 21 | func main() { 22 | rand.Seed(time.Now().UTC().UnixNano()) 23 | c := make(chan os.Signal, 2) 24 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 25 | 26 | if len(os.Args) > 1 { 27 | if os.Args[1] == "--help" || os.Args[1] == "-h" { 28 | fmt.Println(`Usage go run mandelbrot_cli.go [--] 29 | -c --color generate ASCII mandelbrot in color 30 | -m --mono generate ASCII mandelbrot in monochrome`) 31 | os.Exit(1) 32 | } 33 | if os.Args[1] == "--color" || os.Args[1] == "-c" { 34 | isColor = true 35 | } else if os.Args[1] == "--mono" || os.Args[1] == "-m" { 36 | isColor = false 37 | } 38 | } 39 | 40 | zoom = 1.2 + rand.Float64()*1.8 41 | asciibrot.MoveCursor(0, 0) 42 | 43 | var n float64 = 20 44 | for { 45 | n += 0.045 46 | zoom += 0.04 * math.Sin(n) 47 | asciibrot.DrawFractal(zoom, math.Cos(n), math.Sin(n)/zoom*0.02, math.Sin(n), MAX_IT, isColor) 48 | 49 | // On CTRL+C restore default terminal foreground and background color 50 | go func() { 51 | <-c 52 | fmt.Fprint(asciibrot.Screen, "%s%s", "\x1b[49m", "\x1b[39m") 53 | fmt.Fprint(asciibrot.Screen, "\033[2J") 54 | asciibrot.Flush() 55 | os.Exit(1) 56 | }() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | **asciibrot** is a simple ascii mandelbrot fractal generator running in terminal. 4 | It's written in Go and should run on all the existing platforms, however the Linux based is the one on which it was tested. 5 | 6 | ### Install 7 | 8 | ``` 9 | go get github.com/esimov/asciibrot 10 | ``` 11 | 12 | ### Usage 13 | 14 | ``` 15 | go run julia.go --help 16 | ``` 17 | 18 | You can run the example in monochrome or color version. 19 | For the color version use `--color` or `-c`. For monochrome version use `--mono` or `-m`. 20 | 21 | You can build the binary version with `go build github.com/esimov/asciibrot`. 22 | 23 | ### Code example 24 | 25 | To generate different output you can play with values defined in the main function: 26 | 27 | ```go 28 | for { 29 | n += 0.045 30 | zoom += 0.04 * math.Sin(n) 31 | asciibrot.DrawFractal(zoom, math.Cos(n), math.Sin(n)/zoom*0.02, math.Sin(n), MAX_IT, true, isColor) 32 | 33 | // On CTRL+C restore default terminal foreground and background color 34 | go func() { 35 | <-c 36 | fmt.Fprint(asciibrot.Screen, "%s%s", "\x1b[49m", "\x1b[39m") 37 | fmt.Fprint(asciibrot.Screen, "\033[2J") 38 | asciibrot.Flush() 39 | os.Exit(1) 40 | }() 41 | } 42 | ``` 43 | 44 | Blog post on my personal website: http://esimov.com/2016/05/ascii-mandelbrot-renderer-in-go 45 | 46 | ### Sample 47 | 48 | ![asciibrot-gif](https://user-images.githubusercontent.com/883386/68360648-9303ad00-0129-11ea-8db0-30a1f0dc9cb5.gif) 49 | 50 | ## License 51 | This project is under MIT License. 52 | -------------------------------------------------------------------------------- /asciibrot.go: -------------------------------------------------------------------------------- 1 | package asciibrot 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | var ( 10 | wg sync.WaitGroup 11 | height int = Height() 12 | width int = Width() 13 | ) 14 | 15 | func DrawFractal(zoom, moveX, moveY float64, z float64, max_it int, isColor bool) { 16 | ticker := time.Tick(time.Millisecond * 2) 17 | 18 | charTable := map[int]string{1: "˙", 2: "˚", 3: "+", 4: "$", 5: "%", 6: "^", 7: "*", 8: "'", 9: "`"} 19 | foregroundColors := map[int]string{1: "\x1b[40m", 2: "\x1b[100m", 3: "\x1b[41m", 4: "\x1b[101m", 5: "\x1b[44m", 6: "\x1b[43m"} 20 | 21 | for row := 0; row < width; row++ { 22 | wg.Add(1) 23 | <-ticker 24 | Flush() 25 | 26 | go func(row int) { 27 | defer wg.Done() 28 | for col := 0; col <= height; col++ { 29 | MoveCursor(row, col) 30 | 31 | newRe := 1.5*(float64(row)-float64(width)/2.0)/(0.5*zoom*float64(width)) + moveX 32 | newIm := 1.2*(float64(col)-float64(height)/2.0)/(0.5*zoom*float64(height)) + moveY 33 | 34 | var i = iterator(newRe, newIm, z, max_it) 35 | 36 | if i < max_it { 37 | if i > 6 { 38 | if isColor { 39 | fmt.Fprintf(Screen, "π%s%s%s%s%s", "\x1b[39m", "\x1b[1m", "\x1b[49m", "\x1b[41;32m", "\x1b[0m") 40 | } else { 41 | fmt.Fprintf(Screen, "π") 42 | 43 | } 44 | } else { 45 | if ch, ok := charTable[i]; ok { 46 | if isColor { 47 | fmt.Fprintf(Screen, "%s%s", ch, foregroundColors[i]) 48 | } else { 49 | fmt.Fprintf(Screen, "%s", ch) 50 | } 51 | } 52 | } 53 | } else { 54 | fmt.Fprintf(Screen, "∞") 55 | 56 | } 57 | } 58 | }(row) 59 | } 60 | 61 | wg.Wait() 62 | } 63 | 64 | func iterator(cx, cy float64, z float64, maxIter int) int { 65 | var iteration int = 0 66 | 67 | for iteration < maxIter { 68 | var x, y float64 = cx, cy 69 | cx = x*x - y*y + -0.95 70 | cy = 2*x*y + z 71 | 72 | if cx*cx+cy*cy > 4 { 73 | break 74 | } 75 | iteration++ 76 | } 77 | return iteration 78 | } 79 | --------------------------------------------------------------------------------