Generator Armii Afterglow - mój pierwszy program

0

//Screeny i program w formacie exe w załączniku.
Zacząłem się uczyć Javy na początku wakacji 2014 i mniej więcej w październiku / grudniu zacząłem pisać swój projekt, żeby uporządkować wiedzę. Czytałem, że najlepiej jest zrobić coś, co byłoby przydatne dla samego piszącego. Pewnego razu napisał do mnie kolega i zaproponował coś, mianowicie generator armii do gry figurkowej Afterglow (mam nadzieję, że nie zostanie to potraktowane jako reklama, chcę po prostu objaśnić o co chodzi), zachowując odpowiednie zasady (http://afterglowthegame.com/index.php/pl/zasady). W programie możemy wybrać frakcję (aktualnie tylko jedna dostępna), postacie, dokupić im eq i skille i sprawdzić całkowity koszt oraz ewentualne nieprawidłowości. Niestety nie zdążyłem zaimplementować póki co zapisu i wczytywania (czyli w sumie czegoś najbardziej pożytecznego), ale może kiedyś... Napotykałem się z masą błędów, problemów itp., które jakoś rozwiązywałem czy obchodziłem (niekiedy z Waszą pomocą :)). Jako że jestem w maturalnej klasie, to musiałem poświęcić często czas na coś innego, więc przerwy trwały nawet 1-2 miesiące, ale w końcu się udało.

Jak patrzę teraz na to, to widzę słaby layout, marny kod (który starałem się często poprawiać, szczególnie po kilku pierwszych stronach 'Czystego Kodu') i ogólnie lekki syf, ale jestem mega zadowolony, że zrobiłem coś co po prostu działa i nauczyłem się przy tym wielu podstawowych rzeczy.

https://github.com/Naitoreivun/AfterglowArmyGenerator - tutaj link do repo; w folderze Generator znajduje się główna klasa odpalająca

Proszę o konstruktywną krytykę, chyba że ktoś miał zły dzień i widząc 'to coś' chce się po prostu wyżyć. Przy okazji mam kilka pytań i rzeczy do omówienia, które pojawiły się w trakcie pisania:

O:

  1. Jakbym teraz miał się za to zabrać, to pewnie zrobiłbym to bardziej w rodzaju programu, który pozwala stworzyć armię, czyli robimy ludzika, nadajemy mu nazwę, rangę i możliwości co może kupić a czego nie itp itp.
  2. Myślałem żeby użyć dekoratora (Zasoby i projektowanie klas) ale jednak nie byłem wprawiony i bałem się użyć czegoś, w czym mógłbym się zaplątać, a mimo wszystko mój projekt nie jest bardzo mały, więc wolałem nie ryzykować.

//Program docelowo robiony dla osób, które znają zasady i świat gry.

Q:

  1. serialVersionUID - po co to tak naprawdę? cały czas mi wyskakiwał warrning w eclipsie i w końcu dodałem to, co wymagał w wielu klasach
  2. jeden package czy więcej, dla rozdzielenia różnych rzeczy od siebie?
  3. Starałem się robić przy użyciu MVC i chyba wyszło mi takie kulawe MV... W jednych klasach trzymałem i modyfikowałem dane, a w innych wyświetlałem, ale... No właśnie, jak to powinno wyglądać żeby jeszcze 'C' doszło?
  4. Załóżmy że wszystko spakuje do jara albo potem na exeka przerobie. Jak mogę edytować w tym czymś pliki? Np.: pobieram opisy broni z pliku txt załączonym do projektu. Jak dodać nową linijkę z opisem nowej broni? Czy w takim przypadku używa się po prostu baz danych?
  5. Mam klasę Proroka, który rozszerza klasę Bohatera i rzeczywiście jest on bohaterem mając przy tym więcej funkcji. Ale mam też klasę bodajże Piechura który rozszerza klasę Jednostki Podstawowej, ale niczym on się nie różni, klasa jest praktycznie pusta, zrobiłem ją tylko po to, aby wyglądało to "ładniej". Czy to ma sens? (https://github.com/Naitoreivun/AfterglowArmyGenerator/blob/master/Generator%20Armii%20Afterglow/src/models/DelarianFootmanModel.java)
  6. Tutaj: https://github.com/Naitoreivun/AfterglowArmyGenerator/blob/master/Generator%20Armii%20Afterglow/src/generator/GeneratorFrame.java od linijki 668 do 711 tworzę wybraną przez użytkownika postać za pomocą switch / case. Dostaję nazwę i tworzę odpowiednią klasę. Jest to brzydkie, ale nic innego nie przyszło mi do głowy. Jakieś pomysły?
  7. Moja główna klasa ma około 900 linii. To dużo czy nie? Starałem się wszystko dzielić na metody. Wykonuję w niej rzeczy, które się nie powtarzają, tak więc nie wiedziałem czy dzielić to jeszcze osobno na jakieś klasy, które tylko coś inicjują, że tak to ujmę. Jak to powinno wyglądać?

Przy okazji mam kilka dodatkowych pytań: co dalej? Zakładając, że chciałbym pracować jako backendowiec.
Myślałem nad (w kolejności):

  1. HTML + CSS + JS, ale totalne podstawy, żeby wiedzieć z czym to się je.
  2. JDBC, bazy danych, Hibernate
  3. Sockety + wielowątkowość
  4. Spring
    Co o tym myślicie?

Z góry dziękuje za rady, opinie i poświęcony czas.

1
  1. Moja główna klasa ma około 900 linii. To dużo czy nie?

Za dużo. Szczególnie te MyAction w twoich metodach. Po co bawić się w jakieś klasy anonimowe które rozwalają ci kod, jeżeli możesz to sobie wstawić do osobnej klasy. Tak zrób z każdą akcją, najlepiej wstaw je do osobnego folderu.

private JMenuItem makeNewArmyMenuItem(){
        MyAction newArmyAction = new MyAction("Nowa Armia", "Stwórz now¹ armiê", "new")
		{
			/**
			 * 
			 */
			private static final long serialVersionUID = 1L;
			String choseArmyName;
			@Override
			public void actionPerformed(ActionEvent event)
			{
				//TODO: tutaj dodaj mozliwosc zapisu jesli to nie pierwsza armia
				clearAll();
				chooseFaction();
			}

			private void clearAll()
			{
				unitsListModel.removeAllElements();
				unitsList.removeAll();
				layerPanel.removeAll();
			}
			
			private void chooseFaction()
			{
				String choseFaction = showArmyChooserDialog();
				if (choseFaction != null)
				{
					makeArmy(choseFaction);
					makeArmyPanel();
				}
			}

			private String showArmyChooserDialog()
			{
				return (String) JOptionPane.showInternalInputDialog(getContentPane(), "Wybierz frakcjê:",
						"Tworzenie armii", JOptionPane.QUESTION_MESSAGE, null, factionsNames, factionsNames[0]);
			}

			private void makeArmy(String choseFaction)
			{
				String choseName = showNameFieldDialog();
				armyModel = new ArmyModel(choseFaction);
				armyModel.setArmyName(choseName);
				setInfo("Armia zosta³a stworzona");
				
				army = armyModel.getArmy();
				loadDivisionsNames();
			}

			private String showNameFieldDialog()
			{
				choseArmyName = JOptionPane.showInternalInputDialog(getContentPane(), "Wpisz nazwê swojej armii:",
						"Tworzenie armii", JOptionPane.QUESTION_MESSAGE);
				return choseArmyName == null || "".equals(choseArmyName) ? "Bezimienni" : choseArmyName;
			}
			
			private void loadDivisionsNames()
			{
				divisionsNames = new String[armyModel.getFactionUnitsNames().size()];
				divisionsNames = armyModel.getFactionUnitsNames().toArray(divisionsNames);
				addDivisionButton.setEnabled(true); //TODO to i to wyzej armybutton tez musza dzialac na tego boola co zapis
				//TODO w sensie ze jak zapisalem i robie nowa armie to on ma byc... eee? cos tam jakos
			}
			
			private void makeArmyPanel()
			{
				makeLayerPanel(choseArmyName);
				showArmyPanelButton.setEnabled(true);
			}
			
			private void makeLayerPanel(String armyName)
			{
				leadersButtonGroup = new ButtonGroup();
				unitsPanels = new HashMap<>();
				armyPanel = new ArmyPanel(GeneratorFrame.this, armyModel);
				layerPanel.add(armyPanel, ARMY);
				cardLayout.show(layerPanel, ARMY);
			}
		};
		return new JMenuItem(newArmyAction);
	}

Stanie się:

private JMenuItem makeNewArmyMenuItem(){
        MyAction newArmyAction = new NewArmyAction();
        return new JMenuItem(newArmyAction);
}

divisionsNames = new String[armyModel.getFactionUnitsNames().size()];
divisionsNames = armyModel.getFactionUnitsNames().toArray(divisionsNames);

Zamień tablice na kolekcje.


  1. Mam klasę Proroka, który rozszerza klasę Bohatera i rzeczywiście jest on bohaterem mając przy tym więcej funkcji. Ale mam też klasę bodajże Piechura który rozszerza klasę Jednostki Podstawowej, ale niczym on się nie różni, klasa jest praktycznie pusta, zrobiłem ją tylko po to, aby wyglądało to "ładniej". Czy to ma sens? (https://github.com/Naitoreivun[...]dels/DelarianFootmanModel.java)

Ogólnie rzecz biorąc skomplikowane hierarchie klas nie są najlepszym rozwiązaniem, bo jak sam widzisz dosyć łatwo się w tym pogubić. Flat is better than nested.

public class SpecialUnitModel extends UnitModel
{
	public SpecialUnitModel(String unitName, String factionName)
	{
		super(unitName, factionName);
	}

}

Co to? :D Jeżeli potrzebujesz tej klasy jako markera (taga) - czyli oznaczenia, że subklasy dziedziczące po tej klasie są jakiegoś specjalnego rodzaju czy spełniają jakąś specjalną funkcję w programie - to użyj do tego pustego interfejsu. Jeżeli tego nie robisz to wywal tą klasę. Nie zagłębiam się zbytnio w architekturę twojego programu, ale prawdopodobnie dało się to lepiej rozwiązać. Minus takiej klasy/interfejsu łączy się z w.w zbyt rozbudowaną hierarchią klas: jeżeli jakaś klasa dziedziczy po markerze, to wszystkie subklasy automatycznie robią to samo, a to niekoniecznie musi być pożądane.


od linijki 668 do 711 tworzę wybraną przez użytkownika postać za pomocą switch / case.

Ogólnie ta cała klasa zalatuje to 'God-object', czyli wie wszystko i ma do wszystkiego dostęp. Co do switcha i tych metod poniżej (do 800 linjki). Masz jakąś hierarchię klas i zamiast korzystać z polimorfizmu to zarządzaniem armią zajmuje się twój god-object. Zerknij w ten link. Dalej, czym różnią się te dwie metody?:

private void makeHound(String choseNick, String choseDivision)
	{
		HoundModel houndModel = new HoundModel(choseDivision, armyModel.getFactionName());
		army.put(choseNick, houndModel);
		unitsPanels.put(choseNick, new HoundPanel(GeneratorFrame.this, choseNick,
				armyModel, houndModel));
	}
private void makeFanatic(String choseNick, String choseDivision)
	{
		FanaticModel fanaticModel = new FanaticModel(choseDivision, armyModel.getFactionName());
		army.put(choseNick, fanaticModel);
		unitsPanels.put(choseNick, new FanaticPanel(GeneratorFrame.this, choseNick,
				armyModel, fanaticModel));
	}

Skoro mapa army przyjmuje obiekty typu UnitModel to po co ci osobne metody dla każdej podklasy? To łączy się z tym co napisałem wyżej. Użyj dobroci polimorfizmu.


  1. Załóżmy że wszystko spakuje do jara albo potem na exeka przerobie. Jak mogę edytować w tym czymś pliki? Np.: pobieram opisy broni z pliku txt załączonym do projektu. Jak dodać nową linijkę z opisem nowej broni? Czy w takim przypadku używa się po prostu baz danych?

Jar to archiwum ZIP, możesz tam edytować co chcesz i kiedy chcesz.

0

@Wizzie - wow, dziękuję za tak wyczerpującą odpowiedź. Zapamiętam na przyszłość te rady. Jeszcze tylko się dopytam o kilka kwestii:

divisionsNames = new String[armyModel.getFactionUnitsNames().size()];
divisionsNames = armyModel.getFactionUnitsNames().toArray(divisionsNames);

Zamień tablice na kolekcje.

Nazwy dywizji są mi potrzebne tylko po to, aby je wyświetlić w oknie dialogowym, a jego konstruktor przyjmuje jako parametr tablicę, dlatego nie bawiłem się w kolekcje, gdyż wiedziałem, że i tak ich nie użyję. Czy może jednak mimo to powinienem to trzymać w kolekcji, a w razie potrzeby zrobić toArray() czy coś takiego?


Co to? :D Jeżeli potrzebujesz tej klasy jako markera (taga) - czyli oznaczenia, że subklasy dziedziczące po tej klasie są jakiegoś specjalnego rodzaju czy spełniają jakąś specjalną funkcję w programie - to użyj do tego pustego interfejsu. Jeżeli tego nie robisz to wywal tą klasę. Nie zagłębiam się zbytnio w architekturę twojego programu, ale prawdopodobnie dało się to lepiej rozwiązać. Minus takiej klasy/interfejsu łączy się z w.w zbyt rozbudowaną hierarchią klas: jeżeli jakaś klasa dziedziczy po markerze, to wszystkie subklasy automatycznie robią to samo, a to niekoniecznie musi być pożądane.

Ogólnie w grze są: jednostki podstawowe (wśród nich np. piechur), jednostki specjalne (np. akolita) oraz bohaterowie (np. Kapelan, Inkwizytor). Każdy bohater może zostać przywódcą, więc klasa Hero dziedziczy po poprzedniej w hierarchii, gdyż ma właśnie tę dodatkową opcję. Klasa Jednostka specjalna rzeczywiście została użyta jako tag, gdyż w tej chwili nie ma żadnych dodatkowych opcji, ale nie wiem czy wraz z rozwojem gry, nie dojdzie np. coś takiego, że "każda jednostka specjalna może zostać zastępcą dowódcy" i wtedy wystarczy, że w klasie SpecialUnit dodam jedno pole boolean. Gdybym tego nie miał, pewnie trzeba by nieco przebudować program - czy nie?


Dalej, czym różnią się te dwie metody?:

private void makeHound(String choseNick, String choseDivision)
	{
		HoundModel houndModel = new HoundModel(choseDivision, armyModel.getFactionName());
		army.put(choseNick, houndModel);
		unitsPanels.put(choseNick, new HoundPanel(GeneratorFrame.this, choseNick,
				armyModel, houndModel));
	}
private void makeFanatic(String choseNick, String choseDivision)
	{
		FanaticModel fanaticModel = new FanaticModel(choseDivision, armyModel.getFactionName());
		army.put(choseNick, fanaticModel);
		unitsPanels.put(choseNick, new FanaticPanel(GeneratorFrame.this, choseNick,
				armyModel, fanaticModel));
	}

Skoro mapa army przyjmuje obiekty typu UnitModel to po co ci osobne metody dla każdej podklasy? To łączy się z tym co napisałem wyżej. Użyj dobroci polimorfizmu.

Rzeczywiście, tak bardzo o tym nie pomyślałem, nie wiem czemu D: Dobry tip.

1 użytkowników online, w tym zalogowanych: 0, gości: 1