2024. április 28., vasárnap

Gyorskeresés

24 órán keresztül programoztam

Írta: | Kulcsszavak: pathfinding . verseny . kockanap . algoritmus . programozás

[ ÚJ BEJEGYZÉS ]

Az utóbbi pár hónapban rengeteg versenyen vettem részt. No nem azért, mert ennyire ügyes lennék/hajtana a versenyszellem, hanem mert mindig is zárkózottabb és önbizalomhiányos voltam. Általában ha megdicsérnek valami miatt, akkor az is simán lehet elfogult vélemény. Éppen ezért nehéz volt belőnöm a ligám, hogy mi az, amiben ténylegesen jó vagyok és mi az, amit inkább el kéne engedni. Hiszen a pontos időbeosztás egy kincs főleg az IT szektorban, ahol évek alatt 180 fokos fordulatokat vesz az egész industry.

Azt nagyjából magabiztosan tudom magamról, hogy egy-egy technológiát mennyire ismerek, viszont azt sosem tudtam belőni, hogy a többiekhez képest mennyit tudnék még fejlődni az adott területen. Erre úgy néz ki, hogy egy remek alkalom célzottan versenyekre járni.

Ez mind szép és jó, csak egy gond van vele. Anno az OKTV-n elért egész jó helyezésemet leszámítva sosem indultam versenyen, mert ha helyszínen vagyok, sok idegen ember előtt, akik azt várják el, hogy teljesítenem kell, egyszerűen nem tudom hozni a maximumot. A stresszhelyzet gyakran szorongásba torkollik, az pánikba, aztán átmegy az egész kapkodásba és sosem leszek olyan jó, mint otthon, nyugodt körülmények közt higgadtan átgondolva ugyanazt. Erre csak rákontráz, ha csapatosan indulok, hiszen ilyenkor négyzetesen hajt a görcsös teljesítési vágy, hogy ne hozzak szégyent a csapatomra.

Aztán elkezdtem egyetemre járni és az első félévemben betalált egy érdekes verseny. Egy házi CTF verseny volt, "iható nyereményekért". Előtte sosem voltam hasonlón, hacktheboxot sem csinálgattam, a securityről is csak annyit tudtam, ami rám ragadt az évek során az open-source projektjeim fejlesztése során. Azért elküldtem az emailt egy barátomnak, aki jó pár év tapasztalattal rendelkezett a témában és remek programozó. Végül ő győzött meg, hogy jelentkezzek én is.

Úgy voltam vele, hogy vesztenivalóm nincsen, a verseny csinálható otthonról, akkor, amikor szeretném. Ugyanis majdnem egy hétig volt nyitva a játéktér, ami idő alatt volt idő a nulláról megtanulni azt a tudást, ami hiányzott pár feladat megoldásához. Az igaz, hogy nem aludtam sokat, de egyetem mellett is vállalható volt. Végül elsőként végeztem és az összes feladatot sikeresen abszolváltam. A barátom pedig második lett.

Apró sikerélmények, de fantasztikus érzés volt órák hossza után, mikor végre kibökte az adott szolgáltatás a flag-et, azaz azt a karaktersorozatot, amivel tudom bizonyítani, hogy megoldottam a feladatot.

Mikor újból megrendezésre került, nem volt kérdés, hogy jelentkezzek. Ezt meg is tettem, majd ismét sikerült elérni az első helyet az összes feladat megoldásáért, a barátom pedig szintén második lett. :DDD A feladatokról írtam anno beszámolót is.

Ezek után felkértek a szervezők, hogy üljek át a szervezői székbe, a HoneyLab csapatába. A csapat részeként számos CTF feladatot tudtam elkészíteni, amiből ugyan fizikai tőkém sosem lett, szellemi tőkém annál inkább. Rengeteget lehet tanulni egy-egy kutatómunka során, vagy akár az éles versenyen monitorozva a támadó viselkedéseit. Illetve mint azt megtudtam, nem csak házi versenyeket szerveznek, hanem többek közt a HCSC jeopardy részét. Ezenkívül idén először megszerveztük a CyberQuestet is, ami egy bárki számára otthonról elérhető fordulóval indít, majd a top10 megmérettethette magát élőben is egy második fordulón. Egész komoly price pool volt idén, az első egy iPhone 15 Pro-t nyert pl. Nagy büszkeséggel töltött el, hogy megannyi tehetséges játékos tisztelt meg minket azzal, hogy az idejét a versenynek szentelte és hogy valamilyen szinten elértük azt a célt, hogy népszerűsítsük és elérhetővé tegyük a cybersecurity versenyeket Magyarországon is. Node erről majd egyszer, egy hosszabb cikkben részletesen beszámolok. :) Illetve kijutottam Norvégiába, az ECSC-re, hogy a 25 év alatti "top10"-ében kakukktojásként képviseljem Magyarországot egy nemzetközi versenyen, ami szintén hatalmas élmény volt.

Aztán még számos versenyen voltam, de nem hiszem, hogy érdekelne bárkit is a felsorolás. A lényeg, hogy megannyi kiberes versenyen voltam, de nagyon máson nem. Viszont hatalmas személyiségfejlődésen mentem keresztül a lépcsőzetes akadályok leküzdése során és hálás vagyok a HoneyLab csapatnak, hogy végig motiváltak. Már egyre jobban viselem az élő versenyek okozta stresszt is. Ez egyrészt jó feedback volt egy témából, de engem sosem igazán csak a CyberSecurity érdekelt. Kapóra jött, hogy szintén az Óbudai Egyetemen van egy minden évben megrendezésre kerülő verseny. Ez kifejezetten sysadmin skillekre megy rá. Egy másik nagyon kedves barátommal indultunk rajta tavaly is és idén is. Tavaly elsők, idén másodikak lettünk. Élménynek jó volt, de a sysadmin dolgok sem igazán kötnek le. Abból is csak annyit tudok, amennyi nekem feltétlenül szükséges a saját céljaim eléréséhez.

Viszont a nyeremény része volt egy céglátogatás a szponzornál a top3 csapat részére. Itt beszélgettem a harmadik helyezett csapattal, akik mesélték, hogy van egy kockanap nevű esemény, amit szintén az OE és a hallgatói önkormányzatuk szervez. Utánanéztem, hogy mi is ez és annyi infót találtam, hogy háromfős csapatokban kell jelentkezni, majd egy játékban kell AI-t fejleszteni 24 óra alatt. Sosem voltam programozói versenyen, pláne nem olyanon, ahol 24 óráig be vagyok zárva az egyetemen, de úgy voltam vele, hogy itt sincs semmi vesztenivalóm, max hazamegyek.

