3
\$\begingroup\$

I'm working on a program that is supposed to use alpha beta pruning to find the best move for a game. The game is basically a binary tree of degree of 40 and each node will have a different float value. The moves are only true or false. The goal is to score a positive score at the end. The professor gave us the Main and the Tree class and all I have to do is the Player. The opponent class is just a class I made that would randomly play true or false just to test my code. I have implemented the class but I'm not sure if it is working properly since I do get negative scores on some play through with the random opponent I made. If someone could look at this and see if my logic for the alpha beta pruning part is correct, it would be really helpful.

Main:

import java.util.ArrayList;

public class Main {


    public static void main(String[] args) {
        final long randomSeed=564715140;

        Tree t=new Tree(randomSeed);
        ArrayList<Boolean> moves=new ArrayList<Boolean>();

        Player player=new Player(t);
        Opponent other=new Opponent(t);
        int turn=0;
        for (int i=0; i<t.height; i++) {
            long before=System.currentTimeMillis();
            boolean maxNode=(turn==0);
            Boolean newMove=((maxNode)?player.play(moves, maxNode):other.play(moves, maxNode));
            if (newMove==null) throw new RuntimeException("No decision made.");
            moves.add(newMove);
            long after=System.currentTimeMillis();
            if ((after-before)>30000) {
                if (maxNode) System.out.println("The maximizer took too long: "+(after-before));
                else System.out.println("The minimizer took too long: "+(after-before));
                System.exit(0); }
            System.out.println("Time: "+(after-before));
            turn=1-turn;
        }

        System.out.println("Final score: "+t.value(moves));

    }

}

Tree:

import java.util.ArrayList;
import java.util.Random;

public class Tree {
    public final int TOTALNODES=2097152;
    public final float EPSILON=0.00001f;
    public final float distribution=10.0f;

    public long randomSeed=564715140;
    public float[] topTree=new float[TOTALNODES];
    private Random r=new Random();
    public long height=40;

    public Tree(long rs) {
        randomSeed=rs;
        r.setSeed(randomSeed);
        topTree[0]=topTree[1]=0.0f;
        int current=2; int levelNodes=2; float factor=1.0f;
        while (current<TOTALNODES) {
            for (int i=0; i<levelNodes; i++) {
                int parent=current/2;
                float sign=0.0f;
                if (r.nextBoolean()&&r.nextBoolean())
                    if (topTree[parent]>EPSILON) sign=1.0f; else sign=-1.0f;
                else if (topTree[parent]>EPSILON) sign=-1.0f; else sign=1.0f;
                float offset=((Math.abs(topTree[parent])<EPSILON)?(r.nextFloat()*2.0f*distribution-distribution):(r.nextFloat()*distribution*factor*sign));
                topTree[current]=topTree[parent]+offset;
                current++; }
            levelNodes*=2; factor*=0.9f; }
    }

    public float value(ArrayList<Boolean> moves) {
        // low and high will both be values between 0 and 2^21-1=TOTALNODES.
        // The depth will be the number of bits examined, starting with the low order bit of the low int.
        // A depth of 0 will indicate the root.
        int position=1;
        for (int i=0; i<Math.min(20, moves.size()); i++) {
            if (moves.get(i).booleanValue()) position=position*2+1; else position*=2; }
        if (moves.size()<=20) return topTree[position];
        r.setSeed(randomSeed+position);

        float[] bottomTree=new float[TOTALNODES];
        bottomTree[0]=bottomTree[1]=topTree[position];
        int current=2; int levelNodes=2; float factor=0.12157665459056928801f;
        while (current<TOTALNODES) {
            for (int i=0; i<levelNodes; i++) {
                int parent=current/2;
                float sign=0.0f;
                if (r.nextBoolean()&&r.nextBoolean())
                    if (bottomTree[parent]>EPSILON) sign=1.0f; else sign=-1.0f;
                else if (bottomTree[parent]>EPSILON) sign=-1.0f; else sign=1.0f;
                float offset=((Math.abs(bottomTree[parent])<EPSILON)?(r.nextFloat()*2.0f*distribution-distribution):(r.nextFloat()*distribution*factor*sign));
                bottomTree[current]=bottomTree[parent]+offset;
                current++; }
            levelNodes*=2; factor*=0.9f; }

        position=1;
        for (int i=20; i<moves.size(); i++) {
            if (moves.get(i).booleanValue()) position=position*2+1; else position*=2; }
        return bottomTree[position];
    }
}

Player:

import java.util.ArrayList;

public class Player {
    private Tree t;

