Какой хороший класс Graph для реализации моей логической игровой доски? - PullRequest
0 голосов
/ 17 марта 2020

Я создаю настольную игру на своем компьютере, используя Java Swing.

Вот изображение Swing GUI. Я понимаю, что на этом изображении трудно прочитать текст квадратов доски. Требование заключается в том, что игрок может двигаться по часовой стрелке или против часовой стрелки на внешней стороне игрового поля и на собрании акционеров.

Stock Market Game GUI

Когда он пришел Время создать логическую модель игрового поля, я столкнулся с проблемами.

Вот увеличенное изображение в левом нижнем углу игрового поля.

Stock Market Game GUI 2

Первой логической моделью, которую я попробовал, был Список моего класса AbstractSquare.

package com.ggl.stockmarket.game.model.board;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import com.ggl.stockmarket.game.model.GameStatus;
import com.ggl.stockmarket.game.view.StockMarketFont;

public abstract class AbstractSquare {

    public static final int IMAGE_WIDTH = 160;
    public static final int IMAGE_HEIGHT = 192;

    public static final Insets INSETS = new Insets(4, 2, 4, 2);

    /**
     * The key to the square in the bidirectional graph.
     */
    protected Integer graphKey;

    /**
     * The direction to move on the next turn. +1 means clockwise, -1 means
     * counter-clockwise, and zero means that an odd dice roll is clockwise, while
     * an even dice roll is counter-clockwise.
     */
    protected int direction;

    /**
     * The direction and distance to move the market. A positive integer means the
     * market moves down. A negative integer means the market moves up.
     */
    protected int marketAmount;

    /**
     * Pointer to the location in the List of the previous board square.
     */
    protected int previousPointer;

    /**
     * Pointer to the locatio0n in the List of the next board square.
     */
    protected int nextPointer;

    /**
     * The amount to multiply the stock. As an example; "1 for 1" multiplies the
     * stock by 2.
     */
    protected int stockMultiplier;

    protected Color backgroundColor;

    protected Dimension squareSize;

    protected Rectangle boardLocation;

    protected String multiplierText;

    public Integer getGraphKey() {
        return graphKey;
    }

    public void setGraphKey(Integer graphKey) {
        this.graphKey = graphKey;
    }

    /**
     * The direction to move on the next turn.
     * 
     * @param roll - The dice roll total.
     * @return +1 is clockwise and -1 is counter-clockwise.
     */
    public int getDirection(int roll) {
        if (direction == 0) {
            if ((roll / 2 * 2) == roll) {
                return -1;
            } else {
                return +1;
            }
        } else {
            return direction;
        }
    }

    public void setDirection(int direction) {
        this.direction = direction;
    }

    public int getDirection() {
        return direction;
    }

    public int getMarketAmount() {
        return marketAmount;
    }

    public void setMarketAmount(int marketAmount) {
        this.marketAmount = marketAmount;
    }

    public Color getBackgroundColor() {
        return backgroundColor;
    }

    public void setBackgroundColor(Color backgroundColor) {
        this.backgroundColor = backgroundColor;
    }

    public Rectangle getBoardLocation() {
        return boardLocation;
    }

    public void setBoardLocation(Rectangle boardLocation) {
        this.boardLocation = boardLocation;
    }

    public int getPreviousPointer() {
        return previousPointer;
    }

    public void setPreviousPointer(int previousPointer) {
        this.previousPointer = previousPointer;
    }

    public int getNextPointer() {
        return nextPointer;
    }

    public void setNextPointer(int nextPointer) {
        this.nextPointer = nextPointer;
    }

    public int getStockMultiplier() {
        return stockMultiplier;
    }

    public void setStockMultiplier(int stockMultiplier) {
        this.stockMultiplier = stockMultiplier;
    }

    public Dimension getSquareSize() {
        return squareSize;
    }

    public void setSquareSize(Dimension squareSize) {
        this.squareSize = squareSize;
    }

    public String getMultiplierText() {
        return multiplierText;
    }

