|
|
@@ -0,0 +1,251 @@
|
|
|
+using Accord.Video.FFMPEG;
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Drawing;
|
|
|
+using System.IO;
|
|
|
+using System.Linq;
|
|
|
+using System.Text.RegularExpressions;
|
|
|
+
|
|
|
+namespace D17.Bonus
|
|
|
+{
|
|
|
+ class Program
|
|
|
+ {
|
|
|
+ enum EnumType
|
|
|
+ {
|
|
|
+ Clay, StillWater, RunningWater
|
|
|
+ }
|
|
|
+
|
|
|
+ static int panY = 0;
|
|
|
+
|
|
|
+ static void Main(string[] args)
|
|
|
+ {
|
|
|
+ if (args.Length < 1) return;
|
|
|
+ if (File.Exists(args[0]) == false) return;
|
|
|
+ var file = File.OpenText(args[0]);
|
|
|
+
|
|
|
+ var reg = new Regex(@"[xy]=(\d+), [xy]=(\d+)[.][.](\d+)");
|
|
|
+
|
|
|
+ var map = new Dictionary<(int x, int y), EnumType>();
|
|
|
+ var rwater = new Dictionary<(int x, int y), (int x, int y)>();
|
|
|
+
|
|
|
+ int MAXY = 0;
|
|
|
+ int MINX = int.MaxValue;
|
|
|
+ int MINY = int.MaxValue;
|
|
|
+ int MAXX = 0;
|
|
|
+
|
|
|
+ do
|
|
|
+ {
|
|
|
+ var line = file.ReadLine();
|
|
|
+ if (line == null) break;
|
|
|
+
|
|
|
+ var result = reg.Match(line);
|
|
|
+
|
|
|
+ CreateMap(map, ref MAXY, ref MINX, ref MINY, ref MAXX, line, result);
|
|
|
+
|
|
|
+ } while (true);
|
|
|
+
|
|
|
+ // Start with river
|
|
|
+ rwater.Add((500, 0), (500, 0));
|
|
|
+ map.Add((500, 0), EnumType.RunningWater);
|
|
|
+
|
|
|
+ VideoFileWriter writer = new VideoFileWriter();
|
|
|
+
|
|
|
+ var width = MAXX - MINX + 2;
|
|
|
+ if (width % 2 == 1) width++;
|
|
|
+ var height = (width * 3) / 4;
|
|
|
+ if (height % 2 == 1) height++;
|
|
|
+ int tpy = (height * 2) / 3;
|
|
|
+
|
|
|
+ writer.Open("out.avi", width, height);
|
|
|
+
|
|
|
+ while (rwater.Count > 0)
|
|
|
+ {
|
|
|
+ if (rwater.Any(r => r.Key.y > panY + tpy)) panY++;
|
|
|
+ PrintMap(map, writer, MINX, MAXX, MAXY, width, height);
|
|
|
+
|
|
|
+ var enumer = rwater.FirstOrDefault();
|
|
|
+ var water = enumer.Key;
|
|
|
+ var source = enumer.Value;
|
|
|
+
|
|
|
+ if (map.ContainsKey((water.x, water.y + 1)) == false && water.y > MAXY)
|
|
|
+ {
|
|
|
+ rwater.Remove(water);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (map.ContainsKey((water.x, water.y + 1)) == false || map[(water.x, water.y + 1)] == EnumType.RunningWater)
|
|
|
+ {
|
|
|
+ RaindropsOnRoses(map, rwater, water);
|
|
|
+ }
|
|
|
+
|
|
|
+ else
|
|
|
+ {
|
|
|
+ ExpandToBothSides(map, rwater, water, source);
|
|
|
+
|
|
|
+ int maxX, minX;
|
|
|
+ bool canConvert = CanConvertToStillWater(map, rwater, water, source, out maxX, out minX);
|
|
|
+
|
|
|
+ if (canConvert)
|
|
|
+ ConvertToStillWater(map, rwater, water, source, maxX, minX);
|
|
|
+ else
|
|
|
+ rwater.Remove(water);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ PrintMap(map, writer, MINX, MAXX, MAXY, width, height);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void RaindropsOnRoses(Dictionary<(int x, int y), EnumType> map, Dictionary<(int x, int y), (int x, int y)> rwater, (int x, int y) water)
|
|
|
+ {
|
|
|
+ rwater.Remove(water);
|
|
|
+
|
|
|
+ var drop = (water.x, water.y + 1);
|
|
|
+ if (!rwater.ContainsKey(drop)) rwater.Add(drop, water);
|
|
|
+ map[drop] = EnumType.RunningWater;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void ConvertToStillWater(Dictionary<(int x, int y), EnumType> map, Dictionary<(int x, int y), (int x, int y)> rwater, (int x, int y) water, (int x, int y) source, int maxX, int minX)
|
|
|
+ {
|
|
|
+ for (var x = minX; x <= maxX; ++x)
|
|
|
+ {
|
|
|
+ var res = map.TryGetValue((x, water.y), out var type);
|
|
|
+ if (type == EnumType.RunningWater)
|
|
|
+ {
|
|
|
+ map[(x, water.y)] = EnumType.StillWater;
|
|
|
+ rwater.Remove((x, water.y));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ rwater.Remove(water);
|
|
|
+ if (!rwater.ContainsKey(source)) rwater.Add(source, (source.x, source.y - 1));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool CanConvertToStillWater(Dictionary<(int x, int y), EnumType> map, Dictionary<(int x, int y), (int x, int y)> rwater, (int x, int y) water, (int x, int y) source, out int maxX, out int minX)
|
|
|
+ {
|
|
|
+ bool canConvert = true;
|
|
|
+ maxX = 0;
|
|
|
+ minX = 0;
|
|
|
+ for (int x = water.x; canConvert; ++x) // left to right
|
|
|
+ {
|
|
|
+ maxX = x;
|
|
|
+ var res = map.TryGetValue((x, water.y), out var type);
|
|
|
+ if (res == false)
|
|
|
+ {
|
|
|
+ canConvert = false;
|
|
|
+ //rwater.TryAdd((x - 1, water.y), source);
|
|
|
+ }
|
|
|
+ else if (type == EnumType.Clay) break;
|
|
|
+ }
|
|
|
+ for (int x = water.x; canConvert; --x) // right to left
|
|
|
+ {
|
|
|
+ minX = x;
|
|
|
+ var res = map.TryGetValue((x, water.y), out var type);
|
|
|
+ if (res == false)
|
|
|
+ {
|
|
|
+ canConvert = false;
|
|
|
+ //rwater.TryAdd((x + 1, water.y), source);
|
|
|
+ }
|
|
|
+ else if (type == EnumType.Clay) break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return canConvert;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void ExpandToBothSides(Dictionary<(int x, int y), EnumType> map, Dictionary<(int x, int y), (int x, int y)> rwater, (int x, int y) water, (int x, int y) source)
|
|
|
+ {
|
|
|
+ var next = water;
|
|
|
+
|
|
|
+ while (true)
|
|
|
+ {
|
|
|
+ var last = next;
|
|
|
+ next = (last.x - 1, water.y);
|
|
|
+
|
|
|
+ var res = map.TryGetValue((next.x, water.y), out var value);
|
|
|
+ if (res && value == EnumType.Clay) break;
|
|
|
+
|
|
|
+ rwater.Remove(last);
|
|
|
+ if (!rwater.ContainsKey(next)) rwater.Add(next, source);
|
|
|
+ map[next] = EnumType.RunningWater;
|
|
|
+
|
|
|
+ var hasBottom = map.TryGetValue((next.x, next.y + 1), out var bottom);
|
|
|
+ if (hasBottom == false || bottom == EnumType.RunningWater)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ next = water;
|
|
|
+
|
|
|
+ while (true)
|
|
|
+ {
|
|
|
+ var last = next;
|
|
|
+ next = (last.x + 1, water.y);
|
|
|
+
|
|
|
+ var res = map.TryGetValue((next.x, water.y), out var value);
|
|
|
+ if (res && value == EnumType.Clay) break;
|
|
|
+
|
|
|
+ rwater.Remove(last);
|
|
|
+ if (!rwater.ContainsKey(next)) rwater.Add(next, source);
|
|
|
+ map[next] = EnumType.RunningWater;
|
|
|
+
|
|
|
+ var hasBottom = map.TryGetValue((next.x, next.y + 1), out var bottom);
|
|
|
+ if (hasBottom == false || bottom == EnumType.RunningWater)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void CreateMap(Dictionary<(int x, int y), EnumType> map, ref int MAXY, ref int MINX, ref int MINY, ref int MAXX, string line, Match result)
|
|
|
+ {
|
|
|
+ var c1 = int.Parse(result.Groups[1].Value);
|
|
|
+ var rmin = int.Parse(result.Groups[2].Value);
|
|
|
+ var rmax = int.Parse(result.Groups[3].Value);
|
|
|
+
|
|
|
+ if (line.StartsWith("x"))
|
|
|
+ {
|
|
|
+ if (c1 > MAXX) MAXX = c1;
|
|
|
+ if (c1 < MINX) MINX = c1;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (c1 > MAXY) MAXY = c1;
|
|
|
+ if (c1 < MINY) MINY = c1;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int i = rmin; i <= rmax; ++i)
|
|
|
+ {
|
|
|
+ if (line.StartsWith("x"))
|
|
|
+ {
|
|
|
+ if (i > MAXY) MAXY = i;
|
|
|
+ if (i < MINY) MINY = i;
|
|
|
+
|
|
|
+ map[(c1, i)] = EnumType.Clay;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (i > MAXX) MAXX = i;
|
|
|
+ if (i < MINX) MINX = i;
|
|
|
+ map[(i, c1)] = EnumType.Clay;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void PrintMap(Dictionary<(int x, int y), EnumType> map, VideoFileWriter writer, int MINX, int MAXX, int MAXY, int width, int height)
|
|
|
+ {
|
|
|
+ var bmp = new Bitmap(width, height);
|
|
|
+
|
|
|
+ for (int y = panY; y < height + panY; ++y)
|
|
|
+ {
|
|
|
+ int px = 0;
|
|
|
+ int py = y - panY;
|
|
|
+ for (int x = MINX - 1; x < MAXX + 1; ++x)
|
|
|
+ {
|
|
|
+ var res = map.TryGetValue((x, y), out var type);
|
|
|
+ if (res == false) bmp.SetPixel(px, py, Color.White);
|
|
|
+ else if (type == EnumType.Clay) bmp.SetPixel(px, py, Color.Maroon);
|
|
|
+ else if (type == EnumType.RunningWater) bmp.SetPixel(px, py, Color.Aqua);
|
|
|
+ else if (type == EnumType.StillWater) bmp.SetPixel(px, py, Color.Aquamarine);
|
|
|
+ px++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ writer.WriteVideoFrame(bmp);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|