winning/losing page

This commit is contained in:
Tenari
2026-03-09 18:57:53 -05:00
parent 18efda0b71
commit 2eaab9845d
3 changed files with 68 additions and 245 deletions
+16 -56
View File
@@ -7,7 +7,7 @@
* MY_CONSTANT
* */
#include <string.h>
#include <math.h>
//#include <math.h>
#include "base/impl.c"
#include "lib/network.c"
#include "render.c"
@@ -101,6 +101,7 @@ typedef struct TransactionResult {
typedef struct ParsedServerMessage {
Message type;
u8 byte;
u16 port;
u16 port2;
u32 ip;
@@ -323,51 +324,6 @@ fn void renderPercentBar(TuiState* tui, u16 x, u16 y, u16 width, u8 ansi_color,
}
}
fn void renderSpeechBubble(TuiState* tui, u16 x, u16 y, u16 max_width, String message, SpeechTailDirection dir) {
Pixel* buf = tui->frame_buffer;
Dim2 screen_dimensions = tui->screen_dimensions;
// calculate dimenions
u32 height = (message.length / max_width) + 1;
u32 width = message.length;
if (message.length > max_width) {
width = max_width;
}
if (width < 12) {
width = 12; // minimum width
}
// print the upper border
renderUtf8CharToBuffer(buf, x, y, "", screen_dimensions);
for (i32 i = 0; i < width+1; i++) {
renderUtf8CharToBuffer(buf, x+1+i, y, "", screen_dimensions);
}
renderUtf8CharToBuffer(buf, x+width+1, y, "", screen_dimensions);
// start printing the rows
for (u32 i = 0; i < height; i++) {
renderUtf8CharToBuffer(buf, x, y+1+i, "", screen_dimensions);
// print the line of text
for (u32 j = 0; j < width; j++) {
u32 character_index = (i*(width-1))+j;
u16 pos = (x+1+j) + (screen_dimensions.width * (y+1+i));
if (message.length > character_index) {
buf[pos].bytes[0] = message.bytes[character_index];
} else {
buf[pos].bytes[0] = ' ';
}
}
renderUtf8CharToBuffer(buf, x+1+width, y+1+i, "", screen_dimensions);
}
// print the bottom box border
renderUtf8CharToBuffer(buf, x, y+1+height, "", screen_dimensions);
for (i32 i = 0; i < width+1; i++) {
renderUtf8CharToBuffer(buf, x+1+i, y+1+height, "", screen_dimensions);
}
renderUtf8CharToBuffer(buf, x+width+1, y+1+height, "", screen_dimensions);
// TODO actually print the speech tail
}
fn void renderStaticAssetToPixelBuffer(TuiState* tui, u8* asset, u32 len, u16 x, u16 y) {
u16 line = 0;
u16 x_in_line = 0;
@@ -431,6 +387,9 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so
case MessageTurnTick:
case MessageNewAccountCreated:
case MessageBadPw: {/*nothing to parse but the type*/} break;
case MessageGameOver: {
parsed.byte = message[msg_pos++];
} break;
case MessageTransactionResult: {
parsed.tx_result.buying = message[msg_pos++];
parsed.tx_result.qty = readU32FromBufferLE(message + msg_pos);
@@ -551,6 +510,11 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
while (next_net_msg != NULL) {
msg_iters += 0;
switch (msg.type) {
case MessageGameOver: {
state->screen = state->me.id == msg.byte ? ScreenVictory : ScreenDefeat;
sprintf(sbuf,"got winner id: %d", msg.byte);
addSystemMessage((u8*)sbuf);
} break;
case MessagePayoffResult: {
state->ship_tab_states = ShipTabStateMain;
} break;
@@ -732,10 +696,8 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
u32 principal_borrowed = selected_ship_cost - STARTING_DOWN_PAYMENT;
f32 mortgage_rate = calcInterestRate(selected_ship_cost, STARTING_DOWN_PAYMENT);
f32 r = mortgage_rate / 100.0;
f32 rm = r / 12.0;
u32 n = 12 * 4;
u32 payment = principal_borrowed * (rm*pow((1.0+rm),n))/(pow((1+rm),n) - 1);
sprintf(sbuf, "Mortgage: $%d @ %f%% = $%d minimum payment per turn", selected_ship_cost - STARTING_DOWN_PAYMENT, mortgage_rate, payment);
u32 payment = principal_borrowed * r;
sprintf(sbuf, "Mortgage: $%d @ %f%% = $%d interest after first turn", selected_ship_cost - STARTING_DOWN_PAYMENT, mortgage_rate, payment);
renderStrToBuffer(tui->frame_buffer, 5, ++line, sbuf, screen_dimensions);
line++;
// the table
@@ -1359,23 +1321,21 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
case ScreenVictory: {
// SIMULATION
if (user_pressed_esc || input_buffer[0] == ASCII_RETURN || input_buffer[0] == ASCII_LINE_FEED) {
state->screen = ScreenMainGame;
should_quit = true;
}
// RENDERING
renderStrToBuffer(tui->frame_buffer, 10, 10, "You WIN!!!", screen_dimensions);
renderStrToBuffer(tui->frame_buffer, 5, 11, "You paid off your ships mortgage and are a free trader!", screen_dimensions);
} break;
case ScreenDefeat: {
// SIMULATION
if (user_pressed_esc || input_buffer[0] == ASCII_RETURN || input_buffer[0] == ASCII_LINE_FEED) {
state->me.id = 0;
state->menu.selected_index = 0;
clearServerSentState();
state->screen = ScreenCreateCharacter;
should_quit = true;
}
// RENDERING
renderStrToBuffer(tui->frame_buffer, 10, 10, "You died...", screen_dimensions);
renderStrToBuffer(tui->frame_buffer, 10, 10, "You lose, forever doomed to work for the bank...", screen_dimensions);
renderStrToBuffer(tui->frame_buffer, 10, 11, "[press ESC to continue]", screen_dimensions);
} break;
case Screen_Count:
+30 -173
View File
@@ -59,17 +59,6 @@ typedef struct ParsedClientCommandThreadQueue {
Cond not_full;
} ParsedClientCommandThreadQueue;
typedef struct Entity {
bool changed;
u8 x;
u8 y;
u8 color;
EntityType type;
u32 misc;
u64 id;
u64 features;
} Entity;
typedef struct Account {
u8 destination_sys_idx;
bool changed;
@@ -79,26 +68,6 @@ typedef struct Account {
PlayerShip ship;
} Account;
typedef struct EntityList {
u64 length; // the currently used length
u64 capacity;
Entity* items;
} EntityList;
typedef struct EntityChunk {
u64 length; // the currently used # of entities in this chunk
u64 capacity; // the "chunk size" / space in this chunk
struct EntityChunk* next; // the next chunk
Entity* items; // the actual entities
} EntityChunk;
typedef struct ChunkedEntityList {
u64 length; // the current number of entities
u64 chunk_size; // the # of entities per chunk
u64 chunks; // the # of chunks in this list so far
EntityChunk* first; // the first chunk of entities
} ChunkedEntityList;
typedef struct Client {
u16 lan_port;
i32 lan_ip;
@@ -134,9 +103,7 @@ typedef struct State {
///// Global Variables
global State state = { 0 };
global Arena permanent_arena = { 0 };
global const Entity NULL_ENTITY = { 0 };
global bool debug_mode = false;
global ChunkedEntityList free_chunks = { 0, CHUNK_SIZE, 0, NULL };
///// functionImplementations()
fn ParsedClientCommandThreadQueue* newPCCThreadQueue(Arena* a) {
@@ -182,44 +149,6 @@ fn ParsedClientCommand* pccThreadSafeNonblockingQueuePop(ParsedClientCommandThre
return result;
}
fn u64 entitySerialize(Entity current, Account* acct, u64 index, u8 bytes[]) {
// send entity header (common to all entity types)
index += writeU64ToBufferLE(bytes + index, current.id);
index += writeU64ToBufferLE(bytes + index, current.features);
bytes[index++] = current.x;
bytes[index++] = current.y;
bytes[index++] = (u8)current.type; // EntityType
//if (CheckFlag(current.features, FeatureRegensHp)) {
// index += writeU16ToBufferLE(bytes + index, current.hp);
// index += writeU16ToBufferLE(bytes + index, current.max_hp);
//}
// send character-specific details (color and name)
if (current.type == EntityCharacter) {
bytes[index++] = current.color;
// send name string
index += writeU64ToBufferLE(bytes + index, acct->name.length);
for (u32 j = 0; j < acct->name.length; j++) {
bytes[index++] = acct->name.bytes[j];
}
}
return index;
}
fn u64 entityFeaturesFromType(EntityType type) {
u64 result = 0;
switch (type) {
case EntityCharacter: {
SetFlag(result, FeatureWalksAround);
SetFlag(result, FeatureCanFight);
} break;
default: {
} break;
}
return result;
}
fn Account* findAccountByName(String name) {
for (u32 i = 0; i < ACCOUNT_LEN; i++) {
if (stringsEq(&state.accounts[i].name, &name)) {
@@ -229,93 +158,6 @@ fn Account* findAccountByName(String name) {
return NULL;
}
fn u64 pushEntity(EntityChunk* chunk, Entity e) {
assert(chunk->length < chunk->capacity);
chunk->items[chunk->length] = e;
u64 result = chunk->length;
chunk->length += 1;
return result;
}
fn bool isSpaceInChunk(EntityChunk* chunk) {
return chunk->length < chunk->capacity;
}
fn bool allChunksFull(ChunkedEntityList list) {
return list.length >= (list.chunk_size * list.chunks);
}
fn EntityChunk* pushNewChunk(Arena* a, ChunkedEntityList* list) {
EntityChunk* new_chunk;
if (free_chunks.length > 0) {
new_chunk = free_chunks.first;
free_chunks.length -= 1;
free_chunks.first = new_chunk->next;
} else {
new_chunk = arenaAlloc(a, sizeof(EntityChunk));
// alloc the new chunk of entities
new_chunk->length = 0;
new_chunk->capacity = list->chunk_size;
new_chunk->next = NULL;
new_chunk->items = arenaAllocArray(a, Entity, list->chunk_size);
}
// bookeeping in the list
list->chunks += 1;
if (list->first == NULL) {
list->first = new_chunk;
} else {
EntityChunk* last = list->first;
while (last->next != NULL) {
last = last->next;
}
last->next = new_chunk;
}
return new_chunk;
}
fn Entity* entityPtrFromChunkList(ChunkedEntityList* list, i32 index) {
Entity* result = (Entity*)&NULL_ENTITY;
i32 chunk_index = index / list->chunk_size;
EntityChunk* chunk = list->first;
for (i32 i = 0; i < chunk_index; i++) {
chunk = chunk->next;
}
result = &chunk->items[index % list->chunk_size];
return result;
}
fn Entity entityFromChunkList(ChunkedEntityList* list, i32 index) {
Entity result = {0};
i32 chunk_index = index / list->chunk_size;
EntityChunk* chunk = list->first;
for (i32 i = 0; i < chunk_index; i++) {
chunk = chunk->next;
}
result = chunk->items[index % list->chunk_size];
return result;
}
fn bool deleteLastEntity(ChunkedEntityList* list, EntityChunk* last_chunk, EntityChunk* second_to_last_chunk) {
last_chunk->length -= 1;
list->length -= 1;
// don't delete the room's only chunk, but otherwise move the chunk to the free-list
if (last_chunk->length == 0 && last_chunk != list->first) {
list->chunks -= 1;
second_to_last_chunk->next = NULL;
free_chunks.length += 1;
EntityChunk* last_free_chunk = free_chunks.first;
if (last_free_chunk) {
while (last_free_chunk->next != NULL) {
last_free_chunk = last_free_chunk->next;
}
last_free_chunk->next = last_chunk;
} else {
free_chunks.first = last_chunk;
}
}
return true;
}
fn void exitWithErrorMessage(ptr msg) {
printf("error: %s", msg);
exit(1);
@@ -515,8 +357,7 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so
printf("Logging in player: %s %d %s\n", MESSAGE_STRINGS[parsed.type], name_len, message + 7);
} break;
case CommandKeepAlive:
break;
case CommandKeepAlive: break;
case CommandPayMortgage: {
parsed.id = readU64FromBufferLE(message + msg_idx);
printf("paying off %lld\n", parsed.id);
@@ -720,8 +561,10 @@ fn void* gameLoop(void* params) {
u32 qty_traded = 0;
u32 credit_value = 0;
if (is_buying_from_system) {
u32 ship_space = MAX_u32;
if (COMMODITIES[msg.commodity].unit == StorageUnitContainer) {
u32 ship_space = account->ship.cu_m_fuel - account->ship.commodities[CommodityHydrogenFuel];
if (msg.commodity == CommodityOxygen) {
ship_space = account->ship.cu_m_o2 - account->ship.commodities[CommodityOxygen];
} else if (COMMODITIES[msg.commodity].unit == StorageUnitContainer) {
ship_space = account->ship.vacuum_cargo_slots - usedVacuumCargoSlots(account->ship);
}
for (u32 amount_to_buy = Min(msg.qty, total_available); amount_to_buy > 0; amount_to_buy--, total_available--, ship_space--) {
@@ -858,12 +701,12 @@ fn void* gameLoop(void* params) {
.remaining_mortgage = template.base_cost - STARTING_DOWN_PAYMENT,
.interest_rate = calcInterestRate(template.base_cost, STARTING_DOWN_PAYMENT),
.credits = 10000.0,
//.cu_m_fuel; // we are just saying you can buy as much fuel as you want
//.cu_m_o2; // we are just saying you can buy as much o2 as you want
.cu_m_fuel = template.cu_m_fuel,
.cu_m_o2 = template.cu_m_o2,
.id = account->id,
};
player_ship.commodities[CommodityHydrogenFuel] = 2000;
player_ship.commodities[CommodityOxygen] = 1000;
player_ship.commodities[CommodityHydrogenFuel] = player_ship.cu_m_fuel;
player_ship.commodities[CommodityOxygen] = player_ship.cu_m_o2;
account->ship = player_ship;
printf("ship_type=%s, client_handle=%d, acct_id=%d\n", SHIP_TYPE_STRINGS[msg.byte], client_handle, account->id);
u32 starting_system_idx = rand() % STAR_SYSTEM_COUNT;
@@ -933,12 +776,15 @@ fn void* gameLoop(void* params) {
outgoingMessageQueuePush(state.network_send_queue, &outgoing_message);
}
}
return NULL;
}
} unlockMutex(&state.mutex); unlockMutex(&state.client_mutex);
}
if (state.someone_won) {
return NULL;
}
LaneSync();
u32 player_count = 0;
@@ -990,22 +836,30 @@ fn void* gameLoop(void* params) {
acct->ship.ready_to_depart = false;
acct->changed = true;
// look for a winner + increase the mortgage from interest
// increase the mortgage from interest
if (!shipIsNull(&acct->ship)) {
if (acct->ship.remaining_mortgage > 0) {
acct->ship.remaining_mortgage += acct->ship.remaining_mortgage * (acct->ship.interest_rate / 100.0);
}
}
}
}
// look for a winner
Range1u64 ship_range = LaneRange(player_count);
for (u32 i = ship_range.min; i < ship_range.max; i++) {
Account* acct = &state.accounts[i];
if (!shipIsNull(&acct->ship)) {
if (acct->ship.remaining_mortgage == 0) {
state.winner_id = acct->id;
state.someone_won = true;
printf("someone WON!!! %d\n", i);
acct->changed = true;
break;
} else {
acct->ship.remaining_mortgage += acct->ship.remaining_mortgage * (acct->ship.interest_rate / 100.0);
}
}
}
// TODO pay the mortgage payment
}
// 3. scratch cleanup
arenaClear(&scratch_arena);
@@ -1209,6 +1063,9 @@ i32 main(i32 argc, ptr argv[]) {
osThreadJoin(game_threads[i], MAX_u64);
}
osThreadJoin(recv_thread, MAX_u64);
osThreadJoin(send_thread, MAX_u64);
return 0;
}
+20 -14
View File
@@ -6,7 +6,7 @@
///// #define some game-tunable constants
#define STARTING_DOWN_PAYMENT (10000)
#define SHIP_DETAIL_COUNT (8)
#define SHIP_DETAIL_COUNT (7)
#define STAR_SYSTEM_COUNT (40)
#define MAP_WIDTH (36)
#define MAP_HEIGHT (12)
@@ -34,14 +34,14 @@ typedef enum ShipType {
ShipSparrow,
ShipDart,
ShipHaulerPrimeZ1,
ShipNX400,
// ShipNX400,
ShipType_Count,
} ShipType;
global str SHIP_TYPE_STRINGS[] = {
"Sparrow",
"Dart",
"Hauler-Prime Z-1",
"NX-400",
// "NX-400",
};
typedef struct ShipTemplate {
@@ -54,33 +54,39 @@ typedef struct ShipTemplate {
u16 passenger_amenities_flags;
u16 smugglers_hold_cu_m;
u32 base_cost;
u32 cu_m_fuel;
u32 cu_m_o2;
} ShipTemplate;
global ShipTemplate SHIPS[] = {
{
.type = ShipSparrow, .drive_efficiency = 1, .life_support_efficiency = 2,
.type = ShipSparrow, .drive_efficiency = 10, .life_support_efficiency = 2,
.vacuum_cargo_slots = 5, .climate_cargo_slots = 0, .passenger_berths = 0,
.passenger_amenities_flags = 0,
.smugglers_hold_cu_m = 1, .base_cost = 100000,
.cu_m_fuel = 2000, .cu_m_o2 = 1000,
},
{
.type = ShipDart, .drive_efficiency = 12, .life_support_efficiency = 4,
.vacuum_cargo_slots = 9, .climate_cargo_slots = 1, .passenger_berths = 1,
.type = ShipDart, .drive_efficiency = 70, .life_support_efficiency = 4,
.vacuum_cargo_slots = 10, .climate_cargo_slots = 1, .passenger_berths = 1,
.passenger_amenities_flags = 0,
.smugglers_hold_cu_m = 0, .base_cost = 150000,
.cu_m_fuel = 2000, .cu_m_o2 = 1000,
},
{
.type = ShipHaulerPrimeZ1, .drive_efficiency = 3, .life_support_efficiency = 3,
.vacuum_cargo_slots = 25, .climate_cargo_slots = 5, .passenger_berths = 0,
.type = ShipHaulerPrimeZ1, .drive_efficiency = 50, .life_support_efficiency = 3,
.vacuum_cargo_slots = 100, .climate_cargo_slots = 5, .passenger_berths = 0,
.passenger_amenities_flags = 0,
.smugglers_hold_cu_m = 0, .base_cost = 250000,
.cu_m_fuel = 2000, .cu_m_o2 = 1000,
},
{
/* {
.type = ShipNX400, .drive_efficiency = 6, .life_support_efficiency = 8,
.vacuum_cargo_slots = 5, .climate_cargo_slots = 5, .passenger_berths = 5,
.passenger_amenities_flags = 0,
.smugglers_hold_cu_m = 0, .base_cost = 350000,
},
.cu_m_fuel = 2000, .cu_m_o2 = 1000,
},*/
};
fn f32 calcInterestRate(u32 cost, u32 down_payment) {
@@ -93,10 +99,10 @@ global FieldDescriptor SHIP_FIELDS[SHIP_DETAIL_COUNT] = {
{ "Cost", FieldTypeU32, offsetof(ShipTemplate, base_cost), 8 },
{ "DriveEff", FieldTypeU8, offsetof(ShipTemplate, drive_efficiency), 8 },
{ "LifeSupp", FieldTypeU8, offsetof(ShipTemplate, life_support_efficiency), 8 },
{ "V.Cargo", FieldTypeU16, offsetof(ShipTemplate, vacuum_cargo_slots), 7 },
{ "C.Cargo", FieldTypeU16, offsetof(ShipTemplate, climate_cargo_slots), 7 },
{ "Passengers", FieldTypeU16, offsetof(ShipTemplate, passenger_berths), 10 },
{ "SmugglersHold", FieldTypeU16, offsetof(ShipTemplate, smugglers_hold_cu_m), 13 },
{ "Cargo", FieldTypeU16, offsetof(ShipTemplate, vacuum_cargo_slots), 7 },
{ "FuelTank", FieldTypeU32, offsetof(ShipTemplate, cu_m_fuel), 8 },
{ "O2 Tank", FieldTypeU32, offsetof(ShipTemplate, cu_m_o2), 8 },
// { "SmugglersHold", FieldTypeU16, offsetof(ShipTemplate, smugglers_hold_cu_m), 13 },
};
typedef enum EquipmentType {