moved code here. still needs to update gh

This commit is contained in:
klein panic
2025-02-24 15:18:44 -05:00
parent 9ca9f577a4
commit 2726432fe1
106 changed files with 4816 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
CC = gcc
CFLAGS = -Wall -Wextra -std=c11
# The final executable name
TARGET = terrarium
# Source files
SRCS = main.c game.c ascii_plant_data.c
# Object files
OBJS = $(SRCS:.c=.o)
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $(OBJS)
%.o: %.c
$(CC) $(CFLAGS) -c $<
clean:
rm -f $(TARGET) *.o
rm -f terrarium_save.dat

View File

@@ -0,0 +1,581 @@
/******************************************************************************
* ascii_plant_data.c
*
* Defines 40 unique ASCII plants, each 10 lines of up to 20 chars.
*
* For demonstration, theyll all be similar in shape, but each one will have
* some small text or symbol changes so that no two are exactly alike.
* You can replace these with more distinct art as desired.
******************************************************************************/
#include "ascii_plant_data.h"
/*
* We store all 40 plants ASCII lines in a 2D array of strings:
* PLANTS[40][10]
* where each string is up to 20 chars + null terminator.
*
* Each plant: 10 lines. Each line up to 20 chars wide.
*/
/*
Template shape (10 lines, 20 chars each):
" . "
" .. # "
" . ==+*- . - "
" :++*====.# "
" ==++-=#*+ "
" -%*=-#+#. "
" .-+**#. "
" .**+#. "
" .:=*++ "
" :--=. "
Well introduce slight variations for each plant to ensure uniqueness.
*/
static const char PLANT_ART[40][10][21] = {
/* 0: Snake Plant */
{
" . ",
" .. # ",
" . ==+*- . - ",
" :++*====.# ",
" S==++-=#*+ ",
" -%*=-#+#. ",
" .-+**#. S ",
" .**+#. ",
" .:=*++ S ",
" :--=. "
},
/* 1: Aloe Vera */
{
" . ",
" .. A ",
" . ==+*- . - ",
" :++*====.# A ",
" ==++-=#*+ ",
" A -%*=-#+#. ",
" .-+**#. ",
" .**+#. A ",
" .:=*++ ",
" :--=. "
},
/* 2: Rubber fig */
{
" (Rubber) ",
" .. # ",
" R . ==+*- . - ",
" :++*====.# ",
" ==++-=#*+ R ",
" -%*=-#+#. ",
" R .-+**#. ",
" .**+#. R ",
" .:=*++ ",
" :--=. R "
},
/* 3: Maidenhair fern */
{
" M ",
" fern .. # ",
" . ==+*- . - ",
" :++*====.# M ",
" ==++-=#*+ f ",
" -%*=-#+#. ",
" .-+**#. ",
" .**+#. fern ",
" .:=*++ M ",
" :--=. "
},
/* 4: Zebra Plant */
{
" Z e b r a ",
" .. Z ",
" . ==+*- . - ",
" :++*====.# Z ",
" Z ==++-=#*+ ",
" -%*=-#+#.Z ",
" .-+**#. ",
" .**+#. ",
" Z .:=*++ ",
" :--=. Z "
},
/* 5: Jade plant */
{
" J ",
" .. # ",
" . ==+*- . - ",
" :++*====.# J ",
" ==++-=#*+ ",
" -%*=-#+#. ",
" Jade.-+**#. ",
" .**+#. ",
" pl. :=*++ ",
" :--=. Jade "
},
/* 6: Yucca */
{
" Yucca ",
" .. Y ",
" . ==+*- . - Y ",
" :++*====.# ",
" ==++-=#*+ Y ",
" -%*=-#+#. ",
" .-+**#. Y ",
" .**+#. ",
" .:=*++ Y ",
" :--=. "
},
/* 7: Chain of Hearts */
{
" Chain <3 Hearts ",
" .. # ",
" . ==+*- . - <3 ",
" :++*====.# ",
" ==++-=#*+ <3 ",
" <3 -%*=-#+#. ",
" .-+**#. ",
" .**+#. <3 ",
" Chain.:=*++ ",
" :--=. Hearts"
},
/* 8: ZZ plant */
{
" ZZZ ",
" .. # Z ",
" . ==+*- . - ",
" Z :++*====.# ",
" ==++-=#*+ ",
" Z -%*=-#+#. ",
" .-+**#. Z ",
" ZZ .**+#. ",
" .:=*++ ",
" :--=. ZZ "
},
/* 9: Moonstones */
{
" (Moon) ",
" .. # ",
" . ==+*- . - ",
" :++*====.# M ",
" ==++-=#*+ ",
" oo -%*=-#+#. ",
" .-+**#. oo ",
" .**+#. ",
" stones:=*++ ",
" :--=. "
},
/* 10: Chinese Money plant */
{
" Chinese Money ",
" .. # $ ",
" . ==+*- . - ",
" :++*====.# $ ",
" ==++-=#*+ $$ ",
" $$ -%*=-#+#. ",
" .-+**#. ",
" $$ .**+#. ",
" .:=*++ $$ ",
" :--=. "
},
/* 11: String of pearls */
{
" String pearls ",
" ... # o o ",
" . ==+*- . - o ",
" :++*====.# o ",
" ==++-=#*+ o ",
" -%*=-#+#. o ",
" .-+**#. o ",
" .**+#. o ",
" .:=*++ o ",
" :--=. "
},
/* 12: Air plant */
{
" Air plant ",
" .. # ~ ",
" . ==+*- . - ~ ",
" :++*====.# ",
" A ==++-=#*+ ~ ",
" -%*=-#+#. ",
" .-+**#. ~ ",
" ~ .**+#. ",
" .:=*++ ",
" :--=. "
},
/* 13: African milk tree */
{
" African milk tree ",
" AMT .. # ",
" . ==+*- . - AMT ",
" :++*====.# ",
" ==++-=#*+ AMT ",
" -%*=-#+#. ",
" .-+**#. AMT ",
" .**+#. ",
" .:=*++ ",
" :--=. "
},
/* 14: pine bonsai */
{
" pine bonsai ",
" .. # ^ ",
" . ==+*- . - ^ ",
" :++*====.# ",
" ==++-=#*+ ^ ",
" -%*=-#+#. ",
" ^ .-+**#. ",
" .**+#. ^ ",
" ^ .:=*++ ",
" :--=. "
},
/* 15: Lotus */
{
" Lotus ",
" .. # * ",
" . ==+*- . - * ",
" :++*====.# ",
" ==++-=#*+ * ",
" -%*=-#+#. ",
" .-+**#. ",
" * .**+#. ",
" .:=*++ * ",
" :--=. "
},
/* 16: Heart fern */
{
" Heart fern <3 ",
" .. # ",
" . ==+*- . - <3 ",
" :++*====.# <3 ",
" ==++-=#*+ ",
" <3 -%*=-#+#. ",
" .-+**#. <3 ",
" .**+#. ",
" <3 .:=*++ ",
" :--=. "
},
/* 17: Corkscrew rush */
{
" Corkscrew rush ~@ ",
" .. # ~@ ",
" . ==+*- . - ~@ ",
" :++*====.# ",
" ~@==++-=#*+ ",
" -%*=-#+#. ~@ ",
" .-+**#. ",
" .**+#. ~@ ",
" .:=*++ ",
" :--=. ~@ "
},
/* 18: Weeping fig */
{
" Weeping fig ~ ",
" .. # ~ ",
" . ==+*- . - ",
" ~ :++*====.# ",
" ==++-=#*+ ~ ",
" -%*=-#+#. ",
" .-+**#. ~ ",
" .**+#. ",
" ~ .:=*++ ",
" :--=. "
},
/* 19: Corkscrew albuca */
{
" Corkscrew albuca ",
" Cka .. # ~@ ",
" . ==+*- . - ~@ ",
" :++*====.# ",
" ~@==++-=#*+ ",
" -%*=-#+#. ~@ ",
" .-+**#. ",
" ~@ .**+#. ",
" .:=*++ ",
" :--=. ~@ "
},
/* 20: Fiddle leaf fig */
{
" Fiddle leaf fig F ",
" .. # (leaf) ",
" ==+*- . - F ",
" :++*====.# ",
" ==++-=#*+ (leaf) ",
" -%*=-#+#. ",
" .-+**#. F ",
" .**+#. ",
" .:=*++ (leaf) ",
" :--=. "
},
/* 21: Mikado */
{
" Mikado | ",
" .. # | ",
" . ==+*- . - | ",
" :++*====.# ",
" ==++-=#*+ | ",
" -%*=-#+#. ",
" .-+**#. ",
" .**+#. | ",
" .:=*++ ",
" :--=. | "
},
/* 22: Kebab bush */
{
" Kebab bush ~o ",
" .. # o ",
" . ==+*- . - o ",
" :++*====.# ~ ",
" ~ ==++-=#*+ o ",
" -%*=-#+#. ",
" .-+**#. ~ o ",
" .**+#. ",
" o .:=*++ ~ ",
" :--=. o "
},
/* 23: Dwarf Papyrus */
{
" Dwarf Papyrus ^ ",
" .. # ^ ",
" . ==+*- . - ^ ",
" :++*====.# ",
" ==++-=#*+ ^ ",
" ^ -%*=-#+#. ",
" .-+**#. ^ ",
" .**+#. ",
" ^ .:=*++ ",
" :--=. "
},
/* 24: Hobbit Crassula */
{
" Hobbit Crassula (H) ",
" .. # ",
" . ==+*- . - (H) ",
" :++*====.# ",
" ==++-=#*+ ",
" -%*=-#+#. (H) ",
" .-+**#. ",
" (H) .**+#. ",
" .:=*++ ",
" :--=. (H) "
},
/* 25: Bunny ear cactus */
{
" Bunny ear cactus * ",
" .. # (ears) ",
" . ==+*- . - * ",
" :++*====.# (ears) ",
" ==++-=#*+ * ",
" -%*=-#+#. ",
" .-+**#. (ears) ",
" .**+#. * ",
" .:=*++ ",
" :--=. * "
},
/* 26: ghost echoversia */
{
" ghost echoversia # ",
" g.. # # ",
" . ==+*- . - # ",
" :++*====.# ",
" ==++-=#*+ # ",
" -%*=-#+#. # ",
" .-+**#. # ",
" .**+#. # ",
" .:=*++ # ",
" :--=. "
},
/* 27: chinese lantern */
{
" chinese lantern ",
" .. # (lantern) ",
" . ==+*- . - ",
" :++*====.# (lamp) ",
" ==++-=#*+ ",
" -%*=-#+#. (lamp) ",
" .-+**#. ",
" .**+#. (lantern) ",
" .:=*++ ",
" :--=. "
},
/* 28: ginseng ficus */
{
" ginseng ficus Gf ",
" .. # Gf ",
" . ==+*- . - Gf ",
" :++*====.# ",
" ==++-=#*+ ",
" -%*=-#+#. Gf ",
" .-+**#. ",
" .**+#. Gf ",
" .:=*++ ",
" :--=. "
},
/* 29: venus flytrap */
{
" venus flytrap V ",
" .. # x ",
" . ==+*- . - x ",
" :++*====.# ",
" V ==++-=#*+ x ",
" -%*=-#+#. ",
" .-+**#. x ",
" V .**+#. ",
" .:=*++ ",
" :--=. x "
},
/* 30: Flamingo flower */
{
" Flamingo flower F ",
" .. # (fl) ",
" . ==+*- . - F ",
" :++*====.# ",
" (fl)==++-=#*+ F ",
" -%*=-#+#. ",
" .-+**#. (fl) ",
" .**+#. ",
" F .:=*++ ",
" :--=. (fl) "
},
/* 31: Japanese maple bonsai */
{
" Japanese maple bns ",
" .. # Jmb ",
" . ==+*- . - ",
" :++*====.# Jmb ",
" ==++-=#*+ ",
" -%*=-#+#. ",
" Jmb .-+**#. ",
" .**+#. ",
" .:=*++ Jmb ",
" :--=. "
},
/* 32: Fshbone cactus */
{
" Fshbone cactus ~^ ",
" .. # ~^ ",
" . ==+*- . - ~^ ",
" :++*====.# ",
" ==++-=#*+ ~^ ",
" -%*=-#+#. ",
" .-+**#. ~^ ",
" .**+#. ",
" .:=*++ ~^ ",
" :--=. "
},
/* 33: Paddle plant */
{
" Paddle plant ",
" .. # == ",
" . ==+*- . - == ",
" :++*====.# ",
" ==++-=#*+ == ",
" -%*=-#+#. ",
" .-+**#. ",
" == .**+#. ",
" .:=*++ == ",
" :--=. "
},
/* 34: Donkey's tail */
{
" Donkey's tail ~ ",
" .. # ~ ",
" . ==+*- . - ~ ",
" :++*====.# ",
" ~ ==++-=#*+ ",
" -%*=-#+#. ~ ",
" .-+**#. ",
" ~ .**+#. ",
" .:=*++ ",
" :--=. ~ "
},
/* 35: Common ivy */
{
" Common ivy ivy ",
" .. # iv ",
" . ==+*- . - iv ",
" iv:++*====.# ",
" ==++-=#*+ ",
" -%*=-#+#. iv ",
" .-+**#. ",
" .**+#. ",
" iv .:=*++ ",
" :--=. ivy "
},
/* 36: Chinese Crassula */
{
" Chinese Crassula C ",
" .. # (Cr) ",
" . ==+*- . - (Cr) ",
" :++*====.# ",
" ==++-=#*+ ",
" -%*=-#+#. (Cr) ",
" .-+**#. ",
" .**+#. (Cr) ",
" .:=*++ ",
" :--=. (Cr) "
},
/* 37: Blue Chalksticks */
{
" Blue Chalksticks B ",
" .. # Bc ",
" . ==+*- . - Bc ",
" :++*====.# ",
" ==++-=#*+ Bc ",
" -%*=-#+#. ",
" .-+**#. Bc ",
" .**+#. ",
" .:=*++ Bc ",
" :--=. "
},
/* 38: Angel's tears */
{
" Angel's tears @A ",
" .. # tears ",
" . ==+*- . - @A ",
" :++*====.# ",
" ==++-=#*+ ",
" -%*=-#+#. @A ",
" .-+**#. ",
" .**+#. tears ",
" .:=*++ @A ",
" :--=. "
},
/* 39: White clover */
{
" White clover (wc) ",
" .. # (wc) ",
" . ==+*- . - ",
" :++*====.# (wc) ",
" ==++-=#*+ ",
" -%*=-#+#. ",
" .-+**#. (wc) ",
" .**+#. ",
" .:=*++ (wc)",
" :--=. "
}
};
/******************************************************************************
* getPlantArt
*
* Returns the (line)-th line of ASCII art for plantIndex
******************************************************************************/
const char* getPlantArt(int plantIndex, int line) {
if(plantIndex < 0 || plantIndex >= 40) {
/* Safety: return empty string if out of range */
return " ";
}
if(line < 0 || line >= 10) {
return " ";
}
return PLANT_ART[plantIndex][line];
}

