Wire the game screen to the model


Replace the MainActivity layout and connect it to TicTacToeModel

Back to 015a: create TicTacToeModel

Replace activity_main.xml with the 3x3 grid

Replace the file content in app/src/main/res/layout/activity_main.xml with the grid below. Each button uses a tag in the form row,col and calls onCellClick.

כיצד תשעה כפתורים מגיעים למתודה אחת?

לכל כפתור יש אותו android:onClick, ולכן Android קוראת לאותה מתודה ומעבירה לה את ה־View שנלחץ. ה־tag מוסיף לכפתור נתון קטן שאינו מוצג: הקואורדינטות שלו במודל. כך אין צורך לכתוב תשע מתודות כמעט זהות.

זרימת האירוע היא:

tap → Button → onCellClick(view) → tag "row,col" → model → Button text

ה־tag מגיע מן ה־XML כמחרוזת, ולכן הקוד מפרק וממיר אותו למספרים. ביישום גדול נעדיף חיבור מטופס יותר, אך כאן המחרוזת הופכת את המיפוי בין grid חזותי למערך דו־ממדי לגלוי וברור.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center">

    <GridLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:columnCount="3"
        android:rowCount="3">

        <Button
            android:id="@+id/button00"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:tag="0,0"
            android:onClick="onCellClick"
            android:textSize="24sp" />

        <Button
            android:id="@+id/button01"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:tag="0,1"
            android:onClick="onCellClick"
            android:textSize="24sp" />

        <Button
            android:id="@+id/button02"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:tag="0,2"
            android:onClick="onCellClick"
            android:textSize="24sp" />

        <Button
            android:id="@+id/button10"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:tag="1,0"
            android:onClick="onCellClick"
            android:textSize="24sp" />

        <Button
            android:id="@+id/button11"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:tag="1,1"
            android:onClick="onCellClick"
            android:textSize="24sp" />

        <Button
            android:id="@+id/button12"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:tag="1,2"
            android:onClick="onCellClick"
            android:textSize="24sp" />

        <Button
            android:id="@+id/button20"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:tag="2,0"
            android:onClick="onCellClick"
            android:textSize="24sp" />

        <Button
            android:id="@+id/button21"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:tag="2,1"
            android:onClick="onCellClick"
            android:textSize="24sp" />

        <Button
            android:id="@+id/button22"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:tag="2,2"
            android:onClick="onCellClick"
            android:textSize="24sp" />

    </GridLayout>

</LinearLayout>

Update MainActivity.java to use the model

In app/src/main/java/com/example/tictacmenu/activities/MainActivity.java, remove the Edge-to-Edge setup, create the model, and handle cell clicks.

What this change is trying to achieve

Right now the game screen is just a layout. We want it to become a playable Tic-Tac-Toe screen that delegates all game rules to the external model. That means:

  • The Activity owns a TicTacToeModel instance.
  • Every button click becomes a move request to the model.
  • After each move, the UI updates to show the current player and resets the board if the game ends.

New events and how they are wired

onCellClick(View view)

  • This method is called directly by the XML. Each grid <Button> has android:onClick="onCellClick", so Android calls this method when the user taps that button.
  • The method reads the tag (like "0,1") to know which row/col the player selected.
  • It asks the model if the move is legal, then writes the current player symbol into the clicked button.
  • If the model reports a win or tie, it shows a Toast and resets the game state.

resetBoard()

  • This helper clears the UI after a win/tie.
  • It loops over the 9 button ids and resets their text to an empty string.

The wiring is purely XML-driven: the android:onClick attribute in activity_main.xml is what connects each button to onCellClick.

 import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.Toast;
 
-import androidx.activity.EdgeToEdge;
 import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.graphics.Insets;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.WindowInsetsCompat;
 
 import com.example.tictacmenu.R;
+import com.example.tictacmenu.models.TicTacToeModel;
 
 public class MainActivity extends AppCompatActivity {
+    private TicTacToeModel model;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        EdgeToEdge.enable(this);
         setContentView(R.layout.activity_main);
-        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
-            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
-            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
-            return insets;
-        });
+        model = new TicTacToeModel();
+    }
+
+    public void onCellClick(View view) {
+        Button button = (Button) view;
+        String tag = button.getTag().toString();
+        String[] position = tag.split(",");
+        int row = Integer.parseInt(position[0]);
+        int col = Integer.parseInt(position[1]);
+
+        if (model.isLegal(row, col)) {
+            model.makeMove(row, col);
+            button.setText(model.getCurrentPlayer());
+
+            if (model.checkWin()) {
+                model.changePlayer();
+                Toast.makeText(this, "Player " + model.getCurrentPlayer() + " wins!", Toast.LENGTH_SHORT).show();
+                model.resetGame();
+                resetBoard();
+            } else if (model.isTie()) {
+                Toast.makeText(this, "It's a tie!", Toast.LENGTH_SHORT).show();
+                model.resetGame();
+                resetBoard();
+            } else {
+                model.changePlayer();
+            }
+        }
+    }
+
+    private void resetBoard() {
+        int[] buttonIds = {
+                R.id.button00, R.id.button01, R.id.button02,
+                R.id.button10, R.id.button11, R.id.button12,
+                R.id.button20, R.id.button21, R.id.button22
+        };
+
+        for (int id : buttonIds) {
+            Button button = findViewById(id);
+            button.setText("");
+        }
     }
 }
בדיקת הבנה: מי משנה את המצב ומי רק מציג?

הסדר חשוב: קודם isLegal, אחר כך makeMove, ורק אז שינוי הטקסט בכפתור. אילו היינו משנים רק את הטקסט, המודל היה ממשיך לחשוב שהתא ריק. אילו היינו משנים רק את המודל, המשתמש לא היה רואה את המהלך. שתי השכבות צריכות להיות מסונכרנות, אך חוקיות המהלך נקבעת במודל.

בשלב הזה ענפי הניצחון והתיקו עדיין אינם נגישים, מפני שהמתודות המתאימות ב־015a הן placeholders. בדקו לחיצות חוקיות, חסימת לחיצה חוזרת והחלפת תור; אל תפרשו היעדר הודעת ניצחון כתקלה בחיבור ה־Activity.

At the end of this step: the game screen uses the external model and the 3x3 grid accepts legal moves. Commit if you are tracking steps.

Continue

מורה־עזר ב־Gemini

פתחו את Gem: מעבדת TicTacToe: Model, View Binding ועיצוב כדי לקבל רמזים, שאלות אבחון והסברים המתאימים לשלב שבו אתם נמצאים.