START = 20
SIZE = 50

WHITE_PIECE = 1
BLACK_PIECE = 2
DEAD_TEST = 3
COUNTED = 4
WHITE_DEAD = 6
BLACK_DEAD = 7 #these are what will go on the board for "temporarily" dead groups

def array_deep_copy(from_array) 
  to_array = Array.new
  from_array.each { |ary| to_array += [ary.dup] }
  return to_array
end

def array_mul(ary1, ary2)
  ret_ary = []
  ary1.each do |i|
    ary2.each do |i2|
      ret_ary = ret_ary.push([i, i2])
    end
  end
  return ret_ary
end

class Game
  
  attr_reader :game_over, :current_player, :black_captures, :white_captures, :white_points, :black_points, :conflicted, :owning_player
  attr_writer :board
  def initialize(size, square_size, app)
    @app = app
    @size = size
    @square_size = square_size
    @board = []
    @white_captures = 0
    @black_captures = 0
    @last_passed = false
    @game_over = false
#    for i in 0...@size
#      a[i] = 0
#    end
#    @board = a.dup()
#    @board.each do { |e| }
    for i in 0...@size
      a = []
      for j in 0...@size
        a[j] = 0
      end
      @board[i] = a
    end
    @current_player = "black"
    @previous_board = [] #for checking for illegal ko moves
    @pre_previous_board = [] #for undoing and still checking
    @test_board = array_deep_copy(@board) #for various tests
    @owning_player = 0 #for territory counting at end game
    @conflicted = false #ditto
    @white_points = 0
    @black_points = 0
  end
  
  def illegal_move?(x, y)
    if (occupied?(x, y) || suicide?(x, y) || ko?(x, y))
      return true
    else 
      return false
    end
  end
  
  def occupied?(x, y)
    if(@board[x][y] != 0)
      return true
    else
      return false
    end
  end
  
  def suicide?(x, y)
    if(dead?(x, y, get_next_player()) && !kills?(x, y))
      return true
    else
      return false
    end
  end
  
  def kills?(x, y)
    saved_board = array_deep_copy(@board)
    play_at_loc(x, y)
    [[-1,0],[0,-1],[1,0],[0,1]].each do |offset|
      x_offset = offset[0]
      y_offset = offset[1]
      if(!off_board?(x+x_offset, y+y_offset))
        if(occupied_by_enemy?(x+x_offset, y+y_offset) && dead?(x+x_offset, y+y_offset, @current_player))
          @board = array_deep_copy(saved_board)
          return true
        end
      end
    end
    @board = array_deep_copy(saved_board)
    return false
  end
   
  def occupied_by_enemy?(x, y)
    enemy_player = get_next_player()
    enemy_color = get_player_number(enemy_player)
    if(@board[x][y] == enemy_color)
      return true
    else
      return false
    end
  end
  
  def ko?(x, y)
    saved_board = array_deep_copy(@board)
    saved_white_captures = @white_captures
    saved_black_captures = @black_captures
    play_to_board(x, y)
    if(@board == @previous_board)
      @black_captures = saved_black_captures
      @white_captures = saved_white_captures
      @board = array_deep_copy(saved_board)
      return true
    else
      @black_captures = saved_black_captures
      @white_captures = saved_white_captures
      @board = array_deep_copy(saved_board)
      return false
    end
  end
  
  def dead?(x, y, enemy_player)
    @test_board = array_deep_copy(@board) 
    enemy_color = get_player_number(enemy_player)
    return !(alive?(x, y, enemy_color))
  end
  
  def alive?(x, y, enemy_color)
    if(off_board?(x, y))
      return false
    elsif(@test_board[x][y] == 3 || @test_board[x][y] == enemy_color)
      return false
    else
      @test_board[x][y] = 3 #3 = "marked" label
      if(empty?(x-1, y) || empty?(x, y-1) || empty?(x+1, y) || empty?(x, y+1))
        return true
      elsif(alive?(x, y+1, enemy_color) || alive?(x-1, y, enemy_color) || alive?(x, y-1, enemy_color) || alive?(x+1, y, enemy_color))
        return true
      else
        return false
      end
    end
  end

  def empty?(x, y)
    if(off_board?(x, y))
      return false
    elsif(@test_board[x][y] != 0)
      return false
    else 
      return true
    end
  end
  
  def off_board?(x, y)
    return (x >= @size || x < 0 || y >= @size || y < 0)
  end
  
  def play_to_board(x, y)
    play_at_loc(x, y)
    [[-1,0],[0,-1],[1,0],[0,1]].each do |offset|
      x_offset = offset[0]
      y_offset = offset[1]
      if(dead?(x+x_offset, y+y_offset, @current_player))
        kill(x+x_offset, y+y_offset)
      end
    end
  end
  
  def kill(x, y)
    player = get_next_player()
    player_number = get_player_number(player)
    remove_dead(x, y, player_number)
  end
  
  def remove_dead(x, y, player_number)
    if(!off_board?(x,y))
      if(@board[x][y] == player_number)
        @board[x][y] = 0
        increment_captures()
        remove_dead(x-1, y, player_number)
        remove_dead(x, y-1, player_number)
        remove_dead(x+1, y, player_number)
        remove_dead(x, y+1, player_number)
      end
    end
  end
  
  def increment_captures()
    if @current_player == "black"
      @black_captures += 1
    else
      @white_captures += 1
    end
  end
  
  def get_next_player() #I kind of hate myself now
    if @current_player == "black"
      return "white"
    else 
      return "black"
    end
  end
  
  def next_player()
    if @current_player == "black"
      @current_player = "white"
    else 
      @current_player = "black"
    end
  end
  
  def draw_piece(x, y, color)
    if color == "white"
      @app.fill("#FFFFFF")
    end
    size = @square_size
    @app.oval(START+x*size-(size-2)/2, START+y*size-(size-2)/2, size-2, size-2)
    @app.fill("#000000")
  end
  
  def draw_x(x, y)
    size = @square_size
    @app.stroke rgb(255,0,0,0.9)
    @app.line(START+x*size-size/2-1, START+y*size-size/2-1, START+x*size+size/2-1, START+y*size+size/2-1)
    @app.line(START+x*size-size/2-1, START+y*size+size/2-1, START+x*size+size/2-1, START+y*size-size/2-1)
    @app.stroke "#000000"
  end
  
  def draw()
    for i in 0...@size
      for j in 0...@size
        if @board[i][j] == WHITE_PIECE
          draw_piece(i, j, "white")
        elsif @board[i][j] == BLACK_PIECE
          draw_piece(i, j, "black")
        elsif @board[i][j] == WHITE_DEAD
          draw_piece(i, j, "white")
          draw_x(i, j)
        elsif @board[i][j] == BLACK_DEAD
          draw_piece(i, j, "black")
          draw_x(i, j)
        end
      end
    end
  end
  
  def pass()
    if(@last_passed == true)
      @game_over = true
      count()
    else
      @last_passed = true
      next_player()
    end
  end
  
  def count()
    @white_points = 0
    @black_points = 0
    @temp_board = array_deep_copy(@board)
    for i in 0...@size
      for j in 0...@size
        points = count_from(i, j)
        if !(@conflicted == true)
          if @owning_player == "white"
            @white_points += points
          else
            @black_points += points
          end
        end
        @conflicted = false
        @owning_player = 0
      end
    end
  end
  
  def count_from(x, y)
    if(off_board?(x, y) || counted?(x, y))
      return 0
    elsif(occupied?(x, y))
      if(@temp_board[x][y] == WHITE_PIECE)
        if(@owning_player == "black")
          @conflicted = true
        else
          @owning_player = "white"
        end
      elsif(@temp_board[x][y] == BLACK_PIECE)
        if(@owning_player == "white")
          @conflicted = true
        else
          @owning_player = "black"
        end
      end
      return 0
    else
      count = 1
      @temp_board[x][y] = COUNTED #counted
      count += count_from(x-1, y)+count_from(x,y-1)+count_from(x+1,y)+count_from(x,y+1)
      return count
    end
  end
  
  def counted?(x, y)
    if(@temp_board[x][y] == COUNTED)
      return true
    else
      return false
    end
  end
  
  def remove(x, y)
    if(removed?(x,y))
      if(@board[x][y] == WHITE_DEAD)
        @black_captures -= remove_from(x, y, WHITE_DEAD)
      elsif(@board[x][y] == BLACK_DEAD)
        @white_captures -= remove_from(x, y, BLACK_DEAD)
      end
    elsif(occupied?(x,y))
      if(@board[x][y] == WHITE_PIECE)
        @black_captures += remove_from(x, y, WHITE_PIECE)
      elsif(@board[x][y] == BLACK_PIECE)
        @white_captures += remove_from(x, y, BLACK_PIECE)
      end
    end
    count()
  end
  
  def remove_from(x, y, piece) #piece is the piece to replace with, points is true if you are removing, false if you're unremoving
    if(off_board?(x, y) || @board[x][y] != piece)      
      return 0
    else
      count = 1
      @board[x][y] = get_opposite(piece)
      count += remove_from(x-1,y,piece)+remove_from(x,y-1,piece)+remove_from(x+1,y,piece)+remove_from(x,y+1,piece)
      return count
    end
  end
    
  def removed?(x, y)
    if(@board[x][y] == WHITE_DEAD || @board[x][y] == BLACK_DEAD)
      return true
    else
      return false
    end
  end
  
  def get_opposite(piece)
    if(piece == BLACK_DEAD)
      return BLACK_PIECE
    elsif(piece == BLACK_PIECE)
      return BLACK_DEAD
    elsif(piece == WHITE_DEAD)
      return WHITE_PIECE
    elsif(piece == WHITE_PIECE)
      return WHITE_DEAD
    end
  end
    
  #def undo()
  #  @board = array_deep_copy(@previous_board)
  #  @previous_board = array_deep_copy(@pre_previous_board)
  #  next_player()
  #end
  
  def play_piece(x, y)
    if(!illegal_move?(x, y))
      @pre_previous_board = array_deep_copy(@previous_board)
      @previous_board = array_deep_copy(@board)
      play_to_board(x, y)
      @last_passed = false
      next_player()
      return true
    else
      return false
    end
  end
  
  def get_player_number(color) #gets the number from the string color
    if color == "white"
      return 1
    elsif color == "black"
      return 2
    else
      return 5
    end
  end
  
  def play_at_loc(x, y)
    @board[x][y] = get_player_number(@current_player)
  end
  
  def print_board()
    for i in 0...@size
      for j in 0...@size
        print @board[j][i].to_s + " "
      end
      print "\n"
    end
  end
