Add backend projects with core models and tests, setup Godot frontend

This commit is contained in:
sokol
2026-02-20 21:01:09 +03:00
parent fc3ad9f6db
commit f320aa50ed
14 changed files with 518 additions and 0 deletions

11
backend/MyBiz.slnx Normal file
View File

@@ -0,0 +1,11 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/MyBiz.Core/MyBiz.Core.csproj" />
<Project Path="src/MyBiz.Economy/MyBiz.Economy.csproj" />
<Project Path="src/MyBiz.Production/MyBiz.Production.csproj" />
<Project Path="src/MyBiz.Trade/MyBiz.Trade.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/MyBiz.Tests/MyBiz.Tests.csproj" />
</Folder>
</Solution>

View File

@@ -0,0 +1,74 @@
namespace MyBiz.Core;
/// <summary>
/// Тип здания
/// </summary>
public enum BuildingType
{
// Добыча сырья
Farm, // Ферма
CottonField, // Хлопковое поле
Mine, // Шахта
// Производство
FoodFactory, // Пищекомбинат
TextileFactory, // Текстильная фабрика
SteelMill, // Сталелитейный завод
PlasticPlant, // Завод пластика
ElectronicsFactory, // Завод электроники
AutoFactory, // Автозавод
// Торговля
GroceryStore, // Продуктовый магазин
ClothingStore, // Магазин одежды
ElectronicsStore, // Магазин электроники
AutoDealer, // Автосалон
Mall, // Торговый центр
// Исследования
Laboratory, // Лаборатория
// Склад
Warehouse // Склад
}
/// <summary>
/// Здание
/// </summary>
public class Building
{
public Guid Id { get; set; } = Guid.NewGuid();
public BuildingType Type { get; set; }
public string Name { get; set; } = string.Empty;
public string CityId { get; set; } = string.Empty;
/// <summary>
/// Уровень здания (влияет на эффективность)
/// </summary>
public int Level { get; set; } = 1;
/// <summary>
/// Стоимость постройки
/// </summary>
public decimal BuildCost { get; set; }
/// <summary>
/// Стоимость содержания в тик
/// </summary>
public decimal UpkeepCost { get; set; }
/// <summary>
/// Вместимость склада
/// </summary>
public int StorageCapacity { get; set; }
/// <summary>
/// Количество рабочих мест
/// </summary>
public int WorkerSlots { get; set; }
/// <summary>
/// Заполненность рабочими
/// </summary>
public int Workers { get; set; }
}

View File

@@ -0,0 +1,47 @@
namespace MyBiz.Core;
/// <summary>
/// Размер города
/// </summary>
public enum CitySize
{
Small, // Малый
Medium, // Средний
Large // Крупный
}
/// <summary>
/// Город
/// </summary>
public class City
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Name { get; set; } = string.Empty;
public string Country { get; set; } = string.Empty;
public CitySize Size { get; set; }
/// <summary>
/// Население
/// </summary>
public int Population { get; set; }
/// <summary>
/// Доступные здания
/// </summary>
public List<Building> Buildings { get; set; } = new();
/// <summary>
/// Рынок города (спрос на продукты)
/// </summary>
public Dictionary<ProductType, int> MarketDemand { get; set; } = new();
/// <summary>
/// Предложение на рынке
/// </summary>
public Dictionary<ProductType, int> MarketSupply { get; set; } = new();
/// <summary>
/// Текущие цены
/// </summary>
public Dictionary<ProductType, decimal> Prices { get; set; } = new();
}

View File

@@ -0,0 +1,50 @@
namespace MyBiz.Core;
/// <summary>
/// Компания игрока
/// </summary>
public class Company
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; } = string.Empty;
/// <summary>
/// Доступные деньги
/// </summary>
public decimal Cash { get; set; }
/// <summary>
/// Активы компании
/// </summary>
public decimal Assets { get; set; }
/// <summary>
/// Пассивы (долги)
/// </summary>
public decimal Liabilities { get; set; }
/// <summary>
/// Прибыль за последний период
/// </summary>
public decimal LastPeriodProfit { get; set; }
/// <summary>
/// Список зданий компании
/// </summary>
public List<Building> Buildings { get; set; } = new();
/// <summary>
/// Складские запасы
/// </summary>
public Dictionary<ProductType, int> Inventory { get; set; } = new();
/// <summary>
/// Открытые технологии
/// </summary>
public HashSet<string> UnlockedTechnologies { get; set; } = new();
/// <summary>
/// Рассчитать чистую стоимость
/// </summary>
public decimal NetWorth => Assets - Liabilities + Cash;
}

