chesspresso.game.view.HTMLGameBrowser.java Source code

Java tutorial

Introduction

Here is the source code for chesspresso.game.view.HTMLGameBrowser.java

Source

/*
 * Copyright (C) Bernhard Seybold. All rights reserved.
 *
 * This software is published under the terms of the LGPL Software License,
 * a copy of which has been included with this distribution in the LICENSE.txt
 * file.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 *
 * $Id: HTMLGameBrowser.java,v 1.3 2003/01/04 16:23:32 BerniMan Exp $
 */

package chesspresso.game.view;

import java.io.*;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import freemarker.template.Version;

import chesspresso.*;
import chesspresso.game.*;
import chesspresso.move.*;
import chesspresso.position.*;

/**
 * Producer for HTML pages displaying a game.
 *
 * @author Bernhard Seybold
 * @author Jeff Tsay
 * @version $Revision: 1.3 $
 */
public class HTMLGameBrowser implements GameListener {

    //======================================================================
    // GameListener Methods

    @Override
    public void notifyLineStart(int level) {
        m_isLineStart = true;
        m_showMoveNumber = true;
        m_lasts[level + 1] = m_lasts[level];
    }

    @Override
    public void notifyLineEnd(int level) {
        m_isLineEnd = true;
        m_showMoveNumber = true;
    }

    @Override
    public void notifyMove(Move move, short[] nags, String comment, int plyNumber, int level) {
        ImmutablePosition pos = m_game.getPosition();

        final PlyModel plyModel = new PlyModel();
        plyModel.plyNumber = plyNumber;
        plyModel.lineStart = m_isLineStart;
        plyModel.lineEnd = m_isLineEnd;

        m_isLineStart = m_isLineEnd = false;

        plyModel.showMoveNumber = m_showMoveNumber;
        plyModel.moveNumber = m_moveNumber;

        m_showMoveNumber = Chess.isWhitePly(plyNumber + 1);

        plyModel.level = level;
        plyModel.move = move;

        if (nags != null) {
            for (int i = 0; i < nags.length; i++) {
                plyModel.nags.add(NAG.getShortString(nags[i]));
            }
            m_showMoveNumber = true;
        }

        plyModel.comment = comment;

        m_plyModels.add(plyModel);

        addPosData(pos);
        m_lastData.add(m_lasts[level]);
        m_lasts[level] = m_moveNumber;

        m_moveNumber++;
    }

    //======================================================================

    /**
     * Create a new HTMLGameBrowser with default settings.
     */
    public HTMLGameBrowser() {
        m_wimgs = new String[] { "wkw.gif", "wpw.gif", "wqw.gif", "wrw.gif", "wbw.gif", "wnw.gif", "now.gif",
                "bnw.gif", "bbw.gif", "brw.gif", "bqw.gif", "bpw.gif", "bkw.gif" };
        m_bimgs = new String[] { "wkb.gif", "wpb.gif", "wqb.gif", "wrb.gif", "wbb.gif", "wnb.gif", "nob.gif",
                "bnb.gif", "bbb.gif", "brb.gif", "bqb.gif", "bpb.gif", "bkb.gif" };
    }

    //======================================================================

    /**
     * Sets the name of an square image. The default names are set according to
     * the following scheme: First letter is the color of the stone (b, w), second
     * letter the piece (k, q, r, b, n, p) third letter the square color (b, w),
     * extension is gif. now.gif and nob.gif are used for empty squares.<br>
     * For instance: wkw.gif determines a white king on a white square,
     * bbb.gif is a black bishop on a black square.
     *
     * @param stone       the stone displayed
     * @param whiteSquare whether or not the square is white
     * @param name        the name of the corresponding image
     */
    private void setStoneImageName(int stone, boolean whiteSquare, String name) {
        if (whiteSquare) {
            m_wimgs[stone - Chess.MIN_STONE] = name;
        } else {
            m_bimgs[stone - Chess.MIN_STONE] = name;
        }
    }

    //======================================================================

    /**
     * Produces HTML to display a game.
     *
     * @param outStream where the HTML will be sent to
     * @param game      the game to display.
     */
    public void produceHtml(OutputStream outStream, Game game) throws Exception {
        produceHtml(outStream, game, new HtmlGenerationOptions(), false);
    }

    public static class HtmlGenerationOptions {
        /** Creates an instance that uses Bootstrap 3, with contentOnly false. */
        public HtmlGenerationOptions() {
        }

        boolean contentOnly = false;
        String styleHtml = DEFAULT_STYLE_HTML;
        String scriptHtml = DEFAULT_SCRIPT_HTML;
        int bootstrapMajorVersion = 3;
        String imagePrefix = "";
        int moveNumberToDisplay = -1;

        public boolean isContentOnly() {
            return contentOnly;
        }

        public String getStyleHtml() {
            return styleHtml;
        }

        public String getScriptHtml() {
            return scriptHtml;
        }

