Render dynamic weather for ASCII landscapes. Inspired and powered by ~iajrz's climate program.
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Initial commit

Diff 534eab9f

+822
+1
.gitignore
··· 1 + atmosphere
+131
forecast.go
··· 1 + package main 2 + 3 + import ( 4 + "os/exec" 5 + "strings" 6 + ) 7 + 8 + // Forecast is a representation of the current state of the weather. 9 + type Forecast struct { 10 + raw string 11 + time TimeOfDay 12 + cloudiness Cloudiness 13 + raininess Raininess 14 + visibility Visibility 15 + windiness Windiness 16 + } 17 + 18 + // NewForecast parses the output of ~iajrz's climate program. 19 + // TODO?: boolean randomize option to generate completely random weather as a demo mode? 20 + func NewForecast() (Forecast, error) { 21 + out, err := exec.Command("/home/iajrz/climate").Output() 22 + if err != nil { 23 + return Forecast{}, err 24 + } 25 + rawWeather := string(out) 26 + return Forecast{ 27 + raw: rawWeather, 28 + time: TimeOfDay(findSubstring(rawWeather, timeStrings)), 29 + cloudiness: Cloudiness(findSubstring(rawWeather, cloudStrings)), 30 + raininess: Raininess(findSubstring(rawWeather, rainStrings)), 31 + visibility: Visibility(findSubstring(rawWeather, visibilityStrings)), 32 + windiness: Windiness(findSubstring(rawWeather, windStrings)), 33 + }, nil 34 + } 35 + 36 + func (f Forecast) String() string { 37 + return f.raw 38 + } 39 + 40 + func findSubstring(s string, substrings []string) int { 41 + for i := range substrings { 42 + if strings.Contains(s, substrings[i]) { 43 + return i 44 + } 45 + } 46 + return 0 47 + } 48 + 49 + type TimeOfDay int 50 + 51 + const ( 52 + EarlyMorning TimeOfDay = iota 53 + Morning 54 + Afternoon 55 + Night 56 + ) 57 + 58 + var timeStrings = []string{ 59 + "early morning", 60 + "morning", 61 + "afternoon", 62 + "night", 63 + } 64 + 65 + type Cloudiness int 66 + 67 + const ( 68 + ClearSky Cloudiness = iota 69 + AlmostClear 70 + PartlyCloudy 71 + MostlyCloudy 72 + Cloudy 73 + ) 74 + 75 + var cloudStrings = []string{ 76 + "clear", 77 + "almost clear", 78 + "partly cloudy", 79 + "mostly cloudy", 80 + "cloudy", 81 + } 82 + 83 + type Raininess int 84 + 85 + const ( 86 + NoRain Raininess = iota 87 + Drizzle 88 + LightShower 89 + Shower 90 + HeavyShower 91 + ) 92 + 93 + var rainStrings = []string{ 94 + "no rain", 95 + "a drizzle", 96 + "a light shower", 97 + "a shower", 98 + "a heavy shower", 99 + } 100 + 101 + type Visibility int 102 + 103 + const ( 104 + NoFog Visibility = iota 105 + Haze 106 + Mist 107 + Fog 108 + HeavyFog 109 + ) 110 + 111 + var visibilityStrings = []string{ 112 + "visibility", 113 + "There's haze", 114 + "There's mist", 115 + "There's fog", 116 + "There's heavy fog", 117 + } 118 + 119 + type Windiness int 120 + 121 + const ( 122 + NoWind Windiness = iota 123 + Breeze 124 + StiffWind 125 + ) 126 + 127 + var windStrings = []string{ 128 + "no breeze", 129 + "light breeze", 130 + "stiff wind", 131 + }
+202
main.go
··· 1 + package main 2 + 3 + import ( 4 + tea "github.com/charmbracelet/bubbletea" 5 + "github.com/muesli/reflow/wordwrap" 6 + "github.com/muesli/termenv" 7 + "strings" 8 + "time" 9 + ) 10 + 11 + func main() { 12 + app := tea.NewProgram(model{startTime: time.Now()}) 13 + if err := app.Start(); err != nil { 14 + panic(err) 15 + } 16 + } 17 + 18 + // Queues the initial loading of the forecast and 19 + func (m model) Init() tea.Cmd { 20 + return tea.Batch(updateForecast, renderOften) 21 + } 22 + 23 + var updateForecast = func() tea.Msg { 24 + forecast, err := NewForecast() 25 + if err != nil { 26 + return err 27 + } 28 + return forecast 29 + } 30 + 31 + var updateForecastOften = tea.Tick(20*time.Second, func(t time.Time) tea.Msg { 32 + return updateForecast() 33 + }) 34 + 35 + var makeSceneMsg = func(f Forecast) func() tea.Msg { 36 + return func() tea.Msg { 37 + scene, err := NewScene("scene1", f) 38 + if err != nil { 39 + return err 40 + } 41 + return scene 42 + } 43 + } 44 + 45 + type fadeOut float32 46 + type fadeIn float32 47 + 48 + // Used to update the progress of a fade out transition. 49 + var tickFadeOut = tea.Every(time.Millisecond*100, func(t time.Time) tea.Msg { 50 + return fadeOut(0.05) 51 + }) 52 + 53 + // Used to update the progress of a fade in transition. 54 + var tickFadeIn = tea.Every(time.Millisecond*100, func(t time.Time) tea.Msg { 55 + return fadeIn(0.05) 56 + }) 57 + 58 + type renderTick struct{} 59 + 60 + // Used to update the screen at least once per second. 61 + var renderOften = tea.Tick(time.Second, func(t time.Time) tea.Msg { 62 + return renderTick{} 63 + }) 64 + 65 + // Update updates the internal state of the model and queues any events that 66 + // need to be scheduled. 67 + func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 68 + cmds := []tea.Cmd{} 69 + 70 + switch msg := msg.(type) { 71 + case tea.KeyMsg: 72 + switch msg.String() { 73 + case "ctrl+c", "ctrl+d", "q": 74 + return m, tea.Quit 75 + case "esc": 76 + // TODO: Menu for options. Maybe 100% randomized weather? 77 + } 78 + 79 + case tea.WindowSizeMsg: 80 + m.width = msg.Width 81 + m.height = msg.Height 82 + 83 + case Forecast: 84 + // Re-queue the forecast polling. 85 + cmds = append(cmds, updateForecastOften) 86 + 87 + // No action required if the forecast hasn't changed. 88 + if msg == m.Forecast { 89 + break 90 + } 91 + 92 + // If this isn't the initial startup forecast, we need to fade out the scene. 93 + if m.Forecast != (Forecast{}) { 94 + m.fading = true 95 + cmds = append(cmds, tickFadeOut) 96 + } 97 + 98 + // In all cases, we need to load a scene with the forecast. 99 + m.Forecast = msg 100 + cmds = append(cmds, makeSceneMsg(m.Forecast)) 101 + 102 + case Scene: 103 + if m.Scene.foreground == nil { 104 + m.Scene = msg 105 + } else { 106 + m.nextScene = msg 107 + } 108 + 109 + case fadeOut: 110 + m.fadeProgress += float32(msg) 111 + if m.fadeProgress >= 1.0 { 112 + m.fadeProgress = 1.0 113 + // To unfade, we need a Scene ready to rock. If we don't have one, stall. 114 + if m.nextScene.foreground == nil { 115 + cmds = append(cmds, tickFadeOut) 116 + break 117 + } 118 + 119 + m.Scene = m.nextScene 120 + m.nextScene = Scene{} 121 + cmds = append(cmds, tickFadeIn) 122 + break 123 + } 124 + cmds = append(cmds, tickFadeOut) 125 + 126 + case fadeIn: 127 + m.fadeProgress -= float32(msg) 128 + if m.fadeProgress <= 0.0 { 129 + m.fadeProgress = 0.0 130 + m.fading = false 131 + break 132 + } 133 + cmds = append(cmds, tickFadeIn) 134 + 135 + case error: 136 + m.err = msg 137 + 138 + case renderTick: 139 + cmds = append(cmds, renderOften) 140 + } 141 + 142 + return m, tea.Batch(cmds...) 143 + } 144 + 145 + // View lays out the screen and queries the Scene for its contents. 146 + // It also handles transitions when changing from Scene to Scene. 147 + func (m model) View() string { 148 + if m.err != nil { 149 + return m.err.Error() 150 + } 151 + 152 + weather := wordwrap.String(m.Forecast.String(), m.width) 153 + m.height -= strings.Count(weather, "\n") 154 + weather = strings.TrimSpace(weather) // Trim trailing newline 155 + 156 + output := "" 157 + lastStyle := Style{} 158 + profile := termenv.EnvColorProfile() 159 + 160 + // Center scene in terminal 161 + xOff := (m.Scene.Width - m.width) / 2 162 + yOff := (m.Scene.Height - m.height) / 2 163 + 164 + for y := yOff; y < yOff+m.height; y++ { 165 + for x := xOff; x < xOff+m.width; x++ { 166 + char, style := m.Scene.GetCell(x, y, int(time.Since(m.startTime).Seconds())) 167 + style = style.Convert(profile) 168 + 169 + // For wipe transitions, replace character with blank space. 170 + if float32((x-xOff)/2+y-yOff) < m.fadeProgress*float32(m.width/2+m.height) { 171 + char = ' ' 172 + style = Style{} 173 + } 174 + 175 + // Only output formatting escape codes if the style's changed since the last cell. 176 + if lastStyle.String() != style.String() { 177 + output += style.String() 178 + lastStyle = style 179 + } 180 + 181 + output += string(char) 182 + } 183 + output += "\n" 184 + } 185 + 186 + output += termenv.CSI + termenv.ResetSeq + "m" 187 + output += weather 188 + 189 + return output 190 + } 191 + 192 + type model struct { 193 + Forecast 194 + Scene 195 + err error 196 + nextScene Scene 197 + fading bool 198 + fadeProgress float32 199 + width int 200 + height int 201 + startTime time.Time 202 + }
+271
scene.go
··· 1 + package main 2 + 3 + import ( 4 + "bufio" 5 + "fmt" 6 + "github.com/muesli/termenv" 7 + "github.com/ojrac/opensimplex-go" 8 + "io" 9 + "os" 10 + "path" 11 + "unicode" 12 + ) 13 + 14 + // Style represents a pair of colors, foreground and background. 15 + type Style struct { 16 + fg termenv.Color 17 + bg termenv.Color 18 + } 19 + 20 + // Convert is a wrapper for termenv.Profile.Convert, it converts the foreground 21 + // and background colors of a Style to be within a given terminal's Profile. 22 + func (s Style) Convert(profile termenv.Profile) Style { 23 + return Style{ 24 + profile.Convert(s.fg), 25 + profile.Convert(s.bg), 26 + } 27 + } 28 + 29 + // String fills the Stringer interface and returns an ANSI escape code 30 + // formatted to produce text in the specified Style. 31 + func (s Style) String() string { 32 + if s.fg == nil { 33 + s.fg = termenv.NoColor{} 34 + } 35 + if s.bg == nil { 36 + s.bg = termenv.NoColor{} 37 + } 38 + // TODO: Looks like the background color is overriding the foreground color. What's the proper way to do this? 39 + return fmt.Sprintf("%s%s;%sm", termenv.CSI, s.fg.Sequence(false), s.bg.Sequence(true)) 40 + } 41 + 42 + // Scene holds all the necessary information to make a dynamic, weather-y ASCII 43 + // landscape. 44 + type Scene struct { 45 + foreground [][]rune 46 + background [][]rune 47 + windground [][]rune 48 + depth [][]rune 49 + forecast Forecast 50 + 51 + generator opensimplex.Noise 52 + 53 + Width int 54 + Height int 55 + } 56 + 57 + const ( 58 + fgPath = "foreground.txt" 59 + depthPath = "depth.txt" 60 + windPath = "wind.txt" 61 + 62 + earlyPath = "early.txt" 63 + morningPath = "morning.txt" 64 + afternoonPath = "afternoon.txt" 65 + nightPath = "night.txt" 66 + ) 67 + 68 + var timeToPath = []string{ 69 + earlyPath, 70 + morningPath, 71 + afternoonPath, 72 + nightPath, 73 + } 74 + 75 + // NewScene accepts a path to a folder containing foreground, windground, 76 + // depth, and background files and loads them from disk as needed to generate 77 + // imagery for the given forecast. 78 + func NewScene(scenePath string, forecast Forecast) (Scene, error) { 79 + var s Scene 80 + var err error 81 + 82 + s.forecast = forecast 83 + s.generator = opensimplex.NewNormalized(0) 84 + 85 + s.foreground, err = readRunesFromFile(path.Join(scenePath, fgPath)) 86 + if err != nil { 87 + return s, err 88 + } 89 + s.depth, err = readRunesFromFile(path.Join(scenePath, depthPath)) 90 + if err != nil { 91 + return s, err 92 + } 93 + 94 + s.windground, err = readRunesFromFile(path.Join(scenePath, windPath)) 95 + if err != nil { 96 + return s, err 97 + } 98 + 99 + bgPath := timeToPath[forecast.time] 100 + s.background, err = readRunesFromFile(path.Join(scenePath, bgPath)) 101 + if err != nil { 102 + return s, err 103 + } 104 + 105 + s.normalize() 106 + 107 + /*m.depthData = make([][]uint8, 0) 108 + for i := range depthRunes { 109 + m.depthData = append(m.depthData, make([]uint8, 0)) 110 + for j := range depthRunes[i] { 111 + m.depthData[i] = append(m.depthData[i], uint8(depthRunes[i][j])-48) 112 + } 113 + }*/ 114 + return s, nil 115 + } 116 + 117 + // normalize adjusts the foreground, windground, depth map, and background to 118 + // have matching widths and heights. It adjusts by cropping to the shortest 119 + // number of lines between the four and adjusts each line to the shortest of 120 + // any lines in any of the four maps. 121 + func (s *Scene) normalize() { 122 + scenes := [...][][]rune{s.foreground, s.windground, s.depth, s.background} 123 + 124 + s.Height = 999999999 125 + for i := range scenes { 126 + if len(scenes[i]) < s.Height { 127 + s.Height = len(scenes[i]) 128 + } 129 + } 130 + 131 + for i := range scenes { 132 + scenes[i] = scenes[i][:s.Height] 133 + } 134 + 135 + s.Width = 999999999 136 + for j := 0; j < s.Height; j++ { 137 + for i := range scenes { 138 + if len(scenes[i][j]) < s.Width { 139 + s.Width = len(scenes[i][j]) 140 + } 141 + } 142 + 143 + for i := range scenes { 144 + scenes[i][j] = scenes[i][j][:s.Width] 145 + } 146 + } 147 + 148 + // TODO: Change this to a map? Map can be iterated over and still referenced by name. 149 + s.foreground = scenes[0] 150 + s.windground = scenes[1] 151 + s.depth = scenes[2] 152 + s.background = scenes[3] 153 + } 154 + 155 + func (s Scene) GetCell(x, y, time int) (rune, Style) { 156 + fx := float64(x) 157 + fy := float64(y) 158 + ftime := float64(time) 159 + fcloud := float64(s.forecast.cloudiness) 160 + fwind := float64(s.forecast.windiness) 161 + frain := float64(s.forecast.raininess) 162 + 163 + char := ' ' 164 + style := Style{ 165 + termenv.ANSI256Color(255), 166 + termenv.ANSI256Color(232), 167 + } 168 + 169 + // Out of bounds 170 + if y >= s.Height || y < 0 { 171 + return char, Style{} 172 + } 173 + if x >= s.Width || x < 0 { 174 + return char, Style{} 175 + } 176 + 177 + depth := uint8(s.depth[y][x] - 48) 178 + 179 + // Char selection 180 + char = s.foreground[y][x] 181 + // Pull from wind map if the current cell is windswept. 182 + if fwind > 0.0 && s.generator.Eval3(fx/40+fwind/8*ftime*2, fy/20, ftime/5+fwind/10*ftime/2) > 0.6 { 183 + char = s.windground[y][x] 184 + } 185 + // Depth 9 is considered "transparent," so use the char from the background. 186 + if depth == 9 { 187 + char = s.background[y][x] 188 + } 189 + if frain > 0.0 && 0.1+frain/20 > s.generator.Eval3(fx+ftime*fwind*3, fy-ftime*5, ftime/15) { 190 + char = '|' 191 + if fwind > 1.0 { 192 + char = '/' 193 + } 194 + } 195 + 196 + // Style selection 197 + // Calculate fog 198 + fog := (depth - 1) + uint8(s.forecast.visibility-1)*2 199 + if s.forecast.visibility == 0 { 200 + fog = (depth + uint8(s.forecast.visibility)) / 2 201 + } 202 + // Don't show fog in the sky during the night. 203 + if depth == 9 && (s.forecast.time == Night || s.forecast.time == EarlyMorning) { 204 + fog = 0 205 + } 206 + 207 + // Add clouds 208 + cloudiness := 0 209 + if depth > 8 && fcloud/5 > s.generator.Eval3(fx/50+ftime/24+ftime*(fwind/8), fy/12, 1000+ftime/80) { 210 + cloudiness += 10 211 + } 212 + if depth > 6 && fcloud/7 > s.generator.Eval3(fx/20+ftime/14+ftime*(fwind/8), fy/6, 0+ftime/80) { 213 + cloudiness += 10 214 + } 215 + 216 + // At night, fog obscures distant objects with darkness. 217 + if s.forecast.time == Night || s.forecast.time == EarlyMorning { 218 + style.fg = termenv.ANSI256Color(255 - fog - fog/2) 219 + } else { 220 + // Merge fog with clouds during daytime. 221 + cloudiness += int(fog) 222 + } 223 + 224 + if cloudiness > 23 { 225 + cloudiness = 23 226 + } 227 + 228 + if cloudiness > 0 { 229 + style.bg = termenv.ANSI256Color(232 + cloudiness) 230 + } else { 231 + style.bg = termenv.ANSI256Color(232 + fog) 232 + } 233 + return char, style 234 + } 235 + 236 + // readRunesFromFile reads the file at the given path and reads it character by 237 + // character, line by line into a slice of slices of runes. 238 + func readRunesFromFile(filepath string) ([][]rune, error) { 239 + file, err := os.Open(filepath) 240 + if err != nil { 241 + return nil, err 242 + } 243 + 244 + br := bufio.NewReader(file) 245 + img := make([][]rune, 0) 246 + img = append(img, make([]rune, 0)) 247 + 248 + for { 249 + r, s, err := br.ReadRune() 250 + if err != nil { 251 + if err == io.EOF { 252 + break 253 + } 254 + return nil, err 255 + } 256 + 257 + // Invalid unicode characters are skipped. 258 + if r == unicode.ReplacementChar && s == 1 { 259 + continue 260 + } 261 + 262 + // Start a new slice on newline, otherwise append character to current slice. 263 + if r == '\n' { 264 + img = append(img, make([]rune, 0)) 265 + } else { 266 + img[len(img)-1] = append(img[len(img)-1], r) 267 + } 268 + } 269 + 270 + return img, nil 271 + }
+31
scene1/afternoon.txt
··· 1 + ---___ | __------'''' 2 + ---__ ,.---., __--- 3 + -__ ;/-'''-\; --' 4 + :|' `|: 5 + :|. ,|; --__ ___ 6 + __--- ;\-___-/; ''-- --___ 7 + '' , `'---'` -----____ 8 + ___---'' / '' 9 + '' ' \ 10 + ____-- , \ 11 + '' / 12 + / \ 13 + __-- ' \ 14 + \ 15 + / 16 + / 17 + / 18 + ' 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 +
+31
scene1/depth.txt
··· 1 + 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 2 + 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 3 + 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999899 4 + 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999988999999999999988888 5 + 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999889999999999999999999999999999999998888888888899999888888888888 6 + 9999999999999999999999999999999999998999999999999999999999999999999999999999999998888888888889999999999999999999999999888888888888888889998888888888888 7 + 9999999999999999999999999999999999888889999988888888999999988889999998889998888888888888888888888888899999888889999888888888888888888888888888888888888 8 + 9999999999999999999999999999999998888888888888888888888988888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888 9 + 9999999999999999999999999999998888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888555555555888 10 + 9999999999999999999999999999988888888888888888888888888888888888888888888888888888888888888888888888855555555555888888888855555555555555555555555555555 11 + 9999999999999999999999999988888888888888888888888888888888888888888888888888888888888888888888555555555555555555555535555555555555555555555555555555555 12 + 9999999999999998889999999888888888888888888888888888888888888888888888888888888888888888885555555555555555555555555355555555555555555555555555555555555 13 + 9999999999999988888899998888888888888888888888888888888888888888888888888888888888888885555555555555555555555555553555555555555555555555555555555555555 14 + 9999988889999888888888888888888888888888888888888888888888888888888888888888555558888833333333333333333333553333335555555555555555555555555555555555555 15 + 9888888888888888888888888888888888888888888888888888888888888888888888855555555555555333333333333333333333355555533555555555555555555555555555555555555 16 + 8888888888888888888888888888888888888555555558888888888888555555555555555555555555533333333333333333333333335555535355555555555555533333333333333333333 17 + 8888888888888888888888555588885555555555555555555555555555555555555555555555555555333333333333333333333333344444434434444444333333333333333333333333333 18 + 8888888888888888855555555555555555555555555555555555555555555555555555555555555444433333333333333333333333344444434444444433333333333333333333333333333 19 + 8888888855555555555555555555555555555555555555555555544444444444444444444444444444433333333333333333333333344433333333333333333333333333333333333333333 20 + 5555555555555555555555555555555555555555555544444444444444443333333333333333333333333333333333333333333333333333333333333333333333333111111111111111111 21 + 5555555555555555555555555555555555544444444444444444444444333333333333333333333333333333333333333333333333333333333333331111111111111111111111111111111 22 + 5555555555555555544444444444444444444444444444444444443333333333333333333333333333333333333333333333333333333311111111111111111111111111111111111111111 23 + 4444444444444444444444444444444444444444444444444333333333333333333333333333333333333333333333333333333331111111111111111111111111111111111111111111111 24 + 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333111111111111111111111111111111111111111111111111 25 + 3333333333333333333333333333333333333333333333333333333333333333333333333333333333332222222222222221111111111111111111111111111111111111111111111111111 26 + 2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222111111111111111111111111111111111111111111111111111111111111 27 + 2222222222222222222222222222222222221212222222222222222222222222222222222211111111111111111111111111111111111111111111111111111111111111111111111111111 28 + 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 29 + 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 30 + 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 31 + 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
+31
scene1/early.txt
··· 1 + . * 2 + ` * . ` 3 + * ` .: TWA. * 4 + * . . / 0 Vm * 5 + , . 'B . 6 + * * * |' D 7 + ` '| O P 8 + \\ 9 + . ' `=____-' 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 +
+31
scene1/foreground.txt
··· 1 + 2 + 3 + . 4 + __ ./ \. 5 + __ ___.--'' \ _.--'\/ 6 + . ___,.-" '-, _--'' \ / / 7 + ./ \. /''\_.-. _-". .-. __.--'" '--.____ ___.. _-' '-/ 8 + / \_.-' \-. ,-" \_-""" \-" "---' \--" 9 + __/ ' ,----.___ 10 + / _._________ _______..------'' ''- 11 + .-' _.--''' ''--/-'''' 12 + .__ / _-'` / 13 + / '- / .-' __ / 14 + __.. / '--" _.--. /-----""----,-,_____ -----O ' 15 + _.-' '--' __.-' `-__//-\___-' .' _ '. \ |\ ' 16 + ' _,-.,___ ____,,,,..../ ,-'|+|,- .' |+| '-,-" | \ 17 + ____ ,.----" \___,----''`` -,-- ---| | | \ .= 18 + ..--' '--/ . - | == == | _ _ | | 19 + .--.___.' __ | === | |+| |+| | | ' 20 + -'-.___/ ' _ - _ _ __ ___ __|__|||___|_____________|___ _|_ _ 21 + ' .- - ' 22 + ' -' - ' 23 + ' . 24 + _ . - - " ' && ' 25 + ' _..= - ' 26 + ' _.' - ' 27 + ' ' * * ' ' ' 28 + *|*|* @&& , && 29 + ' &&&@& ' ' 30 + ' ' 31 +
+31
scene1/morning.txt
··· 1 + '--__ | __------ 2 + --__ ,.---., __--- 3 + -__ ;/-'''-\; --' 4 + :|' `|: 5 + :|. ,|; --__ ___ 6 + ;\-___-/; ''-- ____ 7 + , `'---'` '----_____ 8 + / -------____ 9 + ' \ ____ 10 + , \ '''-------- 11 + / 12 + / \ 13 + / \ 14 + ' \ 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 +
+31
scene1/night.txt
··· 1 + . * 2 + ` * . ` 3 + * .: TWA. ` * 4 + / 0 Vm * . . * 5 + , . 'B * . 6 + |' D * * * 7 + '| O P 8 + \\ 9 + . `=____-' ' 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 +
+31
scene1/wind.txt
··· 1 + 2 + 3 + . 4 + __ ./ \. 5 + __ ___.--'' \ _.--'\/ 6 + . ___,.-" '-, _--'' \ / / 7 + ./ \. /''\_.-. _-". .-. __.--'" '--.____ ___.. _-' '-/ 8 + / \_.-' \-. ,-" \_-""" \-" "---' \--" 9 + __/ ' ,----.___ 10 + / _._________ _______..------'' ''- 11 + .-' _.--''' ''--/-'''' 12 + .__ / _-'` / 13 + / '- / .-' __ / 14 + __.. / '--" _.--. /-----""----,-,_____ -----O ` 15 + _.-' '--' __.-' `-__//-\___-' .' _ '. \ |\ ` 16 + ' _,-.,___ ____,,,,..../ ,-'|+|,- .' |+| '-,-" | \ 17 + ____ ,.----" \___,----''`` -,-- ---| | | \ .= 18 + ..--' '--/ . - | == == | _ _ | | 19 + .--.___.' __ | === | |+| |+| | | ` 20 + -'-.___/ ` _ - _ _ __ ___ __|__|||___|_____________|___ _|_ _ 21 + ` .- - ' 22 + ` -' - ' 23 + ' . 24 + _ . - - " ` && ' 25 + ' _..= - ` 26 + ' _.` - ' 27 + ` ` + + ` ` ` 28 + +|+|+ @&& , && 29 + ' &&&@& ` ` 30 + ` ` 31 +