end

Shoes.app :width => 800, :height => 600 do
    
  @game_size = 19 #default
  @square_size = self.height/@game_size
  def render_field()
    clear do
      background rgb(219, 169, 108, 0.8)
      stack :margin_top => 10 do
        if(@game.game_over == false)
          button("Pass", :top => 10, :left => 600) do 
            @game.pass()
            render_field()
          end
          #button("Undo", :top => 40, :left => 600) do
          #  @game.undo()
          #  render_field()
          #end
        else
          para("White points: #{@game.white_points}", :top => 130, :left => 600)
          para("Black points: #{@game.black_points}", :top => 150, :left => 600)
          para("Click dead groups to \nremove them from the \nboard.", :top => 210, :left => 600)
          if(@game.black_points > @game.white_points)
            winner = "black"
          else
            winner = "white"
          end
          para("Score: #{winner} by #{(@game.black_points-@game.white_points-0.5).abs()}", :top => 180, :left => 600)
        end
        
        para("White captures: #{@game.white_captures}", :top => 80, :left => 600)
        para("Black captures: #{@game.black_captures}", :top => 100, :left => 600)
        
        line(590, 460, 790, 460)
        para("New Game:", :top => 470, :left => 600)
        button("9x9", :width => 100, :height => 30, :top => 500, :right => 100) do 
          @game_size = 9
          @square_size = self.height/@game_size
          @game = Game.new(@game_size, @square_size, self)
          render_field()
        end
        button("19x19", :width => 100, :height => 30, :top => 540, :right => 100) do
          @game_size = 19
          @square_size = self.height/(@game_size)
          @game = Game.new(@game_size, @square_size, self)
          render_field()
        end
      end
      
      x = START
      y = START
      self.strokewidth(1)
      @game_size.times do 
        line(x, y, x, (@game_size-1)*@square_size+START)
        x += @square_size
      end
      x = START
      @game_size.times do
        line(x, y, @square_size*(@game_size-1)+START, y)
        y += @square_size
      end
      if @game_size == 9
        pt_loc = 3
      else
        pt_loc = 4
      end
      
      oval_size = @square_size/3
      if(@game_size == 9)
        start_pt = 2
      else
        start_pt = 3
      end
      end_pt = @game_size - (start_pt+1)
      mid_pt = (start_pt+end_pt)/2
      array_mul([start_pt,mid_pt,end_pt],[start_pt,mid_pt,end_pt]).each do |pt|
        oval(START+@square_size*pt[0]-@square_size/6, START+@square_size*pt[1]-@square_size/6, oval_size, oval_size)
      end
      self.strokewidth(2)
      @game.draw()
    end
  end
  
  $app = self
  
  @game = Game.new(@game_size, @square_size, self)
  render_field
  click do |button, x, y|
    if(x < @square_size*@game_size && y < @square_size*@game_size)
      x_coord = x/@square_size
      y_coord = y/@square_size
      if @game.game_over == false
        @game.play_piece(x_coord, y_coord) #if the move isn't illegal
      else
        @game.remove(x_coord, y_coord) #remove whatever's at that space
      end
      render_field
    end
  end
end