Search code examples
javaandroidtimertimertask

Running a code after Timer finished or Canceled


enter image description here Follow the Path Game Description:
in this game there are some squares in the screen all invisible(in our case 9), a random sequence of them start to show up in the screen(we only show 4 here). and the user have to select the squares by the order they are shown in the screen.

What are the problems?:
1. user should not be allowed to choose a square during reveal. i can disable the buttons but how to enable them after timer finishes? i can't find a postLaunch or timerCanceled event to enable the buttons for selection again?
2. after user games over the game should start again automatically in 1sec. if i use Thread.sleep(1000) after settingBackgroundColor() it will give me the 1sec but ignores setBackground(). i want user to see the wrong answer(incorrectColor background) before game resets.

Codes:

// allSquares, generatedSquares,generationSpeed can be used to change game difficulty
int allSquares = 9;
int generatedSquares = 4;
int generationSpeed = 500;
Button[] buttons = new Button[allSquares];
@IdRes
int[] ids = {R.id.b1, R.id.b2, R.id.b3, R.id.b4, R.id.b5, R.id.b6, R.id.b7, R.id.b8, R.id.b9};
Button bGenerate;
List<Integer> randomOrder;
int selectedSquare = 0;

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    bGenerate = findViewById(R.id.bGenerate);
    bGenerate.setOnClickListener(this);
}

private void runAlgorithm()
{
    // initiate + invisible all squares
    for (int i = 0; i < allSquares; i++)
    {
        buttons[i] = findViewById(ids[i]);
        buttons[i].setAlpha(0.0f);
        buttons[i].setOnClickListener(this);
        buttons[i].setBackgroundColor(getResources().getColor(R.color.squareColor));
        buttons[i].setEnabled(false);
    }

    // generate random sequence
    randomOrder = new ArrayList<>();
    randomOrder.clear();
    Random random = new Random();
    while (randomOrder.size() < generatedSquares)
    {
        int generatedNumber = random.nextInt(allSquares);
        if (!randomOrder.contains(generatedNumber))
        {
            randomOrder.add(generatedNumber);
            Log.i("FollowThePath", generatedNumber + "");
        }
    }

    // show random sequence by order
    final int[] counter = {0};
    selectedSquare = 0;
    final Timer timer = new Timer();
    timer.schedule(new TimerTask()
    {
        @Override
        public void run()
        {
            buttons[randomOrder.get(counter[0])].setAlpha(1.0f);
            counter[0]++;
            if (counter[0] == generatedSquares)
            {
                timer.cancel();
            }
        }
    }, 0, generationSpeed);
    // Bug 1: need to enable buttons after timer finishes, user should not be allowed to choose a square during square reveal
}



@Override
public void onClick(View v)
{
    if (v.getId() == R.id.bGenerate)
        runAlgorithm();
    else
    {
        Log.i("FollowThePath", v.getTag().toString() + "       " + randomOrder.get(selectedSquare));
        if (v.getTag().toString().equals(randomOrder.get(selectedSquare).toString()))
        {
            v.setBackgroundColor(getResources().getColor(R.color.correctColor));
            v.setEnabled(false);
            selectedSquare++;
        } else
        {
            // Game Over
            Toast.makeText(this, "You failed.", Toast.LENGTH_SHORT).show();
            // Bug: setBackground executes so fast
            v.setBackgroundColor(getResources().getColor(R.color.incorrectColor));
            // Bug 2: we need a delay here, so that user sees the incorrect color and gets ready for next game
            // Thread.sleep(1000) ignores the color change, it means user can't see incorrectColor even if i add a Thread.sleep after that
            // starting a new game
            runAlgorithm();
        }
        if (selectedSquare == generatedSquares)
        {
            // Succeeded
            // Toast.makeText(this,"You did it.",Toast.LENGTH_SHORT).show();
            // run a new game
            runAlgorithm();
        }
    }
}

private void enableAllButtons(boolean enable)
{
    for (int i = 0; i < allSquares; i++)
    {
        buttons[i].setEnabled(enable);
    }
}

Solution

  • To solve your first problem, since the only way for the TimerTask to finish is when it gets cancelled based on this condition counter[0] == generatedSquares, then all you need to do is something like this:

    @Override
    public void run() {
       buttons[randomOrder.get(counter[0])].setAlpha(1.0f);
       counter[0]++;
       if (counter[0] == generatedSquares)
        {
            runOnUiThread(new Runnable()
            {
                @Override
                public void run()
                {
                    enableAllButtons(true);
                }
            });
            timer.cancel();
        }
    }
    

    For the second problem, the color change might not be showing up because the algorithm to restart kicks off too quickly and since all this is on the UI Thread, using Thread.sleep(1000) doesn't help. Try wrapping the runAlgorithm() call in something like:

    Handler handler = new Handler();
    Runnable r = new Runnable()
    {
        public void run()
        {
            //what ever you do here will be done after 500 mili seconds delay.
            runAlgorithm();
        }
    };
    handler.postDelayed(r, 500);
    

    Hopefully that works since I can't test it.