Click here to Skip to main content
15,888,610 members
Articles / Game Development
Tip/Trick

MollyMage - Turn-based Strategy Approach

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
4 Dec 2022MIT2 min read 3.8K   2   2
Overview of the strategy in turn-based MollyMage game
We build random strategy for MollyMage in Java

Introduction

The description is proposed for random strategies in turn-based games.

The MollyMage is a game where five moves are defined: the position moves and potion drop move, after which the potion explodes horizontally and vertically.

The game is to write your own bot which will work with other bots and also destroy them.

Besides bots on map, there are also other players, ghosts and not exploding potions which are to be collected.

The game was presented by EPAM Anywhere on the coding challenge contest.

Background

The reader should be familiar with basics of Java coding in order to proceed to the next section.

I was able to be the finalist of the coding challenge accompanying the software presented here.

We describe the random-based strategy.

Using the code

The arbitrary primitives which are sufficient to determine the next move are defined as follows. Here the variables defined the previous command and the dropped potion position as well as to the avoid moves to be made, random seed is also initialized.

The logical functions return the action type according to position of elements on the map.

The code below defines the dropped potion position in potionX and potionY, the steps till next potion is defined by potionStepsRemained variable, random is a variable to follow the random move.

Java
public static String prevCommand = Command.DROP_POTION;

public int potionX = -1;

public int potionY = -1;

public int potionStepsRemained = 0;

public static Random random = new Random((new Date()).getTime());

Function isBlastPotion() returns answer if the element on the map can explode, isDamagePotion() is a function to determine the potions which are not to be blasted by dropped potion.

Java
public boolean isBlastPotion(Element e) {
    return e == Element.POTION_EXPLODER || e == Element.POTION_TIMER_4
            || e == Element.POTION_TIMER_3
            || e == Element.POTION_TIMER_2
            || e == Element.POTION_TIMER_5
            || e == Element.POTION_TIMER_1
            || e == Element.HERO_POTION
            || e == Element.OTHER_HERO_POTION
            || e == Element.ENEMY_HERO_POTION
            || e == Element.GHOST
            || e == Element.GHOST_DEAD
    ;
}

public boolean isDamagePotion(Element e) {
    return e == Element.POTION_EXPLODER || e == Element.POTION_TIMER_4
            || e == Element.POTION_TIMER_3
            || e == Element.POTION_TIMER_2
            || e == Element.POTION_TIMER_1

            || e == Element.POTION_IMMUNE
            || e == Element.POTION_COUNT_INCREASE
            || e == Element.POTION_TIMER_5
            || e == Element.POTION_REMOTE_CONTROL
            || e == Element.POISON_THROWER
            || e == Element.HERO_POTION
            || e == Element.OTHER_HERO_POTION
            || e == Element.ENEMY_HERO_POTION
            || e == Element.POTION_BLAST_RADIUS_INCREASE
            ;
}

isBlastPoint() is a function for the element on position x and y on the map to be explosive.

Java
public boolean isBlastPoint(int x, int y) {
    return this.isBlastPotion(this.board.getAt(x, y));
}

badCell() is a function with different parameters like x and y and element on that position horizontally and vertically.

Java
public boolean badCell(int x, int y) {
    return isDamagePotion(this.board.getAt(x, y));
}

goodMove() is a function to determine the possible blast in each of four move directions.

Java
public boolean goodMove(int x, int y) {
    for (int i = 1; i <= 6; ++i) {
        if (isBlastPoint(x + i, y) ) return false;
        
        if (isBlastPoint(x - i, y) ) return false;

        if (isBlastPoint(x, y - i))return false;
        
        if (isBlastPoint(x, y + i)) return false;
    }

    return true;
}

goodCell() function determines if the sell can be visited and the character bot is out of possible damage.

