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 🙂