SAP Builders Blog Posts
Learn from peers about their low-code journey and write your own blog posts to share your thoughts and experiences as you become an SAP Builder.
cancel
Showing results for 
Search instead for 
Did you mean: 
JoeBinkley
Product and Topic Expert
Product and Topic Expert
1,297

I'd be the first to admit that I'm a bit of a geek. During the winter holidays, I indulge in my passion for technology, using it as a means to learn and solve puzzles. One of my projects this year was constructing a Tic-Tac-Toe game using SAP Build Apps. This provided an excellent opportunity to delve further into custom components and flow functions. What follows is a tour of the project, not exactly a tutorial, but a pretty good exposure to how I did it. For "pro" SAP Builders, I've included an alternative that involves a bit of JavaScript in SAP Build Apps.

Tic-Tac-Toe AppTic-Tac-Toe App

Part of my inspiration for this was the three part series on custom components by @Dan_Wroblewski:

Please see Dan's blogs for more on custom components.

The GameSpot Component

My app is built using a custom component, which I called  "GameSpot".  To get started, I put a Container on my app canvas, renamed it "GameSpot", and filled it with three Icon components. The icons used were "question", "cicle-o", and "times" from the "Font Awesome" icon library. I also set the font size to 64. In the running game, only one of those Icons is visible at a time. (Three images or three text components could have worked here, as well.)

Then, I clicked on CONVERT TO NEW COMPONENT on the container.

Making a custom compenentMaking a custom compenent

In the context of the new component, I clicked PROPERTIES and added three Private variables: ? Visible, O Visible, and X Visible that control which Icon is shown as the game progresses.

Private VariablesPrivate Variables

I also added three Custom properites:

  • Marker : This text property indicates the current setting of the GameSpot. At the beginning of a game, it will hold "?" and then it will be updated to "X" or "O" as the game progresses. This is a two-way binding. 
  • Set X: This is a True/False property. When true, it indicates that the next click is an "X" and, when false, the next click is an "O". This property also has a two-way binding.
  • Show ?: This property is used to reset the component to the "?" state.

Component Properties WithBorder.png

GameSpot Component Logic

At the start of a game, ? Visible is true, so the "?" is visible, and the both O Visible and X Visible are false. 

The GameSpot component responds to two events: Component Tap and Property "Show ?" Changed

Component Tap

On the component tap event, the logic (in the Process Tap flow function) determines if the spot has already been played by checking the ? Visible. False means the spot has already been played, so the function follows the output 2 branch and no updates happens.  

Otherwise, the Set X property is used to determine whether "X Visible" or "O Visible" should be set to True. The logic then also updates the value of the Marker property to indicate either "X" or "O" and toggle the value of the the Set X property.Main Component Tap LogicMain Component Tap Logic

 

Detemine which Icon should be visibleDetemine which Icon should be visible

Property "Show ?" Changed

The Show ? property change event is used as a trigger to reset the GameSpot for a new game. It sets the "?" Icon to visible and hides the other two.  

Set qvisible to True, ovisible to False, and xvisible to FalseSet qvisible to True, ovisible to False, and xvisible to False

Minimal Tic-Tac-Toe game

I then added nine copies of the GameSpot component laid out in a grid on the app home page and defined two page level properties, Xturn and resetQ. I bound those properties to each GameSpot component's Set X and Show ? properties. (For now, Marker is not bound to anything.)