    public Player(Tree t) {
        this.t = t;
    }
    // play will take in the moves and the player (in this case the maximizer)
    // and return the best possible move
    public boolean play(ArrayList<Boolean> moves, boolean maxNode) {
        float alpha, beta;
        alpha = Float.NEGATIVE_INFINITY;
        beta = Float.POSITIVE_INFINITY;
        ArrayList<Boolean> a = new ArrayList<Boolean>();
        a = (ArrayList<Boolean>) moves.clone();
        float al;
        a.add(true);
        int level = 10; // It will look ahead 10 nodes
        if (moves.size() >= 30) // If we're at move 30
            level = 39 - moves.size();
        alpha = Math.max(alpha, prun(level, a, maxNode, alpha, beta));
        a.remove(a.size()-1);
        al = alpha;
        a.add(false);
        alpha = Math.max(alpha, prun(level, a, maxNode, alpha, beta));
        if (al > alpha)
            return true;
        else 
            return false;
    }
    // prun is a recursive function that will determine alpha and beta
    public float prun(int level, ArrayList<Boolean>m, boolean maxNode, float a, float b) {
        if (level <= 0) {
            //System.out.println(m.size());
            return t.value(m);
        }
        ArrayList<Boolean> moves = new ArrayList<Boolean>();
        moves = (ArrayList<Boolean>) m.clone();
        ArrayList<Boolean> moves1 = new ArrayList<Boolean>();
        moves = (ArrayList<Boolean>) m.clone();
        float score;
        // child arraylist is the possible moves for the node
        ArrayList<ArrayList<Boolean>> child = new ArrayList<ArrayList<Boolean>>();
        child.add(moves);
        child.add(moves1);
        child.get(0).add(true);
        child.get(1).add(false);        
        if (level > 0) {
            if (maxNode) {
                for (ArrayList c : child) {
                    score = prun(level-1, c, !maxNode, a, b);
                    if (score > a) a = score;
                    if (a >= b) break;
                }
                return a;
            }
            else {
                for (ArrayList c : child) {
                    score = prun(level-1, c, !maxNode, a, b);
                    if (score < b) b = score;
                    if (a >= b) break;
                }
                return b;
            }
        }
        return 0;
    }
}

Opponent:

import java.util.ArrayList;

public class Opponent {
    private Tree t;

    public Opponent(Tree t) {
        this.t = t;
    }
    // maxNode = true is player, false is opponent
    // WHEN ALPHA <= BETA CURRENT NODE IS WORTHLESS
    public boolean play(ArrayList<Boolean> moves, boolean maxNode) {
        int i = (int)(Math.random() * 10) + 1;
        if (i % 2 == 0) return false;
        else return true;
    }
}
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Useless variable assignment

   ArrayList<Boolean> a = new ArrayList<Boolean>();
   a = (ArrayList<Boolean>) moves.clone();
   ArrayList<Boolean> moves = new ArrayList<Boolean>();
   moves = (ArrayList<Boolean>) m.clone();
   ArrayList<Boolean> moves1 = new ArrayList<Boolean>();
   moves = (ArrayList<Boolean>) m.clone();

You put a new ArrayList inside variable a, only to be replaced with the result of clone().

    ArrayList<Boolean> a = (ArrayList<Boolean>) moves.clone();

Use interfaces instead of abstract classes

   ArrayList<Boolean> a = new ArrayList<Boolean>();
   a = (ArrayList<Boolean>) moves.clone();

By using interfaces instead of abstract classes, you restrict yourself to a certain type. This prevents yourself later changing the type to for example a LinkedList, this will hinder yourself when testing performance

    List<Boolean> a = (List<Boolean>) moves.clone();

Repeated calculation

score = prun(level-1, c, !maxNode, a, b);

The only place where the level variable is used in the recursion. Yet it is called multiple times, and java needs to subtract it by 1 every time. By doing the subtraction at the top of the method, you can speed it up and reduce the line length.

At the top of the method, change to:

    if (level <= 0) {
        //System.out.println(m.size());
        return t.value(m);
    }
    level--;

Default values of class ignored

public long randomSeed=564715140;

You define a field with a default value, this default value is overridden in every constructor, making it only confusing for the reader to check where this default value is used, only to discover it isn't used.

public long randomSeed;

Bad formatting

public final int TOTALNODES=2097152;
public final float EPSILON=0.00001f;
public final float distribution=10.0f;

public long randomSeed=564715140;
public float[] topTree=new float[TOTALNODES];
private Random r=new Random();
public long height=40;

public Tree(long rs) {
    randomSeed=rs;
    r.setSeed(randomSeed);
    topTree[0]=topTree[1]=0.0f;
    int current=2; int levelNodes=2; float factor=1.0f;
    while (current<TOTALNODES) {
        for (int i=0; i<levelNodes; i++) {
            int parent=current/2;
            float sign=0.0f;
            if (r.nextBoolean()&&r.nextBoolean())
                if (topTree[parent]>EPSILON) sign=1.0f; else sign=-1.0f;
            else if (topTree[parent]>EPSILON) sign=-1.0f; else sign=1.0f;
            float offset=((Math.abs(topTree[parent])<EPSILON)?(r.nextFloat()*2.0f*distribution-distribution):(r.nextFloat()*distribution*factor*sign));
            topTree[current]=topTree[parent]+offset;
            current++; }
        levelNodes*=2; factor*=0.9f; }
}

The formatting in the above piece of code makes it hard to read because it's too compact, putting spaces around operators like =, +, && and all the others makes it less compact, and by that harder to follow.

(I read you said you cannot change this, but sometimes professors also need critic, :) )

Don't declare variables before they are used

float alpha, beta; float al;

Possible bug:

    ArrayList<Boolean> moves = new ArrayList<Boolean>();
    moves = (ArrayList<Boolean>) m.clone();
    ArrayList<Boolean> moves1 = new ArrayList<Boolean>();
    moves = (ArrayList<Boolean>) m.clone();

You assign moves 2 times the result of clone, if you followed above steps you would properly already seen this, but if you didn't, shouldn't the lowest assignment be moves1 instead of moves.

\$\endgroup\$
1
  • \$\begingroup\$ Wow thank you so much for the detailed answer! I will try and look through my code again and fix it accordingly. \$\endgroup\$
    – Ccyan
    Commented Feb 2, 2016 at 18:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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