📜 ⬆️ ⬇️

Writing a tactical game about numbers for Android

When I just started programming (3 months ago), I quickly realized that it was better to start doing my projects right away. It’s impossible to sit at books or courses from morning till night, but if you start doing something special, then you will easily sit at work from morning till morning.

This article is a small tutorial on how to make a logic game with a bot. The game will look like this:


* I will describe the rules in detail again in the section on AI.
')
Readers of the article are divided into three groups.
  1. Started programming a few hours ago.
    It will be difficult for you, it is better to first go through some small course on introduction to Android development, deal with two-dimensional arrays and interfaces. And then download the project from the githab . Comments and this article will help you figure out what works and how.
  2. Already know how to program, but still can not call themselves experienced.
    You will be interested, because you can quickly make your game. I took the dirty job of building the logic of the game and the ui-component, I leave the creative part to you. You can make another game mode (2 on 2, online, etc.), change the bot's algorithms, create levels, etc.
  3. Experienced.
    You may be interested to think about AI - it is not as easy to write as it seems at first glance. I would also be very happy to receive comments on the code from you - I am sure that I did not do everything optimally.


Prelude


Now I will re-create the project (not to miss anything) and will consistently describe all the steps. I will try to write code in a good tone, but there will be bad places that I went to in order to reduce the volume.

We will follow the following plan:

Create a project


Everything is as usual: we create a new project, further, further and further, finish. Considering that a part of the audience can be represented by the group “Began to program a few hours ago,” I will give a detailed instruction.

Instruction
Pay attention, the project is done in Android Studio.
Instead of “livermor” in Company Domain, indicate something of your own






Change at the top of Android on the Project. The screenshot shows an example of how and where to create classes.


Write bot


Let's start with the most difficult and most interesting task - we will write a class for the bot.
You can watch the video again and think about how you would implement the algorithms.
Just in case, I will give the rules again:
regulations
rivals take turns. One plays for the strings, the other for the rows. The number chosen by one player is added to his points and determines the row (line) of moves for another. You cannot go to the same place twice in a row. The winner is the one who has the most points at the end of the game (when there are no possible moves left).

The first idea that came to me was to calculate all the moves to the end. Or to the n-th move. But how to calculate the moves? Let's introduce the concept of a better move . Surely, this is a move that maximizes the difference between your move and the opponent's best move . That is, you calculate your best move, based on the fact that your opponent will calculate your best move, expecting that you calculate your best move, based on ... And so to n. The most recent move will simply be the maximum number in the row.

Do you think this is a normal bot algorithm?

In fact, it is even worse than simply choosing the maximum.
Have you guessed what the problem is?

The fact is that we assume that the opponent will make this best move . We can choose -2, expecting the opponent to take -3 (his best move , which will be justified at the end of the game), but the opponent will take and will go to +6. Then we will count all of them and go to -5, expecting the opponent to go to -4, and he will again take and will choose +8. And so on - we always make long-term moves, and always lose here and now.

The easiest way to make this algorithm workable is to set n = 2. That is, suppose that the opponent simply chooses the maximum from his row, and look for such a move that maximizes the difference between our moves. By the way, this will make the bot quite competitive.

I went a little further and tried to make the bot more human - gave him greed. In other words, I ordered the bot to make short-term moves, if you can extract the difference in the specified number of points. In the code, I called this difference a jackpot, and the bot breaks the jackpot, if on the planning horizon it does not lead to early defeat (in the comments to the code I described everything in more detail).

And finally, before you create a class for the bot, I will describe in more detail what it is like.
A bot is needed by the Game class in order to obtain a move number (in a row or in a row). All data that changes during the game - player points, a boolean matrix with allowed moves, the number of the last perfect move - will be stored in the Game class. Accordingly, creating the essence of the class Bot, we need to transfer to it only things that cannot be changed during one game: whether the bot plays for strings or for rows and a matrix with numbers.

Bot has one public method - to make a move to which we turn every time we want to get a move. Respectively, in this method we transfer all changeable values.

appeal to those who program a few hours
What I called protected can be used for inheritance — that is, creating bot children,
public - to use other classes,
private is an internal kitchen that other classes should not know about.

If you practically understand nothing - this is normal, I also went through my first tutorials.
Class for Bot - the most difficult, it will be easier further.

