#!/usr/bin/env ruby

#    DouShouQi Game Module
#    Copyright (C) 2007  Aurelio A. Heckert <aurium # gmail dot 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 3 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, see <http://www.gnu.org/licenses/>.
#    or directly http://www.gnu.org/licenses/gpl-3.0.html

module DouShouQi


class Game

  def initialize ( player1=nil, player2=nil )
    @winer = nil
    @players = [ player1, player2 ]
    2.times { |n|
      if ( @players[n].class != DouShouQi::Player ) then
        if ( @players[n].class == String ) then
          @players[n] = DouShouQi::Player.new( @players[n] )
        else
          @players[n] = DouShouQi::Player.new(
                 (@players[n])? @players[n].to_s() : "Player #{n+1}"
               )
        end
      end
    }
    @turn = 1
    @currentPlayerNum = 0
    @jungle = [
        [ 0, 0, 2, 3, 2, 0, 0 ],
        [ 0, 0, 0, 2, 0, 0, 0 ],
        [ 0, 0, 0, 0, 0, 0, 0 ],
        [ 0, 1, 1, 0, 1, 1, 0 ],
        [ 0, 1, 1, 0, 1, 1, 0 ],
        [ 0, 1, 1, 0, 1, 1, 0 ],
        [ 0, 0, 0, 0, 0, 0, 0 ],
        [ 0, 0, 0, 2, 0, 0, 0 ],
        [ 0, 0, 2, 3, 2, 0, 0 ]
      ];
    @animals = [
        [
          { :name => 'Mouse',    :symbol => 'A',  :x => 6,  :y => 6 },
          { :name => 'Cat',      :symbol => 'B',  :x => 1,  :y => 7 },
          { :name => 'Dog',      :symbol => 'C',  :x => 5,  :y => 7 },
          { :name => 'Wolf',     :symbol => 'D',  :x => 2,  :y => 6 },
          { :name => 'Monkey',   :symbol => 'E',  :x => 4,  :y => 6 },
          { :name => 'Tyger',    :symbol => 'F',  :x => 0,  :y => 8 },
          { :name => 'Lion',     :symbol => 'G',  :x => 6,  :y => 8 },
          { :name => 'Elephant', :symbol => 'H',  :x => 0,  :y => 6 }
        ],
        [
          { :name => 'Mouse',    :symbol => 'a',  :x => 0,  :y => 2 },
          { :name => 'Cat',      :symbol => 'b',  :x => 5,  :y => 1 },
          { :name => 'Dog',      :symbol => 'c',  :x => 1,  :y => 1 },
          { :name => 'Wolf',     :symbol => 'd',  :x => 4,  :y => 2 },
          { :name => 'Monkey',   :symbol => 'e',  :x => 2,  :y => 2 },
          { :name => 'Tyger',    :symbol => 'f',  :x => 6,  :y => 0 },
          { :name => 'Lion',     :symbol => 'g',  :x => 0,  :y => 0 },
          { :name => 'Elephant', :symbol => 'h',  :x => 6,  :y => 2 }
        ]
      ];
  end

  def animals
    return @animals
  end

  def getAnimalAt ( x, y )
    @animals.each_index { |gNum|
      group = @animals[gNum]
      group.each_index { |aNum|
        animal = group[aNum]
        if ( animal[:x] == x ) && ( animal[:y] == y ) then
          animal[:num] = aNum
          animal[:owner] = gNum
          return animal
        end
      }
    }
    return false
  end

  def getAnimal ( owner, attVal, attType )
    if ( !attType ) || ( attType == :num ) then
      return @animals[owner][attVal]
    else
      @animals[owner].each_index { |aNum|
        animal = @animals[owner][aNum]
        if animal[attType] == attVal
          animal[:num] = aNum
          animal[:owner] = owner
          return animal
        end
      }
      return false
    end
  end

  def killAnimal ( ownerOrAnimal, num=nil )
    if ownerOrAnimal.class == Hash
      animal = @animals[ ownerOrAnimal[:owner] ][ ownerOrAnimal[:num] ]
    else
      animal = @animals[ownerOrAnimal][num]
    end
    animal[:killed] = true
    animal[:x] = -1
    animal[:y] = -1
  end

  def listAliveAnimals ( owner, attribute )
    list = []
    @animals[owner].each_index { |num|
      animal = @animals[owner][num]
      if ! animal[:killed] then
        if ! attribute then
          list << animal
        else
          if attribute == :num then
            list << num
          else
            list << animal[attribute]
          end
        end
      end
    }
    return list
  end

  def jungle
    return @jungle
  end

  def turn
    return @turn
  end
  
  def player (num)
    return @players[ num ]
  end
  def currentPlayer
    return @players[ @currentPlayerNum ]
  end
  def currentPlayerNum
    return @currentPlayerNum
  end

  def direction
    return {
        :N => 1,
        :S => 2,
        :L => 3,
        :W => 4
      }
  end

  def isInsideJungle? ( x, y )
    return ( ( y >= 0 ) && ( x >= 0 ) &&
             ( y < @jungle.length   ) &&
             ( x < @jungle[0].length ) )
  end

  def isInsideRiver? ( x, y )
    return ( [1,2,4,5].include?(x) && [3,4,5].include?(y) )
  end

  def isOponentsArmadilha? ( oponent, x, y )
    if oponent == 0
      return ( [2,4].include?(x) && (y==8) ) || ( (x==3) && (y==7) )
    else
      return ( [2,4].include?(x) && (y==0) ) || ( (x==3) && (y==1) )
    end
  end

  def moveAnimal ( animalNum, direction )
    if self.isOver? then
      return DouShouQi::Error.new( 1, "The game is over." )
    end
    group = @animals[ @currentPlayerNum ]
    movingAnimal = group[animalNum]
    x = movingAnimal[:x]
    y = movingAnimal[:y]
    case direction
      when self.direction[:N];  y -= 1
      when self.direction[:S];  y += 1
      when self.direction[:W];  x -= 1
      when self.direction[:L];  x += 1
      else return DouShouQi::Error.new( 1, "\"#{direction}\" is not a valid direction." )
    end
    if ! isInsideJungle?( x, y ) then
      return DouShouQi::Error.new( 2, movingAnimal[:name]+" can't go out the Jungle." )
    end
    if self.isInsideRiver?( x, y ) then
      case animalNum
        when 0 # Mouse
          message = 'The Mouse is walking on the river.'
        when 5..6  # Lion and Tiger can jump the river
          case direction
            when self.direction[:N];  y = 2
            when self.direction[:S];  y = 6
            when self.direction[:W];  x = ( x==4 || x==5 )? 3 : 0
            when self.direction[:L];  x = ( x==4 || x==5 )? 6 : 3
          end
          message = movingAnimal[:name]+' Jumps the river.'
        else # All other animals
          return DouShouQi::Error.new( 3, movingAnimal[:name] +
                                          " can't walk in the river." )
      end
    end
    if animalOnNewPos = self.getAnimalAt( x, y ) then
      # the movingAnimal is trying to enter in other animal position
      if animalOnNewPos[:owner] == @currentPlayerNum then
        return DouShouQi::Error.new( 4,
                  "Your #{ movingAnimal[:name] } " +
                  "can't walk over your #{animalOnNewPos[:name]}." )
      else
        if animalOnNewPos[:num] > animalNum then
          # the oponent is stronger
          if ( animalNum == 0 ) && ( animalOnNewPos[:num] == 7 ) then
            if self.isInsideRiver?( movingAnimal[:x], movingAnimal[:y] ) then
            return DouShouQi::Error.new( 5,
                      "Your Mouse can't kill a Elephant caming from the river." )
            else
              message = 'Your Mouse kills the oponents Elephant!'
              self.killAnimal animalOnNewPos
            end
          else
            if self.isOponentsArmadilha?( @currentPlayerNum, x, y )
              message = "Your #{ movingAnimal[:name] } " +
                        "kills the oponents #{animalOnNewPos[:name]} " +
                        "on your armadilha!"
              self.killAnimal animalOnNewPos
            else
              return DouShouQi::Error.new( 6,
                      "Your #{ movingAnimal[:name] } " +
                      "can't kill a #{animalOnNewPos[:name]}." )
            end
          end
        else
          message = "Your #{ movingAnimal[:name] } " +
                    "kills the oponents #{animalOnNewPos[:name]}!"
          self.killAnimal animalOnNewPos
        end
      end
    end
    movingAnimal[:x] = x
    movingAnimal[:y] = y
    if ( ( @currentPlayerNum == 0 ) && (x==3) && (y==0) ) ||
       ( ( @currentPlayerNum == 1 ) && (x==3) && (y==8) ) then
      @winer = @currentPlayerNum
      message = "The #{ movingAnimal[:name] } chegou in the oponents toca."
    end
    # Change Player:
    @currentPlayerNum = ( (@currentPlayerNum==0)? 1 : 0 )
    @turn += 1  if @currentPlayerNum == 0
    if message then
      return DouShouQi::Error.new( 0, message )
    else
      return DouShouQi::Error.new( 0, movingAnimal[:name]+" walks on the jungle." )
    end
  end

  def winer
    return @winer
  end

  def isOver?
    return ( @winer == nil )? false : true
  end

  def showJungle
    str = '';
    @jungle.length.times { |y|
      @jungle[0].length.times { |x|
        case @jungle[y][x]
        when 0
          q = '.'
        when 1
          q = '~'
        when 2
          q = '#'
        when 3
          q = '@'
        else
          q = '?'
        end
        @animals.each { |group|
          group.each { |animal|
            if ( ( animal[:x] == x ) && ( animal[:y] == y ) ) then
              q = animal[:symbol]
            end
          }
        }
        str += q
      }
      str += "\n"
    }
    return str
  end

end


class Player

  def initialize ( name=nil )
    name = "Player #{name}" if name.kind_of? Numeric
    @name = name.to_s
  end

  def name
    return @name
  end
  def name= (newName)
    @name = newName
    return @name
  end

end


class Error

  def initialize ( num, comment )
    @num = num
    @comment = comment
  end

  def isError
    return ( @num != 0 )
  end
  def num
    return @num
  end
  def comment
    return @comment
  end

end


end
