3
\$\begingroup\$

I'm trying to follow as many OO concepts as possible. This project is for my github, as a means to enhance my portfolio for possible internships in the future.

Question 1: Is there anything that's declared static that shouldn't be?

Question 2: Should I have created a constructor for the Dungeon class, that way each dungeon is its own instance instead of have a factory make it every time?

Some notes about the program:

I generate a 30x30 array of Room objects, which also randomly generate a random monster in each room. As of now, the player can select new game, and pick a character. From there, they spawn in the middle room [14][14] where they fight each monster and can move room to room.

Any general tips or advice about the code of the program would be greatly appreciated!

ProjectMoria class:

package projectmoria;


import java.util.Random;
import java.util.Scanner; //needed for user_input

public class ProjectMoria {

    public static final Scanner USERINPUT = new Scanner(System.in);
    public static final Random RAND = new Random();
    public static Room[][] currDungeon;
    public static Player currPlayer;

    public static void main(String[] args) {
        mainMenu();
    }

    public static void mainMenu() {
        boolean status = false;
        do {
            IO.Welcome();
            String selection = USERINPUT.nextLine();
            switch (selection) {
                case "1":
                    newGame();
                    status = true;
                    break;
                case "2":
                    loadGame();
                    status = true;
                    break;
                case "3":
                    settings();
                    status = true;
                    break;
                case "4":
                    IO.credits();
                    status = true;
                    break;
                case "5":
                    System.exit(0);
            }

        } while (status == false);

    }

    public static void newGame() {
        Dungeon dungeon = new Dungeon();
        boolean status = false;
        do {
            IO.newGameIntroduction();
            String selection = USERINPUT.nextLine();
            switch (selection) {
                case "1":
                    status = IO.displayPlayerStats("Warrior", "A tough, "
                            + "well-rounded fighter with a balanced skillset.",
                            100, 20, 30, 3, 0.10);
                    currPlayer = Player.newWarrior();

                    break;
                case "2":
                    status = IO.displayPlayerStats("Dueler", "A quick, nimble "
                            + "duelist with an aptitude for landing critical "
                            + "attacks.", 80, 10, 50, 2, 0.18);
                    currPlayer = Player.newDuelist();

                    break;
            }
        } while (status == false);

        currDungeon = Dungeon.newRandomDungeon(currPlayer);
        dungeon.dungeonLogic(currPlayer, currDungeon);

    }

    public static void loadGame() {

    }

    public static void settings() {

    }

}

Dungeon class:

package projectmoria;


public final class Dungeon {

    private static boolean northDirection = false;
    private static boolean southDirection = false;
    private static boolean westDirection = false;
    private static boolean eastDirection = false;

    public static Room[][] newRandomDungeon(Player player) {
        Room[][] dungeon = new Room[30][30];
        for (int i = 0; i < dungeon.length; i++) {
            for (int j = 0; j < dungeon.length; j++) {
                dungeon[i][j] = Room.newRoomInstance();

            }
        }
        player.setCurrRoom(dungeon[14][14]);
        return dungeon;
    }

    public boolean roomExists(int x, int y) {
        return  (rowExists(x)) && (colExists(y));
    }

    public boolean rowExists(int x){
        return  (x >= 0) && (x <= 29);
    }

    public boolean colExists(int y){
        return  (y >= 0) && (y <= 29);
    }

    public void playerMovement(Player player) {
        northDirection = roomExists(player.getCurrX(), player.getCurrY()
                + 1);
        southDirection = roomExists(player.getCurrX(), player.getCurrY()
                - 1);
        eastDirection = roomExists(player.getCurrX() + 1,
                player.getCurrY());
        westDirection = roomExists(player.getCurrX() - 1,
                player.getCurrY());
        IO.movePlayer(player);

    }

    public void battle(Player player, Monster monster, Room[][] dungeon) {
        IO.battleIntro(player, dungeon[player.getCurrX()][player.getCurrY()]);
        IO.battle(player, monster);
    }

    public void dungeonLogic(Player player, Room[][] dungeon) {
        while (player.isAlive()) {
            if (player.isAlive() && dungeon[player.getCurrX()][player.getCurrY()]
                    .getMonster().isAlive()) {
                battle(player, dungeon[player.getCurrX()][player.getCurrY()]
                        .getMonster(), ProjectMoria.currDungeon);
            } else if (player.isAlive()) {
                playerMovement(player);
            }
        }
    }

    public static boolean isNorthDirection() {
        return northDirection;
    }

    public static boolean isSouthDirection() {
        return southDirection;
    }

