Full Game Code

I’m separating the game code into a Content Block just for clarity

CloudPage Code

Remember to get the CloudPage ID…

<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <title>Ampscript TicTacToe</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link href="https://fonts.googleapis.com/css2?family=Fredoka+One&display=swap" rel="stylesheet">
    <style>
        html,
        body {
            background-color: whitesmoke;
            font-family: 'Fredoka One', monospace;
            padding: 0px;
            margin: 0px;
            width: 100%;
            height: 100%;
        }
        
        div {
            display: inline-block;
        }
        
        .center {
            display: flex;
            align-items: center;
            justify-content: center;
            height: 80%;
        }
        
        .theGame {
            /* Yeah, you just lost it. */
            margin: 0;
        }
        
        .title {
            font-family: monospace;
            font-size: 16px;
            color: rgb(163, 163, 163);
            font-weight: 100;
            text-align: center;
        }
        
        .f {
            width: 64px;
            height: 64px;
            display: inline-block;
            background-color: white;
            border: 4px white solid;
            margin: 4px 1px;
            color: white;
            font-family: 'Fredoka One', monospace;
            font-size: 28px;
        }
        
        .f:hover {
            background-color: lightgray;
            transition: background-color 1s;
        }
        
        .e {
            background-color: white !important;
        }
        
        .x {
            background-color: #7A49BF !important;
            color: white;
        }
        
        .o {
            background-color: gold !important;
            color: white;
        }
        
        .d {
            background-color: lightgray !important;
            color: white;
        }
        
        .results {
            display: flex;
            width: 100%;
            height: 48px;
            margin-bottom: 6px !important;
            padding: 0px;
            font-size: large;
            align-items: center;
            justify-content: center;
            text-align: center;
        }
        
        .newGame {
            display: block;
            width: 100%;
            height: 48px;
            background-color: white;
            border: none;
            font-family: monospace;
            margin-bottom: 6px;
            color: rgb(163, 163, 163);
        }
        
        .newMode {
            height: 24px !important;
            color: rgb(163, 163, 163);
        }
        
        .newGame:hover,
        .newMode:hover {
            color: black
        }
        
        h1,
        h2,
        h3,
        h4,
        h5,
        h6,
        div {
            font-family: monospace;
        }

        .credits{
            filter: saturate(0%);
            font-size: smaller;
            opacity: 0.4;
            padding-top: 3rem;
        }
        .logo{
            height: 36px;
            width: auto;
            filter: saturate(0%);
        }
        .credits:hover, .logo{
            filter: saturate(100%);
            transition: filter 200ms;
            opacity: 1;
            transition: opacity 200ms;
        }

        a:hover{
            color: black;
        }

    </style>
</head>

<body>
    <div class="center">
        <div class="theGame">
            <div class="results title">
                Ampscript Tic-Tac-Toe
            </div>
          <br>


            <script runat="server">
                Platform.Load("core", "1.1.5");

                try {
            </script>

            %%=TreatAsContent(ContentBlockbyKey("Ampscript_TicTacToe_3_full"))=%%

            <script runat="server">
                } catch (error) {
                    Write(Stringify(error))
                }
            </script>
        </div>
    </div>
</body>
</html>

Ampscript

Replace the number with your Cloud Page ID at the start of the code

