Check Tic-Tac-Toe Winner, Draw, or Game in Progress
This tic-tac-toe winner checker reads a complete or partial 3x3 board and determines its current outcome.
Run — free
It reports whether X or O has won, whether a filled board is a draw, or whose turn comes next while play remains possible. The checker also rejects malformed boards and impossible X-to-O turn counts, so it is useful for validating saved games, testing game logic, reviewing exercises, and powering small applications without relying on guesswork or visual inspection.
Enter the board in a clear three-line format
Provide exactly three rows, with exactly three cells in every row. Use X for the first player, O for the second player, and a hyphen for any empty square. You may place spaces between cells for readability, as in X O X, because whitespace inside each row is ignored. Keep each board row on its own line. The checker treats lowercase x and o as their uppercase equivalents, but it rejects every other cell character. This strict format makes the input easy to type, paste, store in a fixture, or generate in an application while avoiding ambiguous symbols. The first player is always X, matching standard tic-tac-toe turn order. A blank board is therefore three rows of three hyphens, and a partially played board keeps a hyphen wherever nobody has moved. If the board has too many rows, too few rows, a row with the wrong number of cells, or an unsupported mark, the request returns an input error instead of attempting to repair or reinterpret the position. That behavior keeps automated checks predictable.
Understand winner and draw detection
The checker examines all eight possible winning lines: three horizontal rows, three vertical columns, and the two diagonals. A player wins when one or more of those lines contains three of the same mark. A winning response identifies the player and returns every winning line as row-and-column coordinate pairs, using one-based coordinates so the top-left square is row 1, column 1. Returning all matching lines matters in positions where a final move completes two lines at once. If neither player has a winning line and every cell is occupied, the result is a draw. If empty cells remain, the game is reported as in progress and the response identifies the next player from the mark counts. The algorithm is deterministic and scans only the nine cells and eight known lines. It does not use randomness, network services, stored state, or assumptions about moves that are not represented on the board. The same board always produces the same structured result, making the capability suitable for unit tests and repeatable validation.
Catch positions that could not occur in legal play
A board can look visually plausible while violating the order of play. Since X moves first and players alternate, X must have either the same number of marks as O or exactly one more. The checker rejects a position when O has moved more times than X or when X leads by two or more moves. It also cross-checks a declared win against those counts: an X win requires one extra X mark, while an O win requires equal counts. A board on which both players have winning lines is rejected because play should have stopped after the first valid win. These checks are especially valuable when boards come from user input, database records, tutorials, interview exercises, or a custom game engine under development. They prevent an impossible position from being mislabeled as a valid winner or draw. The capability evaluates the supplied snapshot rather than reconstructing a complete move history, so its validation focuses on the facts a 3x3 board can prove directly. For a valid unfinished position, use next_player to continue with the correct mark.
What you can do with it
Test a game engine
Compare your application's stored board against a deterministic outcome during automated tests.
Validate submitted puzzles
Reject malformed or turn-inconsistent positions before grading a player's answer.
Resume a saved match
Determine whether play has ended and identify the correct next player for an unfinished board.
FAQ
What board format should I use?
Send three lines of three cells, using X, O, and - for an empty square. Spaces between cells are optional.
Does lowercase input work?
Yes. Lowercase x and o are normalized to uppercase before the board is checked.
How are winning lines represented?
Each winning line is an array of three one-based row-and-column coordinate pairs.
What makes the X and O counts invalid?
O may never have more marks than X, and X may never have more than one additional mark because X starts and turns alternate.
What does it cost?
Each API request costs $0.002. The browser version can run locally on the page.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/game/tic-tac-toe-winner-check \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"board":"X O X\nO X -\n- O X"}'const res = await fetch("https://api.kit.forhosting.com/game/tic-tac-toe-winner-check", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"board": "X O X\nO X -\n- O X"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/game/tic-tac-toe-winner-check",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"board": "X O X\nO X -\n- O X"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/game/tic-tac-toe-winner-check", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"board":"X O X\\nO X -\\n- O X"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"board":"X O X\nO X -\n- O X"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/game/tic-tac-toe-winner-check", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"board": "X O X\nO X -\n- O X"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "game.tic_tac_toe_winner_check",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |