Skip to content

Commit

Permalink
Merge pull request #95 from ds306e18/auto-naming
Browse files Browse the repository at this point in the history
Added button for auto-naming teams based on its bots
  • Loading branch information
NicEastvillage authored Oct 17, 2019
2 parents 956c336 + 14e945d commit f6372ae
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 10 deletions.
6 changes: 3 additions & 3 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Changelog

#### Unreleased Changes
#### Version 1.6 - 17. October 2019
- Now writes overlay data to a json next to the cleopetra.jar. This can be disabled in RLBotSettings tab. - NicEastvillage
- Improved the RLBot runner process. - NicEastvillage
- The RLBot console window (RLBot runner) now does not need to be shut down after each match. #88
Expand All @@ -9,11 +9,11 @@
- Skyborg's overlay will work properly.
- Rendering and bot percentages does not have to be toggled each match.
- Improved how round robin decides the top teams of the stage. #92 - NicEastvillage

- Added button for auto-naming teams based on its bots. #79 - NicEastvillage

#### Version 1.5.1 - 3. October 2019
- Added seeding for first round of Swiss. - NicEastvillage
- Bogded the Swiss round generation algorithm to never make rounds with missing matches. Instead
- Bodged the Swiss round generation algorithm to never make rounds with missing matches. Instead
it will accept a few rematches and warn the user. "Fixes" #5. - NicEastvillage

#### Version 1.5 - 1. October 2019
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import dk.aau.cs.ds306e18.tournament.model.*;
import dk.aau.cs.ds306e18.tournament.utility.Alerts;
import dk.aau.cs.ds306e18.tournament.rlbot.BotCollection;
import dk.aau.cs.ds306e18.tournament.utility.AutoNaming;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
Expand All @@ -20,6 +21,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;