View File

@@ -0,0 +1,14 @@
/******************************************************************************
* ascii_plant_data.h
*
* Exposes function(s) to retrieve the 10-line ASCII art for each of the 40 plants.
******************************************************************************/
#ifndef ASCII_PLANT_DATA_H
#define ASCII_PLANT_DATA_H
/* Returns the line-th line (0..9) of ASCII art for plant index (0..39). */
const char* getPlantArt(int plantIndex, int line);
#endif

Binary file not shown.

View File

@@ -0,0 +1,259 @@
/******************************************************************************
* game.c
*
* Implements core game logic: initialization, update, rendering, input, etc.
******************************************************************************/
/*
* game.c - Contains the implementation of the Terrarium game logic
*/
#define _POSIX_C_SOURCE 200809L
#include "game.h"
/* System / standard headers */
#include <stdio.h>
#include <stdlib.h> /* for system() */
#include <time.h> /* for clock_gettime, etc. */
#include <unistd.h> /* for usleep() */
#include <fcntl.h>
#include <termios.h>
#include <string.h> /* for strncpy() */
/******************************************************************************
* Terminal-handling variables/functions (non-blocking input, etc.)
*****************************************************************************/
static struct termios old, current;
/* Initialize new terminal i/o settings */
void initTermios(int echo) {
tcgetattr(0, &old);
current = old;
current.c_lflag &= ~ICANON; /* disable buffered i/o */
if (echo) {
current.c_lflag |= ECHO;
} else {
current.c_lflag &= ~ECHO;
}
tcsetattr(0, TCSANOW, &current);
}
/* Restore old terminal i/o settings */
void resetTermios(void) {
tcsetattr(0, TCSANOW, &old);
}
/* Check if a key has been pressed (non-blocking) */
int kbhit(void) {
struct termios oldt, newt;
int ch;
int oldf;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
fcntl(STDIN_FILENO, F_SETFL, oldf);
if(ch != EOF) {
ungetc(ch, stdin);
return 1;
}
return 0;
}
/* Read one character (like getch in Windows) */
char getch(void) {
return getchar();
}
/* Clear the screen (Unix-like) */
void clearScreen(void) {
system("clear");
}
/******************************************************************************
* Elapsed Time (we store the old time in a static variable)
*****************************************************************************/
double getElapsedTime(void) {
static struct timespec lastTime = {0, 0};
struct timespec now;
/* If first call, initialize lastTime */
if (lastTime.tv_sec == 0 && lastTime.tv_nsec == 0) {
clock_gettime(CLOCK_MONOTONIC, &now);
lastTime = now;
return 0.0;
}
/* current time */
clock_gettime(CLOCK_MONOTONIC, &now);
double elapsed = (now.tv_sec - lastTime.tv_sec) +
(now.tv_nsec - lastTime.tv_nsec) / 1000000000.0;
lastTime = now;
return elapsed;
}
/******************************************************************************
* Game Logic & Rendering
*****************************************************************************/
/* Forward-declarations (private to this file) */
static double calcTotalProduction(const GameState *gs);
static void buyPlant(GameState *gs);
/* initGame: brand-new game (if load fails or for fresh start) */
void initGame(GameState *gs) {
gs->oxygen = 0.0;
gs->plantCost = BASE_PLANT_COST;
gs->plantCount = 0;
/* The 40 plants (names). Adjust as you wish. */
const char* plantNames[MAX_PLANTS] = {
"Snake Plant", "Aloe Vera", "Rubber fig", "Maidenhair fern", "Zebra Plant",
"Jade plant", "Yucca", "Chain of Hearts", "ZZ plant", "Moonstones",
"Chinese Money plant", "String of pearls.", "Air plant", "African milk tree",
"pine bonsai", "Lotus", "Heart fern", "Corkscrew rush", "Weeping fig", "Corkscrew albuca",
"Fiddle leaf fig", "Mikado", "Kebab bush", "Dwarf Papyrus", "Hobbit Crassula",
"Bunny ear cactus", "ghost echoversia", "chinese lantern", "ginseng ficus", "venus flytrap",
"Flamingo flower", "Japanese maple bonsai", "Fshbone cactus", "Paddle plant",
"Donkey's tail", "Common ivy", "Chinese Crassula", "Blue Chalksticks", "Angel's tears", "White clover"
};
for(int i = 0; i < MAX_PLANTS; i++) {
strncpy(gs->plants[i].name, plantNames[i], sizeof(gs->plants[i].name) - 1);
gs->plants[i].name[sizeof(gs->plants[i].name)-1] = '\0';
gs->plants[i].active = 0;
gs->plants[i].productionRate = 0.0;
}
/* Start with the first plant active, if desired */
gs->plants[0].active = 1;
gs->plants[0].productionRate = BASE_OXYGEN_PER_SECOND;
gs->plantCount = 1;
}
/* loadGame: tries to read from a file. If it fails, calls initGame(). */
void loadGame(GameState *gs) {
FILE *f = fopen("terrarium_save.dat", "rb");
if(!f) {
initGame(gs);
return;
}
if(fread(gs, sizeof(GameState), 1, f) != 1) {
fclose(f);
initGame(gs);
return;
}
fclose(f);
/* Basic sanity check */
if(gs->plantCount < 1) {
initGame(gs);
}
}
/* saveGame: writes the current game state to a file. */
void saveGame(const GameState *gs) {
FILE *f = fopen("terrarium_save.dat", "wb");
if(!f) return;
fwrite(gs, sizeof(GameState), 1, f);
fclose(f);
}
/* updateGame: called each loop to add oxygen from production rate. */
void updateGame(GameState *gs, double elapsedSeconds) {
double totalProd = calcTotalProduction(gs);
gs->oxygen += totalProd * elapsedSeconds;
}
/* renderGame: draws shelves + plants in top 3/4, info in bottom 1/4. */
void renderGame(const GameState *gs) {
clearScreen();
printf("========================================\n");
printf(" T E R R A R I U M \n");
printf("========================================\n\n");
/*
* We have 5 shelves (SHELF_COUNT=5).
* Each shelf displays 8 plants => total 40 plants (PLANTS_PER_SHELF=8).
*/
int plantIdx = 0;
for(int shelf = 0; shelf < SHELF_COUNT; shelf++) {
for(int slot = 0; slot < PLANTS_PER_SHELF; slot++) {
if(plantIdx < MAX_PLANTS && gs->plants[plantIdx].active) {
/* Print the plant name (20 chars padded, e.g.) */
printf("[%2d] %-20s | ", plantIdx+1, gs->plants[plantIdx].name);
} else {
printf("[ ] %-20s | ", "(empty)");
}
plantIdx++;
}
printf("\n--------------------------------------------------------\n");
}
/* BOTTOM 1/4: info, oxygen, etc. */
printf("\nOxygen: %.2f O2\n", gs->oxygen);
printf("Total Production: %.3f O2/s\n", calcTotalProduction(gs));
printf("Next Plant Cost: %.2f O2\n\n", gs->plantCost);
printf("[SPACE] Tap the first plant for +%.1f O2 (if active)\n", TAP_BONUS_OXYGEN);
printf("[b] Buy a new plant\n");
printf("[q] Quit\n\n");
}
/* handleInput: user keystrokes (tap or buy, etc.) */
void handleInput(GameState *gs, char input) {
switch(input) {
case ' ':
/* Tap only works on the first plant if active */
if(gs->plants[0].active) {
gs->oxygen += TAP_BONUS_OXYGEN;
}
break;
case 'b':
case 'B':
buyPlant(gs);
break;
default:
break;
}
}
/* Internal helper: calculates sum of production rates */
static double calcTotalProduction(const GameState *gs) {
double total = 0.0;
for(int i = 0; i < MAX_PLANTS; i++) {
if(gs->plants[i].active) {
total += gs->plants[i].productionRate;
}
}
return total;
}
/* Internal helper: buy the next plant if we can afford it */
static void buyPlant(GameState *gs) {
if(gs->plantCount >= MAX_PLANTS) {
return; /* can't buy more than 40 */
}
if(gs->oxygen >= gs->plantCost) {
gs->oxygen -= gs->plantCost;
/* activate next plant in line */
int index = gs->plantCount;
gs->plants[index].active = 1;
gs->plants[index].productionRate = BASE_OXYGEN_PER_SECOND; /* or custom logic */
gs->plantCount++;
/* Increase cost for next time */
gs->plantCost *= COST_MULTIPLIER;
}
}

