diff options
-rw-r--r-- | src/battle.go | 550 | ||||
-rw-r--r-- | templates/battleNew.html | 5 | ||||
-rw-r--r-- | templates/battleQuick.html | 57 | ||||
-rw-r--r-- | templates/battleSingle.html | 108 | ||||
-rw-r--r-- | templates/battles.html | 4 |
5 files changed, 564 insertions, 160 deletions
diff --git a/src/battle.go b/src/battle.go index ccac7e1..b7584eb 100644 --- a/src/battle.go +++ b/src/battle.go @@ -11,16 +11,18 @@ import ( "time" "github.com/gorilla/mux" + "github.com/radareorg/r2pipe-go" ) type Battle struct { - ID int - Name string - Bots []Bot - Owners []User - Public bool - Archs []Arch - Bits []Bit + ID int + Name string + Bots []Bot + Owners []User + Public bool + Archs []Arch + Bits []Bit + RawOutput string } ////////////////////////////////////////////////////////////////////////////// @@ -38,6 +40,10 @@ func BattleLinkBot(botid int, battleid int) error { return globalState.LinkBotBattle(botid, battleid) } +func BattleUnlinkAllBotsForUser(userid int, battleid int) error { + return globalState.UnlinkAllBotsForUserFromBattle(userid, battleid) +} + func BattleGetByIdDeep(id int) (Battle, error) { return globalState.GetBattleByIdDeep(id) } @@ -54,17 +60,23 @@ func BattleLinkBitIDs(battleid int, bitIDs []int) error { return globalState.LinkBitIDsToBattle(battleid, bitIDs) } +func BattleSaveRawOutput(battleid int, rawOutput string) error { + return globalState.UpdateBattleRawOutput(battleid, rawOutput) +} + ////////////////////////////////////////////////////////////////////////////// // DATABASE func (s *State) InsertBattle(battle Battle) (int, error) { res, err := s.db.Exec("INSERT INTO battles VALUES(NULL,?,?,?);", time.Now(), battle.Name, battle.Public) if err != nil { + log.Println(err) return -1, err } var id int64 if id, err = res.LastInsertId(); err != nil { + log.Println(err) return -1, err } return int(id), nil @@ -73,6 +85,7 @@ func (s *State) InsertBattle(battle Battle) (int, error) { func (s *State) UpdateBattle(battle Battle) error { _, err := s.db.Exec("UPDATE battles SET name=?, public=? WHERE id=?", battle.Name, battle.Public, battle.ID) if err != nil { + log.Println(err) return err } return nil @@ -81,17 +94,49 @@ func (s *State) UpdateBattle(battle Battle) error { func (s *State) LinkBotBattle(botid int, battleid int) error { _, err := s.db.Exec("INSERT INTO bot_battle_rel VALUES (?, ?)", botid, battleid) if err != nil { - log.Println("Error linking bot to battle: ", err) + log.Println(err) return err } else { return nil } } +func (s *State) UnlinkAllBotsForUserFromBattle(userid int, battleid int) error { + // get a user with the given id + // for all of their bots + // delete the bots from the bot_battle relation + + // there are some joins to get through the following links: + // bot_battle_rel.bot_id + // -> bot.id + // -> user_bot_rel.bot_id + // -> user_bot_rel.user_id + // -> user.id + + // delete preexisting links + _, err := s.db.Exec(` + DELETE FROM bot_battle_rel + WHERE bot_id IN + (SELECT b.id + FROM bot_battle_rel bb_rel + JOIN bots b ON b.id = bb_rel.bot_id + JOIN user_bot_rel ub_rel ON ub_rel.bot_id = b.id + JOIN users u ON u.id = ub_rel.user_id + WHERE u.id=?)`, userid) + + if err != nil { + log.Println(err) + return err + } + + return nil +} + func (s *State) LinkArchIDsToBattle(battleid int, archIDs []int) error { // delete preexisting links _, err := s.db.Exec("DELETE FROM arch_battle_rel WHERE battle_id=?;", battleid) if err != nil { + log.Println(err) return err } @@ -109,7 +154,7 @@ func (s *State) LinkArchIDsToBattle(battleid int, archIDs []int) error { _, err = s.db.Exec(query) if err != nil { - log.Println("LinkArchIDsToBattle err: ", err) + log.Println(err) return err } else { return nil @@ -120,6 +165,7 @@ func (s *State) LinkBitIDsToBattle(battleid int, bitIDs []int) error { // delete preexisting links _, err := s.db.Exec("DELETE FROM bit_battle_rel WHERE battle_id=?;", battleid) if err != nil { + log.Println(err) return err } @@ -137,7 +183,7 @@ func (s *State) LinkBitIDsToBattle(battleid int, bitIDs []int) error { _, err = s.db.Exec(query) if err != nil { - log.Println("LinkBitIDsToBattle err: ", err) + log.Println(err) return err } else { return nil @@ -148,6 +194,7 @@ func (s *State) GetAllBattles() ([]Battle, error) { rows, err := s.db.Query("SELECT id, name FROM battles;") defer rows.Close() if err != nil { + log.Println(err) return nil, err } @@ -171,6 +218,7 @@ func (s *State) GetBattleByIdDeep(id int) (Battle, error) { var battleid int var battlename string var battlepublic bool + var battlerawoutput string var botids string var botnames string @@ -188,9 +236,13 @@ func (s *State) GetBattleByIdDeep(id int) (Battle, error) { // This fetches the battles and relates the associated bots, users, archs and bits + // TODO(emile): go deeper! we could fetch battle -> bot -> arch (so fetching the linked arch + // for the given bot) + err := s.db.QueryRow(` SELECT DISTINCT - ba.id, ba.name, ba.public, + ba.id, ba.name, ba.public, + COALESCE(ba.raw_output, ""), COALESCE(group_concat(DISTINCT bb.bot_id), ""), COALESCE(group_concat(DISTINCT bo.name), ""), COALESCE(group_concat(DISTINCT ub.user_id), ""), @@ -215,24 +267,12 @@ func (s *State) GetBattleByIdDeep(id int) (Battle, error) { WHERE ba.id=? GROUP BY ba.id; - `, id).Scan(&battleid, &battlename, &battlepublic, &botids, &botnames, &userids, &usernames, &archids, &archnames, &bitids, &bitnames) + `, id).Scan(&battleid, &battlename, &battlepublic, &battlerawoutput, &botids, &botnames, &userids, &usernames, &archids, &archnames, &bitids, &bitnames) if err != nil { - log.Println("Err making GetBattleByID query: ", err) + log.Println(err) return Battle{}, err } - log.Println("battleid: ", battleid) - log.Println("battlename: ", battlename) - log.Println("battlepublic: ", battlepublic) - log.Println("botids: ", botids) - log.Println("botnames: ", botnames) - log.Println("userids: ", userids) - log.Println("usernames: ", usernames) - log.Println("archids: ", archids) - log.Println("archnames: ", archnames) - log.Println("bitids: ", bitids) - log.Println("bitnames: ", bitnames) - // The below is a wonderful examle of how golang could profit from macros // I should just have done this all in common lisp tbh. @@ -246,10 +286,10 @@ func (s *State) GetBattleByIdDeep(id int) (Battle, error) { var bots []Bot if botIDList[0] != "" { - for i, _ := range botIDList { + for i := range botIDList { id, err := strconv.Atoi(botIDList[i]) if err != nil { - log.Println("Err handling bots: ", err) + log.Println(err) return Battle{}, err } bots = append(bots, Bot{id, botNameList[i], "", []User{}, []Arch{}, []Bit{}}) @@ -264,10 +304,10 @@ func (s *State) GetBattleByIdDeep(id int) (Battle, error) { var users []User if userIDList[0] != "" { - for i, _ := range userIDList { + for i := range userIDList { id, err := strconv.Atoi(userIDList[i]) if err != nil { - log.Println("Err handling users: ", err) + log.Println(err) return Battle{}, err } users = append(users, User{id, userNameList[i], []byte{}}) @@ -282,10 +322,10 @@ func (s *State) GetBattleByIdDeep(id int) (Battle, error) { var archs []Arch if archIDList[0] != "" { - for i, _ := range archIDList { + for i := range archIDList { id, err := strconv.Atoi(archIDList[i]) if err != nil { - log.Println("Err handling archs: ", err) + log.Println(err) return Battle{}, err } archs = append(archs, Arch{id, archNameList[i], true}) @@ -300,10 +340,10 @@ func (s *State) GetBattleByIdDeep(id int) (Battle, error) { var bits []Bit if bitIDList[0] != "" { - for i, _ := range bitIDList { + for i := range bitIDList { id, err := strconv.Atoi(bitIDList[i]) if err != nil { - log.Println("Err handling bits: ", err) + log.Println(err) return Battle{}, err } bits = append(bits, Bit{id, bitNameList[i], true}) @@ -312,22 +352,25 @@ func (s *State) GetBattleByIdDeep(id int) (Battle, error) { bits = []Bit{} } - // return it all! - switch { - case err != nil: - log.Println("Overall err in the GetBattleByID func: ", err) - return Battle{}, err - default: - return Battle{ - ID: battleid, - Name: battlename, - Bots: bots, - Owners: users, - Public: battlepublic, - Archs: archs, - Bits: bits, - }, nil + return Battle{ + ID: battleid, + Name: battlename, + Bots: bots, + Owners: users, + Public: battlepublic, + Archs: archs, + Bits: bits, + RawOutput: battlerawoutput, + }, nil +} + +func (s *State) UpdateBattleRawOutput(battleid int, rawOutput string) error { + _, err := s.db.Exec("UPDATE battles SET raw_output=? WHERE id=?", rawOutput, battleid) + if err != nil { + log.Println(err) + return err } + return nil } ////////////////////////////////////////////////////////////////////////////// @@ -346,6 +389,7 @@ func battlesHandler(w http.ResponseWriter, r *http.Request) { } data["pagelinknext"] = []Link{ {Name: "new", Target: "/new"}, + {Name: "quick", Target: "/quick"}, } // sessions @@ -359,6 +403,7 @@ func battlesHandler(w http.ResponseWriter, r *http.Request) { // get the user user, err := UserGetUserFromUsername(username.(string)) if err != nil { + log.Println(err) http.Redirect(w, r, "/login", http.StatusSeeOther) return } @@ -373,7 +418,7 @@ func battlesHandler(w http.ResponseWriter, r *http.Request) { // get the template t, err := template.ParseGlob(fmt.Sprintf("%s/*.html", templatesPath)) if err != nil { - log.Printf("Error reading the template Path: %s/*.html", templatesPath) + log.Println(err) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - Error reading template file")) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -405,6 +450,7 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { data["pagelink2"] = Link{Name: "new", Target: "/new"} data["pagelink2options"] = []Link{ {Name: "list", Target: ""}, + {Name: "quick", Target: "/quick"}, } // display errors passed via query parameters @@ -416,6 +462,7 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { // get data needed user, err := UserGetUserFromUsername(username) if err != nil { + log.Println(err) data["err"] = "Could not fetch the user" } else { data["user"] = user @@ -423,6 +470,7 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { archs, err := ArchGetAll() if err != nil { + log.Println(err) data["err"] = "Could not fetch the archs" } else { data["archs"] = archs @@ -430,6 +478,7 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { bits, err := BitGetAll() if err != nil { + log.Println(err) data["err"] = "Could not fetch the bits" } else { data["bits"] = bits @@ -438,7 +487,7 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { // get the template t, err := template.ParseGlob(fmt.Sprintf("%s/*.html", templatesPath)) if err != nil { - log.Printf("Error reading the template Path: %s/*.html", templatesPath) + log.Println(err) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - Error reading template file")) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -467,6 +516,7 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(k, "arch-") { id, err := strconv.Atoi(strings.TrimPrefix(k, "arch-")) if err != nil { + log.Println(err) msg := "ERROR: Invalid arch id" http.Redirect(w, r, fmt.Sprintf("/battle/new?res=%s", msg), http.StatusSeeOther) return @@ -476,6 +526,7 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(k, "bit-") { id, err := strconv.Atoi(strings.TrimPrefix(k, "bit-")) if err != nil { + log.Println(err) msg := "ERROR: Invalid bit id" http.Redirect(w, r, fmt.Sprintf("/battle/new?res=%s", msg), http.StatusSeeOther) return @@ -489,7 +540,7 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { log.Println("Creating battle") battleid, err := BattleCreate(name, public) if err != nil { - log.Println("Error creating the battle using BattleCreate(): ", err) + log.Println(err) msg := "ERROR: Could not create due to internal reasons" http.Redirect(w, r, fmt.Sprintf("/battle/new?res=%s", msg), http.StatusSeeOther) return @@ -498,7 +549,7 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { // link archs to battle err = BattleLinkArchIDs(battleid, archIDs) if err != nil { - log.Println("Error linking the arch ids to the battle: ", err) + log.Println(err) msg := "ERROR: Could not create due to internal reasons" http.Redirect(w, r, fmt.Sprintf("/battle/new?res=%s", msg), http.StatusSeeOther) return @@ -507,7 +558,7 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { // link bits to battle err = BattleLinkBitIDs(battleid, bitIDs) if err != nil { - log.Println("Error linking the bit ids to the battle: ", err) + log.Println(err) msg := "ERROR: Could not create due to internal reasons" http.Redirect(w, r, fmt.Sprintf("/battle/new?res=%s", msg), http.StatusSeeOther) return @@ -525,14 +576,126 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { } } -// TODO(emile): add user creating battle as default owner -// TODO(emile): allow adding other users as owners to battles -// TODO(emile): implement submitting bots -// TODO(emile): implement running the battle -// TODO(emile): add a "start battle now" button -// TODO(emile): add a "battle starts at this time" field into the battle -// TODO(emile): figure out how time is stored and restored with the db -// TODO(emile): do some magic to display the current fight backlog with all info +func battleQuickHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "GET": + // define data + data := map[string]interface{}{} + data["version"] = os.Getenv("VERSION") + + // breadcrumb foo + session, _ := globalState.sessions.Get(r, "session") + username := session.Values["username"].(string) + data["pagelink1"] = Link{Name: "battle", Target: "/battle"} + data["pagelink1options"] = []Link{ + {Name: "user", Target: "/user"}, + {Name: "bot", Target: "/bot"}, + } + data["pagelink2"] = Link{Name: "quick", Target: "/quick"} + data["pagelink2options"] = []Link{ + {Name: "new", Target: "/new"}, + {Name: "list", Target: ""}, + } + + // display errors passed via query parameters + queryres := r.URL.Query().Get("err") + if queryres != "" { + data["res"] = queryres + } + + // get data needed + user, err := UserGetUserFromUsername(username) + if err != nil { + log.Println(err) + data["err"] = "Could not fetch the user" + } else { + data["user"] = user + } + + // essentiall... ...the list of all bots from which the user can select two that shall + // battle! + bots, err := globalState.GetAllBotsWithUsers() + data["bots"] = bots + + // get the template + t, err := template.ParseGlob(fmt.Sprintf("%s/*.html", templatesPath)) + if err != nil { + log.Println(err) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - Error reading template file")) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // exec! + t.ExecuteTemplate(w, "battleQuick", data) + + case "POST": + // parse the post parameters + r.ParseForm() + + var public bool + query_public := r.Form.Get("public") + if query_public == "on" { + public = true + } + + // gather the information from the arch and bit selection + var botIDs []int + + for k, _ := range r.Form { + if strings.HasPrefix(k, "bot-") { + id, err := strconv.Atoi(strings.TrimPrefix(k, "bot-")) + if err != nil { + log.Println(err) + msg := "ERROR: Invalid bot id" + http.Redirect(w, r, fmt.Sprintf("/battle/quick?res=%s", msg), http.StatusSeeOther) + return + } + botIDs = append(botIDs, id) + } + } + + // create the battle itself + log.Println("Creating battle") + battleid, err := BattleCreate("quick", public) + if err != nil { + log.Println(err) + msg := "ERROR: Could not create due to internal reasons" + http.Redirect(w, r, fmt.Sprintf("/battle/quick?res=%s", msg), http.StatusSeeOther) + return + } + + // allow all archs and all bits + + // link bots to battle + + http.Redirect(w, r, fmt.Sprintf("/battle/%d", battleid), http.StatusSeeOther) + + // // link archs to battle + // err = BattleLinkArchIDs(battleid, archIDs) + // if err != nil { + // log.Println(err) + // msg := "ERROR: Could not create due to internal reasons" + // http.Redirect(w, r, fmt.Sprintf("/battle/quick?res=%s", msg), http.StatusSeeOther) + // return + // } + + // // link bits to battle + // err = BattleLinkBitIDs(battleid, bitIDs) + // if err != nil { + // log.Println(err) + // msg := "ERROR: Could not create due to internal reasons" + // http.Redirect(w, r, fmt.Sprintf("/battle/quick?res=%s", msg), http.StatusSeeOther) + // return + // } + + // http.Redirect(w, r, "/battle", http.StatusSeeOther) + return + default: + http.Redirect(w, r, "/", http.StatusMethodNotAllowed) + } +} func battleSingleHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) @@ -544,6 +707,9 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { return } + // A partially filled format string (the reason for the redirect is still to be filled later) + redir_target := fmt.Sprintf("/battle/%d?res=%%s", battleid) + switch r.Method { case "GET": // define data @@ -562,16 +728,26 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { } session, _ := globalState.sessions.Get(r, "session") - username := session.Values["username"].(string) + username := session.Values["username"] - viewer, err := UserGetUserFromUsername(username) + if username == nil { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + viewer, err := UserGetUserFromUsername(username.(string)) if err != nil { - data["err"] = "Could not get the id four your username... Please contact an admin" + log_and_redir_with_msg(w, r, err, redir_target, "Could not get the id for your username") + return } data["user"] = viewer // get the battle including it's users, bots, archs, bits battle, err := BattleGetByIdDeep(int(battleid)) + if err != nil { + log_and_redir_with_msg(w, r, err, redir_target, "Could not get the battle given the id provided") + return + } data["battle"] = battle data["botAmount"] = len(battle.Bots) data["battleCount"] = (len(battle.Bots) * len(battle.Bots)) * 2 @@ -587,10 +763,9 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { data["pagelink2options"] = opts // get the bots of the user viewing the page, as they might want to submit them - myBots, err := UserGetBotsUsingUsername(username) + myBots, err := UserGetBotsUsingUsername(username.(string)) if err != nil { - log.Println("err: ", err) - http.Redirect(w, r, fmt.Sprintf("/battle/%d", battleid), http.StatusSeeOther) + log_and_redir_with_msg(w, r, err, redir_target, "Could not get your bots") return } data["myBots"] = myBots @@ -598,7 +773,8 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { // get all architectures and set the enable flag on the ones that are enabled in the battle archs, err := ArchGetAll() if err != nil { - data["err"] = "Could not fetch the archs" + log_and_redir_with_msg(w, r, err, redir_target, "Could not get your bots") + return } else { data["archs"] = archs } @@ -614,7 +790,8 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { // get all bits and set the enable flag on the ones that are enabled in the battle bits, err := BitGetAll() if err != nil { - data["err"] = "Could not fetch the bits" + log_and_redir_with_msg(w, r, err, redir_target, "Could not fetch the bits") + return } else { data["bits"] = bits } @@ -649,7 +826,10 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { } // exec! - t.ExecuteTemplate(w, "battleSingle", data) + err = t.ExecuteTemplate(w, "battleSingle", data) + if err != nil { + log_and_redir_with_msg(w, r, err, redir_target, "err rendering template") + } case "POST": log.Println("POST!") @@ -710,8 +890,7 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(k, "arch-") { id, err := strconv.Atoi(strings.TrimPrefix(k, "arch-")) if err != nil { - msg := "ERROR: Invalid arch id" - http.Redirect(w, r, fmt.Sprintf("/battle/%d?res=%s#settings", battleid, msg), http.StatusSeeOther) + log_and_redir_with_msg(w, r, err, redir_target, "Invalid Arch ID") return } archIDs = append(archIDs, id) @@ -719,8 +898,7 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(k, "bit-") { id, err := strconv.Atoi(strings.TrimPrefix(k, "bit-")) if err != nil { - msg := "ERROR: Invalid bit id" - http.Redirect(w, r, fmt.Sprintf("/battle/%d?res=%s#settings", battleid, msg), http.StatusSeeOther) + log_and_redir_with_msg(w, r, err, redir_target, "Invalid Bit ID") return } bitIDs = append(bitIDs, id) @@ -730,30 +908,23 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { // link archs to battle err = BattleLinkArchIDs(battleid, archIDs) if err != nil { - log.Println("Error linking the arch ids to the battle: ", err) - msg := "ERROR: Could not create due to internal reasons" - http.Redirect(w, r, fmt.Sprintf("/battle/%d?res=%s#settings", battleid, msg), http.StatusSeeOther) + log_and_redir_with_msg(w, r, err, redir_target+"#settings", "Could not link arch id to battle") return } // link bits to battle err = BattleLinkBitIDs(battleid, bitIDs) if err != nil { - log.Println("Error linking the bit ids to the battle: ", err) - msg := "ERROR: Could not create due to internal reasons" - http.Redirect(w, r, fmt.Sprintf("/battle/%d?res=%s#settings", battleid, msg), http.StatusSeeOther) + log_and_redir_with_msg(w, r, err, redir_target+"#settings", "Could not link bit id to battle") return } - new_battle := Battle{int(battleid), form_name, []Bot{}, []User{}, public, []Arch{}, []Bit{}} + new_battle := Battle{int(battleid), form_name, []Bot{}, []User{}, public, []Arch{}, []Bit{}, ""} log.Println("Updating battle...") err = BattleUpdate(new_battle) if err != nil { - log.Println("err: ", err) - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("500 - Error inserting battle into db")) - http.Error(w, err.Error(), http.StatusInternalServerError) + log_and_redir_with_msg(w, r, err, redir_target+"#settings", "Could not insert battle into db") return } @@ -774,13 +945,29 @@ func battleSubmitHandler(w http.ResponseWriter, r *http.Request) { return } + redir_target := fmt.Sprintf("/battle/%d?res=%%s", battleid) + switch r.Method { case "POST": r.ParseForm() - log.Println("Adding bot to battle", battleid) + log.Println("Someone submitted the following form:") log.Println(r.Form) + session, _ := globalState.sessions.Get(r, "session") + username := session.Values["username"] + + if username == nil { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + user, err := UserGetUserFromUsername(username.(string)) + if err != nil { + log_and_redir_with_msg(w, r, err, redir_target, "Could not get the id for your username") + return + } + // get all the form values that contain the bot that shall be submitted var botIDs []int for k, _ := range r.Form { @@ -795,15 +982,15 @@ func battleSubmitHandler(w http.ResponseWriter, r *http.Request) { } } - log.Println(botIDs) - battle, err := BattleGetByIdDeep(battleid) if err != nil { msg := "ERROR: Couln't get the battle with the given id" http.Redirect(w, r, fmt.Sprintf("/battle/%d?res=%s", battleid, msg), http.StatusSeeOther) return } - log.Println(battle) + + // clear all bots from that user for that battle before readding them here + BattleUnlinkAllBotsForUser(user.ID, battleid) // for all bots, get their bits and arch and compare them to the one of the battle for _, id := range botIDs { @@ -856,3 +1043,188 @@ func battleSubmitHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusMethodNotAllowed) } } + +// actually run the battle +func battleRunHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + battleid, err := strconv.Atoi(vars["id"]) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - Invalid battle id")) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + redir_target := fmt.Sprintf("/battle/%d?res=%%s", battleid) + + switch r.Method { + case "POST": + r.ParseForm() + + log.Printf("running the battle with the id %d", battleid) + log.Println("Someone submitted the following form:") + log.Println(r.Form) + + // fetch the session and get the user + session, _ := globalState.sessions.Get(r, "session") + username := session.Values["username"] + if username == nil { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + user, err := UserGetUserFromUsername(username.(string)) + if err != nil { + log_and_redir_with_msg(w, r, err, redir_target, "Could not get the id for your username") + return + } + + // open radare without input for building the bot + // TODO(emile): configure a variable memsize for the arena + r2p1, err := r2pipe.NewPipe("malloc://4096") + if err != nil { + panic(err) + } + defer r2p1.Close() + + // Fetch the battle information + // This includes all bots linked to the battle + log.Printf("user %+v wants to run the battle", user) + fullDeepBattle, err := BattleGetByIdDeep(battleid) + + var botSources []string + var rawOutput string + + // for each bot involved within the battle, we need to fetch it again, as the deep battle + // fech doesn't fetch that deep (it fetches the batle and the corresponding bots, but only + // their ids and names and not the archs and bits associated) + for _, b := range fullDeepBattle.Bots { + bot, err := BotGetById(b.ID) + if err != nil { + log.Println(err) + } + + // define the command used to assemble the bot + src := strings.ReplaceAll(bot.Source, "\r\n", "; ") + radareCommand := fmt.Sprintf("rasm2 -a %s -b %s \"%+v\"", bot.Archs[0].Name, bot.Bits[0].Name, src) + rawOutput += fmt.Sprintf("; %s\n", radareCommand) + + // assemble the bot + bytecode, err := r2cmd(r2p1, radareCommand) + if err != nil { + http.Redirect(w, r, fmt.Sprintf("/battle/%d?res=%s", battleid, "err building bot"), http.StatusSeeOther) + return + } + + botSources = append(botSources, bytecode) + } + + // TODO(emile): [L] implement some kind of queue + + // TODO(emile): [S] use the information given from the battle, such as the right arch and bits + cmd := "e asm.arch=arm" + output, _ := r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n%s", cmd, output) + + cmd = "e asm.bits=32" + output, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n%s", cmd, output) + + cmd = "aei" + output, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n%s", cmd, output) + + cmd = "aeim" + output, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n%s", cmd, output) + + // TODO(emile): random offsets + for i, s := range botSources { + log.Printf("writing bot %d to 0x%d", i, 50*(i+1)) + cmd := fmt.Sprintf("wx %s @ 0x%d", s, 50*(i+1)) + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + } + + // print the memory for some pleasing visuals + cmd = fmt.Sprintf("pxc 100 @ 0x50") + output, _ = r2cmd(r2p1, cmd) // print + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + fmt.Println(output) + + // init stack + cmd = "aer PC = 0x50" + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + + cmd = "aer SP = SP + 0x50" + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + + output, _ = r2cmd(r2p1, "pxc 100 @ 0x50") // print + fmt.Println(output) + + // define end conditions + cmd = "e cmd.esil.todo=t theend=1" + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + cmd = "e cmd.esil.trap=t theend=1" + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + cmd = "e cmd.esil.intr=t theend=1" + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + cmd = "e cmd.esil.ioer=t theend=1" + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + + // set the end condition to 0 initially + cmd = "f theend=0" + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + + // TODO(emile): find a sensible default for the max amount of rounds + for i := 0; i < 1000; i++ { + + // this is architecture agnostic and just outputs the program counter + rawOutput += fmt.Sprintf("[0x00000000]> ########################################################################\n") + pc, _ := r2cmd(r2p1, "aer~$(arn PC)~[1]") + arch, _ := r2cmd(r2p1, "e asm.arch") + bits, _ := r2cmd(r2p1, "e asm.bits") + rawOutput += fmt.Sprintf("[0x00000000]> # ROUND %d, PC=%s, arch=%s, bits=%s\n", i, pc, arch, bits) + + // _, _ = r2cmd(r2p1, "aes") // step + cmd = "aes" + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + + // print the arena + cmd := "pxc 100 @ 0x50" + output, _ := r2cmd(r2p1, cmd) // print + rawOutput += fmt.Sprintf("[0x00000000]> %s\n%s\n", cmd, output) + fmt.Println(output) + + // TODO(emile): restore state + + // TODO(emile): check the end condition + _, _ = r2cmd(r2p1, "?v 1+theend") // check end condition + } + + BattleSaveRawOutput(battleid, rawOutput) + + msg := "Success!" + http.Redirect(w, r, fmt.Sprintf("/battle/%d?res=%s#output", battleid, msg), http.StatusSeeOther) + default: + http.Redirect(w, r, "/", http.StatusMethodNotAllowed) + } +} + +// delete a battle +func battleDeleteHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "DELETE": + + http.Redirect(w, r, fmt.Sprintf("/battle?res=%s", battleid, msg), http.StatusSeeOther) + default: + http.Redirect(w, r, "/", http.StatusMethodNotAllowed) + } +} diff --git a/templates/battleNew.html b/templates/battleNew.html index 0460dc7..30923e7 100644 --- a/templates/battleNew.html +++ b/templates/battleNew.html @@ -7,8 +7,9 @@ <span id="newbattle"></span> <h1><a href="#newbattle">New battle</a></h1> - <p>This is the page on which you can create a battle. Select the values you find appropriate. - Bots can be added to the battle later.</p> + <p>A battle is a <b>collection</b> of fights intended to be fought with multiple bots. In case you just want to try out two bots, create a <a href="/battle/quick">quick</a> battle!</p> + <br> + <p>Select the values you find appropriate. Bots can be added to the battle later.</p> <br> diff --git a/templates/battleQuick.html b/templates/battleQuick.html new file mode 100644 index 0000000..e309346 --- /dev/null +++ b/templates/battleQuick.html @@ -0,0 +1,57 @@ +{{ define "battleQuick" }} + +{{ template "head" . }} +<body> + {{ template "nav" . }} + + <span id="quickbattle"></span> + <h1><a href="#quickbattle">Quick battle</a></h1> + + <p>Just quickly want to try out two bots? Select them here and we'll let them fight quickly!</p> + + <br> + + <form id="battle" method="POST" action="/battle/quick"> + <table class="trhover"> + {{ range $bot := .bots }} + <tr> + + <!-- The name of the bot with the link to the bot's page --> + <td> + <a href="/bot/{{ $bot.ID }}">{{ $bot.Name }}</a> + </td> + + <!-- A checkbox allowing the user to select the bot --> + <!-- class="check-with-label" --> + <td> + <input + type="checkbox" + name="bot-{{$bot.ID}}" + id="bot-{{$bot.ID}}" + /> + <label class="label-for-check" for="bot-{{$bot.ID}}"></label> + <td> + + <!-- The list of users the bot belongs to--> + <td> + {{ range $idx, $usr := $bot.Users }}{{if $idx}},{{end}}<a href="/user/{{ $usr.ID }}">{{ $usr.Name }}</a>{{ end }} + </td> + + </tr> + {{ end }} + <tr> + <td></td> + <td>{{ .res }}</td> + </tr> + </table> + + <br> + <div class=".w-100"><input class="border" type="submit" value="Fight!"></div> + + </form> + + <!-- {{ . }} --> + +</body> +{{ template "footer" . }} +{{ end }} diff --git a/templates/battleSingle.html b/templates/battleSingle.html index fb53fc2..b5bb6d6 100644 --- a/templates/battleSingle.html +++ b/templates/battleSingle.html @@ -9,17 +9,14 @@ <pre> <a href="#settings">Settings</a> -<a href="#elimination-ladder">Elimination Ladder</a> - <a href="#round-1">Round 1</a> - <a href="#round-2">Round 2</a> - <a href="#round-3">Round 3</a> -<a href="#current-standings">Current Standings</a> +<a href="#registered-bots">Registered Bots</a> +<a href="#output">Output</a> </pre> <span id="settings"></span> <h2><a href="#settings">Settings</a></h2> - <form id="delete" method="POST" action="/battle/{{ .battle.ID }}/delete"> + <form id="delete" method="DELETE" action="/battle/{{ .battle.ID }}/delete"> </form> <table> @@ -29,6 +26,7 @@ <td><input class="border" type="text" id="name" name="name" value="{{ .battle.Name }}"></td> </tr> + <!-- <tr> <td><label for="latestBotSubmission">Latest Bot Submission</label></td> <td><input @@ -51,26 +49,27 @@ ></td> </tr> -<!-- <tr> <td><label for="owners">Owners:</label></td> <td> {{ range $idx, $usr := .battle.Owners }}{{if $idx}},{{end}}<a href="/user/{{ $usr.ID }}">{{ $usr.Name }}</a>{{ end }} </td> </tr> + --> <tr> - <td><label for="bots">Bots:</label></td> + <td><label for="bots">Bots submitted:</label></td> <td> {{ range $idx, $bot := .battle.Bots }}{{if $idx}},{{end}}<a href="/bot/{{ $bot.ID }}">{{ $bot.Name }}</a>{{ end }} </td> </tr> - --> + <!-- <tr> <td><label for="public">Public?</label></td> <td><input type="checkbox" id="public" name="public" {{ if .battle.Public }}checked{{end}}/></td> </tr> + --> <tr> <td>Archs</td> @@ -132,36 +131,41 @@ {{ if .myBots }} - <form method="POST" action="/battle/{{ .battle.ID }}/submit"> - <tr> - <td><label for="name">My Bots</label></td> - <td style="width: 100%;"> - <table class="trhover"> + <form method="POST" action="/battle/{{ .battle.ID }}/submit"> + <tr> + <td><label for="name">My Bots</label></td> + <td style="width: 100%;"> + <table style="width: 100%;"> + {{ range $bot := .myBots }} - <tr> + <tr class="trhover"> <td style="text-align: center; vertical-align: middle; width: 2ex;"> <input type="checkbox" id="bot-{{$bot.ID}}" name="bot-{{$bot.ID}}" - value="{{$bot.ID}}" /> - </td> - <td style="vertical-align: middle"> + value="{{$bot.ID}}" + {{ range $bbot := $.battle.Bots }} + {{ if eq $bot.ID $bbot.ID }}checked{{ end }} + {{ end }} + /> <label for="bot-{{$bot.ID}}"> <a href="/bot/{{$bot.ID}}">{{$bot.Name}}</a> </label> </td> + <td style="vertical-align: middle"> + </td> </tr> {{ end }} - </table> - </td> - </tr> - <tr> - <td></td> - <td><input class="border" type="submit" value="Submit"></td> - </tr> - </form> + + </table> + </td> + </tr> + <tr> + <td colspan="5"><input class="border" type="submit" value="Submit bots"></td> + </tr> + </form> {{ else }} @@ -171,56 +175,26 @@ </tr> {{ end }} - <table> - - <span id="elimination-ladder"></span> - <h2><a href="#elimination-ladder">Elimination Ladder</a></h2> - <span id="round-1"></span> - <h3><a href="#round-1">Round 1</a></h2> + <table> - <br><a href="">Round 1.1</a>: <a href="">bot1</a> vs <a href="">bot2</a> - <br><a href="">Round 1.2</a>: <a href="">bot3</a> vs <a href="">bot4</a> <br> - <br><a href="">Round 1.3</a>: <a href="">bot1</a> vs <a href="">bot2</a> - <br><a href="">Round 1.4</a>: <a href="">bot3</a> vs <a href="">bot4</a> - - <span id="round-2"></span> - <h3><a href="#round-2">Round 2</a></h2> - <br><a href="">Round 2.1</a>: winner of <a href="">round 1.1</a> vs winner of <a href="">round 1.2</a> - <br><a href="">Round 2.2</a>: winner of <a href="">round 1.3</a> vs winner of <a href="">round 1.4</a> - - <span id="round-3"></span> - <h3><a href="#round-3">Round 3</a></h2> - - <br><a href="">Round 3.1</a>: winner of <a href="">round 2.1</a> vs winner of <a href="">round 2.2</a> - - <span id="current-standings"></span> - <h2><a href="#current-standings">Current Standings</a></h2> - - <p>There are {{ .botAmount }} Bots in this battle, as we're executing each battle two times with all permutations, we're executing {{ .battleCount }} battles.</p> - - <br>What is Lorem Ipsum? - <br>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. + <form method="POST" action="/battle/{{ .battle.ID }}/run"> + <input class="border" type="submit" value="Run the Battle"> + </form> - <br>Why do we use it? - <br>It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like). + <span id="registered bots"></span> + <h2><a href="#registered-bots">Registered Bots</a></h2> + {{ range $idx, $bot := .battle.Bots}}{{if $idx}}, {{end}}<a href="/bot/{{ $bot.ID }}">{{ $bot.Name }}</a>{{ end -}} - <br>Where does it come from? - <br>Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32. + <span id="output"></span> + <h2><a href="#output">Output</a></h2> - <br>The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham. + <pre>{{ .battle.RawOutput }}</pre> - <br>Where can I get some? - <br>There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc. - <br>Where can I get some? - <br>There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc. - <br>Where can I get some? - <br>There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc. - <br>Where can I get some? - <br>There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc. + <span id="current-standings"></span> </body> {{ template "footer" . }} {{ end }} diff --git a/templates/battles.html b/templates/battles.html index bdc4d4b..d3f85da 100644 --- a/templates/battles.html +++ b/templates/battles.html @@ -10,9 +10,9 @@ <p>A battle is a collection of matches. It plays through all matches in the elimination ladder in order to determine an overall winner.</p> <br> - <table class="trhover"> + <table> {{ range $battle := .battles }} - <tr> + <tr class="trhover"> <td> - <a href="/battle/{{ $battle.ID }}">{{ $battle.Name }}</a> </td> |