Created
May 9, 2025 15:08
-
-
Save layou233/944a41e084700e922349e6049905190e to your computer and use it in GitHub Desktop.
Practice real estate system in C++
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright (C) 2025 Launium, all rights reserved. | |
* This is a practice project and comes with ABSOLUTELY | |
* NO WARRANTY, full of unsafe function calls, unverified code | |
* and your computer may end up nuked. | |
* DO NOT COPY ANY PART OF THE CODE or you may face | |
* attacks such as buffer overflow, code injection, etc. | |
* Anyway, use at your own risk. :) | |
* @date May 9th, 2025 | |
*/ | |
#include <algorithm> | |
#include <iostream> | |
#include <string> | |
#include <fstream> | |
#include <sstream> | |
#include <vector> | |
#include <iomanip> | |
#include <cstdint> | |
#include <cstdlib> | |
#ifdef _WIN32 | |
#define clear_screen() system("cls") | |
#else // assume ANSI compatible terminal | |
#define clear_screen() std::cout << "\e[1;1H\e[2J" | |
#endif | |
using namespace std; | |
constexpr int width_id = 6; | |
constexpr int width_area = 10; | |
constexpr int width_unit_price = 15; | |
constexpr int width_total_price = 15; | |
constexpr int width_address = 40; | |
constexpr int width_owner = 15; | |
constexpr int width_contact = 20; | |
constexpr int width_date = 12; | |
string truncate(const string &s, size_t max_length) | |
{ | |
return s.length() >= max_length - 5 ? s.substr(0, max_length - 5) + "..." : s; | |
} | |
struct Resource | |
{ | |
struct StringTable | |
{ | |
string language_name; | |
string you_have_chosen; | |
string as_display_language; | |
string save_is_not_found_and_creating; | |
string available_commands; | |
string prompt_continue_from_save; | |
string prompt_continue_with_enter; | |
string prompt_enter_id; | |
string prompt_enter_to_return_to_main; | |
string prompt_zero_to_generate_an_id; | |
string unknown_command; | |
string no_id_found; | |
string saved_successfully; | |
string property_record_removed; | |
string new_property_record_added; | |
string unsaved_exit_warning; | |
string exit_thanks; | |
string unknown_contact_method; | |
string contact_method_phone; | |
string contact_method_email; | |
string main_menu_view_database; | |
string main_menu_get_by_id; | |
string main_menu_add_new; | |
string main_menu_save_to_file; | |
string main_menu_exit; | |
string database_area; | |
string database_unit_price; | |
string database_total_price; | |
string database_address; | |
string database_owner; | |
string database_contact; | |
string database_purchase_date; | |
string info_menu_commands_for; | |
string info_menu_delete; | |
string info_menu_back; | |
} string; | |
} R; | |
// uint8 causes problems for cin input since | |
// cin would concider it as character type | |
// and store ASCII value of the input into it | |
// Another C shit :) | |
#define command_t uint_fast16_t | |
#define LANG_EN 1 | |
#define LANG_CN 2 | |
void load_translation(command_t lang) | |
{ | |
switch (lang) | |
{ | |
case LANG_EN: | |
R.string.language_name = "English - en_US"; | |
R.string.you_have_chosen = "You have chosen "; | |
R.string.as_display_language = " as the display language."; | |
R.string.save_is_not_found_and_creating = "No save is found. Creating new database..."; | |
R.string.available_commands = "Available Commands"; | |
R.string.prompt_continue_from_save = "Do you want to continue from a previous save? [Y/n]: "; | |
R.string.prompt_continue_with_enter = "Press enter to continue..."; | |
R.string.prompt_enter_id = "Enter the ID: "; | |
R.string.prompt_enter_to_return_to_main = "Press enter to return to main menu..."; | |
R.string.prompt_zero_to_generate_an_id = "0 to generate an ID"; | |
R.string.no_id_found = "No house found with ID: "; | |
R.string.saved_successfully = "Database saved."; | |
R.string.property_record_removed = "Removed property record."; | |
R.string.new_property_record_added = "Added new property record to the database."; | |
R.string.unsaved_exit_warning = "You have unsaved changes. Exit anyway? [y/N]: "; | |
R.string.exit_thanks = "Thanks for using the Real Estate System. Goodbye!"; | |
R.string.unknown_contact_method = "Unknown contact method"; | |
R.string.contact_method_phone = "Phone"; | |
R.string.contact_method_email = "Email"; | |
R.string.main_menu_view_database = "View database"; | |
R.string.main_menu_get_by_id = "Get real estate information by ID"; | |
R.string.main_menu_add_new = "Add new real estate information"; | |
R.string.main_menu_save_to_file = "Save the database to file"; | |
R.string.main_menu_exit = "Exit program"; | |
R.string.info_menu_commands_for = "Available commands for this property record:"; | |
R.string.info_menu_delete = "Delete"; | |
R.string.info_menu_back = "Back to main menu"; | |
R.string.database_area = "Area/m²"; | |
R.string.database_unit_price = "Unit Price/m²"; | |
R.string.database_total_price = "Total Price"; | |
R.string.database_address = "Address"; | |
R.string.database_owner = "Owner"; | |
R.string.database_contact = "Contact"; | |
R.string.database_purchase_date = "Purchase Date"; | |
R.string.unknown_command = "Unknown command: "; | |
break; | |
case LANG_CN: | |
R.string.language_name = "简体中文(中国) - zh_CN"; | |
R.string.you_have_chosen = "您已选择 "; | |
R.string.as_display_language = " 作为显示语言。"; | |
R.string.save_is_not_found_and_creating = "未找到存档。正在创建新的数据库..."; | |
R.string.available_commands = "可用的命令"; | |
R.string.prompt_continue_from_save = "您想从先前的存档中继续吗?[Y/n]: "; | |
R.string.prompt_continue_with_enter = "请按回车键以继续..."; | |
R.string.prompt_enter_id = "输入 ID: "; | |
R.string.prompt_enter_to_return_to_main = "请按回车键以回到主菜单..."; | |
R.string.prompt_zero_to_generate_an_id = "0 以生成一个 ID"; | |
R.string.no_id_found = "没找到对应 ID 的房地产:"; | |
R.string.saved_successfully = "数据库已保存。"; | |
R.string.property_record_removed = "已删除财产记录。"; | |
R.string.new_property_record_added = "已将新财产记录添加到数据库。"; | |
R.string.unsaved_exit_warning = "您有未保存的变更。仍然退出?[y/N]: "; | |
R.string.exit_thanks = "感谢使用房地产系统。再见!"; | |
R.string.unknown_contact_method = "未知联系方式"; | |
R.string.contact_method_phone = "电话"; | |
R.string.contact_method_email = "电子邮件"; | |
R.string.main_menu_view_database = "查看数据库"; | |
R.string.main_menu_get_by_id = "获取指定 ID 房地产信息"; | |
R.string.main_menu_add_new = "添加新的房地产信息"; | |
R.string.main_menu_save_to_file = "保存数据库到文件"; | |
R.string.main_menu_exit = "退出程序"; | |
R.string.info_menu_commands_for = "针对此财产记录的可用命令:"; | |
R.string.info_menu_delete = "删除"; | |
R.string.info_menu_back = "返回主菜单"; | |
R.string.database_area = "面积/m²"; | |
R.string.database_unit_price = "单价/m²"; | |
R.string.database_total_price = "总价"; | |
R.string.database_address = "地址"; | |
R.string.database_owner = "业主"; | |
R.string.database_contact = "联系方式"; | |
R.string.database_purchase_date = "购买日期"; | |
R.string.unknown_command = "未知的命令:"; | |
break; | |
default: | |
cout << "Unknown language code: " << lang << endl; | |
exit(1); | |
} | |
} | |
void pause() | |
{ | |
cout << R.string.prompt_continue_with_enter; | |
cin.ignore(); | |
cin.get(); | |
cout << endl; | |
} | |
void print_banner() | |
{ | |
cout << "______ _ _____ _ _ _____ _" << endl | |
<< "| ___ \\ | | | ___| | | | | / ___| | |" << endl | |
<< "| |_/ /___ __ _| | | |__ ___| |_ __ _| |_ ___ \\ `--. _ _ ___| |_ ___ _ __ ___" << endl | |
<< "| // _ \\/ _` | | | __/ __| __/ _` | __/ _ \\ `--. \\ | | / __| __/ _ \\ '_ ` _ \\" << endl | |
<< "| |\\ \\ __/ (_| | | | |__\\__ \\ || (_| | || __/ /\\__/ / |_| \\__ \\ || __/ | | | | |" << endl | |
<< "\\_| \\_\\___|\\__,_|_| \\____/___/\\__\\__,_|\\__\\___| \\____/ \\__, |___/\\__\\___|_| |_| |_|" << endl | |
<< " Copyright (C) 2025 Launium, all rights reserved. __/ |" << endl | |
<< " |___/" << endl; | |
} | |
#define MAIN_MENU_VIEW_DATABASE 1 | |
#define MAIN_MENU_GET_BY_ID 2 | |
#define MAIN_MENU_ADD_NEW 3 | |
#define MAIN_MENU_SAVE_TO_FILE 4 | |
#define MAIN_MENU_EXIT 5 | |
void print_main_menu() | |
{ | |
cout << "======= " << R.string.available_commands << " =======" << endl | |
<< "1 - " << R.string.main_menu_view_database << endl | |
<< "2 - " << R.string.main_menu_get_by_id << endl | |
<< "3 - " << R.string.main_menu_add_new << endl | |
<< "4 - " << R.string.main_menu_save_to_file << endl | |
<< "5 - " << R.string.main_menu_exit << endl; | |
} | |
#define INFO_MENU_DELETE 1 | |
#define INFO_MENU_BACK 2 | |
void print_information_menu() | |
{ | |
cout << "======= " << R.string.available_commands << " =======" << endl | |
<< "1 - " << R.string.info_menu_delete << endl | |
<< "2 - " << R.string.info_menu_back << endl; | |
} | |
class Contact | |
{ | |
public: | |
Contact() | |
{ | |
} | |
virtual string method_descriptor() | |
{ | |
return "unknown"; | |
} | |
virtual string method_name() | |
{ | |
return R.string.unknown_contact_method; | |
} | |
virtual string to_string() | |
{ | |
return ""; | |
}; | |
}; | |
class PhoneContact : public Contact | |
{ | |
private: | |
uint64_t phone_number; | |
public: | |
PhoneContact(uint64_t phone_number) | |
{ | |
this->phone_number = phone_number; | |
} | |
PhoneContact(const char *raw) | |
{ | |
this->phone_number = strtoull(raw, nullptr, 10); | |
} | |
string method_descriptor() | |
{ | |
return "phone"; | |
} | |
string method_name() | |
{ | |
return R.string.contact_method_phone; | |
} | |
string to_string() | |
{ | |
return std::to_string(phone_number); | |
} | |
}; | |
class EmailContact : public Contact | |
{ | |
private: | |
string email; | |
public: | |
EmailContact(string email) | |
{ | |
this->email = email; | |
} | |
string method_descriptor() | |
{ | |
return "email"; | |
} | |
string method_name() | |
{ | |
return R.string.contact_method_email; | |
} | |
string to_string() | |
{ | |
return email; | |
} | |
}; | |
class House | |
{ | |
public: | |
uint64_t id; | |
uint32_t area; // in square centimeters | |
uint32_t price_per_square_meter; // in coins per square meter | |
string address; | |
string owner; | |
Contact *contact; | |
uint32_t purchase_time; // YYYYMMDD, e.g. 20250102 | |
House() | |
{ | |
} | |
~House() | |
{ | |
if (contact) | |
{ | |
delete contact; | |
} | |
} | |
bool operator<(const House &another) | |
{ | |
return id > another.id; | |
} | |
bool operator>(const House &another) | |
{ | |
return id < another.id; | |
} | |
double get_area_in_square_meters() | |
{ | |
return 1.0e-6 * area; | |
} | |
}; | |
class Database | |
{ | |
private: | |
vector<House *> houses; | |
bool updated; | |
public: | |
Database() | |
{ | |
updated = false; | |
} | |
Database(fstream &f) | |
{ | |
updated = false; | |
string line; | |
while (getline(f, line)) | |
{ | |
if (line.empty()) | |
continue; | |
stringstream ss(line); | |
string temp; | |
House *house = new House(); | |
getline(ss, temp, ','); | |
house->id = strtoull(temp.c_str(), nullptr, 10); | |
getline(ss, temp, ','); | |
house->area = stoul(temp); | |
getline(ss, temp, ','); | |
house->price_per_square_meter = stoul(temp); | |
getline(ss, house->address, ','); | |
getline(ss, house->owner, ','); | |
string contact_method; | |
getline(ss, contact_method, ','); | |
string contact_detail; | |
getline(ss, contact_detail, ','); | |
if (contact_method == "phone") | |
{ | |
house->contact = new PhoneContact(contact_detail.c_str()); | |
} | |
else if (contact_method == "email") | |
{ | |
house->contact = new EmailContact(contact_detail); | |
} | |
else | |
{ | |
house->contact = new Contact(); | |
} | |
getline(ss, temp, ','); | |
house->purchase_time = stoul(temp); | |
houses.push_back(house); | |
} | |
sort_database_by_id(); | |
} | |
~Database() | |
{ | |
for (House *h : houses) | |
delete h; | |
houses.clear(); | |
} | |
inline void sort_database_by_id() | |
{ | |
sort(houses.begin(), houses.end()); | |
} | |
void event_loop() | |
{ | |
for (;;) | |
{ | |
print_main_menu(); | |
cout << endl | |
<< "> "; | |
command_t command; | |
cin >> command; | |
switch (command) | |
{ | |
case MAIN_MENU_VIEW_DATABASE: | |
{ | |
clear_screen(); | |
cout << left | |
<< setw(width_id) << "ID" | |
<< setw(width_area) << R.string.database_area | |
<< setw(width_unit_price) << R.string.database_unit_price | |
<< setw(width_total_price) << R.string.database_total_price | |
<< setw(width_address) << R.string.database_address | |
<< setw(width_owner) << R.string.database_owner | |
<< setw(width_contact) << R.string.database_contact | |
<< setw(width_date) << R.string.database_purchase_date | |
<< endl; | |
for (House *h : houses) | |
{ | |
double area = h->get_area_in_square_meters(); | |
cout << left | |
<< setw(width_id) << h->id | |
<< setw(width_area) << area | |
<< setw(width_unit_price) << h->price_per_square_meter | |
<< setw(width_total_price) << area * h->price_per_square_meter | |
<< setw(width_address) << truncate(h->address, width_address) | |
<< setw(width_owner) << truncate(h->owner, width_owner) | |
<< setw(width_contact) << h->contact->to_string() | |
<< setw(width_date) << h->purchase_time | |
<< endl; | |
} | |
cout << endl; | |
pause(); | |
break; | |
} | |
case MAIN_MENU_GET_BY_ID: | |
{ | |
uint64_t query_id; | |
cout << R.string.prompt_enter_id; | |
cin >> query_id; | |
vector<House *>::iterator it = find_if(houses.begin(), houses.end(), [&](House *h) | |
{ return h->id == query_id; }); | |
if (it != houses.end()) | |
{ | |
House *h = *it; | |
double area = h->get_area_in_square_meters(); | |
cout << "ID: " << h->id << endl | |
<< R.string.database_area << ": " << area << endl | |
<< R.string.database_unit_price << ": " << h->price_per_square_meter << endl | |
<< R.string.database_total_price << ": " << area * h->price_per_square_meter << endl | |
<< R.string.database_address << ": " << h->address << endl | |
<< R.string.database_owner << ": " << h->owner << endl | |
<< R.string.database_contact << ": [" << h->contact->method_name() << "] " << h->contact->to_string() << endl | |
<< R.string.database_purchase_date << "(YYYY MM DD): " << h->purchase_time << endl; | |
cout << endl | |
<< R.string.info_menu_commands_for << endl; | |
bool loop = true; | |
while (loop) | |
{ | |
print_information_menu(); | |
cout << endl | |
<< "> "; | |
cin >> command; | |
switch (command) | |
{ | |
case INFO_MENU_DELETE: | |
{ | |
houses.erase(it); | |
delete h; | |
cout << R.string.property_record_removed << endl; | |
updated = true; | |
loop = false; | |
break; | |
} | |
case INFO_MENU_BACK: | |
{ | |
loop = false; | |
break; | |
} | |
default: | |
{ | |
cout << R.string.unknown_command << command << endl; | |
break; | |
} | |
} | |
} | |
} | |
else | |
{ | |
cout << R.string.no_id_found << query_id << endl; | |
} | |
cout << endl; | |
pause(); | |
break; | |
} | |
case MAIN_MENU_ADD_NEW: | |
{ | |
clear_screen(); | |
House *new_house = new House(); | |
cout << "ID (" << R.string.prompt_zero_to_generate_an_id << "): "; | |
cin >> new_house->id; | |
if (new_house->id <= 0) | |
{ // default ID by rules | |
if (houses.empty()) | |
{ | |
new_house->id = 0; | |
} | |
else | |
{ | |
new_house->id = houses.back()->id + 1; | |
} | |
} | |
double area_m2; | |
cout << R.string.database_area << " (m²): "; | |
cin >> area_m2; | |
new_house->area = area_m2 * 1e6; // converts to square centimeter | |
cout << R.string.database_unit_price << ": "; | |
cin >> new_house->price_per_square_meter; | |
cout << R.string.database_address << ": "; | |
cin.ignore(); | |
getline(cin, new_house->address); | |
cout << R.string.database_owner << ": "; | |
getline(cin, new_house->owner); | |
cout << R.string.database_contact << " (1 = Phone, 2 = Email): "; | |
int contact_type; | |
cin >> contact_type; | |
cin.ignore(); | |
if (contact_type == 1) | |
{ | |
string phone; | |
cout << R.string.contact_method_phone << ": "; | |
getline(cin, phone); | |
new_house->contact = new PhoneContact(phone.c_str()); | |
} | |
else if (contact_type == 2) | |
{ | |
string email; | |
cout << R.string.contact_method_email << ": "; | |
getline(cin, email); | |
new_house->contact = new EmailContact(email); | |
} | |
else | |
{ | |
new_house->contact = new Contact(); // fallback | |
} | |
cout << R.string.database_purchase_date << " (YYYYMMDD): "; | |
cin >> new_house->purchase_time; | |
houses.push_back(new_house); | |
sort_database_by_id(); | |
updated = true; | |
cout << endl | |
<< R.string.new_property_record_added << endl; | |
pause(); | |
break; | |
} | |
case MAIN_MENU_SAVE_TO_FILE: | |
{ | |
ofstream out("real_estate_database.csv"); | |
for (House *h : houses) | |
{ | |
out << h->id << "," << h->area << "," << h->price_per_square_meter << "," | |
<< h->address << "," << h->owner << "," | |
<< h->contact->method_descriptor() << "," << h->contact->to_string() << "," | |
<< h->purchase_time << endl; | |
} | |
out.close(); | |
} | |
updated = false; | |
cout << R.string.saved_successfully << endl; | |
pause(); | |
break; | |
case MAIN_MENU_EXIT: | |
{ | |
if (updated) | |
{ | |
cout << R.string.unsaved_exit_warning; | |
char exit_choice; | |
cin >> exit_choice; | |
if (exit_choice != 'y' && exit_choice != 'Y') | |
{ | |
break; // back to main menu | |
} | |
} | |
cout << endl | |
<< R.string.exit_thanks << endl; | |
pause(); | |
return; | |
} | |
default: | |
cout << R.string.unknown_command << command << endl; | |
pause(); | |
break; | |
} | |
clear_screen(); | |
print_banner(); | |
} | |
} | |
}; | |
int main() | |
{ | |
clear_screen(); | |
#ifdef _WIN32 | |
system("chcp 65001>nul"); // Set the console to expect codepage 65001 = UTF-8. | |
#endif | |
print_banner(); | |
cout << "Welcome to Real Estate System." << endl; | |
cout << endl | |
<< "Choose a display language:" << endl | |
<< endl; | |
cout << "1 - English (United States) - en_US" << endl; | |
cout << "2 - 简体中文(中国) - zh_CN" << endl; | |
cout << endl | |
<< "Default 1. Enter your choice: "; | |
{ | |
command_t lang; | |
cin >> lang; | |
load_translation(lang); | |
} | |
clear_screen(); | |
print_banner(); | |
cout << R.string.you_have_chosen << R.string.language_name << R.string.as_display_language << endl | |
<< endl; | |
// load save | |
Database *database; | |
fstream file_save; | |
file_save.open("real_estate_database.csv", ios_base::in); | |
if (file_save) | |
{ | |
cout << R.string.prompt_continue_from_save; | |
char continue_from_save; | |
cin >> continue_from_save; | |
if (continue_from_save != 'n' && continue_from_save != 'N') | |
{ | |
database = new Database(file_save); | |
} | |
else | |
{ | |
database = new Database(); | |
} | |
file_save.close(); | |
} | |
else | |
{ | |
cout << R.string.save_is_not_found_and_creating << endl; | |
} | |
database->event_loop(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment