Implement Attribute and Skill Systems [closed] - java

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 8 years ago.
Improve this question
I have a problem with two solutions, which one to pick. I wonder what a right way of implementing attributes in a RPG are. Let's for the sake of it say we have three attributes. Offensive, Defensive and Speed. Every character will have those. Somewhere I need to store the name, the description and the values of the attributes. I figure it should be done in Attributes.java. Giving me the following Attribute.java
public class Attribute
{
private String name;
private String desc;
private int value;
public Attribute (String name, String desc, int value) {
this.name = name;
this.desc = desc;
this.value = value;
}
public String getName () { // for desc and value as well
return name;
}
}
Now onto the problem, were do I create and store these attributes?
Option #1, I create them here, filling the ArrayList with them
Character.java
import java.util.ArrayList;
import java.util.List;
public class Character
{
private List<Attribute> attributes;
public Character() {
attributes = new ArrayList<Attribute>();
}
public List<Attribute> getAttributes () {
return attributes;
}
}
Option #2, I create an AttributeSystem
AttributeSystem.java
public class AttributeSystem
{
private List<Attribute> attributes;
public AttributeSystem () {
attributes = new ArrayList<Attribute>();
attributes.add (new Attribute ("Offensive", "Your damage.", 5);
attributes.add (new Attribute ("Defensive", "Your defense.", 5);
attributes.add (new Attribute ("Speed", "Your speed.", 5);
}
public Attribute getAttribute(int i) {
return attributes[i];
}
}
Character.java
public class Character
{
private AttributeSystem attributes;
public Character() {
attributes = new AttributeSystem();
}
public AttributeSystem getAttributes () {
return attributes;
}
}
Conclusion
Option #2 makes logically more sense to me.
In both cases I can use a HashMap instead of an ArrayList.
Which one and why would I use?
Final Note
This does not yet effects on your character. Nor does it have the ability to add attribute points. Ignore those two factors for now.

Both options seems legit to me.
But I would declare the list of Attribute as static, as the list of available attribute for player will not change no matter what.
Then for each player, you would map each available attribute to a given value.

I agree with Pablo, but I'd like to expand it a bit. So I think this design is a bit too meta for your requirements. In that Attribute is so generic it's not that helpful, and its flexibility is getting in your way.
In design #2 a Character could never vary their attributes. They would all be 5. Maybe that's just an example, but it becomes more cumbersome to to work with when you go beyond the hard coded values.
If every character has to have those three values then why not a more straightforward design? This is also much faster and less code to access than the attribute scheme.
class Character {
int defensive, offensive, speed;
}
Now you can encapsulate modifiers like weapons, armor, etc by using getter methods like so:
class Character {
int defensive, offensive, speed;
Item weapon;
Item armor;
Item shoes;
public int getTotalDefensive() {
return armor.getDefensive() + defensive;
}
public int getTotalOffensive() {
return weapon.getOffensive() + offensive;
}
public int getTotalSpeed() {
return shoes.getSpeed() + speed;
}
public List<Attribute> getAttributes() {
// if you really need to work with a character like this then you can do that too.
List<Attribute> attributes = new ArrayList<Attribute>();
attributes.add( new Attribute( "offensive", "How much damage you can do", offensive );
attributes.add( new Attribute( "defensive", "How much damage you can sustain", defensive );
attributes.add( new Attribute( "speed", "How fast you can move", offensive );
return attributes;
}
}

Maybe you are over engineering: Assuming this is the classic RPG the chosen name for those values (attributes) should give you a hint: If you have a Character object, and it will have attributes... then I'd add those attributes to the character itself:
class Character {
int defensive,offensive,speed;
... //other attributes here, like name, race or whatever you need
}
The equipment, skills and other things that can be more variable (you can have an item or 20, you can learn one skill or 20... and their nature can be totally different between them) have more sense to be in a list or in another related object.
Besides, don't mix model and view: the description of each attribute should not be part of the Character, that kind of information is not needed by that object and it would be duplicated each time a new character is created.

Related

What's the best way to change attributes of objects stored in an ArrayList or HashMap?

I have to do a little exercise (homework, like a friendlist) in Java, and i'm a little stuck on one of the tasks that i have to implement in my program.
The exercise is about storing some friend-objects with a variety of attributes in a container-class and implementing some methods in the container-class for various tasks on the friend-objects.
The overall exercise is not a problem at all, but i'm quite unconvinced that my solution is the way to go. I hope you can give me some tips here.
The method that is left over, should be something like a "updateFriend" method, with which you can set the value of a given attribute to a new value, straight from the container-class.
I've already set up my friend-class with a handfull of attributes (e.g. prename, lastname, date of birth, adress, and so on) an getters/setters for all of them. I've also implemented the container-class (as an ArrayList), but i can't seem to find an elegant way to implement this specific method. My updateFriend()-method right now takes three parameters.
1.The specific id of the friend-object
2.The name of the attribute that i want to change
3.The new value of the attribute
It uses an enum to check if the entered attribute is an existing attribute and if yes, the method searches the ArrayList for the object that contains that attribute and should overwrite the existing value. It gets a little bulky, as i have implemented a switch on the enum, that calls the fitting setter-method for each attribute of the friend, if the type in attribute exists at all.
So basically the friend-class looks like this:
public class Friend {
private static int friendCount = 1;
private String firstname;
private String lastname;
private LocalDate dateOfBirth;
private String phonenumber;
private String mobilenumber;
private String eMail;
private Adress home;
private int friendID;
//Getters & Setters
...
}
The method that gives me problems in the container-class looks something like this at the moment:
public void updateFriend(int id, String toChange, String newValue)
{
for(Attribute a : attribute.values())
{
if(String.valueOf(a).equalsIgnoreCase(toChange))
{
for(Friend f : friends)
{
int counter = 1;
if(f.getID() == id)
{
switch(a)
{
case FIRSTNAME:
{
f.setPreName(neuerWert);
break;
}
//a case for each attribute
}
I'm quite certain that my take on the given method is messy, slow, and cumbersome. What would be an elegant way of solving this?
Excuse my wording and thanks in advance, greets.
I would suggest 3 performance improvements.
Use HashMap instead of List with key as id. Since, id will be unique, it will take O(1) time to get the relevant object for modification instead of spending O(n) time on List iteration.
You can change the type of toChange parameter from String to enum. This will avoid enum to String conversion and then comparing it.
Since, you are already doing validation of the attribute to be modified and you must be following standard java convention while naming your getters and setters, you can use reflection to call the method on the Friend object by creating the method name from attribute name like set{Attributename}.
Okay, lets start using the enum Attribute to handle all the changes (Since you already holding the attribute values)
Attribute Enum
public enum Attribute {
FIRSTNAME("fname", (friend, name) -> friend.setFirstname(String.valueOf(name))),
LASTNAME("lname", (friend, lname) -> friend.setLastname(String.valueOf(lname))),
DATEOFBIRTH("dob", (friend, dob) -> friend.setDateOfBirth((LocalDate) dob)),
PHONENUMBER("pno", (friend, pno) -> friend.setFirstname(String.valueOf(pno))),
MOBILENUMBER("mno", (friend, mno) -> friend.setFirstname(String.valueOf(mno)));
private String attributeName;
private BiConsumer<Friend, Object> attributeSetter;
public static Attribute getAttributeSetterByName(String attributeName) {
return Arrays.stream(Attribute.values())
.filter(attribute -> attribute.getAttributeName().equalsIgnoreCase(attributeName))
.findFirst()
.orElseThrow(() -> new RuntimeException(String.format("Invalid Attribute name - %s", attributeName)));
//.orElse(null);
}
//Getter, Setter & Args Constructor (Use Lombok to reduce Boiler Plate code)
}
Update Logic
public void updateFriend(int id, String toChange, String newValue) {
Attribute attribute = Attribute.getAttributeSetterByName(toChange);
for (Friend friend : friends) {
if (friend.getId() == id) {
attribute.getAttributeSetter().accept(friend, newValue);
break;
}
}
}
You can use a java.util.function.Consumer<T> object to change an object inside your container where you have all the type safety you get. Instead of having magic strings and string arguments for values, which might not be even for string fields, you can work directly on the objects type:
public void updateFriend(int id, Consumer<Friend> c) {
// find the friend object
Friend found = null;
for (Friend f: this.friends) {
if (f.getId() == id) {
found = f;
break;
}
}
if (found == null) {
throw new IllegalArgumentException("There is no friend object with the given id");
}
// use the friend object.
c.accept(found);
}
You can use this method like this:
container.updateFriend(42, f -> f.setVorName("abc"));
container.updateFriend(9, f -> f.setAddress(some_address_object));

Java : How to store Integer and String in array and retrieve values in another class? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I have a model: fase.java with Integers and Strings + getters and setters:
public class Fase implements Serializable {
private Integer age;
private String name;
}
I want to store both the Integer and String in a Array or ArrayList. I now use this:
public String[] getAllValues(){
String[] values = {age.toString(), name};
return values;
Then in dataServiceImpl.java I retrieve the data with:
user.getFase().getAllValues()[0];
and retrieve the age.
This works, but I have a lot more than age and name, and was thinking if I could put everything in Fase.java in one Array/ArrayList, because they are Integer and String, and then retrieve it in dataServiceImpl.java?
Something like this in Fase.java: ArrayList <Objects> f3Values = new ArrayList <Objects>();
or Fase [] f3Array = new Fase[34];
and then retrieve that in dataServiceImpl.java with: ArrayList<Fase3.Fase3Array> f3List = new ArrayList<Fase3.Fase3Array>();
and use something like: user.f3List[0]; ?
First, you should learn how Java works.
Is Java "pass-by-reference" or "pass-by-value"?
http://docs.oracle.com/javase/tutorial/java/nutsandbolts/arrays.html
Then, you should learn how to properly create an encapsulated class, by defining both constructor(s) and getters, methods, setters (if needed; note that setters in general break encapsulation) etc.
Then, you should understand that to aggregate data you:
create a class, i.e. definition object that holds all the necessary fields,
create a storage aggregate (array, ArrayList, Map, whatever),
3a. create an object of a given class, setting the values of the fields,
3b. add the object to the aggregate,
3c. goto 3a until the aggregate is filled with the data needed.
Explaining that on the code provided, you should first have
public class Fase implements Serializable {
private int age;
private String name;
public Fase( int age, String name ) {
this.age = age;
this.name = name;
}
public int getAge() { return age; }
public String getName() { return name; }
}
then you can create the aggregate, e.g.
int FASE_MAX = 34;
Fase[] fArray = new Fase[FASE_MAX];
ArrayList<Fase> fArrayList = new ArrayList<Fase>(FASE_MAX);
then you create the objects and add them to the aggregate, e.g.
for( int i = 0; i < FASE_MAX; i++ ) {
Fase newFase = new Fase( i, "John Doe" );
fArrayList.add( newFase );
fArray[i] = newFase;
}
then, and only then, you can access the aggregate:
Fase someFase = fArrayList.get( n );
Fase someOtherFase = fArray[n];
Your Fase class can have whatever members and however many members you like and you can access them all. If you want an array of Fase then create one and each element of the array will contain all the Fase members.
Fase[] myArray = new Fase[34];
You have an array of 34 "Fase's" just add whatever members you want to your Fase class.

How do I add or subtract String values to an object, and set the value to "none" when it's empty?

I have a class Passengers which has member properties String name, int health, and String disease with setter and getter methods. The disease variable will initially hold null. Here's that class
public class Passengers
{
private String name;
private int health;
private String disease;
public Passengers(String _name, int _health, String _disease)
{
name = _name;
health = _health;
disease = _disease;
}
public void setHealth(int _health)
{
health = _health;
}
public void setDisease(String _disease)
{
disease = _disease;
}
public String getName()
{
return name;
}
public int getHealth()
{
return health;
}
public String getDisease()
{
return disease;
}
}
What I want to know is how I could add new strings onto this variable, and then how to take away. For example, a passenger Bill starts at null for his diseases, and then contracts malaria and the cold. Bill's disease variable should now hold malaria, cold. Now say the user chooses to treat Bill's malaria. How would I
1) add malaria and cold
2) subtract just malaria from disease?
Whenever I attempt to change the disease with
passengers[index].setDisease() = null;
it says "error: method setDisease in class Passengers cannot be applied to given types:
required: String
found: no arguments"
I would reccomend making disease a Set of Strings.
Set<String> diseases = new HashSet<String>();
void addDisease(String disease) {
diseases.add(disease);
}
void removeDisease(String deisease) {
diseases.remove(disease);
}
Sets are "better", in this case, than other Collections because they cannot hold duplicates.
You should give the class a List<String> such as an ArrayList<String> and put the diseases in this List.
Better still, create a class or enum of Disease and have your Passenger class use a List<Disease> and avoid over-use of String. You could then give the class public addDisease(Disease disease) and removeDisease(Disease disease) methods.
Incidentally, your class above should be named Passenger, the singular, not Passengers, the plural, since it represents the concept of a single Passenger.
For your requirement if you are using List like ArrayList you can access your elements(disease names) by index, but it will allow duplicate data to be inserted(same disease may be added multiple times, it will unnecessary increase in number of diseases and may arise some problems).
If you use Set like HashSet it will allow unique element only, so no issues related to duplicated entries but at the same time you can't access a particular disease by index (if you need so, as of now I am not aware of your further requirement).
So as best of my knowledge I suggest you to use LinkedHashSet(HashSet with Linked approach) it will provide you FIFO order without duplicate insertion problem.

Benefits of factoring a new class vs string values for elements in a Set

This is more of a design question with implications for code simplicity vs. performance.
Lets say you want to make sure a set of values for a given user id are the same between two systems. The example here is to check that a student id has the same number of course enrollments in System A and System B.
For this we create:
List<String> studentList = new ArrayList<String>();
Set<String> sysAEnrollments = new HashSet<String>();
Set<String> sysBEnrollments = new HashSet<String>();
private Map<String, String> badEnrollList = new HashMap<String, String>();
And fill them appropriately, given a list of student ids(studentList):
studentList = getCurrentStudentList();
for (String id : studentList){
sysAEnrollments = getSysAEnrollments(id);
sysBEnrollments = getSysBEnrollments(id);
if (!sysAEnrollments.containsAll(sysBEnrollments)){
badEnrollList.put(id, getBadEnrollmentsById(id, sysAEnrollments, sysBEnrollments));
}
}
Question: What should the method 'getBadEnrollmentsById' return?
Either a concatenated string with enough meaning so it can just be printed out.
Or have a new object, for example another collection with the list of course ids that could be used for further processing but harder to use for printed output.
Is it worth designing thoroughly all expected objects or replace some of them with concatenated strings for clarity and performance?
NOTES:
System A is preferred as the authoritative source
Output from getBadEnrollmentsById should have all courses and flag those missing in system B.
PROPOSED SOLUTION: (2012-SEP-14)
EDIT (2012-SEP-17): Updated the Course class to include hashCode and equals
As suggested by user351721 I continued modelling the remaining objects that match the expected results/requirements.
Slight changes made a big difference and allowed me to go over this design flaw and finish with the implementation.
The revised collections are:
List<String> studentList = new ArrayList<String>();
Enrollment sysAEnrollments;
Enrollment sysBEnrollments;
Map<String, List<String>> badEnrollList = new HashMap<String, List<String>>();
And we populate the Enrollments:
for (String id : studentList){
sysAEnrollments = getSysAEnrollments(id);
sysBEnrollments = getSysBEnrollments(id);
if (!sysAEnrollments.getCourses().containsAll(sysBEnrollments.getCourses())){
List<String> missingCourses = getProblemEnrollmentListById(id, sysAEnrollments, sysBEnrollments);
badEnrollList.put(id, missingCourses);
}
}
So for now the output can be printed from badEnrollList by getting at each ArrayList and printing the course names. A course name with a * will mean that it's missing in sysB.
The Enrollment class looks like this:
public class Enrollment {
private Set<Course> courses = new HashSet<Course>();
public void setCourses(Set<Course> courses){
this.courses = courses;
}
public Set<Course> getCourses(){
return this.courses;
}
}
And the Course class ended up like this:
public class Course {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(final String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
// Must override hashCode() and equals()
#Override
public boolean equals(Object o){
if (o == this)
return true;
if (!(o instanceof Course))
return false;
Course c = (Course) o;
return c.id.equals(this.id) && c.name.equals(this.name);
}
#Override
public int hashCode(){
// Magic numbers as shown on Joshua Bloch's book "Effective Java" 2nd Edition, p.48
int result = 17;
result = 31 * this.id.hashCode();
result = 31 * this.name.hashCode();
return result;
}
}
The changes might look subtle but the important clue is that Enrollments are not a collection of strings, Enrollments are a collection of Courses AND each Course has a name and a availability property. They don't seem to do much but by using them I am defining the objects that I'm working with and documenting how these classes can be reused in the future.
"Growing Object-Oriented Software, Guided by Tests" addresses this question: chapter 7, "Value Types". Worth reading. An excerpt:
The more code we write, the more we’re convinced that we should define types to represent value concepts in the domain, even if they don’t do much. It helps to create a consistent domain model that is more self-explanatory. If we create, for example, an Item type in a system, instead of just using String, we can f ind all the code that’s relevant for a change without having to chase through the method calls
concatenated strings
would mean you have to define a pattern and corresponding set of valid strings and implement validation and translation to entity classes. Providing an interface or class would make it easier to update your code in a year or so, not to mention other programmers that might work with your application. Why not store student, enrollment or course objects in badEnrollList? How do these objects look like and what do you want to do with them?
In general: Yes, designing thoroughly all expected objects is worth it.
I feel that a collection, such as List<String> would be a desirable return value. This allows you to more efficiently capture multiple discrepancies between the two sets, and process the missing courses in your second object more intuitively. Printing the list wouldn't be that hard, either - depending on how you wished to convey the information.
It's also worth mentioning that the .equals() method for Set is a cleaner and more intuitive way to ensure equivalence between two sets.
Instead of using all these sets and maps, I'd use Plain Old Java Objects (POJOs) that reflect the actual business objects in question. From what you've indicated, you have Students who have an id of some sort, and who are enrolled in classes on System A and on System B. I would build up a set of Student objects defined like so:
public class Student {
private String id;
private List<String> enrollmentsA;
private List<String> enrollmentsB;
// appropriate getters and setters
}
Depending on if you want to do anything else with Classes, it may even be preferable to create some form of EnrolledClass object to represent that too.
Within the students class, I'd then have a method that would determine the "bad" enrollments. If all that you want to do with this data is generate an email message, it may even be as simple as a String:
public String getBadEnrollmentsMessage() {
List<String> enrolledBoth = getCommonEnrollments();
List<String> enrolledOnlyA = getAOnlyEnrollments();
List<String> enrolledOnlyB = getBOnlyEnrollments();
StringBuilder output;
// format the contents of the above lists into output
// format should be however you want it in the email.
return output.toString();
}
Then you could have a map of Students to email enrollments messages:
HashMap<Student, String> studentEmails;
for (Student s : allStudents) {
studentEmails.put(s, s.getBadEnrollmentsMessage());
}
Of course, if you have a method like getBadEnrollmentsMessage(), I'm not even sure you need the Map of students and strings in the first place. Frankly you could just create a sendEnrollmentEmail method, pass in a Student, and extract the message via getBadEnrollmentsMessage() right there.

Java Monopoly-ish Game

I'm working on a Monopoly based game with properties, money, cards, etc. I recently ran into a problem when working on the chance card aspect of the game...
I have an array of Strings that say things, ex,"Tax refund; collect $25" or "You lose money due to stocks; -$100". Each card does something different, not all of them just deal with money. My question: how can i store these cards to each hold a string with its description but also any action that is involved. For example, card1 has a String and an int value (-25), but on the other hand another card, card2 has a String, an int value(+10) and a property. Sorry if the question is really vague but I don't know how else to describe it.
Just to clarify:
A card could contain simply the description and a money value. While another card might contain a description, money value and move certain spaces.
Thanks to everyone who gave out awesome ideas so quickly!
If your range of actions is very limited (say, 2 or 3 actions involving money, move squares, etc) then I might use the following class:
class Card {
// It would be a good practice to not make the following fields
// public and use getters/setters instead but I've made them this
// way just for illustration purposes
public String text;
public String action;
public int value;
Card(String text, String action, int value) {
this.text = text;
this.action = action;
this.value = value;
}
}
This way (as already pointed out by some other answers), you can use an array of Cards instead of array of Strings. You can then have text in one field, the action in a separate field, and the value associated with that action in a third field. For example, you could have the following cards:
Card lose25 = new Card("You lose $25", "money", -25);
Card move5squares = new Card("Move 5 squares ahead!", "move", 5);
When you're 'processing' the cards, you can do so in the following manner:
...
if (card.action.equals("money") {
// Update user's money with card.value
} else if (card.action.equals("move") {
// Update user's position with card.value
} else if (card.action.equals("...") {
// and so on...
}
...
EDIT:
If the cards can hold more than one action, you can use a HashMap to store actions for that card:
class Card {
public String text;
public HashMap<String, Integer> actions;
Card(String text) {
this.text = text;
actions = new HashMap<String, Integer>();
}
addAction(String action, int value) {
actions.put(action, value);
}
}
A HashMap is a collection that can store key-value pairs. So for a card that has 2 actions, you can use the above code as:
Card aCard = new Card("Lose $25 and move back 3 spaces!");
aCard.addAction("money", -25);
aCard.addAction("move", -3);
Now, when you're actually processing the cards, you need to check the HashMap for all actions stored in this card. One way to iterate through the HashMap is to do the following:
Card processCard = ...;
for (Map.Entry<String, Integer> entry : processCard.actions.entrySet()) {
// This loop will get each 'action' and 'value' that you added to
// the HashSet for this card and process it.
String action = entry.getKey();
int value = entry.getValue();
// Add the earlier 'card processing' code here...
if (action.equals("money") {
// Update user's money with value
} else if (action.equals("move") {
// Update user's position with value
} else if (action.equals("...") {
// and so on...
}
}
Create a class for Card, use the Command Pattern design pattern so each card can have a different type of action if need be. So it will hold a String field, text, an int field, value, and another field that is for an interface that represents your action.
The key field will be the "action" field that holds a reference to an interface type, say called CardAction:
interface CardAction {
execute();
}
Or you could use use a Runnable or a Future if you want to use a ready made interface. This way you can inject objects of this class with different actions for different types of cards. This will prevent you from having to create multiple different Card classes.
This is a solution that allows each card to have several actions.
The ActionType is an interface, as it only has several correct values.
All the fields are made final as once the cards have been created, they shouldn't be allowed to change.
The getActions() method returns a copy of the array as we don't want the game to be able to modify the actions of the card after creation.
enum ActionType {
MONEY, MOVE, ...
}
class Action {
private final String description;
private final ActionType type;
private final int value;
public Action(String desc, ActionType type, int value) {
this.description = desc;
this.type = type;
this.value = value;
}
public String getDescription() {
return description;
}
public ActionType getType() {
return type;
}
public int getValue() {
return value;
}
}
class Card {
private final Action[] actions; // If you want to be able to modify the list of actions after creation, use a non-final List<>, otherwise an array
public Card(Action... actions) {
this.actions = actions;
}
public Action[] getActions() {
Action[] copy = new Action[actions.length];
System.arraycopy(actions, 0, copy, 0, actions.length);
}
}
Make a Card Class, it should contain the description (String) and a value (int) (+10,-25, 0 ect...). You then would have a array of Cards instead of array of strings.
Have a Card class with description and value.
Card
{
String description;
int value;
Card(String desc, int value)
{
this.description = desc;
this.value = value;
}
getters..
setters...
}
Create objects like
Card c = new Card("You may loose 25$...", -25);
Note that, these cards with int value can accept values between -2,147,483,648 to 2,147,483,647. If you want some higher values, you can change int to long or something else.
Make a Card class, with a string called text or something, and a method called onAction() or something. Then, make lots of other classes - one for each card - extending the Card class. Give the string the text you want and in the method you can then put exactly what you want to happen. You can create a class called Deck containing instances of all of these Cards and an array/list, and a draw() method or whatever you've done already. On drawing a Card from the Deck, get it to print that Card's text and call its onAction() method.
Hope this helps.

Categories

Resources