#!/usr/local/bin/perl
#
#############################################################################
# this program generates mazes on a text terminal and allows you to traverse 
# them. it requires the Curses module (available from www.cpan.org) 
#
# the current version of this program is available at:
#
# http://www.robobunny.com/projects/textmaze
#
# run textmaze -h for usage information
#
#############################################################################
# Author:
#   Kirk Baucom <kbaucom@schizoid.com>
#
# Contributors:
#   Kyle Guilbert <kguilber@coe.neu.edu>: maze solve code
#   Juan Orlandini <jorlandini@DATALINK.com>: move count, additional move keys
#
# License:
#
# Copyright (C) 2001 Kirk Baucom (kbaucom@schizoid.com)
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#############################################################################

use strict;
use Curses;

$SIG{'INT'} = 'quit';
$SIG{'KILL'} = 'quit';

$| = 1;

##### GLOBAL VARIABLES #####
my $VERSION = "1.2";

# the maze itself
my @maze = ();

# the current and previous cursor locations
my $cursor;
my $prev_cursor;

# used for mapping our path when solving the maze
my @tried = ();

# all of our configuration (keys, colors, etc)
my %c = ();

# what each array element is
my ($north, $south, $east, $west, $set, $setlist) = (0..5);

# set values after generating maze:
my ($trail, $maze_end, $cursor_pos, $empty) = (-3..0);

#### MAIN LOOP ####
init();

$c{'mazestart'} = time();

while(1) {
  handle_input(getch());
}

############ SUBROUTINES ###############

# set up the screen, read the config, generate the maze
sub init {
  read_config("$ENV{'HOME'}/.textmaze");

  initscr();

  # find the width and height of the terminal
  $c{'screen_width'} = getmaxx();
  $c{'screen_height'} = getmaxy();
  if($c{'screen_width'} and $c{'screen_height'}) {
    $c{'autosize'} = 1; # so we know if we can limit the max size or not
  }
  else {
    $c{'autosize'} = 0;
    $c{'screen_width'} = 80;
    $c{'screen_height'} = 24;
  }

  if(($c{'screen_width'} < 11) || ($c{'screen_height'} < 5)) {
    help("Your terminal screen must be at least 11 x 5");
  }

  # reset the defaults to the maximum that will fit on the screen if the 
  # screen is smaller than the default size from the config file
  if(($c{'default_width'} * 2) > $c{'screen_width'}) { $c{'default_width'} = int($c{'screen_width'} / 2); }
  if($c{'default_height'} +1 > $c{'screen_height'}) { $c{'default_height'} = $c{'screen_height'} - 1; }

  # get the width, height, and random seed from the command line
  ($c{'width'}, $c{'height'}, $c{'seed'}) = getinput();

  $c{'size'} = $c{'width'} * $c{'height'};

  # the arrow keys should always work
  push(@{$c{'up'}}, KEY_UP);
  push(@{$c{'down'}}, KEY_DOWN);
  push(@{$c{'left'}}, KEY_LEFT);
  push(@{$c{'right'}}, KEY_RIGHT);

  # always make ctrl-l redraw the screen
  push(@{$c{'redraw'}}, chr(12));

  if(curs_set(0) >= 0) { $c{'hideable_cursor'} = 1; }
  else { $c{'hideable_cursor'} = 0; }
  cbreak();
  noecho();
  keypad(1);
  init_colors();

  initialize_maze();  

  # if we are in dissolve mode, show the full grid firstF
  if($c{'dissolve'}) { tty_display(); }
  generate_maze();
  tty_display();
}


## create the grid (although it is stored as a flat list)
sub initialize_maze {
  my $size = $c{'size'};
  for my $i (0..$size - 1) {
      $maze[$i][$set] = $i;
      @{$maze[$i][$setlist]} = ("$i");
      $maze[$i][$north] = $maze[$i][$south] = $maze[$i][$west]
        = $maze[$i][$east] = "NULL";
  }
}

