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
+65 -591
+1
.gitignore
··· 6 6 build/ 7 7 8 8 ./battleship-arena 9 + ./remote-submissions 9 10 10 11 # Generated files in engine 11 12 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
··· 11 11 "time" 12 12 13 13 tea "github.com/charmbracelet/bubbletea" 14 - "github.com/charmbracelet/lipgloss" 15 14 "github.com/charmbracelet/ssh" 16 15 "github.com/charmbracelet/wish" 17 16 "github.com/charmbracelet/wish/bubbletea" ··· 145 144 // Get proper terminal options for color support 146 145 opts := bubbletea.MakeOptions(s) 147 146 147 + // Create renderer for this session 148 + renderer := bubbletea.MakeRenderer(s) 149 + 148 150 if needsOnboarding { 149 151 // Run onboarding first 150 152 publicKey := "" ··· 152 154 publicKey = val.(string) 153 155 } 154 156 155 - m := tui.NewOnboardingModel(s.User(), publicKey, pty.Window.Width, pty.Window.Height) 157 + m := tui.NewOnboardingModel(s.User(), publicKey, pty.Window.Width, pty.Window.Height, renderer) 156 158 return m, opts 157 159 } 158 160 159 - m := tui.InitialModel(s.User(), pty.Window.Width, pty.Window.Height) 161 + m := tui.InitialModel(s.User(), pty.Window.Width, pty.Window.Height, renderer) 160 162 return m, opts 161 163 } 162 164 ··· 174 176 return nil 175 177 } 176 178 177 - var titleStyle = lipgloss.NewStyle(). 178 - Bold(true). 179 - Foreground(lipgloss.Color("205")). 180 - MarginTop(1). 181 - MarginBottom(1) 179 +
-266
comito@0.0.0.0
··· 1 - #include "memory_functions_comito.h" 2 - 3 - using namespace std; 4 - 5 - void createMove(int row, int col, string &move); 6 - int moveCheck(int row, int col, const ComputerMemory &memory); 7 - void directionChecks(int &nextRow, int &nextCol, int dir, int offset, const ComputerMemory &memory); 8 - int DirFlip(int currDir); 9 - 10 - // initMemory initializes the memory; at the outset of the game the grid of 11 - // shots taken is empty, we've not hit any ships, and our player can only apply 12 - // a general, somewhat random firing strategy until we get a hit on some ship 13 - void initMemorycomito(ComputerMemory &memory) { 14 - memory.mode = RANDOM; 15 - memory.hitRow = -1; 16 - memory.hitCol = -1; 17 - memory.hitShip = NONE; 18 - memory.fireDir = NONE; 19 - memory.fireDist = 1; 20 - memory.lastResult = NONE; 21 - 22 - for (int i = 0; i < BOARDSIZE; i++) { 23 - for (int j = 0; j < BOARDSIZE; j++) { 24 - memory.grid[i][j] = EMPTY_MARKER; 25 - } 26 - } 27 - } 28 - 29 - // complete this function so it produces a "smart" move based on the information 30 - // which appears in the computer's memory 31 - string smartMovecomito(const ComputerMemory &memory) { 32 - string move; 33 - int checkResult = -1; 34 - int nextRow = -1; 35 - int nextCol = -1; 36 - if (memory.mode == SEARCH) 37 - { 38 - int i = 0; 39 - while (i <= 4 && checkResult != 1) 40 - { 41 - 42 - switch (memory.fireDir) 43 - { 44 - case 1: 45 - nextRow = memory.hitRow - 1; 46 - nextCol = memory.hitCol; 47 - break; 48 - case 2: 49 - nextRow = memory.hitRow + 1; 50 - nextCol = memory.hitCol; 51 - break; 52 - case 3: 53 - nextCol = memory.hitCol - 1; 54 - nextRow = memory.hitRow; 55 - break; 56 - case 4: 57 - nextCol = memory.hitCol + 1; 58 - nextRow = memory.hitRow; 59 - break; 60 - } 61 - i++; 62 - checkResult = moveCheck(nextRow, nextCol, memory); 63 - } 64 - } 65 - if (memory.mode == DESTROY) 66 - { 67 - switch (memory.fireDir) 68 - { 69 - case 1: 70 - nextRow = memory.hitRow - memory.fireDist; 71 - nextCol = memory.hitCol; 72 - break; 73 - case 2: 74 - nextRow = memory.hitRow + memory.fireDist; 75 - nextCol = memory.hitCol; 76 - break; 77 - case 3: 78 - nextCol = memory.hitCol - memory.fireDist; 79 - nextRow = memory.hitRow; 80 - break; 81 - case 4: 82 - nextCol = memory.hitCol + memory.fireDist; 83 - nextRow = memory.hitRow; 84 - break; 85 - } 86 - } 87 - createMove(nextRow, nextCol, move); 88 - debug(move); 89 - return move; 90 - } 91 - 92 - void updateMemorycomito(int row, int col, int result, ComputerMemory &memory) { 93 - 94 - int moveRes = result / 10; 95 - int hitShipId = result % 10; 96 - 97 - if (memory.mode == RANDOM) //if random 98 - { 99 - if (result == 0) //if missed 100 - { 101 - memory.fireDir = 0; 102 - } 103 - else //if hit 104 - { 105 - memory.hitShip = isShip(result); 106 - memory.hitRow = row; 107 - memory.hitCol = col; 108 - memory.mode = SEARCH; 109 - memory.fireDir++; 110 - } 111 - } 112 - else if (memory.mode == SEARCH) //if search 113 - { 114 - if (result == 0) //if missed 115 - { 116 - memory.fireDir++; 117 - if (memory.fireDir > 4) 118 - { 119 - memory.mode = RANDOM; 120 - memory.fireDist = 1; 121 - } 122 - } 123 - else 124 - { 125 - memory.mode = DESTROY; 126 - memory.fireDist++; 127 - } 128 - }else //if destroy 129 - { 130 - int i = BOARDSIZE; 131 - int nextRow = -1; 132 - int nextCol = -1; 133 - int checkResult = -1; 134 - while (checkResult != 1 && i > 0) 135 - { 136 - switch (memory.fireDir) 137 - { 138 - case 1: 139 - nextRow = memory.hitRow - memory.fireDist; 140 - nextCol = memory.hitCol; 141 - break; 142 - case 2: 143 - nextRow = memory.hitRow + memory.fireDist; 144 - nextCol = memory.hitCol; 145 - break; 146 - case 3: 147 - nextCol = memory.hitCol - memory.fireDist; 148 - nextRow = memory.hitRow; 149 - break; 150 - case 4: 151 - nextCol = memory.hitCol + memory.fireDist; 152 - nextRow = memory.hitRow; 153 - break; 154 - } 155 - checkResult = moveCheck(nextRow, nextCol, memory); 156 - 157 - switch (checkResult) 158 - { 159 - case 0: 160 - memory.fireDir = DirFlip(memory.fireDir); 161 - memory.fireDist = 1; 162 - break; 163 - case 1: 164 - //check if should fire here and if yes fire 165 - if (moveRes != 0) 166 - { 167 - 168 - } 169 - break; 170 - case 2: 171 - memory.fireDist += 1; 172 - break; 173 - case 3: 174 - memory.fireDir = DirFlip(memory.fireDir); 175 - memory.fireDist = 1; 176 - break; 177 - } 178 - i--; 179 - } 180 - if (i < 1) 181 - { 182 - memory.mode = RANDOM; 183 - } 184 - } 185 - //update memory grid 186 - if (result == 0) 187 - { 188 - memory.grid[row][col] = MISS_MARKER; 189 - } 190 - if (result == 1) 191 - { 192 - memory.grid[row][col] = HIT_MARKER; 193 - } 194 - } 195 - 196 - //I added this function to call within the move so that 197 - //I can input the row number and just get the letter out 198 - //I didn't know how best to do this so I did whatever this is :/ 199 - void createMove(int row, int col, string &move) 200 - { 201 - char letter = 'A'; 202 - letter += row; 203 - move.push_back(letter); 204 - string number = to_string(col + 1); 205 - move.append(number); 206 - } 207 - 208 - int moveCheck(int row, int col, const ComputerMemory &memory) 209 - { 210 - int success = 0; 211 - bool isMovePlayedYet = false; 212 - 213 - if (row < 0 || row >= BOARDSIZE || col < 0 || col >= BOARDSIZE) 214 - { 215 - success = false; 216 - } 217 - else 218 - { 219 - if(memory.grid[row][col] == EMPTY_MARKER) 220 - { 221 - success = 1; 222 - }else if (memory.grid[row][col] == HIT_MARKER) 223 - { 224 - success = 2; 225 - }else 226 - { 227 - success = 3; 228 - } 229 - } 230 - return success; 231 - } 232 - int DirFlip(int currDir) 233 - { 234 - if (currDir == 1) 235 - { 236 - return 2; 237 - } 238 - if (currDir == 3) 239 - { 240 - return 4; 241 - } 242 - return currDir; 243 - } 244 - /* attempted function graveyard 245 - { 246 - switch (dir) 247 - { 248 - case 1: 249 - nextRow = memory.hitRow - offset; 250 - nextCol = memory.hitCol; 251 - break; 252 - case 2: 253 - nextRow = memory.hitRow + offset; 254 - nextCol = memory.hitCol; 255 - break; 256 - case 3: 257 - nextCol = memory.hitCol - offset; 258 - nextRow = memory.hitRow; 259 - break; 260 - case 4: 261 - nextCol = memory.hitCol + offset; 262 - nextRow = memory.hitRow; 263 - break; 264 - } 265 - } 266 - */
-266
comito@localhost
··· 1 - #include "memory_functions_comito.h" 2 - 3 - using namespace std; 4 - 5 - void createMove(int row, int col, string &move); 6 - int moveCheck(int row, int col, const ComputerMemory &memory); 7 - void directionChecks(int &nextRow, int &nextCol, int dir, int offset, const ComputerMemory &memory); 8 - int DirFlip(int currDir); 9 - 10 - // initMemory initializes the memory; at the outset of the game the grid of 11 - // shots taken is empty, we've not hit any ships, and our player can only apply 12 - // a general, somewhat random firing strategy until we get a hit on some ship 13 - void initMemorycomito(ComputerMemory &memory) { 14 - memory.mode = RANDOM; 15 - memory.hitRow = -1; 16 - memory.hitCol = -1; 17 - memory.hitShip = NONE; 18 - memory.fireDir = NONE; 19 - memory.fireDist = 1; 20 - memory.lastResult = NONE; 21 - 22 - for (int i = 0; i < BOARDSIZE; i++) { 23 - for (int j = 0; j < BOARDSIZE; j++) { 24 - memory.grid[i][j] = EMPTY_MARKER; 25 - } 26 - } 27 - } 28 - 29 - // complete this function so it produces a "smart" move based on the information 30 - // which appears in the computer's memory 31 - string smartMovecomito(const ComputerMemory &memory) { 32 - string move; 33 - int checkResult = -1; 34 - int nextRow = -1; 35 - int nextCol = -1; 36 - if (memory.mode == SEARCH) 37 - { 38 - int i = 0; 39 - while (i <= 4 && checkResult != 1) 40 - { 41 - 42 - switch (memory.fireDir) 43 - { 44 - case 1: 45 - nextRow = memory.hitRow - 1; 46 - nextCol = memory.hitCol; 47 - break; 48 - case 2: 49 - nextRow = memory.hitRow + 1; 50 - nextCol = memory.hitCol; 51 - break; 52 - case 3: 53 - nextCol = memory.hitCol - 1; 54 - nextRow = memory.hitRow; 55 - break; 56 - case 4: 57 - nextCol = memory.hitCol + 1; 58 - nextRow = memory.hitRow; 59 - break; 60 - } 61 - i++; 62 - checkResult = moveCheck(nextRow, nextCol, memory); 63 - } 64 - } 65 - if (memory.mode == DESTROY) 66 - { 67 - switch (memory.fireDir) 68 - { 69 - case 1: 70 - nextRow = memory.hitRow - memory.fireDist; 71 - nextCol = memory.hitCol; 72 - break; 73 - case 2: 74 - nextRow = memory.hitRow + memory.fireDist; 75 - nextCol = memory.hitCol; 76 - break; 77 - case 3: 78 - nextCol = memory.hitCol - memory.fireDist; 79 - nextRow = memory.hitRow; 80 - break; 81 - case 4: 82 - nextCol = memory.hitCol + memory.fireDist; 83 - nextRow = memory.hitRow; 84 - break; 85 - } 86 - } 87 - createMove(nextRow, nextCol, move); 88 - debug(move); 89 - return move; 90 - } 91 - 92 - void updateMemorycomito(int row, int col, int result, ComputerMemory &memory) { 93 - 94 - int moveRes = result / 10; 95 - int hitShipId = result % 10; 96 - 97 - if (memory.mode == RANDOM) //if random 98 - { 99 - if (result == 0) //if missed 100 - { 101 - memory.fireDir = 0; 102 - } 103 - else //if hit 104 - { 105 - memory.hitShip = isShip(result); 106 - memory.hitRow = row; 107 - memory.hitCol = col; 108 - memory.mode = SEARCH; 109 - memory.fireDir++; 110 - } 111 - } 112 - else if (memory.mode == SEARCH) //if search 113 - { 114 - if (result == 0) //if missed 115 - { 116 - memory.fireDir++; 117 - if (memory.fireDir > 4) 118 - { 119 - memory.mode = RANDOM; 120 - memory.fireDist = 1; 121 - } 122 - } 123 - else 124 - { 125 - memory.mode = DESTROY; 126 - memory.fireDist++; 127 - } 128 - }else //if destroy 129 - { 130 - int i = BOARDSIZE; 131 - int nextRow = -1; 132 - int nextCol = -1; 133 - int checkResult = -1; 134 - while (checkResult != 1 && i > 0) 135 - { 136 - switch (memory.fireDir) 137 - { 138 - case 1: 139 - nextRow = memory.hitRow - memory.fireDist; 140 - nextCol = memory.hitCol; 141 - break; 142 - case 2: 143 - nextRow = memory.hitRow + memory.fireDist; 144 - nextCol = memory.hitCol; 145 - break; 146 - case 3: 147 - nextCol = memory.hitCol - memory.fireDist; 148 - nextRow = memory.hitRow; 149 - break; 150 - case 4: 151 - nextCol = memory.hitCol + memory.fireDist; 152 - nextRow = memory.hitRow; 153 - break; 154 - } 155 - checkResult = moveCheck(nextRow, nextCol, memory); 156 - 157 - switch (checkResult) 158 - { 159 - case 0: 160 - memory.fireDir = DirFlip(memory.fireDir); 161 - memory.fireDist = 1; 162 - break; 163 - case 1: 164 - //check if should fire here and if yes fire 165 - if (moveRes != 0) 166 - { 167 - 168 - } 169 - break; 170 - case 2: 171 - memory.fireDist += 1; 172 - break; 173 - case 3: 174 - memory.fireDir = DirFlip(memory.fireDir); 175 - memory.fireDist = 1; 176 - break; 177 - } 178 - i--; 179 - } 180 - if (i < 1) 181 - { 182 - memory.mode = RANDOM; 183 - } 184 - } 185 - //update memory grid 186 - if (result == 0) 187 - { 188 - memory.grid[row][col] = MISS_MARKER; 189 - } 190 - if (result == 1) 191 - { 192 - memory.grid[row][col] = HIT_MARKER; 193 - } 194 - } 195 - 196 - //I added this function to call within the move so that 197 - //I can input the row number and just get the letter out 198 - //I didn't know how best to do this so I did whatever this is :/ 199 - void createMove(int row, int col, string &move) 200 - { 201 - char letter = 'A'; 202 - letter += row; 203 - move.push_back(letter); 204 - string number = to_string(col + 1); 205 - move.append(number); 206 - } 207 - 208 - int moveCheck(int row, int col, const ComputerMemory &memory) 209 - { 210 - int success = 0; 211 - bool isMovePlayedYet = false; 212 - 213 - if (row < 0 || row >= BOARDSIZE || col < 0 || col >= BOARDSIZE) 214 - { 215 - success = false; 216 - } 217 - else 218 - { 219 - if(memory.grid[row][col] == EMPTY_MARKER) 220 - { 221 - success = 1; 222 - }else if (memory.grid[row][col] == HIT_MARKER) 223 - { 224 - success = 2; 225 - }else 226 - { 227 - success = 3; 228 - } 229 - } 230 - return success; 231 - } 232 - int DirFlip(int currDir) 233 - { 234 - if (currDir == 1) 235 - { 236 - return 2; 237 - } 238 - if (currDir == 3) 239 - { 240 - return 4; 241 - } 242 - return currDir; 243 - } 244 - /* attempted function graveyard 245 - { 246 - switch (dir) 247 - { 248 - case 1: 249 - nextRow = memory.hitRow - offset; 250 - nextCol = memory.hitCol; 251 - break; 252 - case 2: 253 - nextRow = memory.hitRow + offset; 254 - nextCol = memory.hitCol; 255 - break; 256 - case 3: 257 - nextCol = memory.hitCol - offset; 258 - nextRow = memory.hitRow; 259 - break; 260 - case 4: 261 - nextCol = memory.hitCol + offset; 262 - nextRow = memory.hitRow; 263 - break; 264 - } 265 - } 266 - */
+48 -43
internal/tui/model.go
··· 29 29 fieldLink 30 30 ) 31 31 32 - var titleStyle = lipgloss.NewStyle(). 33 - Bold(true). 34 - Foreground(lipgloss.Color("205")). 35 - MarginTop(1). 36 - MarginBottom(1) 37 - 38 32 type model struct { 33 + renderer *lipgloss.Renderer 34 + titleStyle lipgloss.Style 39 35 username string 40 36 width int 41 37 height int ··· 53 49 saveMessage string 54 50 } 55 51 56 - func InitialModel(username string, width, height int) model { 52 + func InitialModel(username string, width, height int, renderer *lipgloss.Renderer) model { 57 53 externalURL := os.Getenv("BATTLESHIP_EXTERNAL_URL") 58 54 if externalURL == "" { 59 55 externalURL = "localhost" ··· 74 70 // Load user profile 75 71 user, _ := storage.GetUserByUsername(username) 76 72 73 + // Create styles using session renderer 74 + titleStyle := renderer.NewStyle(). 75 + Bold(true). 76 + Foreground(lipgloss.Color("205")). 77 + MarginTop(1). 78 + MarginBottom(1) 79 + 77 80 return model{ 81 + renderer: renderer, 82 + titleStyle: titleStyle, 78 83 username: username, 79 84 width: width, 80 85 height: height, ··· 200 205 func (m model) View() string { 201 206 var b strings.Builder 202 207 203 - title := titleStyle.Render("🚢 Battleship Arena") 208 + title := m.titleStyle.Render("🚢 Battleship Arena") 204 209 b.WriteString(title + "\n") 205 210 206 211 // Skip tabs if in edit mode 207 212 if m.currentView != viewEditProfile { 208 213 // Navigation tabs 209 - tabStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("240")) 210 - activeTabStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("86")).Bold(true) 214 + tabStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("240")) 215 + activeTabStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("86")).Bold(true) 211 216 212 217 tabs := []string{"[h] Home", "[l] Leaderboard", "[p] Profile"} 213 218 for i, tab := range tabs { ··· 248 253 b.WriteString(fmt.Sprintf("User: %s\n\n", m.username)) 249 254 250 255 // Upload instructions 251 - infoStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("86")) 256 + infoStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("86")) 252 257 b.WriteString(infoStyle.Render(fmt.Sprintf("Upload via: scp -P %s memory_functions_yourname.cpp %s@%s:~/", m.sshPort, m.username, m.externalURL))) 253 258 b.WriteString("\n\n") 254 259 255 260 // Show submissions 256 261 if len(m.submissions) > 0 { 257 - b.WriteString(renderSubmissions(m.submissions)) 262 + b.WriteString(m.renderSubmissions(m.submissions)) 258 263 } else { 259 264 b.WriteString("No submissions yet. Upload your first AI!\n") 260 265 } ··· 264 269 265 270 func (m model) renderLeaderboardView() string { 266 271 if len(m.leaderboard) > 0 { 267 - return renderLeaderboard(m.leaderboard) 272 + return m.renderLeaderboard(m.leaderboard) 268 273 } 269 274 return "Loading leaderboard..." 270 275 } ··· 276 281 return "Loading profile..." 277 282 } 278 283 279 - b.WriteString(lipgloss.NewStyle().Bold(true).Render("👤 Profile") + "\n\n") 284 + b.WriteString(m.renderer.NewStyle().Bold(true).Render("👤 Profile") + "\n\n") 280 285 281 286 // Show user info 282 - labelStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("240")) 287 + labelStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("240")) 283 288 b.WriteString(labelStyle.Render("Username: ") + m.user.Username + "\n") 284 289 b.WriteString(labelStyle.Render("Name: ") + m.user.Name + "\n") 285 290 b.WriteString(labelStyle.Render("Bio: ") + m.user.Bio + "\n") 286 291 b.WriteString(labelStyle.Render("Link: ") + m.user.Link + "\n\n") 287 292 288 - hintStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("86")) 293 + hintStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("86")) 289 294 b.WriteString(hintStyle.Render("Press 'e' to edit profile") + "\n\n") 290 295 291 296 // Show user stats from submissions 292 297 if len(m.submissions) > 0 { 293 - b.WriteString(renderSubmissions(m.submissions)) 298 + b.WriteString(m.renderSubmissions(m.submissions)) 294 299 b.WriteString("\n") 295 300 } 296 301 ··· 300 305 func (m model) renderEditProfile() string { 301 306 var b strings.Builder 302 307 303 - b.WriteString(lipgloss.NewStyle().Bold(true).Render("✏️ Edit Profile") + "\n\n") 308 + b.WriteString(m.renderer.NewStyle().Bold(true).Render("✏️ Edit Profile") + "\n\n") 304 309 305 - activeStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("86")).Bold(true) 306 - inactiveStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("240")) 310 + activeStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("86")).Bold(true) 311 + inactiveStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("240")) 307 312 308 313 // Name field 309 314 if m.editingField == fieldName { ··· 329 334 b.WriteString("\n") 330 335 331 336 if m.saveMessage != "" { 332 - msgStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("green")) 337 + msgStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("green")) 333 338 b.WriteString(msgStyle.Render(m.saveMessage) + "\n\n") 334 339 } 335 340 336 - hintStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("240")) 341 + hintStyle := m.renderer.NewStyle().Foreground(lipgloss.Color("240")) 337 342 b.WriteString(hintStyle.Render("Tab/↑↓: Navigate fields | Enter: Save | Esc: Cancel")) 338 343 339 344 return b.String() ··· 387 392 }) 388 393 } 389 394 390 - func renderSubmissions(submissions []storage.Submission) string { 395 + func (m model) renderSubmissions(submissions []storage.Submission) string { 391 396 var b strings.Builder 392 - b.WriteString(lipgloss.NewStyle().Bold(true).Render("📤 Your Submissions") + "\n\n") 397 + b.WriteString(m.renderer.NewStyle().Bold(true).Render("📤 Your Submissions") + "\n\n") 393 398 394 - headerStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("240")) 399 + headerStyle := m.renderer.NewStyle().Bold(true).Foreground(lipgloss.Color("240")) 395 400 b.WriteString(headerStyle.Render(fmt.Sprintf("%-35s %-15s %s", 396 401 "Filename", "Uploaded", "Status")) + "\n") 397 402 ··· 414 419 415 420 // Build the line manually to avoid formatting issues with ANSI codes 416 421 line := fmt.Sprintf("%-35s %-15s ", sub.Filename, relTime) 417 - statusStyled := lipgloss.NewStyle().Foreground(lipgloss.Color(statusColor)).Render(sub.Status) 422 + statusStyled := m.renderer.NewStyle().Foreground(lipgloss.Color(statusColor)).Render(sub.Status) 418 423 b.WriteString(line + statusStyled + "\n") 419 424 } 420 425 ··· 436 441 return fmt.Sprintf("%dd ago", days) 437 442 } 438 443 439 - func renderLeaderboard(entries []storage.LeaderboardEntry) string { 444 + func (m model) renderLeaderboard(entries []storage.LeaderboardEntry) string { 440 445 if len(entries) == 0 { 441 446 return "No entries yet" 442 447 } 443 448 444 449 var b strings.Builder 445 - b.WriteString(lipgloss.NewStyle().Bold(true).Render("🏆 Leaderboard") + "\n\n") 450 + b.WriteString(m.renderer.NewStyle().Bold(true).Render("🏆 Leaderboard") + "\n\n") 446 451 447 452 // Header without styling on the whole line 448 453 b.WriteString(fmt.Sprintf("%-4s %-20s %11s %8s %8s %10s %10s\n", ··· 454 459 // Apply color only to the rank and pad manually 455 460 var displayRank string 456 461 if i == 0 { 457 - displayRank = lipgloss.NewStyle().Foreground(lipgloss.Color("220")).Render(rank) + " " // Gold 462 + displayRank = m.renderer.NewStyle().Foreground(lipgloss.Color("220")).Render(rank) + " " // Gold 458 463 } else if i == 1 { 459 - displayRank = lipgloss.NewStyle().Foreground(lipgloss.Color("250")).Render(rank) + " " // Silver 464 + displayRank = m.renderer.NewStyle().Foreground(lipgloss.Color("250")).Render(rank) + " " // Silver 460 465 } else if i == 2 { 461 - displayRank = lipgloss.NewStyle().Foreground(lipgloss.Color("208")).Render(rank) + " " // Bronze 466 + displayRank = m.renderer.NewStyle().Foreground(lipgloss.Color("208")).Render(rank) + " " // Bronze 462 467 } else { 463 468 displayRank = fmt.Sprintf("%-4s", rank) 464 469 } ··· 472 477 return b.String() 473 478 } 474 479 475 - func renderMatches(matches []storage.MatchResult, username string) string { 480 + func (m model) renderMatches(matches []storage.MatchResult, username string) string { 476 481 var b strings.Builder 477 482 478 483 // Filter to show only matches involving this user ··· 493 498 userMatches = userMatches[:limit] 494 499 } 495 500 496 - headerStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("240")) 501 + headerStyle := m.renderer.NewStyle().Bold(true).Foreground(lipgloss.Color("240")) 497 502 b.WriteString(headerStyle.Render(fmt.Sprintf("%-20s %-20s %-20s %10s\n", 498 503 "Player 1", "Player 2", "Winner", "Avg Moves"))) 499 504 b.WriteString("\n") ··· 504 509 505 510 // Highlight wins in green, losses in red 506 511 if match.WinnerUsername == username { 507 - b.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("green")).Render(line)) 512 + b.WriteString(m.renderer.NewStyle().Foreground(lipgloss.Color("green")).Render(line)) 508 513 } else { 509 514 b.WriteString(line) 510 515 } ··· 513 518 return b.String() 514 519 } 515 520 516 - func renderBracket(matches []storage.MatchResult) string { 521 + func (m model) renderBracket(matches []storage.MatchResult) string { 517 522 var b strings.Builder 518 - b.WriteString(lipgloss.NewStyle().Bold(true).Render("⚔️ Tournament Bracket") + "\n\n") 523 + b.WriteString(m.renderer.NewStyle().Bold(true).Render("⚔️ Tournament Bracket") + "\n\n") 519 524 520 525 if len(matches) == 0 { 521 526 return b.String() ··· 542 547 } 543 548 544 549 // Determine winner styling 545 - player1Style := lipgloss.NewStyle().Foreground(lipgloss.Color("240")) 546 - player2Style := lipgloss.NewStyle().Foreground(lipgloss.Color("240")) 547 - winnerBox := lipgloss.NewStyle(). 550 + player1Style := m.renderer.NewStyle().Foreground(lipgloss.Color("240")) 551 + player2Style := m.renderer.NewStyle().Foreground(lipgloss.Color("240")) 552 + winnerBox := m.renderer.NewStyle(). 548 553 Foreground(lipgloss.Color("green")). 549 554 Bold(true). 550 555 Border(lipgloss.RoundedBorder()). ··· 564 569 // ├── Winner 565 570 // Player2 ┘ 566 571 567 - player1Box := lipgloss.NewStyle(). 572 + player1Box := m.renderer.NewStyle(). 568 573 Border(lipgloss.RoundedBorder()). 569 574 Padding(0, 1). 570 575 Width(15) 571 576 572 - player2Box := lipgloss.NewStyle(). 577 + player2Box := m.renderer.NewStyle(). 573 578 Border(lipgloss.RoundedBorder()). 574 579 Padding(0, 1). 575 580 Width(15) ··· 584 589 b.WriteString(p1 + connector1 + "\n") 585 590 b.WriteString(strings.Repeat(" ", 17) + middle + " " + winnerStr + "\n") 586 591 b.WriteString(p2 + connector2 + "\n") 587 - b.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Render( 592 + b.WriteString(m.renderer.NewStyle().Foreground(lipgloss.Color("240")).Render( 588 593 fmt.Sprintf(" (avg %d moves)\n", match.AvgMoves))) 589 594 b.WriteString("\n") 590 595 ··· 592 597 } 593 598 594 599 if len(matchups) > 8 { 595 - b.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Render( 600 + b.WriteString(m.renderer.NewStyle().Foreground(lipgloss.Color("240")).Render( 596 601 fmt.Sprintf("... and %d more matches\n", len(matchups)-8))) 597 602 } 598 603
+10 -8
internal/tui/onboarding.go
··· 12 12 ) 13 13 14 14 type OnboardingModel struct { 15 + renderer *lipgloss.Renderer 15 16 username string 16 17 publicKey string 17 18 step int // 0=name, 1=bio, 2=link, 3=done ··· 29 30 username string 30 31 } 31 32 32 - func NewOnboardingModel(username, publicKey string, width, height int) OnboardingModel { 33 + func NewOnboardingModel(username, publicKey string, width, height int, renderer *lipgloss.Renderer) OnboardingModel { 33 34 return OnboardingModel{ 35 + renderer: renderer, 34 36 username: username, 35 37 publicKey: publicKey, 36 38 step: 0, ··· 98 100 m.height = msg.Height 99 101 case onboardingCompleteMsg: 100 102 // Transition to main model 101 - mainModel := InitialModel(m.username, m.width, m.height) 103 + mainModel := InitialModel(m.username, m.width, m.height, m.renderer) 102 104 return mainModel, mainModel.Init() 103 105 } 104 106 return m, nil ··· 106 108 107 109 func (m OnboardingModel) View() string { 108 110 if m.completed { 109 - successStyle := lipgloss.NewStyle(). 111 + successStyle := m.renderer.NewStyle(). 110 112 Foreground(lipgloss.Color("green")). 111 113 Bold(true) 112 114 return successStyle.Render("\n✅ Account created successfully!\n\nLoading dashboard...\n") ··· 114 116 115 117 var b strings.Builder 116 118 117 - titleStyle := lipgloss.NewStyle(). 119 + titleStyle := m.renderer.NewStyle(). 118 120 Bold(true). 119 121 Foreground(lipgloss.Color("205")). 120 122 MarginTop(1). 121 123 MarginBottom(1) 122 124 123 - promptStyle := lipgloss.NewStyle(). 125 + promptStyle := m.renderer.NewStyle(). 124 126 Foreground(lipgloss.Color("86")) 125 127 126 - inputStyle := lipgloss.NewStyle(). 128 + inputStyle := m.renderer.NewStyle(). 127 129 Foreground(lipgloss.Color("212")). 128 130 Bold(true) 129 131 130 - errorStyle := lipgloss.NewStyle(). 132 + errorStyle := m.renderer.NewStyle(). 131 133 Foreground(lipgloss.Color("196")) 132 134 133 - helpStyle := lipgloss.NewStyle(). 135 + helpStyle := m.renderer.NewStyle(). 134 136 Foreground(lipgloss.Color("240")) 135 137 136 138 b.WriteString(titleStyle.Render("🚢 Welcome to Battleship Arena!"))