Elég last minute döntés volt, mivel a jelentkezési határidő lejárta előtt egy nappal írtam a hallgatói önkormányzatnak (továbbiakban: HÖK), hogy egyedül jelentkeznék. Bár elég sok embert ismerek az egyetemen, kb mind inkább sysadmin beállítottságú, vagy nem érezte elegendőnek a tudását arra, hogy megmérkőzzön, szóval esélyét nem láttam, hogy embereket szerezzek.

Végül a HÖK azt válaszolta, hogy nincs lehetőségem egyedül indulni, de kaptam egy kontaktot egy másik érdeklődőhöz, aki hasonló cipőben járt. Felvettem vele a kapcsolatot, majd kerestünk egy harmadik embert, csináltunk egy közös Github repót, megállapodtunk a közös kommunikációs platformban, majd izgatottan vártam a verseny napját.

Eljött a nagy nap. Az egyetem aulájában kaptunk egy háromfős asztalt, illetve három ethernet kábelt a csapat három tagjának. Kizárólag ezen keresztül volt elérhető a játékos infrastruktúra. Én egy laptopot vittem magammal, az egyik srác egy Macet, míg a másik egy asztali gépet és egy laptopot.

Próbáltunk megállapodni a srácokkal, hogy milyen programnyelvet fogunk használni. Mondták, hogy C#, vagy Python lehet a közös pont, de a feladattól függ, hogy mit fogunk használni. Ha machine learninges megoldást akarunk, akkor ML.NET-et javasolt az egyik, tehát C#-ot, algoritmusos esetben pedig a Python lett volna a preferált.

Kis késéssel megkaptuk végre a feladatot. Adott volt egy IP cím és egy port, illetve a következő proto fájl:

syntax = "proto3";

option csharp_namespace = "PacketDelivery.GrpcServer";

package pdservice;

service PdService {
rpc RegisterTeam (RegistrationRequestMessage) returns (RegistrationReplyMessage);
rpc CommunicateWithStreams (stream CommandMessage) returns (stream CommandMessage);
}

message CommandMessage {
int32 cmdCounter = 1;
string commandId = 2;
string commandData = 3;
}

message RegistrationRequestMessage {
string teamName = 1;
string teamPassword = 2;
bytes teamImagePng = 3;
}

message RegistrationReplyMessage {
int32 teamId = 1;
bytes mapImagePng = 2;
}

Illetve kaptunk egy PDF-et a feladat részletes leírásával. A lényeg, hogy mikor csatlakoznánk a távoli szerverre, gRPC-n keresztül kell küldeni először egy RegisterTeam parancsot a saját csapatnévvel, jelszóval, illetve csapat képpel, majd a szerver visszadobja a csapatunk azonosítóját, ami egy 1000 feletti szám lesz, illetve megkapjuk a térképet PNG formátumban, ami a verseny indulásakor így nézett ki:

SIkeres regisztrációt követően pedig csatlakozhatunk egy full duplex streamre, ami egyrészt küldhet, másrészt fogadhat üzeneteket. Arról, hogy milyen parancsok vannak, a PDF adott tippet:

A táblázat egyébként több helyen hibás, ugyanis a BuyBike -1-et ad vissza és nem nullát, ha nincs elég pénzünk új motort venni, illetve az ItemLocations parancs nem küldhető, ezt ideális esetben kb. tizedmásodpercenként egyszer broadcastolja minden csapat számára a szerver.

Azt adták kikötésnek, hogy per csapat egy kliens csatlakozhat, illetve elmagyarázták, hogy minden team 1300 "pénzérmével" indul, amiből tud venni 1000-ért egy motort (BuyBike), illetve opcionálisan lehet venni 3 db bombát 300 pénzérméért (BuyMine). Megadták még, hogy az elsődleges cél az ellenfelek aknával való sebesítése, másodlagos cél a csomagok kiszállítása, harmadlagos cél pedig a játéktéren minél nagyobb távolság megtétele. A programnyelv használat nem volt kikötve, bármiben lehetett írni. Volt viszont 4 db side quest, amiknek sok köze nem volt a fő feladathoz, de ezeket az egyszerű programozási problémákat mind helyesen megoldva lehetett növelni az éles verseny során a kezdőtőkét, az aknák robbanási hatósugarát, a motor sebességét és a kezdeti rendelkezésre álló aknák mennyiségét.

Mivel a csapat többi tagja immáron a feladatot ismerve sem tudta eldönteni, hogy melyik programnyelvet célozzuk meg, döntöttem helyettük, hogy Pythonozni fogunk, mivel a C# esetében semmi nem garantálja, hogy a felhasznált könyvtárak (pl wpf ilyen) Linuxon is menni fognak és nem volt kedvem Windows VM-mel szenvedni. Személy szerint amúgy rustban írtam volna mindent, de ekkor még azt hittem, hogy számíthatok a csapattársaimtól is kódra, így ezt hamar elvetettem.

Igen ám, de végig kellett gondolnom, hogy biztos jó ötlet-e Python-t használni mindenre. A proto fájl alapján világossá vált, hogy nagyon fontos lesz a kétirányú kommunikáció és az üzenetek pontosan időzített küldözgetése. Vegyük például a Login-t. Ez lenne ugyebár az első lépés, mikor csatlakozom a streamre. Elküldöm a team nevet és jelszót, majd visszakapok egy LoginResponse-t, ami vagy __FAILURE__ lesz, vagy egy csapatnév. Viszont mivel stream és kapok egy ItemLocations üzenetet minden tizedmásodpercben, semmi nem garantálja, hogyha elküldök egy üzenetet, akkor a következő üzenet, amit a szerver küld, az egy LoginResponse lesz.

Plusz a másik probléma, hogy nem tudom hogy működik pontosan a szerver. Simán lehet, hogy a szerver külön szálakat (thread) használ a küldésre és fogadásra. Szóval ha annyit csinálok, hogy:

send_login_request()
response = wait_for_login_response()

Akkor nem biztos, hogy el tudom kapni a LoginResponse-t, hiszen ha a két sor közt fut be az üzenet, akkor nem volt éppen aktív receiverem, ami várta volna a sikeres belépésről szóló üzenetet, mert még nem jutott oda a kód. Igen ritkán, de előfordult a jelenség.

Így a Python-t el kellett vetnem. Hogy miért? Mert a nyelv rendelkezik beépített aszinkron futtatással, ami nagyon lesarkítva annyit jelent, hogy ahelyett, hogy egy metódus hajtódna egyidőben végre, majd ha az végzett sorról sorra a futással, akkor ugrana vissza az őt meghívó metódusra, lépne tovább a következő metódusra, vagy lépne ki; futhat egyszerre több metódus is, amennyiben ezek taskként lettek létrehozva. Ilyenkor egy fizikai szál van igénybe véve, tehát ugyanúgy egyetlen task fog egyidőben futásidőhöz jutni, de az úgynevezett event loop képes a kontroll váltogatására, ezáltal több taskot tud felváltva futtatni. Tehát simán tudunk egyszerre új üzenetekre várakozni és feldolgozni az adatokat. Igen ám, itt tényleg minden üzenetet el tudnánk kapni, de a sok váltogatás miatt semmi nem garantálja, hogy a továbbiakban pontosan időzítve fogunk tudni üzeneteket küldeni a szervernek.