## yank walls out until we have a real maze
sub generate_maze { 
  srand($c{'seed'});
  my $cell_num;     # current cell that is being looked at
  my $dir; 	    # direction to check
  my $entry;        # where the maze starts
  my $exit;         # where the maze ends

  my $unified = 0;  # maze is finished when this is equal to the # of cells
                    # in the maze

  $c{'starttime'} = time();

  my $dissolve = $c{'dissolve'};
  my $size = $c{'size'};
  my $w = $c{'width'}; my $h = $c{'height'};

  while($unified < $size -1) {
    $cell_num = int(rand($size)); #pick a cell
    $dir= int(rand(4));             #pick a direction
    if($dir == $north) {
      if(($cell_num >= $w) && 
           ($maze[$cell_num][$set] != $maze[$cell_num-$w][$set])) {
        $maze[$cell_num][$north] = $cell_num - $w;
        $maze[$cell_num - $w][$south] = $cell_num;
        $unified = unify($maze[$cell_num][$set], $maze[$cell_num - $w][$set], $unified);
        if($dissolve) { clearwall($cell_num - $w, $south); }
      }
    }

    elsif($dir == $south) {
      if( ($cell_num<($w*($h-1)) ) && 
           ($maze[$cell_num][$set] != $maze[$cell_num+$w][$set]) ) {
        $maze[$cell_num][$south] = $cell_num + $w;
        $maze[$cell_num + $w][$north] = $cell_num;
        $unified = unify($maze[$cell_num][$set], $maze[$cell_num + $w][$set], $unified);
        if($dissolve) { clearwall($cell_num, $south); }
      }
    }

    elsif($dir == $east) {
      if( ($cell_num % $w) != ($w - 1) && 
              ($maze[$cell_num][$set] != $maze[$cell_num+1][$set])) {
        $maze[$cell_num][$east] = $cell_num + 1;
        $maze[$cell_num + 1][$west] = $cell_num;
        $unified = unify($maze[$cell_num][$set], $maze[$cell_num +1][$set], $unified);
        if($dissolve) { clearwall($cell_num+1, $west); }
      }
    }

    elsif($dir == $west) {
      if( (($cell_num % $w) != 0) &&
               ($maze[$cell_num][$set] != $maze[$cell_num-1][$set])) {
        $maze[$cell_num][$west] = $cell_num - 1;
        $maze[$cell_num - 1][$east] = $cell_num;
        $unified = unify($maze[$cell_num][$set], $maze[$cell_num-1][$set], $unified);
        if($dissolve) { clearwall($cell_num, $west); }
      }
    }
  }

  # the maze is built, now set up the beginning, end and cursor positions
  $entry = ( int(rand($h)) * $w ) + ($w - 1);  # beginning of maze
  $exit = int(rand($h)) * $w;		       # end of maze
  $maze[$entry][$east] = $entry;               # entry and exit point to themselves
  $maze[$exit][$west] = $exit;                 #   so that you can't leave the maze
  $maze[$exit][$set] = $maze_end;
  $cursor = $entry;                            # put the cursor at the entry point
  $maze[$cursor][$set] = $cursor_pos;
  $c{'endtime'} = time();
  $c{'dissolve'} = 0;			       # don't want to dissolve twice...
}


# puts two cells in the same set, used when generating the maze
sub unify {
  my ($j, $k, $unified) = @_;  # set numbers for the two sets to combine
  $unified++;
  
  my ($x, $y);
  
  if($j < $k) { $x = $j; $y = $k; }
  else        { $y = $j; $x = $k; }

  for(@{$maze[$y][$setlist]}) {
    $maze[$_][$set] = $x;
  }
  push(@{$maze[$x][$setlist]}, @{$maze[$y][$setlist]});
  @{$maze[$y][$setlist]} = ();

  return($unified);
}


# redraw current and last cursor positions
sub update { 
  
  my $size = $c{'size'};
  my $w = $c{'width'}; my $h = $c{'height'};
  my $color;

  foreach my $i (@_) {
    my $c = (2 * ($i % $w)) + 1;
    my $r = int($i / $w) + 1;

    if($maze[$i][$set] == $trail) { $color = $c{'trail'}; }
    else { $color = $c{'unseen'}; }

    if ($maze[$i][$set] == $cursor_pos) {
      if($maze[$i][$south] eq "NULL") {
        cprint($r, $c, "*", $c{'cursor'}, ::A_UNDERLINE);
      }
      else {
        cprint($r, $c, "*", $c{'cursor'});
      }
    }
    elsif($maze[$i][$south] eq "NULL") {
      cprint($r, $c, "_", $color);
    }
    else {
      cprint($r, $c, " ", $color);
    }

    if($maze[$i][$east] eq "NULL") {
      cprint($r, $c+1, "|", $c{'unseen'});
    }
    elsif($maze[$i][$set] == $trail or $maze[$i][$set] == $cursor_pos) {
      cprint($r, $c+1, "_", $c{'trail'});
    }
    else {
      cprint($r, $c+1, "_", $c{'unseen'});
    }
  }
  move($c{'screen_height'}-1, $c{'screen_width'}-1);
  refresh();
}

