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
TicTacToeModelinstance. - 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>hasandroid: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
Toastand 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 ועיצוב כדי לקבל רמזים, שאלות אבחון והסברים המתאימים לשלב שבו אתם נמצאים.