# aliens.msh

# (c) 2005-2006 Adam Barr

# 12-05-06: renamed time-expression to measure-command

# save these for use later
$rawui = $host.ui.rawui
$fc = $rawui.ForegroundColor
$bc = $rawui.BackgroundColor
$xsize = $rawui.WindowSize.Width
$ysize = $rawui.WindowSize.Height

# these are used to draw elements on the screen
$complete = [System.Management.Automation.Host.BufferCellType]"Complete"
function make_cell([string]$c) {
    return new-object System.Management.Automation.Host.BufferCell ([char]$c),$fc,$bc,$complete
}
function make_cell_fc([string]$c,[System.ConsoleColor]$fc) {
    return new-object System.Management.Automation.Host.BufferCell ([char]$c),$fc,$bc,$complete
}
$space_cell = make_cell " "
$equal_cell = make_cell "="
$missile_cell = make_cell "|"
$live_cell = make_cell "#"
$exp1_cell = make_cell_fc "o" "Yellow"
$exp2_cell = make_cell_fc "O" "DarkYellow"
$exp3_cell = make_cell_fc "@" "Red"
$exp4_cell = make_cell_fc "*" "DarkRed"
$exp5_cell = make_cell_fc "." "Magenta"

# hash table of coordinates of active missiles
$missiles = @{}
# current key for $missiles (wraps around at 1000)
$missileindex = 0

# used when calling ReadKey()
$keyopt = [System.Management.Automation.Host.ReadKeyOptions]"NoEcho,IncludeKeyDown,IncludeKeyUp"

# used to block missiles in the turn right after one has been fired
$blockmissile = $false

# hash table of coordinates of active aliens
$aliens = @{}
# hash table of state of active aliens: 0 (live) or 1 through 5 (exploding)
$alienstates = @{}
# characters for each stage of aliens
$alien_cells = ( $live_cell, $exp1_cell, $exp2_cell, $exp3_cell, $exp4_cell, $exp5_cell )


# functions to draw/clear paddle

function fill_paddle([int]$x,[int]$y,[System.Management.Automation.Host.BufferCell]$cell) {
  $rect = new-object System.Management.Automation.Host.Rectangle $x,$y,($x+7),$y
  $rawui.SetBufferContents($rect,$cell)
}

function draw_paddle {
  fill_paddle $xloc $yloc $equal_cell
}

function clear_paddle {
  fill_paddle $xloc $yloc $space_cell
}

# functions to draw/clear missiles

function fill_missile([int]$x,[int]$y,[System.Management.Automation.Host.BufferCell]$cell) {
  $rect = new-object System.Management.Automation.Host.Rectangle $x,$y,($x+1),($y+1)
  $rawui.SetBufferContents($rect,$cell)
}

function draw_missile([int]$x,[int]$y) {
  fill_missile $x $y $missile_cell
}

function clear_missile([int]$x,[int]$y) {
  fill_missile $x $y $space_cell
}

function clear_missiles {
  foreach ($m in $missiles.Keys) {
    $c = $missiles[$m]
    clear_missile $c.X $c.Y
  }
}

# functions to draw/clear aliens

function fill_alien([int]$x,[int]$y,[System.Management.Automation.Host.BufferCell]$cell) {
  $rect = new-object System.Management.Automation.Host.Rectangle $x,$y,($x+5),($y+1)
  $rawui.SetBufferContents($rect,$cell)
}

function draw_alien([int]$a) {
  $c = $aliens[$a]
  fill_alien ($c.X) ($c.Y) ($alien_cells[$alienstates[$a]])
}

function clear_alien([int]$a) {
  $c = $aliens[$a]
  fill_alien ($c.X) ($c.Y) $space_cell
}


# function to draw all aliens

function draw_aliens {
  foreach ($a in $aliens.Keys) {
    if ($alienstates[$a] -ne 6) {
      draw_alien $a
    }
  }
}

function clear_aliens {
  foreach ($a in $aliens.Keys) {
    if ($alienstates[$a] -ne 6) {
      clear_alien $a
    }
  }
}


# functions to move paddle

function move_paddle_left([int]$x,[int]$y) {
  # assumes paddle is already drawn at $x+2,y (or $x+1,y)
  $rect = new-object System.Management.Automation.Host.Rectangle $x,$y,($x+1),$y
  $rawui.SetBufferContents($rect,$equal_cell)
  $rect = new-object System.Management.Automation.Host.Rectangle ($x+8),$y,($x+9),$y
  $rawui.SetBufferContents($rect,$space_cell)
}

