Package com.barrybecker4.game.twoplayer.common.ui.gametree

Source Code of com.barrybecker4.game.twoplayer.common.ui.gametree.GameTreeViewer

/** Copyright by Barry G. Becker, 2000-2011. Licensed under MIT License: http://www.opensource.org/licenses/MIT  */
package com.barrybecker4.game.twoplayer.common.ui.gametree;

import com.barrybecker4.ui.util.ColorMap;
import com.barrybecker4.common.format.FormatUtil;
import com.barrybecker4.game.twoplayer.common.TwoPlayerMove;
import com.barrybecker4.game.twoplayer.common.search.tree.SearchTreeNode;
import com.barrybecker4.game.twoplayer.common.ui.TwoPlayerPieceRenderer;

import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;

/**
*  This class takes a root node and displays the portion of the tree visible in the text tree control.
*  An actual graphics tree is shown to represent the game search tree.
*
@author Barry Becker
*/
final class GameTreeViewer extends JPanel implements MouseMotionListener
{
    private static final Color BACKGROUND_COLOR = Color.white;
    private static final int BACKGROUND_BAND_OPACITY = 100;
    private static final int MARGIN = 8;

    private static final BasicStroke THIN_STROKE = new BasicStroke(0.4f);
    private static final BasicStroke HIGHLIGHT_STROKE = new BasicStroke(1.4f);
    private static final BasicStroke BRUSH_HIGHLIGHT_STROKE = new BasicStroke(3.7f);
    private static final int PRUNE_SPACING = 12;

    /** circle around highlighted node   */
    private static final int HL_NODE_RADIUS = 3;
    private static final int HL_NODE_DIAMETER = 8;
    private static final long serialVersionUID = 0L;

    private Color backgroundColor_ = BACKGROUND_COLOR;
    private ColorMap colormap_;
    private TwoPlayerPieceRenderer pieceRenderer_;

    private SearchTreeNode root_;
    private int depth_ = 0;

    /** sum of the number of descendants of all nodes at the level */
    private int[] totalAtLevel_;

    private int width_;
    private int levelHeight_;

    /** most recently highlighted path. */
    private TreePath oldHighlightPath_;


    /**
     * Construct the viewer
     * @param root root of text tree to base tree graph on.
     */
    GameTreeViewer( SearchTreeNode root, ColorMap cmap, TwoPlayerPieceRenderer pieceRenderer) {
        colormap_ = cmap;
        pieceRenderer_ = pieceRenderer;
        setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
        setRoot(root);
    }

    public synchronized void setRoot( SearchTreeNode root) {
        root_ = root;
        // this is very expensive because it traverses the whole tree.
        // it might be simpler to just use the depth from the controller, but that would
        // not account for those times when we drill deeper using quiescent search.
        depth_ = root_.getDepth();

        totalAtLevel_ = new int[depth_+2];
        oldHighlightPath_ = null;

        if (root_ != null && root_.getUserObject() != null) {
            initializeTreeStats(root_, 0);
        }
    }

    /**
     *  draw the currently visible game tree.
     */
    public synchronized void refresh() {
        // this will paint the component immediately
        paint( getGraphics() );
        oldHighlightPath_ = null;
    }

    /**
     * @param c  the new color of the tree.
     */
    @Override
    public synchronized void setBackground( Color c ) {
        backgroundColor_ = c;
        refresh();
    }

    private synchronized void initializeTreeStats( SearchTreeNode node, int depth) {
        if (node.isLeaf())  {
            if (node.isPruned()) {
                // give pruned nodes a little more space
                int pruneSpace =
                        node.getSpaceAllocation() + Math.max(1, 4 * PRUNE_SPACING - PRUNE_SPACING * depth);
                node.setSpaceAllocation(pruneSpace);
            }
            else {
                node.setSpaceAllocation(1)// never 0;
            }
            totalAtLevel_[depth] += node.getSpaceAllocation();
            return;
        }

        Enumeration it = node.children();
        while (it.hasMoreElements()) {
            SearchTreeNode child = (SearchTreeNode)it.nextElement();
            initializeTreeStats( child, depth+1 );
            node.setSpaceAllocation(node.getSpaceAllocation() + child.getSpaceAllocation());
        }
        // count the node as a descendant
        node.setSpaceAllocation(node.getSpaceAllocation() + 1);
        totalAtLevel_[depth] += node.getSpaceAllocation();
    }