        public int getBootstrapMajorVersion() {
            return bootstrapMajorVersion;
        }

        public String getImagePrefix() {
            return imagePrefix;
        }

        public int getMoveNumberToDisplay() {
            return moveNumberToDisplay;
        }

        public void setContentOnly(boolean contentOnly) {
            this.contentOnly = contentOnly;
        }

        public void setStyleHtml(String styleHtml) {
            this.styleHtml = styleHtml;
        }

        public void setScriptHtml(String scriptHtml) {
            this.scriptHtml = scriptHtml;
        }

        public void setBootstrapMajorVersion(int bootstrapMajorVersion) {
            this.bootstrapMajorVersion = bootstrapMajorVersion;
        }

        public void setImagePrefix(String imagePrefix) {
            this.imagePrefix = imagePrefix;
        }

        public void setMoveNumbeToDisplayr(int moveNumber) {
            this.moveNumberToDisplay = moveNumber;
        }
    }

    public static HtmlGenerationOptions makeBootstrap2HtmlGenerationOptions() {
        final HtmlGenerationOptions options = new HtmlGenerationOptions();
        options.styleHtml = BOOTSTRAP_2_STYLE_HTML + CHESSPRESSO_STYLE_HTML;
        options.scriptHtml = JQUERY_SCRIPT_HTML + BOOTSTRAP_2_SCRIPT_HTML;
        options.bootstrapMajorVersion = 2;
        return options;
    }

    /**
     * Produces HTML to display a game.
     *
     * @param outStream   where the HTML will be sent to
     * @param game        the game to display.
     * @param contentOnly if true skip header and footer information, use this if you want to
     *                    produce your own header and footer
     */
    public synchronized void produceHtml(final OutputStream outStream, final Game game,
            final HtmlGenerationOptions options, final boolean debugMode) throws Exception {
        m_plyModels = new LinkedList<>();
        m_posData = new LinkedList<>();

        m_lastData = new LinkedList<>();
        m_game = game;
        m_moveNumber = 0;
        m_showMoveNumber = true;
        m_lasts = new int[100];
        m_lasts[0] = 0;

        m_lastData.add(0);

        m_game.gotoStart();
        addPosData(m_game.getPosition());
        m_moveNumber++;

        game.traverse(this, true);

        final List<String> imagePaths = new LinkedList<>();
        for (int stone = Chess.MIN_STONE; stone <= Chess.MAX_STONE; stone++) {
            imagePaths.add(getImageForStone(stone, true));
        }

        for (int stone = Chess.MIN_STONE; stone <= Chess.MAX_STONE; stone++) {
            imagePaths.add(getImageForStone(stone, false));
        }

        final Position startPos = Position.createInitialPosition();
        final List<List<String>> imagePathsPerRow = new LinkedList<>();
        for (int row = Chess.NUM_OF_ROWS - 1; row >= 0; row--) {
            final List<String> imagePathsForRow = new LinkedList<>();
            for (int col = 0; col < Chess.NUM_OF_COLS; col++) {
                int sqi = Chess.coorToSqi(col, row);
                imagePathsForRow.add(getImageForStone(startPos.getStone(sqi), Chess.isWhiteSquare(sqi)));
            }
            imagePathsPerRow.add(imagePathsForRow);
        }

        final Map<String, Object> root = new HashMap<>();

        root.put("game", game);
        root.put("plys", m_plyModels);
        root.put("lastMoveNumber", m_moveNumber - 1);
        root.put("posData", m_posData);
        root.put("lastData", m_lastData);
        root.put("options", options);
        root.put("imagePaths", imagePaths);
        root.put("imagePathsPerRow", imagePathsPerRow);

        final Configuration config = makeConfiguration(debugMode);
        final Template template = config.getTemplate("game.ftl");
        template.process(root, new OutputStreamWriter(outStream));
    }

    private Configuration makeConfiguration(boolean debugMode) {
        final Configuration config = new Configuration();

        config.setIncompatibleImprovements(new Version(2, 3, 20));
        config.setClassForTemplateLoading(HTMLGameBrowser.class, "freemarker");
        config.setDefaultEncoding("UTF-8");

        if (debugMode) {
            config.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
        } else {
            config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        }

        return config;
    }