# this routine is used in "dissolve" (-d) mode to remove walls
sub clearwall {
  my ($cell, $dir) = @_;

  my $w = $c{'width'}; my $h = $c{'height'};

  my $c = 2 * ($cell % $w);
  my $r = int($cell / $w) +1;

  if($dir eq $west) {
    cprint($r, $c, "_", $c{'unseen'});
  }
  elsif($dir eq $south) {
    cprint($r, $c+1, " ", $c{'unseen'});
  }

  refresh();
  # now that we have updated, we might need to sleep some
  select(undef,undef,undef,$c{'dissolve_delay'});
}

## display the maze
sub tty_display { 

  my $size = $c{'size'};
  my $w = $c{'width'}; my $h = $c{'height'};

  cprint(0, 0, " " . "_"x((2*$w)-1), $c{'unseen'});

  for my $i (0..$size - 1) {
    my $c = (2 * ($i % $w)) + 1;
    my $r = int($i / $w) + 1;
    if(($i%$w) == 0) {
      cprint($r, $c - 1, "|", $c{'unseen'});
    }

    if($maze[$i][$set] == $maze_end) {
      cprint($r, $c, "%", $c{'finish'});
    }
    elsif ( $maze[$i][$set] == $cursor_pos) {
      if($maze[$i][$south] eq "NULL") {
        cprint($r, $c, "*", $c{'cursor'}, ::A_UNDERLINE);
      }
      else {
        cprint($r, $c, "*", $c{'cursor'});
      }
    }
    elsif($maze[$i][$south] eq "NULL") {
      if($maze[$i][$set] == $trail) {
        cprint($r, $c, "_", $c{'trail'});
      }
      else {
        cprint($r, $c, "_", $c{'unseen'});
      }
    }
    else {
      if($maze[$i][$set] == $trail) {
        cprint($r, $c, " ", $c{'trail'});
      }
      else {
        cprint($r, $c, " ", $c{'unseen'});
      }
    }

    if($maze[$i][$east] eq "NULL") {
      cprint($r, $c+1, "|", $c{'unseen'});
    }
    else {
      if($maze[$i][$set] == $trail) {
        cprint($r, $c+1, "_", $c{'trail'});
      }
      else {
        cprint($r, $c+1, "_", $c{'unseen'});
      }
    }
  }
  move($c{'screen_height'}-1, $c{'screen_width'}-1);
  refresh();
}

## take input, mostly for moving your little dude around in the maze
sub handle_input {
  my $key = shift;
  my $moved = 0;
  $prev_cursor = $cursor;

  if (compare_key($key, $c{'up'}) && $maze[$cursor][$north] ne "NULL") {
    $maze[$cursor][$set] = $trail;
    $cursor = $maze[$cursor][$north];
    $moved = 1;
  } 
  elsif (compare_key($key, $c{'left'}) && $maze[$cursor][$west] ne "NULL") {
    $maze[$cursor][$set] = $trail;
    $cursor = $maze[$cursor][$west];
    $moved = 1;
  }
  elsif (compare_key($key, $c{'right'}) && $maze[$cursor][$east] ne "NULL") {
    $maze[$cursor][$set] = $trail;
    $cursor = $maze[$cursor][$east];
    $moved = 1;
  }
  elsif (compare_key($key, $c{'down'}) && $maze[$cursor][$south] ne "NULL") {
    $maze[$cursor][$set] = $trail;
    $cursor = $maze[$cursor][$south];
    $moved = 1;
  }
  elsif(compare_key($key, $c{'toggle_color'})) { # toggle the color
    $c{'color'} = 1 - $c{'color'};
    init_colors();
    tty_display();
  }
  elsif (compare_key($key, $c{'redraw'})) { 
    clear();
    refresh();
    tty_display();
  }
  elsif (compare_key($key, $c{'quit'})) { 
    quit();
  }
  elsif (compare_key($key, $c{'solve'})) { 
    solve(1);
    addstr($c{'height'}, 0, "Press any key to exit");
    refresh();
    getch();
    quit();
  }

  if($moved == 1) {
    $c{'movesdone'}++ unless($prev_cursor == $cursor);
    if( $maze[$cursor][$set] == $maze_end ) { 
      win($c{'seed'});
    }
    $maze[$cursor][$set] = $cursor_pos;
  }
  update($prev_cursor, $cursor);
}