    public static boolean isWestDirection() {
        return westDirection;
    }

    public static boolean isEastDirection() {
        return eastDirection;
    }

}

IO class:

package projectmoria;

import java.io.*;

public final class IO {

    public static void playerCrit() {
        System.out.println("Nice! You landed a critical hit! (x2 Damage)");
    }

    public static void monsterCrit() {
        System.out.println("Ouch! The monster landed a critical hit! "
                + "(x2 Damage)");
    }

    public static void Welcome() {
        System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
                + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
        System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
                + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
        System.out.println("Welcome traveler.......");
        System.out.println("To the mines of Moria! ");
        System.out.println("-----------------------");
        System.out.println("       MAIN MENU       ");
        System.out.println("_______________________");
        System.out.println();
        System.out.println(" 1.      New Game        ");
        System.out.println();
        System.out.println(" 2.     Load Game        ");
        System.out.println();
        System.out.println(" 3.      Settings        ");
        System.out.println();
        System.out.println(" 4.       Credits        ");
        System.out.println();
        System.out.println(" 5.     Exit Game        ");
        System.out.println();
        System.out.print(" Make a Selection: ");
        System.out.println();
        System.out.println();
        System.out.println();
    }

    public static void newGameIntroduction() {
        System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
                + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
        System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
                + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
        System.out.println("------------------");
        System.out.println("CHOOSE A CHARACTER");
        System.out.println("------------------");
        System.out.println();
        System.out.println("1. WARRI0R");
        System.out.println("----------");
        System.out.println("A tough, well-rounded fighter with"
                + " a balanced skillset.");
        System.out.println();
        System.out.println("2. DUELIST");
        System.out.println("----------");
        System.out.println("A quick, nimble duelist with an"
                + "aptitude for landing critical attacks.");
        System.out.println();
        System.out.println("________________________");
        System.out.print("Choose Either Character: ");
        System.out.println();
        System.out.println();
        System.out.println();
    }

    public static boolean displayPlayerStats(String name, String description,
            int maxHitPoints, int minDamage, int maxDamage, int defense,
            double critChance) {
        System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
                + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
        System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
                + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
        System.out.println(name);
        System.out.println("-------");
        System.out.println(description);
        System.out.println();
        System.out.println("MAX HP: " + maxHitPoints);
        System.out.println();
        System.out.println(
                "ATTACK: " + minDamage + "-" + maxDamage);
        System.out.println();
        System.out.println("DEFENSE(Scale:1-5): " + defense);
        System.out.println();
        System.out.println("CRIT CHANCE: " + critChance + "%");
        System.out.println();
        System.out.println("ARE YOU SURE YOU WANT TO PLAY AS A "
                + name.toUpperCase() + "? "
                + "(y/n)");
        System.out.println();
        System.out.println();
        System.out.println();
        if (ProjectMoria.USERINPUT.nextLine().equals("y")) {
            return true;
        } else {
            return false;
        }
    }

    public static void credits() {
        System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
                + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
        System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
                + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
        System.out.println("This game was created by Jackson Kolb.");
        System.out.println("Would you like to return to the main menu? (y/n)");
        if (ProjectMoria.USERINPUT.nextLine().equals("y")) {
            ProjectMoria.mainMenu();
        }
    }

    public static void movePlayer(Player player) {

        if (Dungeon.isNorthDirection() == true) {
            System.out.println("North (n)\n");
        }
        if (Dungeon.isSouthDirection() == true) {
            System.out.println("South (s)\n");
        }
        if (Dungeon.isEastDirection() == true) {
            System.out.println("East (e)\n");
        }
        if (Dungeon.isWestDirection() == true) {
            System.out.println("West (w)\n");
        }

        System.out.print("Where would you like to travel?: ");
        String selection = ProjectMoria.USERINPUT.nextLine();
        if (selection.equals("n") && Dungeon.isNorthDirection()) {
            player.setCurrY(player.getCurrY() + 1);
        } else if (selection.equals("s") && Dungeon.isSouthDirection()) {
            player.setCurrY(player.getCurrY() - 1);
        } else if (selection.equals("e") && Dungeon.isEastDirection()) {
            player.setCurrX(player.getCurrX() + 1);
        } else if (selection.equals("w") && Dungeon.isWestDirection()) {
            player.setCurrX(player.getCurrX() - 1);
        }

    }