Java
public boolean goodCell(int x, int y) {
    if (badCell(x, y)) return false;

    Element e = this.board.getAt(x, y);

    return e == Element.NONE
            || e == Element.POTION_IMMUNE
            || e == Element.POTION_COUNT_INCREASE
            || e == Element.POTION_REMOTE_CONTROL
            ;
}

noDamagePotion() determines if no damage can be obtained by the potions dropped by other players on the map.

Java
public boolean noDamagePotion() {
    int hx = this.board.getHero().getX();

    int hy = this.board.getHero().getY();

    for (int i = 1; i <= 6; ++i) {
        Element e = this.board.getAt(hx + i, hy);

        if (isDamagePotion(e)) {
            return false;
        }

        e = this.board.getAt(hx - i, hy);

        if (isDamagePotion(e)) {
            return false;
        }

        e = this.board.getAt(hx, hy + i);

        if (isDamagePotion(e)) {
            return false;
        }

        e = this.board.getAt(hx, hy - i);

        if (isDamagePotion(e)) {
            return false;
        }
    }

    return true;
}

The next move command is defined as follows with respect to possible damage to the bot according to game logic:

Java
public String getNextCommand() {
    int hx = this.board.getHero().getX();

    int hy = this.board.getHero().getY();

    HashSet<String> hsDiag = new HashSet<>();

    if (this.board.getAt(hx - 1, hy) == Element.NONE) {
        if (this.board.getAt(hx - 1, hy - 1) == Element.NONE) {
            hsDiag.add(Command.MOVE_LEFT);
        }

        if (this.board.getAt(hx - 1, hy + 1) == Element.NONE) {
            hsDiag.add(Command.MOVE_LEFT);
        }
    }

    if (this.board.getAt(hx + 1, hy) == Element.NONE) {
        if (this.board.getAt(hx + 1, hy - 1) == Element.NONE) {
            hsDiag.add(Command.MOVE_RIGHT);
        }

        if (this.board.getAt(hx + 1, hy + 1) == Element.NONE) {
            hsDiag.add(Command.MOVE_RIGHT);
        }
    }

    if (this.board.getAt(hx, hy - 1) == Element.NONE) {
        if (this.board.getAt(hx - 1, hy - 1) == Element.NONE) {
            hsDiag.add(Command.MOVE_DOWN);
        }

        if (this.board.getAt(hx + 1, hy - 1) == Element.NONE) {
            hsDiag.add(Command.MOVE_DOWN);
        }
    }

    if (this.board.getAt(hx, hy + 1) == Element.NONE) {
        if (this.board.getAt(hx - 1, hy + 1) == Element.NONE) {
            hsDiag.add(Command.MOVE_UP);
        }

        if (this.board.getAt(hx + 1, hy + 1) == Element.NONE) {
            hsDiag.add(Command.MOVE_UP);
        }
    }

    if (hsDiag.size() > 0) {
        return hsDiag.toArray()[random.nextInt(hsDiag.size())].toString();
    }

    boolean bfLeft = true;
    boolean bfRight = true;
    boolean bfUp = true;
    boolean bfDown = true;

    for (int i = 1; i <= 4; ++i) {
        if (this.board.getAt(hx - i, hy) != Element.NONE) {
            bfLeft = false;
        }

        if (this.board.getAt(hx + i, hy) != Element.NONE) {
            bfRight = false;
        }

        if (this.board.getAt(hx, hy + i) != Element.NONE) {
            bfUp = false;
        }

        if (this.board.getAt(hx, hy - i) != Element.NONE) {
            bfDown = false;
        }
    }

    HashSet<String> hs = new HashSet<>();

    if (bfLeft) {
        hs.add(Command.MOVE_LEFT);
    }

    if (bfRight) {
        hs.add(Command.MOVE_RIGHT);
    }

    if (bfUp) {
        hs.add(Command.MOVE_UP);
    }

    if (bfDown) {
        hs.add(Command.MOVE_DOWN);
    }

    if (hs.size() > 0) {
        return hs.toArray()[random.nextInt(hs.size())].toString();
    }

    return null;
}

