using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using System.Drawing; using System.IO; using static System.Linq.Enumerable; namespace MusicGameMovieMaker { class Note : IComparable { public int Time { get; } public int Line { get; } public Note(int time, int line) { this.Time = time; this.Line = line; } public int CompareTo(Note other) { var p = this.Time.CompareTo(other.Time); if (p != 0) { return p; } return this.Line.CompareTo(other.Line); } } static class Program { const int height = 480;//画面の高さ const int width = 640;//画面の幅 const int noteSize = 20;//各ノーツのサイズ const double distanceBase = 7.5;//HiSpeed x1.0での表示距離 const int border = 20;//判定の位置 const int maxDisplay = height - border;//表示される限界の位置 const int horizonDistance = 75;//各列の幅 const double left = width / 2 - horizonDistance * 1.5;//左端の列 //C++のstd::partition_pointに似た挙動をする関数(詳しくはcpprefjpを見よ) static int PartitionPoint(List list, Func pred) { var min = 0; var max = list.Count; while (max - min > 1) { var mid = (min + max) / 2; if (pred(list[mid])) { min = mid; } else { max = mid; } } return max; } static (int, int) ReadTuple2(TextReader reader) { var ar = reader.ReadLine().Split(' ').Select(int.Parse).ToArray(); return (ar[0], ar[1]); } //BPMのデータを読み込む static List ReadBPMList(string path) { using (var stream = new StreamReader(path)) { var (N, maxTime) = ReadTuple2(stream); var ret = new List(); var impl = new List<(int Time, int BPM)>(); foreach(var i in Range(0, N)) { impl.Add(ReadTuple2(stream)); } impl.Add((maxTime, int.MaxValue)); for (var (index, time) = (0, 0); time < maxTime; ++time) { if (time == impl[index + 1].Time) { ++index; } ret.Add(impl[index].BPM); } return ret; } } //32分刻みでのノーツのデータを読み込む static List ReadNoteList(string path) { using(var stream = new StreamReader(path)) { var ret = new List(); var N = int.Parse(stream.ReadLine()); foreach(var i in Range(0, N)) { var (time, line) = ReadTuple2(stream); ret.Add(new Note(time, line)); } ret.Sort(); return ret; } } static void Main(string[] args) { var BPMList = ReadBPMList("bpmdata.txt"); var noteData = ReadNoteList("notedata.txt"); var minIndex = 0; var bpmIndex = 0; var hispeed = 1.5; var line = 0.0; var (changeTime, realBPMList) = BPMCheck(BPMList); minIndex = SaveImage(noteData, minIndex, hispeed, line, 0); // 1.0/60秒ずつ進むことを仮定する(ほぼ誤差の範囲だが厳密には60FPSにはならないことに注意せよ) const double minTime = 1.0 / 60; var realTime = 0.0; for (var frame = 1; minIndex < noteData.Count; ++frame) { var time = minTime; while (time != default) { // BPMがxのときy秒で8*x*y/60回だけ32分を刻むことができる line += Math.Min(changeTime[bpmIndex] - realTime, time) * realBPMList[bpmIndex] * 8 / 60; time -= Math.Min(changeTime[bpmIndex] - realTime, time); if (time != default) { ++bpmIndex; } } realTime += minTime; minIndex = SaveImage(noteData, minIndex, hispeed, line, frame); } } static Pen Pen = new Pen(Color.Red, noteSize / 4); private static int SaveImage(List noteData, int minIndex, double hispeed, double line, int index) { // ノーツの中心と判定ラインの距離を返す double GetPosition(Note note) { return (note.Time - line) * hispeed * distanceBase; } var pIndex = PartitionPoint(noteData, n => GetPosition(n) < maxDisplay + noteSize); using (var bitmap = new Bitmap(width, height)) { using (var g = Graphics.FromImage(bitmap)) { foreach (var i in Range(0, 4)) { g.DrawRectangle(Pens.Red, (int)(left + (horizonDistance * i) - noteSize), border - noteSize, 2 * noteSize, 2 * noteSize); } for (var i = minIndex; i < pIndex; ++i) { var pos = GetPosition(noteData[i]); if (pos < -noteSize - border) { minIndex = i + 1; } else { var noteLine = noteData[i].Line; g.FillRectangle(Brushes.Blue, (int)(left + (horizonDistance * noteLine) - noteSize), (int)(pos + border - noteSize), 2 * noteSize, 2 * noteSize); g.DrawRectangle(Pen, (int)(left + (horizonDistance * noteLine) - noteSize), (int)(pos + border - noteSize), 2 * noteSize, 2 * noteSize); } } } bitmap.Save($"image/{index + 1}.png", System.Drawing.Imaging.ImageFormat.Png); return minIndex; } } // BPMが変わる時間とそれに対応するBPMのリストをセットを返す private static (List Change, List RealBPMList) BPMCheck(List BPMList) { var bpmChange = new List(); var realBpmList = new List { BPMList[0] }; var prev = BPMList[0]; var time = 60.0 / (8.0 * prev); foreach (var bpm in BPMList.Skip(1)) { if (bpm != prev) { bpmChange.Add(time); realBpmList.Add(bpm); } prev = bpm; time += 60.0 / (8.0 * bpm); } bpmChange.Add(double.MaxValue); return (bpmChange, realBpmList); } } }