    public void setMultiplierText(String multiplierText) {
        this.multiplierText = multiplierText;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((graphKey == null) ? 0 : graphKey.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        AbstractSquare other = (AbstractSquare) obj;
        if (graphKey == null) {
            if (other.graphKey != null)
                return false;
        } else if (!graphKey.equals(other.graphKey))
            return false;
        return true;
    }

    public List<String> splitStockName(String name) {
        List<String> list = new ArrayList<String>();
        int pos = name.lastIndexOf(' ');
        if (pos < 0) {
            list.add(name);
        } else {
            list.add(name.substring(0, pos));
            list.add(name.substring(pos + 1));
        }
        return list;
    }

    public BufferedImage drawOutsideImage() {
        int width = squareSize.width;
        int height = squareSize.height;
        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = (Graphics2D) bufferedImage.getGraphics();
        drawOutsideSquare(g, width, height, INSETS);
        drawMovementText(g, width, height);
        drawMovementArrows(g, width, height);
        g.dispose();

        return bufferedImage;
    }

    protected BufferedImage drawOutsideImage(int width, int height, Insets insets) {
        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = (Graphics2D) bufferedImage.getGraphics();
        drawOutsideSquare(g, width, height, insets);
        drawMovementText(g, width, height);
        drawMovementArrows(g, width, height);

        return bufferedImage;
    }

    protected void drawOutsideSquare(Graphics2D g, int width, int height, Insets insets) {
        g.setColor(Color.black);
        g.fillRect(0, 0, width, height);

        if (backgroundColor == null) {
            g.setColor(Color.white);
        } else {
            g.setColor(backgroundColor);
        }
        g.fillRect(insets.left, insets.top, width - insets.right - insets.left, height - insets.bottom - insets.top);
    }

    protected void drawMovementText(Graphics2D g, int width, int height) {
        Font directionFont = StockMarketFont.getBoldFont(16);
        FontRenderContext frc = g.getFontRenderContext();
        StringBuilder sb = new StringBuilder();
        if (marketAmount < 0) {
            sb.append("Down ");
            sb.append(Math.abs(marketAmount));
        } else if (marketAmount > 0) {
            sb.append("Up ");
            sb.append(marketAmount);
        } else {
            sb.append("Odd    Even");
        }

        setTextColor(g);
        TextLayout layout = new TextLayout(sb.toString(), directionFont, frc);
        Rectangle2D bounds = layout.getBounds();
        float fx = (float) (bounds.getX()) + (float) (width - bounds.getWidth()) * 0.5F;
        float fy = (float) height - 10.0F;
        layout.draw(g, fx, fy);
    }

    public void setTextColor(Graphics2D g) {
        g.setColor(Color.black);
        if ((backgroundColor != null) && (backgroundColor.equals(Color.blue))) {
            g.setColor(Color.white);
        }
    }

    protected void drawMovementArrows(Graphics2D g, int width, int height) {
        if (direction == 0) {
            int w = (width - 30) / 2;
            int x = 10;
            int y = height - 40;
            drawArrow(g, x, y, +1, w);
            x = width - w - 10;
            drawArrow(g, x, y, -1, w);
        } else {
            int w = (width - 40);
            int x = (width - w) / 2;
            int y = height - 40;
            drawArrow(g, x, y, direction, w);
        }
    }

    protected void drawArrow(Graphics2D g, int x, int y, int direction, int length) {
        // arrow thickness, arrow point height and width
        int t = 4;
        int h = 14;
        int w = 20;

        Polygon p = new Polygon();
        if (direction > 0) {
            g.fillRect(x + h, y, length - h, t);
            p.addPoint(x + h, y - ((w - t) / 2));
            p.addPoint(x + h, y + ((w + t) / 2));
            p.addPoint(x, y + (t / 2));
        } else {
            g.fillRect(x, y, length - h, t);
            p.addPoint(x + length - h, y - ((w - t) / 2));
            p.addPoint(x + length - h, y + ((w + t) / 2));
            p.addPoint(x + length, y + (t / 2));
        }
        g.fillPolygon(p);
    }

    public BufferedImage drawInsideImage(Insets insets) {
        int width = squareSize.width;
        int height = squareSize.height;
        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = (Graphics2D) bufferedImage.getGraphics();
        g.dispose();
        return bufferedImage;
    }


    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(graphKey);
        return builder.toString();
    }

