/*
Online Java - IDE, Code Editor, Compiler

Online Java is a quick and easy tool that helps you to build, compile, test your programs online.
*/

import java.util.Random;
import java.util.*;

interface Movable
{
    Set <String> outcomes = new HashSet<>();
    public void assignSnakesLadders();
    public void setGrid();
    public void checkSnakesLadder();
    public void defineMoves();
    public void diceThrow();
    public boolean performMove();
    public boolean checkValidMove();
    public boolean everyPossibleDieRoll();
    public void getMinimumMaximumMoves();
    public void getMovesUnderTaken();
    public void endResults();
}

class RandomSnakesLadders extends SnakesLadders implements Movable 
{
    int [][][] moves = new int [100][6][2];
    int minimumMoves[] = new int[5000];
    int maximumMoves[] = new int[5000];
    int gridNumber;
    int m;
    int boardsCompleted;
    int totalMoves;
    boolean allPreviousCompletedMoves=true;
    int i;
    int j;
    int currentPosition;
    int newPosition;
    int diceValue;
    int [][] Board;
    int count;
    StringJoiner sj = new StringJoiner(" ");
    int grids=100;
    String [] path = new String[5000];
    
    public RandomSnakesLadders(PositionSnakesLadders[] psl)
    {
        assignSnakesLadders();
    }
    
    public boolean everyPossibleDieRoll()
    {
        int i=0;
        System.out.println("grid: " + i);
        do
        {
            for (i=0; i<grids; i++)
            {
                if (psl[m].startPos==i)
                {
                }
                if (psl[m].startPos!=i)
                {
                    for (int p=0; p<6; p++)
                    {
                        if (moves[i][p][1]==0)   // for some reason, the i index is the position of the actual snakes and ladders startPos
                        {
                             allPreviousCompletedMoves=false;
                        }
                    }
                }
                
                if (allPreviousCompletedMoves)
                {
                    System.out.println("SHOULD REACH HERE WHEN ALL MOVES DONE!!!!****************");
                    break;
                }
            }   
            m++;
        }while(m<lengthEnum);
        m=0;
        
        return allPreviousCompletedMoves;
    }
    
    public void defineMoves()
    {
    }
    
    public void endResults()
    {
        for (int s: minimumMoves)
        {
            if (s==min)
            {
            System.out.println("\n" +path[counter]);
            }
        }
        
        for (int s: maximumMoves)
        {
            if (s==min)
            {
            System.out.println("\n" +path[counter]);
            }
            
            counter++;
        }
        average=(int)(total/outcomes.size());
        System.out.println("\n\nAverage turns is: " + average);
    }
    
    public void diceThrow()
    {
        Random rand = new Random();
        do
        {
            do
            {
                diceValue = (rand.nextInt(6)+1);
                System.out.println("\nDie Value thrown:" + diceValue);
                moves[currentPosition][diceValue-1][0]=currentPosition;
                moves[currentPosition][diceValue-1][1]=currentPosition+diceValue;
                sj.add(Integer.toString(diceValue));
                sj.add("{");
                sj.add(Integer.toString(currentPosition));
                sj.add(",");
                
            }while (performMove() && newPosition!=100);
        
            boardsCompleted++;
            System.out.println("Total moves taken: " + totalMoves);
            total=total+totalMoves;
            System.out.println("STARTING AGAIN: " + "Boards complete: " + boardsCompleted);
            System.out.println("have all moves been performed: " + allPreviousCompletedMoves);
            sj.add("   Total moves (including board displacements beyond 100): " + Integer.toString(totalMoves));
            path[count]=sj.toString();
            outcomes.add(path[count]);
            
            System.out.println("******PATH*******: " + path[count]);
            System.out.println("Set size: " + outcomes.size());
            minimumMoves[count] = totalMoves;
            System.out.println("Stored total turns: " + minimumMoves[count]);
            count++;
            getMinimumMaximumMoves();
            
            sj=new StringJoiner(" ");
            currentPosition=0;
            totalMoves=0;
            newPosition=0;
            gridNumber=0;
            
        //}while (!everyPossibleDieRoll());
        }while (outcomes.size()<50);
    }
    
    public boolean performMove()
    {
        newPosition = currentPosition + diceValue;
        System.out.println("Current position on board: " + newPosition);
        
        return checkValidMove();
    }
    