Hát akkor használjuk szálakat, nem? Sajnos a dolog nem ilyen egyszerű. A Python támogat ugyan multithreadinget, ezek a szálak sosem fognak ténylegesen párhuzamosan futni. Hiszen interpretált nyelv és amennyiben bekövetkezik a Global Interpreter Lock, röviden GIL, akkor a többi szál is állni fog, amíg nem végez a lockot előidéző utasítás. Ezt a jelenséget a legkönnyebb úgy előidézni, hogyha írunk egy apró python programot, ami spawnol két szálat, az egyikben csak annyit spammel, hogy:

while True:
print("élek")

Míg a másikba írjuk bele, hogy:

print(99**99**99**99**99)

Mivel a második művelet "C" szinten hajtódik végre, egészen addig lockolva lesz az interpreter, amíg nem végez a számolással a második szál. Jól látható tehát, hogy amikor I/O műveleteknél történik a "blocking" hívás, akkor megoldást jelenthet a Pythonba épített async/threading mechanizmus, CPU igényes/számolós feladatokra nem éppen alkalmas. Nekem pedig mindkettőt kell csinálnom egyszerre.

Ezért döntöttem úgy, hogy készítek egy broker-t, vagy proxy-t, ami lényegében ismerni fogja a protokollt és intézi a kommunikációt a szerverre, majd a motorok számától függően indít Python processzeket, amikkel folyamatosan tartja a kommunikációt, de leszűri a sok zajt a rengeteg üzenetből és a Python nem a teljes map összes történését kapja majd meg tizedmásodpercenként, hanem a brokerem figyeli, hogy melyik objektumok változtak ténylegesen, és csak akkor értesíti a Python-ban írt logikai vezérlést, ha őt is érintő változás van.

Már csak azt kellett eldönteni, hogy miben akarom írni. Az interpretált nyelveket kilőttem. Kellett valami, ami natív kódra fordul és gyors. Napi szinten a C-t, a rustot és a Go-t használom, ami ilyen, szóval ezek egyikét szerettem volna választani. A C-t hamar kilőttem, mivel nem akartam kézzel szálakat kezelni pthreads-szel, illetve egy esetleges szerver halál után potenciálisan memleakeket keresni. 24 óra alatt nem a legjobb ötlet.

Rust... A rustban az a csodálatos, hogy meg tudom csinálni a következőt:

async fn wait_for_event_active(client: &Client) -> Result<(), Error> {
let mut interval = interval(Duration::from_secs(3));

loop {
if is_event_running(client).await? {
return Ok(());
}

interval.tick().await;
}
}

Ahelyett, hogy csak sleepelnék, képes vagyok sokkal pontosabban fixen 3s-enként valamit végrehajtani. Míg egy másik programnyelvben végrehajtanék pár ellenőrzést, majd ha az nem teljesül, adnék ki egy fix időre szóló sleepet, itt képes vagyok olyat csinálni, hogy az adott blokk pontosan 3 másodpercenként fusson le. Nem pedig addig, amennyi a blokknak kell futásidő plusz 3s. Nyilván, ha a blokknak több idő kell, mint 3s, akkor azzal nem tudok mit csinálni, de ilyen nem lesz. Illetve a rust előnye, hogy LLVM-mel fordítható, szóval a végletekig lehet vektorműveletekkel optimalizálni, meg ha akarom, nem kerül bele a binárisba minden sallang és lesz egy gyors, optimalizált és kellő odafigyeléssel memory leak mentes brokerem. Igen ám, de a fordítási idő emiatt sok lehet, illetve macerás cross platform fordítani, márpedig a másik két csapattagomnak is jó lett volna odaadni a binárist backupnak, ha az én laptopom feladná a harcot.

Maradt tehát a Go. Előnye, hogy szintén binárisba fordul, marha egyszerű más platformokra legyártani egy exe-t, vagy egy macos binary-t, remekül használható large scale hálózati kommunikációra, illetve a goroutineok használatával pofonegyszerűen tudok green threadekre küldeni párhuzamosítani kívánt folyamatokat. Támogat channeleket is. Itt egy jó magyarázat a green threadekről. Hátránya, hogy körültekintően kell használni, mert nem véd meg előre az olyan hibáktól, hogy pl több szálon akarnék egy map-be egyszerre írni, illetve, hogy a kész bináris simán lehet 10+ MB, mivel semmi optimalizációt nem csinál a fordító. Ha ki akarok írni valamit és beimportálom az fmt modult, utána teljesen mindegy, hogy csak egy mezei Printf-et akarok használni, az egészet belerakja a binárisba. Viszont pont emiatt, mert előre minden le van fordítva, nyugodtan csinálhatok kb végtelen dolgot egyszerre. Mellette szól még az, hogy mind a Go, mind a gRPC Google fejlesztésnek indult, szóval vélhetőleg kiválóan használható gRPC könyvtára lesz.

Szóval ez eldőlt. Elkezdtem írni a broker-t Go-ban. Még azt kellett kitalálni, hogy hogyan fogok kommunikálni a Pythonnal. Az az ötletem támadt, hogy redis-t fogok használni, hiszen ez gyorsabb, mint az MQTT, támogat kulcs-érték tárolást és PUB/SUB csatornákat, illetve széleskörűen támogatott a nyelvek között. Valószínűleg a tudatalattim nem bízott eléggé a csapattársak produktivitásában és nem akartam nyelv specifikusra megoldani az IPC-t. Illetve abban is segített, hogy a csapattársak is csatlakozhattak így hozzám, nem voltunk egyetlen kapcsolatra limitálva a szerverrel.

A broker írása közben megfigyeltem egy csomó információt, amiket célszerű tisztázni.

Akna:

- 3 db 300 pénz - BuyMine parancs veszi
- Lerakása a PlaceMine metódussal, ami kér egy bikeId-t, meghívva a tank pozíciójába rakja le az aknát
- nincs friendly-fire
- alapból 40 px környezetben robbanthatunk vele
- elsődleges cél, pontot kapunk, ha ellenfelet robbantunk
- az ellenfél nem robban fel, ha ki van kapcsolva a motor motorja, még akkor is, ha 40 px-en belül van :D
- side questet teljesítve megnő a hatótáv 60 px-re
- nem történik semmi, ha nincs elég pénzünk, csak -1-et kapunk válasznak

Motor:

- 1 db 1000 pénz - BuyBike parancs veszi
- irányítani fok alapján lehet
- motorja kapcsolható, ha ki van kapcsolva, nem halad, illetve nem robban fel
- ha felrobban, csak pontot vesztünk, nem tűnik el a pályáról
- sebessége side questtel növelhető
- nem fekete pixeleken haladhat
- random kerül elhelyezésre a BuyBike meghívása után
- itemLocations válaszban kapunk koordinátákat késleltetve, irányítása nehéz
- minél több mozgás harmadlagos cél

Csomag:

- tíz másodpercenként spawnol egy a pályára véletlenszerűen
- értéke 100 és 500 között van
- felszedés után el kell vinni és le kell rakni a csomag adatai közt említett destination x és y koordinátákra
- másodlagos cél
- 40 px távra lehet felszedni

Az itemLocations válasz pedig tartalmazta az összes jelenleg a pályán lévő objektumot. A következő adatokkal rendelkeztek:

type Packet struct {
Bike int `json:"Bike"`
BikeOfPacketId int `json:"BikeOfPacketId"`
DestinationX int `json:"DestinationX"`
DestinationY int `json:"DestinationY"`
Id int `json:"Id"`
OwnerId int `json:"OwnerId"`
Type string `json:"Type"`
Value int `json:"Value"`
X int `json:"X"`
Y int `json:"Y"`
}

type DeliveryBike struct {
CurrentMines int `json:"CurrentMines"`
Id int `json:"Id"`
IsActive bool `json:"IsActive"`
MoneyOfOwnerPlayer int `json:"MoneyOfOwnerPlayer"`
OwnerId int `json:"OwnerId"`
PacketInTransportId int `json:"PacketInTransportId"`
RotDeg int `json:"RotDeg"`
Type string `json:"Type"`
X int `json:"X"`
Y int `json:"Y"`
}

type Mine struct {
Id int `json:"Id"`
OwnerId int `json:"OwnerId"`
Type string `json:"Type"`
X int `json:"X"`
Y int `json:"Y"`
}

A verseny futásideje során egyébként ezek a fieldek többször válttoztak. Eredetileg nem szerepelt a MoneyOfOwnerPlayer és a PacketInTransportId mezők pl., ami okozott némi fejfájást, mikor implementálva lettek, mivel nem volt róla semmi figyelmeztetés a szervezők részéről, de idővel megszoktam, hogy változik a szerver is. :D

Következhetett a tényleges implementálás. Sose dolgoztam gRPC-vel közvetlenül, szóval a dokumentáció alapján legeneráltattam a proto fájl alapján a használható kommunikációs modult, majd kapcsolódtam, küldtem egy regisztrációs kérést és remekül működött:

func loadImage() []byte {
file, err := fs.ReadFile(os.DirFS("."), "res/teampic.png")
if err != nil {
panic(err)
}
return file
}

func saveImage(image []byte) {
err := os.WriteFile("res/map.png", image, 0644)
if err != nil {
panic(err)
}
}
func main() {
serverAddress := "10.8.9.121:9080"

conn, err := grpc.Dial(serverAddress, grpc.WithInsecure(), grpc.WithCompressor(grpc.NewGZIPCompressor()), grpc.WithDecompressor(grpc.NewGZIPDecompressor()))
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()

ctx, cancel := context.WithTimeout(context.Background(), time.Hour*4)
defer cancel()

response, err := client.RegisterTeam(ctx, &pb.RegistrationRequestMessage{TeamName: teamName, TeamPassword: password, TeamImagePng: loadImage()})

teamId = response.TeamId

fmt.Println("Team ID:", response.TeamId)
saveImage(response.MapImagePng)
}

Biztos vagyok benne, hogy a grpc.Dial sornál sokan kiégtek. Tudom, hogy deprecated és régi API, nem illene használni, de a verseny alatt tökéletesen működött ez is. A Gzip-et pedig azért vezettem be, mivel egy switchen volt az összes player, a kapcsolat pedig nem volt titkosítva. Simán előfordulhatott volna, hogy valaki ARP spoofing segítségével bekamuzza, hogy ő a szerver, aztán ellopja a belépési adatokat és szabotálja a többi játékost. Bár a Gzip nem véd sokat ez ellen, legalább nem nyersen történik majd a kommunikációnk, szóval kisebb az esély rá, hogy valaki összelopja a login adataink a hálózati forgalom átjtátszásával. Arra meg nem hiszem, hogy gondol egy ellenséges csapat, hogy még gzipet is decompresseljen... Egyébként meglepően sokat javított a gzip a latency problémákon is. Sokkal kiszámíthatóbb timingokkal tudtunk mozogni, mint nélküle. Igaz, enyhén lassabban.

