Survival Royale 1.0.0
A very simple yet funny card game.
Loading...
Searching...
No Matches
tui.c
Go to the documentation of this file.
1// Copyright (C) 2025 Giulio Salvi, Jacopo Paradisi
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
20
21#include "tui.h"
22
23const char* CARD_RANK_STRINGS[] = {
24 "Ace", "Two", "Three", "Four", "Five",
25 "Six", "Seven", "Jack", "Queen", "King"
26};
27
28const char* CARD_SUIT_STRINGS[] = {
29 "Clubs", "Spades", "Diamonds", "Hearts"
30};
31
32void navigatePages(PageData* pagesData, int totalPages, int maxRows, int maxColumns, int bestStartColumn, int playerIndex, Game* game) {
33 int id = pagesData->players[playerIndex]->id;
34 Player* player = game->players[playerIndex];
35 char input;
36 int currentPage = getPageContainingPlayer(pagesData, totalPages, id);
37
39
40 displayPage(&pagesData[currentPage], maxRows, maxColumns, bestStartColumn);
41
42 cursorPosition(maxRows - (LOG_SECTION_HEIGHT + 1) + 1, 3);
43 printfgr("#b#It's your turn, player %d!!#r# On the #b#playing field#r# there are #b#%d LPs#r#.", id, game->lifePointsOnTheField);
44
45 cursorPosition(maxRows - (LOG_SECTION_HEIGHT + 1) + 2, 3);
46 applyEffect(game, playerIndex, true);
47
48 displayPage(&pagesData[currentPage], maxRows, maxColumns, bestStartColumn);
49
50 cursorPosition(maxRows - (LOG_SECTION_HEIGHT + 1) + 3, 3);
51 if(!player->revealedFacedDownCard) {
53
54 cursorPosition(maxRows - (LOG_SECTION_HEIGHT + 1) + 4, 3);
55 printgr("Do you want reveal it?");
56 } else
57 printfgr("Your #b#faced down card#r# has been #b##%d#already revealed#r#!", FgBrightRed);
58
59 while(1) {
60 cursorPosition(maxRows - 2, maxColumns - 2);
61 printgr(" ");
62 cursorBack(1);
63
64 input = tolower(getchar());
65
66 cursorPosition(maxRows - (LOG_SECTION_HEIGHT + 1) + 4, 3);
67 if(input == 'w') {
68 cursorUp(4);
70
71 currentPage = (currentPage + 1) % totalPages;
72 displayPage(&pagesData[currentPage], maxRows, maxColumns, bestStartColumn);
73 } else if(input == 's') {
74 cursorUp(4);
76
77 currentPage = (currentPage - 1 + totalPages) % totalPages;
78 displayPage(&pagesData[currentPage], maxRows, maxColumns, bestStartColumn);
79 } else if(!player->revealedFacedDownCard) {
80 if(input == 'y') {
81 bool applyEffectHasPrinted = applyEffect(game, playerIndex, false);
82 displayPage(&pagesData[currentPage], maxRows, maxColumns, bestStartColumn);
83
84 if(!applyEffectHasPrinted) {
85 cursorPosition(maxRows - (LOG_SECTION_HEIGHT + 1) + 4, 3);
86 eraseInLine(0);
87 drawPageFrame(maxRows, maxColumns);
88 }
89 } else if(input == 'n')
90 printgr("#b#The card has not been revealed#r#.");
91
92 if(input == 'y' || input == 'n') {
93 fflush(stdin);
94 Pause(true);
95 break;
96 }
97 } else {
98 printfgr("Your #b#faced down card#r# has been #b##%d#already revealed#r#!", FgBrightRed);
99
100 fflush(stdin);
101 Pause(true);
102
103 break;
104 }
105 }
106}
107
108void displayPage(PageData* page, int maxRows, int maxColumns, int bestStartColumn) {
109 drawCardsForPage(page, 3, bestStartColumn); // cards start at the best row to print with even spaces all the data
110
111 drawPageFrame(maxRows, maxColumns);
112}
113
114void drawPageFrame(int maxRows, int maxColumns) {
115 drawLine(1, 1, maxColumns, 'h', 'u');
116
117 drawLine(2, 1, maxRows - 1, 'v', 'n');
118 drawLine(2, maxColumns, maxRows - 1, 'v', 'n');
119
120 drawLine(maxRows, 1, maxColumns, 'h', 'd');
121
122 int logSeparatorRow = maxRows - LOG_SECTION_HEIGHT - 1;
123 drawLine(logSeparatorRow, 2, maxColumns - 2, 'h', 'n');
124
125 // Log section border that separates input from messages
126 drawLine(maxRows - (LOG_SECTION_HEIGHT), maxColumns - 3, LOG_SECTION_HEIGHT, 'v', 'n');
127}
128
129void drawCardsForPage(PageData* page, int startX, int bestStartColumn) {
130 int columnSpacing = CARD_WIDTH + 2;
131 int currentRow = startX;
132
133 for(int i = 0; i < page->playerCount; i++) {
134 int currentCol = bestStartColumn + (i % page->playerPerRow) * columnSpacing;
135
136 Player* player = page->players[i];
137 drawCard(currentRow, currentCol, player->facedUpCard.suit, player->facedUpCard.rank);
138
139 cursorPosition(currentRow + CARD_HEIGHT + 1, currentCol + 1);
140 printfgr("ID: %d", player->id);
141 cursorPosition(currentRow + CARD_HEIGHT + 2, currentCol + 1);
142 printfgr("Life: %d #%d#♥#r#", player->lifePoints, FgRed);
143
144 if((i + 1) % page->playerPerRow == 0) {
145 currentRow += page->rowSpacing;
146 }
147 }
148}
149
150void drawLine(int startingX, int startingY, int length, char direction, char borders) {
151 if(borders == 'u') {
152 cursorPosition(startingX, startingY);
153 printgr("┌");
154 for(int i = 1; i < length - 1; i++) {
155 cursorPosition(startingX, startingY + i);
156 printgr("─");
157 }
158 cursorPosition(startingX, startingY + length - 1);
159 printgr("┐");
160 } else if(borders == 'd') {
161 cursorPosition(startingX, startingY);
162 printgr("└");
163 for(int i = 1; i < length - 1; i++) {
164 cursorPosition(startingX, startingY + i);
165 printgr("─");
166 }
167 cursorPosition(startingX, startingY + length - 1);
168 printgr("┘");
169 } else if(borders == 'n') {
170 for(int i = 0; i < length; i++) {
171 if(direction == 'h') {
172 cursorPosition(startingX, startingY + i);
173 printgr("─");
174 } else if(direction == 'v') {
175 cursorPosition(startingX + i, startingY);
176 printgr("│");
177 }
178 }
179 }
180}
181
182void drawCard(int startingX, int startingY, int suit, int rank) {
183 int color = getCardColor(rank);
184
185 printfgr("#%d#", color);
186 drawLine(startingX, startingY, CARD_WIDTH, 'h', 'u');
187 drawLine(startingX + 1, startingY, CARD_HEIGHT - 1, 'v', 'n');
188 drawLine(startingX + 1, startingY + CARD_WIDTH - 1, CARD_HEIGHT - 1, 'v', 'n');
189 drawLine(startingX + CARD_HEIGHT, startingY, CARD_WIDTH, 'h', 'd');
191
192 int centerCol = startingY + CARD_WIDTH / 2;
193
194 const char* cardSuitString = CARD_SUIT_STRINGS[suit - Clubs];
195 const char* cardRankString = CARD_RANK_STRINGS[rank - 1];
196
197 cursorPosition(startingX + 2, centerCol - strlen(cardSuitString) / 2);
198 printfgr("#%d#%s", color, cardSuitString);
199 graphicReset();
200
201 cursorPosition(startingX + CARD_HEIGHT - 2, centerCol - strlen(cardRankString) / 2);
202 printfgr("#%d#%s", color, cardRankString);
203 graphicReset();
204}
205
206int getCardColor(int cardRank) {
207 switch(cardRank) {
208 case Seven:
209 return FgBlue;
210 case Jack:
211 return FgRed;
212 case Queen:
213 return FgYellow;
214 case Ace:
215 return FgMagenta;
216 case King:
217 return FgGreen;
218 default:
219 return FgWhite;
220 }
221}
222
223PageData* getPageData(int maxRows, int maxColumns, Player** players, int totalPlayers, int* totalPages, int* bestStartColumn) {
224 // Constants for card layout
225 int columnSpacing = CARD_WIDTH + 5; // Card width + spacing between cards
226 int rowSpacing = CARD_HEIGHT + 3; // Card height + name row + life points row + minimum space row
227
228 // Calculate the height of the upper section
229 int upperSectionHeight = maxRows - (1 + 1 + LOG_SECTION_HEIGHT + 1 + 1); // Upper line + upper minimal space + log section + line above
230
231 // Determine usable rows in the upper section
232 int availableRows = upperSectionHeight / rowSpacing;
233
234 // Adjust usable columns for horizontal centering
235 int usableColumns = maxColumns - 2; // Subtract minimal padding
236 int displayablePlayersPerRow = usableColumns / columnSpacing;
237
238 // Calculate the remaining space for centering cards horizontally
239 int totalCardWidth = displayablePlayersPerRow * columnSpacing;
240 int remainingSpace = usableColumns - totalCardWidth;
241 *bestStartColumn = 3 + (remainingSpace / 2); // Center cards horizontally
242
243 // Total players per page
244 int playersInEachFilledPage = availableRows * displayablePlayersPerRow;
245 *totalPages = (totalPlayers + playersInEachFilledPage - 1) / playersInEachFilledPage;
246
247 // Allocate memory for pages
248 PageData* pagesData = (PageData*)malloc(sizeof(PageData) * (*totalPages));
249 if(!pagesData)
250 exit(EXIT_ALLOC_FAILURE);
251
252 int playersArrayIndex = 0; // Track players assigned to pages
253
254 for(int i = 0; i < *totalPages; i++) {
255 int playersOnThisPage = (playersArrayIndex + playersInEachFilledPage > totalPlayers)
256 ? totalPlayers - playersArrayIndex
257 : playersInEachFilledPage;
258
259 // Fill page data
260 pagesData[i].playerRows = availableRows;
261 pagesData[i].playerPerRow = displayablePlayersPerRow;
262 pagesData[i].playerCount = playersOnThisPage;
263 pagesData[i].rowSpacing = rowSpacing;
264
265 pagesData[i].players = (Player**)malloc(sizeof(Player*) * playersOnThisPage);
266 if(!pagesData[i].players)
267 exit(EXIT_ALLOC_FAILURE);
268
269 for(int j = 0; j < playersOnThisPage; j++)
270 pagesData[i].players[j] = players[playersArrayIndex++];
271 }
272
273 return pagesData;
274}
275
276void freePageData(PageData* pages, int totalPages) {
277 for(int i = 0; i < totalPages; i++)
278 free(pages[i].players);
279
280 free(pages);
281}
282
283bool isTerminalSizeValid(int maxRows, int maxColumns) {
284 return (maxRows >= MIN_ROWS) && (maxColumns >= MIN_COLUMNS);
285}
286
287int getPageContainingPlayer(PageData* pagesData, int totalPages, int id) {
288 for(int i = 0; i < totalPages; i++)
289 for(int j = 0; j < pagesData[i].playerCount; j++)
290 if(pagesData[i].players[j]->id == id) return i;
291
292 return -1;
293}
void Pause(bool clear)
Waits for the user to press a key without waiting for 'Enter' to be pressed.
Definition ansi.c:305
void printgr(const char *text)
Prints a text with the graphic rendition specified with custom graphic rendition format specifiers.
Definition ansi.c:127
void eraseInDisplay(int n)
Clears part of the screen.
Definition ansi.c:398
void eraseInLine(int n)
Erases part of the line.
Definition ansi.c:405
void graphicReset()
Resets the graphic rendition.
Definition ansi.c:447
void defaultForegroundColor()
Resets the foreground color.
Definition ansi.c:479
void cursorUp(int n)
Moves the cursor up by n rows.
Definition ansi.c:340
void cursorPosition(int n, int m)
Moves the cursor at n-th row and at the m-th column.
Definition ansi.c:389
void clearScreen()
Clears the entire terminal's window's content.
Definition ansi.c:85
void cursorBack(int n)
Moves the cursor to the left by n columns.
Definition ansi.c:361
void printfgr(char *text,...)
Prints text as printgr but handles standard C format specifiers %, d, i, u, x, X, f,...
Definition ansi.c:222
#define FgYellow
ANSI standard yellow foreground color.
Definition ansi_const.h:30
#define FgBlue
ANSI standard blue foreground color.
Definition ansi_const.h:32
#define FgWhite
ANSI standard white foreground color.
Definition ansi_const.h:38
#define FgMagenta
ANSI standard magenta foreground color.
Definition ansi_const.h:34
#define FgBrightRed
ANSI standard bright red foreground color.
Definition ansi_const.h:42
#define FgRed
ANSI standard red foreground color.
Definition ansi_const.h:26
#define FgGreen
ANSI standard green foreground color.
Definition ansi_const.h:28
#define EXIT_ALLOC_FAILURE
Exit code when dynamic memory allocation fails.
Definition consts.h:22
#define Seven
Integer const to represent rank seven.
Definition main.h:38
#define Jack
Integer const to represent rank jack.
Definition main.h:40
#define Ace
Integer const to represent rank ace.
Definition main.h:26
#define Clubs
Integer const to represent suit clubs.
Definition main.h:47
bool applyEffect(Game *game, int playerPosition, bool facedUpCard)
Applies the effect of the player at the given index in the players vector.
Definition main.c:427
void tellFacedDownCard(Card card, int playerId)
Prints user-friendly the faced down card revealed of the player.
Definition main.c:649
#define Queen
Integer const to represent rank queen.
Definition main.h:42
#define King
Integer const to represent rank king.
Definition main.h:44
Defines a structure for the game.
Definition main.h:80
unsigned int lifePointsOnTheField
The amount of LPs currently on the playing field.
Definition main.h:88
Player ** players
The currently alive players vector.
Definition main.h:90
Struct to represent the layout and data of a page containing player information.
Definition tui.h:45
int playerPerRow
The number of players displayed per row.
Definition tui.h:49
int playerRows
The number of rows used to display players on the page.
Definition tui.h:47
int playerCount
The total number of players on the page.
Definition tui.h:51
Player ** players
An array of pointers to the players displayed on the page.
Definition tui.h:55
int rowSpacing
The vertical spacing between rows, including card height and player details.
Definition tui.h:53
Defines a structure for the player's info.
Definition main.h:66
Card facedDownCard
The faced down card.
Definition main.h:76
bool revealedFacedDownCard
Flag for recording if the faced down card has been revealed; it can be toggled even if the faced down...
Definition main.h:72
unsigned int id
The player ID.
Definition main.h:68
Defines a struct for representing a generic color.
Definition ansi.h:77
void drawPageFrame(int maxRows, int maxColumns)
Draws the frame for a page, including borders and separators.
Definition tui.c:114
int getPageContainingPlayer(PageData *pagesData, int totalPages, int id)
Determines which page contains a player by their ID.
Definition tui.c:287
PageData * getPageData(int maxRows, int maxColumns, Player **players, int totalPlayers, int *totalPages, int *bestStartColumn)
Computes the page layout for players and allocates memory for the page data.
Definition tui.c:223
void drawCard(int startingX, int startingY, int suit, int rank)
Draws a card at a specified position.
Definition tui.c:182
const char * CARD_SUIT_STRINGS[]
Definition tui.c:28
const char * CARD_RANK_STRINGS[]
Definition tui.c:23
void freePageData(PageData *pages, int totalPages)
Frees the memory allocated for page data.
Definition tui.c:276
void drawLine(int startingX, int startingY, int length, char direction, char borders)
Draws a line in the terminal.
Definition tui.c:150
int getCardColor(int cardRank)
Retrieves the color associated with a card rank.
Definition tui.c:206
void displayPage(PageData *page, int maxRows, int maxColumns, int bestStartColumn)
Displays a page of cards and player information.
Definition tui.c:108
void drawCardsForPage(PageData *page, int startX, int bestStartColumn)
Draws all cards for the current page.
Definition tui.c:129
bool isTerminalSizeValid(int maxRows, int maxColumns)
Checks if the terminal size meets the minimum requirements.
Definition tui.c:283
void navigatePages(PageData *pagesData, int totalPages, int maxRows, int maxColumns, int bestStartColumn, int playerIndex, Game *game)
Navigates between pages and handles player actions during their turn.
Definition tui.c:32
Terminal User Interface (TUI) implementation for card game display.
#define CARD_HEIGHT
Integer const to represent the height of a card.
Definition tui.h:36
#define MIN_ROWS
Integer const to represent the minimum rows of the terminal in order to start the game.
Definition tui.h:40
#define MIN_COLUMNS
Integer const to represent the minimum columns of the terminal in order to start the game.
Definition tui.h:42
#define LOG_SECTION_HEIGHT
Integer const to represent the height of the logs section.
Definition tui.h:38
#define CARD_WIDTH
Integer const to represent the width of a card.
Definition tui.h:34