    public static void battleIntro(Player player, Room room) {
        System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
                + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
        System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
                + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
        System.out.println("You arrive at Room [" + player.getCurrX() + "]["
                + player.getCurrY() + "]");
        System.out.println("You enter the room and look around and see...");
        System.out.println(room.getDescription() + "\n\n");
        System.out.println("Number of monsters: " + room.getNumOfMonsters());
        System.out.println("Your fight with " + room.getMonster().getName()
                + " begins.\n");
    }

    public static void battle(Player player, Monster monster) {
        while (player.isAlive() && monster.isAlive()) {
            System.out.println("\nMonster HP: " + monster.getHitPoints()
                    + "    " + "Player HP: " + player.getHitPoints());
            System.out.println("----------------------------------");
            System.out.print("\nAttack (a)   Heal (h)");
            String action = ProjectMoria.USERINPUT.nextLine();
            if (action.equals("a")) {
                monster.defend(player);
                if (monster.isAlive()) {
                    player.defend(monster);
                }
            } 
        }
        if (!player.isAlive()) {
            System.out.println("Your lifeless body hits the floor.  GAME OVER");
        } else if (!monster.isAlive()) {
            System.out.println("The monster has been defeated!");
            System.out.println("--------------------------------\n");
        }

    }

    public static void playerHitPointsMessage(int damage, Monster monster) {
        System.out.println("The " + monster.getName() + " hit you for "
                + damage + " damage.");
    }

    public static void monsterHitPointsMessage(int damage, Monster monster) {
        System.out.println("You hit the " + monster.getName()
                + " for " + damage + " damage.");
    }

}

Player class:

package projectmoria;


import java.util.ArrayList;
import java.util.List;


public class Player {

    private final String name;
    private final String description;
    private final int maxHitPoints;
    private int hitPoints;
    private final int minDamage;
    private final int maxDamage;
    private final int defense;
    private double critChance;
    private int currX;
    private int currY;
    private Room currRoom;
    private List<Item> inventory;

    public Player(String name, String description, int maxHitPoints,
            int minDamage, int maxDamage, int defense, double critChance) {
        this.name = name;
        this.description = description;
        this.maxHitPoints = maxHitPoints;
        this.hitPoints = maxHitPoints;
        this.minDamage = minDamage;
        this.maxDamage = maxDamage;
        this.defense = defense;
        this.critChance = critChance;
        this.currX = 14;
        this.currY = 14;
        inventory = new ArrayList<>();

    }

    public int attack() {
        return ProjectMoria.RAND.nextInt(maxDamage - minDamage + 1);
    }

    public int defend(Monster monster) {
        int incomingAttack = monster.attack();
        int random = ProjectMoria.RAND.nextInt(99) + 1;
        if (random <= monster.getCritChance()) {
            incomingAttack = incomingAttack * 2;
            IO.monsterCrit(); //TODO - move to different spot
        }
        IO.playerHitPointsMessage(incomingAttack, monster);
        hitPoints = (hitPoints * defense > incomingAttack)
                ? hitPoints - incomingAttack : 0;
        return hitPoints;
    }

    public void heal(Item potion){
        this.hitPoints =+ 20;
        inventory.remove(potion);
        IO.heal(this.hitPoints);
    }

    public static Player newWarrior() {
        return new Player("Warrior", "A tough, well-rounded fighter with"
                + " a balanced skillset.", 100, 20, 30, 3, 10);
    }

    public static Player newDuelist() {
        return new Player("Duelist", "A quick, nimble duelist with an"
                + " aptitude for landing critical attacks.", 8000, 10, 50, 2, 
                18);
    }

     public String getDescription() {
        return description;
    }

    public int getHitPoints() {
        return hitPoints;
    }

    public boolean isAlive() {
        return hitPoints > 0;
    }

    public String getName() {
        return name;
    }

    public int getMaxHitPoints() {
        return maxHitPoints;
    }

    public int getMinDamage() {
        return minDamage;
    }

    public int getMaxDamage() {
        return maxDamage;
    }

    public int getDefense() {
        return defense;
    }

    public double getCritChance() {
        return critChance;
    }

    public int getCurrX() {
        return currX;
    }

    public int getCurrY() {
        return currY;
    }

    public List<Item> getInventory() {
        return inventory;
    }


    public Room getCurrRoom() {
        return currRoom;
    }

    public void setCurrRoom(Room room) {
        currRoom = room;
    }

    public void setCurrX(int currX) {
        this.currX = currX;
    }

    public void setCurrY(int currY) {
        this.currY = currY;
    }
}

Monster class:

package projectmoria;

import java.util.Random;

public class Monster {

    private final String name;
    private final String description;
    private final int maxHitPoints;
    private int hitPoints;
    private final int minDamage;
    private final int maxDamage;
    private final int defense;
    private final double critChance;
    private boolean aggression;
    private static final Random rand = new Random();