################## BEGIN MAZE SOLVING FUNCTIONS ##########################
## count the number of exits. used by solve().
sub numExits {
  my $exits = 0;
  for my $i (0..3) {
    if ( $maze[$cursor][$i] ne "NULL" ) {
      $exits++;
    }
  }
  return $exits;
}

## whether or not a given path from an intersection has been tried already
sub beenThere {
  my $pos = shift;
  my $found = 0;
  foreach my $ele (@tried) {
    if( $ele == $pos ) {
      $found = 1;
    }
  }
  return $found;
}

## figure out if we've got nowhere new to go from the current intersection
sub noMoreOptions {
  # if a new path still exists, return 0 (i.e. "more options")
  for my $i (0..3) {
    if( $maze[$cursor][$i] ne "NULL" &&
        $maze[$maze[$cursor][$i]][$set] != $trail &&
        !beenThere($maze[$cursor][$i]) ) {
      return 0;
    }
  }
  return 1;
}

## back-track to the last intersection
sub gotoLastIntersect {
  my ($display) = @_;
  my $backtrack = 0;
  do {
    for my $i (0..3) {
      if( $maze[$cursor][$i] ne 'NULL' &&
          $maze[$maze[$cursor][$i]][$set] == $trail ) {
        $prev_cursor = $cursor;
        $maze[$cursor][$set] = $empty;
        $cursor = $maze[$cursor][$i];
        update($prev_cursor, $cursor) if($display);
        $backtrack++;
        last;
      }
    }
  } while( numExits() < 3 );
  return $backtrack;
}

## solve the maze
sub solve {
  my ($display) = @_;

  my $min_moves = 0;
  @tried = ();

  # reset the maze
  initialize_maze();
  generate_maze();
  tty_display() if($display);

  $c{'solvestart'} = time() if($display);
  while(1) {
    # are we at a dead end?
    if( numExits() == 1 ) {
      $min_moves -= gotoLastIntersect($display);
    }
    # pick a random direction
    my $dir = int(rand(4));

    # don't try moving into a wall
    if( $maze[$cursor][$dir] eq "NULL") {
      next;
    }
    # don't go where we just came from
    if( $maze[$maze[$cursor][$dir]][$set] == $trail ) {
      next;
    }
    # are we at an intersection?
    if( numExits() > 2 ) {
      # have we been in that direction already?
      if( beenThere($maze[$cursor][$dir]) ) {
        # if we've been to all the exits, go to the last intersection
        if( noMoreOptions() ) {
          $min_moves -= gotoLastIntersect($display);
        }
        next;
      }
      else {
        # add on to the list of exits we've taken
        push(@tried, $maze[$cursor][$dir]);
      }
    }

    # make the move
    $min_moves++ unless($prev_cursor == $cursor);
    $maze[$cursor][$set] = $trail;
    $prev_cursor = $cursor;
    $cursor = $maze[$cursor][$dir];
    # did we win?
    if( $maze[$cursor][$set] == $maze_end ) {
      $c{'solveend'} = time();
      $maze[$cursor][$set] = $cursor_pos;
      return $min_moves;
    }
    $maze[$cursor][$set] = $cursor_pos;
    update($prev_cursor, $cursor) if($display);
  }
}
#################### END MAZE SOLVING FUNCTIONS ########################## 


sub quit {
  endwin();
  print "\n\nQuitting...\n\n";
  stats();
  exit(0);
}

sub stats {
  print "\nYou just played: -r $c{'height'} -c $c{'width'} -s $c{'seed'}\n";
  print "Maze generated in ", $c{'endtime'} - $c{'starttime'}, " seconds.\n";
  if($c{'solvestart'}) {
    print "Maze solved in ", $c{'solveend'} - $c{'solvestart'}, " seconds.\n";
  }
  elsif($c{'mazefinish'}) {
    my $min_moves = solve(0);
    my $finish_time = $c{'mazefinish'} - $c{'mazestart'};
    my $score = score($c{'height'}, $c{'width'}, $min_moves, $finish_time, $c{'movesdone'});
    print "---------------------\n";
    print "       Height: $c{'height'}\n";
    print "        Width: $c{'width'}\n";
    print "Minimum moves: $min_moves\n";
    print "   Your moves: $c{'movesdone'}\n";
    print "  Finish time: $finish_time seconds\n";
    print "   Your score: $score\n";
  }
}