Először úgy írtam meg a dolgot, hogy először a szerver próbáljon mindig loginolni, aztán ha az nem sikerül próbálkozzon csak regisztrációval, de mint kiderült akárhányszor küldhetsz regisztrációs parancsot. Feleslegesen szenvedtem... :((( Maradt tehát, hogy felépül a kapcsolat, regisztrálok, majd belépek. A lényeg, hogy a belépési adatok ne változzanak. Viszont előjött bennem a hacker és megfordult a fejemben, hogy mi van akkor, ha regisztrálok a saját csapatnevemmel, de más jelszóval, mint ami eredetileg szerepelt. Felül tudom-e vágni vajon az eredeti bejelentkezési adataim és ezáltal ki tudom-e léptetni a fiók jogos tulajdonosát? Átírtam a jelszót, lefuttattam a kódot és más ID-t kaptam vissza, nem FAILED-et. Szaladtam is a szervezőkhöz jelenteni a bugot, mert gondoltam így becsületes, ők megerősítették, hogy a hiba valóban félig-meddig létezik, kijavították, majd tesztelés után valóban nem tudtam ismét regisztrálni már regisztrált csapatnévvel, de eltérő jelszóval.

Utána a SaveImage metódussal lementettem a térképet png formátumban, hogy lássam, valóban sikeres volt-e eddig a folyamat. Aztán alaposan át kellett gondolnom a Login-t. Végül az lett a megoldás, hogy elindítok egy goroutine-t a háttérben, ez addig fog üzeneteket fogadni, amíg meg nem kap egy 420-as CmdCounter értékű, LoginResponse CommandId-ű üzenetet, aminek a tartalma egyenlő volt a team nevünkkel. Ez a goroutine a háttérben fut és egy csatornán keresztül értesíti majd a fő szálat, hogy sikerült a login. Eközben a fő szálon küldök egy login kérést szintén a 420-as CmdCounter értékkel, Login CommandId-val és a {"teamName": teamName, "password": password} CommandData értékkel.

Tehát a kulcs, hogy előbb hallgatózom, mielőtt az üzenetet küldözgetném. Aztán egészen addig spammelem a login próbálkozásokat, míg el nem kapok egy sikeres LoginResponse üzenetet, ekkor leállítom a receivert és a sendert is. Majd elindítok egy MessageResponseLogger metódust, amiben egyrészt logolom, hogy milyen üzeneteket kapok, másrészt tudok ezekre reagálni.

Megcsináltam, hogy a map png formátumból konvertálódjon át egy 2D-s tömbbé, ahol a fekete pixelek nullák, a fehér pixelek pedig 1-ek lettek. A tömb sorai a kép sorai voltak, míg az oszlopok értelemszerűen az oszlopok. És itt jött az első feketeleves, ugyanis nem minden pixel lett jól konvertálva. Megnéztem a képet és bizony antialiasolva volt. Tehát nem csak fekete és fehér pixelek voltak, hanem átmenetek is. Ezt is jelentettem a szervezőknek, akik mondták, hogy nem szándékos a jelenség és javítva lesz hamarosan. Tényleg javították, de átálltam arra a taktikára, hogy legyen minden 1, ahol fekete pixel van és minden más nulla. A végeredményt letároltam redisben, hogyha kell majd valami oknál fogva a map a logichoz a csapattársaimnak, akkor rendelkezésre álljon egyszerű formában.

Majd azt is megcsináltam, hogy az első motort a go kód vegye meg, hiszen arra mindenképp szükség lesz. Utána készítettem egyszerűen használható redis PUBSUB csatornákat, amikre feliratkozott a broker és ezek segítségével lehetett parancsokat küldeni a szervernek.

Azt, hogy én Machine Learninget használjak elsőre hamar elvetettem, hiszen nincs GPU a krumpli laptopomban, rendes datasetem sincs és a térkép is változik garantáltan az éles futamra. 24 óra egyszerűen nem elég egy profi deep learning algoritmusra, vagy akár egy GAN hálózatra sem. Ha esetleg fix lett volna a térkép, lett volna értelme.

Ezután elkezdtem gondolkozni, hogy milyen módon lenne célszerű algoritmusból begyűjteni a csomagokat. Egyből a Travelling Salesman Problem algoritmusa ugrott be. Ha csak csomagok lennének a pályán és azt kéne eldöntenem, hogy milyen sorrendben szedjem össze őket, akkor remek lenne, hiszen gráfként lehet ábrázolni a problémát. Viszont nehezítő tényező, hogy itt a csomagokat nem csak felszedni, hanem eljuttatni is el kell egy random destinationx és destinationy koordinátákra. Ez persze csomagonként változik. Szóval az az ötletem támadt, hogy a TSP futtatása előtt mesterségesen súlyozom a gráfot azzal, hogy amelyik csomaghoz több út vezet és több út elvinni is, azt virtuálisan messzebb rakom, így a TSP ezt figyelembe fogja venni. Fantasztikus ötletnek tűnt, a teamnek is tetszett, mikor beszámoltam róla. Egy probléma volt. Ki tudtam számolni egyszerűen a légvonalban számolt távolságot, de itt utcáim vannak. Nem biztos, hogy az van hozzám ténylegesen a legrövidebb időre, ami légvonalban a legközelebb van. Meg kellett tehát állapítani valahogy, hogy milyen messze van a csomag ténylegesen tőlem.

Nem volt sok tapasztalatom pathfinding terén, de gondoltam, hogy nem lehet akkora ördöngősség ilyet csinálni, ha az offline navigációm pár másodperc alatt kiböki a legrövidebb utat Budapestről Zadarba. Elkezdtem kutatni, hogy milyen könyvtárak vannak és belebotlottam az osmnx Python könyvtárba. Szépen tette is a dolgát, rendben kidobta, hogy mennyi a távolság Bécs és Budapest között, azonban sehogy nem jöttem rá, hogyan tudnék neki egyedileg készített mapet beadagolni.

Gondoltam, hogy az első lépés az lesz, hogy a térképem utcáit átalakítom gráffá. Találtam egy stackoverflow posztot, ami pontosan ezt akarta elérni. Volt benne egy Github repó, ami igéretesnek bizonyult a JSON kimenete miatt: [link] Viszont sehogy sem tudtam működésre bírni a képeinken, mert elvérzett ebben a sorban: [link] dimenziókra panaszkodva. Próbáltam fixelni, de akkor meg olyan hibákat kaptam, hogy:

ValueError: ndarray is not C-contiguous

Sajnos itt jött ki az, hogy nem értek a numpy-hez és a csapat többi tagjának kellett volna ezt megoldania, de igazság szerint nem igazán akartak foglalkozni a dologgal.

Végül próbáltam minden tudásom bevetni, még a hullám továbbterjedéses algoritmust is megpróbáltam alkalmazni, amit matlabos órán vettünk anno, de ez egyrészt túl butának, másrészt borzasztóan lassúnak bizonyult egy ekkora térképen.

Próbálkoztam olyanokkal, hogy kiszámoltam a célt, aztán labirintusmegoldó algoritmusokat eresztettem rá, mohó algoritmussal próbáltam mindig az aktuális legjobb döntést meghozni kereszteződésbe érve, próbáltam mindig jobbra menni, de semmi nem bizonyult tökéletesnek. Végül belebotlottam a pathfinding szócikkbe a wikipédián, azon belül is az A* algoritmusba, ami a Dijkstra-nak egy speciális esete. Pontosan ez kellett nekem. Az egyik csapattárs eközben ugyanerre a következtetésre jutott, bár azt nem tudom pontosan, hogy mi vezette erre. Talált egy libraryt és az elvezette valami útvonalra, de az nem volt megfelelő.

Kénytelen voltam ezt is egyedül végigszenvedni. A következő kóddal álltam elő:

from pathfinding.core.diagonal_movement import DiagonalMovement
from pathfinding.core.grid import Grid
from pathfinding.finder.a_star import AStarFinder
from matplotlib import pyplot as plt

from json import load

with open('map.txt') as f:
game_map = load(f)

grid = Grid(matrix=game_map)

start = grid.node(2499, 818)
end = grid.node(408, 1496)

finder = AStarFinder(diagonal_movement=DiagonalMovement.only_when_no_obstacle)
path, runs = finder.find_path(start, end, grid)

print('operations:', runs, 'path length:', len(path))
print(path)

image = plt.imread('map.png')

plt.imshow(image)
plt.scatter(start.x, start.y, 100, 'r')
plt.scatter(end.x, end.y, 100, 'b')
for i in range(len(path)):
plt.scatter(path[i].x, path[i].y, 1, 'g')
plt.show()

map.txt-ben a 2D-s tömb volt, amit korábban go-ból generáltam. A következőt generálta:

Kis fény az alagút végén! :) Lassú és iszonyat memória igényes, de egy út kirajzolva. Igen ám, de hogy a fenébe érjem el, hogy a motorom ténylegesen így mozogjon? Hiszen csak annyit tudok neki megadni, hogy milyen irányba nézzen az orra, nem tudom pontosan, hogy milyen gyors éppen és hogy hol tarthat. Hiszen mindent késleltetve kapok meg és a hálózat is belerakja a maga késleltetését. Szóval a precíz mozgáshoz azt találtam ki, hogy elküldöm a forgatási értéket és a motor be értéket, majd várok 200 ms-et, ami elég arra, hogy mozduljon 1 px-et a motor, aztán leállítom a motort. Namost ez nem akart így mozogni. Kiderült, hogy túl lassú a Python. Ezért Go-ban írtam egy olyan metódust, ami egy mozgássorozatot várt pythontól, majd ezt lejátszotta egyben. Ez már elég gyors volt és végre eljutottam odáig, hogy fel tudtam szedni a csomagokat, illetve le tudtam őket tenni. Priorításnak végül a légvonalbeli távolságot adtam meg. A semminél jobb...

Eszembe jutott még egy hack, ami nagyon jó lett volna, ha működik. Segítségével nagyon pontosan tudtam volna network latencytől függetlenül irányítani a motort. TLS-ben szerepel az idő, szóval generálhattam volna olyan csomagokat, amik egy megadott időpillanatig bezárólag validáltak a szerver által, ha pedig nem, akkor a szerver eldobja ezeket a csomagokat. Így spammelhettem volna a megadott irányra vonatkozó üzeneteket, amik pontosan addig hajtódnának csak végre, amíg azok ideje engedi. Sajnos azonban a TLS teljesen le volt tiltva a szolgáltatáson, így ezt hiába kezdtem el leimplementálni, nem tudtam kipróbálni.

Eközben az egyik srác próbált ötletelni, a másik meg elég kezdő kérdéseket tett fel nekem a Pythonnal kapcsolatban, mire megkérdeztem, hogy miért nem megy. Mire bevallotta, hogy régen Pythonozott és Elixirben jobban boldogult volna. Mondtam, hogy akkor írja Elixirben, mert én is ismerem az Erlangot, aztán max besegítek erlang kóddal az Elixirjében, ha oda jutok, de ne szenvedjen feleslegesen, meg hátráltasson engem a kérdésekkel.

Végül az Elixir kódból sosem láttam semmit és nem tudom mit csinált végig, de a Python cucc egész okos lett, amit én csináltam. Közben mindkét csapattársam el-elaludt. Néha felébredtek, megkérdezték, hogy hol tartok, én meg azt hittem, hogy azért kérdezik, mert ténylegesen fel akarják venni a fonalat, de egy idő után nem voltam benne biztos, hogy ez a céljuk, viszont elkezdett idegesíteni, hogy mindig magyaráznom kell és kizökkentenek. Egyikük fejébe vette, hogy machine learningezni akar, aztán bújta a wikipediat, de nem sok sikerrel. Legalábbis kódot nem láttam tőle sem.

Hajnali hat óra volt, kb 15 óra eltelt a versenyből. Az egyik srác bealudt, meg utána egy ideig nem is láttam. Azt hittem hazament a gépe nélkül. A másik meg azért próbált ötletelni legalább, de kódot még mindig csak én raktam bele, meg ő is el-elaludt. Végül bejelentette, hogy rosszul van, hányingere van és hazamenne. Mondtam, hogy ok, ezzel nem tudunk sajnos mit csinálni, az egészsége fontosabb, menjen csak. Erősködött, hogy majd otthonról csatlakozik én meg próbáltam győzködni, hogy nem ér annyit, menjen haza. Szenvedek egyedül.

Hazament, csatlakozott valamikor videóhívásban, megpróbáltuk implementálni a decision tree-s ML algoritmusát, megírtam neki szépen a csv exporter metódust hozzá, ami logolta az összes számára szükséges adatot, aztán annyit értünk el vele, hogy elvetette az ötletet és a loggerem is ment a kukába. Elvesztettünk még egy csomó időt.

Majd leimplementáltam azt is, hogy megadott időközönként rakjon le a motor aknákat, illetve ha valamelyik ellenség közel jön. Meg beraktam az újratervezés funkciót, hogyha valami félremenne a csomag felszedés közben, akkor kezdje újra a processzt, onnan, ahol leállt.

Illetve rájöttem arra, hogy a PickupPacket parancsot lehet minden mozdulat után spammelni, hátha megjelenik egy csomag az úton, amit előre kiszámoltam már. Így nem kell újratervezni minden új csomag megjelenésekor, hanem ha sikerül felszedni egy csomagot, a motor azonnal megáll és megpróbálja lerakni a célba. A DropPacket pedig hasonlóan lett kivitelezve. Azt is spammeltem minden mozdulat után, amíg volt nálam csomag. Működött. :) De még mindig nem volt stabil, beakadt néha, Többször kellett újratervezni stb.

Kétségbeesetten körbementem, meg megnéztem, hogy hogy mozog mindenki a kivetítőn és azt láttam, hogy egyetlen roboton kívül egyik sem mozgott ügyesen és szedett össze sok csomagot. Belegondoltam, hogy sokkal jobban megéri aknákat lerakni, netalántán rátapadni valakire, aki nem tud mozogni és esetleg 40 px-es csak az akna hatótávja. Ekkor már egyedül szenvedtem végig, a csapatom többi tagja elhagyott. Belegondoltam, hogy nem motort kell venni, mert az drága, hanem rámegyek egy ilyen mozdulatlan, 60px-re lévő motorra, aztán spammelem bombákkal. Ő nem tud visszaspammelni, én viszont kapom a pontot. Zseniális taktikának tűnt. Az A* algoritmust is leportoltam rustban, hogy gyorsabb legyen és kevesebb memóriát egyen.

Alig másfél órám volt. Ez alatt igyekeztem az összes eddig alkotott algoritmus próbálkozásom egybegyúrni, hogy addig is mozogjon a motor, amíg út tervezés van és ezzel is növeljem a mozgás pontjait. Raktam be amőba játékból ismert minmaxot nem egyértelmű kanyaroknál, illetve falról visszapattanást, ha 5 tickig nem mozog sokat a motorom. És az éles döntő előtt 5 perccel még azt hegesztettem, hogy miért nem tudok aknát lerakni, mert valamit azon az üzeneten is változtattak a szervezők és ami régen ment, az nem ment többé.

Végül sikerült időre beröffenteni és idegesen figyeltem a monitort, hogy mi a helyzet. Mindenki más felállt a gépétől, én nem mertem. Emiatt kaptam a nyakamba egy hököst, aki felügyelte, hogy nem-e irányítom manuálisan. Tudtam, hogy semmi esélyem nincs nyerni.

Teljesen kész voltam idegileg és fizikailag ezen a ponton. Elég sok demotiváló tényező ért a kockanap során. Eleve az, hogy később kezdtünk, illetve hogy a csapatomból úgy éreztem, hogy számomra a legfontosabb a jó helyezés érintett rosszul. Pláne, hogy az egyik csapattársam végig mondogatta, hogy ő a hft megajánlottért jött és nagyon bízik benne, hogy sikerül. Mivel nekem már van hft jegyem, még csak ez sem motivált. Tényleg azt hittem, hogy csapatként tudunk majd dolgozni és igyekeztem mindent megtenni, hogy a csapat tényleg csapatként tudjon működni. Ha mondjuk eleve egyedül indulok, akkor nem szívok brokerrel, meg hasonló úri dolgokkal, hanem mindent írok rustban.

Vagy a másik, hogy érdeklődtem az egyik hökösnél, hogy lesz-e vegán étel, vagy készüljek-e magamnak. Azt a választ kaptam, hogy lesz. Nos, csak ilyen felöntős tésztás levesek voltak, illetve az egyetlen melegétel paprikás krumpli volt, amit kint a bográcsban készítettek. Abban is volt hús, de úgy voltam vele, hogy max azt lepasszolom. Mire odaértem, nekem már nem maradt. Mindkét csapattársam ott zabálta, csak én nem. A hök rendelt nekem egy pizzát, de abból egy szeletet elkért a srác, másik kettőt meg a csapattársaimnak adtam, hátha az motiválja őket és ugyanúgy éhes maradtam. Illetve éjfélig kongott a hasam. Végül rászántam magam, hogy egyek egy másik ilyen tésztás levest, de csak currys maradt. Gondoltam egye fene. No, ennek borzasztó mesterséges íze volt és hatalmas hiba volt megenni. Utána az egész esemény során eszméletlenül fájt a hasam és nagyon nehéz volt ettől elvonatkoztatni és ennek ellenére hozni a 100%-ot. Másnap hoztak szendvicseket, de abból is minden húsos volt. De akkor már nem érdekelt. Kopogott a szemem. Kibírtam. Egyszer belefér, hogy megegyem.

Az utolsó durván három órában nagyon gondolkoztam rajta, hogy hazamegyek. Esélyét nem láttam annak, hogy megmozdul a robot és nem utolsók leszünk, remegett az egész testem és alig bírtam elájulás nélkül.

Meg az is rátett egy lapáttal, hogy eszméletlen hideg volt az aulában végig, illetve hogy a sok rohangálás miatt a projektor és a gépem közt mindig feltöltődött a cipőm, aztán a laptopomat megérintve megrázott rendesen az áram minden egyes alkalommal.

De végül mégis kibírtam, eljött az eredményhírdetés ideje. Mivel nem láttam semmit az egészből és a végére értem csak oda, nem tudtam mire számítsak. Az utolsó órákban többeknek segítettem is, akik elakadtak a dolgokkal.

25+ csapattal számoltam induláskor. 13 maradt a végére. A végéről kezdték el sorolni a csapatneveket. Mikor elértünk az ötödik helyezettig, rendesen azon gondolkoztam, hogy odamegyek reklamálni, hogy kihagytak minket. Végül nem tettem, gondoltam várok vele az eredményhírdetés végéig.

Meglett az eredmény: másodikak lettünk. Kiderült, hogy az aknás taktika megtette hatását és hozzá tette a szervező, hogy több, mint a döntő feléig elsők voltunk. :Y Ekkor ugyanis meghalt a gépem. Mivel mindent nekem kellett csinálni, a nagy siettségben elfelejtettem bezárni a rengeteg böngésző ablakot, illetve a vscodeot, a csevegőalkalmazásokat meg minden egyéb sallangot és kifutott a RAM-ból. Ez sosem fordult volna elő, ha nem hagy mindkét csapattársam cserben és futhat három gépről is a program.

De ez van, szegény ember vízzel főz. Jövőre ha megszakadok is, becipelem magammal a 15 kilós laptopom és szerverem, ha egyáltalán lesz kedvem indulni.

Egyébként a szervező megkérdezte, hogy egyedül csináltam-e. Ebben a pillanatban teljesen sokkban voltam. Egy hülye vigyor párosulhatott a fejemen vörös pírral. Nem akartam elhinni, amit láttam. Megérte szenvedni. Nem jött ki rendesen hang a torkomon, csak egy magas valami. Az villant át a fejemen, hogy most mit válaszoljak. Végülis pár ötletet leszámítva az én munkám volt benne. A side questeket levették a vállamról, amiért persze hálás vagyok, de szerintem meg tudtam volna én is mindet csinálni. Végül válaszoltam:

- Igen.

Elképedt csend vett körül. Egyszerűen tényleg nem akartam elhinni a dolgot. Kihírdették az elsőt is, akik szintén megérdemelték a díjat és ez a csapat tartotta bennem a lelket végig, mert ők tudtak aludni is, nem nyerni jöttek, szintén úgy érezték, hogy esélyük sincsen, szórakozni jöttek. És mégis bebizonyították. Élmény volt közben beszélgetni velük. Segítettem is nekik nem egyszer, mikor láttam, hogy a loginnal szenvednek 12 óra után is.

Ezután minden nagyon gyorsan történt. Kezembe nyomtak egy hatalmas doboznyi ajándékot, benne három helyezettnek való csomagokkal, meg egy oklevelet. Fotózkodnom kellett valami sráccal. Az utolsó képnél kaptam észbe, hogy az oklevelet bizony tartani is kéne. Bizonyára életem legelőnytelenebb képei készültek akkor el. A vigyor egyszerűen nem akart eltűnni. Aztán mindenki felszívódott.

Hát így történt. Az ajándék egyébként valami bor, meg egy a verseny szponzoraitól származó táskányi céges merchandise, illetve egy céges látogatás náluk. Ha ezért annyira nem is érte meg elmenni, meg azért a jó pár negatív tapasztalatért sem, legalább azért igen, mert rengeteget tanultam a programozásról, az önuralomról és a priorításokról. A feladat maga zseniális volt és bár nem tudom, hogy meg szabadna-e neveznem a készítőt, a kevés idejére ellenére - ami azért érződött a pár bugnak köszönhetően - egy kellően nagy kihívást állított össze feladatnak és rendesen el volt várva a tudás. :) Az is tetszett, hogy nem volt megszabva kötelező programnyelv sem. És meglepődtem, hogy a dotnet + gRPC milyen jól scalel, hiszen a szerver abban volt írva.