View File

@@ -0,0 +1,65 @@
namespace MyBiz.Core;
/// <summary>
/// Категория продукта
/// </summary>
public enum ProductCategory
{
RawMaterial, // Сырьё
Component, // Компоненты
ConsumerGoods // Товары народного потребления
}
/// <summary>
/// Тип продукта
/// </summary>
public enum ProductType
{
// Сырьё
Cotton, // Хлопок
Steel, // Сталь
Plastic, // Пластик
FoodRaw, // Сырьё для еды
// Компоненты
Fabric, // Ткань
MetalParts, // Металлические детали
PlasticParts, // Пластиковые детали
ElectronicsComponents, // Электронные компоненты
// Товары
Food, // Еда
Clothing, // Одежда
Electronics, // Электроника
Automobile // Автомобили
}
/// <summary>
/// Продукт
/// </summary>
public class Product
{
public ProductType Type { get; set; }
public string Name { get; set; } = string.Empty;
public ProductCategory Category { get; set; }
/// <summary>
/// Базовая цена продукта
/// </summary>
public decimal BasePrice { get; set; }
/// <summary>
/// Текущая цена (с учётом спроса/предложения)
/// </summary>
public decimal CurrentPrice { get; set; }
/// <summary>
/// Доступное количество на рынке
/// </summary>
public int AvailableQuantity { get; set; }
/// <summary>
/// Спрос на продукт
/// </summary>
public int Demand { get; set; }
}

View File

@@ -0,0 +1,37 @@
namespace MyBiz.Core;
/// <summary>
/// Шаг производственной цепочки
/// </summary>
public class ProductionStep
{
/// <summary>
/// Требуемый продукт
/// </summary>
public ProductType InputProduct { get; set; }
/// <summary>
/// Количество требуемого продукта
/// </summary>
public int InputQuantity { get; set; }
/// <summary>
/// Время производства (в тиках)
/// </summary>
public int ProductionTime { get; set; }
}
/// <summary>
/// Производственная цепочка
/// </summary>
public class ProductionChain
{
public ProductType OutputProduct { get; set; }
public BuildingType RequiredBuilding { get; set; }
public List<ProductionStep> Steps { get; set; } = new();
/// <summary>
/// Количество выходного продукта за цикл
/// </summary>
public int OutputQuantity { get; set; }
}

View File

@@ -0,0 +1,41 @@
using MyBiz.Core;
namespace MyBiz.Tests;
public class ProductTests
{
[Fact]
public void Product_Creation_ShouldInitializeProperties()
{
// Arrange & Act
var product = new Product
{
Type = ProductType.Food,
Name = "Bread",
Category = ProductCategory.ConsumerGoods,
BasePrice = 10m
};
// Assert
Assert.Equal(ProductType.Food, product.Type);
Assert.Equal("Bread", product.Name);
Assert.Equal(ProductCategory.ConsumerGoods, product.Category);
Assert.Equal(10m, product.BasePrice);
}
[Fact]
public void Product_CurrentPrice_ShouldDefaultToBasePrice()
{
// Arrange
var product = new Product
{
BasePrice = 25m
};
// Act
product.CurrentPrice = product.BasePrice;
// Assert
Assert.Equal(25m, product.CurrentPrice);
}
}

4
frontend/icon.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128">
<rect x="10" y="10" width="108" height="108" fill="#4CAF50" rx="20"/>
<text x="64" y="80" font-family="Arial" font-size="60" fill="white" text-anchor="middle">$</text>
</svg>

After

Width:  |  Height:  |  Size: 245 B

55
frontend/project.godot Normal file
View File

