Initial commit
commit
534eab9fac
|
@ -0,0 +1 @@
|
||||||
|
atmosphere
|
|
@ -0,0 +1,131 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Forecast is a representation of the current state of the weather.
|
||||||
|
type Forecast struct {
|
||||||
|
raw string
|
||||||
|
time TimeOfDay
|
||||||
|
cloudiness Cloudiness
|
||||||
|
raininess Raininess
|
||||||
|
visibility Visibility
|
||||||
|
windiness Windiness
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewForecast parses the output of ~iajrz's climate program.
|
||||||
|
// TODO?: boolean randomize option to generate completely random weather as a demo mode?
|
||||||
|
func NewForecast() (Forecast, error) {
|
||||||
|
out, err := exec.Command("/home/iajrz/climate").Output()
|
||||||
|
if err != nil {
|
||||||
|
return Forecast{}, err
|
||||||
|
}
|
||||||
|
rawWeather := string(out)
|
||||||
|
return Forecast{
|
||||||
|
raw: rawWeather,
|
||||||
|
time: TimeOfDay(findSubstring(rawWeather, timeStrings)),
|
||||||
|
cloudiness: Cloudiness(findSubstring(rawWeather, cloudStrings)),
|
||||||
|
raininess: Raininess(findSubstring(rawWeather, rainStrings)),
|
||||||
|
visibility: Visibility(findSubstring(rawWeather, visibilityStrings)),
|
||||||
|
windiness: Windiness(findSubstring(rawWeather, windStrings)),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Forecast) String() string {
|
||||||
|
return f.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func findSubstring(s string, substrings []string) int {
|
||||||
|
for i := range substrings {
|
||||||
|
if strings.Contains(s, substrings[i]) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimeOfDay int
|
||||||
|
|
||||||
|
const (
|
||||||
|
EarlyMorning TimeOfDay = iota
|
||||||
|
Morning
|
||||||
|
Afternoon
|
||||||
|
Night
|
||||||
|
)
|
||||||
|
|
||||||
|
var timeStrings = []string{
|
||||||
|
"early morning",
|
||||||
|
"morning",
|
||||||
|
"afternoon",
|
||||||
|
"night",
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cloudiness int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ClearSky Cloudiness = iota
|
||||||
|
AlmostClear
|
||||||
|
PartlyCloudy
|
||||||
|
MostlyCloudy
|
||||||
|
Cloudy
|
||||||
|
)
|
||||||
|
|
||||||
|
var cloudStrings = []string{
|
||||||
|
"clear",
|
||||||
|
"almost clear",
|
||||||
|
"partly cloudy",
|
||||||
|
"mostly cloudy",
|
||||||
|
"cloudy",
|
||||||
|
}
|
||||||
|
|
||||||
|
type Raininess int
|
||||||
|
|
||||||
|
const (
|
||||||
|
NoRain Raininess = iota
|
||||||
|
Drizzle
|
||||||
|
LightShower
|
||||||
|
Shower
|
||||||
|
HeavyShower
|
||||||
|
)
|
||||||
|
|
||||||
|
var rainStrings = []string{
|
||||||
|
"no rain",
|
||||||
|
"a drizzle",
|
||||||
|
"a light shower",
|
||||||
|
"a shower",
|
||||||
|
"a heavy shower",
|
||||||
|
}
|
||||||
|
|
||||||
|
type Visibility int
|
||||||
|
|
||||||
|
const (
|
||||||
|
NoFog Visibility = iota
|
||||||
|
Haze
|
||||||
|
Mist
|
||||||
|
Fog
|
||||||
|
HeavyFog
|
||||||
|
)
|
||||||
|
|
||||||
|
var visibilityStrings = []string{
|
||||||
|
"visibility",
|
||||||
|
"There's haze",
|
||||||
|
"There's mist",
|
||||||
|
"There's fog",
|
||||||
|
"There's heavy fog",
|
||||||
|
}
|
||||||
|
|
||||||
|
type Windiness int
|
||||||
|
|
||||||
|
const (
|
||||||
|
NoWind Windiness = iota
|
||||||
|
Breeze
|
||||||
|
StiffWind
|
||||||
|
)
|
||||||
|
|
||||||
|
var windStrings = []string{
|
||||||
|
"no breeze",
|
||||||
|
"light breeze",
|
||||||
|
"stiff wind",
|
||||||
|
}
|
|
@ -0,0 +1,202 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/muesli/reflow/wordwrap"
|
||||||
|
"github.com/muesli/termenv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := tea.NewProgram(model{startTime: time.Now()})
|
||||||
|
if err := app.Start(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queues the initial loading of the forecast and
|
||||||
|
func (m model) Init() tea.Cmd {
|
||||||
|
return tea.Batch(updateForecast, renderOften)
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateForecast = func() tea.Msg {
|
||||||
|
forecast, err := NewForecast()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return forecast
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateForecastOften = tea.Tick(20*time.Second, func(t time.Time) tea.Msg {
|
||||||
|
return updateForecast()
|
||||||
|
})
|
||||||
|
|
||||||
|
var makeSceneMsg = func(f Forecast) func() tea.Msg {
|
||||||
|
return func() tea.Msg {
|
||||||
|
scene, err := NewScene("scene1", f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return scene
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fadeOut float32
|
||||||
|
type fadeIn float32
|
||||||
|
|
||||||
|
// Used to update the progress of a fade out transition.
|
||||||
|
var tickFadeOut = tea.Every(time.Millisecond*100, func(t time.Time) tea.Msg {
|
||||||
|
return fadeOut(0.05)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Used to update the progress of a fade in transition.
|
||||||
|
var tickFadeIn = tea.Every(time.Millisecond*100, func(t time.Time) tea.Msg {
|
||||||
|
return fadeIn(0.05)
|
||||||
|
})
|
||||||
|
|
||||||
|
type renderTick struct{}
|
||||||
|
|
||||||
|
// Used to update the screen at least once per second.
|
||||||
|
var renderOften = tea.Tick(time.Second, func(t time.Time) tea.Msg {
|
||||||
|
return renderTick{}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update updates the internal state of the model and queues any events that
|
||||||
|
// need to be scheduled.
|
||||||
|
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
cmds := []tea.Cmd{}
|
||||||
|
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch msg.String() {
|
||||||
|
case "ctrl+c", "ctrl+d", "q":
|
||||||
|
return m, tea.Quit
|
||||||
|
case "esc":
|
||||||
|
// TODO: Menu for options. Maybe 100% randomized weather?
|
||||||
|
}
|
||||||
|
|
||||||
|
case tea.WindowSizeMsg:
|
||||||
|
m.width = msg.Width
|
||||||
|
m.height = msg.Height
|
||||||
|
|
||||||
|
case Forecast:
|
||||||
|
// Re-queue the forecast polling.
|
||||||
|
cmds = append(cmds, updateForecastOften)
|
||||||
|
|
||||||
|
// No action required if the forecast hasn't changed.
|
||||||
|
if msg == m.Forecast {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this isn't the initial startup forecast, we need to fade out the scene.
|
||||||
|
if m.Forecast != (Forecast{}) {
|
||||||
|
m.fading = true
|
||||||
|
cmds = append(cmds, tickFadeOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
// In all cases, we need to load a scene with the forecast.
|
||||||
|
m.Forecast = msg
|
||||||
|
cmds = append(cmds, makeSceneMsg(m.Forecast))
|
||||||
|
|
||||||
|
case Scene:
|
||||||
|
if m.Scene.foreground == nil {
|
||||||
|
m.Scene = msg
|
||||||
|
} else {
|
||||||
|
m.nextScene = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
case fadeOut:
|
||||||
|
m.fadeProgress += float32(msg)
|
||||||
|
if m.fadeProgress >= 1.0 {
|
||||||
|
m.fadeProgress = 1.0
|
||||||
|
// To unfade, we need a Scene ready to rock. If we don't have one, stall.
|
||||||
|
if m.nextScene.foreground == nil {
|
||||||
|
cmds = append(cmds, tickFadeOut)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Scene = m.nextScene
|
||||||
|
m.nextScene = Scene{}
|
||||||
|
cmds = append(cmds, tickFadeIn)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cmds = append(cmds, tickFadeOut)
|
||||||
|
|
||||||
|
case fadeIn:
|
||||||
|
m.fadeProgress -= float32(msg)
|
||||||
|
if m.fadeProgress <= 0.0 {
|
||||||
|
m.fadeProgress = 0.0
|
||||||
|
m.fading = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cmds = append(cmds, tickFadeIn)
|
||||||
|
|
||||||
|
case error:
|
||||||
|
m.err = msg
|
||||||
|
|
||||||
|
case renderTick:
|
||||||
|
cmds = append(cmds, renderOften)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, tea.Batch(cmds...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// View lays out the screen and queries the Scene for its contents.
|
||||||
|
// It also handles transitions when changing from Scene to Scene.
|
||||||
|
func (m model) View() string {
|
||||||
|
if m.err != nil {
|
||||||
|
return m.err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
weather := wordwrap.String(m.Forecast.String(), m.width)
|
||||||
|
m.height -= strings.Count(weather, "\n")
|
||||||
|
weather = strings.TrimSpace(weather) // Trim trailing newline
|
||||||
|
|
||||||
|
output := ""
|
||||||
|
lastStyle := Style{}
|
||||||
|
profile := termenv.EnvColorProfile()
|
||||||
|
|
||||||
|
// Center scene in terminal
|
||||||
|
xOff := (m.Scene.Width - m.width) / 2
|
||||||
|
yOff := (m.Scene.Height - m.height) / 2
|
||||||
|
|
||||||
|
for y := yOff; y < yOff+m.height; y++ {
|
||||||
|
for x := xOff; x < xOff+m.width; x++ {
|
||||||
|
char, style := m.Scene.GetCell(x, y, int(time.Since(m.startTime).Seconds()))
|
||||||
|
style = style.Convert(profile)
|
||||||
|
|
||||||
|
// For wipe transitions, replace character with blank space.
|
||||||
|
if float32((x-xOff)/2+y-yOff) < m.fadeProgress*float32(m.width/2+m.height) {
|
||||||
|
char = ' '
|
||||||
|
style = Style{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only output formatting escape codes if the style's changed since the last cell.
|
||||||
|
if lastStyle.String() != style.String() {
|
||||||
|
output += style.String()
|
||||||
|
lastStyle = style
|
||||||
|
}
|
||||||
|
|
||||||
|
output += string(char)
|
||||||
|
}
|
||||||
|
output += "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
output += termenv.CSI + termenv.ResetSeq + "m"
|
||||||
|
output += weather
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
type model struct {
|
||||||
|
Forecast
|
||||||
|
Scene
|
||||||
|
err error
|
||||||
|
nextScene Scene
|
||||||
|
fading bool
|
||||||
|
fadeProgress float32
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
startTime time.Time
|
||||||
|
}
|
|
@ -0,0 +1,271 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"github.com/muesli/termenv"
|
||||||
|
"github.com/ojrac/opensimplex-go"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Style represents a pair of colors, foreground and background.
|
||||||
|
type Style struct {
|
||||||
|
fg termenv.Color
|
||||||
|
bg termenv.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert is a wrapper for termenv.Profile.Convert, it converts the foreground
|
||||||
|
// and background colors of a Style to be within a given terminal's Profile.
|
||||||
|
func (s Style) Convert(profile termenv.Profile) Style {
|
||||||
|
return Style{
|
||||||
|
profile.Convert(s.fg),
|
||||||
|
profile.Convert(s.bg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String fills the Stringer interface and returns an ANSI escape code
|
||||||
|
// formatted to produce text in the specified Style.
|
||||||
|
func (s Style) String() string {
|
||||||
|
if s.fg == nil {
|
||||||
|
s.fg = termenv.NoColor{}
|
||||||
|
}
|
||||||
|
if s.bg == nil {
|
||||||
|
s.bg = termenv.NoColor{}
|
||||||
|
}
|
||||||
|
// TODO: Looks like the background color is overriding the foreground color. What's the proper way to do this?
|
||||||
|
return fmt.Sprintf("%s%s;%sm", termenv.CSI, s.fg.Sequence(false), s.bg.Sequence(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scene holds all the necessary information to make a dynamic, weather-y ASCII
|
||||||
|
// landscape.
|
||||||
|
type Scene struct {
|
||||||
|
foreground [][]rune
|
||||||
|
background [][]rune
|
||||||
|
windground [][]rune
|
||||||
|
depth [][]rune
|
||||||
|
forecast Forecast
|
||||||
|
|
||||||
|
generator opensimplex.Noise
|
||||||
|
|
||||||
|
Width int
|
||||||
|
Height int
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
fgPath = "foreground.txt"
|
||||||
|
depthPath = "depth.txt"
|
||||||
|
windPath = "wind.txt"
|
||||||
|
|
||||||
|
earlyPath = "early.txt"
|
||||||
|
morningPath = "morning.txt"
|
||||||
|
afternoonPath = "afternoon.txt"
|
||||||
|
nightPath = "night.txt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var timeToPath = []string{
|
||||||
|
earlyPath,
|
||||||
|
morningPath,
|
||||||
|
afternoonPath,
|
||||||
|
nightPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewScene accepts a path to a folder containing foreground, windground,
|
||||||
|
// depth, and background files and loads them from disk as needed to generate
|
||||||
|
// imagery for the given forecast.
|
||||||
|
func NewScene(scenePath string, forecast Forecast) (Scene, error) {
|
||||||
|
var s Scene
|
||||||
|
var err error
|
||||||
|
|
||||||
|
s.forecast = forecast
|
||||||
|
s.generator = opensimplex.NewNormalized(0)
|
||||||
|
|
||||||
|
s.foreground, err = readRunesFromFile(path.Join(scenePath, fgPath))
|
||||||
|
if err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
s.depth, err = readRunesFromFile(path.Join(scenePath, depthPath))
|
||||||
|
if err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.windground, err = readRunesFromFile(path.Join(scenePath, windPath))
|
||||||
|
if err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bgPath := timeToPath[forecast.time]
|
||||||
|
s.background, err = readRunesFromFile(path.Join(scenePath, bgPath))
|
||||||
|
if err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.normalize()
|
||||||
|
|
||||||
|
/*m.depthData = make([][]uint8, 0)
|
||||||
|
for i := range depthRunes {
|
||||||
|
m.depthData = append(m.depthData, make([]uint8, 0))
|
||||||
|
for j := range depthRunes[i] {
|
||||||
|
m.depthData[i] = append(m.depthData[i], uint8(depthRunes[i][j])-48)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize adjusts the foreground, windground, depth map, and background to
|
||||||
|
// have matching widths and heights. It adjusts by cropping to the shortest
|
||||||
|
// number of lines between the four and adjusts each line to the shortest of
|
||||||
|
// any lines in any of the four maps.
|
||||||
|
func (s *Scene) normalize() {
|
||||||
|
scenes := [...][][]rune{s.foreground, s.windground, s.depth, s.background}
|
||||||
|
|
||||||
|
s.Height = 999999999
|
||||||
|
for i := range scenes {
|
||||||
|
if len(scenes[i]) < s.Height {
|
||||||
|
s.Height = len(scenes[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range scenes {
|
||||||
|
scenes[i] = scenes[i][:s.Height]
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Width = 999999999
|
||||||
|
for j := 0; j < s.Height; j++ {
|
||||||
|
for i := range scenes {
|
||||||
|
if len(scenes[i][j]) < s.Width {
|
||||||
|
s.Width = len(scenes[i][j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range scenes {
|
||||||
|
scenes[i][j] = scenes[i][j][:s.Width]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Change this to a map? Map can be iterated over and still referenced by name.
|
||||||
|
s.foreground = scenes[0]
|
||||||
|
s.windground = scenes[1]
|
||||||
|
s.depth = scenes[2]
|
||||||
|
s.background = scenes[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Scene) GetCell(x, y, time int) (rune, Style) {
|
||||||
|
fx := float64(x)
|
||||||
|
fy := float64(y)
|
||||||
|
ftime := float64(time)
|
||||||
|
fcloud := float64(s.forecast.cloudiness)
|
||||||
|
fwind := float64(s.forecast.windiness)
|
||||||
|
frain := float64(s.forecast.raininess)
|
||||||
|
|
||||||
|
char := ' '
|
||||||
|
style := Style{
|
||||||
|
termenv.ANSI256Color(255),
|
||||||
|
termenv.ANSI256Color(232),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Out of bounds
|
||||||
|
if y >= s.Height || y < 0 {
|
||||||
|
return char, Style{}
|
||||||
|
}
|
||||||
|
if x >= s.Width || x < 0 {
|
||||||
|
return char, Style{}
|
||||||
|
}
|
||||||
|
|
||||||
|
depth := uint8(s.depth[y][x] - 48)
|
||||||
|
|
||||||
|
// Char selection
|
||||||
|
char = s.foreground[y][x]
|
||||||
|
// Pull from wind map if the current cell is windswept.
|
||||||
|
if fwind > 0.0 && s.generator.Eval3(fx/40+fwind/8*ftime*2, fy/20, ftime/5+fwind/10*ftime/2) > 0.6 {
|
||||||
|
char = s.windground[y][x]
|
||||||
|
}
|
||||||
|
// Depth 9 is considered "transparent," so use the char from the background.
|
||||||
|
if depth == 9 {
|
||||||
|
char = s.background[y][x]
|
||||||
|
}
|
||||||
|
if frain > 0.0 && 0.1+frain/20 > s.generator.Eval3(fx+ftime*fwind*3, fy-ftime*5, ftime/15) {
|
||||||
|
char = '|'
|
||||||
|
if fwind > 1.0 {
|
||||||
|
char = '/'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Style selection
|
||||||
|
// Calculate fog
|
||||||
|
fog := (depth - 1) + uint8(s.forecast.visibility-1)*2
|
||||||
|
if s.forecast.visibility == 0 {
|
||||||
|
fog = (depth + uint8(s.forecast.visibility)) / 2
|
||||||
|
}
|
||||||
|
// Don't show fog in the sky during the night.
|
||||||
|
if depth == 9 && (s.forecast.time == Night || s.forecast.time == EarlyMorning) {
|
||||||
|
fog = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add clouds
|
||||||
|
cloudiness := 0
|
||||||
|
if depth > 8 && fcloud/5 > s.generator.Eval3(fx/50+ftime/24+ftime*(fwind/8), fy/12, 1000+ftime/80) {
|
||||||
|
cloudiness += 10
|
||||||
|
}
|
||||||
|
if depth > 6 && fcloud/7 > s.generator.Eval3(fx/20+ftime/14+ftime*(fwind/8), fy/6, 0+ftime/80) {
|
||||||
|
cloudiness += 10
|
||||||
|
}
|
||||||
|
|
||||||
|
// At night, fog obscures distant objects with darkness.
|
||||||
|
if s.forecast.time == Night || s.forecast.time == EarlyMorning {
|
||||||
|
style.fg = termenv.ANSI256Color(255 - fog - fog/2)
|
||||||
|
} else {
|
||||||
|
// Merge fog with clouds during daytime.
|
||||||
|
cloudiness += int(fog)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cloudiness > 23 {
|
||||||
|
cloudiness = 23
|
||||||
|
}
|
||||||
|
|
||||||
|
if cloudiness > 0 {
|
||||||
|
style.bg = termenv.ANSI256Color(232 + cloudiness)
|
||||||
|
} else {
|
||||||
|
style.bg = termenv.ANSI256Color(232 + fog)
|
||||||
|
}
|
||||||
|
return char, style
|
||||||
|
}
|
||||||
|
|
||||||
|
// readRunesFromFile reads the file at the given path and reads it character by
|
||||||
|
// character, line by line into a slice of slices of runes.
|
||||||
|
func readRunesFromFile(filepath string) ([][]rune, error) {
|
||||||
|
file, err := os.Open(filepath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
br := bufio.NewReader(file)
|
||||||
|
img := make([][]rune, 0)
|
||||||
|
img = append(img, make([]rune, 0))
|
||||||
|
|
||||||
|
for {
|
||||||
|
r, s, err := br.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid unicode characters are skipped.
|
||||||
|
if r == unicode.ReplacementChar && s == 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start a new slice on newline, otherwise append character to current slice.
|
||||||
|
if r == '\n' {
|
||||||
|
img = append(img, make([]rune, 0))
|
||||||
|
} else {
|
||||||
|
img[len(img)-1] = append(img[len(img)-1], r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return img, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
---___ | __------''''
|
||||||
|
---__ ,.---., __---
|
||||||
|
-__ ;/-'''-\; --'
|
||||||
|
:|' `|:
|
||||||
|
:|. ,|; --__ ___
|
||||||
|
__--- ;\-___-/; ''-- --___
|
||||||
|
'' , `'---'` -----____
|
||||||
|
___---'' / ''
|
||||||
|
'' ' \
|
||||||
|
____-- , \
|
||||||
|
'' /
|
||||||
|
/ \
|
||||||
|
__-- ' \
|
||||||
|
\
|
||||||
|
/
|
||||||
|
/
|
||||||
|
/
|
||||||
|
'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
|
||||||
|
9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
|
||||||
|
9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999899
|
||||||
|
9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999988999999999999988888
|
||||||
|
9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999889999999999999999999999999999999998888888888899999888888888888
|
||||||
|
9999999999999999999999999999999999998999999999999999999999999999999999999999999998888888888889999999999999999999999999888888888888888889998888888888888
|
||||||
|
9999999999999999999999999999999999888889999988888888999999988889999998889998888888888888888888888888899999888889999888888888888888888888888888888888888
|
||||||
|
9999999999999999999999999999999998888888888888888888888988888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
|
||||||
|
9999999999999999999999999999998888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888555555555888
|
||||||
|
9999999999999999999999999999988888888888888888888888888888888888888888888888888888888888888888888888855555555555888888888855555555555555555555555555555
|
||||||
|
9999999999999999999999999988888888888888888888888888888888888888888888888888888888888888888888555555555555555555555535555555555555555555555555555555555
|
||||||
|
9999999999999998889999999888888888888888888888888888888888888888888888888888888888888888885555555555555555555555555355555555555555555555555555555555555
|
||||||
|
9999999999999988888899998888888888888888888888888888888888888888888888888888888888888885555555555555555555555555553555555555555555555555555555555555555
|
||||||
|
9999988889999888888888888888888888888888888888888888888888888888888888888888555558888833333333333333333333553333335555555555555555555555555555555555555
|
||||||
|
9888888888888888888888888888888888888888888888888888888888888888888888855555555555555333333333333333333333355555533555555555555555555555555555555555555
|
||||||
|
8888888888888888888888888888888888888555555558888888888888555555555555555555555555533333333333333333333333335555535355555555555555533333333333333333333
|
||||||
|
8888888888888888888888555588885555555555555555555555555555555555555555555555555555333333333333333333333333344444434434444444333333333333333333333333333
|
||||||
|
8888888888888888855555555555555555555555555555555555555555555555555555555555555444433333333333333333333333344444434444444433333333333333333333333333333
|
||||||
|
8888888855555555555555555555555555555555555555555555544444444444444444444444444444433333333333333333333333344433333333333333333333333333333333333333333
|
||||||
|
5555555555555555555555555555555555555555555544444444444444443333333333333333333333333333333333333333333333333333333333333333333333333111111111111111111
|
||||||
|
5555555555555555555555555555555555544444444444444444444444333333333333333333333333333333333333333333333333333333333333331111111111111111111111111111111
|
||||||
|
5555555555555555544444444444444444444444444444444444443333333333333333333333333333333333333333333333333333333311111111111111111111111111111111111111111
|
||||||
|
4444444444444444444444444444444444444444444444444333333333333333333333333333333333333333333333333333333331111111111111111111111111111111111111111111111
|
||||||
|
3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333111111111111111111111111111111111111111111111111
|
||||||
|
3333333333333333333333333333333333333333333333333333333333333333333333333333333333332222222222222221111111111111111111111111111111111111111111111111111
|
||||||
|
2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222111111111111111111111111111111111111111111111111111111111111
|
||||||
|
2222222222222222222222222222222222221212222222222222222222222222222222222211111111111111111111111111111111111111111111111111111111111111111111111111111
|
||||||
|
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
|
||||||
|
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
|
||||||
|
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
|
||||||
|
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
|
|
@ -0,0 +1,31 @@
|
||||||
|
. *
|
||||||
|
` * . `
|
||||||
|
* ` .: TWA. *
|
||||||
|
* . . / 0 Vm *
|
||||||
|
, . 'B .
|
||||||
|
* * * |' D
|
||||||
|
` '| O P
|
||||||
|
\\
|
||||||
|
. ' `=____-'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
|
||||||
|
.
|
||||||
|
__ ./ \.
|
||||||
|
__ ___.--'' \ _.--'\/
|
||||||
|
. ___,.-" '-, _--'' \ / /
|
||||||
|
./ \. /''\_.-. _-". .-. __.--'" '--.____ ___.. _-' '-/
|
||||||
|
/ \_.-' \-. ,-" \_-""" \-" "---' \--"
|
||||||
|
__/ ' ,----.___
|
||||||
|
/ _._________ _______..------'' ''-
|
||||||
|
.-' _.--''' ''--/-''''
|
||||||
|
.__ / _-'` /
|
||||||
|
/ '- / .-' __ /
|
||||||
|
__.. / '--" _.--. /-----""----,-,_____ -----O '
|
||||||
|
_.-' '--' __.-' `-__//-\___-' .' _ '. \ |\ '
|
||||||
|
' _,-.,___ ____,,,,..../ ,-'|+|,- .' |+| '-,-" | \
|
||||||
|
____ ,.----" \___,----''`` -,-- ---| | | \ .=
|
||||||
|
..--' '--/ . - | == == | _ _ | |
|
||||||
|
.--.___.' __ | === | |+| |+| | | '
|
||||||
|
-'-.___/ ' _ - _ _ __ ___ __|__|||___|_____________|___ _|_ _
|
||||||
|
' .- - '
|
||||||
|
' -' - '
|
||||||
|
' .
|
||||||
|
_ . - - " ' && '
|
||||||
|
' _..= - '
|
||||||
|
' _.' - '
|
||||||
|
' ' * * ' ' '
|
||||||
|
*|*|* @&& , &&
|
||||||
|
' &&&@& ' '
|
||||||
|
' '
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
'--__ | __------
|
||||||
|
--__ ,.---., __---
|
||||||
|
-__ ;/-'''-\; --'
|
||||||
|
:|' `|:
|
||||||
|
:|. ,|; --__ ___
|
||||||
|
;\-___-/; ''-- ____
|
||||||
|
, `'---'` '----_____
|
||||||
|
/ -------____
|
||||||
|
' \ ____
|
||||||
|
, \ '''--------
|
||||||
|
/
|
||||||
|
/ \
|
||||||
|
/ \
|
||||||
|
' \
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
. *
|
||||||
|
` * . `
|
||||||
|
* .: TWA. ` *
|
||||||
|
/ 0 Vm * . . *
|
||||||
|
, . 'B * .
|
||||||
|
|' D * * *
|
||||||
|
'| O P
|
||||||
|
\\
|
||||||
|
. `=____-' '
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
|
||||||
|
.
|
||||||
|
__ ./ \.
|
||||||
|
__ ___.--'' \ _.--'\/
|
||||||
|
. ___,.-" '-, _--'' \ / /
|
||||||
|
./ \. /''\_.-. _-". .-. __.--'" '--.____ ___.. _-' '-/
|
||||||
|
/ \_.-' \-. ,-" \_-""" \-" "---' \--"
|
||||||
|
__/ ' ,----.___
|
||||||
|
/ _._________ _______..------'' ''-
|
||||||
|
.-' _.--''' ''--/-''''
|
||||||
|
.__ / _-'` /
|
||||||
|
/ '- / .-' __ /
|
||||||
|
__.. / '--" _.--. /-----""----,-,_____ -----O `
|
||||||
|
_.-' '--' __.-' `-__//-\___-' .' _ '. \ |\ `
|
||||||
|
' _,-.,___ ____,,,,..../ ,-'|+|,- .' |+| '-,-" | \
|
||||||
|
____ ,.----" \___,----''`` -,-- ---| | | \ .=
|
||||||
|
..--' '--/ . - | == == | _ _ | |
|
||||||
|
.--.___.' __ | === | |+| |+| | | `
|
||||||
|
-'-.___/ ` _ - _ _ __ ___ __|__|||___|_____________|___ _|_ _
|
||||||
|
` .- - '
|
||||||
|
` -' - '
|
||||||
|
' .
|
||||||
|
_ . - - " ` && '
|
||||||
|
' _..= - `
|
||||||
|
' _.` - '
|
||||||
|
` ` + + ` ` `
|
||||||
|
+|+|+ @&& , &&
|
||||||
|
' &&&@& ` `
|
||||||
|
` `
|
||||||
|
|
Loading…
Reference in New Issue