Launch rocket with countdown and acceleration in stdout
Simulate the countdown of a rocket launch from 5 down to 0 seconds, and then display the moving, accelerating rocket on the standard output device as a simple ASCII art animation.
- Task
8080 Assembly
This program runs under CP/M. It assumes a terminal that understands the ASCII CR, LF, and FF control codes (in practice this will be all of them). If your machine has blinkenlights (e.g. an Altair 8800), the top half of the address bus will additionally show a countdown going from 5 to 0 lights.
The 8080 is assumed to be clocked at 2 Mhz, which was the rated maximum
for the earlier chips. If you overclock your Altair, or run this program on a later Z80-based machine,
the countdown will go faster. In a non-cycle-accurate emulator, the program will run in an instant.
In SIMH, the command d clock 2000
might help.
<lang 8080asm> org 100h lxi d,rocket ; Clear screen and print rocket mvi c,9 call 5 mvi a,0F8h ; Five lights lxi h,number cdwn: lxi d,count ; Print countdown call sout call sec ; Wait a second dcr m ; Decrease number add a ; Turn off a light jnz cdwn ; Again (if not zero yet) lxi d,flame ; Print flame call sout mvi c,24 ; Lines lxi h,20000 ; Liftoff timer lift: lxi d,newln call sout call waithl lxi d,-800 dad d dcr c jnz lift ret sout: push psw ; Print string [DE] preserving registers push b push h mvi c,9 call 5 jmp rsto ;;; Busy wait counting down HL. ;;; 52 + 24*HL cycles = 26 + 12*HL nanoseconds waithl: push psw ; 11 cycles push h ; 11 cycles spin: dcx h ; 5 cycles ---- mov a,h ; 5 cycles ora l ; 4 cycles loop jnz spin ; 10 cycles ---- pop h ; 10 cycles pop psw ; 10 cycles ret ; 10 cycles ;;; Busy wait approximately one second (assuming 2Mhz clock) ;;; Pattern in A on address bus (will appear on blinkenlights ;;; if there are any). sec: push psw push b push h mov b,a ; Set up for pattern lxi h,25000 ; 25.000 * 80 = 2.000.000 cycles ~= 1 sec swait: ldax b ; 56 cycles, and hold pattern on blinkenlights ldax b ; ... ldax b ldax b ldax b ldax b ldax b ldax b dcx h ; 5 cycles mov a,h ; 5 cycles ora l ; 4 cycles jnz swait ; 10 cycles rsto: pop h pop b pop psw ret ;;; Rocket rocket: db 12, 10,10,10,10,10, 10,10,10,10,10 db ' |',13,10 db ' / ',92,13,10 db ' / _ ',92,13,10 db ' |.o ',39,'.|',13,10 db ' |',39,'._.',39,'|',13,10 db ' | |',13,10 db ' ,',39,'| | |`.',13,10 db ' / | | | ',92,13,10 db ' |,-',39,'--|--',39,'-.|' ,13,10,'$' flame: db ' ******* ',13,10 db ' .*****.',13,10 db ' .***.',13,10 db ' .*.',13,10 db ' .',13,10,'$' count: db ' _____ ' number: db '5 _____',13,'$' newln: db 13,10,'$'</lang>
FreeBASIC
<lang freebasic>#define sky 32 dim as string rocket(1 to 7) = { " ^",_
" / " + chr(92),_ " | |",_ " | H |",_ " | |",_ " /|/ \|" + chr(92),_ "/_||.||_" + chr(92) }
dim as double h = 0, dhdt = 0, d2hdt2 = 0.3, t, countdown = 5, dt dim as integer ih, j, cut, lines cls while countdown > 0
t = timer print countdown, while timer < t + 1 wend countdown -= 1
wend cls
while h < sky
lines = 0 t = timer ih = int(h) for j = 1 to sky - 7 - h lines += 1 print next j if ih < sky - 7 then cut = 6 else cut = sky - ih - 1 for j = 7-cut to 7 lines += 1 print rocket(j) next j for j = sky-ih+1 to sky lines += 1 print " *** " next j if lines < sky then print " ***" print "-------------------" h += dhdt * dt dhdt += d2hdt2 * dt while t + 1./30 > timer wend cls dt = timer - t
wend</lang>
Go
...though my rocket is a bit fancier :)
<lang go>package main
import (
"fmt" "time"
)
const rocket = `
/\ ( ) ( ) /|/\|\ /_||||_\
`
func printRocket(above int) {
fmt.Print(rocket) for i := 1; i <= above; i++ { fmt.Println(" ||") }
}
func cls() {
fmt.Print("\x1B[2J")
}
func main() {
// counting for n := 5; n >= 1; n-- { cls() fmt.Printf("%d =>\n", n) printRocket(0) time.Sleep(time.Second) }
// ignition cls() fmt.Println("Liftoff !") printRocket(1) time.Sleep(time.Second)
// liftoff ms := time.Duration(1000) for n := 2; n < 100; n++ { cls() printRocket(n) time.Sleep(ms * time.Millisecond) if ms >= 40 { ms -= 40 } else { ms = 0 } }
}</lang>
Julia
<lang julia>rocket() = println(" /..\\\n |==|\n | |\n | |\n",
" | |\n /____\\\n | |\n |SATU|\n | |\n", " | |\n /| | |\\\n / | | | \\\n /__|_|__|__\\\n /_\\/_\\\n")
exhaust() = println(" *****") cls() = print("\x1B[2J") curup(n) = print("\e[$(n)A") curdown(n) = print("\e[$(n)B")
function countdown(secs)
print("Countdown...T minus ") for i in secs:-1:1 print(i, "... ") sleep(1) end print("LIFTOFF!")
end
engineburn(rows) = (println("\n"); for i in 1:rows exhaust(); sleep(0.9^i); end)
testrocket() = (cls(); rocket(); curup(16); countdown(5); curdown(13); engineburn(30))
testrocket()
</lang>
- Output:
Countdown...T minus 5... 4... 3... /..\ |==| | | | | | | /____\ | | |SATU| | | | | /| | |\ / | | | \ /__|_|__|__\ /_\/_\
Nim
Using terminal
module from standard library rather the escape codes.
<lang Nim>import os, math, terminal
proc rocket() =
echo " /..\\\n |==|\n | |\n | |\n", " | |\n /____\\\n | |\n |SATU|\n | |\n", " | |\n /| | |\\\n / | | | \\\n /__|_|__|__\\\n /_\\/_\\\n"
proc exhaust() =
echo " *****"
proc countDown(secs: Natural) =
stdout.write "Countdown...T minus " stdout.flushFile for i in countdown(secs, 1): stdout.write i, "... " stdout.flushFile os.sleep(1000) stdout.write "LIFTOFF!" stdout.flushFile
proc engineBurn(rows: Natural) =
echo '\n' for i in 1..rows: exhaust() sleep (0.9^i * 1000).toInt
proc testRocket() =
eraseScreen() rocket() cursorUp(16) countDown(5) cursorDown(13) engineBurn(30)
testRocket()</lang>
- Output:
Same as Julia program output.
Pascal
Pascal includes the standard routine page. However its specific behavior is implementation-defined, i. e. up to the compiler vendor. An implementation of page could, for instance, translate into \014 (ASCII form feed) or \033[2J\033[H, the ANSI escape sequence blanking the entire screen and moving the cursor to the upper left-hand corner. If the terminal interprets them properly, the following program could satisfy the task’s requirements: <lang pascal>program launchRocketWithCountdownAndAccelerationOnOutput(output);
const screenWidth = 80;
type wholeNumber = 0..maxInt; natural = 1..maxInt;
var n: wholeNumber;
{ Pascal, as defined by ISO standard 7185, does not provide any facilities to time actions. Nevertheless, most compiler vendors invented their own `sleep` or `delay` procedures. } procedure sleep(n: natural); begin { Yes, in Pascal an empty statement is valid. } { There is really nothing after `do`. } for n := n downto 1 do end;
procedure drawRocket(column: natural); begin { `page` is shorthand for `page(output)`, } { as is `writeLn(…)` shorthand for `writeLn(output, …)`. } page; { It should be an easy feat to adapt this to contain ASCII only. } writeLn(' ':column, '🙮') end;
{ === MAIN ============================================================= } begin for n := 5 downto 0 do begin drawRocket(1); writeLn; writeLn(n); sleep(5318008) end;
n := 1; while n < screenWidth do begin n := round(n * 1.5); sleep(58008); drawRocket(n) end end.</lang>Note, conventionally main engines (liquid fuel) are ignited a few seconds prior liftoff and SRBs are ignited just at/before liftoff, so the graphical depiction of exhaust visible at T−5 is about accurate.
Perl
<lang perl>use strict; use warnings; use Time::HiRes qw(sleep);
$SIG{INT} = \&clean_up;
my ($rows,$cols) = split /\s+/, qx/stty size/; my $v = $rows - 9; my $h = int $cols / 2 - 4; my $a = 0; my $i = 0; my $j = 0; my $t = -5; my $start = $^T;
my @r = (q' |', q' /_\\', q' | |', q' /| |\\', q'/_|_|_\\'); my @x = (q' (/|\\)', q' {/|\\}', q' \\|/', q' |'); my @y = (q' /|\\', q' // \\\\', q' (/ \\)', q' \\ /');
my $sp = ' ' x $h;
my $altitude = 0; my $velocity = 0;
my @pal = ("\e[38;2;255;0;0m", "\e[38;2;255;255;0m", "\e[38;2;255;155;0m"); use constant W => "\e[38;2;255;255;255m";
print "\e[?25l\e[48;5;232m";
while (1) {
if ($t >= 0) { $velocity = 5 * $t**2; $altitude = $velocity * $t / 2; $a = int 0.5 + ($altitude / $v); } clean_up() if $a > $rows + 5;
print "\e[H\e[J", ("\n")x$v, W, $sp, join("\n$sp",@r), "\n", $sp;
if ($t < 0) { print q'\\/ \\/' } else { exhaust( $pal[$i], $a ) } print W, "\n", '▔' x $cols, $pal[1]; printf "\n Time: T %-4s %9s Altitude: %6.2f meters Velocity: %5.1f m/sec\n", $t < 0 ? '- ' . int 0.5 + abs $t : '+ ' . int 0.5 + $t, $t < 0 ? : $a == 0 ? 'Ignition!' : 'Lift-off!', sprintf('%.2f', $altitude), sprintf('%.1f', $velocity);
++$i; $i %= 3; ++$j; $j %= 2; $t = (time() - $start - 5); sleep .05;
}
sub exhaust {
my($clr, $a) = @_; print q'\\/', $clr, q'/^\\', W, q'\\/'; return if $a == 0; if ($a < 4) { print "\n", $clr, $sp, ( $j ? join("\n$sp", @x[0..$a-1]) : join("\n$sp", @y[0..$a-1]) ) } else { print "\n", $clr, $sp, ( $j ? join("\n$sp",@x) : join("\n$sp",@y) ); print "\n" x ($a-4); }
}
- clean up on exit, reset ANSI codes, scroll, re-show the cursor & clear screen
sub clean_up { print "\e[0m", ("\n")x50, "\e[H\e[J\e[?25h"; exit(0) }</lang>
Phix
sequence rocket = split(""" /\ / \ | | | | /|/\|\ /_||||_\ ""","\n") integer lines = 0, l = 0, t = 10 atom s = 0.25 while length(rocket) do if t>0 then rocket[$] = sprintf("T minus %d... ",t) -- allow console resize during countdown: lines = video_config()[VC_SCRNLINES] if l!=lines-7 then l = 0 end if else if l=1 then rocket = rocket[2..$] else l -= (length(rocket)>6) if length(rocket)<12 then rocket = append(rocket," ** ") elsif length(rocket)=12 then rocket = append(rocket," ") end if end if s *= 0.95 end if if l=0 then clear_screen() cursor(NO_CURSOR) l = lines-7 end if position(l,1) puts(1,join(rocket,"\n")) sleep(s) t -= 1 if t=0 then rocket = rocket[1..$-1] end if end while cursor(BLOCK_CURSOR)
Racket
<lang racket>#lang racket
(define rocket #<<EOF
/\ ( ) ( ) /|/\|\
/_||||_\ EOF
)
(define (cls) (displayln "\x1B[2J"))
(define (print-rocket n)
(displayln rocket) (for ([i (in-range n)]) (displayln "")))
(for ([i (in-range 5 0 -1)])
(cls) (printf "~a =>\n" i) (print-rocket 0) (sleep 1))
(cls) (printf "Liftoff!\n") (print-rocket 1) (sleep 1)
(for/fold ([ms 1000] #:result (void)) ([n (in-range 2 100)])
(cls) (print-rocket n) (sleep (/ ms 1000)) (if (>= ms 40) (- ms 40) 0))</lang>
Raku
(formerly Perl 6)
Uses ANSI graphics. Works best in a 24 bit ANSI terminal at least 80x24, though bigger is better.
This is a very simple simulation. It assumes a constant ~2G+ acceleration in a gravitational field; so net +10 meters per second². It completely neglects the effects of air friction, impulse, snap, crackle & pop and has an unrealistically clean fuel burn (no contrail). It does however (unlike most of the entries at this time) start at the base of the terminal (on the ground) and go up, rather than starting at the top and dropping a contrail. It calculates and displays an accurate displacement and velocity over time and uses those to scale its vertical screen displacement.
The motion is a little "notchy" as the vertical resolution in a terminal is rather low. Exits after the rocket leaves the visible area of the terminal. See the example animated gif
<lang perl6>signal(SIGINT).tap: { cleanup() }
my ($rows,$cols) = qx/stty size/.words; my $v = floor $rows - 9; my $h = floor $cols / 2 - 4; my $a = 0; my $start = now; my $t = -5; my $i = 0; my $j = 0;
my @r = Q' |', Q' /_\', Q' | |', Q' /| |\', Q'/_|_|_\'; my @x = Q' (/|\)', Q' {/|\}', Q' \|/', Q' |'; my @y = Q' /|\', Q' // \\', Q' (/ \)', Q' \ /'; #'
my $sp = ' ' x $h;
my $altitude = 0; my $velocity = 0;
my @pal = "\e[38;2;255;0;0m", "\e[38;2;255;255;0m", "\e[38;2;255;155;0m"; constant \W = "\e[38;2;255;255;255m";
print "\e[?25l\e[48;5;232m";
loop {
if $t >= 0 { $velocity = 5 * $t²; $altitude = $velocity * $t / 2; $a = ($altitude / $v).round; } cleanup() if $a > $rows + 5;
print "\e[H\e[J", "\n" xx $v, W, $sp, @r.join("\n$sp"), "\n", $sp; if $t < 0 { print Q'\/ \/' } else { exhaust( @pal[$i], $a ) } print W, "\n", '▔' x $cols, @pal[1]; printf "\n Time: T %-4s %9s Altitude: %6.2f meters Velocity: %5.1f m/sec\n", $t < 0 ?? "- {$t.round.abs}" !! "+ {$t.round}", $t < 0 ?? !! $a == 0 ?? 'Ignition!' !! 'Lift-off!', $altitude.round(.01), $velocity.round(.1);
++$i; $i %= 3; ++$j; $j %= 2; $t = (now - $start - 5); sleep .05;
}
sub exhaust ($clr, $a) {
print Q'\/', $clr, Q'/^\', W, Q'\/'; #' return if $a == 0; if $a < 4 { print "\n", $clr, $sp, ( $j ?? @x[^$a].join("\n$sp") !! @y[^$a].join("\n$sp")) } else { print "\n", $clr, $sp, ( $j ?? @x.join("\n$sp") !! @y.join("\n$sp")); print "\n" x $a - 4; }
}
- clean up on exit, reset ANSI codes, scroll, re-show the cursor & clear screen
sub cleanup () { print "\e[0m", "\n" xx 50, "\e[H\e[J\e[?25h"; exit(0) }</lang>
- Sample output:
See rocket-perl6.gif (offsite animated gif image)
REXX
This REXX program hard-codes the name of the (OS) command to clear the terminal screen (CLS). <lang rexx>/*REXX pgm does a countdown and then display the launching of a rocket (ASCII animation)*/ parse arg cntDown . /*obtain optional argument from the CL.*/ if cntDown== | cntDown=="," then cntDown= 5 /*Not specified? Then use the default.*/
@. = /* [↓] glyphs for the rocket ship. */ @.1= ' /\ ' @.2= ' | | ' @.3= ' | | ' @.4= ' | | ' @.5= ' /|/\|\ ' @.6= '/_||||_\' do rs=1 while @.rs\== /*determine size of the rocket (height)*/ end /*rs*/
rs= rs - 1 /*the true rocket size (height). */ cls= 'CLS' /*the command used to clear the screen.*/ parse value scrsize() with sd sw . sw= sw - 1 /*usable screen width on some systems. */ sd= sd - 3 /* " " depth " " " */ air= sd - 1 - rs /*"amount" of sky above the rocket. */ say
do j=cntDown by -1 to 1 /* [↓] perform countdown; show rocket.*/ cls /*use this command to clear the screen.*/ say right(j, 9) 'seconds' /*display the amount of seconds to go. */ call sky /*display the sky above the rocket. */ call rocket /*display the rocket (on the ground). */ call delay 1 /*wait one second during the countdown.*/ end /*j*/
say left(,9) "liftoff!" /*announce liftoff of the rocket. */ cls /*use this command to clear the screen.*/ call sky /*display the sky above the rocket. */ period= 1 dt= period / sd /*acceleration (period is decreasing).*/ call rocket /*display the rocket (in flight). */
do sd+4; say /*"make" the rocket appear to fly. */ period= format(period-period*dt,,3) /*calculate the decrease in the period.*/ call delay max(period, .001) /*wait for a diminishing time interval.*/ end /*sd+4*/
exit /*stick a fork in it, da rocket is gone*/
/*──────────────────────────────────────────────────────────────────────────────────────*/
sky: do air; say; end /*air*/; return /*display the sky above the rocket. */
rocket: do ship=1 for rs; say left(, sw%2 - 5) @.ship; end /*ship*/; return</lang>
This REXX program makes use of SCRSIZE REXX program (or
BIF) which is used to determine the screen
width and depth of the terminal (console). Some REXXes don't
have this BIF.
The SCRSIZE.REX REXX program is included
here ───► SCRSIZE.REX.
Rust
<lang rust> use std::{thread, time};
fn print_rocket(above: u32) { print!( " oo
oooo oooo oooo
"); for _num in 1..above+1 {
println!(" ||");
} }
fn main() {
// counting for number in (1..6).rev() { print!("\x1B[2J"); println!("{} =>", number); print_rocket(0);
let dur = time::Duration::from_millis(1000);
thread::sleep(dur); }
// ignition print!("\x1B[2J"); println!("Liftoff !"); print_rocket(1); let dur = time::Duration::from_millis(1000); thread::sleep(dur);
// liftoff let mut dur_time : u64 = 1000; for number in 2..100 { print!("\x1B[2J"); print_rocket(number);
let dur = time::Duration::from_millis(dur_time);
thread::sleep(dur);
dur_time -= if dur_time >= 30 {30} else {dur_time};
}
}
</lang>
Wren
<lang ecmascript>import "timer" for Timer
var rocket = "
/\\ ( ) ( ) /|/\\|\\ /_||||_\\
"
var printRocket = Fn.new { |above|
System.write(rocket) if (above == 0) return for (i in 1..above) System.print(" ||")
}
var cls = Fn.new { System.write("\x1B[2J") }
// counting for (n in 5..1) {
cls.call() System.print("%(n) =>") printRocket.call(0) Timer.sleep(1000)
}
// ignition cls.call() System.print("Lifetoff !") printRocket.call(1) Timer.sleep(1000)
// liftoff var ms = 1000 for (n in 2..99) {
cls.call() printRocket.call(n) Timer.sleep(ms) ms = (ms >= 40) ? ms - 40 : 0
}</lang>
zkl
Uses ANSI terminal codes. <lang zkl>var [const] rocket=
- <<<
0'~
/\ ( ) ( ) /|/\|\ /_||||_\
~, flame=" **";
- <<<
fcn cls { print("\x1B[2J") } fcn cursorUp(n) { print("\e[%dA".fmt(n)) } fcn cursorDown(n){ print("\e[%dB".fmt(n)) } fcn cursor2Col(n){ print("\e[%dG".fmt(n)) }
fcn __main__{
tall,tall := rocket.counts(), tall[tall.find("\n")+1]; cls(); print(rocket); cursorUp(tall);
// count down to ignition print("T minus: "); foreach n in ([5..1, -1]){ print(n," "); Atomic.sleep(1); } print(" Liftoff !"); cursorDown(tall); cursor2Col(1); // liftoff ms:=1.0; // 1 sec do(25){ println(flame); Atomic.sleep(ms); ms=(ms - 0.04).max(0); // 40 milliseconds faster than last time }
}</lang>
- Output:
T minus: 5 4 3 2 1 Liftoff ! /\ ( ) ( ) /|/\|\ /_||||_\ ** ** ** **