5.15 Modular Function Decomposition, Game State Machines & Embedded UI Input Scanning
Executive Summary: Building a full interactive Tic-Tac-Toe system. We analyze functional modular decomposition, separation of display rendering from game logic state machines, and translating matrix grid games into embedded button matrix keypad scanning algorithms.
💻 1. Annotated Source Code
#include <iostream> #include <string> using namespace std; const int ROWS = 3; const int COLS = 3; void runGame(); void initializeGameBoard(string gameBoard[ROWS][COLS]); void printCurrentBoard(string gameBoard[ROWS][COLS]); void getUserInput(bool xTurn, string gameBoard[ROWS][COLS]); bool cellAlreadyOccupied(int row, int col, string gameBoard[ROWS][COLS]); string getWinner(string gameBoard[ROWS][COLS]); bool isBoardFull(string gameBoard[ROWS][COLS]); int main() { runGame(); return 0; }//end main //-------------------------------------------------// //----------| the game loop - runGame |------------// //-------------------------------------------------// void runGame() { string winner = ""; bool xTurn = true; //start with X's turn int theRow = 0; int theCol = 0; string gameBoard[ROWS][COLS]; initializeGameBoard(gameBoard); //initial print printCurrentBoard(gameBoard); while (winner == "") { if (xTurn) { cout << "It is X's turn" << endl; } else { cout << "It is O's turn" << endl; } getUserInput(xTurn, gameBoard); cout << endl; //extra spacing printCurrentBoard(gameBoard); //reprint the board winner = getWinner(gameBoard); //check for a winner xTurn = !xTurn; //flip it if (winner == "" && isBoardFull(gameBoard)) { winner = "C"; //Cat's game... no winner! } }//end while //cat's game? cout << endl; //extra space before if (winner == "C") { cout << "It was the Cat's game! NO WINNER!" << endl; } else { cout << "The winner is " << winner << endl; //print's X or O } cout << endl; //extra space } //-------------------------------------------------// //------------| initialize the board |-------------// //-------------------------------------------------// void initializeGameBoard(string gameBoard[ROWS][COLS]) { for (int i = 0; i < ROWS; i++) { for (int j = 0; j < COLS; j++) { gameBoard[i][j] = " "; //empty space } } }//end initialize game board //-------------------------------------------------// //----------| print the current board |------------// //-------------------------------------------------// void printCurrentBoard(string gameBoard[ROWS][COLS]) { for (int i = 0; i < ROWS; i++) { for (int j = 0; j < COLS; j++) { cout << gameBoard[i][j]; if (j < 2) { cout << " | "; } } cout << endl; if (i < 2) { cout << "- - - - -" << endl; } } cout << endl; //extra spacing }//end print the current board //-------------------------------------------------// //------| get user input and place symbol |--------// //-------------------------------------------------// void getUserInput(bool xTurn, string gameBoard[ROWS][COLS]) { int row = -1; int col = -1; bool keepAsking = true; while (keepAsking) { //keep asking until you get a valid answer cout << "Please enter the row THEN the column, each from 0, 1, or 2, separated by a space" << endl; cin >> row; cin >> col; if (row >= 0 && col >= 0 && row <= 2 && col <= 2) { //valid/in-range selection //but it STILL could be occupied by an X or O already... if (!cellAlreadyOccupied(row, col, gameBoard)) { //only set the cell if the row/col is valid AND not occupied keepAsking = false; } else { cout << "That cell is already occupied!" << endl; } } }//end while //by the time it gets here, we know it's a VALID row and col, //in range, and not already occupied! if (xTurn) //must be an X { gameBoard[row][col] = "X"; } else //must be an O { gameBoard[row][col] = "O"; } }//end getUserInput //-------------------------------------------------// //------| test if cell is already occupied |-------// //-------------------------------------------------// bool cellAlreadyOccupied(int row, int col, string gameBoard[ROWS][COLS]) { return gameBoard[row][col] != " "; //if not a space, then it's occupied }//end cellAlreadyOccupied //-------------------------------------------------// //-----------------| get winner |------------------// //-------------------------------------------------// string getWinner(string gameBoard[ROWS][COLS]) { //check rows for (int i = 0; i < ROWS; i++) { if (gameBoard[i][0] != " " && gameBoard[i][0] == gameBoard[i][1] && gameBoard[i][1] == gameBoard[i][2]) { return gameBoard[i][0]; //we have a match (horizontal)! } }//end for //check columns for (int i = 0; i < COLS; i++) { if (gameBoard[0][i] != " " && gameBoard[0][i] == gameBoard[1][i] && gameBoard[1][i] == gameBoard[2][i]) { return gameBoard[0][i]; //we have a match (vertical)! } }//end for //check diagonals //upper-left to bottom right diagonal if (gameBoard[0][0] != " " && gameBoard[0][0] == gameBoard[1][1] && gameBoard[1][1] == gameBoard[2][2]) { return gameBoard[0][0]; //we have a diagonal match! } //lower-left to upper right diagonal if (gameBoard[2][0] != " " && gameBoard[2][0] == gameBoard[1][1] && gameBoard[1][1] == gameBoard[0][2]) { return gameBoard[2][0]; //we have a diagonal match! } return ""; //no winner yet! }//end getWinner //-------------------------------------------------// //-----------------| board full? |-----------------// //-------------------------------------------------// bool isBoardFull(string gameBoard[ROWS][COLS]) { int countFill = 0; for (int i = 0; i < ROWS; i++) { for (int j = 0; j < COLS; j++) { if (gameBoard[i][j] != " ") { countFill++; } } } return countFill == 9; //all 9 cells are full, then board is full }
📐 2. Architecture & UML Class Model
<<compilation-unit>>
TicTacToeModule
Game Module
Attributes / Data Members
-grid[3][3] : char
-currentTurn : char
Operations / Methods
+initializeGame() : void
+drawBoard() : void
+takeTurn(row: int, col: int) : bool
+checkWinner() : char
📚 3. Core C++ Concepts Deep-Dive
1. Functional Decomposition
Breaking a complex system into focused, single-responsibility functions (drawBoard, getUserInput, checkWinCondition, switchPlayer) maximizes testability and maintainability.
2. State Machine Logic
Managing turns, victory checks, and cat's game (draw) conditions using an explicit state machine model.
⚡ 4. Embedded Systems & Hardware Reality
1. Matrix Keypad Scanning
In embedded hardware, a $3\times 3$ grid is physically wired as a Matrix Keypad (3 row GPIOs, 3 column GPIOs). The microcontroller drives rows low sequentially and reads column inputs to detect button presses with hardware debounce filtering.
💡 5. Production-Ready Embedded Refactoring
Embedded matrix keypad scanner state machine:
💡 Production-Ready Refactor
#include <cstdint> #include <array> enum class GridCell : uint8_t { Empty = 0, PlayerX, PlayerO }; enum class GameState : uint8_t { InProgress = 0, X_Won, O_Won, Draw }; class TicTacToeEngine { private: std::array<GridCell, 9> board_{}; GridCell current_player_{GridCell::PlayerX}; public: bool place_move(uint8_t cell_index) noexcept { if (cell_index >= 9 || board_[cell_index] != GridCell::Empty) return false; board_[cell_index] = current_player_; current_player_ = (current_player_ == GridCell::PlayerX) ? GridCell::PlayerO : GridCell::PlayerX; return true; } const std::array<GridCell, 9>& board() const noexcept { return board_; } };
📝 Knowledge Verification Quiz
Test your understanding of the C++ concepts and embedded microcontroller trade-offs covered in this guide. Click any option for instant feedback.
Q1. How does a microcontroller scan a 3x3 matrix button keypad using only 6 GPIO pins?
Detailed Explanation:
Matrix scanning drives one row active at a time and reads column pins, detecting 9 buttons with only 3 rows + 3 cols = 6 pins.
Q2. Why is 'button debouncing' required when reading physical button matrix inputs?
Detailed Explanation:
Mechanical contacts bounce when pressed; debouncing (software delay or timer filtering) ensures only a single stable transition is registered.
Q3. What is the primary architectural advantage of decoupling game state logic from display rendering functions?
Detailed Explanation:
Separating state logic from I/O allows running automated unit tests on host machines without hardware dependencies.
Q4. How many total win combinations exist on a 3x3 Tic-Tac-Toe grid?
Detailed Explanation:
There are 3 horizontal rows + 3 vertical columns + 2 diagonals = 8 possible winning lines.