View File

@@ -0,0 +1,67 @@
/******************************************************************************
* game.h
*
* Exposes the core game logic and data.
******************************************************************************/
#ifndef GAME_H
#define GAME_H
/*
* This header declares functions/data for the Terrarium game.
* Include it from both main.c and game.c.
*/
#define MAX_PLANTS 40
#define SHELF_COUNT 5 /* We'll display 5 shelves total */
#define PLANTS_PER_SHELF 8 /* 8 plants per shelf => 40 plants total */
#define GAME_TICK_INTERVAL_MS 200 /* Loop tick (ms) */
#define BASE_OXYGEN_PER_SECOND 0.1
#define TAP_BONUS_OXYGEN 1.0
#define BASE_PLANT_COST 10.0
#define COST_MULTIPLIER 1.2
/*
* Data Structures
*/
typedef struct {
char name[64]; /* e.g. "Snake Plant" */
int active; /* 1 if purchased/visible, 0 if not */
double productionRate; /* O2 per second contributed by this plant */
} Plant;
/* Main Game State container */
typedef struct {
double oxygen; /* Current total oxygen currency */
double plantCost; /* Cost to buy the next plant */
Plant plants[MAX_PLANTS]; /* All 40 plants */
int plantCount; /* How many plants are active */
} GameState;
/*
* Function Prototypes
*/
/* Game lifecycle */
void initGame(GameState *gs);
void loadGame(GameState *gs);
void saveGame(const GameState *gs);
/* Game logic */
void updateGame(GameState *gs, double elapsedSeconds);
void renderGame(const GameState *gs);
void handleInput(GameState *gs, char input);
/* Timing: returns elapsed seconds since last call */
double getElapsedTime(void);
/* Terminal / I/O helpers */
void initTermios(int echo);
void resetTermios(void);
int kbhit(void);
char getch(void);
void clearScreen(void);
#endif /* GAME_H */