    public Monster(String name, String description, int maxHitPoints,
            int minDamage, int maxDamage, int defense, double critChance,
            boolean aggression) {
        this.name = name;
        this.description = description;
        this.maxHitPoints = maxHitPoints;
        this.hitPoints = maxHitPoints;
        this.minDamage = minDamage;
        this.maxDamage = maxDamage;
        this.defense = defense;
        this.critChance = critChance;
        this.aggression = aggression;
    }

    public int attack() {
        return rand.nextInt(maxDamage - minDamage - 1);
    }

    public int defend(Player player) {
        int incomingAttack = player.attack();
        int random = rand.nextInt(99) + 1;
        if (random <= player.getCritChance()) {
            incomingAttack = incomingAttack * 2;
            IO.playerCrit();
        }
        IO.monsterHitPointsMessage(incomingAttack, this);
        hitPoints = (hitPoints * defense > incomingAttack)
                ? hitPoints - incomingAttack : 0;
        return hitPoints;
    }

    public static Monster newRandomMonster() {
        int random = rand.nextInt(4) + 1;
        Monster monster = null;
        switch (random) {
            case 1:
                monster = newGoblin();
                break;
            case 2:
                monster = newOrc();
                break;
            case 3:
                monster = newShadowAssassin();
                break;
            case 4:
                monster = newFeralGhoul();
                break;
        }
        return monster;
    }

    public static Monster newGoblin() {
        return new Monster("Goblin", "A grotesque, small enemy with a sub-par "
                + "skill-set. ", 30, 10, 15, 2, 10, true);
    }

    public static Monster newOrc() {
        return new Monster("Orc", "A brutish, green mass of destruction. High "
                + "Attack and Defense.", 50, 15, 20, 3, 10, true);
    }

    public static Monster newDragon() {
        return new Monster("Dragon", "A massive scaled wivern.  I hope you "
                + "brought some potions..", 100, 20, 30, 4, 11, true);
    }

    public static Monster newShadowAssassin() {
        return new Monster("Shadow Assassin", "A dark, elusive assassin trained"
                + " to land critical hits.  Low HP, with high attack.", 20, 15,
                35, 2, 20, true);
    }

    public static Monster newGhoul() {
        return new Monster("Ghoul", "A humaniod exposed to radiation, but still"
                + " maintains a consciousness.  Can be aggressive in certain "
                + "situations.", 40, 10, 25, 2, 8, false);
    }

    public static Monster newFeralGhoul() {
        return new Monster("Feral Ghoul", "A humaniod exposed to radiation, so "
                + "much that it can't distinguish friend from foe.",
                40, 10, 25, 2, 8, true);
    }

    public String getDescription() {
        return description;
    }

    public int getHitPoints() {
        return hitPoints;
    }

    public boolean isAlive() {
        return hitPoints > 0;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name + " " + description;
    }

    public double getCritChance() {
        return critChance;
    }
}

Room class:

package projectmoria;

public final class Room {

    private final String description;
    private final Monster monster;
    private final int numOfMonsters;
    private final boolean isBossRoom;
    //private final boolean lighting;

    private Room(String description, Monster monster, boolean isBossRoom,
            int numOfMonsters) {
        this.description = description;
        this.monster = monster;
        //this.lighting = lighting;
        this.isBossRoom = isBossRoom;
        this.numOfMonsters = numOfMonsters;
    }

    public static Room newRoomInstance() {
        String description = null;
        int numOfMonsters = 0;
        //boolean lighting = true;
        boolean isBossRoom = false;
        int random = ProjectMoria.RAND.nextInt(3) + 1; //Change random value parameter to add more room types
        switch (random) {
            case 1:
                description = "A dark corridor with creeping, animated vines.";
                numOfMonsters = 1;
                //lighting = false;
                break;
            case 2:
                description = "A single lantern lays on the floor, illuminating"
                        + " a skeleton of a past explorer. Two pairs of eyes "
                        + "can be seen.";
                numOfMonsters = 1;
                //lighting = true;
                break;
            case 3:
                description = "A green, floral room full of massive plants.";
                numOfMonsters = 1;
                //lighting = true;
                break;
            /*case 4:
                    description = "a giant dragon";
                    numOfMonsters = 1;
                    isBossRoom = true;
                    break;      
                case 5:
                    description = "";
                    numOfMonsters = 1;
                    break;
                case 6:
                    description = "";
                    numOfMonsters = 1;
                    break;
                case 7:
                    description = "";
                    numOfMonsters = 1;
                    break;
                case 8:
                    description = "";
                    numOfMonsters = 1;
                    break;
                case 9:
                    description = "";
                    numOfMonsters = 1;
                    break;
                case 10:
                    description = "";
                    numOfMonsters = 1;
                    break;*/
        }
        return new Room(description, Monster.newRandomMonster(), false,
                numOfMonsters);

    }

