Ekonomický simulátor EcoS programátorská dokumentace
Obsah I.
Úvod …............................................................................................................................. 2 Popis programu …....................................…................................................................... 2 Použité technologie ..….................................................................................................... 2 II. Struktura programu ...................................................................................................... 3 Části programu ................................................................................................................. 3 Základní rozdělení ............................................................................................................ 3 Přehled zdrojových souborů ............................................................................................. 4 Důležité třídy a funkce ..................................................................................................... 4 III. Ekonomický model ....................................................................................................... 13 Základní vlastnosti modelu .............................................................................................13 Volba volných a vázaných proměnných ......................................................................... 14 Výpočet ceny na trhu ...................................................................................................... 14 Rozdělení komodit ......................................................................................................... 16 Krok simulace ve městě ................................................................................................. 16 Meziměstský obchod a typy terénu ................................................................................ 17 Nákup podniků, plánování výroby ................................................................................. 17 IV. Algoritmy a postupy ..................................................................................................... 19 Události .......................................................................................................................... 19 Generování mapy ........................................................................................................... 20 Inicializace simulace ...................................................................................................... 22 Vykreslování mapy ......................................................................................................... 23 Plánování výroby ........................................................................................................... 24 Určení velikosti zásilky .................................................................................................. 24 Rozvoj města .................................................................................................................. 25 Watchery ......................................................................................................................... 25 Ostatní sledované údaje ................................................................................................. 26 Spokojenost obyvatel ..................................................................................................... 27 A
Formáty používaných souborů ................................................................................... 30
I. Úvod Tento text má sloužit jako programátorská dokumentace k programu EcoS. Jeho úkolem není okomentovat každou funkci, třídu nebo metodu, ale poskytnout náhled, jak celá aplikace z programátorského hlediska funguje, umožnit čtenáři (i autorovi) se ve zdrojovém kódu orientovat a případně osvětlit nebo zdůvodnit použité algoritmy. Součástí této dokumentace je samozřejmě i přehled všech zdrojových souborů včetně stručného obsahu a výčet nejdůležitějších tříd spolu s popisem jejich nejdůležitějších metod. Jinak ale dokumentace nebude členěna do kapitol podle souborů nebo tříd, ale spíše podle logických souvislostí (tzn. např. část věnovaná generování mapy, část o kroku simulace apod.) Samostatnou přílohu tvoří popis formátu používaných souborů.
Popis programu: EcoS je program, který má umožnit na vybrané mapě zjednodušeně, avšak názorně simulovat ekonomiku na úrovni několika měst (státu). Důraz je kladen především na srozumitelnost simulace a na velkou svobodu v nastavování parametrů simulace – uživatel si může nejen v editoru map vytvořit své vlastní mapy, ale dokonce si i nadefinovat vlastní model obyvatelstva (jaké komodity se kde vyrábí, v jakém množství, které jsou považovány za základní a které za luxusní apod.)
Použité technologie: Program je napsán v jazyce C++ s využitím STL a knihoven wxWidgets (verze 2.8). Při vývoji byl důležitý požadavek na přenositelnost aplikace – aby byla spustitelná (zkompilovatelná) jak na počítačích s OS Windows tak pod Linuxem. Proto byly zvoleny knihovny wxWidgets, které umožňují psaní multiplatformních grafických aplikací. Existuje verze programu pro Windows XP a verze pro Windows 98. Zdrojové kódy jednotlivých verzí se liší jen minimálně – a to hlavně v návrhu GUI (jiná velikost fontu, šířka ovládacích prvků apod.).
II. Struktura programu Části programu Celá aplikace se sestává ze čtyř víceméně samostatných částí: editor modelu komodit a obyvatelstva, editor map, simulace a hrx. Editor modelu umožňuje definovat model komodit (s čím se bude obchodovat, kde a jak se to bude vyrábět) a model obyvatelstva (rozdělení komodit na základní a luxusní apod.). Umožňuje ukládat model do textového souboru (formát je popsán v samostatné příloze) a takový soubor načítat. Editor mapy slouží pro vytváření nových map. Mapy mohou mít libovolné rozměry v rozmezí 10x10 45x30 čtverců (horní mez je dána velikostí minimapy). Lze měnit typ terénu (i pro víc čtverců mapy najednou), stavět nová města a cesty a upravovat detaily měst. Součástí je i generátor náhodných map. Režim simulace slouží k vizuální prezentaci běžící simulace. Umožňuje simulaci zastavit, spustit nebo zrychlit, zobrazovat detaily o městech - situace na místním trhu, stručná historie vývoje cen - dovoluje některé "božské" zásahy - měnit typ terénu takřka libovolného čtverce, přidávat/odebírat obyvatele nebo zboží na trh, rušit obchodní cesty nebo celá města. Obsahuje též možnost zobrazit celkovou statistiku mapy a možnost nastavit na libovolnou komoditu v libovolném městě watcher. V režimu hry je uživatel postavem do role obchodníka s cílem pokusit se vydělat co nejvíce peněz. Možnosti zasahovat do světa jsou v tomto režimu pochopitelně omezeny.
Základní rozdělení Program vychází se standartního modelu aplikace wxWidgets, odděleny jsou samotná aplikace, rám (frame) graf. okna, panel graf. okna (resp. panely) a ostatní kód. Aplikaci představuje třída MyApp odvozená z wxApp. Rám okna aplikace tvoří instance třídy EcoSFrame a vnitřek okna (panel) buď EcoSPanel, SimPanel nebo EditorPanel (podle toho, zda je zapnutý editor modelu, editor map nebo zda běží simulace). Tyto třídy implementují pouze funkčnost, grafický layout a ovládací prvky obsahují už jejich předci deklarovaní v souboru EcoS_GUI.h. Schéma zachycuje následující obrázek:
Třída EcoSFrame reprezentuje rám a horní menu programu, zbyývající čtyři vždy zastupují panel zobrazený při jednom ze čtyř možných režimů aplikace. Hlavní funkcí spouštěnou při startu aplikace je bool MyApp::OnInit() v souboru EcoS.cpp. Uvnitř té se inicializují některé proměnné třídy MyApp, vytvoří se instance rámu aplikace, ten se zobrazí a dál aplikace čeká ve smyčce na události.
Přehled zdrojových souborů Následuje stručný přehled všech zdrojových souborů včetně nejdůležitějšího obsahu. Popis nejdůležitějších funkcí a tříd můžete nalézt v části Důležité třídy a funkce. EcoS.h, EcoS.cpp - Obsahuje třídu MyApp, která představuje hlavní aplikaci (program) EcoSAbout.h, EcoSAbout.cpp - Implementuje „About dialog“ EcoSFrame.h, EcoSFrame.cpp - Implementuje třídu EcoSFrame reprezentující rám okna aplikace (včetně horního menu) EcoSPanel.h, EcoSPanel.cpp - Obsahuje všechny hlavní panely (EcoSPanel pro editor komodit, EditPanel pro editor map, SimPanel pro prostředí simulace a GamePanel pro prostředí hry) stejně jako velkou většinu všech dialogů map.h, map.cpp - Implementuje třídu Mapa, která představuje mapu světa, spolu se souvisejícími funkcemi např. funkce pro generování mapy nebo I/O funkce PPainters.h, PPainters.cpp - Obsahuje věci potřebné k vykreslování mapy. Třída DrawPanelPainter slouží pro vykreslování bitmap terénu na zvolený panel. Třída DrawPanelHandler, která je odvozená ze třídy wxEvtHandler, je v podstatě event handler, který slouží k posílání událostí myši a klávesnice vhodnému rodiči (panel editoru komodit, editoru map nebo prostředí simulace). simulation.h, simulation.cpp - Implementuje věci důležité pro samotný běh simulace. V první řadě jde o třídy Simulation, Game a Konstanty, která obsahuje konstanty uvedené v konfiguračním souboru EcoS.ini: např. cenu práce jednoho člověka za týden, rychlost simulace (v ms) apod. Dále obsahuje třídu Watcher a funkce pro inicializaci simulace (zejména se jedná o topologické setřídění komodit podle výrobních řetězců a určení teoretických nákladů na výrobu) a především také důležitou funkci Cena, která podle parametrů počítá ceny na místních trzích. Jsou tu také funkce pro ukládání a načítání hry. tridy.h, tridy.cpp - Definuje a iplementuje hlavní „vnitřní“ datové struktury. Zejména se jedná o třídy Komodita, Jednotka, Zavod, Mesto a Obyvatelstvo a výčtový typ TEREN. Obahuje také další pomocné třídy a funkce pro kontrolu, otevírání a ukládání modelu obyvatelstva. EcoS_GUI.h, EcoS_GUI.cpp - Z velké části automaticky generovaný kód, který definuje grafické uživatelské prostředí.
Důležité třídy a funkce: V této části jsou vypsány nejdůležitější třídy a funkce – řazeny podle zdrojových souborů. U funkcí a metod je uveden pouze stručný popis jejich funkce, detailnější rozvedení (pokud je potřebné) je k nalezení ve třetí nebo čtvrté kapitole. Obsluhy událostí (např. void EcoSFrame::OnMapSave) nejsou většinou uvedeny. Jednak je jejich účel obvykle zřejmý z názvu, jednak je jim věnován samostatný odstavec ve 4. kapitole.
EcoS.h: enum STAV { MODEL, SIMUL, EDITOR, HRA}; class MyApp : public wxApp virtual bool OnInit() int GetIDW() Simulation* simulation
Game* game Konstanty konstanty STAV stav
EcoSFrame* e_frame void NactiKonstanty(Konstanty& k)
4 stavy, v jakém se aplikace může nacházet jednou instanciovaná třída představující aplikaci funkce volaná při startu (jako main u C programu) vrátí vhodné ID pro nový watcher ukazatel na probíhající simulaci ukazatel na hru položka obsahující konstanty z .ini souboru stav, ve kterém se aplikace aktuálně nachází ukazatel na hl. rám aplikace načte konstanty ze souboru EcoS.ini
EcoSAbout.h: class EcoSAbout : public AboutDlg
okno s “About dialogem”
EcoSFrame.h: class EcoSFrame : public MainFrame EcoSFrame( wxWindow *parent = NULL, int id = -1 );
void Vyhra() void DisableSimMenu() void DisableMapMenu() void DisableGameMenu() void EnableGameMenu() std::vector
* komodity; std::vector<Jednotka>* jednotky; Obyvatelstvo* obyvatelstvo; EcoSPanel* main_panel SimPanel* sim_panel EditPanel* edit_panel
GamePanel* game_panel MySMDlg* sm_dlg wxTimer m_timer void OnPlayB(wxCommandEvent &event) void OnStopB(wxCommandEvent &event)
bool konec_hry
rám okna aplikace konstruktor, vytvoří všechny panely a 1 zobrazí v režimu hry po dosažení cílové částky zobrazí dialog o výhře zneaktivní položky v submenu "Simulace" zneaktivní položky v submenu "Mapa" zneaktivní položky v submenu "Hra", s výjimkou položky "Načíst" zneaktivní položky v submenu "Simulace" ukazetel na vektor komodit (hl. panelu) ukazetel na vektor jednotek (hl. panelu) ukazetel na obyvatelstvo (hl. Panelu) ukazatel na panel editoru komodit ukazetel na panel simulace ukazetel na panel editoru map ukazatel na panel hry ukazatel na dialog "Statistika mapy" časovač, “stopky”, které odměřují kroky simulace spuštění simulace (“stopky se rozjedou”) zastavení simulace (“stopky se zastaví”) proměnná udávající, zda je zobrazen dialog o výhře
EcoSPanel.h: class Aktualizovatelne
virtual void Aktualizovat()=0 class EcoSPanel : public MainPanel void ShowKomodity() void ShowKomodity() void ShowObyvatelstvo() std::vector komodity; std::vector<Jednotka> jednotky; Obyvatelstvo obyvatelstvo; void OnMM( wxCommandEvent& event ) void OnEditK( wxCommandEvent& event) void ChangeK(int select=-1) OnEditVV( wxCommandEvent& event ) void DelKom_O(long ID, int zkama) void AddKom_O(long ID, int kam) void ShowDetailK( wxCommandEvent& event ) void ShowDetailJ( wxCommandEvent& event ) void ShowVzorce(Jednotka& jedn) void ShowObdelnikO(wxTextCtrl* m_tCtrl, std::vector* u_vektor) bool GetIdJedn(long& ID)
abs. předek pro dynamická (aktualizovaná) okna abs. metoda – pro aktualizování obsahu okna panel editoru modelu komodit a obyvatelstva vypíše informace o komoditách vypíše informace o jednotkách vypíše informace o modelu obyvatelstva vektor komodit modelu vektor ek. Jednotek modelu část modelu týkající se obyvatelstva načte model z mapy v editoru map editace vybrané komodity změní vybranou komoditu nebo přidá novou otevře dialog editace vzorce pro rozvoj smaže komoditu z vybrané části modelu obyvatelstva přidá komoditu do vybrané části modelu obyvatelstva vypíše datail vybrané komodity vypíše datail vybrané jednotky vypíše výrobní vzoerce vybrané jednotky vypíše vybranou část modelu obyvatelstva v parametru vrátí id vybrané komodity, vrátí TRUE v případě úspěchu
panel prostředí simulace ukazatel na mapu simulace souřadnice posunutí výřezu mapy časovač sloužící pro automatický refresh položka, která se stará o vykreslení výřezu s mapou vektor (dynamických) oken s detaily měst vektor (dynamických) oken s detaily cest vektor (dynamických) oken s historií měst vektor watcherů okno watcherů odstraní okno z vektoru,v případě úspěchu vrátí TRUE přidá okno (resp. ukazatel) do vektrou přidá nový watcher do seznamu odstraní watcher s daným id smaže watchery, které se týkaly města *M natáhne nové informace pro watchery smaže a zavře všechna okna s detily, které ukazují na město *M bool JeZobrazeno(wxPoint PT) vrátí TRUE, pokud je čtverec PT v aktuálním výřezu void OnTimer(wxTimerEvent& event) refresh okna, zastaví časovač void OnStep( wxCommandEvent& event) provede 1 krok simulace void Q(wxPaintEvent& event) obsluha události vykreslení wxPoint SpoctiCtverec(wxPoint souradnice) spočítá skutečné souřadnice čtverce na mapě void SmazCtverec(wxPoint bod) vymaže obsah (cestu nebo město) daného čtverce void ApplyFiltr() aplikuje filtr "pohledem 1 komodity" void FiltrMesto(Mesto& M, wxClientDC& dc, long vypíše údeje podle filtru vedle města M
class SimPanel: public SimPanel2 Mapa* mapa wxPoint offset wxTimer m_timer DrawPanelPainter DPP std::vector<MyDMSim*> okna std::vector<MyDCDlg*> okna_cest std::vector<MyHDlg*> okna_hist std::vector<Watcher> watchers MyWDlg* watch_dlg bool SmazOkno(MyDMSim* okno) void AddOkno(MyDMSim* okno) AddWatcher(const Watcher& W) void SmazWatcher(int idw) void SmazVsechnyWatchery(Mesto* M) void AktualizujWatchery() void SmazVsechnaOkna(Mesto* M)
IDK) bool maz; bool ruka; TEREN teren wxPoint lastMousePosition
udává, jestli je aktivován "mód mazání" udává, jestli je aktivován "mód výběru" aktuálně vybraný typ terénu čtverec výřezu, na kterém nastala posl. událost myši
panel prostředí hry ukazatel na mapu hry souřadnice posunutí výřezu mapy časovač sloužící pro automatický refresh položka, která se stará o vykreslení výřezu s mapou vektor (dynamických) oken s detaily měst vektor (dynamických) oken pro obchodování vektor (dynamických) oken s historií měst vektor watcherů okno watcherů aktualizuje obsah hl. panelu a otevřených dynavoid UpdatePanel(bool no_repaint=false) mických oken void PaintMM() vykreslení minimapy bool SmazOkno(MyDMSim* okno) odstraní okno z vektoru,v případě úspěchu vrátí TRUE void AddOkno(MyDMSim* okno) přidá okno (resp. ukazatel) do vektrou AddWatcher(const Watcher& W) přidá nový watcher do seznamu void SmazWatcher(int idw) odstraní watcher s daným id void SmazVsechnyWatchery(Mesto* M) smaže watchery, které se týkaly města *M void AktualizujWatchery() natáhne nové informace pro watchery void SmazVsechnaOkna(Mesto* M) smaže a zavře všechna okna s detily, které ukazují na město *M bool JeZobrazeno(wxPoint PT) vrátí TRUE, pokud je čtverec PT v aktuálním výřezu void OnTimer(wxTimerEvent& event) refresh okna, zastaví časovač void OnStep( wxCommandEvent& event) provede 1 krok simulace void Q(wxPaintEvent& event) obsluha události vykreslení wxPoint SpoctiCtverec(wxPoint souradnice) spočítá skutečné souřadnice čtverce na mapě void SmazCtverec(wxPoint bod) vymaže obsah (cestu nebo město) daného čtverce void ApplyFiltr() aplikuje filtr "pohledem 1 komodity" void FiltrMesto(Mesto& M, wxClientDC& dc, long vypíše údeje podle filtru vedle města M
class GamePanel: public SimPanel2 Mapa* mapa wxPoint offset wxTimer m_timer DrawPanelPainter DPP std::vector<MyDMSim*> okna std::vector<MyDCDlg*> okna_obch std::vector<MyHDlg*> okna_hist std::vector<Watcher> watchers MyWDlg* watch_dlg
IDK)
class EditPanel: public EditPanel2 Mapa* mapa
panel editoru map ukazatel na mapu simulace
wxPoint offset wxTimer m_timer int rozmer TEREN teren bool ruka bool cesta bool maz bool extra Cesta nova_cesta double cena_cesty wxPoint lastMousePosition void NovaMapa(int sirka, int vyska) void PaintMM() void OnTimer(wxTimerEvent& event) wxPoint SpoctiCtverec(wxPoint souradnice) void OnMestoCh( wxCommandEvent& event) void OnChM( wxCommandEvent& event) void VypisSousedy(const Mesto& M, vector<Mesto>& mesta) void OnGenerate( wxCommandEvent& event) void OnEditM( wxCommandEvent& event) void OnBil( wxCommandEvent& event) void P(wxPaintEvent& event) void Ovlivneno(vector<wxPoint>& ctverce, wxPoint bod) bool JeNaMape(wxPoint ctverec) double SpoctiCenu(wxPoint p1, wxPoint p2) class MyEVVDlg: public EVVDialog class MyNMDlg: public NMDialog class MyNGDlg: public NGDialog class MyNJDlg: public NJDialog MyNJDlg(wxWindow* parent, std::vector<Jednotka>* _v_jedn) MyNJDlg(wxWindow* parent, std::vector<Jednotka>* _v_jedn, Jednotka* _J)
class MyDMDlg: public DMDialog class MyDMSim: public DMDialogSim, public Aktualizovatelne class MyDMGame: public DMDialogGame, public Aktualizovatelne class MyOGame: public ODialog, public Aktualizovatelne void OnSell( wxCommandEvent& event) void OnBuy( wxCommandEvent& event) void VypisPanely() class MyKPDlg: public KPDialog, public Aktualizovatelne class MyKNDlg: public KNDialog, public Aktualizovatelne class MyHDlg: public HDialog, public Aktualizovatelne class MyBMDlg: public BMDialog class MySMDlg: public SMSialog class MySKDlg: public SKDialog, public Aktualizovatelne class MyDialog4 : public wxDialog class MSDialog : public wxDialog class MyDCDlg: public DCDialog, public Aktualizovatelne class MyWDlg: public WDialog, public Aktualizovatelne class MyVyhraDlg: public VyhraDialog class MyOBDlg: public OBDialog, public Aktualizovatelne
souřadnice posunutí výřezu mapy časovač sloužící pro automatický refresh strana čtverce editovaného území aktuálně vybraný typ terénu udává, jestli je aktivován "mód výběru" udává, jestli je aktivován "mód přidávání cesty" udává, jestli je aktivován "mód mazání" udává, jestli je aktivován "mód extra kvality" nově stavěná cesta cena nové cesty čtverec výřezu, na kterém nastala posl. událost myši vytvoří novou mapu zadaných rozměrů vykreslí minimapu refresh okna, zastaví časovač spočítá skutečné souřadnice čtverce na mapě vypíše detaily vybraného města provede změnu detailů vybraného města vypíše všechny sousedy daného města generování náhodné mapy zobrazí okno s detaily města zobrazí okno s celkovou bilancí mapy obsluha události vykreslení v parametru vrátí vektor ovlivněných čtverců (podle nastavených rozměrů 1x1 - 5x5) vrátí TRUE, pokud jsou dané souřednice uvnitř mapy spočítá cenu přechodu mezi 2 sousedními čtverci dialog editace vzorečku rozvoje dialog zadávání rozměrů nové mapy dialog zadávání parametrů při startu hry dialog editace (resp. přidání nové) jednotky konstruktor, pokud se přidává nová jednotka konstruktor v případě editace stávající jednotky dialog detialu města (pro editor map) dialog datailu města (pro prostředí simulace) dialog detailu města (pro prostředí hry) dialog pro obchodování obsluha prodávání hráče obluha nakupování hráče vypsání tabulek s detaily o stranách transakce dialog přehledu karavan dialog koupě nové karavany dialog stručné historie města dialog celkové bilance mapy dialog celkové statistiky mapy - pro simulaci dialog "Statistiky komodit" okno s výzvou pro uložení stávajícího modelu okno se "Splnitelností" modelu dialog datailu cesty (pro prostředí simulace) okno s watchery dialog zobrazující se při dosažení cílové částy dialog "Obchodní statistiky" pro režim hry
wxPoint SpoctiNovyOffset(wxPoint bod, int X, int Y) spočítá nový offset tak, aby byl daný čtverec co možná void PaintMiniM(Mapa* mapa, wxPanel* minimap, wxPoint offset); void FiltrMesto(Mesto& mesto, wxClientDC& dc, long IDK, wxPoint offset);
nejblíž prostředku výřezu (pozice [13,10] ) vykreslení minimapy na zadaný panel zobrazení filtru "1 komodity" na zadaný panel
map.h: enum SMER { NORTH, EAST, SOUTH, WEST} typedef std::vector Cesta class Mapa std::vector<Mesto> mesta std::vector cesty std::vector extra TEREN* data std::vector komodity std::vector<Jednotka> jednotky void AlokujData(int pocet) void SmazCestu(int start, int cil) void PridejCestu(const Cesta& C) void InitTrhy() bool DeleteXY(wxPoint PT) bool DeleteMesto(wxPoint PT) bool DeleteCestu(wxPoint PT) void ZmenExtra(wxPoint PT) int GetX() const int GetY() const TEREN GetTeren(wxPoint pt) const int Naalokovano() const const std::vector* GetPK() const const std::vector<Jednotka>* GetPJ() const const std::vector<Mesto>* GetPM() const const std::vector* GetPC() const void Step() bool Sousedi(int poradi1, int poradi2) const TEREN GetTerenSoused(int poradi, SMER sm) double CenaCesty(Mapa* M, Cesta& C) void Generuj(Mapa* M, int hustota) void NactiMapu(wxTextInputStream& text, Mapa& M) void UlozMapu(wxTextOutputStream& text, const Mapa& M) bool KontrolaMapy(Mapa& M) bool MestoNaVode(Mapa& M) bool CestaNaVode(Mapa& M) bool ChybnaJednotka(Mapa& M) bool JeKolemVolno(Mapa* M, wxPoint kde) bool JeExtra(const Mapa* M, wxPoint bod) bool JsouSousedi(wxPoint p1, wxPoint p2) bool JeNaMape(const Mapa* const M, wxPoint kde) Mesto* FindMesto(std::vector<Mesto>& mest, int poziceX, int poziceY) unsigned int GetBitSousedy(const Mapa* M, int X, int Y, bool extra=false) wxString TerenToStr(TEREN T)
udává možné směry Cesta ~ seznam čtverců mapa "světa" vektor měst na mapě vektor cest vektor č. čtverců s extra kvalitou pole s typem terénu pro každý čtverec použité komodity použité jednotky vyhradí potřebnou paměť na terén smaže cestu, která začíná a končí na těchto čtvercích přidá cestu do svého vektoru inicializuje trhy všech svých měst (na začátku simul.) smaže město nebo cestu na čtverci PT smaže město na čtverci PT, vrátí TRUE, pokud tam nějaké město bylo smaže cestu na čtverci PT, vrátí TRUE, pokud tam nějaká cesta vedla normálnímu čtverci dá extra kvalitu, extra kvalitní vrátí k normálu vrátí x-ový rozměr mapy vrátí y-ový rozměr mapy vrátí typ terénu na daném čtverci vrátí počet naalokovaných čtverců vrátí (konstantní) ukazatel na vektor komodit vrátí (konstantní) ukazatel na vektor jednotek vrátí (konstantní) ukazatel na vektor měst vrátí (konstantní) ukazatel na vektor cest provede krok simulace vrátí TRUE, pokud jsou čtverce přímí sousedi vrátí typ terénu souseda v daném směru spočítá a vrátí cenu cesty C (v poh. bodech) vygeneruje mapu se zadanou četností vody načte mapu z uvedeného zdroje uloží mapu do uvedeného zdroje provede kontrolu mapy, TRUE pokud nenajde chybu TRUE, poku nějaké město stojí na vodě TRUE, pokud nějaká cesta vede po vodě TRUE, pokud je chybná vazba jednotky na terén TRUE, pokud v okolí bodu kde není město TRUE, pokud je bod "extra kvalitní" zjistí, jestli jsou 2 čtverce sousedé vrátí TRUE, pokud je čtverec uvnitř mapy *M vrátí ukazatel na město, které leží na zadaných souřadnicích, vrátí 0, pokud tam žádné město není vrátí bitovou masku reprezentující typy terénů, které jsou v přímém okolí k nalezení převede typ terénu na string
PPainters.h: class DrawPanelPainter bool loaded wxPoint focused wxPoint offset void PaintCestu(const Cesta& C,
třída, která se stará o vykreslování mapy udává, zda se podařilo úspěšně načíst obrázky zvýrazněný čtverec mapy aktuální offset mapy vykreslí 1 cestu
wxBufferedDC& dc, int sirka) void PaintMesto(const Mesto& M, wxBufferedDC& dc) void Repaint(wxPoint off, Mapa* map=0, bool clear=false, bool jenMC=false) void RepaintRect(wxPoint off, wxRect region, Mapa* map=0) void PaintKaravany(wxPoint off, Mapa* map=0) void PaintBod(wxPoint kam, TEREN typ) void SetFocused(wxPoint ctverec) void Zvyrazni(wxPoint ctverec, wxBufferedDC& dc) void Znevyrazni() void NacistImg() void PripravitBitmapy() wxBitmap* NajdiBMP(TEREN T, unsigned int bit_smery); class DrawPanelHandler: public wxEvtHandler
int FROM wxPanel* parent void void void void void void void
OnLeave(wxMouseEvent& evt) OnMouse(wxMouseEvent& evt) OnMouseR(wxMouseEvent& evt) OnDblClick(wxMouseEvent& evt) OnMouseUp(wxMouseEvent& evt) OnMotion(wxMouseEvent& evt) OnKey(wxKeyEvent& evt)
vykreslí 1 město překreslí celou mapu překreslí jen čtverce, které zasahují do regionu vykreslení karavan na zadaný bod nakreslí texturu terénu naství zvýraznění na daný čtverec nakreslí zvýraznění daného čtverce zruší zvýraznění načte obrázky z pevně daných rel. adres z načtených obrázků připraví bitmapy najde vhoudnou texturu pro čtverec vzhledem k typům terénu na okolních čtvercích třída, která se stará o správné předávání událostí, které souvisejí s vykreslováním identifikátor, který se nastaví každé události předávané dál rodič, kterému se budou upravené události předávat nastaví události identifikátor a pošle "rodičovi" nastaví události identifikátor a pošle "rodičovi" nastaví události identifikátor a pošle "rodičovi" nastaví události identifikátor a pošle "rodičovi" nastaví události identifikátor a pošle "rodičovi" nastaví události identifikátor a pošle "rodičovi" nastaví události identifikátor a pošle "rodičovi"
simulation.h: enum TYPT { INIT, NAKUP, PRODEJ, NAKLAD_KARAVAN, NAKLAD_SKLADU, NEW_KAR}; class Konstanty int PROCENTO_PRACUJICICH int MAX_OBYVATEL int CENA_PRACE int PRACE_NAVIC int SIM_STEP_MLS int EXTRA_BONUS int GAME_STEP_MLS class Zbozi wxString jmenoK long IDK int mnozstvi int prumer long cena_celkem void Prodej(int kolik) void Pridej(int kolik, int cenaza1)
class Karavana
typy transakcí, které lze provádět třída obsahující v sobě konstanty ze souboru EcoS.ini podíl práceschopných obyvatel horní limit na počet obyvatel cena práce za 1 tyden náklady na nadlimitní výrobu délka kroku simulace v ms bonus za extra terén délka kroku hry v ms položka skladu nebo karavany jméno komodity id komodity mnostvi zbozi prumerna cena za 1ks celkova cena za vsechny ks dohormady odebere zadané množství zboží přídá zadané množství s pr. cenou cenaza1 karavana pro hráče vrátí množství komodity s id idk v karavaně
int GetMnozstvi(long idk) void PridejZbozi(const Komodita& K, int kolik,přidá do karavany zboží int cenaza1) void OdeberZbozi(long idk, int kolik) odebere zboží z karavany void Step() krok karavany (pokud je na cestě) void NaplanujCestu(const Cesta& C, int kroku, naplánování cesty (čtverce, kde se bude objevovat) int m) wxString jmeno jméno karavany wxPoint aktual_pt aktuální pozice karavany na mapě int dorazi za jak dlouho karavana dorazí do cíle int kapacita kapacita karavany int nalozeno objem naloženého zboží std::vector naklad náklad karavany
std::deque<wxPoint> plan_cesty plán cesty (čtverce, kde se bude objevovat) class Sklad sklad ve městě wxPoint kde souřednice města std::vector zbozi zboží na skladě void PridejZbozi(const Komodita&K, int kolik, přidá určené množství dané komodity int cenaza1) void OdeberZbozi(long idk, int kolik) odebere určené množství int GetMnozstvi(long idk) const vrátí množství komodity s id idk na skladě int GetCenuZa1(long idk) const vrátí cenu za 1ks komodity s id idk na skladě int GetMnozstviAll() const vrátí celkovou cenu komodity s id idk na skladě class StatistikaPenez (roční) statistika void Vynuluj() vynulování statistik long nakup abs. hodnota nákupů long prodej celkový zisk z prodeje long naklad_k abs. hodnota nákladů na karavany long naklad_s abs. hodnota nákladů na uskladnění zboží long new_k abs. hodnota nákladů na nákup karavan class Simulation třída představující celou simulaci Mapa* mapa mapa, na které simulace probíhá Obyvatelstvo obyvatelstvo model obyvatelstva, který simulace používá std::vector naklady "konstanty" pro výpočet ceny zboží long tyden č. "logického" týdne long tydnu_vehre; č. týdne skutečně stráveného ve hře (simulaci) void InitNaklady() spočítá "konstanty", které se budou používat při void Step() void Stop() void Play() class Game: public Simulation void Step() long account
long target bool uz_vyhral bool AddAcc(long sum, TYPT TT) std::vector karavany std::vector<Sklad> sklady wxString jmeno_vybrane_kar StatistikaPenez stat_letos class Watcher int IDW long IDK int MX, int MY void Aktualizovat(int prodej, int nakup, int mnozstvi, int produkce) int prod_ceny[13], int nak_ceny[13], int mnozstvi[13], int produkce[13] class KomTS void SeradKom(const std::vector& kom, vector<Jednotka>& jed, vector& ceny) void SpoctiNaklady(std::vector<Jednotka>& jed, std::vector& ceny) void NajdiProducenty(long IDP, vector<Jednotka>& jed, vector<Jednotka*>& prod) double NakladJ(Jednotka* jedn, vector& ceny) double NajdiCenu(long ID, std::vector& ceny) double Cena(double mnozstvi, long IDK, double spotreba) void SaveGame(wxTextOutputStream& text) void LoadGame(wxTextInputStream& text)
výpočtu cen (teoretické výrobní náklady) provede krok simulace zastaví simulaci nastaví příznak běhu simulace třída reprezentující hru provede krok simulace a navíc odečte náklady stav v hráčově pokladnici cílová částka TRUE, pokud hráč už jednou dosáhl cílové částky změní účet o 'sum', TT udává typ transakce, vrátí TRUE, pokud nebude přkročen max. debet hráčovy karavany sklady ve městech jméno karavany, která je v game-panelu zvýrazněná statistiké údaje za letošek třída představující watcher na komoditu ID watcheru ID komodity souřadnice města dodá aktuální informace pole sledovaných údajů pomocná třída pro "topologické setřídění" komodit "topologicky" seřadí komodity tak, aby se na jeden průchod daly spočítat výrobní náklady pro (vhodně seřazené) komodity spočítá "výr. náklady" v posl. parametru vrátí jednotky produkující danou komoditu spočítá teoretické náklady na výrobu v tété jednotce vrátí cenu vybrané komodity na základě množství na trhu a spotřebě obyvatel spočítá střední cenu komodity uložení hry načtení hry
tridy.h: class ChybnyFormat class PolozkaHistorie typedef std::vector historie enum TEREN { nic=0, voda, louka, plan, les, poust, kopce} class Komodita long ID, wxString jmeno, double spotreba class Obyvatelstvo class PolozkaTrhu: public Komodita class VyrobniVzorec class Jednotka long ID_jedn, wxString jmeno, long ID_produkt VyrobniVzorec vzorecky[MAX_VZORCU] TEREN potr_teren class Zavod Jednotka typ int pocet bool naplno[4] class Soused int mestoX, int mestoY, int vzdalenost class Mesto std::vector<wxString> zpravy std::vector zavody std::vector<Soused> sousedi std::vector trh std::vector incoming
třída pro vyhazování vyjímek při I/O operacích třída pro "zapamatování si" množství a cen komodit historie komodit pro 1 den možné typy terénu komodita, zboží parametry komodity třída reprezentující model obyvatelstva položka na místním trhu 1 vzorec, podle kterého mohou jednotky vyrábět ekonomická jednotka (elementární podnik) parametry jednotky pole všech dostupných vzorců vyžadovaný typ terénu 1 závod ve městě (může se skládat z více jednotek) typ závodu počet zákl. jednotek, z kterých je závod složen kdy během posl. 4 týdnů jel závod na plnou kapacitu soused města parametry souseda 1 město zprávy o aktuálním dění ve městě seznam místních závodů seznam sousedů místní trh pomocný vektor, obsahuje zboží, které má v budoucnu dorazit na místní trh historie komodit pár týdnů nazpátek souřadnice města bitmaska udávající aktuální typy terénu v okolí aktualizuje bitmasky města a provede krok
historie history[MAX_HISTORY] int x, int y unsigned int bit_sous void Step(unsigned int sous, unsigned int extra) void AddInc(long id, int mnozstvi, unsigned přidá zboží do "fronty příchozího zboží" int deadline, wxPoint from, int delka_prod=1) void CountSpokojenost() přepočítá spokojenost ve městě void AddZbozi(long id, int mnozstvi) přidá (nebo odebere!) zadané množství daného typu void InitTrh(const std::vector& kom) void UpdateCeny() int CenaNaTrhu(long ID) const int CenaProdej(long ID) const int CenaNakup(long ID) const bool DostStavebnin() const bool DostStavebninTed() const long GetAktualProdukce(long ID) void UpdateInc() void NakupZavody() void NakupLidi() void NakupKaravany() void NaplanujVyrobu(Zavod& Z) Soused* VyberSousedaPro(PolozkaTrhu& PT, Mesto** M)
void RecordHistory() void Rozvoj() void ZavriZavody()
zboží nastaví náh. množství na trhu, vynuluje historii (mělo by se volat pouze 1x ) přepočítá a upraví ceny všch komodit na trhu vrátí "střední cenu" vybrané komodity vrátí prodejní cenu komodity na trhu vrátí nákupní cenu komodity na trhu vrátí TRUE, pokud je ve městě dlouhodobě dost stavebnin vrátí TRUE, pokud je ve městě okamžitý dostatek stavebnin vrátí celkovou aktuální produkci dané komodity aktualizuje "frontu příchozích" - sníží "dobu čekání" provede krok závodů - nákup + plánování výroby provede nákup lidí provede nákup "karavan" - pro meziměstský obchod naplánuje výrobu daného závodu (a plán provede) vybere nejvhodnějšího souseda pro obchod s daným zbožím zaznamená historii komodit pro tento týden zodpovídá zo rozvoj města při nedostatku pracovníků vybere a zavře nějaký závod převede souřadnice na číslo čtverce (m=šířka mapy) spočítá bilanci všech měst dohromady
int PoziceToPoradi(int x, int y, int m) void SpoctiBilanci(vector<Mesto>& mesta, const vector* kom, vector& vyroba, vector& spotreba) void NactiModelK(wxTextInputStream& text, načte model komodit (a obyvetelstva )
vector& kom, vector<Jednotka>& jedn, Obyvatelstvo& obyv) void UlozModelK(wxTextOutputStream& text, const vector& kom, const vector<Jednotka>& jedn, const Obyvatelstvo& ob) bool NajdiJ(long id, const std::vector<Jednotka>& jedn, Jednotka* J) bool KontrolaModelu(const vector* kom, const vector<Jednotka>* jedn) bool KontrolaMest(const std::vector<Mesto>* mes, const vector<Jednotka>* jedn, int n, int m) bool JeT_v_bitmasce(TEREN T,unsigned int bitmaska)
uloží model komodit ( a obyvetelstva ) do posl. parametru uloží jednotku s hledaným id,
vrátí TRUE, pokud taková existuje provede kontrolu modelu (jednoznačná ID apod.) provede kontrolu seznamu měst vrátí TRUE pokud bitmaska indikuje přítomnost typu T
III. Ekonomický model Tato kapitola se věnuje ekonomickému modelu, podle kterého simulace funguje. Tanto model si neklade za cíl přesně realisticky emulovat ekonomiku, ale přiblížit se skutečnému chování ve srozumitlené podobě. Při stanovování modelu hrála roli zejména tato kritéria: věrnost (použitý model a algoritmy by měly věrně napodobovat realitu), srozumitelnost (aby uživatel věděl, co a proč se kde děje) a v neposlední řadě výpočetní složitost (aby simulace mohla běžet dostatečně rychle).
Základní vlastnosti modelu Ekonomický model upravuje fungování celé simulace. Zde je výčet jeho základních vlastností: - mapa (svět) = neorientovaný graf, kde vrcholy představují města a hrany obchodní cesty - obchodní cesty mohou vést pouze po souši - 1 krok simulace odpovídá 1 týdnu - střídání ročních období se nebere v úvahu - je dán seznam komodit, se kterými se obchoduje; komodity mají určený parametr, který udává spotřebu na 100 obyvatel - komodity se dělí na základní a luxusní (základní jsou více potřebné, lidé se bez nich neobejdou) - některé komodity jsou označeny jako potraviny - ty jsou potřeba pro růst populace, pokud jich je dlouhodobý nedostatek, může vypuknout hladomor - některé komodity jsou označeny jako stavebniny - ty jsou potřeba při rozvoji města (nové podniky a nová obydlí) - je dán seznam ekonomických jednotek (elementárních podniků); ty mají určeno který produkt vyrábí, kolik potřebuje pracovníků, jaký typ terénu vyžaduje, délku produkce a seznam výrobních vzorců (nejvýše 4) - v každém městě funguje místní trh: pro každou komoditu jsou uvedeny prodejní a nákupní ceny - ekonomické jednotky stejného typu jsou v jednom městě sloučeny v jeden podnik - podnik má pevný počet zaměstnanců daný počtem ek. jednotek, tito lidé nemohou pracovat nikde jinde, ani v případě, že daný podnik aktuálně nevyužívá plnou výrobní kapacitu (dělníci nejsou na práci přidělováni podle aktuální potřeby) - podniky nakupují pouze na místním trhu a dodávají pouze na místní trh (výrobu tedy plánují pouze na základě situace na místním trhu) - podniky se snaží naplánovat výrobu tak, aby přinesla maximální zisk - výrobní kapacita je dána výrobními vzorci a počtem elementárních jednotek, ze kterých se podnik skládá - podnik může krátkodobě vyrábět přes svoji normální kapacitu - a to až o 20% - to ale za cenu zvýšených nákladů (dělníkům je třeba zaplatit za práci přesčas) - některé podniky mohou požadovat specifický typ terénu (např. rybář potřebuje vodu), pokud se takový v bezprostředním okolí (4 sousední čtverce) nevyskytuje, podnik nemůže vyrábět - některé čtverce na mapě mohou být extra kvalitní - takové potom dávají podnikům sousedních měst bonus na produkci - meziměstký obchod probíhá pouze po existujících cestách - města obchodují pouze se svými sousedy - v každém městě pro každou komoditu existuje imaginární obchodník, který rozhoduje, do kterého města (pokud vůbec) bude daná komodita exportována - "obchodník" má plnou informaci - tzn. že má informaci o aktuálních cenách na všech trzích - pro 1 komoditu může být v 1 kroku simulace z 1 města vypravena maximálně 1 karavana
- možné obchodní cesty jsou poměřovány z hlediska výnosnosti (zisk / týden) - ve městě žije určitý počet obyvatel (defaultní horní limit 4000), 25% z nich je práceschopných (toto číslo lze upravit v konfiguračním souboru EcoS.ini) - každé město má ubytovací kapacitu, která udává maximální snesitelný počet obyvatel, přičemž normální počet obyvatel do 92% využití kapacity, vyšší procento znamená přelidnění a nespokojenost obyvatel - každé město má dále ukazatel spokojenosti obyvatel - ta je ovlivněna dostupností zboží, výší nezaměstnanosti, dostatkem potravin a do určité míry využitím ubytovacích kapacit - počet obyvatel roste: pokud je ve městě za poslední 4 týdny dostatek nadpoloviční většiny potravin a je volná ubytovací kapacita - i ubytovací kapacita může růst: pokud je ve městě dost lidí a je dostatek stavebnin - pokud je spokojenost obyvatel vysoká, zvýší se produktivita práce Většina vlastností ekonomického modelu je neměnná, přímo daná zdrojovým kódem. Pouze některé konstanty jdou měnit přepsáním konfiguračního souboru EcoS.ini. Jejich výčet můžete nelézt v příloze A.
Volba volných a vázaných proměnných Podle základní mikroekonomické teorie existují tyto základní tržní kategorie: nabídka, poptávka, cena a konkurence. Pokud jde o konkurenci, tu použitý model pro jednoduchost zanedbává (všechny elementární ek. jednoty stejného typu jsou ve městě sloučeny v jeden podnik). Zbylé tři faktory jsou velmi úzce propojené, neexistuje jednoznačná odpověď na otázku, který z nich je primární. Pro potřeby "zjednodušené" počítačové simulace je ale nanejvýš vhodné určit, která "proměnná" je volná a které jsou odvozené (a explicitně uvést jak). Použitý model bere jako základní nabídku a poptávku, z nich se odvozuje cena. Přesněji řečeno: množství zboží na trhu (které je v daný okamžik známé) a spotřeba obyvatel (která je pro každý druh zboží uvedena jako konstanta - parametr komodity) určuje cenu. Model ovšem reflektuje i zpětný vztah, kdy cena ovlivňuje nabídku a poptávku. Tzv. zákon rostoucí nabídky říká, že růst ceny vyvolá růst nabízeného množství - toho je tu docíleno pomocí algoritmů, kterými podniky plánují svou výrobu. Při vysoké ceně se jim vyplatí více vyrábět a tedy množství zboží na trhu poroste (tím dojde k nasycení trhu, cena poklesne a v důsledku toho zas poklesne i výroba). Na druhou stranu s rostoucí cenou poptávané množství klesá (zákon klesající poptávky). Toho je docíleno tak, že při malém množství zboží na trhu (a tudíž vysoké ceně) obyvatelé nekoupí tolik zboží, kolik by koupili při ceně obvyklé (tento vliv je o něco patrnější u luxusních komodit - viz níže).
Výpočet ceny na trhu Jak bylo řečeno, cena je určena množstvím zboží na trhu a spotřebou obyvatel. Standartní ekonomická teorie ovšem žádnou jednoduchou funkci, explicitní vzorec pro takový výpočet neposkytuje. Maximálně přibližnou křivku, která takový vztah vyjadřuje:
Funkce pro výpočet ceny měla splňovat několik požadavků: - být klesající (nižší cena při dostatku zboží) - být ryze konvexní - být zdola omezená kladnou konstantou (ani při velkém přebytku se zboží neprodává zadarmo) - malá výpočetní složitost (jedná se o nejpoužívanější funkci v celé simulaci) - měla by být určena ne absolutním množstvím na trhu, ale relativním (tzn. podílem absolutního množství a spotřeby obyvatel) - být rozumně definována v 0 (může se stát, že na trhu nic nebude) Pokud jde o omezující konstantu (označme ji třeba C), ta je určena při inicializaci simulace a vyjadřuje teoretické výrobní náklady (cenu práce + cenu za suroviny). Pod tuto teoretickou hranici cena na místním trhu nikdy neklesne. Nejjednodušší funkce, která se nabízela, typu C + C * spotreba / mnozstvi
bohužel není definována v nule. Bylo nutno najít jinou funkci než lomenou. Požadované vlastnosti má funkce exponenciální s parametrem x z int. (0,1). Tedy C + C * konstanta * (x) mnozstvi / spotreba
V exponentu je mnozstvi / spotreba právě aby funkce byla klesající. Parametry byly následně určeny tak, aby funkce neklesala "příliš prudce" a aby cena při obvyklém množství (cca dvojnásobek spotřeby obyvatel) byla zhruba na dvojnásobku "výrobních nákladů". Konečná formule tedy je: C + C * 4 * (1/2) mnozstvi / spotreba
Touto funkcí je určena "střední" cena na místním trhu. V rámci simulace ale existují na trhu ceny dvě: cena prodejní, za kterou se na trhu prodává (tzn. za kterou se dá zboží pořídit) a která je stanovana jako 1,05 * střední cena, a cena nákupní, za kterou se na trhu vykupuje (0,95 * střední cena).
Rozdělení komodit Komodity jsou rozděleny na základní a luxusní. Jejich rozdíl je v tom, jak je lidé nakupují v případě jejich nedostatku. Obecně základní je takové zboží, které lidé nutně potřebují. Ve spotřebě takového zboží se omezí jen pokud je cena opravdu vysoká. Zato bez luxusního zboží se spíše obejdou. Přesněji u základních komodit obyvatelé nakupují normální množství, pokud jsou ve městě zásoby apoň na 1 týden, pokud je zboží méně, nakoupí pouze 1/3 obvyklého množství. U luxusních nakoupí polovinu obvyklého množství, pokud zásoby klesnou pod množství na 2 týdny. Pokud je tohoto zboží méně než na 1 týden, nakoupí pouze 1/4. Různý je taky dopad (ne)dostatku na spokojenost obyvatel (určený podílem počtu dostatkových a nedostatkových komodit): základní
luxusní
poměr dost. / nedost.
vliv na spokojenost
poměr dost. / nedost.
vliv na spokojenost
žádné nedostatkové
+6
žádné nedostatkové
+4
≥4
+4
>2
+3
z intervalu (3,4]
+3
z intervalu [1-2]
+1
z intervalu (2,3]
+2
z intervalu [0,5;1)
-1
z intervalu (1-2]
0
<0,5
-2
z intervalu (0,5-1]
-1
≤0,5
-2
Krok simulace ve městě Simulace je diskrétní v čase - to znamená, že jde "po krocích", vždy se udělá 1 krok (několik operací, které mají pevně dané pořadí), potom aplikace na chvíli počká, potom provede další krok atd. Krok znamená. že se v každém městě provedou následující operace (v tomto pořadí): - nákup lidí - nákup "obchodníků" - příjem nového zboží - nákup závodů - rozvoj města - výpočet spokojenosti
... nejprve uspokojí své potřeby obyvatelé ... pro meziměstký obchod ... ať už z příchozích karavan nebo z místních podniků ... naplánování výroby (pokud nejsou uprostřed výrobního cyklu) ... zvýšení ubyt. kapacity nebo rozšíření nějakého podniku
Výpočet spokojenosti musí být samozřejmě poslední operací. Jinak by se mohlo například stát, že by spokojenost ve městě byla nízká, přičemž důvody mezitím pominuly. První zas musí být nákup lidí, aby uživatel viděl situaci ve městě těsně před efektem nákupu obyvatel (pokud je nedostatek, musí se to odrazit na jejich nákupu a spokojenosti - kdyby třeba příjem nového zboží předcházel nákupu lidí, mohlo by se stát, že uživatel uvidí, že je nedostatek potravin, jenže po příjmu nového zboží se potraviny na trhu objeví a z hlediska obyvatel jich bude dostatek - ale to by pro uživatele zůstalo skryto!) Nákup závodů a plánování výroby musí zase následovat až po příjmu nového zboží. Pokud by tomu bylo obráceně, podniky s jednotýdenním výrobním cyklem by vyráběly "okamžitě" (v jednom kroku naplánují a zadají výrobu a ve stejném kroku by se zboží objevilo na trhu)
Meziměstský obchod a typy terénu Obchod mezi městy probíhá pouze po uvedených cestách a pouze přímo mezi sousedy. Nestává se tak, že by se zboží z města A posílalo do města B přes město C. Vzdálenost mezi dvěma městy je dána typem terénu čtverců, přes které obchodní cesta prochází. Imaginární "karavna" má na 1 týden 10 pohybových bodů (PB). Náročnost různých typů terénu je vyjádřena počtem PB, který karavana na jeho překonání potřebuje. Počet PB na 1 čtverec daného typu je zachycen v následující tabulce:
typ terénu
PB
voda
N/A
louka
1
pláň
2
les
5
poušť
6
kopce
8
Pokud cesta přes daný čtverec nevede přímo, ale šikmo, je potřeba 1,5-násobek PB. Počet týdnů, který karavana potřebuje na zdolání cesty s cenou x PB je potom roven horní celé části z x / 10
V každém městě pro každou komoditu funguje imaginární "obchodník", který má k dispozici 1 "karavanu" a rozhoduje, do kterého města se bude daná komodita vyvážet a v jakém množství. Obchodník nejprve porovná výnosnost všech obchodních cest, které z města vedou, z hlediska zisku za 1 ks zboží / 1 týden. (Pro každého souseda vypočítá rozdíl místní prodejní ceny a tamější nákupní ceny, který vydělí vzdáleností měst v týdnech). Vybere to nejvýhodnější sousední město (nejvýnosnější cestu). Teprve poté určí velikost zásilky. Protože "obchodník" chce maximalizovat svůj zisk, nakupuje na místním trhu tak dlouho, dokud je místní prodejní cena nižší než nákupní cena ve vybraném sousedním městě. (Pro jednoduchost se předpokládá, že cena, za kterou v cílovém městě zboží na trh dodá je pevná, všechno zboží - v libovolném množství - bude prodáno za tuto cenu. Naopak cena, za kterou nakupuje na místním trhu se dynamicky mění - čím více nakoupí, tím méně zboží na trhu zůstane a tím bude dražší - v určitém bodě pak cena na místním trhu dosáhne aktuální ceny na trhu cílovém a obchodníkovi se už nevyplatí nakoupit více zboží.) Pozn.: Při porovnávání obchodních cest se neberou v úvahu náklady na přepravu. I v případě, že by se tyto náklady uvažovaly, by se jejich závislost na množství přepravovaného zboží a na délce cesty nejspíš velmi podobala přímé úměře. Na výsledku porovnávání by to tedy příliš nezměnilo - bylo by to jako ode všech porovnávaných hodnot (zisk za 1 ks zboží / 1 týden) odečíst konstantu.
Nákup podniků, plánování výroby Pokud podnik není uprostřed svého výrobního cyklu, může v určenou chvíli naplánovat svou výrobu. Pokud zrovna něco vyrábí, nemůže začít souběžně další výrobu, i kdyby měl dostatečnou volnou výrobní kapacitu. Každý podnik (resp. ekonomická jednotka) může mít až 4 výrobní vzorce, které určují suroviny a objem produkce. Podnik vyzkouší naplánovat výrobu podle každého svého výrobního vzorce a aplikuje ten plán, který přinese největší čistý zisk. Naplánování výroby podle 1 pevně zvoleného vzorce vlastně spočívá pouze ve stanovení počtu elementárních ek. jednotek, které se do výroby zapojí (tedy určit, na kolik procent své výrobní kapacity
podnik pojede). Podobně jako u nákupu "obchodníků" se předpokládá, že všechno vyrobené zboží bude na trh dodáno za jednu pevnou cenu (aktuální nákupní cena), zatímco cena případných surovin pro výrobu se dynamicky mění. Je tedy možné si předem vypočítat předpokládanou tržbu za 1 ek. jednotku zapojenou do výroby: trzba = produkce * (nakupni_cena_na_trhu - cena_prace_za_1ks)
Celkový zisk se potom dá určit takto: zisk = trzba * zapojenych_jednotek - f1 - f2 - ... -fn
kde f1,f2, ... -fn vyjadřují cenu za nákup potřebného množství surovin 1 - n. Přesně vyjádřeno je fi rovno určitému integrálu funkce určující cenu zboží na trhu, tedy
kde C je konstanta používaná při výpočtu ceny (teoretická cena výroby), b aktuální množství zboží na trhu a a množství na trhu po odkoupení potřebného množství. Protože se ale jedná o kritický výpočet, na předním místě je rychlost výpočtu, není tento způsob výpočtu nákladů přiliš vhodný. Cena, za kterou podniky nakupují suroviny je proto stanovena jinak. Cena, za kterou podnik nakoupí (b-a) ks zboží při aktuálním množství b je určena takto: cena = ( cena_pri_b + cena_pri_a ) / 2 * (b-a)
Pokud se jako nejvýhodnější ukáže využít výrobní kapacitu podniku 100%, je možné krátkodobě překročit tuto kapacitu a zvýšit produkci o dalších 20%. Za to je ale potřeba zaplatit zvýšenými náklady na lidskou práci (dělníkům je třeba zaplatit přesčas). Standartní zvýšení je 50% mzdy navíc (tento bonus lze měnit v konfiguračním souboru EcoS.ini). Pokud bude celkový zisk i přes zvýšené náklady vyšší než při 100% využití kapacity, bude se následující výrobní cyklus produkovat zboží navíc. Vlivy ovlivňující produktivitu Při plánování výroby podnik nebere v úvahu dva faktory, které mohou produkci ovlivnit. Na prvním místě je to spokojenost obyvatelstva. Spokojení dělníci mají vyšší produktivitu práce, nespokojení naopak nízkou. Pokud je spokojenost ve městě větší než 85%, produktivita stoupne o 5%, pokud je mezi 65% a 75%, pak produktivita klesne o 10%, pokud spokojenost klesne pod 65%, pak produktivita klesne o 20%. Druhým faktorem, který má vliv na objem produkce, jsou extra kvalitní čtverce na mapě. Takový čtverec dává všem podnikům, které daný typ terénu vyžadují, v sousedních městech bonus na výrobu (standartně 40%, toto číslo jde měnit v konfiguračním souboru EcoS.ini)
IV. Algoritmy a postupy Základní struktura programu byla představena už ve druhé kapitole. Tato kapitola se věnuje použitým algoritmům z programátorského pohledu. Nejsou zde přímo popisovány všechny funkce a proměnné, spíše jsou představeny důležité funkce a je předložen celkový náhled na řešení vybraných problémů.
Události K ošetření událostí (klinutí na ovládací prvek, stisknutí klávesy apod.) je využit mechanismus statických tabulek událostí, který nabízí knihovny wxWidgets. Všechy třídy, které mají nějak reagovat na události mají tuto tabulku. Ta se deklaruje pomocí makra DECLARE_EVENT_TABLE() uvnitř deklarace třídy. Samotná tabulka, určující která událost má být ošetřena kterou funkcí, je v implementačním souboru (.cpp) uvedená a ukončená pomocí BEGIN_EVENT_TABLE... END_EVENT_TABLE. Řádky tabulky mapují pomocí dalších maker události na odpovídající členské funkce. Všechny funkce, které ošetřují události, mají stejnou formu - jejich návratovým typem je void, nejsou virtuální a mají jeden argument, který se mění podle typu události; například příkazy základních ovládacích prvků sdílejí třídu události wxCommandEvent. Řádek tabulky může vypadat třeba takto: EVT_BUTTON(wxID_MM, EcoSPanel::OnMM)
Tento řádek určuje, že pokud bude stisknuto tlačítko s identifikátorem "wxID_MM", má se spustit funkce EcoSPanel::OnMM(wxCommandEvent&). Mechanismus událostí je podrobněji popsán v kníze CrossPlatform GUI Programming with wxWidgets. Mechanismus ošetření většiny událostí je s využitím nástrojů wxWidgets poměrně jednoduchý. Výjimku tvoří panel pro výřez mapy v editoru map a v prostředí simulace a hry. Tento panel tvoří instance třídy wxPanel (wxPanel* Draw_panel1;), na který se vykreslují textury terénu. Je ovšem potřeba ošetřit události myši a klávesnice na tomto panelu. Prvním možným řešením by bylo odvodit ze třídy wxPanel třídu novou, nadefinovat v ní příslušné funkce, pomocí statické tabulky událostí namapovat události na funkce a tuto novou třídu pak použít. Na druhou stranu je výhodnější mít všechny související funkce "na jednom místě" - tedy ve třídě EditPanel nebo SimPanel - jednak protože některé akce na výřezu mapy mají stejný efekt jako stisknutí určitého tlačítka příslušného panelu a bylo by nešikovné ho implementovat dvakrát, jednak ošetření události z výřezu mapy obvykle ovlivní celé okno, ne jen samotný výřez. Menší problém je v tom, že tyto události (typu wxMouseEvent a wxKeyEvent) se narozdíl od wxCommandEvent automaticky nepropagují do svých bazických tříd a rodičů. Řešením je odvodit novou třídu z wxEventHandler (konkrétně class DrawPanelHandler: public wxEvtHandler), která bude mít jediný účel, a to přeposlat zachycené události k ošetření rodičovskému panelu. Tuto třídu je třeba přidat do zásobníku "event handlerů" výřezu mapy pomocí Draw_panel1->PushEventHandler(new DrawPanelHandler(this,FROM_DRPANEL);
uvnitř konstruktoru třídy EditPanel resp. SimPanel. FROM_DRPANEL je identifikátor, který je "přilepen" ke každé přeposílané události. Pomocí něj se zjišťuje, jestli je událost původní, nebo přeposlaná. Typická metoda třídy DrawPanelHandler pak vypadá následovně: void DrawPanelHandler::OnDblClick(wxMouseEvent& evt) { evt.SetId(FROM); parent->GetEventHandler()->ProcessEvent(evt); }
objektu události je nastaveno nové Id a je poslán k ošetření rodiči. Ten ve funkci ošetřující daný typ
události obykle na začátku obsahuje podobnou podmínku: void EditPanel::OnMouse(wxMouseEvent& event) { if(event.GetId()==FROM_DRPANEL) { ... } ... }
právě aby se odlišily originální a přeposlané události.
Generování mapy Editor map obsahuje i automatický generátor map. Vygenerování nové mapy se skládá z několika kroků: nejprve se vygeneruje terén (voda, louka atd.), potom se generují města a obchodní cesty a nakonec se generuje průmysl (ekonomické jednotky ve městech). Generování terénu Pro vygenerování terénu slouží funkce void Generuj(Mapa* M, int hustota) Parametr hustota může nabývat hodnot 0,1 nebo 2 a určuje podíl vody. Za základ byl vzat algoritmus popsaný na stránce http://fortressgame.wordpress.com/2007/08/16/mapgeneration-and-the-hill-algorithm : * Pick a random spot on the map (rx,ry) * Pick a random radius size (radius) * For each pixel on the map (x,y) * Apply the following equation ignoring negative values: height[rx][ry] += radius^2 + ((x-rx)^2 - (y-ry)^2) * Repeat about 1000 times
Původně algoritmus generuje profil terénu (jeho výšku) - stačí ovšem nahrazovat intervaly výšky určeným typem terénu, takže např. čtverce s nejmenší výškou odpovídají vodě apod. Nicméně tento algoritmus se neukázal být úplně optimálním. Jednak má tendence vytvářet vrcholky při horním a dolním okraji mapy (to se spraví úpravou rovnice na height[rx][ry] += radius^2 + ((x-rx)^2 + (y-ry)^2)). Navíc nebere v vůbec úvahu velikost mapy - proto se v upravené verzi algoritmu neberou v úvahu všechny čtverce (v originálu pixely), ale pouze ty uvnitř náhodného poloměru. Poslední úprava spočívá v tom, že se negeneruje mapa o velikosti m x n, ale mapa o velikosti max_m x max_m, z které se použije pouze výřez. Důvodem je, že pro čtvercové mapy dává algoritmus "hezčí" mapy. Terén se tedy generuje následovně: - pomocí výše popsaného algoritmu se všem čtvercům spočítá jejich "výška" - "výška" je znormalizována na interval (0-1) - jsou vymezeny intervaly, které určují jaké hodnoty znamenají jaký typ terénu - základní intervaly jsou tanoveny takto: 0 - 0,22 ... poušť 0,22 - 0,3 ... kopce 0,3 - 0,4 ... les 0,4 - 0,5 ... pláň 0,5 - 0,65 ... louka 0,65 - 1 ... voda - tyto intervaly se mohou měnit (např. vlivem parametru určujícího podíl vody na mapě) - "vyhlazení" - aby na mapě nebyly osamocené ostrůvky uprostřed vody nebo naopak čtverce vody uprostřed souše
Generování měst Pro vygenerování měst slouží funkce void GenerujMesta(Mapa *M, int hustota) Parametr hustota může nabývat hodnot 0,1 nebo 2 a určuje hustotu měst. Hodnota 0 znamená v průměru 35 čtverců souše na jedno město, 1 znamená 26 čtverců a 2 v průměru 20 čtverců souše na 1 město. Každému městu se vygenerují náhodné souřadnice uvnitř mapy a náhodný počet obyvatel (v rozmezí 100-3000). Ubytovací kapacita města je napevno stanovena na 110% počtu obyvatel. Město se nesmí nacházet na čtverci s vodou ani těsně vedle jiného města. Generování cest Pro vygenerování cest na mapě s městy slouží funkce void GenerujCesty(Mapa* M) Pro každé město bude nalezena cesta k jeho nejbližšímu sousedu. Nejbližšímu z hlediska vzdálenosti ve čtvercích - nebere se v úvahu typ terénu, takže například město vzdálené 3 čtverce pouště je považováno za bližší, než město vzdálené 6 čtverců po louce, i když cestu do něj vyžaduje mnohem více PB. Pokud je město se svým nejbližším sousedem už spojeno, nová paralelní cesta se nestaví. Cesta k nejbližšímu sousedu se najde pomocí Cesta NajdiCestuKNej(Mesto& Mes, Mapa* M) První parametr je město, ze kterého se hledá cesta, a druhý mapa, na které se cesty generují. Nejbližší soused se hledá prohledáváním do šířky. Nejprve se vytvoří a inicializuje dvourozměrné pole int predchudci[MAX_MX+1][MAX_MY+1] for(int x=1; x<=MAX_MX; ++x) for(int y=1; y<=MAX_MY; ++y) { if(M->GetTerenInt(PoziceToPoradi(x,y,M->GetX())) == 1) predchudci[x][y]=-1; else predchudci[x][y]=-2; }
// voda // jako ze tam nesmim // ze jsem tam jeste nebyl
pole predchudci slouží k zapamatování čísla předchozího čtverce na cestě. Číslo -1 značí, že na zadaném čtverci je voda (tudíž přes něj nemůže vést žádná cesta), číslo -2 znamená volný čtverec, který ještě nebyl do prohledáváhí zahrnut, jakékoliv nezáporné číslo na pozici [x,y] označuje číslo čtverce, který leží jako předposlední na nejkratší cestě ze startovního pole (určeného městem Mes) na čtverec [x,y]. Začne se tak, že se do zvláštní fronty ("okraje") přidá čtverec, na kterém leží startovní město. Samotné hledání probíhá tak, že se vezme první čtverec z fronty, zkotroluje se, jestli na něm neleží nějaké město: pokud ano, tak je nejbližší, pokud ne, tak se do okraje přidají všechny sousední suchozemské čtverce, které ješte nebyly prohledány. Pokud existuje nějaké nejbližší, po souši dosažitelné město, tento algoritmus ho najde. Samotná cesta se jednoduše zrekonstruuje zpětným průchodem přes pole predchudci. Generování průmyslu Pro generování průmyslu slouží funkce void GenerujPrumysl(Mapa* M, std::vector& vyroba, std::vector& spotreba)
kde M je ukazatel na mapu s městy, vyroba vektor komodit, které mají v parametru spotreba uložený celkový objem výroby na mapě (před vygenerováním průmyslu tedy samé nuly) a spotreba vektor komodit, které mají v paramertu spotreba uloženou celkovou spotřebu obyvatelstva. Nejprve se vyberou ty ek. jednotky, které mají nějaký požadavek na typ terénu: vector<Jednotka> j_sT; vector<Jednotka>::const_iterator jedn_it; for(jedn_it=jednotky->begin(); jedn_it!=jednotky->end(); ++jedn_it) if(jedn_it->GetTeren()!=nic)
{
j_sT.push_back(*jedn_it); }
Tyto jednotky se generují přednostně. Pro každou jednotku se určí seznam vhodných měst (ta, v jejichž okolí se nachází vyžadovaný typ terénu) a pomocí funkce void PridejJedn(const Jednotka& J, vector<Mesto*>& mesta, vector& vyroba, vector& spotreba)
se podniky tohoto typu přidají do vybraných měst (pokud je v nich dostatek volné prac. síly) tak, aby celková produkce mírně překračovala celkovou spotřebu - ze spotřeby, maximální produkce 1 jednotky a počtu vhodných měst se vypočítá potřebný počet jednotek na 1 město, ve skutečnosti se ale funkce do každého města snaží umístit o několik jednotek víc. Tato funkce také aktualizuje vektor vyroba tak, aby odpovídal situaci po přidání nových závodů. Poté, co jsou vygenerovány jednotky s požadavky na typ terénu, generují se ostatní (ty, které nejsou na žádný terén vázané). U těch se jako seznam vhodných měst bere seznam všech měst na mapě.
Inicializace simulace Před spuštěním simulace je třeba ji ještě inicializovat. To zahrnuje jednak inicializaci místních trhů (nastavit nějaké počáteční množství pro každou komoditu) a jednak vypočítat konstanty vyjadřující "nejnižší teoretické výrobní náklady", které se posléze používají při výpočtu ceny zboží na trhu (viz Výpočet ceny na trhu). K této inicializaci dojde při prvním zobrazení panelu prostředí simulace - ve funkci void EcoSFrame::ShowSimP( wxCommandEvent& event ) Inicializace místních trhů Inicializaci místního trhu obstarává funkce void Mesto::InitTrh(const vector& kom), která v paramteru kom dostane seznam všech komodit. Pro každou komoditu se nastaví vhodné počáteční množství na 1 - 6-ti násobek množství spotřebovaného obyvateli města za 1 týden. Déle se "vynuluje" historie města (aby záznam o historii neobsahoval náhodné údaje). Výpočet "minimálních výrobních nákladů" Výpočet "minimálních výrobních nákladů" zajišťuje funkce void Simulation::InitNaklady(). Ta ve výsledku uloží do členské proměnné vector naklady třídy Simulation vektor s hledanými konstantami. Všechny komodity je neprve nutné správně seřadit. (Jde o topologické třídění orientovaného grafu, kde vrcholy představují komodity a hrana A→B znamená, že A se používá jako surovina při výrobě B.) Pokud se v takovém grafu vyskytuje kružnice (což sice logicky příliš smysl nedává, ale je to teoreticky možné), komodity nemůžou být seřazeny a objeví se chybová hláška. Pro seřazení komodit slouží funkce void SeradKom(const std::vector& kom, std::vector<Jednotka>& jed, std::vector& ceny);
a pomocná třída KomTS reprezentující vrchol grafu: class KomTS { public: long ID; std::vector predchudci; std::vector<size_t> indexy_nasl; size_t stupen; };
kde ID je Id příslušné komodity, predchudci obsahuje Id všech surovin používaných při výrobě, indexy_nasl obsahuje indexy všech komodit, které se z této vyrábí, do seznamu všech vrcholů a stupen je
aktuální vstupní stupeň příslušného vrcholu v orientovaném grafu. Prvním krokem je připravení si grafu - pro každou komoditu se vytvoří vrchol grafu ( KomTS bod; bod.ID = kom[ck].GetID(); ), vyhledají se všechny suroviny (NajdiSuroviny(producenti,bod.predchudci);), stanoví se správný stupeň vrcholu (bod.stupen = bod.predchudci.size(); ). Poté se ještě projde celý seznam vrcholů a naplní se vektory následníků (pro každý vrchol x se projde seznam surovin a odpovídajícím vrcholům se do vektrou indexy_nasl přidá x.ID). Získám vektor body, který obsahuje všechny vrcholy a potřebné informace. V druhém kroku se najdou všechny vrcholy (resp. jejich idnexy do vektoru body), do nichž nevede žádná hrana, a přidají se do fronty nulove: for(size_t ck=0; ck
pak už se stále z fronty odebírá 1. prvek, odpovídající vrchol se přidá na konec vektoru serazene a jeho následníkům v grafu se sníží stupeň o 1 (pokud se tak stupeň vrcholu dostane až na nulu, přidá se do fronty nulove), až dokud není fronta prázdná. Následně se zkotroluje, jestli se podařilo uspořádat všechny komodity, a pokud ano, tak se do vektoru ceny dají komodity ve "správném" pořadí (v serazene jsou pouze indexy odpovídajících vrcholů), kterým se parametr spotreba nastaví na -1 jako příznak toho, že jejich "výrobní náklady" ještě nebyly stanoveny. Pro takto vhodně seřazené komodity už se můžou hledané konstanty vypočítat pomocí funkce void SpoctiNaklady(std::vector<Jednotka>& jed, std::vector& ceny)
"Teoretické minimální výrobní náklady" se počítají tak, že zprůměrují výrobní náklady všech jednotek, které danou komoditu produkují. Výrobní náklady pro jeden daný typ jednotky počítá funkce double NakladJ(Jednotka* jedn, std::vector& ceny)
vyzkouší se každý výrobní vzorec, spočítají se teoretické náklady při jeho použití a jako výsledek se vrátí průměrné nalezené náklady. Náklady při použití zvoleného vzorce se vypočítají jednoduše - náklady na pracovní sílu jsou známé (celková odměna dělníkům se vydělí objemem produkce) a k těm se pouze připočtou "výrobní náklady" všech surovin (a protože suroviny byly v grafu "před" produktem, měly by jejich "výrobní náklady" už být stanovené).
Vykreslování mapy O vykreslování výřezu mapy, ať už v editoru map, nebo v prostředí simulace, se stará třída DrawPanelPainter. Jak EditPanel tak SimPanel obsahují členskou proměnnou DPP tohoto typu a také členskou proměnnou wxPanel* Draw_panel1. Třída reprezentující aplikaci (MyApp) má dvě veřejné členské proměnné typu ukazatel na wxPanel : wxPanel *drPanelS a wxPanel *drPanelE. Ty obsahují ukazatele na panely Draw_panel1 třídy SimPanel, resp. EditPanel. Každá instance třídy DrawPanelPainter v sobě obsahuje malou bitmapu pro každý druh čtverce, které se při vytváření objektu musí nahrát z disku. Pro každý typ terénu (kromě louky) existuje 16 variant bitmapy - podle toho, s jakými typy terénů čtverec na mapě sousedí. Pokud na některé straně sousedí s jiným typem terénu, je na příslušné bitmapě na této straně přechod přes úzký pás trávy, aby přechody mezi čtverci nebyly tak ostré. Pro vykreslení mapy má třída dvě základní metody: void Repaint(wxPoint off, Mapa* map=0, bool clear=false, bool jenMC=false); void RepaintRect(wxPoint off, wxRect region, Mapa* map=0);
zatímco Repaint překreslí celý výřez mapy (a pokud je parametr clear roven true, potom nejprve překreslí i pozadí), RepaintRect překreslí jen ty čtverce, které zasahují do vybraného regionu. Obě funkce nejprve provedou kontrolu (jestli se správně načetly textury a jestli je v argumentu vůbec nějaká mapa k vykreslení), poté podle stavu aplikace (wxGetApp().stav) určí, na který panel se bude kreslit, potom projdou všechny čtverce určené k vykreslení a na správný panel se vykreslí příslušná textura. Aby se předešlo efektu blikání a postupného překreslování, je použito bufferované vykreslování kreslí se pomocí třídy wxBufferedDC. Když jsou vykresleny všechny čtverce, vykreslí se cesty - pouze pomocí čar (wxClientDC.DrawLine()) a až nakonec města. Existují 3 typy obrázků pro města, 1 pro města do 800 obyvatel, druhý pro města s 800 - 2000 obyvateli a poslední pro města s více než 2000 obyvateli. Vykreslování karavan v režimu hry zajišťuje funkce void PaintKaravany(wxPoint off, Mapa* map=0).
Plánování výroby Plán výroby pro volný podnik se stonovuje pomocí void Mesto::NaplanujVyrobu(Zavod& Z). Tato metoda třídy Mesto projde přes všechny výrobní vzorce a pro každý z nich najde nejvýnosnější plán: NajdiNejPlan(Z, i, &aktual_jednotek, &aktual_zisk);
kde Z je místní podnik, i číslo zkoumaného vzorce. Ve třetím a čtvrtém parametru tato metoda vrátí optimální počet zapojených jednotek a dosažený zisk. Tyto hodnoty pro všechny vzorce se porovnají a vzybere se ten nejvýnosnější. Optimální počet jednotek se určí půlením intervalu - to provádí členská funkce int Mesto::NejPocetJ(const Zavod& Z, int prod_cena, int cv, int a, int b, int *zisk)
kde prod_cena je nákupní cena produktu na místním trhu, cv číslo zkoumaného vzorce a a a b hranice intrvalu. Poslední parametr slouží k získání celkového zisku. Funkce vrátí optimální počet jednotek.
Určení velikosti zásilky Obecný postup při výběru města pro export nějaké komodity byl popsán ve třetí kapitole. Zde je přesněji popsáno, jak se počítá, kolik zboží se má odesílat. Hledat optimální hodnotu půlením intervalu by mohlo být příliš náročné (narozdíl od velikosti podniku na množství zboží na trhu není horní limit). "Obchodník" si musí vystačit s odhadem. "Obchodník" logicky nakupuje zboží pro karavanu do té doby, dokud je místní nákupní cena nižší, než očekávaná cena, za kterou zboží prodá. Zkoušet takto nakupovat po jednotlivých kusech je sice nesmyslné, ale "obchodník" může zkoušet nakupovat i po větších kvantech (za cenu ztráty úplně optimálního výsledku). Zde zvolenené kvantum je 1/20 aktuálního množství daného zboží na místním trhu. Tak se za nejvýše 20 kroků zjistí množství zboží k odeslání. Tento výpočet provádí funkce int Mesto::UrciVelikostZasilky(Mesto* cil, long ID, int mnozstvi, double sp)
proměnná double cast určuje, kolik zboží má zůstat ve městě (tedy se pošle (1-cast)*mnozstvi_na_trhu). Začne se na hodnotě 0,95 a postupně se odečítá 0,05 a zjišťuje se, jaká bude prodejní cena na místním trhu po odkoupení odpovídajícího množství zboží. Pokud nová prodejní cena přesáhne nákupní cenu v cílovém městě, tak už se "obchodníkovi" nevyplatí nakupovat - a stačí se vrátit k předchozímu kroku: return (int) ( (1-(cast+0.05))*mnozstvi );
Obchodníci jsou dále omezeni v nákupu tím, že nemůžou vykoupit více než 50% všeho zboží na trhu ani tolik, aby zbylo zásob na méně než 2 týdny. Pouze v případě, že v cílovém městě je absolutní nedostatek
daného zboží (na trhu není ani jeden kus) může tato omezení "obchodník" obejít a poslat až 2/3 všeho zboží na trhu. Pozn.: Tato umělá omezení bylo nutné přidat proto, že se neupravený "hladový" algoritmus nechová příliš chytře a realisticky. Pokud totiž v nějakém městě dojde k absolutnímu nedostatku nějakého zboží, "obchodníci" sousedních měst by s vidinou velkého zisku poslali všechno zboží z místního trhu (a tím by nastal absolutní nedostatek v dalších městech).
Rozvoj města V každém kroku simulace dochází k rozvoji měst: roste/klesá počet obyvatel a rozšiřují se existující závody. Všechny změny obstarává funkce void Mesto::Rozvoj() Změna populace Pokud je poslední týdny absolutní nedostatek nadpoloviční většiny potravin, dojde ke HLADOMORU. Dostatek potravin hlídá proměnná do_hladomoru, jejíž hodnota poklesne vždy, když je aktuální nedostatek vetšiny potravin, v opačném případě její hodnota vzroste. Pokud hodnota klesna na 0, vypukne hladomor. Počet obyvatel se sníží o 30 za každé kolo, po které hladomor přetrvává. Obyvatel může ubývat i vlivem nespokojenosti; pokud je spokojenost menší než 75% , ubyde 5 - 10 obyvatel. Naopak v případě dlouhodobého dostatku potravin a volné ubytovací kapacity počet obyvatel roste. Pokud je však nezaměstnanost vyšší než 30%, k nárustu nedojde. Velikost přírůstku je 0 - 10 obyvatel + bonus za spokojenost, kde bonus = spokojenost - 79. Takže při spokojenosti 86% přibyde 7-17 obyvatel. Nárůst ubytovací kapacity Aby byla postavena nová obydlí, musí být splněno několik požadavků: - využití současné kapacity musí být nad 92% (aby se nestavělo zbytečne) - nesmí být překročena max. kapacita (1.1 * max. velikost města, která je standartně 4000) - ve městě musí být dlouhodobý dostatek stavebnin (zjistí se pomocí bool DostStavebnin() ) Pokud jsou tyto kritéria splněna a na trhu je dostatek surovin, které jsou uvedeny ve vzorci rozvoje modelu obyvatelstva, dojde k rozšíření kapacity o 10 míst. Rozvoj podniků Jak se vyvíjí město, roste počet obyvatel, je třeba i rozšiřovat výrobní kapacity podniků. K tomu ale dojde jen s 50% pravděpodobností a jen v případě, že je ve městě dlouhodobý dostatek stavebnin. V jednom kroku se může rozšířit pouze 1 podnik - a to ten, který byl v posl. týdnech nejčastěji vytížený na 100%. Pokud je víc podniků, které byly stejně často využívané na svou maximální kapacitu, vybere se náhodně jeden z nich. Pokud je ve městě dostatek volných pracovníků, rozšíří se vybraný podnik o 1 elementární ek. jednotku. Pokud naopak dojde k úbytku lidí, ať už vlivem hladomoru nebo díky zásahu hráče, zavře se náhodné množství náhodně vybraných podniků tak, aby počet práceschopných obyvatel nebyl nižší, než počet zaměstnaných. Pozn.: Nedochází k zakládání nových závodů! Pouze se rozšiřují ty stávající.
Watchery Ačkoliv si města zaznamenávají historii vývoje cen a množství všech typů komodit na trhu, je vhodné mít nástroj pro datailní sledování vybraných položek trhu - watcher. Watcher umožňuje sledovat údaje o prodejní a nákupní ceně, množství zboží na místním trhu a místní produkci 12 týdnů nazpět. Watcher reprezentuje třída Watcher: class Watcher { public:
Watcher(); Watcher(const Watcher& W); ~Watcher(); int GetPrumerProdej() const; int GetPrumerNakup() const; int GetPrumerMnozstvi() const; int GetPrumerProd() const; int IDW; long IDK; wxString jmenoKomodity; wxString jmenoMesta; int prod_ceny[13]; int nak_ceny[13]; int mnozstvi[13]; int produkce[13]; int MX; int MY; // souradnice mesta -> pro vyhledavani void Aktualizovat(int prodej, int nakup, int mnozstvi, int produkce); // doda aktualni info };
pole prod_ceny, nak_ceny, mnozstvi a produkce slouží k zaznamenávání sledovaných údajů. IDK je id sledované komodity a IDW unikátní identifikátor watcheru, přidělovaný samotnou apikací pomocí funkce wxGetApp().GetIDW(). Watcher je napevno svázán s jedním typem zboží v jednom městě - souřadnice města na mapě obsahují proměnné MX a MY. Funkce GetPrumer... slouží k výpočtu průměrných hodnot. Funkce Aktualizovat naplňuje watcher novými údaji, které se zapíší na 1. pozici v příslušném poli (nejstarší údaj z pole vypadne). Seznam všech aktuálních watcherů je uložen v členské proměnné watchery třídy SimPanel typu std::vector<Watcher>. Aktualizaci sledovaných údajů zabezpečuje veřejná metoda void AktualizujWatchery(). Ta projde celý seznam watcherů a každému dodá nové údaje: void SimPanel::AktualizujWatchery() { for(size_t i=0; i<watchers.size(); ++i) { // pro kazdy Watcher najit mesto Mesto* M = FindMesto(mapa->mesta,watchers[i].MX, watchers[i].MY); long idk = watchers[i].IDK; watchers[i].Aktualizovat(M->CenaProdej(idk),M->CenaNakup(idk),M->PocetNaTrhu(idk), M->GetAktualProdukce(idk)); } } Je nutné zajistit, aby se při každém kroku simulace tato metoda třídy SimPanel volala právě jednou!
Pokud na komoditu není nastavem watcher, tyto údaje si aplikace v dalším kroku neuchovává. Nastavení wacheru tedy neovlivňuje pouze prezentaci dat (údaje o kterých jednotkých se budou vypisovat), ale samotný sběr dat, přičemž každému jednotlivému watcheru je třeba aktualní údaje dodat pomocí jeho metody void Aktualizovat(int prodej, int nakup, int mnozstvi, int produkce). Pozn.: Maximální počet watcherů je z důvodu rychlosti běhu simulace omezen konstantou v souboru EcoSPanel.h: const int MAX_WATCHERS = 500;
Ostatní sledované údaje Aplikace si - ať už v režimu simulace, nebo v režimu hry - uchovává některé údaje bez nutnosti nastavovat watchery - a některé z nich i prezentuje uživateli.
Každé město si uchovává informace o importu a exportu každého druhu zboží za poslední týden. K tomu slouží členská proměnná std::vector obchod, která pro každý druh komodity obsahuje objem importu a exportu. Na začátku kroku každého města se hodnoty vynulují. Když se aktualizuje fronta příchozího zboží, se identifikuje každé zboží, které pochází z jiného města, a objem takové zásilky se zaznamená. Export se zjišťuje v průběhu nákupu obchodníků, kteří zajišťují meziměstský obchod. Každý druh zboží může být poslán nanejvýš do jednoho sousedního města. Pokud se tomu tak skutečně stane, objem zásilky se zaznamená jako export Město si dále pamatuje, zda v něm byl dostatek stavebnin. Pole bool dost_stavebnin[MAX_HISTORY] uchovává informaci o dostatku (hodnota true znamená, že byl daný týden dostatek stavebnin, false znamená nedostatek). Toéto pole ve skutečnosti funguje jako cyklická fronta, členská proměnná history_uk udává aktuální index zápisu. Na podobném principu funguje i uchovávání informací o střední ceně a množství všech druhů zboží na místním trhu v poli historie history[MAX_HISTORY]. Rozdíl je ten, že toto je pole vektorů. Konstanta MAX_HISTORY udává počet týdnů, po který se mají data uchovávat, a její standartní hodnota je 5. Pro sledování dostatku/nedostatku potravin ve městě slouží členská proměnná int do_hladomoru. Její hodnota se může pochybovat v rozmezí 0-4, přičemž 4 znamená dlouhodobý dostatek jídla, 0 indikuje vypuknuvší hladomor. Tato proměnná se aktualizuje po skončení kroku nákupu lidí - pokud byl nedostatek nadpoloviční většiny potravin, její hodnota se sníží. Pro každou komoditu je sledován celkový objem výroby na mapě. Tyto údaje jsou uchovávány 25 týdnů nazpět a je z nich vypočítáván dlouhodobý průměr. Pro tyto účely má třída Simulace členskou proměnnou std::vector<pole10> produkce_hist. pole10 je v podstatě pole 25 hodnot s přidanou metodou long GetPrumer(int cislo_tydne) const.
V režimu hry má hráč možnost zobrazit si přehled své obchodní bilance. Objeví se mu přehled za poslední dva roky (rok~52 týdnů), který obsahuje celkový zisk z prodeje a celkové výdaje rozepsané podle druhu (nákup zboží, náklady na uskladnění, náklady na provoz karavan a nákup nových karavan). Statistiku udržují ve třídě Game členské proměnné stat_letos, stat_loni a stat_all typu StatistikaPenez. Všechny údaje se uchovávají jako absolutní hodnoty. Aktualizace statistik probíhá uvnitř funkce bool Game::AddAcc(long sum, TYPT TT), která představuje jediný způsob, jak změnit stav v hráčově pokladně. Výčtový typ, uvedený jako druhý argument, identifikuje druh transakce a podle tohoto druhu se částka zapíše do příslušné položky statistiky. Po dosažení každého 52. týdne se hodnoty letošní statistiky okopírují do loňské a hotnoty letošní se vynulují (začíná nový rok).
Spokojenost obyvatel Výchozí hodnota spokojenosti obyvatel ve městě je 80%. Následující tabulky uvádějí vliv různých faktorů na spokojenost: Dostatek / nedostatek jídla hodnota proměnné do_hladomoru
vliv na spokojenost
0
-10
1
-4
4
+1
Nezaměstnanost nezaměstnanost
vliv na spokojenost
0 - 5%
+5
5% - 10%
+2
10% - 20%
0
20% - 30%
-1
30% - 40%
-3
40% - 100%
-5
procento využití ubytovací kapacity
vliv na spokojenost
98 - 100
-5
95 - 98
-3
92 - 95
-1
60 - 92
+1
≤ 60
0
Využítí ubytovací kapacity
Dostatek základních a luxusních komodit základní
luxusní
poměr dost. / nedost.
vliv na spokojenost
poměr dost. / nedost.
vliv na spokojenost
žádné nedostatkové
+6
žádné nedostatkové
+4
≥4
+4
>2
+3
z intervalu (3,4]
+3
z intervalu [1,2]
+1
z intervalu (2,3]
+2
z intervalu [0,5;1)
-1
z intervalu (1, 2]
0
<0,5
-2
z intervalu (0,5;1]
-1
≤ 0,5
-2
Výpočet spokojenosti provádí členská funkce void Mesto::CountSpokojenost().
Příloha A Formát souborů Konvence ID_komodity “, cesty: [ ]
… … …
[ ],
…
takto jsou uvedeny jména parametrů (za která se dosazují hodnoty) tímto fontem jsou uvedeny znaky, které skutečně v souboru mají být seznam, položky uvedené uvnitř závorek se mohou opakovat (závorky se neuvádějí!) seznam, kde jednotlivé položky jsou odděleny znakem “,“
všechna jména musí být jednoslovná, desetinná čísla se píší s des. Tečkou ( tedy 2.25 a ne 2,25) Terén Existuje 6 typů terénu (voda, louka, pláň, les, poušť, kopce). Do souborů se zapisují jako celá čísla podle následující tablky: 1 2 3 4 5 6 0
… … … … … … …
voda louka pláň les poušť kopce nic
Číslo 0 by se nemělo objevit v popisu čtverce mapy. Používá se v seznamu ek. jednotek – pokud nějaká jednotka nemá žádné požadavky na terén, zapíše se na místo cislo_vyzadovaneho_terenu právě 0. Číslování čtverců mapy Čtverce na mapě jsou očíslovány od nuly od levého horního rohu po řádkách (pravý dolní čtverec má tedy číslo (m*n)-1, kde m a n jsou rozměry mapy). Pro převod ze souřadnic platí násl. vzorec: č. čtverce [x,y] = (y-1)*m + x - 1
kde m je x-ovy rozměr mapy.
* Soubor modelu komodit (obyvatelstva) Tento soubor má vlastně dvě části. První, seznam komodit a seznam jednotek, zatímco druhá část, specifikuje podrobnosti modelu obyvatelstva. Obě části musí být uvedeny, ačkoli seznamy v druhé části mohou být prázdné. [
# ID_komodity jmeno_komodity spotreba_komodity_na_100_obyvatel ]
[ @ ID_jednotky jmeno_jednotky ID_produktu pocet_pracovniku cislo_vyzadovaneho_terenu [ delka_produkce vyprodukovane_mnozstvi “ [ ID_suroiviny mnozstvi_suroviny ] “ ] , ] $ $ $ $
[ ID_zakladnich_komodit ] [ ID_potravin ] [ ID_stavebnin ] “ [ ID_suroiviny_pro_rozvoj mnozstvi_suroviny_pro_rozvoj ] “
* Soubor mapy Soubor mapy obsahuje na prvním místě obě části modelu komodit, přestože část o modelu obyvatelstva mapa samotná nevyužívá. Následuje seznam čísel udavající typ terénu na jednotlivých čtvercích, následně seznam měst a seznam cest a nakonec seznam čtverců s extra kvalitou. [ # ID_komodity jmeno_komodity spotreba_komodity_na_100_obyvatel ] [ @ ID_jednotky jmeno_jednotky ID_produktu pocet_pracovniku cislo_vyzadovaneho_terenu [ delka_produkce vyprodukovane_mnozstvi “ [ ID_suroiviny mnozstvi_suroviny ] “ ] , ] $ $ $ $
[ ID_zakladnich_komodit ] [ ID_potravin ] [ ID_stavebnin ] “ [ ID_suroiviny_pro_rozvoj mnozstvi_suroviny_pro_rozvoj ] “
# X-ovy_rozmer Y-ovy_rozmer [ typ_terenu_na_i-tem_ctverci ] [
# x-ova_souradnice y-ova_souradnice jmeno_mesta pocet_obyvatel kapacita “ [ ID_jednotky pocet_jednotek] “ ]
cesty: extra:
[ * [ ctverec_cesty ] * ] [ extra_ctverce ]
* Soubor se hrou Soubor s uloženou hrou obsahuje podobně jako soubor mapy na prvním místě obě části modelu komodit. Následují informace o mapě v podobném formátu, jako je tomu u souboru s uloženou mapou, pouze města jsou doplněna informací o počtu zboží na trhu. Následuje seznam hráčových karavan, poté seznam skladů a na konec statistické informace. [ # ID_komodity jmeno_komodity spotreba_komodity_na_100_obyvatel ] [ @ ID_jednotky jmeno_jednotky ID_produktu pocet_pracovniku cislo_vyzadovaneho_terenu [ delka_produkce vyprodukovane_mnozstvi “ [ ID_suroiviny mnozstvi_suroviny ] “ ] , ] $ $ $ $
[ ID_zakladnich_komodit ] [ ID_potravin ] [ ID_stavebnin ] “ [ ID_suroiviny_pro_rozvoj mnozstvi_suroviny_pro_rozvoj ] “
# X-ovy_rozmer Y-ovy_rozmer [ typ_terenu_na_i-tem_ctverci ] [
# x-ova_souradnice y-ova_souradnice jmeno_mesta pocet_obyvatel kapacita
“ [ ID_jednotky pocet_jednotek] “ [ pocet_i._komodity_na_trhu ]
]
cesty: [ * [ ctverec_cesty ] * ] extra: [ extra_ctverce ] karavany:
sklady:
[ * jmeno x-ova_startu y-ova_startu x-ova_cile y-ova_cile x-ova_aktualni y-ova_aktualni dorazi pohyb kapacita nalozno “ [ jmeno_zbozi ID_zbozi mnozstvi pr_cena celkova_cena ] “ “ posloupnost_souradnic_planu_cesty “ * ] [ * x-ova_souradnice y-ova_souradnice “ [ jmeno_zbozi ID_zbozi mnozstvi pr_cena celkova_cena ] “ * ]
stat_letos: nakup prodej naklady_na_karavay naklady_za_sklady naklady_za_nove_karavany stat_loni: nakup prodej naklady_na_karavay naklady_za_sklady naklady_za_nove_karavany stat_all: nakup prodej naklady_na_karavay naklady_za_sklady naklady_za_nove_karavany uz_vyhral (0~false, 1~true) stav_v_pokladne cilova_castka tyden
Neukládají se informace o nastavených watcherech.
* Konfiguračního soubor EcoS.ini Jedná se o typ .ini souboru. Jeho formát je následující: jméno_parametru = hodnota_parametru
kde jmeno je jednoslovny řetězec (seznam vic níže) a povolené hodoty jsou pouze celá čísla.Formát dovoluje také jednořádkové komentáře: vše uvedené za znakem “;“ je ignorováno. [ hlavicka ] funguje jako nadpis sekce (obsah je opět igorován). Nejsou dovoleny prázdné řádky. Seznam úváděných parametrů a význam hodnot: Jméno parametru
Význam hodnoty
Výchozí hodnota
Procento_pracujicich
kolik procent obyvetel je práceschopných
25
Max_obyvatel
horní limit na počet obyvatel ve městech
4000
Cena_prace
cena práce 1 člověka za týden
10
Prace_navic
o kolik % stoupne cena práce při výrobě nad kapacitu
50
Extra_bonus
Jaka bude vyroba (v % normaliho stavu), pokud se započítá bonus za extra kvalitní čtverec
140
GAME_STEP
Délka 1 kroku v režimu hry v ms
8000
Délka 1 kroku simulace v ms
2000
SIM_STEP