    public abstract void execute(GameStatus gameStatus);

}

На данный момент игнорируйте поле graphKey. Класс AbstractSquare содержит все поля, необходимые для всех клеток на игровом поле. Ни один квадрат не использует все поля. Каждый квадрат использует поля, необходимые для квадратуры.

Проблема в том, что доска не может быть представлена ​​простым графиком. Как видно на увеличенной игровой доске, направление вашего движения определяется стрелкой внизу квадрата, в котором вы начинаете свой ход. Вы можете двигаться со стартового поля в любом направлении, в зависимости от того, был ли бросок костей нечетным или четным.

Когда вы приземляетесь на площадь встречи стохолдеров во время броска, у вас есть выбор, продолжать ли двигаться по за пределами совета директоров или на собрании акционеров, если у вас есть хотя бы одна акция этой акции.

После того, как я решил, что Список абстрактных квадратов не будет работать, я исследовал ориентированные графы. Я нашел этот код на Geeks For Geeks

// Java program to implement Graph 
// with the help of Generics 

import java.util.*; 

class Graph<T> { 

    // We use Hashmap to store the edges in the graph 
    private Map<T, List<T> > map = new HashMap<>(); 

    // This function adds a new vertex to the graph 
    public void addVertex(T s) 
    { 
        map.put(s, new LinkedList<T>()); 
    } 

    // This function adds the edge 
    // between source to destination 
    public void addEdge(T source, 
                        T destination, 
                        boolean bidirectional) 
    { 

        if (!map.containsKey(source)) 
            addVertex(source); 

        if (!map.containsKey(destination)) 
            addVertex(destination); 

        map.get(source).add(destination); 
        if (bidirectional == true) { 
            map.get(destination).add(source); 
        } 
    } 

    // This function gives the count of vertices 
    public void getVertexCount() 
    { 
        System.out.println("The graph has "
                        + map.keySet().size() 
                        + " vertex"); 
    } 

    // This function gives the count of edges 
    public void getEdgesCount(boolean bidirection) 
    { 
        int count = 0; 
        for (T v : map.keySet()) { 
            count += map.get(v).size(); 
        } 
        if (bidirection == true) { 
            count = count / 2; 
        } 
        System.out.println("The graph has "
                        + count 
                        + " edges."); 
    } 

    // This function gives whether 
    // a vertex is present or not. 
    public void hasVertex(T s) 
    { 
        if (map.containsKey(s)) { 
            System.out.println("The graph contains "
                            + s + " as a vertex."); 
        } 
        else { 
            System.out.println("The graph does not contain "
                            + s + " as a vertex."); 
        } 
    } 

    // This function gives whether an edge is present or not. 
    public void hasEdge(T s, T d) 
    { 
        if (map.get(s).contains(d)) { 
            System.out.println("The graph has an edge between "
                            + s + " and " + d + "."); 
        } 
        else { 
            System.out.println("The graph has no edge between "
                            + s + " and " + d + "."); 
        } 
    } 

    // Prints the adjancency list of each vertex. 
    @Override
    public String toString() 
    { 
        StringBuilder builder = new StringBuilder(); 

        for (T v : map.keySet()) { 
            builder.append(v.toString() + ": "); 
            for (T w : map.get(v)) { 
                builder.append(w.toString() + " "); 
            } 
            builder.append("\n"); 
        } 

        return (builder.toString()); 
    } 
} 

// Driver Code 
public class Main { 