We now have a functional Tic-Tac-Toe game. But the users have to determine when a "win" happens.  (Of course, that's normal for Tic-Tac-Toe.) 

Minimal Page VariablesMinimal Page Variables

Minimal BindingsMinimal Bindings

Detecting a Winner

In order to detect a winner, the app needs to determine when three Xs ot three Os occur in a line.  To do this, we need a data structure that represents the current state of the board and some logic to look at the data structure after each turn to determine if there's a winner.

Page variables needed for win detectionPage variables needed for win detectionThe current state of the board is represented in the page variable boardList, which is a list of text items. That list starts out as a list of nine "?". After a couple moves is might look like this:  

 

 

 

 

 

 

 

["O", "?", "X", "?", "X", "?", "?", "?", "?"]

 

 

 

 

 

 

 

In order to keep the boardList updated as play progresses, the Marker property of each GameSpot is set to a formula like this:

 

 

 

 

 

 

 

pageVars.boardList[1]

 

 

 

 

 

 

 

The number indicates the index of the "cell" within the grid, with 0, 1, and 2 for the first row, 3, 4, and 5 for the second row, and 6, 7, and 8 for the final row. 

Grid.png

Check Three Cells flow function

Check Three Cells is used to determine if three cells match each other. It takes as inputs, the board list and three cell positions to check, First, it checks whether the first cell is a "?". if so, we know immediately that there's no need to look further.  Then, it check whether the first and second cell match and whether the second and thir cell match.  On a match, the page variable latestBoardCheck is updated. 

Check Three Cells flow functionCheck Three Cells flow function

The conditions checked are:

 

 

 

 

 

 

 

NOT(IS_EQUAL(inputs.cellList[inputs.position1], "?"))
IS_EQUAL(inputs.cellList[inputs.position1], inputs.cellList[inputs.position2])
IS_EQUAL(inputs.cellList[inputs.position2], inputs.cellList[inputs.position3])

 

 

 

 

 

 

 

The latestBoardCheck variable is update used this formula:

 

 

 

 

 

 

 

{
  cellindices: [
    inputs.position1, 
    inputs.position2,
    inputs.position3
  ],
  value: inputs.cellList[inputs.position1]
}

 

 

 

 

 

 

 

Check Board flow function

We need to look for three in a row for eight different patterns of cells:

  • Three rows: 0, 1, and 2; 3, 4, and 5; or 6, 7, and 8
  • Three columns: 0, 3, and 6; 1, 4, and 7; or 2, 5, and 8
  • Two diagonals: 0, 4, and 8 or 2, 4, and 6.

The Check Board flow function calls Check Three Cells for each of the patterns.

Check Board flow functionCheck Board flow function

Page Level Event triggers

At the page level, three event triggers have attached logic.

On Page mounted, the page variables are set to their initial state. 

On Page variable 'Xturn' changed, the board check, as described above, is executed.

On Page variable 'latestBoardCheck' changed, an alert is presented indicating the win.

Page event logicPage event logic

Game Win alertGame Win alert

A "Pro-Code" Alternative

The flow function for determining when the game has been one is a little tedious to build using drag and drop editiing.  Those with coding skills may find coding that logic a bit easier using JavaScript. Here's what that looks like:

Alternative using JavaScriptAlternative using JavaScript

Here's the code:

 

 

 

 

 

 

 

var board = inputs.boardList;

function detectThreeInARow(cells) {
  function checkCells(cell1, cell2, cell3) {
    return cell1 !== "?" && cell1 === cell2 && cell2 === cell3;
  }

  if (checkCells(cells[0], cells[1], cells[2])) {
    return { cellindices: [0, 1, 2], value: cells[0] };
  }
  if (checkCells(cells[3], cells[4], cells[5])) {
    return { cellindices: [3, 4, 5], value: cells[3] };
  }
  if (checkCells(cells[6], cells[7], cells[8])) {
    return { cellindices: [6, 7, 8], value: cells[6] };
  }
  if (checkCells(cells[0], cells[3], cells[6])) {
    return { cellindices: [0, 3, 6], value: cells[0] };
  }
  if (checkCells(cells[1], cells[4], cells[7])) {
    return { cellindices: [1, 4, 7], value: cells[1] };
  }
  if (checkCells(cells[2], cells[5], cells[8])) {
    return { cellindices: [2, 5, 8], value: cells[2] };
  }
  if (checkCells(cells[0], cells[4], cells[8])) {
    return { cellindices: [0, 4, 8], value: cells[0] };
  }
  if (checkCells(cells[2], cells[4], cells[6])) {
    return { cellindices: [2, 4, 6], value: cells[2] };
  }
  return false;
}

var result = detectThreeInARow(board);

return { result: result };

 

 

 

 

 

 

 

Before this project, most of my SAP Build Apps projects were focused around connections to APIs and usually focused on providing an interface to a some sort of service. Those rarely involved custom components, so this was a great opportunity to learn more about how that works. 

Now, what should I do next year? Maybe a Wordle derivative...

 

 

 

2 Comments