    @Override
    public String toString() {
        return description;
    }

    public boolean isBossRoom() {
        return isBossRoom;
    }

    public String getDescription() {
        return description;
    }

    public Monster getMonster() {
        return monster;
    }

    public int getNumOfMonsters() {
        return numOfMonsters;
    }

    public boolean isIsBossRoom() {
        return isBossRoom;
    }

}

Item class:

package projectmoria;

public class Item {

    private final String name;
    private final String type;
    private final String description;

    public Item(String name, String type, String description) {
        this.name = name;
        this.type = type;
        this.description = description;
    }

    public void use(Player player, Item item) {
        if (item.type.equals("Potion")) {
            player.heal(item);
        }
    }

    public static void addPotion(int numOfPotions, Player player) {
        for (int i = 0; i < numOfPotions; i++) {
            player.getInventory().add(potion());
        }
    }

    private static Item potion() {
        return new Item("Potion", "Potion", " a small vial filled with a " + "translucent red liquid");
    }
}
\$\endgroup\$
  • \$\begingroup\$ is this hosted somewhere? \$\endgroup\$ – RE60K Jul 28 '17 at 16:35
  • \$\begingroup\$ @RE60K Im sorry, I'm kind of new to some jargon. Do you mean to ask if I have the current version added to a website somewhere? If so, I do have it up on my github, for version control. \$\endgroup\$ – JKolb Jul 28 '17 at 16:38
  • \$\begingroup\$ Yes I meant that, it would be easy to clone that and try running. \$\endgroup\$ – RE60K Jul 28 '17 at 16:44
  • \$\begingroup\$ github.com/JacksonKolb94/ProjectMoria \$\endgroup\$ – JKolb Jul 28 '17 at 16:47
1
\$\begingroup\$

The biggest issue I see with your code is your ability to extend it effectively. I would recomend you store a lot of you data in a format like JSON. So you could create for a Monster something like:

{
  "name" : "Ooogle",
  "attack" : 20,
  "defense" : 30,
  ...
}, 
{
  "name" : "Argle",
  "attack" : 31,
  "defense" : 20
  ...
}, 

There actually may be is a better way to represent this with JSON. I'm not a JSON expert. The point I'm trying to make is this will help clean up a lot monster creation code, and room creation that looks like this:

    switch (random) {
        case 1:
            description = "A dark corridor with creeping, animated vines.";
            numOfMonsters = 1;
            //lighting = false;
            break;
        case 2:
            description = "A single lantern lays on the floor, illuminating"
                    + " a skeleton of a past explorer. Two pairs of eyes "
                    + "can be seen.";
            numOfMonsters = 1;
            //lighting = true;
            break;
        case 3:
            description = "A green, floral room full of massive plants.";
            numOfMonsters = 1;
            //lighting = true;
            break;
        /*case 4:
                description = "a giant dragon";
                numOfMonsters = 1;
                isBossRoom = true;
                break;      
            case 5:
                description = "";
                numOfMonsters = 1;
                break;
            case 6:
                description = "";
                numOfMonsters = 1;
                break;
            case 7:
                description = "";
                numOfMonsters = 1;
                break;
            case 8:
                description = "";
                numOfMonsters = 1;
                break;
            case 9:
                description = "";
                numOfMonsters = 1;
                break;
            case 10:
                description = "";
                numOfMonsters = 1;
                break;*/

There might be even better ways than JSON, but it is definitely a push in the right direction as it isolates the data models from your code.

\$\endgroup\$
  • \$\begingroup\$ After doing a little reading on JSON (stackoverflow.com/questions/383692/…), It looks like it's a universal short hand language based on javascript. Like xml if it was skinnier. What would that accomplish for my swing statements? Should i create a JSON file seperately, and then read from it? Thank you! \$\endgroup\$ – JKolb Jul 29 '17 at 2:54
  • 1
    \$\begingroup\$ You should write a JSON file and read from it. As for the switch statements, I don't know the exact syntax in Java but you can randomly select a JSON entry and inject that data into an object. In otherwords, don't do a bunch of case statments, just access a random element in JSON. \$\endgroup\$ – Dair Jul 30 '17 at 2:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.