bot code
package com.livermor.plusminus; //   "livermor"   Company Domain public class Bot { protected int[][] mMatrix; //digits for buttons protected boolean[][] mAllowedMoves; //,     protected int mSize; //  protected int mPlayerPoints = 0, mAiPoints = 0; //  protected boolean mIsVertical; //     protected int mCurrentActiveNumb; //   ( 0   (mSize)) //   private final static int CANT_GO_THERE = -1000; //  ,    -1000 private final static int WORST_MOVE = -500; // ,     private final static int VICTORY_MOVE = 500; // ,     private final static int JACKPOT_INCREASE = 9; //  ,     private static final int GOOD_ADVANTAGE = 6;// (),    6    int depth = 3; //    3   public Bot( int[][] matrix, boolean vertical ) { mMatrix = matrix; mSize = matrix.length; mIsVertical = vertical; } //,    public int move( int playerPoints, int botPoints, boolean[][] moves, int activeNumb ) { mPlayerPoints = playerPoints; mAiPoints = botPoints; mCurrentActiveNumb = activeNumb; mAllowedMoves = moves; return calcMove(); } //     public void setDepth(int depth) { this.depth = depth; } protected int calcMove() { //      return calcBestMove(depth, mAllowedMoves, mCurrentActiveNumb, mIsVertical, mAiPoints, mPlayerPoints); } private int calcBestMove(int depth, boolean[][] moves, int lastMove, boolean isVert, int myPoints, int hisPoints) { int result = mSize; //  ,     int[] moveRatings = new int[mSize]; //      //  ,     () if (depth == 1) return findMaxInRow(lastMove, isVert); else { int yMe, xMe; //     int yHe, xHe; //    for (int i = 0; i < mSize; i++) { //   ,     (i)   (lastMove) yMe = isVert ? i : lastMove; xMe = isVert ? lastMove : i; //  ,     if (!mAllowedMoves[yMe][xMe]) { moveRatings[i] = CANT_GO_THERE; continue; //    } int myNewP = myPoints + mMatrix[yMe][xMe];//    moves[yMe][xMe] = false;//   ,    //     int hisBestMove = calcBestMove(depth - 1, moves, i, !isVert, hisPoints, myPoints); //  ,      (..   ), .. if (hisBestMove == mSize) { if (myNewP > hisPoints) //    ,     moveRatings[i] = VICTORY_MOVE; else // ,     moveRatings[i] = WORST_MOVE; moves[yMe][xMe] = true;// ,     continue; } //   ,        yHe = isVert ? i : hisBestMove; xHe = isVert ? hisBestMove : i; int hisNewP = hisPoints + mMatrix[yHe][xHe]; moveRatings[i] = myNewP - hisNewP; //        ,     //    1,        if (depth - 1 != 1) { //        hisBestMove = findMaxInRow(i, !isVert); yHe = isVert ? i : hisBestMove; xHe = isVert ? hisBestMove : i; hisNewP = hisPoints + mMatrix[yHe][xHe]; int jackpot = myNewP - hisNewP;//      if (jackpot >= GOOD_ADVANTAGE) { // ,    moveRatings[i] = moveRatings[i] + JACKPOT_INCREASE; } } moves[yMe][xMe] = true;// ,     } //   ,     .  //  ,   —     (  ) int max = CANT_GO_THERE; for (int i = 0; i < mSize; i++) { if (moveRatings[i] > max) { max = moveRatings[i];//   ,      result = i; } } } //     return result; } // ,      () private int findMaxInRow(int lastM, boolean isVert) { int currentMax = -10; int move = mSize; int y = 0, x = 0; for (int i = 0; i < mSize; i++) { y = isVert ? i : lastM; x = isVert ? lastM : i; int temp = mMatrix[y][x]; if (mAllowedMoves[y][x] && currentMax <= temp) { currentMax = temp; move = i; } } return move; } } 


We write a class for the game


At first I warned that there would be bad places in the code. And here is one of them. Instead of first writing the parent class to control the game, and then expanding it to a specific game class with a bot, I will immediately write a game class with a bot. I do this to shorten the tutorial.

The game class, let's call it Game, needs two things:
1. Interface for working with ui-elements;
2. The size of the matrix.

appeal to those who program a few hours
Carefully, the Game class uses AsyncTask and Handler - either deal with them beforehand, or just ignore them. In a nutshell, these are classes for working with threads. Android cannot change interface elements from the main stream. The above classes solve this problem.