Thus, the obstacles are to be avoided one in a row horizontally or vertically.

The function for next step is defined as follows:

Java
@Override
public String get(Board board) {
    this.board = board;
    if (board.isGameOver()) return "";

    if (this.nextCommand != null) {

        prevCommand = this.nextCommand;

        this.nextCommand = null;

        return prevCommand;
    }


    HashSet<String> avails = new HashSet<>();

    HashSet<String> avails1 = new HashSet<>();

    HashSet<String> avails2 = new HashSet<>();

    if (goodCell(this.board.getHero().getX() - 1, this.board.getHero().getY())
        && board.getAt(this.board.getHero().getX() - 1, this.board.getHero().getY()) != Element.GHOST
            && goodMove(this.board.getHero().getX() - 1, this.board.getHero().getY())
            && board.getAt(this.board.getHero().getX() - 1, this.board.getHero().getY()) != Element.TREASURE_BOX) {
        avails.add(Command.MOVE_LEFT);

        avails1.add(Command.MOVE_LEFT);
    }

    if (goodCell(this.board.getHero().getX() + 1, this.board.getHero().getY())
            && board.getAt(this.board.getHero().getX() + 1, this.board.getHero().getY()) != Element.GHOST
            && goodMove(this.board.getHero().getX() + 1, this.board.getHero().getY())
            && board.getAt(this.board.getHero().getX() + 1, this.board.getHero().getY()) != Element.TREASURE_BOX) {
        avails.add(Command.MOVE_RIGHT);

        avails1.add(Command.MOVE_RIGHT);
    }

    if (goodCell(this.board.getHero().getX(), this.board.getHero().getY() - 1)
            && board.getAt(this.board.getHero().getX(), this.board.getHero().getY() - 1) != Element.GHOST
            && goodMove(this.board.getHero().getX(), this.board.getHero().getY() - 1)
            && board.getAt(this.board.getHero().getX(), this.board.getHero().getY() - 1) != Element.TREASURE_BOX) {
        avails.add(Command.MOVE_DOWN);

        avails2.add(Command.MOVE_DOWN);
    }

    if (goodCell(this.board.getHero().getX(), this.board.getHero().getY() + 1)
            && board.getAt(this.board.getHero().getX(), this.board.getHero().getY() + 1) != Element.GHOST
            && goodMove(this.board.getHero().getX(), this.board.getHero().getY() + 1)
            && board.getAt(this.board.getHero().getX(), this.board.getHero().getY() + 1) != Element.TREASURE_BOX) {
        avails.add(Command.MOVE_UP);

        avails2.add(Command.MOVE_UP);
    }

    String t = this.getNextCommand();

    if ((avails1.size() > 0 || avails2.size() > 0) && t != null && noDamagePotion() && numberOfPotions > 0 && potionStepsRemained == 0) {
        potionX = board.getHero().getX();

        potionY = board.getHero().getY();

        potionStepsRemained = 6;

        this.nextCommand = t;

        return Command.DROP_POTION;
    } else if (potionStepsRemained > 0) {
        --potionStepsRemained;

        if ((potionX != this.board.getHero().getX()) && (potionY != this.board.getHero().getY())) {
            return Command.NONE;
        }

        for (String s: avails) {
            if (s.equals(Command.MOVE_DOWN)) {
                if (potionY - 1 != this.board.getHero().getY() && potionX != this.board.getHero().getX()) {
                    return prevCommand = s;
                }
            } else if (s.equals(Command.MOVE_UP)) {
                if (potionY + 1 != this.board.getHero().getY() && potionX != this.board.getHero().getX()) {
                    return prevCommand = s;
                }
            } else if (s.equals(Command.MOVE_LEFT)) {
                if (potionX - 1 != this.board.getHero().getX() && potionY != this.board.getHero().getY()) {
                    return prevCommand = s;
                }
            } else if (s.equals(Command.MOVE_RIGHT)) {
                if (potionX + 1 != this.board.getHero().getX() && potionY != this.board.getHero().getY()) {
                    return prevCommand = s;
                }
            }
        }

        if (potionY == this.board.getHero().getY()) {
            if (this.board.getHero().getX() > potionX && avails1.contains(Command.MOVE_RIGHT)) {
                return prevCommand = Command.MOVE_RIGHT;
            } else if (avails1.contains(Command.MOVE_LEFT)) {
                return prevCommand = Command.MOVE_LEFT;
            }

            return Command.NONE;
        }

        if (potionX == this.board.getHero().getX()) {
            if (this.board.getHero().getY() > potionY && avails2.contains(Command.MOVE_UP)) {
                return prevCommand = Command.MOVE_UP;
            } else if (avails2.contains(Command.MOVE_DOWN)) {
                return prevCommand = Command.MOVE_DOWN;
            }

            return Command.NONE;
        }

    if (avails.size() == 0) {
        return Command.NONE;
    }

    Map<Integer, String> ad = new HashMap<>();

    for (String s: avails) {
        List<Point> ghosts = new ArrayList<>();
        List<Point> potions = this.board.getPotions();

        ghosts.addAll(potions);

        int mean = -1;

        if (s.equals(Command.MOVE_LEFT)) {
            for (Point pt : ghosts) {
                int k = Math.abs(this.board.getHero().getX() - 1 - pt.getX())
                        + Math.abs(this.board.getHero().getY() - pt.getY());

                if (k < mean || mean == -1) mean = k;
            }

            if (mean != -1) ad.put(mean, s);
        }

        mean = -1;

        if (s.equals(Command.MOVE_RIGHT)) {
            for (Point pt : ghosts) {
                int k = Math.abs(this.board.getHero().getX() + 1 - pt.getX())
                        + Math.abs(this.board.getHero().getY() - pt.getY());

                if (k < mean || mean == -1) mean = k;
            }

            if (mean != -1) ad.put(mean, s);
        }

        mean = -1;

        if (s.equals(Command.MOVE_UP)) {
            for (Point pt : ghosts) {
                int k = Math.abs(this.board.getHero().getX() - pt.getX())
                        + Math.abs(this.board.getHero().getY() + 1 - pt.getY());

                if (k < mean || mean == -1) mean = k;
            }

            if (mean != -1) ad.put(mean, s);
        }

        mean = -1;

        if (s.equals(Command.MOVE_DOWN)) {
            for (Point pt : ghosts) {
                int k = Math.abs(this.board.getHero().getX() - pt.getX())
                        + Math.abs(this.board.getHero().getY() - 1 - pt.getY());

                if (k < mean || mean == -1) mean = k;
            }

            if (mean != -1) ad.put(mean, s);
        }
    }

    int vmax = -1;

    for (Integer ii: ad.keySet()) {
        if (ii > vmax) {
            vmax = ii;
        }
    }

    if (vmax != -1 && vmax > 0 && vmax < 4) {
        return Command.NONE;
    }

    return avails.toArray()[random.nextInt(avails.size())].toString();
}

Since no direction can be chosen because of the current game state, the random move is chosen.

This strategy has brought author of this trick to finals.

Points of Interest

Thus, we have learned the random strategies in turn-based games like MollyMage.

As you can see the random strategy may lead to the winning outcome.

Image 1

History

3rd December 2022 - Initial release.

License

This article, along with any associated source code and files, is licensed under The MIT License



Comments and Discussions

 
QuestionMixed strategy Pin
Randor 4-Dec-22 18:00
professional Randor 4-Dec-22 18:00 
AnswerRe: Mixed strategy Pin
Mirzakhmet Syzdykov5-Dec-22 21:11
professionalMirzakhmet Syzdykov5-Dec-22 21:11 
Exactly, thank you

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.