    public boolean checkValidMove()
    {
        //this is only exception that it evaluates as false.
        //once it has performed every die roll.
        // it is terminating just to examine how many sets (boards traversed 0-100)
        // Enabling this code is also causing issues, and terminating code incorrectly..
        //so it is commented out..
        
        /*
        if (allPreviousCompletedMoves && !firstMove)
        {
            System.out.println("Terminate method");
            return false;
        }
        */
        
        System.out.println("In valid move");
        
        if (newPosition>100)
        {
            System.out.println("Roll back");
            
            newPosition=currentPosition;
            System.out.println("new position: " + newPosition);
            sj.add(Integer.toString(newPosition));
            sj.add("}");
            
            return true;
        }
        
        if (newPosition==100)
        {
            newPosition=currentPosition+diceValue;
            sj.add(Integer.toString(newPosition));
            sj.add("},  ");
            
            return true;
        }
        
        System.out.println("Checking if new position is a snake or ladder: " + newPosition);
         
        for (PositionSnakesLadders enum1: psl)
        {
            if (newPosition==enum1.startPos)
            {
                if (enum1.startPos>enum1.endPos)
                {
                    newPosition=enum1.endPos;
                    System.out.println("Hit a snake, moving down:" + enum1.endPos);
                    sj.add(Integer.toString(newPosition));
                    sj.add("},  ");
                    currentPosition=newPosition;
                    totalMoves=totalMoves+1;
                     
                    return true;
                }
                
                if (enum1.startPos<enum1.endPos)
                {
                    System.out.println("Hit a ladder, moving up:" + enum1.endPos);
                    newPosition=enum1.endPos;
                    sj.add(Integer.toString(newPosition));
                    sj.add("},  ");
                    currentPosition=newPosition;
                    totalMoves=totalMoves+1;
                    
                    return true;
                }
            }
        }//end for
        
        currentPosition=newPosition;
        sj.add(Integer.toString(newPosition));
        sj.add("}, ");
         
        firstMove=false;
         
        System.out.println(newPosition + " at end of valid move");
        totalMoves=totalMoves+1;
        System.out.println("Total moves taken: " + totalMoves);
        return true;
    }  //end boolean method
    
    public void checkSnakesLadder()
    {
        for (PositionSnakesLadders enum1: psl)
        {
            j=0;
            for (PositionSnakesLadders enum2: psl)
            {
                if (i==j  && i!=lengthEnum-1)
                {
                    j++;
                }
                
                if (j!=(lengthEnum-1))
                {
                    PositionSnakesLadders current = enum1;
                    
                        if (psl[i].startPos == psl[j].startPos)
                        {
                            System.out.println("Invalid board configuration1: " + current.name()  + " {" + current.startPos+","+ current.endPos+"}");
                            System.out.println("Invalid board configuration2: " + enum2.name()  + " {" + enum2.startPos+","+ enum2.endPos+"}");
                        }
                        if (psl[i].endPos == psl[j].startPos)
                        {
                            System.out.println("Invalid board configuration3: " + current.name()  + " {" + current.startPos+","+ current.endPos+"}");
                            System.out.println("Invalid board configuration4: " + enum2.name()  + " {" + enum2.startPos+","+ enum2.endPos+"}");
                        }
                        if (psl[i].startPos == psl[j].startPos)
                        {
                            System.out.println("Invalid board configuration5: " + current.name()  + " {" + current.startPos+","+ current.endPos+"}");
                            System.out.println("Invalid board configuration6: " + enum2.name()  + " {" + enum2.startPos+","+ enum2.endPos+"}");
                        }
                        if (psl[i].endPos == psl[j].endPos)
                        {
                            System.out.println("Invalid board configuration7: " + current.name()  + " {" + current.startPos+","+ current.endPos+"}");
                            System.out.println("Invalid board configuration8: " + enum2.name()  + " {" + enum2.startPos+","+ enum2.endPos+"}");
                        }
                        j++; 
                }
            }//end inner for
            i++;   
        } //end outer for
    }//end method
    
    public void setBoard()
    {
        Board = new int[][]  { 
                                        {100,99,98,97,96,95,94,93,92,91},
                                        {81,82,83,84,85,86,87,88,89,90},
                                        { 80,79,78,77,76,75,74,73,72,71},
                                        { 61,62,63,64,65,66,67,68,69,70},
                                        { 60,59,58,57,56,55,54,53,52,51},
                                        { 41,42,43,44,45,46,47,48,49,50},
                                        { 40,39,38,37,36,35,34,33,32,31},
                                        { 21,22,23,24,25,26,27,28,29,30},
                                        { 20,19,18,17,16,15,14,13,12,11},
                                        { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
                                        
                                        };
    }
    
    public void assignSnakesLadders()
    {
        checkSnakesLadder();
    }
    
    public void setGrid()
    {
        
    }
    
    public void getMovesUnderTaken()
    {
        for (int[][] arr : moves) 
        {
            System.out.println("Grid number: " + gridNumber + "   " + Arrays.deepToString(arr));
            gridNumber++;
        }
    }
    
    public void getMinimumMaximumMoves()
    {
        min=minimumMoves[0];
        max=maximumMoves[0]; 
        
        System.out.println("*******ANALYSIS OF ALL THE MOVES UNDERTAKEN  [Start Position => End Position]******");
        minimumTurns = new int[outcomes.size()];
        maximumTurns = new int[outcomes.size()];
        
        for (int val: minimumMoves)
        {
            if (val!=0)
            {
                if (val==min && posProcessed!=0)
                {
                    if (counter<minimumMoves.length)
                    {
                    minimumTurns[counter]=min;
                    }
                }
            
                if (val==max && posProcessed!=0)
                {
                    if (counter<maximumMoves.length)
                    {
                        maximumTurns[counter]=max;
                    }
                }
                
                if (val<min && counter!=1)
                {
                    min=val;
                    minimumTurns = new int[outcomes.size()];
                    minimumTurns[0]=min;
                    
                }
                
                if (val>max && counter!=1)
                {
                    max=val;
                    maximumTurns = new int[outcomes.size()];
                    maximumTurns[0]=max;
                }
            }
            counter++;
            posProcessed++;
        }
        
        counter=0; 
        System.out.println("Minimum turns is: " +  min);
        System.out.println("Maximum turns is: " +  max);
    }//end of method
}//end of class

public class SnakesLadders
{
     static PositionSnakesLadders[] psl;
     static int lengthEnum;
     static int min;
     static int max;
     static int counter=1;
     static int [] minimumTurns;
     static int [] maximumTurns;
     static int average;
     static int total;
     static int posProcessed;
     static boolean firstMove=true;
    