game code
 package com.livermor.plusminus; import android.os.AsyncTask; import android.os.Handler; import java.util.Random; public class Game { //    ,   public static final int mTimeToWait = 800; protected MyAnimation mAnimation; // AsyncTask   //      protected int[][] mMatrix; //digits for buttons protected volatile boolean[][] mAllowedMoves; protected int mSize; //  protected int playerOnePoints = 0, playerTwoPoints = 0;//  protected volatile boolean isRow = true; //       protected volatile int currentActiveNumb; //     protected ResultsCallback mResults;//,    MainActivity protected volatile Bot bot;//   Random rnd; //          public Game(ResultsCallback results, int size) { mResults = results; //   mSize = size; rnd = new Random(); generateMatrix(); //    // ,      currentActiveNumb = rnd.nextInt(mSize); isRow = true; //         (  ) for (int yPos = 0; yPos < mSize; yPos++) { for (int xPos = 0; xPos < mSize; xPos++) { //         mResults.setButtonText(yPos, xPos, mMatrix[yPos][xPos]); if (yPos == currentActiveNumb) //    mResults.changeButtonBg(yPos, xPos, isRow, true); } } bot = new Bot(mMatrix, true); } public void startGame() { activateRawOrColumn(true); } protected void generateMatrix() { mMatrix = new int[mSize][mSize]; mAllowedMoves = new boolean[mSize][mSize]; for (int i = 0; i < mSize; i++) { for (int j = 0; j < mSize; j++) { mMatrix[i][j] = rnd.nextInt(19) - 9; // -9  9 mAllowedMoves[i][j] = true; //     } } } //    MainActivity,         public void OnUserTouchDigit(int y, int x) { mResults.onClick(y, x, true); activateRawOrColumn(false);//      mAllowedMoves[y][x] = false; //       playerOnePoints += mMatrix[y][x]; //    mResults.changeLabel(false, playerOnePoints);//   mAnimation = new MyAnimation(y, x, true, isRow);//    mAnimation.execute(); isRow = !isRow; //      currentActiveNumb = x; //     ,     } //       protected void onAnimationFinished() { if (!isRow) {//        () // Handler,      ui,    //   . Handel       Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { botMove(); // } }, mTimeToWait / 2); } else //   ,    activateRawOrColumn(true); } private void botMove() { //   int botMove = bot.move(playerOnePoints, playerTwoPoints, mAllowedMoves, currentActiveNumb); if (botMove == mSize) {//    ,    onResult(); //    return; //    } int y = botMove; //     int x = currentActiveNumb; mAllowedMoves[y][x] = false; playerTwoPoints += mMatrix[y][x]; mResults.onClick(y, x, false); //    mResults.changeLabel(true, playerTwoPoints); //   mAnimation = new MyAnimation(y, x, true, isRow); //   mAnimation.execute(); isRow = !isRow; //    currentActiveNumb = botMove; //   ,     } protected void activateRawOrColumn(final boolean active) { int countMovesAllowed = 0; //  ,     int y, x; for (int i = 0; i < mMatrix.length; i++) { y = isRow ? currentActiveNumb : i; x = isRow ? i : currentActiveNumb; if (mAllowedMoves[y][x]) { //  ,  mResults.changeButtonClickable(y, x, active); //,    countMovesAllowed++; //   ,    } } if (active && countMovesAllowed == 0) onResult(); } //:      //    —    protected class MyAnimation extends AsyncTask<Void, Integer, Void> { int timeToWait = 35; //    int y, x; boolean activate; boolean row; protected MyAnimation(int y, int x, boolean activate, boolean row) { this.activate = activate; this.row = !row; this.y = y; this.x = x; } @Override protected Void doInBackground(Void... params) { int downInc = row ? x - 1 : y - 1; int uppInc = row ? x : y; if (activate) sleep(Game.mTimeToWait);//     if (activate) { //  ,        while (downInc >= 0 || uppInc < mSize) { //Log.i(TAG, "while in Animation"); sleep(timeToWait); if (downInc >= 0) publishProgress(downInc--); // AsyncTask    sleep(timeToWait); if (uppInc < mSize) publishProgress(uppInc++); } } else {//  ,        int downInc2 = 0; int uppInc2 = mSize - 1; while (downInc2 <= downInc || uppInc2 > uppInc) { sleep(timeToWait); if (downInc2 <= downInc) publishProgress(downInc2++); sleep(timeToWait); if (uppInc2 > uppInc) publishProgress(uppInc2--); } } return null; } @Override protected void onProgressUpdate(Integer... values) { int numb = values[0]; int yPos = row ? y : numb; int xPos = row ? numb : x; //         () if (activate) mResults.changeButtonBg(yPos, xPos, row, activate); else mResults.changeButtonBg(yPos, xPos, row, activate); } @Override protected void onPostExecute(Void aVoid) { if (activate) //   ,      new MyAnimation(y, x, false, row).execute(); else //,   ,     onAnimationFinished(); } //    private void sleep(int time) { try { Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } } } protected void onResult() { //     mResults.onResult(playerOnePoints, playerTwoPoints); } //  MainActivity,    ui  //********************************************************************************* public interface ResultsCallback { //       void changeLabel(boolean upLabel, int points); //    void changeButtonBg(int y, int x, boolean row, boolean active); //    void setButtonText(int y, int x, int text); // /  void changeButtonClickable(int y, int x, boolean clickable); //   void onResult(int one, int two); //    void onClick(int y, int x, boolean flyDown); } } 


We work on the user interface


The final part remains - to connect the logic of the game with the user interface. There will be fewer comments and explanations, we will simply take all the necessary things step by step
Mark for those who program a few hours
Make sure you have a Project at the top, not Android.
At the moment we have 3 classes: the Bot and Game created by us and the already existing MainActivity class. Now we have to change several xml documents (circled in red), create another class for the number buttons, and create a drawable element (I show with a black arrow how this is done).


1. Forbidding the screen to turn:

In AndroidManifest we add under MainActivity - android: screenOrientation = "portrait"
We do this in order to prohibit turning the screen (to simplify the tutorial).
AndroidManifest.xml
 <?xml version="1.0" encoding="utf-8"?> <!--   ,     package  . , ,      >>> android:screenOrientation="portrait" --> <manifest package="com.livermor.plusminus" xmlns:android="http://schemas.android.com/apk/res/android"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> </manifest> 


2. Add the colors we need:

Go to colors.xml, delete the existing colors, add these:
colors.xml
 <?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary" >#7C7B7B</color> <color name="colorPrimaryDark" >#424242</color> <color name="colorAccent" >#FF4081</color> <color name="bgGrey" >#C4C4C4</color> <color name="bgRed" >#FC5C70</color> <color name="bgBlue" >#4A90E2</color> <color name="black" >#000</color> <color name="lightGreyBg" >#DFDFDF</color> <color name="white" >#fff</color> </resources> 


3. Change the application theme:

In styles.xml, replace Theme.AppCompat.Light.DarkActionBar with Theme.AppCompat.Light.NoActionBar :
styles.xml
 <resources> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> </resources> 


4. Set the dimensions:

Replace sizes in dimens.xml with the following:
dimens.xml
 <resources> <dimen name="button.radius">10dp</dimen> <dimen name="sides">10dp</dimen> <dimen name="up_bottom">20dp</dimen> <dimen name="label_height">55dp</dimen> <dimen name="label_text_size">40dp</dimen> <dimen name="label_padding_sides">6dp</dimen> </resources> 


5. Create backgrounds for buttons:

You need to create three xml in the drawable folder:
bg_blue.xml
 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/bgBlue"/> <corners android:bottomRightRadius="@dimen/button_radius" android:bottomLeftRadius="@dimen/button_radius" android:topLeftRadius="@dimen/button_radius" android:topRightRadius="@dimen/button_radius"/> </shape> 


bg_red.xml
 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/bgRed"/> <corners android:bottomRightRadius="@dimen/button_radius" android:bottomLeftRadius="@dimen/button_radius" android:topLeftRadius="@dimen/button_radius" android:topRightRadius="@dimen/button_radius"/> </shape> 


bg_grey.xml
 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/bgGrey"/> <corners android:bottomRightRadius="@dimen/button_radius" android:bottomLeftRadius="@dimen/button_radius" android:topLeftRadius="@dimen/button_radius" android:topRightRadius="@dimen/button_radius"/> </shape> 


6. Change screen layout:

For the matrix, I will use GridLayout - perhaps not the best solution, but it seemed to me rather simple and short.

Just replace the existing code with mine - there is an empty GridLayout (fill it with the code in MainActivity) and two TextView-elements for indicators of player points (RelativeLayout inside another RelativeLayout - in order to center everything vertically. points score to the center horizontally).

Yes, and do not worry, in the preview you will not see anything, except for the top Bot, it should be so.
activity_main.xml
 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000" tools:context="com.livermor.myapplication.MainActivity"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerVertical="true" android:background="@color/lightGreyBg"> <View android:id="@+id/center" android:layout_width="10dp" android:layout_height="1dp" android:layout_centerInParent="true"/> <TextView android:id="@+id/upper_scoreboard" android:background="@drawable/bg_red" android:layout_width="match_parent" android:layout_height="55dp" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_marginLeft="@dimen/sides" android:layout_marginTop="15dp" android:layout_toLeftOf="@id/center" android:gravity="center_vertical|center_horizontal" android:paddingLeft="@dimen/label_padding_sides" android:paddingRight="@dimen/label_padding_sides" android:text=": 0" android:textColor="@color/white" android:textSize="@dimen/label_text_size"/> <GridLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/my_grid" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@+id/upper_scoreboard" android:layout_gravity="center" android:foregroundGravity="center" android:layout_marginLeft="@dimen/sides" android:layout_marginRight="@dimen/sides" android:layout_marginBottom="@dimen/up_bottom" android:layout_marginTop="@dimen/up_bottom"/> <TextView android:id="@+id/lower_scoreboard" android:background="@drawable/bg_blue" android:layout_width="match_parent" android:layout_height="@dimen/label_height" android:layout_alignParentRight="true" android:layout_alignParentEnd="true" android:layout_below="@+id/my_grid" android:layout_marginBottom="15dp" android:layout_marginRight="15dp" android:layout_toRightOf="@id/center" android:gravity="center_vertical|center_horizontal" android:paddingLeft="@dimen/label_padding_sides" android:paddingRight="@dimen/label_padding_sides" android:text=": 0" android:textColor="@color/white" android:textSize="@dimen/label_text_size"/> </RelativeLayout> </RelativeLayout> 


7. Create the MyButton class that inherits the Button:

We create our own class for the buttons so that it is more convenient to get the coordinates of each button in the matrix.
Code
 package com.livermor.plusminus; import android.content.Context; import android.util.AttributeSet; import android.widget.Button; public class MyButton extends Button { private MyOnClickListener mClickListener;//     MainActivity int idX = 0; int idY = 0; //,         public MyButton(Context context, int x, int y) { super(context); idX = x; idY = y; } public MyButton(Context context) { super(context); } public MyButton(Context context, AttributeSet attrs) { super(context, attrs); } public MyButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override // View    public boolean performClick() { super.performClick(); mClickListener.OnTouchDigit(this);//    return true; } public void setOnClickListener(MyOnClickListener listener){ mClickListener = listener; } public int getIdX(){ return idX; } public int getIdY(){ return idY; } //  MainActivity //************************************ public interface MyOnClickListener { void OnTouchDigit(MyButton v); } } 


8. And finally, edit the MainActivity class:

Code
 package com.livermor.plusminus; import android.graphics.Typeface; import android.os.Handler; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.animation.AlphaAnimation; import android.view.animation.AnimationSet; import android.view.animation.TranslateAnimation; import android.widget.Button; import android.widget.GridLayout; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends AppCompatActivity implements Game.ResultsCallback, MyButton.MyOnClickListener { private static final int MATRIX_SIZE = 5;//    2  20)) //ui private TextView mUpText, mLowText; GridLayout mGridLayout; private MyButton[][] mButtons; private Game game; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mGridLayout = (GridLayout) findViewById(R.id.my_grid); mGridLayout.setColumnCount(MATRIX_SIZE); mGridLayout.setRowCount(MATRIX_SIZE); mButtons = new MyButton[MATRIX_SIZE][MATRIX_SIZE];//5   5  //    for (int yPos = 0; yPos < MATRIX_SIZE; yPos++) { for (int xPos = 0; xPos < MATRIX_SIZE; xPos++) { MyButton mBut = new MyButton(this, xPos, yPos); mBut.setTextSize(30-MATRIX_SIZE); Typeface boldTypeface = Typeface.defaultFromStyle(Typeface.BOLD); mBut.setTypeface(boldTypeface); mBut.setTextColor(ContextCompat.getColor(this, R.color.white)); mBut.setOnClickListener(this); mBut.setPadding(1, 1, 1, 1); //      mBut.setAlpha(1); mBut.setClickable(false); mBut.setBackgroundResource(R.drawable.bg_grey); mButtons[yPos][xPos] = mBut; mGridLayout.addView(mBut); } } mUpText = (TextView) findViewById(R.id.upper_scoreboard); mLowText = (TextView) findViewById(R.id.lower_scoreboard); //      mGridLayout mGridLayout.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { setButtonsSize(); //    OnGlobalLayoutListener mGridLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this); } }); game = new Game(this, MATRIX_SIZE); //   game.startGame(); //   }//onCreate private void setButtonsSize() { int pLength; final int MARGIN = 6; int pWidth = mGridLayout.getWidth(); int pHeight = mGridLayout.getHeight(); int numOfCol = MATRIX_SIZE; int numOfRow = MATRIX_SIZE; // mGridLayout  if (pWidth >= pHeight) pLength = pHeight; else pLength = pWidth; ViewGroup.LayoutParams pParams = mGridLayout.getLayoutParams(); pParams.width = pLength; pParams.height = pLength; mGridLayout.setLayoutParams(pParams); int w = pLength / numOfCol; int h = pLength / numOfRow; for (int yPos = 0; yPos < MATRIX_SIZE; yPos++) { for (int xPos = 0; xPos < MATRIX_SIZE; xPos++) { GridLayout.LayoutParams params = (GridLayout.LayoutParams) mButtons[yPos][xPos].getLayoutParams(); params.width = w - 2 * MARGIN; params.height = h - 2 * MARGIN; params.setMargins(MARGIN, MARGIN, MARGIN, MARGIN); mButtons[yPos][xPos].setLayoutParams(params); //Log.w(TAG, "process goes in customizeMatrixSize"); } } } //MyButton.MyOnClickListener  //************************************************************************* @Override public void OnTouchDigit(MyButton v) { game.OnUserTouchDigit(v.getIdY(), v.getIdX()); } //Game.ResultsCallback  //************************************************************************* @Override public void changeLabel(boolean upLabel, int points) { if (upLabel) mUpText.setText(String.format(": %d", points)); else mLowText.setText(String.valueOf(String.format(": %d", points))); } @Override public void changeButtonBg(int y, int x, boolean row, boolean active) { if (active) { if (row) mButtons[y][x].setBackgroundResource(R.drawable.bg_blue); else mButtons[y][x].setBackgroundResource(R.drawable.bg_red); } else { mButtons[y][x].setBackgroundResource(R.drawable.bg_grey); } } @Override public void setButtonText(int y, int x, int text) { mButtons[y][x].setText(String.valueOf(text)); } @Override public void changeButtonClickable(int y, int x, boolean clickable) { mButtons[y][x].setClickable(clickable); } @Override public void onResult(int playerOnePoints, int playerTwoPoints) { String text; if (playerOnePoints > playerTwoPoints) text = " "; else if (playerOnePoints < playerTwoPoints) text = " "; else text = ""; Toast.makeText(this, text, Toast.LENGTH_SHORT).show(); // 1500    run new Handler().postDelayed(new Runnable() { @Override public void run() { recreate(); //   —   MainActivity } }, 1500); } @Override public void onClick(final int y, final int x, final boolean flyDown) { final Button currentBut = mButtons[y][x]; currentBut.setAlpha(0.7f); currentBut.setClickable(false); AnimationSet sets = new AnimationSet(false); int direction = flyDown ? 400 : -400; TranslateAnimation animTr = new TranslateAnimation(0, 0, 0, direction); animTr.setDuration(810); AlphaAnimation animAl = new AlphaAnimation(0.4f, 0f); animAl.setDuration(810); sets.addAnimation(animTr); sets.addAnimation(animAl); currentBut.startAnimation(sets); new Handler().postDelayed(new Runnable() { @Override public void run() { currentBut.clearAnimation(); currentBut.setAlpha(0); } }, 800); } } 


The finish


You can run the project. If something goes wrong, write in the comments or in PM. Just in case, once again I give a link to the githab . I will be glad to hear ideas on the bot and comments on the code.

Source: https://habr.com/ru/post/271899/


All Articles