@@ -0,0 +1,55 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="MyBiz - Economic Simulator"
run/main_scene="res://scenes/main.tscn"
config/features=PackedStringArray("4.0", "Forward Plus")
config/icon="res://icon.svg"
[display]
window/size/viewport_width=1280
window/size/viewport_height=720
window/stretch/mode="canvas_items"
[input]
move_up={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"echo":false,"script":null)
]
}
move_down={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"echo":false,"script":null)
]
}
move_left={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"echo":false,"script":null)
]
}
move_right={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"echo":false,"script":null)
]
}
[layer_names]
2d_physics/layer_1="Default"
2d_physics/layer_2="Buildings"
2d_physics/layer_3="UI"
[rendering]
renderer/rendering_method="forward_plus"

14
frontend/scenes/main.tscn Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<scene format="3" uid="1" namespaced="false" unique_name_in_tree="true">
<node name="Main" type="Node2D">
<node name="GameManager" instance="res://scripts/core/GameManager.cs" parent="." index="0"/>
<node name="UI" type="CanvasLayer" parent="." index="1">
<node name="TopBar" type="Panel" parent="UI">
<node name="HBoxContainer" type="HBoxContainer" parent="TopBar">
<node name="MoneyLabel" type="Label" parent="HBoxContainer"/>
<node name="DateLabel" type="Label" parent="HBoxContainer"/>
</node>
</node>
</node>
</node>
</scene>

View File

@@ -0,0 +1,13 @@
using Godot;
[GlobalClass]
public partial class GameData : Resource
{
[Export] public string CompanyName { get; set; } = "My Company";
[Export] public int StartYear { get; set; } = 1980;
[Export] public decimal StartingCash { get; set; } = 100000m;
// Настройки карты
[Export] public int CityCount { get; set; } = 10;
[Export] public bool EnableImports { get; set; } = true;
}

View File

@@ -0,0 +1,59 @@
using Godot;
/// <summary>
/// Менеджер игры - управляет состоянием игры
/// </summary>
public partial class GameManager : Node
{
public static GameManager Instance { get; private set; } = null!;
[Signal] public delegate void MoneyChangedEventHandler(decimal newAmount);
[Signal] public delegate void DateChangedEventHandler(int year, int month, int day);
public decimal Money { get; private set; } = 100000m;
public int GameYear { get; private set; } = 1980;
public int GameMonth { get; private set; } = 1;
public int GameDay { get; private set; } = 1;
public override void _Ready()
{
if (Instance != null && Instance != this)
{
QueueFree();
return;
}
Instance = this;
}
public void AddMoney(decimal amount)
{
Money += amount;
EmitSignal(SignalName.MoneyChanged, Money);
}
public void SpendMoney(decimal amount)
{
if (Money >= amount)
{
Money -= amount;
EmitSignal(SignalName.MoneyChanged, Money);
}
}
public void AdvanceTime(int ticks)
{
GameDay += ticks;
if (GameDay > 30)
{
GameDay = 1;
GameMonth++;
if (GameMonth > 12)
{
GameMonth = 1;
GameYear++;
}
}
EmitSignal(SignalName.DateChanged, GameYear, GameMonth, GameDay);
}
}

View File

@@ -0,0 +1,13 @@
using Godot;
public partial class Main : Node2D
{
public override void _Ready()
{
GD.Print("MyBiz - Game Started!");
}
public override void _Process(double delta)
{
}
}

View File

@@ -0,0 +1,35 @@
using Godot;
/// <summary>
/// Базовый класс для всех UI элементов
/// </summary>
public partial class GameUI : Control
{
protected GameManager GameManager => GameManager.Instance;
public override void _Ready()
{
ConnectSignals();
}
protected virtual void ConnectSignals()
{
if (GameManager != null)
{
GameManager.MoneyChanged += OnMoneyChanged;
GameManager.DateChanged += OnDateChanged;
}
}
protected virtual void OnMoneyChanged(decimal newAmount) { }
protected virtual void OnDateChanged(int year, int month, int day) { }
public override void _ExitTree()
{
if (GameManager != null)
{
GameManager.MoneyChanged -= OnMoneyChanged;
GameManager.DateChanged -= OnDateChanged;
}
}
}