    // ---  these methods implement the MouseMotionListener interface   ---
    @Override
    public void mouseMoved( MouseEvent e ) {}
    @Override
    public void mouseDragged( MouseEvent e ) {}


    /**
     * perform a sequence of moves from somewhere in the game;
     * not necessarily the start. We do, however,
     * assume the moves are valid. It is for display purposes only.
     *
     * @param path path corresponding to a the list of moves to make
     */
    public synchronized void highlightPath( TreePath path ) {
        // unhighlight the old path and highlight the new one without redrawing the whole tree.
        // GameContext.log(2, "about to highlight "+path );
        Graphics2D g2 = (Graphics2D)getGraphics();
        g2.setXORMode(Color.WHITE);
        g2.setStroke(BRUSH_HIGHLIGHT_STROKE);

        // first unhighlight the old path
        if (oldHighlightPath_ != null)  {
            highlight( oldHighlightPath_, g2);
        }

        highlight( path, g2);
        g2.setPaintMode();
        oldHighlightPath_ = path;
    }

    private void highlight( TreePath path, Graphics2D g2) {
        Object[] pathNodes = path.getPath();
        SearchTreeNode lastNode = (SearchTreeNode)pathNodes[0];
        int diameter = HL_NODE_DIAMETER;
        Point lastLoc = lastNode.getPosition();
        g2.drawOval(lastLoc.x - HL_NODE_RADIUS, lastLoc.y - HL_NODE_RADIUS, diameter, diameter);
        for (int i=1; i<pathNodes.length; i++) {
            SearchTreeNode node = (SearchTreeNode)pathNodes[i];
            TwoPlayerMove m = (TwoPlayerMove)node.getUserObject();
            g2.setColor(colormap_.getColorForValue(m.getInheritedValue()));

            Point nodeLoc = node.getPosition();
            g2.drawLine(lastLoc.x, lastLoc.y, nodeLoc.x,nodeLoc.y);
            g2.setColor(colormap_.getColorForValue(m.getValue()));
            g2.drawOval(nodeLoc.x-HL_NODE_RADIUS, nodeLoc.y-HL_NODE_RADIUS, diameter, diameter);
            lastLoc = nodeLoc;
        }
    }

    /**
     * Draw the nodes and arcs in the game tree.
     * It can get quite huge.
     * @return the number of nodes that were drawn.
     */
    private long drawTree( SearchTreeNode root, Graphics2D g2) {
        int oldDepth = 0;
        int depth;
        int[] offsetAtLevel = new int[depth_+2];
        long numNodes = 0;

        drawNode(root, 0, 0, g2);
        List<SearchTreeNode> q = new LinkedList<SearchTreeNode>();
        q.add(root);

        while (q.size() > 0) {
            SearchTreeNode p = q.remove(0);
            numNodes++;
            depth = p.getLevel();
            // draw the arc and child root for each child c of p
            if (depth > oldDepth) {
                oldDepth = depth;
                offsetAtLevel[depth] = 0;
            }
            Enumeration enumeration = p.children();
            while (enumeration.hasMoreElements()) {
                SearchTreeNode c = (SearchTreeNode)enumeration.nextElement();
                drawArc(p, c, depth, offsetAtLevel[depth], offsetAtLevel[depth+1], g2);
                drawNode(c, depth+1, offsetAtLevel[depth+1], g2);
                offsetAtLevel[depth+1] += c.getSpaceAllocation();
                q.add(c);
            }
            offsetAtLevel[depth] += p.getSpaceAllocation();
        }
        return numNodes;
    }

