start auction tab logic

This commit is contained in:
Tenari
2026-03-25 07:48:51 -05:00
parent f7403581ae
commit 4aece817b6
4 changed files with 170 additions and 27 deletions
+2
View File
@@ -503,6 +503,8 @@ global const u64 MAX_u64 = 0xffffffffffffffffull;
global const u32 MAX_u32 = 0xffffffff; global const u32 MAX_u32 = 0xffffffff;
global const u16 MAX_u16 = 0xffff; global const u16 MAX_u16 = 0xffff;
global const u8 MAX_u8 = 0xff; global const u8 MAX_u8 = 0xff;
#define EULERS_E (2.71828)
#define PI (3.14159265358979323846)
///// CUSTOM ENTRY POINT ///// CUSTOM ENTRY POINT
/* TODO? /* TODO?
+76 -6
View File
@@ -30,9 +30,9 @@
///// TYPES ///// TYPES
typedef enum Tab { typedef enum Tab {
TabDebug, TabDebug,
// TabChat,
TabMap, TabMap,
TabShip, TabShip,
TabAuction,
TabMarket, TabMarket,
TabPassengers, TabPassengers,
Tab_Count, Tab_Count,
@@ -62,6 +62,13 @@ typedef enum PassengersTabStates {
PassengersTabStates_Count, PassengersTabStates_Count,
} PassengersTabStates; } PassengersTabStates;
typedef enum AuctionTabStates {
AuctionTabStateMain,
AuctionTabStateLoading,
AuctionTabStateResult,
AuctionTabStates_Count,
} AuctionTabStates;
typedef enum Screen { typedef enum Screen {
ScreenLogin, ScreenLogin,
ScreenCreateCharacter, ScreenCreateCharacter,
@@ -165,6 +172,7 @@ typedef struct GameState {
MarketTabStates market_tab_state; MarketTabStates market_tab_state;
ShipTabStates ship_tab_states; ShipTabStates ship_tab_states;
PassengersTabStates passenger_tab_state; PassengersTabStates passenger_tab_state;
AuctionTabStates auction_tab_state;
bool passenger_job_accepted; bool passenger_job_accepted;
TransactionResult tx_result; TransactionResult tx_result;
u64 turn_tick_started_on; u64 turn_tick_started_on;
@@ -179,7 +187,7 @@ global u8 system_message_index = 0;
global GameState state = {0}; global GameState state = {0};
global OutgoingMessageQueue* network_send_queue = {0}; global OutgoingMessageQueue* network_send_queue = {0};
global ParsedServerMessageThreadQueue* network_recv_queue = {0}; global ParsedServerMessageThreadQueue* network_recv_queue = {0};
global str TAB_STRS[Tab_Count] = {"Debug", /*"Chat",*/ "Map", "Ship", "Market", "Passengers"}; global str TAB_STRS[Tab_Count] = {"Debug", "Map", "Ship", "Auction", "Market", "Passengers"};
global FieldDescriptor COMPARE_COMMODITY_FIELDS[STAR_SYSTEM_COUNT] = { global FieldDescriptor COMPARE_COMMODITY_FIELDS[STAR_SYSTEM_COUNT] = {
{ "System", FieldTypeString, offsetof(CompareCommodity, system_name), 16 }, { "System", FieldTypeString, offsetof(CompareCommodity, system_name), 16 },
{ "Bid", FieldTypeU32, offsetof(CompareCommodity, bid), 6 }, { "Bid", FieldTypeU32, offsetof(CompareCommodity, bid), 6 },
@@ -306,6 +314,8 @@ fn void resetTabRow(Tab tab) {
state.row.selected_index = 0; state.row.selected_index = 0;
state.modal_choice.selected_index = 0; state.modal_choice.selected_index = 0;
state.modal_choice.len = 2; state.modal_choice.len = 2;
} else if (state.menu.selected_index == TabAuction) {
state.auction_tab_state = AuctionTabStateMain;
} }
} }
@@ -341,6 +351,16 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so
case MessageGameOver: { case MessageGameOver: {
parsed.byte = message[msg_pos++]; parsed.byte = message[msg_pos++];
} break; } break;
case MessageAuctionDetails: {
parsed.sys.auction.type = message[msg_pos++];
parsed.sys.auction.qty = message[msg_pos++];
parsed.sys.auction.price = readU32FromBufferLE(message + msg_pos);
msg_pos += 4;
parsed.sys.auction.started_at = readU32FromBufferLE(message + msg_pos);
msg_pos += 4;
parsed.sys.auction.finished_at = readU32FromBufferLE(message + msg_pos);
msg_pos += 4;
} break;
case MessageJobComplete: { case MessageJobComplete: {
parsed.byte = message[msg_pos++]; parsed.byte = message[msg_pos++];
} break; } break;
@@ -628,6 +648,11 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
} }
} }
} break; } break;
case MessageAuctionDetails: {
u32 sys_idx = state->me.system_idx;
StarSystem* sys = &state->map[sys_idx];
sys->auction = msg.sys.auction;
} break;
case MessageSystemCommodities: { case MessageSystemCommodities: {
u32 sys_idx = msg.id; u32 sys_idx = msg.id;
StarSystem* sys = &state->map[sys_idx]; StarSystem* sys = &state->map[sys_idx];
@@ -878,7 +903,7 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
// draw the tabs box // draw the tabs box
u32 tabs_y = 0; u32 tabs_y = 0;
u32 box_y = tabs_y + 3; u32 box_y = tabs_y + 2;
Box box = { Box box = {
.x = 1, .x = 1,
.y = box_y, .y = box_y,
@@ -903,9 +928,6 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
} }
renderSystemMessages(tui->frame_buffer, tui->screen_dimensions, box); renderSystemMessages(tui->frame_buffer, tui->screen_dimensions, box);
} break; } break;
/*case TabChat: {
renderStrToBuffer(tui->frame_buffer, box.x+2, box.y+1, "You haven't heard anything interesting lately...", screen_dimensions);
} break;*/
case TabMap: { case TabMap: {
if (user_pressed_up) { if (user_pressed_up) {
state->pos.y -= 1; state->pos.y -= 1;
@@ -1248,6 +1270,54 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
renderStrToBuffer(tui->frame_buffer, modal_outline.x+2, y_off, "Loading...", screen_dimensions); renderStrToBuffer(tui->frame_buffer, modal_outline.x+2, y_off, "Loading...", screen_dimensions);
} }
} break; } break;
case TabAuction: {
u32 yoff = box.y+1;
renderStrToBuffer(tui->frame_buffer, box.x+((box.width - 13)/2), yoff++, "Auction House", screen_dimensions);
yoff++;
if (curr.auction.finished_at > curr.auction.started_at) {
MemoryZero(sbuf, SBUFLEN);
sprintf(sbuf, "Sold! %d containers of %s were sold for $%d", curr.auction.qty, COMMODITY_STRINGS[curr.auction.type], curr.auction.price);
renderStrToBuffer(tui->frame_buffer, box.x+2, yoff++, sbuf, screen_dimensions);
} else if (state->auction_tab_state == AuctionTabStateMain) {
MemoryZero(sbuf, SBUFLEN);
sprintf(sbuf, "Commodity up for Auction: %s", COMMODITY_STRINGS[curr.auction.type]);
renderStrToBuffer(tui->frame_buffer, box.x+2, yoff++, sbuf, screen_dimensions);
MemoryZero(sbuf, SBUFLEN);
sprintf(sbuf, "Offering %d containers", curr.auction.qty);
renderStrToBuffer(tui->frame_buffer, box.x+2, yoff++, sbuf, screen_dimensions);
yoff++;
MemoryZero(sbuf, SBUFLEN);
sprintf(sbuf, "Current price: %d", curr.auction.price);
renderStrToBuffer(tui->frame_buffer, box.x+2, yoff++, sbuf, screen_dimensions);
yoff++;
const char* label = "ACCEPT OFFER [ENTER]";
Range1u32 range = {
.min = XYToPos(box.x+6, yoff, screen_dimensions.width),
.max = XYToPos(box.x+6+strlen(label), yoff, screen_dimensions.width),
};
tui->cursor.x = box.x+6;
tui->cursor.y = yoff;
colorizeRange(tui, range, ANSI_WHITE, ANSI_BLACK);
renderStrToBuffer(tui->frame_buffer, box.x+6, yoff++, label, screen_dimensions);
if (user_pressed_enter) {
// send the "buy this auction" message to the server
u32 msg_idx = 0;
UDPMessage msg = {0};
msg.address = udp->server_address;
msg.bytes[msg_idx++] = CommandBuyAuction;
msg.bytes_len = msg_idx;
outgoingMessageQueuePush(network_send_queue, &msg);
state->auction_tab_state = AuctionTabStateLoading;
}
} else if (state->auction_tab_state == AuctionTabStateLoading) {
renderStrToBuffer(tui->frame_buffer, box.x+((box.width - 13)/2), yoff++, "Bid submitted...", screen_dimensions);
}
} break;
case TabMarket: { case TabMarket: {
if (input_buffer[0] == 'q' || user_pressed_esc) { if (input_buffer[0] == 'q' || user_pressed_esc) {
state->market_tab_state = MarketTabStateTable; state->market_tab_state = MarketTabStateTable;
+66 -10
View File
@@ -9,6 +9,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/random.h> #include <sys/random.h>
#include <math.h>
#include "shared.h" #include "shared.h"
#include "base/impl.c" #include "base/impl.c"
#define NET_OUTGOING_MESSAGE_QUEUE_LEN 64 #define NET_OUTGOING_MESSAGE_QUEUE_LEN 64
@@ -286,6 +287,14 @@ fn u32 starSystemPlanetCount(StarSystem* sys) {
return planet_count; return planet_count;
} }
fn bool accountIsEmpty(Account* a) {
return a->id == 0 && a->name.length == 0;
}
fn bool shipIsNull(PlayerShip* ship) {
return ship->id == 0 && ship->base_cost == 0;
}
fn UDPMessage makeMessageSystemPassengers(StarSystem* sys) { fn UDPMessage makeMessageSystemPassengers(StarSystem* sys) {
UDPMessage outgoing_message = {0}; UDPMessage outgoing_message = {0};
u32 msg_i = 0; u32 msg_i = 0;
@@ -562,6 +571,26 @@ fn void* sendNetworkUpdates(void* sock) {
sendUDPu8List(socket, &client.address, &sys_msg); sendUDPu8List(socket, &client.address, &sys_msg);
// and the passenger offers // and the passenger offers
sendUDPu8List(socket, &client.address, &sys_pass_msg); sendUDPu8List(socket, &client.address, &sys_pass_msg);
// send the client the current auction details for their current system
Account* account = &state.accounts[client.account_id];
if (!accountIsEmpty(account)) {
StarSystem curr = state.map[account->ship.system_idx];
MemoryZero(sbuf, SBUFLEN);
u32 msgidx = 0;
sbuf[msgidx++] = MessageAuctionDetails;
sbuf[msgidx++] = curr.auction.type;
sbuf[msgidx++] = curr.auction.qty;
msgidx += writeU32ToBufferLE((u8*)sbuf + msgidx, curr.auction.price);
msgidx += writeU32ToBufferLE((u8*)sbuf + msgidx, curr.auction.started_at);
msgidx += writeU32ToBufferLE((u8*)sbuf + msgidx, curr.auction.finished_at);
u8List msg = {
.capacity = UDP_MAX_MESSAGE_LEN,
.items = (u8*)sbuf,
.length = msgidx,
};
sendUDPu8List(socket, &client.address, &msg);
}
} }
// update all the changed systems // update all the changed systems
@@ -621,14 +650,6 @@ fn void* sendNetworkUpdates(void* sock) {
return NULL; return NULL;
} }
fn bool accountIsEmpty(Account* a) {
return a->id == 0 && a->name.length == 0;
}
fn bool shipIsNull(PlayerShip* ship) {
return ship->id == 0 && ship->base_cost == 0;
}
fn void* gameLoop(void* params) { fn void* gameLoop(void* params) {
LaneCtx* lane_ctx = (LaneCtx*)params; LaneCtx* lane_ctx = (LaneCtx*)params;
ThreadContext tctx = { ThreadContext tctx = {
@@ -1003,6 +1024,27 @@ fn void* gameLoop(void* params) {
} }
} }
// tick all the star systems (auctions)
StarSystem* sys = NULL;
Range1u64 sys_range = LaneRange(STAR_SYSTEM_COUNT);
for (u32 i = sys_range.min; i < sys_range.max; i++) {
sys = &state.map[i];
// tick the auction price
u32 grace_period_ends_at = sys->auction.started_at + (GOAL_GAME_LOOPS_PER_S * 3);
bool is_auction_still_running = sys->auction.finished_at == 0;
bool is_auction_grace_period_finished = state.frame > grace_period_ends_at;
if (is_auction_still_running && is_auction_grace_period_finished) {
f32 initial_price = COMMODITIES[sys->auction.type].price * AUCTION_PRICE_START_MULTIPLE;
f32 t_sec = ((f32)state.frame - (f32)grace_period_ends_at) / (f32)GOAL_GAME_LOOPS_PER_S;
// t is in minutes
f32 t = t_sec / 60;
f32 decay = -0.30 * t;
f32 floor_price = COMMODITIES[sys->auction.type].price * 0.9;
sys->auction.price = Max((initial_price * pow(EULERS_E, decay)), floor_price);
}
}
// 2. tick non-user entities // 2. tick non-user entities
if (state.all_accounts_ready) { if (state.all_accounts_ready) {
addSystemMessage((u8*)"NEW TURN: ticking all_accounts_ready"); addSystemMessage((u8*)"NEW TURN: ticking all_accounts_ready");
@@ -1012,7 +1054,7 @@ fn void* gameLoop(void* params) {
for (u32 i = sys_range.min; i < sys_range.max; i++) { for (u32 i = sys_range.min; i < sys_range.max; i++) {
sys = &state.map[i]; sys = &state.map[i];
sys->changed = true; sys->changed = true;
// consume and produce commodities // 1. consume and produce commodities
for (u32 ii = 0; ii < Commodity_Count; ii++) { for (u32 ii = 0; ii < Commodity_Count; ii++) {
for (u32 iii = 0; iii < MAX_PLANETS; iii++) { for (u32 iii = 0; iii < MAX_PLANETS; iii++) {
if (sys->planets[iii].type != PlanetTypeNull) { if (sys->planets[iii].type != PlanetTypeNull) {
@@ -1025,7 +1067,8 @@ fn void* gameLoop(void* params) {
} }
} }
} }
// potentially create a new passenger job
// 2. potentially create a new passenger job
u32 planet_divisor = ((MAX_PLANETS - starSystemPlanetCount(sys))+1); u32 planet_divisor = ((MAX_PLANETS - starSystemPlanetCount(sys))+1);
u32 max_passenger_job_count = 1 + ((MAX_PASSENGER_JOB_OFFERS-1) / planet_divisor); u32 max_passenger_job_count = 1 + ((MAX_PASSENGER_JOB_OFFERS-1) / planet_divisor);
for (u32 ii = 0; ii < max_passenger_job_count; ii++) { for (u32 ii = 0; ii < max_passenger_job_count; ii++) {
@@ -1041,6 +1084,13 @@ fn void* gameLoop(void* params) {
sys->offers[ii].offer = (u32)((f32)sys->offers[ii].offer * 1.05); sys->offers[ii].offer = (u32)((f32)sys->offers[ii].offer * 1.05);
} }
} }
// 3. reset the auction
MemoryZero(&sys->auction, sizeof(Auction));
sys->auction.started_at = state.frame;
sys->auction.type = AUCTION_COMMODITY_CUTOFF + (rand() % (Commodity_Count - AUCTION_COMMODITY_CUTOFF));
sys->auction.price = COMMODITIES[sys->auction.type].price * AUCTION_PRICE_START_MULTIPLE;
sys->auction.qty = COMMODITIES[sys->auction.type].consumption;
} }
Range1u64 ship_range = LaneRange(player_count); Range1u64 ship_range = LaneRange(player_count);
@@ -1420,6 +1470,12 @@ i32 main(i32 argc, ptr argv[]) {
// now, build out the commodity info and passenger jobs for the systems // now, build out the commodity info and passenger jobs for the systems
char sbuf[SBUFLEN] = {0}; char sbuf[SBUFLEN] = {0};
for (u32 i = 0; i < STAR_SYSTEM_COUNT; i++) { for (u32 i = 0; i < STAR_SYSTEM_COUNT; i++) {
// setup the auction
state.map[i].auction.started_at = state.frame;
state.map[i].auction.type = AUCTION_COMMODITY_CUTOFF + (rand() % (Commodity_Count - AUCTION_COMMODITY_CUTOFF));
state.map[i].auction.price = COMMODITIES[state.map[i].auction.type].price * AUCTION_PRICE_START_MULTIPLE;
state.map[i].auction.qty = COMMODITIES[state.map[i].auction.type].consumption;
// setup the planets
u32 planet_count = rand() % MAX_PLANETS + 1; u32 planet_count = rand() % MAX_PLANETS + 1;
for (u32 ii = 0; ii < planet_count; ii++) { for (u32 ii = 0; ii < planet_count; ii++) {
Planet* p = &state.map[i].planets[ii]; Planet* p = &state.map[i].planets[ii];
+26 -11
View File
@@ -7,14 +7,16 @@
///// #define some game-tunable constants ///// #define some game-tunable constants
#define STARTING_DOWN_PAYMENT (10000) #define STARTING_DOWN_PAYMENT (10000)
#define SHIP_DETAIL_COUNT (8) #define SHIP_DETAIL_COUNT (8)
#define STAR_SYSTEM_COUNT (16) #define STAR_SYSTEM_COUNT (6)
#define MAP_WIDTH (32) #define MAP_WIDTH (28)
#define MAP_HEIGHT (12) #define MAP_HEIGHT (12)
#define MAX_PLANETS (3) #define MAX_PLANETS (3)
#define MAX_PASSENGER_JOB_OFFERS (16) #define MAX_PASSENGER_JOB_OFFERS (12)
#define MAX_PASSENGER_JOB_PEOPLE (4) #define MAX_PASSENGER_JOB_PEOPLE (4)
#define MAX_PASSENGER_JOB_PRICE (10000) #define MAX_PASSENGER_JOB_PRICE (10000)
#define MAX_PASSENGER_BERTHS (8) #define MAX_PASSENGER_BERTHS (8)
#define AUCTION_COMMODITY_CUTOFF (6)
#define AUCTION_PRICE_START_MULTIPLE (12)
typedef enum ShipType { typedef enum ShipType {
ShipSparrow, ShipSparrow,
@@ -252,8 +254,8 @@ typedef struct PlayerShip {
u32 base_cost; u32 base_cost;
u32 remaining_mortgage; u32 remaining_mortgage;
f32 interest_rate; f32 interest_rate;
u32 cu_m_fuel; // we are just saying you can buy as much fuel as you want u32 cu_m_fuel;
u32 cu_m_o2; // we are just saying you can buy as much o2 as you want u32 cu_m_o2;
u32 credits; u32 credits;
u64 id; u64 id;
u32 commodities[Commodity_Count]; u32 commodities[Commodity_Count];
@@ -276,6 +278,14 @@ typedef struct Planet {
u32 production[Commodity_Count]; u32 production[Commodity_Count];
} Planet; } Planet;
typedef struct Auction {
CommodityType type;
u8 qty;
u32 price;
u32 started_at;
u32 finished_at;
} Auction;
typedef struct StarSystem { typedef struct StarSystem {
bool changed; bool changed;
u32 x; u32 x;
@@ -285,26 +295,27 @@ typedef struct StarSystem {
str name; str name;
Planet planets[MAX_PLANETS]; Planet planets[MAX_PLANETS];
PassengerJobOffer offers[MAX_PASSENGER_JOB_OFFERS]; PassengerJobOffer offers[MAX_PASSENGER_JOB_OFFERS];
Auction auction;
} StarSystem; } StarSystem;
global str STAR_NAMES[STAR_SYSTEM_COUNT] = { global str STAR_NAMES[STAR_SYSTEM_COUNT] = {
"Vega", "Vega",
"Aldebaran", "Aldebaran",
"Mining Colony 17", "Mining Colony 17",
"Antares", "Centauri Prime",
"Mintaka", "Mintaka",
"Barnard's Star", "Barnard's Star",
"Betelgeuse", /* "Betelgeuse",
"Antares",
"Tau Ceti", "Tau Ceti",
"Capella", "Capella",
"Castor", "Castor",
"Centauri Prime",
"Deneb", "Deneb",
"Epsilon Eridani", "Epsilon Eridani",
"Fomalhaut", "Fomalhaut",
"Gliese 581", "Gliese 581",
"Hadar", "Hadar",
/* "Izar", "Izar",
"Achernar", "Achernar",
"Kepler-186", "Kepler-186",
"Lacaille 8760", "Lacaille 8760",
@@ -352,6 +363,7 @@ typedef enum CommandType {
CommandSetDestination, CommandSetDestination,
CommandPayMortgage, CommandPayMortgage,
CommandAcceptPassengerJob, CommandAcceptPassengerJob,
CommandBuyAuction,
CommandType_Count, CommandType_Count,
} CommandType; } CommandType;
@@ -364,6 +376,7 @@ static const char* command_type_strings[CommandType_Count] = {
"SetDestination", "SetDestination",
"PayMortgage", "PayMortgage",
"AcceptPassengerJob", "AcceptPassengerJob",
"BuyAuction",
}; };
typedef enum Message { typedef enum Message {
@@ -382,6 +395,7 @@ typedef enum Message {
MessageJobAcceptResult, MessageJobAcceptResult,
MessageJobComplete, MessageJobComplete,
MessageNotAlive, MessageNotAlive,
MessageAuctionDetails,
Message_Count, Message_Count,
} Message; } Message;
@@ -399,7 +413,8 @@ static const char* MESSAGE_STRINGS[] = {
"PayoffResult", "PayoffResult",
"SystemPassengers", "SystemPassengers",
"JobAcceptResult", "JobAcceptResult",
"MessageNotAlive", "NotAlive",
"AuctionDetails",
}; };
///// shared helper functions ///// shared helper functions
@@ -445,7 +460,7 @@ fn f32 priceForCommodity(CommodityType type, u32 quantity, bool bid) {
} }
fn f32 calcInterestRate(u32 cost, u32 down_payment) { fn f32 calcInterestRate(u32 cost, u32 down_payment) {
return 3.0; return 4.0;
//f32 mortgage_rate = 10.0 *(1.0 - (3.0*(f32)down_payment / ((f32)cost/3.0))); //f32 mortgage_rate = 10.0 *(1.0 - (3.0*(f32)down_payment / ((f32)cost/3.0)));
//return mortgage_rate; //return mortgage_rate;
} }