// ******** This source code corresponds to tutorial 8 of the developer guide ********** // Import the win32api MessageBox function import user32 int MessageBoxA(int, string, string, int); // The font used for displaying text declare global MyFont font; // The pen for the grid declare global GridPen pen; // The O and X images declare global SymbolX image; declare global SymbolO image; declare global PosX int; declare global PosY int; // The grid array declare global GridStatus[9] int; int global FREESQUARE = 0; int global XSQUARE = 1; int global OSQUARE = 2; // The grid size declare global iGridTopX int; declare global iGridTopY int; declare global iGridWidth int; declare global iGridHeight int; // ************************************************************************************************************ // Called once when the plugin is started. Do one time setup here. // ************************************************************************************************************ function Init() { createfont(MyFont, "Arial", 12); createpen(GridPen, 0, 2, RGB(255, 255, 255)); // Create the O and X symbols from the GIFs. Parameter 1 is the image variable // to load into, parameter 2 is the path to the image to load. // Supply an invalid filename to force the error message to appear if(createimage(SymbolX, "SSE\\TicTacImages\\NOT-THERE.gif") == FALSE) { MessageBoxA(0, "Couldn't load the X image", "Error", 0); } createimage(SymbolO, "SSE\\TicTacImages\\tictac-o.gif"); // Call the function that resets the grid status ResetGridStatus(); } // ************************************************************************************************************ // The plugin must draw all output here ONLY. This is the only valid drawing function. // ************************************************************************************************************ function Render() { int iWidth = getpanelwidth(); int iHeight = getpanelheight(); // Set the number of pixels in from the left of the display panel the grid should be at iGridTopX = 10; iGridWidth = iWidth - iGridTopX - 10; // The grid should be 1/4 of the way from the top of the display panel, and take up half the // total height of the panel. iGridTopY = iHeight / 4; iGridHeight = iHeight / 2; selectpen(GridPen); // First draw the vertical lines at 1/3 and 2/3 of the grid width int iGridThirdW = iGridWidth / 3; drawline(iGridTopX + iGridThirdW, iGridTopY, iGridTopX + iGridThirdW, iGridTopY + iGridHeight); drawline(iGridTopX + (iGridThirdW * 2), iGridTopY, iGridTopX + (iGridThirdW * 2), iGridTopY + iGridHeight); // Next draw the horizontal lines at 1/3 and 2/3 of the grid height int iGridThirdH = iGridHeight / 3; drawline(iGridTopX, iGridTopY + iGridThirdH, iGridTopX + iGridWidth, iGridTopY + iGridThirdH); drawline(iGridTopX, iGridTopY + (iGridThirdH * 2), iGridTopX + iGridWidth, iGridTopY + (iGridThirdH * 2)); int iSymbolPosX = iGridTopX; int iSymbolPosY = iGridTopY; // Iterate through the grid array and draw any symbols we find. The iGridElement++ // statement is shorthand for "iGridElement = iGridElement + 1", ie: increase the // value of iGridElement by one. for(int iGridElement = 0; iGridElement < 9; iGridElement++) { // Is this grid element an X? If so, draw the X symbol if(GridStatus[iGridElement] == XSQUARE) drawimage(SymbolX, iSymbolPosX, iSymbolPosY); // Is this grid element an O? If so, draw the O symbol if(GridStatus[iGridElement] == OSQUARE) drawimage(SymbolO, iSymbolPosX, iSymbolPosY); // Note: This is not the best way to calculate where to draw the symbols! It's // the easiest to understand though, which is the purpose of this tutorial. // Update the positioning variables. If we're currently processing element 2 or 5, // the next symbol must be reset to the left hand side of the grid, on the next line. if(iGridElement == 2 or iGridElement == 5) { iSymbolPosX = iGridTopX; iSymbolPosY = iSymbolPosY + (iGridHeight / 3); } else iSymbolPosX = iSymbolPosX + iGridWidth / 3; } } // ************************************************************************************************************ // Called when left mouse button is released. Params give X,Y pos of cursor at release. // ************************************************************************************************************ function OnMouseLButtonUp(int iX, int iY) { if(iX > iGridTopX and iY > iGridTopY and iX < iGridTopX + iGridWidth and iY < iGridTopY + iGridHeight) { // Store the size of 1/3 of the grid width and height int iGridThirdW = iGridWidth / 3; int iGridThirdH = iGridHeight / 3; // Set the click position to a zero based index, ie: remove the offset that put the // grid in the middle of the plugin window. This makes it much easier to calculate // where the click was. iX = iX - iGridTopX; iY = iY - iGridTopY; // By dividing the X position of the click by the size of 1/3 of the grid, we can // tell which column was clicked. Because we use integers the decimal is automatically // floored by Shiny for us. Example: If the grid is 30 pixels wide, 1/3 of the grid // is 10 pixels. If the click is at 15 pixels X position, 15 / 10 = 1.5, which is // floored to 1 for us. Therefore the click was in the second column of the grid! // (Remember: zero based index!) int iElementClicked = iX / iGridThirdW; // If we now divide the Y position of the click by 1/3 of the grid height we will // find out what row was clicked (for the same reasons as the X calc above). There // are 3 elements per row, so we multiply the value by 3. We can then add that // to the iElementClicked variable to point us at the right element! iElementClicked = iElementClicked + ((iY / iGridThirdH) * 3); // Now test if the click was on an empty square if(GridStatus[iElementClicked] == FREESQUARE) { // Yes it was, so set the square to an X! GridStatus[iElementClicked] = XSQUARE; // Now we need to make the computer move. As per the tutorial we'll use dumb // logic here just to demonstrate the principle. We search for the first // free square and move there. int iGridElement = 0; bool bFoundFreeSquare = False; // Loop while the conditions are true while(iGridElement < 9 and bFoundFreeSquare == False) { // Is this a free square? if(GridStatus[iGridElement] == FREESQUARE) { // Yes! Make the move by assigning it to the O symbol GridStatus[iGridElement] = OSQUARE; // Flag that we've found a free square and we want to exit the loop bFoundFreeSquare = True; } iGridElement++; } // If no free squares were found, reset the grid because it's full if(bFoundFreeSquare == False) ResetGridStatus(); } } // Tell Brass to redraw our panel immediately redraw(); } // ************************************************************************************************************ // This function resets the grid status array // ************************************************************************************************************ function ResetGridStatus() { // Initialize the grid array. The array has 9 elements on a zero based index, which means // elements 0 to 8 are the only valid indexes. for(int iGridElement = 0; iGridElement < 9; iGridElement = iGridElement + 1) { // Set each element to a free square GridStatus[iGridElement] = FREESQUARE; } }