    public static void main(String[] args) {
        if (args.length < 1) {
            args = new String[] { "fischer_plain.pgn" };

            /*
            System.out.println("Usage: java " + HTMLGameBrowser.class.getName() +
             " <PGN filename>");
            System.exit(0); */
        }

        boolean debugMode = true;
        HtmlGenerationOptions htmlGenerationOptions = new HtmlGenerationOptions();
        //htmlGenerationOptions = makeBootstrap2HtmlGenerationOptions();
        //htmlGenerationOptions.imagePrefix = "ugly/";

        try {
            chesspresso.pgn.PGNReader pgn = new chesspresso.pgn.PGNReader(new FileReader(args[0]), "game");
            final Game game = pgn.parseGame();

            final HTMLGameBrowser html = new HTMLGameBrowser();
            html.produceHtml(System.out, game, htmlGenerationOptions, debugMode);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Needs to be public for Freemarker.
     */
    public static class PlyModel {
        boolean lineStart = false;
        boolean lineEnd = false;
        int plyNumber = 0;
        boolean showMoveNumber = true;
        int moveNumber = 0;
        int level = 0;
        Move move;
        List<String> nags = new LinkedList<>();
        String comment;

        public boolean getLineStart() {
            return lineStart;
        }

        public boolean getLineEnd() {
            return lineEnd;
        }

        public int getPlyNumber() {
            return plyNumber;
        }

        public boolean isShowMoveNumber() {
            return showMoveNumber;
        }

        public int getMoveNumber() {
            return moveNumber;
        }

        public int getLevel() {
            return level;
        }

        public Move getMove() {
            return move;
        }

        public List<String> getNags() {
            return nags;
        }

        public String getComment() {
            return comment;
        }

        @Override
        public String toString() {
            return "PlyModel{" + "lineStart=" + lineStart + ", lineEnd=" + lineEnd + ", plyNumber=" + plyNumber
                    + ", showMoveNumber=" + showMoveNumber + ", moveNumber=" + moveNumber + ", level=" + level
                    + ", move=" + move + ", nags=" + nags + ", comment='" + comment + '\'' + '}';
        }
    }

    /**
     * Returns the name of the image.
     *
     * @param stone       the stone displayed
     * @param whiteSquare whether or not the square is white
     */
    private String getImageForStone(int stone, boolean isWhite) {
        return (isWhite ? m_wimgs[stone - Chess.MIN_STONE] : m_bimgs[stone - Chess.MIN_STONE]);
    }

    private void addPosData(ImmutablePosition pos) {
        final int[] data = new int[Chess.NUM_OF_ROWS * Chess.NUM_OF_COLS];
        int j = 0;
        for (int row = Chess.NUM_OF_ROWS - 1; row >= 0; row--) {
            for (int col = 0; col < Chess.NUM_OF_COLS; col++) {
                int sqi = Chess.coorToSqi(col, row);
                data[j++] = pos.getStone(sqi) - Chess.MIN_STONE;
            }
        }

        m_posData.add(data);
    }

    private List<PlyModel> m_plyModels;
    private boolean m_isLineStart = false;
    private boolean m_isLineEnd = false;
    private List<int[]> m_posData;
    private List<Integer> m_lastData;
    private Game m_game;
    private int m_moveNumber;
    private boolean m_showMoveNumber;
    private int[] m_lasts;
    private String[] m_wimgs;
    private String[] m_bimgs;

    public static final String CHESSPRESSO_CSS = "  .chesspresso_centered { margin: 0 auto; }\n"
            + "  #chesspresso_container { width: 272px; }\n"
            + "  table.chesspresso_board { border-collapse: collapse; }\n"
            + "  table.chesspresso_board td, { padding : 0; }\n"
            + "  #chesspresso_tape_control { margin-top: 6px; text-align: center; }\n"
            + "   .chesspresso_main { text-decoration: none }\n"
            + "   .chesspresso_line { text-decoration: none }\n"
            + "  a.chesspresso_main { font-weight: bold; color: black; }\n"
            + "  a.chesspresso_line { color: black; }\n" + "  span.chesspresso_comment { font-style: italic; }\n"
            + "  .chesspresso_deselected_ply_link { background: white !important; color: black !important; }\n"
            + "  .chesspresso_selected_ply_link { background: black !important; color: white !important; }\n";

    public static final String CHESSPRESSO_STYLE_HTML = "<style type=\"text/css\">\n" + CHESSPRESSO_CSS
            + "</style>\n";

    public static final String CHESSPRESSO_SCOPED_STYLE_HTML = "<style scoped type=\"text/css\">\n"
            + CHESSPRESSO_CSS + "</style>\n";

    public static final String BOOTSTRAP_2_STYLE_HTML = "<link href=\"https://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css\" rel=\"stylesheet\" />\n";

    public static final String BOOTSTRAP_3_STYLE_HTML = "<link href=\"https://netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css\" rel=\"stylesheet\" />\n";

    public static final String DEFAULT_STYLE_HTML = BOOTSTRAP_3_STYLE_HTML + CHESSPRESSO_STYLE_HTML;

    public static final String JQUERY_SCRIPT_HTML = "<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js\"></script>\n";

    public static final String BOOTSTRAP_2_SCRIPT_HTML = "<script src=\"https://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js\"></script>";

    public static final String BOOTSTRAP_3_SCRIPT_HTML = "<script src=\"https://netdna.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js\"></script>";

    public static final String DEFAULT_SCRIPT_HTML = JQUERY_SCRIPT_HTML + BOOTSTRAP_3_SCRIPT_HTML;
}