# calculate score based on the size
# of the maze, min number of moves,
# actual moves made, and how long it
# took to solve
sub score {
  my ($h, $w, $min, $time, $moves) = @_;
  my $area = $h * $w;
  my $perc = $min / $moves;
  my $time_bonus = $min / ($time * 2);
  my $score = $area * $perc * $time_bonus;
  return int($score);
}

## read the command line arguments
sub getinput {
  my ($height, $width, $random_seed);
  while(my $arg = shift @ARGV) {
    if($arg eq "--help" || $arg eq "-h") { help(); }
    elsif($arg eq "-v") { version(); exit; }
    elsif($arg eq "-d") { $c{'dissolve'} = 1 - $c{'dissolve'}; }
    elsif($arg eq "-a") { $c{'color'} = 1 - $c{'color'}; }
    elsif($arg eq "-m") {
      $width = int(($c{'screen_width'}-1)/2);
      $height = $c{'screen_height'}-1;
    }
    elsif($arg eq "-c") {
      $width = shift @ARGV;
      if($width < 1) { help("Maze width must be at least 1"); }
      elsif($c{'autosize'} && ($width > $c{'screen_width'})) { help("Maze width must be less than half of the screen width (Max: $c{'screen_width'})\n"); }
    }
    elsif($arg eq "-r") {
      $height = shift @ARGV; 
      if($height < 1) { help("Maze height must be at least 1"); }
      elsif($c{'autosize'} && ($height > $c{'screen_height'})) { help("Maze height must be one less than the screen height (Max: $c{'screen_height'})\n"); }
    }
    elsif($arg eq "-s") { $random_seed = shift @ARGV; }
  }
  unless($width) { $width = $c{'default_width'}; }
  unless($height) { $height = $c{'default_height'}; }
  if(($width + $height) < 3) { help("A 1 x 1 maze is too small."); } 
  unless($random_seed) { $random_seed = (time); }

  return($width, $height, $random_seed);
}


## hot dog! you won! ##
sub win {
  endwin();

  print q;
             _                                     |###|
            ( )                                    \###/
            |H|              YOU                   (o^o)
           _|=|_                   WON!             / \
         =|/:M:\|=                                  \_/
         U{ :W: }U    TOM and CROW                ___=___
           \___/                                [|=======|]
         TOM SERVO        congratulate you      | CROOOOW |
         /___8___\                              |   / \   |
        (_________)                                 L_J
                          www.robobunny.com
;;


  $c{'mazefinish'} = time();
  stats();
  exit;
}

sub help {
  my $msg = shift;
  endwin();
  if($msg) { print "\n$msg\n\n"; }
  my $max_height = $c{'screen_height'} -1;
  my $max_width = int(($c{'screen_width'}-1)/2);
  print <<END;
Usage: $0 [-h] [-m] [-d] [-r <rows>] [-c <columns>] [-s <seed>]
     -m           set maze size to the maximum allowed by your screen
     -d           toggle \"dissolve\" mode, slower but fun to watch
     -h           print this help text
     -r <num>     set the number of rows in the maze
     -c <num>     set the number of columns in the maze
     -s <num>     provide a seed value for generating a maze
     -a           toggle ANSI color
     -v           print version

Default Keys:
    Movement:            Arrow keys, or vi movement keys (h,j,k,l)
    Redraw:              r
    Solve Maze:          S
    Quit:                q
    Toggle ANSI color:   c

END

  printf "Default height: %3d   Max height: %3d\n", $c{'default_height'}, $max_height;
  printf "Default width:  %3d   Max Width:  %3d\n", $c{'default_width'}, $max_width;
  exit;
}

sub version {
  print "TextMaze v$VERSION by Kirk Baucom <kbaucom\@schizoid.com>\n";
}

# read in the configuration file
sub read_config {
  my $conf = shift;

  my $pos = tell(*DATA);
  parse_config(*DATA);

  # generate a default config file if one doesn't exist.
  if(-f "$conf") {
    if(-r "$conf") { open(C, "<$conf"); parse_config(*C); }
  }
  else {
    open(C, ">$conf") || return; # can't create config file
    print C "# This is a config file for textmaze by Kirk Baucom <kbaucom\@schizoid.com>\n";
    print C "# Automatically generated by version $VERSION ", scalar(localtime), "\n";

    seek(DATA,$pos,0);

    while(<DATA>) {
      print C;
    }
    close(C);
  }
}

