Results screen between race and menu

After a console-marshaled race ends, the race loop now shows a RACE
RESULTS screen (place / pilot / final score, sorted descending, with a
CONTINUE button) before returning to the setup menu. Scores come from
the local console's intake; single-player rows carry the pilot's own
name, additional pods show their host number until the Steam roster
maps IDs to personas.

The setup menu also keeps the player's selections and pilot name across
races now instead of resetting to defaults each cycle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-12 19:05:55 -05:00
co-authored by Claude Fable 5
parent 570eb3aceb
commit 8d7373974f
5 changed files with 350 additions and 2 deletions
+6
View File
@@ -223,6 +223,12 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
{
}
// last race's final scores first (no-op on first boot)
if (!RPL4FrontEnd_ShowResults(hInstance, hWnd))
{
break;
}
static char frontend_egg[MAX_PATH];
if (!RPL4FrontEnd_Run(hInstance, hWnd, frontend_egg, sizeof(frontend_egg)))
{
+18
View File
@@ -196,3 +196,21 @@ Logical
{
return gPhase == PhaseStopped;
}
int
RPL4LocalConsole_ResultCount()
{
return gResultCount;
}
Logical
RPL4LocalConsole_GetResult(int index, int *host_ID, int *score)
{
if (index < 0 || index >= gResultCount)
{
return False;
}
*host_ID = gResults[index].hostID;
*score = gResults[index].score;
return True;
}
+8
View File
@@ -25,3 +25,11 @@ void
// back to the setup screen for the next race in the same process.
Logical
RPL4LocalConsole_MissionCompleted();
// The last completed mission's final scores (what each pod reported at
// mission end). Valid until the next mission starts running.
int
RPL4LocalConsole_ResultCount();
Logical
RPL4LocalConsole_GetResult(int index, int *host_ID, int *score);
+307 -1
View File
@@ -2,6 +2,7 @@
#pragma hdrstop
#include "rpl4fe.h"
#include "rpl4console.h"
#include <stdio.h>
#include <string>
@@ -154,6 +155,12 @@ namespace
FEState *gFE = NULL;
int gLastMissionSeconds = 0;
// carried across races so cycling back to the menu keeps the
// player's loadout (and names the results screen's score rows)
int gPersistSelection[GroupCount];
Logical gHavePersist = False;
char gLastPilotName[24] = "Pilot";
const COLORREF kGreenBright = RGB(64, 255, 64);
const COLORREF kGreenDim = RGB(24, 140, 24);
const COLORREF kBlack = RGB(0, 0, 0);
@@ -588,8 +595,15 @@ Logical
{
FEState fe;
memset(&fe, 0, sizeof(fe));
strcpy(fe.pilotName, "Pilot");
strcpy(fe.pilotName, gLastPilotName);
if (gHavePersist)
{
memcpy(fe.selection, gPersistSelection, sizeof(fe.selection));
}
else
{
fe.selection[GroupLength] = 2; // 5:00
}
gFE = &fe;
//---------------------------------------------------------------
@@ -688,6 +702,9 @@ Logical
{
strcpy(fe.pilotName, "Pilot");
}
strcpy(gLastPilotName, fe.pilotName);
memcpy(gPersistSelection, fe.selection, sizeof(gPersistSelection));
gHavePersist = True;
std::string egg;
egg.reserve(16384);
@@ -727,6 +744,295 @@ int
return gLastMissionSeconds;
}
//########################################################################
//######################## RPL4FrontEnd_ShowResults ######################
//########################################################################
namespace
{
struct ResultRow
{
char name[32];
int score;
};
struct ResultsState
{
HWND window;
HFONT textFont;
HFONT titleFont;
ResultRow rows[16];
int rowCount;
RECT continueRect;
Logical dismissed;
Logical closed;
};
ResultsState *gRS = NULL;
const char *PlaceName(int place)
{
static const char *kPlaces[] = { "1ST", "2ND", "3RD", "4TH" };
static char other[8];
if (place < 4)
{
return kPlaces[place];
}
sprintf(other, "%dTH", place + 1);
return other;
}
void PaintResults(ResultsState *rs)
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(rs->window, &ps);
RECT client;
GetClientRect(rs->window, &client);
HDC mem = CreateCompatibleDC(hdc);
HBITMAP surface = CreateCompatibleBitmap(hdc, client.right, client.bottom);
HBITMAP old_surface = (HBITMAP) SelectObject(mem, surface);
FillRect(mem, &client, (HBRUSH) GetStockObject(BLACK_BRUSH));
SetBkMode(mem, TRANSPARENT);
SelectObject(mem, rs->titleFont);
SetTextColor(mem, kGreenBright);
RECT title = client;
title.top = client.bottom / 28;
title.bottom = title.top + client.bottom / 10;
DrawTextA(mem, "RACE RESULTS", -1, &title,
DT_CENTER | DT_TOP | DT_SINGLELINE);
SelectObject(mem, rs->textFont);
int row_h = client.bottom / 16;
int top = client.bottom / 4;
int left = client.right / 3;
int right = 2 * client.right / 3;
char text[48];
for (int i = 0; i < rs->rowCount; ++i)
{
RECT row;
row.left = left;
row.right = right;
row.top = top + i * row_h;
row.bottom = row.top + row_h;
SetTextColor(mem, (i == 0) ? kGreenBright : kGreenDim);
DrawTextA(mem, PlaceName(i), -1, &row,
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
RECT name = row;
name.left += (right - left) / 5;
DrawTextA(mem, rs->rows[i].name, -1, &name,
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
sprintf(text, "%d", rs->rows[i].score);
DrawTextA(mem, text, -1, &row,
DT_RIGHT | DT_VCENTER | DT_SINGLELINE);
}
HBRUSH frame_brush = CreateSolidBrush(kGreenBright);
FrameRect(mem, &rs->continueRect, frame_brush);
DeleteObject(frame_brush);
SetTextColor(mem, kGreenBright);
DrawTextA(mem, "C O N T I N U E", -1, &rs->continueRect,
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
BitBlt(hdc, 0, 0, client.right, client.bottom, mem, 0, 0, SRCCOPY);
SelectObject(mem, old_surface);
DeleteObject(surface);
DeleteDC(mem);
EndPaint(rs->window, &ps);
}
LRESULT CALLBACK
ResultsWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
ResultsState *rs = gRS;
switch (message)
{
case WM_PAINT:
if (rs != NULL)
{
PaintResults(rs);
return 0;
}
break;
case WM_LBUTTONDOWN:
if (rs != NULL)
{
POINT point = { (int)(short) LOWORD(lParam), (int)(short) HIWORD(lParam) };
if (PtInRect(&rs->continueRect, point))
{
rs->dismissed = True;
PostMessageA(rs->window, WM_NULL, 0, 0);
}
return 0;
}
break;
case WM_ERASEBKGND:
return 1;
}
return DefWindowProcA(hwnd, message, wParam, lParam);
}
}
Logical
RPL4FrontEnd_ShowResults(
HINSTANCE instance,
HWND main_window
)
{
int result_count = RPL4LocalConsole_ResultCount();
if (result_count <= 0)
{
return True; // nothing to show (first boot)
}
if (!IsWindow(main_window))
{
return False;
}
ResultsState rs;
memset(&rs, 0, sizeof(rs));
//---------------------------------------------------------------
// Intake, then sort descending by score. Single-player results
// carry the pilot's own name; with more pods the roster will map
// host IDs to Steam personas (until then: pod number).
//---------------------------------------------------------------
for (int i = 0; i < result_count && rs.rowCount < 16; ++i)
{
int host_ID, score;
if (!RPL4LocalConsole_GetResult(i, &host_ID, &score))
{
continue;
}
ResultRow *row = &rs.rows[rs.rowCount++];
if (result_count == 1)
{
sprintf(row->name, "%s", gLastPilotName);
}
else
{
sprintf(row->name, "POD %d", host_ID);
}
row->score = score;
}
for (int i = 1; i < rs.rowCount; ++i)
{
ResultRow key = rs.rows[i];
int j = i - 1;
while (j >= 0 && rs.rows[j].score < key.score)
{
rs.rows[j + 1] = rs.rows[j];
--j;
}
rs.rows[j + 1] = key;
}
static Logical class_registered = False;
if (!class_registered)
{
class_registered = True;
WNDCLASSA window_class;
memset(&window_class, 0, sizeof(window_class));
window_class.style = CS_HREDRAW | CS_VREDRAW;
window_class.lpfnWndProc = ResultsWndProc;
window_class.hInstance = instance;
window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
window_class.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
window_class.lpszClassName = "RPResults";
RegisterClassA(&window_class);
}
RECT client;
GetClientRect(main_window, &client);
rs.window = CreateWindowExA(
0, "RPResults", "",
WS_CHILD | WS_VISIBLE,
0, 0, client.right, client.bottom,
main_window, NULL, instance, NULL);
if (rs.window == NULL)
{
return False;
}
int text_h = client.bottom / 40;
if (text_h < 16) text_h = 16;
if (text_h > 24) text_h = 24;
rs.textFont = CreateFontA(-(text_h * 3 / 2), 0, 0, 0, FW_NORMAL,
FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_MODERN,
"Consolas");
rs.titleFont = CreateFontA(-(text_h * 2), 0, 0, 0, FW_BOLD,
FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_MODERN,
"Consolas");
int row_h = client.bottom / 36;
if (row_h < 18) row_h = 18;
if (row_h > 30) row_h = 30;
int col_w = client.right / 5;
rs.continueRect.left = (client.right - col_w) / 2;
rs.continueRect.right = rs.continueRect.left + col_w;
rs.continueRect.top = client.bottom - 3 * row_h;
rs.continueRect.bottom = client.bottom - row_h;
gRS = &rs;
SetFocus(rs.window);
InvalidateRect(rs.window, NULL, TRUE);
//---------------------------------------------------------------
// Modal until CONTINUE (or any key) - same shape as the menu loop
//---------------------------------------------------------------
MSG msg;
while (!rs.dismissed && !rs.closed)
{
BOOL result = GetMessageA(&msg, NULL, 0, 0);
if (result <= 0)
{
rs.closed = True;
break;
}
if (msg.message == WM_QUIT ||
(msg.message == WM_SYSCOMMAND && msg.wParam == SC_CLOSE))
{
rs.closed = True;
}
if (msg.message == WM_CLOSE && msg.hwnd == main_window)
{
rs.closed = True;
}
if (msg.message == WM_KEYDOWN &&
(msg.wParam == VK_RETURN || msg.wParam == VK_SPACE ||
msg.wParam == VK_ESCAPE))
{
rs.dismissed = True;
}
TranslateMessage(&msg);
DispatchMessageA(&msg);
if (!IsWindow(main_window))
{
rs.closed = True;
}
}
DestroyWindow(rs.window);
DeleteObject(rs.textFont);
DeleteObject(rs.titleFont);
gRS = NULL;
return !rs.closed;
}
//########################################################################
// Ordinal plasma graphics (verbatim from the console / TEST.EGG)
//########################################################################
+10
View File
@@ -28,3 +28,13 @@ Logical
// successful launch - the local console marshals the mission with it.
int
RPL4FrontEnd_LastMissionSeconds();
// The between-races results screen: shows the last mission's final
// scores (from the local console) with a CONTINUE button. Returns
// immediately when there are no results (first boot). False only if
// the player closed the window instead of continuing.
Logical
RPL4FrontEnd_ShowResults(
HINSTANCE instance,
HWND main_window
);