    public static void main(String args[]) 
    { 

        // Object of graph is created. 
        Graph<Integer> g = new Graph<Integer>(); 

        // edges are added. 
        // Since the graph is bidirectional, 
        // so boolean bidirectional is passed as true. 
        g.addEdge(0, 1, true); 
        g.addEdge(0, 4, true); 
        g.addEdge(1, 2, true); 
        g.addEdge(1, 3, true); 
        g.addEdge(1, 4, true); 
        g.addEdge(2, 3, true); 
        g.addEdge(3, 4, true); 

        // print the graph. 
        System.out.println("Graph:\n"
                        + g.toString()); 

        // gives the no of vertices in the graph. 
        g.getVertexCount(); 

        // gives the no of edges in the graph. 
        g.getEdgesCount(true); 

        // tells whether the edge is present or not. 
        g.hasEdge(3, 4); 

        // tells whether vertex is present or not 
        g.hasVertex(5); 
    } 
} 

Проблема с этим кодом заключается в том, что в моих экземплярах AbstractSqare не было естественного ключа, который я мог бы использовать для создания графика игровой доски.

Мой вопрос: как я могу использовать график для реализации логической модели игрового поля?

Мне нужно определить промежуточные квадраты броска костей, чтобы я мог оживить движение кусок. Мне также нужно спросить игрока, участвовать или нет в собрании акционеров, опять же при условии, что игрок владеет хотя бы одной акцией.

1 Ответ

0 голосов
/ 17 марта 2020

После недели проб и ошибок я предложил модифицированную версию класса Graph от Geeks For Geeks.

package com.ggl.stockmarket.game.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * This class creates a directed or a bidirectional graph.
 * 
 * @author Copied from the website GeeksForGeeks. Modified heavily by Gilbert Le
 *         Blanc
 * 
 * @param <T> - Class to be modeled as a graph..
 */
public class Graph<T> {

    // We use a List to store the objects
    private List<T> list = new ArrayList<>();
    // We use a Hashmap to store the key and edges in the graph
    private Map<Integer, List<Integer>> map = new HashMap<>();

    /**
     * This method adds the class object and returns an Integer
     * key to the class object.
     * @param e - The class object to add.
     * @return The Integer key to the class object.
     */
    public Integer add(T e) {
        list.add(e);
        return list.size() - 1;
    }

    /**
     * This method retrieves the Integer key for the class object.
     * 
     * @param e - The class object to test.
     * @return The Integer key to the class object, or -1 if the class object is not
     *         in the List.
     */
    public Integer getKey(T e) {
        for (int i = 0; i < list.size(); i++) {
            T g = list.get(i);
            if (g.equals(e)) {
                return i;
            }
        }
        return -1;
    }

    public T get(int key) {
        return list.get(key);
    }

    public int size() {
        return list.size();
    }

    /**
     * This method adds the vertexes and edge between the Integer source and
     * destination keys.
     * 
     * @param source        - Source key.
     * @param destination   - Destination key.
     * @param bidirectional - True if the edge is bidirectio0nal, false if the edge
     *                      is in one direction.
     * 
     */
    public void addEdge(Integer source, Integer destination, boolean bidirectional) {
        if (!map.containsKey(source)) {
            addVertex(source);
        }
        if (!map.containsKey(destination)) {
            addVertex(destination);
        }
        map.get(source).add(destination);
        if (bidirectional == true) {
            map.get(destination).add(source);
        }
    }

    /**
     * This method adds an Integer vertex to the graph.
     * 
     * @param s - Key to add as a vertex.
     */
    public void addVertex(Integer s) {
        map.put(s, new LinkedList<>());
    }

    // This function gives the count of vertices
    public String getVertexCount() {
        return "The graph has " + map.keySet().size() + " vertex";
    }

    // This function gives the count of edges
    public String getEdgesCount() {
        int count = 0;
        for (Integer v : map.keySet()) {
            count += map.get(v).size();
        }
        return "The graph has " + count + " edges.";
    }

    // This method returns the number of edges for a vertex
    public int getEdgeCount(Integer key) {
        return map.get(key).size();
    }

    // This method returns the edges for a vertex
    public List<Integer> getEdges(Integer key) {
        return map.get(key);
    }