# parse a config file ( like the one in __DATA__ )
sub parse_config {
  my $config = shift;

  my %valid_colors = map { $_, 1 } ( 'BLACK', 'BLUE', 'CYAN', 'GREEN', 'MAGENTA', 'RED', 'WHITE', 'YELLOW' );
  my @field_types =  map { $_, 'list' }    ( 'up', 'down', 'left', 'right', 'redraw', 'quit', 'solve', 'toggle_color' );
  push(@field_types, map { $_, 'single' }  ( 'default_height', 'default_width', 'dissolve_delay' ));
  push(@field_types, map { $_, 'color' }   ( 'trail', 'finish', 'cursor', 'unseen' ));
  push(@field_types, map { $_, 'boolean' } ( 'color', 'dissolve' ));

  my %field_types = @field_types;

  while(<$config>) {
    chomp(my $line = $_); $line =~ s/([:,])\s/$1/g; $line =~ s/\#.*$//;
    if(!$line) { next; }

    my ($field, $value) = split(':', $line);
    chomp($value);
    my $type = $field_types{$field};

    if(!$type) {
      print "Malformed config file line:\n$line\n";
    }
    elsif($type eq 'list') {
      @{$c{$field}} = split(",", $value);
    }    
    elsif($type eq 'single') {
      $c{$field} = $value;
    }
    elsif($type eq 'boolean') {
      $c{$field} = boolstring($value);
    }
    elsif($type eq 'color') {
      $value = uc($value);
      my ($fg, $bg, $bold) = split(' ', $value);
      if($valid_colors{$fg}) { $c{"${field}_fg"} = $fg; }
      else {  print "$value is not a valid ANSI color\n"; exit 1; }
      if($valid_colors{$bg}) { $c{"${field}_bg"} = $bg; }
      else { print "$value is not a valid ANSI color\n"; exit 1; }
      if(defined($bold) and $bold eq 'BOLD') { $c{"${field}_bold"} = 1; }
      else { $c{"${field}_bold"} = 0; }
    }
  }
  close(C);
}

# set up the colors
sub init_colors {
  if($c{'color'}) {
    start_color();
    my $cid = 1;
    foreach my $type ('trail', 'unseen', 'finish', 'cursor') {
      init_pair($cid, eval "::COLOR_$c{\"${type}_fg\"}", eval "::COLOR_$c{\"${type}_bg\"}");
      push(@{$c{$type}}, COLOR_PAIR($cid));
      if($c{"${type}_bold"}) { push(@{$c{$type}}, ::A_BOLD); }
      $cid++;
    }
  }
  else {
    foreach my $type ('trail', 'unseen', 'finish', 'cursor') {
      @{$c{$type}} = ();
    }
    push(@{$c{'cursor'}}, ::A_BOLD);
    push(@{$c{'finish'}}, ::A_BOLD);
  }
}

# return 1 or 0 when handed a yes/no or on/off type string
sub boolstring {
  my $string = shift;
  if($string eq "on" || $string eq "ON" || $string eq "yes" || $string eq "YES") {
    return 1;
  }
  else { return 0; }
}

# check the key that was pressed against a list to see what we should do
sub compare_key {
  my ($key, $set) = @_;
  for(@$set) { if($key eq $_) { return 1; } }
  return 0;
}

# print something to the screen with the specified attributes
sub cprint {
  my ($row, $col, $str, $attr, @xattr) = @_;
  foreach my $attr (@{$attr}) { attron($attr); }
  foreach my $attr (@xattr) { attron($attr); }
  addstr($row, $col, $str);
  foreach my $attr (@xattr) { attroff($attr); }
  foreach my $attr (@{$attr}) { attroff($attr); }
}

# i decided this would be the easiest way to distribute a default config file
# don't edit this stuff! edit your own config file after you run textmaze
# the first time. it will be ~/.textmaze
__DATA__

# key values are case sensitive. arguments should be comma separated lists.
up: k, w
down: j, s
left: h, a
right: l, d
redraw: r
quit: q
solve: S
toggle_color: c

# some default behavior
default_height: 20
default_width: 20
color: on
dissolve: off
# higher = slower
dissolve_delay: .02

## colors to use, if the maze is in color
#
# available colors:      
#
# black
# red
# green
# yellow
# blue
# magenta
# cyan
# white
#
# the first color is the foreground, second color is background
# adding a third argument of 'bold' makes the foreground bold

trail:  cyan   green   bold
cursor: black  yellow
finish: red    black
unseen: cyan   black