Binary file not shown.

View File

@@ -0,0 +1,57 @@
/*
* main.c - Entry point of the Terrarium program
*/
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h> /* for system() */
#include <unistd.h> /* for usleep() */
#include "game.h"
int main(void) {
GameState gs;
/* Load a previous save or start fresh. */
loadGame(&gs);
/* Setup terminal for non-blocking input, no echo */
initTermios(0);
int running = 1;
while (running) {
/* Time since the last loop in seconds */
double elapsedSeconds = getElapsedTime();
/* Update game logic (oxygen accumulation, etc.) */
updateGame(&gs, elapsedSeconds);
/* Render the shelves and the info area */
renderGame(&gs);
/* Check user input (non-blocking) */
if (kbhit()) {
char c = getch();
if (c == 'q' || c == 'Q') {
running = 0; /* quit */
} else {
handleInput(&gs, c);
}
}
/* Control frame/tick rate (~5 times a second) */
usleep(GAME_TICK_INTERVAL_MS * 1000);
/* Auto-save each loop (optional) */
saveGame(&gs);
}
/* Cleanup */
resetTermios();
clearScreen();
printf("Thanks for playing Terrarium!\n");
return 0;
}

Binary file not shown.

BIN
terrarium_bk/terrarium/terrarium Executable file