%%[ 
set @url = CloudPagesURL(4592) /* Replace the number with the ID of your CloudPage */

set @mode = RequestParameter('mode')
set @playerTurn = RequestParameter('playerTurn')
set @firstPlayer = RequestParameter('firstPlayer')
set @fields = RequestParameter('fields')

IF Empty(@mode)         THEN set @mode = "PvE" ENDIF
IF Empty(@playerTurn)   THEN set @playerTurn = "X" ENDIF
IF Empty(@firstPlayer)  THEN set @firstPlayer = IIF(Random(1,2) == 1, "X", "O") ENDIF
IF Empty(@fields)       THEN set @fields = "123456789" ENDIF

/* Get current field values :
    1 2 3
    4 5 6
    7 8 9
*/
set @f1 = Substring(@fields, 1, 1) /* 📐 Top Left Corner        */
set @f2 = Substring(@fields, 2, 1) /* 📏 Top Middle Edge        */
set @f3 = Substring(@fields, 3, 1) /* 📐 Top Right Corner       */
set @f4 = Substring(@fields, 4, 1) /* 📏 Middle Left Edge       */
set @f5 = Substring(@fields, 5, 1) /* 🟨 Middle Middle Middle   */
set @f6 = Substring(@fields, 6, 1) /* 📏 Middle Right Edge      */
set @f7 = Substring(@fields, 7, 1) /* 📐 Bottom Left Corner     */
set @f8 = Substring(@fields, 8, 1) /* 📏 Bottom Middle Edge     */
set @f9 = Substring(@fields, 9, 1) /* 📐 Bottom Right Corner    */

/* Build rows, columns and diagonals */
/* Rows */
set @r1 = Concat(@f1, @f2, @f3, '^')
set @r2 = Concat(@f4, @f5, @f6, '^')
set @r3 = Concat(@f7, @f8, @f9, '^')
/* Columns */  
set @c1 = Concat(@f1, @f4, @f7, '^')
set @c2 = Concat(@f2, @f5, @f8, '^')
set @c3 = Concat(@f3, @f6, @f9, '^')
/* Diagonals */
set @d1 = Concat(@f1, @f5, @f9, '^')
set @d2 = Concat(@f7, @f5, @f3, '^')

set @verification = Concat(@r1, @r2, @r3, @c1, @c2, @c3, @d1, @d2)

/* Get the game state */
IF 
    IndexOf(@verification,'OOO') > 0
        THEN 
            set @state = "Os Win!"
            set @gameOver = true
            set @stateClass = "o"
            set @firstPlayer = "X"
            
ELSEIF 
    IndexOf(@verification,'XXX') > 0
        THEN 
            set @state = "Xs Win!"
            set @gameOver = true
            set @stateClass = "x"
            set @firstPlayer = "O"
ELSEIF 
    Replace(Replace(@fields, "O"), "X") == ""
        THEN 
            set @state = "Draw!"
            set @gameOver = true
            set @firstPlayer = IIF(@playerTurn == "X", "O", "X")
ELSE  
    set @gameOver = false
ENDIF

 /* Game modes */ 
IF
    @mode == "PvP"
    AND 
    @gameOver == false
        THEN
            set @debug = "PvP Mode"
            set @state = Concat("<sup>Local Multiplayer:</sup><br>", @playerTurn, "s turn")
            set @nextPlayer = IIF(@playerTurn == "X", "O", "X")
ELSEIF
    @mode == "PvE"
    AND 
    @gameOver == false
        THEN
            set @state = "Single Player"
            set @playerTurn = 'X'
            /* The game is not over yet and AI can make a move */
            set @opportunities = ReplaceList(@verification, "E", "1", "2", "3", "4", "5", "6", "7", "8", "9")
            set @opportunitiesForO = ReplaceList(@opportunities, "WIN", "OOE", "OEO", "EOO")
            set @opportunitiesForX = ReplaceList(@opportunities, "WIN", "XXE", "XEX", "EXX")

            /* Move number detection */
            set @moveNumber = ADD(1, Length(ReplaceList(@fields, "", "1", "2", "3", "4", "5", "6", "7", "8", "9")))

            set @chance = Random(1,10)

            /* AI Logic: START */
            /* Grab the win opportuniny*/
            IF
                IndexOf(@opportunitiesForO, "WIN") > 0
                    THEN
                        set @winPosition = IndexOf(@opportunitiesForO, "WIN")

                        set @emptyPositionInWin = IndexOf(Substring(@opportunities, @winPosition, 3), "E")
                        set @fieldPosition = Add(@winPosition, Add(@emptyPositionInWin, -1))
                        set @fieldIndex = Substring(@verification, @fieldPosition, 1)
                        set @fields = Replace(@fields, @fieldIndex, "O")

                        set @state = "Os Win!"
                        set @stateClass = "o"
                        set @gameOver = true
                        set @firstPlayer = "X"
            /* Block Xs from winning */
            ELSEIF
                IndexOf(@opportunitiesForX, "WIN") > 0
                    THEN
                        set @winPosition = IndexOf(@opportunitiesForX, "WIN")
                                            
                        set @emptyPositionInWin = IndexOf(Substring(@opportunities, @winPosition, 3), "E")
                        set @fieldPosition = Add(@winPosition, Add(@emptyPositionInWin, -1))
                        set @fieldIndex = Substring(@verification, @fieldPosition, 1)
                        set @fields = Replace(@fields, @fieldIndex, "O")
            
            /* First Move Logic */
            ELSEIF 
                @fields == "123456789"
                AND
                @firstPlayer == "O"
                    THEN 
                        set @case = "First move: Empty field"
                        
                        /* Don't be a perfect opponent */
                        IF @chance <= 6
                            THEN 
                                set @fields = Replace(@fields, Substring('1379', Random(1,4), 1), "O") /* Select a corner */
                        ELSEIF
                            @chance > 6 AND @chance < 10
                            THEN 
                                set @fields = Replace(@fields, "5", "O") /* Select the middle*/
                        ELSEIF
                            @chance == 10
                            THEN 
                                set @fields = Replace(@fields, Multiply(Random(1,4),2), "O") /* Select an edge*/
                        ENDIF
            /* Second move */
            ELSEIF
                /* if X has played a corner field first */
                @fields == "X23456789"
                OR
                @fields == "12X456789"
                OR
                @fields == "123456X89"
                OR
                @fields == "12345678X"
                    THEN
                        set @fields = Replace(@fields, "5", "O")
            ELSEIF
                /* if X has played the center field first */
                @fields == "1234X6789"
                    THEN
                        set @fields = Replace(@fields, Add(Multiply(Random(1,4),2), -1), "O")
            /* Fourth Move: Counter play for corner forks :D */
            ELSEIF
                (@fields == "X234O678X"
                OR
                @fields == "12X4O6X89")
                AND
                @chance >= 9
                    THEN
                        set @fields = Replace(@fields, Multiply(Random(1,4),2), "O")
            ELSEIF
                /*  When none of the above cases apply, select a field at random */
                @moveNumber > 1
                    THEN
                    set @tempFields = ReplaceList(@fields, "", "X", "O")
                    set @newField = Substring(@tempFields, Random(1, Length(@tempFields)), 1)
                    set @fields = Replace(@fields, @newField, "O")
            ENDIF
        /* AI Logic: END*/
        /* Check if the game didn't end with a draw after the AI move */
        IF
            @gameOver == false
            AND
            Replace(Replace(@fields, "O"), "X") == ""
                THEN 
                    set @state = "Draw!"
                    set @gameOver = true
                    set @stateClass = "d"
        ENDIF
ENDIF       

/* Field Display: START*/
FOR @i = 1 TO 9 DO

    IF 
        Substring(@fields, @i, 1) == "X"
        THEN
            ]%% 
            <div>
                <input type="submit" class="f x" value="X" disabled>
            </div>
            %%[
    ELSEIF 
        Substring(@fields, @i, 1) == "O"
        THEN
            ]%% 
            <div>
                <input type="submit" class="f o" value="O" disabled>
            </div>
            %%[
    ELSEIF 
        Substring(@fields, @i, 1) != "X"
        AND
        Substring(@fields, @i, 1) != "O"
        AND
        IndexOf(@state, "Win") > 0
        THEN
            ]%% 
            <div>
                <input type="submit" class="f" value=" " disabled>
            </div>
            %%[
    ELSE 
        ]%%
            <div>
                <form method="POST" action="%%=RedirectTo(@url)=%%">
                    <input type="hidden" name="fields" value="%%=v(Replace(@fields, @i, @playerTurn))=%%">
                    <input type="hidden" name="playerTurn" value="%%=v(@nextPlayer)=%%">
                    <input type="hidden" name="mode" value="%%=v(@mode)=%%">
                    <input type="hidden" name="firstPlayer" value="%%=v(@firstPlayer)=%%">
                    <input type="submit" class="f" value="%%=v(@playerTurn)=%%">
                </form>
            </div>
        %%[
    ENDIF

    IF MOD(@i, 3) == 0
        THEN ]%% <br> %%[
        ENDIF   

NEXT @i

/* Field Display: STOP*/
]%%
<!-- Display the game state -->
<br>
<div class="results %%=v(@stateClass)=%%">
    <div>%%=v(@state)=%%</div>
</div>

<!-- Reset the board -->
<form method="POST" action="%%=RedirectTo(@url)=%%">
    <input type="submit" class="newGame" value="New Round">
    <input type="hidden" name="mode" value="%%=v(@mode)=%%">
    <input type="hidden" name="fields" value="123456789">
    <input type="hidden" name="playerTurn" value="%%=v(@nextPlayer)=%%">
    <input type="hidden" name="firstPlayer" value="%%=v(@firstPlayer)=%%">
</form>

<!-- Switch the game mode and reset the board-->
<form method="POST" action="%%=RedirectTo(@url)=%%">
    <button type="submit" class="newGame">Switch Mode:<br> %%=v(IIF(@mode == "PvP", "Single Player ", "Local Multiplayer"))=%%</button>
    <input type="hidden" name="mode" value="%%=v(IIF(@mode == "PvP", "PvE", "PvP"))=%%">
    <input type="hidden" name="fields" value="123456789">
    <input type="hidden" name="playerTurn" value="%%=v(@nextPlayer)=%%">
    <input type="hidden" name="firstPlayer" value="%%=v(@firstPlayer)=%%">
</form>

Good luck playing against the NPC 🙂