The computer thinks of a number between 1 and 100; you guess until you find it. It is a toy, but it raises a real question: where do you keep a value that must survive from one click to the next, but must not be sent to the browser?
Every named control's value travels to the browser and back. Put the secret number in a hidden
input and anyone with developer tools can read it. So the secret goes into per-user session
state instead, with sp_api_modal_set and sp_api_modal_get, which stays on the server.
The script
-- Variables
DECLARE @Secret NVARCHAR(MAX);
DECLARE @Tries NVARCHAR(MAX);
DECLARE @Guess NVARCHAR(MAX);
DECLARE @GuessButton NVARCHAR(MAX);
DECLARE @NewButton NVARCHAR(MAX);
DECLARE @GuessNo INT;
DECLARE @SecretNo INT;
DECLARE @TriesNo INT;
DECLARE @Hint NVARCHAR(MAX);
DECLARE @Message NVARCHAR(MAX);
-- Server-side state: the secret and the number of tries
EXEC sp_api_modal_get @key = N'number_game_secret', @value = @Secret OUT;
EXEC sp_api_modal_get @key = N'number_game_tries', @value = @Tries OUT;
-- Client-side state: what the user typed and clicked
EXEC sp_api_modal_get_value @name = N'@Guess', @value = @Guess OUT;
EXEC sp_api_modal_get_value @name = N'@GuessButton', @value = @GuessButton OUT;
EXEC sp_api_modal_get_value @name = N'@NewButton', @value = @NewButton OUT;
-- Start a game when there is none, or when the player asks for a new one
IF @Secret IS NULL OR @NewButton IS NOT NULL
BEGIN
SET @SecretNo = 1 + ABS(CHECKSUM(NEWID())) % 100;
SET @Secret = CONVERT(NVARCHAR(10), @SecretNo);
SET @Tries = N'0';
EXEC sp_api_modal_set @key = N'number_game_secret', @value = @Secret;
EXEC sp_api_modal_set @key = N'number_game_tries', @value = @Tries;
EXEC sp_api_modal_value @name = N'@NewButton', @value = NULL;
EXEC sp_api_modal_value @name = N'@GuessButton', @value = NULL;
SET @GuessButton = NULL;
END
SET @SecretNo = TRY_CONVERT(INT, @Secret);
SET @TriesNo = ISNULL(TRY_CONVERT(INT, @Tries), 0);
SET @GuessNo = TRY_CONVERT(INT, @Guess);
-- Work out the hint before drawing, so it shows on this run
IF @GuessButton IS NOT NULL AND @GuessNo BETWEEN 1 AND 100
BEGIN
SET @TriesNo += 1;
SET @Tries = CONVERT(NVARCHAR(10), @TriesNo);
EXEC sp_api_modal_set @key = N'number_game_tries', @value = @Tries;
IF @GuessNo = @SecretNo
BEGIN
SET @Message = CONCAT(N'You found ', @SecretNo, N' in ', @TriesNo, IIF(@TriesNo = 1, N' try.', N' tries.'));
EXEC sp_api_modal_set @key = N'number_game_secret', @value = NULL; -- game over
EXEC sp_api_modal_set @key = N'number_game_tries', @value = NULL;
EXEC sp_api_modal_clear @alert_text = N'Got it!', @sub_text = @Message;
RETURN;
END
SET @Hint = CONCAT(@GuessNo, IIF(@GuessNo < @SecretNo, N' is too low.', N' is too high.'),
N' Tries so far: ', @TriesNo);
END
ELSE IF @GuessButton IS NOT NULL
SET @Hint = N'Enter a whole number from 1 to 100.';
ELSE
SET @Hint = N'I am thinking of a number between 1 and 100.';
-- Draw
EXEC sp_api_modal_text @text = N'Number Game', @class = N'h3';
EXEC sp_api_modal_text @text = @Hint;
EXEC sp_api_modal_input @name = N'@Guess', @value = @Guess OUT, @placeholder = N'Your guess', @focus = 1, @select = 1;
EXEC sp_api_modal_button @name = N'@GuessButton', @value = N'Guess',
@valueout = @GuessButton OUT, @class = N'btn-primary', @key = N'Enter';
EXEC sp_api_modal_button @name = N'@NewButton', @value = N'New game',
@valueout = @NewButton OUT, @class = N'btn-outline-secondary';
-- Ready for the next guess
EXEC sp_api_modal_value @name = N'@GuessButton', @value = NULL;
What to notice
Two kinds of state. sp_api_modal_get_value reads what came back from the browser.
sp_api_modal_get reads a value the server kept for this user. The secret never leaves the
server; only the hint does.
Session values outlive the dialog. Values stored with sp_api_modal_set belong to the user
and persist until overwritten — close the dialog halfway and the same game is waiting next time.
That is why winning sets both keys back to NULL: it is the clean-up, not an afterthought.
The logic runs before the drawing. Unlike a form that saves and closes, a game stays open after each click. Working out the hint first means the reply appears on the same run as the guess.
The button is reset at the end of every run. Without the final sp_api_modal_value, the
Guess button would still count as clicked on the next run, even if the user only pressed
New game.
@key = N'Enter' binds the Guess button to the Enter key, and @focus/@select put the
cursor back in the input with the previous guess selected — small touches that make it playable
from the keyboard.