Sokan győzködtek azok közül, akiknek meséltem, hogy ne adjam oda az ajándékokat a többi csapattagnak. Még a helyszínen is többen ezt javasolták. De úgy érzem, ha másért nem, a side questek és az ötletek miatt nem lenne fair nem odaadni. Azt meg külön sajnálom, hogy továbbra sem lehet egyedül indulni, de ez van.

Meg egy jó feedback volt. Tudtam, hogy nem vagyok erős programozásban, a végleges kód is egyre jobban spagetti lett, ahogy egyre jobban sietni kellett, de bevallom, egy kicsit azért büszkeséggel tölt el, hogy 36 óráig képes voltam alvás nélkül, abból 26 órán át a 100%-ot adva létezni.

Köszönöm, hogy elolvastad! :R

Hozzászólások

(#1) Mr Dini


Mr Dini
addikt
LOGOUT blog

Akkor ezt most megnyitom... :)

Hogy hívják az éhes horgászt? Gyere Pista, kész a kaja!

(#2) sonar válasza Mr Dini (#1) üzenetére


sonar
addikt

:R Good Job!

A tudást mástól kapjuk, a siker a mi tehetségünk - Remember: Your life – Your choices!

(#3) ergoGnomik


ergoGnomik
tag

A kifejezés amit keresel a 180 fokos fordulat. Aki vagy ami 360 fokot fordul, azzal nem történik semmi, megy tovább változatlan irányban.

(#4) Mr Dini válasza ergoGnomik (#3) üzenetére


Mr Dini
addikt
LOGOUT blog

Olyan gyors, hogy kétszer 180-at is fordul. Kösz, javítottam. :B

Hogy hívják az éhes horgászt? Gyere Pista, kész a kaja!

(#5) Krugszvele


Krugszvele
aktív tag

Ilyen betekintést nyerni egy zseni észjárásába.

[ Szerkesztve ]

(#6) joghurt


joghurt
addikt

Szép teljesítmény volt! Egyben jó szoktatás éles határidőkhöz, vagy valódi biztonsági incidenshez.

Jövőre esetleg a csapattársakat a vicc alapján lehetne instruálni: "Gombokhoz nem nyúlni, malacokat etetni!" :DDD

A tej élet, erő, egészség.

(#7) lajosdani2


lajosdani2
csendes tag

Gratulálok, és le a kalappal a teljesítményed előtt! :)

Szeretném, ha csak feleakkora tudásom lenne programozás terén, mint amiről te azt mondod, hogy kevés :D

Mindig öröm olvasni az írásaid, de ez különösen tetszett, mert pár éve mi is elmentünk hárman haverokkal egy programozó versenyre. (Úgy, hogy egyébként én közgazdász vagyok, és csak az Excel-ezgetés meg BI-ozás révén ragadt rám pár dolog, egyébként csak csúfolom a programozás szakmát. Na meg egy ideje OMV szerver meg Home Assistant itthon)
Sikerrel bejutottunk az elődöntők során, és a döntőben 12 csapattal kellett egy LEGO robotot irányítani egy pályán.
A döntő is két részes volt, az első részben nyilvános volt a pálya, és lehetett rajta háromszor tesztelni is, hogy tud-e rajta menni a robot - színérzékelő meg távolságérzékelő szenzorokat kellett főként használni.
A második részben viszont le volt takarva a pálya, tehát látatlanban kellett tovább fejleszteni a robotot, és lehetőleg minden eshetőségre gondolni. Itt tesztelési lehetőség sem volt.
Aztán mikor leleplezték a pályát, mindenki nagy megkönnyebbülésére ugyanaz volt a második pálya is, mint az első :D
Végig tök jó hangulatban programoztunk, felosztottuk egymás között a feladatokat, és jól is haladtunk. Jött az utolsó próba, az éles döntő. Letettük a LEGO robotot a startra, és vártuk, hogy körbemenjen a pályán, kerülje ki az akadályokat, stb... És a robotunk az indulás után csak egyhelyben pörgött.
Utólag hamar megtaláltuk a hibát - az egyik fordulási ágban elfelejtettük a forgásszenzor mértékegységét fokra állítani. Már nem is emlékszem, mi volt a default, talán cm, de mindegy is :D Ezen az amatőr bug-on buktuk el az egész döntőt.
Rossz érzés volt, de az eredményt leszámítva a verseny élménye pozitív volt számunkra - egy egész napos baráti csapatépítő program :)
Mivel alapból nem volt egy nehéz feladvány - 6 óra alatt sztem bárki megoldotta volna, aki minimális affinitással rendelkezik az IT felé - inkább az volt a nehezítő körülmény, hogy limitált volt a tesztelés lehetősége.

(#8) Mr Dini válasza lajosdani2 (#7) üzenetére


Mr Dini
addikt
LOGOUT blog

Hát, én is jót szórakoztam. Nem gondoltam volna, hogy ilyesmi le tud kötni, mert sosem érdekeltek a játékok. De amikor algoritmust kell a mozgásra írni, az tényleg nagy sikerélménnyel tud eltölteni. :)

LEGO robotnál gondolom azért limitáltabb a számolási kapacítás. Ott milyen módon írtatok AI-t? Felmappeltétek, hogy hol járt már, aztán arra nem ment többet és próbálkozott?

Meg írod, hogy egész napos volt. Hány óra volt rá?

Mondjuk nem tudom mit csinálnék, ha még szenzorokra is kellene figyelnem... :DDD

Hogy hívják az éhes horgászt? Gyere Pista, kész a kaja!

(#9) lajosdani2 válasza Mr Dini (#8) üzenetére


lajosdani2
csendes tag

Ha jól emlékszem - mert ennek már jó 5 éve - az algoritmus annyi volt, hogy figyelje az útpálya színét.
Ugyanis a pálya egy nagy fehér papír volt, amire rajzoltak egy színes pályát.
Kék volt a normál út, piros volt az akadály, és sárga körök voltak a pályán két helyen, ezeken a körvonalakon ha végigment a robot, akkor az pluszpontot ért.
Amíg kék pályát látott maga alatt a színszenzor, addig menjen egyenesen.
Ha letér jobbra vagy balra, akkor a kék ugye átmegy lassan fehérbe (a pálya alapszíne), ekkor korrigáljon vissza 3 fokot.
Ha pedig elért a más színekhez, akkor az akadályt kerülje ki egyik oldalról. A sárgán menjen végig, amíg újra kéket nem talál.
Tehát az egész szabály a színekre épült.
Volt még távolságszenzor is, mert a pálya szélén volt egy fal emelve. Ha ezt megközelíti, akkor a távszenzor jelez, és visszaküldjük ellenkező irányba - 180 fok fordulás.

Nagy vonalakban ennyire emlékszem, de tényleg nagyon ötletes és jó kis verseny volt :)
Azt hiszem az OTP rendezte az EcoSim-mel közösen, és a győztes csapat állásinterjúra is mehetett a bankhoz.

(#10) lajosdani2 válasza Mr Dini (#8) üzenetére


lajosdani2
csendes tag

Azt hiszem 3 óra volt az első részre, és 3 a másodikra is. Közben volt ebédszünet is - nagyon jól voltunk tartva :)

További hozzászólások megtekintése...
Copyright © 2000-2024 PROHARDVER Informatikai Kft.