This game is written in Perl language, so it depends on Perl interpreter. It also needs libtermkey and some extra Perl modules:
If you don't know how to install Perl modules, look at How to install CPAN modules. If you have problems with installation of Term::TermKey, make sure that libtermkey is available in your operating system. If not - Installing Term::TermKey returns error should help you.
To install Terminal Hero type in your shell:
perl Makefile.PL make make install
In ArchLinux you can use existing PKGBUILD for this game from AUR:
yaourt -S terminalhero-git
The binary will be installed in:
/usr/bin/vendor_perl
terminalhero [options] Options: -e, --easy turn on easy mode -h, --help display this help Shortcuts: Ctrl+D or Esc exit Rules: Press keys with letters which are in the green area. Your score will increase if you do it well and decrease if you press wrong key. You can also lose health points and lives if the letters turns red. Levels: You will reach new levels every 64 points. Each level is a new line, so it is going harder.
#!/usr/bin/perl
BEGIN {
$FRAMERATE = 0.08;
# help
my $h = "\nTerminal Hero\n"
. "Linux society's response to Microsoft's Guitar Hero. :)\n\n"
. "Usage: terminalhero [options]\n\n"
. "Options:\n"
. "-e, --easy\t\t turn on easy mode\n"
. "-h, --help\t\t display this help\n\n"
. "Shortcuts:\n"
. "Ctrl+D or Esc\t\t exit\n\n"
. "Rules:\n"
. "Press keys with letters which are in the green area.\n"
. "Your score will increase if you do it well and decrease \n"
. "if you press wrong key. You can also lose health points \n"
. "and lives if letters turns red. \n\n"
. "Levels:\n"
. "You will reach new levels every 64 points.\n"
. "Each level is a new line, so it is going harder.\n\n"
. "Now go and play! :)\n\n";
# take arguments
if ($#ARGV >= 0) {
foreach (@ARGV) {
if ($_ ne "-h"
and $_ ne "--help"
and $_ ne "-e"
and $_ ne "--easy") {
print "Unknown argument " . $_ . ".\n";
}
}
if ($ARGV[0] ne "-e" and $ARGV[0] ne "--easy") {
print($h);
exit;
}
else {
$FRAMERATE = 0.12;
}
}
# define Perl modules
my @modules = (
"strict",
"POSIX",
"IO::Handle",
"Term::ReadKey",
"Term::TermKey",
"POE",
"POE::Wheel::TermKey",
"Time::HiRes"
);
# load Perl modules
foreach (@modules) {
eval "use " . $_ . ";";
die "\nUnable to load " . $_
. " Perl module. Please, install it using cpan.\n\n"
. "Error details:\n$@\n" if $@;
}
}
########################################################################
# terminal features (calling tput is slow... don't use it in a loop)
my %esc = (
"hide_cursor" => `tput civis`,
"show_cursor" => `tput cnorm`,
"clear_scr" => `tput ed`,
"font_white" => `tput setf 7`,
"font_green" => `tput setf 2`,
"font_red" => `tput setf 4`,
"font_black" => `tput setf 0`,
"reset" => `tput sgr0`,
"bg_green" => `tput setb 2`,
"bg_black" => `tput setb 0`,
"bg_white" => `tput setb 7`,
"bold" => `tput bold`,
"line_up" => `tput cuu1`
);
# game levels
my @levels = (
"n00b",
"user",
"root",
"geek",
"hacker",
"God",
"cheater"
);
# lines with letters to shoot
my @lines = ();
my @letters = ('a'..'z');
my $HEALTH = 32;
my $NEXT_LEVEL_POINTS = 64;
my $LIVES = 4;
# we need a timestamp, the game should be smooth
my $timestamp = 0;
# state of the game
my %game_stat = (
"lives" => $LIVES,
"health" => $HEALTH,
"level" => 0,
"score" => 0
);
# terminal width
my ($width) = GetTerminalSize();
# range of hit area
my %hit_range = (
"start" => floor(($width) / 4) - 5,
"end" => floor(($width) / 4) + 5
);
# states of letters with their colors
my %sign_states = (
"normal" => $esc{"font_white"},
"shooted" => $esc{"font_green"},
"missed" => $esc{"font_red"}
);
########################################################################
POE::Session->create(
inline_states => {
_start => sub {
STDOUT->autoflush(1);
# hide cursor
print($esc{"hide_cursor"});
$_[KERNEL]->yield("next_life");
$_[HEAP]{termkey} = POE::Wheel::TermKey->new(
InputEvent => 'keypressed',
);
},
# user pressed a key, he want to hit a letter ######################
keypressed => sub {
my $key = $_[ARG0];
my $termkey = $_[HEAP]{termkey};
if (('<C-d>' eq $termkey->format_key($key, FORMAT_VIM))
or ('<Escape>' eq $termkey->format_key($key, FORMAT_VIM))) {
print($esc{"reset"});
# show cursor
print($esc{"show_cursor"});
# clear screen
print($esc{"clear_scr"});
exit;
}
# check if he hit a letter in the green area
my $test = 0;
for (my $j=0; $j <= $game_stat{"level"}; $j++) {
for (my $i=$hit_range{"start"}; $i<$hit_range{"end"}; $i++) {
if ( $lines[$j][$i]{"character"}
eq $termkey->format_key($key, FORMAT_VIM) ) {
if ("normal" eq $lines[$j][$i]{"state"}) {
$game_stat{"score"}++;
}
$lines[$j][$i]{"state"} = "shooted";
$test = 1;
}
}
}
if ($test == 0
and $game_stat{"score"}
> $NEXT_LEVEL_POINTS * ($game_stat{"level"})) {
$game_stat{"score"}--;
}
# gotta exit somehow
delete $_[HEAP]{termkey} if $key->type_is_unicode and
$key->utf8 eq "C" and
$key->modifiers & KEYMOD_CTRL;
},
# game over, clear the screen and write a message ##################
game_over => sub {
print($esc{"reset"});
# show cursor
print($esc{"show_cursor"});
# clear screen
print($esc{"clear_scr"});
print("\nGame over! You are a "
. @levels[$game_stat{"level"}] . ". ;)\n\n");
exit(0);
},
# win, clear the screen and write a message ########################
win => sub {
print($esc{"reset"});
# show cursor
print($esc{"show_cursor"});
# clear screen
print($esc{"clear_scr"});
print("\nYou win! Neo, you must be... the choosen one. O_O\n\n");
exit(0);
},
# let's start a new level ##########################################
next_level => sub {
if ($game_stat{"level"} < scalar(@levels)) {
$game_stat{"health"} = $HEALTH;
$_[KERNEL]->yield("next_life");
}
else {
$_[KERNEL]->yield("win");
}
},
# let's start a new life, with new letters #########################
next_life => sub {
if ($game_stat{"lives"} < 1) {
$_[KERNEL]->yield("game_over");
}
else {
$game_stat{"health"} = $HEALTH;
# prepare an array with empty lines
@lines = ();
for (my $j=0; $j <= $game_stat{"level"}; $j++) {
for (my $i=0; $i<$width; $i++) {
my %sign = (
"character" => " ",
"state" => "empty"
);
push @{ $lines[$j] }, \%sign;
}
}
$_[KERNEL]->yield("play");
}
},
# this is the main loop (recursion) ################################
play => sub {
# frame rate should be stable, this may helps
$timestamp = [ Time::HiRes::gettimeofday( ) ];
# save cursor position
print(`tput sc`);
my $output = $esc{"bg_white"};
$output .= $esc{"font_black"};
# prepare bar with the game's state
my $bar = " whoami: " . @levels[$game_stat{"level"}]
. " | "
. "lives: " . $game_stat{"lives"}
. " | "
. "health: " . $game_stat{"health"}
. " | "
. "score: " . $game_stat{"score"};
if (length($bar)<=$width) {
# print rest of a bar
for (my $i=length($bar); $i<$width; $i++) {
$bar .= " ";
}
}
else {
# cut it if it's too long
$bar = substr($bar, 0, $width);
}
$output .= $bar . "\n" . $esc{"bg_black"}
. $esc{"reset"};
# for each line with signs
for (my $j=0; $j<$game_stat{"level"}+1; $j++) {
# remove first letter
shift($lines[$j]);
($width) = GetTerminalSize();
# generate new letter(s - if someone change terminal size)
for (my $i=scalar(@{ $lines[$j] }); $i<$width; $i++) {
my %sign = (
"character" => " ",
"state" => "normal"
);
if (int(rand(10)) < 1) {
$sign{"character"} = $letters[int rand @letters];
}
push($lines[$j], \%sign);
}
# iterate over every sign in the line
for (my $i=0; $i<$width; $i++) {
# check for missed signs
if ($i < $hit_range{"start"}
and ($lines[$j][$i]{"state"} eq "normal")
and ($lines[$j][$i]{"character"} ne " ") ) {
$game_stat{"health"}--;
$lines[$j][$i]{"state"} = "missed";
}
# set hit area colors
if ($i eq $hit_range{"start"}) {
# green background
$output .= $esc{"bg_green"};
# bold font
$output .= $esc{"bold"};
}
# set standard area colors
if ($i eq $hit_range{"end"}) {
# black background
$output .= $esc{"bg_black"};
# standard text
$output .= $esc{"reset"};
}
# print color escape code
if ($sign_states{$lines[$j][$i]{"state"}}) {
$output .= $sign_states{$lines[$j][$i]{"state"}};
}
# print the letter
$output .= $lines[$j][$i]{"character"};
}
$output .= "\n";
}
# print the frame
print($output);
# clear screen to the end (if minified terminal)
print($esc{"clear_scr"});
# restore cursor position
# tput rc doesn't work whent cursor is in the last line :(
# I should find a way to get position of cursor
# print(`tput rc`);
# this solution will couse problems after terminal resizing
for (my $i=0; $i<=$game_stat{"level"}+1; $i++) {
print($esc{"line_up"});
}
# if he's good enought;)
if ($game_stat{"score"}
>= $NEXT_LEVEL_POINTS * ($game_stat{"level"} + 1)) {
$game_stat{"level"}++;
$_[KERNEL]->yield("next_level");
}
else {
# if he's dead
if ($game_stat{"health"} < 1) {
$game_stat{"lives"}--;
$_[KERNEL]->yield("next_life");
}
else {
# display next frame
# period may be below 0, don't worry, it actually will be 0 :)
$_[KERNEL]->delay(
play => $FRAMERATE - Time::HiRes::tv_interval($timestamp)
);
}
}
},
}
);
########################################################################
POE::Kernel->run;