// REFERENCE: Curses.dll class Car { mutable pos : int; mutable crash_state : bool; win : Curses.Window; dc : Curses.DrawingContext; public this (dc : Curses.DrawingContext) { this.dc = dc; win = dc.Window; pos = win.Width / 2; } public Draw () : void { mutable y = win.Height - 7; def down (off) { dc.Move (pos + off, y); ++y; }; dc.ChangeAttrs (Curses.Attributes.A_BOLD, true); if (crash_state) { dc.Foreground = Curses.Color.Yellow; crash_state = false; } else dc.Foreground = Curses.Color.Red; down (2); dc.Add ("/\\"); down (2); dc.Add ("||"); down (1); dc.Add ("/"); dc.Foreground = Curses.Color.Blue; dc.Add ("%%"); dc.Foreground = Curses.Color.Red; dc.Add("\\"); down (0); dc.Add ("[]"); dc.Foreground = Curses.Color.Yellow; dc.Add ("%%"); dc.Foreground = Curses.Color.Red; dc.Add("[]"); down (2); dc.Add ("~~"); } public Move (ch : int) : void { def move (delta) { def new_pos = pos + delta; when (new_pos >= 0 && new_pos <= win.Width - 7) { pos = new_pos; Game.NeedUpdate (); } }; if (ch == Curses.KeyCode.KEY_LEFT || ch == ('z' :> int) || ch == ('Z' :> int)) move (-1) else if (ch == Curses.KeyCode.KEY_RIGHT || ch == ('x' :> int) || ch == ('X' :> int)) move (+1) else (); } public Position : int { get { pos + 3 - win.Width / 2 } } public Crash () : void { crash_state = true; } } class Road { mutable current : int; mutable target : int; mutable last_update : int; mutable forward_pos : int; positions : array [int]; dc : Curses.DrawingContext; public this (dc : Curses.DrawingContext) { this.dc = dc; positions = array (dc.Window.Height); forward_pos = 1000000000; } public Draw () : void { def draw_line (y) { dc.Move (positions [y] + dc.Window.Width / 2 - 20, y); dc.Foreground = Curses.Color.Green; dc.Add ("..----="); dc.Foreground = Curses.Color.White; dc.Add ("#"); dc.Foreground = Curses.Color.Black; dc.Add ("###########"); when (((y + forward_pos) / 3) % 2 == 1) dc.Foreground = Curses.Color.White; dc.Add ("#"); dc.Foreground = Curses.Color.Black; dc.Add ("###########"); dc.Foreground = Curses.Color.White; dc.Add ("#"); dc.Foreground = Curses.Color.Green; dc.Add ("=----.."); }; dc.ChangeAttrs (Curses.Attributes.A_BOLD, true); for (mutable i = 0; i < dc.Window.Height; ++i) draw_line (i); } public Update () : void { when (Game.Ticks - last_update > 5) { last_update = Game.Ticks; when (current == target) target = Game.Random.Next (-30, 30); when (Game.Random.Next (3) == 0) if (current < target) ++current else --current; for (mutable i = positions.Length - 1; i > 0; --i) positions[i] = positions[i - 1]; positions[0] = current; --forward_pos; Game.NeedUpdate (); } } public CrashTest (pos : int) : bool { def y = dc.Window.Height - 3; def x = positions[y]; (pos < x - 12 || pos > x + 12) } } module Game { Main () : void { Init (); MainLoop (); } Init () : void { unless (Curses.Setup.Initialize ()) throw System.Exception ("cannot initialize curses"); Curses.Misc.ReportTimeouts = true; Curses.Misc.DockCursor (); def time = System.TimeSpan (100000L); // 1/100th sec def t = Curses.Timeout (time, time); t.Fired += (fun (_, _) { ++Ticks; }); Random = System.Random (); dc = Curses.Window.Screen.DrawingContext; car = Car (dc); road = Road (dc); need_update = true; } public NeedUpdate () : void { need_update = true; } MaybeUpdate () : void { when (need_update) { need_update = false; dc.Clear (); road.Draw (); car.Draw (); dc.Refresh (); } } MainLoop () : void { def handle_char (ch) { car.Move (ch); when (ch == 27 || ch == ('q' :> int) || ch == ('Q' :> int)) Curses.Setup.Exit (0); when (ch != -1 && ch != 0) { handle_char (Curses.Input.GetNextChar ()); } }; while (true) { MaybeUpdate (); handle_char (Curses.Input.GetNextChar ()); road.Update (); when (road.CrashTest (car.Position)) car.Crash (); } } // private variables mutable need_update : bool; mutable car : Car; mutable road : Road; mutable dc : Curses.DrawingContext; public mutable Ticks : int; public mutable Random : System.Random; }