Project 5.15 Section 5 ⚡ Embedded Relevance: High State Machines Modularity Matrix Grid Input Debouncing Embedded Architecture

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

📐 Modular Procedural Tic-Tac-Toe Game Architecture
+ Public - Private # Protected
<<compilation-unit>> TicTacToeModule Game Module
-grid[3][3] : char
-currentTurn : char
+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?
A It sequentially drives each row pin LOW and reads the 3 column pins to detect intersections
B It uses 9 separate analog-to-digital converters
C It connects all buttons to a single ground wire
D It uses WiFi telemetry
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?
A Mechanical switch contacts bounce physically for 5-20 milliseconds upon closing, creating rapid false transition pulses
B Buttons generate AC mains voltage
C To prevent the CPU clock from freezing
D To calibrate temperature drift
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?
A The core state logic can be unit-tested on a PC without requiring physical LCD hardware or console I/O
B It makes the game run in 3D
C It compresses source code into binary
D It allows multiple players on CAN bus
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?
A 8 combinations (3 horizontal rows, 3 vertical columns, 2 diagonals)
B 9 combinations
C 6 combinations
D 12 combinations
Detailed Explanation: There are 3 horizontal rows + 3 vertical columns + 2 diagonals = 8 possible winning lines.