procball.h 81 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856
  1. /*
  2. procball
  3. This is a zero player simple and fun simulator of football matches with
  4. procedurally generated players and teams from many countries. Ideal for
  5. watching football when you don't have TV or Internet at hand :) You can run
  6. your own championship whenever and wherever you want. There are over 4 billion
  7. unique matches, each one having with ID by which you can replay the match at
  8. any time, or share it with a friend. Players are procedurally generated based
  9. on their country, each one having many attributes including name, height,
  10. weight, preferred foot, speed, strength, intelligence etc. Dislike what
  11. players you got on your favorite team? Just change season -- different ones
  12. will be generated. But please keep in mind that the game is minimalist,
  13. simplified (no offside, no cards, ...) and aimed at fun, not realism.
  14. By drummyfish, released under CC0 1.0, public domain.
  15. */
  16. #define SAF_PROGRAM_NAME "procball"
  17. #define DEBUG 0
  18. #define SAF_SETTING_FORCE_1BIT 0 // set to 1 for 1bit version
  19. #if DEBUG
  20. #define DEBUG_PRINT(s) puts(s)
  21. #else
  22. #define DEBUG_PRINT(s) ;
  23. #endif
  24. #define SAF_SETTING_FASTER_1BIT 1
  25. #define SAF_SETTING_1BIT_DITHER 0
  26. #define SAF_SETTING_ENABLE_SAVES 0
  27. #include "../saf.h"
  28. //====== static data ======
  29. static const uint8_t imageBall[11] =
  30. {
  31. #if SAF_PLATFORM_COLOR_COUNT > 2
  32. 0x03,0x03,0xb7,0xff,0xb7,0xff,0xff,0xff,0xb7,0xff,0xb7
  33. #else
  34. 0x03,0x03,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00
  35. #endif
  36. };
  37. static const uint8_t imageBallShadow[11] =
  38. {
  39. #if SAF_PLATFORM_COLOR_COUNT > 2
  40. 0x03,0x03,0x52,0x4d,0x52,0x4d,0x4d,0x4d,0x52,0x4d,0x52
  41. #else
  42. 0x03,0x03,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00
  43. #endif
  44. };
  45. static const uint8_t imageGrass[66] =
  46. {
  47. 0x08,0x08,0x75,0x75,0x75,0x75,0x75,0x96,0x75,0x75,0x96,0x75,0x75,0x95,0x75,
  48. 0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x95,0x75,0x75,0x75,0x95,0x75,
  49. 0x75,0x75,0x75,0x96,0x75,0x75,0x75,0x96,0x75,0x75,0x75,0x75,0x95,0x75,0x75,
  50. 0x75,0x75,0x95,0x75,0x75,0x75,0x75,0x95,0x75,0x75,0x75,0x96,0x75,0x96,0x75,
  51. 0x75,0x96,0x75,0x75,0x75,0x75
  52. };
  53. static const uint8_t imageGoal[194] =
  54. {
  55. 0x08,0x18,0xdb,0xff,0xff,0xb7,0x75,0x75,0x75,0x75,0xff,0xb6,0xfb,0xff,0xb7,
  56. 0x75,0x75,0x75,0xff,0xfb,0x75,0xd7,0x75,0xb7,0x75,0x75,0xff,0xb6,0xfb,0xff,
  57. 0xbb,0x75,0xb7,0x75,0xff,0xfb,0x75,0xd7,0x75,0xbb,0x50,0xb7,0xff,0xb6,0xfb,
  58. 0xff,0xbb,0x75,0xbb,0xb7,0xff,0xfb,0x75,0xd7,0x75,0xbb,0x50,0xbb,0xff,0xb6,
  59. 0xfb,0xff,0xbb,0xdb,0xbb,0xb7,0xff,0xfb,0x75,0xd7,0x75,0xbb,0x50,0xbb,0xff,
  60. 0xb6,0xfb,0xff,0xbb,0x75,0xbb,0xb7,0xff,0xfb,0x75,0xd7,0x75,0xbb,0x50,0xbb,
  61. 0xff,0xb6,0xfb,0xff,0xbb,0x75,0xbb,0xb7,0xff,0xfb,0x75,0xd7,0x75,0xbb,0x50,
  62. 0xbb,0xff,0xb6,0xfb,0xff,0xbb,0x75,0xbb,0xb7,0xff,0xfb,0x75,0xd7,0x75,0xbb,
  63. 0x50,0xbb,0xff,0xb6,0xfb,0xff,0xbb,0x75,0xbb,0xb7,0xdb,0xff,0xff,0xff,0x75,
  64. 0xbb,0x50,0xbb,0xdb,0xb6,0xb7,0x75,0xdb,0x50,0xbb,0xb7,0xdb,0xb6,0x75,0xb7,
  65. 0x75,0xdb,0x50,0xbb,0xdb,0xb6,0xb7,0x75,0xb7,0x50,0xdb,0xb7,0xdb,0xb6,0x75,
  66. 0xb7,0x50,0xb7,0x50,0xdb,0xdb,0xb6,0xb7,0x75,0xb7,0x50,0xb7,0xdb,0xdb,0xb6,
  67. 0x75,0xb7,0x50,0xb7,0x50,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb
  68. };
  69. #define PLAYER_IMAGE_W 5
  70. #define PLAYER_IMAGE_H 8
  71. static const uint8_t playerImages[] =
  72. {
  73. 0x75,0x44,0x44,0x44,0x75,0x75,0x44,0xf6,0x44,0x75,0xa4,0xa4,0xa4,0xa4,0xa4,
  74. 0xf6,0xa4,0xa4,0xa4,0xf6,0x75,0xdb,0xdb,0xdb,0x75,0x52,0xdb,0x4d,0xdb,0x52,
  75. 0x4d,0x65,0x4d,0x65,0x4d,0x75,0x4d,0x4d,0x4d,0x75, // down
  76. 0x75,0x44,0x44,0x44,0x75,0x75,0x44,0xf6,0x44,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,
  77. 0xf6,0xa4,0xa4,0xa4,0x75,0x75,0xb6,0xdb,0xdb,0x75,0x52,0x4d,0xb6,0xdb,0x52,
  78. 0x4d,0x4d,0x4d,0x65,0x4d,0x75,0x4d,0x4d,0x4d,0x75, // down running (L foot)
  79. 0x75,0x44,0x44,0x44,0x75,0x75,0x44,0x44,0x44,0x75,0xa4,0xa4,0xa4,0xa4,0xa4,
  80. 0xf6,0xa4,0xa4,0xa4,0xf6,0x75,0xdb,0xdb,0xdb,0x75,0x52,0xdb,0x4d,0xdb,0x52,
  81. 0x4d,0x65,0x4d,0x65,0x4d,0x75,0x4d,0x4d,0x4d,0x75, // up
  82. 0x75,0x44,0x44,0x44,0x75,0x75,0x44,0x44,0x44,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,
  83. 0xf6,0xa4,0xa4,0xa4,0x75,0x75,0xdb,0xb6,0xdb,0x75,0x52,0x4d,0x4d,0xdb,0x52,
  84. 0x4d,0x4d,0x4d,0x65,0x4d,0x75,0x4d,0x4d,0x4d,0x75, // up running (L foot)
  85. 0x75,0x44,0x44,0x75,0x75,0x75,0xf6,0x44,0x75,0x75,0x75,0xa4,0xa4,0x75,0x75,
  86. 0x75,0xa4,0xa4,0x75,0x75,0x75,0xdb,0xf6,0x75,0x75,0x52,0x65,0xdb,0x52,0x75,
  87. 0x4d,0x4d,0x65,0x4d,0x75,0x75,0x4d,0x4d,0x75,0x75, // left
  88. 0x75,0x44,0x44,0x75,0x75,0x75,0xf6,0x44,0x75,0x75,0x75,0xa4,0xa4,0x75,0x75,
  89. 0xf6,0xa4,0xa4,0x75,0x75,0xdb,0xdb,0xdb,0x75,0x75,0x65,0x4d,0xb6,0xdb,0x75,
  90. 0x4d,0x4d,0x4d,0x65,0x75,0x75,0x4d,0x4d,0x75,0x75, // left running (R foot)
  91. 0x75,0x44,0x44,0x75,0x75,0x75,0xf6,0x44,0x75,0x75,0xf6,0xa4,0xa4,0x75,0x75,
  92. 0x75,0xa4,0xa4,0xa4,0x75,0x75,0xdb,0xdb,0xf6,0x75,0xdb,0xb6,0x4d,0x52,0x75,
  93. 0x65,0x4d,0x4d,0x4d,0x75,0x75,0x4d,0x4d,0x75,0x75, // left running (L foot)
  94. 0x75,0x75,0x75,0x75,0x75,0x75,0x65,0x75,0x65,0x75,0x75,0xdb,0x4d,0xdb,0x75,
  95. 0x75,0xdb,0xdb,0xdb,0x75,0x75,0xa4,0xa4,0xa4,0x75,0xa4,0xa4,0xa4,0xa4,0xa4,
  96. 0xa4,0x44,0x44,0x44,0xa4,0xf6,0x4d,0x44,0x4d,0xf6, // down lying
  97. 0x75,0x75,0x75,0x75,0x75,0xf6,0x4d,0x44,0x4d,0xf6,0xa4,0x44,0x44,0x44,0xa4,
  98. 0xa4,0xa4,0xa4,0xa4,0xa4,0x4d,0xa4,0xa4,0xa4,0x4d,0x52,0xdb,0xdb,0xdb,0x52,
  99. 0x75,0xdb,0x52,0xdb,0x75,0x75,0x65,0x75,0x65,0x75, // up lying
  100. 0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0xf6,0x75,0x75,0x75,0x75,
  101. 0xa4,0xb6,0xdb,0x65,0x75,0xa4,0xa4,0xb6,0xdb,0x65,0xa4,0xa4,0xa4,0x4d,0x75,
  102. 0x44,0x44,0xa4,0x75,0x75,0x44,0x44,0xf6,0x75,0x75, // left lying
  103. 0xf6,0x44,0x44,0x44,0xf6,0xa4,0x44,0xf6,0x44,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,
  104. 0x75,0xa4,0xa4,0xa4,0x75,0x75,0xdb,0xdb,0xdb,0x75,0x52,0xdb,0x4d,0xdb,0x52,
  105. 0x4d,0x65,0x4d,0x65,0x4d,0x75,0x4d,0x4d,0x4d,0x75, // down throwing
  106. 0xf6,0x44,0x44,0x44,0xf6,0xa4,0x44,0x44,0x44,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,
  107. 0x75,0xa4,0xa4,0xa4,0x75,0x75,0xdb,0xdb,0xdb,0x75,0x52,0xdb,0x4d,0xdb,0x52,
  108. 0x4d,0x65,0x4d,0x65,0x4d,0x75,0x4d,0x4d,0x4d,0x75 // up throwing
  109. };
  110. #define SQUARE_SIZE 8 // small spatial unit
  111. #define PITCH_W (27 * SQUARE_SIZE)
  112. #define PITCH_H (16 * SQUARE_SIZE)
  113. #define CAMERA_SPEED 4 // free camera speed
  114. #define PLAY_FRAMES (SAF_FPS * 600) // playing time of one game, in frames
  115. #define PLAYERS_IN_TEAM 8
  116. #define PLAYERS_TOTAL (PLAYERS_IN_TEAM * 2 + 1) // including referee
  117. #define NAME_MAX_LEN 7 // player name max len (without term. 0)
  118. // pitch line dimensions:
  119. #define PENALTY_AREA_W (PITCH_W / 6)
  120. #define PENALTY_AREA_H (4 * PITCH_H / 9)
  121. #define GOAL_AREA_W (PITCH_W / 10)
  122. #define GOAL_AREA_H (PITCH_H / 4)
  123. #define CENTER_CIRCLE_R SQUARE_SIZE
  124. #define DIST_CLOSE (PITCH_H / 3) // distance considered "close"
  125. #define DIST_AT (SQUARE_SIZE / 2) // reachable distance
  126. #define BALL_BOUNCE 4 // higher number => smaller bounce
  127. #define BALL_CATCH_HEIGHT 5 // max height where ball can be caught
  128. #define BALL_HAND_HEIGHT 2 // min height for hand to occur
  129. #define TRIP_TIME (SAF_FPS * 8) // max time in which someone trips
  130. #define COUNTRIES 32
  131. #define GOAL_WIDTH (SQUARE_SIZE * 2 + SQUARE_SIZE / 2)
  132. #define GOAL_HEIGHT 7
  133. #define COLOR_LINE 0xb7 // pitch line color
  134. #define COLOR_TEAM_A 0xa4
  135. #define COLOR_TEAM_B 0x0f
  136. #define COLOR_REFEREE 0x92
  137. #define COLOR_SKIN_WHITE 0xf6
  138. #define COLOR_SKIN_BLACK 0x68
  139. #define COLOR_HAIR_DARK 0x44
  140. #define COLOR_HAIR_LIGHT 0xac
  141. #define COLOR_SHOES_1 0x65
  142. #define COLOR_SHOES_2 0x0a
  143. #define COLORTEXT_1 SAF_COLOR_GRAY_DARK
  144. #define COLORTEXT_2 SAF_COLOR_WHITE
  145. #define MENU_BACKGROUND_COLOR 0x99
  146. #define SOUND_WHISTLE SAF_SOUND_CLICK
  147. #define SOUND_BUMP SAF_SOUND_BUMP
  148. #define SOUND_BOOM SAF_SOUND_BOOM
  149. #define SOUND_BEEP SAF_SOUND_BEEP
  150. #define PLAYER_STATE_MASK 0xf0
  151. #define PLAYER_DIR_MASK 0x0f
  152. #define PLAYER_BASE_STATE_FRAMES 32 // base no. of frames for state duration
  153. #define PLAYER_STATE_STANDING 0x00
  154. #define PLAYER_STATE_WALKING 0x10
  155. #define PLAYER_STATE_RUNNING 0x20
  156. #define PLAYER_STATE_LYING 0x30
  157. #define PLAYER_STATE_KICK_R 0x40
  158. #define PLAYER_STATE_KICK_L 0x50
  159. #define PLAYER_STATE_THROWING 0x60
  160. #define PLAYER_DIR_U 0
  161. #define PLAYER_DIR_UR 1
  162. #define PLAYER_DIR_R 2
  163. #define PLAYER_DIR_DR 3
  164. #define PLAYER_DIR_D 4
  165. #define PLAYER_DIR_DL 5
  166. #define PLAYER_DIR_L 6
  167. #define PLAYER_DIR_UL 7
  168. #define RACE_BIT 0x01
  169. #define HAIR_BIT 0x02
  170. #define SHOE_BIT 0x04
  171. #define FOOT_BIT 0x08
  172. #define ATTRIBUTE_MAX 127 // maximum for certain attributes
  173. #define BALL_ANIM_FRAMES 32
  174. #if SAF_PLATFORM_COLOR_COUNT <= 2
  175. // redefine colors for 1 bit version:
  176. #undef COLOR_LINE
  177. #undef COLORTEXT_1
  178. #undef COLORTEXT_2
  179. #undef COLOR_TEAM_A
  180. #undef COLOR_TEAM_B
  181. #undef COLOR_REFEREE
  182. #undef COLOR_SKIN_WHITE
  183. #undef COLOR_SKIN_BLACK
  184. #undef COLOR_HAIR_DARK
  185. #undef COLOR_HAIR_LIGHT
  186. #undef COLOR_SHOES_1
  187. #undef COLOR_SHOES_2
  188. #undef MENU_BACKGROUND_COLOR
  189. #define COLOR_LINE SAF_COLOR_BLACK
  190. #define COLORTEXT_1 SAF_COLOR_BLACK
  191. #define COLORTEXT_2 SAF_COLOR_BLACK
  192. #define COLOR_TEAM_A SAF_COLOR_BLACK
  193. #define COLOR_TEAM_B SAF_COLOR_WHITE
  194. #define COLOR_REFEREE SAF_COLOR_BLACK
  195. #define COLOR_SKIN_WHITE SAF_COLOR_BLACK
  196. #define COLOR_SKIN_BLACK SAF_COLOR_BLACK
  197. #define COLOR_HAIR_DARK SAF_COLOR_BLACK
  198. #define COLOR_HAIR_LIGHT SAF_COLOR_BLACK
  199. #define COLOR_SHOES_1 SAF_COLOR_BLACK
  200. #define COLOR_SHOES_2 SAF_COLOR_BLACK
  201. #define MENU_BACKGROUND_COLOR SAF_COLOR_WHITE
  202. #endif
  203. static const char countryNames[] =
  204. "AR" /* Argentina */ "AT" /* Austria */ "AU" /* Australia */
  205. "AQ" /* Antarctica */ "BR" /* Brazil */ "CA" /* Canada */
  206. "CN" /* China */ "CZ" /* Czechia */ "DE" /* Germany */
  207. "ES" /* Spain */ "FI" /* Finland */ "FR" /* France */
  208. "GB" /* England */ "GR" /* Greece */ "HR" /* Croatia */
  209. "HU" /* Hungary */ "IL" /* Israel */ "IN" /* India */
  210. "IT" /* Italy */ "JP" /* Japan */ "KP" /* North Korea */
  211. "KR" /* South Korea */ "NE" /* Niger */ "NL" /* Netherlands */
  212. "PO" /* Poland */ "PT" /* Portugal */ "RU" /* Russia */
  213. "SE" /* Sweden */ "SK" /* Slovakia */ "TR" /* Turkey */
  214. "UA" /* Ukraine */ "US" /* USA */;
  215. // Converts two country characters to a single number.
  216. #define COUNTRY_NUM(c1,c2) ((((int) c2) << 8) | ((int) c1))
  217. typedef struct
  218. {
  219. // purely aesthetic:
  220. char name[NAME_MAX_LEN + 1];
  221. uint8_t look; // bits: 0: race, 1: hair, 3: shoes, 2: foot (L/R)
  222. // personality, may affect play:
  223. uint8_t ageYears; // will affect some attributes
  224. uint8_t heightCm; // will affect some attributes
  225. uint8_t weightKg; // will affect some attributes
  226. uint8_t ego; // less likely to pass etc.
  227. uint8_t aggressivity; // more likely to foul, go for ball etc.
  228. // skills (higher = better):
  229. uint8_t speed; // faster run, turns, decisions
  230. uint8_t strength; // faster and longer kicks/throws, better fights
  231. uint8_t stamina; // less likely to trip or rest
  232. uint8_t accuracy; // more precise shots and throws, better catch
  233. uint8_t agility; // better 1v1, catching, less likely to trip
  234. uint8_t iq; // faster and better decisions, fewer blunders
  235. // dynamic attributes:
  236. uint8_t stateDir; // state and direction combined
  237. uint8_t nextStateChange; // frame till next state change
  238. int16_t position[2]; // pixel position on the pitch
  239. uint8_t goalsFouls; // goals scored (lower 4 bits), fouls committed
  240. } Player;
  241. //====== global variables ======
  242. #define CAMERA_FOLLOW_NONE 255
  243. #define CAMERA_FOLLOW_BALL 128
  244. uint32_t currentRand; // for LCG
  245. uint8_t playerSortArray[PLAYERS_TOTAL]; // helper for drawing
  246. uint8_t tmpImage[2 + PLAYER_IMAGE_W * PLAYER_IMAGE_H]; // helper image
  247. int cameraPos[2]; // screen top left pos. in pixels
  248. uint8_t cameraFollowMode; /* follow mode of camera:
  249. 0 to PLAYERS_TOTAL -1: follow player
  250. 128: follow ball
  251. 255: no follow */
  252. #define MENU_ITEMS 9
  253. #define MENU_STATE_OFF 255
  254. uint8_t menuState;
  255. uint32_t menuGameNumber;
  256. #define GAME_STATE_PLAYING 0 // normal play
  257. #define GAME_STATE_AFTER_GOAL 1 // goal was scored, also start of game
  258. #define GAME_STATE_FREE_KICK 2 // free kick, pen., throw, corner etc.
  259. #define GAME_STATE_KICKOFF 3 // goal keeper to kick off
  260. #define GAME_STATE_HALFTIME 4
  261. #define GAME_STATE_END 5
  262. #define DISPLAY_TEXT_SIZE 10
  263. struct
  264. {
  265. uint16_t number; /* complete game seed, structure (from lowest bits):
  266. 5 bits: country 1
  267. 5 bits: country 2
  268. 8 bits: season
  269. 14 bits: random seed */
  270. uint8_t state;
  271. uint8_t score[2];
  272. uint16_t stateFrame; // frames elapsed since start of state
  273. char displayText[DISPLAY_TEXT_SIZE];
  274. uint8_t displayTextCountdown;
  275. uint32_t playFrame; // frames of play time since start of match
  276. uint16_t nextTripIn; // frames until next random trip
  277. uint8_t playerToKick; // player index, for free kicks, throws etc.
  278. Player players[PLAYERS_TOTAL]; /* first team, then second, then referee,
  279. player 0 on a team is goal keeper */
  280. struct
  281. {
  282. int16_t position[2]; // current, interpolated position
  283. int16_t posFrom[2]; // start position
  284. int16_t vecTo[2]; // vector to end position
  285. uint8_t height; // current height
  286. uint8_t maxHeight; // maximum height to reach
  287. uint8_t interpolParam; // determines position between start and end pos.
  288. int8_t lastPlayer; // player that touched the ball last
  289. int8_t player; // player in possesion or -1
  290. } ball;
  291. } game;
  292. //====== functions ======
  293. void clampAttribute(uint8_t *attr)
  294. {
  295. if (*attr > ATTRIBUTE_MAX)
  296. *attr = ATTRIBUTE_MAX;
  297. }
  298. int clamp(int v, int min, int max)
  299. {
  300. return v > max ? max : (v < min ? min : v);
  301. }
  302. int16_t distTaxi(int16_t x0, int16_t y0, int16_t x1, int16_t y1)
  303. {
  304. return (x0 > x1 ? x0 - x1 : x1 - x0) + (y0 > y1 ? y0 - y1 : y1 - y0);
  305. }
  306. void gameSetState(uint8_t state)
  307. {
  308. DEBUG_PRINT("changing game state");
  309. game.state = state;
  310. game.stateFrame = 0;
  311. }
  312. // turns one step towards desired direction
  313. uint8_t dirTurnTowards(uint8_t current, uint8_t desired)
  314. {
  315. if (desired == current)
  316. return current;
  317. uint8_t opposite = (current + 4) % 8;
  318. return ((current + 8) + // god bless this mess
  319. ((((desired > opposite && desired < current) |
  320. (desired > current && desired < opposite)) ==
  321. (opposite < current)) ? -1 : 1)) % 8;
  322. }
  323. void dirToVector(uint8_t dir, int16_t v[2])
  324. {
  325. v[0] = (dir == PLAYER_DIR_R || dir == PLAYER_DIR_UR ||
  326. dir == PLAYER_DIR_DR) +
  327. ((dir == PLAYER_DIR_L || dir == PLAYER_DIR_UL ||
  328. dir == PLAYER_DIR_DL) ? -1 : 0);
  329. v[1] = (dir == PLAYER_DIR_D || dir == PLAYER_DIR_DR ||
  330. dir == PLAYER_DIR_DL) +
  331. ((dir == PLAYER_DIR_U || dir == PLAYER_DIR_UR ||
  332. dir == PLAYER_DIR_UL) ? -1 : 0);
  333. }
  334. char hexDigitToChar(uint8_t digit)
  335. {
  336. return digit < 10 ? ('0' + digit) : ('A' + digit - 10);
  337. }
  338. uint32_t createGameNum(uint8_t country1, uint8_t country2, uint8_t season,
  339. uint16_t seed)
  340. {
  341. seed &= 0x3fff;
  342. return ((uint32_t) country1) | (((uint32_t) country2) << 5) |
  343. (((uint32_t) season) << 10) | (((uint32_t) seed) << 18);
  344. }
  345. uint16_t gameNumToSeed(uint32_t n)
  346. {
  347. return n >> 18;
  348. }
  349. uint8_t gameNumToCountryNum(uint32_t n, uint8_t countryNum)
  350. {
  351. return (n >> (countryNum == 0 ? 0 : 5)) & 0x1f;
  352. }
  353. const char *gameNumToCountryCode(uint32_t n, uint8_t countryNum)
  354. {
  355. return countryNames + 2 * gameNumToCountryNum(n,countryNum);
  356. }
  357. uint8_t gameNumToSeason(uint32_t n)
  358. {
  359. return (n >> 10) & 0xff;
  360. }
  361. // 16 bit random num, 32 bit period
  362. unsigned int rnd(void)
  363. {
  364. currentRand = 22695477 * currentRand + 123;
  365. return currentRand >> 16;
  366. }
  367. // normal distribution random number
  368. unsigned int rndNorm(unsigned int maximum)
  369. {
  370. // enforce order of evaluation:
  371. unsigned int result = rnd() % maximum;
  372. result += rnd() % maximum;
  373. result += maximum - 1 - rnd() % maximum;
  374. result += maximum - 1 - rnd() % maximum;
  375. return result / 4;
  376. }
  377. uint8_t getPlayerSkill(const Player *p)
  378. {
  379. return (((int) p->speed) + ((int) p->strength) + ((int) p->stamina) +
  380. ((int) p->accuracy) + ((int) p->agility) + ((int) p->iq)) / 6;
  381. }
  382. int areaHasAtLeastPlayers(int16_t centerX, int16_t centerY, int16_t dist,
  383. uint8_t team, uint8_t count)
  384. {
  385. centerX -= dist;
  386. centerY -= dist;
  387. dist *= 2;
  388. for (int i = team * PLAYERS_IN_TEAM;
  389. i < PLAYERS_IN_TEAM + team * PLAYERS_IN_TEAM; ++i)
  390. if (game.players[i].position[0] >= centerX &&
  391. game.players[i].position[0] <= centerX + dist &&
  392. game.players[i].position[1] >= centerY &&
  393. game.players[i].position[1] <= centerY + dist)
  394. {
  395. count--;
  396. if (!count)
  397. return 1;
  398. }
  399. return 0;
  400. }
  401. void ballReset(int16_t x, int16_t y)
  402. {
  403. game.ball.posFrom[0] = x;
  404. game.ball.posFrom[1] = y;
  405. game.ball.position[0] = x;
  406. game.ball.position[1] = y;
  407. game.ball.vecTo[0] = 0;
  408. game.ball.vecTo[1] = 0;
  409. game.ball.height = 0;
  410. game.ball.player = -1;
  411. game.ball.lastPlayer = -1;
  412. game.ball.interpolParam = BALL_ANIM_FRAMES + 1;
  413. }
  414. void playerGetStartPos(uint8_t index, int16_t result[2])
  415. {
  416. if (index == PLAYERS_IN_TEAM * 2) // referee?
  417. {
  418. result[0] = PITCH_W / 2;
  419. result[1] = PITCH_H / 2 - SQUARE_SIZE;
  420. return;
  421. }
  422. int flip = 0;
  423. if (index >= PLAYERS_IN_TEAM)
  424. {
  425. flip = 1;
  426. index -= PLAYERS_IN_TEAM;
  427. }
  428. switch (index)
  429. {
  430. case 0:
  431. result[0] = SQUARE_SIZE * 2;
  432. result[1] = PITCH_H / 2;
  433. break;
  434. case 1: // fall through
  435. case 2:
  436. result[0] = PITCH_W / 6;
  437. result[1] = PITCH_H / 3;
  438. break;
  439. case 3: // fall through
  440. case 4:
  441. result[0] = PITCH_W / 4 + SQUARE_SIZE;
  442. result[1] = PITCH_H / 4;
  443. break;
  444. case 5: // fall through
  445. case 6:
  446. result[0] = PITCH_W / 2 - SQUARE_SIZE - SQUARE_SIZE / 2;
  447. result[1] = 3 * SQUARE_SIZE;
  448. break;
  449. default:
  450. result[0] = PITCH_W / 2 - 2 * SQUARE_SIZE;
  451. result[1] = PITCH_H / 2;
  452. break;
  453. }
  454. if (index == 2 || index == 4 || index == 6)
  455. result[1] = PITCH_H - result[1];
  456. if (flip)
  457. result[0] = PITCH_W - result[0];
  458. }
  459. // Shoots ball from its current pos to new pos, reaching given max height.
  460. void ballShoot(int16_t toX, int16_t toY, uint8_t maxHeight, uint8_t accuracy)
  461. {
  462. DEBUG_PRINT("ball shoot");
  463. int spread = 32 - (32 * accuracy) / 128;
  464. spread += spread == 0;
  465. toX = toX - spread / 2 + (rnd() % spread);
  466. toY = toY - spread / 2 + (rnd() % spread);
  467. game.ball.player = -1;
  468. game.ball.vecTo[0] = toX - game.ball.position[0];
  469. game.ball.vecTo[1] = toY - game.ball.position[1];
  470. if (game.ball.position[0] == toX && game.ball.position[1] == toY)
  471. game.ball.interpolParam = BALL_ANIM_FRAMES + 1;
  472. else
  473. {
  474. game.ball.posFrom[0] = game.ball.position[0];
  475. game.ball.posFrom[1] = game.ball.position[1];
  476. game.ball.interpolParam = 0;
  477. game.ball.maxHeight = maxHeight;
  478. }
  479. }
  480. void gameDisplayText(const char *text)
  481. {
  482. #if DEBUG
  483. if (*text != 0)
  484. printf("[%4x: %s]\n",game.playFrame,text);
  485. #endif
  486. int i = 0;
  487. game.displayTextCountdown = SAF_FPS * 3;
  488. while (text && i < DISPLAY_TEXT_SIZE - 1)
  489. {
  490. game.displayText[i] = *text;
  491. text++;
  492. i++;
  493. }
  494. game.displayText[i] = 0;
  495. }
  496. Player playerGenerate(const char *country2Chars, uint8_t index, uint8_t season)
  497. {
  498. uint32_t seedBackup = currentRand;
  499. currentRand = ((uint32_t) index) |
  500. (((uint32_t) country2Chars[0]) << 8) |
  501. (((uint32_t) country2Chars[1]) << 16) |
  502. (((uint32_t) season) << 24);
  503. Player p;
  504. #define IS_COUNTRY(c1,c2) (country2Chars[0] == c1 && country2Chars[1] == c2)
  505. // base values:
  506. p.weightKg = 72;
  507. p.heightCm = 175;
  508. p.iq = 100;
  509. p.ego = 90;
  510. p.ageYears = 27;
  511. p.aggressivity = 90;
  512. p.speed = 95;
  513. p.strength = 78;
  514. p.stamina = 95;
  515. p.accuracy = 90;
  516. p.agility = 90;
  517. char nameLetters[] = // letters to make names from
  518. "rtpsdfghjklzcbnm" // first letters
  519. "aeiouyeo"; // second letters
  520. uint8_t blackProbability = 10; // probability of being black, max 127
  521. uint8_t blondeProbability = 30;
  522. uint8_t countrySkill = 50;
  523. switch (COUNTRY_NUM(country2Chars[0],country2Chars[1]))
  524. {
  525. case COUNTRY_NUM('U','S'):
  526. p.weightKg += 10;
  527. blackProbability *= 5;
  528. p.iq -= 6;
  529. p.strength += 7;
  530. countrySkill -= 8;
  531. p.ego += 32;
  532. p.aggressivity += 20;
  533. break;
  534. case COUNTRY_NUM('I','N'):
  535. blackProbability *= 4;
  536. blondeProbability /= 4;
  537. break;
  538. case COUNTRY_NUM('K','P'):
  539. p.weightKg -= 4;
  540. p.heightCm -= 3;
  541. // fall through
  542. case COUNTRY_NUM('J','P'):
  543. // fall through
  544. case COUNTRY_NUM('K','R'):
  545. // fall through
  546. case COUNTRY_NUM('C','N'):
  547. p.iq += 8;
  548. p.weightKg -= 5;
  549. p.heightCm -= 5;
  550. blackProbability = 0;
  551. blondeProbability = 5;
  552. p.agility += 5;
  553. p.ego -= 25;
  554. p.aggressivity -= 11;
  555. countrySkill -= 35;
  556. nameLetters[0] = 'i';
  557. nameLetters[2] = 'o';
  558. nameLetters[21] = '-';
  559. nameLetters[22] = '\'';
  560. nameLetters[23] = 'g';
  561. nameLetters[24] = 'c';
  562. break;
  563. case COUNTRY_NUM('I','L'):
  564. p.iq += 10;
  565. blackProbability = 0;
  566. break;
  567. case COUNTRY_NUM('N','E'):
  568. p.iq -= 15;
  569. p.weightKg -= 5;
  570. blackProbability = 120;
  571. blondeProbability = 10;
  572. break;
  573. case COUNTRY_NUM('F','R'):
  574. blackProbability *= 4;
  575. nameLetters[22] = '\'';
  576. countrySkill += 18;
  577. p.ego += 5;
  578. break;
  579. case COUNTRY_NUM('S','E'):
  580. blondeProbability = 55;
  581. break;
  582. case COUNTRY_NUM('B','R'):
  583. countrySkill += 10;
  584. // fall through
  585. case COUNTRY_NUM('P','T'):
  586. countrySkill += 10;
  587. // fall through
  588. case COUNTRY_NUM('A','R'):
  589. p.agility += 20;
  590. blondeProbability /= 2;
  591. countrySkill += 15;
  592. break;
  593. case COUNTRY_NUM('E','S'):
  594. countrySkill += 20;
  595. blondeProbability /= 2;
  596. p.agility += 4;
  597. break;
  598. case COUNTRY_NUM('G','B'):
  599. countrySkill += 20;
  600. break;
  601. case COUNTRY_NUM('I','T'):
  602. countrySkill += 14;
  603. break;
  604. case COUNTRY_NUM('R','U'):
  605. p.aggressivity += 12;
  606. p.strength += 7;
  607. blackProbability = 2;
  608. break;
  609. case COUNTRY_NUM('A','Q'):
  610. countrySkill -= 20;
  611. break;
  612. default: break;
  613. }
  614. // randomize
  615. p.weightKg += -36 + rndNorm(72);
  616. p.heightCm += -30 + rndNorm(60);
  617. p.ageYears += -17 + rndNorm(34);
  618. p.iq += -50 + rndNorm(100);
  619. p.ego += -20 + rndNorm(40);
  620. p.aggressivity += -22 + rndNorm(44);
  621. p.speed -= p.ageYears / 8;
  622. p.stamina -= p.ageYears / 7;
  623. p.strength -= p.ageYears / 6;
  624. p.iq -= p.ageYears / 8;
  625. p.speed -= p.weightKg / 8;
  626. p.strength += p.weightKg / 16;
  627. p.strength += p.heightCm / 16;
  628. int skillPoints = rndNorm(countrySkill);
  629. for (int i = 0; i < 4; ++i) // take away some skill point for reshuffling
  630. {
  631. #define TAKE_AWAY 15
  632. switch (rnd() % 5)
  633. {
  634. case 0: p.speed -= TAKE_AWAY; break;
  635. case 1: p.stamina -= TAKE_AWAY; break;
  636. case 2: p.strength -= TAKE_AWAY; break;
  637. case 3: p.accuracy -= TAKE_AWAY; break;
  638. case 4: p.agility -= TAKE_AWAY; break;
  639. default: break;
  640. }
  641. skillPoints += TAKE_AWAY;
  642. #undef TAKE_AWAY
  643. }
  644. while (skillPoints) // now pour the skill points back in
  645. {
  646. switch (rnd() % 5)
  647. {
  648. case 0: p.speed++; break;
  649. case 1: p.stamina++; break;
  650. case 2: p.strength++; break;
  651. case 3: p.accuracy++; break;
  652. case 4: p.agility++; break;
  653. default: break;
  654. }
  655. skillPoints--;
  656. }
  657. for (int i = 0; i < NAME_MAX_LEN + 1; ++i) // make the name
  658. {
  659. p.name[i] = 0;
  660. if ((i == NAME_MAX_LEN) | ((i > 2) & (rnd() % 16 < 2)))
  661. {
  662. if (!(
  663. (p.name[i - 1] >= 'A' && p.name[i - 1] <= 'Z') ||
  664. (p.name[i - 1] >= 'a' && p.name[i - 1] <= 'z')))
  665. p.name[i] = 'u'; // let last name be always a letter
  666. break;
  667. }
  668. int r = rnd();
  669. p.name[i] = (rnd() % 64 < 58) ?
  670. ((unsigned int) nameLetters[((i % 2) * 16) + (r % (i % 2 ? 8 : 16))])
  671. : (unsigned int) ('a' + r % 26); // sometimes add completely random letter
  672. if (i == 0)
  673. p.name[i] = (p.name[i] - 'a') + 'A';
  674. }
  675. p.look = 0;
  676. if ((rnd() % 128) < blackProbability)
  677. p.look |= RACE_BIT;
  678. if ((rnd() % 128) < blondeProbability)
  679. p.look |= HAIR_BIT;
  680. if ((rnd() % 128) < 36)
  681. p.look |= FOOT_BIT;
  682. if (rnd() & 0x80)
  683. p.look |= SHOE_BIT;
  684. if (p.look & RACE_BIT)
  685. {
  686. // less intelligent but faster
  687. p.iq -= 8;
  688. p.speed += 8;
  689. }
  690. clampAttribute(&p.ego);
  691. clampAttribute(&p.aggressivity);
  692. clampAttribute(&p.speed);
  693. clampAttribute(&p.strength);
  694. clampAttribute(&p.stamina);
  695. clampAttribute(&p.accuracy);
  696. clampAttribute(&p.agility);
  697. currentRand = seedBackup;
  698. p.position[0] = PITCH_W / 2;
  699. p.position[1] = PITCH_H / 2;
  700. p.stateDir = PLAYER_STATE_STANDING | PLAYER_DIR_D;
  701. p.nextStateChange = 0;
  702. p.goalsFouls = 0;
  703. return p;
  704. }
  705. // finds a player to kick a free kick (also throw in etc.)
  706. uint8_t findPlayerToKick(uint8_t team)
  707. {
  708. uint8_t start = ((team != 0) * PLAYERS_IN_TEAM) + 1; // exclude goal keeper
  709. int16_t bestD = 10000;
  710. uint8_t best = start;
  711. for (int i = start; i < start + PLAYERS_IN_TEAM - 1; ++i)
  712. {
  713. int16_t d = distTaxi(game.players[i].position[0],
  714. game.players[i].position[1],game.ball.position[0],game.ball.position[1]);
  715. if (d < bestD)
  716. {
  717. best = i;
  718. bestD = d;
  719. }
  720. }
  721. return best;
  722. }
  723. void playerMisconducts(uint8_t index)
  724. {
  725. Player *p = game.players + index;
  726. DEBUG_PRINT("misconduct");
  727. game.playerToKick = findPlayerToKick(1 - (index / PLAYERS_IN_TEAM));
  728. if (
  729. p->position[1] > (PITCH_H / 2 - PENALTY_AREA_H / 2) &&
  730. p->position[1] < (PITCH_H / 2 + PENALTY_AREA_H / 2) &&
  731. (
  732. (((index / PLAYERS_IN_TEAM) == 1) && p->position[0]
  733. > PITCH_W - SQUARE_SIZE - PENALTY_AREA_W) ||
  734. (((index / PLAYERS_IN_TEAM) == 0) && p->position[0]
  735. < SQUARE_SIZE + PENALTY_AREA_W)
  736. ))
  737. {
  738. gameDisplayText("penalty!");
  739. DEBUG_PRINT("penalty!");
  740. game.ball.vecTo[0] = SQUARE_SIZE + PENALTY_AREA_W;
  741. if (game.ball.position[0] > PITCH_W / 2)
  742. game.ball.vecTo[0] = PITCH_W - game.ball.vecTo[0];
  743. game.ball.vecTo[0] = game.ball.vecTo[0] - game.ball.position[0];
  744. game.ball.vecTo[1] = PITCH_H / 2 - game.ball.position[1];
  745. }
  746. else
  747. ballReset(game.ball.position[0],game.ball.position[1]);
  748. SAF_playSound(SOUND_WHISTLE);
  749. gameSetState(GAME_STATE_FREE_KICK);
  750. }
  751. // Converts two points to direction.
  752. uint8_t pointsToDir(int16_t p0[2], int16_t p1[2])
  753. {
  754. int16_t tangentTimes2 = (2 * (p0[0] - p1[0])) /
  755. (p0[1] != p1[1] ? (p0[1] - p1[1]) : 1);
  756. switch ((p1[0] >= p0[0]) | ((p1[1] >= p0[1]) << 1) |
  757. ((tangentTimes2 != 0) << 4) |
  758. (((tangentTimes2 > 4) | (tangentTimes2 < -4)) << 5))
  759. {
  760. case 0x01: // fall through
  761. case 0x00: return PLAYER_DIR_U; break;
  762. case 0x10: return PLAYER_DIR_UL; break;
  763. case 0x20: return PLAYER_DIR_UL; break;
  764. case 0x11: return PLAYER_DIR_UR; break;
  765. case 0x32: // fall through
  766. case 0x30: return PLAYER_DIR_L; break;
  767. case 0x33: // fall through
  768. case 0x31: return PLAYER_DIR_R; break;
  769. case 0x03: // fall through
  770. case 0x02: return PLAYER_DIR_D; break;
  771. case 0x12: return PLAYER_DIR_DL; break;
  772. case 0x13: return PLAYER_DIR_DR; break;
  773. default: return PLAYER_DIR_U;
  774. }
  775. }
  776. void playerShoot(uint8_t index, int16_t point[2], uint8_t height)
  777. {
  778. Player *p = game.players + index;
  779. int16_t dest[2];
  780. dest[0] = point[0];
  781. dest[1] = point[1];
  782. game.ball.player = -1;
  783. p->stateDir = // we just rotate immediately here
  784. pointsToDir(p->position,dest) |
  785. ((p->look & FOOT_BIT) ? PLAYER_STATE_KICK_L : PLAYER_STATE_KICK_R);
  786. p->nextStateChange = clamp(
  787. PLAYER_BASE_STATE_FRAMES - ((p->agility + p->speed) / 32),
  788. 0,PLAYER_BASE_STATE_FRAMES * 10);
  789. if (distTaxi(dest[0],dest[1],game.ball.position[0],game.ball.position[1])
  790. > DIST_CLOSE + p->strength / 16)
  791. for (int i = 0; i < 2; ++i)
  792. {
  793. dest[i] -= p->position[i];
  794. dest[i] = (dest[i] * clamp(p->strength + 10,0,128)) / 128;
  795. dest[i] += p->position[i];
  796. }
  797. ballShoot(dest[0],dest[1],height,p->accuracy);
  798. SAF_playSound(SOUND_BUMP);
  799. }
  800. void playerPass(uint8_t index)
  801. {
  802. DEBUG_PRINT("pass");
  803. int bestScore = -1;
  804. Player *p = game.players +
  805. (index / PLAYERS_IN_TEAM) * PLAYERS_IN_TEAM;
  806. Player *bestP = p + 1;
  807. int16_t enemyGoalX = index < PLAYERS_IN_TEAM ? PITCH_W : 0;
  808. for (int i = 0; i < PLAYERS_IN_TEAM; ++i)
  809. {
  810. if (p != (game.players + index))
  811. {
  812. int score = 1000 -
  813. (distTaxi(enemyGoalX,PITCH_H / 2,p->position[0],p->position[1]) +
  814. distTaxi(p->position[0],p->position[1],
  815. game.players[i].position[0],game.players[i].position[1]))
  816. / SQUARE_SIZE;
  817. if ((score > bestScore) & ((rnd() % 128) < p->iq))
  818. {
  819. bestScore = score;
  820. bestP = p;
  821. }
  822. }
  823. p++;
  824. }
  825. playerShoot(index,bestP->position,0);
  826. }
  827. void playerTrip(uint8_t index)
  828. {
  829. game.players[index].stateDir = PLAYER_STATE_LYING |
  830. (game.players[index].stateDir & PLAYER_DIR_MASK);
  831. game.players[index].nextStateChange =
  832. clamp(2 * PLAYER_BASE_STATE_FRAMES - game.players[index].speed / 2,
  833. 0,PLAYER_BASE_STATE_FRAMES * 3) +
  834. (game.players[index].ego / 4); // bigger ego => longer drama
  835. game.players[index].nextStateChange += PLAYER_BASE_STATE_FRAMES / 3;
  836. if (game.ball.player == index)
  837. {
  838. int16_t dest[2];
  839. dirToVector(game.players[index].stateDir & PLAYER_DIR_MASK,dest);
  840. dest[0] = game.players[index].position[0] + 2 * SQUARE_SIZE;
  841. dest[1] = game.players[index].position[1] + 2 * SQUARE_SIZE;
  842. game.ball.player = -1;
  843. ballShoot(dest[0],dest[1],0,100);
  844. }
  845. SAF_playSound(SOUND_BOOM);
  846. }
  847. // The player will start turning or running towards given point.
  848. void playerWantsToRunTo(uint8_t index, int16_t point[2])
  849. {
  850. point[0] = clamp(point[0],0,PITCH_W);
  851. point[1] = clamp(point[1],0,PITCH_H);
  852. Player *p = game.players + index;
  853. if (p->position[0] == point[0] && p->position[1] == point[1])
  854. {
  855. p->stateDir =
  856. (p->stateDir & PLAYER_DIR_MASK) | PLAYER_STATE_STANDING;
  857. }
  858. else
  859. {
  860. uint8_t direction = p->stateDir & PLAYER_DIR_MASK;
  861. uint8_t desired = pointsToDir(p->position,point);
  862. uint8_t turnSpeed = clamp((p->speed + p->agility) / 64,1,10);
  863. while (turnSpeed && direction != desired)
  864. {
  865. direction = dirTurnTowards(direction,desired);
  866. turnSpeed--;
  867. }
  868. p->stateDir =
  869. (direction == desired ? PLAYER_STATE_RUNNING : PLAYER_STATE_STANDING)
  870. | direction;
  871. }
  872. p->nextStateChange = PLAYER_BASE_STATE_FRAMES / 3;
  873. }
  874. // Bounces a ball horizontally.
  875. void ballHorizontalBounce(int right)
  876. {
  877. right = right ? PITCH_W - SQUARE_SIZE : SQUARE_SIZE;
  878. game.ball.posFrom[0] = -1 * game.ball.posFrom[0] + 2 * right;
  879. game.ball.position[0] = -1 * game.ball.position[0] + 2 * right;
  880. game.ball.vecTo[0] *= -1;
  881. }
  882. void ballUpdate(void)
  883. {
  884. if (game.ball.player != -1) // someone holds the ball?
  885. {
  886. game.ball.interpolParam = BALL_ANIM_FRAMES + 1;
  887. dirToVector(game.players[game.ball.player].stateDir & PLAYER_DIR_MASK,
  888. game.ball.position);
  889. game.ball.position[0] = 2 * game.ball.position[0] +
  890. game.players[game.ball.player].position[0];
  891. game.ball.position[1] = 2 * game.ball.position[1] +
  892. game.players[game.ball.player].position[1];
  893. }
  894. else
  895. {
  896. // no one holds the ball
  897. if (game.ball.interpolParam <= BALL_ANIM_FRAMES)
  898. {
  899. game.ball.position[0] = game.ball.posFrom[0] +
  900. (game.ball.interpolParam * game.ball.vecTo[0]) / BALL_ANIM_FRAMES;
  901. game.ball.position[1] = game.ball.posFrom[1] +
  902. (game.ball.interpolParam * game.ball.vecTo[1]) / BALL_ANIM_FRAMES;
  903. int newHeight = game.ball.interpolParam;
  904. newHeight -= BALL_ANIM_FRAMES / 2;
  905. newHeight *= newHeight;
  906. newHeight = ((BALL_ANIM_FRAMES / 2) * (BALL_ANIM_FRAMES / 2)) - newHeight;
  907. newHeight = (game.ball.maxHeight * newHeight) /
  908. ((BALL_ANIM_FRAMES / 2) * (BALL_ANIM_FRAMES / 2));
  909. if (newHeight == 0 && game.ball.height != 0)
  910. SAF_playSound(SOUND_BUMP);
  911. game.ball.height = newHeight;
  912. game.ball.interpolParam++;
  913. if (game.ball.interpolParam >= BALL_ANIM_FRAMES) // bounce
  914. ballShoot(game.ball.position[0] + game.ball.vecTo[0] / BALL_BOUNCE,
  915. game.ball.position[1] + game.ball.vecTo[1] / BALL_BOUNCE,
  916. game.ball.maxHeight / BALL_BOUNCE,128);
  917. }
  918. if (game.ball.height <= BALL_CATCH_HEIGHT) // see if someone catches it
  919. {
  920. // make it more fair, don't prioritize one team in ball pick:
  921. int index = (game.playFrame % 2) * PLAYERS_IN_TEAM;
  922. for (int i = 0; i < PLAYERS_IN_TEAM * 2; ++i)
  923. {
  924. uint8_t state = game.players[index].stateDir & PLAYER_STATE_MASK;
  925. uint8_t goalieBonus = ((index % PLAYERS_IN_TEAM) == 0) ? 2 : 0;
  926. if (
  927. state != PLAYER_STATE_KICK_R &&
  928. state != PLAYER_STATE_KICK_L &&
  929. state != PLAYER_STATE_LYING &&
  930. distTaxi(
  931. game.players[index].position[0],
  932. game.players[index].position[1],
  933. game.ball.position[0],
  934. game.ball.position[1]) <= (DIST_AT + goalieBonus) &&
  935. ( // prob. of catch depends on ball trajectory len. and agility
  936. (rnd() % 128 +
  937. distTaxi(0,0,game.ball.vecTo[0],game.ball.vecTo[1]) /
  938. (2 * SQUARE_SIZE)) <= ((unsigned int)
  939. (game.players[index].agility + game.players[index].accuracy) / 2
  940. + goalieBonus * 4)
  941. )
  942. )
  943. {
  944. game.ball.player = index;
  945. game.ball.lastPlayer = index;
  946. game.ball.height = 0;
  947. if (game.state == GAME_STATE_PLAYING)
  948. gameDisplayText(game.players[index].name);
  949. if ((game.ball.height >= BALL_HAND_HEIGHT) & ((rnd() % 128) < 20))
  950. {
  951. gameDisplayText("hand!");
  952. DEBUG_PRINT("hand!");
  953. playerMisconducts(index);
  954. }
  955. break;
  956. }
  957. index = (index + 1) % (PLAYERS_IN_TEAM * 2);
  958. }
  959. }
  960. }
  961. }
  962. void playerFightForBall(uint8_t index)
  963. {
  964. DEBUG_PRINT("fight for ball");
  965. if (game.ball.player < 0)
  966. return;
  967. if ((rnd() % 128) <
  968. ((3 * ((int) game.players[index].aggressivity) / 4)))
  969. {
  970. // foul
  971. DEBUG_PRINT("player attacked");
  972. game.players[index].stateDir =
  973. (game.players[index].stateDir & PLAYER_DIR_MASK) |
  974. ((game.players[index].look & FOOT_BIT) ?
  975. PLAYER_STATE_KICK_L : PLAYER_STATE_KICK_R);
  976. playerTrip(game.ball.player);
  977. if ((rnd() % 128) <
  978. ((unsigned int) 40 + game.players[index].aggressivity / 16))
  979. {
  980. gameDisplayText("foul!");
  981. DEBUG_PRINT("foul!");
  982. playerMisconducts(index);
  983. if ((game.players[index].goalsFouls & 0xf0) != 0xf0)
  984. game.players[index].goalsFouls += 0x10;
  985. }
  986. }
  987. else
  988. {
  989. // normal fight
  990. // enforce evaluation order:
  991. unsigned int cond =
  992. (rnd() % (game.players[index].agility + 1 +
  993. game.players[index].aggressivity % 32));
  994. cond = cond >
  995. (rnd() % (game.players[game.ball.player].agility + 12));
  996. if (cond)
  997. {
  998. DEBUG_PRINT("won the ball");
  999. game.ball.player = index;
  1000. }
  1001. }
  1002. }
  1003. void playerShootOnGoal(uint8_t index)
  1004. {
  1005. gameDisplayText("shoots!");
  1006. DEBUG_PRINT("shoot on goal!");
  1007. int16_t dest[2], stren;
  1008. stren = game.players[index].strength;
  1009. dest[0] = rnd() % (2 + stren / 8);
  1010. dest[0] = (index / PLAYERS_IN_TEAM) ?
  1011. (SQUARE_SIZE - dest[0]) : (PITCH_W - SQUARE_SIZE + dest[0]);
  1012. dest[1] = PITCH_H / 2 - GOAL_WIDTH / 2 + (rnd() % GOAL_WIDTH);
  1013. playerShoot(index,dest,rnd() % (2 + stren / 16));
  1014. }
  1015. void playerUpdate(uint8_t index)
  1016. {
  1017. Player *p = game.players + index;
  1018. uint8_t state = p->stateDir & PLAYER_STATE_MASK;
  1019. uint8_t dir = p->stateDir & PLAYER_DIR_MASK;
  1020. int16_t dest[2]; // helper
  1021. uint8_t myTeam = index / PLAYERS_IN_TEAM;
  1022. dest[0] = PITCH_W / 2;
  1023. dest[1] = PITCH_H / 2;
  1024. if (p->nextStateChange == 0)
  1025. {
  1026. // by default just stand:
  1027. p->stateDir = (p->stateDir & PLAYER_DIR_MASK) | PLAYER_STATE_STANDING;
  1028. p->nextStateChange = PLAYER_BASE_STATE_FRAMES;
  1029. if (index == 2 * PLAYERS_IN_TEAM) // am I the referee?
  1030. {
  1031. // just stay near the ball
  1032. for (int i = 0; i < 2; ++i)
  1033. dest[i] = clamp(p->position[i],game.ball.position[i] - SQUARE_SIZE * 3,
  1034. game.ball.position[i] + SQUARE_SIZE * 3);
  1035. playerWantsToRunTo(index,dest);
  1036. }
  1037. else if (index % PLAYERS_IN_TEAM == 0) // am I goal keeper?
  1038. {
  1039. if (game.ball.player == index) // I have the ball?
  1040. {
  1041. if (game.ball.height == 0)
  1042. {
  1043. // pick it up
  1044. DEBUG_PRINT("picking up the ball");
  1045. game.ball.height = 5;
  1046. p->nextStateChange = 2 * PLAYER_BASE_STATE_FRAMES + rnd() % 32;
  1047. }
  1048. else
  1049. {
  1050. DEBUG_PRINT("kicking the ball out");
  1051. dest[0] = PITCH_W / 4 + rnd() % (PITCH_W / 4) + SQUARE_SIZE;
  1052. if (index >= PLAYERS_IN_TEAM)
  1053. dest[0] = PITCH_W - dest[0];
  1054. dest[1] = PITCH_H / 2 - SQUARE_SIZE * 4 + rnd() % (SQUARE_SIZE * 8);
  1055. playerShoot(index,dest,p->strength / 8 + rnd() % 8);
  1056. }
  1057. }
  1058. else
  1059. {
  1060. int16_t dist = distTaxi(p->position[0],p->position[1],
  1061. game.ball.position[0],game.ball.position[1]);
  1062. if (dist <= DIST_AT)
  1063. playerFightForBall(index);
  1064. else if ((dist <= (DIST_CLOSE / 2)) & ((rnd() % 128) < 85)) // near?
  1065. {
  1066. dest[0] = game.ball.position[0];
  1067. dest[1] = game.ball.position[1];
  1068. playerWantsToRunTo(index,dest);
  1069. p->nextStateChange /= 4;
  1070. }
  1071. else if (dist < 2 * DIST_CLOSE) // a bit close
  1072. {
  1073. dest[0] = index == 0 ? 2 * SQUARE_SIZE : (PITCH_W - 2 * SQUARE_SIZE);
  1074. dest[1] = clamp(game.ball.position[1],PITCH_H / 2 - GOAL_WIDTH / 2,
  1075. PITCH_H / 2 + GOAL_WIDTH / 2);
  1076. playerWantsToRunTo(index,dest);
  1077. dir = p->stateDir & PLAYER_DIR_MASK;
  1078. if ((
  1079. (myTeam == (game.playFrame < PLAY_FRAMES / 2))
  1080. && dir == PLAYER_DIR_R) ||
  1081. (myTeam == (game.playFrame >= PLAY_FRAMES / 2)
  1082. && dir == PLAYER_DIR_L))
  1083. {
  1084. /* here rotate immediatelly (fixes a visual bug when goalie stares
  1085. into own goal */
  1086. p->stateDir = (p->stateDir & PLAYER_STATE_MASK) |
  1087. pointsToDir(p->position,dest);
  1088. }
  1089. p->nextStateChange /= 2;
  1090. }
  1091. else
  1092. {
  1093. p->stateDir = PLAYER_STATE_STANDING |
  1094. (index == 0 ? PLAYER_DIR_R : PLAYER_DIR_L);
  1095. }
  1096. }
  1097. }
  1098. else if (game.ball.player == -1) // no one has the ball?
  1099. {
  1100. // if close, run for it, else decide randomly if run or not
  1101. if ((distTaxi(p->position[0],p->position[1],game.ball.position[0],
  1102. game.ball.position[1]) < DIST_CLOSE) | ((rnd() % 127) < 30))
  1103. playerWantsToRunTo(index,game.ball.position);
  1104. else if (rnd() % 128 < 64)
  1105. {
  1106. // run to start position
  1107. playerGetStartPos(index,dest);
  1108. playerWantsToRunTo(index,dest);
  1109. }
  1110. // else just stand
  1111. }
  1112. else if (game.ball.player == index) // I have the ball?
  1113. {
  1114. // enforce evaluation order:
  1115. int doBlunder = (rnd() % 16) == 0;
  1116. doBlunder &= (rnd() % 128) > p->iq;
  1117. if (doBlunder)
  1118. {
  1119. gameDisplayText("oh no!");
  1120. DEBUG_PRINT("blunder");
  1121. for (int i = 0; i < 2; ++i)
  1122. dest[i] = p->position[i] - 8 * SQUARE_SIZE +
  1123. (rnd() % (16 * SQUARE_SIZE));
  1124. playerShoot(index,dest,rnd() % 16);
  1125. }
  1126. else
  1127. {
  1128. // own goal coords:
  1129. dest[0] = index >= PLAYERS_IN_TEAM ? PITCH_W : 0;
  1130. dest[1] = PITCH_H / 2;
  1131. if (distTaxi(p->position[0],p->position[1],dest[0],dest[1]) < DIST_CLOSE)
  1132. {
  1133. // close to own goal
  1134. if (areaHasAtLeastPlayers(p->position[0],p->position[1],DIST_CLOSE,
  1135. !myTeam,1))
  1136. {
  1137. // enemies nearby => kick the ball away
  1138. dest[0] = (((int16_t) p->strength) *
  1139. ((p->position[0] >= PITCH_W / 2) ?
  1140. -1 * PITCH_H / 2 : PITCH_H / 2)) / 128;
  1141. dest[1] = -1 * PITCH_H / 4 + (rnd() % (PITCH_H / 2));
  1142. dest[0] += p->position[0];
  1143. dest[1] += p->position[1];
  1144. playerShoot(index,dest,p->strength / 8);
  1145. }
  1146. else
  1147. {
  1148. dest[0] = PITCH_W / 2;
  1149. dest[1] = PITCH_H / 2 - 4 * SQUARE_SIZE + rnd() % (SQUARE_SIZE * 8);
  1150. playerWantsToRunTo(index,dest);
  1151. }
  1152. }
  1153. else
  1154. {
  1155. // not close to own goal
  1156. dest[0] = index >= PLAYERS_IN_TEAM ? 0 : PITCH_W; // enemy goal coord
  1157. int enemyGoalDist = distTaxi(p->position[0],p->position[1],dest[0],
  1158. dest[1]);
  1159. // enforce evaluation order
  1160. int cond = ((enemyGoalDist < DIST_CLOSE) & ((rnd() % 2)));
  1161. cond |= (enemyGoalDist < 3 * DIST_CLOSE / 2) &
  1162. ((rnd() % 16) <= (p->ego / 32));
  1163. if (cond)
  1164. {
  1165. // close to enemy goal + some chance
  1166. playerShootOnGoal(index);
  1167. }
  1168. else
  1169. {
  1170. // somewhere else
  1171. // where I'd like to run:
  1172. dest[0] = p->position[0] + (index < PLAYERS_IN_TEAM ?
  1173. 4 * SQUARE_SIZE : -4 * SQUARE_SIZE);
  1174. dest[1] = clamp(
  1175. ((p->position[1] + PITCH_H / 2) / 2) - (SQUARE_SIZE * 4)
  1176. + rnd() % (SQUARE_SIZE * 8),
  1177. SQUARE_SIZE + SQUARE_SIZE / 2,
  1178. PITCH_H - SQUARE_SIZE - SQUARE_SIZE / 2);
  1179. if (areaHasAtLeastPlayers(dest[0],dest[1],SQUARE_SIZE,!myTeam,1))
  1180. {
  1181. // occupied by enemies
  1182. if ((rnd() > p->ego) & !(
  1183. // in front of goal only pass if there is any teammate
  1184. (enemyGoalDist < 3 * DIST_CLOSE / 4) &&
  1185. !areaHasAtLeastPlayers(
  1186. game.players[myTeam * PLAYERS_IN_TEAM].position[0],
  1187. game.players[myTeam * PLAYERS_IN_TEAM].position[1],
  1188. DIST_CLOSE / 2,myTeam,2)))
  1189. playerPass(index);
  1190. else
  1191. {
  1192. DEBUG_PRINT("going sideways");
  1193. dest[0] = p->position[0] + rnd() % SQUARE_SIZE;
  1194. dest[1] = PITCH_H / 2 +
  1195. ((rnd() & 0x01) ? PITCH_W / 3 : PITCH_W / -3);
  1196. playerWantsToRunTo(index,dest);
  1197. }
  1198. }
  1199. else
  1200. {
  1201. DEBUG_PRINT("going forward");
  1202. playerWantsToRunTo(index,dest);
  1203. }
  1204. }
  1205. }
  1206. }
  1207. }
  1208. else // someone else has the ball
  1209. {
  1210. uint8_t ballTeam = game.ball.player / PLAYERS_IN_TEAM;
  1211. uint8_t runAround = 1;
  1212. dest[0] = p->position[0];
  1213. dest[1] = p->position[1];
  1214. if (ballTeam != myTeam && // enemy has the ball?
  1215. game.ball.height == 0 && // don't go after goal keeper holding ball
  1216. (distTaxi(p->position[0],p->position[1],
  1217. game.ball.position[0],game.ball.position[1]) <= DIST_CLOSE) &&
  1218. !areaHasAtLeastPlayers(game.ball.position[0],game.ball.position[1],
  1219. SQUARE_SIZE,myTeam,1 + (p->aggressivity + p->ego) / 128))
  1220. {
  1221. if (distTaxi(p->position[0],p->position[1],
  1222. game.ball.position[0],game.ball.position[1]) <= DIST_AT)
  1223. {
  1224. playerFightForBall(index);
  1225. runAround = 0;
  1226. }
  1227. else if ((rnd() % 128) <
  1228. ((unsigned int) (p->aggressivity + p->ego) / 2 + 25))
  1229. {
  1230. // go after him
  1231. dest[0] = game.ball.position[0];
  1232. dest[1] = game.ball.position[1];
  1233. runAround = 0;
  1234. }
  1235. }
  1236. if ((rnd() % 128 <= ((unsigned int) p->stamina + 20)) // not lazy?
  1237. & runAround)
  1238. {
  1239. // find some spot near the action
  1240. playerGetStartPos(index,dest);
  1241. // if we have the ball, stretch the starting pos
  1242. if (myTeam == game.ball.player / PLAYERS_IN_TEAM)
  1243. dest[0] = myTeam ?
  1244. (PITCH_W - (2 * (PITCH_W - dest[0]))) : (2 * dest[0]);
  1245. dest[0] = (dest[0] + game.ball.position[0]) / 2; // average with ball X
  1246. }
  1247. playerWantsToRunTo(index,dest);
  1248. }
  1249. // closer to ball will react faster
  1250. if (distTaxi(p->position[0],p->position[1],game.ball.position[0],
  1251. game.ball.position[1]) < DIST_CLOSE)
  1252. p->nextStateChange /= 2;
  1253. // sometimes randomly walk instead of run
  1254. if (((p->stateDir & PLAYER_STATE_MASK) == PLAYER_STATE_RUNNING) &
  1255. ((rnd() % 128) > p->stamina))
  1256. p->stateDir = (p->stateDir & PLAYER_DIR_MASK) | PLAYER_STATE_WALKING;
  1257. // lower IQ makes decisions less frequent
  1258. p->nextStateChange += (128 - p->iq) / 8;
  1259. }
  1260. else // not changing state
  1261. {
  1262. if (state == PLAYER_STATE_RUNNING || state == PLAYER_STATE_WALKING)
  1263. {
  1264. uint8_t moveOnceIn = (ATTRIBUTE_MAX - p->speed) / 16;
  1265. if (dir % 2) // diagonal?
  1266. moveOnceIn++; // move a bit slower
  1267. moveOnceIn += moveOnceIn == 0; // prevent div by zero
  1268. if (state == PLAYER_STATE_WALKING)
  1269. moveOnceIn *= 2;
  1270. if ((p->nextStateChange % moveOnceIn) == 0)
  1271. {
  1272. int16_t addVec[2];
  1273. dirToVector(dir,addVec);
  1274. p->position[0] += addVec[0];
  1275. p->position[1] += addVec[1];
  1276. if (p->position[0] >= PITCH_W - SQUARE_SIZE)
  1277. p->position[0] = PITCH_W - SQUARE_SIZE - 1;
  1278. else if (p->position[0] < SQUARE_SIZE)
  1279. p->position[0] = SQUARE_SIZE;
  1280. if (p->position[1] >= PITCH_H - SQUARE_SIZE)
  1281. p->position[1] = PITCH_H - SQUARE_SIZE - 1;
  1282. else if (p->position[1] < SQUARE_SIZE)
  1283. p->position[1] = SQUARE_SIZE;
  1284. }
  1285. }
  1286. // slowly shift goalies to the center of the goal
  1287. if (game.ball.player != index &&
  1288. (index == 0 || index == PLAYERS_IN_TEAM) &&
  1289. game.playFrame % SAF_FPS == 0)
  1290. p->position[1] += p->position[1] > PITCH_H / 2 ? -1 :
  1291. (p->position[1] < PITCH_H / 2 ? 1 : 0);
  1292. p->nextStateChange--;
  1293. }
  1294. }
  1295. void gamePlayStep(void)
  1296. {
  1297. if (game.nextTripIn == 0)
  1298. {
  1299. uint8_t playerIndex = rnd() % PLAYERS_TOTAL;
  1300. // chance of tripping
  1301. if ((rnd() % 128) >
  1302. (game.players[playerIndex].agility +
  1303. game.players[playerIndex].stamina) / 2)
  1304. playerTrip(playerIndex);
  1305. game.nextTripIn = rndNorm(TRIP_TIME);
  1306. }
  1307. else
  1308. game.nextTripIn--;
  1309. for (int i = 0; i < PLAYERS_TOTAL; ++i)
  1310. playerUpdate(i);
  1311. ballUpdate();
  1312. if (game.ball.position[0] < SQUARE_SIZE ||
  1313. game.ball.position[0] > PITCH_W - SQUARE_SIZE ||
  1314. game.ball.position[1] < SQUARE_SIZE ||
  1315. game.ball.position[1] > PITCH_H - SQUARE_SIZE)
  1316. {
  1317. SAF_playSound(SOUND_WHISTLE);
  1318. DEBUG_PRINT("ball out");
  1319. if (game.ball.position[1] < SQUARE_SIZE ||
  1320. game.ball.position[1] > PITCH_H - SQUARE_SIZE)
  1321. {
  1322. gameDisplayText("out");
  1323. DEBUG_PRINT("throw in");
  1324. game.playerToKick =
  1325. findPlayerToKick(1 - game.ball.lastPlayer / PLAYERS_IN_TEAM);
  1326. ballReset(game.ball.position[0],game.ball.position[1]);
  1327. gameSetState(GAME_STATE_FREE_KICK);
  1328. }
  1329. else
  1330. {
  1331. int ballOut = game.ball.position[0] < SQUARE_SIZE;
  1332. for (int i = 0; i < 2; ++i)
  1333. {
  1334. if (ballOut)
  1335. {
  1336. if (
  1337. (game.ball.position[1] < PITCH_H / 2 - GOAL_WIDTH / 2) ||
  1338. (game.ball.height > GOAL_HEIGHT &&
  1339. game.ball.position[1] <= PITCH_H / 2) ||
  1340. (game.ball.position[1] > PITCH_H / 2 + GOAL_WIDTH / 2) ||
  1341. (game.ball.height > GOAL_HEIGHT &&
  1342. game.ball.position[1] > PITCH_H / 2))
  1343. {
  1344. if (i == (game.ball.lastPlayer / PLAYERS_IN_TEAM))
  1345. {
  1346. gameDisplayText("corner");
  1347. DEBUG_PRINT("corner");
  1348. game.playerToKick =
  1349. findPlayerToKick(1 - game.ball.lastPlayer / PLAYERS_IN_TEAM);
  1350. game.ball.height = 0;
  1351. game.ball.maxHeight = 0;
  1352. game.ball.vecTo[0] =
  1353. (i ? PITCH_W - SQUARE_SIZE - 1 : (SQUARE_SIZE + 1))
  1354. - game.ball.position[0];
  1355. game.ball.vecTo[1] =
  1356. ((game.ball.position[1] < PITCH_H / 2) ?
  1357. (SQUARE_SIZE + 1) : (PITCH_H - SQUARE_SIZE - 1))
  1358. - game.ball.position[1];
  1359. gameSetState(GAME_STATE_FREE_KICK);
  1360. }
  1361. else
  1362. {
  1363. DEBUG_PRINT("kickoff");
  1364. game.ball.player = i * PLAYERS_IN_TEAM; // give to the goal keeper
  1365. gameSetState(GAME_STATE_KICKOFF);
  1366. }
  1367. }
  1368. else if (game.ball.height == GOAL_HEIGHT ||
  1369. (game.ball.position[1] == PITCH_H / 2 - GOAL_WIDTH / 2) ||
  1370. (game.ball.position[1] == PITCH_H / 2 + GOAL_WIDTH / 2)
  1371. )
  1372. {
  1373. SAF_playSound(SOUND_BOOM);
  1374. gameDisplayText("bar!");
  1375. DEBUG_PRINT("bar!");
  1376. ballHorizontalBounce(i);
  1377. }
  1378. else
  1379. {
  1380. if (game.ball.lastPlayer >= 0 &&
  1381. ((game.players[game.ball.lastPlayer].goalsFouls & 0x0f) != 0x0f))
  1382. game.players[game.ball.lastPlayer].goalsFouls++;
  1383. gameDisplayText("scores!");
  1384. game.score[!i]++;
  1385. gameSetState(GAME_STATE_AFTER_GOAL);
  1386. DEBUG_PRINT("goal!");
  1387. SAF_playSound(SOUND_BEEP);
  1388. }
  1389. }
  1390. ballOut = game.ball.position[0] > PITCH_W - SQUARE_SIZE;
  1391. }
  1392. }
  1393. }
  1394. game.playFrame++;
  1395. if (game.playFrame == PLAY_FRAMES / 2)
  1396. {
  1397. gameDisplayText("half time");
  1398. SAF_playSound(SOUND_WHISTLE);
  1399. gameSetState(GAME_STATE_HALFTIME);
  1400. game.playFrame++;
  1401. }
  1402. if (game.playFrame == PLAY_FRAMES)
  1403. {
  1404. gameDisplayText("end!");
  1405. SAF_playSound(SOUND_WHISTLE);
  1406. gameSetState(GAME_STATE_END);
  1407. }
  1408. }
  1409. void cameraUpdate(void)
  1410. {
  1411. if (cameraFollowMode != 255)
  1412. {
  1413. for (int i = 0; i < 2; ++i)
  1414. {
  1415. int newPos =
  1416. (cameraFollowMode < PLAYERS_TOTAL ?
  1417. game.players[cameraFollowMode].position[i] :
  1418. game.ball.position[i]);
  1419. if (i == 0 && game.playFrame > PLAY_FRAMES / 2 + 1)
  1420. newPos = PITCH_W - newPos;
  1421. newPos -= 32;
  1422. newPos = clamp(cameraPos[i],newPos - 4,newPos + 4);
  1423. cameraPos[i] = (cameraPos[i] + newPos) / 2;
  1424. }
  1425. }
  1426. else
  1427. {
  1428. if (SAF_buttonPressed(SAF_BUTTON_LEFT))
  1429. cameraPos[0] -= CAMERA_SPEED;
  1430. else if (SAF_buttonPressed(SAF_BUTTON_RIGHT))
  1431. cameraPos[0] += CAMERA_SPEED;
  1432. else if (SAF_buttonPressed(SAF_BUTTON_UP))
  1433. cameraPos[1] -= CAMERA_SPEED;
  1434. else if (SAF_buttonPressed(SAF_BUTTON_DOWN))
  1435. cameraPos[1] += CAMERA_SPEED;
  1436. }
  1437. cameraPos[0] = clamp(cameraPos[0],-1 * SQUARE_SIZE,
  1438. PITCH_W - SAF_SCREEN_WIDTH + SQUARE_SIZE);
  1439. cameraPos[1] = clamp(cameraPos[1],-1 * SQUARE_SIZE,
  1440. PITCH_H - SAF_SCREEN_HEIGHT + SQUARE_SIZE);
  1441. }
  1442. // Gets a menu item increment/decrement depending on buttons and time pressed.
  1443. int32_t menuItemIncrement(int32_t maxIncrement)
  1444. {
  1445. uint8_t timePressed = SAF_buttonPressed(SAF_BUTTON_RIGHT) |
  1446. SAF_buttonPressed(SAF_BUTTON_LEFT);
  1447. int32_t result = SAF_buttonPressed(SAF_BUTTON_RIGHT) ? 1 : -1;
  1448. if (timePressed < 2 * SAF_FPS)
  1449. return (timePressed == 1 || timePressed > SAF_FPS) ? result : 0;
  1450. if (timePressed == 255)
  1451. return result * maxIncrement;
  1452. maxIncrement /= 8;
  1453. return result * (maxIncrement ? maxIncrement : 1);
  1454. }
  1455. void gameInit(uint32_t gameNumber)
  1456. {
  1457. DEBUG_PRINT("init game");
  1458. const char *country1 = gameNumToCountryCode(gameNumber,0);
  1459. const char *country2 = gameNumToCountryCode(gameNumber,1);
  1460. game.score[0] = 0;
  1461. game.score[1] = 0;
  1462. game.number = gameNumber;
  1463. ballReset(PITCH_W / 2,PITCH_H / 2);
  1464. gameDisplayText("");
  1465. for (int i = 0; i < PLAYERS_TOTAL; ++i)
  1466. {
  1467. game.players[i] = playerGenerate(
  1468. i < PLAYERS_IN_TEAM ? country1 : (
  1469. i == PLAYERS_TOTAL - 1 ? "??" : // referee
  1470. country2),
  1471. (country1[0] == country2[0] && // same countries -> make diff. teams
  1472. country1[1] == country2[1]) ?
  1473. i : (i % PLAYERS_IN_TEAM),gameNumToSeason(gameNumber));
  1474. playerGetStartPos(i,game.players[i].position);
  1475. }
  1476. currentRand = gameNumToSeed(gameNumber);
  1477. game.playFrame = 0;
  1478. game.nextTripIn = rndNorm(TRIP_TIME);
  1479. game.playerToKick = 0;
  1480. gameSetState(GAME_STATE_AFTER_GOAL);
  1481. }
  1482. void gameUpdate(void)
  1483. {
  1484. if (menuState != MENU_STATE_OFF)
  1485. {
  1486. if (SAF_buttonJustPressed(SAF_BUTTON_A) && menuState == 6)
  1487. {
  1488. gameInit(menuGameNumber);
  1489. menuState = MENU_STATE_OFF;
  1490. }
  1491. if (SAF_buttonPressed(SAF_BUTTON_LEFT) ||
  1492. SAF_buttonPressed(SAF_BUTTON_RIGHT))
  1493. {
  1494. switch (menuState)
  1495. {
  1496. case 0: /* fall through */ // countries
  1497. case 1:
  1498. {
  1499. uint8_t country1 = gameNumToCountryNum(menuGameNumber,0);
  1500. uint8_t country2 = gameNumToCountryNum(menuGameNumber,1);
  1501. if (menuState == 0)
  1502. country1 =
  1503. (country1 + COUNTRIES + menuItemIncrement(1)) % COUNTRIES;
  1504. else
  1505. country2 =
  1506. (country2 + COUNTRIES + menuItemIncrement(1)) % COUNTRIES;
  1507. menuGameNumber = createGameNum(country1,country2,
  1508. gameNumToSeason(menuGameNumber),gameNumToSeed(menuGameNumber));
  1509. break;
  1510. }
  1511. case 2: // season
  1512. menuGameNumber = createGameNum(
  1513. gameNumToCountryNum(menuGameNumber,0),
  1514. gameNumToCountryNum(menuGameNumber,1),
  1515. gameNumToSeason(menuGameNumber) + menuItemIncrement(16),
  1516. gameNumToSeed(menuGameNumber));
  1517. break;
  1518. case 3: // seed
  1519. menuGameNumber = createGameNum(
  1520. gameNumToCountryNum(menuGameNumber,0),
  1521. gameNumToCountryNum(menuGameNumber,1),
  1522. gameNumToSeason(menuGameNumber),
  1523. gameNumToSeed(menuGameNumber) + menuItemIncrement(0x100));
  1524. break;
  1525. case 4: // game number
  1526. menuGameNumber += menuItemIncrement(0x100000);
  1527. break;
  1528. case 5: // camera follow
  1529. cameraFollowMode += menuItemIncrement(1);
  1530. switch (cameraFollowMode)
  1531. {
  1532. case (CAMERA_FOLLOW_NONE - 1):
  1533. cameraFollowMode = CAMERA_FOLLOW_BALL; break;
  1534. case (CAMERA_FOLLOW_BALL - 1):
  1535. cameraFollowMode = PLAYERS_TOTAL - 2; break;
  1536. case (CAMERA_FOLLOW_BALL + 1):
  1537. cameraFollowMode = CAMERA_FOLLOW_NONE; break;
  1538. case (PLAYERS_TOTAL - 1):
  1539. cameraFollowMode = CAMERA_FOLLOW_BALL; break;
  1540. default: break;
  1541. }
  1542. break;
  1543. default: break;
  1544. }
  1545. }
  1546. else if (SAF_buttonJustPressed(SAF_BUTTON_DOWN))
  1547. {
  1548. menuState = (menuState + 1) % MENU_ITEMS;
  1549. SAF_playSound(SAF_SOUND_CLICK);
  1550. }
  1551. else if (SAF_buttonJustPressed(SAF_BUTTON_UP))
  1552. {
  1553. menuState = (MENU_ITEMS + menuState - 1) % MENU_ITEMS;
  1554. SAF_playSound(SAF_SOUND_CLICK);
  1555. }
  1556. else if (SAF_buttonJustPressed(SAF_BUTTON_B))
  1557. {
  1558. menuState = MENU_STATE_OFF;
  1559. SAF_playSound(SAF_SOUND_BUMP);
  1560. }
  1561. return;
  1562. }
  1563. if (SAF_buttonJustPressed(SAF_BUTTON_B))
  1564. {
  1565. menuState = 0;
  1566. menuGameNumber = game.number;
  1567. SAF_playSound(SAF_SOUND_BUMP);
  1568. return;
  1569. }
  1570. if (game.displayTextCountdown)
  1571. game.displayTextCountdown--;
  1572. else
  1573. gameDisplayText("");
  1574. switch (game.state)
  1575. {
  1576. case GAME_STATE_FREE_KICK:
  1577. if (game.stateFrame < 2 * SAF_FPS)
  1578. {
  1579. int16_t dest[2];
  1580. for (int i = 0; i < PLAYERS_TOTAL; ++i)
  1581. {
  1582. Player *p = game.players + i;
  1583. if (i == 0 || i == PLAYERS_IN_TEAM)
  1584. {
  1585. // goal keeper shifts towards his place (for penalty)
  1586. dest[0] = p->position[0];
  1587. dest[1] = PITCH_H / 2;
  1588. }
  1589. else if (i == game.playerToKick)
  1590. {
  1591. dest[0] = game.ball.position[0] + game.ball.vecTo[0];
  1592. dest[1] = game.ball.position[1] + game.ball.vecTo[1];
  1593. }
  1594. else if (
  1595. distTaxi(p->position[0],p->position[1],
  1596. game.ball.position[0] + game.ball.vecTo[0],
  1597. game.ball.position[1] + game.ball.vecTo[1]) < (3 * DIST_CLOSE) / 4)
  1598. {
  1599. // get players away from the ball
  1600. for (int j = 0; j < 2; ++j)
  1601. dest[j] = (p->position[j] +
  1602. game.players[(i / PLAYERS_IN_TEAM) *
  1603. PLAYERS_IN_TEAM].position[j]) / 2;
  1604. dest[1] += i * (SQUARE_SIZE / 2); // space them apart
  1605. }
  1606. else
  1607. {
  1608. dest[0] = p->position[0];
  1609. dest[1] = p->position[1];
  1610. }
  1611. if (game.stateFrame > SAF_FPS / 2)
  1612. {
  1613. if (p->position[0] == dest[0] && p->position[1] == dest[1])
  1614. p->stateDir = (p->stateDir & PLAYER_DIR_MASK) |
  1615. PLAYER_STATE_STANDING;
  1616. else
  1617. {
  1618. p->stateDir =
  1619. (p->stateDir & PLAYER_DIR_MASK) | PLAYER_STATE_RUNNING;
  1620. p->nextStateChange++;
  1621. dirToVector(pointsToDir(p->position,dest),dest);
  1622. p->position[0] += dest[0];
  1623. p->position[1] += dest[1];
  1624. }
  1625. }
  1626. }
  1627. }
  1628. else if (game.stateFrame >= 5 * SAF_FPS)
  1629. {
  1630. ballReset(
  1631. clamp(game.ball.position[0] + game.ball.vecTo[0],
  1632. SQUARE_SIZE + 1,PITCH_W - SQUARE_SIZE - 1),
  1633. clamp(game.ball.position[1] + game.ball.vecTo[1],
  1634. SQUARE_SIZE + 1,PITCH_H - SQUARE_SIZE - 1));
  1635. if (distTaxi(game.ball.position[0],game.ball.position[1],
  1636. (game.playerToKick / PLAYERS_IN_TEAM) ? SQUARE_SIZE :
  1637. (PITCH_W - SQUARE_SIZE),PITCH_H / 2) < DIST_CLOSE)
  1638. playerShootOnGoal(game.playerToKick);
  1639. else
  1640. playerPass(game.playerToKick);
  1641. SAF_playSound(SOUND_WHISTLE);
  1642. gameSetState(GAME_STATE_PLAYING);
  1643. }
  1644. else
  1645. {
  1646. Player *p = game.players + game.playerToKick;
  1647. for (int i = 0; i < 2; ++i)
  1648. {
  1649. game.ball.position[i] +=
  1650. game.ball.vecTo[i] - (game.ball.vecTo[i] / 2);
  1651. game.ball.vecTo[i] /= 2;
  1652. }
  1653. if (game.ball.position[1] < SQUARE_SIZE || // out?
  1654. game.ball.position[1] > PITCH_H - SQUARE_SIZE)
  1655. {
  1656. game.ball.height = 7;
  1657. p->stateDir = PLAYER_STATE_THROWING |
  1658. (game.ball.position[1] < PITCH_H / 2 ? PLAYER_DIR_D : PLAYER_DIR_U);
  1659. }
  1660. else
  1661. p->stateDir = PLAYER_STATE_STANDING |
  1662. ((game.playerToKick / PLAYERS_IN_TEAM) ? PLAYER_DIR_L : PLAYER_DIR_R);
  1663. p->position[0] = game.ball.position[0];
  1664. p->position[1] = game.ball.position[1];
  1665. }
  1666. break;
  1667. case GAME_STATE_KICKOFF:
  1668. if (game.stateFrame > 4 * SAF_FPS)
  1669. gameSetState(GAME_STATE_PLAYING);
  1670. else if (game.stateFrame > 2 * SAF_FPS)
  1671. for (int i = 0; i < 2; ++i)
  1672. game.ball.position[i] = (game.ball.position[i] +
  1673. game.players[game.ball.player].position[i]) / 2;
  1674. break;
  1675. case GAME_STATE_HALFTIME:
  1676. if (game.stateFrame < 2 * SAF_FPS)
  1677. {
  1678. for (int i = 0; i < PLAYERS_TOTAL; ++i)
  1679. game.players[i].stateDir = PLAYER_STATE_STANDING |
  1680. (game.players[i].stateDir & PLAYER_DIR_MASK);
  1681. }
  1682. else if (game.stateFrame < 6 * SAF_FPS)
  1683. {
  1684. for (int i = 0; i < PLAYERS_TOTAL; ++i)
  1685. {
  1686. if (game.players[i].position[0] > PITCH_W / 2)
  1687. {
  1688. game.players[i].stateDir = PLAYER_STATE_RUNNING | PLAYER_DIR_L;
  1689. game.players[i].position[0]--;
  1690. }
  1691. else if (game.players[i].position[0] < PITCH_W / 2)
  1692. {
  1693. game.players[i].stateDir = PLAYER_STATE_RUNNING | PLAYER_DIR_R;
  1694. game.players[i].position[0]++;
  1695. }
  1696. else
  1697. game.players[i].stateDir = PLAYER_STATE_STANDING | PLAYER_DIR_D;
  1698. game.players[i].nextStateChange++;
  1699. game.ball.position[0] = (game.ball.position[0] + PITCH_W / 2) / 2;
  1700. game.ball.position[1] = (game.ball.position[1] + PITCH_H / 2) / 2;
  1701. }
  1702. }
  1703. else
  1704. {
  1705. ballReset(PITCH_W / 2,PITCH_H / 2);
  1706. game.playFrame++; // flip rendering
  1707. gameSetState(GAME_STATE_AFTER_GOAL); // from now on it's like after a goal
  1708. }
  1709. break;
  1710. case GAME_STATE_AFTER_GOAL:
  1711. if (game.stateFrame < 2 * SAF_FPS)
  1712. {
  1713. if (game.ball.lastPlayer >= 0 &&
  1714. (game.ball.lastPlayer / PLAYERS_IN_TEAM) != // if not own goal
  1715. (game.ball.position[0] > PITCH_W / 2)
  1716. )
  1717. game.players[game.ball.lastPlayer].stateDir =
  1718. PLAYER_STATE_THROWING | PLAYER_DIR_D; // celebrate
  1719. }
  1720. else if (game.stateFrame >= 7 * SAF_FPS)
  1721. {
  1722. ballReset(PITCH_W / 2,PITCH_H / 2);
  1723. gameSetState(GAME_STATE_PLAYING);
  1724. SAF_playSound(SOUND_WHISTLE);
  1725. }
  1726. else
  1727. {
  1728. int16_t dest[2];
  1729. game.ball.position[0] = (game.ball.position[0] + PITCH_W / 2) / 2;
  1730. game.ball.position[1] = (game.ball.position[1] + PITCH_H / 2) / 2;
  1731. game.ball.height /= 2;
  1732. for (int i = 0; i < PLAYERS_TOTAL; ++i)
  1733. {
  1734. Player *p = game.players + i;
  1735. playerGetStartPos(i,dest);
  1736. p->stateDir = PLAYER_STATE_STANDING |
  1737. ((i < PLAYERS_IN_TEAM) ? PLAYER_DIR_R : PLAYER_DIR_L);
  1738. if (p->position[0] != dest[0] || p->position[1] != dest[1])
  1739. {
  1740. dirToVector(pointsToDir(p->position,dest),dest);
  1741. p->position[0] += dest[0];
  1742. p->position[1] += dest[1];
  1743. p->stateDir = (p->stateDir & PLAYER_DIR_MASK) | PLAYER_STATE_RUNNING;
  1744. p->nextStateChange++;
  1745. }
  1746. }
  1747. }
  1748. break;
  1749. case GAME_STATE_END:
  1750. {
  1751. uint8_t winTeam = (game.score[0] == game.score[1]) ? 255 :
  1752. (game.score[1] > game.score[0]);
  1753. for (int i = 0; i < PLAYERS_TOTAL; ++i)
  1754. game.players[i].stateDir = PLAYER_DIR_D |
  1755. (i / 8 == winTeam ? PLAYER_STATE_THROWING : PLAYER_STATE_STANDING);
  1756. break;
  1757. }
  1758. default: // GAME_STATE_PLAYING
  1759. gamePlayStep();
  1760. break;
  1761. }
  1762. cameraUpdate();
  1763. if (menuState == MENU_STATE_OFF && game.stateFrame < 4096)
  1764. game.stateFrame++;
  1765. }
  1766. void drawPitchLine(int x, int y, int len, uint8_t horizontal)
  1767. {
  1768. int *xx = horizontal ? &x : &y;
  1769. int *yy = horizontal ? &y : &x;
  1770. int incDec = 1;
  1771. (*yy)--;
  1772. x -= cameraPos[0];
  1773. y -= cameraPos[1];
  1774. if (*yy < 0 || *yy >= 64 || *xx >= 64 || *xx + len < 0)
  1775. return;
  1776. if (*xx + len >= 64)
  1777. len = 64 - *xx;
  1778. if (*xx < 0)
  1779. {
  1780. len += *xx;
  1781. *xx = 0;
  1782. }
  1783. if ((x % 2 == y % 2) ==
  1784. ((cameraPos[0] + 512) % 2 == (cameraPos[1] + 512) % 2))
  1785. {
  1786. *yy += 1;
  1787. incDec = 0;
  1788. }
  1789. while (len)
  1790. {
  1791. SAF_drawPixel(x,y,COLOR_LINE);
  1792. *xx += 1;
  1793. *yy += incDec ? 1 : -1;
  1794. incDec = !incDec;
  1795. len--;
  1796. }
  1797. }
  1798. void drawPlayer(uint8_t index, int16_t screenX, int16_t screenY, uint8_t flip)
  1799. {
  1800. Player *p = game.players + index;
  1801. const uint8_t *p1;
  1802. uint8_t *p2 = tmpImage + 2;
  1803. uint8_t shirtColor = index < PLAYERS_IN_TEAM ? COLOR_TEAM_A :
  1804. (index == PLAYERS_IN_TEAM * 2 ? COLOR_REFEREE : COLOR_TEAM_B);
  1805. uint8_t skinColor = p->look & RACE_BIT ? COLOR_SKIN_BLACK : COLOR_SKIN_WHITE;
  1806. uint8_t hairColor = p->look & HAIR_BIT ? COLOR_HAIR_LIGHT : COLOR_HAIR_DARK;
  1807. uint8_t shoeColor = p->look & SHOE_BIT ? COLOR_SHOES_2 : COLOR_SHOES_1;
  1808. uint8_t transform = 0;
  1809. uint8_t imgIndex = 0;
  1810. uint8_t state = p->stateDir & PLAYER_STATE_MASK;
  1811. uint8_t quantizedDir = ((p->stateDir & PLAYER_DIR_MASK) / 2) * 2;
  1812. if (flip)
  1813. {
  1814. if (state == PLAYER_STATE_KICK_R)
  1815. state = PLAYER_STATE_KICK_L;
  1816. else if (state == PLAYER_STATE_KICK_L)
  1817. state = PLAYER_STATE_KICK_R;
  1818. if (quantizedDir == PLAYER_DIR_R)
  1819. quantizedDir = PLAYER_DIR_L;
  1820. else if (quantizedDir == PLAYER_DIR_L)
  1821. quantizedDir = PLAYER_DIR_R;
  1822. }
  1823. switch (state | quantizedDir)
  1824. {
  1825. // standing:
  1826. case (PLAYER_STATE_STANDING | PLAYER_DIR_U): imgIndex = 2; break;
  1827. case (PLAYER_STATE_STANDING | PLAYER_DIR_D): imgIndex = 0; break;
  1828. case (PLAYER_STATE_STANDING | PLAYER_DIR_R): transform = SAF_TRANSFORM_FLIP;
  1829. // fall through
  1830. case (PLAYER_STATE_STANDING | PLAYER_DIR_L): imgIndex = 4; break;
  1831. // running and kicking up and down:
  1832. case (PLAYER_STATE_KICK_R | PLAYER_DIR_D): // fall through
  1833. case (PLAYER_STATE_KICK_R | PLAYER_DIR_U): transform = SAF_TRANSFORM_FLIP;
  1834. // fall through
  1835. case (PLAYER_STATE_WALKING | PLAYER_DIR_U): // fall through
  1836. case (PLAYER_STATE_WALKING | PLAYER_DIR_D): // fall through
  1837. case (PLAYER_STATE_RUNNING | PLAYER_DIR_U): // fall through
  1838. case (PLAYER_STATE_RUNNING | PLAYER_DIR_D): // fall through
  1839. case (PLAYER_STATE_KICK_L | PLAYER_DIR_U): // fall through
  1840. case (PLAYER_STATE_KICK_L | PLAYER_DIR_D): // fall through
  1841. imgIndex = 1 + 2 * (quantizedDir == PLAYER_DIR_U);
  1842. if (state == PLAYER_STATE_RUNNING)
  1843. transform = ((p->nextStateChange >> 2) & 0x01) ? SAF_TRANSFORM_FLIP : 0;
  1844. else if (state == PLAYER_STATE_WALKING)
  1845. transform = ((p->nextStateChange >> 3) & 0x01) ? SAF_TRANSFORM_FLIP : 0;
  1846. break;
  1847. // running and kicking left and right:
  1848. case (PLAYER_STATE_KICK_R | PLAYER_DIR_R): // fall through
  1849. case (PLAYER_STATE_KICK_L | PLAYER_DIR_R): // fall through
  1850. case (PLAYER_STATE_RUNNING | PLAYER_DIR_R): // fall through
  1851. case (PLAYER_STATE_WALKING | PLAYER_DIR_R): transform = SAF_TRANSFORM_FLIP;
  1852. // fall through
  1853. case (PLAYER_STATE_KICK_R | PLAYER_DIR_L): // fall through
  1854. case (PLAYER_STATE_KICK_L | PLAYER_DIR_L): // fall through
  1855. case (PLAYER_STATE_RUNNING | PLAYER_DIR_L): // fall through
  1856. case (PLAYER_STATE_WALKING | PLAYER_DIR_L): // fall through
  1857. imgIndex = 5 +
  1858. (
  1859. (state | quantizedDir) == (PLAYER_STATE_KICK_R | PLAYER_DIR_R) ||
  1860. (state | quantizedDir) == (PLAYER_STATE_KICK_L | PLAYER_DIR_L) ||
  1861. (state == PLAYER_STATE_RUNNING && ((p->nextStateChange >> 2) & 0x01)) ||
  1862. (state == PLAYER_STATE_WALKING && ((p->nextStateChange >> 3) & 0x01))
  1863. );
  1864. break;
  1865. // lying:
  1866. case (PLAYER_STATE_LYING | PLAYER_DIR_D): imgIndex = 7; break;
  1867. case (PLAYER_STATE_LYING | PLAYER_DIR_U): imgIndex = 8; break;
  1868. case (PLAYER_STATE_LYING | PLAYER_DIR_R): transform = SAF_TRANSFORM_FLIP;
  1869. // fall through
  1870. case (PLAYER_STATE_LYING | PLAYER_DIR_L): imgIndex = 9; break;
  1871. // throwing:
  1872. case (PLAYER_STATE_THROWING | PLAYER_DIR_D): imgIndex = 10; break;
  1873. case (PLAYER_STATE_THROWING | PLAYER_DIR_U): // fall through
  1874. case (PLAYER_STATE_THROWING | PLAYER_DIR_R): // fall through
  1875. case (PLAYER_STATE_THROWING | PLAYER_DIR_L): imgIndex = 11; break;
  1876. default: break;
  1877. }
  1878. p1 = playerImages + imgIndex * (PLAYER_IMAGE_W * PLAYER_IMAGE_H);
  1879. // copy to tmpImage with replaced colors:
  1880. for (int i = 0; i < PLAYER_IMAGE_W * PLAYER_IMAGE_H; ++i)
  1881. {
  1882. switch (*p1)
  1883. {
  1884. case 0xf6: *p2 = skinColor; break;
  1885. case 0x44: *p2 = hairColor; break;
  1886. case 0xa4: *p2 = shirtColor; break;
  1887. case 0x65: *p2 = shoeColor; break;
  1888. default: *p2 = *p1; break;
  1889. }
  1890. p1++;
  1891. p2++;
  1892. }
  1893. SAF_drawImage(tmpImage,screenX,screenY,transform,0x75);
  1894. }
  1895. void drawBall(uint8_t flip)
  1896. {
  1897. int16_t drawPos[2];
  1898. drawPos[0] = (flip ? PITCH_W - game.ball.position[0] : game.ball.position[0])
  1899. - cameraPos[0] - 1;
  1900. drawPos[1] = game.ball.position[1] - cameraPos[1] - 1;
  1901. if (drawPos[0] < -1 || drawPos[0] > SAF_SCREEN_WIDTH + 1 ||
  1902. drawPos[1] < -1)
  1903. return;
  1904. if (drawPos[1] < SAF_SCREEN_HEIGHT + 1)
  1905. SAF_drawImage(imageBallShadow,drawPos[0],drawPos[1],0,SAF_COLOR_RED);
  1906. drawPos[1] -= game.ball.height + 1;
  1907. if (drawPos[1] < SAF_SCREEN_HEIGHT + 1)
  1908. SAF_drawImage(imageBall,drawPos[0],drawPos[1],0,SAF_COLOR_RED);
  1909. }
  1910. void drawGame(void)
  1911. {
  1912. int8_t startX = -1 * ((cameraPos[0] + 512) % 8),
  1913. startY = -1 * ((cameraPos[1] + 512) % 8);
  1914. uint8_t flip = game.playFrame > (PLAY_FRAMES / 2) + 1;
  1915. #if SAF_PLATFORM_COLOR_COUNT > 2
  1916. for (int y = 0; y < 9; ++y)
  1917. for (int x = 0; x < 9; ++x)
  1918. SAF_drawImage(imageGrass,startX + x * 8,startY + y * 8,0,0);
  1919. #else
  1920. SAF_clearScreen(SAF_COLOR_WHITE);
  1921. #endif
  1922. drawPitchLine(SQUARE_SIZE,SQUARE_SIZE,PITCH_W - 2 * SQUARE_SIZE,1);
  1923. drawPitchLine(SQUARE_SIZE,SQUARE_SIZE,PITCH_H - 2 * SQUARE_SIZE,0);
  1924. drawPitchLine(SQUARE_SIZE,PITCH_H - SQUARE_SIZE,
  1925. PITCH_W - 2 * SQUARE_SIZE + 1,1);
  1926. drawPitchLine(PITCH_W - SQUARE_SIZE,SQUARE_SIZE,
  1927. PITCH_H - 2 * SQUARE_SIZE + 1,0);
  1928. drawPitchLine(PITCH_W / 2,SQUARE_SIZE,PITCH_H - 2 * SQUARE_SIZE,0);
  1929. drawPitchLine(SQUARE_SIZE,PITCH_H / 2 - PENALTY_AREA_H / 2,PENALTY_AREA_W,1);
  1930. drawPitchLine(SQUARE_SIZE,PITCH_H / 2 + PENALTY_AREA_H / 2,PENALTY_AREA_W,1);
  1931. drawPitchLine(SQUARE_SIZE + PENALTY_AREA_W,PITCH_H / 2 - PENALTY_AREA_H / 2,
  1932. PENALTY_AREA_H,0);
  1933. drawPitchLine(SQUARE_SIZE,PITCH_H / 2 - GOAL_AREA_H / 2,GOAL_AREA_W,1);
  1934. drawPitchLine(SQUARE_SIZE,PITCH_H / 2 + GOAL_AREA_H / 2,GOAL_AREA_W,1);
  1935. drawPitchLine(SQUARE_SIZE + GOAL_AREA_W,PITCH_H / 2 - GOAL_AREA_H / 2,
  1936. GOAL_AREA_H,0);
  1937. drawPitchLine(PITCH_W - SQUARE_SIZE - PENALTY_AREA_W,
  1938. PITCH_H / 2 - PENALTY_AREA_H / 2,PENALTY_AREA_W,1);
  1939. drawPitchLine(PITCH_W - SQUARE_SIZE - PENALTY_AREA_W,
  1940. PITCH_H / 2 + PENALTY_AREA_H / 2,PENALTY_AREA_W,1);
  1941. drawPitchLine(PITCH_W - SQUARE_SIZE - PENALTY_AREA_W,
  1942. PITCH_H / 2 - PENALTY_AREA_H / 2,PENALTY_AREA_H,0);
  1943. drawPitchLine(PITCH_W - SQUARE_SIZE - GOAL_AREA_W,
  1944. PITCH_H / 2 - GOAL_AREA_H / 2,GOAL_AREA_W,1);
  1945. drawPitchLine(PITCH_W - SQUARE_SIZE - GOAL_AREA_W,
  1946. PITCH_H / 2 + GOAL_AREA_H / 2,GOAL_AREA_W,1);
  1947. drawPitchLine(PITCH_W - SQUARE_SIZE - GOAL_AREA_W,
  1948. PITCH_H / 2 - GOAL_AREA_H / 2,GOAL_AREA_H,0);
  1949. int imgX, imgY;
  1950. imgY = PITCH_H / 2 - 2 * SQUARE_SIZE + 2 - cameraPos[1];
  1951. if (imgY < SAF_SCREEN_HEIGHT && imgY > -3 * SQUARE_SIZE)
  1952. for (int i = 0; i < 2; ++i) // goals
  1953. {
  1954. imgX = (i ? PITCH_W - SQUARE_SIZE : 0) - cameraPos[0];
  1955. if (imgX < SAF_SCREEN_WIDTH && imgX > -1 * SQUARE_SIZE)
  1956. SAF_drawImage(imageGoal,imgX,imgY,i ? 0 : SAF_TRANSFORM_FLIP,0);
  1957. }
  1958. imgX = PITCH_W / 2 - cameraPos[0];
  1959. imgY = PITCH_H / 2 - cameraPos[1];
  1960. if (imgX < SAF_SCREEN_WIDTH + CENTER_CIRCLE_R && imgX > -1 * CENTER_CIRCLE_R &&
  1961. imgY < SAF_SCREEN_HEIGHT + CENTER_CIRCLE_R && imgY > -1 * CENTER_CIRCLE_R)
  1962. SAF_drawCircle(imgX,imgY,CENTER_CIRCLE_R,COLOR_LINE,0);
  1963. // sort players by vertical position for correct drawing:
  1964. {
  1965. int swapped = 1;
  1966. for (int j = 0; swapped && j < PLAYERS_TOTAL - 1; ++j)
  1967. {
  1968. swapped = 0;
  1969. for (int i = 0; i < PLAYERS_TOTAL - 1 - j; ++i)
  1970. if (game.players[playerSortArray[i]].position[1] >
  1971. game.players[playerSortArray[i + 1]].position[1])
  1972. {
  1973. uint8_t tmp = playerSortArray[i];
  1974. playerSortArray[i] = playerSortArray[i + 1];
  1975. playerSortArray[i + 1] = tmp;
  1976. swapped = 1;
  1977. }
  1978. }
  1979. }
  1980. {
  1981. int ballDrawn = 0;
  1982. for (int i = 0; i < PLAYERS_TOTAL; ++i)
  1983. {
  1984. int16_t drawPos[2];
  1985. Player *p = game.players + playerSortArray[i];
  1986. drawPos[0] = (flip ? PITCH_W - p->position[0] : p->position[0])
  1987. - cameraPos[0];
  1988. drawPos[1] = p->position[1] - cameraPos[1];
  1989. if (!ballDrawn && p->position[1] > game.ball.position[1])
  1990. {
  1991. drawBall(flip);
  1992. ballDrawn = 1;
  1993. }
  1994. if (drawPos[0] > -1 * PLAYER_IMAGE_W / 2 &&
  1995. drawPos[0] < SAF_SCREEN_WIDTH + PLAYER_IMAGE_W / 2 &&
  1996. drawPos[1] > -1 * PLAYER_IMAGE_W / 2 &&
  1997. drawPos[1] < SAF_SCREEN_HEIGHT + PLAYER_IMAGE_H)
  1998. drawPlayer(playerSortArray[i],
  1999. drawPos[0] - PLAYER_IMAGE_W / 2,
  2000. drawPos[1] - PLAYER_IMAGE_H + PLAYER_IMAGE_W / 2,flip);
  2001. }
  2002. if (!ballDrawn)
  2003. drawBall(flip);
  2004. }
  2005. // HUD:
  2006. if (!SAF_buttonPressed(SAF_BUTTON_A))
  2007. {
  2008. char str[8];
  2009. str[0] = (game.playFrame * 90) / PLAY_FRAMES; // minutes
  2010. if (str[0] < 10)
  2011. {
  2012. str[0] = '0' + str[0];
  2013. str[1] = 0;
  2014. SAF_drawText(str,59,59,COLORTEXT_1,1);
  2015. }
  2016. else
  2017. {
  2018. str[1] = '0' + (str[0] % 10);
  2019. str[0] = '0' + (str[0] / 10);
  2020. str[2] = 0;
  2021. SAF_drawText(str,54,59,COLORTEXT_1,1);
  2022. }
  2023. const char *country = gameNumToCountryCode(game.number,0);
  2024. str[0] = country[0];
  2025. str[1] = country[1];
  2026. str[2] = ':';
  2027. country = gameNumToCountryCode(game.number,1);
  2028. str[3] = country[0];
  2029. str[4] = country[1];
  2030. str[5] = 0;
  2031. SAF_drawText(str,19,1,COLORTEXT_1,1);
  2032. str[2] = 0;
  2033. for (int i = 0; i < 2; ++i)
  2034. {
  2035. str[0] = '0' + (game.score[i] >= 10 ? game.score[i] / 10 : game.score[i]);
  2036. str[1] = (game.score[i] >= 10 ? '0' + game.score[i] % 10 : 0);
  2037. SAF_drawText(str,i ? (game.score[1] < 10 ? 59 : 54) : 1,1,COLORTEXT_2,1);
  2038. }
  2039. SAF_drawText(game.displayText,1,59,COLORTEXT_2,1);
  2040. }
  2041. }
  2042. void drawMenu(void)
  2043. {
  2044. SAF_clearScreen(MENU_BACKGROUND_COLOR);
  2045. int drawY;
  2046. char str[16];
  2047. str[15] = 0;
  2048. #if SAF_PLATFORM_COLOR_COUNT > 2
  2049. SAF_drawCircle(10,12,28,SAF_COLOR_GRAY,1);
  2050. #endif
  2051. if (menuState >= (MENU_ITEMS - 2))
  2052. {
  2053. SAF_drawRect(0,0,SAF_SCREEN_WIDTH,6,SAF_COLOR_BLUE,1);
  2054. SAF_drawText("stats",1,1,SAF_COLOR_WHITE,1);
  2055. const char *strPtr = gameNumToCountryCode(game.number,
  2056. menuState == (MENU_ITEMS - 1));
  2057. str[0] = strPtr[0];
  2058. str[1] = strPtr[1];
  2059. str[2] = 0;
  2060. SAF_drawText(str,1,7,COLORTEXT_2,1);
  2061. uint8_t stat = (SAF_frame() >> 6) % 13;
  2062. switch (stat)
  2063. {
  2064. case 0: strPtr = " AGE"; break;
  2065. case 1: strPtr = "HEIGHT"; break;
  2066. case 2: strPtr = "WEIGHT"; break;
  2067. case 3: strPtr = " EGO"; break;
  2068. case 4: strPtr = "AGGRES"; break;
  2069. case 5: strPtr = " SPEED"; break;
  2070. case 6: strPtr = "STRENG"; break;
  2071. case 7: strPtr = "STAMIN"; break;
  2072. case 8: strPtr = " ACCUR"; break;
  2073. case 9: strPtr = "AGILIT"; break;
  2074. case 10: strPtr = " IQ"; break;
  2075. case 11: strPtr = " GOALS"; break;
  2076. case 12: strPtr = " FOULS"; break;
  2077. default: strPtr = 0; break;
  2078. }
  2079. SAF_drawText(strPtr,30,7,COLORTEXT_2,1);
  2080. uint8_t playerIndex = (menuState == (MENU_ITEMS - 1)) * PLAYERS_IN_TEAM;
  2081. Player *p = game.players + playerIndex;
  2082. drawY = 13;
  2083. str[3] = 0;
  2084. for (int i = 0; i < PLAYERS_IN_TEAM; ++i)
  2085. {
  2086. uint8_t statePrev = p->stateDir;
  2087. uint8_t statNum = 0;
  2088. p->stateDir = PLAYER_STATE_STANDING | PLAYER_DIR_D;
  2089. drawPlayer(playerIndex,1,drawY,0);
  2090. p->stateDir = statePrev;
  2091. SAF_drawText(p->name,7,drawY + 1,COLORTEXT_2,1);
  2092. switch (stat)
  2093. {
  2094. case 0: statNum = p->ageYears; break;
  2095. case 1: statNum = p->heightCm; break;
  2096. case 2: statNum = p->weightKg; break;
  2097. case 3: statNum = p->ego; break;
  2098. case 4: statNum = p->aggressivity; break;
  2099. case 5: statNum = p->speed; break;
  2100. case 6: statNum = p->strength; break;
  2101. case 7: statNum = p->stamina; break;
  2102. case 8: statNum = p->accuracy; break;
  2103. case 9: statNum = p->agility; break;
  2104. case 10: statNum = p->iq; break;
  2105. case 11: statNum = p->goalsFouls & 0x0f; break;
  2106. case 12: statNum = p->goalsFouls >> 4; break;
  2107. default: break;
  2108. }
  2109. str[0] = statNum >= 100 ? '0' + statNum / 100 : ' ';
  2110. str[1] = statNum >= 10 ? '0' + (statNum / 10) % 10 : ' ';
  2111. str[2] = '0' + statNum % 10;
  2112. SAF_drawText(str,48,drawY + 1,COLORTEXT_1,1);
  2113. playerIndex++;
  2114. drawY += 6;
  2115. p++;
  2116. }
  2117. }
  2118. else
  2119. {
  2120. drawY = 3;
  2121. for (int i = 0; i < MENU_ITEMS - 1; ++i)
  2122. {
  2123. switch (i)
  2124. {
  2125. #define S(n,v) str[n] = v;
  2126. case 0: // fall through
  2127. case 1:
  2128. {
  2129. const char *c = gameNumToCountryCode(menuGameNumber,i);
  2130. S(0,'t') S(1,'e') S(2,'a') S(3,'m') S(4,' ') S(5,'0' + i) S(6,' ')
  2131. S(7,' ') S(8,c[0]) S(9,c[1]) S(10,0)
  2132. break;
  2133. }
  2134. case 2:
  2135. {
  2136. uint8_t s = gameNumToSeason(menuGameNumber);
  2137. S(0,'s') S(1,'e') S(2,'a') S(3,'s') S(4,'o') S(5,'n') S(6,' ')
  2138. S(7,'0' + s / 100) S(8,'0' + (s / 10) % 10) S(9,'0' + s % 10) S(10,0)
  2139. break;
  2140. }
  2141. case 3:
  2142. {
  2143. uint16_t seed = gameNumToSeed(menuGameNumber);
  2144. S(0,'g') S(1,'a') S(2,'m') S(3,'e') S(4,' ')
  2145. S(5,'0' + (seed / 10000)) S(6,'0' + (seed / 1000) % 10)
  2146. S(7,'0' + (seed / 100) % 10) S(8,'0' + (seed / 10) % 10)
  2147. S(9,'0' + seed % 10) S(10,0)
  2148. break;
  2149. }
  2150. case 4:
  2151. {
  2152. S(0,'#') S(1, ' ')
  2153. for (int j = 0; j < 8; ++j)
  2154. S(2 + j,hexDigitToChar((menuGameNumber >> (28 - 4 * j)) & 0x0f))
  2155. S(10,0)
  2156. break;
  2157. }
  2158. case 5:
  2159. S(0,'c') S(1,'a') S(2,'m') S(3,':') S(4,' ') S(5,' ')
  2160. if (cameraFollowMode == CAMERA_FOLLOW_BALL)
  2161. {
  2162. S(6,'b') S(7,'a') S(8,'l') S(9,'l') S(10,0)
  2163. }
  2164. else if (cameraFollowMode == CAMERA_FOLLOW_NONE)
  2165. {
  2166. S(6,'n') S(7,'o') S(8,'n') S(9,'e') S(10,0)
  2167. }
  2168. else
  2169. {
  2170. S(6,' ') S(7,' ')
  2171. S(8,'0' + (cameraFollowMode + 1) / 10)
  2172. S(9,'0' + (cameraFollowMode + 1) % 10)
  2173. S(10,0)
  2174. }
  2175. break;
  2176. case 6:
  2177. S(0,'n') S(1,'e') S(2,'w') S(3,0)
  2178. break;
  2179. default:
  2180. str[0] = 's'; str[1] = 't'; str[2] = 'a'; str[3] = 't';
  2181. str[4] = 's'; str[5] = 0;
  2182. break;
  2183. }
  2184. #undef S
  2185. if (menuState == i)
  2186. SAF_drawRect(0,drawY - 1,SAF_SCREEN_WIDTH,6,SAF_COLOR_BLUE,
  2187. SAF_PLATFORM_COLOR_COUNT > 2);
  2188. SAF_drawText(str,2,drawY,COLORTEXT_2,1);
  2189. drawY += 7;
  2190. }
  2191. }
  2192. }
  2193. void draw(void)
  2194. {
  2195. if (menuState != MENU_STATE_OFF)
  2196. drawMenu();
  2197. else
  2198. drawGame();
  2199. }
  2200. #if DEBUG
  2201. void playerPrint(Player p)
  2202. {
  2203. printf("player %s (skill: %d)\n",p.name,getPlayerSkill(&p));
  2204. printf(" %d years, %s, %s hair, %s foot, shoes %c, %d cm, %d kg\n",
  2205. p.ageYears,
  2206. (p.look & RACE_BIT) ? "black" : "white",
  2207. (p.look & HAIR_BIT) ? "blonde" : "dark",
  2208. (p.look & FOOT_BIT) ? "left" : "right",
  2209. (p.look & SHOE_BIT) ? '1' : '2',
  2210. p.heightCm,p.weightKg);
  2211. printf(" %d ego, %d aggress., %d IQ\n",p.ego,p.aggressivity,p.iq);
  2212. printf(" speed, stamina, accuracy, agility, strength: %d %d %d %d %d\n",
  2213. p.speed, p.stamina, p.accuracy, p.agility, p.strength);
  2214. puts("------");
  2215. }
  2216. void countryPrint(const char *code)
  2217. {
  2218. printf("============= country: %c%c =============\n",code[0],code[1]);
  2219. for (int i = 0; i < PLAYERS_IN_TEAM; ++i)
  2220. playerPrint(playerGenerate(code,i,1));
  2221. }
  2222. void runQuickMatch(uint32_t gameNumber)
  2223. {
  2224. const char *country = gameNumToCountryCode(gameNumber,0);
  2225. printf("================\nrunning match: %c%c ",country[0],country[1]);
  2226. country = gameNumToCountryCode(gameNumber,1);
  2227. printf("%c%c\n",country[0],country[1]);
  2228. gameInit(gameNumber);
  2229. menuState = MENU_STATE_OFF;
  2230. while (game.state != GAME_STATE_END)
  2231. gameUpdate();
  2232. printf("================\nresult: %d %d\n",game.score[0],game.score[1]);
  2233. }
  2234. void runTournament(int season, int seed, int rounds)
  2235. {
  2236. printf("================\nrunning tournament\n");
  2237. uint8_t countryWins[COUNTRIES];
  2238. uint8_t countryLosses[COUNTRIES];
  2239. uint8_t countryGoals[COUNTRIES];
  2240. uint8_t playerGoals[COUNTRIES * PLAYERS_IN_TEAM];
  2241. uint8_t playerFouls[COUNTRIES * PLAYERS_IN_TEAM];
  2242. unsigned int averageGoals = 0;
  2243. for (int i = 0; i < COUNTRIES; ++i)
  2244. {
  2245. countryWins[i] = 0;
  2246. countryLosses[i] = 0;
  2247. countryGoals[i] = 0;
  2248. }
  2249. for (int i = 0; i < COUNTRIES * PLAYERS_IN_TEAM; ++i)
  2250. {
  2251. playerGoals[i] = 0;
  2252. playerFouls[i] = 0;
  2253. }
  2254. for (int k = 0; k < rounds; ++k)
  2255. for (int i = 0; i < COUNTRIES - 1; ++i)
  2256. for (int j = i + 1; j < COUNTRIES; ++j)
  2257. {
  2258. runQuickMatch(createGameNum(i,j,season,seed + k));
  2259. averageGoals += game.score[0] + game.score[1];
  2260. countryGoals[i] += game.score[0];
  2261. countryGoals[j] += game.score[1];
  2262. if (game.score[0] > game.score[1])
  2263. {
  2264. countryWins[i]++;
  2265. countryLosses[j]++;
  2266. }
  2267. else if (game.score[1] > game.score[0])
  2268. {
  2269. countryLosses[i]++;
  2270. countryWins[j]++;
  2271. }
  2272. for (int k = 0; k < PLAYERS_IN_TEAM; ++k)
  2273. {
  2274. playerGoals[i * PLAYERS_IN_TEAM + k] +=
  2275. game.players[k].goalsFouls & 0x0f;
  2276. playerFouls[i * PLAYERS_IN_TEAM + k] +=
  2277. game.players[k].goalsFouls >> 4;
  2278. }
  2279. for (int k = 0; k < PLAYERS_IN_TEAM; ++k)
  2280. {
  2281. playerGoals[j * PLAYERS_IN_TEAM + k] +=
  2282. game.players[PLAYERS_IN_TEAM + k].goalsFouls & 0x0f;
  2283. playerFouls[j * PLAYERS_IN_TEAM + k] +=
  2284. game.players[PLAYERS_IN_TEAM + k].goalsFouls >> 4;
  2285. }
  2286. }
  2287. printf("===== done =====\n");
  2288. for (int i = 0; i < COUNTRIES; ++i)
  2289. printf("%c%c: %d wins, %d losses, %d draws, %d goals\n",
  2290. countryNames[i * 2],countryNames[i * 2 + 1],
  2291. countryWins[i],countryLosses[i],
  2292. rounds * (COUNTRIES - 1) - countryWins[i] -
  2293. countryLosses[i],countryGoals[i]);
  2294. unsigned int mostGoals = 0, mostFouls = 0;
  2295. for (int i = 0; i < COUNTRIES * PLAYERS_IN_TEAM; ++i)
  2296. {
  2297. if (playerGoals[i] > playerGoals[mostGoals])
  2298. mostGoals = i;
  2299. if (playerFouls[i] > playerFouls[mostFouls])
  2300. mostFouls = i;
  2301. }
  2302. averageGoals /= (((COUNTRIES - 1) * COUNTRIES) / 2) * rounds;
  2303. printf(
  2304. "average goals: %u\nmost goals: %d, %d (%d)\nmost fouls: %d, %d (%d)\n",
  2305. averageGoals,mostGoals / PLAYERS_IN_TEAM,mostGoals % PLAYERS_IN_TEAM,
  2306. playerGoals[mostGoals],
  2307. mostFouls / PLAYERS_IN_TEAM,mostFouls % PLAYERS_IN_TEAM,
  2308. playerFouls[mostFouls]);
  2309. }
  2310. #endif
  2311. void SAF_init(void)
  2312. {
  2313. tmpImage[0] = PLAYER_IMAGE_W;
  2314. tmpImage[1] = PLAYER_IMAGE_H;
  2315. cameraFollowMode = CAMERA_FOLLOW_BALL;
  2316. game.displayText[DISPLAY_TEXT_SIZE - 1] = 0;
  2317. for (int i = 0; i < PLAYERS_TOTAL; ++i)
  2318. playerSortArray[i] = i;
  2319. gameInit(createGameNum(0,1,0,0));
  2320. menuState = 1;
  2321. }
  2322. uint8_t SAF_loop(void)
  2323. {
  2324. draw();
  2325. gameUpdate();
  2326. return 1;
  2327. }