123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856 |
- /*
- procball
- This is a zero player simple and fun simulator of football matches with
- procedurally generated players and teams from many countries. Ideal for
- watching football when you don't have TV or Internet at hand :) You can run
- your own championship whenever and wherever you want. There are over 4 billion
- unique matches, each one having with ID by which you can replay the match at
- any time, or share it with a friend. Players are procedurally generated based
- on their country, each one having many attributes including name, height,
- weight, preferred foot, speed, strength, intelligence etc. Dislike what
- players you got on your favorite team? Just change season -- different ones
- will be generated. But please keep in mind that the game is minimalist,
- simplified (no offside, no cards, ...) and aimed at fun, not realism.
- By drummyfish, released under CC0 1.0, public domain.
- */
- #define SAF_PROGRAM_NAME "procball"
- #define DEBUG 0
- #define SAF_SETTING_FORCE_1BIT 0 // set to 1 for 1bit version
- #if DEBUG
- #define DEBUG_PRINT(s) puts(s)
- #else
- #define DEBUG_PRINT(s) ;
- #endif
- #define SAF_SETTING_FASTER_1BIT 1
- #define SAF_SETTING_1BIT_DITHER 0
- #define SAF_SETTING_ENABLE_SAVES 0
- #include "../saf.h"
- //====== static data ======
- static const uint8_t imageBall[11] =
- {
- #if SAF_PLATFORM_COLOR_COUNT > 2
- 0x03,0x03,0xb7,0xff,0xb7,0xff,0xff,0xff,0xb7,0xff,0xb7
- #else
- 0x03,0x03,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00
- #endif
- };
- static const uint8_t imageBallShadow[11] =
- {
- #if SAF_PLATFORM_COLOR_COUNT > 2
- 0x03,0x03,0x52,0x4d,0x52,0x4d,0x4d,0x4d,0x52,0x4d,0x52
- #else
- 0x03,0x03,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00
- #endif
- };
- static const uint8_t imageGrass[66] =
- {
- 0x08,0x08,0x75,0x75,0x75,0x75,0x75,0x96,0x75,0x75,0x96,0x75,0x75,0x95,0x75,
- 0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x95,0x75,0x75,0x75,0x95,0x75,
- 0x75,0x75,0x75,0x96,0x75,0x75,0x75,0x96,0x75,0x75,0x75,0x75,0x95,0x75,0x75,
- 0x75,0x75,0x95,0x75,0x75,0x75,0x75,0x95,0x75,0x75,0x75,0x96,0x75,0x96,0x75,
- 0x75,0x96,0x75,0x75,0x75,0x75
- };
- static const uint8_t imageGoal[194] =
- {
- 0x08,0x18,0xdb,0xff,0xff,0xb7,0x75,0x75,0x75,0x75,0xff,0xb6,0xfb,0xff,0xb7,
- 0x75,0x75,0x75,0xff,0xfb,0x75,0xd7,0x75,0xb7,0x75,0x75,0xff,0xb6,0xfb,0xff,
- 0xbb,0x75,0xb7,0x75,0xff,0xfb,0x75,0xd7,0x75,0xbb,0x50,0xb7,0xff,0xb6,0xfb,
- 0xff,0xbb,0x75,0xbb,0xb7,0xff,0xfb,0x75,0xd7,0x75,0xbb,0x50,0xbb,0xff,0xb6,
- 0xfb,0xff,0xbb,0xdb,0xbb,0xb7,0xff,0xfb,0x75,0xd7,0x75,0xbb,0x50,0xbb,0xff,
- 0xb6,0xfb,0xff,0xbb,0x75,0xbb,0xb7,0xff,0xfb,0x75,0xd7,0x75,0xbb,0x50,0xbb,
- 0xff,0xb6,0xfb,0xff,0xbb,0x75,0xbb,0xb7,0xff,0xfb,0x75,0xd7,0x75,0xbb,0x50,
- 0xbb,0xff,0xb6,0xfb,0xff,0xbb,0x75,0xbb,0xb7,0xff,0xfb,0x75,0xd7,0x75,0xbb,
- 0x50,0xbb,0xff,0xb6,0xfb,0xff,0xbb,0x75,0xbb,0xb7,0xdb,0xff,0xff,0xff,0x75,
- 0xbb,0x50,0xbb,0xdb,0xb6,0xb7,0x75,0xdb,0x50,0xbb,0xb7,0xdb,0xb6,0x75,0xb7,
- 0x75,0xdb,0x50,0xbb,0xdb,0xb6,0xb7,0x75,0xb7,0x50,0xdb,0xb7,0xdb,0xb6,0x75,
- 0xb7,0x50,0xb7,0x50,0xdb,0xdb,0xb6,0xb7,0x75,0xb7,0x50,0xb7,0xdb,0xdb,0xb6,
- 0x75,0xb7,0x50,0xb7,0x50,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb
- };
- #define PLAYER_IMAGE_W 5
- #define PLAYER_IMAGE_H 8
- static const uint8_t playerImages[] =
- {
- 0x75,0x44,0x44,0x44,0x75,0x75,0x44,0xf6,0x44,0x75,0xa4,0xa4,0xa4,0xa4,0xa4,
- 0xf6,0xa4,0xa4,0xa4,0xf6,0x75,0xdb,0xdb,0xdb,0x75,0x52,0xdb,0x4d,0xdb,0x52,
- 0x4d,0x65,0x4d,0x65,0x4d,0x75,0x4d,0x4d,0x4d,0x75, // down
- 0x75,0x44,0x44,0x44,0x75,0x75,0x44,0xf6,0x44,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,
- 0xf6,0xa4,0xa4,0xa4,0x75,0x75,0xb6,0xdb,0xdb,0x75,0x52,0x4d,0xb6,0xdb,0x52,
- 0x4d,0x4d,0x4d,0x65,0x4d,0x75,0x4d,0x4d,0x4d,0x75, // down running (L foot)
- 0x75,0x44,0x44,0x44,0x75,0x75,0x44,0x44,0x44,0x75,0xa4,0xa4,0xa4,0xa4,0xa4,
- 0xf6,0xa4,0xa4,0xa4,0xf6,0x75,0xdb,0xdb,0xdb,0x75,0x52,0xdb,0x4d,0xdb,0x52,
- 0x4d,0x65,0x4d,0x65,0x4d,0x75,0x4d,0x4d,0x4d,0x75, // up
- 0x75,0x44,0x44,0x44,0x75,0x75,0x44,0x44,0x44,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,
- 0xf6,0xa4,0xa4,0xa4,0x75,0x75,0xdb,0xb6,0xdb,0x75,0x52,0x4d,0x4d,0xdb,0x52,
- 0x4d,0x4d,0x4d,0x65,0x4d,0x75,0x4d,0x4d,0x4d,0x75, // up running (L foot)
- 0x75,0x44,0x44,0x75,0x75,0x75,0xf6,0x44,0x75,0x75,0x75,0xa4,0xa4,0x75,0x75,
- 0x75,0xa4,0xa4,0x75,0x75,0x75,0xdb,0xf6,0x75,0x75,0x52,0x65,0xdb,0x52,0x75,
- 0x4d,0x4d,0x65,0x4d,0x75,0x75,0x4d,0x4d,0x75,0x75, // left
- 0x75,0x44,0x44,0x75,0x75,0x75,0xf6,0x44,0x75,0x75,0x75,0xa4,0xa4,0x75,0x75,
- 0xf6,0xa4,0xa4,0x75,0x75,0xdb,0xdb,0xdb,0x75,0x75,0x65,0x4d,0xb6,0xdb,0x75,
- 0x4d,0x4d,0x4d,0x65,0x75,0x75,0x4d,0x4d,0x75,0x75, // left running (R foot)
- 0x75,0x44,0x44,0x75,0x75,0x75,0xf6,0x44,0x75,0x75,0xf6,0xa4,0xa4,0x75,0x75,
- 0x75,0xa4,0xa4,0xa4,0x75,0x75,0xdb,0xdb,0xf6,0x75,0xdb,0xb6,0x4d,0x52,0x75,
- 0x65,0x4d,0x4d,0x4d,0x75,0x75,0x4d,0x4d,0x75,0x75, // left running (L foot)
- 0x75,0x75,0x75,0x75,0x75,0x75,0x65,0x75,0x65,0x75,0x75,0xdb,0x4d,0xdb,0x75,
- 0x75,0xdb,0xdb,0xdb,0x75,0x75,0xa4,0xa4,0xa4,0x75,0xa4,0xa4,0xa4,0xa4,0xa4,
- 0xa4,0x44,0x44,0x44,0xa4,0xf6,0x4d,0x44,0x4d,0xf6, // down lying
- 0x75,0x75,0x75,0x75,0x75,0xf6,0x4d,0x44,0x4d,0xf6,0xa4,0x44,0x44,0x44,0xa4,
- 0xa4,0xa4,0xa4,0xa4,0xa4,0x4d,0xa4,0xa4,0xa4,0x4d,0x52,0xdb,0xdb,0xdb,0x52,
- 0x75,0xdb,0x52,0xdb,0x75,0x75,0x65,0x75,0x65,0x75, // up lying
- 0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0xf6,0x75,0x75,0x75,0x75,
- 0xa4,0xb6,0xdb,0x65,0x75,0xa4,0xa4,0xb6,0xdb,0x65,0xa4,0xa4,0xa4,0x4d,0x75,
- 0x44,0x44,0xa4,0x75,0x75,0x44,0x44,0xf6,0x75,0x75, // left lying
- 0xf6,0x44,0x44,0x44,0xf6,0xa4,0x44,0xf6,0x44,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,
- 0x75,0xa4,0xa4,0xa4,0x75,0x75,0xdb,0xdb,0xdb,0x75,0x52,0xdb,0x4d,0xdb,0x52,
- 0x4d,0x65,0x4d,0x65,0x4d,0x75,0x4d,0x4d,0x4d,0x75, // down throwing
- 0xf6,0x44,0x44,0x44,0xf6,0xa4,0x44,0x44,0x44,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,
- 0x75,0xa4,0xa4,0xa4,0x75,0x75,0xdb,0xdb,0xdb,0x75,0x52,0xdb,0x4d,0xdb,0x52,
- 0x4d,0x65,0x4d,0x65,0x4d,0x75,0x4d,0x4d,0x4d,0x75 // up throwing
- };
- #define SQUARE_SIZE 8 // small spatial unit
- #define PITCH_W (27 * SQUARE_SIZE)
- #define PITCH_H (16 * SQUARE_SIZE)
- #define CAMERA_SPEED 4 // free camera speed
- #define PLAY_FRAMES (SAF_FPS * 600) // playing time of one game, in frames
- #define PLAYERS_IN_TEAM 8
- #define PLAYERS_TOTAL (PLAYERS_IN_TEAM * 2 + 1) // including referee
- #define NAME_MAX_LEN 7 // player name max len (without term. 0)
- // pitch line dimensions:
- #define PENALTY_AREA_W (PITCH_W / 6)
- #define PENALTY_AREA_H (4 * PITCH_H / 9)
- #define GOAL_AREA_W (PITCH_W / 10)
- #define GOAL_AREA_H (PITCH_H / 4)
- #define CENTER_CIRCLE_R SQUARE_SIZE
- #define DIST_CLOSE (PITCH_H / 3) // distance considered "close"
- #define DIST_AT (SQUARE_SIZE / 2) // reachable distance
- #define BALL_BOUNCE 4 // higher number => smaller bounce
- #define BALL_CATCH_HEIGHT 5 // max height where ball can be caught
- #define BALL_HAND_HEIGHT 2 // min height for hand to occur
- #define TRIP_TIME (SAF_FPS * 8) // max time in which someone trips
- #define COUNTRIES 32
- #define GOAL_WIDTH (SQUARE_SIZE * 2 + SQUARE_SIZE / 2)
- #define GOAL_HEIGHT 7
- #define COLOR_LINE 0xb7 // pitch line color
- #define COLOR_TEAM_A 0xa4
- #define COLOR_TEAM_B 0x0f
- #define COLOR_REFEREE 0x92
- #define COLOR_SKIN_WHITE 0xf6
- #define COLOR_SKIN_BLACK 0x68
- #define COLOR_HAIR_DARK 0x44
- #define COLOR_HAIR_LIGHT 0xac
- #define COLOR_SHOES_1 0x65
- #define COLOR_SHOES_2 0x0a
- #define COLORTEXT_1 SAF_COLOR_GRAY_DARK
- #define COLORTEXT_2 SAF_COLOR_WHITE
- #define MENU_BACKGROUND_COLOR 0x99
- #define SOUND_WHISTLE SAF_SOUND_CLICK
- #define SOUND_BUMP SAF_SOUND_BUMP
- #define SOUND_BOOM SAF_SOUND_BOOM
- #define SOUND_BEEP SAF_SOUND_BEEP
- #define PLAYER_STATE_MASK 0xf0
- #define PLAYER_DIR_MASK 0x0f
- #define PLAYER_BASE_STATE_FRAMES 32 // base no. of frames for state duration
- #define PLAYER_STATE_STANDING 0x00
- #define PLAYER_STATE_WALKING 0x10
- #define PLAYER_STATE_RUNNING 0x20
- #define PLAYER_STATE_LYING 0x30
- #define PLAYER_STATE_KICK_R 0x40
- #define PLAYER_STATE_KICK_L 0x50
- #define PLAYER_STATE_THROWING 0x60
- #define PLAYER_DIR_U 0
- #define PLAYER_DIR_UR 1
- #define PLAYER_DIR_R 2
- #define PLAYER_DIR_DR 3
- #define PLAYER_DIR_D 4
- #define PLAYER_DIR_DL 5
- #define PLAYER_DIR_L 6
- #define PLAYER_DIR_UL 7
- #define RACE_BIT 0x01
- #define HAIR_BIT 0x02
- #define SHOE_BIT 0x04
- #define FOOT_BIT 0x08
- #define ATTRIBUTE_MAX 127 // maximum for certain attributes
- #define BALL_ANIM_FRAMES 32
- #if SAF_PLATFORM_COLOR_COUNT <= 2
- // redefine colors for 1 bit version:
- #undef COLOR_LINE
- #undef COLORTEXT_1
- #undef COLORTEXT_2
- #undef COLOR_TEAM_A
- #undef COLOR_TEAM_B
- #undef COLOR_REFEREE
- #undef COLOR_SKIN_WHITE
- #undef COLOR_SKIN_BLACK
- #undef COLOR_HAIR_DARK
- #undef COLOR_HAIR_LIGHT
- #undef COLOR_SHOES_1
- #undef COLOR_SHOES_2
- #undef MENU_BACKGROUND_COLOR
- #define COLOR_LINE SAF_COLOR_BLACK
- #define COLORTEXT_1 SAF_COLOR_BLACK
- #define COLORTEXT_2 SAF_COLOR_BLACK
- #define COLOR_TEAM_A SAF_COLOR_BLACK
- #define COLOR_TEAM_B SAF_COLOR_WHITE
- #define COLOR_REFEREE SAF_COLOR_BLACK
- #define COLOR_SKIN_WHITE SAF_COLOR_BLACK
- #define COLOR_SKIN_BLACK SAF_COLOR_BLACK
- #define COLOR_HAIR_DARK SAF_COLOR_BLACK
- #define COLOR_HAIR_LIGHT SAF_COLOR_BLACK
- #define COLOR_SHOES_1 SAF_COLOR_BLACK
- #define COLOR_SHOES_2 SAF_COLOR_BLACK
- #define MENU_BACKGROUND_COLOR SAF_COLOR_WHITE
- #endif
- static const char countryNames[] =
- "AR" /* Argentina */ "AT" /* Austria */ "AU" /* Australia */
- "AQ" /* Antarctica */ "BR" /* Brazil */ "CA" /* Canada */
- "CN" /* China */ "CZ" /* Czechia */ "DE" /* Germany */
- "ES" /* Spain */ "FI" /* Finland */ "FR" /* France */
- "GB" /* England */ "GR" /* Greece */ "HR" /* Croatia */
- "HU" /* Hungary */ "IL" /* Israel */ "IN" /* India */
- "IT" /* Italy */ "JP" /* Japan */ "KP" /* North Korea */
- "KR" /* South Korea */ "NE" /* Niger */ "NL" /* Netherlands */
- "PO" /* Poland */ "PT" /* Portugal */ "RU" /* Russia */
- "SE" /* Sweden */ "SK" /* Slovakia */ "TR" /* Turkey */
- "UA" /* Ukraine */ "US" /* USA */;
- // Converts two country characters to a single number.
- #define COUNTRY_NUM(c1,c2) ((((int) c2) << 8) | ((int) c1))
- typedef struct
- {
- // purely aesthetic:
- char name[NAME_MAX_LEN + 1];
- uint8_t look; // bits: 0: race, 1: hair, 3: shoes, 2: foot (L/R)
- // personality, may affect play:
- uint8_t ageYears; // will affect some attributes
- uint8_t heightCm; // will affect some attributes
- uint8_t weightKg; // will affect some attributes
- uint8_t ego; // less likely to pass etc.
- uint8_t aggressivity; // more likely to foul, go for ball etc.
-
- // skills (higher = better):
- uint8_t speed; // faster run, turns, decisions
- uint8_t strength; // faster and longer kicks/throws, better fights
- uint8_t stamina; // less likely to trip or rest
- uint8_t accuracy; // more precise shots and throws, better catch
- uint8_t agility; // better 1v1, catching, less likely to trip
- uint8_t iq; // faster and better decisions, fewer blunders
- // dynamic attributes:
- uint8_t stateDir; // state and direction combined
- uint8_t nextStateChange; // frame till next state change
- int16_t position[2]; // pixel position on the pitch
- uint8_t goalsFouls; // goals scored (lower 4 bits), fouls committed
- } Player;
- //====== global variables ======
- #define CAMERA_FOLLOW_NONE 255
- #define CAMERA_FOLLOW_BALL 128
- uint32_t currentRand; // for LCG
- uint8_t playerSortArray[PLAYERS_TOTAL]; // helper for drawing
- uint8_t tmpImage[2 + PLAYER_IMAGE_W * PLAYER_IMAGE_H]; // helper image
- int cameraPos[2]; // screen top left pos. in pixels
- uint8_t cameraFollowMode; /* follow mode of camera:
- 0 to PLAYERS_TOTAL -1: follow player
- 128: follow ball
- 255: no follow */
- #define MENU_ITEMS 9
- #define MENU_STATE_OFF 255
- uint8_t menuState;
- uint32_t menuGameNumber;
- #define GAME_STATE_PLAYING 0 // normal play
- #define GAME_STATE_AFTER_GOAL 1 // goal was scored, also start of game
- #define GAME_STATE_FREE_KICK 2 // free kick, pen., throw, corner etc.
- #define GAME_STATE_KICKOFF 3 // goal keeper to kick off
- #define GAME_STATE_HALFTIME 4
- #define GAME_STATE_END 5
- #define DISPLAY_TEXT_SIZE 10
- struct
- {
- uint16_t number; /* complete game seed, structure (from lowest bits):
- 5 bits: country 1
- 5 bits: country 2
- 8 bits: season
- 14 bits: random seed */
- uint8_t state;
- uint8_t score[2];
- uint16_t stateFrame; // frames elapsed since start of state
- char displayText[DISPLAY_TEXT_SIZE];
- uint8_t displayTextCountdown;
- uint32_t playFrame; // frames of play time since start of match
- uint16_t nextTripIn; // frames until next random trip
- uint8_t playerToKick; // player index, for free kicks, throws etc.
- Player players[PLAYERS_TOTAL]; /* first team, then second, then referee,
- player 0 on a team is goal keeper */
- struct
- {
- int16_t position[2]; // current, interpolated position
- int16_t posFrom[2]; // start position
- int16_t vecTo[2]; // vector to end position
- uint8_t height; // current height
- uint8_t maxHeight; // maximum height to reach
- uint8_t interpolParam; // determines position between start and end pos.
- int8_t lastPlayer; // player that touched the ball last
- int8_t player; // player in possesion or -1
- } ball;
- } game;
- //====== functions ======
- void clampAttribute(uint8_t *attr)
- {
- if (*attr > ATTRIBUTE_MAX)
- *attr = ATTRIBUTE_MAX;
- }
- int clamp(int v, int min, int max)
- {
- return v > max ? max : (v < min ? min : v);
- }
- int16_t distTaxi(int16_t x0, int16_t y0, int16_t x1, int16_t y1)
- {
- return (x0 > x1 ? x0 - x1 : x1 - x0) + (y0 > y1 ? y0 - y1 : y1 - y0);
- }
- void gameSetState(uint8_t state)
- {
- DEBUG_PRINT("changing game state");
- game.state = state;
- game.stateFrame = 0;
- }
- // turns one step towards desired direction
- uint8_t dirTurnTowards(uint8_t current, uint8_t desired)
- {
- if (desired == current)
- return current;
- uint8_t opposite = (current + 4) % 8;
- return ((current + 8) + // god bless this mess
- ((((desired > opposite && desired < current) |
- (desired > current && desired < opposite)) ==
- (opposite < current)) ? -1 : 1)) % 8;
- }
- void dirToVector(uint8_t dir, int16_t v[2])
- {
- v[0] = (dir == PLAYER_DIR_R || dir == PLAYER_DIR_UR ||
- dir == PLAYER_DIR_DR) +
- ((dir == PLAYER_DIR_L || dir == PLAYER_DIR_UL ||
- dir == PLAYER_DIR_DL) ? -1 : 0);
- v[1] = (dir == PLAYER_DIR_D || dir == PLAYER_DIR_DR ||
- dir == PLAYER_DIR_DL) +
- ((dir == PLAYER_DIR_U || dir == PLAYER_DIR_UR ||
- dir == PLAYER_DIR_UL) ? -1 : 0);
- }
- char hexDigitToChar(uint8_t digit)
- {
- return digit < 10 ? ('0' + digit) : ('A' + digit - 10);
- }
- uint32_t createGameNum(uint8_t country1, uint8_t country2, uint8_t season,
- uint16_t seed)
- {
- seed &= 0x3fff;
- return ((uint32_t) country1) | (((uint32_t) country2) << 5) |
- (((uint32_t) season) << 10) | (((uint32_t) seed) << 18);
- }
- uint16_t gameNumToSeed(uint32_t n)
- {
- return n >> 18;
- }
- uint8_t gameNumToCountryNum(uint32_t n, uint8_t countryNum)
- {
- return (n >> (countryNum == 0 ? 0 : 5)) & 0x1f;
- }
- const char *gameNumToCountryCode(uint32_t n, uint8_t countryNum)
- {
- return countryNames + 2 * gameNumToCountryNum(n,countryNum);
- }
- uint8_t gameNumToSeason(uint32_t n)
- {
- return (n >> 10) & 0xff;
- }
- // 16 bit random num, 32 bit period
- unsigned int rnd(void)
- {
- currentRand = 22695477 * currentRand + 123;
- return currentRand >> 16;
- }
- // normal distribution random number
- unsigned int rndNorm(unsigned int maximum)
- {
- // enforce order of evaluation:
- unsigned int result = rnd() % maximum;
- result += rnd() % maximum;
- result += maximum - 1 - rnd() % maximum;
- result += maximum - 1 - rnd() % maximum;
- return result / 4;
- }
- uint8_t getPlayerSkill(const Player *p)
- {
- return (((int) p->speed) + ((int) p->strength) + ((int) p->stamina) +
- ((int) p->accuracy) + ((int) p->agility) + ((int) p->iq)) / 6;
- }
- int areaHasAtLeastPlayers(int16_t centerX, int16_t centerY, int16_t dist,
- uint8_t team, uint8_t count)
- {
- centerX -= dist;
- centerY -= dist;
- dist *= 2;
- for (int i = team * PLAYERS_IN_TEAM;
- i < PLAYERS_IN_TEAM + team * PLAYERS_IN_TEAM; ++i)
- if (game.players[i].position[0] >= centerX &&
- game.players[i].position[0] <= centerX + dist &&
- game.players[i].position[1] >= centerY &&
- game.players[i].position[1] <= centerY + dist)
- {
- count--;
-
- if (!count)
- return 1;
- }
- return 0;
- }
- void ballReset(int16_t x, int16_t y)
- {
- game.ball.posFrom[0] = x;
- game.ball.posFrom[1] = y;
- game.ball.position[0] = x;
- game.ball.position[1] = y;
- game.ball.vecTo[0] = 0;
- game.ball.vecTo[1] = 0;
- game.ball.height = 0;
- game.ball.player = -1;
- game.ball.lastPlayer = -1;
- game.ball.interpolParam = BALL_ANIM_FRAMES + 1;
- }
- void playerGetStartPos(uint8_t index, int16_t result[2])
- {
- if (index == PLAYERS_IN_TEAM * 2) // referee?
- {
- result[0] = PITCH_W / 2;
- result[1] = PITCH_H / 2 - SQUARE_SIZE;
- return;
- }
- int flip = 0;
- if (index >= PLAYERS_IN_TEAM)
- {
- flip = 1;
- index -= PLAYERS_IN_TEAM;
- }
- switch (index)
- {
- case 0:
- result[0] = SQUARE_SIZE * 2;
- result[1] = PITCH_H / 2;
- break;
- case 1: // fall through
- case 2:
- result[0] = PITCH_W / 6;
- result[1] = PITCH_H / 3;
- break;
- case 3: // fall through
- case 4:
- result[0] = PITCH_W / 4 + SQUARE_SIZE;
- result[1] = PITCH_H / 4;
- break;
- case 5: // fall through
- case 6:
- result[0] = PITCH_W / 2 - SQUARE_SIZE - SQUARE_SIZE / 2;
- result[1] = 3 * SQUARE_SIZE;
- break;
- default:
- result[0] = PITCH_W / 2 - 2 * SQUARE_SIZE;
- result[1] = PITCH_H / 2;
- break;
- }
- if (index == 2 || index == 4 || index == 6)
- result[1] = PITCH_H - result[1];
- if (flip)
- result[0] = PITCH_W - result[0];
- }
- // Shoots ball from its current pos to new pos, reaching given max height.
- void ballShoot(int16_t toX, int16_t toY, uint8_t maxHeight, uint8_t accuracy)
- {
- DEBUG_PRINT("ball shoot");
- int spread = 32 - (32 * accuracy) / 128;
- spread += spread == 0;
- toX = toX - spread / 2 + (rnd() % spread);
- toY = toY - spread / 2 + (rnd() % spread);
- game.ball.player = -1;
- game.ball.vecTo[0] = toX - game.ball.position[0];
- game.ball.vecTo[1] = toY - game.ball.position[1];
- if (game.ball.position[0] == toX && game.ball.position[1] == toY)
- game.ball.interpolParam = BALL_ANIM_FRAMES + 1;
- else
- {
- game.ball.posFrom[0] = game.ball.position[0];
- game.ball.posFrom[1] = game.ball.position[1];
- game.ball.interpolParam = 0;
- game.ball.maxHeight = maxHeight;
- }
- }
- void gameDisplayText(const char *text)
- {
- #if DEBUG
- if (*text != 0)
- printf("[%4x: %s]\n",game.playFrame,text);
- #endif
- int i = 0;
- game.displayTextCountdown = SAF_FPS * 3;
- while (text && i < DISPLAY_TEXT_SIZE - 1)
- {
- game.displayText[i] = *text;
- text++;
- i++;
- }
- game.displayText[i] = 0;
- }
- Player playerGenerate(const char *country2Chars, uint8_t index, uint8_t season)
- {
- uint32_t seedBackup = currentRand;
- currentRand = ((uint32_t) index) |
- (((uint32_t) country2Chars[0]) << 8) |
- (((uint32_t) country2Chars[1]) << 16) |
- (((uint32_t) season) << 24);
- Player p;
- #define IS_COUNTRY(c1,c2) (country2Chars[0] == c1 && country2Chars[1] == c2)
- // base values:
- p.weightKg = 72;
- p.heightCm = 175;
- p.iq = 100;
- p.ego = 90;
- p.ageYears = 27;
- p.aggressivity = 90;
- p.speed = 95;
- p.strength = 78;
- p.stamina = 95;
- p.accuracy = 90;
- p.agility = 90;
- char nameLetters[] = // letters to make names from
- "rtpsdfghjklzcbnm" // first letters
- "aeiouyeo"; // second letters
- uint8_t blackProbability = 10; // probability of being black, max 127
- uint8_t blondeProbability = 30;
- uint8_t countrySkill = 50;
- switch (COUNTRY_NUM(country2Chars[0],country2Chars[1]))
- {
- case COUNTRY_NUM('U','S'):
- p.weightKg += 10;
- blackProbability *= 5;
- p.iq -= 6;
- p.strength += 7;
- countrySkill -= 8;
- p.ego += 32;
- p.aggressivity += 20;
- break;
- case COUNTRY_NUM('I','N'):
- blackProbability *= 4;
- blondeProbability /= 4;
- break;
- case COUNTRY_NUM('K','P'):
- p.weightKg -= 4;
- p.heightCm -= 3;
- // fall through
- case COUNTRY_NUM('J','P'):
- // fall through
- case COUNTRY_NUM('K','R'):
- // fall through
- case COUNTRY_NUM('C','N'):
- p.iq += 8;
- p.weightKg -= 5;
- p.heightCm -= 5;
- blackProbability = 0;
- blondeProbability = 5;
- p.agility += 5;
- p.ego -= 25;
- p.aggressivity -= 11;
- countrySkill -= 35;
- nameLetters[0] = 'i';
- nameLetters[2] = 'o';
- nameLetters[21] = '-';
- nameLetters[22] = '\'';
- nameLetters[23] = 'g';
- nameLetters[24] = 'c';
- break;
- case COUNTRY_NUM('I','L'):
- p.iq += 10;
- blackProbability = 0;
- break;
- case COUNTRY_NUM('N','E'):
- p.iq -= 15;
- p.weightKg -= 5;
- blackProbability = 120;
- blondeProbability = 10;
- break;
- case COUNTRY_NUM('F','R'):
- blackProbability *= 4;
- nameLetters[22] = '\'';
- countrySkill += 18;
- p.ego += 5;
- break;
- case COUNTRY_NUM('S','E'):
- blondeProbability = 55;
- break;
- case COUNTRY_NUM('B','R'):
- countrySkill += 10;
- // fall through
- case COUNTRY_NUM('P','T'):
- countrySkill += 10;
- // fall through
- case COUNTRY_NUM('A','R'):
- p.agility += 20;
- blondeProbability /= 2;
- countrySkill += 15;
- break;
- case COUNTRY_NUM('E','S'):
- countrySkill += 20;
- blondeProbability /= 2;
- p.agility += 4;
- break;
- case COUNTRY_NUM('G','B'):
- countrySkill += 20;
- break;
- case COUNTRY_NUM('I','T'):
- countrySkill += 14;
- break;
- case COUNTRY_NUM('R','U'):
- p.aggressivity += 12;
- p.strength += 7;
- blackProbability = 2;
- break;
- case COUNTRY_NUM('A','Q'):
- countrySkill -= 20;
- break;
- default: break;
- }
- // randomize
- p.weightKg += -36 + rndNorm(72);
- p.heightCm += -30 + rndNorm(60);
- p.ageYears += -17 + rndNorm(34);
- p.iq += -50 + rndNorm(100);
- p.ego += -20 + rndNorm(40);
- p.aggressivity += -22 + rndNorm(44);
- p.speed -= p.ageYears / 8;
- p.stamina -= p.ageYears / 7;
- p.strength -= p.ageYears / 6;
- p.iq -= p.ageYears / 8;
- p.speed -= p.weightKg / 8;
- p.strength += p.weightKg / 16;
- p.strength += p.heightCm / 16;
- int skillPoints = rndNorm(countrySkill);
- for (int i = 0; i < 4; ++i) // take away some skill point for reshuffling
- {
- #define TAKE_AWAY 15
- switch (rnd() % 5)
- {
- case 0: p.speed -= TAKE_AWAY; break;
- case 1: p.stamina -= TAKE_AWAY; break;
- case 2: p.strength -= TAKE_AWAY; break;
- case 3: p.accuracy -= TAKE_AWAY; break;
- case 4: p.agility -= TAKE_AWAY; break;
- default: break;
- }
- skillPoints += TAKE_AWAY;
- #undef TAKE_AWAY
- }
- while (skillPoints) // now pour the skill points back in
- {
- switch (rnd() % 5)
- {
- case 0: p.speed++; break;
- case 1: p.stamina++; break;
- case 2: p.strength++; break;
- case 3: p.accuracy++; break;
- case 4: p.agility++; break;
- default: break;
- }
- skillPoints--;
- }
- for (int i = 0; i < NAME_MAX_LEN + 1; ++i) // make the name
- {
- p.name[i] = 0;
- if ((i == NAME_MAX_LEN) | ((i > 2) & (rnd() % 16 < 2)))
- {
- if (!(
- (p.name[i - 1] >= 'A' && p.name[i - 1] <= 'Z') ||
- (p.name[i - 1] >= 'a' && p.name[i - 1] <= 'z')))
- p.name[i] = 'u'; // let last name be always a letter
- break;
- }
- int r = rnd();
- p.name[i] = (rnd() % 64 < 58) ?
- ((unsigned int) nameLetters[((i % 2) * 16) + (r % (i % 2 ? 8 : 16))])
- : (unsigned int) ('a' + r % 26); // sometimes add completely random letter
- if (i == 0)
- p.name[i] = (p.name[i] - 'a') + 'A';
- }
- p.look = 0;
- if ((rnd() % 128) < blackProbability)
- p.look |= RACE_BIT;
- if ((rnd() % 128) < blondeProbability)
- p.look |= HAIR_BIT;
- if ((rnd() % 128) < 36)
- p.look |= FOOT_BIT;
- if (rnd() & 0x80)
- p.look |= SHOE_BIT;
- if (p.look & RACE_BIT)
- {
- // less intelligent but faster
- p.iq -= 8;
- p.speed += 8;
- }
- clampAttribute(&p.ego);
- clampAttribute(&p.aggressivity);
- clampAttribute(&p.speed);
- clampAttribute(&p.strength);
- clampAttribute(&p.stamina);
- clampAttribute(&p.accuracy);
- clampAttribute(&p.agility);
- currentRand = seedBackup;
- p.position[0] = PITCH_W / 2;
- p.position[1] = PITCH_H / 2;
- p.stateDir = PLAYER_STATE_STANDING | PLAYER_DIR_D;
- p.nextStateChange = 0;
- p.goalsFouls = 0;
- return p;
- }
- // finds a player to kick a free kick (also throw in etc.)
- uint8_t findPlayerToKick(uint8_t team)
- {
- uint8_t start = ((team != 0) * PLAYERS_IN_TEAM) + 1; // exclude goal keeper
- int16_t bestD = 10000;
- uint8_t best = start;
- for (int i = start; i < start + PLAYERS_IN_TEAM - 1; ++i)
- {
- int16_t d = distTaxi(game.players[i].position[0],
- game.players[i].position[1],game.ball.position[0],game.ball.position[1]);
- if (d < bestD)
- {
- best = i;
- bestD = d;
- }
- }
- return best;
- }
- void playerMisconducts(uint8_t index)
- {
- Player *p = game.players + index;
- DEBUG_PRINT("misconduct");
- game.playerToKick = findPlayerToKick(1 - (index / PLAYERS_IN_TEAM));
- if (
- p->position[1] > (PITCH_H / 2 - PENALTY_AREA_H / 2) &&
- p->position[1] < (PITCH_H / 2 + PENALTY_AREA_H / 2) &&
- (
- (((index / PLAYERS_IN_TEAM) == 1) && p->position[0]
- > PITCH_W - SQUARE_SIZE - PENALTY_AREA_W) ||
- (((index / PLAYERS_IN_TEAM) == 0) && p->position[0]
- < SQUARE_SIZE + PENALTY_AREA_W)
- ))
- {
- gameDisplayText("penalty!");
- DEBUG_PRINT("penalty!");
- game.ball.vecTo[0] = SQUARE_SIZE + PENALTY_AREA_W;
- if (game.ball.position[0] > PITCH_W / 2)
- game.ball.vecTo[0] = PITCH_W - game.ball.vecTo[0];
- game.ball.vecTo[0] = game.ball.vecTo[0] - game.ball.position[0];
- game.ball.vecTo[1] = PITCH_H / 2 - game.ball.position[1];
- }
- else
- ballReset(game.ball.position[0],game.ball.position[1]);
- SAF_playSound(SOUND_WHISTLE);
- gameSetState(GAME_STATE_FREE_KICK);
- }
- // Converts two points to direction.
- uint8_t pointsToDir(int16_t p0[2], int16_t p1[2])
- {
- int16_t tangentTimes2 = (2 * (p0[0] - p1[0])) /
- (p0[1] != p1[1] ? (p0[1] - p1[1]) : 1);
- switch ((p1[0] >= p0[0]) | ((p1[1] >= p0[1]) << 1) |
- ((tangentTimes2 != 0) << 4) |
- (((tangentTimes2 > 4) | (tangentTimes2 < -4)) << 5))
- {
- case 0x01: // fall through
- case 0x00: return PLAYER_DIR_U; break;
- case 0x10: return PLAYER_DIR_UL; break;
- case 0x20: return PLAYER_DIR_UL; break;
- case 0x11: return PLAYER_DIR_UR; break;
- case 0x32: // fall through
- case 0x30: return PLAYER_DIR_L; break;
- case 0x33: // fall through
- case 0x31: return PLAYER_DIR_R; break;
- case 0x03: // fall through
- case 0x02: return PLAYER_DIR_D; break;
- case 0x12: return PLAYER_DIR_DL; break;
- case 0x13: return PLAYER_DIR_DR; break;
- default: return PLAYER_DIR_U;
- }
- }
- void playerShoot(uint8_t index, int16_t point[2], uint8_t height)
- {
- Player *p = game.players + index;
- int16_t dest[2];
- dest[0] = point[0];
- dest[1] = point[1];
- game.ball.player = -1;
- p->stateDir = // we just rotate immediately here
- pointsToDir(p->position,dest) |
- ((p->look & FOOT_BIT) ? PLAYER_STATE_KICK_L : PLAYER_STATE_KICK_R);
- p->nextStateChange = clamp(
- PLAYER_BASE_STATE_FRAMES - ((p->agility + p->speed) / 32),
- 0,PLAYER_BASE_STATE_FRAMES * 10);
- if (distTaxi(dest[0],dest[1],game.ball.position[0],game.ball.position[1])
- > DIST_CLOSE + p->strength / 16)
- for (int i = 0; i < 2; ++i)
- {
- dest[i] -= p->position[i];
- dest[i] = (dest[i] * clamp(p->strength + 10,0,128)) / 128;
- dest[i] += p->position[i];
- }
- ballShoot(dest[0],dest[1],height,p->accuracy);
- SAF_playSound(SOUND_BUMP);
- }
- void playerPass(uint8_t index)
- {
- DEBUG_PRINT("pass");
- int bestScore = -1;
- Player *p = game.players +
- (index / PLAYERS_IN_TEAM) * PLAYERS_IN_TEAM;
- Player *bestP = p + 1;
- int16_t enemyGoalX = index < PLAYERS_IN_TEAM ? PITCH_W : 0;
- for (int i = 0; i < PLAYERS_IN_TEAM; ++i)
- {
- if (p != (game.players + index))
- {
- int score = 1000 -
- (distTaxi(enemyGoalX,PITCH_H / 2,p->position[0],p->position[1]) +
- distTaxi(p->position[0],p->position[1],
- game.players[i].position[0],game.players[i].position[1]))
- / SQUARE_SIZE;
- if ((score > bestScore) & ((rnd() % 128) < p->iq))
- {
- bestScore = score;
- bestP = p;
- }
- }
-
- p++;
- }
- playerShoot(index,bestP->position,0);
- }
- void playerTrip(uint8_t index)
- {
- game.players[index].stateDir = PLAYER_STATE_LYING |
- (game.players[index].stateDir & PLAYER_DIR_MASK);
- game.players[index].nextStateChange =
- clamp(2 * PLAYER_BASE_STATE_FRAMES - game.players[index].speed / 2,
- 0,PLAYER_BASE_STATE_FRAMES * 3) +
- (game.players[index].ego / 4); // bigger ego => longer drama
- game.players[index].nextStateChange += PLAYER_BASE_STATE_FRAMES / 3;
- if (game.ball.player == index)
- {
- int16_t dest[2];
- dirToVector(game.players[index].stateDir & PLAYER_DIR_MASK,dest);
- dest[0] = game.players[index].position[0] + 2 * SQUARE_SIZE;
- dest[1] = game.players[index].position[1] + 2 * SQUARE_SIZE;
- game.ball.player = -1;
- ballShoot(dest[0],dest[1],0,100);
- }
- SAF_playSound(SOUND_BOOM);
- }
- // The player will start turning or running towards given point.
- void playerWantsToRunTo(uint8_t index, int16_t point[2])
- {
- point[0] = clamp(point[0],0,PITCH_W);
- point[1] = clamp(point[1],0,PITCH_H);
- Player *p = game.players + index;
- if (p->position[0] == point[0] && p->position[1] == point[1])
- {
- p->stateDir =
- (p->stateDir & PLAYER_DIR_MASK) | PLAYER_STATE_STANDING;
- }
- else
- {
- uint8_t direction = p->stateDir & PLAYER_DIR_MASK;
- uint8_t desired = pointsToDir(p->position,point);
- uint8_t turnSpeed = clamp((p->speed + p->agility) / 64,1,10);
- while (turnSpeed && direction != desired)
- {
- direction = dirTurnTowards(direction,desired);
- turnSpeed--;
- }
- p->stateDir =
- (direction == desired ? PLAYER_STATE_RUNNING : PLAYER_STATE_STANDING)
- | direction;
- }
- p->nextStateChange = PLAYER_BASE_STATE_FRAMES / 3;
- }
- // Bounces a ball horizontally.
- void ballHorizontalBounce(int right)
- {
- right = right ? PITCH_W - SQUARE_SIZE : SQUARE_SIZE;
- game.ball.posFrom[0] = -1 * game.ball.posFrom[0] + 2 * right;
- game.ball.position[0] = -1 * game.ball.position[0] + 2 * right;
- game.ball.vecTo[0] *= -1;
- }
- void ballUpdate(void)
- {
- if (game.ball.player != -1) // someone holds the ball?
- {
- game.ball.interpolParam = BALL_ANIM_FRAMES + 1;
- dirToVector(game.players[game.ball.player].stateDir & PLAYER_DIR_MASK,
- game.ball.position);
- game.ball.position[0] = 2 * game.ball.position[0] +
- game.players[game.ball.player].position[0];
- game.ball.position[1] = 2 * game.ball.position[1] +
- game.players[game.ball.player].position[1];
- }
- else
- {
- // no one holds the ball
- if (game.ball.interpolParam <= BALL_ANIM_FRAMES)
- {
- game.ball.position[0] = game.ball.posFrom[0] +
- (game.ball.interpolParam * game.ball.vecTo[0]) / BALL_ANIM_FRAMES;
- game.ball.position[1] = game.ball.posFrom[1] +
- (game.ball.interpolParam * game.ball.vecTo[1]) / BALL_ANIM_FRAMES;
- int newHeight = game.ball.interpolParam;
- newHeight -= BALL_ANIM_FRAMES / 2;
- newHeight *= newHeight;
- newHeight = ((BALL_ANIM_FRAMES / 2) * (BALL_ANIM_FRAMES / 2)) - newHeight;
- newHeight = (game.ball.maxHeight * newHeight) /
- ((BALL_ANIM_FRAMES / 2) * (BALL_ANIM_FRAMES / 2));
- if (newHeight == 0 && game.ball.height != 0)
- SAF_playSound(SOUND_BUMP);
- game.ball.height = newHeight;
- game.ball.interpolParam++;
- if (game.ball.interpolParam >= BALL_ANIM_FRAMES) // bounce
- ballShoot(game.ball.position[0] + game.ball.vecTo[0] / BALL_BOUNCE,
- game.ball.position[1] + game.ball.vecTo[1] / BALL_BOUNCE,
- game.ball.maxHeight / BALL_BOUNCE,128);
- }
- if (game.ball.height <= BALL_CATCH_HEIGHT) // see if someone catches it
- {
- // make it more fair, don't prioritize one team in ball pick:
- int index = (game.playFrame % 2) * PLAYERS_IN_TEAM;
- for (int i = 0; i < PLAYERS_IN_TEAM * 2; ++i)
- {
- uint8_t state = game.players[index].stateDir & PLAYER_STATE_MASK;
- uint8_t goalieBonus = ((index % PLAYERS_IN_TEAM) == 0) ? 2 : 0;
- if (
- state != PLAYER_STATE_KICK_R &&
- state != PLAYER_STATE_KICK_L &&
- state != PLAYER_STATE_LYING &&
- distTaxi(
- game.players[index].position[0],
- game.players[index].position[1],
- game.ball.position[0],
- game.ball.position[1]) <= (DIST_AT + goalieBonus) &&
- ( // prob. of catch depends on ball trajectory len. and agility
- (rnd() % 128 +
- distTaxi(0,0,game.ball.vecTo[0],game.ball.vecTo[1]) /
- (2 * SQUARE_SIZE)) <= ((unsigned int)
- (game.players[index].agility + game.players[index].accuracy) / 2
- + goalieBonus * 4)
- )
- )
- {
- game.ball.player = index;
- game.ball.lastPlayer = index;
- game.ball.height = 0;
- if (game.state == GAME_STATE_PLAYING)
- gameDisplayText(game.players[index].name);
- if ((game.ball.height >= BALL_HAND_HEIGHT) & ((rnd() % 128) < 20))
- {
- gameDisplayText("hand!");
- DEBUG_PRINT("hand!");
- playerMisconducts(index);
- }
- break;
- }
- index = (index + 1) % (PLAYERS_IN_TEAM * 2);
- }
- }
- }
- }
- void playerFightForBall(uint8_t index)
- {
- DEBUG_PRINT("fight for ball");
- if (game.ball.player < 0)
- return;
- if ((rnd() % 128) <
- ((3 * ((int) game.players[index].aggressivity) / 4)))
- {
- // foul
- DEBUG_PRINT("player attacked");
- game.players[index].stateDir =
- (game.players[index].stateDir & PLAYER_DIR_MASK) |
- ((game.players[index].look & FOOT_BIT) ?
- PLAYER_STATE_KICK_L : PLAYER_STATE_KICK_R);
- playerTrip(game.ball.player);
- if ((rnd() % 128) <
- ((unsigned int) 40 + game.players[index].aggressivity / 16))
- {
- gameDisplayText("foul!");
- DEBUG_PRINT("foul!");
- playerMisconducts(index);
- if ((game.players[index].goalsFouls & 0xf0) != 0xf0)
- game.players[index].goalsFouls += 0x10;
- }
- }
- else
- {
- // normal fight
- // enforce evaluation order:
- unsigned int cond =
- (rnd() % (game.players[index].agility + 1 +
- game.players[index].aggressivity % 32));
- cond = cond >
- (rnd() % (game.players[game.ball.player].agility + 12));
- if (cond)
- {
- DEBUG_PRINT("won the ball");
- game.ball.player = index;
- }
- }
- }
- void playerShootOnGoal(uint8_t index)
- {
- gameDisplayText("shoots!");
- DEBUG_PRINT("shoot on goal!");
- int16_t dest[2], stren;
- stren = game.players[index].strength;
- dest[0] = rnd() % (2 + stren / 8);
- dest[0] = (index / PLAYERS_IN_TEAM) ?
- (SQUARE_SIZE - dest[0]) : (PITCH_W - SQUARE_SIZE + dest[0]);
- dest[1] = PITCH_H / 2 - GOAL_WIDTH / 2 + (rnd() % GOAL_WIDTH);
- playerShoot(index,dest,rnd() % (2 + stren / 16));
- }
- void playerUpdate(uint8_t index)
- {
- Player *p = game.players + index;
- uint8_t state = p->stateDir & PLAYER_STATE_MASK;
- uint8_t dir = p->stateDir & PLAYER_DIR_MASK;
- int16_t dest[2]; // helper
- uint8_t myTeam = index / PLAYERS_IN_TEAM;
- dest[0] = PITCH_W / 2;
- dest[1] = PITCH_H / 2;
- if (p->nextStateChange == 0)
- {
- // by default just stand:
- p->stateDir = (p->stateDir & PLAYER_DIR_MASK) | PLAYER_STATE_STANDING;
- p->nextStateChange = PLAYER_BASE_STATE_FRAMES;
- if (index == 2 * PLAYERS_IN_TEAM) // am I the referee?
- {
- // just stay near the ball
- for (int i = 0; i < 2; ++i)
- dest[i] = clamp(p->position[i],game.ball.position[i] - SQUARE_SIZE * 3,
- game.ball.position[i] + SQUARE_SIZE * 3);
- playerWantsToRunTo(index,dest);
- }
- else if (index % PLAYERS_IN_TEAM == 0) // am I goal keeper?
- {
- if (game.ball.player == index) // I have the ball?
- {
- if (game.ball.height == 0)
- {
- // pick it up
- DEBUG_PRINT("picking up the ball");
- game.ball.height = 5;
- p->nextStateChange = 2 * PLAYER_BASE_STATE_FRAMES + rnd() % 32;
- }
- else
- {
- DEBUG_PRINT("kicking the ball out");
- dest[0] = PITCH_W / 4 + rnd() % (PITCH_W / 4) + SQUARE_SIZE;
- if (index >= PLAYERS_IN_TEAM)
- dest[0] = PITCH_W - dest[0];
- dest[1] = PITCH_H / 2 - SQUARE_SIZE * 4 + rnd() % (SQUARE_SIZE * 8);
- playerShoot(index,dest,p->strength / 8 + rnd() % 8);
- }
- }
- else
- {
- int16_t dist = distTaxi(p->position[0],p->position[1],
- game.ball.position[0],game.ball.position[1]);
- if (dist <= DIST_AT)
- playerFightForBall(index);
- else if ((dist <= (DIST_CLOSE / 2)) & ((rnd() % 128) < 85)) // near?
- {
- dest[0] = game.ball.position[0];
- dest[1] = game.ball.position[1];
- playerWantsToRunTo(index,dest);
- p->nextStateChange /= 4;
- }
- else if (dist < 2 * DIST_CLOSE) // a bit close
- {
- dest[0] = index == 0 ? 2 * SQUARE_SIZE : (PITCH_W - 2 * SQUARE_SIZE);
- dest[1] = clamp(game.ball.position[1],PITCH_H / 2 - GOAL_WIDTH / 2,
- PITCH_H / 2 + GOAL_WIDTH / 2);
- playerWantsToRunTo(index,dest);
- dir = p->stateDir & PLAYER_DIR_MASK;
- if ((
- (myTeam == (game.playFrame < PLAY_FRAMES / 2))
- && dir == PLAYER_DIR_R) ||
- (myTeam == (game.playFrame >= PLAY_FRAMES / 2)
- && dir == PLAYER_DIR_L))
- {
- /* here rotate immediatelly (fixes a visual bug when goalie stares
- into own goal */
- p->stateDir = (p->stateDir & PLAYER_STATE_MASK) |
- pointsToDir(p->position,dest);
- }
- p->nextStateChange /= 2;
- }
- else
- {
- p->stateDir = PLAYER_STATE_STANDING |
- (index == 0 ? PLAYER_DIR_R : PLAYER_DIR_L);
- }
- }
- }
- else if (game.ball.player == -1) // no one has the ball?
- {
- // if close, run for it, else decide randomly if run or not
- if ((distTaxi(p->position[0],p->position[1],game.ball.position[0],
- game.ball.position[1]) < DIST_CLOSE) | ((rnd() % 127) < 30))
- playerWantsToRunTo(index,game.ball.position);
- else if (rnd() % 128 < 64)
- {
- // run to start position
- playerGetStartPos(index,dest);
- playerWantsToRunTo(index,dest);
- }
- // else just stand
- }
- else if (game.ball.player == index) // I have the ball?
- {
- // enforce evaluation order:
- int doBlunder = (rnd() % 16) == 0;
- doBlunder &= (rnd() % 128) > p->iq;
- if (doBlunder)
- {
- gameDisplayText("oh no!");
- DEBUG_PRINT("blunder");
- for (int i = 0; i < 2; ++i)
- dest[i] = p->position[i] - 8 * SQUARE_SIZE +
- (rnd() % (16 * SQUARE_SIZE));
- playerShoot(index,dest,rnd() % 16);
- }
- else
- {
- // own goal coords:
- dest[0] = index >= PLAYERS_IN_TEAM ? PITCH_W : 0;
- dest[1] = PITCH_H / 2;
- if (distTaxi(p->position[0],p->position[1],dest[0],dest[1]) < DIST_CLOSE)
- {
- // close to own goal
- if (areaHasAtLeastPlayers(p->position[0],p->position[1],DIST_CLOSE,
- !myTeam,1))
- {
- // enemies nearby => kick the ball away
- dest[0] = (((int16_t) p->strength) *
- ((p->position[0] >= PITCH_W / 2) ?
- -1 * PITCH_H / 2 : PITCH_H / 2)) / 128;
- dest[1] = -1 * PITCH_H / 4 + (rnd() % (PITCH_H / 2));
- dest[0] += p->position[0];
- dest[1] += p->position[1];
- playerShoot(index,dest,p->strength / 8);
- }
- else
- {
- dest[0] = PITCH_W / 2;
- dest[1] = PITCH_H / 2 - 4 * SQUARE_SIZE + rnd() % (SQUARE_SIZE * 8);
- playerWantsToRunTo(index,dest);
- }
- }
- else
- {
- // not close to own goal
- dest[0] = index >= PLAYERS_IN_TEAM ? 0 : PITCH_W; // enemy goal coord
- int enemyGoalDist = distTaxi(p->position[0],p->position[1],dest[0],
- dest[1]);
- // enforce evaluation order
- int cond = ((enemyGoalDist < DIST_CLOSE) & ((rnd() % 2)));
- cond |= (enemyGoalDist < 3 * DIST_CLOSE / 2) &
- ((rnd() % 16) <= (p->ego / 32));
- if (cond)
- {
- // close to enemy goal + some chance
- playerShootOnGoal(index);
- }
- else
- {
- // somewhere else
- // where I'd like to run:
- dest[0] = p->position[0] + (index < PLAYERS_IN_TEAM ?
- 4 * SQUARE_SIZE : -4 * SQUARE_SIZE);
- dest[1] = clamp(
- ((p->position[1] + PITCH_H / 2) / 2) - (SQUARE_SIZE * 4)
- + rnd() % (SQUARE_SIZE * 8),
- SQUARE_SIZE + SQUARE_SIZE / 2,
- PITCH_H - SQUARE_SIZE - SQUARE_SIZE / 2);
- if (areaHasAtLeastPlayers(dest[0],dest[1],SQUARE_SIZE,!myTeam,1))
- {
- // occupied by enemies
- if ((rnd() > p->ego) & !(
- // in front of goal only pass if there is any teammate
- (enemyGoalDist < 3 * DIST_CLOSE / 4) &&
- !areaHasAtLeastPlayers(
- game.players[myTeam * PLAYERS_IN_TEAM].position[0],
- game.players[myTeam * PLAYERS_IN_TEAM].position[1],
- DIST_CLOSE / 2,myTeam,2)))
- playerPass(index);
- else
- {
- DEBUG_PRINT("going sideways");
- dest[0] = p->position[0] + rnd() % SQUARE_SIZE;
- dest[1] = PITCH_H / 2 +
- ((rnd() & 0x01) ? PITCH_W / 3 : PITCH_W / -3);
- playerWantsToRunTo(index,dest);
- }
- }
- else
- {
- DEBUG_PRINT("going forward");
- playerWantsToRunTo(index,dest);
- }
- }
- }
- }
- }
- else // someone else has the ball
- {
- uint8_t ballTeam = game.ball.player / PLAYERS_IN_TEAM;
- uint8_t runAround = 1;
-
- dest[0] = p->position[0];
- dest[1] = p->position[1];
- if (ballTeam != myTeam && // enemy has the ball?
- game.ball.height == 0 && // don't go after goal keeper holding ball
- (distTaxi(p->position[0],p->position[1],
- game.ball.position[0],game.ball.position[1]) <= DIST_CLOSE) &&
- !areaHasAtLeastPlayers(game.ball.position[0],game.ball.position[1],
- SQUARE_SIZE,myTeam,1 + (p->aggressivity + p->ego) / 128))
- {
- if (distTaxi(p->position[0],p->position[1],
- game.ball.position[0],game.ball.position[1]) <= DIST_AT)
- {
- playerFightForBall(index);
- runAround = 0;
- }
- else if ((rnd() % 128) <
- ((unsigned int) (p->aggressivity + p->ego) / 2 + 25))
- {
- // go after him
- dest[0] = game.ball.position[0];
- dest[1] = game.ball.position[1];
- runAround = 0;
- }
- }
- if ((rnd() % 128 <= ((unsigned int) p->stamina + 20)) // not lazy?
- & runAround)
- {
- // find some spot near the action
- playerGetStartPos(index,dest);
- // if we have the ball, stretch the starting pos
- if (myTeam == game.ball.player / PLAYERS_IN_TEAM)
- dest[0] = myTeam ?
- (PITCH_W - (2 * (PITCH_W - dest[0]))) : (2 * dest[0]);
- dest[0] = (dest[0] + game.ball.position[0]) / 2; // average with ball X
- }
- playerWantsToRunTo(index,dest);
- }
- // closer to ball will react faster
- if (distTaxi(p->position[0],p->position[1],game.ball.position[0],
- game.ball.position[1]) < DIST_CLOSE)
- p->nextStateChange /= 2;
- // sometimes randomly walk instead of run
- if (((p->stateDir & PLAYER_STATE_MASK) == PLAYER_STATE_RUNNING) &
- ((rnd() % 128) > p->stamina))
- p->stateDir = (p->stateDir & PLAYER_DIR_MASK) | PLAYER_STATE_WALKING;
- // lower IQ makes decisions less frequent
- p->nextStateChange += (128 - p->iq) / 8;
- }
- else // not changing state
- {
- if (state == PLAYER_STATE_RUNNING || state == PLAYER_STATE_WALKING)
- {
- uint8_t moveOnceIn = (ATTRIBUTE_MAX - p->speed) / 16;
- if (dir % 2) // diagonal?
- moveOnceIn++; // move a bit slower
- moveOnceIn += moveOnceIn == 0; // prevent div by zero
- if (state == PLAYER_STATE_WALKING)
- moveOnceIn *= 2;
- if ((p->nextStateChange % moveOnceIn) == 0)
- {
- int16_t addVec[2];
- dirToVector(dir,addVec);
- p->position[0] += addVec[0];
- p->position[1] += addVec[1];
- if (p->position[0] >= PITCH_W - SQUARE_SIZE)
- p->position[0] = PITCH_W - SQUARE_SIZE - 1;
- else if (p->position[0] < SQUARE_SIZE)
- p->position[0] = SQUARE_SIZE;
- if (p->position[1] >= PITCH_H - SQUARE_SIZE)
- p->position[1] = PITCH_H - SQUARE_SIZE - 1;
- else if (p->position[1] < SQUARE_SIZE)
- p->position[1] = SQUARE_SIZE;
- }
- }
- // slowly shift goalies to the center of the goal
- if (game.ball.player != index &&
- (index == 0 || index == PLAYERS_IN_TEAM) &&
- game.playFrame % SAF_FPS == 0)
- p->position[1] += p->position[1] > PITCH_H / 2 ? -1 :
- (p->position[1] < PITCH_H / 2 ? 1 : 0);
- p->nextStateChange--;
- }
- }
- void gamePlayStep(void)
- {
- if (game.nextTripIn == 0)
- {
- uint8_t playerIndex = rnd() % PLAYERS_TOTAL;
- // chance of tripping
- if ((rnd() % 128) >
- (game.players[playerIndex].agility +
- game.players[playerIndex].stamina) / 2)
- playerTrip(playerIndex);
- game.nextTripIn = rndNorm(TRIP_TIME);
- }
- else
- game.nextTripIn--;
-
- for (int i = 0; i < PLAYERS_TOTAL; ++i)
- playerUpdate(i);
- ballUpdate();
- if (game.ball.position[0] < SQUARE_SIZE ||
- game.ball.position[0] > PITCH_W - SQUARE_SIZE ||
- game.ball.position[1] < SQUARE_SIZE ||
- game.ball.position[1] > PITCH_H - SQUARE_SIZE)
- {
- SAF_playSound(SOUND_WHISTLE);
- DEBUG_PRINT("ball out");
- if (game.ball.position[1] < SQUARE_SIZE ||
- game.ball.position[1] > PITCH_H - SQUARE_SIZE)
- {
- gameDisplayText("out");
- DEBUG_PRINT("throw in");
- game.playerToKick =
- findPlayerToKick(1 - game.ball.lastPlayer / PLAYERS_IN_TEAM);
- ballReset(game.ball.position[0],game.ball.position[1]);
- gameSetState(GAME_STATE_FREE_KICK);
- }
- else
- {
- int ballOut = game.ball.position[0] < SQUARE_SIZE;
- for (int i = 0; i < 2; ++i)
- {
- if (ballOut)
- {
- if (
- (game.ball.position[1] < PITCH_H / 2 - GOAL_WIDTH / 2) ||
- (game.ball.height > GOAL_HEIGHT &&
- game.ball.position[1] <= PITCH_H / 2) ||
- (game.ball.position[1] > PITCH_H / 2 + GOAL_WIDTH / 2) ||
- (game.ball.height > GOAL_HEIGHT &&
- game.ball.position[1] > PITCH_H / 2))
- {
- if (i == (game.ball.lastPlayer / PLAYERS_IN_TEAM))
- {
- gameDisplayText("corner");
- DEBUG_PRINT("corner");
- game.playerToKick =
- findPlayerToKick(1 - game.ball.lastPlayer / PLAYERS_IN_TEAM);
- game.ball.height = 0;
- game.ball.maxHeight = 0;
- game.ball.vecTo[0] =
- (i ? PITCH_W - SQUARE_SIZE - 1 : (SQUARE_SIZE + 1))
- - game.ball.position[0];
- game.ball.vecTo[1] =
- ((game.ball.position[1] < PITCH_H / 2) ?
- (SQUARE_SIZE + 1) : (PITCH_H - SQUARE_SIZE - 1))
- - game.ball.position[1];
- gameSetState(GAME_STATE_FREE_KICK);
- }
- else
- {
- DEBUG_PRINT("kickoff");
- game.ball.player = i * PLAYERS_IN_TEAM; // give to the goal keeper
- gameSetState(GAME_STATE_KICKOFF);
- }
- }
- else if (game.ball.height == GOAL_HEIGHT ||
- (game.ball.position[1] == PITCH_H / 2 - GOAL_WIDTH / 2) ||
- (game.ball.position[1] == PITCH_H / 2 + GOAL_WIDTH / 2)
- )
- {
- SAF_playSound(SOUND_BOOM);
- gameDisplayText("bar!");
- DEBUG_PRINT("bar!");
- ballHorizontalBounce(i);
- }
- else
- {
- if (game.ball.lastPlayer >= 0 &&
- ((game.players[game.ball.lastPlayer].goalsFouls & 0x0f) != 0x0f))
- game.players[game.ball.lastPlayer].goalsFouls++;
- gameDisplayText("scores!");
- game.score[!i]++;
- gameSetState(GAME_STATE_AFTER_GOAL);
- DEBUG_PRINT("goal!");
- SAF_playSound(SOUND_BEEP);
- }
- }
- ballOut = game.ball.position[0] > PITCH_W - SQUARE_SIZE;
- }
- }
- }
- game.playFrame++;
- if (game.playFrame == PLAY_FRAMES / 2)
- {
- gameDisplayText("half time");
- SAF_playSound(SOUND_WHISTLE);
- gameSetState(GAME_STATE_HALFTIME);
- game.playFrame++;
- }
-
- if (game.playFrame == PLAY_FRAMES)
- {
- gameDisplayText("end!");
- SAF_playSound(SOUND_WHISTLE);
- gameSetState(GAME_STATE_END);
- }
- }
- void cameraUpdate(void)
- {
- if (cameraFollowMode != 255)
- {
- for (int i = 0; i < 2; ++i)
- {
- int newPos =
- (cameraFollowMode < PLAYERS_TOTAL ?
- game.players[cameraFollowMode].position[i] :
- game.ball.position[i]);
- if (i == 0 && game.playFrame > PLAY_FRAMES / 2 + 1)
- newPos = PITCH_W - newPos;
- newPos -= 32;
- newPos = clamp(cameraPos[i],newPos - 4,newPos + 4);
- cameraPos[i] = (cameraPos[i] + newPos) / 2;
- }
- }
- else
- {
- if (SAF_buttonPressed(SAF_BUTTON_LEFT))
- cameraPos[0] -= CAMERA_SPEED;
- else if (SAF_buttonPressed(SAF_BUTTON_RIGHT))
- cameraPos[0] += CAMERA_SPEED;
- else if (SAF_buttonPressed(SAF_BUTTON_UP))
- cameraPos[1] -= CAMERA_SPEED;
- else if (SAF_buttonPressed(SAF_BUTTON_DOWN))
- cameraPos[1] += CAMERA_SPEED;
- }
- cameraPos[0] = clamp(cameraPos[0],-1 * SQUARE_SIZE,
- PITCH_W - SAF_SCREEN_WIDTH + SQUARE_SIZE);
- cameraPos[1] = clamp(cameraPos[1],-1 * SQUARE_SIZE,
- PITCH_H - SAF_SCREEN_HEIGHT + SQUARE_SIZE);
- }
- // Gets a menu item increment/decrement depending on buttons and time pressed.
- int32_t menuItemIncrement(int32_t maxIncrement)
- {
- uint8_t timePressed = SAF_buttonPressed(SAF_BUTTON_RIGHT) |
- SAF_buttonPressed(SAF_BUTTON_LEFT);
- int32_t result = SAF_buttonPressed(SAF_BUTTON_RIGHT) ? 1 : -1;
- if (timePressed < 2 * SAF_FPS)
- return (timePressed == 1 || timePressed > SAF_FPS) ? result : 0;
- if (timePressed == 255)
- return result * maxIncrement;
- maxIncrement /= 8;
- return result * (maxIncrement ? maxIncrement : 1);
- }
- void gameInit(uint32_t gameNumber)
- {
- DEBUG_PRINT("init game");
- const char *country1 = gameNumToCountryCode(gameNumber,0);
- const char *country2 = gameNumToCountryCode(gameNumber,1);
- game.score[0] = 0;
- game.score[1] = 0;
- game.number = gameNumber;
- ballReset(PITCH_W / 2,PITCH_H / 2);
- gameDisplayText("");
- for (int i = 0; i < PLAYERS_TOTAL; ++i)
- {
- game.players[i] = playerGenerate(
- i < PLAYERS_IN_TEAM ? country1 : (
- i == PLAYERS_TOTAL - 1 ? "??" : // referee
- country2),
- (country1[0] == country2[0] && // same countries -> make diff. teams
- country1[1] == country2[1]) ?
- i : (i % PLAYERS_IN_TEAM),gameNumToSeason(gameNumber));
- playerGetStartPos(i,game.players[i].position);
- }
- currentRand = gameNumToSeed(gameNumber);
- game.playFrame = 0;
- game.nextTripIn = rndNorm(TRIP_TIME);
- game.playerToKick = 0;
- gameSetState(GAME_STATE_AFTER_GOAL);
- }
- void gameUpdate(void)
- {
- if (menuState != MENU_STATE_OFF)
- {
- if (SAF_buttonJustPressed(SAF_BUTTON_A) && menuState == 6)
- {
- gameInit(menuGameNumber);
- menuState = MENU_STATE_OFF;
- }
- if (SAF_buttonPressed(SAF_BUTTON_LEFT) ||
- SAF_buttonPressed(SAF_BUTTON_RIGHT))
- {
- switch (menuState)
- {
- case 0: /* fall through */ // countries
- case 1:
- {
- uint8_t country1 = gameNumToCountryNum(menuGameNumber,0);
- uint8_t country2 = gameNumToCountryNum(menuGameNumber,1);
- if (menuState == 0)
- country1 =
- (country1 + COUNTRIES + menuItemIncrement(1)) % COUNTRIES;
- else
- country2 =
- (country2 + COUNTRIES + menuItemIncrement(1)) % COUNTRIES;
- menuGameNumber = createGameNum(country1,country2,
- gameNumToSeason(menuGameNumber),gameNumToSeed(menuGameNumber));
- break;
- }
- case 2: // season
- menuGameNumber = createGameNum(
- gameNumToCountryNum(menuGameNumber,0),
- gameNumToCountryNum(menuGameNumber,1),
- gameNumToSeason(menuGameNumber) + menuItemIncrement(16),
- gameNumToSeed(menuGameNumber));
- break;
- case 3: // seed
- menuGameNumber = createGameNum(
- gameNumToCountryNum(menuGameNumber,0),
- gameNumToCountryNum(menuGameNumber,1),
- gameNumToSeason(menuGameNumber),
- gameNumToSeed(menuGameNumber) + menuItemIncrement(0x100));
- break;
- case 4: // game number
- menuGameNumber += menuItemIncrement(0x100000);
- break;
- case 5: // camera follow
- cameraFollowMode += menuItemIncrement(1);
- switch (cameraFollowMode)
- {
- case (CAMERA_FOLLOW_NONE - 1):
- cameraFollowMode = CAMERA_FOLLOW_BALL; break;
- case (CAMERA_FOLLOW_BALL - 1):
- cameraFollowMode = PLAYERS_TOTAL - 2; break;
- case (CAMERA_FOLLOW_BALL + 1):
- cameraFollowMode = CAMERA_FOLLOW_NONE; break;
- case (PLAYERS_TOTAL - 1):
- cameraFollowMode = CAMERA_FOLLOW_BALL; break;
- default: break;
- }
- break;
- default: break;
- }
- }
- else if (SAF_buttonJustPressed(SAF_BUTTON_DOWN))
- {
- menuState = (menuState + 1) % MENU_ITEMS;
- SAF_playSound(SAF_SOUND_CLICK);
- }
- else if (SAF_buttonJustPressed(SAF_BUTTON_UP))
- {
- menuState = (MENU_ITEMS + menuState - 1) % MENU_ITEMS;
- SAF_playSound(SAF_SOUND_CLICK);
- }
- else if (SAF_buttonJustPressed(SAF_BUTTON_B))
- {
- menuState = MENU_STATE_OFF;
- SAF_playSound(SAF_SOUND_BUMP);
- }
- return;
- }
- if (SAF_buttonJustPressed(SAF_BUTTON_B))
- {
- menuState = 0;
- menuGameNumber = game.number;
- SAF_playSound(SAF_SOUND_BUMP);
- return;
- }
- if (game.displayTextCountdown)
- game.displayTextCountdown--;
- else
- gameDisplayText("");
- switch (game.state)
- {
- case GAME_STATE_FREE_KICK:
- if (game.stateFrame < 2 * SAF_FPS)
- {
- int16_t dest[2];
- for (int i = 0; i < PLAYERS_TOTAL; ++i)
- {
- Player *p = game.players + i;
- if (i == 0 || i == PLAYERS_IN_TEAM)
- {
- // goal keeper shifts towards his place (for penalty)
- dest[0] = p->position[0];
- dest[1] = PITCH_H / 2;
- }
- else if (i == game.playerToKick)
- {
- dest[0] = game.ball.position[0] + game.ball.vecTo[0];
- dest[1] = game.ball.position[1] + game.ball.vecTo[1];
- }
- else if (
- distTaxi(p->position[0],p->position[1],
- game.ball.position[0] + game.ball.vecTo[0],
- game.ball.position[1] + game.ball.vecTo[1]) < (3 * DIST_CLOSE) / 4)
- {
- // get players away from the ball
- for (int j = 0; j < 2; ++j)
- dest[j] = (p->position[j] +
- game.players[(i / PLAYERS_IN_TEAM) *
- PLAYERS_IN_TEAM].position[j]) / 2;
- dest[1] += i * (SQUARE_SIZE / 2); // space them apart
- }
- else
- {
- dest[0] = p->position[0];
- dest[1] = p->position[1];
- }
- if (game.stateFrame > SAF_FPS / 2)
- {
- if (p->position[0] == dest[0] && p->position[1] == dest[1])
- p->stateDir = (p->stateDir & PLAYER_DIR_MASK) |
- PLAYER_STATE_STANDING;
- else
- {
- p->stateDir =
- (p->stateDir & PLAYER_DIR_MASK) | PLAYER_STATE_RUNNING;
- p->nextStateChange++;
- dirToVector(pointsToDir(p->position,dest),dest);
- p->position[0] += dest[0];
- p->position[1] += dest[1];
- }
- }
- }
- }
- else if (game.stateFrame >= 5 * SAF_FPS)
- {
- ballReset(
- clamp(game.ball.position[0] + game.ball.vecTo[0],
- SQUARE_SIZE + 1,PITCH_W - SQUARE_SIZE - 1),
- clamp(game.ball.position[1] + game.ball.vecTo[1],
- SQUARE_SIZE + 1,PITCH_H - SQUARE_SIZE - 1));
- if (distTaxi(game.ball.position[0],game.ball.position[1],
- (game.playerToKick / PLAYERS_IN_TEAM) ? SQUARE_SIZE :
- (PITCH_W - SQUARE_SIZE),PITCH_H / 2) < DIST_CLOSE)
- playerShootOnGoal(game.playerToKick);
- else
- playerPass(game.playerToKick);
- SAF_playSound(SOUND_WHISTLE);
- gameSetState(GAME_STATE_PLAYING);
- }
- else
- {
- Player *p = game.players + game.playerToKick;
- for (int i = 0; i < 2; ++i)
- {
- game.ball.position[i] +=
- game.ball.vecTo[i] - (game.ball.vecTo[i] / 2);
- game.ball.vecTo[i] /= 2;
- }
- if (game.ball.position[1] < SQUARE_SIZE || // out?
- game.ball.position[1] > PITCH_H - SQUARE_SIZE)
- {
- game.ball.height = 7;
- p->stateDir = PLAYER_STATE_THROWING |
- (game.ball.position[1] < PITCH_H / 2 ? PLAYER_DIR_D : PLAYER_DIR_U);
- }
- else
- p->stateDir = PLAYER_STATE_STANDING |
- ((game.playerToKick / PLAYERS_IN_TEAM) ? PLAYER_DIR_L : PLAYER_DIR_R);
- p->position[0] = game.ball.position[0];
- p->position[1] = game.ball.position[1];
- }
- break;
- case GAME_STATE_KICKOFF:
- if (game.stateFrame > 4 * SAF_FPS)
- gameSetState(GAME_STATE_PLAYING);
- else if (game.stateFrame > 2 * SAF_FPS)
- for (int i = 0; i < 2; ++i)
- game.ball.position[i] = (game.ball.position[i] +
- game.players[game.ball.player].position[i]) / 2;
- break;
- case GAME_STATE_HALFTIME:
- if (game.stateFrame < 2 * SAF_FPS)
- {
- for (int i = 0; i < PLAYERS_TOTAL; ++i)
- game.players[i].stateDir = PLAYER_STATE_STANDING |
- (game.players[i].stateDir & PLAYER_DIR_MASK);
- }
- else if (game.stateFrame < 6 * SAF_FPS)
- {
- for (int i = 0; i < PLAYERS_TOTAL; ++i)
- {
- if (game.players[i].position[0] > PITCH_W / 2)
- {
- game.players[i].stateDir = PLAYER_STATE_RUNNING | PLAYER_DIR_L;
- game.players[i].position[0]--;
- }
- else if (game.players[i].position[0] < PITCH_W / 2)
- {
- game.players[i].stateDir = PLAYER_STATE_RUNNING | PLAYER_DIR_R;
- game.players[i].position[0]++;
- }
- else
- game.players[i].stateDir = PLAYER_STATE_STANDING | PLAYER_DIR_D;
- game.players[i].nextStateChange++;
- game.ball.position[0] = (game.ball.position[0] + PITCH_W / 2) / 2;
- game.ball.position[1] = (game.ball.position[1] + PITCH_H / 2) / 2;
- }
- }
- else
- {
- ballReset(PITCH_W / 2,PITCH_H / 2);
- game.playFrame++; // flip rendering
- gameSetState(GAME_STATE_AFTER_GOAL); // from now on it's like after a goal
- }
- break;
- case GAME_STATE_AFTER_GOAL:
- if (game.stateFrame < 2 * SAF_FPS)
- {
- if (game.ball.lastPlayer >= 0 &&
- (game.ball.lastPlayer / PLAYERS_IN_TEAM) != // if not own goal
- (game.ball.position[0] > PITCH_W / 2)
- )
- game.players[game.ball.lastPlayer].stateDir =
- PLAYER_STATE_THROWING | PLAYER_DIR_D; // celebrate
- }
- else if (game.stateFrame >= 7 * SAF_FPS)
- {
- ballReset(PITCH_W / 2,PITCH_H / 2);
- gameSetState(GAME_STATE_PLAYING);
- SAF_playSound(SOUND_WHISTLE);
- }
- else
- {
- int16_t dest[2];
- game.ball.position[0] = (game.ball.position[0] + PITCH_W / 2) / 2;
- game.ball.position[1] = (game.ball.position[1] + PITCH_H / 2) / 2;
- game.ball.height /= 2;
- for (int i = 0; i < PLAYERS_TOTAL; ++i)
- {
- Player *p = game.players + i;
- playerGetStartPos(i,dest);
- p->stateDir = PLAYER_STATE_STANDING |
- ((i < PLAYERS_IN_TEAM) ? PLAYER_DIR_R : PLAYER_DIR_L);
- if (p->position[0] != dest[0] || p->position[1] != dest[1])
- {
- dirToVector(pointsToDir(p->position,dest),dest);
- p->position[0] += dest[0];
- p->position[1] += dest[1];
- p->stateDir = (p->stateDir & PLAYER_DIR_MASK) | PLAYER_STATE_RUNNING;
- p->nextStateChange++;
- }
- }
- }
- break;
- case GAME_STATE_END:
- {
- uint8_t winTeam = (game.score[0] == game.score[1]) ? 255 :
- (game.score[1] > game.score[0]);
- for (int i = 0; i < PLAYERS_TOTAL; ++i)
- game.players[i].stateDir = PLAYER_DIR_D |
- (i / 8 == winTeam ? PLAYER_STATE_THROWING : PLAYER_STATE_STANDING);
-
- break;
- }
- default: // GAME_STATE_PLAYING
- gamePlayStep();
- break;
- }
- cameraUpdate();
- if (menuState == MENU_STATE_OFF && game.stateFrame < 4096)
- game.stateFrame++;
- }
- void drawPitchLine(int x, int y, int len, uint8_t horizontal)
- {
- int *xx = horizontal ? &x : &y;
- int *yy = horizontal ? &y : &x;
- int incDec = 1;
- (*yy)--;
- x -= cameraPos[0];
- y -= cameraPos[1];
- if (*yy < 0 || *yy >= 64 || *xx >= 64 || *xx + len < 0)
- return;
- if (*xx + len >= 64)
- len = 64 - *xx;
- if (*xx < 0)
- {
- len += *xx;
- *xx = 0;
- }
- if ((x % 2 == y % 2) ==
- ((cameraPos[0] + 512) % 2 == (cameraPos[1] + 512) % 2))
- {
- *yy += 1;
- incDec = 0;
- }
- while (len)
- {
- SAF_drawPixel(x,y,COLOR_LINE);
- *xx += 1;
- *yy += incDec ? 1 : -1;
- incDec = !incDec;
- len--;
- }
- }
- void drawPlayer(uint8_t index, int16_t screenX, int16_t screenY, uint8_t flip)
- {
- Player *p = game.players + index;
- const uint8_t *p1;
- uint8_t *p2 = tmpImage + 2;
- uint8_t shirtColor = index < PLAYERS_IN_TEAM ? COLOR_TEAM_A :
- (index == PLAYERS_IN_TEAM * 2 ? COLOR_REFEREE : COLOR_TEAM_B);
- uint8_t skinColor = p->look & RACE_BIT ? COLOR_SKIN_BLACK : COLOR_SKIN_WHITE;
- uint8_t hairColor = p->look & HAIR_BIT ? COLOR_HAIR_LIGHT : COLOR_HAIR_DARK;
- uint8_t shoeColor = p->look & SHOE_BIT ? COLOR_SHOES_2 : COLOR_SHOES_1;
- uint8_t transform = 0;
- uint8_t imgIndex = 0;
- uint8_t state = p->stateDir & PLAYER_STATE_MASK;
- uint8_t quantizedDir = ((p->stateDir & PLAYER_DIR_MASK) / 2) * 2;
- if (flip)
- {
- if (state == PLAYER_STATE_KICK_R)
- state = PLAYER_STATE_KICK_L;
- else if (state == PLAYER_STATE_KICK_L)
- state = PLAYER_STATE_KICK_R;
- if (quantizedDir == PLAYER_DIR_R)
- quantizedDir = PLAYER_DIR_L;
- else if (quantizedDir == PLAYER_DIR_L)
- quantizedDir = PLAYER_DIR_R;
- }
- switch (state | quantizedDir)
- {
- // standing:
- case (PLAYER_STATE_STANDING | PLAYER_DIR_U): imgIndex = 2; break;
- case (PLAYER_STATE_STANDING | PLAYER_DIR_D): imgIndex = 0; break;
- case (PLAYER_STATE_STANDING | PLAYER_DIR_R): transform = SAF_TRANSFORM_FLIP;
- // fall through
- case (PLAYER_STATE_STANDING | PLAYER_DIR_L): imgIndex = 4; break;
- // running and kicking up and down:
- case (PLAYER_STATE_KICK_R | PLAYER_DIR_D): // fall through
- case (PLAYER_STATE_KICK_R | PLAYER_DIR_U): transform = SAF_TRANSFORM_FLIP;
- // fall through
- case (PLAYER_STATE_WALKING | PLAYER_DIR_U): // fall through
- case (PLAYER_STATE_WALKING | PLAYER_DIR_D): // fall through
- case (PLAYER_STATE_RUNNING | PLAYER_DIR_U): // fall through
- case (PLAYER_STATE_RUNNING | PLAYER_DIR_D): // fall through
- case (PLAYER_STATE_KICK_L | PLAYER_DIR_U): // fall through
- case (PLAYER_STATE_KICK_L | PLAYER_DIR_D): // fall through
- imgIndex = 1 + 2 * (quantizedDir == PLAYER_DIR_U);
- if (state == PLAYER_STATE_RUNNING)
- transform = ((p->nextStateChange >> 2) & 0x01) ? SAF_TRANSFORM_FLIP : 0;
- else if (state == PLAYER_STATE_WALKING)
- transform = ((p->nextStateChange >> 3) & 0x01) ? SAF_TRANSFORM_FLIP : 0;
- break;
- // running and kicking left and right:
- case (PLAYER_STATE_KICK_R | PLAYER_DIR_R): // fall through
- case (PLAYER_STATE_KICK_L | PLAYER_DIR_R): // fall through
- case (PLAYER_STATE_RUNNING | PLAYER_DIR_R): // fall through
- case (PLAYER_STATE_WALKING | PLAYER_DIR_R): transform = SAF_TRANSFORM_FLIP;
- // fall through
- case (PLAYER_STATE_KICK_R | PLAYER_DIR_L): // fall through
- case (PLAYER_STATE_KICK_L | PLAYER_DIR_L): // fall through
- case (PLAYER_STATE_RUNNING | PLAYER_DIR_L): // fall through
- case (PLAYER_STATE_WALKING | PLAYER_DIR_L): // fall through
- imgIndex = 5 +
- (
- (state | quantizedDir) == (PLAYER_STATE_KICK_R | PLAYER_DIR_R) ||
- (state | quantizedDir) == (PLAYER_STATE_KICK_L | PLAYER_DIR_L) ||
- (state == PLAYER_STATE_RUNNING && ((p->nextStateChange >> 2) & 0x01)) ||
- (state == PLAYER_STATE_WALKING && ((p->nextStateChange >> 3) & 0x01))
- );
- break;
- // lying:
- case (PLAYER_STATE_LYING | PLAYER_DIR_D): imgIndex = 7; break;
- case (PLAYER_STATE_LYING | PLAYER_DIR_U): imgIndex = 8; break;
- case (PLAYER_STATE_LYING | PLAYER_DIR_R): transform = SAF_TRANSFORM_FLIP;
- // fall through
- case (PLAYER_STATE_LYING | PLAYER_DIR_L): imgIndex = 9; break;
- // throwing:
- case (PLAYER_STATE_THROWING | PLAYER_DIR_D): imgIndex = 10; break;
- case (PLAYER_STATE_THROWING | PLAYER_DIR_U): // fall through
- case (PLAYER_STATE_THROWING | PLAYER_DIR_R): // fall through
- case (PLAYER_STATE_THROWING | PLAYER_DIR_L): imgIndex = 11; break;
- default: break;
- }
- p1 = playerImages + imgIndex * (PLAYER_IMAGE_W * PLAYER_IMAGE_H);
- // copy to tmpImage with replaced colors:
- for (int i = 0; i < PLAYER_IMAGE_W * PLAYER_IMAGE_H; ++i)
- {
- switch (*p1)
- {
- case 0xf6: *p2 = skinColor; break;
- case 0x44: *p2 = hairColor; break;
- case 0xa4: *p2 = shirtColor; break;
- case 0x65: *p2 = shoeColor; break;
- default: *p2 = *p1; break;
- }
- p1++;
- p2++;
- }
- SAF_drawImage(tmpImage,screenX,screenY,transform,0x75);
- }
- void drawBall(uint8_t flip)
- {
- int16_t drawPos[2];
- drawPos[0] = (flip ? PITCH_W - game.ball.position[0] : game.ball.position[0])
- - cameraPos[0] - 1;
- drawPos[1] = game.ball.position[1] - cameraPos[1] - 1;
- if (drawPos[0] < -1 || drawPos[0] > SAF_SCREEN_WIDTH + 1 ||
- drawPos[1] < -1)
- return;
- if (drawPos[1] < SAF_SCREEN_HEIGHT + 1)
- SAF_drawImage(imageBallShadow,drawPos[0],drawPos[1],0,SAF_COLOR_RED);
- drawPos[1] -= game.ball.height + 1;
- if (drawPos[1] < SAF_SCREEN_HEIGHT + 1)
- SAF_drawImage(imageBall,drawPos[0],drawPos[1],0,SAF_COLOR_RED);
- }
- void drawGame(void)
- {
- int8_t startX = -1 * ((cameraPos[0] + 512) % 8),
- startY = -1 * ((cameraPos[1] + 512) % 8);
- uint8_t flip = game.playFrame > (PLAY_FRAMES / 2) + 1;
- #if SAF_PLATFORM_COLOR_COUNT > 2
- for (int y = 0; y < 9; ++y)
- for (int x = 0; x < 9; ++x)
- SAF_drawImage(imageGrass,startX + x * 8,startY + y * 8,0,0);
- #else
- SAF_clearScreen(SAF_COLOR_WHITE);
- #endif
- drawPitchLine(SQUARE_SIZE,SQUARE_SIZE,PITCH_W - 2 * SQUARE_SIZE,1);
- drawPitchLine(SQUARE_SIZE,SQUARE_SIZE,PITCH_H - 2 * SQUARE_SIZE,0);
-
- drawPitchLine(SQUARE_SIZE,PITCH_H - SQUARE_SIZE,
- PITCH_W - 2 * SQUARE_SIZE + 1,1);
- drawPitchLine(PITCH_W - SQUARE_SIZE,SQUARE_SIZE,
- PITCH_H - 2 * SQUARE_SIZE + 1,0);
-
- drawPitchLine(PITCH_W / 2,SQUARE_SIZE,PITCH_H - 2 * SQUARE_SIZE,0);
-
- drawPitchLine(SQUARE_SIZE,PITCH_H / 2 - PENALTY_AREA_H / 2,PENALTY_AREA_W,1);
- drawPitchLine(SQUARE_SIZE,PITCH_H / 2 + PENALTY_AREA_H / 2,PENALTY_AREA_W,1);
- drawPitchLine(SQUARE_SIZE + PENALTY_AREA_W,PITCH_H / 2 - PENALTY_AREA_H / 2,
- PENALTY_AREA_H,0);
- drawPitchLine(SQUARE_SIZE,PITCH_H / 2 - GOAL_AREA_H / 2,GOAL_AREA_W,1);
- drawPitchLine(SQUARE_SIZE,PITCH_H / 2 + GOAL_AREA_H / 2,GOAL_AREA_W,1);
- drawPitchLine(SQUARE_SIZE + GOAL_AREA_W,PITCH_H / 2 - GOAL_AREA_H / 2,
- GOAL_AREA_H,0);
- drawPitchLine(PITCH_W - SQUARE_SIZE - PENALTY_AREA_W,
- PITCH_H / 2 - PENALTY_AREA_H / 2,PENALTY_AREA_W,1);
- drawPitchLine(PITCH_W - SQUARE_SIZE - PENALTY_AREA_W,
- PITCH_H / 2 + PENALTY_AREA_H / 2,PENALTY_AREA_W,1);
- drawPitchLine(PITCH_W - SQUARE_SIZE - PENALTY_AREA_W,
- PITCH_H / 2 - PENALTY_AREA_H / 2,PENALTY_AREA_H,0);
- drawPitchLine(PITCH_W - SQUARE_SIZE - GOAL_AREA_W,
- PITCH_H / 2 - GOAL_AREA_H / 2,GOAL_AREA_W,1);
- drawPitchLine(PITCH_W - SQUARE_SIZE - GOAL_AREA_W,
- PITCH_H / 2 + GOAL_AREA_H / 2,GOAL_AREA_W,1);
- drawPitchLine(PITCH_W - SQUARE_SIZE - GOAL_AREA_W,
- PITCH_H / 2 - GOAL_AREA_H / 2,GOAL_AREA_H,0);
- int imgX, imgY;
- imgY = PITCH_H / 2 - 2 * SQUARE_SIZE + 2 - cameraPos[1];
- if (imgY < SAF_SCREEN_HEIGHT && imgY > -3 * SQUARE_SIZE)
- for (int i = 0; i < 2; ++i) // goals
- {
- imgX = (i ? PITCH_W - SQUARE_SIZE : 0) - cameraPos[0];
- if (imgX < SAF_SCREEN_WIDTH && imgX > -1 * SQUARE_SIZE)
- SAF_drawImage(imageGoal,imgX,imgY,i ? 0 : SAF_TRANSFORM_FLIP,0);
- }
- imgX = PITCH_W / 2 - cameraPos[0];
- imgY = PITCH_H / 2 - cameraPos[1];
- if (imgX < SAF_SCREEN_WIDTH + CENTER_CIRCLE_R && imgX > -1 * CENTER_CIRCLE_R &&
- imgY < SAF_SCREEN_HEIGHT + CENTER_CIRCLE_R && imgY > -1 * CENTER_CIRCLE_R)
- SAF_drawCircle(imgX,imgY,CENTER_CIRCLE_R,COLOR_LINE,0);
- // sort players by vertical position for correct drawing:
- {
- int swapped = 1;
- for (int j = 0; swapped && j < PLAYERS_TOTAL - 1; ++j)
- {
- swapped = 0;
- for (int i = 0; i < PLAYERS_TOTAL - 1 - j; ++i)
- if (game.players[playerSortArray[i]].position[1] >
- game.players[playerSortArray[i + 1]].position[1])
- {
- uint8_t tmp = playerSortArray[i];
- playerSortArray[i] = playerSortArray[i + 1];
- playerSortArray[i + 1] = tmp;
- swapped = 1;
- }
- }
- }
- {
- int ballDrawn = 0;
- for (int i = 0; i < PLAYERS_TOTAL; ++i)
- {
- int16_t drawPos[2];
- Player *p = game.players + playerSortArray[i];
- drawPos[0] = (flip ? PITCH_W - p->position[0] : p->position[0])
- - cameraPos[0];
- drawPos[1] = p->position[1] - cameraPos[1];
- if (!ballDrawn && p->position[1] > game.ball.position[1])
- {
- drawBall(flip);
- ballDrawn = 1;
- }
- if (drawPos[0] > -1 * PLAYER_IMAGE_W / 2 &&
- drawPos[0] < SAF_SCREEN_WIDTH + PLAYER_IMAGE_W / 2 &&
- drawPos[1] > -1 * PLAYER_IMAGE_W / 2 &&
- drawPos[1] < SAF_SCREEN_HEIGHT + PLAYER_IMAGE_H)
- drawPlayer(playerSortArray[i],
- drawPos[0] - PLAYER_IMAGE_W / 2,
- drawPos[1] - PLAYER_IMAGE_H + PLAYER_IMAGE_W / 2,flip);
- }
- if (!ballDrawn)
- drawBall(flip);
- }
- // HUD:
- if (!SAF_buttonPressed(SAF_BUTTON_A))
- {
- char str[8];
- str[0] = (game.playFrame * 90) / PLAY_FRAMES; // minutes
- if (str[0] < 10)
- {
- str[0] = '0' + str[0];
- str[1] = 0;
- SAF_drawText(str,59,59,COLORTEXT_1,1);
- }
- else
- {
- str[1] = '0' + (str[0] % 10);
- str[0] = '0' + (str[0] / 10);
- str[2] = 0;
- SAF_drawText(str,54,59,COLORTEXT_1,1);
- }
- const char *country = gameNumToCountryCode(game.number,0);
- str[0] = country[0];
- str[1] = country[1];
- str[2] = ':';
- country = gameNumToCountryCode(game.number,1);
- str[3] = country[0];
- str[4] = country[1];
- str[5] = 0;
- SAF_drawText(str,19,1,COLORTEXT_1,1);
- str[2] = 0;
- for (int i = 0; i < 2; ++i)
- {
- str[0] = '0' + (game.score[i] >= 10 ? game.score[i] / 10 : game.score[i]);
- str[1] = (game.score[i] >= 10 ? '0' + game.score[i] % 10 : 0);
- SAF_drawText(str,i ? (game.score[1] < 10 ? 59 : 54) : 1,1,COLORTEXT_2,1);
- }
- SAF_drawText(game.displayText,1,59,COLORTEXT_2,1);
- }
- }
- void drawMenu(void)
- {
- SAF_clearScreen(MENU_BACKGROUND_COLOR);
- int drawY;
- char str[16];
- str[15] = 0;
- #if SAF_PLATFORM_COLOR_COUNT > 2
- SAF_drawCircle(10,12,28,SAF_COLOR_GRAY,1);
- #endif
- if (menuState >= (MENU_ITEMS - 2))
- {
- SAF_drawRect(0,0,SAF_SCREEN_WIDTH,6,SAF_COLOR_BLUE,1);
- SAF_drawText("stats",1,1,SAF_COLOR_WHITE,1);
- const char *strPtr = gameNumToCountryCode(game.number,
- menuState == (MENU_ITEMS - 1));
- str[0] = strPtr[0];
- str[1] = strPtr[1];
- str[2] = 0;
- SAF_drawText(str,1,7,COLORTEXT_2,1);
- uint8_t stat = (SAF_frame() >> 6) % 13;
- switch (stat)
- {
- case 0: strPtr = " AGE"; break;
- case 1: strPtr = "HEIGHT"; break;
- case 2: strPtr = "WEIGHT"; break;
- case 3: strPtr = " EGO"; break;
- case 4: strPtr = "AGGRES"; break;
- case 5: strPtr = " SPEED"; break;
- case 6: strPtr = "STRENG"; break;
- case 7: strPtr = "STAMIN"; break;
- case 8: strPtr = " ACCUR"; break;
- case 9: strPtr = "AGILIT"; break;
- case 10: strPtr = " IQ"; break;
- case 11: strPtr = " GOALS"; break;
- case 12: strPtr = " FOULS"; break;
- default: strPtr = 0; break;
- }
- SAF_drawText(strPtr,30,7,COLORTEXT_2,1);
- uint8_t playerIndex = (menuState == (MENU_ITEMS - 1)) * PLAYERS_IN_TEAM;
- Player *p = game.players + playerIndex;
- drawY = 13;
- str[3] = 0;
- for (int i = 0; i < PLAYERS_IN_TEAM; ++i)
- {
- uint8_t statePrev = p->stateDir;
- uint8_t statNum = 0;
- p->stateDir = PLAYER_STATE_STANDING | PLAYER_DIR_D;
- drawPlayer(playerIndex,1,drawY,0);
- p->stateDir = statePrev;
- SAF_drawText(p->name,7,drawY + 1,COLORTEXT_2,1);
- switch (stat)
- {
- case 0: statNum = p->ageYears; break;
- case 1: statNum = p->heightCm; break;
- case 2: statNum = p->weightKg; break;
- case 3: statNum = p->ego; break;
- case 4: statNum = p->aggressivity; break;
- case 5: statNum = p->speed; break;
- case 6: statNum = p->strength; break;
- case 7: statNum = p->stamina; break;
- case 8: statNum = p->accuracy; break;
- case 9: statNum = p->agility; break;
- case 10: statNum = p->iq; break;
- case 11: statNum = p->goalsFouls & 0x0f; break;
- case 12: statNum = p->goalsFouls >> 4; break;
- default: break;
- }
- str[0] = statNum >= 100 ? '0' + statNum / 100 : ' ';
- str[1] = statNum >= 10 ? '0' + (statNum / 10) % 10 : ' ';
- str[2] = '0' + statNum % 10;
-
- SAF_drawText(str,48,drawY + 1,COLORTEXT_1,1);
- playerIndex++;
- drawY += 6;
- p++;
- }
- }
- else
- {
- drawY = 3;
- for (int i = 0; i < MENU_ITEMS - 1; ++i)
- {
- switch (i)
- {
- #define S(n,v) str[n] = v;
- case 0: // fall through
- case 1:
- {
- const char *c = gameNumToCountryCode(menuGameNumber,i);
- S(0,'t') S(1,'e') S(2,'a') S(3,'m') S(4,' ') S(5,'0' + i) S(6,' ')
- S(7,' ') S(8,c[0]) S(9,c[1]) S(10,0)
- break;
- }
- case 2:
- {
- uint8_t s = gameNumToSeason(menuGameNumber);
- S(0,'s') S(1,'e') S(2,'a') S(3,'s') S(4,'o') S(5,'n') S(6,' ')
- S(7,'0' + s / 100) S(8,'0' + (s / 10) % 10) S(9,'0' + s % 10) S(10,0)
- break;
- }
- case 3:
- {
- uint16_t seed = gameNumToSeed(menuGameNumber);
- S(0,'g') S(1,'a') S(2,'m') S(3,'e') S(4,' ')
- S(5,'0' + (seed / 10000)) S(6,'0' + (seed / 1000) % 10)
- S(7,'0' + (seed / 100) % 10) S(8,'0' + (seed / 10) % 10)
- S(9,'0' + seed % 10) S(10,0)
- break;
- }
- case 4:
- {
- S(0,'#') S(1, ' ')
- for (int j = 0; j < 8; ++j)
- S(2 + j,hexDigitToChar((menuGameNumber >> (28 - 4 * j)) & 0x0f))
- S(10,0)
- break;
- }
- case 5:
- S(0,'c') S(1,'a') S(2,'m') S(3,':') S(4,' ') S(5,' ')
- if (cameraFollowMode == CAMERA_FOLLOW_BALL)
- {
- S(6,'b') S(7,'a') S(8,'l') S(9,'l') S(10,0)
- }
- else if (cameraFollowMode == CAMERA_FOLLOW_NONE)
- {
- S(6,'n') S(7,'o') S(8,'n') S(9,'e') S(10,0)
- }
- else
- {
- S(6,' ') S(7,' ')
- S(8,'0' + (cameraFollowMode + 1) / 10)
- S(9,'0' + (cameraFollowMode + 1) % 10)
- S(10,0)
- }
- break;
- case 6:
- S(0,'n') S(1,'e') S(2,'w') S(3,0)
- break;
- default:
- str[0] = 's'; str[1] = 't'; str[2] = 'a'; str[3] = 't';
- str[4] = 's'; str[5] = 0;
- break;
- }
- #undef S
- if (menuState == i)
- SAF_drawRect(0,drawY - 1,SAF_SCREEN_WIDTH,6,SAF_COLOR_BLUE,
- SAF_PLATFORM_COLOR_COUNT > 2);
- SAF_drawText(str,2,drawY,COLORTEXT_2,1);
- drawY += 7;
- }
- }
- }
- void draw(void)
- {
- if (menuState != MENU_STATE_OFF)
- drawMenu();
- else
- drawGame();
- }
- #if DEBUG
- void playerPrint(Player p)
- {
- printf("player %s (skill: %d)\n",p.name,getPlayerSkill(&p));
- printf(" %d years, %s, %s hair, %s foot, shoes %c, %d cm, %d kg\n",
- p.ageYears,
- (p.look & RACE_BIT) ? "black" : "white",
- (p.look & HAIR_BIT) ? "blonde" : "dark",
- (p.look & FOOT_BIT) ? "left" : "right",
- (p.look & SHOE_BIT) ? '1' : '2',
- p.heightCm,p.weightKg);
- printf(" %d ego, %d aggress., %d IQ\n",p.ego,p.aggressivity,p.iq);
- printf(" speed, stamina, accuracy, agility, strength: %d %d %d %d %d\n",
- p.speed, p.stamina, p.accuracy, p.agility, p.strength);
- puts("------");
- }
- void countryPrint(const char *code)
- {
- printf("============= country: %c%c =============\n",code[0],code[1]);
- for (int i = 0; i < PLAYERS_IN_TEAM; ++i)
- playerPrint(playerGenerate(code,i,1));
- }
- void runQuickMatch(uint32_t gameNumber)
- {
- const char *country = gameNumToCountryCode(gameNumber,0);
- printf("================\nrunning match: %c%c ",country[0],country[1]);
- country = gameNumToCountryCode(gameNumber,1);
- printf("%c%c\n",country[0],country[1]);
- gameInit(gameNumber);
- menuState = MENU_STATE_OFF;
- while (game.state != GAME_STATE_END)
- gameUpdate();
- printf("================\nresult: %d %d\n",game.score[0],game.score[1]);
- }
- void runTournament(int season, int seed, int rounds)
- {
- printf("================\nrunning tournament\n");
- uint8_t countryWins[COUNTRIES];
- uint8_t countryLosses[COUNTRIES];
- uint8_t countryGoals[COUNTRIES];
- uint8_t playerGoals[COUNTRIES * PLAYERS_IN_TEAM];
- uint8_t playerFouls[COUNTRIES * PLAYERS_IN_TEAM];
- unsigned int averageGoals = 0;
- for (int i = 0; i < COUNTRIES; ++i)
- {
- countryWins[i] = 0;
- countryLosses[i] = 0;
- countryGoals[i] = 0;
- }
- for (int i = 0; i < COUNTRIES * PLAYERS_IN_TEAM; ++i)
- {
- playerGoals[i] = 0;
- playerFouls[i] = 0;
- }
- for (int k = 0; k < rounds; ++k)
- for (int i = 0; i < COUNTRIES - 1; ++i)
- for (int j = i + 1; j < COUNTRIES; ++j)
- {
- runQuickMatch(createGameNum(i,j,season,seed + k));
- averageGoals += game.score[0] + game.score[1];
- countryGoals[i] += game.score[0];
- countryGoals[j] += game.score[1];
- if (game.score[0] > game.score[1])
- {
- countryWins[i]++;
- countryLosses[j]++;
- }
- else if (game.score[1] > game.score[0])
- {
- countryLosses[i]++;
- countryWins[j]++;
- }
- for (int k = 0; k < PLAYERS_IN_TEAM; ++k)
- {
- playerGoals[i * PLAYERS_IN_TEAM + k] +=
- game.players[k].goalsFouls & 0x0f;
- playerFouls[i * PLAYERS_IN_TEAM + k] +=
- game.players[k].goalsFouls >> 4;
- }
- for (int k = 0; k < PLAYERS_IN_TEAM; ++k)
- {
- playerGoals[j * PLAYERS_IN_TEAM + k] +=
- game.players[PLAYERS_IN_TEAM + k].goalsFouls & 0x0f;
- playerFouls[j * PLAYERS_IN_TEAM + k] +=
- game.players[PLAYERS_IN_TEAM + k].goalsFouls >> 4;
- }
- }
- printf("===== done =====\n");
- for (int i = 0; i < COUNTRIES; ++i)
- printf("%c%c: %d wins, %d losses, %d draws, %d goals\n",
- countryNames[i * 2],countryNames[i * 2 + 1],
- countryWins[i],countryLosses[i],
- rounds * (COUNTRIES - 1) - countryWins[i] -
- countryLosses[i],countryGoals[i]);
- unsigned int mostGoals = 0, mostFouls = 0;
- for (int i = 0; i < COUNTRIES * PLAYERS_IN_TEAM; ++i)
- {
- if (playerGoals[i] > playerGoals[mostGoals])
- mostGoals = i;
- if (playerFouls[i] > playerFouls[mostFouls])
- mostFouls = i;
- }
- averageGoals /= (((COUNTRIES - 1) * COUNTRIES) / 2) * rounds;
- printf(
- "average goals: %u\nmost goals: %d, %d (%d)\nmost fouls: %d, %d (%d)\n",
- averageGoals,mostGoals / PLAYERS_IN_TEAM,mostGoals % PLAYERS_IN_TEAM,
- playerGoals[mostGoals],
- mostFouls / PLAYERS_IN_TEAM,mostFouls % PLAYERS_IN_TEAM,
- playerFouls[mostFouls]);
- }
- #endif
- void SAF_init(void)
- {
- tmpImage[0] = PLAYER_IMAGE_W;
- tmpImage[1] = PLAYER_IMAGE_H;
- cameraFollowMode = CAMERA_FOLLOW_BALL;
- game.displayText[DISPLAY_TEXT_SIZE - 1] = 0;
- for (int i = 0; i < PLAYERS_TOTAL; ++i)
- playerSortArray[i] = i;
- gameInit(createGameNum(0,1,0,0));
- menuState = 1;
- }
- uint8_t SAF_loop(void)
- {
- draw();
- gameUpdate();
- return 1;
- }
|