     enum PositionSnakesLadders
     {
        SNAKE1(16,6), SNAKE2(48,26), SNAKE3(49,11), SNAKE4(56,53), SNAKE5(62,19), SNAKE6(64,60),  SNAKE7(87,24), SNAKE8(93,73), SNAKE9(95,75), SNAKE10(98,78),
        LADDERS1(1,38), LADDERS2(4,14), LADDERS3(9,31), LADDERS4(21,42), LADDERS5(28,84), LADDERS6(36,44),  LADDERS7(51,67), LADDERS8(71,91), LADDERS9(80,100);
        
        int startPos;
        int endPos;
        
        PositionSnakesLadders(int startPos, int endPos)
        {
            this.startPos=startPos;
            this.endPos=endPos;
        }
    }//end of enum
    
    public static void main(String[] args) 
    {
        System.out.println("\n\nWelcome to Online IDE!! Happy Coding :)");
        
        psl = PositionSnakesLadders.values();
        lengthEnum = psl.length;
        RandomSnakesLadders sl = new RandomSnakesLadders(psl);
        sl.diceThrow();
        System.out.println("\n");
        sl.getMovesUnderTaken();
        sl.endResults();
        
        //**** NEED TO IMPLEMENT THIS PART ******* (AS PER DOCUMENTATION)
        Combination c = new Combination(PositionSnakesLadders.values());
        
    }//end of main
}//end of class

//***NOT IMPLEMENTED***
class SnakesLaddersManipulated extends SnakesLadders
{
    public SnakesLaddersManipulated(long combinations, PositionSnakesLadders[] psl)
    {
        Set<PositionSnakesLadders> set1;

        // Adding the elements
        set1 = EnumSet.of(PositionSnakesLadders.SNAKE1);
        
    }
}


class Combination extends SnakesLadders
{
    public Combination(PositionSnakesLadders[] psl)
    {
        System.out.println("Welcome to Online IDE!! Happy Coding :)");
        int originalNumber=0;
        int n=originalNumber;
        int r;
        Set <String> s = new HashSet<>();
        int [] X = new int []{1,1,1,1,1,1};
        int minimum;
        int maxRValue;
        Map <Integer, Long> m;
        originalNumber=n;
        m= new HashMap<>();
        maxRValue=0;
        
        if (maxRValue>=64)
        {
            System.out.println("Value is too high computationally. It will be reduced to: " + (maxRValue-1));
        }

        for (r=0; r<=0; r++)
        {
            System.out.println("\n***COMBINATIONS*** (WITH REPLACEMENT)");
            System.out.println("C^R(n + r) = " + "(n+r-1)! / r!(n-1)!");
            System.out.println("C^R(" + n+","+r+") = " + (n+r-1)+ "!" + " / " + r+"!"+"("+(n-1)+")!");
            SnakesLaddersManipulated smc = new SnakesLaddersManipulated(Combinations (n,r,originalNumber,m), psl);
        }
    }
    
    public static long Combinations (int n, int r, int originalNumber, Map factorialResults)
    {
        long result=0;
        int denominator1; 
        int denominator2;
        int Numerator=n+r-1;
        int zero=0;
        long zeroFactorial = 1;
        
        if (originalNumber==0 && r==0)
        {
            System.out.println("n and r can not both be equal to zero");
            return 0;
        }
        
        if (originalNumber==0 && originalNumber+r-1<r)
        {
            System.out.println("n+r-1 must be > or = to r");
            return 0;
        }
        if (Numerator>=1)
        {
            result = ((n+r-1)* (Combinations (n-1, r,originalNumber, factorialResults)));
            factorialResults.put(Numerator,result);
            
            if (n==originalNumber)
            {
                denominator1 = r;
                denominator2 = originalNumber-1;
                factorialResults.put(zero,zeroFactorial);
                
                if (factorialResults.containsKey(denominator1) && factorialResults.containsKey(denominator2))
                {
                    long returnValue = result / ((long)factorialResults.get(denominator1) *(long)factorialResults.get(denominator2));
                    return returnValue;
                }
            }
            return result;
        }
        return 1; // it will reach here only when condition not met (Numerator>=1)
    }//end of method
}//end of class