    // This function gives whether a vertex is present or not.
    public String hasVertex(Integer s) {
        if (map.containsKey(s)) {
            return "The graph contains " + s + " as a vertex.";
        } else {
            return "The graph does not contain " + s + " as a vertex.";
        }
    }

    // This function gives whether an edge is present or not.
    public String hasEdge(Integer s, Integer d) {
        if (map.get(s).contains(d)) {
            return "The graph has an edge between " + s + " and " + d + ".";
        } else {
            return "The graph has no edge between " + s + " and " + d + ".";
        }
    }

    // Prints the adjacency list of each vertex.
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        for (Integer v : map.keySet()) {
            builder.append(v.toString() + ": ");
            for (Integer w : map.get(v)) {
                builder.append(w.toString() + " ");
            }
            builder.append(System.lineSeparator());
        }
        return (builder.toString());
    }

}

Моя склонность хранить экземпляры AbstractSquare в ArrayList оказалась правильный. То, что мне не хватало, было хорошим способом сохранить ребра и вершины графа.

Я сохранил каждый AbstractSquare в ArrayList. Когда я добавил каждый объект AbstractSquare в список, я сохранил индекс объекта как ребро. Затем я создал вершины для соединения ребер.

Вот отображение первых 13 квадратных ребер и их вершин. На доске 120 логических квадратов, поэтому я не буду перечислять все вершины. Мне пришлось дважды добавить квадраты собрания акционеров к логической модели. Один раз для учета движения clockws ie на собрании акционеров и один раз для учета движения против часовой стрелки на собрании акционеров.

0: 1 47 
1: 0 2 
2: 1 3 
3: 2 4 111 
4: 3 5 
5: 4 6 
6: 5 7 
7: 6 8 
8: 7 9 
9: 8 10 48 
10: 9 11 
11: 10 12 
12: 11 13 

Квадрат 0 может переместиться на квадрат 47 или квадрат 1. Вне игрового поля 48 квадратов. Как видите, игрок в этой части логической модели может двигаться в 2 или 3 направлениях.

Вот еще одна часть графика. Это один из 8 графиков собраний акционеров.

111: 112 
112: 113 
113: 114 
114: 115 
115: 116 
116: 117 
117: 118 
118: 119 
119: 45 

На собрании акционеров игрок может двигаться только в одном направлении.

Вот небольшой код из заводского класса это строит логическую модель платы. Весь класс составляет 862 строки. Вы можете видеть, как я сохраняю ребра и создаю вершины графа.

    // Position 0, starting at the bottom of the board,
    // going clockwise;
    StartSquare startSquare = createStartSquare();
    Integer originKey = board.add(startSquare);
    startSquare.setGraphKey(originKey);
    startSquareGraphKey[0] = originKey;

    BuySquare buySquare = createBuySquare(gameStatus, 3, +1, +1);
    Integer key2 = board.add(buySquare);
    board.addEdge(originKey, key2, true);
    buySquare.setGraphKey(key2);

    BuySquare buySquare2 = createBuySquare(gameStatus, 0, +1, -2);
    Integer key = board.add(buySquare2);
    board.addEdge(key2, key, true);
    buySquare2.setGraphKey(key);

    BuyLimitSquare buyLimitSquare = createBuyLimitSquare(gameStatus, 6, +1, +3, -1, 45);
    key2 = board.add(buyLimitSquare);
    board.addEdge(key, key2, true);
    buyLimitSquare.setGraphKey(key2);
    meetings[6] = buyLimitSquare;  

Поскольку я знаю, что меня спросят, моя логическая модель отделена от моего вида и классов контроллеров. У меня каждый квадрат нарисован сам, и у меня есть классы, которые берут каждое квадратное изображение и размещают их на изображении доски. Я сохраняю прямоугольник изображения каждого квадрата в логической модели, так что я знаю, где рисовать фигуры игрока на изображении доски.

Смысл этого вопроса и ответа состоял в том, чтобы передать мой модифицированный класс Graph, в надежды на то, что это поможет кому-то бороться с графиком, более сложным, чем простые примеры графиков, которые можно найти в учебниках и онлайн.

...