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}; 4 stavy, v jakém se aplikace může nacházet class MyApp : public wxapp jednou instanciovaná třída představující aplikaci virtual bool OnInit() funkce volaná při startu (jako main u C programu) int GetIDW() vrátí vhodné ID pro nový watcher Simulation* simulation ukazatel na probíhající simulaci Game* game ukazatel na hru Konstanty konstanty položka obsahující konstanty z.ini souboru STAV stav stav, ve kterém se aplikace aktuálně nachází EcoSFrame* e_frame ukazatel na hl. rám aplikace void NactiKonstanty(Konstanty& k) načte konstanty ze souboru EcoS.ini EcoSAbout.h: class EcoSAbout : public AboutDlg okno s About dialogem EcoSFrame.h: class EcoSFrame : public MainFrame rám okna aplikace EcoSFrame( wxwindow *parent = NULL, int id = -1 ); konstruktor, vytvoří všechny panely a 1 zobrazí void Vyhra() v režimu hry po dosažení cílové částky zobrazí dialog o výhře void DisableSimMenu() zneaktivní položky v submenu "Simulace" void DisableMapMenu() zneaktivní položky v submenu "Mapa" void DisableGameMenu() zneaktivní položky v submenu "Hra", s výjimkou položky "Načíst" void EnableGameMenu() zneaktivní položky v submenu "Simulace" std::vector<komodita>* komodity; ukazetel na vektor komodit (hl. panelu) std::vector<jednotka>* jednotky; ukazetel na vektor jednotek (hl. panelu) Obyvatelstvo* obyvatelstvo; ukazetel na obyvatelstvo (hl. Panelu) EcoSPanel* main_panel ukazatel na panel editoru komodit SimPanel* sim_panel ukazetel na panel simulace EditPanel* edit_panel ukazetel na panel editoru map GamePanel* game_panel ukazatel na panel hry MySMDlg* sm_dlg ukazatel na dialog "Statistika mapy" wxtimer m_timer časovač, stopky, které odměřují kroky simulace void OnPlayB(wxCommandEvent &event) spuštění simulace ( stopky se rozjedou ) void OnStopB(wxCommandEvent &event) zastavení simulace ( stopky se zastaví ) bool konec_hry proměnná udávající, zda je zobrazen dialog o výhře EcoSPanel.h: class Aktualizovatelne abs. předek pro dynamická (aktualizovaná) okna virtual void Aktualizovat()=0 abs. metoda pro aktualizování obsahu okna class EcoSPanel : public MainPanel panel editoru modelu komodit a obyvatelstva void ShowKomodity() vypíše informace o komoditách void ShowKomodity() vypíše informace o jednotkách void ShowObyvatelstvo() vypíše informace o modelu obyvatelstva std::vector<komodita> komodity; vektor komodit modelu std::vector<jednotka> jednotky; vektor ek. Jednotek modelu Obyvatelstvo obyvatelstvo; část modelu týkající se obyvatelstva void OnMM( wxcommandevent& event ) načte model z mapy v editoru map void OnEditK( wxcommandevent& event) editace vybrané komodity void ChangeK(int select=-1) změní vybranou komoditu nebo přidá novou OnEditVV( wxcommandevent& event ) otevře dialog editace vzorce pro rozvoj void DelKom_O(long ID, int zkama) smaže komoditu z vybrané části modelu obyvatelstva void AddKom_O(long ID, int kam) přidá komoditu do vybrané části modelu obyvatelstva void ShowDetailK( wxcommandevent& event ) vypíše datail vybrané komodity void ShowDetailJ( wxcommandevent& event ) vypíše datail vybrané jednotky void ShowVzorce(Jednotka& jedn) vypíše výrobní vzoerce vybrané jednotky void ShowObdelnikO(wxTextCtrl* m_tctrl, std::vector<long>* u_vektor) vypíše vybranou část modelu obyvatelstva bool GetIdJedn(long& ID) v parametru vrátí id vybrané komodity, vrátí TRUE v případě úspěchu
class SimPanel: public SimPanel2 panel prostředí simulace Mapa* mapa ukazatel na mapu simulace wxpoint offset souřadnice posunutí výřezu mapy wxtimer m_timer časovač sloužící pro automatický refresh DrawPanelPainter DPP položka, která se stará o vykreslení výřezu s mapou std::vector<mydmsim*> okna vektor (dynamických) oken s detaily měst std::vector<mydcdlg*> okna_cest vektor (dynamických) oken s detaily cest std::vector<myhdlg*> okna_hist vektor (dynamických) oken s historií měst std::vector<watcher> watchers vektor watcherů MyWDlg* watch_dlg okno watcherů 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 IDK) bool maz; udává, jestli je aktivován "mód mazání" bool ruka; udává, jestli je aktivován "mód výběru" TEREN teren aktuálně vybraný typ terénu wxpoint lastmouseposition čtverec výřezu, na kterém nastala posl. událost myši class GamePanel: public SimPanel2 panel prostředí hry Mapa* mapa ukazatel na mapu hry wxpoint offset souřadnice posunutí výřezu mapy wxtimer m_timer časovač sloužící pro automatický refresh DrawPanelPainter DPP položka, která se stará o vykreslení výřezu s mapou std::vector<mydmsim*> okna vektor (dynamických) oken s detaily měst std::vector<mydcdlg*> okna_obch vektor (dynamických) oken pro obchodování std::vector<myhdlg*> okna_hist vektor (dynamických) oken s historií měst std::vector<watcher> watchers vektor watcherů MyWDlg* watch_dlg okno watcherů aktualizuje obsah hl. panelu a otevřených dynamických void UpdatePanel(bool no_repaint=false) 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 IDK) class EditPanel: public EditPanel2 panel editoru map Mapa* mapa ukazatel na mapu simulace
wxpoint offset souřadnice posunutí výřezu mapy wxtimer m_timer časovač sloužící pro automatický refresh int rozmer strana čtverce editovaného území TEREN teren aktuálně vybraný typ terénu bool ruka udává, jestli je aktivován "mód výběru" bool cesta udává, jestli je aktivován "mód přidávání cesty" bool maz udává, jestli je aktivován "mód mazání" bool extra udává, jestli je aktivován "mód extra kvality" Cesta nova_cesta nově stavěná cesta double cena_cesty cena nové cesty wxpoint lastmouseposition čtverec výřezu, na kterém nastala posl. událost myši void NovaMapa(int sirka, int vyska) vytvoří novou mapu zadaných rozměrů void PaintMM() vykreslí minimapu void OnTimer(wxTimerEvent& event) refresh okna, zastaví časovač wxpoint SpoctiCtverec(wxPoint souradnice) spočítá skutečné souřadnice čtverce na mapě void OnMestoCh( wxcommandevent& event) vypíše detaily vybraného města void OnChM( wxcommandevent& event) provede změnu detailů vybraného města void VypisSousedy(const Mesto& M, vector<mesto>& mesta) vypíše všechny sousedy daného města void OnGenerate( wxcommandevent& event) generování náhodné mapy void OnEditM( wxcommandevent& event) zobrazí okno s detaily města void OnBil( wxcommandevent& event) zobrazí okno s celkovou bilancí mapy void P(wxPaintEvent& event) obsluha události vykreslení void Ovlivneno(vector<wxPoint>& ctverce, wxpoint bod) v parametru vrátí vektor ovlivněných čtverců (podle nastavených rozměrů 1x1-5x5) bool JeNaMape(wxPoint ctverec) vrátí TRUE, pokud jsou dané souřednice uvnitř mapy double SpoctiCenu(wxPoint p1, wxpoint p2) spočítá cenu přechodu mezi 2 sousedními čtverci class MyEVVDlg: public EVVDialog dialog editace vzorečku rozvoje class MyNMDlg: public NMDialog dialog zadávání rozměrů nové mapy class MyNGDlg: public NGDialog dialog zadávání parametrů při startu hry class MyNJDlg: public NJDialog dialog editace (resp. přidání nové) jednotky MyNJDlg(wxWindow* parent, std::vector<jednotka>* konstruktor, pokud se přidává nová jednotka _v_jedn) MyNJDlg(wxWindow* parent, std::vector<jednotka>* konstruktor v případě editace stávající jednotky _v_jedn, Jednotka* _J) class MyDMDlg: public DMDialog dialog detialu města (pro editor map) class MyDMSim: public DMDialogSim, dialog datailu města (pro prostředí simulace) public Aktualizovatelne class MyDMGame: public DMDialogGame, dialog detailu města (pro prostředí hry) public Aktualizovatelne class MyOGame: public ODialog, dialog pro obchodování public Aktualizovatelne void OnSell( wxcommandevent& event) obsluha prodávání hráče void OnBuy( wxcommandevent& event) obluha nakupování hráče void VypisPanely() vypsání tabulek s detaily o stranách transakce class MyKPDlg: public KPDialog, dialog přehledu karavan public Aktualizovatelne class MyKNDlg: public KNDialog, dialog koupě nové karavany public Aktualizovatelne class MyHDlg: public HDialog, dialog stručné historie města public Aktualizovatelne class MyBMDlg: public BMDialog dialog celkové bilance mapy class MySMDlg: public SMSialog dialog celkové statistiky mapy - pro simulaci class MySKDlg: public SKDialog, dialog "Statistiky komodit" public Aktualizovatelne class MyDialog4 : public wxdialog okno s výzvou pro uložení stávajícího modelu class MSDialog : public wxdialog okno se "Splnitelností" modelu class MyDCDlg: public DCDialog, dialog datailu cesty (pro prostředí simulace) public Aktualizovatelne class MyWDlg: public WDialog, okno s watchery public Aktualizovatelne class MyVyhraDlg: public VyhraDialog dialog zobrazující se při dosažení cílové částy class MyOBDlg: public OBDialog, dialog "Obchodní statistiky" pro režim hry public Aktualizovatelne
wxpoint SpoctiNovyOffset(wxPoint bod, int X, int Y) spočítá nový offset tak, aby byl daný čtverec co možná nejblíž prostředku výřezu (pozice [13,10] ) void PaintMiniM(Mapa* mapa, wxpanel* minimap, vykreslení minimapy na zadaný panel wxpoint offset); void FiltrMesto(Mesto& mesto, wxclientdc& dc, zobrazení filtru "1 komodity" na zadaný panel long IDK, wxpoint offset); map.h: enum SMER { NORTH, EAST, SOUTH, WEST} udává možné směry typedef std::vector<int> Cesta Cesta ~ seznam čtverců class Mapa mapa "světa" std::vector<mesto> mesta vektor měst na mapě std::vector<cesta> cesty vektor cest std::vector<int> extra vektor č. čtverců s extra kvalitou TEREN* data pole s typem terénu pro každý čtverec std::vector<komodita> komodity použité komodity std::vector<jednotka> jednotky použité jednotky void AlokujData(int pocet) vyhradí potřebnou paměť na terén void SmazCestu(int start, int cil) smaže cestu, která začíná a končí na těchto čtvercích void PridejCestu(const Cesta& C) přidá cestu do svého vektoru void InitTrhy() inicializuje trhy všech svých měst (na začátku simul.) bool DeleteXY(wxPoint PT) smaže město nebo cestu na čtverci PT bool DeleteMesto(wxPoint PT) smaže město na čtverci PT, vrátí TRUE, pokud tam nějaké město bylo bool DeleteCestu(wxPoint PT) smaže cestu na čtverci PT, vrátí TRUE, pokud tam nějaká cesta vedla void ZmenExtra(wxPoint PT) normálnímu čtverci dá extra kvalitu, extra kvalitní vrátí k normálu int GetX() const vrátí x-ový rozměr mapy int GetY() const vrátí y-ový rozměr mapy TEREN GetTeren(wxPoint pt) const vrátí typ terénu na daném čtverci int Naalokovano() const vrátí počet naalokovaných čtverců const std::vector<komodita>* GetPK() const vrátí (konstantní) ukazatel na vektor komodit const std::vector<jednotka>* GetPJ() const vrátí (konstantní) ukazatel na vektor jednotek const std::vector<mesto>* GetPM() const vrátí (konstantní) ukazatel na vektor měst const std::vector<cesta>* GetPC() const vrátí (konstantní) ukazatel na vektor cest void Step() provede krok simulace bool Sousedi(int poradi1, int poradi2) const vrátí TRUE, pokud jsou čtverce přímí sousedi TEREN GetTerenSoused(int poradi, SMER sm) vrátí typ terénu souseda v daném směru double CenaCesty(Mapa* M, Cesta& C) spočítá a vrátí cenu cesty C (v poh. bodech) void Generuj(Mapa* M, int hustota) vygeneruje mapu se zadanou četností vody void NactiMapu(wxTextInputStream& text, Mapa& M) načte mapu z uvedeného zdroje void UlozMapu(wxTextOutputStream& text, const uloží mapu do uvedeného zdroje Mapa& M) bool KontrolaMapy(Mapa& M) provede kontrolu mapy, TRUE pokud nenajde chybu bool MestoNaVode(Mapa& M) TRUE, poku nějaké město stojí na vodě bool CestaNaVode(Mapa& M) TRUE, pokud nějaká cesta vede po vodě bool ChybnaJednotka(Mapa& M) TRUE, pokud je chybná vazba jednotky na terén bool JeKolemVolno(Mapa* M, wxpoint kde) TRUE, pokud v okolí bodu kde není město bool JeExtra(const Mapa* M, wxpoint bod) TRUE, pokud je bod "extra kvalitní" bool JsouSousedi(wxPoint p1, wxpoint p2) zjistí, jestli jsou 2 čtverce sousedé bool JeNaMape(const Mapa* const M, wxpoint kde) vrátí TRUE, pokud je čtverec uvnitř mapy *M Mesto* FindMesto(std::vector<Mesto>& mest, vrátí ukazatel na město, které leží na zadaných int pozicex, int pozicey) souřadnicích, vrátí 0, pokud tam žádné město není unsigned int GetBitSousedy(const Mapa* M, int X, vrátí bitovou masku reprezentující typy terénů, které int Y, bool extra=false) jsou v přímém okolí k nalezení wxstring TerenToStr(TEREN T) 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 OnLeave(wxMouseEvent& evt) void OnMouse(wxMouseEvent& evt) void OnMouseR(wxMouseEvent& evt) void OnDblClick(wxMouseEvent& evt) void OnMouseUp(wxMouseEvent& evt) void OnMotion(wxMouseEvent& evt) void 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, typy transakcí, které lze provádět NAKLAD_SKLADU, NEW_KAR}; class Konstanty třída obsahující v sobě konstanty ze souboru EcoS.ini int PROCENTO_PRACUJICICH podíl práceschopných obyvatel int MAX_OBYVATEL horní limit na počet obyvatel int CENA_PRACE cena práce za 1 tyden int PRACE_NAVIC náklady na nadlimitní výrobu int SIM_STEP_MLS délka kroku simulace v ms int EXTRA_BONUS bonus za extra terén int GAME_STEP_MLS délka kroku hry v ms class Zbozi položka skladu nebo karavany wxstring jmenok jméno komodity long IDK id komodity int mnozstvi mnostvi zbozi int prumer prumerna cena za 1ks long cena_celkem celkova cena za vsechny ks dohormady void Prodej(int kolik) odebere zadané množství zboží void Pridej(int kolik, int cenaza1) přídá zadané množství s pr. cenou cenaza1 class Karavana karavana pro hráče int GetMnozstvi(long idk) vrátí množství komodity s id idk v karavaně 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<zbozi> 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> 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<komodita> 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 výpočtu cen (teoretické výrobní náklady) void Step() provede krok simulace void Stop() zastaví simulaci void Play() nastaví příznak běhu simulace class Game: public Simulation třída reprezentující hru void Step() provede krok simulace a navíc odečte náklady long account stav v hráčově pokladnici long target cílová částka bool uz_vyhral TRUE, pokud hráč už jednou dosáhl cílové částky bool AddAcc(long sum, TYPT TT) změní účet o 'sum', TT udává typ transakce, vrátí TRUE, pokud nebude přkročen max. debet std::vector<karavana> karavany hráčovy karavany std::vector<sklad> sklady sklady ve městech wxstring jmeno_vybrane_kar jméno karavany, která je v game-panelu zvýrazněná StatistikaPenez stat_letos statistiké údaje za letošek class Watcher třída představující watcher na komoditu int IDW ID watcheru long IDK ID komodity int MX, int MY souřadnice města void Aktualizovat(int prodej, int nakup, dodá aktuální informace int mnozstvi, int produkce) int prod_ceny[13], int nak_ceny[13], pole sledovaných údajů int mnozstvi[13], int produkce[13] class KomTS pomocná třída pro "topologické setřídění" komodit void SeradKom(const std::vector<komodita>& kom, "topologicky" seřadí komodity tak, aby se na jeden vector<jednotka>& jed, vector<komodita>& ceny) průchod daly spočítat výrobní náklady void SpoctiNaklady(std::vector<Jednotka>& jed, pro (vhodně seřazené) komodity spočítá "výr. náklady" std::vector<komodita>& ceny) void NajdiProducenty(long IDP, vector<jednotka>& v posl. parametru vrátí jednotky produkující danou jed, vector<jednotka*>& prod) komoditu double NakladJ(Jednotka* jedn, vector<komodita>& spočítá teoretické náklady na výrobu v tété jednotce ceny) double NajdiCenu(long ID, std::vector<komodita>& vrátí cenu vybrané komodity ceny) double Cena(double mnozstvi, long IDK, na základě množství na trhu a spotřebě obyvatel spočítá double spotreba) střední cenu komodity void SaveGame(wxTextOutputStream& text) uložení hry void LoadGame(wxTextInputStream& text) načtení hry
tridy.h: class ChybnyFormat třída pro vyhazování vyjímek při I/O operacích class PolozkaHistorie třída pro "zapamatování si" množství a cen komodit typedef std::vector<polozkahistorie> historie historie komodit pro 1 den enum TEREN { nic=0, voda, louka, plan, les, možné typy terénu poust, kopce} class Komodita komodita, zboží long ID, wxstring jmeno, double spotreba parametry komodity class Obyvatelstvo třída reprezentující model obyvatelstva class PolozkaTrhu: public Komodita položka na místním trhu class VyrobniVzorec 1 vzorec, podle kterého mohou jednotky vyrábět class Jednotka ekonomická jednotka (elementární podnik) long ID_jedn, wxstring jmeno, long ID_produkt parametry jednotky VyrobniVzorec vzorecky[max_vzorcu] pole všech dostupných vzorců TEREN potr_teren vyžadovaný typ terénu class Zavod 1 závod ve městě (může se skládat z více jednotek) Jednotka typ typ závodu int pocet počet zákl. jednotek, z kterých je závod složen bool naplno[4] kdy během posl. 4 týdnů jel závod na plnou kapacitu class Soused soused města int mestox, int mestoy, int vzdalenost parametry souseda class Mesto 1 město std::vector<wxstring> zpravy zprávy o aktuálním dění ve městě std::vector<zavod> zavody seznam místních závodů std::vector<soused> sousedi seznam sousedů std::vector<polozkatrhu> trh místní trh std::vector<novezbozi> incoming pomocný vektor, obsahuje zboží, které má v budoucnu dorazit na místní trh historie history[max_history] historie komodit pár týdnů nazpátek int x, int y souřadnice města unsigned int bit_sous bitmaska udávající aktuální typy terénu v okolí void Step(unsigned int sous, unsigned int extra) aktualizuje bitmasky města a provede krok 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 zboží void InitTrh(const std::vector<komodita>& kom) nastaví náh. množství na trhu, vynuluje historii (mělo by se volat pouze 1x ) void UpdateCeny() přepočítá a upraví ceny všch komodit na trhu int CenaNaTrhu(long ID) const vrátí "střední cenu" vybrané komodity int CenaProdej(long ID) const vrátí prodejní cenu komodity na trhu int CenaNakup(long ID) const vrátí nákupní cenu komodity na trhu bool DostStavebnin() const vrátí TRUE, pokud je ve městě dlouhodobě dost stavebnin bool DostStavebninTed() const vrátí TRUE, pokud je ve městě okamžitý dostatek stavebnin long GetAktualProdukce(long ID) vrátí celkovou aktuální produkci dané komodity void UpdateInc() aktualizuje "frontu příchozích" - sníží "dobu čekání" void NakupZavody() provede krok závodů - nákup + plánování výroby void NakupLidi() provede nákup lidí void NakupKaravany() provede nákup "karavan" - pro meziměstský obchod void NaplanujVyrobu(Zavod& Z) naplánuje výrobu daného závodu (a plán provede) Soused* VyberSousedaPro(PolozkaTrhu& PT, vybere nejvhodnějšího souseda pro obchod s daným Mesto** M) zbožím void RecordHistory() zaznamená historii komodit pro tento týden void Rozvoj() zodpovídá zo rozvoj města void ZavriZavody() při nedostatku pracovníků vybere a zavře nějaký závod int PoziceToPoradi(int x, int y, int m) převede souřadnice na číslo čtverce (m=šířka mapy) void SpoctiBilanci(vector<Mesto>& mesta, const spočítá bilanci všech měst dohromady vector<komodita>* kom, vector<komodita>& vyroba, vector<komodita>& spotreba) void NactiModelK(wxTextInputStream& text, načte model komodit (a obyvetelstva )
vector<komodita>& kom, vector<jednotka>& jedn, Obyvatelstvo& obyv) void UlozModelK(wxTextOutputStream& text, const uloží model komodit ( a obyvetelstva ) vector<komodita>& kom, const vector<jednotka>& jedn, const Obyvatelstvo& ob) bool NajdiJ(long id, const std::vector<jednotka>& do posl. parametru uloží jednotku s hledaným id, jedn, Jednotka* J) vrátí TRUE, pokud taková existuje bool KontrolaModelu(const vector<komodita>* kom, provede kontrolu modelu (jednoznačná ID apod.) const vector<jednotka>* jedn) bool KontrolaMest(const std::vector<mesto>* mes, provede kontrolu seznamu měst const vector<jednotka>* jedn, int n, int m) bool JeT_v_bitmasce(TEREN T,unsigned int bitmaska) 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í... nejprve uspokojí své potřeby obyvatelé - nákup "obchodníků"... pro meziměstký obchod - příjem nového zboží... ať už z příchozích karavan nebo z místních podniků - nákup závodů... naplánování výroby (pokud nejsou uprostřed výrobního cyklu) - rozvoj města... zvýšení ubyt. kapacity nebo rozšíření nějakého podniku - výpočet spokojenosti 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 voda PB 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: Celkový zisk se potom dá určit takto: trzba = produkce * (nakupni_cena_na_trhu - cena_prace_za_1ks) zisk = trzba * zapojenych_jednotek - f 1 - f 2 -... -f n kde f 1,f 2,... -f n vyjadřují cenu za nákup potřebného množství surovin 1 - n. Přesně vyjádřeno je f i 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 Cross- Platform 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