feat: honest AI opponent in quick game
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 49s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m12s

New Game's quick game gains an explicit opponent selector — 🤖 AI (default)
or 👤 Random player. AI starts a game seated with a pooled robot that joins
and moves at once: no per-move timeout (a 7-day inactivity loss reusing the
turn-timeout sweeper), chat/nudge disabled, no statistics, the opponent shown
as 🤖 everywhere. The random path (disguised robot) is unchanged.

Driven by one game flag (games.vs_ai), set only on AI-started games so the
disguised path is never revealed; Matchmaker.StartVsAI seats the robot
directly (no open pool); the robot replies event-driven via the game service's
after-commit/after-create hook. Wire: vs_ai on EnqueueRequest + GameView.
This commit is contained in:
Ilia Denisov
2026-06-15 20:14:24 +02:00
parent 91d5c341ef
commit aa765a0c06
56 changed files with 901 additions and 86 deletions
+12 -2
View File
@@ -32,8 +32,13 @@ multipleWordsPerTurn():boolean {
return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : false;
}
vsAi():boolean {
const offset = this.bb!.__offset(this.bb_pos, 8);
return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : false;
}
static startEnqueueRequest(builder:flatbuffers.Builder) {
builder.startObject(2);
builder.startObject(3);
}
static addVariant(builder:flatbuffers.Builder, variantOffset:flatbuffers.Offset) {
@@ -44,15 +49,20 @@ static addMultipleWordsPerTurn(builder:flatbuffers.Builder, multipleWordsPerTurn
builder.addFieldInt8(1, +multipleWordsPerTurn, +false);
}
static addVsAi(builder:flatbuffers.Builder, vsAi:boolean) {
builder.addFieldInt8(2, +vsAi, +false);
}
static endEnqueueRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createEnqueueRequest(builder:flatbuffers.Builder, variantOffset:flatbuffers.Offset, multipleWordsPerTurn:boolean):flatbuffers.Offset {
static createEnqueueRequest(builder:flatbuffers.Builder, variantOffset:flatbuffers.Offset, multipleWordsPerTurn:boolean, vsAi:boolean):flatbuffers.Offset {
EnqueueRequest.startEnqueueRequest(builder);
EnqueueRequest.addVariant(builder, variantOffset);
EnqueueRequest.addMultipleWordsPerTurn(builder, multipleWordsPerTurn);
EnqueueRequest.addVsAi(builder, vsAi);
return EnqueueRequest.endEnqueueRequest(builder);
}
}
+12 -2
View File
@@ -98,8 +98,13 @@ lastActivityUnix():bigint {
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
}
vsAi():boolean {
const offset = this.bb!.__offset(this.bb_pos, 28);
return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : false;
}
static startGameView(builder:flatbuffers.Builder) {
builder.startObject(12);
builder.startObject(13);
}
static addId(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset) {
@@ -162,12 +167,16 @@ static addLastActivityUnix(builder:flatbuffers.Builder, lastActivityUnix:bigint)
builder.addFieldInt64(11, lastActivityUnix, BigInt('0'));
}
static addVsAi(builder:flatbuffers.Builder, vsAi:boolean) {
builder.addFieldInt8(12, +vsAi, +false);
}
static endGameView(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createGameView(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset, variantOffset:flatbuffers.Offset, dictVersionOffset:flatbuffers.Offset, statusOffset:flatbuffers.Offset, players:number, toMove:number, turnTimeoutSecs:number, multipleWordsPerTurn:boolean, moveCount:number, endReasonOffset:flatbuffers.Offset, seatsOffset:flatbuffers.Offset, lastActivityUnix:bigint):flatbuffers.Offset {
static createGameView(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset, variantOffset:flatbuffers.Offset, dictVersionOffset:flatbuffers.Offset, statusOffset:flatbuffers.Offset, players:number, toMove:number, turnTimeoutSecs:number, multipleWordsPerTurn:boolean, moveCount:number, endReasonOffset:flatbuffers.Offset, seatsOffset:flatbuffers.Offset, lastActivityUnix:bigint, vsAi:boolean):flatbuffers.Offset {
GameView.startGameView(builder);
GameView.addId(builder, idOffset);
GameView.addVariant(builder, variantOffset);
@@ -181,6 +190,7 @@ static createGameView(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset,
GameView.addEndReason(builder, endReasonOffset);
GameView.addSeats(builder, seatsOffset);
GameView.addLastActivityUnix(builder, lastActivityUnix);
GameView.addVsAi(builder, vsAi);
return GameView.endGameView(builder);
}
}