a geicko-2 based round robin ranking system designed to test c++ battleship submissions battleship.dunkirk.sh

bug: handle rendering over ssh properly

dunkirk.sh 9a95d7f7 0cd98e60

verified
Changed files
+65 -591
cmd
battleship-arena
internal
+1
.gitignore
···
build/
./battleship-arena
+
./remote-submissions
# Generated files in engine
battleship-engine/src/memory_functions_*.cpp
battleship-arena

This is a binary file and will not be displayed.

+6 -8
cmd/battleship-arena/main.go
···
"time"
tea "github.com/charmbracelet/bubbletea"
-
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/ssh"
"github.com/charmbracelet/wish"
"github.com/charmbracelet/wish/bubbletea"
···
// Get proper terminal options for color support
opts := bubbletea.MakeOptions(s)
+
// Create renderer for this session
+
renderer := bubbletea.MakeRenderer(s)
+
if needsOnboarding {
// Run onboarding first
publicKey := ""
···
publicKey = val.(string)
}
-
m := tui.NewOnboardingModel(s.User(), publicKey, pty.Window.Width, pty.Window.Height)
+
m := tui.NewOnboardingModel(s.User(), publicKey, pty.Window.Width, pty.Window.Height, renderer)
return m, opts
}
-
m := tui.InitialModel(s.User(), pty.Window.Width, pty.Window.Height)
+
m := tui.InitialModel(s.User(), pty.Window.Width, pty.Window.Height, renderer)
return m, opts
}
···
return nil
}
-
var titleStyle = lipgloss.NewStyle().
-
Bold(true).
-
Foreground(lipgloss.Color("205")).
-
MarginTop(1).
-
MarginBottom(1)
+
-266
comito@0.0.0.0
···
-
#include "memory_functions_comito.h"
-
-
using namespace std;
-
-
void createMove(int row, int col, string &move);
-
int moveCheck(int row, int col, const ComputerMemory &memory);
-
void directionChecks(int &nextRow, int &nextCol, int dir, int offset, const ComputerMemory &memory);
-
int DirFlip(int currDir);
-
-
// initMemory initializes the memory; at the outset of the game the grid of
-
// shots taken is empty, we've not hit any ships, and our player can only apply
-
// a general, somewhat random firing strategy until we get a hit on some ship
-
void initMemorycomito(ComputerMemory &memory) {
-
memory.mode = RANDOM;
-
memory.hitRow = -1;
-
memory.hitCol = -1;
-
memory.hitShip = NONE;
-
memory.fireDir = NONE;
-
memory.fireDist = 1;
-
memory.lastResult = NONE;
-
-
for (int i = 0; i < BOARDSIZE; i++) {
-
for (int j = 0; j < BOARDSIZE; j++) {
-
memory.grid[i][j] = EMPTY_MARKER;
-
}
-
}
-
}
-
-
// complete this function so it produces a "smart" move based on the information
-
// which appears in the computer's memory
-
string smartMovecomito(const ComputerMemory &memory) {
-
string move;
-
int checkResult = -1;
-
int nextRow = -1;
-
int nextCol = -1;
-
if (memory.mode == SEARCH)
-
{
-
int i = 0;
-
while (i <= 4 && checkResult != 1)
-
{
-
-
switch (memory.fireDir)
-
{
-
case 1:
-
nextRow = memory.hitRow - 1;
-
nextCol = memory.hitCol;
-
break;
-
case 2:
-
nextRow = memory.hitRow + 1;
-
nextCol = memory.hitCol;
-
break;
-
case 3:
-
nextCol = memory.hitCol - 1;
-
nextRow = memory.hitRow;
-
break;
-
case 4:
-
nextCol = memory.hitCol + 1;
-
nextRow = memory.hitRow;
-
break;
-
}
-
i++;
-
checkResult = moveCheck(nextRow, nextCol, memory);
-
}
-
}
-
if (memory.mode == DESTROY)
-
{
-
switch (memory.fireDir)
-
{
-
case 1:
-
nextRow = memory.hitRow - memory.fireDist;
-
nextCol = memory.hitCol;
-
break;
-
case 2:
-
nextRow = memory.hitRow + memory.fireDist;
-
nextCol = memory.hitCol;
-
break;
-
case 3:
-
nextCol = memory.hitCol - memory.fireDist;
-
nextRow = memory.hitRow;
-
break;
-
case 4:
-
nextCol = memory.hitCol + memory.fireDist;
-
nextRow = memory.hitRow;
-
break;
-
}
-
}
-
createMove(nextRow, nextCol, move);
-
debug(move);
-
return move;
-
}
-
-
void updateMemorycomito(int row, int col, int result, ComputerMemory &memory) {
-
-
int moveRes = result / 10;
-
int hitShipId = result % 10;
-
-
if (memory.mode == RANDOM) //if random
-
{
-
if (result == 0) //if missed
-
{
-
memory.fireDir = 0;
-
}
-
else //if hit
-
{
-
memory.hitShip = isShip(result);
-
memory.hitRow = row;
-
memory.hitCol = col;
-
memory.mode = SEARCH;
-
memory.fireDir++;
-
}
-
}
-
else if (memory.mode == SEARCH) //if search
-
{
-
if (result == 0) //if missed
-
{
-
memory.fireDir++;
-
if (memory.fireDir > 4)
-
{
-
memory.mode = RANDOM;
-
memory.fireDist = 1;
-
}
-
}
-
else
-
{
-
memory.mode = DESTROY;
-
memory.fireDist++;
-
}
-
}else //if destroy
-
{
-
int i = BOARDSIZE;
-
int nextRow = -1;
-
int nextCol = -1;
-
int checkResult = -1;
-
while (checkResult != 1 && i > 0)
-
{
-
switch (memory.fireDir)
-
{
-
case 1:
-
nextRow = memory.hitRow - memory.fireDist;
-
nextCol = memory.hitCol;
-
break;
-
case 2:
-
nextRow = memory.hitRow + memory.fireDist;
-
nextCol = memory.hitCol;
-
break;
-
case 3:
-
nextCol = memory.hitCol - memory.fireDist;
-
nextRow = memory.hitRow;
-
break;
-
case 4:
-
nextCol = memory.hitCol + memory.fireDist;
-
nextRow = memory.hitRow;
-
break;
-
}
-
checkResult = moveCheck(nextRow, nextCol, memory);
-
-
switch (checkResult)
-
{
-
case 0:
-
memory.fireDir = DirFlip(memory.fireDir);
-
memory.fireDist = 1;
-
break;
-
case 1:
-
//check if should fire here and if yes fire
-
if (moveRes != 0)
-
{
-
-
}
-
break;
-
case 2:
-
memory.fireDist += 1;
-
break;
-
case 3:
-
memory.fireDir = DirFlip(memory.fireDir);
-
memory.fireDist = 1;
-
break;
-
}
-
i--;
-
}
-
if (i < 1)
-
{
-
memory.mode = RANDOM;
-
}
-
}
-
//update memory grid
-
if (result == 0)
-
{
-
memory.grid[row][col] = MISS_MARKER;
-
}
-
if (result == 1)
-
{
-
memory.grid[row][col] = HIT_MARKER;
-
}
-
}
-
-
//I added this function to call within the move so that
-
//I can input the row number and just get the letter out
-
//I didn't know how best to do this so I did whatever this is :/
-
void createMove(int row, int col, string &move)
-
{
-
char letter = 'A';
-
letter += row;
-
move.push_back(letter);
-
string number = to_string(col + 1);
-
move.append(number);
-
}
-
-
int moveCheck(int row, int col, const ComputerMemory &memory)
-
{
-
int success = 0;
-
bool isMovePlayedYet = false;
-
-
if (row < 0 || row >= BOARDSIZE || col < 0 || col >= BOARDSIZE)
-
{
-
success = false;
-
}
-
else
-
{
-
if(memory.grid[row][col] == EMPTY_MARKER)
-
{
-
success = 1;
-
}else if (memory.grid[row][col] == HIT_MARKER)
-
{
-
success = 2;
-
}else
-
{
-
success = 3;
-
}
-
}
-
return success;
-
}
-
int DirFlip(int currDir)
-
{
-
if (currDir == 1)
-
{
-
return 2;
-
}
-
if (currDir == 3)
-
{
-
return 4;
-
}
-
return currDir;
-
}
-
/* attempted function graveyard
-
{
-
switch (dir)
-
{
-
case 1:
-
nextRow = memory.hitRow - offset;
-
nextCol = memory.hitCol;
-
break;
-
case 2:
-
nextRow = memory.hitRow + offset;
-
nextCol = memory.hitCol;
-
break;
-
case 3:
-
nextCol = memory.hitCol - offset;
-
nextRow = memory.hitRow;
-
break;
-
case 4:
-
nextCol = memory.hitCol + offset;
-
nextRow = memory.hitRow;
-
break;
-
}
-
}
-
*/
-266
comito@localhost
···
-
#include "memory_functions_comito.h"
-
-
using namespace std;
-
-
void createMove(int row, int col, string &move);
-
int moveCheck(int row, int col, const ComputerMemory &memory);
-
void directionChecks(int &nextRow, int &nextCol, int dir, int offset, const ComputerMemory &memory);
-
int DirFlip(int currDir);
-
-
// initMemory initializes the memory; at the outset of the game the grid of
-
// shots taken is empty, we've not hit any ships, and our player can only apply
-
// a general, somewhat random firing strategy until we get a hit on some ship
-
void initMemorycomito(ComputerMemory &memory) {
-
memory.mode = RANDOM;
-
memory.hitRow = -1;
-
memory.hitCol = -1;
-
memory.hitShip = NONE;
-
memory.fireDir = NONE;
-
memory.fireDist = 1;
-
memory.lastResult = NONE;
-
-
for (int i = 0; i < BOARDSIZE; i++) {
-
for (int j = 0; j < BOARDSIZE; j++) {
-
memory.grid[i][j] = EMPTY_MARKER;
-
}
-
}
-
}
-
-
// complete this function so it produces a "smart" move based on the information
-
// which appears in the computer's memory
-
string smartMovecomito(const ComputerMemory &memory) {
-
string move;
-
int checkResult = -1;
-
int nextRow = -1;
-
int nextCol = -1;
-
if (memory.mode == SEARCH)
-
{
-
int i = 0;
-
while (i <= 4 && checkResult != 1)
-
{
-
-
switch (memory.fireDir)
-
{
-
case 1:
-
nextRow = memory.hitRow - 1;
-
nextCol = memory.hitCol;
-
break;
-
case 2:
-
nextRow = memory.hitRow + 1;
-
nextCol = memory.hitCol;
-
break;
-
case 3:
-
nextCol = memory.hitCol - 1;
-
nextRow = memory.hitRow;
-
break;
-
case 4:
-
nextCol = memory.hitCol + 1;
-
nextRow = memory.hitRow;
-
break;
-
}
-
i++;
-
checkResult = moveCheck(nextRow, nextCol, memory);
-
}
-
}
-
if (memory.mode == DESTROY)
-
{
-
switch (memory.fireDir)
-
{
-
case 1:
-
nextRow = memory.hitRow - memory.fireDist;
-
nextCol = memory.hitCol;
-
break;
-
case 2:
-
nextRow = memory.hitRow + memory.fireDist;
-
nextCol = memory.hitCol;
-
break;
-
case 3:
-
nextCol = memory.hitCol - memory.fireDist;
-
nextRow = memory.hitRow;
-
break;
-
case 4:
-
nextCol = memory.hitCol + memory.fireDist;
-
nextRow = memory.hitRow;
-
break;
-
}
-
}
-
createMove(nextRow, nextCol, move);
-
debug(move);
-
return move;
-
}
-
-
void updateMemorycomito(int row, int col, int result, ComputerMemory &memory) {
-
-
int moveRes = result / 10;
-
int hitShipId = result % 10;
-
-
if (memory.mode == RANDOM) //if random
-
{
-
if (result == 0) //if missed
-
{
-
memory.fireDir = 0;
-
}
-
else //if hit
-
{
-
memory.hitShip = isShip(result);
-
memory.hitRow = row;
-
memory.hitCol = col;
-
memory.mode = SEARCH;
-
memory.fireDir++;
-
}
-
}
-
else if (memory.mode == SEARCH) //if search
-
{
-
if (result == 0) //if missed
-
{
-
memory.fireDir++;
-
if (memory.fireDir > 4)
-
{
-
memory.mode = RANDOM;
-
memory.fireDist = 1;
-
}
-
}
-
else
-
{
-
memory.mode = DESTROY;
-
memory.fireDist++;
-
}
-
}else //if destroy
-
{
-
int i = BOARDSIZE;
-
int nextRow = -1;
-
int nextCol = -1;
-
int checkResult = -1;
-
while (checkResult != 1 && i > 0)
-
{
-
switch (memory.fireDir)
-
{
-
case 1:
-
nextRow = memory.hitRow - memory.fireDist;
-
nextCol = memory.hitCol;
-
break;
-
case 2:
-
nextRow = memory.hitRow + memory.fireDist;
-
nextCol = memory.hitCol;
-
break;
-
case 3:
-
nextCol = memory.hitCol - memory.fireDist;
-
nextRow = memory.hitRow;
-
break;
-
case 4:
-
nextCol = memory.hitCol + memory.fireDist;
-
nextRow = memory.hitRow;
-
break;
-
}
-
checkResult = moveCheck(nextRow, nextCol, memory);
-
-
switch (checkResult)
-
{
-
case 0:
-
memory.fireDir = DirFlip(memory.fireDir);
-
memory.fireDist = 1;
-
break;
-
case 1:
-
//check if should fire here and if yes fire
-
if (moveRes != 0)
-
{
-
-
}
-
break;
-
case 2:
-
memory.fireDist += 1;
-
break;
-
case 3:
-
memory.fireDir = DirFlip(memory.fireDir);
-
memory.fireDist = 1;
-
break;
-
}
-
i--;
-
}
-
if (i < 1)
-
{
-
memory.mode = RANDOM;
-
}
-
}
-
//update memory grid
-
if (result == 0)
-
{
-
memory.grid[row][col] = MISS_MARKER;
-
}
-
if (result == 1)
-
{
-
memory.grid[row][col] = HIT_MARKER;
-
}
-
}
-
-
//I added this function to call within the move so that
-
//I can input the row number and just get the letter out
-
//I didn't know how best to do this so I did whatever this is :/
-
void createMove(int row, int col, string &move)
-
{
-
char letter = 'A';
-
letter += row;
-
move.push_back(letter);
-
string number = to_string(col + 1);
-
move.append(number);
-
}
-
-
int moveCheck(int row, int col, const ComputerMemory &memory)
-
{
-
int success = 0;
-
bool isMovePlayedYet = false;
-
-
if (row < 0 || row >= BOARDSIZE || col < 0 || col >= BOARDSIZE)
-
{
-
success = false;
-
}
-
else
-
{
-
if(memory.grid[row][col] == EMPTY_MARKER)
-
{
-
success = 1;
-
}else if (memory.grid[row][col] == HIT_MARKER)
-
{
-
success = 2;
-
}else
-
{
-
success = 3;
-
}
-
}
-
return success;
-
}
-
int DirFlip(int currDir)
-
{
-
if (currDir == 1)
-
{
-
return 2;
-
}
-
if (currDir == 3)
-
{
-
return 4;
-
}
-
return currDir;
-
}
-
/* attempted function graveyard
-
{
-
switch (dir)
-
{
-
case 1:
-
nextRow = memory.hitRow - offset;
-
nextCol = memory.hitCol;
-
break;
-
case 2:
-
nextRow = memory.hitRow + offset;
-
nextCol = memory.hitCol;
-
break;
-
case 3:
-
nextCol = memory.hitCol - offset;
-
nextRow = memory.hitRow;
-
break;
-
case 4:
-
nextCol = memory.hitCol + offset;
-
nextRow = memory.hitRow;
-
break;
-
}
-
}
-
*/
+48 -43
internal/tui/model.go
···
fieldLink
)
-
var titleStyle = lipgloss.NewStyle().
-
Bold(true).
-
Foreground(lipgloss.Color("205")).
-
MarginTop(1).
-
MarginBottom(1)
-
type model struct {
+
renderer *lipgloss.Renderer
+
titleStyle lipgloss.Style
username string
width int
height int
···
saveMessage string
}
-
func InitialModel(username string, width, height int) model {
+
func InitialModel(username string, width, height int, renderer *lipgloss.Renderer) model {
externalURL := os.Getenv("BATTLESHIP_EXTERNAL_URL")
if externalURL == "" {
externalURL = "localhost"
···
// Load user profile
user, _ := storage.GetUserByUsername(username)
+
// Create styles using session renderer
+
titleStyle := renderer.NewStyle().
+
Bold(true).
+
Foreground(lipgloss.Color("205")).
+
MarginTop(1).
+
MarginBottom(1)
+
return model{
+
renderer: renderer,
+
titleStyle: titleStyle,
username: username,
width: width,
height: height,
···
func (m model) View() string {
var b strings.Builder
-
title := titleStyle.Render("🚢 Battleship Arena")
+
title := m.titleStyle.Render("🚢 Battleship Arena")
b.WriteString(title + "\n")
// Skip tabs if in edit mode
if m.currentView != viewEditProfile {
// Navigation tabs
-
tabStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
-
activeTabStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("86")).Bold(true)
+
tabStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("240"))
+
activeTabStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("86")).Bold(true)
tabs := []string{"[h] Home", "[l] Leaderboard", "[p] Profile"}
for i, tab := range tabs {
···
b.WriteString(fmt.Sprintf("User: %s\n\n", m.username))
// Upload instructions
-
infoStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("86"))
+
infoStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("86"))
b.WriteString(infoStyle.Render(fmt.Sprintf("Upload via: scp -P %s memory_functions_yourname.cpp %s@%s:~/", m.sshPort, m.username, m.externalURL)))
b.WriteString("\n\n")
// Show submissions
if len(m.submissions) > 0 {
-
b.WriteString(renderSubmissions(m.submissions))
+
b.WriteString(m.renderSubmissions(m.submissions))
} else {
b.WriteString("No submissions yet. Upload your first AI!\n")
}
···
func (m model) renderLeaderboardView() string {
if len(m.leaderboard) > 0 {
-
return renderLeaderboard(m.leaderboard)
+
return m.renderLeaderboard(m.leaderboard)
}
return "Loading leaderboard..."
}
···
return "Loading profile..."
}
-
b.WriteString(lipgloss.NewStyle().Bold(true).Render("👤 Profile") + "\n\n")
+
b.WriteString(m.renderer.NewStyle().Bold(true).Render("👤 Profile") + "\n\n")
// Show user info
-
labelStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
+
labelStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("240"))
b.WriteString(labelStyle.Render("Username: ") + m.user.Username + "\n")
b.WriteString(labelStyle.Render("Name: ") + m.user.Name + "\n")
b.WriteString(labelStyle.Render("Bio: ") + m.user.Bio + "\n")
b.WriteString(labelStyle.Render("Link: ") + m.user.Link + "\n\n")
-
hintStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("86"))
+
hintStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("86"))
b.WriteString(hintStyle.Render("Press 'e' to edit profile") + "\n\n")
// Show user stats from submissions
if len(m.submissions) > 0 {
-
b.WriteString(renderSubmissions(m.submissions))
+
b.WriteString(m.renderSubmissions(m.submissions))
b.WriteString("\n")
}
···
func (m model) renderEditProfile() string {
var b strings.Builder
-
b.WriteString(lipgloss.NewStyle().Bold(true).Render("✏️ Edit Profile") + "\n\n")
+
b.WriteString(m.renderer.NewStyle().Bold(true).Render("✏️ Edit Profile") + "\n\n")
-
activeStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("86")).Bold(true)
-
inactiveStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
+
activeStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("86")).Bold(true)
+
inactiveStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("240"))
// Name field
if m.editingField == fieldName {
···
b.WriteString("\n")
if m.saveMessage != "" {
-
msgStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("green"))
+
msgStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("green"))
b.WriteString(msgStyle.Render(m.saveMessage) + "\n\n")
}
-
hintStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
+
hintStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("240"))
b.WriteString(hintStyle.Render("Tab/↑↓: Navigate fields | Enter: Save | Esc: Cancel"))
return b.String()
···
})
}
-
func renderSubmissions(submissions []storage.Submission) string {
+
func (m model) renderSubmissions(submissions []storage.Submission) string {
var b strings.Builder
-
b.WriteString(lipgloss.NewStyle().Bold(true).Render("📤 Your Submissions") + "\n\n")
+
b.WriteString(m.renderer.NewStyle().Bold(true).Render("📤 Your Submissions") + "\n\n")
-
headerStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("240"))
+
headerStyle := m.renderer.NewStyle().Bold(true).Foreground(lipgloss.Color("240"))
b.WriteString(headerStyle.Render(fmt.Sprintf("%-35s %-15s %s",
"Filename", "Uploaded", "Status")) + "\n")
···
// Build the line manually to avoid formatting issues with ANSI codes
line := fmt.Sprintf("%-35s %-15s ", sub.Filename, relTime)
-
statusStyled := lipgloss.NewStyle().Foreground(lipgloss.Color(statusColor)).Render(sub.Status)
+
statusStyled := m.renderer.NewStyle().Foreground(lipgloss.Color(statusColor)).Render(sub.Status)
b.WriteString(line + statusStyled + "\n")
}
···
return fmt.Sprintf("%dd ago", days)
}
-
func renderLeaderboard(entries []storage.LeaderboardEntry) string {
+
func (m model) renderLeaderboard(entries []storage.LeaderboardEntry) string {
if len(entries) == 0 {
return "No entries yet"
}
var b strings.Builder
-
b.WriteString(lipgloss.NewStyle().Bold(true).Render("🏆 Leaderboard") + "\n\n")
+
b.WriteString(m.renderer.NewStyle().Bold(true).Render("🏆 Leaderboard") + "\n\n")
// Header without styling on the whole line
b.WriteString(fmt.Sprintf("%-4s %-20s %11s %8s %8s %10s %10s\n",
···
// Apply color only to the rank and pad manually
var displayRank string
if i == 0 {
-
displayRank = lipgloss.NewStyle().Foreground(lipgloss.Color("220")).Render(rank) + " " // Gold
+
displayRank = m.renderer.NewStyle().Foreground(lipgloss.Color("220")).Render(rank) + " " // Gold
} else if i == 1 {
-
displayRank = lipgloss.NewStyle().Foreground(lipgloss.Color("250")).Render(rank) + " " // Silver
+
displayRank = m.renderer.NewStyle().Foreground(lipgloss.Color("250")).Render(rank) + " " // Silver
} else if i == 2 {
-
displayRank = lipgloss.NewStyle().Foreground(lipgloss.Color("208")).Render(rank) + " " // Bronze
+
displayRank = m.renderer.NewStyle().Foreground(lipgloss.Color("208")).Render(rank) + " " // Bronze
} else {
displayRank = fmt.Sprintf("%-4s", rank)
}
···
return b.String()
}
-
func renderMatches(matches []storage.MatchResult, username string) string {
+
func (m model) renderMatches(matches []storage.MatchResult, username string) string {
var b strings.Builder
// Filter to show only matches involving this user
···
userMatches = userMatches[:limit]
}
-
headerStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("240"))
+
headerStyle := m.renderer.NewStyle().Bold(true).Foreground(lipgloss.Color("240"))
b.WriteString(headerStyle.Render(fmt.Sprintf("%-20s %-20s %-20s %10s\n",
"Player 1", "Player 2", "Winner", "Avg Moves")))
b.WriteString("\n")
···
// Highlight wins in green, losses in red
if match.WinnerUsername == username {
-
b.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("green")).Render(line))
+
b.WriteString(m.renderer.NewStyle().Foreground(lipgloss.Color("green")).Render(line))
} else {
b.WriteString(line)
}
···
return b.String()
}
-
func renderBracket(matches []storage.MatchResult) string {
+
func (m model) renderBracket(matches []storage.MatchResult) string {
var b strings.Builder
-
b.WriteString(lipgloss.NewStyle().Bold(true).Render("⚔️ Tournament Bracket") + "\n\n")
+
b.WriteString(m.renderer.NewStyle().Bold(true).Render("⚔️ Tournament Bracket") + "\n\n")
if len(matches) == 0 {
return b.String()
···
}
// Determine winner styling
-
player1Style := lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
-
player2Style := lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
-
winnerBox := lipgloss.NewStyle().
+
player1Style := m.renderer.NewStyle().Foreground(lipgloss.Color("240"))
+
player2Style := m.renderer.NewStyle().Foreground(lipgloss.Color("240"))
+
winnerBox := m.renderer.NewStyle().
Foreground(lipgloss.Color("green")).
Bold(true).
Border(lipgloss.RoundedBorder()).
···
// ├── Winner
// Player2 ┘
-
player1Box := lipgloss.NewStyle().
+
player1Box := m.renderer.NewStyle().
Border(lipgloss.RoundedBorder()).
Padding(0, 1).
Width(15)
-
player2Box := lipgloss.NewStyle().
+
player2Box := m.renderer.NewStyle().
Border(lipgloss.RoundedBorder()).
Padding(0, 1).
Width(15)
···
b.WriteString(p1 + connector1 + "\n")
b.WriteString(strings.Repeat(" ", 17) + middle + " " + winnerStr + "\n")
b.WriteString(p2 + connector2 + "\n")
-
b.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Render(
+
b.WriteString(m.renderer.NewStyle().Foreground(lipgloss.Color("240")).Render(
fmt.Sprintf(" (avg %d moves)\n", match.AvgMoves)))
b.WriteString("\n")
···
}
if len(matchups) > 8 {
-
b.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Render(
+
b.WriteString(m.renderer.NewStyle().Foreground(lipgloss.Color("240")).Render(
fmt.Sprintf("... and %d more matches\n", len(matchups)-8)))
}
+10 -8
internal/tui/onboarding.go
···
)
type OnboardingModel struct {
+
renderer *lipgloss.Renderer
username string
publicKey string
step int // 0=name, 1=bio, 2=link, 3=done
···
username string
}
-
func NewOnboardingModel(username, publicKey string, width, height int) OnboardingModel {
+
func NewOnboardingModel(username, publicKey string, width, height int, renderer *lipgloss.Renderer) OnboardingModel {
return OnboardingModel{
+
renderer: renderer,
username: username,
publicKey: publicKey,
step: 0,
···
m.height = msg.Height
case onboardingCompleteMsg:
// Transition to main model
-
mainModel := InitialModel(m.username, m.width, m.height)
+
mainModel := InitialModel(m.username, m.width, m.height, m.renderer)
return mainModel, mainModel.Init()
}
return m, nil
···
func (m OnboardingModel) View() string {
if m.completed {
-
successStyle := lipgloss.NewStyle().
+
successStyle := m.renderer.NewStyle().
Foreground(lipgloss.Color("green")).
Bold(true)
return successStyle.Render("\n✅ Account created successfully!\n\nLoading dashboard...\n")
···
var b strings.Builder
-
titleStyle := lipgloss.NewStyle().
+
titleStyle := m.renderer.NewStyle().
Bold(true).
Foreground(lipgloss.Color("205")).
MarginTop(1).
MarginBottom(1)
-
promptStyle := lipgloss.NewStyle().
+
promptStyle := m.renderer.NewStyle().
Foreground(lipgloss.Color("86"))
-
inputStyle := lipgloss.NewStyle().
+
inputStyle := m.renderer.NewStyle().
Foreground(lipgloss.Color("212")).
Bold(true)
-
errorStyle := lipgloss.NewStyle().
+
errorStyle := m.renderer.NewStyle().
Foreground(lipgloss.Color("196"))
-
helpStyle := lipgloss.NewStyle().
+
helpStyle := m.renderer.NewStyle().
Foreground(lipgloss.Color("240"))
b.WriteString(titleStyle.Render("🚢 Welcome to Battleship Arena!"))