Expand All @@ -30,6 +32,7 @@ public class ParticipantSettingsTabController {
@FXML private HBox participantSettingsTab;
@FXML private ChoiceBox<SeedingOption> seedingChoicebox;
@FXML private TextField teamNameTextField;
@FXML private Button autoNameTeamButton;
@FXML private Spinner<Integer> seedSpinner;
@FXML private Button addTeamBtn;
@FXML private Button removeTeamBtn;
Expand Down Expand Up @@ -192,7 +195,8 @@ void teamNameTextFieldOnKeyReleased(KeyEvent event) {
void onActionAddTeam(ActionEvent actionEvent) {

//Create a team with a bot and add the team to the tournament
Team team = new Team("Team " + (Tournament.get().getTeams().size() + 1), new ArrayList<>(), teamsListView.getItems().size() + 1, "");
Team team = new Team("Unnamed Team", new ArrayList<>(), teamsListView.getItems().size() + 1, "");
AutoNaming.autoName(team, Tournament.get().getTeams());
Tournament.get().addTeam(team);

teamsListView.setItems(FXCollections.observableArrayList(Tournament.get().getTeams()));
Expand Down Expand Up @@ -228,6 +232,8 @@ private void updateParticipantFields() {
SeedingOption seedingOption = Tournament.get().getSeedingOption();
seedingChoicebox.setDisable(started);

teamsListView.refresh();

int selectedIndex = getSelectedTeamIndex();

// Handle team order button disabling / enabling
Expand Down Expand Up @@ -300,6 +306,7 @@ private void checkForEmptyTeamName() {
String nameCheck = team.getTeamName();
nameCheck = nameCheck.replaceAll("\\s+", "");
if (nameCheck.compareTo("") == 0) {

team.setTeamName("Team ?");
}
}
Expand Down Expand Up @@ -410,4 +417,11 @@ public void onActionLoadFolder(ActionEvent actionEvent) {
Main.lastSavedDirectory = folder;
}
}

public void onActionAutoNameTeam(ActionEvent actionEvent) {
Team team = getSelectedTeam();
AutoNaming.autoName(team, Tournament.get().getTeams());
updateTeamFields();
teamsListView.refresh();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,16 @@
<Font size="16.0" />
</font>
</Text>
<TextField fx:id="teamNameTextField" onKeyReleased="#teamNameTextFieldOnKeyReleased" GridPane.rowIndex="1">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</TextField>
<HBox GridPane.rowIndex="1">
<children>
<TextField fx:id="teamNameTextField" onKeyReleased="#teamNameTextFieldOnKeyReleased" HBox.hgrow="ALWAYS">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</TextField>
<Button fx:id="autoNameTeamButton" mnemonicParsing="false" onAction="#onActionAutoNameTeam" prefHeight="27.0" prefWidth="47.0" text="Auto" />
</children>
</HBox>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Seed:" GridPane.columnIndex="1">
<font>
<Font size="16.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
<Font name="System Bold" size="16.0" />
</font>
</Text>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="CleoPetra's little helper for running matches.&#10;It will be open automatically if missing.&#10;Rocket League will also be opened, when the RLBot Runner is opened.">
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="CleoPetra's little helper for running matches.&#10;It will be opened automatically if missing.&#10;Rocket League will also be opened, when the RLBot Runner is opened.">
<font>
<Font size="14.0" />
</font>
Expand Down
154 changes: 154 additions & 0 deletions src/dk/aau/cs/ds306e18/tournament/utility/AutoNaming.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package dk.aau.cs.ds306e18.tournament.utility;

import dk.aau.cs.ds306e18.tournament.model.Bot;
import dk.aau.cs.ds306e18.tournament.model.Team;
import dk.aau.cs.ds306e18.tournament.model.Tournament;

import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
* Contains an algorithm for auto-naming teams using a piece of the team member's name.
*/
public class AutoNaming {

private static final Pattern BOT_PATTERN = Pattern.compile("[Bb]ot|BOT");

private static final Set<String> IGNORED_PIECES = new HashSet<>(Arrays.asList(
"bot", "psyonix",
"a", "an", "the", "my", "that", "it",
"of", "in", "as", "at", "on", "by", "for", "from", "to",
"and", "or", "but"
));

/**
* Find a give a unique name of the given team. The name will consists of pieces from the
* team members' name.
*/
public static void autoName(Team team, Collection<Team> otherTeams) {
String teamName = getName(team);
Set<String> otherTeamNames = otherTeams.stream()
.filter(t -> t != team)
.map(Team::getTeamName)
.collect(Collectors.toSet());
teamName = uniquify(teamName, otherTeamNames);
team.setTeamName(teamName);
}

/**
* Checks if a team name is already present in a set of names, and if it is, a postfix "(i)" will
* be added the end of the team name. E.g. "Team" already exist, then "Team" becomes "Team (1)".
* The new unique name is returned.
*/
public static String uniquify(String teamName, Set<String> otherTeamsNames) {
int i = 1;
String candidateName = teamName;
while (otherTeamsNames.contains(candidateName)) {
i++;
candidateName = teamName + " (" + i + ")";
}
return candidateName;
}

/**
* Finds a team name for the given team. The algorithm will try to find an
* interesting piece from each bot's name and use it in the team name.
* The algorithm isn't perfect, and characters other than letters and digits
* can create weird names.
* Example: ReliefBot + Beast from the East => Relief-Beast.
*/
public static String getName(Team team) {
return getName(team.getBots().stream().map(Bot::getName).collect(Collectors.toList()));
}

/**
* Finds a team name for the given list of bots. The algorithm will try to find an
* interesting part from each bot's name and use it in the team name.
* If there is only one unique name, that name will be used.
* The algorithm isn't perfect, and characters other than letters and digits
* can create weird names.
* Example: ReliefBot + Beast from the East => Relief-Beast.
*/
public static String getName(List<String> botNames) {
int botCount = botNames.size();
if (botCount == 0) return "Team";
if (botCount == 1) return botNames.get(0);

// Check if there is only one unique name, in that case use that name
long uniqueNames = botNames.stream().distinct().count();
if (uniqueNames == 1) return botNames.get(0);

// Construct names from the short names of bots, separated by "-"
// Example: ReliefBot + Beast from the East => Relief-Beast
StringBuilder str = new StringBuilder();
for (int i = 0; i < botCount; i++) {
String name = botNames.get(i);
String shortName = getShortName(name);
str.append(shortName);
if (i != botCount - 1) {
str.append("-");
}
}

return str.toString();
}

/**
* Finds a short name for the given bot name. The short name will be the first interesting "word" in
* the bot's name. The function knows how to split logically ("RelieftBot" => "Relieft" + "Bot")
* and ignore common words ("of", "the", etc.). The algorithm isn't perfect, and characters
* other than letters and digits can create weird names.
*/
public static String getShortName(String botName) {
String[] pieces = getPieces(botName);

Optional<String> shortName = Arrays.stream(pieces)
// Remove pieces that are "Bot", "Psyonix", or common words like "of", "the", etc.
.filter(s -> !IGNORED_PIECES.contains(s.toLowerCase()))
// Remove substrings that are "bot", e.g. "Skybot" => "Sky"
.map(s -> s.endsWith("bot") ? s.substring(0, s.length() - 3) : s)
.findFirst();

return shortName.orElse(botName);
}

/**
* Find all pieces of a name.
*/
private static String[] getPieces(String botName) {
/*
The following regex is used to split the bot name into pieces.
The string is split the following places:
- Between two letters/digits separated by spaces, regardless of casing (removing spaces)
Examples:
"A B" = ["A", "B"]
"a b" = ["a", "b"]
"2 a" = ["2", "a"]
"A B" = ["A", "B"]
"Air Bud" = ["Air", "Bud"]
"Beast from the East" = ["Beast", "from", "the", "East"]
- Between a letter/digit and an uppercase letter separated by -'s (removing the -'s)
Examples:
"A-B" = ["A", "B"]
"A-b" = ["A-b"]
"2-A" = ["2", "A"]
"2-a" = ["2-a"]
"2--A" = ["2", "A"]
"2--a" = ["2--a"]
"A-2" = ["A-2"]
"Self-driving" = ["Self-driving"]
- Between two chars where the first is lowercase or a digit and second is uppercase
Examples:
"AaB" = ["Aa", "B"]
"2B" = ["2", "B"]
"ReliefBot" = ["Relief", "Bot"]
Since the split function removes what is matches, the regex uses positive lookbehind (?<=) and
positive lookahead (?=) to check the characters around the split.
Other characters than letters and digits will probably create errors, but they are rare in bot names,
so it should be fine.
Try it out on https://regex101.com
*/
return botName.split("(?<=[\\da-z])-*(?=[A-Z])|(?<=[\\da-zA-Z])\\ +(?=[\\da-zA-Z])");
}
}
74 changes: 74 additions & 0 deletions test/dk/aau/cs/ds306e18/tournament/utility/AutoNamingTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package dk.aau.cs.ds306e18.tournament.utility;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import static org.junit.Assert.assertEquals;

public class AutoNamingTest {

@RunWith(Parameterized.class)
static public class GetNameTest {
private String result;
private String expectedResult;

public GetNameTest(String[] botNames, String expectedResult) {
this.result = AutoNaming.getName(Arrays.asList(botNames));
this.expectedResult = expectedResult;
}

@Parameterized.Parameters
public static Collection parameters() {
return Arrays.asList(new Object[][]{
{new String[0], "Team"},
{new String[]{"ReliefBot", "Beast from the East"}, "Relief-Beast"},
{new String[]{"Air Bud", "Skybot"}, "Air-Sky"},
{new String[]{"Botimus Prime", "Gas Gas Gas"}, "Botimus-Gas"},
{new String[]{"Self-driving car", "Diablo"}, "Self-driving-Diablo"},
{new String[]{"Gosling", "Self-driving car"}, "Gosling-Self-driving"},
{new String[]{"NV Derevo", "AdversityBot"}, "NV-Adversity"},
{new String[]{"Wildfire V2", "Boolean Algebra Calf"}, "Wildfire-Boolean"},
{new String[]{"Psyonix Allstar", "Zoomelette"}, "Allstar-Zoomelette"},
{new String[]{"Kamael", "Kamael"}, "Kamael"},
});
}

@Test
public void getNameTest() {
assertEquals(expectedResult, result);
}
}

@RunWith(Parameterized.class)
static public class UniquifyTest {
private String result;
private String expectedResult;

public UniquifyTest(String teamName, String[] otherTeams, String expectedResult) {
this.result = AutoNaming.uniquify(teamName, new HashSet<>(Arrays.asList(otherTeams)));
this.expectedResult = expectedResult;
}

@Parameterized.Parameters
public static Collection parameters() {
return Arrays.asList(new Object[][]{
{ "TSM", new String[]{"NRG", "C9"}, "TSM"},
{ "Team", new String[]{"Team", "AnotherTeam"}, "Team (2)"},
{ "Team", new String[]{"Team (1)", "Team (2)"}, "Team"},
{ "Team", new String[]{"Team", "Team (2)"}, "Team (3)"},
{ "", new String[]{"", "Another Team"}, " (2)"},
});
}

@Test
public void uniquifyTest() {
assertEquals(expectedResult, result);
}
}
}

0 comments on commit f6372ae

Please sign in to comment.