    /**
     * Draw one of the game tree nodes
     */
    private synchronized void drawNode( SearchTreeNode node, int depth, int offset, Graphics2D g2) {
        TwoPlayerMove m = (TwoPlayerMove)node.getUserObject();
        g2.setColor( colormap_.getColorForValue(m.getValue()));
        int x = MARGIN + (int) (width_ * (offset + node.getSpaceAllocation() / 2.0) / totalAtLevel_[depth]);
        int y = MARGIN + depth*levelHeight_;
        node.setLocation(x, y);

        int width = 2;
        if (m.isSelected()) {
            width = 3;
            g2.fillRect(x, y, width, 3);
        }
        else {
            g2.fillRect(x, y, width, 3);
        }
        if (node.isPruned())  {
           // draw a marker to show that it has been pruned
           g2.setColor(Color.black);
           g2.fillRect(x, y+3, width, 1);
        }
    }

    /**
     * Draw a line/arc connecting a parent and child node.
     */
    private synchronized void drawArc( SearchTreeNode parent, SearchTreeNode child,
                                       int depth, int offset1, int offset2, Graphics2D g2) {
        TwoPlayerMove m = (TwoPlayerMove)child.getUserObject();
        boolean highlighted = m.isSelected() && ((TwoPlayerMove)parent.getUserObject()).isSelected();
        if (highlighted)
            g2.setStroke(HIGHLIGHT_STROKE);
        g2.setColor( colormap_.getColorForValue(m.getInheritedValue()));
        g2.drawLine(MARGIN + (int) (width_*(offset1 + parent.getSpaceAllocation() / 2.0) / totalAtLevel_[depth]),
                    MARGIN + depth*levelHeight_,
                    MARGIN + (int) (width_*(offset2 + child.getSpaceAllocation() / 2.0) / totalAtLevel_[depth+1]),
                    MARGIN + (depth+1)*levelHeight_);
        if (highlighted) {
            g2.setStroke(THIN_STROKE);
        }
    }

    /**
     * draw colored bands to give an indication of who is moving on each ply.
     * @param g2 graphics
     */
    private synchronized void drawBackground( Graphics2D g2 ) {
        g2.setColor( backgroundColor_ );
        g2.fillRect( 0, 0, getWidth(), getHeight() );

        Color c = pieceRenderer_.getPlayer1Color(); //colormap_.getMaxColor();
        Color p1Color = new Color(c.getRed(), c.getGreen(), c.getBlue(), BACKGROUND_BAND_OPACITY);
        c = pieceRenderer_.getPlayer2Color()//colormap_.getMinColor();
        Color p2Color = new Color(c.getRed(), c.getGreen(), c.getBlue(), BACKGROUND_BAND_OPACITY);
        GradientPaint gp = new GradientPaint(0, (float)levelHeight_/4.0f, p2Color, 0, 5.0f*levelHeight_/4.0f, p1Color, true);
        g2.setPaint(gp);

        g2.fillRect( 0, 0, getWidth(), getHeight() );
    }

    /**
     * This renders the current tree to the canvas
     */
    @Override
    protected synchronized void paintComponent( Graphics g )  {
        Graphics2D g2 = (Graphics2D)g;

        g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setRenderingHint( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        super.paintComponents( g );
        drawBackground( g2 );

        if (root_==null || root_.getUserObject()==null) return;

        width_ = getWidth()-2*MARGIN;
        int height= getHeight()-2*MARGIN;
        if (depth_ == 0)
            return; // tree not ready to be painted.
        levelHeight_ = height /depth_;

        g2.setStroke(THIN_STROKE);
        long numNodes = drawTree(root_, g2);

        g2.setColor(Color.BLACK);
        g2.drawString("Nodes = " + FormatUtil.formatNumber(numNodes), MARGIN + width_ - 120 , MARGIN + 10);
    }
}
TOP

Related Classes of com.barrybecker4.game.twoplayer.common.ui.gametree.GameTreeViewer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.