function move_paddle_right([int]$x,[int]$y) {
  # assumes paddle is already drawn at $x-2,y (or $x-1,y)
  $rect = new-object System.Management.Automation.Host.Rectangle ($x+6),$y,($x+7),$y
  $rawui.SetBufferContents($rect,$equal_cell)
  $rect = new-object System.Management.Automation.Host.Rectangle ($x-2),$y,($x-1),$y
  $rawui.SetBufferContents($rect,$space_cell)
}

# functions to move alien

function move_alien_down([int]$x,[int]$y) {
  # assumes alien is already drawn at $x,$y-1
  $rect = new-object System.Management.Automation.Host.Rectangle $x,($y-1),($x+5),($y-1)
  $rawui.SetBufferContents($rect,$space_cell)
  $rect = new-object System.Management.Automation.Host.Rectangle $x,($y+1),($x+5),($y+1)
  $rawui.SetBufferContents($rect,$live_cell)
}


# keyboard input/move paddle

function drain_keys {
  while ($rawui.KeyAvailable) {
    $key = $rawui.ReadKey($keyopt)
    if ($key.KeyDown) {
      switch ($key.Character) {
        ([char]"z") {
          if ($xloc -gt 1) {
            $script:xloc = $xloc - 1
            if ($xloc -gt 1) {
              $script:xloc = $xloc - 1
            }
            move_paddle_left $xloc $yloc
          }
          break
        }
        ([char]"x") {
          if ($xloc -lt $xsize-9) {
            $script:xloc = $xloc + 1
            if ($xloc -lt $xsize-9) {
              $script:xloc = $xloc + 1
            }
            move_paddle_right $xloc $yloc
          }
          break
        }
        ([char]" ") {
          if (!$blockmissile) {
            $missilex = $xloc+3
            $missiley = $yloc-2
            $script:missileindex = (($missileindex + 1) % 1000)
            $script:missiles[$missileindex] =
              new-object System.Management.Automation.Host.Coordinates $missilex,$missiley
            draw_missile $missilex $missiley
            $script:blockmissile = $true
            return $false
          }
        }
        ([char]"q") {
          return $true
        }
      }
    }
  }
  $script:blockmissile = $false
  return $false
}

# move missiles and aliens

function move_others {
  $keys = new-object object[] $missiles.Count
  $missiles.Keys.CopyTo($keys,0)
  foreach ($m in $keys) {
    $c = $missiles[$m]
    clear_missile $c.X $c.Y
    if ($c.Y -gt 1) {
      $c.Y -= 1
      if ($c.Y -gt 1) {
        $c.Y -= 1
      }
      draw_missile $c.X $c.Y
      $script:missiles[$m] = $c
      foreach ($a in $aliens.Keys) {
        if ($alienstates[$a] -eq 0) {
          # check for intersection
          if ((($aliens[$a].X) -le ($c.X+1)) -and
              (($aliens[$a].X+5) -ge ($c.X)) -and
              (($aliens[$a].Y) -le ($c.Y+1)) -and
              (($aliens[$a].Y+1) -ge ($c.Y))) {
              $script:alienstates[$a] = 1
              $script:missiles.Remove($m)
              clear_missile $c.X $c.Y
          }
        }
      }
    } else {
      $script:missiles.Remove($m)
    }
  }
  foreach ($a in $aliens.Keys) {
    if ($alienstates[$a] -eq 0) {
      if ($aliens[$a].Y -eq ($ysize-4)) {
        clear_alien $a
        $script:aliens[$a].Y = 0
        draw_alien $a
      } else {
        $script:aliens[$a].Y += 1
        $c = $aliens[$a]
        move_alien_down ($c.X) ($c.Y)
      }
    } else {
      if ($alienstates[$a] -eq 5) {
          clear_alien $a
      }
      if ($alienstates[$a] -ne 6) {
        $script:alienstates[$a] += 1
      }
      if ($alienstates[$a] -ne 6) {
        draw_alien $a
      }
    }
  }
}

# clear missiles and aliens

function clear_others {
  clear_missiles
  clear_aliens
}


#
# main program begins here
#

cls

# set up paddle
$xloc = ($xsize - 8) / 2
$yloc = $ysize - 2
draw_paddle

# set up aliens
$alien_count = ([int]($xsize / 10)) - 1
$curx = ($xsize - ((($alien_count-1)*10) + 6)) / 2
$cury = 10
for ($i = 0 ; $i -lt $alien_count; $i++) {
  $aliens[$i] =
    new-object System.Management.Automation.Host.Coordinates $curx,$cury
  $alienstates[$i] = 0
  $curx += 10
}
draw_aliens

# main loop

while ($true) {

  $t = measure-command {
    $script:done = drain_keys
    move_others

  }

  if ($done) {
    clear_paddle
    clear_others
    break
  }

  if ($t.Milliseconds -lt 100) {
    start-sleep -millisecond (100-$t.Milliseconds)
  }
}