Binary file not shown.

View File

@@ -0,0 +1,388 @@
/******************************************************************************
* Terrarium - An ASCII-based terminal idle game in C
*
* - Top 3/4 of the screen: shelves to visualize plants with their names.
* - Bottom 1/4 of the screen: oxygen total, controls, etc.
* - Tapping (space bar) works only for the first plant to give bonus oxygen.
* - Press 'b' to buy new plants, which appear on the next available shelf.
* - Press 'q' to quit.
* - Auto-saves and auto-loads from "terrarium_save.dat".
*
* Compile (Linux/Unix):
* gcc terrarium.c -o terrarium -lm
*
* Run:
* ./terrarium
*
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <string.h>
/******************************************************************************
* Platform/Terminal-Handling Code (Non-Blocking Input, Clear Screen, etc.)
*****************************************************************************/
static struct termios old, current;
/* Initialize new terminal i/o settings */
void initTermios(int echo) {
tcgetattr(0, &old); /* grab old terminal i/o settings */
current = old;
current.c_lflag &= ~ICANON; /* disable buffered i/o */
if (echo) {
current.c_lflag |= ECHO; /* set echo mode */
} else {
current.c_lflag &= ~ECHO; /* set no echo mode */
}
tcsetattr(0, TCSANOW, &current); /* apply the new settings */
}
/* Restore old terminal i/o settings */
void resetTermios(void) {
tcsetattr(0, TCSANOW, &old);
}
/* Check if a key has been pressed (non-blocking) */
int kbhit(void) {
struct termios oldt, newt;
int ch;
int oldf;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
fcntl(STDIN_FILENO, F_SETFL, oldf);
if(ch != EOF) {
ungetc(ch, stdin);
return 1;
}
return 0;
}
/* Read one character without echo */
char getch() {
return getchar();
}
/* Clear the screen (Unix-like) */
void clearScreen() {
system("clear");
}
/******************************************************************************
* Game Constants
*****************************************************************************/
#define MAX_PLANTS 10 /* Max number of plants we can display */
#define SHELF_COUNT 5 /* How many shelves to visualize */
#define GAME_TICK_INTERVAL_MS 200 /* Game loop tick interval in ms */
#define BASE_OXYGEN_PER_SECOND 0.1
#define TAP_BONUS_OXYGEN 1.0
#define BASE_PLANT_COST 10.0
#define COST_MULTIPLIER 1.2
/* Save file name */
#define SAVE_FILE "terrarium_save.dat"
/******************************************************************************
* Data Structures
*****************************************************************************/
/* Each Plant has a name and a productionRate. We could expand this in the future. */
typedef struct {
char name[32];
int active; /* 1 if this plant is purchased and displayed, 0 otherwise */
double productionRate; /* Oxygen production contribution from this plant */
} Plant;
typedef struct {
double oxygen; /* Current total oxygen */
double plantCost; /* Cost to buy the next plant */
Plant plants[MAX_PLANTS]; /* Our plants (0th is the "first" plant that can be tapped) */
int plantCount; /* How many plants are active/purchased */
} GameState;
/******************************************************************************
* Function Declarations
*****************************************************************************/
void initGame(GameState *gs);
void loadGame(GameState *gs);
void saveGame(const GameState *gs);
void updateGame(GameState *gs, double elapsedSeconds);
void renderGame(const GameState *gs);
void handleInput(GameState *gs, char input);
void buyPlant(GameState *gs);
double calcTotalProduction(const GameState *gs);
double getElapsedTime(struct timespec *lastTick);
/******************************************************************************
* Main Function
*****************************************************************************/
int main(void) {
GameState gs;
/* Attempt to load a previous save. If no save file is found, initialize. */
loadGame(&gs);
/* Terminal setup for non-blocking input */
initTermios(0); // 0 = no echo
/* Timing for the game loop */
struct timespec lastTick;
clock_gettime(CLOCK_MONOTONIC, &lastTick);
int running = 1;
while(running) {
/* 1) Compute elapsed time */
double elapsedSeconds = getElapsedTime(&lastTick);
/* 2) Update game logic */
updateGame(&gs, elapsedSeconds);
/* 3) Render the game */
clearScreen();
renderGame(&gs);
/* 4) Check user input (non-blocking) */
if(kbhit()) {
char input = getch();
if(input == 'q' || input == 'Q') {
running = 0;
} else {
handleInput(&gs, input);
}
}
/* 5) Sleep to control game speed */
usleep(GAME_TICK_INTERVAL_MS * 1000);
/* 6) Auto-save occasionally (for example, every loop).
* You could also do it less frequently to avoid disk writes. */
saveGame(&gs);
}
/* Cleanup */
resetTermios();
clearScreen();
printf("Thanks for playing Terrarium!\n");
return 0;
}
/******************************************************************************
* Initialize Game State (default if no save is found)
*****************************************************************************/
void initGame(GameState *gs) {
gs->oxygen = 0.0;
gs->plantCost = BASE_PLANT_COST;
/* Initialize all plants as inactive, except the first one. */
for(int i = 0; i < MAX_PLANTS; i++){
snprintf(gs->plants[i].name, sizeof(gs->plants[i].name), "Plant #%d", i+1);
gs->plants[i].active = 0;
gs->plants[i].productionRate = 0.0;
}
/* Start with one plant active (the first one) */
gs->plants[0].active = 1;
gs->plants[0].productionRate = BASE_OXYGEN_PER_SECOND;
gs->plantCount = 1;
}
/******************************************************************************
* Load Game State from SAVE_FILE (if it exists). Otherwise, init fresh.
*****************************************************************************/
void loadGame(GameState *gs) {
FILE *f = fopen(SAVE_FILE, "rb");
if(!f) {
/* No save found, start a new game */
initGame(gs);
return;
}
/* Read the saved struct. For a real game, you'd want version checks, etc. */
fread(gs, sizeof(GameState), 1, f);
fclose(f);
/* Sanity check that at least one plant is active. If not, re-init. */
if(gs->plantCount < 1) {
initGame(gs);
}
}
/******************************************************************************
* Save Game State to SAVE_FILE
*****************************************************************************/
void saveGame(const GameState *gs) {
FILE *f = fopen(SAVE_FILE, "wb");
if(!f) {
return; /* if we can't open it, just skip saving */
}
fwrite(gs, sizeof(GameState), 1, f);
fclose(f);
}
/******************************************************************************
* Update Game State
* - Accumulate oxygen based on the total production rate * elapsed time
*****************************************************************************/
void updateGame(GameState *gs, double elapsedSeconds) {
double totalProd = calcTotalProduction(gs);
gs->oxygen += totalProd * elapsedSeconds;
}
/******************************************************************************
* Render/Draw the game to the terminal
*
* Well reserve the top 3/4 of the screen for shelves (with ASCII pots/plants),
* and the bottom 1/4 for stats, cost, etc.
*
* For simplicity, let's assume a typical 24- or 25-line terminal height.
* We'll do 15-16 lines for shelves, then ~8-9 lines for the bottom portion.
*
*****************************************************************************/
void renderGame(const GameState *gs) {
/* We'll define how many lines we want for shelves, ignoring actual
terminal height detection for simplicity. Adjust as you like. */
const int shelfLines = 15;
/* Each shelf is represented by two lines:
1) The plant pot / name
2) The "--------" shelf line
We'll show up to SHELF_COUNT shelves from top to bottom.
*/
int shelfIndex = 0;
int plantIndex = 0;
/* Print the top banner */
printf("========================================\n");
printf(" T E R R A R I U M \n");
printf("========================================\n\n");
/* We have SHELF_COUNT shelves to display. Lets map each shelf to a plant index if available. */
for(shelfIndex = 0; shelfIndex < SHELF_COUNT; shelfIndex++) {
/* If there's a plant for this shelfIndex, show it.
We'll just go in the order plants are purchased (0,1,2,...)
and display them as long as they are active, up to the number of shelves. */
if(plantIndex < MAX_PLANTS && gs->plants[plantIndex].active) {
/* Show an ASCII pot and the plants name */
printf(" [%s]\n", gs->plants[plantIndex].name);
printf(" ---------------------- (shelf #%d)\n", shelfIndex+1);
plantIndex++;
} else {
/* Empty shelf */
printf(" [empty]\n");
printf(" ---------------------- (shelf #%d)\n", shelfIndex+1);
}
}
/* Now we display the bottom portion (the UI area). We'll show the last 1/4 lines. */
/* Let's just put a few blank lines to ensure separation. */
printf("\n");
/* Bottom section with info and controls */
printf("Oxygen: %.2f O2\n", gs->oxygen);
printf("Total Production Rate: %.3f O2/sec\n", calcTotalProduction(gs));
printf("Next Plant Cost: %.2f O2\n\n", gs->plantCost);
printf("[SPACE] Tap the first plant for +%.1f O2 (only if the first plant exists)\n", TAP_BONUS_OXYGEN);
printf("[b] Buy a new plant\n");
printf("[q] Quit\n");
printf("\n");
}
/******************************************************************************
* Handle User Input
*****************************************************************************/
void handleInput(GameState *gs, char input) {
switch(input) {
case ' ':
/* Space bar: Tapping only affects the first plant if active. */
if(gs->plants[0].active) {
gs->oxygen += TAP_BONUS_OXYGEN;
}
break;
case 'b':
case 'B':
/* Buy a new plant if possible */
buyPlant(gs);
break;
default:
/* ignore other keys */
break;
}
}
/******************************************************************************
* Buy a new plant (if we can afford it and haven't reached MAX_PLANTS)
*****************************************************************************/
void buyPlant(GameState *gs) {
if(gs->plantCount >= MAX_PLANTS) {
/* No more plants can be bought */
return;
}
/* Check if we have enough oxygen for the next plant */
if(gs->oxygen >= gs->plantCost) {
gs->oxygen -= gs->plantCost;
/* Activate the next plant and set its production rate */
int index = gs->plantCount; /* next available slot */
gs->plants[index].active = 1;
/* For demonstration: each new plant has the same base production,
or you could do something fancier. */
gs->plants[index].productionRate = BASE_OXYGEN_PER_SECOND;
gs->plantCount++;
/* Increase the cost for the next plant */
gs->plantCost *= COST_MULTIPLIER;
}
}
/******************************************************************************
* Calculate the Total Production from all active plants
*****************************************************************************/
double calcTotalProduction(const GameState *gs) {
double total = 0.0;
for(int i = 0; i < MAX_PLANTS; i++) {
if(gs->plants[i].active) {
total += gs->plants[i].productionRate;
}
}
return total;
}
/******************************************************************************
* Get elapsed time in seconds since the last tick
*****************************************************************************/
double getElapsedTime(struct timespec *lastTick) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
double elapsed = (now.tv_sec - lastTick->tv_sec)
+ (now.tv_nsec - lastTick->tv_nsec) / 1000000000.0;
*lastTick = now;
return elapsed;
}

Binary file not shown.